Question

I am making a topojson map with d3.js. I have three data sets within one big topojson that draw different maps, and I would like to swap between the maps on mouseclick.

I thought I could achieve this by adding a function to the mouseclick event and put the result in the .datum() operator.


UPDATE: here is the working code, thanks Lars!

var mapPath = d3.geo.path().projection(mapProjection),
    jsondata,
    jsonobject,
    jsonobjectkeys,
    numberOfKeys,
    currentMap
    mapNumber;

d3.json("test.json", function(error, json){
      if (error) return console.warn(error);
      jsondata = json; //Store data in the variable "jsondata"
      jsonobject = json.objects;
      jsonobjectkeys = [];
      numberOfKeys = 0;

      //Get the maps(keys) from the jsonobject
      for(var k in jsonobject) jsonobjectkeys.push(k);

      //Find number of objects in jsondata
      for (objects in jsonobject){
        if((jsonobject).hasOwnProperty(objects)){
            numberOfKeys++;
        }
      }

mapNumber = jsonobjectkeys[0];

currentMap = eval("jsonobject." + (mapNumber));

//Map
var mapSVG = d3.select(".the_map")
                .append("svg")
                .attr("width", mapW)
                .attr("height", mapH);
    mapSVG.append("path")
            .datum(topojson.object(jsondata, currentMap))
            .attr("d", mapPath)
            .attr("width", mapW)
            .attr("height", mapH)
            .attr("class", "land");

//Timeline

//Create scale
var xScale = d3.scale.linear()
    .domain([0, (numberOfKeys-1)])
    .range([timelinePadding, timelineW - timelinePadding]);

//Axis
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .ticks(numberOfKeys-1);

var timeline = d3.select("#timeline")
        .append("svg")
        .attr("width", timelineW)
        .attr("height", timelineH);
    timeline.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(0, " + timelinePadding + ")")
        .call(xAxis);
    timeline.selectAll("circle")
       .data(jsonobjectkeys)
       .enter()
       .append("circle")
       .attr("width", timelineW)
       .attr("height", timelineH)
       .attr("cx", function(d,i){return xScale(i);})
       .attr("cy", timelinePadding)
       .attr("r", 7)
       .attr("class", "events")
       .style("cursor", "hand")
       .on("click", function(d){
            redrawMap(d);
       });

 function redrawMap(i){
            currentMap = eval("jsonobject." + (i));
    //Update
    mapSVG.selectAll("path")
    .datum(topojson.object(jsondata, currentMap))
    .attr("d", mapPath);
}

});

Original, not working code:

var mapPath = d3.geo.path().projection(mapProjection),
    jsondata,
    jsonobject,
    jsonobjectkeys,
    numberOfKeys;


d3.json("test.json", function(error, json){
      if (error) return console.warn(error);
      jsondata = json; //Store data in the variable "jsondata"
      jsonobject = json.objects;
      jsonobjectkeys = [];
      numberOfKeys = 0;

      //Get the maps(keys) from the jsonobject
      for(var k in jsonobject) jsonobjectkeys.push(k);

      //Find number of objects in jsondata
      for (objects in jsonobject){
        if((jsonobject).hasOwnProperty(objects)){
            numberOfKeys++;
        }
      }

var mapNumber = jsonobjectkeys[0];

var currentMap = eval("jsonobject." + (mapNumber));

currentMapData(mapNumber);

//Map
var mapSVG = d3.select(".the_map")
                .append("svg")
                .attr("width", mapW)
                .attr("height", mapH);
    mapSVG.append("path")
            .datum(topojson.object(jsondata, currentMap))
            .attr("d", mapPath)
            .attr("width", mapW)
            .attr("height", mapH)
            .attr("class", "land");

//Timeline

//Create scale
var xScale = d3.scale.linear()
    .domain([0, (numberOfKeys-1)])
    .range([timelinePadding, timelineW - timelinePadding]);

//Axis
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .ticks(numberOfKeys-1);

var timeline = d3.select("#timeline")
        .append("svg")
        .attr("width", timelineW)
        .attr("height", timelineH);
    timeline.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(0, " + timelinePadding + ")")
        .call(xAxis);
    timeline.selectAll("circle")
       .data(jsonobjectkeys)
       .enter()
       .append("circle")
       .attr("width", timelineW)
       .attr("height", timelineH)
       .attr("cx", function(d,i){return xScale(i);})
       .attr("cy", timelinePadding)
       .attr("r", 7)
       .attr("class", "events")
       .style("cursor", "hand")
       .on("click", function(d,i){
            currentMapData(i);
       });

 function currentMapData(i){
            mapNumber = jsonobjectkeys[i];
    console.log("showing this map: " + mapNumber);
    currentMap = eval("jsonobject." + (mapNumber));
    return currentMap;
}

});
Was it helpful?

Solution

It looks like you're binding the object keys as data, but expecting to receive an index in currentMapData(). So the error that you're seeing is caused by you attempting to use a key to index into an array. You can pass the index instead of the key by using the second argument of the onclick handler, i.e. replace

.on("click", function(d){
     currentMapData(d);
});

with

.on("click", function(d, i){
     currentMapData(i);
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top