I am working with d3js where lib create nodes and links according to data.
At some place have some specific requirement to add multiple text in same line like url "www.xyz.com/home/user" (here 3 strings "www.xyz.com","/home","/user"). They are not separate nodes so I can't find position with d3. It's just a <text> element with 3 <tspan> children.
<text id="TaxFilingResource_URL" class="label url" dy="24" dx="30">
<tspan id="TaxFilingResource.URL.1">www.xyz.com</tspan>
<tspan id="TaxFilingResource.URL.2">/home</tspan>
<tspan id="TaxFilingResource.URL.3">/user</tspan>
</text>
and displaying like this below
www.xyz.com/home/user
I need to get the position and width of each <tspan> element. My code is
var el = document.getElementById(d.source);
x = el.getStartPositionOfChar('').x (or)
x = el.getClientRects().left;
that give relative position on text inside g element , but in some browser and on mac it will return absolute position.
Is there any right way to find position and width of tspan in JavaScript that worked with all browsers ( IE must > 9th version).
In SVG 1.1 tspan doesn't have a getBBox method, but in SVG2 it does, I've reported a chromium bug for it reporting the wrong value, http://code.google.com/p/chromium/issues/detail?id=349835.
This would give you the proper position and dimensions if the browsers implemented SVG2:
var bbox = document.getElementById("TaxFilingResource.URL.2").getBBox();
See jsfiddle for a full example.
For now, you can do a workaround using the methods that are available in SVG 1.1:
var home = document.getElementById("TaxFilingResource.URL.2");
var extent = home.getExtentOfChar(0); // pos+dimensions of the first glyph
var width = home.getComputedTextLength(); // width of the tspan
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.x.baseVal.value = extent.x;
rect.y.baseVal.value = extent.y;
rect.width.baseVal.value = width;
rect.height.baseVal.value = extent.height;
See jsfiddle.
If you need to transform to another place in the tree:
var dest = document.getElementById("dest"); // some arbitrary container element
var fromHometoDestmatrix = home.getTransformToElement(dest);
rect.transform.baseVal.appendItem(
rect.transform.baseVal.createSVGTransformFromMatrix(fromHometoDestmatrix));
dest.appendChild(rect);
See jsfiddle.
Related
I need to write some svgs on pdf file at precise position using javascript.
My webapp let the users make their drawings then I crop these drawings to remove unused white space and save them on a pdf.
Cropping svg adds viewBox attribute which isn't supported by the majority of js pdf library available.
This means that position and scale factor are wrong!
So my attempt to solve the problem was simplify the svg before put it on pdf file.
As far as I know there are few utils that could do this task:
svgcleaner
svgo
scour
Unfortunately none of them completely remove viewbox attribute.
Consider that this task should be completely automated so using programs like inkscape or Adobe Illustrator is not a possible solution.
As I've commented:
The value of the viewBox attribute is a list of four numbers: min-x, min-y, width and height. If min-x = min-y = 0 you can use the other 2 numbers as the width and the height of the svg element. However when you are cropping the svg element the min-x & min-y won't be 0. This means that you will need to wrap everything in a group and translate the group in the opposite direction: - min-x & - min-y.
Alternatively you can first convert the d attribute to all relative commands and instead of translating you can change the first move-to (M) values, thus recalculating the path.
Please read the comments in my code.
// get the viewBox of the original svg element and split it by space or commas
let vb = original.getAttribute("viewBox").split(/[ ,]+/);
//console.log(vb); // ["103", "118", "94", "83"]
//set the width and the height of the copy
copy.setAttribute("width",vb[2]);
copy.setAttribute("height",vb[3]);
// get the d attribute of the original
let path = document.querySelector("#original path").getAttribute("d");
let pathRel = Snap.path.toRelative(path);
//console.log(pathRel)
//change the coords of the first move to command
pathRel[0][1] -= vb[0];
pathRel[0][2] -= vb[1];
//a variable to be used as the d attribute for the copy path
let d = ""
pathRel.forEach(p=>{d+=(p.join(" "))})
//console.log(d)
//set the d attribute of the copy path
document.querySelector("#copy path").setAttribute("d",d)
#original{width:94px;}
svg{border:solid}
<script src="https://cdn.jsdelivr.net/snap.svg/0.3.0/snap.svg.js"></script>
<svg id="original" viewBox="103 118 94 83"><path d="M144.974,122.645Q150,114,155.026,122.645L194.974,191.355Q200,200,190,200L110,200Q100,200,105.026,191.355Z"></path></svg>
<svg id="copy" ><path></path></svg>
I have this group element and a text node inside it:
var legend = svg_container.append('g');
var legend_text = legend.append('text').attr('y', 0)
//other elements inside 'g'
This is just part of the code. In actuality, I have other child nodes inside the group element created on the fly after the text node(these other elements decide the width of the group element).
How do I position the text node centrally inside the group element?
To center it, you can use the text attribute text-anchor="middle" with the x and y attributes for positioning:
legend_text.attr("text-anchor","middle");
legend_text.attr("x", 0);
legend_text.attr("y", 0);
Here, your text will be fixed to the center origin of your group legend.
In fact, it won't be perfectly centered, to do so, you should set y to the half of the font-size attribute of your text.
Hoping I've correctly understood your problem !
EDIT: With your indication, I see two solutions :
My favorite one would include a modification of the legend group, by adding him a transform="translate(x,y)" attribute,
where the x and y are the center of your svg element. This one may not work with the rest of your code, but I find that doing so in general is a clean way to have multiple drawings living together in a single svg.
The other one is simplier by far, and I think will answer to your problem for now :
You replace the x and y values by the center of your legend group. The little problem here is the calculation of thoses values, because you can't read them directly from the element.
In fact, a g element doesn't have width or height attributes, it's only a container for applying attributes for a group of svg elements. That's why I recommend using my first solution.
Another way of centering it in the group using bbox
//make a g
var svg = d3.select("body").append("svg").attr("width", "900")
.attr("height", "900").append("g");
//add circle to it
svg.append("circle").attr("cx", 50).attr("cy", 50).attr("r", 30);
svg.append("circle").attr("cx", 250).attr("cy", 150).attr("r", 30);
//get bbox
var bb = svg.node().getBBox();
var centery = bb.y + bb.height/2;
var centerx = bb.x + bb.width/2;
svg.append("text").text("Hello").attr("x",centerx).attr("y", centery)
Working code here
I'm using d3 library to create a svg graphic. The problem I have is when I resize the window. The whole graphic resizes meaning that texts (legend and axis) resize as well, to the point where it's unreadable. I need it to keep the same size when resizing.
I've been searching online and I found this solution:
var resizeTracker;
// Counteracts all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
var unscale = function (el) {
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf) {
// Keep a single transform matrix in the stack for fighting transformations
xf = el.scaleIndependentXForm = svg.createSVGTransform();
// Be sure to apply this transform after existing transforms (translate)
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);
};
[].forEach.call($("text"), unscale);
$(window).resize(function () {
if (resizeTracker) clearTimeout(resizeTracker);
resizeTracker = setTimeout(function () { [].forEach.call($("text"), unscale); }, 0);
});
And added it to my code, but it's not working. I debugged it and at this part of the code:
var xf = el.scaleIndependentXForm;
It always returns the same matrix: 1 0 0 1 0 0 and the text keeps resizing as does the rest of the svg elements instead of keeping static.
Could anyone help me, please?
Thanks in advance.
The same thing was happening to me with an SVG generated by SnapSVG until I noted that the example page on which this does work wraps its 'main' SVG tag in another SVG tag before using el.ownerSVGElement.ownerSVGElement rather than el.ownerSVGElement.
Wrapping my SVG in an 'empty' wrapper SVG (note style overflow:visible;) I had much better results!
Edit: oh, wait. Internet Explorer still isn't happy. Seems the author of the solution is aware...
I have a page with a svg loaded via object.
Then, I call a function that loads a div, using width, height, left and top of an internal g element
var cartina = document.getElementById(whatMap);
var cartinaContext;
cartina.addEventListener("load",function(){
cartinaContext = cartina.contentDocument;
var el = $("#mappaBase", cartinaContext)[0];
var rect = el.getBoundingClientRect();
var whatContainer = "#containerIcone"+whatMap;
$(whatContainer).css("position", "absolute");
$(whatContainer).width(rect.width);
$(whatContainer).height(rect.height);
$(whatContainer).css("left", rect.left);
$(whatContainer).css("top", rect.top);
}
I'm using getBoundingClientRect(). I'm applying the div #containerIcone over the svg.
In Chrome it works smoothly well. The problem is in Firefox: when I load the page, width and height are properly loaded but left and top are not. If I inspect the svg with Firefox, it appears that the g element is placed in a fixed position, while the rendered g element has another one (responsive to window dimensions and other elements position). Still, the g fixed element reacts well to window various sizes. The div is placed over the g element fixed inspect-position.
Inspecting the same element with Chrome reveals that the g element inspect box is drawed everytime where the g rendered element is.
How can I make this work in Firefox?
I found a solution, it's working cross-browser.
Instead of positioning with .top and .left of the nested g element, I get width and height of nested element
var el = $(nestedElement);
var rect = el.getBoundingClientRect();
Then I get width and height of the parent svg element
var rectSvg = mysvg.getBoundingClientRect();
Then I subtract the el width and height from the svg ones, and divide results by 2. The final result is the white space the svg has inside it, top and left, used to center the rendered element el inside the svg element (used by browser to mantain aspect ratio). Then I apply those results as css top and left of my div containing icons - it will be positioned exactly over the g el element.
var leftSpace = (rectSvg.width - rect.width)/2;
var topSpace = (rectSvg.height - rect.height)/2;
$(myPositionedDiv).css("left", leftSpace);
$(myPositionedDiv).css("top", topSpace);
This manner, however Firefox positions the element despite of rendering, left and top are correctly calculated.
I am looking for a way to create a circle with a gradient fill in Leaflet.
My approach so far is to define the fillColor of the circle as 'url(#gradient)' and add the Gradient definition manually via the following code:
function createGradient (map) {
// Get the SVG element from the overlayPane
var svg = map.getPanes().overlayPane.firstChild;
var doc = document.implementation.createDocument(null, null, null);
// Create def element
var svgDef = doc.createElement('defs');
// Create gradient and stops element
var svgGradient = doc.createElement("radialGradient");
var svgStart = doc.createElement('stop');
var svgStop = doc.createElement('stop');
// Set ID attribute of gradient
svgGradient.setAttribute('id', 'gradient');
// set stops and colors
svgStart.setAttribute('offset', '0%');
svgStop.setAttribute('offset', '100%');
svgStart.setAttribute('class', 'circle-start');
svgStop.setAttribute('class', 'circle-stop');
svgGradient.appendChild(svgStart);
svgGradient.appendChild(svgStop);
// Append blur element to filter element
svgDef.appendChild(svgGradient);
// Append filter element to SVG element
svg.appendChild(svgDef);
}
The funny thing is, that the gradient fill is not shown initially. However, if I go into devtools and remove the 'defs' block and add it again it the gradient fill is shown correctly.
Can anyone help me to get rid of this issue or alternatively another way to get a gradient fill?
You cannot create SVG elements using createElement, that's only for html elements. If you want to create an SVG element you must create an element in the SVG namespace using createElementNS i.e.
var svgDef = doc.createElementNS('http://www.w3.org/2000/svg', 'defs');
// Create gradient and stops element
var svgGradient = doc.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
var svgStart = doc.createElementNS('http://www.w3.org/2000/svg', 'stop');
var svgStop = doc.createElementNS('http://www.w3.org/2000/svg', 'stop');
Using devtools reruns the html parser on the content which magically corrects the namespace.