Skip to content

Instantly share code, notes, and snippets.

@larsvers
Last active January 18, 2018 20:25
Show Gist options
  • Save larsvers/4b39be68e8cb77e7c402bd96df292db0 to your computer and use it in GitHub Desktop.
Save larsvers/4b39be68e8cb77e7c402bd96df292db0 to your computer and use it in GitHub Desktop.
Step 6: programmatic zoom (bonus)
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 */
/* ============ */
svg {
border: 1px solid #ccc;
}
/* 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;
}
/* Chart */
circle {
fill: white;
stroke: deeppink;
stroke-width: 4px;
}
.label {
fill: #777;
}
</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);
/* Scales */
/* ====== */
var rExtent = d3.extent(data, function(d) { return d.radius; });
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 */
/* --------------- */
var gPlanets = svg.insert('g', '.listener-rect').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 d.scaledRadius;
});
/* 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;
// 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; });
// 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()
/* Initial programmatic zoom */
/* ------------------------- */
// set an initial transform object on the zoom element
var initialTransform = d3.zoomIdentity.scale(20);
listenerRect.call(zoom.transform, initialTransform);
// Trigger programmatic zoom
progZoom()
// Programmatic zoom function
function progZoom() {
var zoomOutTransform = d3.zoomIdentity.translate(0, 0).scale(minZoom);
listenerRect
.transition()
.duration(5000)
.call(zoom.transform, zoomOutTransform)
.on('end', zoomToNormal)
function zoomToNormal() {
listenerRect
.transition()
.duration(3000)
.ease(d3.easeQuadInOut)
.call(zoom.transform, d3.zoomIdentity)
}
}
} // 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