Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active July 24, 2017 08:03
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 HarryStevens/a32d0737827d874e98a93c19d8cefe9f to your computer and use it in GitHub Desktop.
Save HarryStevens/a32d0737827d874e98a93c19d8cefe9f to your computer and use it in GitHub Desktop.
Barbell Chart
license: gpl-3.0

A scatter chart comparing two variables at each point along the x-axis. A connector line between the circles indicates that the two variables are related (e.g. male and female). As with other bubble charts, each circle can encode four data attributes: (1) horizontal position, (2) vertical position, (3) size, and (4) color.

<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: "Helvetica Neue", sans-serif;
}
.dot {
stroke-width: 1px;
fill-opacity: .6;
}
.dot.a {
stroke: steelblue;
fill: steelblue;
}
.dot.b {
stroke: tomato;
fill: tomato;
}
.connect-line {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<body>
<div id="viz"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-marcon/build/d3-marcon.min.js"></script>
<script>
// config
var m = d3.marcon().top(40).bottom(40).left(40).right(40).width(window.innerWidth).height(window.innerHeight);
m.render();
var width = m.innerWidth(),
height = m.innerHeight(),
svg = m.svg(),
x_scale = d3.scaleLinear()
.range([0, width])
.domain([0, 100]),
y_scale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1000]),
size_scale = d3.scaleLinear()
.range([1, 30])
.domain([1, 30]),
t = d3.transition()
.duration(750),
x_axis = d3.axisBottom()
.scale(x_scale),
y_axis = d3.axisLeft()
.scale(y_scale);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x_axis);
svg.append("g")
.attr("class", "y axis")
.call(y_axis);
draw_chart(generate_binned_data());
d3.interval(function(){
draw_chart(generate_binned_data());
}, 2000);
function draw_chart(data){
// JOIN
var dot_a = svg.selectAll(".dot.a")
.data(data, function(d){ return d.x; });
var dot_b = svg.selectAll(".dot.b")
.data(data, function(d){ return d.x; });
var connect_line = svg.selectAll(".connect-line")
.data(data, function(d){ return d.x });
// EXIT
dot_a.exit()
.transition(t)
.style("r", 1e-6)
.remove();
dot_b.exit()
.transition(t)
.style("r", 1e-6)
.remove();
connect_line.exit()
.transition(t)
.style("opacity", 1e-6)
.remove();
// UPDATE
dot_a
.transition(t)
.attr("cx", function(d){ return x_scale(d.x); })
.attr("cy", function(d){ return y_scale(d.y_a); })
.attr("r", function(d){ return size_scale(d.size_a); });
dot_b
.transition(t)
.attr("cx", function(d){ return x_scale(d.x); })
.attr("cy", function(d){ return y_scale(d.y_b); })
.attr("r", function(d){ return size_scale(d.size_b); });
connect_line
.transition(t)
.attr("y1", function(d){ return y_scale(d.y_a) + (size_scale(d.size_a) * multiplier(d.y_a, d.y_b)); })
.attr("y2", function(d){ return y_scale(d.y_b) - (size_scale(d.size_b) * multiplier(d.y_a, d.y_b)); })
// ENTER
dot_a.enter().append("circle")
.attr("class", "dot a")
.attr("cx", function(d){ return x_scale(d.x); })
.attr("cy", function(d){ return y_scale(d.y_a); })
.attr("r", 1e-6)
.transition(t)
.attr("r", function(d){ return size_scale(d.size_a); });
dot_b.enter().append("circle")
.attr("class", "dot b")
.attr("cx", function(d){ return x_scale(d.x); })
.attr("cy", function(d){ return y_scale(d.y_b); })
.attr("r", 1e-6)
.transition(t)
.attr("r", function(d){ return size_scale(d.size_b); });
connect_line.enter().append("line")
.attr("class", "connect-line")
.attr("x1", function(d){ return x_scale(d.x); })
.attr("y1", function(d){ return y_scale(d.y_a) + (size_scale(d.size_a) * multiplier(d.y_a, d.y_b)); })
.attr("x2", function(d){ return x_scale(d.x); })
.attr("y2", function(d){ return y_scale(d.y_b) - (size_scale(d.size_b) * multiplier(d.y_a, d.y_b)); })
}
// a or b on top
function multiplier(a, b){
return a > b ? 1 : -1;
}
// make some data
function generate_binned_data(){
var bin_size = 5,
arr = [];
for (var i = bin_size; i <= 95; i += bin_size){
arr.push({
x: i,
size_a: random(1, 30),
size_b: random(1, 30),
y_a: random(1, 1000),
y_b: random(1, 1000)
});
}
return arr;
}
// random number function
function random(min, max){
return Math.floor(Math.random() * (max - min + 1) + min);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment