block by johnburnmurdoch 3cc7b27036af99c07c18

3D Scatter Plot Using three.js

Full Screen

This is a 3D scatter plot rendered using webGL using on mobile phone accelerometer data. You can change view by rotating cube using the mouse. At present there’s no relationship between the colours of the particles.

The visualisation uses the fantastic threejs library for the 3D and hooks into webGL. The example presented here is heavily based on the threejs scatter plot example. I’ve also used d3.js for some of convenience functions to import the data, scale the data and set up the ranges for the axis’s.

index.html

<html>

<head>
    <script src="three.min.js"></script>
    <script src="https://raw.github.com/sole/tween.js/master/build/tween.min.js"></script>
    <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <style type="text/css">
    body {
        margin: 0px;
        padding: 0px;
    }
    #container {
        width:960px;
        height:500px;
    }
    </style>

</head>

<body>

<!--div id="container"></div-->
    <script>
    // <!--

    function createTextCanvas(text, color, font, size) {
        size = size || 16;
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var fontStr = (size + 'px ') + (font || 'Arial');
        ctx.font = fontStr;
        var w = ctx.measureText(text).width;
        var h = Math.ceil(size);
        canvas.width = w;
        canvas.height = h;
        ctx.font = fontStr;
        ctx.fillStyle = color || 'black';
        ctx.fillText(text, 0, Math.ceil(size * 0.8));
        return canvas;
    }

    function createText2D(text, color, font, size, segW, segH) {
        var canvas = createTextCanvas(text, color, font, size);
        var plane = new THREE.PlaneGeometry(canvas.width, canvas.height, segW, segH);
        var tex = new THREE.Texture(canvas);
        tex.needsUpdate = true;
        var planeMat = new THREE.MeshBasicMaterial({
            map: tex,
            color: 0xffffff,
            transparent: true
        });
        var mesh = new THREE.Mesh(plane, planeMat);
        mesh.scale.set(0.5, 0.5, 0.5);
        mesh.doubleSided = true;
        return mesh;
    }

    // from //stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
    function hexToRgb(hex) { //TODO rewrite with vector output
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    var renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    var w = 960;
    var h = 500;
    renderer.setSize(w, h);
    document.body.appendChild(renderer.domElement);

    renderer.setClearColorHex(0xEEEEEE, 1.0);

    var camera = new THREE.PerspectiveCamera(45, w / h, 1, 10000);
    camera.position.z = 200;
    camera.position.x = -100;
    camera.position.y = 100;

    var scene = new THREE.Scene();

    var scatterPlot = new THREE.Object3D();
    scene.add(scatterPlot);

    scatterPlot.rotation.y = 0;

    function v(x, y, z) {
        return new THREE.Vector3(x, y, z);
    }

    var unfiltered = [],
        lowPass = [],
        highPass = [];

    var format = d3.format("+.3f");

    var data = d3.csv("defaultData.csv", function (d) {
        
        d.forEach(function (d,i) {
            unfiltered[i] = {
                x: +d.x,
                y: +d.y,
                z: +d.z
            };
            lowPass[i] = {
                x: +d.lp_x,
                y: +d.lp_y,
                z: +d.lp_z
            };
            highPass[i] = {
                x: +d.hp_x,
                y: +d.hp_y,
                z: +d.hp_z
            }
        })

    var xExent = d3.extent(unfiltered, function (d) {return d.x; }),
        yExent = d3.extent(unfiltered, function (d) {return d.y; }),
        zExent = d3.extent(unfiltered, function (d) {return d.z; });

    var vpts = {
        xMax: xExent[1],
        xCen: (xExent[1] + xExent[0]) / 2,
        xMin: xExent[0],
        yMax: yExent[1],
        yCen: (yExent[1] + yExent[0]) / 2,
        yMin: yExent[0],
        zMax: zExent[1],
        zCen: (zExent[1] + zExent[0]) / 2,
        zMin: zExent[0]
    }

    var colour = d3.scale.category20c();

    var xScale = d3.scale.linear()
                  .domain(xExent)
                  .range([-50,50]);
    var yScale = d3.scale.linear()
                  .domain(yExent)
                  .range([-50,50]);                  
    var zScale = d3.scale.linear()
                  .domain(zExent)
                  .range([-50,50]);

    var lineGeo = new THREE.Geometry();
    lineGeo.vertices.push(
        v(xScale(vpts.xMin), yScale(vpts.yCen), zScale(vpts.zCen)), v(xScale(vpts.xMax), yScale(vpts.yCen), zScale(vpts.zCen)),
        v(xScale(vpts.xCen), yScale(vpts.yMin), zScale(vpts.zCen)), v(xScale(vpts.xCen), yScale(vpts.yMax), zScale(vpts.zCen)),
        v(xScale(vpts.xCen), yScale(vpts.yCen), zScale(vpts.zMax)), v(xScale(vpts.xCen), yScale(vpts.yCen), zScale(vpts.zMin)),

        v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMin)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMin)),
        v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMax)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMax)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMax)), v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMax)),

        v(xScale(vpts.xMin), yScale(vpts.yCen), zScale(vpts.zMax)), v(xScale(vpts.xMax), yScale(vpts.yCen), zScale(vpts.zMax)),
        v(xScale(vpts.xMin), yScale(vpts.yCen), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yCen), zScale(vpts.zMin)),
        v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zCen)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zCen)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zCen)), v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zCen)),

        v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMin)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMin)),
        v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMax)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMax)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMax)), v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMax)),

        v(xScale(vpts.xCen), yScale(vpts.yMin), zScale(vpts.zMax)), v(xScale(vpts.xCen), yScale(vpts.yMax), zScale(vpts.zMax)),
        v(xScale(vpts.xCen), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xCen), yScale(vpts.yMax), zScale(vpts.zMin)),
        v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zCen)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zCen)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zCen)), v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zCen)),

        v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yMax), zScale(vpts.zMax)),
        v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yMin), zScale(vpts.zMax)),
        v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMin)), v(xScale(vpts.xMin), yScale(vpts.yMax), zScale(vpts.zMax)),
        v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xMin), yScale(vpts.yMin), zScale(vpts.zMax)),

        v(xScale(vpts.xMin), yScale(vpts.yCen), zScale(vpts.zMin)), v(xScale(vpts.xMin), yScale(vpts.yCen), zScale(vpts.zMax)),
        v(xScale(vpts.xMax), yScale(vpts.yCen), zScale(vpts.zMin)), v(xScale(vpts.xMax), yScale(vpts.yCen), zScale(vpts.zMax)),
        v(xScale(vpts.xCen), yScale(vpts.yMax), zScale(vpts.zMin)), v(xScale(vpts.xCen), yScale(vpts.yMax), zScale(vpts.zMin)),
        v(xScale(vpts.xCen), yScale(vpts.yMin), zScale(vpts.zMin)), v(xScale(vpts.xCen), yScale(vpts.yMin), zScale(vpts.zMax))

    );
    var lineMat = new THREE.LineBasicMaterial({
        color: 0x000000,
        lineWidth: 1
    });
    var line = new THREE.Line(lineGeo, lineMat);
    line.type = THREE.Lines;
    scatterPlot.add(line);

    var titleX = createText2D('-X');
    titleX.position.x = xScale(vpts.xMin) - 12,
    titleX.position.y = 5;
    scatterPlot.add(titleX);

    var valueX = createText2D(format(xExent[0]));
    valueX.position.x = xScale(vpts.xMin) - 12,
    valueX.position.y = -5;
    scatterPlot.add(valueX);

    var titleX = createText2D('X');
    titleX.position.x = xScale(vpts.xMax) + 12;
    titleX.position.y = 5;
    scatterPlot.add(titleX);

    var valueX = createText2D(format(xExent[1]));
    valueX.position.x = xScale(vpts.xMax) + 12,
    valueX.position.y = -5;
    scatterPlot.add(valueX);

    var titleY = createText2D('-Y');
    titleY.position.y = yScale(vpts.yMin) - 5;
    scatterPlot.add(titleY);

    var valueY = createText2D(format(yExent[0]));
    valueY.position.y = yScale(vpts.yMin) - 15,
    scatterPlot.add(valueY);

    var titleY = createText2D('Y');
    titleY.position.y = yScale(vpts.yMax) + 15;
    scatterPlot.add(titleY);

    var valueY = createText2D(format(yExent[1]));
    valueY.position.y = yScale(vpts.yMax) + 5,
    scatterPlot.add(valueY);

    var titleZ = createText2D('-Z ' + format(zExent[0]));
    titleZ.position.z = zScale(vpts.zMin) + 2;
    scatterPlot.add(titleZ);

    var titleZ = createText2D('Z ' + format(zExent[1]));
    titleZ.position.z = zScale(vpts.zMax) + 2;
    scatterPlot.add(titleZ);

    var mat = new THREE.ParticleBasicMaterial({
        vertexColors: true,
        size: 10
    });

    var pointCount = unfiltered.length;
    var pointGeo = new THREE.Geometry();
    for (var i = 0; i < pointCount; i ++) {
        var x = xScale(unfiltered[i].x);
        var y = yScale(unfiltered[i].y);
        var z = zScale(unfiltered[i].z);

        pointGeo.vertices.push(new THREE.Vector3(x, y, z));
        console.log(pointGeo.vertices);
        //pointGeo.vertices[i].angle = Math.atan2(z, x);
        //pointGeo.vertices[i].radius = Math.sqrt(x * x + z * z);
        //pointGeo.vertices[i].speed = (z / 100) * (x / 100);
        pointGeo.colors.push(new THREE.Color().setRGB(
          hexToRgb(colour(i)).r / 255, hexToRgb(colour(i)).g / 255, hexToRgb(colour(i)).b / 255 
        ));

    }
    var points = new THREE.ParticleSystem(pointGeo, mat);
    scatterPlot.add(points);

    renderer.render(scene, camera);
    var paused = false;
    var last = new Date().getTime();
    var down = false;
    var sx = 0,
        sy = 0;
        
    window.onmousedown = function(ev) {
        down = true;
        sx = ev.clientX;
        sy = ev.clientY;
    };
    window.onmouseup = function() {
        down = false;
    };
    window.onmousemove = function(ev) {
        if (down) {
            var dx = ev.clientX - sx;
            var dy = ev.clientY - sy;
            scatterPlot.rotation.y += dx * 0.01;
            camera.position.y += dy;
            sx += dx;
            sy += dy;
        }
    }
    var animating = false;
    window.ondblclick = function() {
        animating = !animating;
    };

    function animate(t) {
        if (!paused) {
            last = t;
            if (animating) {
                var v = pointGeo.vertices;
                for (var i = 0; i < v.length; i++) {
                    var u = v[i];
                    console.log(u)
                    u.angle += u.speed * 0.01;
                    u.x = Math.cos(u.angle) * u.radius;
                    u.z = Math.sin(u.angle) * u.radius;
                }
                pointGeo.__dirtyVertices = true;
            }
            renderer.clear();
            camera.lookAt(scene.position);
            renderer.render(scene, camera);
        }
        window.requestAnimationFrame(animate, renderer.domElement);
    };
    animate(new Date().getTime());
    onmessage = function(ev) {
        paused = (ev.data == 'pause');
    };

    })
    //-->
    </script>
