D3 skipping first two features in a TopoJson - javascript

I am creating an interactive visualization tool with D3. I just got my map working today and I noticed that the first two features from the topojson are not loading in.
The first two entries of the topojson look like this and geojson.io reads these countries in fine. (They appear on the map)
"objects":{"ne_10m_admin_0_countries":{"type":"GeometryCollection","geometries":[{"arcs":[[[0,1]],[[2,3,4,5]],[[6,7]],[[8,9]],[[10]],[[11]],[[12]],[[13]],[[14]],[[15]],[[16]],[[17]],[[18]],[[19]],[[20]],[[21]],[[22]],[[23]],[[24]],[[25]],[[26]],[[27]],[[28]],[[29]],[[30]],[[31]],[[32]],[[33]],[[34]],[[35]],[[36]],[[37]],[[38]],[[39]],[[40]],[[41]],[[42]],[[43]],[[44]],[[45]],[[46]],[[47]],[[48]],[[49]],[[50]],[[51]],[[52]],[[53]],[[54]],[[55]],[[56]],[[57]],[[58]],[[59]],[[60]],[[61]],[[62]],[[63]],[[64]],[[65]],[[66]],[[67]],[[68]],[[69]],[[70]],[[71]],[[72]],[[73]],[[74]],[[75]],[[76]],[[77]],[[78]],[[79]],[[80]],[[81]],[[82]],[[83]],[[84]],[[85]],[[86]],[[87]],[[88]],[[89]],[[90]],[[91]],[[92]],[[93]],[[94]],[[95]],[[96]],[[97]],[[98]],[[99]],[[100]],[[101]],[[102]],[[103]],[[104]],[[105]],[[106]],[[107]],[[108]],[[109]],[[110]],[[111]],[[112]],[[113]],[[114]],[[115]],[[116]],[[117]],[[118]],[[119]],[[120]],[[121]],[[122]],[[123]],[[124]],[[125]],[[126]],[[127]],[[128]],[[129]],[[130]],[[131]]],"type":"MultiPolygon","properties":{"featurecla":"Admin-0 country","scalerank":5,"LABELRANK":2,"SOVEREIGNT":"Indonesia","SOV_A3":"IDN","ADM0_DIF":0,"LEVEL":2,"TYPE":"Sovereign country","ADMIN":"Indonesia","ADM0_A3":"IDN","GEOU_DIF":0,"GEOUNIT":"Indonesia","GU_A3":"IDN","SU_DIF":0,"SUBUNIT":"Indonesia","SU_A3":"IDN","BRK_DIFF":0,"NAME":"Indonesia","NAME_LONG":"Indonesia","BRK_A3":"IDN","BRK_NAME":"Indonesia","BRK_GROUP":"","ABBREV":"Indo.","POSTAL":"INDO","FORMAL_EN":"Republic of Indonesia","FORMAL_FR":"","NAME_CIAWF":"Indonesia","NOTE_ADM0":"","NOTE_BRK":"","NAME_SORT":"Indonesia","NAME_ALT":"","MAPCOLOR7":6,"MAPCOLOR8":6,"MAPCOLOR9":6,"MAPCOLOR13":11,"POP_EST":260580739,"POP_RANK":17,"GDP_MD_EST":3028000,"POP_YEAR":2017,"LASTCENSUS":2010,"GDP_YEAR":2016,"ECONOMY":"4. Emerging region: MIKT","INCOME_GRP":"4. Lower middle income","WIKIPEDIA":-99,"FIPS_10_":"ID","ISO_A2":"ID","ISO_A3":"IDN","ISO_A3_EH":"IDN","ISO_N3":"360","UN_A3":"360","WB_A2":"ID","WB_A3":"IDN","WOE_ID":23424846,"WOE_ID_EH":23424846,"WOE_NOTE":"Exact WOE match as country","ADM0_A3_IS":"IDN","ADM0_A3_US":"IDN","ADM0_A3_UN":-99,"ADM0_A3_WB":-99,"CONTINENT":"Asia","REGION_UN":"Asia","SUBREGION":"South-Eastern Asia","REGION_WB":"East Asia & Pacific","NAME_LEN":9,"LONG_LEN":9,"ABBREV_LEN":5,"TINY":-99,"HOMEPART":1,"MIN_ZOOM":0,"MIN_LABEL":1.7,"MAX_LABEL":6.7,"NE_ID":1159320845,"WIKIDATAID":"Q252","NAME_AR":"إندونيسيا","NAME_BN":"ইন্দোনেশিয়া","NAME_DE":"Indonesien","NAME_EN":"Indonesia","NAME_ES":"Indonesia","NAME_FR":"Indonésie","NAME_EL":"Ινδονησία","NAME_HI":"इंडोनेशिया","NAME_HU":"Indonézia","NAME_ID":"Indonesia","NAME_IT":"Indonesia","NAME_JA":"インドネシア","NAME_KO":"인도네시아","NAME_NL":"Indonesië","NAME_PL":"Indonezja","NAME_PT":"Indonésia","NAME_RU":"Индонезия","NAME_SV":"Indonesien","NAME_TR":"Endonezya","NAME_VI":"Indonesia","NAME_ZH":"印度尼西亚"}},{"arcs":[[[132,-1]],[[133,134]],[[135,-8,136,137,138,139]],[[140]],[[141]],[[142]],[[143]],[[144]],[[145]]],"type":"MultiPolygon","properties":{"featurecla":"Admin-0 country","scalerank":5,"LABELRANK":3,"SOVEREIGNT":"Malaysia","SOV_A3":"MYS","ADM0_DIF":0,"LEVEL":2,"TYPE":"Sovereign country","ADMIN":"Malaysia","ADM0_A3":"MYS","GEOU_DIF":0,"GEOUNIT":"Malaysia","GU_A3":"MYS","SU_DIF":0,"SUBUNIT":"Malaysia","SU_A3":"MYS","BRK_DIFF":0,"NAME":"Malaysia","NAME_LONG":"Malaysia","BRK_A3":"MYS","BRK_NAME":"Malaysia","BRK_GROUP":"","ABBREV":"Malay.","POSTAL":"MY","FORMAL_EN":"Malaysia","FORMAL_FR":"","NAME_CIAWF":"Malaysia","NOTE_ADM0":"","NOTE_BRK":"","NAME_SORT":"Malaysia","NAME_ALT":"","MAPCOLOR7":2,"MAPCOLOR8":4,"MAPCOLOR9":3,"MAPCOLOR13":6,"POP_EST":31381992,"POP_RANK":15,"GDP_MD_EST":863000,"POP_YEAR":2017,"LASTCENSUS":2010,"GDP_YEAR":2016,"ECONOMY":"6. Developing region","INCOME_GRP":"3. Upper middle income","WIKIPEDIA":-99,"FIPS_10_":"MY","ISO_A2":"MY","ISO_A3":"MYS","ISO_A3_EH":"MYS","ISO_N3":"458","UN_A3":"458","WB_A2":"MY","WB_A3":"MYS","WOE_ID":23424901,"WOE_ID_EH":23424901,"WOE_NOTE":"Exact WOE match as country","ADM0_A3_IS":"MYS","ADM0_A3_US":"MYS","ADM0_A3_UN":-99,"ADM0_A3_WB":-99,"CONTINENT":"Asia","REGION_UN":"Asia","SUBREGION":"South-Eastern Asia","REGION_WB":"East Asia & Pacific","NAME_LEN":8,"LONG_LEN":8,"ABBREV_LEN":6,"TINY":-99,"HOMEPART":1,"MIN_ZOOM":0,"MIN_LABEL":3,"MAX_LABEL":8,"NE_ID":1159321083,"WIKIDATAID":"Q833","NAME_AR":"ماليزيا","NAME_BN":"মালয়েশিয়া","NAME_DE":"Malaysia","NAME_EN":"Malaysia","NAME_ES":"Malasia","NAME_FR":"Malaisie","NAME_EL":"Μαλαισία","NAME_HI":"मलेशिया","NAME_HU":"Malajzia","NAME_ID":"Malaysia","NAME_IT":"Malesia","NAME_JA":"マレーシア","NAME_KO":"말레이시아","NAME_NL":"Maleisië","NAME_PL":"Malezja","NAME_PT":"Malásia","NAME_RU":"Малайзия","NAME_SV":"Malaysia","NAME_TR":"Malezya","NAME_VI":"Malaysia","NAME_ZH":"马来西亚"}},{"arcs":[[[146,147,148,149]],[[150,151,152,153]],[[154]],[[155]],[[156]],[[157]],[[158]],[[159]],[[160]],[[161]],[[162]],[[163]],[[164]],[[165]],[[166]],[[167]],[[168]],[[169]],[[170]],[[171]],[[172]],[[173]],[[174]],[[175]],[[176]],[[177]],[[178]],[[179]],[[180]],[[181]],[[182]],[[183]],[[184]],[[185]],[[186]],[[187]],[[188]],[[189]],[[190]],[[191]],[[192]],[[193]],[[194]],[[195]],[[196]],[[197]],[[198]],[[199]],[[200]],[[201]],[[202]],[[203]],[[204]],[[205]],[[206]],[[207]],[[208]],[[209]],[[210]],[[211]],[[212]],[[213]],[[214]],[[215]],[[216]],[[217]],[[218]],[[219]],[[220]],[[221]],[[222]],[[223]],[[224]],[[225]],[[226]],[[227]],[[228]],[[229]],[[230]],[[231]],[[232]],[[233]],[[234]],[[235]],[[236]],[[237]]],
Here is my JS:
<script>window.onload = setMap();
function setMap(){
d3.csv("/data/fdata.csv").then(function(data) {
//console.log(data);
d3.json("/data/a.topojson").then(function(data2) {
//console.log(data2);
var width = 960,
height = 460;
//create new svg container for the map
var map = d3.select("body")
.append("svg")
.attr("class", "map")
.attr("width", width)
.attr("height", height);
var projection = d3.geoNaturalEarth1()
.center([0, 0])
.rotate([-2, 0, 0])
//.parallels([43, 62])
.scale(175)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
d3.selectAll(".boundary")
.style("stroke-width", 1 / 1);
var b = topojson.feature(data2, data2.objects.ne_10m_admin_0_countries);
var graticule = d3.geoGraticule();
var attrArray = ["x1","x2","x3" ];
function joinData(b, data){
for (var i=0; i<data.length; i++){
var csvRegion = data[i]; //the current region
var csvKey = data[i].Country; //the CSV primary key
for (var a=0; a<b.features.length; a++){
var geojsonProps = b.features[a].properties; //gj props
var geojsonKey = geojsonProps.ADMIN; //the geojson primary key
if (geojsonKey == csvKey){
attrArray.forEach(function(attr){
var val = parseFloat(csvRegion[attr]);
geojsonProps[attr] = val;
});
};
};
};
return b;
};
joinData(b,data);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var currentF = "B2";
var color = d3.scaleQuantile()
.domain(d3.range(0, 1000))
.range(d3.schemeReds[7]);
map.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
map.append("path")
.datum(graticule.outline)
.attr("class", "graticule outline")
.attr("d", path);
map.selectAll("path")
.data(b.features)
.enter()
.append("path")
.attr("d", path)
//.style("stroke", "black")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9)
.style("stroke-opacity", 1.0);
tooltip.html(d.properties.ADMIN)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0)
.style("stroke-opacity", 0);
})
.style("fill", function(d) { return color(d.properties[currentF]); });
}); //csv
}); //json
};
I believe the issue is with the map.selectAll("path") chunk. When I run it without D3 iterating through features it seems to come out fine. (With Indonesia and Malaysia still there. )

The issue is here:
map.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
map.append("path")
.datum(graticule.outline)
.attr("class", "graticule outline")
.attr("d", path);
map.selectAll("path")
.data(b.features)
.enter()
.append("path")
.attr("d", path)
The two append statements create paths, you then select these existing paths when you do: map.selectAll("path"). To confirm, before you append the features, you can do console.log(map.selectAll("path").size()) to see how many elements are selected, it should be two.
An enter selection can be used to create elements in the DOM for each item in the data array that does not have a corresponding element in the DOM.
As there are two paths in your selection, the first two items in the data array correspond to these two elements, and thus do not need to be entered: they already exist (By binding the data with .data() you are binding the first two datums in the data array to the already existing paths). D3 doesn't "know" that these paths aren't supposed to be affiliated with those data array items.
In order to ensure all items are entered you can make sure that you have an empty selection before you bind data:
map.selectAll(null).data()...
map.selectAll().data()...
map.selectAll(".className").data()... // where no element has that class yet

Related

D3 chart can't update -- enter and exit property of selection both empty

I'm trying to make a scatter plot using a .json file. It will let the user to select which group of data in the json file to be displayed. So I'm trying to use the update pattern.
The following code will make the first drawing, but every time selectGroup() is called(the code is in the html file), nothing got updated. The console.log(selection) did come back with a new array each time, but the enter and exit property of that selection is always empty.
Can anyone help me take a look? Thanks a lot!
var margin = {
top: 30,
right: 40,
bottom: 30,
left: 40
}
var width = 640 - margin.right - margin.left,
height = 360 - margin.top - margin.bottom;
var dataGroup;
var groupNumDefault = "I";
var maxX, maxY;
var svg, xAxis, xScale, yAxis, yScale;
//select and read data by group
function init() {
d3.json("data.json", function (d) {
maxX = d3.max(d, function (d) {
return d.x;
});
maxY = d3.max(d, function (d) {
return d.y;
});
console.log(maxY);
svg = d3.select("svg")
.attr("id", "scatter_plot")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("id", "drawing_area")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//x-axis
xScale = d3.scale.linear().range([0, width]).domain([0, maxX]);
xAxis = d3.svg.axis()
.scale(xScale).orient("bottom").ticks(6);
//y-axis
yScale = d3.scale.linear().range([0, height]).domain([maxY, 0]);
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(6);
svg.append("g")
.attr("class", "x_axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y_axis")
.call(yAxis);
});
selectGroup(groupNumDefault);
}
//update data
function selectGroup(groupNum) {
d3.json("/data.json", function (d) {
dataGroup = d.filter(function (el) {
return el.group == groupNum;
});
console.log(dataGroup);
drawChart(dataGroup);
});
}
//drawing function
function drawChart(data) {
var selection = d3.select("svg").selectAll("circle")
.data(data);
console.log(selection);
selection.enter()
.append("circle")
.attr("class", "dots")
.attr("cx", function (d) {
console.log("updating!");
return xScale(d.x);
})
.attr("cy", function (d) {
return yScale(d.y);
})
.attr("r", function (d) {
return 10;
})
.attr("fill", "red");
selection.exit().remove();
}
init();
The problem here is on two fronts:
Firstly, your lack of a key function in your data() call means data is matched by index (position in data array) by default, which will mean no enter and exit selections if the old and current datasets sent to data() are of the same size. Instead, most (perhaps all) of the data will be put in the update selection when d3 matches by index (first datum in old dataset = first datum in new dataset, second datum in old dataset = second datum in new dataset etc etc)
var selection = d3.select("svg").selectAll("circle")
.data(data);
See: https://bl.ocks.org/mbostock/3808221
Basically, you need your data call adjusted to something like this (if your data has an .id property or anything else that can uniquely identify each datum)
var selection = d3.select("svg").selectAll("circle")
.data(data, function(d) { return d.id; });
This will generate enter() and exit() (and update) selections based on the data's actual contents rather than just their index.
Secondly, not everything the second time round is guaranteed be in the enter or exit selections. Some data may be just an update of existing data and not in either of those selections (in your case it may be intended to be completely new each time). However, given the situation just described above it's pretty much guaranteed most of your data will be in the update selection, some of it by mistake. To show updates you will need to alter the code like this (I'm assuming d3 v3 here, apparently it's slightly different for v4)
selection.enter()
.append("circle")
.attr("class", "dots")
.attr("r", function (d) {
return 10;
})
.attr("fill", "red");
// this new bit is the update selection (which includes the just added enter selection
// now, the syntax is different in v4)
selection // v3 version
// .merge(selection) // v4 version (remove semi-colon off preceding enter statement)
.attr("cx", function (d) {
console.log("updating!");
return xScale(d.x);
})
.attr("cy", function (d) {
return yScale(d.y);
})
selection.exit().remove();
Those two changes should see your visualisation working, unless of course the problem is something as simple as an empty set of data the second time around which would also explain things :-)

