Concurrency transitions in D3 (circle following filling path) - javascript

I am trying to create simple animated gauge charts, with red points on the end of each circle. Whole example is in here https://jsfiddle.net/yhLch8fc/3/ (please do not solve text position, it's not a problem). I tried to search on stack overflow, but tutorials like Change starting point of alongPath animation are not helping me, because my path is translated by X,Y and rotated. I thing the key part of code is:
circle.transition().duration(_duration)
.attrTween('cx', arcCircleX(_myPath.node()))
.attrTween('cy', arcCircleY(_myPath.node()));
you can easy switch to similar version (code from stack overflow above):
circle.transition().duration(_duration)
.attrTween('transformation', circleTween(_myPath.node()));
I don't know why, but it seems the red circle goes to the left side. Maybe it's because the original path is translated and rotated.
(The original version was, when I try to follow filling path, but it does not work either.)

Instead of rotating the hover path, rotate the g element that contains both the path and the circle:
enter.append("g").attr("class", "hover")
.attr("transform", "translate(" + _width / 2 + "," + _width / 2 + "), rotate(-90)");
Then:
circle.transition().duration(_duration)
.attrTween('transform', circleTween(_myPath.node()));
Here's an example. Is that what you were after?

Related

D3 tooltip not following the mouse

I have created a webpage with multiple charts using D3 and Object Oriented Programming.
Working code link--> https://codepen.io/mohan-ys/pen/LYLwqrK
The problem I am facing is that the tooltip is not at the mouse location, it is somewhere else.
I tried using d3.event.pageX & d3.event.pageY instead of vis.mouse[0] & vis.mouse[1] which is in the code above but it does not work.
I am getting the tooltip as shown. When the mouse is at the right end of the graph, the tool tip moves further right, it gets closer somewhere in the middle & it goes to the other side by the time the cursor is on the left end of the chart!
The page is resized, then it is a totally different behaviour!
Can anyone help get the tooltip right a the mouse pointer (top-left corner at the mouse pointer) for all graphs & even when the page is resized (the graphs scale with page resize).
The vertical line follows the mouse perfectly!, so, if there is another way of creating the tooltip instead of a div, that is also ok for me.
The underlying problem is addressed here but the answer doesn't directly solve your problem. Basically, there's a second and optional argument to d3.pointer called target such that:
If target is not specified, it defaults to the source event’s
currentTarget property, if available. If the target is an SVG element,
the event’s coordinates are transformed using the inverse of the
screen coordinate transformation matrix...
You can make use of this argument per below noting that it will break your vertical tracking line if you try and just update vis.mouse:
// mouse moving over canvas
vis.mouse = d3.pointer(event); // keep this for the vertical tracking line
vis.mouse2 = d3.pointer(event, d3.select(vis.chartLocation)); // <--- NEW VARIABLE!
Now vis.mouse2 has a relative x and y - so use them where you set the style of the div:
d3
.select(vis.chartLocation)
.selectAll("#tooltip")
.html((d, i) => {
vis.xDate = d.values[vis.idx - 1].date;
return vis.xDate.toLocaleDateString("pt-PT");
})
.style("display", "block")
.style("left", vis.mouse2[0] + "px") // use vis.mouse2
.style("top", vis.mouse2[1] + "px") // use vis.mouse2
The clue is in that your first selection is vis.chartLocation.

Cannot get labels to work with D3-tile projection

I am trying to add some simple country labels to a D3 vector map which is layered on top of a D3-tile raster map. The labels are being created in as expected, but I am not able to project them properly on the map. The projection in D3-tile is a bit messed up (by which I mean it doesn't work like on a 'normal' vector map, and I don't understand it).
I have created a jsfiddle where I create the maps and then try to project them so that they move around with user interaction.
Bit of code that fails to achieve this is here:
d3.selectAll(".country_labels")
.attr("transform", function(d) {return "translate(" + path.centroid(d) + ")"})
UPDATE
I suspect my issue on this question is similar to the one I raised earlier today on here. I also note that a similar-ish question was raised here too.
I have made some progress and put together this new fiddle. The labels are now all on the map, but floating around the gulf of guinea, close to geocoordinates [0,0]. To me, this means they may have been projected properly but that the zoom has not functioned as expected. The issue here is that there are three separate types of coordinates in this script:
Geocoordinates - these are the starting point and always fixed
The 'd3-tile' coordinates. The ones that fit within a single pixel, and therefore always very close to zero
Pixel coordinates - these correspond to the actual coordinates on the screen
This is similar to your other question, just it is on the forward projection & zoom rather than the inverts. (I started writing this before the update, but had to run, I'll continue with your original code).
As with the paths, you append your labels as expected:
country_labels.selectAll("text")
.data(collection.features)
.enter().append("text")
.attr("x", function(d){return path.centroid(d)[0];})
.attr("y", function(d){return path.centroid(d)[1];})
.attr("dx", -40)
.text(function(d){ return d.properties.name })
.style("fill", "#aeaeaf")
.style("font-size", "15px")
There is one concern here, as the projection of most d3-tile examples, including yours, use a d3-projection scale of 1/tau, the world is projected within the space of 1 pixel, so the dx value is equal to 40 worlds, this won't work when applying the zoom, so let's drop that part
Now you are appending the features more or less just like the paths, but the issue is in the zoom handling:
d3.selectAll(".country_labels")
.attr("transform", function(d) {return "translate(" + path.centroid(d) + ")"})
The paths are given a similar treatment:
vector
.attr("transform", "translate(" + [transform.x, transform.y] + ")scale(" + transform.k + ")")
.style("stroke-width", 1 / transform.k);
But there are a couple differences here:
you are applying a different transform (scale and translate) to the paths as compared to the text: for the text there is no reference to the current zoom transform, instead, you only use the projection, which is anchored at 0,0 with all features lying within an area of one pixel (and anchored at 0,0 will have its baseline at y=0, the text will be largely out of view). If you inspect the svg, you'll see the text, just in the wrong spot.
The paths have a reduced stroke width as one zooms in (as we are zooming the svg, the stroke width itself increases), the same would apply for text, so even if the text was correctly positioned, it would be very very large (more than most any screen holding the browser).
One way we can address this is we apply the zoom transform on the x/y coordinates of the text, not the element itself (which would scale the text size as well, this way we don't need to resize the text at all):
country_labels.selectAll("text")
.attr("x", function(d){return transform.apply(path.centroid(d))[0];})
.attr("y", function(d){return transform.apply(path.centroid(d))1;})
Like with the inversion from svg pixel to lat/long, we go through the same motions, but in reverse order: apply the projection, then apply the zoom.
Here's an updated fiddle.
However, I have bad news - the labels are positioned exactly where you are telling them to be positioned now. But they aren't where you want them to be (how's the saying go, the best thing about programming is that the code does exactly what you tell it, the worst thing about programming is that the code does exactly what you tell it?).
You are using path centroids to place labels, this works sometimes for some features, but it doesn't work all the time. Take the United States for example, the centroid of the US using a Mercator projection isn't in the United States because it is between Alaska and the lower 48 states (sorry Hawaii, you don't have much pull here). The centroid of Canada is partly in the Arctic Ocean, and in many datasets (not this one surprisingly), France is labelled in the middle of the Atlantic because of French Guiana, when using centroids as the text anchor.
You can improve the visual appearance slightly by using .style("text-anchor","middle"), which at least centers labels where the are (very useful for smaller or equitorial countries), but ultimately centroid placement isn't ideal.
I'll just finish with: Annotations are the bane of cartography.
But, there is hope, here's one of the more promising futures I've seen.

How to calculate the center of a rotated element, after resizing it

I've already asked this same question months ago, but no one was able to answer me, even after making a fully functional example on Plunker, then, I am going to ask it again, and yes, I still have the same problem.
My problem: find the centre of an element who have some rotation in it, after resizing it, to use it as the new pivot of rotation.
In my practical example, it is possible to see the problem in action; I have created two circles to show the problem better. After rotating and resizing the element, it's possible to see how the red and blue circles are apart from each other.
Blue Circle: the "correct" position of the centre, achieved by setting the cx/cy coordinates as the calculated element centre, plus, applying the transform rotate in it. The transform translates the circle to the correct position.
Red Circle: same as the blue circle, minus the transform rotate, these values are the ones used as the rotation pivot for the transform rotate().
My assumptions until here: By applying the transform rotate() in the blue circle, I'm considering the rotation angle in the calculated centre, so all I have to do is replicate the matrix calculations made by the rotate() function. I'm already doing this with the four handles that the user can click to make a rotation, what could go wrong?
My goal: Resize an element with rotation keeping the pivot of rotation in the centre.
I think this answer gave me some info, the math here helped me with the rotation handles starting position, but still, I can't find the right way to calculate the new centre after the resize.
The example was made using D3js + AngularJS v1. I work actively with both, but I am new to the geometry math world.
Again, this is the project on Plunker.
To get the centre of the transformed and rotated element, the most accurate way would probably be to get the browser to calculate it for you.
First create an SVGPoint object to hold our original centre point.
var centre = svg.createSVGPoint();
Initialize this point with the centre of the original object. You can get that by calling getBBox() on the element, and performing a smiple calculation.
var bbox = obj.getBBox();
centre.x = bbox.x + bbox.width / 2;
centre.y = bbox.y + bbox.height / 2;
Next, get the transform matrix from the transform attribute of the transformed object
var matrix = transformedObj.transform.baseVal.consolidate().matrix
Now we can transform our SVGPoint object with this matrix.
var transformedCentre = centre.matrixTransform(matrix);
After this, the x and y properties of transformedCentre should be your transformed centre point.
This should work, but I haven't tested it.

Rotate svg path around its centre d3

I have a simple cog wheel drawn in illustrator. I've placed the svg markup in the index page and all is well. I can access it by its id.
The code below is part of a function that is executed on that time period so I don't need to worry about the time element.
I've looked around and am not understanding the examples I've found. This is what I have:
var motor=d3.select('#wheel')
motor.attr('transform', 'rotate(0, 0, 2)');
I want to rotate a couple of degrees every 300 milliseconds.
Your original code doesn't seem very functional (the commas in rotate are not needed). So taking those out, your statement is asking the svg to rotate by 0 degrees around the x, y coordinates 0, 2 (2 pixels down from the top-left corner). What you want is closer to:
var motor=d3.select('#wheel')
motor.attr('transform', 'rotate(2 ' + (objectwidth / 2) + ' ' + (objectHeight/2) + ')');

Linear/Incremental rotation animation with transition

I'm trying to get a SVG shape to rotate using the transform attribute and the transition method in D3. Here is the jsfiddle containing an example: http://jsfiddle.net/TJd2a/
I'm using two buttons, Left and Right, to rotate the rectangle by incrementing by its angle by 45 or -45 degrees. When the shape reaches either 180 or -180 degrees, the transition rotates the shape the opposite way, rather than moving linearly to the next angle. Using console logging, I've found there is nothing wrong with the angles that my code is generating. It appears to be how D3 is dealing the transition, as the generated XML does not show the same angle as the current (eg. when at 225 degrees, D3 gives the rectangle a rotation of -135 instead).
From what I've read and understand from the documentation, I need to use a custom Tween, but I 'm not sure where to start with a custom tween as I cannot find any examples specific or similar examples to help me understand how it works.
Correct; you can use a custom tween to change the interpolator. D3 has a special interpolator for transforms, but it's not doing the right thing in your case. (I think that's probably a bug which should be fixed, so I filed issue 661.) Here's an example using interpolateString instead:
d3.select("rect").transition().attrTween("transform", function(d) {
return d3.interpolateString(
"rotate("+ d.a +")",
"rotate(" + (d.a += 45) + ")"
);
});

Categories