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.
Related
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]}
]
I'm following Mike Bostock's tutorial here to create a bubble chart... except that I'm using my own dataset and I'm using d3 v4. I'm quite new to d3 and I understand a lot has changed in v4 from v3. I'm having trouble converting the sample code to v4.
For instance, I've converted this code in d3 v3:
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
to:
var bubble = d3.pack(dataset)
.size([diameter, diameter])
.padding(1.5);
Is the above correct? I'm not sure since I'm not having any errors till this point.
But I get an error in the following piece of code:
var node = svg.selectAll(".node")
.data(
bubble.nodes(root)
.filter(function(d) {
return !d.children;
})
)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
with a bubble.nodes is not a function. What is the equivalent in d3 v4?
Fiddle: https://jsfiddle.net/r24e8xd7
Here is your updated fiddle: https://jsfiddle.net/r24e8xd7/9/.
Root node should be constructed using d3.hierarchy:
var nodes = d3.hierarchy(dataset)
.sum(function(d) { return d.responseCount; });
Then pack layout can be called:
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
Comparing the different docs, v3
# pack(root)
# pack.nodes(root)
Runs the pack layout, returning the array of nodes associated with the
specified root node. The cluster layout is part of D3's family of
hierarchical layouts. These layouts follow the same basic structure:
the input argument to the layout is the root node of the hierarchy,
and the output return value is an array representing the computed
positions of all nodes. Several attributes are populated on each node:
parent - the parent node, or null for the root.
children the array of child nodes, or null for leaf nodes.
value - the node value, as returned by the value accessor.
depth - the depth of the node, starting at 0 for the root.
x - the computed x-coordinate of the node position.
y - the computed y-coordinate of the node position.
r - the computed node radius.
to the newer v4
# pack(root) <>
Lays out the specified root hierarchy, assigning the following
properties on root and its descendants:
node.x - the x-coordinate of the circle’s center
node.y - the y-coordinate of the circle’s center
node.r - the radius of the circle
You must call root.sum before passing the hierarchy to the pack
layout. You probably also want to call root.sort to order the
hierarchy before computing the layout.
it looks like pack() is what you are looking for, but it looks like you might need a change or two before you do.
update
Quick look into different things and there are a few things going on that its not just a simple fix.
your data is entirely different to the example and is flat, which
effects the diagram.
why not use v3? Most of the examples out there are in v3 and like you
said you are new to d3. why make things difficult.
Finally start small. I would suggest trying to find a small bubble
chart first and then make your way up, or substitute your data into
the example code and get that working and then incrementally change
it instead of trying to change multiple things at once.
I've been attempting to learn better visualization with node.js and the mapbox library.
Using this example here: Running Map Example
I'd like to add a graph of speed, and allow a user to click on a node, and see data about that position in a little popup - For today, I just want to get speed working.
It seems to be a recursive algorithm, so I need to implement variables to store the previous position and time, but I've ran into three problems:
I don't know how to use this date format: "2015-01-19T21:24:20Z" or Chroniton's parsing of it to generate a subtractable number to get the difference in time.
I don't know how to get the distance between two points using the code given, I could simply do sqrt((.x(point1) - .x(point2)) + (.y(point1) + .y(point2)), but I'm not sure how coordinates are stored or parsed in this example.
I don't know where to calculate the speed. It seems like the coordinates are only defined after the graphs are displayed, since the coordinates aren't used in the graphics. I am probably wrong, but I need some direction.
Here is what I have now:
Using the elevation display as my template, I think I have made it able to display the line by adding in the following three snippets:
Setting the scale:
var speed = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(dataRet, function(d) {
return d[1][2];
})]);
Adding in the line, with data:
var SpeedLine = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return speed(d[2])})
Displaying the line:
svg.append('path')
.datum(dataRet)
.attr('class', 'speed-line')
.attr('d', speedLine);
I know I have to add in a speed function similar to this psudocode:
var dt = chroniton.domain(Time1, Time2)
var speed[i] = LongLat(previousPoint).distanceto(currentPoint)/dt
And on the popup box:
dt.format(something to do with time formatting)
Note 1:, I changed the name of the function datePlaceHeart to dataRet since I'll be adding new things to do it, and datePlaceHeartSpeedStuffAndThings was getting a bit long ;)
Note 2: I haven't been able to start the pop-up because I haven't figured out how to calculate speed using the given data, and well, it seems kinda silly to do the easy one first. (With my luck, its actually not easy)
Please help? Here is my edited code in full (Edited index.js):
Code
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.
I have three arrays of points that define three paths, and need to move a different point along each path w/ the option of changing speed. The three arrays are contained in another array, so the structure basically looks like this:
DataByDays = [ [(array of x1's), (array of y1's)], [(array of x2's), (array of y2's)], [(array of x3's), (array of y3's)]
I have already plotted the paths and have an array that holds them similarly, like:
PathArray = [path1, path2, path3]
Currently, I've created each of the trackers:
trackers = svg.selectAll("circle").data(dataByDays)
.enter()
.append("circle")
.attr("cx", function(d){return d[0][0];}) //x coord # start of path
.attr("cy", function(d){return d[0][1];}) //y coord # start of path
.attr("r", 5)
.attr("fill", "black");
I've tried to adapt a variety of solutions to similar problems but nothing has worked. I'd like to start with just getting the trackers to move along the path, but also need to incorporate the ability to change the speed at which the trackers move with a slider that I've already created.