Skip to content

Instantly share code, notes, and snippets.

@larsvers
Last active January 18, 2018 20:25
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 larsvers/6f4305086c832298167a2334e3c68990 to your computer and use it in GitHub Desktop.
Save larsvers/6f4305086c832298167a2334e3c68990 to your computer and use it in GitHub Desktop.
Step 4: geometric zoom with Canvas
license: mit
<!DOCTYPE html>
<html lang="en">
<head>
<title>Planets</title>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.js"></script>
<style type="text/css">
/* HTML elements */
/* ============= */
/* Base elements */
body {
font-family: Avenir, sans-serif;
font-size: 0.75rem;
margin: 0;
}
#headline {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
color: #555;
}
#pink {
border-bottom: 2px solid deeppink;
padding-bottom: 0.25rem;
}
/* Links */
a {
color: #555;
transition: color 200ms;
}
a:link {
text-decoration: none;
}
a:visited {
color: #555;
}
a:hover {
color: deeppink;
}
a:active {
color: #555;
}
/* SVG elements */
/* ============ */
/* Axes */
.tick text {
font-family: Avenir, sans-serif;
fill: #555;
font-size: 0.75rem;
}
.tick line, .lines {
stroke: #555;
stroke-width: 0.5;
shape-rendering: crispEdges;
}
path.domain {
display: none;
}
/* Canvas */
/* ====== */
canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
</style>
</head>
<body>
<h1 id="headline">Measuring our planets'
<span id="pink">
<a href="https://en.wikipedia.org/wiki/Solar_System#Distances_and_scales" target="_blank">distances</a>
</span>
</h1>
<div id="vis"></div>
<script type="text/javascript">
function make(data) {
/* Set up */
/* ====== */
/* Dimensions and base */
/* ------------------- */
var margin = {
top: window.innerHeight * 0.3,
left: 50,
bottom: window.innerHeight * 0.4,
right: 50
};
// The chart *and* screen height.
var height = window.innerHeight - margin.top - margin.bottom;
// Calculate width
var mapScale = 1/10e4;
var maxDist = d3.max(data, function(d) { return d.distance; });
// The full width of all planets
var chartWidth = maxDist * mapScale;
// SVG width will only be as large as screen
var screenWidth = window.innerWidth - margin.left - margin.right;
/* SVG */
var svg = d3.select('#vis')
.append('svg')
.attr('width', screenWidth + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('class', 'chart')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
var listenerRect = svg
.append('rect')
.attr('class', 'listener-rect')
.attr('x', 0)
.attr('y', -margin.top)
.attr('width', screenWidth)
.attr('height', height + margin.top + margin.bottom)
.style('opacity', 0);
/* Canvas and context */
var canvas = d3.select('#vis').append('canvas')
.attr('width', screenWidth + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
var context = canvas.node().getContext('2d');
/* Scales */
/* ====== */
var rExtent = d3.extent(data, function(d) { return d.radius; });
// Make the sun fit into the height
// with a little bit of breathing space.
var rScale = d3.scaleLinear()
.domain([0, rExtent[1]])
.range([3, height/2 * 0.9]);
var xScale = d3.scaleLinear()
.domain([0, maxDist])
.range([0, chartWidth]);
/* Axes components */
/* --------------- */
var xAxis = d3.axisBottom(xScale)
.tickSizeOuter(0)
.tickPadding(10)
.tickValues(data.map(function(el) { return el.distance; }))
.tickFormat(function(d, i) {
return data[i].planet + ' ' + d3.format(',')(d) + ' km';
});
/* Axes draw */
/* --------- */
// Drawing the axis
var xAxisDraw = svg.insert('g', ':first-child')
.attr('class', 'x axis')
.call(xAxis);
// Move the axis-labels and -lines down
var labelHeight = xAxisDraw.select('text').node().getBBox().height;
xAxisDraw.attr('transform', 'translate(0, ' + (height + labelHeight * data.length) + ')');
// Position the axis text
xAxisDraw.selectAll('text')
.attr('y', function(d, i) { return -(i * labelHeight + labelHeight); })
.attr('dx', '-0.15em')
.attr('dy', '1.15em')
.style('text-anchor', 'start');
// Draw the axis lines
xAxisDraw.selectAll('line')
.attr('y1', function(d, i) { return -(i * labelHeight + labelHeight); })
.attr('y2', function(d, i) {
return -(i * labelHeight + labelHeight + // the start position from axis-y 0
(data.length-1-i) * labelHeight + // label start to the chart bottom
height); // the height
});
/* Build vis */
/* ========= */
/* Sun and planets */
/* --------------- */
// We comment out the SVG planet base:
// var gPlanets = svg.append('g').attr('class', 'planet-group');
// var planets = gPlanets.selectAll('.planet')
// .data(data).enter()
// .append('circle')
// .attr('class', 'planet')
// .attr('id', function(d) { return d.planet; })
// .attr('cx', function(d) { return xScale(d.distance); })
// .attr('cy', 0)
// .attr('r', function(d) {
// d.scaledRadius = rScale(d.radius);
// return rScale(d.radius);
// });
/* Sun and planets function */
/* ------------------------ */
function drawGeometricCircles(data, transform) {
context.clearRect(0, 0, screenWidth + margin.left + margin.right, height + margin.top + margin.bottom);
context.save();
context.lineWidth = 4;
context.strokeStyle = 'deeppink';
context.fillStyle = 'white';
context.translate(transform.x + margin.left, margin.top);
context.scale(transform.k, transform.k);
for (var i = 0; i < data.length; i++) {
context.beginPath()
context.arc(xScale(data[i].distance), 0, rScale(data[i].radius), 0, 2 * Math.PI, false);
context.stroke();
context.fill();
}
context.restore();
} // drawGeometricCircles()
/* Sun and planets build */
/* --------------------- */
drawGeometricCircles(data, d3.zoomIdentity);
/* Zoom */
/* ==== */
// Cross-multiplication to get the minimum zoom so that the furthest away planet
// (Pluto) just fits on the screen. 0.75 gives some leeway for the label.
var minZoom = 1 / (chartWidth / window.innerWidth) * 0.75;
// Define the zoom behaviour
var zoom = d3.zoom()
.scaleExtent([minZoom, 20])
.on('zoom', zoomed);
// Listen for zoom events
listenerRect.call(zoom);
// Zoom function
function zoomed() {
// Get the transform
var transform = d3.event.transform;
// console.log(transform);
// Lock at the Sun
transform.x = Math.min(0, transform.x);
// Re-scale the x scale
var xScaleNew = transform.rescaleX(xScale);
// // Move the planets (semantic)
// planets
// .attr('cx', function(d) { return xScaleNew(d.distance); })
// .attr('r', function(d) { return d.scaledRadius * transform.k; });
// Redraw the planets on Canvas (geometric)
drawGeometricCircles(data, transform);
// Semantically zoom and pan the axis
xAxis.scale(xScaleNew);
xAxisDraw.call(xAxis);
// Stagger the axis-labels
xAxisDraw.selectAll('text')
.attr('y', function(d, i) { return -(i * labelHeight + labelHeight); })
// Stagger the axis-lines
xAxisDraw.selectAll('line')
.attr('y1', function(d, i) { return -(i * labelHeight + labelHeight); })
.attr('y2', function(d, i) {
return -(i * labelHeight + labelHeight + (data.length-1-i) * labelHeight + height);
});
} // zoomed()
} // make()
/* Data load and visual */
/* ==================== */
function row(d) {
return {
planet: d.planet,
distance: +d.distance,
radius: +d.radius,
text: d.text
};
}
d3.csv('planets.csv', row, function(error, data) {
if (error) throw error;
console.log(data);
make(data);
});
</script>
</body>
</html>
planet distance radius
Sun 0 695000
Mercury 58000000 2440
Venus 108000000 6052
Earth 150000000 6378
Mars 228000000 3397
Jupiter 778000000 71492
Saturn 1429000000 60268
Uranus 2871000000 25559
Neptune 4504000000 24766
Pluto 5913000000 1150
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment