Importing JSON for D3 map manipulation - javascript

tl;dr
I'm creating arcs between two points on a map as shown here, but I want to save that huge array of coordinates in a json/csv file. How should I save that file and what should I change in the script so it correctly parses the json/csv file.
Long version
I'm trying to draw arcs between two points on a map as shown here.
Here's what I did.
First I defined my coordinates (hard-coded). Notice that they are lon/lat.
var trainRoutes = [
{sourceLocation: [94.91542,27.485983],targetLocation: [77.549934,8.079252]}
];
Then I defined my arcs.
var arcs = svg.append("g").attr("class","arcs"); // adding a class for CSS stuff
And finally the code for drawing them.
arcs.selectAll("path")
.data(trainRoutes)
.enter()
.append("path")
.attr('d', function(d) {
return makeArc(d, 'sourceLocation', 'targetLocation', 1);
});
makeArc is just a function that returns a string for the path to be drawn, again, as shown here.
As you can see, I'm just creating one arc with two sets of coordinates (say city A and city B). I would like to draw more arcs but not clutter my index.html. I want to put the coordinates in a JSON file and create arcs from there rather than declaring coordinates within index.html.
I did try putting the coordinates in a JSON file and used d3.json to do the same.
d3.json("trainRoutes.json", function(json){
arcs.selectAll("path")
.data(json)
.enter()
.append("path")
.attr('d', function(d) {
return makeArc(d, d.sourceLocation, d.targetLocation, 1); });
});
But this didn't work and the console says arcs.selectAll("path") is not a function. How do I solve this issue? I'm open to using both d3.csv/d3.json, just want to move the coordinates to another file.
Here's what my JSON file (trainRoutes.json) looked like in case my declaration of JSON was wrong.
[
{sourceLocation: [94.91542,27.485983],targetLocation: [77.549934,8.079252]}
]

Related

Mapping data with topojson and D3

so here is my first implementation using D3's library to create a map with U.S. counties.
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client#3"></script>
<svg width="960" height="600"></svg>
<script>
'use strict';
let path = d3.geoPath();
let svg = d3.select("svg");
d3.queue()
.defer(d3.json, "https://d3js.org/us-10m.v1.json")
.await(ready)
function ready(error, us) {
if (error) throw error;
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
.attr("d", path)
}
simple and straight forward. it works. However when I change the src to a better json with the names in the props of each county object:
d3.queue()
.defer(d3.json, "https://gist.githubusercontent.com/NealTaylor715/a08cc300e661aa45c464fa1e553b6f33/raw/eaa03db6827f2d6435b3898cae6fba03d6f55956/USCounties.json")
.await(ready);
the map breaks. I get a blank SVG with this little gray blotch. the weird thing is that the data is appending to the path, just not the way I thought it should. Any pointers? Is my new JSON just not formatted correctly. The names on the objects' properties property are a necessity for the functionality I am shooting for. Any help would be greatly appreciated. Thank you in advance.
Both data sources are formatted correctly, but they are different in one regard, one is projected while one is not.
The dataset in your first code block is already projected, that is to say it has already been transformed from points on a 3 dimensional ellipsoid to points on a Cartesian plane. We can tell they are not latitude longitude pairs as the coordinates, when converting to regular geojson, show points such as ...[649.7055250753067,200.18804561250514]...
The dataset in your second code block is unprojected (or 'projected' with WGS84 or some other datum). It is made up of points that represent degrees north/south and degrees east/west. Converting this topojson to regular geojson we see coordinates like: ...[-167.36922883183183,53.30289293393393]...
With already projected data, and no geoTransform (API documentation, example), you are simply taking the coordinates in the dataset and converting them to SVG coordinate space with no transform.
With unprojected data, you need to use a projection, such as d3.geoAlbers, to properly scale and transform your lat long pairs, points on an ellipsoid, to x,y points on a plane.

D3 geo: getting projection.clipAngle to work on all specified elements

