How do I use inches with snap svg? - svg

I have just started playing around with snap and have been trying to work in inches instead of pixels, is this possible? If so how do I go about using inches? It seems that some functions accept inches and some don't?
// This works
var s = Snap("1.5in", "1.5in");
// This works
s.rect("0in", "0in", "1.5in", "1.5in").attr({
stroke: "#f00",
fill: "transparent"
});
// This works
s.path("M0,144L144,0").attr({
stroke: "#f00"
});
// This fails (Error: Invalid value for <path> attribute d="M0in,0inL1.5in,1.5in")
s.path("M0in,0inL1.5in,1.5in").attr({
stroke: "#f00"
});

As you've discovered, certain SVG values only accept raw numbers (interpretted as SVG user coordinates), not lengths with units. The path "d" attribute is one of those.
That is not a snap.svg issue, that's how the underlying SVG elements work.
The easiest approach would be to create for yourself a conversion factor. The initial value for SVG user coordinates is that 1 user unit equals one CSS "px" (pixel) unit. By CSS standards, a "px" unit is exactly equal to 1/96th of an "in" (inch) unit. If you scale your SVG with transforms or a viewBox attribute, all the length units scale accordingly, so the ratio remains constant.
Therefore, to create a path equivalent to "M0in,0inL1.5in,1.5in", you would multiply each number by 96 and then concatenate the string together:
s.path("M0,0L" + 1.5*96 + "," + 1.5*96).attr({
stroke: "#f00"
});
If you've got a long and complex path to create, it's worth mentioning that Array.join("") is more efficient than the + operator for joining a long list of strings and numbers into one string.
However, be aware that the CSS/SVG "in" unit won't always display on screen as exactly an inch. If you're printing the document, it should be pretty accurate. But of course, if you're scaling or transforming your SVG, it might not be anywhere close to an inch.

Related

Proper way to calculate the height and width of a SVG text

I need to calculate the height and width of a svg text element. Is there a way to do so without actually adding it to the DOM of my page? I need only the measures not the actual element.
I am using neither d3 nor Raphael, but only plain JavaScript. (Maybe I should use one of the former for my calculations?)
What I am after is just a function like imagettfbbox in PHP, but in plain JavaScript. Is there such a thing? Or is it easy to write?
Since I am not actually using the text elements it seems strange to me to add them and hide them (I also have read somewhere that Firefox has problems with calculating the bbox of hidden elements, Calculating vertical height of a SVG text). But maybe this is the only way to go? Will I have to work with opacity in that case? Do I destroy the element somehow afterwards?
Maybe there is no good way to achieve exactly what I was after. However, giving up the "without actually adding it to the DOM of my page" part, the following function seems to achieve my goal.
function bboxText( svgDocument, string ) {
var data = svgDocument.createTextNode( string );
var svgElement = svgDocument.createElementNS( svgns, "text" );
svgElement.appendChild(data);
svgDocument.documentElement.appendChild( svgElement );
var bbox = svgElement.getBBox();
svgElement.parentNode.removeChild(svgElement);
return bbox;
}
Edit:
A note on the calculation of the height: the height of the bbox returned, i.e. bbox.height, is always the full height of the glyph, i.e. a will have the same height as A. And I could not find a way to calculate them more exactly.
However, one can calculate the height of uppercase accented characters, e.g. Ä. This will be just the negative of the y coordinate of the bbox, i.e. -bbox.y.
Using this one can calculate, for example, some coordinates for vertical alignment. For example to emulate the dominantBaseline attribute set to text-before-edge, text-after-edge, and central.
text-before-edge: dy = -bbox.y
text-after-edge: dy = -bbox.height -bbox.y
central: dy = -bbox.y -bbox.height/2
Where dy is the vertical translation. This can be used to get around limitations of some applications that do not support these alignments set by attributes.
I have encountered a similar problem in VB.Net !
I written a VB.Net program that generate a SVG file and that needs to compute width of text to compute next vertical bar position as you can see in following image
The B line vertical bar is positionned in computing max text width of A line elements.
To do that in Console VB.Net application, I execute following lines of code
Private Sub WriteTextInfo(sInfo As String)
If sInfo <> "" Then
'display Text in SVG file
sw.WriteLine("<text x ='" & (xPos + 10) & "' y='" & yPos & "' fill='black' font-family='Arial' font-size='20'>" & sInfo & "</text>")
'Create Font object as defined in <text> SVG tag
Dim font As New Font("Arial", 20.0F)
'Compute width of text in pixels
Dim xSize = TextRenderer.MeasureText(sInfo, font)
'Divide pixels witdh by a factor 1.4 to obtain SVG width !
Dim iWidth As Decimal = Math.Truncate(CDec(xSize.Width) / 1.4)
'If new vertical position is greater than old vertical position
If xPos + iWidth > xPosMax Then
xPosMax = xPos + iWidth
End If
End If
End Sub
In resume, I compute text's width in pixels using TextRenderer.MeasureText() function and I divide this number by 1.4 to obtain SVG width.
1.4 value is obtained by experiment relatively to my case !
To use TextRendered and Font objects, I have added reference to
System.Drawing
System.Windows.Forms
As you can see, I don't use any DOM method to compute text's width because I compute it in VB.Net program.

