Skip to content

Instantly share code, notes, and snippets.

@jalapic
Last active September 22, 2015 00:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jalapic/b629a4c2470b8646781c to your computer and use it in GitHub Desktop.
Save jalapic/b629a4c2470b8646781c to your computer and use it in GitHub Desktop.
bars: links,sorting

Linked bar charts with sorting functions

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
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment