3D Scatter generalizations

81 views
Skip to first unread message

Daniel LaLiberte

unread,
Jul 11, 2017, 9:43:25 PM7/11/17
to MathBox
I've been working on the scatter.html example, commenting and generalizing the code, trying to get it in a form that it accepts other data sets, scales automatically, etc.  I'm probably not going to continue much further right now, so I thought I would share it with you guys in case it helps someone make more progress.  

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>MathBox - Scatterplot</title>
  <script src="../../build/mathbox-bundle.js"></script>
  <link rel="stylesheet" href="../../build/mathbox.css">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1">

</head>
<body>

    <div class="goog-engedu-widgets-linreg-widget">
      <div style="margin: 0 auto; width: 640px; height: 480px;">
        <div id="3DScatter"
             style="margin: 0 auto; width: 630px; height: 470px; padding: 0; border:0;">
        </div>
      </div>
    </div>


  <script>

    // Insert into container
    var container = document.getElementById('3DScatter');

        // 3D Scatterplot by Max Goldstein, CC-BY
    var mathbox = mathBox({
      plugins: ['core', 'controls', 'cursor'],
      controls: {
        klass: THREE.OrbitControls
      },
      element: container,
    });

    var three = mathbox.three;



    var data = [
      [5.1,3.5,1.4,0.2],
      [4.9,3.0,1.4,0.2],
      [4.7,3.2,1.3,0.2],
      [4.6,3.1,1.5,0.2],
      [5.0,3.6,1.4,0.2],
      [5.4,3.9,1.7,0.4],
      [4.6,3.4,1.4,0.3],
      [5.0,3.4,1.5,0.2],
      [4.4,2.9,1.4,0.2],
      [4.9,3.1,1.5,0.1],
      [5.4,3.7,1.5,0.2],
      [4.8,3.4,1.6,0.2],
      [4.8,3.0,1.4,0.1],
      [4.3,3.0,1.1,0.1],
      [5.8,4.0,1.2,0.2],
      [5.7,4.4,1.5,0.4],
      [5.4,3.9,1.3,0.4],
      [5.1,3.5,1.4,0.3],
      [5.7,3.8,1.7,0.3],
      [5.1,3.8,1.5,0.3],
      [5.4,3.4,1.7,0.2],
      [5.1,3.7,1.5,0.4],
      [4.6,3.6,1.0,0.2],
      [5.1,3.3,1.7,0.5],
      [4.8,3.4,1.9,0.2],
      [5.0,3.0,1.6,0.2],
      [5.0,3.4,1.6,0.4],
      [5.2,3.5,1.5,0.2],
      [5.2,3.4,1.4,0.2],
      [4.7,3.2,1.6,0.2],
      [4.8,3.1,1.6,0.2],
      [5.4,3.4,1.5,0.4],
      [5.2,4.1,1.5,0.1],
      [5.5,4.2,1.4,0.2],
      [4.9,3.1,1.5,0.1],
      [5.0,3.2,1.2,0.2],
      [5.5,3.5,1.3,0.2],
      [4.9,3.1,1.5,0.1],
      [4.4,3.0,1.3,0.2],
      [5.1,3.4,1.5,0.2],
      [5.0,3.5,1.3,0.3],
      [4.5,2.3,1.3,0.3],
      [4.4,3.2,1.3,0.2],
      [5.0,3.5,1.6,0.6],
      [5.1,3.8,1.9,0.4],
      [4.8,3.0,1.4,0.3],
      [5.1,3.8,1.6,0.2],
      [4.6,3.2,1.4,0.2],
      [5.3,3.7,1.5,0.2],
      [5.0,3.3,1.4,0.2],
      [7.0,3.2,4.7,1.4],
      [6.4,3.2,4.5,1.5],
      [6.9,3.1,4.9,1.5],
      [5.5,2.3,4.0,1.3],
      [6.5,2.8,4.6,1.5],
      [5.7,2.8,4.5,1.3],
      [6.3,3.3,4.7,1.6],
      [4.9,2.4,3.3,1.0],
      [6.6,2.9,4.6,1.3],
      [5.2,2.7,3.9,1.4],
      [5.0,2.0,3.5,1.0],
      [5.9,3.0,4.2,1.5],
      [6.0,2.2,4.0,1.0],
      [6.1,2.9,4.7,1.4],
      [5.6,2.9,3.6,1.3],
      [6.7,3.1,4.4,1.4],
      [5.6,3.0,4.5,1.5],
      [5.8,2.7,4.1,1.0],
      [6.2,2.2,4.5,1.5],
      [5.6,2.5,3.9,1.1],
      [5.9,3.2,4.8,1.8],
      [6.1,2.8,4.0,1.3],
      [6.3,2.5,4.9,1.5],
      [6.1,2.8,4.7,1.2],
      [6.4,2.9,4.3,1.3],
      [6.6,3.0,4.4,1.4],
      [6.8,2.8,4.8,1.4],
      [6.7,3.0,5.0,1.7],
      [6.0,2.9,4.5,1.5],
      [5.7,2.6,3.5,1.0],
      [5.5,2.4,3.8,1.1],
      [5.5,2.4,3.7,1.0],
      [5.8,2.7,3.9,1.2],
      [6.0,2.7,5.1,1.6],
      [5.4,3.0,4.5,1.5],
      [6.0,3.4,4.5,1.6],
      [6.7,3.1,4.7,1.5],
      [6.3,2.3,4.4,1.3],
      [5.6,3.0,4.1,1.3],
      [5.5,2.5,4.0,1.3],
      [5.5,2.6,4.4,1.2],
      [6.1,3.0,4.6,1.4],
      [5.8,2.6,4.0,1.2],
      [5.0,2.3,3.3,1.0],
      [5.6,2.7,4.2,1.3],
      [5.7,3.0,4.2,1.2],
      [5.7,2.9,4.2,1.3],
      [6.2,2.9,4.3,1.3],
      [5.1,2.5,3.0,1.1],
      [5.7,2.8,4.1,1.3],
      [6.3,3.3,6.0,2.5],
      [5.8,2.7,5.1,1.9],
      [7.1,3.0,5.9,2.1],
      [6.3,2.9,5.6,1.8],
      [6.5,3.0,5.8,2.2],
      [7.6,3.0,6.6,2.1],
      [4.9,2.5,4.5,1.7],
      [7.3,2.9,6.3,1.8],
      [6.7,2.5,5.8,1.8],
      [7.2,3.6,6.1,2.5],
      [6.5,3.2,5.1,2.0],
      [6.4,2.7,5.3,1.9],
      [6.8,3.0,5.5,2.1],
      [5.7,2.5,5.0,2.0],
      [5.8,2.8,5.1,2.4],
      [6.4,3.2,5.3,2.3],
      [6.5,3.0,5.5,1.8],
      [7.7,3.8,6.7,2.2],
      [7.7,2.6,6.9,2.3],
      [6.0,2.2,5.0,1.5],
      [6.9,3.2,5.7,2.3],
      [5.6,2.8,4.9,2.0],
      [7.7,2.8,6.7,2.0],
      [6.3,2.7,4.9,1.8],
      [6.7,3.3,5.7,2.1],
      [7.2,3.2,6.0,1.8],
      [6.2,2.8,4.8,1.8],
      [6.1,3.0,4.9,1.8],
      [6.4,2.8,5.6,2.1],
      [7.2,3.0,5.8,1.6],
      [7.4,2.8,6.1,1.9],
      [7.9,3.8,6.4,2.0],
      [6.4,2.8,5.6,2.2],
      [6.3,2.8,5.1,1.5],
      [6.1,2.6,5.6,1.4],
      [7.7,3.0,6.1,2.3],
      [6.3,3.4,5.6,2.4],
      [6.4,3.1,5.5,1.8],
      [6.0,3.0,4.8,1.8],
      [6.9,3.1,5.4,2.1],
      [6.7,3.1,5.6,2.4],
      [6.9,3.1,5.1,2.3],
      [5.8,2.7,5.1,1.9],
      [6.8,3.2,5.9,2.3],
      [6.7,3.3,5.7,2.5],
      [6.7,3.0,5.2,2.3],
      [6.3,2.5,5.0,1.9],
      [6.5,3.0,5.2,2.0],
      [6.2,3.4,5.4,2.3],
      [5.9,3.0,5.1,1.8],
    ];
    draw3DScatter(data);

  function draw3DScatter(data) {

    three.renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0); // was 0xFAFAF8

    var dataMinimums = [Infinity, Infinity, Infinity, Infinity];
    var dataMaximums = [-Infinity, -Infinity, -Infinity, -Infinity];
    for (var i = 0; i < data.length; i++) {
      var row = data[i];
      for (var j = 0; j < row.length; j++) {
        if (row[j] < dataMinimums[j]) dataMinimums[j] = row[j];
        if (row[j] > dataMaximums[j]) dataMaximums[j] = row[j];
      }
    }
    var dataRanges = [0,1,2,3].map(function(i) {
      return Math.max(1, dataMaximums[i] - dataMinimums[i]); // Default to at least 1.
    });
    // Compute the order of magnitude of each range.
    var dataOOM = [0,1,2,3].map(function(i) {
      return Math.pow(10, Math.floor(Math.log10(dataRanges[i])));
    });

    // Round mins maxes and ranges based on OOM
    dataMinimums = dataMinimums.map(function(m, i) {
      return Math.floor(m / dataOOM[i]) * dataOOM[i];
    });
    dataMaximums = dataMaximums.map(function(m, i) {
      return Math.ceil(m / dataOOM[i]) * dataOOM[i];
    });
    dataRanges = [0, 1, 2, 3].map(function(i) {
      return dataMaximums[i] - dataMinimums[i];
    });
    var dataScaledMinimums = [0,1,2,3].map(function(i){
      return dataMinimums[i] / dataRanges[i];
    });

    three.camera.position.set(dataRanges[0], dataRanges[1], dataRanges[2]);
    three.controls.maxDistance = 2 * Math.max(dataRanges[0], dataRanges[1], dataRanges[2]);

    // Compute the view scaling as half the dataRanges.
    var viewScales = [dataRanges[0] / 2, dataRanges[1] / 2, dataRanges[2] / 2];
    // Each view range is always 0 to view scale.
    var viewRanges = viewScales.map(function(s){ return [0, s]; });

    var view = mathbox.cartesian({
      range: viewRanges,
      scale: viewScales,
    });

    var colors = {
      x: 0xFF4136,   // red
      y: 0xFFDC00,   // yellow
      z: 0x0074D9,   // blue
      xy: 0xFF851B,  // orange
      xz: 0xB10DC9,  // purple
      yz: 0x2ECC40,  // green
      xyz: 0x654321, // brown
    }

    function interpolate(lo, hi, n, oom){
      n--; // go to end of range
      var vals = [];
      for (var i = 0; i <= n; i++){
        var val = lo + (hi - lo)*(i/n);
        // Format based on OOMs. show up to 3 significant digits.
        vals.push(Math.floor(100 * val / oom) * oom / 100);
      }
      return vals;
    }

    var numTicks = [0, 1, 2].map(function(i) {
      return Math.floor(dataRanges[i]) + 1;
    });

    // Draw x axis tick labels
    view.scale({
      divide: numTicks[0],
      origin: [0, 0, viewScales[2], 0],
      axis: "x",
    }).text({
      live: true,
      data: interpolate(dataMinimums[0], dataMaximums[0], numTicks[0], dataOOM[0])
    }).label({
      color: colors.x,
    });

    // Draw y axis tick labels
    view.scale({
      divide: numTicks[1],
      origin: [0,0,viewScales[2], 0],
      axis: "y",
    }).text({
      live: true,
      data: interpolate(dataMinimums[1], dataMaximums[1], numTicks[1], dataOOM[1])
    }).label({
      color: colors.y,
      offset: [-16, 0]
    });

    // Draw z axis tick labels
    view.scale({
      divide: numTicks[2],
      origin: [viewScales[0],0,0,0],
      axis: "z",
    }).text({
      live: true,
      data: interpolate(dataMinimums[2], dataMaximums[2], numTicks[2], dataOOM[2])
    }).label({
      color: colors.z,
      offset: [16, 0]
    })

    // Draw grids for xy xz and yz planes.
    view.grid({
      axes: "xy",
      divideX: dataRanges[0] + 1,
      divideY: dataRanges[1] + 1,
      width: 5,
      opacity: 0.3,
    })
    .grid({
      axes: "xz",
      divideX: dataRanges[0] + 1,
      divideY: dataRanges[2] + 1,
      width: 5,
      opacity: 0.3,
    })
    .grid({
      axes: "yz",
      divideX: dataRanges[1] + 1,
      divideY: dataRanges[2] + 1,
      width: 5,
      opacity: 0.3,
    });

    view.array({
      items: 1,
      channels: 4,
      live: true,
      id: 'data',
      // data: is set below
    }).swizzle({
      order: "xyz"
    }).transform({
      // Scale data by dividing by the range and multiplying by the viewScales
      scale: dataRanges.slice(0,3).map(function(r,i){ return viewScales[i]/r; }),
      // Position data around center of each range by shifting by the scaled minimums * viewScales.
      position: dataScaledMinimums.slice(0,3).map(function(m,i){ return -viewScales[i]*m; }),
    }).point({ // Draw points for each data point
      color: 0x222222,
      size: 12,
    })
    // Do not .end() here.

    .transform({ // Project data on to xy plane.
      scale: [1, 1, 0],
      position: [0, 0, dataMinimums[2]],
    }).point({
      color: colors.xy,
      size: 7,
    }).end()

    .transform({ // Project data on to xz plane.
      scale: [1, 0, 1],
      position: [0, dataMinimums[1], 0],
    }).point({
      color: colors.xz,
      size: 7,
    }).end()

    .transform({ // Project data on to yz plane.
      scale: [0, 1, 1],
      position: [dataMinimums[0], 0, 0],
    }).point({
      color: colors.yz,
      size: 7,
    }).end()

    // Draw x value ticks at max y and min z
    .transform({
      position: [0, dataMaximums[1], dataMinimums[2]],
      scale: [1, 0.001, 0],
    }).repeat({
      items: 2,
    }).spread({
      unit: "absolute",
      alignItems: "first",
      items: [0, 100, 0, 0],
    }).vector({
      color: colors.x,
    }).end()

    // Draw y value ticks at max x and min z
    .transform({
      position: [dataMaximums[0], 0, dataMinimums[2]],
      scale: [0.001, 1, 0],
    }).repeat({
      items: 2,
    }).spread({
      unit: "absolute",
      alignItems: "first",
      items: [100, 0, 0, 0],
    }).vector({
      color: colors.y,
    }).end()

    // Draw z value ticks at min x and max y
    .transform({
      position: [dataMinimums[0], dataMaximums[1], 0],
      scale: [0, 0.001, 1],
    }).repeat({
      items: 2,
    }).spread({
      unit: "absolute",
      alignItems: "first",
      items: [0, 100, 0, 0],
    }).vector({
      color: colors.z,
    }).end()

    view.select('#data').set('data', data);

    }
  </script>

</body>
</html>


Reply all
Reply to author
Forward
0 new messages