block by enjalot a50efa43ec84aba00e33349c8da7fc9a

Earthquake Map with fisheye (projected) canvas

Full Screen

Render with canvas instead of SVG for better re-rendering performance. Makes the awesome fisheye effect a little bit smoother.

forked from pnavarrc‘s block: Earthquake Map

forked from jonpage‘s block: Earthquake Map

forked from jgondin‘s block: Earthquake Map

forked from Fil‘s block: Earthquake Map with fisheye (polar)

forked from Fil‘s block: Earthquake Map with fisheye (projected) + throttle

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Earthquake Map</title>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
  <script src="fisheye.js"></script>
  <script src="debounce.js"></script>
</head>
<body>

  <style>
  body {
    margin: 0;
  }

  svg {
    background-color: #111;
    cursor: pointer;
  }

  .land {
    fill: #333;
    stroke: #555;
    stroke-width: 1;
  }

  .earthquake {
    fill-opacity: 0.6;
  }
  </style>

  <div id="map-container"></div>

  <script>
// Set the dimensions of the map
var width = 960,
    height = 480;

// Create a selection for the container div and append the svg element
var div = d3.select('#map-container'),
    canvas = div.append('canvas');

var land;
var earthquakes;

// Set the size of the SVG element
canvas.attr('width', width).attr('height', height)
var c = canvas.node();
c.width = width;
c.height = height;
var ctx = c.getContext('2d');
    
ctx.globalCompositeOperation = "lighter"

// Create and configure a geographic projection
var _projection = d3.geo.equirectangular()
    .translate([width / 2, height / 2]);

var fisheye = d3.fisheye();

// to start, unclutter JAPAN
var DEG = 180 / Math.PI,
    JAPAN = [138, 36],
    RADIUS = 90,
    POWER = 2.75;
fisheye.center(JAPAN).radius(RADIUS).power(POWER);

var projection = d3.geo.projection(function (λ, φ) {
        λ *= DEG, φ *= DEG;
        var p = fisheye(_projection([λ, φ]));
        return [p[0], -p[1]];
    })
    .scale(1)
    .translate([width / 2, height / 2]);




// Create and configure a path generator
var pathGenerator = d3.geo.path()
    .projection(projection)
    .context(ctx)

//var land = circles = svg.select('nada');
var movehandle = helpers.throttle(function () {
  ctx.fillStyle = "#111"
  ctx.clearRect(0, 0, width, height)
  ctx.fillRect(0, 0, width, height)
  renderLand();
  renderEarthquakes();
    //land.attr('d', pathGenerator);
  /*
    circles
        .each(function (d) {
            var pos = (projection(d.coords));
            //console.log(pos);
            d3.select(this)
                .attr('cx', function (d) {
                    return pos[0];
                })
                .attr('cy', function (d) {
                    return pos[1];
                });
        });
  */
    //land.attr('d', pathGenerator);
}, 10, this);

canvas.on("mousemove", function () {
    fisheye.center(d3.mouse(this));
    movehandle();
});
    
ctx.fillStyle = "#111"
ctx.fillRect(0, 0, width, height)


// Retrieve the geographic data asynchronously
d3.json('land.geojson', function (err, data) {

    // Throw errors on getting or parsing the file
    if (err) {
        throw err;
    }

    // Land
    land = data;
  /*
    land = svg.select('g.map').selectAll('path.land').data([data]);

    land.enter().append('path').classed('land', true);
    land.attr('d', pathGenerator);
    land.exit().remove();
    */
  renderLand()
});
    
function renderLand() {
  ctx.fillStyle = "#333";
  ctx.strokeStyle = "#555";
  land.features.forEach(function(d) {
    ctx.beginPath();
    pathGenerator(d);
    ctx.fill();
    ctx.stroke();
  })
}   


