Skip to content

Instantly share code, notes, and snippets.

@zanarmstrong
Last active May 29, 2019 21:02

Revisions

  1. zanarmstrong revised this gist Sep 8, 2015. 1 changed file with 0 additions and 0 deletions.
    Binary file added thumbnail.png

    Unable to render rich display

    Invalid image source.

  2. zanarmstrong revised this gist Jul 28, 2015. 4 changed files with 270 additions and 9846 deletions.
    9,504 changes: 0 additions & 9,504 deletions d3.js
    0 additions, 9,504 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    5 changes: 0 additions & 5 deletions d3.min.js
    0 additions, 5 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@ <h2>Is Africa bigger than North America?</h2>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="Europe,Brazil">Europe vs Brazil</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button><button name="Europe,Antarctica">Europe vs Antarctica</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    </div>
    <div id="tool"></div>
    <div id="tool"><table><tr><td id="small0"></td><td id="large" rowspan="2"></td></tr><tr><td id="small1"></td></tr></table></div>
    <br>
    <div id="longDesc">
    <h2>What's going on?</h2>
    @@ -21,6 +21,6 @@ <h2>What's going on?</h2>
    </div>
    <div id="testMobile">
    </div>
    <script src="d3.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
    <script src="http://d3js.org/topojson.v1.min.js"></script>
    <script src="overlay.js"></script>
    603 changes: 268 additions & 335 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -1,17 +1,28 @@
    // Zan Armstrong - May, 2015
    // parts of the code adapted from Mike Bostock's Lambert Azimuthal Equal Area http://bl.ocks.org/mbostock/3757101
    "use strict";

    // set up variables
    var width = 300,
    height = 300,
    var smallWidth = 300,
    smallHeight = 300,
    largeWidth = 600,
    largeHeight = 600;

    var margin = {
    top: 0
    }

    comparisons = {
    var padding = 5;
    var transitionDuration = 800;

    // to hold land and border data
    var land;
    var borders;

    // land colors
    var colors = ["black", "lightgreen"]

    var comparisons = {
    "Sweden,Madagascar": {
    scale: 1120,
    latLon: [{
    @@ -114,7 +125,7 @@ comparisons = {
    }
    }

    // use/manage hash fragment and state
    // define state, with default values
    var state = {
    scale: 450,
    latLon: [{
    @@ -126,6 +137,7 @@ var state = {
    }]
    }

    // use hash fragment from URl to set state
    if (window.location.hash.split("&").length != 0) {
    var windowState = window.location.hash.split("&");
    for (var i = 0; i < windowState.length; i++) {
    @@ -146,32 +158,16 @@ if (window.location.hash.split("&").length != 0) {
    }
    }

    var mobile = false;
    if (document.getElementById("testMobile").offsetWidth <= 0) {
    mobile = true;
    }

    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;

    var mainSVG = d3.select("#tool").append("svg")
    .attr("width", 905)
    .attr("height", 600)
    .attr("class", "mainSVG");

    var mapObjects = []

    // set up ranges/scales
    // define slider
    var zoomRange = [220, 1600]
    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);

    // slider setup for scale
    var slider = d3.select("#tool")
    .append("p")
    .append("input")
    .attr("type", "range")
    .style("margin-left", "750px")
    .style("margin-left", "650px")
    .style("width", 240 + "px")
    .attr("min", zoomRange[0])
    .attr("max", zoomRange[1])
    .attr("step", (zoomRange[1] - zoomRange[0]) / 400)
    @@ -181,86 +177,79 @@ slider.property("value", state.scale)

    function slided(d) {
    var duration = 0;
    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration)
    //updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration)

    state.scale = d3.select(this).property("value");
    updateLargeScale(largeMapObjects, state.scale, duration);
    updateLargeScaleOnly();

    updateHash()
    }

    // create small maps
    // define canvas object and context for small canvases
    var canvasObj = [
    d3.select("#small0").append("canvas")
    .attr("width", smallWidth)
    .attr("height", smallHeight),

    d3.select("#small1").append("canvas")
    .attr("width", smallWidth)
    .attr("height", smallHeight)
    ]

    var smallContext = [
    canvasObj[0].node().getContext("2d"),
    canvasObj[1].node().getContext("2d")
    ]
    // set opacity
    smallContext[0].globalAlpha = 0.8
    smallContext[1].globalAlpha = 0.8

    // define canvas object and context for large canvas
    var largeCanvasObj = d3.select("#large").append("canvas")
    .attr("width", largeWidth)
    .attr("height", largeHeight)

    var largeCanvasContext = largeCanvasObj.node().getContext("2d")
    largeCanvasContext.globalAlpha = .9

    var graticule = d3.geo.graticule()()

    var mapObjects = []
    var largeMapObjects = []

    // set up ranges/scales
    var zoomRange = [220, 1600]
    // var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);

    // set up initial visable state
    for (var i = 0; i < 2; i++) {
    mapObjects[i] = setUpSmallMaps(state.latLon[i].lat, state.latLon[i].lon, i)
    largeMapObjects[i] = setUpLargeMaps(state.latLon[i].lat, state.latLon[i].lon, i)
    }

    // set up small maps
    function setUpSmallMaps(lat, lon, name) {
    var center = [-lon, -lat]
    var projectionSmall = d3.geo.orthographic()
    .translate([width / 2, height / 2])
    .scale(width / 2 * .9)
    .translate([smallWidth / 2, smallHeight / 2])
    .scale(smallWidth / 2 * .9)
    .center([0, 0])
    .rotate(center)
    .rotate([-lon, -lat])
    .clipAngle(90)
    .precision(.7);

    var path = d3.geo.path()
    .projection(projectionSmall);
    .projection(projectionSmall)
    .context(smallContext[name]);

    var graticule = d3.geo.graticule();

    var svg = mainSVG.append("g")
    .attr('transform', 'translate(' + padding + ',' + (height * name + margin.top) + ')')
    .classed("g_" + name, true);

    svg.append("defs").append("path")
    .datum({
    type: "Sphere"
    })
    .attr("id", "sphere")
    .attr("d", path);

    svg.append("use")
    .attr("class", "stroke")
    .attr("xlink:href", "#sphere");

    svg.append("use")
    .attr("class", "fill")
    .attr("xlink:href", "#sphere");

    svg.append("path")
    .datum(graticule)
    .attr("class", "graticule graticule_" + name)
    .attr("d", path);



    svg.call(dragSetupSmall(name));
    // end drag behavior setup
    canvasObj[name].call(dragSetupSmall(name))

    return {
    "svg": svg,
    "projection": projectionSmall,
    "path": path,
    "graticule": graticule,
    "lat": lat,
    "lon": lon
    }

    }

    // set up large maps
    var largeSvg = mainSVG.append("g").attr("class", "largeSvg")
    .attr('transform', 'translate(' + (2 * padding + width) + ',' + (margin.top) + ')')

    var largeMapObjects = []
    for (var i = 0; i < 2; i++) {
    largeMapObjects[i] = setUpLargeMaps(state.latLon[i].lat, state.latLon[i].lon, i, largeSvg)
    }

    function setUpLargeMaps(lat, lon, name, svg) {
    var center = [-lon, -lat]
    function setUpLargeMaps(lat, lon, name) {

    var projectionLarge = d3.geo.azimuthalEqualArea()
    .translate([largeWidth / 2, largeHeight / 2])
    @@ -271,83 +260,193 @@ function setUpLargeMaps(lat, lon, name, svg) {
    [2 * padding, 2 * padding],
    [largeWidth - 2 * padding, largeHeight - 2 * padding]
    ])
    .rotate(center)
    .rotate([-lon, -lat])
    .precision(.7);

    var path = d3.geo.path()
    .projection(projectionLarge);
    .projection(projectionLarge)
    .context(largeCanvasContext);

    largeCanvasObj.call(dragSetupLarge())

    return {
    "svg": svg,
    "projection": projectionLarge,
    "path": path,
    "lat": lat,
    "lon": lon
    }

    }

    // what to draw on the canvas for large/small
    var drawCanvasLarge = function() {
    largeCanvasContext.clearRect(0, 0, largeWidth, largeHeight);
    largeCanvasContext.strokeStyle = "#333", largeCanvasContext.lineWidth = 1, largeCanvasContext.strokeRect(2 * padding, 2 * padding, largeWidth - 4 * padding, largeHeight - 4 * padding);
    largeCanvasContext.fillStyle = "white", largeCanvasContext.fillRect(2 * padding, 2 * padding, largeWidth - 4 * padding, largeHeight - 4 * padding);

    largeCanvasContext.fillStyle = colors[0], largeCanvasContext.beginPath(), largeMapObjects[0].path(land), largeCanvasContext.fill();
    largeCanvasContext.strokeStyle = "#fff", largeCanvasContext.lineWidth = .5, largeCanvasContext.beginPath(), largeMapObjects[0].path(borders), largeCanvasContext.stroke();
    largeCanvasContext.fillStyle = colors[1], largeCanvasContext.beginPath(), largeMapObjects[1].path(land), largeCanvasContext.fill();
    largeCanvasContext.strokeStyle = "#fff", largeCanvasContext.lineWidth = .5, largeCanvasContext.beginPath(), largeMapObjects[1].path(borders), largeCanvasContext.stroke();
    }

    var drawCanvasSmall = function(i) {
    smallContext[i].clearRect(0, 0, smallWidth, smallHeight);
    smallContext[i].fillStyle = "white", smallContext[i].strokeStyle = "#333", smallContext[i].beginPath(), smallContext[i].arc(smallWidth / 2, smallHeight / 2, smallWidth / 2 - 3 * padding, 0, 2 * Math.PI, false), smallContext[i].stroke(), smallContext[i].fill();
    smallContext[i].strokeStyle = "#ccc", smallContext[i].lineWidth = .5, smallContext[i].beginPath(), mapObjects[i].path(graticule), smallContext[i].stroke();
    smallContext[i].fillStyle = colors[i], smallContext[i].beginPath(), mapObjects[i].path(land), smallContext[i].fill();
    }


    d3.json("world-110m.json", function(error, world) {
    largeMapObjects.data = world;
    // use world data to set up small maps
    for (var i = 0; i < mapObjects.length; i++) {
    svg = mapObjects[i].svg;
    svg
    .insert("path", ".graticule" + i)
    .datum(topojson.feature(world, world.objects.land))
    .attr("class", "land land_" + i)
    .attr("d", mapObjects[i].path);

    addCenterBox(svg, [mapObjects[i].lon, mapObjects[i].lat], mapObjects[i].projection)
    }
    land = topojson.feature(world, world.objects.land);
    borders = topojson.mesh(world, world.objects.countries)

    // use world data to set up large maps
    for (var i = 0; i < 2; i++) {
    svg = largeMapObjects[i].svg.append('g').attr("class", "large_" + i);
    svg.insert("path", ".large_graticule_" + i)
    .datum(topojson.feature(world, world.objects.land))
    .attr("class", "large_land_" + i)
    .attr("d", largeMapObjects[i].path);

    svg.insert("path", ".large_graticule" + i)
    .datum(topojson.mesh(world, world.objects.countries, function(a, b) {
    return a !== b;
    }))
    .attr("class", "boundary boundary_" + i)
    .attr("d", largeMapObjects[i].path);
    }
    // use data to draw paths on small maps
    drawCanvasLarge()
    drawCanvasSmall(0)
    addBoxSmall(0, zoomToBoxScale(largeMapObjects[0].projection.scale()))
    drawCanvasSmall(1)
    addBoxSmall(1, zoomToBoxScale(largeMapObjects[1].projection.scale()))

    // append rectangle for large box
    largeSvg.append('rect')
    .attr("height", largeHeight - 4 * padding)
    .attr("x", 2 * padding)
    .attr("y", 2 * padding)
    .attr("width", largeWidth - 4 * padding)
    .attr("class", "largeBackgroundRect")
    if (!mobile) {
    d3.select(".largeSvg").call(dragSetupLarge())
    })

    // updates on button click
    d3.selectAll("button").on("click", function() {
    // update state, view, hash
    state.latLon[0] = comparisons[this.name].latLon[0];
    state.latLon[1] = comparisons[this.name].latLon[1];
    state.scale = comparisons[this.name].scale;

    rotateAndScale()
    updateHash()
    })

    // updating just the small context w/ rotation
    var rotateSmallTween = function(iter) {
    return function(d) {
    var r = d3.interpolate(mapObjects[iter].projection.rotate(), [-state.latLon[iter].lon, -state.latLon[iter].lat]);
    var halfSideTween = d3.interpolate(zoomToBoxScale(largeMapObjects[iter].projection.scale()), zoomToBoxScale(state.scale))
    return function(t) {
    mapObjects[iter].projection.rotate(r(t));
    drawCanvasSmall(iter)
    addBoxSmall(iter, halfSideTween(t))
    };
    }
    // do an immediate update (in case safari) - not best practice, need to fix
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
    });

    // adjustable box sizes on small worlds
    function addCenterBox(svg) {
    var distance = zoomToBoxScale(state.scale);

    svg.append("rect").attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    .attr("stroke", "black")
    .attr("fill", "none")
    .attr("class", "littleBox")
    }

    var addBoxSmall = function(iter, halfSide) {
    smallContext[iter].strokeStyle = "#333";
    smallContext[iter].strokeRect(
    smallWidth / 2 - halfSide,
    smallHeight / 2 - halfSide,
    2 * halfSide,
    2 * halfSide);
    }

    var updateSmallCanvasOverDuration = function(iter) {
    d3.select("#small" + iter)
    .select("canvas")
    .transition()
    .duration(transitionDuration)
    .tween("rotate", rotateSmallTween(iter))
    }

    // update large & small smallContext, based on update function (either rotationAndScaleTween or rotationTween)
    function rotateAndScale() {
    (function transition() {
    updateSmallCanvasOverDuration(0)
    updateSmallCanvasOverDuration(1)

    d3.select("#large").select("canvas")
    .transition()
    .duration(transitionDuration)
    .tween("d", rotationAndScaleTween)
    })()

    }

    // update only scale, and therefore only large canvas
    function updateLargeScaleOnly() {

    (function transition() {
    d3.select("#large").select("canvas")
    .transition()
    .duration(0)
    .tween("d", scaleTween)
    })()
    }

    // update large & small canvases, based on update function (rotationAndScaleTween or rotationTween)
    function updateRotationFromSmallPan(name) {
    drawCanvasSmall(name)
    addBoxSmall(name, zoomToBoxScale(largeMapObjects[name].projection.scale()))
    drawCanvasLarge()
    }

    function updateRotationFromLargePan() {
    drawCanvasSmall(0)
    addBoxSmall(0, zoomToBoxScale(largeMapObjects[0].projection.scale()))
    drawCanvasSmall(1)
    addBoxSmall(1, zoomToBoxScale(largeMapObjects[1].projection.scale()))
    drawCanvasLarge()
    }

    function rotationTween() {
    var r1 = d3.interpolate(largeMapObjects[0].projection.rotate(), [-state.latLon[0].lon, -state.latLon[0].lat]);
    var r2 = d3.interpolate(largeMapObjects[1].projection.rotate(), [-state.latLon[1].lon, -state.latLon[1].lat]);
    return function(t) {
    // update rotation
    largeMapObjects[0].projection.rotate(r1(t));
    largeMapObjects[1].projection.rotate(r2(t));

    drawCanvasLarge()
    };
    }

    function scaleTween() {
    var interpolateScale = d3.interpolate(largeMapObjects[0].projection.scale(), state.scale);
    var halfSideTween = d3.interpolate(zoomToBoxScale(largeMapObjects[0].projection.scale()), zoomToBoxScale(state.scale))

    return function(t) {
    // update scale for large projections
    largeMapObjects[0].projection.scale(interpolateScale(t));
    largeMapObjects[1].projection.scale(interpolateScale(t));

    // and redraw large
    drawCanvasLarge()

    // redraw small canvases, with box
    drawCanvasSmall(0)
    addBoxSmall(0, halfSideTween(t))
    drawCanvasSmall(1)
    addBoxSmall(1, halfSideTween(t))

    };
    }

    function rotationAndScaleTween() {
    var r1 = d3.interpolate(largeMapObjects[0].projection.rotate(), [-state.latLon[0].lon, -state.latLon[0].lat]);
    var r2 = d3.interpolate(largeMapObjects[1].projection.rotate(), [-state.latLon[1].lon, -state.latLon[1].lat]);
    var interpolateScale = d3.interpolate(largeMapObjects[0].projection.scale(), state.scale);
    return function(t) {
    // update rotation
    largeMapObjects[0].projection.rotate(r1(t));
    largeMapObjects[1].projection.rotate(r2(t));

    // update scale
    largeMapObjects[0].projection.scale(interpolateScale(t));
    largeMapObjects[1].projection.scale(interpolateScale(t));

    drawCanvasLarge()
    };
    }

    // update functions
    var updateHash = function() {
    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    slider.property("value", state.scale)
    }

    // might be nice to combine these two drag functions in the future, but ok for now
    // DRAG
    function dragSetupSmall(name) {
    function resetDrag() {
    dragDistance = {
    @@ -368,17 +467,15 @@ function dragSetupSmall(name) {
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnSmallMapPan(dragDistance, name, 'drag')
    updateRotateFromSmallDrag(dragDistance, name)
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnSmallMapPan(dragDistance, name, 'dragend')
    updateRotateFromSmallDrag(dragDistance, name)
    resetDrag()
    })
    }

    var count = 0;

    function dragSetupLarge() {

    function resetDrag() {
    @@ -394,225 +491,61 @@ function dragSetupLarge() {
    };

    return d3.behavior.drag()
    .on("dragstart", function() {
    d3.event.sourceEvent.preventDefault();
    })
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    updateRotateFromLargeDrag(dragDistance)
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    updateRotateFromLargeDrag(dragDistance)
    resetDrag()
    });

    }

    // update functions
    var updateHash = function() {
    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    slider.property("value", state.scale)
    function updateRotateFromSmallDrag(pixelDifference, name) {
    var newRotate = pixelDiff_to_rotation_small(mapObjects[name].projection, pixelDifference)

    // set new rotate
    mapObjects[name].projection.rotate(newRotate)
    largeMapObjects[name].projection.rotate(newRotate)
    updateStateRotation(newRotate, name)

    updateRotationFromSmallPan(name)
    }

    function pixelDiff_to_rotation_large(projection, pxDiff) {
    var k = projection.invert([largeWidth / 2 - pxDiff.x, largeHeight / 2 - pxDiff.y])
    return [-k[0], -k[1], 0]
    function updateRotateFromLargeDrag(pixelDifference) {
    var newRotate0 = pixelDiff_to_rotation_large(largeMapObjects[0].projection, pixelDifference)
    var newRotate1 = pixelDiff_to_rotation_large(largeMapObjects[1].projection, pixelDifference)

    // set new rotate
    mapObjects[0].projection.rotate(newRotate0)
    mapObjects[1].projection.rotate(newRotate1)
    largeMapObjects[0].projection.rotate(newRotate0)
    largeMapObjects[1].projection.rotate(newRotate1)
    updateStateRotation(newRotate0, 0)
    updateStateRotation(newRotate1, 1)

    updateRotationFromLargePan(name)
    }

    function pixelDiff_to_rotation_small(projection, pxDiff) {
    var k = projection.rotate()
    return ([k[0] + pxDiff.x / 136 * 90, k[1] - pxDiff.y, k[2]])
    }

    function pixelDiff_to_rotation_large(projection, pxDiff) {
    var k = projection.invert([largeWidth / 2 - pxDiff.x, largeHeight / 2 - pxDiff.y])
    return [-k[0], -k[1], 0]
    }

    function updateStateRotation(rotateCoord, name) {
    state.latLon[name] = {
    lon: rotateCoord[0],
    lat: rotateCoord[1]
    }
    }

    // currently not used
    function updateLargeRotationDuringDrag(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    d3.selectAll(".boundary_" + name).transition().duration([transitionDuration]).remove();
    }

    // currently not used
    function updateLargeRotationEndDrag(map, name, newRotate, transitionDuration) {
    map[name].projection.rotate(newRotate)
    d3.select(".large_" + name).insert("path", ".large_graticule" + name)
    .datum(topojson.mesh(map.data, map.data.objects.countries, function(a, b) {
    return a !== b;
    }))
    .attr("class", "boundary boundary_" + name)
    .transition().duration([500])
    .attr("d", map[name].path);
    }

    function updateLargeRotation(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    }


    function updateLargeRotationAndScale(map, name, newRotate, newScale, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
    }


    function updateSmallRotation(mapObject, name, newRotate, transitionDuration) {
    if (transitionDuration == 0) {
    mapObject[name].projection.rotate(newRotate)
    d3.selectAll(".graticule_" + name).attr("d", mapObject[name].path);
    d3.selectAll(".land_" + name).attr("d", mapObject[name].path);
    } else {
    d3.selectAll(".graticule_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));

    d3.selectAll(".land_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));
    }
    }


    function updateLargeScale(maps, newScale, duration) {
    if (duration > 0) {
    for (var i = 0; i < 2; i++) {
    d3.selectAll(".large_land_" + i)
    .transition().duration([duration])
    .attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
    d3.selectAll(".boundary_" + i)
    .transition().duration([duration])
    .attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
    }
    } else {
    for (var i = 0; i < 2; i++) {
    maps[i].projection.scale(newScale)
    d3.selectAll(".large_land_" + i).attr("d", maps[i].path);
    d3.selectAll(".boundary_" + i).attr("d", maps[i].path);
    }
    }
    }

    function updateCenterBoxSize(oldDistance, newDistance, duration) {
    if (duration == 0) {
    d3.selectAll(".littleBox")
    .attr("x", width / 2 - newDistance)
    .attr("y", height / 2 - newDistance)
    .attr("width", newDistance * 2)
    .attr("height", newDistance * 2)
    } else {
    d3.selectAll(".littleBox")
    .transition().duration([duration])
    .attrTween("x", distanceTween(width / 2 - oldDistance, width / 2 - newDistance))
    .attrTween("y", distanceTween(height / 2 - oldDistance, height / 2 - newDistance))
    .attrTween("width", distanceTween(2 * oldDistance, 2 * newDistance))
    .attrTween("height", distanceTween(2 * oldDistance, 2 * newDistance))
    }
    }

    // updates on drag
    function updateRotateBasedOnSmallMapPan(pixelDifference, name, stage) {
    var projection = mapObjects[name].projection;
    projection.rotate(pixelDiff_to_rotation_small(projection, pixelDifference))

    updateView(projection.rotate(), "na", name, 0, 'na')
    }

    function updateRotateBasedOnLargeMapPan(pixelDifference, name, stage) {
    var projection = largeMapObjects[name].projection;
    var newRotate = pixelDiff_to_rotation_large(projection, pixelDifference)
    updateView(newRotate, "na", name, 0, stage)
    }

    function updateView(newRotate, newScale, name, duration, stage) {
    updateStateRotation(newRotate, name)

    updateSmallRotation(mapObjects, name, newRotate, duration)

    if (newScale == 'na') {
    if (stage == 'drag') {
    updateLargeRotationDuringDrag(largeMapObjects, name, newRotate, duration)
    } else if (stage == 'dragend') {
    updateLargeRotationEndDrag(largeMapObjects, name, newRotate, duration)
    } else {
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    }
    } else {
    updateLargeRotationAndScale(largeMapObjects, name, newRotate, newScale, duration)
    if (name == 0) {
    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(newScale), duration)
    state.scale = newScale
    }

    }

    updateHash()
    }

    // updates on button click
    d3.selectAll("button").on("click", function() {
    state.latLon[0] = comparisons[this.name].latLon[0];
    state.latLon[1] = comparisons[this.name].latLon[1];

    updateView([-state.latLon[0].lon, -state.latLon[0].lat, 0], comparisons[this.name].scale, 0, transitionDuration)
    updateView([-state.latLon[1].lon, -state.latLon[1].lat, 0], comparisons[this.name].scale, 1, transitionDuration)
    })

    // transition
    function rotationTween(path, projection, new3Rotation) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(projection.rotate(), new3Rotation);
    return function(t) {
    projection.rotate(interpolate(t));
    return path(d);
    }
    }
    }

    function scaleTween(path, projection, newScale) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(projection.scale(), newScale);
    return function(t) {
    projection.scale(interpolate(t));
    return path(d);
    }
    }
    }

    function distanceTween(oldDist, newDist) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(oldDist, newDist);
    return function(t) {
    return interpolate(t);
    }
    }
    }

    function rotationAndScaleTween(path, projection, new3Rotation, newScale) {
    return function(d, i, a) {
    var interpolateScale = d3.interpolate(projection.scale(), newScale);
    var interpolateRotate = d3.interpolate(projection.rotate(), new3Rotation);
    return function(t) {
    projection.scale(interpolateScale(t));
    projection.rotate(interpolateRotate(t));
    return path(d);
    }
    }
    }
  3. zanarmstrong revised this gist Jul 9, 2015. 1 changed file with 7 additions and 2 deletions.
    9 changes: 7 additions & 2 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -117,7 +117,13 @@ comparisons = {
    // use/manage hash fragment and state
    var state = {
    scale: 450,
    latLon: comparisons["Africa,North_America"].latLon
    latLon: [{
    lat: 6.52865,
    lon: 20.3586336
    }, {
    lat: 48.2392291,
    lon: -98.9443219
    }]
    }

    if (window.location.hash.split("&").length != 0) {
    @@ -546,7 +552,6 @@ function updateView(newRotate, newScale, name, duration, stage) {
    } else if (stage == 'dragend') {
    updateLargeRotationEndDrag(largeMapObjects, name, newRotate, duration)
    } else {
    console.log("sending request to rotate", name, newRotate)
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    }
    } else {
  4. zanarmstrong revised this gist Jun 4, 2015. 2 changed files with 76 additions and 88 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="Europe,Brazil">Europe vs Brazil</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button><button name="Europe,Antarctica">Europe vs Antarctica</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    </div>
    <div id="tool"></div>
    162 changes: 75 additions & 87 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -11,125 +11,113 @@ var margin = {
    top: 0
    }

    // country specific lat/lon for buttons
    var countries = {
    Australia: {
    latLon: {
    comparisons = {
    "Sweden,Madagascar": {
    scale: 1120,
    latLon: [{
    lat: 62.6,
    lon: 16.3
    }, {
    lat: -19,
    lon: 46.7
    }]
    },
    "Australia,Antarctica": {
    scale: 470,
    latLon: [{
    lat: -27,
    lon: 130
    }
    },
    Antarctica: {
    latLon: {
    }, {
    lat: -90,
    lon: 0
    }
    },
    Sweden: {
    latLon: {
    lat: 62.6,
    lon: 16.3
    }
    }]
    },
    Madagascar: {
    latLon: {
    lat: -19,
    lon: 46.7
    }
    "Europe,Brazil": {
    scale: 527,
    latLon: [{
    lat: 56.44,
    lon: 17.1
    }, {
    lat: -6.07833,
    lon: -53.2052
    }]
    },
    US: {
    latLon: {
    "US,Australia": {
    scale: 640,
    latLon: [{
    lat: 35.65,
    lon: -97.24
    }
    }, {
    lat: -27,
    lon: 130
    }]
    },
    Greenland: {
    latLon: {
    "South_America,Greenland": {
    scale: 458,
    latLon: [{
    lat: -15.97,
    lon: -52.87
    }, {
    lat: 65.88,
    lon: -42.21
    }
    }]
    },
    South_America: {
    latLon: {
    lat: -18.97,
    lon: -61.479
    }
    },
    Brazil: {
    latLon: {
    "Brazil,US": {
    scale: 360,
    latLon: [{
    lat: -16.7833,
    lon: -53.2052
    }
    }, {
    lat: 35.65,
    lon: -97.24
    }]
    },
    Africa: {
    latLon: {
    "Africa,North_America": {
    scale: 350,
    latLon: [{
    lat: 6.52865,
    lon: 20.3586336
    }
    },
    North_America: {
    latLon: {
    }, {
    lat: 48.2392291,
    lon: -98.9443219
    }
    }]
    },
    North_Africa: {
    latLon: {
    "North_Africa,Russia": {
    scale: 470,
    latLon: [{
    lat: 15.0,
    lon: 18.82
    }
    },
    Russia: {
    latLon: {
    }, {
    lat: 60.65,
    lon: 95.995
    }
    }]
    },
    Alaska: {
    latLon: {
    "Saudi_Arabia,Alaska": {
    scale: 713,
    latLon: [{
    lat: 22.389,
    lon: 46.59
    }, {
    lat: 64.23,
    lon: -149.862
    }
    }]
    },
    Saudi_Arabia: {
    latLon: {
    lat: 22.389,
    lon: 46.59
    }
    "Europe,Antarctica": {
    scale: 454,
    latLon: [{
    lat: 56.44,
    lon: 17.1
    }, {
    lat: -90,
    lon: 0
    }]
    }
    }

    comparisons = {
    "Sweden,Madagascar": {
    scale: 1120
    },
    "Australia,Antarctica": {
    scale: 470
    },
    "US,Australia": {
    scale: 640
    },
    "South_America,Greenland": {
    scale: 458
    },
    "Brazil,US": {
    scale: 360
    },
    "Africa,North_America": {
    scale: 350
    },
    "North_Africa,Russia": {
    scale: 470
    },
    "Saudi_Arabia,Alaska": {
    scale: 713
    },
    }

    // use/manage hash fragment and state
    var state = {
    scale: 450,
    latLon: [countries.Africa.latLon, countries.North_America.latLon]
    latLon: comparisons["Africa,North_America"].latLon
    }

    if (window.location.hash.split("&").length != 0) {
    @@ -575,8 +563,8 @@ function updateView(newRotate, newScale, name, duration, stage) {

    // updates on button click
    d3.selectAll("button").on("click", function() {
    state.latLon[0] = countries[this.name.split(",")[0]].latLon;
    state.latLon[1] = countries[this.name.split(",")[1]].latLon;
    state.latLon[0] = comparisons[this.name].latLon[0];
    state.latLon[1] = comparisons[this.name].latLon[1];

    updateView([-state.latLon[0].lon, -state.latLon[0].lat, 0], comparisons[this.name].scale, 0, transitionDuration)
    updateView([-state.latLon[1].lon, -state.latLon[1].lat, 0], comparisons[this.name].scale, 1, transitionDuration)
  5. zanarmstrong revised this gist Jun 4, 2015. 1 changed file with 5 additions and 14 deletions.
    19 changes: 5 additions & 14 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -157,11 +157,6 @@ if (document.getElementById("testMobile").offsetWidth <= 0) {
    mobile = true;
    }

    var safari = false;
    if (navigator.userAgent.indexOf("Safari") > -1) {
    safari = true
    }

    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;
    @@ -336,14 +331,12 @@ d3.json("world-110m.json", function(error, world) {
    .attr("y", 2 * padding)
    .attr("width", largeWidth - 4 * padding)
    .attr("class", "largeBackgroundRect")
    if (!mobile & !safari) {
    if (!mobile) {
    d3.select(".largeSvg").call(dragSetupLarge())
    }
    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (safari) {
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
    }
    // do an immediate update (in case safari) - not best practice, need to fix
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
    });

    // adjustable box sizes on small worlds
    @@ -408,9 +401,7 @@ function dragSetupLarge() {

    return d3.behavior.drag()
    .on("drag", function() {
    console.log(dragDistance, count)
    count = count++
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
  6. zanarmstrong revised this gist Jun 3, 2015. 1 changed file with 19 additions and 21 deletions.
    40 changes: 19 additions & 21 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -336,9 +336,9 @@ d3.json("world-110m.json", function(error, world) {
    .attr("y", 2 * padding)
    .attr("width", largeWidth - 4 * padding)
    .attr("class", "largeBackgroundRect")

    d3.select(".largeSvg").call(dragSetupLarge())

    if (!mobile & !safari) {
    d3.select(".largeSvg").call(dragSetupLarge())
    }
    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (safari) {
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    @@ -406,23 +406,22 @@ function dragSetupLarge() {
    y: 0
    };

    if (!mobile & !safari) {
    return d3.behavior.drag()
    .on("drag", function() {
    console.log(dragDistance, count)
    count = count++
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    });
    }
    return d3.behavior.drag()
    .on("drag", function() {
    console.log(dragDistance, count)
    count = count++
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    });

    }

    // update functions
    @@ -475,7 +474,6 @@ function updateLargeRotation(map, name, newRotate, transitionDuration) {
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    console.log('done')
    }


  7. zanarmstrong revised this gist Jun 2, 2015. 1 changed file with 16 additions and 4 deletions.
    20 changes: 16 additions & 4 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -157,6 +157,11 @@ if (document.getElementById("testMobile").offsetWidth <= 0) {
    mobile = true;
    }

    var safari = false;
    if (navigator.userAgent.indexOf("Safari") > -1) {
    safari = true
    }

    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;
    @@ -335,7 +340,7 @@ d3.json("world-110m.json", function(error, world) {
    d3.select(".largeSvg").call(dragSetupLarge())

    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (navigator.userAgent.indexOf("Safari") > -1) {
    if (safari) {
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
    }
    @@ -385,6 +390,7 @@ function dragSetupSmall(name) {
    })
    }

    var count = 0;

    function dragSetupLarge() {

    @@ -400,15 +406,16 @@ function dragSetupLarge() {
    y: 0
    };

    if (!mobile) {
    if (!mobile & !safari) {
    return d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    console.log(dragDistance, count)
    count = count++
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    // }
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    @@ -441,13 +448,15 @@ function updateStateRotation(rotateCoord, name) {
    }
    }

    // currently not used
    function updateLargeRotationDuringDrag(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    d3.selectAll(".boundary_" + name).transition().duration([transitionDuration]).remove();
    }

    // currently not used
    function updateLargeRotationEndDrag(map, name, newRotate, transitionDuration) {
    map[name].projection.rotate(newRotate)
    d3.select(".large_" + name).insert("path", ".large_graticule" + name)
    @@ -466,6 +475,7 @@ function updateLargeRotation(map, name, newRotate, transitionDuration) {
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    console.log('done')
    }


    @@ -559,6 +569,7 @@ function updateView(newRotate, newScale, name, duration, stage) {
    } else if (stage == 'dragend') {
    updateLargeRotationEndDrag(largeMapObjects, name, newRotate, duration)
    } else {
    console.log("sending request to rotate", name, newRotate)
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    }
    } else {
    @@ -567,6 +578,7 @@ function updateView(newRotate, newScale, name, duration, stage) {
    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(newScale), duration)
    state.scale = newScale
    }

    }

    updateHash()
  8. zanarmstrong revised this gist Jun 2, 2015. 1 changed file with 2 additions and 5 deletions.
    7 changes: 2 additions & 5 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -336,11 +336,9 @@ d3.json("world-110m.json", function(error, world) {

    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (navigator.userAgent.indexOf("Safari") > -1) {
    console.log('safari')
    updateLargeRotation(largeMapObjects[0], 0, largeMapObjects[0].path, 0)
    updateLargeRotation(largeMapObjects[1], 1, largeMapObjects[1].path, 0)
    updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
    updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
    }

    });

    // adjustable box sizes on small worlds
    @@ -407,7 +405,6 @@ function dragSetupLarge() {
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    // if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
  9. zanarmstrong revised this gist Jun 2, 2015. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion overlay.js
    Original file line number Diff line number Diff line change
    @@ -307,7 +307,6 @@ d3.json("world-110m.json", function(error, world) {

    addCenterBox(svg, [mapObjects[i].lon, mapObjects[i].lat], mapObjects[i].projection)
    }
    console.log(state)

    // use world data to set up large maps
    for (var i = 0; i < 2; i++) {
  10. zanarmstrong revised this gist Jun 2, 2015. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -337,6 +337,7 @@ d3.json("world-110m.json", function(error, world) {

    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (navigator.userAgent.indexOf("Safari") > -1) {
    console.log('safari')
    updateLargeRotation(largeMapObjects[0], 0, largeMapObjects[0].path, 0)
    updateLargeRotation(largeMapObjects[1], 1, largeMapObjects[1].path, 0)
    }
  11. zanarmstrong revised this gist Jun 2, 2015. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -307,6 +307,7 @@ d3.json("world-110m.json", function(error, world) {

    addCenterBox(svg, [mapObjects[i].lon, mapObjects[i].lat], mapObjects[i].projection)
    }
    console.log(state)

    // use world data to set up large maps
    for (var i = 0; i < 2; i++) {
    @@ -334,6 +335,12 @@ d3.json("world-110m.json", function(error, world) {

    d3.select(".largeSvg").call(dragSetupLarge())

    // for some reason Safari is failing the first draw of large maps, so call an immediate update
    if (navigator.userAgent.indexOf("Safari") > -1) {
    updateLargeRotation(largeMapObjects[0], 0, largeMapObjects[0].path, 0)
    updateLargeRotation(largeMapObjects[1], 1, largeMapObjects[1].path, 0)
    }

    });

    // adjustable box sizes on small worlds
  12. zanarmstrong revised this gist Jun 1, 2015. 5 changed files with 9611 additions and 35 deletions.
    9,504 changes: 9,504 additions & 0 deletions d3.js
    9,504 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    5 changes: 5 additions & 0 deletions d3.min.js
    5 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    8 changes: 6 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -9,14 +9,18 @@ <h2>Is Africa bigger than North America?</h2>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    </div>
    <div id="tool"></div>
    <br><br>
    <br>
    <div id="longDesc">
    <h2>What's going on?</h2>
    <p>The earth is a sphere, but maps are flat. This means that when we create maps, we have to somehow flatten the sphere. This flattening must create some distortion. This distortion could affect the relative angles, shapes of countries, or sizes, or all of these. While we can't avoid distortion, we can flatten the sphere in different ways, based on the "projection" we choose, and depending on what we're going to use the map for.</p>
    <p>One of the most commonly used projections is the <a href="http://en.wikipedia.org/wiki/Mercator_projection">Mercator projection</a>. The Mercator was incredibly useful for ocean navigation because it keeps angles accurate, making it easier for sailers to take compass bearings and navigate the open ocean. The (significant) downside is that it distrorts how large things are. That said, many maps found online, like Google Maps, Open Street Maps, Mapquest, and others, use a variation of Mercator known as "Web Mercator. Advantages include the fact that north is "up" and that meridians (lines of longitude) are equally spaced vertical lines."</p>
    <p>Many of us imagine the world as it was depicted on the map on the wall in our grade school classrooms or the maps we use online. But, this can lead to incorrect assumptions about the world around us. I was inspired to create this after reading <a href="https://iansierraleone2015.wordpress.com/">a friend's account of his time fighting Ebola in Sierra Leone</a>. He was frustrated with misunderstanding about the disease, including that a "school in New Jersey that panicked and refused to admit two elementary school children from Rwanda. Never mind that Rwanda is 2,600 miles from the epidemic area in West Africa. That’s the distance from my apartment in DC to Lake Tahoe." Misunderstanding scale in the world around us can have a real impact on our opinions and decisions. I hope that this website plays a small part in helping us better understand our wonderful world.</p>
    <p>If you liked this, you might also enjoy the <a href="http://kai.subblue.com/en/africa.html">True Size of Africa</a> or the <a href="https://gmaps-samples.googlecode.com/svn/trunk/poly/puzzledrag.html">Mercator Puzzle</a>.</p>
    </div>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <div id="testMobile">
    </div>
    <script src="d3.js"></script>
    <script src="http://d3js.org/topojson.v1.min.js"></script>
    <script src="overlay.js"></script>
    36 changes: 34 additions & 2 deletions overlay.css
    Original file line number Diff line number Diff line change
    @@ -13,7 +13,17 @@ button {

    #description {
    margin-left: 50px;
    width: 900px;
    width: 905px;
    }

    #longDesc {
    margin-left: 50px;
    width: 905px;
    }

    #tool {
    margin-left: 50px;
    width: 905px;
    }

    .stroke {
    @@ -26,6 +36,17 @@ button {
    fill: #fff;
    }

    .mainSVG {
    }

    .g_0 {
    touch-action: none;
    }

    .g_2 {
    touch-action: none;
    }


    .graticule {
    fill: none;
    @@ -73,8 +94,19 @@ button {
    }

    .largeBackgroundRect {
    touch-action: none;
    fill-opacity: 0;
    stroke-opacity: 1;
    stroke: black;
    touch-action: none;
    /*touch-action: none;*/
    }

    @media screen and (max-device-width: 480px) and (orientation: portrait){
    #testMobile { display: none; }

    }

    @media screen and (max-device-width: 640px) and (orientation: landscape){
    #testMobile { display: none; }

    }
    93 changes: 62 additions & 31 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,6 @@
    // Zan Armstrong - May, 2015
    // parts of the code adapted from Mike Bostock's Lambert Azimuthal Equal Area http://bl.ocks.org/mbostock/3757101

    document.body.addEventListener('dragstart', function(event) {
    event.preventDefault();
    event.stopPropagation();
    }, false);

    // set up variables
    var width = 300,
    height = 300,
    @@ -157,13 +152,19 @@ if (window.location.hash.split("&").length != 0) {
    }
    }

    var mobile = false;
    if (document.getElementById("testMobile").offsetWidth <= 0) {
    mobile = true;
    }

    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;

    var mainSVG = d3.select("#tool").append("svg")
    .attr("width", 1000)
    .attr("height", 600);
    .attr("width", 905)
    .attr("height", 600)
    .attr("class", "mainSVG");

    var mapObjects = []

    @@ -216,7 +217,8 @@ function setUpSmallMaps(lat, lon, name) {
    var graticule = d3.geo.graticule();

    var svg = mainSVG.append("g")
    .attr('transform', 'translate(' + padding + ',' + (height * name + margin.top) + ')');
    .attr('transform', 'translate(' + padding + ',' + (height * name + margin.top) + ')')
    .classed("g_" + name, true);

    svg.append("defs").append("path")
    .datum({
    @@ -293,6 +295,7 @@ function setUpLargeMaps(lat, lon, name, svg) {


    d3.json("world-110m.json", function(error, world) {
    largeMapObjects.data = world;
    // use world data to set up small maps
    for (var i = 0; i < mapObjects.length; i++) {
    svg = mapObjects[i].svg;
    @@ -362,14 +365,17 @@ function dragSetupSmall(name) {
    };

    return d3.behavior.drag()
    .on("dragstart", function() {
    d3.event.sourceEvent.preventDefault();
    })
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnSmallMapPan(dragDistance, name)
    updateRotateBasedOnSmallMapPan(dragDistance, name, 'drag')
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnSmallMapPan(dragDistance, name)
    updateRotateBasedOnSmallMapPan(dragDistance, name, 'dragend')
    resetDrag()
    })
    }
    @@ -389,21 +395,23 @@ function dragSetupLarge() {
    y: 0
    };

    return d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    // if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    // }
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    });
    if (!mobile) {
    return d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    // if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    // }
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
    updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
    resetDrag()
    });
    }
    }

    // update functions
    @@ -429,6 +437,23 @@ function updateStateRotation(rotateCoord, name) {
    }
    }

    function updateLargeRotationDuringDrag(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    d3.selectAll(".boundary_" + name).transition().duration([transitionDuration]).remove();
    }

    function updateLargeRotationEndDrag(map, name, newRotate, transitionDuration) {
    map[name].projection.rotate(newRotate)
    d3.select(".large_" + name).insert("path", ".large_graticule" + name)
    .datum(topojson.mesh(map.data, map.data.objects.countries, function(a, b) {
    return a !== b;
    }))
    .attr("class", "boundary boundary_" + name)
    .transition().duration([500])
    .attr("d", map[name].path);
    }

    function updateLargeRotation(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    @@ -506,26 +531,32 @@ function updateCenterBoxSize(oldDistance, newDistance, duration) {
    }

    // updates on drag
    function updateRotateBasedOnSmallMapPan(pixelDifference, name) {
    function updateRotateBasedOnSmallMapPan(pixelDifference, name, stage) {
    var projection = mapObjects[name].projection;
    projection.rotate(pixelDiff_to_rotation_small(projection, pixelDifference))

    updateView(projection.rotate(), "na", name, 0)
    updateView(projection.rotate(), "na", name, 0, 'na')
    }

    function updateRotateBasedOnLargeMapPan(pixelDifference, name) {
    function updateRotateBasedOnLargeMapPan(pixelDifference, name, stage) {
    var projection = largeMapObjects[name].projection;
    var newRotate = pixelDiff_to_rotation_large(projection, pixelDifference)
    updateView(newRotate, "na", name, 0)
    updateView(newRotate, "na", name, 0, stage)
    }

    function updateView(newRotate, newScale, name, duration) {
    function updateView(newRotate, newScale, name, duration, stage) {
    updateStateRotation(newRotate, name)

    updateSmallRotation(mapObjects, name, newRotate, duration)

    if (newScale == 'na') {
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    if (stage == 'drag') {
    updateLargeRotationDuringDrag(largeMapObjects, name, newRotate, duration)
    } else if (stage == 'dragend') {
    updateLargeRotationEndDrag(largeMapObjects, name, newRotate, duration)
    } else {
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    }
    } else {
    updateLargeRotationAndScale(largeMapObjects, name, newRotate, newScale, duration)
    if (name == 0) {
  13. zanarmstrong revised this gist May 29, 2015. 2 changed files with 16 additions and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    15 changes: 15 additions & 0 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -89,6 +89,18 @@ var countries = {
    lat: 60.65,
    lon: 95.995
    }
    },
    Alaska: {
    latLon: {
    lat: 64.23,
    lon: -149.862
    }
    },
    Saudi_Arabia: {
    latLon: {
    lat: 22.389,
    lon: 46.59
    }
    }
    }

    @@ -114,6 +126,9 @@ comparisons = {
    "North_Africa,Russia": {
    scale: 470
    },
    "Saudi_Arabia,Alaska": {
    scale: 713
    },
    }

    // use/manage hash fragment and state
  14. zanarmstrong revised this gist May 29, 2015. 1 changed file with 6 additions and 12 deletions.
    18 changes: 6 additions & 12 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -145,7 +145,6 @@ if (window.location.hash.split("&").length != 0) {
    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;
    var scaleTimer = Date.now();

    var mainSVG = d3.select("#tool").append("svg")
    .attr("width", 1000)
    @@ -171,12 +170,7 @@ var slider = d3.select("#tool")
    slider.property("value", state.scale)

    function slided(d) {
    if (Math.abs(state.scale - d3.select(this).property("value")) > 80) {
    var duration = 500
    } else {
    var duration = 0
    }

    var duration = 0;
    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration)

    state.scale = d3.select(this).property("value");
    @@ -384,11 +378,11 @@ function dragSetupLarge() {
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    }
    // if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    // }
    })
    .on("dragend", function() {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
  15. zanarmstrong revised this gist May 29, 2015. 1 changed file with 307 additions and 173 deletions.
    480 changes: 307 additions & 173 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -19,59 +19,107 @@ var margin = {
    // country specific lat/lon for buttons
    var countries = {
    Australia: {
    lat: -27,
    lon: 130
    latLon: {
    lat: -27,
    lon: 130
    }
    },
    Antarctica: {
    lat: -90,
    lon: 0
    latLon: {
    lat: -90,
    lon: 0
    }
    },
    Sweden: {
    lat: 62.6,
    lon: 16.3
    latLon: {
    lat: 62.6,
    lon: 16.3
    }
    },
    Madagascar: {
    lat: -19,
    lon: 46.7
    latLon: {
    lat: -19,
    lon: 46.7
    }
    },
    US: {
    lat: 35.65,
    lon: -97.24
    latLon: {
    lat: 35.65,
    lon: -97.24
    }
    },
    Greenland: {
    lat: 65.88,
    lon: -42.21
    latLon: {
    lat: 65.88,
    lon: -42.21
    }
    },
    South_America: {
    lat: -18.97,
    lon: -61.479
    latLon: {
    lat: -18.97,
    lon: -61.479
    }
    },
    Brazil: {
    lat: -16.7833,
    lon: -53.2052
    latLon: {
    lat: -16.7833,
    lon: -53.2052
    }
    },
    Africa: {
    lat: 6.52865,
    lon: 20.3586336
    latLon: {
    lat: 6.52865,
    lon: 20.3586336
    }
    },
    North_America: {
    lat: 48.2392291,
    lon: -98.9443219
    latLon: {
    lat: 48.2392291,
    lon: -98.9443219
    }
    },
    North_Africa: {
    lat: 15.0,
    lon: 18.82
    latLon: {
    lat: 15.0,
    lon: 18.82
    }
    },
    Russia: {
    lat: 60.65,
    lon: 95.995
    latLon: {
    lat: 60.65,
    lon: 95.995
    }
    }
    }

    comparisons = {
    "Sweden,Madagascar": {
    scale: 1120
    },
    "Australia,Antarctica": {
    scale: 470
    },
    "US,Australia": {
    scale: 640
    },
    "South_America,Greenland": {
    scale: 458
    },
    "Brazil,US": {
    scale: 360
    },
    "Africa,North_America": {
    scale: 350
    },
    "North_Africa,Russia": {
    scale: 470
    },
    }

    // use/manage hash fragment and state
    var state = {
    scale: 450,
    latLon: [countries.Africa, countries.North_America]
    latLon: [countries.Africa.latLon, countries.North_America.latLon]
    }

    if (window.location.hash.split("&").length != 0) {
    @@ -94,19 +142,49 @@ if (window.location.hash.split("&").length != 0) {
    }
    }

    var updateHash = function() {
    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    }
    // set upvariables
    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;
    var scaleTimer = Date.now();

    var mainSVG = d3.select("#tool").append("svg")
    .attr("width", 1000)
    .attr("height", 600);

    var mapObjects = []

    // set up ranges/scales
    var zoomRange = [220, 1600]
    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);

    // slider setup for scale
    var slider = d3.select("#tool")
    .append("p")
    .append("input")
    .attr("type", "range")
    .style("margin-left", "750px")
    .attr("min", zoomRange[0])
    .attr("max", zoomRange[1])
    .attr("step", (zoomRange[1] - zoomRange[0]) / 400)
    .on("input", slided);

    slider.property("value", state.scale)

    function slided(d) {
    if (Math.abs(state.scale - d3.select(this).property("value")) > 80) {
    var duration = 500
    } else {
    var duration = 0
    }

    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration)

    state.scale = d3.select(this).property("value");
    updateLargeScale(largeMapObjects, state.scale, duration);

    updateHash()
    }

    // create small maps
    for (var i = 0; i < 2; i++) {
    mapObjects[i] = setUpSmallMaps(state.latLon[i].lat, state.latLon[i].lon, i)
    @@ -151,36 +229,9 @@ function setUpSmallMaps(lat, lon, name) {
    .attr("class", "graticule graticule_" + name)
    .attr("d", path);

    // set up drag behavior
    function updateSmall(difference) {
    resetDrag()
    updateSmallMap(difference, name)
    }

    function resetDrag() {
    dragDistance = {
    x: 0,
    y: 0
    };
    }

    var dragDistance = {
    x: 0,
    y: 0
    };
    var drag = d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateSmall(dragDistance)
    })
    .on("dragend", function() {
    lastStart = 'NA'
    updateSmall(dragDistance)
    resetDrag()
    });

    svg.call(drag);
    svg.call(dragSetupSmall(name));
    // end drag behavior setup

    return {
    @@ -194,40 +245,6 @@ function setUpSmallMaps(lat, lon, name) {

    }

    // updates on drag
    function updateSmallMap(pixelDifference, name) {
    var projection = mapObjects[name].projection;
    var path = mapObjects[name].path
    var k = projection.rotate()
    projection.rotate([k[0] + pixelDifference.x / 136 * 90, k[1] - pixelDifference.y, k[2]])

    updateAllWithNewRotation(projection.rotate(), name)
    }

    function updateLargeMap(pixelDifference, name) {
    var projection = largeMapObjects[name].projection;
    var path = largeMapObjects[name].path
    var k = projection.invert([300 - pixelDifference.x, 300 - pixelDifference.y])
    var newRotate = [-k[0], -k[1], 0]
    projection.rotate(newRotate)

    updateAllWithNewRotation(newRotate, name)
    }

    function updateAllWithNewRotation(newRotate, name) {
    state.latLon[name] = {
    lon: newRotate[0],
    lat: newRotate[1]
    }

    mapObjects[name].projection.rotate(newRotate)
    d3.selectAll(".graticule_" + name).attr("d", mapObjects[name].path);
    d3.selectAll(".land_" + name).attr("d", mapObjects[name].path);

    updateLarge(largeMapObjects, name, newRotate, 0)
    updateHash()
    }

    // set up large maps
    var largeSvg = mainSVG.append("g").attr("class", "largeSvg")
    .attr('transform', 'translate(' + (2 * padding + width) + ',' + (margin.top) + ')')
    @@ -265,37 +282,6 @@ function setUpLargeMaps(lat, lon, name, svg) {

    }

    // for update responding to button click
    function updateSmallRotate(mapObject, name, newRotate, transitionDuration) {
    d3.selectAll(".graticule_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));

    d3.selectAll(".land_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));
    }

    // update large w/ new rotation
    function updateLarge(mapObject, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));;
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));;
    }

    // responding to scale change
    function updateLargeScale(mapObjects, newScale) {
    for (var i = 0; i < 2; i++) {
    mapObjects[i].projection.scale(newScale)
    d3.selectAll(".large_land_" + i).attr("d", mapObjects[i].path);
    d3.selectAll(".boundary_" + i).attr("d", mapObjects[i].path);
    }
    }

    d3.json("world-110m.json", function(error, world) {
    // use world data to set up small maps
    @@ -334,7 +320,7 @@ d3.json("world-110m.json", function(error, world) {
    .attr("width", largeWidth - 4 * padding)
    .attr("class", "largeBackgroundRect")

    d3.select(".largeSvg").call(dragSetup())
    d3.select(".largeSvg").call(dragSetupLarge())

    });

    @@ -352,22 +338,35 @@ function addCenterBox(svg) {

    }

    function updateCenterBox(distance) {
    d3.selectAll(".littleBox")
    .attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    // might be nice to combine these two drag functions in the future, but ok for now
    function dragSetupSmall(name) {
    function resetDrag() {
    dragDistance = {
    x: 0,
    y: 0
    };
    }

    var dragDistance = {
    x: 0,
    y: 0
    };

    return d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    updateRotateBasedOnSmallMapPan(dragDistance, name)
    resetDrag()
    })
    .on("dragend", function() {
    updateRotateBasedOnSmallMapPan(dragDistance, name)
    resetDrag()
    })
    }

    function dragSetup() {
    // start drag setup
    function update(difference) {
    resetDrag()

    updateLargeMap(difference, 0)
    updateLargeMap(difference, 1)
    }
    function dragSetupLarge() {

    function resetDrag() {
    dragDistance = {
    @@ -385,32 +384,161 @@ function dragSetup() {
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    update(dragDistance)
    if (Math.pow(dragDistance.x * dragDistance.x + dragDistance.y * dragDistance.y, .5 > 50)) {
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    }
    })
    .on("dragend", function() {
    lastStart = 'NA'
    update(dragDistance)
    updateRotateBasedOnLargeMapPan(dragDistance, 0)
    updateRotateBasedOnLargeMapPan(dragDistance, 1)
    resetDrag()
    });
    }

    // buttons
    d3.selectAll("button").on("click", function() {
    state.latLon[0] = countries[this.name.split(",")[0]];
    state.latLon[1] = countries[this.name.split(",")[1]];

    // update small maps with new rotation
    updateSmallRotate(mapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], transitionDuration)
    updateSmallRotate(mapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], transitionDuration)
    // update large maps
    updateLarge(largeMapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], transitionDuration)
    updateLarge(largeMapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], transitionDuration)
    // update hash
    // update functions
    var updateHash = function() {
    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    slider.property("value", state.scale)
    }

    function pixelDiff_to_rotation_large(projection, pxDiff) {
    var k = projection.invert([largeWidth / 2 - pxDiff.x, largeHeight / 2 - pxDiff.y])
    return [-k[0], -k[1], 0]
    }

    function pixelDiff_to_rotation_small(projection, pxDiff) {
    var k = projection.rotate()
    return ([k[0] + pxDiff.x / 136 * 90, k[1] - pxDiff.y, k[2]])
    }

    function updateStateRotation(rotateCoord, name) {
    state.latLon[name] = {
    lon: rotateCoord[0],
    lat: rotateCoord[1]
    }
    }


    function updateLargeRotation(map, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
    }


    function updateLargeRotationAndScale(map, name, newRotate, newScale, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
    }


    function updateSmallRotation(mapObject, name, newRotate, transitionDuration) {
    if (transitionDuration == 0) {
    mapObject[name].projection.rotate(newRotate)
    d3.selectAll(".graticule_" + name).attr("d", mapObject[name].path);
    d3.selectAll(".land_" + name).attr("d", mapObject[name].path);
    } else {
    d3.selectAll(".graticule_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));

    d3.selectAll(".land_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));
    }
    }


    function updateLargeScale(maps, newScale, duration) {
    if (duration > 0) {
    for (var i = 0; i < 2; i++) {
    d3.selectAll(".large_land_" + i)
    .transition().duration([duration])
    .attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
    d3.selectAll(".boundary_" + i)
    .transition().duration([duration])
    .attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
    }
    } else {
    for (var i = 0; i < 2; i++) {
    maps[i].projection.scale(newScale)
    d3.selectAll(".large_land_" + i).attr("d", maps[i].path);
    d3.selectAll(".boundary_" + i).attr("d", maps[i].path);
    }
    }
    }

    function updateCenterBoxSize(oldDistance, newDistance, duration) {
    if (duration == 0) {
    d3.selectAll(".littleBox")
    .attr("x", width / 2 - newDistance)
    .attr("y", height / 2 - newDistance)
    .attr("width", newDistance * 2)
    .attr("height", newDistance * 2)
    } else {
    d3.selectAll(".littleBox")
    .transition().duration([duration])
    .attrTween("x", distanceTween(width / 2 - oldDistance, width / 2 - newDistance))
    .attrTween("y", distanceTween(height / 2 - oldDistance, height / 2 - newDistance))
    .attrTween("width", distanceTween(2 * oldDistance, 2 * newDistance))
    .attrTween("height", distanceTween(2 * oldDistance, 2 * newDistance))
    }
    }

    // updates on drag
    function updateRotateBasedOnSmallMapPan(pixelDifference, name) {
    var projection = mapObjects[name].projection;
    projection.rotate(pixelDiff_to_rotation_small(projection, pixelDifference))

    updateView(projection.rotate(), "na", name, 0)
    }

    function updateRotateBasedOnLargeMapPan(pixelDifference, name) {
    var projection = largeMapObjects[name].projection;
    var newRotate = pixelDiff_to_rotation_large(projection, pixelDifference)
    updateView(newRotate, "na", name, 0)
    }

    function updateView(newRotate, newScale, name, duration) {
    updateStateRotation(newRotate, name)

    updateSmallRotation(mapObjects, name, newRotate, duration)

    if (newScale == 'na') {
    updateLargeRotation(largeMapObjects, name, newRotate, duration)
    } else {
    updateLargeRotationAndScale(largeMapObjects, name, newRotate, newScale, duration)
    if (name == 0) {
    updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(newScale), duration)
    state.scale = newScale
    }
    }

    updateHash()
    }

    // updates on button click
    d3.selectAll("button").on("click", function() {
    state.latLon[0] = countries[this.name.split(",")[0]].latLon;
    state.latLon[1] = countries[this.name.split(",")[1]].latLon;

    updateView([-state.latLon[0].lon, -state.latLon[0].lat, 0], comparisons[this.name].scale, 0, transitionDuration)
    updateView([-state.latLon[1].lon, -state.latLon[1].lat, 0], comparisons[this.name].scale, 1, transitionDuration)
    })

    // transition
    function arcTween(path, projection, new3Rotation) {
    function rotationTween(path, projection, new3Rotation) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(projection.rotate(), new3Rotation);
    return function(t) {
    @@ -420,27 +548,33 @@ function arcTween(path, projection, new3Rotation) {
    }
    }

    // set up ranges/scales
    var zoomRange = [220, 1600]
    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);
    var zoomScaleToLongPixelRatio = d3.scale.linear().domain(zoomRange).range([.5, .03333])

    // slider setup for scale
    var slider = d3.select("#tool")
    .append("p")
    .append("input")
    .attr("type", "range")
    .style("margin-left", "750px")
    .attr("min", zoomRange[0])
    .attr("max", zoomRange[1])
    .attr("step", (zoomRange[1] - zoomRange[0]) / 400)
    .on("input", slided);
    function scaleTween(path, projection, newScale) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(projection.scale(), newScale);
    return function(t) {
    projection.scale(interpolate(t));
    return path(d);
    }
    }
    }

    slider.property("value", state.scale)
    function distanceTween(oldDist, newDist) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(oldDist, newDist);
    return function(t) {
    return interpolate(t);
    }
    }
    }

    function slided(d) {
    state.scale = d3.select(this).property("value");
    updateLargeScale(largeMapObjects, state.scale);
    updateCenterBox(zoomToBoxScale(state.scale))
    updateHash()
    function rotationAndScaleTween(path, projection, new3Rotation, newScale) {
    return function(d, i, a) {
    var interpolateScale = d3.interpolate(projection.scale(), newScale);
    var interpolateRotate = d3.interpolate(projection.rotate(), new3Rotation);
    return function(t) {
    projection.scale(interpolateScale(t));
    projection.rotate(interpolateRotate(t));
    return path(d);
    }
    }
    }
  16. zanarmstrong revised this gist May 29, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion overlay.js
    Original file line number Diff line number Diff line change
    @@ -201,7 +201,7 @@ function updateSmallMap(pixelDifference, name) {
    var k = projection.rotate()
    projection.rotate([k[0] + pixelDifference.x / 136 * 90, k[1] - pixelDifference.y, k[2]])

    updateAllWithNewRotation(projection, path, name)
    updateAllWithNewRotation(projection.rotate(), name)
    }

    function updateLargeMap(pixelDifference, name) {
  17. zanarmstrong revised this gist May 29, 2015. 1 changed file with 13 additions and 14 deletions.
    27 changes: 13 additions & 14 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -205,27 +205,26 @@ function updateSmallMap(pixelDifference, name) {
    }

    function updateLargeMap(pixelDifference, name) {
    var projection = mapObjects[name].projection;
    var path = mapObjects[name].path
    var k = projection.rotate()
    projection.rotate([k[0] + pixelDifference.x * zoomScaleToLongPixelRatio(state.scale),
    k[1] - pixelDifference.y * zoomScaleToLongPixelRatio(state.scale),
    k[2]
    ])
    var projection = largeMapObjects[name].projection;
    var path = largeMapObjects[name].path
    var k = projection.invert([300 - pixelDifference.x, 300 - pixelDifference.y])
    var newRotate = [-k[0], -k[1], 0]
    projection.rotate(newRotate)

    updateAllWithNewRotation(projection, path, name)
    updateAllWithNewRotation(newRotate, name)
    }

    function updateAllWithNewRotation(projection, path, name) {
    function updateAllWithNewRotation(newRotate, name) {
    state.latLon[name] = {
    lon: projection.rotate()[0],
    lat: projection.rotate()[1]
    lon: newRotate[0],
    lat: newRotate[1]
    }

    d3.selectAll(".graticule_" + name).attr("d", path);
    d3.selectAll(".land_" + name).attr("d", path);
    mapObjects[name].projection.rotate(newRotate)
    d3.selectAll(".graticule_" + name).attr("d", mapObjects[name].path);
    d3.selectAll(".land_" + name).attr("d", mapObjects[name].path);

    updateLarge(largeMapObjects, name, projection.rotate(), 0)
    updateLarge(largeMapObjects, name, newRotate, 0)
    updateHash()
    }

  18. zanarmstrong revised this gist May 28, 2015. 2 changed files with 134 additions and 65 deletions.
    7 changes: 7 additions & 0 deletions overlay.css
    Original file line number Diff line number Diff line change
    @@ -71,3 +71,10 @@ button {
    stroke-width: 1px;
    opacity: .9;
    }

    .largeBackgroundRect {
    fill-opacity: 0;
    stroke-opacity: 1;
    stroke: black;
    touch-action: none;
    }
    192 changes: 127 additions & 65 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -6,6 +6,7 @@ document.body.addEventListener('dragstart', function(event) {
    event.stopPropagation();
    }, false);

    // set up variables
    var width = 300,
    height = 300,
    largeWidth = 600,
    @@ -15,6 +16,7 @@ var margin = {
    top: 0
    }

    // country specific lat/lon for buttons
    var countries = {
    Australia: {
    lat: -27,
    @@ -66,19 +68,18 @@ var countries = {
    }
    }

    // use/manage hash fragment and state
    var state = {
    scale: 450,
    latLon: [countries.Africa, countries.North_America]
    }

    var transitionDuration = 1000;

    if (window.location.hash.split("&").length != 0) {
    var windowState = window.location.hash.split("&");
    for (var i = 0; i < windowState.length; i++) {
    var k = windowState[i].replace('#', '').split('=');
    if (k[0] == "scale") {
    state.scale = k[1];
    state.scale = +k[1];
    } else if (k[0] == "center0") {
    state.latLon[0] = {
    lat: k[1].split(",")[0],
    @@ -94,22 +95,24 @@ if (window.location.hash.split("&").length != 0) {
    }

    var updateHash = function() {
    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    }


    window.location.hash = "scale=" + state.scale + "&center0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "&center1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
    }
    // set upvariables
    var padding = 5;
    var transitionDuration = 1000;

    var mainSVG = d3.select("#tool").append("svg")
    .attr("width", 1000)
    .attr("height", 600);

    var mapObjects = []

    // create small maps
    for (var i = 0; i < 2; i++) {
    mapObjects[i] = setUpSmallMaps(state.latLon[i].lat, state.latLon[i].lon, i)
    }

    // set up small maps
    function setUpSmallMaps(lat, lon, name) {
    var center = [-lon, -lat]
    var projectionSmall = d3.geo.orthographic()
    @@ -148,23 +151,10 @@ function setUpSmallMaps(lat, lon, name) {
    .attr("class", "graticule graticule_" + name)
    .attr("d", path);

    // set up drag behavior
    function updateSmall(difference) {
    // if (lastStart == 'NA' || Date.now() - lastStart > 80) {
    resetDrag()
    //lastStart = Date.now();
    var k = projectionSmall.rotate()
    projectionSmall.rotate([k[0] + difference.x / 136 * 90, k[1] - difference.y, k[2]])
    state.latLon[name] = {
    lon: -projectionSmall.rotate()[0],
    lat: -projectionSmall.rotate()[1]
    }

    d3.selectAll(".graticule_" + name).attr("d", path);
    d3.selectAll(".land_" + name).attr("d", path);

    updateLarge(largeMapObjects, name, projectionSmall.rotate(), 0)
    updateHash()
    // }
    updateSmallMap(difference, name)
    }

    function resetDrag() {
    @@ -174,7 +164,6 @@ function setUpSmallMaps(lat, lon, name) {
    };
    }

    //var lastStart = 'NA';
    var dragDistance = {
    x: 0,
    y: 0
    @@ -192,7 +181,7 @@ function setUpSmallMaps(lat, lon, name) {
    });

    svg.call(drag);

    // end drag behavior setup

    return {
    "svg": svg,
    @@ -205,18 +194,44 @@ function setUpSmallMaps(lat, lon, name) {

    }

    // updates on drag
    function updateSmallMap(pixelDifference, name) {
    var projection = mapObjects[name].projection;
    var path = mapObjects[name].path
    var k = projection.rotate()
    projection.rotate([k[0] + pixelDifference.x / 136 * 90, k[1] - pixelDifference.y, k[2]])

    var largeSvg = mainSVG.append("g")
    .attr('transform', 'translate(' + (2 * padding + width) + ',' + (margin.top) + ')');
    updateAllWithNewRotation(projection, path, name)
    }

    function updateLargeMap(pixelDifference, name) {
    var projection = mapObjects[name].projection;
    var path = mapObjects[name].path
    var k = projection.rotate()
    projection.rotate([k[0] + pixelDifference.x * zoomScaleToLongPixelRatio(state.scale),
    k[1] - pixelDifference.y * zoomScaleToLongPixelRatio(state.scale),
    k[2]
    ])

    largeSvg.append('rect')
    .attr("height", largeHeight - 4 * padding)
    .attr("x", 2 * padding)
    .attr("y", 2 * padding)
    .attr("width", largeWidth - 4 * padding)
    .attr("fill", "none")
    .attr("stroke", "black")
    updateAllWithNewRotation(projection, path, name)
    }

    function updateAllWithNewRotation(projection, path, name) {
    state.latLon[name] = {
    lon: projection.rotate()[0],
    lat: projection.rotate()[1]
    }

    d3.selectAll(".graticule_" + name).attr("d", path);
    d3.selectAll(".land_" + name).attr("d", path);

    updateLarge(largeMapObjects, name, projection.rotate(), 0)
    updateHash()
    }

    // set up large maps
    var largeSvg = mainSVG.append("g").attr("class", "largeSvg")
    .attr('transform', 'translate(' + (2 * padding + width) + ',' + (margin.top) + ')')

    var largeMapObjects = []
    for (var i = 0; i < 2; i++) {
    @@ -241,7 +256,6 @@ function setUpLargeMaps(lat, lon, name, svg) {
    var path = d3.geo.path()
    .projection(projectionLarge);


    return {
    "svg": svg,
    "projection": projectionLarge,
    @@ -252,9 +266,8 @@ function setUpLargeMaps(lat, lon, name, svg) {

    }

    // need to set update function
    // for update responding to button click
    function updateSmallRotate(mapObject, name, newRotate, transitionDuration) {

    d3.selectAll(".graticule_" + name)
    .transition()
    .duration([transitionDuration])
    @@ -266,7 +279,7 @@ function updateSmallRotate(mapObject, name, newRotate, transitionDuration) {
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));
    }

    // need to set update function
    // update large w/ new rotation
    function updateLarge(mapObject, name, newRotate, transitionDuration) {
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    @@ -276,6 +289,7 @@ function updateLarge(mapObject, name, newRotate, transitionDuration) {
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));;
    }

    // responding to scale change
    function updateLargeScale(mapObjects, newScale) {
    for (var i = 0; i < 2; i++) {
    mapObjects[i].projection.scale(newScale)
    @@ -284,27 +298,8 @@ function updateLargeScale(mapObjects, newScale) {
    }
    }

    function addCenterBox(svg) {
    var distance = zoomToBoxScale(state.scale);
    svg.append("rect")
    .attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    .attr("stroke", "black")
    .attr("fill", "none")
    .attr("class", "littleBox")
    }

    function updateCenterBox(distance) {
    d3.selectAll(".littleBox")
    .attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    }

    d3.json("world-110m.json", function(error, world) {
    // use world data to set up small maps
    for (var i = 0; i < mapObjects.length; i++) {
    svg = mapObjects[i].svg;
    svg
    @@ -316,8 +311,9 @@ d3.json("world-110m.json", function(error, world) {
    addCenterBox(svg, [mapObjects[i].lon, mapObjects[i].lat], mapObjects[i].projection)
    }

    // use world data to set up large maps
    for (var i = 0; i < 2; i++) {
    svg = largeMapObjects[i].svg.append('g').attr("class", "large_" + name);
    svg = largeMapObjects[i].svg.append('g').attr("class", "large_" + i);
    svg.insert("path", ".large_graticule_" + i)
    .datum(topojson.feature(world, world.objects.land))
    .attr("class", "large_land_" + i)
    @@ -331,8 +327,74 @@ d3.json("world-110m.json", function(error, world) {
    .attr("d", largeMapObjects[i].path);
    }

    // append rectangle for large box
    largeSvg.append('rect')
    .attr("height", largeHeight - 4 * padding)
    .attr("x", 2 * padding)
    .attr("y", 2 * padding)
    .attr("width", largeWidth - 4 * padding)
    .attr("class", "largeBackgroundRect")

    d3.select(".largeSvg").call(dragSetup())

    });

    // adjustable box sizes on small worlds
    function addCenterBox(svg) {
    var distance = zoomToBoxScale(state.scale);

    svg.append("rect").attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    .attr("stroke", "black")
    .attr("fill", "none")
    .attr("class", "littleBox")

    }

    function updateCenterBox(distance) {
    d3.selectAll(".littleBox")
    .attr("x", width / 2 - distance)
    .attr("y", height / 2 - distance)
    .attr("width", distance * 2)
    .attr("height", distance * 2)
    }

    function dragSetup() {
    // start drag setup
    function update(difference) {
    resetDrag()

    updateLargeMap(difference, 0)
    updateLargeMap(difference, 1)
    }

    function resetDrag() {
    dragDistance = {
    x: 0,
    y: 0
    };
    }

    var dragDistance = {
    x: 0,
    y: 0
    };

    return d3.behavior.drag()
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
    update(dragDistance)
    })
    .on("dragend", function() {
    lastStart = 'NA'
    update(dragDistance)
    resetDrag()
    });
    }

    // buttons
    d3.selectAll("button").on("click", function() {
    state.latLon[0] = countries[this.name.split(",")[0]];
    @@ -359,24 +421,24 @@ function arcTween(path, projection, new3Rotation) {
    }
    }

    // slider
    var zoomRange = [220, 2000]

    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20])
    // set up ranges/scales
    var zoomRange = [220, 1600]
    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);
    var zoomScaleToLongPixelRatio = d3.scale.linear().domain(zoomRange).range([.5, .03333])

    // slider setup for scale
    var slider = d3.select("#tool")
    .append("p")
    .append("input")
    .attr("type", "range")
    .style("margin-left", "750px")
    .attr("value", function(d) {
    return state.scale
    })
    .attr("min", zoomRange[0])
    .attr("max", zoomRange[1])
    .attr("step", (zoomRange[1] - zoomRange[0]) / 400)
    .on("input", slided);

    slider.property("value", state.scale)

    function slided(d) {
    state.scale = d3.select(this).property("value");
    updateLargeScale(largeMapObjects, state.scale);
  19. zanarmstrong revised this gist May 28, 2015. 3 changed files with 5 additions and 11 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">North Africa vs Russia</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    4 changes: 3 additions & 1 deletion overlay.css
    Original file line number Diff line number Diff line change
    @@ -5,8 +5,10 @@ body {
    }

    button {
    margin-right: 10px;
    margin-right: 20px;
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    }

    #description {
    10 changes: 1 addition & 9 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,9 @@
    // Zan Armstrong - May, 2015
    // parts of the code adapted from Mike Bostock's Lambert Azimuthal Equal Area http://bl.ocks.org/mbostock/3757101

    document.body.addEventListener('drag', function(event) {
    event.preventDefault();
    }, false);

    document.body.addEventListener('dragstart', function(event) {
    event.preventDefault();
    event.stopPropagation();
    }, false);

    var width = 300,
    @@ -18,8 +15,6 @@ var margin = {
    top: 0
    }



    var countries = {
    Australia: {
    lat: -27,
    @@ -185,9 +180,6 @@ function setUpSmallMaps(lat, lon, name) {
    y: 0
    };
    var drag = d3.behavior.drag()
    // .on("dragstart", function() {
    // d3.event.sourceEvent.preventDefault();
    // })
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
  20. zanarmstrong revised this gist May 28, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">North Africa vs Russia</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
  21. zanarmstrong revised this gist May 28, 2015. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -5,6 +5,10 @@ document.body.addEventListener('drag', function(event) {
    event.preventDefault();
    }, false);

    document.body.addEventListener('dragstart', function(event) {
    event.preventDefault();
    }, false);

    var width = 300,
    height = 300,
    largeWidth = 600,
    @@ -181,9 +185,9 @@ function setUpSmallMaps(lat, lon, name) {
    y: 0
    };
    var drag = d3.behavior.drag()
    .on("dragstart", function() {
    d3.event.sourceEvent.preventDefault();
    })
    // .on("dragstart", function() {
    // d3.event.sourceEvent.preventDefault();
    // })
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
  22. zanarmstrong revised this gist May 28, 2015. 3 changed files with 12 additions and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    3 changes: 3 additions & 0 deletions overlay.css
    Original file line number Diff line number Diff line change
    @@ -30,16 +30,19 @@ button {
    stroke: lightgrey;
    stroke-width: .5px;
    stroke-opacity: .5;
    touch-action: none;
    }

    .land_0 {
    fill: black;
    opacity: .8;
    touch-action: none;
    }

    .land_1 {
    fill: lightgreen;
    opacity: .8;
    touch-action: none;
    }

    .large_land_0 {
    8 changes: 8 additions & 0 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -56,6 +56,14 @@ var countries = {
    North_America: {
    lat: 48.2392291,
    lon: -98.9443219
    },
    North_Africa: {
    lat: 15.0,
    lon: 18.82
    },
    Russia: {
    lat: 60.65,
    lon: 95.995
    }
    }

  23. zanarmstrong revised this gist May 27, 2015. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -173,6 +173,9 @@ function setUpSmallMaps(lat, lon, name) {
    y: 0
    };
    var drag = d3.behavior.drag()
    .on("dragstart", function() {
    d3.event.sourceEvent.preventDefault();
    })
    .on("drag", function() {
    dragDistance.x = dragDistance.x + d3.event.dx;
    dragDistance.y = dragDistance.y + d3.event.dy;
  24. zanarmstrong revised this gist May 27, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion overlay.js
    Original file line number Diff line number Diff line change
    @@ -64,7 +64,7 @@ var state = {
    latLon: [countries.Africa, countries.North_America]
    }

    var transitionDuration = 800;
    var transitionDuration = 1000;

    if (window.location.hash.split("&").length != 0) {
    var windowState = window.location.hash.split("&");
  25. zanarmstrong revised this gist May 27, 2015. 2 changed files with 20 additions and 14 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button><button name="Africa,North_America">Africa vs North America</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    32 changes: 19 additions & 13 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -14,16 +14,7 @@ var margin = {
    top: 0
    }

    var state = {
    scale: 400,
    latLon: [{
    lat: 16.52865,
    lon: 20.3586336
    }, {
    lat: 59.2392291,
    lon: -98.9443219
    }]
    }


    var countries = {
    Australia: {
    @@ -57,9 +48,22 @@ var countries = {
    Brazil: {
    lat: -16.7833,
    lon: -53.2052
    },
    Africa: {
    lat: 6.52865,
    lon: 20.3586336
    },
    North_America: {
    lat: 48.2392291,
    lon: -98.9443219
    }
    }

    var state = {
    scale: 450,
    latLon: [countries.Africa, countries.North_America]
    }

    var transitionDuration = 800;

    if (window.location.hash.split("&").length != 0) {
    @@ -349,7 +353,7 @@ function arcTween(path, projection, new3Rotation) {
    }

    // slider
    var zoomRange = [200, 2000]
    var zoomRange = [220, 2000]

    var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20])

    @@ -358,10 +362,12 @@ var slider = d3.select("#tool")
    .append("input")
    .attr("type", "range")
    .style("margin-left", "750px")
    .attr("value", state.scale)
    .attr("value", function(d) {
    return state.scale
    })
    .attr("min", zoomRange[0])
    .attr("max", zoomRange[1])
    .attr("step", (zoomRange[1] - zoomRange[0]) / 100)
    .attr("step", (zoomRange[1] - zoomRange[0]) / 400)
    .on("input", slided);

    function slided(d) {
  26. zanarmstrong revised this gist May 27, 2015. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -60,6 +60,7 @@ var countries = {
    }
    }

    var transitionDuration = 800;

    if (window.location.hash.split("&").length != 0) {
    var windowState = window.location.hash.split("&");
    @@ -327,11 +328,11 @@ d3.selectAll("button").on("click", function() {
    state.latLon[1] = countries[this.name.split(",")[1]];

    // update small maps with new rotation
    updateSmallRotate(mapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], 750)
    updateSmallRotate(mapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], 750)
    updateSmallRotate(mapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], transitionDuration)
    updateSmallRotate(mapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], transitionDuration)
    // update large maps
    updateLarge(largeMapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], 750)
    updateLarge(largeMapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], 750)
    updateLarge(largeMapObjects, 0, [-state.latLon[0].lon, -state.latLon[0].lat, 0], transitionDuration)
    updateLarge(largeMapObjects, 1, [-state.latLon[1].lon, -state.latLon[1].lat, 0], transitionDuration)
    // update hash
    updateHash()
    })
  27. zanarmstrong revised this gist May 27, 2015. 2 changed files with 32 additions and 11 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="US,Brazil">US vs Brazil</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="Brazil,US">Brazil vs US</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    41 changes: 31 additions & 10 deletions overlay.js
    Original file line number Diff line number Diff line change
    @@ -27,8 +27,8 @@ var state = {

    var countries = {
    Australia: {
    lat: -25,
    lon: 135
    lat: -27,
    lon: 130
    },
    Antarctica: {
    lat: -90,
    @@ -44,7 +44,7 @@ var countries = {
    },
    US: {
    lat: 35.65,
    lon: -90.24
    lon: -97.24
    },
    Greenland: {
    lat: 65.88,
    @@ -56,7 +56,7 @@ var countries = {
    },
    Brazil: {
    lat: -16.7833,
    lon: -47.2052
    lon: -53.2052
    }
    }

    @@ -242,16 +242,26 @@ function setUpLargeMaps(lat, lon, name, svg) {

    // need to set update function
    function updateSmallRotate(mapObject, name, newRotate, transitionDuration) {
    mapObject[name].projection.rotate(newRotate)
    d3.selectAll(".graticule_" + name).transition().duration([transitionDuration]).attr("d", mapObject[name].path);
    d3.selectAll(".land_" + name).transition().duration([transitionDuration]).attr("d", mapObject[name].path);

    d3.selectAll(".graticule_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));

    d3.selectAll(".land_" + name)
    .transition()
    .duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));
    }

    // need to set update function
    function updateLarge(mapObject, name, newRotate, transitionDuration) {
    mapObject[name].projection.rotate(newRotate)
    d3.selectAll(".large_land_" + name).transition().duration([transitionDuration]).attr("d", mapObject[name].path);
    d3.selectAll(".boundary_" + name).transition().duration([transitionDuration]).attr("d", mapObject[name].path);
    d3.selectAll(".large_land_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));;
    d3.selectAll(".boundary_" + name)
    .transition().duration([transitionDuration])
    .attrTween("d", arcTween(mapObject[name].path, mapObject[name].projection, newRotate));;
    }

    function updateLargeScale(mapObjects, newScale) {
    @@ -326,6 +336,17 @@ d3.selectAll("button").on("click", function() {
    updateHash()
    })

    // transition
    function arcTween(path, projection, new3Rotation) {
    return function(d, i, a) {
    var interpolate = d3.interpolate(projection.rotate(), new3Rotation);
    return function(t) {
    projection.rotate(interpolate(t));
    return path(d);
    }
    }
    }

    // slider
    var zoomRange = [200, 2000]

  28. zanarmstrong revised this gist May 27, 2015. 2 changed files with 6 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button><button name="US,Brazil">US vs Brazil</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    6 changes: 5 additions & 1 deletion overlay.js
    Original file line number Diff line number Diff line change
    @@ -43,7 +43,7 @@ var countries = {
    lon: 46.7
    },
    US: {
    lat: 38.65,
    lat: 35.65,
    lon: -90.24
    },
    Greenland: {
    @@ -54,6 +54,10 @@ var countries = {
    lat: -18.97,
    lon: -61.479
    },
    Brazil: {
    lat: -16.7833,
    lon: -47.2052
    }
    }


  29. zanarmstrong revised this gist May 27, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -14,7 +14,7 @@ <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <h2>What's going on?</h2>
    <p>The earth is a sphere, but maps are flat. This means that when we create maps, we have to somehow flatten the sphere. This flattening must create some distortion. This distortion could affect the relative angles, shapes of countries, or sizes, or all of these. While we can't avoid distortion, we can flatten the sphere in different ways, based on the "projection" we choose, and depending on what we're going to use the map for.</p>
    <p>One of the most commonly used projections is the <a href="http://en.wikipedia.org/wiki/Mercator_projection">Mercator projection</a>. The Mercator was incredibly useful for ocean navigation because it keeps angles accurate, making it easier for sailers to take compass bearings and navigate the open ocean. The (significant) downside is that it distrorts how large things are. That said, many maps found online, like Google Maps, Open Street Maps, Mapquest, and others, use a variation of Mercator known as "Web Mercator. Advantages include the fact that north is "up" and that meridians (lines of longitude) are equally spaced vertical lines."</p>
    <p>Many of us imagine the world as it was depited on the map on the wall in our grade school classrooms or the maps we use online. But, this can lead to incorrect assumptions about the world around us. I was inspired to create this after reading <a href="https://iansierraleone2015.wordpress.com/">a friend's account of his time fighting Ebola in Sierra Leone</a>. He was frustrated with misunderstanding about the disease, including that a "school in New Jersey that panicked and refused to admit two elementary school children from Rwanda. Never mind that Rwanda is 2,600 miles from the epidemic area in West Africa. That’s the distance from my apartment in DC to Lake Tahoe." Misunderstanding scale in the world around us can have a real impact on our opinions and decisions. I hope that this website plays a small part in helping us better understand our wonderful world.</p>
    <p>Many of us imagine the world as it was depicted on the map on the wall in our grade school classrooms or the maps we use online. But, this can lead to incorrect assumptions about the world around us. I was inspired to create this after reading <a href="https://iansierraleone2015.wordpress.com/">a friend's account of his time fighting Ebola in Sierra Leone</a>. He was frustrated with misunderstanding about the disease, including that a "school in New Jersey that panicked and refused to admit two elementary school children from Rwanda. Never mind that Rwanda is 2,600 miles from the epidemic area in West Africa. That’s the distance from my apartment in DC to Lake Tahoe." Misunderstanding scale in the world around us can have a real impact on our opinions and decisions. I hope that this website plays a small part in helping us better understand our wonderful world.</p>
    <p>If you liked this, you might also enjoy the <a href="http://kai.subblue.com/en/africa.html">True Size of Africa</a> or the <a href="https://gmaps-samples.googlecode.com/svn/trunk/poly/puzzledrag.html">Mercator Puzzle</a>.</p>
    </div>
    <script src="http://d3js.org/d3.v3.min.js"></script>
  30. zanarmstrong revised this gist May 26, 2015. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    <h2>Is Africa bigger than North America?</h2>
    <p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p>
    <p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antartica">Australia vs Antartica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button></div>
    <div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="US,Australia">US vs Australia</button><button name="Greenland,South_America">Greenland vs South America</button></div>
    <h3>Drag on the small world maps to compare different parts of the world.</h3>
    <div id="tool"></div>
    <br><br>
    2 changes: 1 addition & 1 deletion overlay.js
    Original file line number Diff line number Diff line change
    @@ -30,7 +30,7 @@ var countries = {
    lat: -25,
    lon: 135
    },
    Antartica: {
    Antarctica: {
    lat: -90,
    lon: 0
    },