Copied and tweaked from Scott Murray's examples. This gives introduction to:
- linking together two graphs built from same dataset
- adding 3 different sorting buttons based on ascending/descending of different variables
Copied and tweaked from Scott Murray's examples. This gives introduction to:
<!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: gray; | |
} | |
#buttonContainer { | |
margin-bottom: 10px; | |
} | |
button { | |
padding: 15px; | |
} | |
svg { | |
display: block; | |
margin-bottom: 10px; | |
background-color: white; | |
} | |
svg text { | |
font-family: sans-serif; | |
font-size: 16px; | |
fill: black; | |
font-style: bold; | |
} | |
g.bar { | |
fill: #000775; | |
} | |
g.bar text { | |
font-family: sans-serif; | |
font-size: 14px; | |
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 Giving</button> | |
<button id="sortDescending">Sort Receiving</button> | |
<button id="sortRank">Sort Rank</button> | |
</div> | |
<script type="text/javascript"> | |
//Width, height, padding | |
var w = 600; | |
var h = 350; | |
var padding = 50; | |
//Sample data | |
var dataset = [ | |
{"name":"A","sales":1520,"bonus":20}, | |
{"name":"B","sales":656,"bonus":340}, | |
{"name":"C","sales":182,"bonus":87}, | |
{"name":"D","sales":187,"bonus":242}, | |
{"name":"E","sales":35,"bonus":47}, | |
{"name":"F","sales":193,"bonus":321}, | |
{"name":"G","sales":56,"bonus":320}, | |
{"name":"H","sales":61,"bonus":447}, | |
{"name":"I","sales":21,"bonus":210}, | |
{"name":"J","sales":26,"bonus":420}, | |
{"name":"K","sales":28,"bonus":511}] ; | |
//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 salesScale = d3.scale.linear() | |
.domain([ 0, d3.max(dataset, function(d) { | |
return d.sales; | |
}) ]) | |
.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(salesScale) | |
.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", "salesChart") | |
.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 salesScale(d.sales) - 12; | |
}) | |
.text(function(d) { | |
return d.name + ": " + d.sales; | |
}) | |
//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 salesScale(d.sales); | |
}) | |
.attr("height", function(d) { | |
return h - padding - salesScale(d.sales); | |
}); | |
//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("Giving") | |
.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("#salesChart g.bar").sort(function(a, b) { | |
return d3.descending(a.sales, b.sales); | |
}) | |
.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.sales, b.sales); | |
}) | |
.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("#salesChart g.bar").sort(function(a, b) { | |
return d3.descending(a.bonus, b.bonus); | |
}) | |
.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.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("#salesChart 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> |