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;
}
});
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);
});