Preserve descendant elements' size while scaling the parent element - svg

I have an XHTML page with SVG embedded into it as an <svg> element. I need to implement scaling, but there is a requirement that inner <text> elements must not scale. An obvious solution is
<svg ...>
<!-- Root <g> to programmatically `setAttribute("transform", ...)` -->
<g transform="scale(0.5)">
<!-- Various inner elements here -->
<!-- Here is a text element.
Additional <g> is to apply text position which
*must* depend on the scaling factor set above -->
<g transform="translate(100 50)">
<!-- Apply the opposite scaling factor.
Must be done for every `<text>` element on each change of the scaling factor... -->
<text x="0" y="0" transform="scale(2)">Hello, World!</text>
</g>
</g>
</svg>
Is there a better solution than that? Maybe, there is a way to "reset" the resulting scaling factor on inner <text> elements? The code must work in Firefox and Chrome.
UPD. There is also an option to place text elements outside the element being scaled and manually control text elements' positions. It avoids visual glitches appearing on the text because of scaling. Currently I use this method.

There is transform="ref(svg)" which is defined in SVG Tiny 1.2. To the best of my knowledge this is not implemented in any browsers except Opera (Presto).
<svg xmlns="http://www.w3.org/2000/svg" font-size="24" text-rendering="geometricPrecision">
<!-- Root <g> to programmatically `setAttribute("transform", ...)` -->
<text x="0" y="1em" stroke="gray" fill="none">Hello, World!</text>
<g transform="scale(0.5) rotate(25)">
<!-- Various inner elements here -->
<!-- Here is a text element.
Additional <g> is to apply text position which
*must* depend on the scaling factor set above -->
<g transform="translate(100 50)">
<!-- Apply the opposite scaling factor.
Must be done for every `<text>` element on each change of the scaling factor... -->
<text x="0" y="1em" transform="ref(svg)">Hello, World!</text>
</g>
</g>
</svg>
In the above example the text should appear where the gray outline is (in the top-left corner). No rotation or scaling should be applied to the element that has transform="ref(svg)", for the purposes of transformations it's as if it was a direct child of the root svg element.

If it is acceptable to (a) use JavaScript and (b) specify the position of each unscaling element via transform="translate(…,…)" either on the element's themselves or in a wrapping group (as you have done), then you can use my unscaling code:
http://phrogz.net/svg/libraries/SVGPanUnscale.js
Demo: http://jsfiddle.net/uPSc4/
As you can see in the demo it undoes all transformation except translation (including scale, rotation, and skew) while keeping the elements positioned where they are supposed to be.
See also this demo, which allows you to zoom and pan the graphic using SVGPan while keeping certain marking elements unscaled:
http://phrogz.net/svg/scale-independent-elements.svg
Here's all the code you need:
// Copyright 2012 © Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/JS/_ReuseLicense.txt
// Removes all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
function unscale(el){
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf){
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
xf = el.scaleIndependentXForm = svg.createSVGTransform();
el.transform.baseVal.appendItem(xf);
}
var m = svg.getTransformToElement(el.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
}
Here's the demo code, in case JSFiddle is down:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:x="http://www.w3.org/1999/xlink"
font-size="36">
<g transform="matrix(1 0 0 1 0 0)">
<g transform="translate(100 50)"><text x="0" y="1em">A!</text></g>
<g transform="translate(200 150)"><text x="0" y="1em">B!</text></g>
<g transform="translate(150 75)"><text x="0" y="1em">C!</text></g>
</g>
<script x:href="http://phrogz.net/svg/libraries/SVGPanUnscale.js"></script>
<script>
var root = document.querySelector('g'),
text = document.querySelectorAll('text'),
xf = root.transform.baseVal.getItem(0);
// Keep scaling, spinning, and sliding the outer graphic
// but "unscale" each text element afterwards.
setInterval(function(){
var m = xf.matrix.scale(0.99).translate(10,0).rotate(1);
xf.setMatrix(m);
[].forEach.call(text,unscale);
},100);​
</script>
</svg>​

