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'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.
I try to make a simple transition / interpolation between two path / shapes (designed in illustrator).
I have d3 in my project, so I use it; but I could try something else if I can figure out how to do.
I Define a few variables (including the two path) :
var width = 300,
height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = svg.append("path"),
d0 = "M12,2c5.514,0,10,4.486,10,10c0,5.514-4.486,10-10,10C6.486,22,2,17.514,2,12C2,6.486,6.486,2,12,2z M12,0 C5.372,0,0,5.372,0,12c0,6.629,5.372,12,12,12s12-5.371,12-12C24,5.372,18.628,0,12,0z",
d1 = "M12,19.938c5.514,0,10-13.452,10-7.938c0,5.514-4.486,10-10,10C6.486,22,2,17.514,2,12C2,6.486,6.486,19.938,12,19.938z M12,0C5.372,0,0,5.372,0,12c0,6.629,5.372,12,12,12s12-5.371,12-12C24,5.372,18.628,0,12,0z";
Then I start to loop on the transition :
loop();
function loop() {
path
.attr("d", d0)
.transition()
.duration(5000)
.attr("d", d1)
.transition()
.delay(5000)
.attr("d", d0)
.each("end", loop);
}
And the weird things happen !
I have a long list of errors in my console : Error: Problem parsing d="M12,10.9402992c5.514,0,10-4.4542992,101.0597007999999999c0,5.514-4.486,10-10,10C6.486,22,2,17.514,2,12C2,6.486,6.486,10.9402992,12,10.9402992z M12,0C5.372,0,0,5.372,0,12c0,6.629,5.372,12,12,12s12-5.371,12-12C24,5.372,18.628,0,12,0z"
Which is not a valid svg. As far as I understand what this is about, svg path with float numbers are non valid (but I do have floats in my base path and they work ; so I'm not so sure).
It appears that I should try to round all the numbers all along the transition.
But the fact I really don't understand is why it works on the last 75% of the loop and not on the first 25%.
Here is a fiddle to see the fail in action : http://jsfiddle.net/vQabH/
And why it works on this one : http://jsfiddle.net/9bC6M/
(which I made from this example : http://bl.ocks.org/mbostock/3081153 )
The problem is that in your paths, negative values are not separated by space or comma. For example, there is c5.514,0,10,4.486,10,10 (3 pairs of values, separated by comma), but c0,5.514-4.486,10-10,10 (again 3 pairs of values, but not all separated with commas). This messes up the D3 transition.
D3 simply doesn't know how to interpolate between strings like that. For example, you're getting a parse error for c5.514,0,10-4.4542992,101.0597007999999999 (2 pairs of values and a spurious number).
It works fine when you separate the negative values from the rest, see here. Technically, you could argue that it should still work as it's a valid SVG path, so you may want to open an issue about this on the D3 website.
I have a series of horizon graphs that have been created like this:
d3.select("body")
.selectAll(".horizon")
.data(metrics)
.enter()
.append("div")
.attr("class", "horizon")
.attr("id", function(d){return d.toString();})
.call(context.horizon().height(['75']));
where metrics is an array of metric objects.
I want to redefine the extent for one of those horizon objects.
I've tried calling the extent function on the one graph, but I'm not sure if I'm calling it, or even selecting it, correctly:
d3.select("#id-attribute")
.call(context.horizon().extent(function(d,i){return([0,1000]);}));
This sort-of seems to work, but the graph display gets screwed up, with additional whitespace being added below the graph and the motion of the graph not accounting for that and leaving the top of the graph unanimated. I suspect that it's in some way due to it being a new instance of the extent object, so I tried this:
d3.select("#id-attribute")
.call(extent(function(d,i){return([0,1000]);}));
but that generates: "ReferenceError: extent is not defined".
I've tried redefining the metric's extent function, effectively:
metrics[3].extent = function(d,i) {return([0,100]);};
but that causes the graph to be blank (although mousing over it reveals numbers in the readout), and causes its readout and the readouts of the graphs that appear below it to be blank when the mouse is not hovering over any of the graphs.
I honestly have no comprehension of how this stuff fits together, nor am I particularly experienced with JavaScript, so I'm not sure where my error lies. I'm totally out of my depth. Any tips would be greatly appreciated.
Do you need to redefine the extent after the initial call where the horizons are generated?
If not, try something like this:
d3.select("body")
.selectAll(".horizon")
.data(metrics)
.enter()
.append("div")
.attr("class", "horizon")
.attr("id", function(d){return d.toString();})
.call(context.horizon()
.height(['75'])
.extent(function(d,i){ //use this function to change extents
if (d.toString() == 'Whatever the name is'){
return [0,1000];
} else {
return [ <default min>, <default max>]
}
})
)
You can either set your own default extent, or use a function like d3.min()/d3.max() to get the value for the horizons. You just need to return an array with two elements.