Skip to content

Instantly share code, notes, and snippets.

@monfera
Last active August 8, 2016 21:05
Show Gist options
  • Save monfera/10527804 to your computer and use it in GitHub Desktop.
Save monfera/10527804 to your computer and use it in GitHub Desktop.
circinus
license: gpl-3.0

Playing with reusing d3.geo for plotting. Zoom or pan on any of the plots. Subsequent transitioned example: http://bl.ocks.org/monfera/11085004

Mike Bostock and Jason Davies have added cool geo features, some of which can be of interest for regular data plotting too (most of which not covered in this example):

  1. The use of projections to easily change between Cartesian, polar, spiral, etc. plots
  • d3.geo supports spherical projections; this example projects a sphere-bound spiral
  1. Traditional, but data-intensive plots can benefit from adaptive resampling and line simplification

  2. Time series plots benefit from zooming and panning (usually sufficient to do so along one dimension)

  3. Vector tiles can be useful for scalable "slippy plots"

  4. Mobile platforms benefit from pure SVG transforms (hardware accelerated, low energy use)

  • currently, projections recompute, but one can create chart plot projections using just linear transformations
  1. It's fun to overlay time series on a sphere; also, there is a connection between the 'day' period and the sphere
  • this example has a graticule that divides time/space to 24 hours, and rotates various projections
  1. One projection (e.g. spiral) with different parametrization (e.g. rotation, scale) can support multiple chart idioms:
  • spiral (naturally)
  • circle/polar/radial (special case)
  • cartesian plot (special, limit case, can be approximated)
  1. It is possible to smoothly transition among these cases, preserving object constancy

  2. One can tween among geography views (choropleth, voronoi...) and abstract plot views (time, categorical variables etc.)

  3. Rotate or zoom / pan tweening to the bounding box of the area of interest in the visualization

Finally, it's possibly a new application and definitely fun!

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.graticule {
fill: none;
stroke: #777;
stroke-width: 1px;
}
.graticule:first-child {
fill: none;
stroke: #777;
stroke-width: 4px;
}
.spiral {
fill: lightblue;
stroke: #000;
stroke-width: 0.5px;
}
</style>
<body></body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://s3-us-west-2.amazonaws.com/s.cdpn.io/12431/d3.geo.zoom.js"></script>
<script>
var width = 960,
height = 500;
// Spiral function derived from https://www.jasondavies.com/maps/spiral/
var n = 1e4, dy = 3, rot=50;
var spiral = d3.range(0, 1 + 1 / n, 1 / n).map(function(t) {
return [(360 * rot * t) % 360 - 180, -90 + dy
- Math.random()
+ (Math.cos(100 * Math.PI * t) - 1) / 2
+ (Math.cos(3 * Math.PI * t) - 1) / 2
+ (90 - dy) * 2 * t];
}).concat(d3.range(1, 0, -1 / n).map(function(t) {
return [(360 * rot * t) % 360 - 180, -90 + (90 - dy ) * 2 * t];
}));
spiral.push(spiral[0]);
var spiralPoly = {type: "Polygon", coordinates: [spiral]};
// Projections
var selectedProjection = 0; // select any of 0..4
var graticule = d3.geo.graticule()
.majorStep([90,90]) // Major step indicates 6-hour intervals
.minorStep([15,0]); // Minor step indicates 1-hour intervals
function render(selectedProjection) {
var projections = [
d3.geo.equirectangular().rotate([0,0]).scale(150),
d3.geo.equirectangular().rotate([0,-90]).clipAngle(41).scale(520),
d3.geo.equirectangular().rotate([-180,0, 90]).scale(150),
d3.geo.conicEquidistant().scale(120),
d3.geo.orthographic().rotate([120,-30,0]).clipAngle(90).scale(220)
];
var projection = projections[selectedProjection % projections.length]
.translate([width / 2 - .5, height / 2 - .5]);
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
// The excellent zoom/pan from Jason Davies (https://www.jasondavies.com/maps/rotate/)
.call(d3.geo.zoom().projection(projection)
.scaleExtent([projection.scale() * .7, projection.scale() * 10])
.on("zoom.redraw", function () {
d3.event.sourceEvent.preventDefault();
svg.selectAll("path").attr("d", path);
}));
svg.selectAll(".graticule")
.data(graticule.lines)
.enter().append("path")
.attr("class", "graticule")
.attr("d", path);
svg.selectAll(".spiral")
.data([spiralPoly])
.enter().append("path")
.attr("class", "spiral")
.attr("d", path);
}
render(selectedProjection++)
setInterval(function () {
d3.selectAll('svg').remove();
render(selectedProjection++);
}, 4000)
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment