Transition partition arcs back to original angles - d3.js

How can I transition arc angles, initially defined by d3.svg.arc(), back to their original values when using d3.layout.partition?
I am trying to store the initial startAngle and endAngle values d.x and d.dx somewhere so that I can transition back to them at a later state. However, I do not know:
At what point d.x and d.dx are initialized, whether in d3.svg.arc() or when the arc paths are actually appended.
How to bind d.x and d.dx to the elements when I am using a partition to render them.
Normally I might bind the initial startAngle and endAngle to the elements with datum(). I believe I am looking for something similar to:
selectAll('path').data(function(d) {
return partition.values({
'initialStart': d.x,
'initialEnd': d.dx
}).nodes(d)
});

In principle this is the same as transitioning pie charts, where you need a custom tween function to get the animation right. For this it is necessary to save the original value in a separate attribute -- in your case you can do the same thing.
Attributes are set when the layout is run; this is completely independent of the rendering and appending elements to the DOM.

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(...)

Graphing live re-syncing data in D3js in a fixed window with linear easing

I'm interested in graphing live-ish data in D3js. Now, when I say "live-ish" I mean that I'll be collecting data every 200ms +/- 10ms, but there may be several minute long periods of inactivity. Fortunately, the input data is time-stamped!
What I have so far: I've followed some line drawing in d3 guides (eg: this) and I have a Y axis with the value range/domain I want. I have an X axis with the range I want and a moving domain as per a standard time-series fixed-width graph. That is, if my graph's x axis domain is (0:15, 0:35) in 5 seconds it will be (0:20, 0:40). This transitions nicely as it's using linear easing.
I have mock-data being output each iteration of the graph tick. My domain is set up as such that new points are just out of the x-axis domain such as to allow the smooth effect as per 1. All in all, it looks great.
So where do I go from here? My desired result: data comes in asynchronously and is placed precisely at its x-axis time-stamped location. If data is up to date, it gets placed juuust outside the x-axis domain and has a smooth transition in. If data doesn't arrive in time, the graph continues without drawing any new points until data is received, at which time it adds each point at its appropriate time-stamp retroactively. If data for the missing period doesn't arrive at all, we just continue with a gap in the graph. I can emulate this by calling...
d3.select(window).on("click", .. )
Effectively, I can click to add random data at the current time-stamp using some anonymous function which allows me to mimic the data / event structure my code should handle.
I think my current confusion is due to how I add data and draw the path from it.
var line = d3.svg.line()
.interpolate("basis-open")
.x(function(d, i) { return x(now - (n-1-i)*duration); })
.y(function(d, i) { return y(d); });
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
The big question: my y-values correspond to the path.datum(data) (data is just an array of values) appropriately, but when I push to the data array to draw the line, it always places each point graphically at equal distance apart. How do I break out of the mono-variable graph without destroying my time-series smooth scrolling animation? I could record a second array of timestamps alongside my data array, but how to I integrate those into the line? Ideally, I'd have them both be a part of the same array so I could sort by timestamp so when I call data.shift() truly the oldest data is gone. I tried changing the "duration" of the transition function but it made the graph accelerate weirdly and didn't actually break the equidistance of points on x.
How do I set up the y-axis graph to also take into account x-location without breaking my graph?
Alright, so I figured out a pretty straightforward way of doing exactly what I wanted while still using .datum(data) instead of .data(data) (where 'data' is my array). Instead of passing in an array of values to .datum, I pass in an array of objects. Or, what was once data = [value1, value2, ...] is now data = [{gx:timestamp1, gy:value1}, {gx:timestamp2, gy:value2}, ...].
My line x-axis / y-axis functions are now...
.x(function(d, i) { return x(d.gx); })
.y(function(d, i) { return y(d.gy); });
Which ends up being a bit neater than my initial run at it. My transition functions didn't have to change.
The final puzzle piece was what to do about data that comes in time-stamped but out of order. Fortunately, my display domain isn't very large and thus, I don't need to store many values in the array. As such, when new data comes in I simply sort it to make the line not a wobbly-bobbly mess.
data.sort(function(a,b){ return a.gx - b.gx});
And voila! If the size of the array is beyond the boundary, shift off data. This gives us the effect of a sliding timeseries window of the past n seconds where time-stamped data points may be arbitrarily dumped on and displayed properly.

why does d3 rescale all scales during zoom?