// Load the Earthquake data
// //earthquake.usgs.gov/earthquakes/search/
// //earthquake.usgs.gov/earthquakes/map/doc_aboutdata.php
d3.csv('earthquakes.csv', function (err, csvdata) {
  console.log("csv", csvdata)
  if (err) {
      throw err;
  }

  // Parse the data
  csvdata.forEach(function (d) {
      d.coords = [+d.longitude, +d.latitude];
      d.magnitude = +d.mag;
  });

  csvdata.sort(function (a, b) {
      return a.magnitude > b.magnitude ? -1 : 1;
  });

  earthquakes = csvdata;
  renderEarthquakes();
})
function renderEarthquakes() {
  // Radius scale
  var rScale = d3.scale.sqrt()
      .domain(d3.extent(earthquakes, function (d) {
          return d.magnitude;
      }))
      .range([1, 10]);

  var cScale = d3.scale.sqrt()
      .domain(rScale.domain())
      .range(['#ECD078', '#C02942']);

  earthquakes.forEach(function(d) {
    var p = projection(d.coords)
    var color = d3.rgb(cScale(d.magnitude))

    ctx.fillStyle = "rgba(" + [color.r, color.g, color.b, 0.2] + ")"
    ctx.beginPath();
    ctx.arc(p[0], p[1], rScale(d.magnitude), 0, 2 * Math.PI)
    ctx.fill();
  })
}
  	/*
    circles = svg.select('g.dots').selectAll('circle.earthquake').data(csvdata);

    circles.enter().append('circle')
        .classed('earthquake', true);

    circles
        .attr('r', function (d) {
            return rScale(d.magnitude);
        })
        .attr('cx', function (d) {
            return projection(d.coords)[0];
        })
        .attr('cy', function (d) {
            return projection(d.coords)[1];
        })
        .attr('fill', function (d) {
            return cScale(d.magnitude);
        });

    circles.exit().remove();
    */
;

  </script>

</body>
</html>

debounce.js

/**
 * Created by thephpjo on 21.04.14.
 * http://demo.nimius.net/debounce_throttle/
 */


var helpers = {

    /**
     * debouncing, executes the function if there was no new event in $wait milliseconds
     * @param func
     * @param wait
     * @param scope
     * @returns {Function}
     */
    debounce: function (func, wait, scope) {
        var timeout;
        return function () {
            var context = scope || this, args = arguments;
            var later = function () {
                timeout = null;
                func.apply(context, args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    },

    /**
     * in case of a "storm of events", this executes once every $threshold
     * @param fn
     * @param threshhold
     * @param scope
     * @returns {Function}
     */
    throttle: function (fn, threshhold, scope) {
        threshhold || (threshhold = 250);
        var last,
            deferTimer;
        return function () {
            var context = scope || this;

            var now = +new Date,
                args = arguments;
            if (last && now < last + threshhold) {
                // hold on to it
                clearTimeout(deferTimer);
                deferTimer = setTimeout(function () {
                    last = now;
                    fn.apply(context, args);
                }, threshhold);
            } else {
                last = now;
                fn.apply(context, args);
            }
        };
    }
}

fisheye.js

(function() {
  d3.fisheye = function() {
    var radius = 200,
        power = 2,
        k0,
        k1,
        center = [0, 0];

    function fisheye(d) {
      var dx = d[0] - center[0],
          dy = d[1] - center[1],
          dd = Math.sqrt(dx * dx + dy * dy);
      if (dd >= radius) return d;
      var k = k0 * (1 - Math.exp(-dd * k1)) / dd * .75 + .25;
      return [center[0] + dx * k, center[1] + dy * k];
    }

    function rescale() {
      k0 = Math.exp(power);
      k0 = k0 / (k0 - 1) * radius;
      k1 = power / radius;
      return fisheye;
    }

    fisheye.radius = function(_) {
      if (!arguments.length) return radius;
      radius = +_;
      return rescale();
    };

    fisheye.power = function(_) {
      if (!arguments.length) return power;
      power = +_;
      return rescale();
    };

    fisheye.center = function(_) {
      if (!arguments.length) return center;
      center = _;
      return fisheye;
    };

    return rescale();
  };
})();