|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
|
|
<title>D3: Linked charts</title> |
|
|
|
<style type="text/css"> |
|
|
|
|
|
|
|
body { |
|
background-color: white; |
|
} |
|
|
|
#buttonContainer { |
|
margin-bottom: 10px; |
|
} |
|
|
|
button { |
|
padding: 5px; |
|
} |
|
|
|
svg { |
|
display: block; |
|
margin-bottom: 10px; |
|
background-color: turquoise; |
|
} |
|
|
|
svg text { |
|
font-family: sans-serif; |
|
font-size: 10px; |
|
fill: black; |
|
font-style: bold; |
|
} |
|
|
|
g.bar { |
|
fill: #000dcc; |
|
} |
|
|
|
g.bar text { |
|
font-family: sans-serif; |
|
font-size: 08px; |
|
fill: black; |
|
font-style: bold; |
|
text-anchor: middle; |
|
opacity: 0; |
|
} |
|
|
|
g.bar.highlight text { |
|
opacity: 1; |
|
} |
|
|
|
g.bar.highlight rect { |
|
fill: #8ec1ff; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: black; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.axis text { |
|
font-family: sans-serif; |
|
font-size: 11px; |
|
} |
|
|
|
|
|
|
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div id="buttonContainer"> |
|
<button id="sort">Sort Descending</button> |
|
<button id="sortDescending">Sort Ascending</button> |
|
</div> |
|
|
|
<script type="text/javascript"> |
|
|
|
|
|
|
|
//Width, height, padding |
|
var w = 950; |
|
var h = 450; |
|
var padding = 50; |
|
|
|
//Sample data |
|
var dataset = [ |
|
{"name":23802620, "grades":4.85}, |
|
{"name":23802825, "grades":4.865}, |
|
{"name":23801894, "grades":3.24}, |
|
{"name":23802926, "grades":5}, |
|
{"name":23800661, "grades":3.19}, |
|
{"name":23800768, "grades":3.98}, |
|
{"name":23800972, "grades":4.89}, |
|
{"name":23801922, "grades":3.73}, |
|
{"name":23805498, "grades":4.795}, |
|
{"name":23805913, "grades":4.85}, |
|
{"name":23800311, "grades":2.81}, |
|
{"name":23806395, "grades":4.72}, |
|
{"name":23808850, "grades":3.85}, |
|
{"name":23802872, "grades":2.16}, |
|
{"name":23802105, "grades":4.715}, |
|
{"name":23809880, "grades":4.92}, |
|
{"name":23802056, "grades":4.48}, |
|
{"name":23807897, "grades":5.2}, |
|
{"name":23807916, "grades":5}, |
|
{"name":23801962, "grades":3.62}, |
|
{"name":23808246, "grades":4.61}, |
|
{"name":23802600, "grades":0.11}, |
|
{"name":23808311, "grades":4.7} |
|
]; |
|
|
|
//Configure x and y scale functions |
|
var xScale = d3.scale.ordinal() |
|
.domain(d3.range(dataset.length)) |
|
.rangeRoundBands([ padding, w - padding ], 0.1); |
|
|
|
//Now using two different y scales for two different charts |
|
var gradesScale = d3.scale.linear() |
|
.domain([ 0, d3.max(dataset, function(d) { |
|
return d.grades; |
|
}) ]) |
|
.rangeRound([ h - padding, padding ]); |
|
|
|
var bonusScale = d3.scale.linear() |
|
.domain([ 0, d3.max(dataset, function(d) { |
|
return d.bonus; |
|
}) ]) |
|
.rangeRound([ h - padding, padding ]); |
|
|
|
//Now using two different y axes |
|
var salesAxis = d3.svg.axis() |
|
.scale(gradesScale) |
|
.orient("left") |
|
.ticks(5) |
|
.outerTickSize(0); // remove outer tick mark on axis |
|
|
|
var bonusAxis = d3.svg.axis() |
|
.scale(bonusScale) |
|
.orient("left") |
|
.ticks(5) |
|
.outerTickSize(0); |
|
|
|
|
|
|
|
// |
|
// Make the first chart (sales data) |
|
// |
|
|
|
//Create SVG element |
|
var svg = d3.select("body") |
|
.append("svg") |
|
.attr("id", "gradesChart") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
//Create groups |
|
var groups = svg.selectAll("g") |
|
.data(dataset) |
|
.enter() |
|
.append("g") |
|
.attr("class", "bar") |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
|
|
//Add bar to each group |
|
var rects = groups.append("rect") |
|
.attr("x", 0) |
|
.attr("y", function(d) { |
|
return h - padding; |
|
}) |
|
.attr("width", xScale.rangeBand()) |
|
.attr("height", 0) |
|
// .attr("fill", "SteelBlue") |
|
; |
|
|
|
//Add label to each group |
|
groups.append("text") |
|
.attr("x", xScale.rangeBand() / 2) |
|
.attr("y", function(d) { |
|
return gradesScale(d.grades) - 12; |
|
}) |
|
.text(function(d) { |
|
//return d.name + ": " + d.grades; |
|
return d.grades; |
|
}) |
|
|
|
//Add second piece of text - remove css opacity directly |
|
// height is related to height-padding of svg |
|
groups.append("text") |
|
.attr("x", xScale.rangeBand() / 2) |
|
.attr("y", [h - padding/1.6]) |
|
.text(function(d) { |
|
return d.name; |
|
}) |
|
.style("opacity", 1) |
|
|
|
//Transition rects into place |
|
rects.transition() |
|
.delay(function(d, i) { |
|
return i * 100; |
|
}) |
|
.duration(1500) |
|
.attr("y", function(d) { |
|
return gradesScale(d.grades); |
|
}) |
|
.attr("height", function(d) { |
|
return h - padding - gradesScale(d.grades); |
|
}); |
|
|
|
//Create y axis |
|
svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + padding + ",0)") |
|
.attr("opacity", 0) |
|
.call(salesAxis) |
|
.transition() |
|
.delay(2000) |
|
.duration(1500) |
|
.attr("opacity", 1.0); |
|
|
|
|
|
// Title for chart 1 |
|
svg.append("text") |
|
.attr("x", padding/2) |
|
.attr("y", padding/2.5) |
|
.text("Grades") |
|
.style("text-anchor", "start"); |
|
|
|
|
|
|
|
// |
|
// Make the second chart (bonus data) |
|
// |
|
|
|
//Create SVG element |
|
svg = d3.select("body") |
|
.append("svg") |
|
.attr("id", "bonusChart") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
//Create groups |
|
groups = svg.selectAll("g") |
|
.data(dataset) |
|
.enter() |
|
.append("g") |
|
.attr("class", "bar") |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
|
|
//Add bar to each group |
|
rects = groups.append("rect") |
|
.attr("x", 0) |
|
.attr("y", function(d) { |
|
return h - padding; |
|
}) |
|
.attr("width", xScale.rangeBand()) |
|
.attr("height", 0) |
|
// .attr("fill", "SteelBlue") |
|
; |
|
|
|
//Add label to each group |
|
groups.append("text") |
|
.attr("x", xScale.rangeBand() / 2) |
|
.attr("y", function(d) { |
|
return bonusScale(d.bonus) - 10; |
|
}) |
|
.text(function(d) { |
|
return d.name + ": " + d.bonus; |
|
}) |
|
|
|
//Add second piece of text - remove css opacity directly |
|
// height is related to height-padding of svg |
|
groups.append("text") |
|
.attr("x", xScale.rangeBand() / 2) |
|
.attr("y", [h - padding/1.6]) |
|
.text(function(d) { |
|
return d.name; |
|
}) |
|
.style("opacity", 1) |
|
|
|
//Transition rects into place |
|
rects.transition() |
|
.delay(function(d, i) { |
|
return i * 100; |
|
}) |
|
.duration(1500) |
|
.attr("y", function(d) { |
|
return bonusScale(d.bonus); |
|
}) |
|
.attr("height", function(d) { |
|
return h - padding - bonusScale(d.bonus); |
|
}); |
|
|
|
//Create y axis |
|
svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + padding + ",0)") |
|
.attr("opacity", 0) |
|
.call(bonusAxis) |
|
.transition() |
|
.delay(2000) |
|
.duration(1500) |
|
.attr("opacity", 1.0); |
|
|
|
|
|
|
|
//New functionality for interaction for ALL groups |
|
//in BOTH charts |
|
d3.selectAll("g.bar") |
|
.on("mouseover", function(d) { |
|
|
|
//Instead of d3.select(this), now we want to |
|
//select ALL groups that match the same |
|
//criteria as this one, i.e. this group |
|
//and the corresponding one in the other chart. |
|
// |
|
//We begin by selecting all groups, then |
|
//filtering to exclude those that don't match. |
|
// |
|
//We'll use each person's "name" as a unique |
|
//identifier. (With a real-world data set, you'd |
|
//probably use an ID number here.) |
|
|
|
var thisName = d.name; |
|
|
|
d3.selectAll("g.bar") |
|
.filter(function(d) { |
|
//If the name from the original group on |
|
//which mouseover was triggered matches the |
|
//name on the group we're evaluating right now… |
|
if (thisName == d.name) { |
|
return true; //…then it's a match |
|
} |
|
}) |
|
.classed("highlight", true); |
|
|
|
}) |
|
.on("mouseout", function() { |
|
|
|
//We could be selective, using the same filtering |
|
//criteria above, or, for simplicity, just |
|
//de-highlight all the groups. |
|
|
|
d3.selectAll("g.bar") |
|
.classed("highlight", false); |
|
|
|
}) |
|
|
|
// Title for chart 2 |
|
svg.append("text") |
|
.attr("x", padding/2) |
|
.attr("y", padding/2.5) |
|
.text("Receiving") |
|
.style("text-anchor", "start"); |
|
|
|
|
|
|
|
|
|
//Sort Ascending button - keep both graphs but link. |
|
d3.select("#sort") |
|
.on("click", function() { |
|
|
|
//Need to reselect all groups in each chart |
|
d3.selectAll("#gradesChart g.bar").sort(function(a, b) { |
|
return d3.descending(a.grades, b.grades); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
|
|
d3.selectAll("#bonusChart g.bar").sort(function(a, b) { |
|
return d3.descending(a.grades, b.grades); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
}) |
|
; |
|
|
|
|
|
/// Descending Button - sort both graphs but keep linked |
|
d3.select("#sortDescending") |
|
.on("click", function() { |
|
|
|
//Need to reselect all groups in each chart |
|
d3.selectAll("#gradesChart g.bar").sort(function(a, b) { |
|
return d3.ascending(a.grades, b.grades); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
|
|
d3.selectAll("#bonusChart g.bar").sort(function(a, b) { |
|
return d3.ascending(a.bonus, b.bonus); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
}); |
|
|
|
/// Descending Button - sort both graphs but keep linked |
|
d3.select("#sortRank") |
|
.on("click", function() { |
|
|
|
//Need to reselect all groups in each chart |
|
d3.selectAll("#gradesChart g.bar").sort(function(a, b) { |
|
return d3.ascending(a.name, b.name); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
|
|
d3.selectAll("#bonusChart g.bar").sort(function(a, b) { |
|
return d3.ascending(a.name, b.name); |
|
}) |
|
.transition() |
|
.delay(function(d, i) { |
|
return i * 50; |
|
}) |
|
.duration(1000) |
|
.attr("transform", function(d, i) { |
|
return "translate(" + xScale(i) + ",0)"; |
|
}); |
|
}); |
|
|
|
|
|
|
|
|
|
</script> |
|
</body> |
|
</html> |