Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Last active May 20, 2017 19:22

Revisions

  1. micahstubbs revised this gist May 20, 2017. 1 changed file with 0 additions and 0 deletions.
    Binary file added preview.gif
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  2. micahstubbs revised this gist May 20, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    an experiment that swaps the [chroma.brewer.YlGnBu](https://gka.github.io/chroma.js/#chroma-brewer) for [d3.interpolateMagma(t)](https://github.com/d3/d3-scale#interpolateMagma)
    an experiment that swaps the color source [chroma.brewer.YlGnBu](https://gka.github.io/chroma.js/#chroma-brewer) for [d3.interpolateMagma(t)](https://github.com/d3/d3-scale#interpolateMagma)

    an iteration on the block [ReGL circle animation example](http://bl.ocks.org/rflow/39692bd181fb1eb0b077a4caf886b077) from [@ajdant](https://twitter.com/ajdant)

  3. micahstubbs created this gist May 20, 2017.
    2 changes: 2 additions & 0 deletions .block
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    border: no
    license: Apache-2.0
    5 changes: 5 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    an experiment that swaps the [chroma.brewer.YlGnBu](https://gka.github.io/chroma.js/#chroma-brewer) for [d3.interpolateMagma(t)](https://github.com/d3/d3-scale#interpolateMagma)

    an iteration on the block [ReGL circle animation example](http://bl.ocks.org/rflow/39692bd181fb1eb0b077a4caf886b077) from [@ajdant](https://twitter.com/ajdant)

    commit history and related experiments [here](https://github.com/micahstubbs/regl-experiments)
    7 changes: 7 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    <html>
    <body>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.0/regl.min.js'></script>
    <script src='https://d3js.org/d3.v4.min.js'></script>
    <script src='vis.js'></script>
    </body>
    </html>
    12 changes: 12 additions & 0 deletions lebab.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    # safe
    lebab --replace vis.js --transform arrow
    lebab --replace vis.js --transform for-of
    lebab --replace vis.js --transform for-each
    lebab --replace vis.js --transform arg-rest
    lebab --replace vis.js --transform arg-spread
    lebab --replace vis.js --transform obj-method
    lebab --replace vis.js --transform obj-shorthand
    lebab --replace vis.js --transform multi-var
    # unsafe
    lebab --replace vis.js --transform let
    lebab --replace vis.js --transform template
    Binary file added preview.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
    Binary file added preview.xcf
    Binary file not shown.
    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.
    225 changes: 225 additions & 0 deletions vis.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,225 @@
    /* global createREGL document window */

    const regl = createREGL();

    const doc = document.body;
    const docWidth = window.innerWidth;
    const docHeight = window.innerHeight;
    const aspectRatio = docHeight / docWidth;

    const colCount = 150;
    const colWidth = docWidth / colCount;

    const rowCount = parseInt(colCount * aspectRatio, 10);
    const rowHeight = docHeight / rowCount;

    const circleCount = rowCount * colCount;
    const maxRadius = Math.min(colWidth, rowHeight) / 2;

    const minScale = 1e-6; // use small number to avoid reaching zero
    const maxScale = 1.0;
    const randomScale = () => minScale + ((maxScale - minScale) * Math.random());

    // sample nine colors from the magma color space
    const palette = [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0].map(t => d3.interpolateMagma(t));
    console.log('palette', palette);

    // gl mode uses [r,g,b] vector w normalised values
    const paletteGL = palette.map(d => {
    const cGL = d3.color(d).rgb();
    return [cGL.r, cGL.g, cGL.b].map(e => e / 255);
    });
    console.log('paletteGL', paletteGL);

    const randomColor = () => paletteGL[Math.floor(palette.length * Math.random())];

    const getGridPoints = (cols, rows, colWidth, rowHeight, gridWidth, gridHeight) => {
    const count = cols * rows;
    const points = new Float32Array(count * 2);

    let col;
    let row;
    let xPos;
    let yPos;
    let xIndex;
    let yIndex;

    for (let i = 0; i < count; i += 1) {
    col = i % cols;
    row = parseInt(i / cols, 10);

    xPos = (col * colWidth) + (colWidth / 2);
    yPos = (row * rowHeight) + (rowHeight / 2);

    xIndex = (2 * i);
    yIndex = xIndex + 1;

    points[xIndex] = ((2 * xPos) / gridWidth) - 1; // convert to (-1, 1) GL coord space
    points[yIndex] = ((2 * yPos) / gridHeight) - 1; // convert to (-1, 1) GL coord space
    }

    return points;
    };

    const getAnimStates = (count) => {
    const colors = new Float32Array(count * 3);
    const scales = new Float32Array(count);

    let r;
    let g;
    let b;
    let rIndex;
    let gIndex;
    let bIndex;

    for (let i = 0; i < count; i += 1) {
    [r, g, b] = randomColor();

    rIndex = (3 * i);
    gIndex = rIndex + 1;
    bIndex = rIndex + 2;

    colors[rIndex] = r;
    colors[gIndex] = g;
    colors[bIndex] = b;

    scales[i] = randomScale();
    }

    return { colors, scales };
    };

    // build inputs for our animation:
    const centroids = getGridPoints(colCount, rowCount, colWidth, rowHeight, docWidth, docHeight);

    // create a series of animation states for each circle
    const numStates = 50;
    const allStates = [];

    while (allStates.length < numStates) {
    allStates.push(getAnimStates(circleCount));
    }

    // regl command featuring shaders that will draw supplied points
    const drawPoints = regl({
    vert: `
    precision highp float;
    uniform float progress; // interpolation progress (from 0.0 to 1.0)
    uniform float maxRadius; // max radius to draw (n.b. point is square of 2*r x 2*r)
    attribute vec2 point; // position at which to draw
    attribute float scaleA; // scale factor A
    attribute float scaleB; // scale factor B
    attribute vec3 colorA; // color A
    attribute vec3 colorB; // color B
    varying vec3 rgb; // interpolated color
    varying float scale; // interpolated scale
    void main () {
    rgb = mix(colorA, colorB, progress);
    scale = mix(scaleA, scaleB, progress);
    gl_PointSize = maxRadius * 2. * scale;
    gl_Position = vec4(point, 0, 1);
    }
    `,

    frag: `
    precision highp float;
    varying vec3 rgb;
    varying float scale;
    void main () {
    // determine normalized distance from center of point
    float point_dist = length(gl_PointCoord * 2. - 1.);
    // calc scale at which to start fading out the circle
    float min_dist = scale * 0.70;
    // calc scale at which we find the edge of the circle
    float max_dist = scale;
    // https://thebookofshaders.com/glossary/?search=smoothstep
    float alpha = 1. - smoothstep(min_dist, max_dist, point_dist);
    gl_FragColor = vec4(rgb, alpha);
    }
    `,

    // using textbook example from http://regl.party/api#blending
    blend: {
    enable: true,
    func: {
    srcRGB: 'src alpha',
    srcAlpha: 1,
    dstRGB: 'one minus src alpha',
    dstAlpha: 1,
    },
    equation: {
    rgb: 'add',
    alpha: 'add',
    },
    color: [0, 0, 0, 0],
    },

    attributes: {
    point: centroids,
    colorA: regl.prop('currColors'),
    colorB: regl.prop('nextColors'),
    scaleA: regl.prop('currScales'),
    scaleB: regl.prop('nextScales'),
    },

    uniforms: {
    maxRadius,
    progress: regl.prop('progress'),
    },

    count: circleCount,

    primitive: 'points',

    });

    // render loop
    const fps = 60;
    const tweenTime = 2;
    const tweenFrames = fps * tweenTime;

    let state = 0;

    regl.frame(({ tick }) => {
    regl.clear({
    color: [0, 0, 0, 1],
    depth: 1,
    });

    // increment frame counter until we reach the desired loop point
    const frame = tick % tweenFrames;

    // increment state counter once we've looped back around
    if (frame === 0) {
    state = (state + 1) % numStates;
    // console.log('state counter', state);
    }

    // track progress as proportion of frames completed
    const progress = frame / tweenFrames;

    // determine current and next state
    const currState = allStates[state];
    const nextState = allStates[(state + 1) % numStates];

    drawPoints({
    currColors: currState.colors,
    currScales: currState.scales,
    nextColors: nextState.colors,
    nextScales: nextState.scales,
    progress,
    });
    });