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 );
}
};
Related
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);
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 want to add zoom feature my app . I use Kinetic js and somewhere I found solutions for this feature but I can't apply these solution for some reason . I tried to adapt the solutions but unsuccesful . I have many Kinetic.Layer , some of them will scale when zooming apply. my challenge is that : zoom will happen on mouse position . solution that I found gives me : layer.setPosition() after scaling . As I mentioned before , I must not use "layer.setPosition" I will do this as using stage.setPosition() but I couldn't calculate new x and y of position 100% accurately. Could anyone suggest me any solution way ?
What you really want to do when zooming is to set the scale.
You can set the scale for any layer, node, or the entire stage. Just do:
layer1.setScale(2,2); // this doubles the layer size from the original
This doesn't affect any other layer, so your overlay will stay in place.
In addition, you should also do:
layer1.setPosition(x,y); // this will move the layer to the fixed point you want.
All together you could do:
function zoom(){
var position = stage.getUserPosition();
layer1.setScale(2,2);
layer1.setPosition(position.x - layer2.getX(), position.y - layer2.getY()); //move the layer by an offset based on the second layer. This isn't exactly correct so it's something you have to experiment with.
}
Check out this: http://jsfiddle.net/TFU7Z/1/ Maybe is what you are looking for, I did not quite understand the question.
var zoom = function(e) {
var zoomAmount = 1;
layer.setScale(layer.getScale().x+zoomAmount)
layer.draw();
}
document.addEventListener("click", zoom, false)
Just click anywhere to zoom. You can attach the "click" event listener to whatever part of the stage / document you want.
These answers seems not to work awith the KineticJS 5.1.0. These do not work mainly for the signature change of the scale function:
stage.setScale(newscale); --> stage.setScale({x:newscale,y:newscale});
However, the following solution seems to work with the KineticJS 5.1.0:
JSFiddle: http://jsfiddle.net/rpaul/ckwu7u86/3/
I'm using Raphael.js to draw images to canvas. I need to be able to select certain image elements (this I can do) and make them look like they are selected (this is the problem).
Before Raphael.js I used regular Html5 canvas and simple rectangles. It was simple to delete selected rectangle and draw a new one with a different color to that same place.
But now that I'm using images, it's a different story. The image I'm using is here. It's a small gif.
So the question(s):
Is there a simple way to change color of a Raphael.js image-element programmatically?
Can I make an image-element to blink by changing its opacity?
Only requirement is that the selected element must be movable.
Code for drawing image when user clicks on canvas:
var NodeImage = RCanvas.image("../vci3/images/valaisin.gif", position.x, position.y, 30, 30);
NodeImage.toFront();
RSet.push(NodeImage);
NodeImage.node.id = 'lamp';
NodeImage.node.name = name;
NodeImage.click(function() {
console.log("Clicked on node " + NodeImage.node.name);
// Here should be the code that blinks or changes color or does something else
});
Is this completely bad idea? Is there a better way to achieve my goal?
i would suggest granting the image with an opacity of some level, and assign a value of 1 to it upon click:
NodeImage.attr('opacity', 0.6);
// ...
NodeImage.click(function() {
this.attr('opacity', 1);
});
of course, you will probably want to manage the shape's selected state, to switch the selected style off later on. in fact, you'll want to manage all selectable shapes in the same manner, so let's do that:
// keep all selectable shapes in a group to easily manage them
var selectableShapesArray = [NodeImage, otherNodeImage, anotherSelectableShape];
// define the behavior for shape click event
var clickHandler = function() {
for (var i in selectableShapesArray) {
var image = selectableShapesArray[i];
if (image.selected) {
image.attr('opacity', .6);
image.selected = false;
break;
}
}
this.attr('opacity', 1);
this.selected = true;
}
// attach this behavior as a click handler to each shape
for (var i in selectableShapesArray) {
var shape = selectableShapesArray[i];
shape.click(clickHandler);
}
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.