A 3d Scatterplot of Registered Voter Populations of Wake County NC Voting Precincts. Based on Hoorvees' 3d scatter plot.
Last active
August 29, 2015 13:57
-
-
Save gerbal/9631405 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Create a 3d scatter plot within d3 selection parent. | |
function scatterPlot3d(parent) { | |
var x3d = parent | |
.append("x3d") | |
.style("width", parseInt(parent.style("width")) + "px") | |
.style("height", parseInt(parent.style("height")) + "px") | |
.style("border", "none") | |
var scene = x3d.append("scene") | |
scene.append("orthoviewpoint") | |
.attr("centerOfRotation", [2000, 2000, 2000]) | |
.attr("fieldOfView", [-2500, -2500, 7200, 7200]) | |
.attr("orientation", [-0.5, 1, 0.2, 1.12 * Math.PI / 4]) | |
.attr("position", [4000, 2000, 7500]) | |
scene.append("navigationInfo") | |
.attr("headlight", 'true') | |
.attr("type", '"EXAMINE"'); | |
scene.append("directionalLight") | |
.attr("on", "true") | |
.attr("ambientIntensity", '1'); | |
//var rows = initializeDataGrid(); | |
var axisRange = [0, 4500]; | |
var scales = []; | |
var initialDuration = 0; | |
var defaultDuration = 0; | |
var ease = 'linear'; | |
var time = 0; | |
var axisKeys = ["x", "y", "z"] | |
// Helper functions for initializeAxis() and drawAxis() | |
function axisName(name, axisIndex) { | |
return ["Democratic", "Republican", "Independent"][axisIndex] + name; | |
} | |
function constVecWithAxisValue(otherValue, axisValue, axisIndex) { | |
var result = [otherValue, otherValue, otherValue]; | |
result[axisIndex] = axisValue; | |
return result; | |
} | |
// Used to make 2d elements visible | |
function makeSolid(selection, color) { | |
selection.append("appearance") | |
.append("material") | |
.attr("diffuseColor", color || "black") | |
return selection; | |
} | |
// Initialize the axes lines and labels. | |
function initializePlot() { | |
initializeAxis(0); | |
initializeAxis(1); | |
initializeAxis(2); | |
} | |
function initializeAxis(axisIndex) { | |
var key = axisKeys[axisIndex]; | |
drawAxis(axisIndex, key, initialDuration); | |
var scaleMin = axisRange[0]; | |
var scaleMax = axisRange[1]; | |
// the axis line | |
var newAxisLine = scene.append("transform") | |
.attr("class", axisName("Axis", axisIndex)) | |
.attr("rotation", ([ | |
[0, 0, 0, 0], | |
[0, 0, 1, Math.PI / 2], | |
[0, 1, 0, -Math.PI / 2] | |
][axisIndex])) | |
.append("shape") | |
newAxisLine | |
.append("appearance") | |
.append("material") | |
.attr("emissiveColor", "black") | |
newAxisLine | |
.append("polyline2d") | |
// Line drawn along y axis does not render in Firefox, so draw one | |
// along the x axis instead and rotate it (above). | |
.attr("lineSegments", "0 0, 200 0") | |
// axis labels | |
var newAxisLabel = scene.append("transform") | |
.attr("class", axisName("AxisLabel", axisIndex)) | |
.attr("translation", constVecWithAxisValue(0, scaleMin + 1.1 * (scaleMax - scaleMin), axisIndex)) | |
.attr("scale", "200 200 200") | |
var newAxisLabelShape = newAxisLabel | |
.append("billboard") | |
.attr("axisOfRotation", "0 0 0") // face viewer | |
.append("shape") | |
.call(makeSolid) | |
var labelFontSize = 2.3; | |
newAxisLabelShape | |
.append("text") | |
.attr("string", axisName("", axisIndex)) | |
.attr("class", axisName("AxisLabelText", axisIndex)) | |
.attr("solid", "true") | |
.append("fontstyle") | |
.attr("size", labelFontSize) | |
.attr("family", "SANS") | |
.attr("justify", "END MIDDLE") | |
} | |
// Assign key to axis, creating or updating its ticks, grid lines, and labels. | |
function drawAxis(axisIndex, key, duration) { | |
var scale = d3.scale.linear() | |
.domain([0, 4500]) // demo data range | |
.range(axisRange) | |
scales[axisIndex] = scale; | |
var numTicks = 8; | |
var tickSize = 50; | |
var tickFontSize = 0.8; | |
// ticks along each axis | |
var ticks = scene.selectAll("." + axisName("Tick", axisIndex)) | |
.data(scale.ticks(numTicks)); | |
var newTicks = ticks.enter() | |
.append("transform") | |
.attr("class", axisName("Tick", axisIndex)) | |
newTicks.append("shape") | |
.call(makeSolid) | |
.append("box") | |
.attr("size", tickSize + " " + tickSize + " " + tickSize); | |
// enter + update | |
ticks.transition() | |
.duration(duration) | |
.attr("translation", function (tick) { | |
return constVecWithAxisValue(0, scale(tick), axisIndex); | |
}) | |
ticks.exit() | |
.remove(); | |
// tick labels | |
var tickLabels = ticks.selectAll("billboard shape text") | |
.data(function (d) { | |
return [d]; | |
}); | |
var newTickLabels = tickLabels.enter() | |
.append("transform") | |
.attr("scale", "200 200 200") | |
.append("billboard") | |
.attr("axisOfRotation", "0 0 0") | |
.append("shape") | |
.call(makeSolid) | |
newTickLabels.append("text") | |
.attr("string", scale.tickFormat(10)) | |
.attr("solid", "true") | |
.append("fontstyle") | |
.attr("size", tickFontSize) | |
.attr("family", "SANS") | |
.attr("justify", "END MIDDLE"); | |
tickLabels // enter + update | |
.attr("string", scale.tickFormat(10)) | |
tickLabels.exit() | |
.remove(); | |
// base grid lines | |
var gridLines = scene.selectAll("." + axisName("GridLine", axisIndex)) | |
.data(scale.ticks(numTicks)); | |
gridLines.exit() | |
.remove(); | |
var newGridLines = gridLines.enter() | |
.append("transform") | |
.attr("class", axisName("GridLine", axisIndex)) | |
.attr("rotation", [0, 0, 0, 0]) | |
.append("shape"); | |
newGridLines.append("appearance") | |
.append("material") | |
.attr("emissiveColor", "gray"); | |
newGridLines.append("polyline2d"); | |
gridLines.selectAll("shape polyline2d") | |
.transition() | |
.duration(duration) | |
.attr("lineSegments", "0 0, " + axisRange[1] + " 0"); | |
gridLines.transition() | |
.duration(duration) | |
.attr("translation", function (d) { | |
return "0 0 0 0"; | |
}); | |
} | |
// Update the data points (spheres) and stems. | |
function plotData(duration) { | |
if (!rows) { | |
console.log("no rows to plot.") | |
return; | |
} | |
var x = scales[0], | |
y = scales[1], | |
z = scales[2]; | |
var sphereRadius = 50; | |
// Draw a sphere at each x,y,z coordinate. | |
var datapoints = scene.selectAll(".datapoint") | |
.data(rows); | |
datapoints.exit() | |
.remove(); | |
var newDatapoints = datapoints.enter() | |
.append("transform") | |
.attr("class", "datapoint") | |
.attr("scale", [sphereRadius, sphereRadius, sphereRadius]) | |
.append("shape"); | |
newDatapoints | |
.append("appearance") | |
.append("material"); | |
newDatapoints | |
.append("sphere"); | |
// Does not work on Chrome; use transform instead | |
//.attr("radius", sphereRadius) | |
datapoints.selectAll("shape appearance material") | |
.attr("diffuseColor", 'steelblue'); | |
var tmpDatapoints = scene.selectAll(".datapoint"); | |
tmpDatapoints.selectAll("material") | |
.each(function (d, i) { | |
if (d.x > d.y && d.x > d.z) { | |
d3.select(this) | |
.attr("diffuseColor", '#0000ff') | |
} else if (d.y > d.x && d.y > d.z) { | |
d3.select(this) | |
.attr("diffuseColor", '#ff0000') | |
} else if (d.z > d.x && d.z > d.y) { | |
d3.select(this) | |
.attr("diffuseColor", '#00ff00') | |
} else { | |
d3.select(this) | |
.attr("diffuseColor", '#888888') | |
} | |
}); | |
var datapointsXY = scene.selectAll(".datapointXY") | |
.data(rows); | |
datapointsXY.exit() | |
.remove(); | |
var newDatapointsXY = datapointsXY.enter() | |
.append("transform") | |
.attr("class", "datapointXY") | |
.attr("scale", [sphereRadius, sphereRadius, 0]) | |
.append("shape"); | |
newDatapointsXY | |
.append("appearance") | |
.append("material"); | |
newDatapointsXY | |
.append("sphere"); | |
var datapointsYZ = scene.selectAll(".datapointYZ") | |
.data(rows); | |
datapointsYZ.exit() | |
.remove(); | |
var newDatapointsYZ = datapointsYZ.enter() | |
.append("transform") | |
.attr("class", "datapointYZ") | |
.attr("scale", [0, sphereRadius, sphereRadius]) | |
.append("shape"); | |
newDatapointsYZ | |
.append("appearance") | |
.append("material"); | |
newDatapointsYZ | |
.append("sphere"); | |
var datapointsXZ = scene.selectAll(".datapointXZ") | |
.data(rows); | |
datapointsXZ.exit() | |
.remove(); | |
var newDatapointsXZ = datapointsXZ.enter() | |
.append("transform") | |
.attr("class", "datapointXZ") | |
.attr("scale", [sphereRadius, 0, sphereRadius]) | |
.append("shape"); | |
newDatapointsXZ | |
.append("appearance") | |
.append("material"); | |
newDatapointsXZ | |
.append("sphere"); | |
var tmpDatapoints = scene.selectAll(".datapointXY"); | |
tmpDatapoints.selectAll("material") | |
.each(function (d, i) { | |
var tmpTotal = d.x + d.y; | |
var xRed = Math.round(((d.y) / tmpTotal) * 255); | |
var yBlue = Math.round(((d.x) / tmpTotal) * 255); | |
d3.select(this) | |
.attr("shininess", '1') | |
.attr("ambientIntensity", '1') | |
.attr("diffuseColor", rgbToHex(xRed, 0, yBlue)) | |
}); | |
var tmpDatapoints = scene.selectAll(".datapointXZ"); | |
tmpDatapoints.selectAll("material") | |
.each(function (d, i) { | |
var tmpTotal = d.x + d.z; | |
var xRed = Math.round(((d.z) / tmpTotal) * 255); | |
var yBlue = Math.round(((d.x) / tmpTotal) * 255); | |
d3.select(this) | |
.attr("shininess", '1') | |
.attr("ambientIntensity", '1') | |
.attr("diffuseColor", rgbToHex(0, xRed, yBlue)) | |
}); | |
var tmpDatapoints = scene.selectAll(".datapointYZ"); | |
tmpDatapoints.selectAll("material") | |
.each(function (d, i) { | |
var tmpTotal = d.z + d.y; | |
var xRed = Math.round(((d.y) / tmpTotal) * 255); | |
var yBlue = Math.round(((d.z) / tmpTotal) * 255); | |
d3.select(this) | |
.attr("shininess", "1") | |
.attr("ambientIntensity", "1") | |
.attr("diffuseColor", rgbToHex(xRed, yBlue, 0)) | |
.attr("specularColor", rgbToHex(xRed, yBlue, 0)) | |
}); | |
//hacking in the 2d planes | |
datapointsXY.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr("translation", function (row) { | |
return x(row[axisKeys[0]]) + " " + y(row[axisKeys[1]]) + " 0" | |
}) | |
datapointsYZ.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr("translation", function (row) { | |
return "0 " + y(row[axisKeys[1]]) + " " + z(row[axisKeys[2]]) | |
}) | |
datapointsXZ.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr("translation", function (row) { | |
return x(row[axisKeys[0]]) + " 0 " + z(row[axisKeys[2]]) | |
}) | |
datapoints.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr("translation", function (row) { | |
return x(row[axisKeys[0]]) + " " + y(row[axisKeys[1]]) + " " + z(row[axisKeys[2]]) | |
}) | |
// Draw a stem from the x-z plane to each sphere at elevation y. | |
// This convention was chosen to be consistent with x3d primitive ElevationGrid. | |
// var stems = scene.selectAll(".stem").data( rows ); | |
// stems.exit().remove(); | |
// var newStems = stems.enter() | |
// .append("transform") | |
// .attr("class", "stem") | |
// .append("shape"); | |
// newStems | |
// .append("appearance") | |
// .append("material") | |
// .attr("emissiveColor", "gray") | |
// newStems | |
// .append("polyline2d") | |
// .attr("lineSegments", function(row) { return "0 1, 0 0"; }) | |
// stems.transition().ease(ease).duration(duration) | |
// .attr("translation", | |
// function(row) { return x(row[axisKeys[0]]) + " 0 " + z(row[axisKeys[2]]); }) | |
// .attr("scale", | |
// function(row) { return [1, y(row[axisKeys[1]])]; }) | |
} | |
function componentToHex(c) { | |
var hex = c.toString(16); | |
return hex.length == 1 ? "0" + hex : hex; | |
} | |
function rgbToHex(r, g, b) { | |
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); | |
} | |
initializePlot(); | |
plotData(defaultDuration); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var rows = [{x:831,y:667,z:340}, | |
{x:868,y:683,z:474}, | |
{x:723,y:444,z:704}, | |
{x:436,y:340,z:401}, | |
{x:427,y:221,z:384}, | |
{x:1021,y:791,z:525}, | |
{x:708,y:466,z:289}, | |
{x:215,y:55,z:26}, | |
{x:491,y:328,z:239}, | |
{x:614,y:371,z:429}, | |
{x:678,y:421,z:1014}, | |
{x:677,y:455,z:411}, | |
{x:779,y:468,z:199}, | |
{x:1763,y:1073,z:438}, | |
{x:711,y:390,z:491}, | |
{x:1200,y:571,z:571}, | |
{x:384,y:273,z:386}, | |
{x:1237,y:794,z:736}, | |
{x:1636,y:316,z:49}, | |
{x:1604,y:474,z:136}, | |
{x:1818,y:1050,z:700}, | |
{x:2783,y:592,z:92}, | |
{x:1780,y:3057,z:1264}, | |
{x:297,y:92,z:23}, | |
{x:1914,y:675,z:99}, | |
{x:1021,y:622,z:188}, | |
{x:2631,y:937,z:360}, | |
{x:901,y:485,z:838}, | |
{x:485,y:295,z:477}, | |
{x:1555,y:1394,z:478}, | |
{x:1079,y:821,z:467}, | |
{x:699,y:389,z:388}, | |
{x:1612,y:817,z:60}, | |
{x:2053,y:347,z:63}, | |
{x:577,y:403,z:710}, | |
{x:769,y:511,z:545}, | |
{x:1222,y:628,z:445}, | |
{x:1194,y:782,z:559}, | |
{x:3471,y:709,z:169}, | |
{x:794,y:768,z:375}, | |
{x:877,y:761,z:1146}, | |
{x:1067,y:637,z:459}, | |
{x:1700,y:929,z:784}, | |
{x:661,y:493,z:516}, | |
{x:2000,y:787,z:257}, | |
{x:762,y:566,z:876}, | |
{x:994,y:707,z:370}, | |
{x:1339,y:1073,z:546}, | |
{x:2060,y:369,z:93}, | |
{x:464,y:318,z:356}, | |
{x:886,y:950,z:1247}, | |
{x:1294,y:1567,z:2101}, | |
{x:692,y:900,z:1333}, | |
{x:577,y:693,z:1102}, | |
{x:274,y:353,z:418}, | |
{x:660,y:729,z:1224}, | |
{x:784,y:798,z:949}, | |
{x:896,y:636,z:563}, | |
{x:977,y:859,z:661}, | |
{x:812,y:677,z:485}, | |
{x:690,y:566,z:413}, | |
{x:1107,y:1094,z:900}, | |
{x:972,y:976,z:917}, | |
{x:814,y:822,z:1207}, | |
{x:754,y:828,z:508}, | |
{x:617,y:1030,z:968}, | |
{x:812,y:1020,z:899}, | |
{x:624,y:615,z:366}, | |
{x:967,y:951,z:893}, | |
{x:1132,y:1657,z:1262}, | |
{x:604,y:575,z:524}, | |
{x:752,y:921,z:690}, | |
{x:713,y:770,z:568}, | |
{x:686,y:747,z:527}, | |
{x:786,y:1071,z:710}, | |
{x:539,y:736,z:608}, | |
{x:1123,y:1057,z:723}, | |
{x:690,y:646,z:316}, | |
{x:1240,y:1463,z:1166}, | |
{x:644,y:1054,z:1021}, | |
{x:2137,y:2128,z:1644}, | |
{x:1209,y:1933,z:1564}, | |
{x:1934,y:2795,z:1336}, | |
{x:779,y:897,z:536}, | |
{x:979,y:841,z:692}, | |
{x:744,y:940,z:1409}, | |
{x:1333,y:1539,z:1487}, | |
{x:1012,y:1151,z:1058}, | |
{x:869,y:679,z:683}, | |
{x:925,y:1318,z:1306}, | |
{x:753,y:741,z:835}, | |
{x:704,y:501,z:457}, | |
{x:570,y:404,z:474}, | |
{x:564,y:474,z:544}, | |
{x:1717,y:1371,z:954}, | |
{x:686,y:514,z:553}, | |
{x:1068,y:992,z:1230}, | |
{x:1157,y:1120,z:1101}, | |
{x:283,y:92,z:170}, | |
{x:1110,y:952,z:812}, | |
{x:1694,y:1506,z:1249}, | |
{x:425,y:307,z:342}, | |
{x:1126,y:716,z:690}, | |
{x:744,y:595,z:562}, | |
{x:1215,y:1121,z:1203}, | |
{x:1401,y:1459,z:1333}, | |
{x:496,y:563,z:672}, | |
{x:671,y:660,z:770}, | |
{x:979,y:763,z:448}, | |
{x:684,y:860,z:1047}, | |
{x:1172,y:1447,z:1515}, | |
{x:757,y:681,z:504}, | |
{x:829,y:792,z:775}, | |
{x:1160,y:1302,z:1035}, | |
{x:1042,y:521,z:638}, | |
{x:1944,y:761,z:704}, | |
{x:919,y:483,z:844}, | |
{x:443,y:290,z:532}, | |
{x:1668,y:916,z:1005}, | |
{x:1282,y:614,z:719}, | |
{x:2958,y:1437,z:1540}, | |
{x:1534,y:1359,z:1002}, | |
{x:1422,y:1082,z:1173}, | |
{x:1355,y:1348,z:2011}, | |
{x:1354,y:1157,z:1327}, | |
{x:1009,y:1100,z:1548}, | |
{x:1720,y:2135,z:2609}, | |
{x:965,y:829,z:1158}, | |
{x:803,y:699,z:948}, | |
{x:1667,y:1119,z:1253}, | |
{x:1025,y:1029,z:1295}, | |
{x:2556,y:1163,z:546}, | |
{x:608,y:610,z:723}, | |
{x:1006,y:544,z:378}, | |
{x:996,y:888,z:971}, | |
{x:979,y:529,z:426}, | |
{x:1499,y:611,z:334}, | |
{x:2033,y:1106,z:662}, | |
{x:1629,y:1673,z:1942}, | |
{x:830,y:935,z:1222}, | |
{x:217,y:175,z:271}, | |
{x:1132,y:1416,z:2260}, | |
{x:1287,y:1076,z:1651}, | |
{x:1732,y:1386,z:1884}, | |
{x:1311,y:1163,z:1851}, | |
{x:719,y:512,z:866}, | |
{x:900,y:653,z:794}, | |
{x:3218,y:1056,z:562}, | |
{x:805,y:437,z:344}, | |
{x:1092,y:635,z:766}, | |
{x:1196,y:665,z:765}, | |
{x:813,y:310,z:311}, | |
{x:1511,y:874,z:949}, | |
{x:4532,y:1424,z:664}, | |
{x:1491,y:811,z:771}, | |
{x:1575,y:654,z:470}, | |
{x:447,y:260,z:272}, | |
{x:2902,y:1718,z:1396}, | |
{x:2378,y:1296,z:1282}, | |
{x:2086,y:1011,z:716}, | |
{x:770,y:517,z:616}, | |
{x:2382,y:840,z:379}, | |
{x:3330,y:1848,z:1626}, | |
{x:2270,y:603,z:176}, | |
{x:714,y:293,z:273}, | |
{x:2050,y:1099,z:615}, | |
{x:1697,y:1556,z:891}, | |
{x:1146,y:1446,z:1882}, | |
{x:635,y:734,z:778}, | |
{x:1345,y:1113,z:1366}, | |
{x:1263,y:1638,z:1882}, | |
{x:1399,y:1317,z:814}, | |
{x:946,y:910,z:1309}, | |
{x:1170,y:1418,z:1142}, | |
{x:855,y:900,z:1098}, | |
{x:1993,y:1827,z:2169}, | |
{x:745,y:897,z:1427}, | |
{x:953,y:507,z:681}, | |
{x:1298,y:1009,z:1456}, | |
{x:1807,y:2042,z:2582}, | |
{x:763,y:830,z:956}, | |
{x:1351,y:1448,z:1659}, | |
{x:699,y:698,z:642}, | |
{x:642,y:765,z:991}, | |
{x:692,y:733,z:945}, | |
{x:1135,y:690,z:884}, | |
{x:1910,y:1239,z:979}, | |
{x:1164,y:1012,z:866}, | |
{x:820,y:1043,z:1131}, | |
{x:872,y:1417,z:1058}, | |
{x:916,y:1260,z:1381}, | |
{x:1280,y:1214,z:1078}, | |
{x:850,y:1249,z:1226}, | |
{x:1276,y:1547,z:1520}, | |
{x:701,y:958,z:892}, | |
{x:816,y:1375,z:1095}, | |
{x:836,y:1224,z:1217}, | |
{x:674,y:901,z:928}, | |
{x:2238,y:3037,z:2145}, | |
{x:1230,y:1966,z:1348}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html > | |
<script type="text/javascript" src="data.js"></script> | |
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> | |
<script type="text/javascript" src="http://x3dom.org/x3dom/dist/x3dom-full.js"></script> | |
<script type="text/javascript" src="3dscatter.js"></script> | |
<link rel="stylesheet" type="text/css" href="http://www.x3dom.org/download/dev/x3dom.css"/> | |
<div id="divPlot"></div> | |
<script> | |
d3.select('html').style('height','100%').style('width','100%') | |
d3.select('body').style('height','100%').style('width','100%') | |
d3.select('#divPlot').style('width', "100%").style('height', "100%") | |
scatterPlot3d( d3.select('#divPlot')); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment