RaphaelJS applying a custom transform - svg

I am having trouble with applying a transformation to each path in my SVG. I know the exact transform I need to add as I am using a map from wikimedia and have tested it by adding it using firebug/chromebug.
I need to add:
transform="translate(0,239) scale(0.016963,-0.016963)"
This is an example of what I have:
http://jsfiddle.net/m7t4X/1/
If you inspect it, each path looks like this:
<path fill="#ff0000" stroke="#ccc6ae" d="M16760,5958C16752,5945,16745,5920,16743,5901C16740,5859,16734,5853,16710,5868C16694,5878,16690,5878,16690,5866C16690,5858,16696,5849,16703,5846C16709,5843,16705,5843,16693,5846C16670,5851,16666,5845,16669,5808C16670,5799,16666,5789,16660,5785C16655,5782,16650,5770,16650,5760C16650,5747,16645,5743,16635,5746C16626,5750,16620,5746,16620,5737C16620,5717,16549,5667,16495,5649C16473,5642,16451,5632,16446,5628C16440,5624,16426,5620,16413,5620C16395,5620,16390,5615,16390,5593C16390,5577,16381,5552,16371,5536C16351,5506,16351,5482,16371,5367C16383,5300,16383,5298,16354,5242C16338,5211,16321,5182,16317,5179C16282,5154,16269,5063,16294,5016C16302,5000,16306,4972,16303,4946C16300,4920,16302,4900,16309,4896C16315,4892,16320,4881,16320,4871C16320,4849,16350,4820,16373,4820C16382,4820,16390,4816,16390,4810C16390,4795,16442,4799,16464,4815C16475,4823,16496,4830,16511,4830C16541,4830,16580,4870,16580,4899C16580,4908,16586,4924,16594,4935C16601,4946,16613,4980,16620,5010C16628,5040,16642,5081,16651,5101C16661,5121,16675,5159,16681,5186C16688,5213,16710,5278,16731,5332C16752,5385,16772,5446,16775,5467C16787,5539,16790,5550,16803,5547C16815,5545,16816,5554,16804,5630L16797,5675L16817,5651L16837,5626L16854,5658C16866,5682,16868,5695,16860,5709C16855,5720,16850,5749,16850,5773C16850,5797,16845,5832,16839,5851C16825,5895,16786,5979,16780,5980C16777,5980,16768,5970,16760,5958Z" stroke-opacity="1" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); stroke-opacity: 1;"></path>
If I add it manually via firebug etc to change the start to look like this:
<path transform="translate(0,239) scale(0.016963,-0.016963)" fill="#ff0000" stroke="#ccc6ae" d="
then it works perfectly. I just cannot replicate this.
I have tried various ways of adding the transform, but none have worked fully.
From what I gather, RaphaelJS is backwards compatible so all of the solutions I have found (which haven't fully worked anyway) wouldn't have worked in any older IEs for instance.
The first thing I tried was to change:
paper.setViewBox(0, 0, w, h, true);
to:
paper.setViewBox(true);
However this did not work with the shapes I am using (http://upload.wikimedia.org/wikipedia/commons/9/95/Continents.svg)
This is where I got the transform amounts from (can be seen if you view source).
Please advise on where I'm going wrong, and how I could go about adding this.
Many thanks!
---------------------------------EDIT -------------------------------------
After applying the fix below, I have found it appears to be displaying the SVG rotated 180degrees and flipped horizontally. I don't quite understand why either.
This is how it is meant to look:
And this is how it comes out:
(also noticed I'm missing south america despite it being referenced)
This is a new fiddle of how it currently looks:
http://jsfiddle.net/wrayvon/m7t4X/3/
Thanks again

The transform should work, but I think you just need to set a bigger width/height for the viewBox. These should be for the co-ordinate space, not for pixels on screen.
var w = 15000;
var h = 25000;
paper.setViewBox(10000, 0, w, h, true);
Should do it. You can also transform it if you want, by adding something like the following to the element when created.
.transform('t2000,1000')
edit: As you have changed the SVG... If there are transforms in the original, you will probably need to apply the same transforms to the elements...so a line like this...
var c = paper.path(worldmap.shapes[country]).attr({ stroke: "#ccc6ae", fill: "#ff0000", "stroke-opacity": 1 }).transform('s1,-1,13000,10000');
I'm just guessing the centre point to scale from there (13,000,10,000 its probably a bit more), you will need to figure that separately.
Or you could match the scale as per the original SVG, and then you probably won't need to change the viewBox (apart from matching the original).
Original jsfiddle
Updated jsfiddle

I have had success with the exact same problem. Your question is the clearest articulation of it.
As a bit of background, I found an SVG map of the US and all of its counties. I thought, "great, I'll just copy these paths into Raphael, and Bob's your uncle". So, I did, but it appeared exactly upside down after the import. I considered applying some math to the paths, but decided against it. Instead I investigated the transform() option too.
In the transform method, there are a few options. One of the options is to use the matrix ("m"). The matrix method has 6 options. - see here
for more details. But the matrix setting for the 'as-the-path' dictates is this:
.transform("m1,0,0,1,0,0");
If you make the 4th place negative, it will invert your image.
.transform("m1,0,0,-1,0,0");
HOWEVER...it has to choose a location for the axis on which to flip. That, as far as I can tell, is the top edge of the the container div. So your image is now above you. Use the viewbox to find it and zoom as you desire:
R.setViewBox(x,y,x-size,y-size);//The Y (second param) will need to be negative.
Here's my example to find my shape: (make your 200's into 1000's to more easily locate you image, then play with the numbers to center, and enlarge)
R.setViewBox(0,-575,200,200);
The downside using the transform is the overhead of drawing, then relocating the images. In my case, I am less concerned about this as speed doesn't seem to be affected, which may be due to the relatively small number of paths involved.

Related

SVGPanZoom discards original viewBox

I am using SVGPanZoom to manage the zooming of an SVG image in my hybrid Android (for all intents and purposes the same behavior as in Chrome) app. While zooming works well I have found a strange issue. My original inline SVG element goes like this
<svg id='puzzle' viewBox='0 0 1600 770' preserveAspectRatio='none'
width='100vw' height='85.5vh' fill-rule='evenodd' clip-rule='evenodd'
stroke-linejoin='round' stroke-miterlimit='1.414'
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://
www.w3.org/1999/xlink'>
Initially this SVG element is empty and gets populated programmatically from JavaScript at run time after which I initiate SVGPanZoom as follows
var panZoom = svgPanZoom('#puzzle',
{panEnabled:false,controlIconsEnabled:false,
zoomEnabled:true,dblClickZoomEnabled:true,onZoom:postZoom});
panZoom.refreshRate = 10;
panZoom.zoomScaleSensitivity = 0.02;
The problem I have run into is this - I want my SVG image to fill the available area, 100vw x 85.5vhcompletely to do which I instruct it via the preserveAspectRatio="none"attribute above along with the viewBox="0 0 1600 770" attribute. I have found that this works - so long as I don't use SVGPanZoom. As soon as I initiate panZoom thezoomBox`attribute gets stripped out and I end up with an image that does not quite behave in terms of its default stretching/filling behavior.
SVGPanZoom is widely used so I assume that this behavior is down to me not quite setting it up properly. Dipping into the code I have found SVGPanZoom creates a cacheViewBoxand then proceeds to remove the original zoomBox attribute.
Which is fine if after that zooming works and the original behavior of the application does not change which is not what I find. What am I doing wrong here?
I've also run into this issue recently. From my research, this is just how the library works. I chose to live with this limitation for now but I found a couple other libraries that may work the way you intend (I haven't tried them yet):
jquery.panzoom is a jquery library that provides this functionality and also has some nice features. I know many people try to avoid jquery but it's pretty small and may do what you want. It handles SVG but I don't know what it does with the viewBox attribute.
react-svg-pan-zoom is a react component which may be useful if you are working in react.
I've also tried the PanZoom library but this also suffers the same viewBox limitation.
A note for anyone running into this thread. In the end I abandoned SVGPanZoom and decided to eschew the route of using any pan/zoom library at all. At the same time I decided to completely stop using the SVG viewBox and handle all zooming/panning entirely on my own through SVG transforms. The core steps involved
Wrap the entire SVG contents in a group to make it easier to manage the transform. I use the id attribute gOuter for this group
Set an initial scale for the SVG to occupy the desired client rectangle. In my case I had an original viewBox of 0 0 1600 770 intended to occupy 100% of screen width and 85% of screen height. So my scaling was scaleX = 1600/window.innerWidth and scaleY = 770/)0.85*window.innerHeight).
Apply this initial transform to the wrapping outer group, gOuter.setAttribute('transform','0 0 scaleX,scaleY)
Now in order to zoom to a an object whose virtual top left hand coordinates in the original viewBox were Ox,Oy you would use the transform
gOuter.setAttribute('transform',
scale(scaleX,scaleY) translate(-Ox,-Oy) scale(2*scaleX,2*scaleY) translate(Ox,Oy))
to zoom in by a factor of x 2. The important things to understand here
In SVG transformations are applied right to left.
Here we are translating the zoom point to the top l.h.s. scaling and then translating it back to its original location.
The problem is that we also need to allow for the original level of zoom through the initial scaling so we tag that on as one last transform
This leaves you in complete control of the zooming process and as a fringe benefit the operation becomes considerably more smooth than when using a pan/zoom library.

fabricjs: Issue with multiple paths of SVG having the exact same gradient

For my web app, I am creating SVG elements in Illustrator and then using them in a library of elements that users can add to the fabric canvas.
Some elements are simple but some complex with multiple compound paths etc.
I have came across an unusual issue where if I create a path with a gradient fill, and then copy that path, save the SVG and add it onto the canvas, only the first path would have the gradient and the rest would be flat colors.
Here is a screenshot of what I mean...
After experimenting and trying different things, I finally discovered that this is happening because the paths have the exact same gradient properties.
So if the gradient slider (color stops, opacity, location etc.) of two or more paths have the exact same properties in Illustrator, then the issue occurs.
So the workaround is to alter something like the location (for example) to be 99.9% instead of 100% on the copied path, then the issue goes away. However, this will quickly become a tedious and annoying way to fix this. Basically, each path with a gradient, needs to have a unique gradient set up and cannot be identical to another paths gradient properties.
Here are more screenshots to better explain...
After making this change...
The first and second path's gradient's location are different.
The first, third, fourth and fifth paths have exact same gradient.
This is what it looks like when I add it to the canvas now...
Here is the code I am using to add the SVG to the canvas...
fabric.loadSVGFromURL(image, function(objects, options) {
var oImg = fabric.util.groupSVGElements(objects, options);
oImg.perPixelTargetFind = true;
oImg.targetFindTolerance = 4;
canvas.add(oImg);
canvas.renderAll();
});
Can anyone tell me why this is happening and if there is a way to fix this with code rather than Illustrator? I have hundreds of elements to create that will have many paths with the same gradients. I know it will be a real pain to have to worry about paths not having the exact same gradient.
http://jsfiddle.net/oc70xjsq/
Link to the SVG

Combining Multiple SVG Transformations

I'm new to Snap.svg and SVG and experimenting with transformations (an illustraiting plunk can be found here). Basically I'm trying to move, scale and rotate a shape according to its configuration. This is what I've found out so far:
rotating around a point is possible with rotate(angle, x, y)
there is no direct transform method to scale around a point but it can be done as described in "SVG Essentials"
However combining these transforms doesn't give me the expected result - my expected calculated center of the shape differs from the rendered one. Can anyone give me some pointers on how to correctly put these transforms together?
Regards,
Andi
To combine transforms, I would use Snaps own transformString format. I would first have a read of my previous answer on SO here, this is slightly different so posting a slightly different example and answer.
Whilst Snap can use SVGs transform strings (rotate() scale() transform()). They don't by default centre around itself for example, whereas Snaps (and Raphaels) do. This makes it a bit easier. For more complex situations, one may need to look into Matrix methods, but I think the following should be ok...
Snaps transformString uses string t (transform), s (scale), r (rotate), and you can add them repeatedly if wanted.
Here is an example of both methods, to highlight the difference.
jsfiddle here
s = Snap(400, 620);
var r1 = s.rect(0, 0, 100, 100).attr({
fill : 'blue',
stroke : 'black',
opacity: 0.5
});
var r2 = r1.clone().attr({ fill: "red" });
r1.transform('t100,100s2,2r45'); //typical Snap way, rotation/scale around centre
r2.transform('translate(100,100) scale(2,2,) rotate(45)'); //SVG way
The getBBox() method should be pretty reliable as far as I know (maybe post up a separate question on SO if you find an example where it is wrong)

How do I rotate or scale (transform) an SVG path relative to its center point?

I'm trying to rotate and scale shapes within an SVG around their center point. I've looked into several libraries, including Jquery, Greensock, D3, RaphaelJS, but I haven't been able to find any that provide a straightforward way to accomplish this. Each animates the shape from the origin point (which I understand is the default). I want to be able to spin a shape around its center point or scale it up or down from the center point.
Here are a couple examples using Greensock and D3 that illustrate the default behavior: http://jsbin.com/AHEXiPa/1/edit?html,js,output
Each of these examples bounce in and out from the top left as opposed to remaining stationary and expanding from the center of the triangle out in all directions.
Can one of the libraries I mentioned accomplish this, or is there another library or method I should consider?
Ideally, I need to be able to apply the animation/transform to an existing object in the DOM. D3 is good at this for instance, but Raphael seems to require converting an SVG to Raphael first prior to injecting it into the DOM.
Really its a case of pick the library that suits your needs, and then you will figure a way. As BigBadaboom says, if you do a search, there are lots of solutions.
To try and combine your questions, as sometimes the tricky bit is using an existing DOM object, I've included an example in Snap.svg. You can often do something similar in most libraries.
jsfiddle here Fiddle using your existing html.
s = Snap("#mySVGContainer1"); // create a canvas from existing svg
var triangle1 = s.select("#myShape1").transform("r90"); //select&transform existing object
p = Snap("#mySVGContainer2");
var triangle2 = p.select("#myShape2");
var bbox = triangle2.getBBox(); //bounding box, centre cx/cy
//rotate and scale with transform string (raphael/snap format)
triangle2.animate({ transform: "r180," + bbox.cx + ',' + bbox.cy + "s3,3," + bbox.cx + "," + bbox.cy }, 2000);
For rotations, as #Ian points out, you can specify the center of rotation. For other transformations, changes are defined relative to the path's (0,0) point.
The easiest way to get transformations to work relative to the path's center is to either:
Define the path so that it is centered around the (0,0) point; or
Wrap the path in a <g> element, and then translate it so it is centered on the (0,0) point of the <g> element's coordinate system.
Then, you can apply rotations, scales and transforms (on the <g> element, if using) and they will all be nicely centred.
The trickiest part is figuring out the "center" of an arbitrary shape. #Ian's approach of using the center of the bounding box will usually give decent results. If your shape is a polygon there are d3 functions you could use.
Example showing a shape moving with the mouse, rotating and changing scale, all centered around the center of the bounding box:
http://fiddle.jshell.net/LgfE3/
Edit: simplier jsfiddle
I've been looking for a long time, and will settle for the following.
1. Design your svg shape at coordinate x:0,y:0.
2. Identify by hand the center of rotation, by example, center = [ x:50,y:100].
3. Build a spinIt() function such :
function spinIt() {
needle.transition()
.duration(2000)
.attrTween("transform", tween);
function tween() {
return d3.interpolateString("rotate(-180, 50, 100)", "rotate(90, 50, 100)");
}
}
4. Use it on a triger:
svg.on("click", spinIt);
http://jsfiddle.net/SHF2M/79/

How to set the rotation center of an MKAnnotationView

I've got a problem with my MKAnnotationViews when MKUserTrackingModeFollowWithHeading is enabled on the MKMapView.
I positioned my images using the centerOffset property of the MKAnnotationView. Specifying the coordinates of the pin's tip relative to the coordinate system at the center of the image is somewhat counter-intutive, but I came up with the following formula:
annotationView.centerOffset = CGPointMake(imageWidth/2.0 - tipXCoordinate, imageHeight/2.0 - tipYCordinate);
This works fine for zooming the map in and out. The tips of the pins keep their relative position on the map.
However, when I enable MKUserTrackingModeFollowWithHeading, it won't work anymore. The Pins rotate around the center of the image, instead of the tip. So when the map rotates, the tips do no point to the locations they are supposed to annotate.
I've played around a bit with the frameand centerproperties of the MKAnnotationView, but I feel, they are having no effect on the alignement of the pins whatsoever.
Interestingly, the MKPinAnnotationView does not seem to use centerOffset at all, but a shifted frame instead. However, I was unable to reproduce this. Changing the frame of my custom view did not move it at all.
Thanks for any insights you can provide :-)
Solution:
Don't use centerOffset! Use annotationView.layer.anchorPoint instead. The coordinate system of achor point is much nicer, too. Coordinates range from 0.0 (top/left) to 1.0 (bottom/right) of the image rectangle:
annotationView.layer.anchorPoint = CGPointMake(tipXCoordinate/imageWidth, tipYCordinate/imageHeight);
A friend asks me to let you know that you should "try this for instance":
self.layer.anchorPoint = CGPointMake (0.5f, 1.0f);

Resources