How do you get the rotation value of an svg object with snap.svg?

So, I'm using snap.svg and I'd like to dynamically rotate an object over time. Something like this:
function rotateObject()
{
myObject.rotation += value;
}
The problem is I don't know how to access the rotation values for my display objects (or if they even exist!) So given something simple, let's say a circle declared like this:
snap = Snap(800,600);
circle = snap.circle(400,300,50);
I know I can access the x and y values like so:
circle.attr("cx");
circle.attr("cy");
What I need help with is:
Is there a rotation property of some sort that I can use to rotate this object?
If not, how do I rotate an object with snap.svg?
Better rotate objects using Snap.Matrix()
The way suggested by Ian, works for me perfectly while I used Chrome version < 36.0
When I updated Chrome to 36.0.1985.125 I saw bug with text rotation.
So, the soulution was using
var matrix = new Snap.Matrix();
matrix.rotate(-90, x, y);
Paper.text(x, y, 'Text').attr({
fontWeight: 'bold',
fill: '#434343',
transform: matrix
});
instead of
Paper.text(x, y, 'Text').attr({
fontWeight: 'bold',
fill: '#434343',
transform: 'r-90'
});
Maybe it will be useful for somebody.
Ideally you will control the rotation yourself, (rather than figuring it out from the attributes which is possible, but fiddlier). Animation can be easier, depending on what you need. Here is an example showing some basic animation with a rect (as circle rotation is just itself if around the centre)...
s = Snap(400, 620);
var myRect = s.rect(100, 100, 100, 200).attr({
fill : 'white',
stroke : 'black'
});
var myRotate = 45;
// if you wanted to rotate manually using your own adjust variable then this
// myRect.transform("r" + myRotate);
// but simpler in most cases to use the animate method
myRect.animate( { transform: "r" + myRotate + ",150,200" }, 1000 ); //rotate around centre of 150,200
Fiddle at http://jsfiddle.net/XG7ks/6/
Really it would probably be best to get a basic grounding on transformations with SVG (and translate, rotate, scale) just for it to make a bit more sense. You can 'see' the resultant transform with myRect.attr('transform') but I would probably leave that just at first.

transition a circle into a line by unrolling it with SVG and d3

