JavaScript, SVG clippath not displaying properly after attribute change - javascript

I have an SVG-based graph. On top is a scrollable area, where the user can navigate around the graph, as shown in the image:
By dragging either of the tabs on the left/right, the user can zoom in on a particular part of the plot, as shown in the image:
Notice that only the selected portion of the graph is blue, and has a grey highlight. Both the blue and the highlight are unchanged, but are clipped using a clipping mask:
<clippath id="clip_path_scroll">
<rect id="clip_path_scroll_rect" x="x" y="y" width="w" height="h" stroke-width="0" />
</clippath>
and
<g clip-path="url(#clip_path_scroll)">
<rect id="highlighted" width="w" height="h" x="x" y="y" fill="rgba(0, 0, 0, .03)" stroke="none" />
<path id="blue_plot" d="..." stroke="rgba(26, 171, 219, 1)" stroke-width="2" fill="none"/>
</g>
And this all works fine and dandy. The clip path works flawlessly, when dragging the tabs. In that situation, it is changed incrementally, as the tab(s) slide.
But I have a reset button, that calls the following:
$('#left_scroll_group').attr('transform', 'translate(0, 0)');
$('#right_scroll_group').attr('transform', 'translate(0, 0)');
$('#clip_path_scroll_rect').attr('width', width).attr('x', x);
and when this runs, the outcome is the following:
Notice that the tab groups snap back to exactly where they should be, but it appears the clip path has only partially gone back to its original width and x-position.
BUT THIS ISN'T THE CASE
If I view the page source, and hover over the clippath rect object, it shows the following:
That is to say, it shows the rect object DID resize and move to its original position, but is not displaying properly. Moving either of the tabs immediately snaps the clippath back to working properly, as in the first two images.
Why is this happening? It happens whether the right tab is moved, or the left, or both. If the tabs are moved only a little, there doesn't seem to be much issue, but the obvious glitch/error increases as the tabs are moved further.
A similar function performs the same attr changes to the clippath, but it always shortens the path length. This glitch only seems to be happening when lengthening the length.
I am using the same types of operations on many other elements in the graph, and all work fine. All are referenced with jQuery notation (i.e. $('#element')).
I am not interested in using d3 or other libraries. I am looking for jQuery or pure JS solutions only.
EDIT 1
I've also tried
var steps = Math.floor(scrollPlotWidth - $('#clip_path_scroll_rect').attr('width'));
var curWidth = Math.ceil($('#clip_path_scroll_rect').attr('width'));
var curX = $('#clip_path_scroll_rect').attr('x');
for(var i = 0; i < steps; i++){
$('#clip_path_scroll_rect').attr('x', curX).attr('width', curWidth);
curX--;
curWidth++;
}
With no success. The changes are happening incrementally, but still no dice - it looks the same as before. This clipping issue is only happening in Safari, and seems to be fine in FireFox. Haven't tested others.
WORKING SOLUTION (HACKED)
When defining the rect within the clippath, I changed the width property. I've changed it from what is shown above to the following:
<rect id="highlighted" style="width:w;" height="h" x="x" y="y" fill="rgba(0, 0, 0, .03)" stroke="none" />
And then, the clippath functionality works properly IFF I apply the two operations:
$('#clip_path_scroll_rect').attr('x', x).css('width', w);
$('#clip_path_scroll_rect').animate(
{"width": w},
{duration: 1,
step: function(){
this.setAttribute("x", x);
}
});
Removing either of these will prevent it from working. I'm not sure why, but it will have to be a temporary fix until something better comes along. Answers are still very much welcome!

Related

document.elementFromPoint returning different elements for same input

I am using document.elementFromPoint to figure out what svg shape is at a certain point. I was experimenting with elementFromPoint on this image when I noticed that I was getting different outputs for the same input.
Here is exactly what I did for reproducibility.
I cut and pasted the svg tag from the linked svg and inserted it into the body of an html file. The html file was set up using the single ! emmet shortcut in vscode. Nothing else was in the body. The only css was body{margin:0;}.
In chrome I went around calling elementFromPoint. Mostly at .25,50 but sometimes just a little to the right or a little to the left. Sometimes calling with the same arguments over and over again to see if it would change.
There was no scrolling done during this time. There wasn't even an option as there was no scroll bar present.
My question is why does this happen? Is there a pattern to it? Is there a way to prevent it?
Thanks.
While I can't tell you why this happens, or how to prevent it, the "pattern", or let's better say the reason for the inconsistent behavior can be narrowed down.
Let's look at the paths that are returned by your calls to elementFromPoint. There are two of them, and if you leave out the ids and classes, both look identical, even taking into consideration their parent elements:
<g transform="translate(-1.775,-1.575)">
<path d="M 1.9,551.3 V 1.7 H 1102 V 551.3 H 2.3" />
</g>
If you rewrite the path such that the transform is resolved, you get a path in (browser) viewport coordinates - for clarity, I have rewritten the V and H commands as L:
<path d="M 0.125,549.725 L 0.125,0.125 L 1100.125,0.125 L 1100.125,549.725 L 0.525,549.725" />
The SVG contains a stylesheet that describes the filling and stroking of these elements. This is the relevant excerpt:
.seabase {
fill: #C6ECFF;
stroke: none;
}
.mapborder {
fill: none;
stroke: #0978AB;
}
path, circle {
stroke-width: 0.25;
}
So what should be returned from document.elementFromPoint(0.25,50)? The topmost element to be found at the given coordinates, provided the pointer-events property allows the element to be the target of a hit-test.
pointer-events is not explicitely set for the elements, so the default value of visiblePainted applies. This means that an (at least partially) opaque fill or stroke both can be hit.
The bottom element of the two candidates, <path class="seabase"/>, has a visible fill, but no border. Point (0.25,50) is inside that fill.
The topmost element of the two candidates, <path class="mapborder"/>, has no fill but a visible border of width 0.25, extending half to the inside and half to the outside of the path outline. At the left side, there is a vertical subpath M 0.125,549.725 L 0.125,0.125. Not going into the details of corners, the stroke could be drawn equivalently as a filled path with a definition
M 0,549.725 L 0,0.125 L 0.25,0.125 L 0.25,549.725 Z
In other words, the point (0.25,50) is exactly on the outline of the stroke. The spec is quite clear about the consequences:
The zero-width geometric outline of a shape is included in the area to be painted.
The point should hit the mapborder element, as the point is part of its visible, painted stroke. If, as you describe, the browser sometimes returns the seabase element, it is in error.
I could go on and speculate about the reasons for this behavior, but that is moot, I think.
Can you prevent it? Only if you would be able to consistently avoid the exact outline of all paths, or if they have a stroke, the outline of that stroke. That's hardly practical, since you would have to find out if the browser missed something it should have hit, while you don't know what was missed.
Finally, there is a second interface that can be used. It is part of the SVG specification, while elementFromPoint is part of the CSSOM spec. Whether that makes a difference in the browser implementation, I cannot say.
document.querySelector('.seabase').isPointInFill(new DOMPoint(2.025,48.425));
document.querySelector('.mapborder').isPointInStroke(new DOMPoint(2.025,48.425));
Note that you need to provide an element to test against, that you get no information which element is on top, and that the coordinates are expressed in local userspace (before transform is applied).

Manipulating path element in HTML

I am creating a hexmap chart using svg element in d3js. The chart is working fine though I need to reduce the size of the hexagons and is unable to.
As per my knowledge, it is somewhat related to the path element of HTML
This is what my path element looks like:
<path d="M395.1641842489362,-477.03703703081476L431.0882010021053,-456.29629628799995L467.0122177552744,-477.03703703081476L467.0122177552744,
-518.5185185164444L431.0882010021053,-539.2592592592592L395.1641842489362,-518.5185185164444Z" class="border" fill="rgb(240, 75, 35)" stroke="#FFFFFF" stroke-width="3"></path>
Please let me know if you have any idea around it!
I don't see the whole picture but every element can be transformed. For example if you want to scale down your path, just add the property transform="scale(0.75)" to your tag. The number is a multiplier, so 0.75 means 25% smaller. 0.5 would mean half the size.

Transition destroys SVG rotation

