1

I have been struggling for a few days trying to use dc.js with d3.js projection to draw a map of South Africa and the provinces. I have exhausted my search as most results incorporate the path used for SVG when not using dc.js and I can't seem to find a suitable example for correcting a projection in dc.js.

I can't seem to find the map thats being drawing and I don't know how to correct my projection.

I really really don't know what i'm missing, and anyone that can assist will be appreciated.

UPDATE: I have geoJson that ive tested in mapshaper and it works so the geojson is fine. I am just struggling with the projection.

 zaMap = zaMapString

//new array
var zaMapData = [];
    for(var p in zaMap["features"])
        {
            console.log("ndx2 province data " + zaMap["features"][p]["properties"]["name"]);
            zaMapData.push({

                province: zaMap["features"][p]["properties"]["name"],
                donation: 1000
            })
        };

//crossfilter instance
var ndx2 = crossfilter(zaMapData);

//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
        return d["donation"];
        });

//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");

//start of chart code using d3 and dc


zaChart.dimension(provinceDim)
    .group(donationsByProvince)
    .width(1000)
    .height(330)
    .colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
    .projection(d3.geo.mercator()
                .scale(26778)
                .translate([8227, 3207]))
    .overlayGeoJson(zaMap["features"], "name", function (d) {
        return d.properties.name;
    });

    dc.renderAll();
    $("#progress").css({"display": "none"});
})

UPDATE 2: I switched from fiddle to codepen so I could upload the geoJson file as a asset. The geoJson takes a while to load but using code from an existing stackoverflow question, I have gotten the map to draw and projection to correct itself automatically. The d3.js function is not wrapping the dc.js to tie in with crossfilter.js as yet but I am working on that. But this is progress :)

Community
  • 1
  • 1
Jimmypoo
  • 21
  • 3
  • can you put this up on a fiddle. – Cyril Cherian Oct 01 '15 at 11:20
  • Hi Cyril, here is the link: http://jsfiddle.net/Jimmypoo/f67xo5ry/1/ I am unfamiliar with fiddle but put everything in, there seems to be an issue parsing the string variable with geoJson, which on my project I don't do as I use a Json file. – Jimmypoo Oct 01 '15 at 15:15
  • I've also tried it with only d3.js here http://jsfiddle.net/Jimmypoo/9z20ezxr/ but having strange error in console about access the "features" of my geoJson. But the geoJson is accessible as shown here in the console.log http://jsfiddle.net/Jimmypoo/jd5knks6/ – Jimmypoo Oct 01 '15 at 19:27

2 Answers2

2

In http://jsfiddle.net/Jimmypoo/f67xo5ry/1/, you are trying to use JSON.parse to parse an zaMapString, which is already a JS object. You don't need to parse it, it's not a string.

Secondly, d3.json is meant for passing in a remote URL, which d3 grabs for you. You are trying to pass in a JS object, which already exists. So you can remove that function, and simply use .overlayGeoJson(zaMap["features"], "name", function (d) { inside.

You also forgot to include jQuery, yet you use it in $("#progress").css({"display": "none"});. You'll need to wrap the entire JS section in a $(document).ready as well.

Also, you are including the scripts multiple times, in both minified and unminified forms.You only need one instance of each library.

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/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://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.js"></script>

<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.min.js"></script>

You are also trying to include dc's CSS as JavaScript.

<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.css"></script>

It should be added in JsFiddle's left-hand side resource panel instead.

I also don't think assigning #map directly to the body of your document is going to make things easier for you either..would recommend including something interior of that like <div id="map" style="width:100%;height:300px"></div>

These suggestions don't solve all your problems but get you most of the way along.You still have projection issues. Here is an forked fiddle to move from - http://jsfiddle.net/uggtjem6/

snkashis
  • 2,951
  • 1
  • 18
  • 35
  • Thanks so much @snkashis will work from your fiddle as a starting point. I think i must've been in a bit of a flat spin trying everything. Your answer will sure help me get there! Really appreciated. – Jimmypoo Oct 12 '15 at 08:24
  • Thanks for you help @snkashis, I started using [codepen](http://codepen.io/jimmypoo/pen/epEXLG) to be able to use uploaded resources, and it made a lot more sense. The projection is also working now. – Jimmypoo Oct 13 '15 at 17:57
0

I have gotten the geoJson to work with d3.js, dc.js and crossfiler.

 var width  = 300;
  var height = 400;
var zaMapData = [];

//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
  d3.json("https://s3-us-west-2.amazonaws.com/s.cdpn.io/384835/layer1.json", function(json) {

  var zaMap = JSON.stringify(json);
    console.log(zaMap);

      for (var i = 0; i < json.features.length; i++) { 


            console.log("ndx2 province data " + json["features"][i]["properties"]["PROVINCE"]);
            zaMapData.push({

                province: json["features"][i]["properties"]["PROVINCE"],
                donation: i*1000
            })
        };

//crossfilter instance
var ndx2 = crossfilter(zaMapData);

//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
        return d["donation"];
        });
var max_province = donationsByProvince.top(1)[0].value;
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);


   //create dc.js chart   
      zaChart.dimension(provinceDim)
    .group(donationsByProvince)
    .width(width)
    .height(height)
    .colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
      .colorDomain([0, max_province])
    .projection(d3.geo.mercator()
              .center(center)
        .scale(scale)
              .translate(offset))
    .overlayGeoJson(json["features"], "PROVINCE", function (d) {
        return d.properties.PROVINCE;
    })
      .title(function (p) {
        return "Province: " + p["key"]
        + "\n"
        + "Total Donations: R " + Math.round(p["value"])
      });

    dc.renderAll();
    });

My codepen here.

Jimmypoo
  • 21
  • 3