Precompute scale of svg paths to given widths and heights - javascript

Lets say I have an arbitrary svg with paths:
<svg>
<g>
<path d="...." />
</g>
<g>
<path d="...." />
</g>
</svg>
For each path, I have calculated the exact width and height bbox values (in pixels) that I want them to be scaled down to on render. These values are correct and (when needed) will preserve the aspect ratio of the paths.
With no change to scaling (scale(1)), the default size of the paths are very big (hundreds of pixels), covering a lot of the svg.
I have figured out that once I know the original width and height values of a given path at scale(1), then I can divide width by the original width and height by the original height to get the scale value I am looking for. This method, however, requires rendering the path first at scale(1) to determine the original dimensions.
Is there a way to calculate the transform: scale(x) value that yeilds a path of size width and height before rendering the svg? Or are there other beneficial scaling methods or factors at play that I am missing? I am using jsx/React components for rendering the svg although this is probably inconsequential.
Update
The SVG.js library solves this issue another way by scaling the path strings themselves instead of using transforms. This is not as efficient as vanilla js but still quite fast.

Set the paths to visibility: hidden and then show them only after they are scaled. You can then render them at scale(1), calculate the scale you want and have no visible artifacts affecting your work.

Have you tried manipulating the viewBox attribute on the parent SVG element?
viewBox reference
Excellent in-depth article regarding SVG coordinate system
A simple example:
<svg id="my-svg">
<g style="background:white;">
<path d="M0,0 L0,6 L9,3 z" fill="#003" />
</g>
</svg>
And then the following javascript as a simplistic example:
let svg = document.getElementById("my-svg");
svg.setAttribute("viewBox", "0, 0, 100, 80");

Related

SVG & HTML: getBBox and getBoundingClientRect not the same as when hover over node in inspector

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>

How to manipulate the layer of a element in an existing SVG?

