I'd like to create a line using polar coordinates.
Example:
A line whose centre is at cx=0, cy=0, at an angle of pi/4, but only drawn from a start radius of 4 and end radius of 7.
I could use maths and do the job myself, but d3.js appears to have a radial line generator, but I'm finding the documentation hard to grasp, being a d3 noob.
Using the line generator is relatively straightforward. You can use it with the default options and specify everything in the data like this:
d3.svg.line.radial()([[4,Math.PI/4],[7,Math.PI/4]]);
Alternatively, you could only provide the data that changes and everything else as a default:
var line = d3.svg.line.radial()
.angle(Math.PI/4)
.radius(function(d) { return d; });
line([4,7]);
Related
I am using NVD3.js multiChart to show multiple lines and bars in the chart. All is working fine, but the x-axis labels is aligned only to the line points, not bars. I want to correctly align labels directly below the bars as it should. But I get this:
With red lines I marked where the labels should be.
I made jsFiddle: http://jsfiddle.net/n2hfN/
Thanks!
As #Miichi mentioned, this is a bug in nvd3...
I'm surprised that they have a TODO to "figure out why the value appears to be shifted" because it's pretty obvious... The bars use an ordinal scale with .rangeBands() and the line uses a linear scale, and the two scales are never made to relate to one another, except in that they share the same endpoints.
One solution would be to take the ordinal scale from the bars, and simply adjust it by half of the bar width to make the line's x-scale. That would put the line points in the center of the bars. I imagine that something similar is done in the nv.models.linePlusBarChart that #LarsKotthoff mentioned.
Basically, your line's x-scale would look something like this:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
...where xScaleBars is the x-scale used for the bar portion of the chart.
By combing through the source code for nvd3, it seems that this scale is accessible as chart.bars1.scale().
Maybe someday the authors of nvd3 will decide that their kludge of a library deserves some documentation. For now, I can show you the kind of thing that would solve the problem, by making a custom chart, and showing how the two scales would relate.
First, I'll use your data, but separate the line and bar data into two arrays:
var barData = [
{"x":0,"y":6500},
{"x":1,"y":8600},
{"x":2,"y":17200},
{"x":3,"y":15597},
{"x":4,"y":8600},
{"x":5,"y":814}
];
var lineData = [
{"x":0,"y":2},
{"x":1,"y":2},
{"x":2,"y":4},
{"x":3,"y":6},
{"x":4,"y":2},
{"x":5,"y":5}
];
Then set up the scales for the bars. For the x-scale, I'll use an ordinal scale and rangeRoundBands with the default group spacing for nvd3's multiBar which is 0.1. For the y-scale I'll use a regular linear scale, using .nice() so that the scale doesn't end on an awkward value as it does by default in nvd3. Having some space above the largest value gives you some context, which is "nice" to have when trying to interpret a chart.
var xScaleBars = d3.scale.ordinal()
.domain(d3.range(barData.length))
.rangeRoundBands([0, w], 0.1);
var yScaleBars = d3.scale.linear()
.domain([0, d3.max(barData, function(d) {return d.y;})])
.range([h, 0])
.nice(10);
Now here's the important part. For the line's x-scale, don't make a separate scale, but just make it a function of the bars' x-scale:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
Here's the complete example as a JSBin. I've tried to document the major sections with comments so it's easy to follow the overall logic of it. If you can figure out from the nvd3 source code exactly what each of the elements of the multiChart are called and how to set the individual scales of the constituent parts, then you might be able to just plug in the new scale.
My feeling on it is that you need to have a pretty good handle on how d3 works to do anything useful with nvd3, and if you want to customize it, you're probably better off just rolling your own chart. That way you have complete knowledge and control of what the element classes and variable names of the parts of your chart are, and can do whatever you want with them. If nvd3 ever gets proper documentation, maybe this will become a simple fix. Good luck, and I hope this at least helps you get started.
I have some geoJson data that I am charting using d3.geo.
When I write something like
d3.select("svg")
...
.attr("d", function(d) {
return path({
type:"MultiPoint",
coordinates: get_activity_coords_(d.activities)
});
})
I always get a circle for each coordinate. The coordinates represent locations of various stopping points of a journey. What I would prefer is a different shape for the first and the last coordinate.
Is it possible to do this using MultiPoint, is there an example that I can follow? I could draw the points one by one, but I recall reading that MultiPoint is far faster. Plus, the code would be much clearer to read.
Thanks a lot.
You can't do different shapes for MultiPoint geoJSON with d3.geo.path. You can change the radius based on a function, but it looks like you can only set it per feature and not per point, so you'd have to break your set of points into multiple features and lose any performance benefit from using the single element.
However, there are other ways to go about doing this.
One option, as you mentioned, is to create a nested selection with a separate <path> element for each point, and draw each path using a d3.svg.symbol() function. You can then customize the symbol function to be based on data or index.
var trips = d3.select("svg").selectAll("g.trips")
.data(/*The data you were currently using for each path,
now gets to a group of paths */)
.attr("class", "trips");
//also set any other properties for the each trip as a whole
var pointSymbol = d3.svg.symbol().type(function(d,i){
if (i === 0)
//this is the first point within its groups
return "cross";
if ( this === this.parentNode.querySelector("path:last-of-type") )
//this is the last point within its group
return "square";
//else:
return "circle";
});
var points = trips.selectAll("path")
.data(function(d) {
return get_activity_coords_(d.activities);
//return the array of point objects
})
.attr("transform", function(d){
/* calculate the position of the point using
your projection function directly */
})
.attr("d", pointSymbol);
Another option, which allows you to set custom shapes for the first and last point (but all intermediary points would be the same) is to connect the points as the vertices of a single, invisible <path> element and use line markers to draw the point symbols.
Your approach would be:
Create a <defs> element within your SVG (either hard-coded or dynamically with d3), and define the start, middle and end marker points within them. (You can use d3.svg.symbol() functions to draw the paths, or make your own, or use images, it's up to you.)
Use a d3.svg.line() function to create the path's "d" attribute based on your array of point coordinates; the x and y accessor functions for the line should use the projection function that you're using for the map to get the x/y position from the coordinates of that point. To avoid calculating the projection twice, you can save the projected coordinates in the data object:
var multipointLine = d3.svg.line()
.x(function(d,i) {
d.projectedCoords = projection(d);
return d.projectedCoords[0];
})
.y(function(d){ return d.projectedCoords[1];});
(You can't use your d3.geo.path() function to draw the lines as a map feature, because it will break the line into curves to match the curves of longitude and latitude lines in your map projection; to get the line markers to work, the path needs to be just a simple straight-line connection between points.)
Set the style on that path to be no stroke and no fill, so the line itself doesn't show up, but then set the marker-start, marker-mid and marker-end properties on the line to reference the id values of the correct marker element.
To get you started, here's an example using d3 to dynamically-generate line markers:
Is it possible to use d3.svg.symbol along with svg.marker
I followed D3 multi-series line chart example and could successfully draw it. Now I need to add a line which is mean or average of all the lines drawn in the chart. Consider following points are plotted on chart to draw two lines:
Line 1 : [x:10, y:10], [x:20, y:20], [x:30, y:10]
Line 2 : [x:10, y:10], [x:20, y:18], [x:30, y:15]
Then mean line would be :
Mean : [x:10, y:(10+10)/2], [x:20,y : (20+18)/2], [x:30,y:(10+15)/2]
Is there a direcy way of drawing this mean line in D3 or i'll have to compute the mean values for y and draw a new line?
Hope this makes it clear.
Thanks in advance.
You can use d3.mean to compute this. The easiest way is to simply add another element to cities:
cities.push({
name: "mean",
values: data.map(function(d) {
return {date: d.date,
temperature: d3.mean(color.domain().map(function(e) { return +d[e]; } ))};
})
});
Full example here.
In a D3 or NVD3.js line graph, how can I select a particular line once the graph is rendered? For example, suppose I want to animate the stroke width on a line, like this:
d3.selectAll('path').transition().duration(2000).style("stroke-width", "20");
The above will select all paths, obviously, but I would like to select a particular series—for example, the Oranges series in a data set defined like this:
var data = [{key: "Apples", values: array1},{key: "Oranges", values: array2}]
I thought something this might work, but it did not:
d3.select('#chart svg').datum(data[1]).transition... // or alternatively,
d3.select('#chart svg').datum(data[1].values).transition...
I've been trying to figure it out using the Cumulative Line Chart example in the code editor here, with no success: http://nvd3.org/livecode/#codemirrorNav
This is a very basic question, but I'm new to D3 and have been unable to figure it out.
There are couple of simple ways that I can think of:
You can store each path in its own variable (or inside an array):
var path1 = graph.append("g").append("path").data([data1]).attr("class", "line1");
Now you can apply your transitions to just this path variable and it should work.
Another option is to give each path a unique class and then use d3.selectAll(".uniqueclassname") and apply your transitions.
In this fiddle, look at the tick function (specially for the following piece of code).
// redraw the lines
graph.select(".line1").attr("d", line).attr("transform", null);
path2.attr("d", line).attr("transform", null);
path3.attr("d", line).attr("transform", null);
graph.select(".line4").attr("d", line).attr("transform", null);
Is there a way in d3 to not draw overlapping tick labels? For example, if I have a bar chart, but the bars are only 5 pixels wide and the labels are 10 pixels wide, I end up with a cluttered mess. I'm currently working on an implementation to only draw the labels when they do not overlap. I can't find any existing way to do that, but wasn't sure if anyone else had dealt with this problem.
There is no way of doing this automatically in D3. You can set the number of ticks or the tick values explicitly (see the documentation), but you'll have to figure out the respective numbers/values yourself. Another option would be to rotate the labels such that there is less chance of them overlapping.
Alternatively, like suggested in the other answer, you could try using a force layout to place the labels. To clarify, you would use the force layout on the labels only -- this is completely independent of the type of chart. I have done this in this example, which is slightly more relevant than the one linked in the other answer.
Note that if you go with the force layout solution, you don't have to animate the position of the labels. You could simply compute the force layout until it converges and then plot the labels.
I've had a similar problem with multiple (sub-)axis, where the last tick overlaps my vertical axis in some situations (depending on the screen width), so I've just wrote a little function that compares the position of the end of the text label with the position of the next axis. This code is very specific to my use case, but could adapted easily to your needs:
var $svg = $('#svg');
// get the last tick of each of my sub-axis
$('.tick-axis').find('.tick:last-of-type').each(function() {
// get position of the end of this text field
var endOfTextField = $(this).offset().left + $(this).find('text').width();
// get the next vertical axis
var $nextAxis = $('line[data-axis="' + $(this).closest('.tick-axis').attr('data-axis') + '"]');
// there is no axis on the very right, so just use the svg width
var positionOfAxis = ($nextAxis.length > 0) ? $nextAxis.offset().left : $svg.offset().left + $svg.width();
// hide the ugly ones!
if (endOfTextField > positionOfAxis) {
$(this).attr('class', 'tick hide');
}
});
The ticks with color: aqua are the hidden ones: