How to pass different arrays to d3.svg.line() function? - d3.js

I have seen an example of d3.svg.line() being as follows:
var altitude = some_array() // I read this from a .json file
y = d3.scale.linear().domain([0, d3.max(altitude)]).range([0 + margin, h - margin]),
x = d3.scale.linear().domain([0, altitude]).range([0 + margin, w - margin])
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d); })
some_svg_group.append("svg:path").attr("d", line(altitude));
although I didn't quite understood what is actually happening, I get that this is a sort of generator function, where line().x and line().y are sort of generator functions, and the idiom shown is a way to have an equally spaced x array versus the actual values in the y array.
What I would like to do, though, is to pass TWO arrays to d3.svg.line(), say distance and altitude, more or less like this:
var line = d3.svg.line()
.x(function_that_gives_distance)
.y(function_that_fives_altitude)
some_svg_group.append("svg:path").attr("something", line(some_other_thing));
Any suggestion on what to replace those placeholders with? Or, if they are wrong, how to achieve that at all?

line.x and line.y are actually accessors. The array you pass into the line generator represents a set of data points and the accessor functions are used to map each data point onto appropriate x and y pixel coordinates.
So with that in mind, it probably doesn't make sense represent the same data points using 2 separate arrays. You should try to transform distance and altitude into a single array of data points ahead of time. Then, you can defined your x and y accessors to map the properties on your data point to the correct pixel locations. Using your placeholder terminology, I would think that function_that_gives_distance and function_that_gives_altitude would actually be invoked as part of the construction of the data points, not in the line generator's accessors.
To summarize, break apart the workflow into 2 steps:
Transform the data into a set of data points you want to base the line on.
Use the line generator accessor functions to do a projection of these data points onto the drawing system's coordinates (i.e. pixel positions).

Related

get point coordination using path.getPointAtLength after rotation in d3 and svg

i want get some points from path after rotating it
i use below code to rotate path
let path= svg.append("path")
.attr("class","offset control")
.attr("d", lineFunction(offsetLineData))
.style("stroke-width", 0.5)
.style("stroke", "red")
.style("fill", "none")
.attr("transform","rotate(90,"+p.x+","+p.y+")")
.attr('transform-origin',"center")
then i want get end point and start point of path
path.node().getPointAtLength(0)
but it return coordination where it don't rotated
how can i get x and y of point after rotation
Think of a SVG as a tree of nested coordinate systems. Every element has its own coordinates, and is fitted into its parent with a rule how to re-calculate them. These rules can be explicit transform attributes, but also implicit combinations of width, height and viewBox ("fit box A in size B").
A transform attribute is considered to be the link to the parent element. That means, if you ask for a geometric property of an element, most of the times you get the value before the transform is applied. After it is applied would be asking for geometric values in the coordinate system of the parent.
Because of this complexity there are several SVG API functions to find out how these coordinate systems fit together. getPointAtLength() gets you coordinates before applying the transform attribute.
var localPoint = path.node().getPointAtLength(0)
First, you have to find out what the transform attribute does. This looks a bit complicated, partly because the attribute can be animated and can contain a list of functions, partly because the API is...well...:
// construct a neutral matrix
var localMatrix = path.node().viewportElement.createSVGMatrix()
var localTransformList = path.node().transform.baseVal
// is there at least one entry?
if (localTransformList.length) {
// consolidate multiple entries into one
localMatrix = localTransformList.consolidate().matrix
}
You can then apply the found transformation to the point with
var transformedPoint = localPoint.matrixTransform(localMatrix)
There are several functions hat will return a SVGMatrix to transform data from the coordinate system after application of the transform attribute (i. e. after the step above):
to ask for the transformation to the nearest viewport (in most cases the nearest parent <svg>) element: element.getCTM()
to ask for the transformation to screen pixels: element.getScreenCTM()
to ask for the transformation to an arbitrary element: element.getTransformToElement(...)

Time axis: Remove scale points with no data

I have a dataset which I am plotting. I've modelled it as a network, and have used a force-directed layout to display it, except that I have constrained the layout such that on the x-axis, the nodes are arranged according to time.
An example of what I've done so far is here on my own website: http://www.ericmajinglong.com/force/force.html
As you can see, I have one time axis. The axis scale is derived from the data. However, you'll notice a big gap in the middle.
I understand the concept of scales, where I have a domain and a range, and a scale basically maps the domain to the range. I have a few questions, however.
I was wondering if it might be possible, without creating two horizontal time axes, to exclude empty months?
Instead of an linear scale, would I have to go to an ordinal scale?
Would there be any disadvantages to going to an ordinal scale instead of a time scale?
Code is not posted here for brevity, but I have it at: http://www.ericmajinglong.com/force/force.js
You can probably use an ordinal scale, but in that case you should make sure that the domain is sorted, and add some mark between the two intervals so the user of the visualization understand that there is a a period not shown. Another option is to create a custom scale that automatically shorten gaps in the data, but will still to add special markers to indicate missing time periods.
If you use an ordinal scale instead of a time scale, you will need to format the axis manually.
EDIT: Add a small example of how a custom scale may be implemented
I would implement a custom scale as a closure with accessors.
function customScale() {
// Scale attributes
var domain = [0, 1], // Default domain
range = [0, 1]; // Default range
function scale(x) {
var y = 0;
// Compute the output value...
return y;
}
// Domain and Range Accessors
scale.domain = function(value) {
if (!arguments.length) { return domain; }
domain = value;
return scale;
};
// range accessor...
return scale;
}
And then configure and use the scale
var scale = customScale()
.domain([0, 10])
.range([2, 3]);
console.log(scale(5));
Using a custom scale will probably implies to create custom axis as well.

