markmarkoh / datamaps

Customizable SVG map visualizations for the web in a single Javascript file using D3.js
http://datamaps.github.io
MIT License
3.78k stars 1.01k forks source link

Choropleth auto-calculated color + Fetching remote data #264

Closed Serse closed 8 years ago

Serse commented 8 years ago

I'm trying to create a Choropleth with auto-calculated color and data taken from a json file, but it doesn't work. Is it possible to do it? (reference https://github.com/markmarkoh/datamaps/blob/master/src/examples/highmaps_world.html and http://bl.ocks.org/markmarkoh/11331459).

workgena commented 8 years ago

Please, provide example of your JSON-data

Serse commented 8 years ago

Javascript:

var series = [];
    var dataset = {};

    // We need to colorize every country based on "numberOfWhatever"
    // colors should be uniq for every value.
    // For this purpose we create palette(using min/max series-value)
    var onlyValues = series.map(function(obj){ return obj[1]; });
    var minValue = Math.min.apply(null, onlyValues),
        maxValue = Math.max.apply(null, onlyValues);

    // create color palette function
    // color can be whatever you wish
    var paletteScale = d3.scale.linear()
            .domain([minValue,maxValue])
            .range(["#fbdcd2","#c0282d"]); // blue color

    // fill dataset in appropriate format
    series.forEach(function(item){ //
        var iso = item[0],
                value = item[1];
        dataset[iso] = { numberOfThings: value, fillColor: paletteScale(value) };
    });

    new Datamap({
    element: document.getElementById('container1'),
    scope: 'ita',
    responsive: true,
    setProjection: function (element) {
      var projection = d3.geo.mercator()
          .center([12.560077144500099, 41.287229413500036])
          .rotate([0, 0])
          .scale(6144.37545703623 / 3)
          .translate([275, 325]);
      var path = d3.geo.path()
          .projection(projection);

      return {path: path, projection: projection};
    },
    fills: { defaultFill: '#F5F5F5' },
        dataUrl: 'geo-1.json',
        dataType: 'json',
        data: {},
        geographyConfig: {
            borderColor: '#DEDEDE',
            highlightBorderWidth: 2,
            // don't change color on mouse hover
            highlightFillColor: function(geo) {
                return geo['fillColor'] || '#F5F5F5';
            },
            // only change border
            highlightBorderColor: '#B7B7B7',
            // show desired information in tooltip
            popupTemplate: function(geo, data) {
                // don't show tooltip if country don't present in dataset
                if (!data) { return ; }
                // tooltip content
                return ['<div class="hoverinfo">',
                    '<strong>', geo.properties.name, '</strong>',
                    '<br>Count: <strong>', data.numberOfThings, '</strong>',
                    '</div>'].join('');
            }
        }
    });

and JSON:

{ "LIGURIA": 75, "PIEMONTE": 150 }

workgena commented 8 years ago

It seems like this example is broke. Perhaps of recent updates. I'll investigate this today, and fix the example.

markmarkoh commented 8 years ago

@Serse The first section of code from the example is computing the colors based on the data it has access to at runtime. That code will run before the remote data is fetched, rendering it useless.

I recommend wrapping that code inside of a d3.json call, so something like this:

d3.json('geo-1.json', function(err, data) {
  if (err) return console.error(err);

  var series = data;
  // continue with the code you posted *inside* this function.
  // also remove 'dataUrl', since you'd be handling the remote fetching instead of datamaps
});

That only solves one problem (the one you posted about).

Although!

I see a second problem, the data you are fetching looks like it's trying to color in specific regions of Italy based on the region name, like LIGURIA. That's not how the data is set up for datamaps.ita.js, instead it's more like:

type":"Polygon","properties":{"name":"Cremona"},"id":"IT.CR","

So the right keys for your AJAX response would be:

{
   "IT.CR": 75,
   "IT.LO": 100
}

Here is a visual of how the Italian map data is split up: https://github.com/markmarkoh/datamaps/blob/master/src/js/data/ita.topo.json

workgena commented 8 years ago

I checked example in repo - it works.

@markmarkoh said right things about d3.json and using city ID - not name.

This is Italy example https://jsbin.com/wazuvowobi/1/edit?html,output works in Firefox, didn't check in other browsers I don't know how to make online example with JSON, so it only with static dataset.

If you have JSON-data in format like

{
   "IT.CR": 75,
   "IT.LO": 100
}

You'll need prepare dataset for datamaps in a different way (see example above).

markmarkoh commented 8 years ago

@workgena that's a fantastic example, thanks for putting that together.

@Serse let us know if you continue running into problems with getting an Italian map up and running.

Serse commented 8 years ago

Thank you very much @markmarkoh and @workgena, tomorrow I'll check your solutions.

BTW the json format wasn't a problem, I successfully transformed datamaps.ita.js from provincial to regional and it was working with static dataset

Serse commented 8 years ago

@markmarkoh @workgena I tried the d3.json solution but I've got this error: "TypeError: series.map is not a function"

workgena commented 8 years ago

"map" function only works with Array. If server returns a hash - this code should be rewritten in appropriate way. Final idea is to get data from server and convert them to an object(hash) like:

{
  "IT.CR": { "fillColor": "#42a844", numberOfWhatever: 75},
  "IT.LO": { "fillColor": "#8dc386", numberOfWhatever: 43 }
}
Serse commented 8 years ago

Ok, then I'm not able to do it...