How to draw non-scalable text in SVG with Javascript? - javascript

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...

Related

Snap.js - how to set attribute to loaded svg

I have a created simple snapjs sample here https://plnkr.co/edit/CyeVHuuuWrTAy1yKsp4d?p=preview
This is my Script - I am loading an svg and I would like to paint it in green.
Surprisingly not the svg is colored, but the "bigCircle", which I draw as well.
$(document).ready(function() {
var s = Snap("#svg");
var bigCircle = s.circle(150, 150, 100);
Snap.load("world.svg", function (f) {
var world = s.append(f);
world.attr({
fill: "#bada55",
});
bigCircle.drag();
world.drag();
});
})
Thanks for your help!
You need to select the world after you place it. You are trying to do this with:
var world = s.append(f)
But that doesn't return the paths that make up the world. It returns the entire svg. I would add the world first and then select it. I fixed it by adding this line:
world=s.select('[id="layer1"]')
I saw that the world is all grouped under that id in your svg. Once it's selected, you can apply color/drag like you wanted. Here's the plunker: https://plnkr.co/edit/t4JmZMz4JPJ0dDb95aQc?p=preview

Mouse click transformation to svg after pan and zoom

Im using snap.svg an snap.svg.zpd libraries. Same issue I have if I use snap.svg and jQuery panzoom library combination.
Code sample you can find here.
var mySvg = $("#plan")[0];
var snap = Snap("#plan");
//create an image
var imagePlan = snap.image("http://upload.wikimedia.org/wikipedia/commons/4/42/Cathedral_schematic_plan_fr_vectorial.svg", 10, 10, 900, 500);
var group = snap.group(imagePlan);
snap.zpd();
var pt = mySvg.createSVGPoint(); // create the point;
imagePlan.click(function(evt)
{
console.log(evt);
pt.x = evt.x;
pt.y = evt.y;
console.log(mySvg.getScreenCTM().inverse());
//When click, create a rect
var transformed = pt.matrixTransform(mySvg.getScreenCTM().inverse());
var rect1 = snap.rect(transformed.x, transformed.y, 40, 40);
group.add(rect1);
});
Problem is...if you click on initial svg it will add rectangle to the mouse position. If you pan/zoom image and then add rectangle it will be shiffted.
It looks like problem is in method mySvg.getScreenCTM().inverse(). Matrix returned is always same one, panning and zooming does not change it. It always use matrix from initialy rendered svg. However, if I inspect svg element, I can see that pann/zoom change transform matrix directly on element (image below).
Does anybody know how to fix this. My requirement is to be able to drag and drop elements outside svg into svg on any zoom scale or pan context, so I need transformation from mouse click point to svg offset coordinates. If you know any other approach or any other library combination that could done this, it would be ok for me.
Thanks in advance.
Problem is, the transform isn't in mySvg. Its on the 'g' group element thats inside the svg. Zpd will create a group to operate on as far as I know, so you want to look at that.
To hightlight this, take a look at
console.log(mySvg.firstElementChild.getScreenCTM().inverse());
In this case its the g element (there's more direct ways of accessing it, depending on whether you want to just work in js, or snap, or svg.js).
jsfiddle
Its not quite clear from your description where you want the rect (within the svg, separate or whatt) to go and at what scale etc though, and if you want it to be part of the zoom/panning, or static or whatever. So I'm not sure whether you need this or not.
I'm guessing you want something like this
var tpt = pt.matrixTransform( mySvg.firstElementChild.getScreenCTM().inverse() )
var rect1 = snap.rect(tpt.x, tpt.y, 40, 40);

Can't select svg element with d3

I am working on a visualization tool that uses a svg image of the brain. Now this svg has paths that are filled with a color. I want to loop over all these paths to set the fill color to white, but for some reason I cannot get the element.
The project can be seen here. The svg is inside a div and I even assigned an identifier brain to the div. The svg itself has an id svg2. So far I've tried the following:
function clearBrainColors() {
var brain = d3.select("#svg2");
console.log(brain);
var paths = brain.selectAll("path");
console.log(paths.length);
brain.selectAll('path').each(function(d,i) { console.log(this); });
}
But it outputs null in the array[0] component of the selection and 0 with paths.length.
I've also tried to use lines such as
var brain = d3.select("#brain svg"); and var brain = d3.select("#brain svg#svg2"); but those do not work either.
So, how can I select the brain svg using d3?
Decided to put the svg inline as it apparently speeds things up.
The code I used to fill the svg is now:
$("#svg2").find("path").each(function(){
$(this).css({ fill: "#ff0000" });
});
You can try setTimeOut() , following example
setTimeOut(function() {
var brain = d3.select("#svg2");
console.log(brain);
}, 1000);
this could be the svg is generate on the spot, d3 unable get at that moment.
Hope this help :)

Infovis: Label disappears on partial nodes