I'm using d3 to manipulate an existing svg. The svg appears to have multiple layers. I'm able to get a handle to an expected element and manipulate it with d3. However, the element is on a lower layer. For example, I can set stroke (border color) and stroke-width on the element through d3 and I can see the updated border expanding out from beneath a higher layer with the same shape.
I need to figure out how to dynamically change element layers as needed on the fly. I tried setting z-index style and attr to 999 for the layer I'm trying to raise. No other z-index attrs exist in the svg so my assumption was that setting an element z-index to 999 would most likely raise it to the top but this did not happen. This assumption was mainly based on my background in html/css.
Can you recommend some basic troubleshooting steps for this? Is svg layering implementation and manipulation more complex than what I have in mind? Can you recommend any resources or possible shortcuts?
There is no z-index in a SVG. In an SVG, the order of the elements defines the order of the "painting", and the order of the painting defines who goes on top. The specs are clear:
Elements in an SVG document fragment have an implicit drawing order, with the first elements in the SVG document fragment getting "painted" first. Subsequent elements are painted on top of previously painted elements.
Therefore, you'll have to reposition the elements. There is a very simple solution, just do:
selection.raise();
Raise re-inserts each selected element, in order, as the last child of its parent.
Here is a demo, hover over the circle to bring it to the top:
d3.selectAll("circle").on("mouseover", function(){
d3.select(this).raise()
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height=200>
<circle cy="100" cx="80" r="60" fill="blue"></circle>
<circle cy="100" cx="160" r="60" fill="yellow"></circle>
<circle cy="100" cx="240" r="60" fill="red"></circle>
<circle cy="100" cx="320" r="60" fill="green"></circle>
</svg>
Note that raise() will only work for elements in the same level (that is, having the same parent element).

how to explicitly draw svg viewbox coordinates

I have an svg viewbox element to draw waveform as follows:
<svg id="waveform" viewBox="0 -1 2000 2" preserveAspectRatio="none">
<g id="waveform">
<path id="waveform1" d="{{some data}}"/>
</g>
</svg>
It plots the waveform as expected. However it doesn't give me the x and y label of the viewbox, is there any alternate way by which we can add the x and y label of viewbox on webpage?
Your svg is rendering a single path, the waveform. If you want labeled axes to be drawn, you need to render them. For example, inspect the source of these SVG graphs with labeled axes: http://www.goat1000.com/svggraph-titles.php. You'll probably want to use a library to do this.
The svg element is not a "smart" graphing widget, it is a plain canvas.
The viewbox attribute on the svg element sets the coordinate system within which the content of the svg is drawn (its internal dimensions, as opposed to the actual pixel dimensions when it is rendered).

How to set svg print (in mm for CorelDraw) and real/preview (in px) sizes?

I have a basic SVG file, that has a fix 50mm x 25mm print size (so if I open it with CorelDraw the document size will be this.)
<svg
width=50mm
height=25mm
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<g>
<text
x=0
y=55
font-family="Verdana"
font-size=55
fill="black"
>NOS?</text>
<line
class='v_pos'
stroke="green"
x1=0
y1=55
x2=500
y2=55
stroke-width="1"
/>
</g>
</svg>
How can I achieve 500x250 px size in the browser? The ratio does not change, but I need a fixed canvas size in the web-browser too.
I need reword/extend the problem:
I'd like to export the graphics (created in browser) to CorelDraw, as it can read SVG files. The canvas in browser is for example 500x250px, and every object are measured first in pixel. After the export everything must be resided, started from the canvas (to 50x25mm) followed by the objects:
So the questions are:
witch attribute is responsible for canvas width and height in CorelDraw?
is there any fast way (preserveAspectRatio, viewBox, style media) of resizing containing objects, or I have to convert every object's width,height,x,y, etc. attributes one by one?
Thank you for any advice!
There are two different size aspects of a SVG image: how much do you want to see from the infinite canvas, and how big should the resulting image be. The first one is defined by the viewBox, which contains x and y coordinates for the top-left corner, and a width and a height. The second one is defined using the width and height attributes or style properties.
So, you use the viewBox attribute to say that you're interested in the area inside the (0px, 0px) and (500px, 250px) rectangle, since that is what you see in the browser: viewBox="0 0 500 250"
Then, since you want the image to be displayed as 50mm wide and 25mm high, you set the width and height accordingly. You can do that either setting them as attributes on the root svg element, which means that you have to set them only when exporting, since otherwise they will apply in the browser as well, or you can set them using a style element valid only for print media.
For browsers, if you're defining the viewBox you don't need to specify the width and height explicitly, since by default the area defined in the viewBox is displayed pixel per pixel.
When dealing with different media, use stylesheet media selectors. And SVG has support for that natively, using the media attribute of the <style> element. The basic syntax would be:
<style media="something" type="text/css">
svg:root {
width: 50mm;
height: 25mm;
}
</style>
Now, depending on what you want to do, you can:
Use media="print" to specify the size for print media, letting the default width and height specified in the attributes set on the root <svg> element be used in all other cases
Use media="screen" to specify the target width and height just for browsers when using a screen to display, where screen is defined as: "Intended primarily for color computer screens".

How to use viewbox when height of svg is dynamically changing

I have a resizable div. It has two inner divs. One of the inner divs has an svg element in it.
In the svg element I am adding and removing the content dynamically such that each time I add something in my svg. I increase its height by adding 20px to it and when I remove I subtract the height by 20px. When my svg height become greater than its parent div a scroll bar appears in the parent div. Similarly scroll bar is removed when svg height is less than parent div.
The problem starts when I do the resizing. I have added a viewbox option in my svg for resizing. But when I increase the size some of my svg elements are not visible.
And when I decrease the size my svg get placed at a lower position leaving empty space.
Its all messed up in my mind that how to deal svg position/height with viewbox property.
I have tried to make a fiddle to simulate the behavior somehow. But here elements in svg are not adding dynamically and the svg height is constant.
Link to my code
Any help will be appreciated
LATEST UPDATE:
Most important if you're going to use SVG it is better to be acquainted with spec or read a definite guide like "SVG Essentials" by J. Eisenberg, cause it is not such a trivial thing you might think at first.
Then if I understood you right, there is another approach jsFiddle.
First, set correctly the value of the viewBox attribute. In your current example in should be viewBox="0 0 130 220" so that all you content be visible (you have to specify at least 220 as the last number cause your last group <g> is translated i.e. it's coordinate system moved down to 200 units, if you don't do that your content will be not visible cause it is far beyond the outmost y-point of your viewbox, which in your jsfiddle is set to 70).
Second, specify the correct value for preserveAspectRatio which should be preserveAspectRatio="xMinYMax meet", which means (courtesy of the "SVG Essentials" by J. Eisenberg):
Align minimum x of viewBox with left corner of
viewport.
Align maximum y value of viewBox with bottom
edge of viewport.
meet means meet the content, not slice it, if it doesn't fit.
Third, if you are going to add elements to the bottom of your svg you have to change the viewBox value accordingly, cause if you will insert into it smth like:
<g transform="translate(0, 300)">
<text>text 55 </text>
</g>
it will occur beyound your viewBox, which has a max y-point at 220 (if you would set it as I said earlier)
For now hope that helps, let me know.
OLD STUFF
remove style="height:200px" from your svg
Then if you need height, you can dynamically change the height of your svg
var $wrapper = $('#resize'),
$svg = $('#svg2');
$wrapper.resizable({
handles: 'se',
aspectRatio: 400/200,
resize: function(ev, ui) {
$svg.css('height', ui.size.height);
}
});
'#resize' - is the id of the wrapper of the svg
I'm now very confused of what you want. You specified viewbox attr on the svg. That means that only a part of your svg will be visible. Try to remove viewbox and see whether result looks satisfactory for you.
It then be all visible and normally adjusted to parent div

Categories