I am working on a JavaScript project, and as part of it, trying to rotate an svg path element around a given point.
Here is a simplified example of the js file:
var angle=0;
d3.select("#canvas")
.append("path")
.attr("id", "sector")
.attr("fill", "red")
.attr("d", "M150,150 L150,50 A100,100 0 0,1 236.6,200 Z")
.on("click", function(){
console.log("click");
angle=(angle+120)%360;
d3.select("#sector")
.transition().duration(2500) //removing this line leads to a nice transform attribute in the resulting html
.attr("transform","rotate("+angle+",150,150)");
})
And the html is just:
<svg id="canvas" width="300" height="300">
<circle cx="150" cy="150" r="100" stroke="blue" fill="none"/>
</svg>
Here you can find it on JSfiddle.
As the comment in the above excerpt suggests, it all works fine with the 3-argument version of the rotate function, where I can specify the x and y coordinates of the point which I want to use as the center of rotation. The resulting path element gets a transform attribute with the value of "rotate(120,150,150)" Unless I want to use a transition.
When I insert the line about the transition, the transformation gets some weird extra things added, it looks like "translate(354.9038105676658, 95.09618943233417) rotate(119.99999999999999) skewX(-3.1805546814635176e-15) scale(0.9999999999999999,0.9999999999999999)"
I guess in the background the non-(0,0)-centered rotation gets replaced with some translations and a (0,0)-centered rotation, just as you can do it in geometry. The position and orientation after the transition is fine indeed. However, during the transformation the element is moving on a funny path, in the example the sector leaves the circle.
Is there a way I can suppress the transition doing all these transformations and just apply a single non-(0,0)-based rotation?
Are there any other workarounds?
Changing the transform-origin for the path element attribute did not seem to work, but maybe I was doing it wrong.
I am looking for a CSS-free solution. It is an architectural decision which I cannot overrule in the project.
As you see, D3 is already involved in the project, but I would like to use as few additional external libraries as possible.
Thanks in advance!
create your arc with its center at (0,0). Then translate it to the center of the circle.
Then the d3 transition will work nicely as follows:
d3.select("#sector")
.transition().duration(2500)
.attr("transform","translate(150,150)rotate("+angle+")");

clipPath circle not inheriting the correct position in Firefox

I'm using a circular clip path for my nodes (in d3.js) as follows:
<g class="node" id="140" transform="translate(392.3261241288836,64.3699106556645)">
<image class="mainImage" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="images/manual-story-140.jpg" width="100" height="116.66705555685185" x="-50" y="-50" clip-path="url(#140)">
</image>
<clipPath class="clipPath" id="140">
<circle class="clipPathCircle" stroke-width="1" r="42"></circle>
</clipPath>
<circle class="outlinecircle" stroke="#0099FF" fill="url(#myLinearGradient1)" stroke-width="3" r="42"></circle>
</g>
But in Firefox the images don't load because the circle element within the clipPath element doesn't inherit the position of the node (i.e. from the g element).
In Chrome/Safari, everything works great and when I open up the console and hover over the circle element that's within the clipPath element it clearly shows the circle in the correct place (with dimensions of 84x84 since the radius is 42).
But in Firefox I see no images, and when I hover over the circle using the console I see it's positioned at the top left of the screen with dimensions 0x0.
Any ideas what I'm doing wrong here? Do I have to give an absolute position of the circle for firefox or should it already understand from the g-element it's in?
Apologies for the false alarm, but the problem here (as you can see in my original code) was that I was using the same id on my parent g element as I was to reference my clipPath! I changed the "id" attribute for my clip path to start with the string "clipPath-" and now it works on Firefox. Not sure why that would affect different browsers differently (which is why I kinda went 'round the houses trying to troubleshoot the bug), but thankfully enough it's quite a trivial fix!

Adding SVG elements on click of other elements

I have some circles made in d3. I need to add more circles when these circles are clicked and make the existing circles disappear(they should not be removed as I will use them again).
The way I'm doing this right now is through a listener to a click event on the original circles. (these are created with .selectAll('circle.nodes')
.on("click",function(d){
//do stuff}).duration(1000);
populateSubCircles);
I want the sub circles to appear around a center (I looked at my calculations and they seem to be correct).
var populateSubCircles = function(){
var subCircles = nodesG.selectAll("circles.subNodes").data(....
This correctly adds the secondary circles, and cx and cy seem to be correct(not too far off from the center circle). However, they don't seem to appear on the page(they appear at the top left at 0,0). Why is that happening? How do I fix that?
Thanks.
EDIT-
A picture or two may help.
It's likely that you don't have the radius set on the circles (it must be set explicitly on the circle element, and cannot be set via CSS, as only style properties such as fill, stroke, etc., can be set) .
As you can see, one svg circle shows normally when set with a radius (r) of 25, and the other does not, and its size is correctly reported as 0px x 0px. (The tooltip reports size, not position). Here's the SVG I used:
<svg>
<circle cx="100" cy="100" fill="#ffdf00"
stroke="#222222" stroke-width="2px" />
<circle cx="50" cy="50" fill="#ffdf00" r="25"
stroke="#222222" stroke-width="2px" />
</svg>
This image shows the second of two circles, using inspector in Chrome web tools, it correctly reports the size:
Again, using inspector in Chrome, highlighted the first SVG element where no radius was set:
It shows the size as 0px x 0px and shows the circle as if it were in the upper left corner of the SVG document.
http://jsfiddle.net/wiredprairie/gNrZ3/

Categories

Resources