Code-District Meetup
Dan Isla
Data Scientist at JPL
July 9, 2015
https://github.com/danisla/d3-talk
Get from CDN:
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js
Get via Bower:
bower install d3
Get via npm
npm install d3
Github wiki: https://github.com/mbostock/d3/wiki/API-Reference
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>D3</h1>
<div id="chart"></div>
</body>
</html>
Source: http://ethanschoonover.com/solarized
/* light is default mode, so pair with general html definition */
html, .light {
background-color: #fdf6e3;
color: #657b83; }
html *, .light * {
color: #657b83; }
html h1, html h2, html h3, html h4, html h5, html h6, .light h1, .light h2, .light h3, .light h4, .light h5, .light h6 {
color: #586e75;
border-color: #657b83; }
html a, html a:active, html a:visited, .light a, .light a:active, .light a:visited {
color: #586e75; }
.dark {
background-color: #002b36;
color: #839496; }
.dark * {
color: #839496; }
.dark h1, .dark h2, .dark h3, .dark h4, .dark h5, .dark h6 {
color: #93a1a1;
border-color: #839496; }
.dark a, .dark a:active, .dark a:visited {
color: #93a1a1; }
html * {
color-profile: sRGB;
rendering-intent: auto; }
/*# sourceMappingURL=style.css.map */
d3.select("h1")
.style("font-size","48px")
.style("color","#2aa198")
d3.select("body")
.append("img")
.attr("width","400px")
.attr("src", "http://apod.nasa.gov/apod/image/1507/2015Jul8_plutoNH_nasajhuaplswri950.jpg")
.data
method to bind data to a selection..enter
method to select "placeholder" elements that will be added in the future.d3.selectAll(".item")
.data(["Earth","Mars","Saturn","Jupiter"])
.enter()
.append("div").classed("item",true)
.text(function(d,i) {
return d;
})
d3.select("h1").text("Relative Planet Sizes in our Solar System ... and Pluto")
var data = [
{name: "Mercury", color: "#839496", radius: 1516},
{name: "Venus", color: "#b58900", radius: 3760},
{name: "Earth", color: "#268bd2", radius: 3963},
{name: "Mars", color: "#dc322f", radius: 2110},
{name: "Jupiter", color: "#d33682", radius: 44423},
{name: "Saturn", color: "#b58900", radius: 37449},
{name: "Uranus", color: "#586e75 ", radius: 15882},
{name: "Neptune", color: "#268bd2 ", radius: 15882},
{name: "Pluto", color: "#b58900", radius: 715},
]
Add the items and their text to the DOM
d3.selectAll(".item")
.data(data)
.enter()
.append("div").classed("item", true)
.style("font-size", "24px")
.text(function(d) {
return d.name;
})
Use CSS styling to visualize the data
.style("color", "white")
.style("background-color": function(d) {
return d.color
})
.style("width": function(d) {
return (d.radius/10) + "px"
})
rect
: Rectangle with attributes x
, y
, width
, and height
circle
: Circle with attributes: cx
, cy
, and r
<svg width=400 height=300>
<circle cx="20" cy="20" r="10"/>
</svg>
Add a border to the SVG box:
svg {
border-width: 1px;
border-style: solid;
}
d3.select("h1").text("SVG Hello World")
d3.select("#chart")
.append("svg")
.attr("width",600)
.attr("height", 400)
Add the first shape, a rectangle.
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 200)
.attr("height", 200)
.attr("fill","#002b36")
Add a circle inside the rectangle.
d3.select("svg")
.append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 100)
.attr("fill", "#268bd2")
d3.select("h1").text("Relative Planet Sizes in our Solar System ... and Pluto")
var data = [
{name: "Mercury", color: "#839496", radius: 1516},
{name: "Venus", color: "#b58900", radius: 3760},
{name: "Earth", color: "#268bd2", radius: 3963},
{name: "Mars", color: "#dc322f", radius: 2110},
{name: "Jupiter", color: "#d33682", radius: 44423},
{name: "Saturn", color: "#b58900", radius: 37449},
{name: "Uranus", color: "#586e75 ", radius: 15882},
{name: "Neptune", color: "#268bd2 ", radius: 15882},
{name: "Pluto", color: "#b58900", radius: 715},
];
Add SVG and dark background
d3.select("#chart")
.append("svg")
.attr({width: 640, height: 500})
.append("rect")
.attr({
width: "100%",
height: "100%",
fill: "#002b36"
})
Bind the data and create overlapping circles.
d3.select("svg").selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr({
cx: 10,
cy: 10,
r: function(d) {
return d.radius/100
},
fill: function (d) {
return d.color
}
})
API Reference: https://github.com/mbostock/d3/wiki/Scales
API Reference: https://github.com/mbostock/d3/wiki/Scales
For example, planet names to radii:
var scale = d3.scale.ordinal()
.domain(["Earth","Mars","Jupiter"])
.range([3963, 2110, 44423])
console.log(scale("Earth"));
// Output: 3963
.rangeBands
scale to create evenly-spaced discrete intervalshttps://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeBands
d3.select("h1").text("Planets of our Solar System ...and Pluto")
var data = [
{name: "Mercury", color: "#839496", radius: 1516},
{name: "Venus", color: "#b58900", radius: 3760},
{name: "Earth", color: "#268bd2", radius: 3963},
{name: "Mars", color: "#dc322f", radius: 2110},
{name: "Jupiter", color: "#d33682", radius: 44423},
{name: "Saturn", color: "#b58900", radius: 37449},
{name: "Uranus", color: "#586e75 ", radius: 15882},
{name: "Neptune", color: "#268bd2 ", radius: 15882},
{name: "Pluto", color: "#b58900", radius: 715},
]
var w = 800,
h = 400;
Create the ordinal scale
// Scale mapping position in data array to width of chart.
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, w], 0.3);
SVG chart with dark background
d3.select("#chart")
.append("svg")
.attr({width: w, height: h})
.append("rect")
.attr({width: "100%", height: "100%", fill: "#002b36"})
Create circles using ordinal scale for positioning
d3.select("svg").selectAll(".planet")
.data(data)
.enter().append("g")
.classed("planet", true)
.append("circle")
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", (h/2))
.attr("r", function(d) {
return d.radius/1000;
})
.attr("fill", function (d) {
return d.color;
})
Add some text labels
d3.selectAll(".planet")
.append("text")
.text(function(d) { return d.name})
.attr("fill","white")
.attr("font-family", "sans-serif")
.attr("font-size", "18px")
.attr("x", function(d,i) { return xScale(i) - 20})
.attr("y", (h/2 + 80))
.on("mouseover", function(d) {
d3.select(this)
.style("opacity", 0.5);
})
.on("mouseout", function(d) {
d3.select(this)
.style("opacity", 1.0);
})
d
is passed to the event handler, different compared to standard Javascript events.Use a transition to animate the size of the circles.
.transition()
.attr("r", function(d) {
return d.radius/1000;
})
.duration(1400)
.ease("elastic")
Another transition to animate the text.
.transition()
.attr("font-size", function(d) {
return 18;
})
.duration(400)
r
and font-size
to 0D3 library handles AJAX calls to download and parse remote data from sources like CSV and JSON.
Example datasets:
// Near-Earth Comets - Orbital Elements: https://data.nasa.gov/Space-Science/Near-Earth-Comets-Orbital-Elements/b67r-rgxc
// Terminology:
// MOID: Minimum Orbit Intersection Distance
// Node: Oribt angle of intersection
d3.csv("https://data.nasa.gov/api/views/b67r-rgxc/rows.csv", function(data) {
d3.select("body").append("pre").text(JSON.stringify(data, null, 2));
})
Data fetch and SVG setup.
d3.select("h1").text("Near-Earth Comets")
d3.csv("https://data.nasa.gov/api/views/b67r-rgxc/rows.csv", function(data) {
var w = 600,
h = 400;
d3.select("#chart").append("svg")
.attr({width: w, height: h})
.append("rect")
.attr({width: "100%", height: "100%", fill: "#002b36"})
d3.select("svg")
.append("circle")
.attr({cx: w/2, cy: h/2, r: 50, fill: "#268bd2"})
});
Create the scales
// Copy MOIDs to array and convert to float.
neo_moid = data.map(function(i) { return parseFloat(i["MOID (AU)"]) })
// Copy node intersection angles to array convert to float.
neo_node = data.map(function(i) { return parseFloat(i["Node (deg)"])})
// Make scale to map moid distances to center circle.
xScale = d3.scale.linear()
.domain([d3.min(neo_moid), d3.max(neo_moid)])
.rangeRound([0,150])
// Make scale to map moid distances to color gradiant.
colorScale = d3.scale.linear()
.domain([0, d3.max(neo_moid)*.33, d3.max(neo_moid)*.66, d3.max(neo_moid)])
.range(['#B58929','#C61C6F', '#268BD2', '#85992C'])
Add circles for the Near-Earth objects
d3.select("svg").selectAll(".neo")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
var mag = xScale(parseFloat(d["MOID (AU)"])),
angle = parseFloat(d["Node (deg)"]);
return ( (mag+55) * Math.cos(angle)) + w/2;
})
.attr("cy", function(d) {
var mag = xScale(parseFloat(d["MOID (AU)"])),
angle = parseFloat(d["Node (deg)"]);
return ( (mag+55) * Math.sin(angle)) + h/2;
})
.attr("r", 5)
.attr("fill",function(d) {
return colorScale(parseFloat(d["MOID (AU)"]))
})
Add event to display lables.
.on("mouseover", function(d) {
d3.select("svg")
.append("text")
.classed("neo-name", true)
.text(function() {
var label = d["Object"];
label += ": moid=";
label += parseFloat(d["MOID (AU)"]).toFixed(3) + "(AU)";
label += ", node=" + parseFloat(d["Node (deg)"]).toFixed(3) + " (deg)";
return label;
})
.attr("fill","white")
.attr("x", 5)
.attr("y", (h-5))
})
.on("mouseout", function(d) {
d3.selectAll("svg .neo-name").remove();
})
// Near-Earth Comets - Orbital Elements: https://data.nasa.gov/Space-Science/Near-Earth-Comets-Orbital-Elements/b67r-rgxc
// Terminology:
// MOID: Minimum Orbit Intersection Distance
// Node: Oribt angle of intersection
d3.select("h1").text("Near-Earth Comets")
d3.csv("https://data.nasa.gov/api/views/b67r-rgxc/rows.csv", function(data) {
var w = 600,
h = 400;
// Copy MOIDs to array and convert to float.
neo_moid = data.map(function(i) { return parseFloat(i["MOID (AU)"]) })
// Copy node intersection angles to array convert to float.
neo_node = data.map(function(i) { return parseFloat(i["Node (deg)"])})
// Make scale to map moid distances to center circle.
xScale = d3.scale.linear()
.domain([d3.min(neo_moid), d3.max(neo_moid)])
.rangeRound([0,150])
// Make scale to map moid distances to color gradiant.
colorScale = d3.scale.linear()
.domain([0, d3.max(neo_moid)*.33, d3.max(neo_moid)*.66, d3.max(neo_moid)])
.range(['#B58929','#C61C6F', '#268BD2', '#85992C'])
d3.select("#chart").append("svg")
.attr({width: w, height: h})
.append("rect")
.attr({width: "100%", height: "100%", fill: "#002b36"})
d3.select("svg")
.append("circle")
.attr({cx: w/2, cy: h/2, r: 50, fill: "#268bd2"})
d3.select("svg").selectAll(".neo")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
var mag = xScale(parseFloat(d["MOID (AU)"])),
angle = parseFloat(d["Node (deg)"]);
return ( (mag+55) * Math.cos(angle)) + w/2;
})
.attr("cy", function(d) {
var mag = xScale(parseFloat(d["MOID (AU)"])),
angle = parseFloat(d["Node (deg)"]);
return ( (mag+55) * Math.sin(angle)) + h/2;
})
.attr("r", 5)
.attr("fill",function(d) {
return colorScale(parseFloat(d["MOID (AU)"]))
})
.on("mouseover", function(d) {
d3.select("svg")
.append("text")
.classed("neo-name", true)
.text(function() {
var label = d["Object"];
label += ": moid=";
label += parseFloat(d["MOID (AU)"]).toFixed(3) + "(AU)";
label += ", node=" + parseFloat(d["Node (deg)"]).toFixed(3) + " (deg)";
return label;
})
.attr("fill","white")
.attr("x", 5)
.attr("y", (h-5))
})
.on("mouseout", function(d) {
d3.selectAll("svg .neo-name").remove();
})
});
// Mars Image Database - https://msl-raws.s3.amazonaws.com/images/image_manifest.json
d3.csv("https://msl-raws.s3.amazonaws.com/images/image_manifest.json", function(data) {
d3.select("body").append("pre").text(JSON.stringify(data, null, 2));
})
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w, h])
.linkStrength(0.1)
.friction(0.9)
.linkDistance(20)
.charge(-30)
.gravity(0.1)
.theta(0.8)
.alpha(0.1)
.start();
tick
event.var w = 400,
h = 400;
var circleWidth = 5;
var palette = {
"lightgray": "#819090",
"gray": "#708284",
"mediumgray": "#536870",
"darkgray": "#475B62",
"darkblue": "#0A2933",
"darkerblue": "#042029",
"paleryellow": "#FCF4DC",
"paleyellow": "#EAE3CB",
"yellow": "#A57706",
"orange": "#BD3613",
"red": "#D11C24",
"pink": "#C61C6F",
"purple": "#595AB7",
"blue": "#2176C7",
"green": "#259286",
"yellowgreen": "#738A05"
}
var nodes = [
{ name: "Parent"},
{ name: "child1"},
{ name: "child2", target: [0]},
{ name: "child3", target: [0]},
{ name: "child4", target: [1]},
{ name: "child5", target: [0, 1, 2, 3]}
];
var links = [];
for (var i = 0; i< nodes.length; i++) {
if (nodes[i].target !== undefined) {
for (var x = 0; x< nodes[i].target.length; x++ ) {
links.push({
source: nodes[i],
target: nodes[nodes[i].target[x]]
})
}
}
}
var myChart = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.3)
.charge(-1000)
.size([w, h])
var link = myChart.selectAll('line')
.data(links).enter().append('line')
.attr('stroke', palette.gray)
var node = myChart.selectAll('circle')
.data(nodes).enter()
.append('g')
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
})
node.append('text')
.text(function(d) { return d.name})
.attr('font-family', 'Roboto Slab')
.attr('fill', function(d, i) {
if (i>0) { return palette.mediumgray}
else { return palette.yellowgreen}
})
.attr('x', function(d, i) {
if (i>0) { return circleWidth + 4 }
else { return circleWidth -15 }
})
.attr('y', function(d, i) {
if (i>0) { return circleWidth }
else { return 8 }
})
.attr('text-anchor', function(d, i) {
if (i>0) { return 'beginning' }
else { return 'end'}
})
.attr('font-size', function(d, i) {
if (i>0) { return '1em' }
else { return '1.8em'}
})
force.on('tick', function(e) {
node.attr('transform', function(d, i) {
return 'translate('+ d.x +', '+ d.y +')';
})
link
.attr('x1', function(d) { return d.source.x })
.attr('y1', function(d) { return d.source.y })
.attr('x2', function(d) { return d.target.x })
.attr('y2', function(d) { return d.target.y })
})
force.start();
Overview of capabilities: http://mbostock.github.io/d3/talk/20110921/#26
Gallery: https://github.com/mbostock/d3/wiki/Gallery
Another space vis: http://joshworth.com/dev/pixelspace/pixelspace_solarsystem.html