I'm a newcomer to D3 and I'm trying to make a world globe with some points ("pins") on it. Demo here: http://bl.ocks.org/nltesown/66eee134d6fd3babb716
Quite commonly, the projection is defined as:
var proj = d3.geo.orthographic()
.center([0, 0])
.rotate([50, -20, 0])
.scale(250)
.clipAngle(90)
.translate([(width / 2), (height / 2)]);
the clipAngle works well for the svg paths, but not the pins (which are svg circles). As you can see on the demo, the pin that sits between Iceland and Greenland should be hidden (it's Taiwan).
So I suppose the problem comes from these lines, but I can't understand why:
.attr("transform", function(d) {
return "translate(" + proj([ d.lng, d.lat ]) + ")";
});
It is not sufficient to just set the clipping radius via clipAngle() to get the desired behavior. The projection alone will not do the clipping, but just calculate the projected coordinates without taking into account any clipping. That is the reason, why Taiwan gets rendered, although you expected it to be hidden.
But, thanks to D3, salvation is near. You just need to re-think the way you are inserting your circles representing places. D3 has the mighty concept of geo path generators which will take care of the majority of the work needed. When fed a projection having a clipping angle set, the path generator will take this into account when calculating which features to actually render. In fact, you have already set up a proper path generator as your variable path. You are even correctly applying it for the globe, the land and the arcs.
The path generator will operate on GeoJSON data, so all you need to do is convert your places to valid GeoJSON features of type Point. This could be done with a little helper function similar to that used for the arcs:
function geoPlaces(places) {
return places.map(function(d) {
return {
type: "Point",
coordinates: [d.lng, d.lat]
};
});
}
With only minor changes you are then able to bind these GeoJSON data objects to make them available for the path generator which in turn takes care of the clipping:
svg.selectAll(".pin") // Places
.data(geoPlaces(places))
.enter().append("path")
.attr("class", "pin")
.attr("d", path);
Have a look at my fork of your example for a working demo.

Jittering geo paths using D3.js

I'm trying to add 'jitter' or add random noise to a D3.js map that contains line features. Note, this is slightly different from this other example because it involves geo paths. Additionally, while I'd like to use a custom transformation to do this, I don't think I can because I need to be able to use a standard transformation (from WGS84 to NY State Plane). I think the jittering function should either be based on a modified path function, or be a separate function which takes a path as input.
var projection = d3.geo.conicConformal()
.parallels([40 + 40 / 60, 41 + 2 / 60])
.rotate([74, -40 - 10 / 60]);
var path = d3.geo.path()
.projection(projection);
Note that I don't really want to modify the input data at all (i.e., the jittering should be on the paths, not the input geodata). Note also that the jittering can be totally random (i.e., it does not have to be the same every time). My initial thought is to wrap the data in a jitter function, or to wrap the path function in a jitter function. Either way, I'm not really sure where to start on this? Any suggestions? Even a link to the relevant API item would be awesome!
svg.selectAll("path")
.data(jitter(lines.features)) // Wrap data in jitter function... or...
.enter().append("path")
.attr("class", "line")
.attr("d", function(d) { return jitter(path(d)); }) // Jitter path directly
A (simplified) jsfiddle is available here for reference.

Dynamically updating in d3 works for circles but not external SVGs

Suppose I want to dynamically update the position and number of circles on a page using d3. I can do this, using the .data(), .enter(), .exit() pattern. Here is a working example.
http://jsfiddle.net/csaid/MFBye/6/
function updatePositions(data) {
var circles = svg.selectAll("circle").data(data);
circles.enter().append("circle");
circles.exit().remove();
circles.attr("r", 6)
.attr("cx", 50)
.attr("cy", function (d) {
return 20 * d
});
}
However, when I try to do the same thing with external SVGs instead of circles, many of the new data points after the first update do not appear on the page. Example:
http://jsfiddle.net/csaid/bmdQz/8/
function updatePositions(data) {
var gs = svg.selectAll("g")
.data(data);
gs.enter().append("g");
gs.exit().remove();
gs.attr("transform", function (d, i) {
return "translate(50," + d * 20 + ")";
})
.each(function (d, i) {
var car = this.appendChild(importedNode.cloneNode(true));
d3.select(car).select("path")
});
}
I suspect this has something to do with the .each() used to append the external SVG objects, but I am at a loss for how to get around this. Also, the "cx" and "cy" attributes are specific for circles, and so I can't think how they could be used for external SVGs.
Thanks in advance!
There are two problems with your code. The first problem, and reason why you're not seeing all the data points, is that your external SVGs contain g elements, which you are selecting. What this means is that after you first appended the elements, any subsequent .selectAll("g") selections will contain elements from those external SVGs. This in turn means that the data you pass to .data() gets matched to those and hence your selections do not contain what you expect. This is easily fixed by adding a class to the g elements you add explicitly and selecting accordingly.
The second problem is that you're executing the code that appends the external SVGs as part of the update selection. This means that those elements get added multiple times -- not something you would notice (as they overlap), but not desirable either. This is easily fixed by moving the call to clone the nodes to the .enter() selection.
Complete jsfiddle here. As for your question about cx and cy, you don't really need them. You can set the position of any elements you append using the transform attribute, as you are doing already in your code.

d3.js Update circle on line chart

I'm posting there because I couldn't find something on other post or on google.
http://jsfiddle.net/CUQaN/9/
As you can see on the JS fiddle, I have a line chart with circle on each point.
I want to update this chart with new data. The problem is that I can receive less or more point than I already have on the graph. For example, I can have 8 point on my line chart and then when I'm updating the chart, I can have just 4, or even 15 point. And my circle are not updating properly because I'm just changing the value of the circle which already exist.
But I really don't know how to update them properly.
I can have that data sometimes :
var data = [
{"date":"4-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"3-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"2-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"1-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"30-Apr-12","close":Math.random()*354.98,"open":Math.random()*424.56},
{"date":"27-Apr-12","close":Math.random()*24.00,"open":Math.random()*253.89},
{"date":"26-Apr-12","close":Math.random()*490.70,"open":Math.random()*215.54},
{"date":"25-Apr-12","close":Math.random()*42.00,"open":Math.random()*351.23},
{"date":"24-Apr-12","close":Math.random()*210.28,"open":Math.random()*20.23},
{"date":"23-Apr-12","close":Math.random()*20.70,"open":Math.random()*368.34},
{"date":"20-Apr-12","close":Math.random()*412.98,"open":Math.random()*42},
{"date":"19-Apr-12","close":Math.random()*26.44,"open":Math.random()*20.56},
{"date":"18-Apr-12","close":Math.random()*48.34,"open":Math.random()*356.45},
{"date":"17-Apr-12","close":Math.random()*26.44,"open":Math.random()*20.56},
{"date":"15-Apr-12","close":Math.random()*48.34,"open":Math.random()*356.45},
];
And just that data other times : (more or less)
var data = [
{"date":"4-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"3-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"2-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"1-May-12","close":Math.random()*568.13,"open":Math.random()*35.12},
{"date":"30-Apr-12","close":Math.random()*354.98,"open":Math.random()*424.56},
];
Is someone can help me please ?
Thanks a lot !
It might be better to remove the old points and add the new ones rather than trying to move them. To do this you can use an id function to make the points unique - see here
A small example:
svg.selectAll("circle")
.data(myData, function(d) { return d.x; })
.enter()
.append("circle");
The important part here is that points are identified by their x-value, rather than their index in the data point array. Infact this might help in your current situation as the points will only move up/down and not side to side.

Categories