Try this
HTML
<svg>
<g transform="scale(0.5)">
<g transform="translate(100 50)">
<text x="0" y="0">Hello, World!</text>
</g>
</g>
</svg>
Find the inverse matrix and add required transformations only.
var svg = document.getElementsByTagName('svg')[0];
var text = document.querySelectorAll('text')[0];
var matrix = svg.createSVGTransformFromMatrix(text.getCTM().inverse().translate(100,50));
text.transform.baseVal.initialize(matrix);
JsFiddle - http://jsfiddle.net/uPSc4/10/

Related

Is there a change layer function or method? [duplicate]

I'm using the svg circles in my project like this,
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120">
<g>
<g id="one">
<circle fill="green" cx="100" cy="105" r="20" />
</g>
<g id="two">
<circle fill="orange" cx="100" cy="95" r="20" />
</g>
</g>
</svg>
And I'm using the z-index in the g tag to show the elements the first. In my project I need to use only z-index value, but I can't use the z-index to my svg elements. I have googled a lot but I didn't find anything relatively.
So please help me to use z-index in my svg.
Here is the DEMO.
Specification
In the SVG specification version 1.1 the rendering order is based on the document order:
first element -> "painted" first
Reference to the SVG 1.1. Specification
3.3 Rendering Order
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.
Solution (cleaner-faster)
You should put the green circle as the latest object to be drawn. So swap the two elements.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 70 160 120">
<!-- First draw the orange circle -->
<circle fill="orange" cx="100" cy="95" r="20"/>
<!-- Then draw the green circle over the current canvas -->
<circle fill="green" cx="100" cy="105" r="20"/>
</svg>
Here the fork of your jsFiddle.
Solution (alternative)
The tag use with the attribute xlink:href (just href for SVG 2) and as value the id of the element. Keep in mind that might not be the best solution even if the result seems fine. Having a bit of time, here the link of the specification SVG 1.1 "use" Element.
Purpose:
To avoid requiring authors to modify the referenced document to add an ID to the root element.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 70 160 120">
<!-- First draw the green circle -->
<circle id="one" fill="green" cx="100" cy="105" r="20" />
<!-- Then draw the orange circle over the current canvas -->
<circle id="two" fill="orange" cx="100" cy="95" r="20" />
<!-- Finally draw again the green circle over the current canvas -->
<use xlink:href="#one"/>
</svg>
Notes on SVG 2
SVG 2 Specification is the next major release and still supports the above features.
3.4. Rendering order
Elements in SVG are positioned in three dimensions. In addition to their position on the x and y axis of the SVG viewport, SVG elements are also positioned on the z axis. The position on the z-axis defines the order that they are painted.
Along the z axis, elements are grouped into stacking contexts.
3.4.1. Establishing a stacking context in SVG
...
Stacking contexts are conceptual tools used to describe the order in which elements must be painted one on top of the other when the document is rendered, ...
SVG 2 Support Mozilla - Painting
How do I know if my browser supports svg 2.0
Can I use SVG
Deprecated XLink namespace For SVG 2 use href instead of the additional deprecated namespace xlink:href (Thanks G07cha)
As others here have said, z-index is defined by the order the element appears in the DOM. If manually reordering your html isn't an option or would be difficult, you can use D3 to reorder SVG groups/objects.
Use D3 to Update DOM Order and Mimic Z-Index Functionality
Updating SVG Element Z-Index With D3
At the most basic level (and if you aren't using IDs for anything else), you can use element IDs as a stand-in for z-index and reorder with those. Beyond that you can pretty much let your imagination run wild.
Examples in code snippet
var circles = d3.selectAll('circle')
var label = d3.select('svg').append('text')
.attr('transform', 'translate(' + [5,100] + ')')
var zOrders = {
IDs: circles[0].map(function(cv){ return cv.id; }),
xPos: circles[0].map(function(cv){ return cv.cx.baseVal.value; }),
yPos: circles[0].map(function(cv){ return cv.cy.baseVal.value; }),
radii: circles[0].map(function(cv){ return cv.r.baseVal.value; }),
customOrder: [3, 4, 1, 2, 5]
}
var setOrderBy = 'IDs';
var setOrder = d3.descending;
label.text(setOrderBy);
circles.data(zOrders[setOrderBy])
circles.sort(setOrder);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 100">
<circle id="1" fill="green" cx="50" cy="40" r="20"/>
<circle id="2" fill="orange" cx="60" cy="50" r="18"/>
<circle id="3" fill="red" cx="40" cy="55" r="10"/>
<circle id="4" fill="blue" cx="70" cy="20" r="30"/>
<circle id="5" fill="pink" cx="35" cy="20" r="15"/>
</svg>
The basic idea is:
Use D3 to select the SVG DOM elements.
var circles = d3.selectAll('circle')
Create some array of z-indices with a 1:1 relationship with your SVG elements (that you want to reorder). Z-index arrays used in the examples below are IDs, x & y position, radii, etc....
var zOrders = {
IDs: circles[0].map(function(cv){ return cv.id; }),
xPos: circles[0].map(function(cv){ return cv.cx.baseVal.value; }),
yPos: circles[0].map(function(cv){ return cv.cy.baseVal.value; }),
radii: circles[0].map(function(cv){ return cv.r.baseVal.value; }),
customOrder: [3, 4, 1, 2, 5]
}
Then, use D3 to bind your z-indices to that selection.
circles.data(zOrders[setOrderBy]);
Lastly, call D3.sort to reorder the elements in the DOM based on the data.
circles.sort(setOrder);
Examples
You can stack by ID
With leftmost SVG on top
Smallest radii on top
Or Specify an array to apply z-index for a specific ordering -- in my example code the array [3,4,1,2,5] moves/reorders the 3rd circle (in the original HTML order) to be 1st in the DOM, 4th to be 2nd, 1st to be 3rd, and so on...
Try to invert #one and #two. Have a look to this fiddle : http://jsfiddle.net/hu2pk/3/
Update
In SVG, z-index is defined by the order the element appears in the document. You can have a look to this page too if you want : https://stackoverflow.com/a/482147/1932751
You can use use.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120">
<g>
<g id="one">
<circle fill="green" cx="100" cy="105" r="20" />
</g>
<g id="two">
<circle fill="orange" cx="100" cy="95" r="20" />
</g>
</g>
<use xlink:href="#one" />
</svg>
The green circle appears on top.
jsFiddle
As discussed, svgs render in order and don't take z-index into account (for now). Maybe just send the specific element to the bottom of its parent so that it'll render last.
function bringToTop(targetElement){
// put the element at the bottom of its parent
let parent = targetElement.parentNode;
parent.appendChild(targetElement);
}
// then just pass through the element you wish to bring to the top
bringToTop(document.getElementById("one"));
Worked for me.
Update
If you have a nested SVG, containing groups, you'll need to bring the item out of its parentNode.
function bringToTopofSVG(targetElement){
let parent = targetElement.ownerSVGElement;
parent.appendChild(targetElement);
}
A nice feature of SVG's is that each element contains it's location regardless of what group it's nested in :+1:
Using D3:
If you want to re-inserts each selected element, in order, as the last child of its parent.
selection.raise()
Using D3:
If you want to add the element in the reverse order to the data, use:
.insert('g', ":first-child")
Instead of .append('g')
Adding an element to top of a group element
There is no z-index for svgs. But svg determines which of your elements are the uppermost by theire position in the DOM. Thus you can remove the Object and place it to the end of the svg making it the "last rendered" element. That one is then rendered "topmost" visually.
Using jQuery:
function moveUp(thisObject){
thisObject.appendTo(thisObject.parents('svg>g'));
}
usage:
moveUp($('#myTopElement'));
Using D3.js:
d3.selection.prototype.moveUp = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
usage:
myTopElement.moveUp();
This is the top Google result for searches regarding z-index and SVGs. After reading all the answers, some of which are very good, I was still confused.
So for rookies like me, here is the current summary, 9 years later in 2022.
You can't use z-index with SVGs.
In SVGs, z-index is defined by the order the element appears in the document.
If you want something to appear on top, or closer to the user, draw it last or insert it before. Source
SVG 2 could support z-index but might never come out
SVG 2 is a proposal to implement that and other features but it is at risk of never moving forward.
SVG 2 reached the Candidate Recommendation stage in 2016, and was revised in 2018 and the latest draft was released on 8 June 2021. Source
However it doesn't have a lot of support and very few people are working on it. Source So don't hold your breath waiting for this.
You could use D3 but probably shouldn't
D3 a commonly used to visualize data supports z-index by binding your z-index and then sorting but it is a large and complex library and might not be the best bet if you just want a certain SVG to appear on top of a stack.
The clean, fast, and easy solutions posted as of the date of this answer are unsatisfactory. They are constructed over the flawed statement that SVG documents lack z order. Libraries are not necessary either. One line of code can perform most operations to manipulate the z order of objects or groups of objects that might be required in the development of an app that moves 2D objects around in an x-y-z space.
Z Order Definitely Exists in SVG Document Fragments
What is called an SVG document fragment is a tree of elements derived from the base node type SVGElement. The root node of an SVG document fragment is an SVGSVGElement, which corresponds to an HTML5 <svg> tag. The SVGGElement corresponds to the <g> tag and permits aggregating children.
Having a z-index attribute on the SVGElement as in CSS would defeat the SVG rendering model. Sections 3.3 and 3.4 of W3C SVG Recommendation v1.1 2nd Edition state that SVG document fragments (trees of offspring from an SVGSVGElement) are rendered using what is called a depth first search of the tree. That scheme is a z order in every sense of the term.
Z order is actually a computer vision shortcut to avoid the need for true 3D rendering with the complexities and computing demands of ray tracing. The linear equation for the implicit z-index of elements in an SVG document fragment.
z-index = z-index_of_svg_tag + depth_first_tree_index / tree_node_qty
This is important because if you want to move a circle that was below a square to above it, you simply insert the square before the circle. This can be done easily in JavaScript.
Supporting Methods
SVGElement instances have two methods that support simple and easy z order manipulation.
parent.removeChild(child)
parent.insertBefore(child, childRef)
The Correct Answer That Doesn't Create a Mess
Because the SVGGElement (<g> tag) can be removed and inserted just as easily as a SVGCircleElement or any other shape, image layers typical of Adobe products and other graphics tools can be implemented with ease using the SVGGElement. This JavaScript is essentially a Move Below command.
parent.insertBefore(parent.removeChild(gRobot), gDoorway)
If the layer of a robot drawn as children of SVGGElement gRobot was before the doorway drawn as children of SVGGElement gDoorway, the robot is now behind the doorway because the z order of the doorway is now one plus the z order of the robot.
A Move Above command is almost as easy.
parent.insertBefore(parent.removeChild(gRobot), gDoorway.nextSibling())
Just think a=a and b=b to remember this.
insert after = move above
insert before = move below
Leaving the DOM in a State Consistent With the View
The reason this answer is correct is because it is minimal and complete and, like the internals of Adobe products or other well designed graphics editors, leaves the internal representation in a state that is consistent with the view created by rendering.
Alternative But Limited Approach
Another approach commonly used is to use CSS z-index in conjunction with multiple SVG document fragments (SVG tags) with mostly transparent backgrounds in all but the bottom one. Again, this defeats the elegance of the SVG rendering model, making it difficult to move objects up or down in the z order.
NOTES:
(https://www.w3.org/TR/SVG/render.html v 1.1, 2nd Edition, 16 August 2011)
3.3 Rendering Order 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.
3.4 How groups are rendered Grouping elements such as the ‘g’ element (see container elements) have the effect of producing a temporary
separate canvas initialized to transparent black onto which child
elements are painted. Upon the completion of the group, any filter
effects specified for the group are applied to create a modified
temporary canvas. The modified temporary canvas is composited into the
background, taking into account any group-level masking and opacity
settings on the group.
Another solution would be to use divs, which do use zIndex to contain the SVG elements.As here:
https://stackoverflow.com/a/28904640/4552494
We have already 2019 and z-index is still not supported in SVG.
You can see on the site SVG2 support in Mozilla that the state for z-index – Not implemented.
You can also see on the site Bug 360148 "Support the 'z-index' property on SVG elements" (Reported: 12 years ago).
But you have 3 possibilities in SVG to set it:
With element.appendChild(aChild);
With parentNode.insertBefore(newNode, referenceNode);
With targetElement.insertAdjacentElement(positionStr, newElement); (No support in IE for SVG)
Interactive demo example
With all this 3 functions.
var state = 0,
index = 100;
document.onclick = function(e)
{
if(e.target.getAttribute('class') == 'clickable')
{
var parent = e.target.parentNode;
if(state == 0)
parent.appendChild(e.target);
else if(state == 1)
parent.insertBefore(e.target, null); //null - adds it on the end
else if(state == 2)
parent.insertAdjacentElement('beforeend', e.target);
else
e.target.style.zIndex = index++;
}
};
if(!document.querySelector('svg').insertAdjacentElement)
{
var label = document.querySelectorAll('label')[2];
label.setAttribute('disabled','disabled');
label.style.color = '#aaa';
label.style.background = '#eee';
label.style.cursor = 'not-allowed';
label.title = 'This function is not supported in SVG for your browser.';
}
label{background:#cef;padding:5px;cursor:pointer}
.clickable{cursor:pointer}
With:
<label><input type="radio" name="check" onclick="state=0" checked/>appendChild()</label>
<label><input type="radio" name="check" onclick="state=1"/>insertBefore()</label><br><br>
<label><input type="radio" name="check" onclick="state=2"/>insertAdjacentElement()</label>
<label><input type="radio" name="check" onclick="state=3"/>Try it with z-index</label>
<br>
<svg width="150" height="150" viewBox="0 0 150 150">
<g stroke="none">
<rect id="i1" class="clickable" x="10" y="10" width="50" height="50" fill="#80f"/>
<rect id="i2" class="clickable" x="40" y="40" width="50" height="50" fill="#8f0"/>
<rect id="i3" class="clickable" x="70" y="70" width="50" height="50" fill="#08f"/>
</g>
</svg>
Push SVG element to last, so that its z-index will be in top. In SVG, there s no property called z-index. try below javascript to bring the element to top.
var Target = document.getElementById(event.currentTarget.id);
var svg = document.getElementById("SVGEditor");
svg.insertBefore(Target, svg.lastChild.nextSibling);
Target: Is an element for which we need to bring it to top
svg: Is the container of elements
Move to front by transform:TranslateZ
Warning: Only works in FireFox
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" style="width:160px; height:160px;">
<g style="transform-style: preserve-3d;">
<g id="one" style="transform-style: preserve-3d;">
<circle fill="green" cx="100" cy="105" r="20" style="transform:TranslateZ(1px);"></circle>
</g>
<g id="two" style="transform-style: preserve-3d;">
<circle fill="orange" cx="100" cy="95" r="20"></circle>
</g>
</g>
</svg>
A better example of use, that I've ended up using.
<svg>
<defs>
<circle id="one" fill="green" cx="40" cy="40" r="20" />
<circle id="two" fill="orange" cx="50" cy="40" r="20"/>
</defs>
<use href="#two" />
<use href="#one" />
</svg>
To control the order you can change href attribute values of these use elements. This can be useful for animation.
Thanks to defs, circle elements are drawn only once.
jsfiddle.net/7msv2w5d
its easy to do it:
clone your items
sort cloned items
replace items by cloned
function rebuildElementsOrder( selector, orderAttr, sortFnCallback ) {
let $items = $(selector);
let $cloned = $items.clone();
$cloned.sort(sortFnCallback != null ? sortFnCallback : function(a,b) {
let i0 = a.getAttribute(orderAttr)?parseInt(a.getAttribute(orderAttr)):0,
i1 = b.getAttribute(orderAttr)?parseInt(b.getAttribute(orderAttr)):0;
return i0 > i1?1:-1;
});
$items.each(function(i, e){
e.replaceWith($cloned[i]);
})
}
$('use[order]').click(function() {
rebuildElementsOrder('use[order]', 'order');
/* you can use z-index property for inline css declaration
** getComputedStyle always return "auto" in both Internal and External CSS decl [tested in chrome]
rebuildElementsOrder( 'use[order]', null, function(a, b) {
let i0 = a.style.zIndex?parseInt(a.style.zIndex):0,
i1 = b.style.zIndex?parseInt(b.style.zIndex):0;
return i0 > i1?1:-1;
});
*/
});
use[order] {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="keybContainer" viewBox="0 0 150 150" xml:space="preserve">
<defs>
<symbol id="sym-cr" preserveAspectRatio="xMidYMid meet" viewBox="0 0 60 60">
<circle cx="30" cy="30" r="30" />
<text x="30" y="30" text-anchor="middle" font-size="0.45em" fill="white">
<tspan dy="0.2em">Click to reorder</tspan>
</text>
</symbol>
</defs>
<use order="1" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="0" y="0" width="60" height="60" style="fill: #ff9700; z-index: 1;"></use>
<use order="4" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="50" y="20" width="50" height="50" style="fill: #0D47A1; z-index: 4;"></use>
<use order="5" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="15" y="30" width="50" height="40" style="fill: #9E9E9E; z-index: 5;"></use>
<use order="3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="25" y="30" width="80" height="80" style="fill: #D1E163; z-index: 3;"></use>
<use order="2" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="30" y="0" width="50" height="70" style="fill: #00BCD4; z-index: 2;"></use>
<use order="0" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sym-cr" x="5" y="5" width="100" height="100" style="fill: #E91E63; z-index: 0;"></use>
</svg>
Just wanted to add a trick that works when you want to put a specific element on top.
function moveInFront(element) {
const svg = element.closest('svg'); // Find the parent SVG
svg.appendChild(element); // Append child moves the element to the end
}
This works because, and I quote the docs, "appendChild() moves [the element] from its current position to the new position" instead of adding a copy.
Note: If the element is nested, you would have to move the element to front within the group, and perhaps move the group to front as well.
use works for this purpose, but those elements that are placed with use help after is hard to manipulate...
What I couldn't figure out after I used it was: why I couldn't hover (neither mouseover, mouseenter manipulations from js would work) on the use elements to get additional functionality - like ~ showing text over the circles ~
After returned to circle reordering as it was only way to manipulate with those svg objects

Value of x and y of SVG text as percentage + pixel

I need to virtually divide the svg element into 9 equal grids for text positioning, like x=0, y=10; x=33.33%, y=10; x=0, y=33.33% + 10 and so on.
The last one in css can be done using calc(33.33% + 10) but how do I set it for the x and y of svg text. Is there a way to add percentage to pixel and assign it x and y of svg text or is there an alternative better way to proceed ahead. Please guide.
You need to have a place where you can use percentage values, and another where you can use pixel values. For the case of text elements, this is relatively easy. Position the text with percentage x/y values, and then move it with a transform attribute, which takes unitless numbers interpreted in the local coordinate system:
<svg>
<text x="33.3%" y="0%" transform="translate(0, 10)"></text>
<text x="0" y="33.3%%" transform="translate(5, 15)"></text>
<svg>
If the texts are always in the same place in relation to the grid, you could simplify:
<svg>
<g transform="translate(0, 10)">
<text x="33.3%" y="0%"></text>
<text x="0" y="33.3%%"></text>
</g>
<svg>
In more general cases the best and most semantic strategy might be to nest <svg> elements, where the inner elements represent single cells in the grid:
<svg>
<!-- width and height are not strictly needed for the nested
svg elements, they default to 100% -->
<svg x="33.3%" y="0%" width="33.3%" height="33.3%">
<text x="0" y="10"></text>
<circle cx="50" cy="20" r="20" />
</svg>
<svg x="0%" y="33.3%" width="33.3%" height="33.3%">
<text x="0" y="10"></text>
<rect x="0" cy="0" width="100" height="50" />
</svg>
</svg>

How can I translate SVG group element to the center of the root SVG?

Question: How can I translate the center of a SVG group element to the center of the root SVG. I tried to use transform="translate(x,y)" on the <g> element, but this transformation will only translate with respect to the top left corner of the group element.
Example case and goal: In the following SVG, the two rectangles <rect> are grouped together with <g>. Assume we don't know the position, size, and which types are elements are inside the group. We only know the width/height of the parent SVG. Goal is to translate the center of the group (bounding box of the two rectangles) to the center of the root SVG. The issue is that we don't know the height/width of the "bounding box" which is the group itself, thus when using transform="translate(x,y)" alone won't get us to the center of the root SVG.
<svg width="500px" height="300px" preserveAspectRatio="none" viewBox="0,0,5.0,3.0">
<g transform="translate(0,0)">
<rect x="1" y="0.25" width="0.5" height="0.5" fill="green" />
<rect x="1.25" y="1" width="0.5" height="0.5" fill="red" />
</g>
</svg>
Requirements:
The solution can only use pure SVG. CSS or external libraries can NOT be used.
Using Python to do basic calculation is okay. However, remember we don't what elements are inside the <g>.
The coordinate system for the root SVG (viewBox, width, height) must not be change because in example use case, these coordinate system are used for conversion of real world spatial units (ex: millimeters) to pixels for the end application.
You need some way to do calculations. I'm using Javascript:
const SVG_NS = 'http://www.w3.org/2000/svg';
const svg = document.querySelector("svg");
// the viewBox of the svg element splited
let svgVB = svg.getAttribute("viewBox").split(/[ ,]+/);
let o = test.getBBox();
let oCenter = {};//the center of the g#test element
oCenter.x = o.x + o.width/2;
oCenter.y = o.y + o.height/2;
// the valuefor the transform attribute
let tr = `
translate(${-oCenter.x},${-oCenter.y})
translate(${svgVB[2]/2},${svgVB[3]/2})`;
test.setAttributeNS(null, "transform",tr);
// for debugging I'm drawing the bounding box
bbox.setAttributeNS(null, "transform",tr);
function drawRect(o, parent) {
let rect = document.createElementNS(SVG_NS, 'rect');
for (let name in o) {
rect.setAttributeNS(null, name, o[name]);
}
parent.appendChild(rect);
return rect;
}
drawRect(o, bbox);
svg{border:1px solid;}
<svg width="500px" height="300px" preserveAspectRatio="none" viewBox="0,0,5.0,3.0">
<g id="bbox"></g>
<g id="test" transform="translate(0 0)">
<rect x="1" y="0.25" width="0.5" height="0.5" fill="green" />
<rect x="1.95" y="1" width="0.5" height="0.5" fill="red" />
</g>
</svg>
I hope it helps

SVG text error - text getting collapsed and doesn't show properly

In the example http://jsfiddle.net/q5yN3/1/ could anyone tell me why does the Text Example gets collapsed and doesn't show properly? Am I missing some property of the tag or should I add it differently?
<text font-family="Tahoma" font-size="8" text-anchor="middle" transform="translate(533316.59375 -4757133.5)">Text Example</text>
I'm using Firefox.
Thanks!
The text element is inside a <g> element that has an extreme zoom-out scale factor applied to it:
<g transform="translate(0 85) scale(1)">
<g transform="translate(221 2952) scale(0.000607469)">
<text font-family="Tahoma" font-size="8" text-anchor="middle"
transform="translate(533316.59375 -4757133.5)"
>Text Example</text>
That scale factor applies to the font-size as well, so your text is being drawn as 8px*0.000607469 = a tiny fraction of a pixel tall. The only reason the text is visible in other browsers is because of accessibility features which prevent them from drawing super-tiny text.
The solution is simply to reverse the scale factor from the <g> element on the <text> element:
<g transform="translate(0 85) scale(1)">
<g transform="translate(221 2952) scale(0.000607469)">
<text font-family="Tahoma" font-size="8" text-anchor="middle"
transform="translate(533316.59375 -4757133.5) scale(1646)"
>Text Example</text>
Note that the reverse scale is added after the translation, so the units for the translation are still in the scaled coordinates.

SVG border appears trimmed

I think I am missing an apparent offset issue in my very first svg here, The top and left borders are tirmmed (pointed by red arrow), Also if something like nested <g> or <symbol> is possible pleas let me know, Thanks. (screenshot in FF27).
The simplified code and a fiddle
<svg>
<defs>
<symbol id="ringCenters" style="fill:black;">
<circle cx="50" cy="50" r="2" />
/*...more circles*/
</symbol>
<symbol id="ring1" class="rings">
<path d="M99.9746,51.5943
A50 50 0 1 0
62.5254,98.4057"
stroke="green" />
<path d="M99.9746,51.5943
A50 50 0 0 1
62.5254,98.4057"
stroke="red" />
</symbol>
/*...more rings*/
</defs>
<use xlink:href="#ringCenters" x="10" y="10" />
/*...more rings*/
</svg>
.rings{fill:none;}
svg {
width:600px;
height:300px;
}
The stroke around a shape is always centered on the exact geometric border of the shape, and so extends beyond the shape by half the stroke width.
If for any reason you don't want to use overflow:visible, another option is therefore to just adjust the positions of your shape so that you have a bit of padding on the edges, equal to half the stroke width. (Of course, that assumes that the stroke-width will be the same every time you use the symbol.)
Note that you have to adjust the position of the <path> within the <symbol>, not just the position of the <symbol> within your SVG. That's because each reference to a symbol element is drawn as if it was a nested <svg> element within it's own viewport, with a fixed height, width, and "viewBox" coordinate system. You're not setting those attributes on the symbol, so they end up as the defaults for nested SVGs: width and height equal to 100% of the parent, and a default coordinate system with (0,0) at the top left corner of the viewport.
That "nested SVG" then gets positioned so that it's top left corner is at the (x,y) position specified in the <use> element, but as far as the drawing content within the symbol is concerned, the rectangular edges of that viewport are the edges of the drawing surface. Unless, of course, you specifically allow overflow, as #helderdarocha suggested.
(By the way, the symbols-are-drawn-as-nested-SVGs idea is probably why you needed to specify svg{ overflow:visible;} for Firefox, although it really should work by setting the property on the symbols directly; I'd call that a Firefox bug.)
On your other question: <g> elements can be nested multiple times without problem. For <symbol> elements, it's not so clear. The specs say that symbols are much like <g> elements except (a) they have viewports and (b) the symbol is not drawn directly, it is only available for reference by a <use> element.
Now, that suggests that <symbol>s can be nested like <g> elements. And on Chrome it works. On Firefox, not so much. I assume what is happening is that when Firefox copies the internal content of the outer <symbol>, it treats the internal <symbol> elements as if they were just symbol definitions, not as if they were renderings of those symbols.
I.e. code like this
<svg>
<defs>
<symbol id="complicated">
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
</symbol>
</defs>
<use xlink:href="#complicated"/>
</svg>
Gets rendered as if it was
<svg>
<defs>
<symbol id="complicated">
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
</symbol>
</defs>
<g> <!-- The <use> element is drawn as if it was a <g> element -->
<svg> <!-- The <symbol> element is drawn as if it was a nested <svg> -->
<symbol id="firstPart"> <!-- the internal symbol is copied as-is -->
<path d="...."/>
</symbol>
<symbol id="secondPart"> <!-- but symbols aren't drawn directly! -->
<path d="...."/>
</symbol>
<svg>
</g>
</svg>
...and that shouldn't really be rendered as anything at all. This, I wouldn't call a Firefox "bug", just a literal interpretation of the specs, which don't give any special treatment to nested symbol elements.
There is a way to get symbols to nest, however, and that's to use <use> elements within the symbol definition:
<svg>
<defs>
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
<symbol id="complicated">
<use xlink:href="#firstPart"/>
<use xlink:href="#secondPart"/>
</symbol>
</defs>
<use xlink:href="#complicated"/>
</svg>
Here's your fiddle updated with that structure: http://jsfiddle.net/vGNMu/6/
Half of your stroke is outside the viewBox. You can avoid clipping by using:
svg, symbol {
overflow: visible;
}
Or adding an overflow="visible" attribute to each symbol.
Your updated JSFiddle

Resources