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.
<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>