Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active September 30, 2022 16:15

Revisions

  1. HarryStevens revised this gist Sep 27, 2022. 1 changed file with 7 additions and 8 deletions.
    15 changes: 7 additions & 8 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -32,7 +32,7 @@
    <div class="control">
    <div class="label">Jiggle</div>
    <div class="range">
    <input type="range" min="0.2" step="0.1" max="10" value="1.5">
    <input type="range" min="0.1" step="0.1" max="9.9" value="1.5">
    <div class="value"></div>
    </div>
    </div>
    @@ -59,10 +59,9 @@
    const x = randomUniform(radius, width - radius);
    const y = randomUniform(radius, height - radius);

    const data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    .split("")
    .map(name => ({ name, x: x(), y: y() }));

    const data = Array.from({ length: 64 })
    .map((_, id) => ({ id, x: x(), y: y() }));

    const color = scaleSequential()
    .domain([0, width * height / data.length * 2])
    .interpolator(interpolatePalette(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]));
    @@ -89,10 +88,10 @@

    // JOIN
    const polygon = svg.selectAll(".voronoi")
    .data(voronoi, d => d.data.name);
    .data(voronoi, d => d.data.id);

    const circle = svg.selectAll(".dot")
    .data(data, d => d.name);
    .data(data, d => d.id);

    // UPDATE
    polygon
    @@ -123,7 +122,7 @@
    }

    redraw(data);

    interval(() => {
    const jiggle = +input.value;
    const random = randomUniform(-jiggle, jiggle);
  2. HarryStevens revised this gist Sep 27, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -41,7 +41,6 @@

    <script src="https://unpkg.com/flubber@0.3.0"></script>
    <script type="module">
    import { range } from "https://cdn.skypack.dev/d3-array@3";
    import { Delaunay } from "https://cdn.skypack.dev/d3-delaunay@6";
    import { interpolateLab } from "https://cdn.skypack.dev/d3-interpolate@3";
    import { polygonArea } from "https://cdn.skypack.dev/d3-polygon@3";
    @@ -124,6 +123,7 @@
    }

    redraw(data);

    interval(() => {
    const jiggle = +input.value;
    const random = randomUniform(-jiggle, jiggle);
  3. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,8 @@
    }
    .control {
    position: absolute;
    right: 0px;
    right: 5px;
    top: 5px;
    }
    .control .label {
    font-family: sans-serif;
  4. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -75,13 +75,12 @@
    .attr("height", height);

    function redraw(data){
    const cloned = JSON.parse(JSON.stringify(data));
    const voronoi = Array.from(
    Delaunay
    .from(cloned.map(({x, y}) => [x, y]))
    .from(data.map(({x, y}) => [x, y]))
    .voronoi([0, 0, width, height])
    .cellPolygons(),
    (p, i) => Object.assign(p, { data: cloned[i] })
    (p, i) => Object.assign(p, { data: data[i] })
    );

    // transition
  5. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -69,7 +69,6 @@

    const input = document.querySelector(".control input");
    const value = document.querySelector(".control .value");
    value.innerHTML = input.value;

    const svg = select(".jiggle").append("svg")
    .attr("width", width)
  6. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -115,7 +115,7 @@
    polygon.enter().append("path")
    .attr("class", "voronoi")
    .attr("d", d => `M${d.join("L")}Z`)
    .style("fill", d => color(abs(polygonArea(d))))
    .style("fill", d => color(abs(polygonArea(d))));

    circle.enter().append("circle")
    .attr("class", "dot")
  7. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -42,8 +42,8 @@
    <script type="module">
    import { range } from "https://cdn.skypack.dev/d3-array@3";
    import { Delaunay } from "https://cdn.skypack.dev/d3-delaunay@6";
    import { polygonArea } from "https://cdn.skypack.dev/d3-polygon@3";
    import { interpolateLab } from "https://cdn.skypack.dev/d3-interpolate@3";
    import { polygonArea } from "https://cdn.skypack.dev/d3-polygon@3";
    import { randomUniform } from "https://cdn.skypack.dev/d3-random@3";
    import { scaleLinear, scaleSequential } from "https://cdn.skypack.dev/d3-scale@4";
    import { select } from "https://cdn.skypack.dev/d3-selection@3";
  8. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -132,12 +132,12 @@
    value.innerHTML = jiggle.toFixed(1);

    data.forEach(d => {
    d.x = min(width, max(0, d.x + random()));
    d.y = min(height, max(0, d.y + random()));
    d.x = min(width - radius, max(radius, d.x + random()));
    d.y = min(height - radius, max(radius, d.y + random()));

    return d;
    });

    redraw(data);
    }, duration * 2);

  9. HarryStevens revised this gist Sep 26, 2022. 1 changed file with 113 additions and 107 deletions.
    220 changes: 113 additions & 107 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -4,150 +4,156 @@
    body {
    margin: 0;
    }
    .voronoi {
    stroke-width: 1px;
    stroke: #3a403d;
    .control {
    position: absolute;
    right: 0px;
    }
    .control .label {
    font-family: sans-serif;
    text-align: center;
    }
    .dot {
    fill: #3a403d;
    .control input {
    display: inline-block;
    vertical-align: middle;
    }
    .control .value {
    display: inline-block;
    font-family: monospace;
    vertical-align: middle;
    }
    .voronoi {
    stroke: black;
    }
    </style>
    </head>
    <body>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script>
    <script>
    var width = window.innerWidth,
    height = window.innerHeight,
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(""),
    duration = 12, // milliseconds of transition duration
    n = 5, // amount of random movement
    r = [],
    data = [],
    c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]); // color scale
    for (var i = n * -1; i <= n; i++){
    r.push(i / 125);
    }
    alphabet.forEach(function(d){
    data.push({name: d, x: random(0, 100), y: random(0, 100)})
    });

    // scales
    var x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    var y = d3.scaleLinear().domain([0, 100]).range([height, 0]);

    // wrapper
    var svg = d3.select("body").append("svg")

    <div class="control">
    <div class="label">Jiggle</div>
    <div class="range">
    <input type="range" min="0.2" step="0.1" max="10" value="1.5">
    <div class="value"></div>
    </div>
    </div>

    <div class="jiggle"></div>

    <script src="https://unpkg.com/flubber@0.3.0"></script>
    <script type="module">
    import { range } from "https://cdn.skypack.dev/d3-array@3";
    import { Delaunay } from "https://cdn.skypack.dev/d3-delaunay@6";
    import { polygonArea } from "https://cdn.skypack.dev/d3-polygon@3";
    import { interpolateLab } from "https://cdn.skypack.dev/d3-interpolate@3";
    import { randomUniform } from "https://cdn.skypack.dev/d3-random@3";
    import { scaleLinear, scaleSequential } from "https://cdn.skypack.dev/d3-scale@4";
    import { select } from "https://cdn.skypack.dev/d3-selection@3";
    import { interval } from "https://cdn.skypack.dev/d3-timer@3";
    import { transition } from "https://cdn.skypack.dev/d3-transition@3";

    const { abs, max, min } = Math;

    const width = window.innerWidth;
    const height = window.innerHeight;
    const duration = 12; // milliseconds of transition duration
    const radius = 5; // circle radius
    const x = randomUniform(radius, width - radius);
    const y = randomUniform(radius, height - radius);

    const data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    .split("")
    .map(name => ({ name, x: x(), y: y() }));

    const color = scaleSequential()
    .domain([0, width * height / data.length * 2])
    .interpolator(interpolatePalette(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]));

    const input = document.querySelector(".control input");
    const value = document.querySelector(".control .value");
    value.innerHTML = input.value;

    const svg = select(".jiggle").append("svg")
    .attr("width", width)
    .attr("height", height);

    // voronoi tesselation
    var voronoi = d3.voronoi()
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); })
    .extent([[0, 0], [width, height]]);

    function redraw(data){
    const cloned = JSON.parse(JSON.stringify(data));
    const voronoi = Array.from(
    Delaunay
    .from(cloned.map(({x, y}) => [x, y]))
    .voronoi([0, 0, width, height])
    .cellPolygons(),
    (p, i) => Object.assign(p, { data: cloned[i] })
    );

    // transition
    var t = d3.transition()
    const t = transition()
    .duration(duration);

    // JOIN
    var voronoiGroup = svg.selectAll(".voronoi")
    .data(voronoi(data).polygons(), function(d){ return d.data.name; });

    var circle = svg.selectAll(".dot")
    .data(data, function(d){ return d.name; });

    // EXIT
    voronoiGroup.exit().remove();
    const polygon = svg.selectAll(".voronoi")
    .data(voronoi, d => d.data.name);

    circle.exit().remove();
    const circle = svg.selectAll(".dot")
    .data(data, d => d.name);

    // UPDATE
    voronoiGroup
    polygon
    .transition(t)
    .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
    .style("fill", function(d){ return c(area(d) * -1); });
    .attrTween("d", (d, i, e) => {
    const prev = select(e[i]).attr("d");
    const curr = `M${d.join("L")}Z`;
    return flubber.interpolate(prev, curr);
    })
    .style("fill", d => color(abs(polygonArea(d))));

    circle
    .transition(t)
    .attr("cx",function(d){ return x(d.x); })
    .attr("cy",function(d){ return y(d.y); });
    .attr("cx", d => d.x)
    .attr("cy", d => d.y);

    // ENTER
    voronoiGroup.enter().append("path")
    polygon.enter().append("path")
    .attr("class", "voronoi")
    .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
    .style("fill", function(d){ return c(area(d) * -1); })
    .attr("d", d => `M${d.join("L")}Z`)
    .style("fill", d => color(abs(polygonArea(d))))

    circle.enter().append("circle")
    .attr("class", "dot")
    .attr("r", 5)
    .attr("cx",function(d){ return x(d.x); })
    .attr("cy",function(d){ return y(d.y); });
    .attr("cx", d => d.x)
    .attr("cy", d => d.y);
    }

    redraw(data);
    d3.interval(function() {
    data.forEach(function(d,i){
    d.x = d.x + (1 * r[random(0, r.length - 1)]);
    d.y = d.y + (1 * r[random(0, r.length - 1)]);
    if (d.x < 0){
    d.x = 0;
    } else if (d.x > 100){
    d.x = 100;
    }
    if (d.y < 0){
    d.y = 0;
    } else if (d.y > 100){
    d.y = 100;
    }
    data[i] = d;
    });
    redraw(data);
    }, duration * 2);
    interval(() => {
    const jiggle = +input.value;
    const random = randomUniform(-jiggle, jiggle);

    value.innerHTML = jiggle.toFixed(1);

    /*FUNCTIONS*/
    data.forEach(d => {
    d.x = min(width, max(0, d.x + random()));
    d.y = min(height, max(0, d.y + random()));

    function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function shuffle(array) {
    var m = array.length, t, i;

    // While there remain elements to shuffle…
    while (m) {

    // Pick a remaining element…
    i = Math.floor(Math.random() * m--);

    // And swap it with the current element.
    t = array[m];
    array[m] = array[i];
    array[i] = t;
    }

    return array;
    }
    return d;
    });

    redraw(data);
    }, duration * 2);

    function area(points) {
    var sum = 0.0;
    var length = points.length;
    if (length < 3) {
    return sum;
    // https://observablehq.com/@harrystevens/roll-your-own-color-palette-interpolator
    function interpolatePalette(palette){
    const domain = [0];
    for (let i = 1, l = palette.length - 1; i <= l; i++){
    domain[i] = i / l;
    }
    points.forEach(function(d1, i1) {
    i2 = (i1 + 1) % length;
    d2 = points[i2];
    sum += (d2[1] * d1[0]) - (d1[1] * d2[0]);
    });
    return sum / 2;

    const scale = scaleLinear(domain, palette)
    .interpolate(interpolateLab)
    .clamp(true);

    return t => scale(t);
    }

    </script>
    </body>
    </html>
  10. HarryStevens revised this gist Dec 13, 2016. 1 changed file with 13 additions and 14 deletions.
    27 changes: 13 additions & 14 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -20,45 +20,44 @@
    var width = window.innerWidth,
    height = window.innerHeight,
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(""),
    duration = 12,
    n = 5,
    duration = 12, // milliseconds of transition duration
    n = 5, // amount of random movement
    r = [],
    data = [],
    c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]);
    c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]); // color scale
    for (var i = n * -1; i <= n; i++){
    r.push(i / 125);
    }
    alphabet.forEach(function(d){
    data.push({name: d, x: random(0, 100), y: random(0, 100)})
    });

    // scales
    var x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    var y = d3.scaleLinear().domain([0, 100]).range([height, 0]);

    // wrapper
    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    var g = svg.append("g")
    .attr("width", width)
    .attr("height", height);
    // voronoi tesselation
    var voronoi = d3.voronoi()
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); })
    .extent([[0, 0], [width, height]]);

    function redraw(data){

    // transition
    var t = d3.transition()
    .duration(duration);

    // voronoi tesselation
    var voronoi = d3.voronoi()
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); })
    .extent([[0, 0], [width, height]]);

    // JOIN
    var voronoiGroup = g.selectAll(".voronoi")
    var voronoiGroup = svg.selectAll(".voronoi")
    .data(voronoi(data).polygons(), function(d){ return d.data.name; });

    var circle = g.selectAll(".dot")
    var circle = svg.selectAll(".dot")
    .data(data, function(d){ return d.name; });

    // EXIT
  11. HarryStevens revised this gist Dec 13, 2016. 1 changed file with 0 additions and 0 deletions.
    Binary file added thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  12. HarryStevens created this gist Dec 13, 2016.
    1 change: 1 addition & 0 deletions .block
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    license: gpl-3.0
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    D3's general update pattern with a Voronoi diagram & rapidly updating data.
    154 changes: 154 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    <html>
    <head>
    <style>
    body {
    margin: 0;
    }
    .voronoi {
    stroke-width: 1px;
    stroke: #3a403d;
    }
    .dot {
    fill: #3a403d;
    }
    </style>
    </head>
    <body>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script>
    <script>
    var width = window.innerWidth,
    height = window.innerHeight,
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(""),
    duration = 12,
    n = 5,
    r = [],
    data = [],
    c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]);
    for (var i = n * -1; i <= n; i++){
    r.push(i / 125);
    }
    alphabet.forEach(function(d){
    data.push({name: d, x: random(0, 100), y: random(0, 100)})
    });
    var x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    var y = d3.scaleLinear().domain([0, 100]).range([height, 0]);

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    var g = svg.append("g")
    .attr("width", width)
    .attr("height", height);

    function redraw(data){

    // transition
    var t = d3.transition()
    .duration(duration);

    // voronoi tesselation
    var voronoi = d3.voronoi()
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); })
    .extent([[0, 0], [width, height]]);

    // JOIN
    var voronoiGroup = g.selectAll(".voronoi")
    .data(voronoi(data).polygons(), function(d){ return d.data.name; });

    var circle = g.selectAll(".dot")
    .data(data, function(d){ return d.name; });

    // EXIT
    voronoiGroup.exit().remove();

    circle.exit().remove();

    // UPDATE
    voronoiGroup
    .transition(t)
    .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
    .style("fill", function(d){ return c(area(d) * -1); });

    circle
    .transition(t)
    .attr("cx",function(d){ return x(d.x); })
    .attr("cy",function(d){ return y(d.y); });

    // ENTER
    voronoiGroup.enter().append("path")
    .attr("class", "voronoi")
    .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
    .style("fill", function(d){ return c(area(d) * -1); })

    circle.enter().append("circle")
    .attr("class", "dot")
    .attr("r", 5)
    .attr("cx",function(d){ return x(d.x); })
    .attr("cy",function(d){ return y(d.y); });
    }

    redraw(data);
    d3.interval(function() {
    data.forEach(function(d,i){
    d.x = d.x + (1 * r[random(0, r.length - 1)]);
    d.y = d.y + (1 * r[random(0, r.length - 1)]);
    if (d.x < 0){
    d.x = 0;
    } else if (d.x > 100){
    d.x = 100;
    }
    if (d.y < 0){
    d.y = 0;
    } else if (d.y > 100){
    d.y = 100;
    }
    data[i] = d;
    });
    redraw(data);
    }, duration * 2);


    /*FUNCTIONS*/

    function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function shuffle(array) {
    var m = array.length, t, i;

    // While there remain elements to shuffle…
    while (m) {

    // Pick a remaining element…
    i = Math.floor(Math.random() * m--);

    // And swap it with the current element.
    t = array[m];
    array[m] = array[i];
    array[i] = t;
    }

    return array;
    }

    function area(points) {
    var sum = 0.0;
    var length = points.length;
    if (length < 3) {
    return sum;
    }
    points.forEach(function(d1, i1) {
    i2 = (i1 + 1) % length;
    d2 = points[i2];
    sum += (d2[1] * d1[0]) - (d1[1] * d2[0]);
    });
    return sum / 2;
    }

    </script>
    </body>
    </html>