I'm using D3.js to render g SVG elements, each containing a foreignObject so that I can attach a styled div text. The HTML looks like this:
<g class=node ...>
<circle ...></circle>
<clipPath ...>
<circle ...></circle>
</clipPath>
<image ...></image>
<foreignObject ...>
<div ...>...</div>
</foreignObject>
</g>
There are 60 of these elements contained in a parent g element which is translated with transform: translate(...) when the user scrolls.
Here, the foreignObject has height=1 and width=1 with overflow: visible. This allows the contained div to be sized based on the text it contains.
This works fine on Chrome and Edge (and Safari if I remember correctly), however on Firefox, the div element inside foreignObject gets 'clipped' when I call translate on the parent g element. The image below shows the correct rendering:
And the image below shows the rendering during translate.
I suspect this has something to do with the width and height of the g elements (i.e. node class) because the clipping occurs outside the boundary of g. How would I fix this? I've thought of using JavaScript to modify the width and height of g, but these attributes are based on the rendering which I don't know in advance.
Any help is appreciated!
Turns out this is fixed by setting the width and height properties of foreignObject to 100%.
Related
Is there a way to display a D3 element only within a specific group?
Lets say, I have 2 rect elements, side by side within an SVG. And I want to show a circle only within the first rect. If the circle moved to the second rect element position, the circle shouldn't show over second rect.
This can be achieved by using a SVG element within a SVG element like bellow:
<svg id="outer">
<svg id="inner1">
</svg>
<svg id="inner2">
</svg>
</svg>
Same can be seen in the folowing link: https://hitokun-s.github.io/old/demo/path-between-two-svg.html
But If I use it like this, I'm unable to style the inner SVGs.
Thanks in advance
Answer to my own Question, after hours of searching.
This can be achieved by having the "inner" SVG inside a div element.
<svg id="outer">
<foreignObject>
<div>
<svg id="inner" style="border-radius: 20px; border: 1px solid red;">
</svg>
</div>
</foreignObject>
</svg
Div element can be placed within the svg using the foreignObject element. Then the "inner" SVG is considered as an "outer" element and the styles can be applied.
Hope this helps to any other poor soul.
So I have an <g> tag in an svg element with a clip-path that consists of a rectangle defined by {x:0,y:0,width:1000,height;800}. I added the clipping path as I wanted to hide some overflown children of this tag.
When I select this tag and call either getBBox() or getBoundingClientRect() for some reason I get the rectangle of the clipping path - not the dimensions of the overflow.
This is strange for two reasons:
mouse-over the element in the browser inspector (mozilla and chrome) shows the correct dimensions (width:1200, height:800).
in a similarly structured document, these methods return the dimensions with overflow.
So what is the correct behavior? and how do I get the full width of an svg element with clip-path hidden elements?
Both the SVG 1.1 spec and the CSS masking spec state this:
A clipping path affects the rendering of an element. It does not affect the element’s inherent geometry. The geometry of a clipped element (i.e. an element which references a <clipPath> element via a clip-path property, or a child of the referencing element) must remain the same as if it were not clipped.r
And this is what happens in the example below. So this might not be what your result is about.
Note that the results for .getBBox() and .getBoundingClientRect() differ. That is because the first states size in the local userpace coordinate system, while the latter states size in screen pixels. It might not be obvious that a transformation has been taking place between the two, as it might be hidden implicitely in the relation between viewBox, width and height attributes of the <svg> element.
const clipped = document.querySelector('#clipped');
const bbox = clipped.getBBox();
console.log(bbox.x, bbox.y, bbox.width, bbox.height);
const bcrect = clipped.getBoundingClientRect();
console.log(bcrect.x, bcrect.y, bcrect.width, bcrect.height);
<svg width="400" height="300" viewBox="0 0 200 200">
<clipPath id="cp">
<rect x="50" y="50" width="100" height="100" />
</clipPath>
<rect id="clipped" width="200" height="200" clip-path="url(#cp)" />
</svg>
I am placing an angular directive inside a dynamically-sized element. The directive itself consists of an SVG which is computed based on the element size. I am trying to make the SVG auto-resize and redraw based on the size of the container.
I initially tried something like this:
my-directive.js
angular
.module('myModule')
.directive('myDirective', function () {
return {
templateUri: 'path/to/my-directive-template.html',
...
};
});
my-directive-template.html
<svg style="width: 100%; height: 100%; max-width: 100%; max-height: 100%">
...
</svg>
Note the style attributes on that SVG element. This resizes correctly in Chrome, but fails to work in Firefox. Also, I still don't have a hook to recalculate the SVG contents.
I've also tried adding an onresize handler to the element in the link function, However, JQLite supports onresize only on the main window. I cannot use window.onresize, because my window size does not change.
I've tried to use the answers here: AngularJS - bind to directive resize, but they don't give the required results either.
In short, here's what I am trying to do:
Resize the SVG element inside the directive when the parent element resizes.
Re-calculate the SVG contents by calling some handler function when this happens.
I would prefer not to add a JQuery dependency at this point in the project.
This behavior can be achieved using the viewBox and preserveAspectRatio attributes of the <svg> tag.
First, set the viewBox attribute to a normalized bounding box for your SVG image. Your entire drawing should be scaled to fit inside this view box. For example,
viewBox="0 0 100 100"
will set up a coordinate system with the origin at (0, 0) and having the dimensions 100 units x 100 units.
Next, set the resizing behavior using the preserveAspectRatio attribute.
The first part of the value determines the alignment of the SVG with respect to the parent element. This includes left/right/center horizontal alignment and top/bottom/middle vertical alignment. For example,
preserveAspectRatio="xMidYMid ..."
will align the SVG centrally in its container.
The second part of the value determines how the SVG fills the container. For example,
preserveAspectRatio="... meet"
will scale the SVG such that it just fits within the container without cropping.
So the complete example becomes:
<svg viewBox="0 0 64 64" preserveAspectRatio="xMidYMid meet">
...
</svg>
Because the image scales automatically with the container, there is no need to recalculate the positions of the content elements. It is handled automatically by the SVG tag.
My D3-based visualization produces an HTML5 SVG element with animated GIFs in it. For simplicity, take this example:
<svg>
<image href="animated.gif"></image>
<svg>
Upon mouseover, I'd like to highlight the image by putting a circle behind with a fading gradient for a glow effect. As SVG renders elements on top of each other, the output must look like this:
<svg>
<circle class="gloweffect"></circle>
<image href="animated.gif"></image>
<svg>
After two wasted days, I gave up trying to insert the circle element at the correct position immediately with D3's insert. It just didn't work, esp. since the visualization contains lots of other stuff and the insert position is hard to express.
So instead, I use D3's append to add the circle at the end. Then I call a sorter which removes all elements from the SVG, sorts them, and re-appends them in the correct order:
<svg>
<image href="animated.gif"></image>
<circle class="gloweffect"></circle>
<svg>
--> remove everything
<svg>
<svg>
--> sort and reinsert
<svg>
<circle class="gloweffect"></circle>
<image href="animated.gif"></image>
<svg>
And here comes the challenge: This works fine in all browsers, except ... wait for it ... IE9. (Okay, lame wait.)
As soon as the image element is removed, IE9 stops the GIF animation and does not restart or continue it upon reinsertion. The image simply gets stuck at the first frame and stays that way.
So my question: Is there a way to make IE9 continue the animation after reinsertion? I found plenty of old threads regarding regular img elements, esp. suggesting to reset the picture in a delayed thread, but none of them seems to work for the image element in SVG.
--Florian
Instead of inserting and removing elements (which is expensive and error prone) just make them invisible, you could do something such as...
<svg>
<g class="glow">
<circle class="gloweffect"></circle>
<image href="animated.gif"></image>
</g>
<svg>
And then in your CSS:
g.glow .gloweffect {
opacity: 0;
}
g.glow:hover .gloweffect {
opacity: 1;
}
I have faced to a quite strange situation. I have a script which draws some lines using jQuery SVG plugin. It is working in a separate html file. But once I copy that script and insert into another html file it stops showing SVG elements in a browser. It works perfectly, because when I see the source code of the page after running a script I could see that the script is adding SVG elements to the page. Here is SVG code of the page in any case:
<svg version="1.1">
<line x1="492" y1="503" x2="717" y2="576" stroke="#4A4A4A" stroke-width="2"></line>
<line x1="500" y1="400" x2="600" y2="400" stroke="#4A4A4A" stroke-width="2"></line>
<line x1="604.5" y1="539.5" x2="587.5" y2="542.5" stroke="red" stroke-width="2"></line>
<line x1="604.5" y1="539.5" x2="592.5" y2="527.5" stroke="red" stroke-width="2"></line>
</svg>
What could be the problem the those SVG elements are not shown in the browser? Have anybody else faced such a strange situation?
You forgot to add the SVG namespace tag to your page?
Try adding a viewBox attribute to the svg element to ensure that the coordinate system makes the lines appear within the svg viewport. Or make sure that in both documents size of the svg element is the same. My guess is that in one document the svg is quite wide, and in the other it's not as wide (thus clipping away the lines).
The problem is solved. I didn't notice that in the new page where SVG elements were not shown, the DIV element where I was drawing SVG elements had been wrapped by another DIV. And in the CSS file the wrapper DIV had an attribute display: table;. I removed that attribute and now SVG elements are shown. Thanks guys for you help and suggestions.