populating existing SVG elements with JSON data using D3 - json

What I want to achieve is an SVG that has several separate polygons that can be coloured according to a set of JSON data. I'm nearly there, but having problems with matching up the data objects with the SVG elements in the correct order.
I've created an SVG, which has several polygons such as
<polygon id="Zone2" fill="#DFECC5" stroke="#000000" stroke-miterlimit="10" points="828.815,660.425 841.261,594.05 842.82,594.422 849.014,564.644 837.769,562.306 841.108,546.883 851.945,549.118 866.75,474.55 837.799,468.253 837.254,470.748 833.572,470.097 833.982,467.844 817.591,463.942 818.746,458.473 875.479,470.067 838.604,655.601 837.24,662.395 " style="fill: rgb(244, 246, 252);"><title>Car Park South, 1230</title></polygon>
Here's the JSON file I'm parsing:
{
"zoneData":[
{"zoneID":"Zone1","name":"Car Park South", "value":1230},
{"zoneID":"Zone2","name":"Outdoor North", "value":3453},
{"zoneID":"Zone3","name":"Outdoor West", "value":2342},
{"zoneID":"Zone4","name":"Outdoor South", "value":3453},
{"zoneID":"Zone5","name":"High St. East", "value":1023},
{"zoneID":"Zone6","name":"High St. West", "value":2322},
{"zoneID":"Zone7","name":"Car Park North", "value":4562},
{"zoneID":"Zone8","name":"Indoor North", "value":5644},
{"zoneID":"Zone9","name":"Indoor South", "value":2356}
]
}
I've managed to parse everything and create a proper colour scale, and even use that scale to set colours for each polygon, but they are in the wrong order. The script is here:
d3.json("zoneAnalysisData.json", function(error, data) {
var minDataValue = d3.min(data.zoneData, function(d) { return d.value; })
var maxDataValue = d3.max(data.zoneData, function(d) { return d.value; })
var color = d3.scale.linear()
.domain([minDataValue, maxDataValue])
.range(["#ffffff", "#0038c5"]);
d3.selectAll("[id*='Zone']")
.data(data.zoneData)
.style("fill", function(d){
return color(d.value);
})
.append("title")
.text(function(d) {
return d.name + "\n" + d.value;
});
});
I've looked at the docs on selections and thinking with joins but was not able to figure out how to match the existing elements in my selection with JSON data array elements.