The problem is that my yScale changes upon panning.
Here's the definition of the scale:
this.yScale = d3.scale.linear()
.domain([0, this.maxY * this.yHeader])
.rangeRound([0, height * this.yHeightScalor]);
I need to keep hold of the scale (i.e. use this.yScale instead of just var yScale) because of my redraw function. The trick is, panning = zooming where d3.event.scale === 1 and zooming rescales domains as you can see if you put a breakpoint in D3's zoom.js rescale function.
I can get around it by making sure my yScale is defined correctly when used but it seems to me that somethings amiss.
UPDATE: I've included the code and removed the line causing the issues. Truth be told, I only needed the xScale to zoom for user feedback. I redraw everything after the zoom anyway (in zoomEndFunction).
zoom = d3.behavior.zoom()
.x(this.xScale)
.y(this.yScale) // <--------------------- I removed this
.scaleExtent([0.5, 2])
.on("zoom", zoomFunction(this))
.on("zoomend", zoomEndFunction(this));
svg = histogramContainer.append("svg")
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.call(zoom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ' , ' +
(height - this.margin.bottom) + ')');
// everything else is in the svg
The d3 zoom behaviour primarily acts as a wrapper for handling multiple events (mousewheel, drag, and various touch gestures), and converting them into translate and scale values, which are passed to your zoom event handlers as properties of the d3.event object.
However, you can also register a quantitative (non-ordinal) scale on the zoom behaviour using zoom.x(xScale) and zoom.y(yScale). The zoom behaviour will adjust each scale's domain to reflect the translation and scale prior to triggering the zoom event, so that all you have to do is redraw your visualization with those scales.
You do not have to register your scales on the zoom behaviour, or you can register one scale but not the other. For example,
if you are using transformations to zoom the visualization (a "geometric zoom", in d3 parlance), you don't need to change your scales at all;
if you are using a polar coordinates system, instead of x/y coordinates, you'll need to calculate the adjustments directly;
if you have a graph with a meaningful baseline, but very dense data, you may want to zoom/pan the horizontal axis but not the vertical.
From the comments, it sounds like the last situation reflects your case.

Maximum distance limit for D3 Force Layout?

I'm using a force layout with a small negative charge to avoid having SVG elements on top of each other. However, I need the items to remain within ~20px of their original location. Is there any means of limiting to the total net X/Y distance the items move?
Each SVG element represents a bus stop, so it's important that items do not overlap but also do not move too far from their original location.
There's no option for this in the force layout itself, but you can do that quite easily yourself in the function that handles the tick event:
force.on("tick", function() {
nodes.attr("transform", function(d) {
return "translate(" + Math.min(0, d.x) + "," + Math.min(0, d.y) + ")";
});
});
This would bound the positions to be to the top right of (0,0). You can obviously modify that to bound in any other way as well (potentially with a nested Math.min/Math.max). You could even do that dynamically by storing allowed position ranges with the element and referencing that.
See here for an example that uses this technique to restrict the position of labels floated using the force layout.

how to avoid overlap of shapes when using d3.js

I am drawing circles by setting a fixed x position but a changing y position. The problem is the circles are overlapping since the radius of each circle is different.
Ideally in theory to solve that I would probably want to get the y position of the previous circle and add the radius of the current circle to it to get the y position of the current circle. Correct me if I am thinking it wrong.
Right now I am doing something like this now
var k = 10;
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d) {
return (k++) * 10; //this is a very gray area
})
And I am getting an overlap. Ideally I would like to space the circles form each other. Even if the outer edges touch each other I could live with that. How should I approach it?
I am writing a range which i am using to get the radius
var rScale = d3.scale.linear()
.domain([min, max])
.range([10, 150]);
and simply passing that as the radius like this
.attr("r", function(d) { return rScale(d.consumption_gj_);})
This is my fiddle
http://jsfiddle.net/sghoush1/Vn7mf/27/
Did a solution here: http://tributary.io/inlet/6283630
The key was to keep track of the sum of the radius of all previous circles. I did that in a forEach loop:
data.forEach(function(d,i){
d.radius = rScale(d.consumption_gj_);
if (i !== 0){
d.ypos = d.radius*2 + data[i-1].ypos;
}
else {
d.ypos = d.radius*2;
}
})
then, when setting the attributes of the circles you can use your new d.radius and d.ypos
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d,i) {
return d.ypos + 5*i;
})
.attr("r", function(d) { return d.radius;})
The Charge Property
The charge in a force layout refers to how nodes in the environment push away from one another or attract one another. Kind of like magnets, nodes have a charge that can be positive (attraction force) or negative (repelling force).
From the Documentation:
If charge is specified, sets the charge strength to the specified value. If charge is not specified, returns the current charge strength, which defaults to -30. If charge is a constant, then all nodes have the same charge. Otherwise, if charge is a function, then the function is evaluated for each node (in order), being passed the node and its index, with the this context as the force layout; the function's return value is then used to set each node's charge. The function is evaluated whenever the layout starts.
A negative value results in node repulsion, while a positive value results in node attraction. For graph layout, negative values should be used; for n-body simulation, positive values can be used. All nodes are assumed to be infinitesimal points with equal charge and mass. Charge forces are implemented efficiently via the Barnes–Hut algorithm, computing a quadtree for each tick. Setting the charge force to zero disables computation of the quadtree, which can noticeably improve performance if you do not need n-body forces.
A good tutorial that will help you see this in action:
http://vallandingham.me/bubble_charts_in_d3.html

Resources