How to group and filter using crossfilter in d3.js

js users,
I just started using d3.js & crossfilter and was attempting to make use of Adam Pearce's example on meteor map to visualise weather data. Instead of having only one record for each name in the "name" column, I have multiple-year records as I'm keen to analyse the weather changes over the years. Below is a sample of the original data and my own data:
orginal data
name mass_g year cartodb_id ....
Vilna 1 1967 34039
Silistra 1 1917 34017
My data
name rainfall year cartodb_id ....
station_A 100 1997 34039
station_A 200 1998 34039
station_B 300 1997 34017
station_B 400 1998 34017
I'm not sure where went wrong as it did not produce any error(s). However,
the crossfilter did not seem to take effect on the map
it did not group the rainfall values by the stations on the map (I think they are overlapping each other)
I couldn't group the two bar charts such that I could show the time series of rainfall over the years, as well as the rainfall distribution (I think need to use some Reduce function but I can't figure it out)
Below is my js script to replace the original drawMap.js (sorry, it's a little too long; Thank you for any kind advice!):
var width = 1000,
height = 500;
var proj = d3.geo.mercator()
.center([103.8,1.33])
.scale(100000)
.rotate([0,0]);
var path = d3.geo.path()
.projection(proj);
// for map
var zoom = d3.behavior.zoom()
.translate(proj.translate())
.scale(proj.scale())
.scaleExtent([100000,Infinity]) //.scaleExtent([height*.33, 4 * height])
.on("zoom", zoom);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
function zoom() {
proj.translate(d3.event.translate).scale(d3.event.scale);
svg.selectAll("path").attr("d", path);
circles
.attr("cx", function(d){return proj([d.long, d.lat])[0];})
.attr("cy", function(d){return proj([d.long, d.lat])[1];});
}
var borders = svg.append("g");
var impacts = svg.append("g");
// colours and scales for the corresponding values in map
var metorScale = d3.scale.pow().exponent(.5).domain([0, 100, 1000, 2000, 3000, 5000, 8000]);
var colorScale = d3.scale.linear().domain([1980, 1888, 1996, 2004, 2014]);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6)
.style("background", "rgba(250,250,250,.7)");
tooltip.append("img")
.attr("id", "tooltipImg")
.attr("height", 200)
.attr("width", 200)
.style("opacity", "1");
// change the fell file to our rainfall
// file has to be at aggregated level for each year
// one station one value for one year
// change the json file to our map
queue()
.defer(d3.json, "Planning_Area_Census2010_WGS84.json")
.defer(d3.csv, "yearlyrainfall.csv")
/*.defer(d3.json, "pics.json")*/
.await(ready);
var metors;
// for the rest of the code
// removed pics in the inputs
function ready(error, topology, csv){
borders.selectAll("path")
.data(topojson.object(topology, topology.objects.Planning_Area_Census2010_WGS84)
.geometries)
.enter()
.append("path")
.attr("d", path)
.attr("class", "border")
rawMetors = csv;
// change mass_g to rainfall
// cartodb_id is a key for our station
metors = [];
rawMetors.forEach(function(d){
d.rainfall = +d.rainfall;
d.year = +d.year;
d.id = +d.cartodb_id;
d.name = d.name;
d.region = d.region;
metors.push(d);
});
metors.sort(function(a, b){return a.id - b.id;})
// size tag to rainfall
//colour tag to year
metorScale
.range([1.5, 2, 3, 4, 5, 8, 10]);
colorScale
.range(["#FFFF66", "#FFFF00", "#E68000", "#D94000", "#CC0000"]);
circles = impacts.selectAll("circle")
.data(metors).enter()
.append("svg:a")
.attr("xlink:href", function(d) { return d.database; })
.attr("xlink:show", "new")
.append("circle")
.attr("cx", function(d){return proj([d.long, d.lat])[0];})
.attr("cy", function(d){return proj([d.long, d.lat])[1];})
.attr("r", function(d){return metorScale(d.rainfall);})
.attr("id", function(d){return "id" + d.id;})
.style("fill", function(d){return colorScale(d.year); })
.on("mouseover", function(d){
d3.select(this)
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("fill-opacity", 1);
tooltip
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 5) + "px")
.transition().duration(300)
.style("opacity", 1)
.style("display", "block")
updateDetails(d);
})
.on("mouseout", function(d){
d3.select(this)
.attr("stroke", "")
.attr("fill-opacity", function(d){return 1;})
tooltip.transition().duration(700).style("opacity", 0);
});
metorsCF = crossfilter(metors),
all = metorsCF.groupAll(),
year = metorsCF.dimension(function(d){return d.year;}), /*creating a type dimension*/
years = year.group(function(d){return Math.floor(d);}),
rainfall = metorsCF.dimension(function(d){return d.rainfall }), /*creating a type dimension*/
rainfalls = rainfall.group(function(d){return Math.floor(d/10)*10;}),
// for grouping at var charts
// group by type, change to region
region = metorsCF.dimension(function(d){return d.region;}), /*creating a type dimension*/
regions = region.group();
// create group of ids
cartoDbId = metorsCF.dimension(function(d){return d.id;}); /*creating a type dimension*/
cartoDbIds = cartoDbId.group()
// create all the bar charts
// first one draw by group of years
// change the domain relate to the format tick in barchart.js
var charts = [
barChart()
.dimension(year)
.group(years)
.x(d3.scale.linear()
.domain([1970,2020])
.rangeRound([-1, 20*24-5])),
// change the domain relate to the format tickvalues in barchart.js
barChart()
.dimension(rainfall)
.group(rainfalls)
.x(d3.scale.linear()
.domain([1,10000])
.rangeRound([0,20*24]))
];
var chart = d3.selectAll(".chart")
.data(charts)
.each(function(chart){chart.on("brush", renderAll).on("brushend", renderAll)});
d3.selectAll("#total")
.text(metorsCF.size());
function render(method){
d3.select(this).call(method);
}
lastFilterArray = [];
metors.forEach(function(d, i){
lastFilterArray[i] = 1;
});
function renderAll(){
chart.each(render);
var filterArray = cartoDbIds.all();
filterArray.forEach(function(d, i){
if (d.value != lastFilterArray[i]){
lastFilterArray[i] = d.value;
d3.select("#id" + d.key).transition().duration(500)
.attr("r", d.value == 1 ? 2*metorScale(metors[i].rainfall) : 0)
.transition().delay(550).duration(500)
.attr("r", d.value == 1 ? metorScale(metors[i].rainfall) : 0);
}
})
d3.select("#active").text(all.value());
}
window.reset = function(i){
charts[i].filter(null);
renderAll();
}
renderAll();
}
// to show the labels on what to display on map
var printDetails = [
{'var': 'name', 'print': 'Station'},
{'var': 'region', 'print': 'Region'},
{'var': 'rainfall', 'print': 'Rainfall(mm)'},
{'var': 'year', 'print': 'Year'}];
function updateDetails(metorsCF){
// var image = new Image();
// image.onload = function(){
// document.getElementById("tooltipImg").src = 'pictures/' + metor.cartodb_id + '.jpg';}
// image.src = 'pictures/' + metor.cartodb_id + '.jpg';
tooltip.selectAll("div").remove();
tooltip.selectAll("div").data(printDetails).enter()
.append("div")
.append('span')
.text(function(d){return d.print + ": ";})
.attr("class", "boldDetail")
.insert('span')
.text(function(d){return metorsCF[d.var];})
.attr("class", "normalDetail");
}
[1]: http://roadtolarissa.com/meteors/

