Fork me on GitHub

Version 0.1.6 - 10 Color Regions.

Ten color regions.

comment count

Test Case:

This map is generated from data in this Google Docs Spreasheet:
https://docs.google.com/spreadsheet/pub?key=0AhLWjwzZgPNGdEFib2pYeVg1VEU5U0w0MXV5Y254NlE&output=html

Code:

<p>
  This map is generated from data in this Google Docs Spreasheet:<br />
  <a href="https://docs.google.com/spreadsheet/pub?key=0AhLWjwzZgPNGdEFib2pYeVg1VEU5U0w0MXV5Y254NlE&output=html">https://docs.google.com/spreadsheet/pub?key=0AhLWjwzZgPNGdEFib2pYeVg1VEU5U0w0MXV5Y254NlE&output=html</a>
</p>

<!-- These are shims for IE6,7,8 and the like "lesser-browsers" -->
<script src="/choropleth/js/es5-shim.min.js"></script>
<script src="/choropleth/js/json2.js"></script>

<!-- used for javascript mustache templating -->
<script src="/choropleth/js/mustache.js"></script>

<!-- You must set the height of the div for the map, yes this should be in an external file -->
<div id="map_test_1" style="height: 400px; margin: 3em 0;"></div>

<script src="/choropleth/js/tabletop.js"></script>
<script type="text/javascript" src="/choropleth/js/us-states.js"></script>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" />
<!--[if lte IE 8]>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.ie.css" />
<![endif]-->
<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script>
<script type="text/javascript">

  // Once all tests are complete this version is complete.
  // [Done] include all required .js files
  // [Done] use tabletop.js to load data table from google spreadsheet and turn geoJSON into real objects
  // [Done] make array to associate machine headers to human headers from spreadsheet
  // [Done] forEach spreadsheet-data-row display geoJSON object to map
  // [Done] forEach spreadsheet-data-row update the map control layer
  // [TODO] Add feedback to the control layer to show loading of map.


  // This should probably change to a jQuery on.doc.load thing...
  window.onload = function() { init() };

  // Initialize the different constants that this uses to get information from a Google-Drive spreadsheet
  var mapKey = "0AhLWjwzZgPNGdEFib2pYeVg1VEU5U0w0MXV5Y254NlE";
  var sheetName = 'Hunger Data'
  var geoJSONHeaderName = "ubergeojson";
  var headersSheetName = "uberMap_meta-data";
  var choroplethRangeHeaderName = "foodinsecurityrate";
  var choroplethRangeHigh = 0;
  var choroplethRangeLow = 0;
  var choroplethRangeSteps = [];

  // Initialize map specific variables
  var defaultMapCenter = [41.05, -77.37]; // A good way to get this number is go to maps.google.com zoom to where you want to center the map - right click and "Drop LatLng marker" copy and paste that little set of numbers in here just the way it is.
  var defaultZoomLevel = 7; // Self explanitory: Sets the zoom level from 0 to 18 usually of how close you want to be to the earth when the map loads.  For Pennsylvania it's ~7

  // Constants that are needed in multiple places in the app below.
  var sheetHeaders = [];
  var masterGeoJSON = {
    "type": "FeatureCollection",
    "features": []
    };
  var geojson;
  var controlPanelTemplate = "{{={u{ }u}=}}";

  // init() is where we actually go to the Google-Drive spreadsheet and load the data in.
  function init() {
    Tabletop.init( { key: mapKey,
                     callback: processSpreadsheetData,
                     wanted: [sheetName, headersSheetName]
                   });
  }

  // This is where we actually process all of the data from the Google-Drive spreadsheet
  // and populate the masterGeoJSON object to get it ready for the map display function below.
  function processSpreadsheetData(data, tabletop) {

    var choroplethRangeCandidate = [];

    // I leave these here to toggle the ability to check out these variables in the browser
    // - so I can see what we're actually pulling from the Google-Drive spreadsheet.

    // --- Before data processing variables --- //

    //console.log(data);
    //console.log(tabletop.sheets(headersSheetName));
    //console.log(tabletop.sheets(sheetName));
    //console.log(tabletop.sheets(sheetName).elements);

    // ---

    // this creates a sheetHeaders[] object that holds two different
    // - properties: humanReadable and machineReadable
    // - sheetHeaders[] is an array that we can step through using forEach (thank you ES5)
    // - to display all of the map properties for each map object when we get to updating the map control layer.
    tabletop.sheets(headersSheetName).elements.forEach( function(element, index) {
      sheetHeaders[index] = {
        machineReadable: tabletop.sheets(sheetName).column_names[index],
        humanReadable: element.uberheaderlabel
      };
    });

    // browser-view toggle for debugging:
    console.log("Headers:");
    console.log(sheetHeaders);

    tabletop.sheets(sheetName).elements.forEach(function(element, rowIndex) {
      masterGeoJSON.features[rowIndex] = JSON.parse(element[geoJSONHeaderName]).features[0];
      tabletop.sheets(sheetName).column_names.forEach(function(headerName, headerIndex) {
        if (headerName !== geoJSONHeaderName) {
          masterGeoJSON.features[rowIndex].properties[headerName] = element[headerName];
        } else {
          // Do nothing
        }
      });
      //console.log("County Name Check: " + masterGeoJSON.features[rowIndex].properties['countyname'] + " - " + masterGeoJSON.features[rowIndex].properties['name']);
      choroplethRangeCandidate[rowIndex] = parseFloat(masterGeoJSON.features[rowIndex].properties[choroplethRangeHeaderName]);
    });

    choroplethRangeHigh = Math.max.apply(null, choroplethRangeCandidate);
    choroplethRangeLow = Math.min.apply(null, choroplethRangeCandidate);
    for (i=0;i<10;i++) {
      choroplethRangeSteps[i] = parseFloat(choroplethRangeLow + (((choroplethRangeHigh - choroplethRangeLow) / 10) * i));
    }
    console.log("Max Choropleth Range: " + choroplethRangeHigh);
    console.log("Min Choropleth Range: " + choroplethRangeLow);
    console.log("Steps = " + choroplethRangeSteps);


    // More browser-view toggles for debugging:

    // --- After data processing variables --- //

    //console.log(data);
    console.log("masterGeoJSON:");
    console.log(masterGeoJSON);
    //console.log(statesData);

    //Build the mustache template
    controlPanelTemplate += "<ul>";

    sheetHeaders.forEach( function(sheetHeader, sheetHeaderIndex){
      if (sheetHeader.machineReadable !== geoJSONHeaderName) {
        controlPanelTemplate += "<li>" +
          '<span class="map-data-property label">' + sheetHeader.humanReadable + "</span>" +
          ": " + 
          '<span class="map-data-property value">' + "{u{" + sheetHeader.machineReadable + "}u}" + "</span>" +
          "</li>";
      }
    });

    controlPanelTemplate += "</ul>";

    console.log(controlPanelTemplate);

    // ---

    // Now that we have actually loaded all of the data from the Google-Drive spreadsheet
    // - go ahead and load masterGeoSJON up to the map.
    loadMapData(masterGeoJSON);
  }

  // Setup the map to center where you would like it to.  You can always to go maps.google.com and right click anywhere on the map and "Drop LatLng Marker".
  var map = L.map('map_test_1').setView(defaultMapCenter, defaultZoomLevel);

  // This is where you get your map tiles.  You can get your own free API key from cloudmade.com
  // - please replace my key with yours if you're using this code in your own project.
  var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
    key: 'c5007019bb4e4787afb0135c36690912',
    styleId: 22677
  }).addTo(map);

  // This is where we decide which colors the polygons we draw on the map will be.
  // - this needs to be re-written before version 1.0 to calculate the proper range
  // - from the data provided automatically to provide a 5 to 10 color change range.

  // [TODO] re-write getColor() to calculate the 5 to 10 color step automatically based on the data from the spreadsheet.
  function getColor(d) {
    return d > choroplethRangeSteps[9]   ? '#F54002' :
           d > choroplethRangeSteps[8]   ? '#F64B06' :
           d > choroplethRangeSteps[7]   ? '#F46817' :
           d > choroplethRangeSteps[6]   ? '#F67124' :
           d > choroplethRangeSteps[5]   ? '#F78931' :
           d > choroplethRangeSteps[4]   ? '#F9913E' :
           d > choroplethRangeSteps[3]   ? '#FAB247' :
           d > choroplethRangeSteps[2]   ? '#FCBA51' :
           d > choroplethRangeSteps[1]   ? '#FDB96F' :
                                           '#FFC77F';
  }

  // [TODO] re-write this to get the styles from a stylesheet instead of hard-coding them here.
  // ------  Maybe I won't actually do this since some of these don't really match a CSS standard.
  // ------  Maybe the we will css standard the colors?  Some of these things can be set as
  // ------  variables at the top of this document... perhaps we'll just set it there due to the
  // ------  styleing constraints set forth from our leaflet.js buddies

  function style(feature) {
    return {
      fillColor: getColor(feature.properties.foodinsecurityrate),
      weight: 1,
      opacity: 1,
      color: 'white',
      dashArray: '3',
      fillOpacity: 0.7
    };
  }

  var info = L.control();

  info.onAdd = function (map) {
    this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
    this.update();
    return this._div;
  };

  // method that we will use to update the control based on feature properties passed
  info.update = function (props) {

    var grades = [0, 10, 20, 50, 100, 200, 500, 1000];

    if (props) {
      // This is called with when the mouse over an element.
      this._div.innerHTML = Mustache.render(controlPanelTemplate, props);
    } else {
      // [TODO] Display Map-Legend when not hovering
    }
  };

  info.addTo(map);


  // This is what happens when your mouse hovers over a map element.
  function highlightFeature(e) {
    var layer = e.target;

    layer.setStyle({
      fillColor: '#78A700',
      dashArray: '',
      fillOpacity: 0.7
    });

    if (!L.Browser.ie && !L.Browser.opera) {
      layer.bringToFront();
    }
    info.update(layer.feature.properties);
  }

  // This is what happens when your mouse goes away from an element.
  function resetHighlight(e) {
    geojson.resetStyle(e.target);
    info.update();
  }

  // This is where we assign the behavior to each map element we draw.
  // [TODO] Add on click event
  //        - zoom in when first clicked - then update map control layer
  //        - zoom out when clicked again to pan back to the overall map view
  function onEachFeature(feature, layer) {
    layer.on({
      mouseover: highlightFeature,
      mouseout: resetHighlight
    });
  }

  // This is the bit where we load all the geoJSON information into the map.
  function loadMapData(geoJSONData) {
    geojson = L.geoJson(geoJSONData, {
      style: style,
      onEachFeature: onEachFeature
    }).addTo(map);
  }

</script>

<style type="text/css">
.info {
  padding: 6px 8px;
  font: 14px/16px Arial, Helvetica, sans-serif;
  background: white;
  background: rgba(255,255,255,0.8);
  box-shadow: 0 0 15px rgba(0,0,0,0.2);
  border-radius: 5px;
}
.info h4 {
  margin: 0 0 5px;
  color: #777;
}
.legend-i {
  clear: both;
  width: 18px;
  height: 18px;
  float: left;
  margin-right: 8px;
  opacity: 0.7;
}
.legend-label {
  line-height: 18px;
}
</style>