block by renecnielsen f7609b95f1053b1b9de7

logo

Full Screen

Logo Animation

Copy of http://www.brendansudol.com/posts/google-logo-d3/

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <link rel="stylesheet" href="style.css">
</head>

<body>
<div id="ungp-logo">
  <div class="inputs">
      <label>
        <input type="radio" name="mode" value="letters" /> Logotype
      </label>
      <label>
        <input type="radio" name="mode" value="dots" /> Dots
      </label>
  </div>
  <div class="canvas">
  </div>
</div>

<script>
var w = 1132,
    h = 372;

var x = d3.scale.ordinal()
    .domain(d3.range(4)) // 4 dots
    .rangeRoundBands([0, w], 1);

var path = d3.svg.line()
    .x(function(d) { return d[0]; })
    .y(function(d) { return d[1]; });

var timeout;

// recreating svg attribute eccentricities 
// when logo png was converted to svg
var svg = d3.select("#ungp-logo .canvas").append('svg')
    .attr('class', 'logo')
    .attr('width', w)
    .attr('height', h)
    .attr('viewBox', "0 0 " + w + " " + h)
  .append("g")
    .attr('transform', '' +
        'translate(' + w / 2 + ', ' + h / 2 + ') ' +
        'scale(1, -1) ' +
        'translate(-' + w / 2 + ', -' + h / 2 + ') ' +
        'translate(0, 1)'
    );

d3.json("logo.json", function(data) {
    // add circle coordinates based on letter coordinates
    // and circle radii (note 2 letters essentially shrink to nothing)
    data.forEach(function(d) {
        d.circle_radius = d.circle >= 0 ? 50 : 1;
        d.circle_coords = compute_circle(d.letter_coords, d.circle_radius);
    });

    // add letters to page
    svg.selectAll('path')
        .data(data)
        .enter().append('path')
        .attr("class", function(d) { return d.color; })
        .attr("d", function(d) { return path(d.letter_coords); });

    // initial transition trigger
    timeout = setTimeout(function() {
        d3.select('input[value="dots"]')
            .property("checked", true).each(change);
    }, 2000);
});

// handle input changing
d3.selectAll("#ungp-logo input").on("change", change);

function change() {
    clearTimeout(timeout);
    if (this.value === "dots") morph_to_dots();
    else morph_to_letters();
}

// letter to dot transition
function morph_to_dots() {
    var t = svg.transition().duration(500);

    t.selectAll('path')
        .attr("d", function(d) { return path(d.circle_coords); })
        .attr('transform', function(d, i) {
            var el = d3.select(this),
                bb = el.node().getBBox();

            var x_centroid = bb.x + bb.width / 2,
                y_centroid = bb.y + bb.height / 2;

            var x_new = d.circle >= 0 ? x(d.circle) - x_centroid : 0,
                y_new = (h / 2) - y_centroid;

            return "translate(" + x_new + ", " + y_new + ")";
        });
}

// dot to letter transition
function morph_to_letters() {
    var t = svg.transition().duration(500);

    t.selectAll('path')
        .attr('d', function(d) { return path(d.letter_coords); })
        .attr('transform', 'translate(0, 0)');
}

// use coordinates of existing path element to 
// compute circle coordinates for shape tweening
function compute_circle(coordinates, r) {
    var circle = [],
        length = 0,
        lengths = [length],
        polygon = d3.geom.polygon(coordinates),
        p0 = coordinates[0],
        p1,
        x,
        y,
        i = 0,
        n = Math.min(250, coordinates.length);

    // Compute the distances of each coordinate.
    while (++i < n) {
        p1 = coordinates[i];
        x = p1[0] - p0[0];
        y = p1[1] - p0[1];
        lengths.push(length += Math.sqrt(x * x + y * y));
        p0 = p1;
    }

    var area = polygon.area(),
        radius = r || 50,
        centroid = polygon.centroid(-1 / (6 * area)),
        angleOffset = -Math.PI / 2,
        angle,
        i = -1,
        k = 2 * Math.PI / lengths[lengths.length - 1];

    // Compute points along the circle’s 
    // circumference at equivalent distances.
    while (++i < n) {
        angle = angleOffset + lengths[i] * k;
        circle.push([
            centroid[0] + radius * Math.cos(angle),
            centroid[1] + radius * Math.sin(angle)
        ]);
    }

    return circle;
}

// custom path interpolator that transitions 
// nicely when path data is of different sizes
// thanks to: //stackoverflow.com/a/14330503
d3.interpolators.push(function(a, b) {
    var isPath, isArea, interpolator, ac, bc, an, bn, d;

    function fill(value, length) {
        return d3.range(length).map(function() {
            return value;
        });
    }

    function extractCoordinates(path) {
        return path.substr(1, path.length - (isArea ? 2 : 1)).split('L');
    }

    function makePath(coordinates) {
        return 'M' + coordinates.join('L') + (isArea ? 'Z' : '');
    }

    function bufferPath(p1, p2) {
        var d = p2.length - p1.length;

        if (isArea) {
            return fill(p1[0], d / 2).concat(p1, fill(p1[p1.length - 1], d / 2));
        } else {
            return fill(p1[0], d).concat(p1);
        }
    }

    isPath = /M-?\d*\.?\d*,-?\d*\.?\d*(L-?\d*\.?\d*,-?\d*\.?\d*)*Z?/;

    if (isPath.test(a) && isPath.test(b)) {
        isArea = a[a.length - 1] === 'Z';
        ac = extractCoordinates(a);
        bc = extractCoordinates(b);
        an = ac.length;
        bn = bc.length;

        if (an > bn) bc = bufferPath(bc, ac);
        if (bn > an) ac = bufferPath(ac, bc);

        interpolator = d3.interpolateString(
            bn > an ? makePath(ac) : a,
            an > bn ? makePath(bc) : b
        );

        return bn > an ? interpolator : function(t) {
            return t === 1 ? b : interpolator(t);
        };
    }
});
  </script>
</body>

style.css