Zooming datasets in d3.js

I have overlayed two datasets, a boundary map and a point map in d3.js. I want to be able to zoom both datasets at the same time. With the current code, only the point map responds to the zoom. How can I zoom both datasets at the same time
The code is shown below
var canvas = d3.select("body").append("svg")
.attr("width",260)
.attr("height",400)
d3.json("/Maps/iowastate.json",function (data){
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
var projection =d3.geo.mercator()
.scale(250)
//.translate([0,0]);
var path = d3.geo.path().projection(projection);
var areas = group.append("path")
.attr("d", path)
.attr("class","area")
.attr("fill","black");
d3.csv("/Maps/detectors.csv",function (d){
var group = canvas.selectAll("g")
.data(d)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.StartLong,d.StartLat])[0];
})
.attr("cy", function(d,i) {
return projection([d.StartLong,d.StartLat])[1];
})
.attr("r", 0.1)
.style("fill", "red");
//console.log(projection(d[0].StartLat))
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
You are applying the right modifications, but twice to the same set of elements instead of the two different layers. To make it work, keep a reference to the other group (e.g. by using different variable names) and apply the transformations to both groups.

Add images to d3 chord diagram

I would like images as the endpoints. I have tried adding but no luck. Any ideas/working examples?
http://bost.ocks.org/mike/uberdata/
Each neighborhood in that example is given a <g> element with a class of group.
// Add a group per neighborhood.
var group = svg.selectAll(".group")
.data(layout.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", mouseover);
This is the element to which the text label and the endpoint path are appended.
// Add the group arc.
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return cities[i].color; });
// Add a text label.
var groupText = group.append("text")
.attr("x", 6)
.attr("dy", 15);
You could append each image to this group also, using an svg <image> element. If, for example, your dataset contains the urls for your images, you might do the following:
var groupImage = group.append("image")
.attr("xlink:href", function(d) {return d.image_url;})

