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);
Related
I want to create an animation of a path, like a journey/timeline. The user is shown a circle (eventually to be an image), when they click this circle the animation begins and shows a path animating/traveling to another circle with a fade in effect. I have attached an image which I think explains my idea best.
My question is - what would be the recommended way of doing this? css animation or is there a jquery library that would be helpful?
Thank you
I would take svg as base. With Inkscape (or similar) like that, you can design the path visually and include the blue circle.
Than you can inject the svg-code in your html like so (copy the svg code from the generated file):
<div class="svg-container">
<svg>…</svg>
</div>
Finally you can use javascript to reference the circle and the path:
var path = document.querySelector('.path'), //these selectors are just arbitrary
circle = document.querySelector('.circle');
To get a point on the path, you can use:
var point = path.getPointAtLength();
For animation, I assume that you basically know how to do that, since this would be too much to explain here. But lets say that p is the progress of you animation and will be in the range [0,1]. To calculate a point at a given p could be done like so:
let pointAtT = (path, t) => {
let l_total = path.getTotalLength();
return path.getPointAtLength(l_total * t);
}
Having that, you can use the x and y coordinate to manipulate the circle. Be aware of possibly applied transformations, that is why I recommend to transform everything to global coordinate space, calculate there and transform the result back to the item's coordinate space.
Documentation on mdn
There are a some svg libraries that might help you: svg.js, snap.svg and Raphaël.
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 project for manipulating SVGs.
Users can zoom in and out of the image. I want to have a thumbnail of the whole image that shows and highlights the area that users are currently zooming in/out.
Something along these lines http://www.ancientlives.org/transcribe
I have tried playing around with http://snapsvg.io/, without success.
Can anyone help working something out with the library?
As the specific question mentions Snap, I'll go down that road.
You could clone the svg element, and drag a rect over it, or I was wondering if you could drag a rect thats actually a clip or something, that could be a slightly better solution, but a bit fiddlier to work out, so for the moment here's the first way.
First off, we can load our image..
Snap.load("Dreaming_Tux.svg", onLoad)
Then the main onLoad func..
This works by cloning the image (I also use toDefs() which isn't necessary, but if the image is a large file, you could possibly use just one the set of elements, and reference them in a 'use' method. So I'm leaving that in as just a simple example for the moment.
We also define a viewBox,
var svg = s.svg(0,0,800,800,0,0,200,200);
Which will be our 'window'
And then when we drag the rect, we make the image (placed in a group so we can transform it) move.
You will need to tweak the drag handler, to make it work completely (atm it will just drag via dx,dy and reset each time) and also tweak the zoom and window sizes and relationship to what you want, but it should give a proof of concept.
example (drag the rect)
function onLoad( fragment ) {
s.append( fragment );
var tux = s.select('#tux');
var clone = tux.clone();
var svg = s.svg(0,0,800,800,0,0,200,200);
var g = s.g( tux ).transform('t0,0').appendTo(svg);
var defElement = svg.toDefs();
var dragRect = s.rect(0,0,100,100).attr({ opacity: 0.2, transform: 't600,50', id: 'dragrect' }).drag( dragMove, dragStart );
var tux1 = defElement.use().appendTo( s );
var tux2 = clone.appendTo( s.g().transform('t600,50s0.5') );
s.append( dragRect );
function dragMove(dx,dy) {
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy]
});
g.transform('t' + -dx +',' + -dy);
}
function dragStart() {
this.data('origTransform', this.transform().local );
}
};
I'm trying to make an interactive floor plan. When the user hovers a room I want to display some sort of message.
All my floor-plans are in JPEG format.
I would like to make something like this: http://dciarletta.github.io/d3-floorplan but I need to also make a tool in the backend that would create those overlays.
My questions is, how can I do it? Ideally I would just click around the room to create the overlay, but I don't think d3.js allows it. I'm also having a problem getting the correct coordinates:
$('#floor').click(function(e) {
var $this = $(this);
var offset = $this.offset();
var pos = [];
pos.x=(e.pageX-offset.left);
pos.y=(e.pageY-offset.top);
console.log('x: '+pos.x+' | y: '+pos.y);
});
http://jsfiddle.net/5nTEk/
So, not only I don't think I'm getting the correct coordinates as I don't know how to add an overlay as the link above... Any suggestions?
You can probably do it by overlaying an SVG over your <img>. D3 would render into this svg panel. You can create a polygon in the SVG based on the user clicks.
If you use the d3.event mouse locations (mouseX and mouseY, I think), you can get click positions relative to the SVG element, and then use those as vertex locations on a polygon. Checking for click proximity to the original point will allow you to decide when to close the polygon.
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.