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).
Related
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");
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>
In an attempt to build a responsive scatter graph with d3.js, I'm using %-based coordinates in a 100% x 100% svg element.
How can I .call(axis) and get it to layout the axis using % and not px values, so that they always fit the svg and the plotted data?
Do I need to manually draw the axes in this case? If so how would I get the regular tick values for each axis?
<svg width="100%" height="100%">
<g class='data'>
<circle cx='1%' cy='2%' />
<circle cx='3%' cy='12%' />
<circle cx='10%' cy='24%' />
</g>
<g class='axis'>
<!-- is there a way to generate the axis ticks with x=% y=% ? -->
</g>
</svg>
This isn't possible with D3. The axis component will generate an axis that corresponds to the associated range, i.e. to make it any particular size, you have to modify the output range of the associated scale. You can do this quite easily in a responsive manner though by computing the size of the range in relation to the window size and updating the axis when that changes, for example
xScale.range([0, window.innerWidth]);
Here is the problem,
I try to add a custom layer (svg) on a google map.
The layer I chose is really simple, it is just a "rect" but sooner or later these are gonna be much more complex with paths & so on... but that's not the problem actually.
I finally could add the svg on the map and make it visible,
but, since svg are not like image tags, i cannot find a way to scale/size the svg with the google map like simple images would...
here is a google example, when you scale (mousewheel) the map, the custom overlay size is changing too :
https://developers.google.com/maps/documentation/javascript/examples/overlay-simple
And, here is the svg I tried to add on the map, you will notice that the div (container) is located at specific points (lat/lng), and scales correctly with mousewheel on the map. BUT, the svg layer I tried to add into it, is jut NOT into it at all, and, does not scale on mousewheel... the only point going fine with this svg layer is that it's working with map dragging...
svg layer should be contained in the defined div (with bounds...). Svg is a simple layer :
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" class="svg-editor">
<g>
<rect id="svg_5" height="181" width="311" y="95.25" x="47.75" stroke-width="5" fill="#FF0000"/>
</g>
</svg>
here is the fiddle :
http://jsfiddle.net/7b3byzrf/27/
Thanks for help!
If you want your svg image to be correctly scaled, you need to have
a viewBox (you've put it, this part is OK)
no dimension in the svg element (here's the problem).
Remove those lines :
svg.setAttribute('width','400');
svg.setAttribute('height','400');
Demonstration
I'm working on some JavaScript code to render standard 2D SVG/Canvas elements (drawn with Raphael-JS) in an isometric 3Dish view.
Say we have two rectangles drawn next to each other. I then have them redrawn at the correct angles (basically a 30 degree twist) for an isometric view.
(In the image above I've shown the origin for two corresponding elements.)
My problem is I don't know how to properly translate all the individual elements so they "tile" correctly instead of just overlapping.
While actually using tiles would make things easier as I could just base any given tile's placement on the one before it, tiles won't work in this case. Everything is dynamic and will be more complex than simple x/y planes.
Here is an image of some isometric tiles if there's any confusions as to how I want these objects to be placed.
You shouldn't apply the transformation to the individual elements, but to the source elements as a collection. In Raphael, you could use something like
var s = paper.set();
s.push(square1, square2);
and now do the transformations without too much math, which is supposed to work like this:
// s.clone(); // if you want to keep originals
s.rotate(45, 0, 0).scale(1, .7).translate(100, 0);
(However, scaling of rotated items seems to be broken in RaphaelJS.)
Plain SVG example:
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="-200,-500 1000,1000">
<title>Isometric</title>
<g id="source"> <!-- group -->
<circle cx="-50" cy="-50" r="50"/>
<rect width="100" height="100"/>
<rect width="100" height="100" x="101"/>
<rect width="100" height="100" x="50" y="-200"/>
</g>
<!-- make copy of group and apply transformations -->
<use xlink:href="#source" transform="translate(500) scale(1, .7) rotate(-45)"/>
</svg>
Using Raphel.js 2.0 you can do this using the .transform() method and providing a transform string that rotates 45 degrees and scales vertically 70% (or whatever pitch you want). It's important to pay attention to the position you are rotating and scaling around as well - in this case I'm using 0,0. You will also notice I'm translating 100 over to the right to compensate for the rotation.
Transform strings are also great for this use case because you can simply prepend the projection transformation to the transformation of other objects in the scene and they will all end up in the right place.
For example (see http://jsfiddle.net/k22yG/):
var paper = Raphael(10, 10, 320, 240),
set = paper.set();
// Build a set to make it easier to transform them all at once
set.push(
// Grid of rectangles
paper.rect(0, 0, 50, 50),
paper.rect(60, 0, 50, 50),
paper.rect(0, 60, 50, 50),
paper.rect(60, 60, 50, 50)
);
// Rotate, then scale, then move (describe in "reverse" order)
set.transform('t100,0s1,0.7,0,0r45,0,0');