</body>

</html>

defaultData.csv

x,y,z,lp_x,lp_y,lp_z,hp_x,hp_y,hp_z,
0.235458,-0.597702,-0.724487,0.232433,-0.593757,-0.717156,0.003025,-0.003945,-0.007332,
0.235458,-0.597702,-0.724487,0.232735,-0.594152,-0.717889,0.002723,-0.003550,-0.006598,
0.217346,-0.597702,-0.724487,0.231197,-0.594507,-0.718549,-0.013850,-0.003195,-0.005939,
0.217346,-0.579590,-0.724487,0.229812,-0.593015,-0.719143,-0.012465,0.013425,-0.005345,
0.199234,-0.579590,-0.724487,0.226754,-0.591673,-0.719677,-0.027520,0.012083,-0.004810,
0.199234,-0.597702,-0.760712,0.224002,-0.592276,-0.723781,-0.024768,-0.005426,-0.036931,
0.163010,-0.579590,-0.706375,0.217903,-0.591007,-0.722040,-0.054893,0.011417,0.015665,
0.108673,-0.597702,-0.724487,0.206980,-0.591676,-0.722285,-0.098307,-0.006026,-0.002203,
0.090561,-0.615814,-0.724487,0.195338,-0.594090,-0.722505,-0.104777,-0.021724,-0.001982,
0.126785,-0.615814,-0.742599,0.188483,-0.596263,-0.724514,-0.061697,-0.019552,-0.018085,
0.108673,-0.597702,-0.706375,0.180502,-0.596407,-0.722701,-0.071828,-0.001295,0.016325,
0.108673,-0.615814,-0.742599,0.173319,-0.598347,-0.724690,-0.064646,-0.017467,-0.017909,
0.144897,-0.597702,-0.724487,0.170477,-0.598283,-0.724670,-0.025579,0.000581,0.000183,
0.126785,-0.597702,-0.760712,0.166107,-0.598225,-0.728274,-0.039322,0.000523,-0.032437,
0.108673,-0.633926,-0.778824,0.160364,-0.601795,-0.733329,-0.051691,-0.032131,-0.045495,
-0.054337,-0.597702,-0.742599,0.138894,-0.601386,-0.734256,-0.193231,0.003684,-0.008343,
-0.054337,-0.615814,-0.742599,0.119571,-0.602828,-0.735091,-0.173907,-0.012986,-0.007509,
-0.144897,-0.633926,-0.760712,0.093124,-0.605938,-0.737653,-0.238022,-0.027988,-0.023059,
-0.217346,-0.543365,-0.815048,0.062077,-0.599681,-0.745392,-0.279423,0.056316,-0.069656,
-0.398468,-0.615814,-0.760712,0.016023,-0.601294,-0.746924,-0.414491,-0.014520,-0.013787,
-0.760712,-0.815048,-0.670151,-0.061651,-0.622670,-0.739247,-0.699061,-0.192379,0.069096,
-0.724487,-0.597702,-0.959946,-0.127935,-0.620173,-0.761317,-0.596553,0.022471,-0.198629,
-0.344131,-0.452805,-0.996170,-0.149554,-0.603436,-0.784802,-0.194577,0.150632,-0.211368,
0.199234,0.036224,-1.430862,-0.114675,-0.539470,-0.849408,0.313909,0.575694,-0.581454,
0.434692,0.380356,-1.521423,-0.059739,-0.447487,-0.916610,0.494431,0.827843,-0.604814,
0.362244,0.326019,-0.815048,-0.017540,-0.370137,-0.906453,0.379784,0.696156,0.091405,
-0.072449,0.181122,-0.235458,-0.023031,-0.315011,-0.839354,-0.049418,0.496133,0.603896,
-1.032394,-0.633926,0.199234,-0.123968,-0.346902,-0.735495,-0.908427,-0.287024,0.934729,
-1.376526,-1.285965,0.054337,-0.249223,-0.440809,-0.656512,-1.127303,-0.845156,0.710849,
-1.159180,-1.503311,-0.579590,-0.340219,-0.547059,-0.648820,-0.818961,-0.956252,0.069230,
-0.434692,-0.941833,-1.249741,-0.349666,-0.586536,-0.708912,-0.085026,-0.355297,-0.540829,
0.018112,0.126785,-1.974228,-0.312888,-0.515204,-0.835443,0.331001,0.641990,-1.138784,
0.416580,0.543365,-1.720657,-0.239942,-0.409347,-0.923965,0.656522,0.952713,-0.796692,
0.416580,0.452805,-0.742599,-0.174289,-0.323132,-0.905828,0.590870,0.775937,0.163229,
0.144897,0.199234,0.090561,-0.142371,-0.270895,-0.806189,0.287268,0.470129,0.896750,
-0.579590,-0.235458,0.452805,-0.186093,-0.267352,-0.680290,-0.393497,0.031893,1.133095,
-1.086731,-0.778824,0.688263,-0.276156,-0.318499,-0.543435,-0.810574,-0.460325,1.231698,
-1.611984,-1.249741,0.489029,-0.409739,-0.411623,-0.440188,-1.202245,-0.838117,0.929217,
-1.485199,-1.177292,-1.014282,-0.517285,-0.488190,-0.497598,-0.967914,-0.689102,-0.516684,
-1.177292,-0.543365,-2.282135,-0.583286,-0.493708,-0.676051,-0.594006,-0.049658,-1.606084,
-0.235458,0.163010,-2.300247,-0.548503,-0.428036,-0.838471,0.313045,0.591045,-1.461776,
0.289795,0.470917,-1.829330,-0.464673,-0.338141,-0.937557,0.754468,0.809057,-0.891773,
0.470917,0.489029,-0.597702,-0.371114,-0.255424,-0.903571,0.842031,0.744453,0.305869,
0.362244,0.344131,0.307907,-0.297779,-0.195468,-0.782424,0.660022,0.539600,1.090331,
-0.271683,-0.018112,0.905609,-0.295169,-0.177733,-0.613620,0.023486,0.159620,1.519229,
-1.376526,-0.796936,1.340302,-0.403305,-0.239653,-0.418228,-0.973221,-0.557283,1.758530,
-2.028564,-1.195404,0.633926,-0.565831,-0.335228,-0.313013,-1.462734,-0.860176,0.946939,
-2.010452,-0.742599,-1.104843,-0.710293,-0.375965,-0.392196,-1.300159,-0.366634,-0.712647,
-1.376526,-0.072449,-2.300247,-0.776916,-0.345614,-0.583001,-0.599610,0.273165,-1.717246,
-0.072449,0.199234,-2.300247,-0.706469,-0.291129,-0.754726,0.634021,0.490363,-1.545522,
0.688263,0.416580,-1.122955,-0.566996,-0.220358,-0.791549,1.255259,0.636938,-0.331407,
0.724487,0.416580,-0.036224,-0.437848,-0.156664,-0.716016,1.162335,0.573244,0.679792,
0.199234,0.108673,1.050507,-0.374140,-0.130130,-0.539364,0.573374,0.238803,1.589870,
-1.050507,-0.416580,1.285965,-0.441776,-0.158775,-0.356831,-0.608730,-0.257805,1.642796,
-1.774994,-0.796936,1.104843,-0.575098,-0.222591,-0.210664,-1.199896,-0.574345,1.315507,
-2.028564,-0.941833,0.289795,-0.720445,-0.294516,-0.160618,-1.308120,-0.647318,0.450413,
-1.992340,-0.507141,-1.720657,-0.847634,-0.315778,-0.316622,-1.144706,-0.191363,-1.404036,
-1.068619,0.054337,-2.300247,-0.869733,-0.278767,-0.514984,-0.198886,0.333103,-1.785263,
-0.253571,0.289795,-2.245911,-0.808116,-0.221911,-0.688077,0.554546,0.511705,-1.557834,
0.543365,0.271683,-0.652039,-0.672968,-0.172551,-0.684473,1.216334,0.444234,0.032434,
0.507141,0.271683,0.235458,-0.554957,-0.128128,-0.592480,1.062098,0.399811,0.827938,
-0.253571,0.054337,1.050507,-0.524819,-0.109881,-0.428181,0.271248,0.164218,1.478688,
-1.104843,-0.181122,0.996170,-0.582821,-0.117005,-0.285746,-0.522022,-0.064116,1.281916,