block by wboykinm 8a87b89f3600a7175f27

D3 Globe

Full Screen

GISDevs Slack Channel Members

A map of GISDev participants, based on the excellent work of Marc Neuwirth.

script.js

$.getJSON(
"https://spreadsheets.google.com/feeds/list/1p0NKYGkH2MHcqzd7olnTbmLEexZ8-Uz7pTaK--1mPvI/1/public/values?alt=json",
  function(data) {
    // process google sheet into geojson
    var devGeojson = {
      type: 'FeatureCollection',
      features: []
    };
    var devs = data.feed.entry;
    for (var i in devs) {
      if (devs[i].gsx$latitude.$t) {
        var feature = {
          type: 'Feature',
          properties: {
            title: '<h1>' + devs[i].gsx$name.$t + '</h1>',
            'marker-color': '#227FBB',
            'marker-size': 'large',
            'marker-symbol': 'rocket',
            url: 'https://gisdevs.slack.com/team/' + devs[i].gsx$name.$t
          },
          geometry: {
            type: 'Point',
            coordinates: [devs[i].gsx$longitude.$t, devs[i].gsx$latitude.$t]
          }
        };
        devGeojson.features.push(feature);
      }
    }
  (function(w, d3, undefined) {
  "use strict";
  var width, height;

  function getSize() {
    width = w.innerWidth,
      height = w.innerHeight;
    if (width === 0 || height === 0) {
      setTimeout(function() {
        getSize();
      }, 100);
    } else {
      init();
    }
  }

  function init() {
    //Setup path for outerspace
    var space = d3.geo.azimuthal()
      .mode("equidistant")
      .translate([width/2, height/2]);
    space
      .scale(space.scale() * 3);
    var spacePath = d3.geo.path()
      .projection(space)
      .pointRadius(1);

    //Setup path for globe
    var projection = d3.geo.azimuthal()
      .mode("orthographic")
      .translate([width/2, height/2])
      .scale(300)
      .origin([-30,40])
    var scale0 = projection.scale();
    var path = d3.geo.path()
      .projection(projection)
      .pointRadius(2);

    //Setup path for devs
    var devPath = d3.geo.path()
      .projection(projection)
      .pointRadius(6);

    //Setup zoom behavior
    var zoom = d3.behavior
      .zoom(true)
      .translate(projection.origin())
      .scale(projection.scale())
      .scaleExtent([200, 800])
      .on("zoom", move);

    var circle = d3.geo.greatCircle()
      .origin([-30,40]);

    var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
        .call(zoom)
        .on("dblclick.zoom", null);

    //Create a list of random stars and add them to outerspace
    var starList = createStars(1000);
    var stars = svg.append("g")
      .selectAll("g")
      .data(starList)
      .enter()
      .append("path")
        .attr("class", "star")
        .attr("d", function(d) {
          spacePath.pointRadius(d.properties.radius);
          return spacePath(d);
        });
    svg.append("rect")
      .attr("class", "frame")
      .attr("width", width)
      .attr("height", height);

    //Create the base globe
    var backgroundCircle = svg.append("circle")
      .attr('cx', width / 2)
      .attr('cy', height / 2)
      .attr('r', projection.scale())
      .attr('class', 'globe')
      .attr("filter", "url(#glow)")
      .attr("fill", "url(#gradBlue)");

    var countryG = svg.append("g"),
      devG = svg.append("g"),
      globeFeatures,
      devFeatures;

    //Add all of the countries to the globe
    d3.json("world-countries.json", function(collection) {
      globeFeatures = countryG.selectAll(".feature")
        .data(collection.features);
      globeFeatures.enter().append("path")
        .attr("class", "feature")
        .attr("d", function(d) {
          return path(circle.clip(d));
        });
    });

    // add dev points to the globe
    //console.log(devGeojson.features);
    devFeatures = devG.selectAll(".dev")
      .data(devGeojson.features);
    devFeatures.enter()
      .append("path")
        .attr("class", "dev")
        .attr("d", function(d) {
          return devPath(circle.clip(d));
        });
        
    //Redraw all items with new projections
    function redraw() {
      globeFeatures.attr("d", function(d) {
        return path(circle.clip(d));
      });
      devFeatures.attr("d", function(d) {
        return devPath(circle.clip(d));
      });
      stars.attr("d", function(d) {
        spacePath.pointRadius(d.properties.radius);
        return spacePath(d);
      });
    }

    function move() {
      if (d3.event) {
        var scale = d3.event.scale;
        var origin = [d3.event.translate[0] * -1, d3.event.translate[1]];
        projection.scale(scale);
        space.scale(scale * 3);
        backgroundCircle.attr('r', scale);
        path.pointRadius(2 * scale / scale0);
        devPath.pointRadius(6 * scale / scale0);
        projection.origin(origin);
        circle.origin(origin);
        //globe and stars spin in the opposite direction because of the projection mode
        var spaceOrigin = [origin[0] * -1, origin[1] * -1];
        space.origin(spaceOrigin);
        redraw();
      }
    }

    function createStars(number) {
      var data = [];
      for (var i = 0; i < number; i++) {
        data.push({
          geometry: {
            type: 'Point',
            coordinates: randomLonLat()
          },
          type: 'Feature',
          properties: {
            radius: Math.random() * 1.5
          }
        });
      }
      return data;
    }

    function randomLonLat() {
      return [Math.random() * 360 - 180, Math.random() * 180 - 90];
    }
  }
  getSize();
}(window, d3));
});

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <svg id="defs">
        <defs>
            <linearGradient id="gradBlue" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" style="stop-color:#005C99;stop-opacity:1" />
                <stop offset="100%" style="stop-color:#0099FF;stop-opacity:1" />
            </linearGradient>
            <filter id="glow">
                <feColorMatrix type="matrix"
                    values=
                    "0 0 0 0   0
                     0 0 0 0.9 0
                     0 0 0 0.9 0
                     0 0 0 1   0"/>
                <feGaussianBlur stdDeviation="5.5" result="coloredBlur"/>
                <feMerge>
                    <feMergeNode in="coloredBlur"/>
                    <feMergeNode in="SourceGraphic"/>
                </feMerge>
            </filter>
        </defs>
    </svg>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="d3.custom.js"></script>
    <script src="script.js"></script>
</body>
</html>

style.css

body {
    background-color: #444444;
    background-image: -webkit-gradient(linear, left top, right bottom, from(#000), to(#333));
    background-image: -webkit-linear-gradient(left top, #000, #333);
    background-image: -moz-linear-gradient(left top, #000, #333);
    overflow: hidden;
}

#info {
    position: absolute;
    z-index: 10;
    left: 25px;
    top: 25px;
}

#defs {
    height: 0;
    width:0;
}

.frame {
    fill: none;
    pointer-events: all;
}

.feature {
    fill: #c2c2c2;
    stroke: #e2e2e2;
    stroke-width: .5px;
}

.dev {
  fill: #ff8e00;
  fill-opacity: 0.6;
  stroke: #333;
  stroke-width: 1px;
}

.globe {
    stroke: rgba(255, 255, 255, .5);
    stroke-width: .25px;
}

.star {
    fill: #fff;
    stroke: rgba(255, 255, 255, .5);
    stroke-width: .25px;
}