First of all, yes, I have searched and I have seen the many other answers available. But they didn't work for me.
I would like to be able to center the text in my SVG.
Since I have to be able to put it left, center or right, (both horizontally and vertically) I tried to be a little bit generic and compute x and y programmatically.
Here is an example : https://codesandbox.io/s/ll6ppwkyq7
You can see in the result that the text is not vertically centered.
The red box is the bounding box of the text.
The black box is the box in which I am supposed to be centered.
The relationship between x coordinate of an element and the coordinate of the right position of the text is not the same as of the y coordinate of an element and the bottom position. In fact it's inverted.
That's because vertically, the text is place at the bottom of an element and y coordinates go from top to bottom, while horizontally coordinates go in the same direction as the text.
This is easily seen when using GetBBbox() method, which you are using. For example, when adding a text element at coordinates 0 for x and y, you'll see that getBBbox will give you a x = 0 but a negative y.
console.log(document.getElementById('testbbox').getBBox());
<svg id="testbbox"><text x=0 y=0>test</text></svg>
So already, calculations to center your text will need to be different. What you have now is:
outerRect.x - ((outerRect.width - innerRect.width) / 2)
This works because you assume that inner rectangle starts from 0, which is not the case. Your bounding box is vertically in the negative. So complete formula should be something like:
outerRect.x - innerRect.x - ((outerRect.width - innerRect.width) / 2)
Which in your example would be:
x - bboxx + ( (width - boxWidth) / 2)
Where bbox is x of getBBox()
You could use baseline, but with dy on the tspan, it makes the calculations a bit more complex, not sure it's the good approach. Since anyway getBBox takes baseline into account in the calculation, it's not really necessary.
Also, if you look here, baseline for text should be dominant-baseline, not alignement-baseline: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline
See how it affects the bounding box, especially y coordinate:
console.log(document.getElementById('central').getBBox());
console.log(document.getElementById('top').getBBox());
<svg id="central">
<text y=10 dominant-baseline="central" >test</text>
</svg>
<svg id="top">
<text y=10 dominant-baseline="top" >test</text>
</svg>
See end result:
https://codesandbox.io/s/52wl31l8zn
Related
I'm making a diagramming library in Blazor which uses HTML nodes and SVG links. I am wondering how can I draw links between two nodes when they aren't always rectangular.
All the solutions I find are based on nodes that are rectangles/squares, where it's easy to draw a link on the borders (or even the center, but only works for direct links).
But what about nodes that have custom stuff in them that makes them non rectangular, for example a div with border-radius: 50%?
One possible solution is to draw the lines from/to the center of the elements, but that would only work with simple lines, curved lines would look weird.
In this example:
How does arrow position get calculated?
You need to have an container, width and height of the container, then inside the container find the x / y point of the element that you want to connect and draw a line to the next elements x / y point, the x/y points can be calculated using x,y,w,h of the element, for an example x:100 y:100 w:100 h:100 the center point sits at x:150, y:150 x = x + ( w / 2 ), y = y + ( h / 2 ).. using math just calculate the point of connection of the elements, the complexity of math for calculating the connection point is in the shape of the element, for each different shape you need a different calculation metod if not in center
I'm currently trying to figure out how SVG.js calculates the corrected bounding box x, y coordinates (top left corner) from an SVG text object.
My SVG object looks like the following:
<svg xmlns="http://www.w3.org/2000/svg" width="266" height="59" viewBox="0 0 266 59">
<text id="TText" data-name="TText" fill="#707070" font-size="50" font-family="Boogaloo"><tspan x="0" y="0">TText</tspan></text>
</svg>
If I add this code into SVG.js then the position is y-offset by 11 pixels (which I assume is the position when taking into consideration that SVG is based on the baseline). Can someone explain how the bounding box x and y coordinates are calculated. I tried to solve it by digging through the SVG.js repo, but couldn't solve it myself.
I would assume this is based on the font? If that's the case how does one extract that information out of a font file?
Here's my SVG.js code, that shows the corrected X, Y bounding box coordinates.
var draw = SVG().addTo('body');
var text = draw.text('TText');
text.font({
family: 'Boogaloo',
size: 50,
});
console.log(text.bbox());
The simple answer is: we use the browser api to get the bounding box (el.getBBox()). Our bbox() method is just a simple wrapper around that. In case of text we don't calculate any corrected bounding box. We have some magic involved when moving the text because text is normally moved by its baseline but we unified the api so that all shapes are moved by their upper left corner.
If you want to know how a browser calculates the bounding box of text, you can have a look at this answer: reproduce Bounding Box of text in Browsers
TL:DR the numbers you get are different from browser to browser. And ofc you need the font file
I'm animating the height of a drawRoundedRect instance, however, because it starts drawing from the upper left corner, it's animating from top to bottom, and I need it to start from the bottom.
Is it possible to flip my graphics instance (I tried by setting the scale to inverse, but this doesn't render anything, perhaps it only works on sprites), or to start drawing a rounded rectangle from the bottom?
EDIT:
Okay so I found out it's possible to animate my height going in to the other direction by just multiplying my interpolated value by -1:
graphics.drawRoundedRect(
x,
y,
barsWidth,
interpolatedHeight * -1,
10
);
However, now the radius isn't working anymore, it's just drawing square rectangles..
TIA!
I am not entirely sure that I did understand the question but if a got it right, all you need to do is to change the pivot of the rectangle you are animating. In your case, pivot.y should be equal to the rectangle's height rectangle.pivot.y = rectangle.height. After you change the pivot you also need to position it /the rectangle/ accordingly, so it could retain its visual position rectangle.y += rectangle.pivot.y
a simple demo https://jsfiddle.net/4L1od09n/23/
So I'm trying to get the top left x,y values for the green box to crop an image that has been rotated into a square.
The green square can move left and right on the dotted line, so I'm able to get the x value, I'm just having a hard time getting the Y especially when the blue rectangle is a different aspect ratio.
So I know how much the blue rectangle has be rotated and with width and height, also the width and height of the bounding box and same for the green square.
So I need the min and max Y at a certain X value with a rotation between -10 - 10 degrees?
I'm using html and js right now.
the blue rectangle is a scrollable div that's rotated inside the square div so you can see a preview of the crop.
diagram:
http://i.stack.imgur.com/FwXjZ.jpg
thanks
You can setup a line equation for the top and bottom side of the rotated rectangle: y1 = mx + b1, resp. y2 = mx + b2
m is the slope, which is the tangent of the angle between the line and the x-axis.
b is the intercept (the y-value at which the line intersects the y-axis; b = y(0)).
Look at this site for more information.
Transitions in combination with rotations have odd results.
Here is a fiddle with my problem: http://jsfiddle.net/emperorz/E3G3z/1/
Try clicking on each square to see the varying behaviour.
Please forgive the hacked code, but if I use transition with rotation (and x/y placement) then it loops about.
I have tried:
1) all in the transform (rotate then translate), and that seems mostly okay. A little wobbly.
2) just rotate in the transform, positioned using x/y attributes. Flies all over the place, but ends up at the correct spot. Very weird.
3) all in the transform (translate then rotate), flies away, and ends up in the (completely) wrong place.
Hmmm. Strange.
Is there a correct approach to rotating shapes with transitions?
Intuitively, it would be good if the second option worked.
Thanks
To rotate an SVG object on an arbitrary axis, you need two transformations: translate (to set the axis) and rotate. What you really want is to apply the translate fully first and then rotate the already moved element, but it appears that translate and rotate operate independently and simultaneously. This ends at the right place, but animating the translate is essentially moving the axis during rotation, creating the wobble. You can isolate the translate from the rotate by having them occur at separate places in the SVG element hierarchy. For example, take a look at the following:
<g class="outer">
<g class="rect-container">
<rect class="rotate-me" width=200 height=100 />
</g>
</g>
You can center the <rect> on (0,0) with translate (-100, -50). It will wobble if you apply your rotation to the <rect> element, but it will rotate cleanly if you rotate the g.rect-container element. If you want to reposition, scale, or otherwise transform the element further, do so on g.outer. That's it. You now have full control of your transforms.
Finding a <rect>'s center is easy, but finding the center of a <path>, <g>, etc. is much harder. Luckily, a simple solution is available in the .getBBox() method (code in CoffeeScript; see below for a JavaScript version*):
centerToOrigin = (el) ->
boundingBox = el.getBBox()
return {
x: -1 * Math.floor(boundingBox.width/2),
y: -1 * Math.floor(boundingBox.height/2)
}
You can now center your element/group by passing the non-wrapped element (using D3's .node() method)
group = d3.select("g.rotate-me")
center = centerToOrigin(group.node())
group.attr("transform", "translate(#{center.x}, #{center.y})")
For code that implements this on a both a single <rect> and <g> of of 2 rects with repositioning and scaling, see this fiddle.
*Javascript of the above code:
var center, centerToOrigin, group;
centerToOrigin = function(el) {
var boundingBox;
boundingBox = el.getBBox();
return {
x: -1 * Math.floor(boundingBox.width / 2),
y: -1 * Math.floor(boundingBox.height / 2)
};
};
group = d3.select("g.rotate-me");
center = centerToOrigin(group.node());
group.attr("transform", "translate(" + center.x + ", " + center.y + ")");
iirc translate is relative to 0,0 whereas rotate is around the center point of the object
As such, because your shapes are offset from 0,0 (e.g. 100,200, or 200,100) they end up migrating when translated. This can be seen by changing the offsets for Diamond3 to [50,50] - much smaller migration around the screen
The solution would be rebase the 0,0 point to the center of the diamond. There is a way to do this in D3 - but I can't remember what it is off the top of my head :(