generating vertices for voronoi diagram in d3js

In the two examples in d3js website:
Easy Version
More complicated
I find some of their code hard to understand. The D3 reference API did not offer in depth explanation. In the easy version, the vertices were generated using Math.random() like so:
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
Width and height are the document size. This ensure all vertices are within the scope of the SVG element ( svg tag has width and height attribute set to these values too).
However in the more complicated version, it uses an algorithm to generate the vertices:
var vertices = d3.range(numVertices).map(function(d) { return {x: d.x, y: d.y}; })
voronoiVertices = vertices.map(function(o){return [o.x, o.y, o]})
path = path.data(d3.geom.voronoi(voronoiVertices))
When examining the variable vertices in console. I found out that it is a two dimensional array. Each sub-array has a length of 3, first and second element are numbers, and the last element is an object which contains a lot more properties like x, y and index and so on.
The resulting Voronoi diagram are very different. Why is that? They are both generated using d3.geom.voronoi() and (from what I can hopefully understand) the only differences lies in the object that is passed into it. In the easy example it was simply a two dimensional array with 2 numbers in each sub-array. I cannot seem to see how the two lines in the complicated example works to create such a complex object and append it to the end of each sub-array.
How is it ensured that the veritices are within the bound of the parent SVG element too?
I am really stuck, thank you so much for any help.

D3's scale not working properly

Well, I'm starting with D3 and I'm trying to create a vertical bar chart.
I really don't know what's happening but some things are not working as expected for me (maybe because I'm just a noob on the matter).
I'm using line scales, works pretty well with axes, but it's miscalculating the height of the bars, for instance the higher values are not displayed (because of the low value of the result).
I've used the d3.max to determine the range. I really don't get what's happening.
var yScaleLeft = d3.scale.linear()
.domain([0, d3.max(stats)])
.range([realHeight, 0]);
.attr("height", function(d) {
return yScaleLeft(d);
});
Here is the code: http://jsfiddle.net/aKhhb/ Look at * Scales * and // Stats bars
(Just forget about the x-alignement of the bars, I will see that later, I want to set its height properly)
Thanks a lot! Y saludos desde Chile :)
The issue is that your input and output ranges are mirrored -- that is, the largest input value maps to the smallest output value. That is fine, but you need to take it into account when calculating the y and height attributes. Essentially, you had the calculations for both reversed.
Fixed fiddle here. I've also fixed the x axis by adding your margin and half of the bar width to the computed x positions. Oh and you don't need parseInt() when doing calculations, only when you actually want to parse an integer from a string.

What does this.__chart__ refer to in a d3 selection?

I've been poking around in d3.box and d3.bullet. In both cases, the current scale is retrieved using something like the following...
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
...where x1 is the current scale object. I realise that this idiom is used to update the box plot or bullet chart, but could anyone explain how? What does this.__chart__ refer to? A scale object? Why the conditional? (||) Why does x0's domain cover the range 0 to infinity when the current scale is unlikely to have such a large range?
Apologies if my questions are poorly specified. Any help would be greatly appreciated.
The this context is the DOM element that contains the chart: i.e. a g element. Binding some variable to the DOM element, e.g. this.myvar = state, provides a way to deal with chart specific state. Multiple update calls on one specific chart g element, will then all have access to the same variable.
Mike and Jason have used the property name __chart__ in various charts and also in the d3 axis component, for keeping track of chart specific state.
You're right that in this case it's the scale that is being stored in the g element's __chart__ property. See excerpt from bullet.js:
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
So, a scale x1 is determined based on the current data. It will be stored in __chart__, to be used some time in the future when this chart is updated with new data.
The previous scale is taken from this.__chart__ and kept in x0. The this.__chart__ will return undefined when the chart is just being constructed (i.e. the enter phase). In that case x0 will instead become d3.scale.linear().domain([0, Infinity]).range(x1.range()). See short circuit eval.
The old scale is needed for smooth transition. When new data points are entered, we first want to plot them on the chart using the old scale. After that we will transition all points (new and updated) according to the new scale.
Regarding the [0, Infinity] domain. So, a scale with this domain will only be used when the chart is just constructed. That means it provides a way to setup the initial transition when introducing the chart. A infinite domain with a finite range means that all points are scaled to 0. So, when the chart is set up, all points will be plotted at 0 and transition to the proper values according the x1 scale.

Resources