plots on multiple instances of graph

I'm using the d3 library to plot a bar graph with JSON objects recieved from the server through websockets. What is happening though is that each time the graph is plotted it draws a new instance of a graph. So I end up with multiple graphs.
But I want the JSON data to be all plotted onto the same one graph.
Here's my code:
ws = new WebSocket("ws://localhost:8888/dh");
var useData = []
//var chart;
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 200);
ws.onmessage = function(evt)
{
var distances = JSON.parse(evt.data);
data = distances.miles;
console.log(data);
if(useData.length <= 10){
useData.push(data)
}
else
{
var draw = function(data){
// Set the width relative to max data value
var x = d3.scale.linear()
.domain([0, d3.max(useData)])
.range([0, 420]);
var y = d3.scale.ordinal()
.domain(useData)
.rangeBands([0, 120]);
var rect = chart.selectAll("rect")
.data(useData)
// enter rect
rect.enter().append("svg:rect")
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand());
// update rect
rect
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand());
var text = chart.selectAll("text")
.data(useData)
// enter text
text.enter().append("svg:text")
.attr("x", x)
.attr("y", function (d) { return y(d) + y.rangeBand() / 2; })
.attr("dx", -3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
// update text
text
.data(useData)
.attr("x", x)
.text(String);
}
useData.length = 0;
}
}
How can I plot all points onto on graph which is being constantly updated?
It's a shame that d3 cannot handle data in real-time and update charts accordingly, or if it can that there's no clear tutorial/ explanation of how to.
Thanks
My guess is because you're creating a chart every time with:
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 20 * useData.length);
Rather you need to check if chart exists, and if so, don't call that line.
// outside of .onmessage
var chart;
// inside of .onmessage
if (!chart) {
chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 20 * useData.length);
}
Like Brian said you keep creating a new svg element at every onmessage event. However, in D3 you don't need to use an if statement to check for element existence; after you do a data join, the enter() selection will only contain the elements that did not exist yet:
// data join
var chart = d3.select("body").selectAll(".chart")
.data([useData]); // if you wanted multiple charts: .data([useData1, useDate2, useData3])
// enter
chart.enter().append("svg") // this will only execute if the .chart did not exist yet
.attr("class", "chart")
.attr("width", 420);
// update (both new and existing charts)
chart
.attr("height", function(d) { return 20 * d.length; });
The concepts of the data join, enter(), update(), and exit() selections are explained in the Thing with Joins article. See also the 3 General Update Pattern articles.
You have to use a similar approach when you update or add new rect elements in your chart. Assuming for the moment that useData contains all accumulated data (although the useData.length = 0 might mean that is not the case):
// data join
var rects = chart.selectAll("rect")
.data(function(d) { return d; }); // use the data bound to each chart
// enter
rects.enter().append("rect");
// update
rects
.attr("y", function(d) { return y(d.yValue); }) // not sure what your data structure looks like
.attr("width", function(d) { return x(d.xValue); })
.attr("height", y.rangeBand());
// exit
rects.exit().remove();
Some suggestions how to update a path with real time data are given in Path Transitions.

Categories