The label on the left most node disappears if the node is partially displayed on the canvas.
How do I resolve this?
Thanks
InfoVis tries to hide the node labels when it asserts that the label would fall off the canvas, if it were to be displayed on the node.
It basically computes the canvas position and dimensions, the node position and dimensions, and tries to see if the label's position is out of the canvas.
This can be seen on placeLabeland fitsInCanvas functions, around lines 9683 and 7669 of the final jit.js file, respectively.
I faced this problem too, while working with SpaceTree visualizations. This became an issue when we tried present a decent experience in mobile, where I could not find a way to put the canvas panning to work (so, when a node label partially disappeared, I had no way to select that node and centre the whole tree by consequence, to allow the further exploration of other nodes...).
What I did was change the function fitsInCanvas:
fitsInCanvas: function(pos, canvas, nodeW, nodeH) {
var size = canvas.getSize();
if(pos.x >= size.width || (pos.x + nodeW) < 0
|| pos.y >= size.height || pos.y + nodeH < 0) return false;
return true;
}
And called it accordingly on placeLabel:
placeLabel: function(tag, node, controller) {
...
...
...
var style = tag.style;
style.left = labelPos.x + 'px';
style.top = labelPos.y + 'px';
// Now passing on the node's width and heigh
style.display = this.fitsInCanvas(labelPos, canvas, w, h)? '' : 'none';
controller.onPlaceLabel(tag, node);
}
However, this is no solution.
You now will probably see your labels falling off the canvas, in a weird effect, until the whole node disappears.
And, obviously, I changed the source directly... a ticket should be filled on github.
EDIT:
Actually, it seems that I was working with an old version of the lib. The discussed behaviour changed to something similar to what I was describing. So, there is no need to change the code. Just download again your files. Specifically, the following link should give you these changes:
https://github.com/philogb/jit/blob/3d51899b51a17fa630e1af64d5393def589f874e/Jit/jit.js
There is a much simpler way to fix this although it might not be as elegant.
First, use the CSS 3 overflow attribute on the div associated with the Spacetree. For example, if the div in your HTML page that is being used by infovis is
<div id="infovis"> </div>
Then, you want some CSS that makes sure that your canvas does not allow overflow.
#infovis {
position:relative;
width:inherit;
height:inherit;
margin:auto;
overflow:hidden;
}
Next, in your your space tree definition, you probably have a placeLabel : function defined. At the end of it, simply set the style.display = "";. This force the label to be shown if the node is placed onto the canvas. For example:
onPlaceLabel: function(label, node, controllers){
//override label styles
var style = label.style;
if (node.selected) {
style.color = '#ccc';
}
else {
style.color = '#fff';
}
// show the label and let the canvas clip it
style.display = '';
}
Thus, you are displaying the text and turning it over to the browser to clip any part of the node or the label that extend off the canvas.

Get the real size of a SVG/G element

Is there any accurate way to get the real size of a svg element that includes stroke, filters or other elements contributing to the element's real size from within Javascript?
I have tried pretty much everything coming to my mind and now I feel I'm coming to a dead end :-(
Updated question to add more context (Javascript)
You can't get the values directly. However, you can get the dimensions of the bounding rectangle:
var el = document.getElementById("yourElement"); // or other selector like querySelector()
var rect = el.getBoundingClientRect(); // get the bounding rectangle
console.log( rect.width );
console.log( rect.height);
It is supported at least in the actual versions of all major browser.
Check fiddle
Both raphael js http://dmitrybaranovskiy.github.io/raphael/ and d3 js http://d3js.org/ have various methods to find the size of an svg object or sets of svg object. It depends on if it's a circle, square, path, etc... as to which method to use.
I suspect you are using complex shapes, so in that case bounding box would be your best bet http://raphaeljs.com/reference.html#Element.getBBox
(Edit: updated reference site.) http://dmitrybaranovskiy.github.io/raphael/reference.html#Element.getBBox
Here is an example using D3.js:
Starting with a div:
<div style="border:1px solid lightgray;"></div>
The javascript code looks like this:
var myDiv = d3.select('div');
var mySvg = myDiv.append('svg');
var myPath = mySvg.append('path');
myPath.attr({
'fill': '#F7931E',
'd': 'M37,17v15H14V17H37z M50,0H0v50h50V0z'
});
// Get height and width.
console.log(myPath.node().getBBox());
If it is an SVG used as a CSS background image and you're using React you can use background-image-size-hook.
import { useBackgroundImageSize } from 'background-image-size-hook'
const App = () => {
const [ref, svg] = useBackgroundImageSize()
console.log(svg) // { width, height, src }
return <SVGBackgroundImageComponent ref={ref} />
}
You didn't specify any programming language. So I can suggest to use Inkscape.
In the file menu you find document's properties and in the first page there's "resize page to content" command. In this way you remove all the white space around your draw and you see the real size. After width and height values apprear inside the header of svg.
I know that Inkscape supports scripting and command line operations but I don't know if it's possible to do the trimming operatation in this way. But if it's possible you can do that from every programming language.

Categories