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.