I've just started learning d3. I've made some progress on learning, but I've run into something I haven't been able to figure out on my own.
Here is what I have so far: http://tributary.io/inlet/83fba4500986b4638326
What I've been trying to figure out how to do is fade in the data points as the line path animates through them. The best idea I had was dividing the transition time by the number of points and then have the delay for each data point be decided by that, but I had trouble getting that working properly.
Is there a reasonable way to do this?
P.S. I also seem to have lost my y-axis labels and am not sure why... any ideas?
Thanks for your time and help!
So let's start by the easy part. The Y axis is missing because your svg elements have an overflow:hidden and the second svg element is stuck to the top left of corner of the first one. some x,y space plus overflow:auto solve the problem.
For fading the circle when the path go trought it, I don't think you can do that with one transition. So, a solution, because the path drawing works on offset from total length to 0, you can calculate the distance between each circle and "transition" the path from circle to circle, fading it at the end of the transition. To do so, get the circles coordinates, calculate the distances, and set the transition loop.
//Get coordinates
svg.selectAll("circle").each(function(){
coordinates.push({x:d3.select(this).attr("cx"),y:d3.select(this).attr("cy")});
});
//Get Distances
for(var j=0;j<coordinates.length-2;j++)
{
var a,b,distance;
a= coordinates[j];
b= coordinates[j+1];
distance = Math.sqrt((Math.pow(b.x-a.x,2))+(Math.pow(b.y-a.y,2)));
//console.log("j:" + j + " a: " + JSON.stringify(a) + " b: " + JSON.stringify(b) + " distance: " + distance);
distanceData.push(distance)
}
//Loop transition
var counterTransition =0,currentLength=distanceData[counterTransition];
path
.attr("stroke-dasharray", totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(500)
.ease("cubic")
.attr("stroke-dashoffset",totalLength-currentLength)
.attr("class", "line").each("end",animeNext);
function animeNext(){
counterTransition +=1;
if(counterTransition<=distanceData.length)
{
var circle =svg.selectAll("circle")[0][counterTransition];
circle=d3.select(circle);
circle.attr("opacity",0.5);
currentLength += distanceData[counterTransition] ;
path
.transition()
.duration(500)
.ease("cubic")
.attr("stroke-dashoffset",totalLength - currentLength)
.attr("class", "line").each("end",animeNext);;
}
Example : http://tributary.io/inlet/e2eab2e689479008f11c
I simplified the data generation for the sake of the test. Removed the draw on click. It looks like that the code executes 1 to 3 times randomly but I think it is tributary.
You can play with the duration to improve transition smoothness.
Hope it helps!
EDIT: Other solution with one transition, using a short interval(100ms) and checking if the path's stroke-dashoffset is equal or greater than each circle's distance and if so fade the circle. Clear the interval at the end of the transition.
Related
I'm fiddling around with D3.js in this plunker.
It mostly does what I want, but I noticed a small inconsistency.
The idea is that the 2nd slider rotates the SVG elements in the container. As the elements within are actually text elements, I would like to have them displayed unrotated, and as such, I applied a transform: rotate to them with the symmetric value from the slider.
Although, I noticed that by doing that, the text elements don't rotate around their center, but rather around their top-left corner (I think). This is mostly visible when you observe a point near the edge and see how in transposes the edge on rotation.
I tried already setting both text-anchor and alignment-baseline to middle on these elements, hoping it would offset the text path, but apparently it doesn't change the point around which they pivot when rotated.
Additionally, should I try to set the rotate with pivot point coordinates, it boggles the effect entirely, by applying some translate to the elements, which I can't figure out.
Not sure if getBBox() could help me either in this subject, but I've considered maybe offsetting the points by half their width/height. This seems a bit convoluted though, and I was hoping for an easier/more elegant/cleaner fix.
Any thoughts?
Well, this is awkward. Right after I posted the question, I found an answer in D3.js docs: polygon.centroid().
Basically, I used the return value of this function as the transform: rotate pivot point coordinates and the offset is taken care of. Example (line 99 of the above plunker, rotate() function):
(...)
var textElement = d3.select(this);
return justTranslate+"rotate("+ -value+ textElement.centroid() +")";
(...)
Hope this helps anyone with the same issue.
EDIT: for some reason, Chrome's console says:
Uncaught TypeError: textElement.centroid is not a function
But the behavior is what I was looking for. Have no idea why.
EDIT2: ended up changing the above answer to a getBBox() approach, because the slider movement was bonked because of the previous edit error.
Changed it to this:
text.attr("transform", function(d){
var textElement = d3.select(this);
var current = textElement.attr("transform");
var alreadyRotated = current.indexOf('rotate');
var justTranslate = current.substring(0, alreadyRotated != -1 ? alreadyRotated : current.length);
var bbox = textElement.node().getBBox();
var point = [bbox.x + 0.5*bbox.width, bbox.y + 0.5*bbox.height];
return justTranslate+"rotate("+ -value +" "+ point +")";
});
I want to make chart, that you can not drag out of its svg element.
I'm doing this at the moment like this jsfiddle
As you can see, you can zoom and drag this freely. What i want is this:
If you drag it for example to the right and the y axis hits the edge of your screen on the left it should stop and not be able to be dragged anymore to the right.
Which also means, that you can't drag it around while not zoomed in, because it already fills its svg area.
I guess i have to somehow restrict my redraw method. At the moment it's just this
function redraw() {
plotChart.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
};
It probably has to check if for example the left edge of the chart hits coordinate [0][x] and then somehow stop drawing it any further out.
To know the axis point on scaling and on translation you need to get the CTM and then transform to get the real points.
//gets CTM on the first y line that is the group
var ctm = d3.select(".x").select(".tick")[0][0].getCTM();
//the first y line
var k = d3.select("svg")[0][0].createSVGPoint();
//current point without transform
k.x = d3.select(".x").select("line").attr("x2");
k.y = d3.select(".x").select("line").attr("y2")
j = k.matrixTransform(ctm)
//j is the real point of the line post transform.
//your code to zoom or pan depending on the value of j
I wish to set a boundary for two rectangles in my SVG.
I found this example: http://bl.ocks.org/mbostock/1557377
In the example the boundaries get worked out from the position of the object that is dragged. Every circle in the example can only move a certain distance from where it started. What I wish to do is to create one drag function and use it on multiple shapes. This drag function will stop the shapes from going out of a certain area.
For example: I have a rectangle on the left side of the screen and one on the right but I don't want any of them to be able to go off screen. I started working on this but figured out this worked with regards to the position of the object getting dragged. So this works for the left hand rectangle but the right hand rectangle can go offscreen to the right but only so far to the left
.on("drag", function(d) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
if(x<-10){x=-10}
if(x>width-10){width-10}
if(y<-10){y=-10}
if(y>height-10){y=height-10}
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
My question is: how do I impose the same boundary on anything that is dragged? i.e I don't want it to go off screen. I have variables width and height which are screen width and screen height respectively
I have a d3 visualisation on a web page that shows a collection of plot points on a chart.
It's all very simple really with just two axis and sometimes maybe 30 points to plot. As I render these plots I perform a transition on the r attributes of the SVG circles. The code that does this is:
g.append("svg:circle")
.attr("class", "plot-circle")
.attr("cx", xCo)
.attr("cy", yCo)
.attr("r", 0)
.transition()
.delay(function() { return delayInput * 100; })
.duration(plotExplosionDuration)
.ease(Math.sqrt)
.attr("r", 6);
All that occurs is that the circles are initially set to r=0 which means they aren't rendered at all. Then I start a transition on the appended circle to take this radius up to 6.
The problem is that it appears on some machines these transitions don't stop at r=6 and some plots end up being much bigger than the value set after the transition.
I simply cannot duplicate this on my main development machine (PC), my iPad nor my MacBook Pro which leads me to think it might be performance or machine load causing this?
has anyone got any ideas on how to ensure the transition stops at the defined final r value?
I'm using d3.js. I'm trying to rotate an SVG element 360 degrees, so that it spins once and returns to it's original position.
Rotating it 3/4 of the way like this works fine:
thing
.transition()
.attr('transform', 'rotate(270,640,426)')
.duration(6000);
But trying to animate the complete rotation does nothing:
thing
.transition()
.attr('transform', 'rotate(360,640,426)')
.duration(6000);
I think d3 (or maybe this a more general fact about svg transform attribute) sees that the end is the same as the beginning and just takes the shortcut by doing nothing. Similarly, if I do 365 degrees, it only moves +5 degrees.
A. Why is this?
B. What's the right way to do it?
D3 normalizes the SVG transforms; this is the cause for the effect you're seeing. You can do this however with a custom tween function:
function rotTween() {
var i = d3.interpolate(0, 360);
return function(t) {
return "rotate(" + i(t) + ")";
};
}
Complete example here.