For a project we are trying to make a circle into a line (and back again) while it is rotating along a linear path, much like a tire rotates and translates when rolling on a road, or a curled fore finger is extended and recurled into the palm.
In this Fiddle, I have a static SVG (the top circle) that rotates along the linear black path (which is above the circle, to mimic a finger extending) that is defined in the HTML.
I also use d3 to generate a "circle" that is made up of connected points (and can unfurl if you click on/in the circle thanks to #ChrisJamesC here ), and is translated and rotated
in the function moveAlongLine when you click on the purple Line:
function moveAlongLine() {
circle.data([lineData])
.attr("transform", "translate(78.5,0) rotate(-90, 257.08 70) ")
.duration(1000)
circle.on("click", transitionToCircle)
}
The first problem is that the .duration(1000) is not recognized and throws a Uncaught TypeError: Object [object Array] has no method 'duration' in the console, so there is a difference between the static definition of dur in SVG and dynamically setting it in JS/D3, but this is minor.
The other is should the transform attributes be abstracted from one another like in the static circle? in the static circle, the translate is one animation, and the rotation is another, they just have the same star and duration, so they animate together. How would you apply both in d3?
The challenge that I can not get, is how to let it unroll upwards(and also re-roll back), with the static point being the top center of the circle also being the same as the leftmost point on the line.
these seem better:
I should try to get the unfurl animation to occur while also rotating? This seems like it would need to be stepwise/sequential based...
Or Consider an octogon (defined as a path), and if it were to rotate 7 of the sides, then 6, then 5.... Do this for a rather large number of points on a polyhedron? (the circle only needs to be around 50 or so pixels, so 100 points would be more than enough) This is the middle example in the fiddle. Maybe doing this programmatically?
Or This makes me think of a different way: (in the case of the octogon), I could have 8 line paths (with no Z, just an additional closing point), and transition between them? Like this fiddle
Or anything todo with keyframes? I have made an animation in Synfig, but am unsure ho get it to SVG. The synfig file is at http://specialorange.org/filedrop/unroll.sifz if you can convert to SVG, but the xsl file here doesn't correctly convert it for me using xsltproc.
this seems really complicated but potential:
Define a path (likely a bézier curve with the same number of reference points) that the points follow, and have the reference points dynamically translate as well... see this for an concept example
this seems complicated and clunky:
Make a real circle roll by, with a growing mask in front of it, all while a line grows in length
A couple of notes:
The number of points in the d3 circle can be adjusted in the JS, it is currently set low so that you can see a bit of a point in the rendering to verify the rotation has occurred (much like the gradient is in the top circle).
this is to help students learn what is conserved between a number line and a circle, specifically to help learn fractions. For concept application, take a look at compthink.cs.vt.edu:3000 to see our prototype, and this will help with switching representations, to help you get a better idea...
I ended up using the same function that generates the circle as in the question, and did a bit of thinking, and it seemed like I wanted an animation that looked like a finger unrolling like this fiddle. This lead me to the math and idea needed to make it happen in this fiddle.
The answer is an array of arrays, with each nested array being a line in the different state, and then animate by interpolating between the points.
var circleStates = [];
for (i=0; i<totalPoints; i++){
//circle portion
var circleState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*i + radius * Math.sin(2 * j * Math.PI / (numberOfPoints - 1));
var y = margintop + radius - radius * Math.cos(2 * j * Math.PI / (numberOfPoints - 1));
return { x: x, y: y};
})
circleState.splice(numberOfPoints-i);
//line portion
var lineState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*j;
var y = margintop;
return { x: x, y: y};
})
lineState.splice(i);
//together
var individualState = lineState.concat(circleState);
circleStates.push(individualState);
}
and the animation(s)
function all() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
function reverse() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[numberOfPoints-1-i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
(Note: This should be in comments but not enough spacing)
Circle Animation
Try the radial wipe from SO. Need to tweak it so angle starts at 180 and ends back at same place (line#4-6,19) and move along the X-axis (line#11) on each interation. Change the <path... attribute to suit your taste.
Line Animation Grow a line from single point to the length (perimeter) of the circle.
Sync both animation so that it appears good on all browsers (major headache!).

Line width in raphaeljs

Is it real to make line with 1px weight in SVG or raphaeljs?
The follow code
var p = Paper.path("M1 1 L50 1");
p.attr("stroke", "#D7D7D7");
p.attr("stroke-width", "1");
p.attr("opacity", 0.5);
draw line which looks like 2px or 3px. Any alternative?
When SVG lines lie at their apparently correct coordinates they actually lie inbetween pixels, so when you state M1 1 L50 1 it paints half a pixel on the top and the other half in the bottom of the pixel, making it look like a thick, semitransparent line.
To solve this problem you need to either paint at half pixels, or translate your elements half a pixel, ie. element.translate(0.5, 0.5)
You can see the blurry and sharp lines here:
http://jsfiddle.net/k8AKy/
You should also use the Paper.renderfix() function since you do not know which browser your users will be using.
From the documentation
Fixes the issue of Firefox and IE9 regarding subpixel rendering. If
paper is dependant on other elements after reflow it could shift half
pixel which cause for lines to lost their crispness. This method fixes
the issue.
This links take you point what's going wrong with integer coordinates and why +0.5 was fixed edge blurring (with nice pictures!!):
http://diveintohtml5.info/canvas.html#pixel-madness
http://kilianvalkhof.com/2010/design/the-problem-with-svg-and-canvas/
Compare:
with +0.5:
You can avoid +0.5 by:
SVG_Area.setAttribute("viewBox", "0.5 0.5 " + width + " " + height);
or by wrapper:
function fiXY(x) { return parseInt(x) + 0.5; }
var rect = document.createElementNS(SVGobj.svgNS, "rect");
rect.setAttribute("x", fiXY(x));
rect.setAttribute("y", fiXY(y));
or by:
SVG_Area.setAttribute("shape-rendering", "crispEdges");
which effect on all shapes in you SVG image....

Raphael 2 rotate and translate

Here is my script:
<script>
Raphael.fn.polyline = function(pointString) {
return this.path("M" + pointString);
};
window.onload = function() {
var paper = Raphael("holder", 500, 500);
paper.circle(100, 175, 70).attr({"stroke-width":10, "stroke":"red"});
var a = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(25, 100, 175);
var b = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(45, 100, 175);
var c = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(65, 100, 175);
var group = paper.set();
group.push(a, b, c);
group.translate(60);
};
</script>
When I use raphael-1.5.2, the result is:
When I use raphael 2.0, the result is:
In 1.5.2 it uses the rotate transformation to rotate the objects around the circle and in 2.0 it uses the matrix transformation. I assume the matrix transformation transforms the coordinate system for that object, so when you later translate the object in the xy direction it translates it in the xy that is relative for that object.
I need to be able to add green objects around the edge of the red circle and then be able to drag and move everything in the same direction. Am I stuck using 1.5.2 or am I just missing how translate has changed in 2.0?
Use an absolute transform instead of translate. Say you want to move of 100 in x and 50 in y do this:
Element.transform("...T100,50");
Make sure you use a capital T and you'll get an absolute translation. Here's what the documentation says about it:
There are also alternative “absolute” translation, rotation and scale: T, R and S. They will not take previous transformation into account. For example, ...T100,0 will always move element 100 px horisontally, while ...t100,0 could move it vertically if there is r90 before. Just compare results of r90t100,0 and r90T100,0.
See documentation
Regarding translate, according to the documentation in Raphael JS 2.0 translate does this:
Adds translation by given amount to the list of transformations of the element.
See documentation
So what happens is it appends a relative transformation based on what was already applied to the object (it basically does "...t100,50").
I suspect that with 1 your transform correctly treats the set as one object but with 2 the little greeny things rotate indepently
Two is a complete redesign so little disconnects like this will occur
Use getBBox and find the centre of your set, then use 1 rotate command on the whole set specifying cx cy derived from getBBox

Resources