block by harrystevens a32d0737827d874e98a93c19d8cefe9f

Barbell Chart

Full Screen

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.

index.html

<!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>