The problem is that you need the polygons to be selected in a specific order, which isn't guaranteed by .selectAll(). You also can't reorder them easily, as D3's .sort() method works on data, which hasn't been bound to the elements at that point.
One way of fixing this would be "attach" some data manually to the elements that allows to match up elements and data. Then all you need is a key function:
d3.selectAll("[id*='Zone']")
.each(function() { this.__data__ = { zoneID: this.id })
.data(data.zoneData, function(d) { return d.zoneID; })
...
The second line here mimics what D3 does internally and creates the attribute that D3 looks for when trying to find bound data.

Related

D3 choropleth + overpass api >> csv URL call = invalid characters in csv header?

Experimenting a bit here with using an overpass api URL call >> csv for direct d3 charting. As longitude and latitude are special column declarations for overpass (called with an initial ::), they result in a CSV column header with an '#' symbol: #lon & #lat respectively.
If I change d.lon & d.lat to d.#lon & d.#lat in a function, I get an invalid character error.
Is there a way to get around this, eg telling overpass to use different header names without the #, parsing the # columns to an #-free string before using them in the function, or somehow escaping the # symbols?
Many thanks
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
d3.queue()
.defer(d3.json, "map.geojson")
.defer(d3.csv, "http://overpass-api.de/api/interpreter?data=[bbox:37.8885,-122.2966,37.8906,-122.2945][out:csv(::lat,::lon,::id,wheelchair)];(node[entrance];);out;>;out skel qt;")
.await(ready);
svg
.selectAll("myCircles")
.data(data.sort(function(a,b) { return +b.n - +a.n }).filter(function(d,i){ return < 10 }))
.enter()
.append("circle")
.attr("cx", function(d){ return projection(+d.#lon, +d.#lat)[0] })
.attr("cy", function(d){ return projection(+d.#lon, +d.#lat)[1] })
.attr("r", 5)

D3 Line Generator Handling Multiple Arrays

I have 3 arrays within a JSON file of the following structure:
I am trying to parse a JSON file of coordinate data into something that can be read by D3 path/line generators. I first had to find a way to ensure the values were actual numbers and not strings. A full discussion can be found here:
D3/JS mapping a JSON data callback
That discussion prompted me to not only consider formatting the JSON data via .map() to numbers, but also consider a nested/zipped format that the line generator can make sense of. That is really the target I've been after all along. As depicted above, My JSON has 3 arrays, xs, ys and id. id only governs color, and takes 1 of 3 values (0,1,2). I was recommended this approach:
var obj = raw_json[0];
var data = obj.id.map((id, i) => [+id, +obj.xs[i], +obj.ys[i]]);
My line generator function is:
var valueLine = d3.svg.line()
.x(function(d) {return xScale(d.xs);})
.y(function(d) {return yScale(d.ys)});
However I am getting tens of thousands of errors, and unfortunately I do not have that much experience with parsing related issues and I am not sure how to proceed.
Full block & JSON here.
Glad to see you took my advice on restructuring your data as we are moving in the right direction. I suggested that you should convert your three separate arrays into one array of individual arrays per point to make the use of the line generator more easy and to eliminate the need for cross-array reads to collect data for the points.
This time, though, you are not accessing your values correctly. By using function(d) { return xScale(d.xs); } you are assuming that your points were represented by objects having properties xs and ys. My suggested approach however got rid of these properties by storing the information into arrays. There are basically two ways around this:
Adjust you path generator's .x() and .y() callbacks while keeping your data structure.
var obj = raw_json[0];
var data = obj.id.map((id, i) => [+id, +obj.xs[i], +obj.ys[i]]);
// Remember, that d is a point's array [id, x, y]
var valueLine = d3.svg.line()
.x(function(d) { return xScale(d[1]); })
.y(function(d) { return yScale(d[2]); });
If you prefer to store your points' data in objects instead, another solution would be to adjust how your data is built.
// Structure data into an array of objects {id, x, y}
var obj = raw_json[0];
var data = obj.id.map((id, i) => ({
id: +id,
x: +obj.xs[i],
y: +obj.ys[i]
}));
// Keep your code as d now has properties x and y.
var valueLine = d3.svg.line()
.x(function(d) { return xScale(d.x); })
.y(function(d) { return yScale(d.y); });
I think you aren't modeling correctly the data to use to use with data(data).enter() and/or d3.svg.line(). As you defined your valueLine function it expects an array of objects with xs and ys properties and you are feeding it with an array of arrays.
Try it out manually to see how it works
valueLine([{xs: 1, ys: 1}, {xs: 3, ys: 5}, {xs: 12, ys: 21}])
and see the generated svg path:
"M100,400L300,-400L1200,-3600"
So maybe you can change how you are preparing your data for something like :
var data = obj.id.map((id,i) => ({id: +id, xs:+obj.xs[i], ys: +obj.ys[i]}));

Bubble chart in d3.js

http://plnkr.co/edit/7aw93EnMyCR3HjTu1uHa?p=preview
I have added the working fiddle.
I need to plot bubble chart on the basis of "profit" value in properties of "taluks.geojson" file.
This is working for flare.json but not for taluks.geojson file.
I have tried modifying the code in index.html as-
d3.json("taluks.geojson", function(error, root) { if (error) throw error;
var node = svg.selectAll(".node") .data(bubble.nodes(classes(root)) .filter(function(d) { return !d.properties; }))
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) { if (node.properties) node.properties.forEach(function(child) { recurse(node.name, child); }); else classes.push({packageName: name, className: node.NAME_2, value: node.profit}); }
recurse(null, root); return {properties: classes}; }
But the code is not working for taluks.geojson but only working for flare.json.
Please help regarding how to plot bubble chart on the basis of profit in properties of taluks.geojson file.
Please kindly suggest any further modificatons.
Thank you.
To render bubble chart or tree chart data should be in relational format parent and child, If you see flare.json data
{
"name": "flare",//root parent or level 0
"children": [
{
"name": "analytics",//child node or level 1
"children": [
{
"name": "cluster", //child node to analytics or level 2
"children": [
{"name": "AgglomerativeCluster", "size": 3938},
{"name": "CommunityStructure", "size": 3812},
{"name": "HierarchicalCluster", "size": 6714},
{"name": "MergeEdge", "size": 743}
]
}]
}]
}
/*
* In the above the data is hierarchical here flare will be like parent and children are in children array, name will be name of the node and at the end node or leaf node we don't have any children so here we reached to leaf
and taluks.geojson is not having the structure of desired manner. So we have to make it into our desired structure.
*
*/
var data= d3.nest().key(function(d){ return d.properties.NAME_2;}).key(function(d){ return d.properties.NAME_3;}).entries(root.features);
/*
* d3.nest function will perform the grouping operation mentioned/returned by the key function, this function is used to group elements by using key and it takes the data from entries function.
* so as per above code we are grouping by NAME_2 which is district name and again we are grouping that data based on NAME_3
* from above we'll get data in below format
* [{"key":"East Godavari","values":[
* {"key":"Kottapeta","values":[
* {"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Kottapeta","profit":326,"npa":174},
* "geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.75195312500006,16.772489547729492],[81.76336669921892,16.7611598968507],[81.7764129638673,16.755041122436637],[81.76336669921875,16.7611598968507],[81.75195312500006,16.772489547729492]]]
* ]}}]
* },
* {"key":"Rajahmundry","values":[{"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Rajahmundry","profit":1762,"npa":1683},"geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.71717071533203,17.0606307983399],[81.72284698486357,17.047969818115348],[81.72709655761736,17.035369873046875],[81.72931671142607,17.02490997314453],[81.73168945312517,17.009309768676758],[81.73249053955084,16.990680694580135],[81.731689453125,17.009309768676758],[81.7293167114259,17.02490997314453],[81.72709655761719,17.035369873046875],[81.7228469848634,17.047969818115348],[81.71717071533203,17.0606307983399]]],
* ]}}]
* },
* {"key":"Rampa Chodavaram","values":[{"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Rampa Chodavaram","profit":376,"npa":362},"geometry":{"type":"MultiPolygon","coordinates":[[[[81.64000701904303,17.217769622802678],[81.63854217529308,17.24398994445812],[81.64405822753918,17.25922966003418],[81.64591217041021,17.293151855468864],[81.64405822753935,17.25922966003418],[81.63854217529325,17.24398994445812],[81.64000701904303,17.217769622802678]]],
* [[[81.51127624511724,17.463871002197266],[81.51648712158232,17.458469390869084],[81.52410888671903,17.454042434692326],[81.53122711181658,17.4517498016358],[81.55007171630854,17.447589874267692],[81.5312271118164,17.4517498016358],[81.52410888671886,17.454042434692326],[81.51648712158214,17.458469390869084],[81.51127624511724,17.463871002197266]]]]}}]
* },
* {"key":"Razole","values":[
* {"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Razole","profit":1185,"npa":1141},"geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.6993026733399,16.41020011901867],[81.70881652832048,16.383939743041992],[81.7134628295899,16.35638809204113],[81.70881652832031,16.383939743041992],[81.6993026733399,16.41020011901867]]],
* ]}}]
* }
* .........
* ]}]
To know more about d3 nest function go this link https://github.com/mbostock/d3/wiki/Arrays
* above structure is generated by d3.nest function, now we need to arrange that data into parent child hierarchical data, for this we are using below code
*/
var myData={name:'flare', children:[]};//this will be root
data.forEach(function(distc){
var dis={};
dis.name=distc.key;//here distc will hold one level write console.log(distc); and observe the data
dis.children = [];
myData.children.push(dis);
distc.values.forEach(function(chil){
var chis={};
chis.name=chil.key;
// chis.children=chil.values;
chis.size=chil.values[0].properties.profit;//we are using size as value in recurse function of classes function and we want to display bubble chart on profit value
dis.children.push(chis);
});
});
console.log(myData);//myData has the desired structure
root=myData;//assigning that json to root variable
I think you know what happens with the rest of the code.
Hope everything is clear, If not ask me for more.
:D
In order to create bubble chart we are using d3 pack layout, this layout we are generated at
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5); and this bubble calling nodes at this line
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))//here bubble.nodes takes the json, that json should be in leaf structure, nodes function will generate relational data, write console.log(bubble.nodes(classes(root))); Here we are using classes function, because flare.json has already relational format json but bubbles.nodes need leaf structure so in classes function we are converting that flare.json relational structure into leaf
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

D3JS Map Citiy Circles

I'm currently creating a map of germany with d3js to visualize some data. I now added some Cities by using a csv, which are shown as circles in the map. I want some cities to have a different color the more data are used in them. Does anybody have a idea how I can achieve this?
You can declare a range of color like this:
var color = d3.scale.linear()
.domain([d3.min(array), d3.max(array)])
.range(["#fff7f3", "#49006a"]);
then style every circle:
.style("fill", function(d) {
return color(+d.data);
});
But you need to compact your data to just one value per city like so:
d.values.reduce(function(sum, d){
return sum + d.amount;
},0)
Here's an example from Mike's Site: http://bl.ocks.org/mbostock/4060606

D3 reusable multi-line chart with JSON data

I'm trying to do some re-factoring on my charts to make them re-usable using this as a guide: http://bost.ocks.org/mike/chart/
I'm having problems drawing the lines in my multi-line graph though - specifically passing the data to the x and y values. If I hard code the element names it works, but if I try to use the xValue and yValue objects this does not work. I'm assuming that this is because I'm trying to call a function within the parameter of an other object, but I'm not sure how to get around this. In the exmaple Mike uses d[0] and d[1], but this won't work with JSON data (or I'm not sure how to make it work).
I've posted this JSFiddle so you can see the code. The problem lines are 125 to 131 which in turn is being called from line 165.
var main_line = d3.svg.line()
.interpolate("cardinal")
// Hard coding the elements works
//.x(function(d) { return main_x(d.date); })
//.y(function(d) { return main_y(d.buildFixTime); });
// Passing xValue and yValue does not work
.x(function(d) { return main_x(xValue); })
.y(function(d) { return main_y(yValue); });
http://jsfiddle.net/goodspeedj/fDyLY/
Thank you in advance.
You need to redefine your accessor method within .x() and .y(). The accessor method defines the way that a datum is pulled out of the data that is bound to the selection that you call the line generator on.
Suppose you have a relatively flat data structure such as the following.
data = [{x : 1, y : 2}, {x:1, y:3}, {x:4, y:5}];
You then bind the data to a selection with the following statement
d3.select("body").datum(data).append("path").attr("d",lineGenerator);
Quite a bit is going on underneath this statement. I'll give you a bit more of a walkthrough after showing you a commonly used example.
The important aspect to understand is that similarly to other calls in d3 such as
var exampleRectangles = d3.select("body")
.data(data).enter()
.append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",function(datum){return datum.x}) // pay attention to this line
.attr("y",0);
d3 is implicitly iterating over each element in your data. For each datum in your data array, in this case there is a total of three datum, you are going to add a rectangle to the dom.
In the line that I tell you to pay attention to you notice that you're defining an anonymous (unnamed) function. What is that datum parameter coming from? It's implicitly being passed to your anonymous function.
So each rectangle has it's own corresponding datum {x : 1, y : 2}, {x:1, y:3}, {x:4, y:5} respectively. Each rectangle's x coordinate is defined by the respective datum.x attribute. Under the sheets, d3 is implicitly looping over the data array that you've defined. A similar approach to the example d3 code could be written as above.
for (var i = 0; i < data.length; i++)
{
d3.select("body").append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",data[i].x)
.attr("y",0);
}
This follows from the notion of data driven documents (d3). For each item added (a rectangle in the above example a piece of data is tied to it. In the above example you see that there is something kind of similar to your .x() and .y() accessor functions :
.attr("x",function(datum){return datum.x})
This function is telling d3 how to filter over the total datum that's being passed to the .attr() accessor method.
So, you need to determine which data you need to get a hold of to make your .attr("d", lineGenerator)call make sense. The difference between your.datum(data)call and the typical.data(data)call is that instead of parceling the data that's being passed to.data(data)`, the whole array is given as a single piece of data to the line generator function (similar to main_line(data), wherein it will again implicitly loop over the points to construct your path.
So, what you need to do is determine what a single datum will be defined as for your function to operate on.
I'm not going to define that as I don't seem to know quite which information you are operating on, but I would hazard a guess at something like.
.x(xAccessor)
.y(yAccessor)
function xAccessor(datum)
{
return xScale(datum._id.month);
}
function yAccessor(datum)
{
return yScale(datum.buildFixTime);
}
The way you have it set up, xValue and yValue are functions; you have to actually execute them on something to get a value back.
.x(function(d) { return main_x( xValue(d) ); })
.y(function(d) { return main_y( yValue(d) ); });
If you weren't using a scale, you could use
.x(xValue)
.y(yValue);
but only because if you pass in a function d3 executes it for you with the data as a parameter. And that only works for d3 methods that expect functions as possible input -- the scale functions expect data values as input.
I wrote a long piece work for another user last week that you may find useful, explaining methods that accept functions as parameters.