Skip to content

Instantly share code, notes, and snippets.

@Adlopez2016
Last active September 7, 2016 00:21
Show Gist options
  • Save Adlopez2016/3ba1c494e12d90ca4bfe5877f08e7003 to your computer and use it in GitHub Desktop.
Save Adlopez2016/3ba1c494e12d90ca4bfe5877f08e7003 to your computer and use it in GitHub Desktop.
grades visualization
license: mit

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

forked from jalapic's block: bars: links,sorting

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