SVG Coordinate translation - svg

Why doesn't this SVG inline code produce a box 60 by 40 with a dot in the middle?
I would like to plot items on a regular Cartesian coordinate map using SVG. The data I would have would be the size of the displayable map and center point on the Cartesian main map.
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="40" viewBox="0 0 60 40">
<g transform="translate(-470,480) scale(1,-1)">
<circle title="Center" cx="500" cy="500" r="3" fill="red"/>
</g>
</svg>

Your code is creating an 60 by 40 pixel SVG, then:
Draws a circle centred on (500, 500)
The scale transform moves the circle to (500, -500)
The translate transform moves it to (30, -20)
If you change the transform to transform="translate(-470,520) scale(1,-1)" you should get what I think you want.

I came up with this code snippet that will create a map of with Cartesian coordinates and plot them in an SVG window using those coordinates. Hope this will help somebody.
The function takes the center of the map as $x, $y and draws the map around that coordinate.
public static function xyMap( $x, $y, $width = 0, $height = 0, $show = array('X')) {
$minx = $x - ($width / 2);
$maxx = $x + ($width / 2);
if ($minx < 0) {
$minx = 0;
$maxx = $width;
} elseif ($maxx > Yii::app()->params['maxMapX']) {
$maxx = Yii::app()->params['maxMapX'];
$minx = Yii::app()->params['maxMapX'] - $width;
}
$miny = $y - ($height / 2);
$maxy = $y + ($height / 2);
if ($miny < 0) {
$miny = 0;
$maxy = $height;
} elseif ($maxy > Yii::app()->params['maxMapY']) {
$maxy = Yii::app()->params['maxMapY'];
$miny = Yii::app()->params['maxMapY'] - $height;
}
$x_xform = -1 * $minx;
$y_xform = $maxy;
$x_scale = 1;
$y_scale = -1;
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"$width\" height=\"$height\" viewBox=\"0 0 $width $height\">\n";
echo "<g transform=\"translate($x_xform, $y_xform) scale($x_scale,$y_scale)\">\n";
echo "<rect x=\"$minx\" y=\"$miny\" width=\"$width\" height=\"$height\" stroke=\"black\" stroke-width=\"2\" fill=\"white\" />\n";
echo "<circle title=\"Center\" cx=\"$x\" cy=\"$y\" r=\"3\" fill=\"red\"/>\n";
echo "</g>\n</svg>\n";
}

Related

SVG mouse position relative to transformed element

I have some SVG elements and I would like to add the ability to resize using mouse.
It works great when element doesn't have any transforms (matrix, rotate, scale etc).
If I remove transform (transform="matrix(1.1,1.1,1,0,0,0)") from the rect element - it works great.
My understanding is that I should use matrix.inverse() function and transform it so I get mouse coordinates in the element space, but everything I tried didn't work for me.
I need to correctly calculate the x, y difference between start drag point and current point, but it doesn't work.
var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
function moveGrip() {
var baseMatrix = rect.getCTM(),
x = rect.width.baseVal.value + rect.x.baseVal.value,
y = rect.height.baseVal.value + rect.y.baseVal.value;
grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}
grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);
var startDragPoint, startDragSize;
function onGripMouseDown(evt) {
startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
startDragPoint = {x: evt.clientX, y: evt.clientY};
}
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
rect.width.baseVal.value = startDragSize.w + evt.clientX - startDragPoint.x;
rect.height.baseVal.value = startDragSize.h + evt.clientY - startDragPoint.y;
moveGrip();
}
}
moveGrip();
<svg width="500" height="400">
<rect id="rect" x="20" y="20" width="200" height="100"
transform="matrix(1.1,1.1,1,0,0,0)"
style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
<g>
<circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
</g>
</svg>
Probably, you'd need to apply the inverse matrix to delta-x,y as follows;
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
var invMatrix = rect.getCTM().inverse(),
x = evt.clientX - startDragPoint.x,
y = evt.clientY - startDragPoint.y;
rect.width.baseVal.value = startDragSize.w + x * invMatrix.a + y * invMatrix.c;
rect.height.baseVal.value = startDragSize.h + x * invMatrix.b + y * invMatrix.d;
moveGrip();
}
}

How can I distribute points evenly along an oval?

I have an oval (as an svg)
I want to distribute n points along the oval:
evenly
inset the points by x percent
constrained to the lower f percent of the oval
How can I do this programmatically? I just need a list of coordinates as output.
SVG of an ellipse:
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 744.09448819 1052.3622047">
<g id="layer1">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" cy="489.51" cx="314.29" stroke-width=".38188px" fill="none"/>
</g>
</svg>
Calculating equidistant point on ellipse circumference is quite complex math problem. Moreover, parallel curve (inward) for an ellipse is not ellipse.
If your ellipse is not very squeezed (a/b ratio is in range 0.5..2), you can use simple approximation through equidistantly spaced t parameter of ellipse equaion. Otherwise distance variance will too high and you need more complex approach based on arc length/distance calculation (requires numerical integration).
[Edit]: I added some correction of t to make point distribution better. Idea taken from here,
Using parallel curve equation, we can calculate points in such way (Delphi code as reference):
var
i, a, b, cx, cy, x, y, k, N: Integer;
sq, t: Double;
begin
N := 30; // Number of points
a := 120; //semiaxes
b := 200;
cx := 300; //center
cy:= 300;
k := 30; //inward distance
Canvas.Ellipse(cx - a, cy - b, cx + a, cy + b);
for i := 0 to N - 1 do begin
t := 2 * Pi * i / N;
//empirically adopted coefficient 0.3
t := t + 0.3 * ArcTan((a-b)*tan(t)/(a + b * sqr(tan(t))));
sq := 1.0 / Hypot(a * sin(t), b * cos(t));
x := Round(cx + (a - b * k * sq) * Cos(t));
y := Round(cy + (b - a * k * sq) * Sin(t));
Canvas.Ellipse(x-2,y-2,x+3,y+3);
end;
This is how I would do it:
I've changed the ellipse you have so that I center it around 0. To keep the position I translate the group <g id="layer1" transform="translate(314.29,489.51)">.
I draw another ellipse inside. The rx attribute of this ellipse is the rx of the path8074 ellipse multiplied by a factor. Let's say .8. The same for the ry. I'm calling this ellipse inner
I calculate the total length of the inner ellipse using let innerLength = inner.getTotalLength();
let n = 10: this is the number of points you need to inset
I'm using a loop for(let i = 0; i < n; i++){ to calculate the coords of the points on the inner path let length = i * innerLength / n; let p = inner.getPointAtLength(length);and to draw a circle to mark the point: drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
const SVG_NS = 'http://www.w3.org/2000/svg';
let rx = path8074.getAttribute("rx");
let ry = path8074.getAttribute("ry");
let factor = .8;
inner.setAttributeNS(null,"rx",rx*factor);
inner.setAttributeNS(null,"ry",ry*factor);
let innerLength = inner.getTotalLength();
let n = 10;//n points
for(let i = 0; i < n; i++){
let length = i * innerLength / n;
let p = inner.getPointAtLength(length);
drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
}
// a function to draw a circle
function drawCircle(o, parent) {
var circle = document.createElementNS(SVG_NS, 'circle');
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="200 350 744.09448819 1052.3622047">
<g id="layer1" transform="translate(314.29,489.51)">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" stroke-width=".38188px" fill="none"/>
<ellipse id="inner" stroke="#000" stroke-width=".38188px" fill="none"/>
</g>
</svg>
I hope it helps.
OBSERVATION: I've changed the viewBox of the svg element because I wanted the ellipses in view. You can change it back to what it was.

How to draw a line in a right angle from another line in a SVG Path

I am drawing an arch and then want to draw a line from the end of the arch with a given length in an right angle.
I know about the L and the V command, but for the L command I need a specific point which I dont have and the V command is not in a right angle to the given point.
Is there any other command that could help me or should I just try to calculate the endpoint and then use L command?
If so how would you try to calculate it?
Thanks in advance!
EDIT:
My code looks like this:
$arcpath = "<path d=\"M $circstartx $circstarty L $startx $starty A $radius $radius, 0, 0 $largeArcflag, $endx $endy L $circendx $circendy\" fill=\"none\" stroke=\"black\" />";
and generates a path like this
now the rest of the path needs to complete it like
this
Hope this explains it better
EDIT 2:
I have an approach which is working alright but I have one problem.
for ($i=0; $i<count($json_a->pie); $i++) {
//value in degrees
$valdeg = $json_a->pie[$i]->value1 / $valges * 360.0;
//startang is 0 in the first iteration
$endang = $startang + $valdeg;
//transform deg in rad of start and end angle
$valradstart = ($startang-90) * $M_PI /180.0;
$valradend = ($endang-90)* $M_PI /180.0;
if ($endang - $startang <= 180){
$largeArcflag = 0;
}else {
$largeArcflag = 1;
}
$valradstart2 = $valradstart + ($M_PI/6);
$valradend2 = $valradend - ($M_PI/6);
//calculate the start and end coordinates
$startx= $x + ($radius * cos($valradend));
$starty= $y + ($radius * sin($valradend));
$endx= $x + ($radius * cos($valradstart));
$endy= $y + ($radius * sin($valradstart));
//inner
$innerstartx= $x + ($innerradius * cos($valradend));
$innerstarty= $y + ($innerradius * sin($valradend));
$innerendx= $x + ($innerradius * cos($valradstart));
$innerendy= $y + ($innerradius * sin($valradstart));
//height depending on var in .json
//$circradius -= $pieheight;
//circle around the middle for the fold up
$circstartx= $x + ($circradius * cos($valradend));
$circstarty= $y + ($circradius * sin($valradend));
$circendx= $x + ($circradius * cos($valradstart));
$circendy= $y + ($circradius * sin($valradstart));
//circle around the middle for the fold up
$circstartx2= $x + ($circradius * cos($valradend2));
$circstarty2= $y + ($circradius * sin($valradend2));
$circendx2= $x + ($circradius * cos($valradstart2));
$circendy2= $y + ($circradius * sin($valradstart2));
$startx2= $x + ($radius * cos($valradend2));
$starty2= $y + ($radius * sin($valradend2));
$endx2= $x + ($radius * cos($valradstart2));
$endy2= $y + ($radius * sin($valradstart2));
$innerstartx2= $x + ($innerradius * cos($valradend2));
$innerstarty2= $y + ($innerradius * sin($valradend2));
$innerendx2= $x + ($innerradius * cos($valradstart2));
$innerendy2= $y + ($innerradius * sin($valradstart2));
//write into the svg file
$arcpath = "<path d=\"M $circstartx $circstarty L $startx $starty A $radius $radius, 0, 0 $largeArcflag, $endx $endy\" fill=\"none\" stroke=\"black\" />";
//$arcpath = "<path d=\"M $startx $starty A $radius $radius, 0, 0 $largeArcflag, $endx $endy L $circendx $circendy\" fill=\"none\" stroke=\"black\" />";
$lineconnect = "<path d=\"M $circendx $circendy L $circstartx2 $circstarty2 M $circendx2 $circendy2 L $circstartx $circstarty \" fill=\"none\" stroke=\"black\" stroke-dasharray=\"1, 1\" />";
$line2 = "<path d=\"M $circstartx2 $circstarty2 L $innerstartx2 $innerstarty2 M $innerendx2 $innerendy2 L $circendx2 $circendy2 \" fill=\"none\" stroke=\"black\" />";
$innerarcpath ="<path d=\"M $innerendx2 $innerendy2 A $innerradius $innerradius, 0, 0 $largeArcflag, $innerstartx2 $innerstarty2 \" fill=\"none\" stroke=\"black\" />";
$innerarcpath2 ="<path d=\"M $innerstartx $innerstarty A $innerradius $innerradius, 0, 0 $largeArcflag, $innerendx $innerendy \" fill=\"none\" stroke=\"black\" stroke-dasharray=\"1, 1\" />";
I now only need to access a point on the middle and inner circle with a given distance.
So e.g. a point that is on the arc but 10px along the line of the arc.
Is there any way that you can access a point like that?
$valradstart2 = $valradstart + ($M_PI/6);
$valradend2 = $valradend - ($M_PI/6);
I tried it like this but the distance on the arc comes out different depending on the size of the pie chart.
I only need a way to access a point on an Arc X distance to the right and left.
Thank you!
As Paul LeBeau already commented, you will probably have to work with some sin and cos functions to get what you want. In particular, when it comes to arcs and curves, things get a bit more complicated.
However, there exists a simple solution, when you work with
straight lines and
use relative drawing commands in your path (small letter l for "line")
You will automatically draw at right angles if you follow an l<a>,<b> line by l<n*b>,<-n*a> or l<-n*b>,<n*a> (n can be an arbitrary factor). Check out the little fiddle below. By entering any values into the interactive prompt you will be generating lines at different angles, but always perpendicular to each other.
var [a,b]=prompt('please enter a,b:','40,70').split(',');
document.querySelector('svg').innerHTML='<path d="M20,20 l'
+( 2*a)+','+( 2*b)
+' '+(-.5*b)+','+( .5*a)
+' '+(-.5*a)+','+(-.5*b)
+' '+ b +','+( -a)
+'" stroke="#000" fill="none" >';
<svg width=400 height=400></svg>
Second answer, now addressing the OP's modified question:
If you want to draw a pie chart with some fixed gaps between the segments, you could do something like the following:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="600">
<g style="stroke:black;stroke-width:1">
<?php
function pt($r,$ang,$mns=-1){ global $x,$y,$rad,$gap;
$a=($ang-90)*$rad-$mns*asin($gap/$r);
return sprintf('%0.2f,%0.2f',$x+$r*cos($a),$y+$r*sin($a));
}
// define global variables:
$json_a=new stdClass();
$json_a->pie=array(30,20,40,40,30,30); // pie chart values: as few or many as you like!
$r=50;$x=210;$y=210;$ri=100; $rc=200;
$a360=360/array_sum($json_a->pie); $M_PI=3.14159265358; $rad=$M_PI/180.;
$out='';$gap=4;$ang1=0; // start angle
foreach ($json_a->pie as $num) {
$dang = $num * $a360; // delta angle
$laf = $dang > 180? 1 : 0; // Large Arc Flag
$ang2 = $ang1 + $dang; // second angle
$out.= '<path d="M'.pt($rc,$ang1).'L'.pt($r, $ang1)."A $r,$r, 0,$laf,1 " .pt($r,$ang2,1).
'L'.pt($rc,$ang2,1)."A $rc,$rc, 0,$laf,0, ".pt($rc,$ang1).'" style="fill:hsl('.floor($ang1).',80%,55%)" />'."\n";
$ang1=$ang2;
}
echo "$out";
?></g></svg>
A php-demo can be found under http://rextester.com/HGGAH45381.
This produces the following:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="600">
<g style="stroke:black;stroke-width:1">
<path d="M214.00,10.04L214.00,160.16A 50,50, 0,0,1 249.54,179.39L375.21,97.28A 200,200, 0,0,0, 214.00,10.04" style="fill:hsl(0,80%,55%)" />
<path d="M379.59,103.98L253.91,186.09A 50,50, 0,0,1 260.00,210.13L409.61,222.53A 200,200, 0,0,0, 379.59,103.98" style="fill:hsl(56,80%,55%)" />
<path d="M408.95,230.50L259.34,218.10A 50,50, 0,0,1 222.15,258.50L246.86,406.57A 200,200, 0,0,0, 408.95,230.50" style="fill:hsl(94,80%,55%)" />
<path d="M238.97,407.89L214.26,259.82A 50,50, 0,0,1 165.96,233.68L28.49,293.99A 200,200, 0,0,0, 238.97,407.89" style="fill:hsl(170,80%,55%)" />
<path d="M25.28,286.66L162.75,226.36A 50,50, 0,0,1 166.09,186.09L40.41,103.98A 200,200, 0,0,0, 25.28,286.66" style="fill:hsl(246,80%,55%)" />
<path d="M44.79,97.28L170.46,179.39A 50,50, 0,0,1 206.00,160.16L206.00,10.04A 200,200, 0,0,0, 44.79,97.28" style="fill:hsl(303,80%,55%)" />
</g></svg>
Notice the gap between the segments. This gap is now (=recently edited!) calculated by $deltaAngle = asin($gap/$radius). The calculation is integrated in function pt(). The original calculation on the basis of the arc length (without using asin()) produces errors for largers gaps.

How can I get a rect with just two of the corners rounded? [duplicate]

I have the following SVG:
<svg>
<g>
<path id="k9ffd8001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="#a0a700"></path>
<path id="kb8000001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="url(#k9ffb0001)"></path>
</g>
</svg>
I want to get a CSS-like border-top-right-radius and border-top-bottom-radius effect.
How can I achieve that rounded corner effect?
Here is how you can create a rounded rectangle with SVG Path:
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" />
Explanation
m100,100: move to point(100,100)
h200: draw a 200px horizontal line from where we are
a20,20 0 0 1 20,20: draw an arc with 20px X radius, 20px Y radius, clockwise, to a point with 20px difference in X and Y axis
v200: draw a 200px vertical line from where we are
a20,20 0 0 1 -20,20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and 20px difference in Y axis
h-200: draw a -200px horizontal line from where we are
a20,20 0 0 1 -20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and -20px difference in Y axis
v-200: draw a -200px vertical line from where we are
a20,20 0 0 1 20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with 20px difference in X and -20px difference in Y axis
z: close the path
<svg width="440" height="440">
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" fill="none" stroke="black" stroke-width="3" />
</svg>
Not sure why nobody posted an actual SVG answer. Here is an SVG rectangle with rounded corners (radius 3) on the top:
<path d="M0,0 L0,27 A3,3 0 0,0 3,30 L7,30 A3,3 0 0,0 10,27 L10,0 Z" />
This is a Move To (M), Line To (L), Arc To (A), Line To (L), Arc To (A), Line To (L), Close Path (Z).
The comma-delimited numbers are absolute coordinates. The arcs are defined with additional parameters specifying the radius and type of arc. This could also be accomplished with relative coordinates (use lower-case letters for L and A).
The complete reference for those commands is on the W3C SVG Paths page, and additional reference material on SVG paths can be found in this article.
As referenced in my answer to Applying rounded corners to paths/polygons, I have written a routine in javascript for generically rounding corners of SVG paths, with examples, here: http://plnkr.co/edit/kGnGGyoOCKil02k04snu.
It will work independently from any stroke effects you may have. To use, include the rounding.js file from the Plnkr and call the function like so:
roundPathCorners(pathString, radius, useFractionalRadius)
The result will be the rounded path.
The results look like this:
You have explicitly set your stroke-linejoin to round but your stroke-width to 0, so of course you're not going to see rounded corners if you have no stroke to round.
Here's a modified example with rounded corners made through strokes:
http://jsfiddle.net/8uxqK/1/
<path d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z"
stroke-width="5"
stroke-linejoin="round"
stroke="#808600"
fill="#a0a700" />
Otherwise—if you need an actual rounded shape fill and not just a rounded fatty stroke—you must do what #Jlange says and make an actual rounded shape.
I'd also consider using a plain old <rect> which provides the rx and ry attributes
MDN SVG docs <- note the second drawn rect element
I've happened upon this problem today myself and managed to solve it by writing a small JavaScript function.
From what I can tell, there is no easy way to give a path element in an SVG rounded corners except if you only need the borders to be rounded, in which case the (CSS) attributes stroke, stroke-width and most importantly stroke-linejoin="round" are perfectly sufficient.
However, in my case I used a path object to create custom shapes with n corners that are filled out with a certain color and don't have visible borders, much like this:
I managed to write a quick function that takes an array of coordinates for an SVG path and returns the finished path string to put in the d attribute of the path html element. The resulting shape will then look something like this:
Here is the function:
/**
* Creates a coordinate path for the Path SVG element with rounded corners
* #param pathCoords - An array of coordinates in the form [{x: Number, y: Number}, ...]
*/
function createRoundedPathString(pathCoords) {
const path = [];
const curveRadius = 3;
// Reset indexes, so there are no gaps
pathCoords = pathCoords.slice();
for (let i = 0; i < pathCoords.length; i++) {
// 1. Get current coord and the next two (startpoint, cornerpoint, endpoint) to calculate rounded curve
const c2Index = ((i + 1) > pathCoords.length - 1) ? (i + 1) % pathCoords.length : i + 1;
const c3Index = ((i + 2) > pathCoords.length - 1) ? (i + 2) % pathCoords.length : i + 2;
const c1 = pathCoords[i];
const c2 = pathCoords[c2Index];
const c3 = pathCoords[c3Index];
// 2. For each 3 coords, enter two new path commands: Line to start of curve, bezier curve around corner.
// Calculate curvePoint c1 -> c2
const c1c2Distance = Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
const c1c2DistanceRatio = (c1c2Distance - curveRadius) / c1c2Distance;
const c1c2CurvePoint = [
((1 - c1c2DistanceRatio) * c1.x + c1c2DistanceRatio * c2.x).toFixed(1),
((1 - c1c2DistanceRatio) * c1.y + c1c2DistanceRatio * c2.y).toFixed(1)
];
// Calculate curvePoint c2 -> c3
const c2c3Distance = Math.sqrt(Math.pow(c2.x - c3.x, 2) + Math.pow(c2.y - c3.y, 2));
const c2c3DistanceRatio = curveRadius / c2c3Distance;
const c2c3CurvePoint = [
((1 - c2c3DistanceRatio) * c2.x + c2c3DistanceRatio * c3.x).toFixed(1),
((1 - c2c3DistanceRatio) * c2.y + c2c3DistanceRatio * c3.y).toFixed(1)
];
// If at last coord of polygon, also save that as starting point
if (i === pathCoords.length - 1) {
path.unshift('M' + c2c3CurvePoint.join(','));
}
// Line to start of curve (L endcoord)
path.push('L' + c1c2CurvePoint.join(','));
// Bezier line around curve (Q controlcoord endcoord)
path.push('Q' + c2.x + ',' + c2.y + ',' + c2c3CurvePoint.join(','));
}
// Logically connect path to starting point again (shouldn't be necessary as path ends there anyway, but seems cleaner)
path.push('Z');
return path.join(' ');
}
You can determine the rounding strength by setting the curveRadius variable at the top. The default is 3 for a 100x100 (viewport) coordinate system, but depending on the size of your SVG, you may need to adjust this.
For my case I need to radius begin and end of path:
With stroke-linecap: round; I change it to what I want:
This question is the first result for Googling "svg rounded corners path". Phrogz suggestion to use stroke has some limitations (namely, that I cannot use stroke for other purposes, and that the dimensions have to be corrected for the stroke width).
Jlange suggestion to use a curve is better, but not very concrete. I ended up using quadratic Bézier curves for drawing rounded corners. Consider this picture of a corner marked with a blue dot and two red points on adjacent edges:
The two lines could be made with the L command. To turn this sharp corner into a rounded corner, start drawing a curve from the left red point (use M x,y to move to that point). Now a quadratic Bézier curve has just a single control point which you must set on the blue point. Set the end of the curve at the right red point. As the tangent at the two red points are in the direction of the previous lines, you will see a fluent transition, "rounded corners".
Now to continue the shape after the rounded corner, a straight line in a Bézier curve can be achieved by setting the control point between on the line between the two corners.
To help me with determining the path, I wrote this Python script that accepts edges and a radius. Vector math makes this actually very easy. The resulting image from the output:
#!/usr/bin/env python
# Given some vectors and a border-radius, output a SVG path with rounded
# corners.
#
# Copyright (C) Peter Wu <peter#lekensteyn.nl>
from math import sqrt
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
def sub(self, vec):
return Vector(self.x - vec.x, self.y - vec.y)
def add(self, vec):
return Vector(self.x + vec.x, self.y + vec.y)
def scale(self, n):
return Vector(self.x * n, self.y * n)
def length(self):
return sqrt(self.x**2 + self.y**2)
def normal(self):
length = self.length()
return Vector(self.x / length, self.y / length)
def __str__(self):
x = round(self.x, 2)
y = round(self.y, 2)
return '{},{}'.format(x, y)
# A line from vec_from to vec_to
def line(vec_from, vec_to):
half_vec = vec_from.add(vec_to.sub(vec_from).scale(.5))
return '{} {}'.format(half_vec, vec_to)
# Adds 'n' units to vec_from pointing in direction vec_to
def vecDir(vec_from, vec_to, n):
return vec_from.add(vec_to.sub(vec_from).normal().scale(n))
# Draws a line, but skips 'r' units from the begin and end
def lineR(vec_from, vec_to, r):
vec = vec_to.sub(vec_from).normal().scale(r)
return line(vec_from.add(vec), vec_to.sub(vec))
# An edge in vec_from, to vec_to with radius r
def edge(vec_from, vec_to, r):
v = vecDir(vec_from, vec_to, r)
return '{} {}'.format(vec_from, v)
# Hard-coded border-radius and vectors
r = 5
a = Vector( 0, 60)
b = Vector(100, 0)
c = Vector(100, 200)
d = Vector( 0, 200 - 60)
path = []
# Start below top-left edge
path.append('M {} Q'.format(a.add(Vector(0, r))))
# top-left edge...
path.append(edge(a, b, r))
path.append(lineR(a, b, r))
path.append(edge(b, c, r))
path.append(lineR(b, c, r))
path.append(edge(c, d, r))
path.append(lineR(c, d, r))
path.append(edge(d, a, r))
path.append(lineR(d, a, r))
# Show results that can be pushed into a <path d="..." />
for part in path:
print(part)
Here are some paths for tabs:
https://codepen.io/mochime/pen/VxxzMW
<!-- left tab -->
<div>
<svg width="60" height="60">
<path d="M10,10
a10 10 0 0 1 10 -10
h 50
v 47
h -50
a10 10 0 0 1 -10 -10
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- right tab -->
<div>
<svg width="60" height="60">
<path d="M10 0
h 40
a10 10 0 0 1 10 10
v 27
a10 10 0 0 1 -10 10
h -40
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- tab tab :) -->
<div>
<svg width="60" height="60">
<path d="M10,40
v -30
a10 10 0 0 1 10 -10
h 30
a10 10 0 0 1 10 10
v 30
z"
fill="#ff3600"></path>
</svg>
</div>
The other answers explained the mechanics. I especially liked hossein-maktoobian's answer.
The paths in the pen do the brunt of the work, the values can be modified to suite whatever desired dimensions.
This basically does the same as Mvins answer, but is a more compressed down and simplified version. It works by going back the distance of the radius of the lines adjacent to the corner and connecting both ends with a bezier curve whose control point is at the original corner point.
function createRoundedPath(coords, radius, close) {
let path = ""
const length = coords.length + (close ? 1 : -1)
for (let i = 0; i < length; i++) {
const a = coords[i % coords.length]
const b = coords[(i + 1) % coords.length]
const t = Math.min(radius / Math.hypot(b.x - a.x, b.y - a.y), 0.5)
if (i > 0) path += `Q${a.x},${a.y} ${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == 0) path += `M${a.x},${a.y}`
else if (i == 0) path += `M${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == length - 1) path += `L${b.x},${b.y}`
else if (i < length - 1) path += `L${a.x * t + b.x * (1 - t)},${a.y * t + b.y * (1 - t)}`
}
if (close) path += "Z"
return path
}
Here’s a piece of react code to generate rectangles with different corner radiuses:
const Rect = ({width, height, tl, tr, br, bl}) => {
const top = width - tl - tr;
const right = height - tr - br;
const bottom = width - br - bl;
const left = height - bl - tl;
const d = `
M${tl},0
h${top}
a${tr},${tr} 0 0 1 ${tr},${tr}
v${right}
a${br},${br} 0 0 1 -${br},${br}
h-${bottom}
a${bl},${bl} 0 0 1 -${bl},-${bl}
v-${left}
a${tl},${tl} 0 0 1 ${tl},-${tl}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="black" />
</svg>
);
};
ReactDOM.render(
<Rect width={200} height={100} tl={20} tr={0} br={20} bl={60} />,
document.querySelector('#app'),
);
https://jsfiddle.net/v1Ljpxh7/
Just to simplify implementing answer of #hmak.me, here's a commented piece of React code to generate rounded rectangles.
const Rect = ({width, height, round, strokeWidth}) => {
// overhang over given width and height that we get due to stroke width
const s = strokeWidth / 2;
// how many pixels do we need to cut from vertical and horizontal parts
// due to rounded corners and stroke width
const over = 2 * round + strokeWidth;
// lengths of straight lines
const w = width - over;
const h = height - over;
// beware that extra spaces will not be minified
// they are added for clarity
const d = `
M${round + s},${s}
h${w}
a${round},${round} 0 0 1 ${round},${round}
v${h}
a${round},${round} 0 0 1 -${round},${round}
h-${w}
a${round},${round} 0 0 1 -${round},-${round}
v-${h}
a${round},${round} 0 0 1 ${round},-${round}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="none" stroke="black" strokeWidth={strokeWidth} />
</svg>
);
};
ReactDOM.render(
<Rect width={64} height={32} strokeWidth={2} round={4} />,
document.querySelector('#app'),
);
Jsfiddle link.
I wrote this little typescript function so I can dynamically create the path for a complex rounded rectangle that function similar to a div with border-radius.
export function roundedRectPath(
x: number,
y: number,
width: number,
height: number,
bevel: [number, number, number, number] = [3, 3, 3, 3]
): string {
return "M" + x + "," + y
+ `m 0 ${bevel[0]}`
+ `q 0 -${bevel[0]} ${bevel[0]} -${bevel[0]}`
+ `l ${width - bevel[0] - bevel[1]} 0`
+ `q ${bevel[1]} 0 ${bevel[1]} ${bevel[1]}`
+ `l 0 ${height - bevel[1] - bevel[2]}`
+ `q 0 ${bevel[2]} -${bevel[2]} ${bevel[2]}`
+ `l -${width - bevel[2] - bevel[3]} 0`
+ `q -${bevel[3]} 0 -${bevel[3]} -${bevel[3]}`
+ `z`;
}
I found a solution but it is a bit hacky so it may not always work. I found that if you have an arc (A or a) with really small values it forces it to create a curve in one spot thus forming a rounded comer...
<svg viewBox="0 0 1 0.6" stroke="black" fill="grey" style="stroke-width:0.05px;">
<path d="M0.7 0.2 L0.1 0.1 A0.0001 0.0001 0 0 0 0.099 0.101 L0.5 0.5Z"></path>
</svg>
You are using a path element, why don't you just give the path a curve? See here for how to make curves using path elements: http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands

Rotate rectangle around its own center in SVG

I have following piece of code :
<svg>
<defs>
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "red"
fill = "blue" />
</defs>
<g transform = "translate(100,30)">
<use xlink:href = "#myRect" />
</g>
<g transform = "translate(100, 100) rotate(45 ? ?)">
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "green"
fill = "yellow" />
</g>
</svg>
When I translate rectangle without rotation, it is working fine. But when I rotate it, I wanted to rotate it around its center axis point. What should I need to pass to rotate attribute?
You would have to set the center as the center of the filled element. Like this:
svg .rotate {
transform-box: fill-box;
transform-origin: center;
transform: rotate(45deg);
}
You just need to add half the width/height of the rectangle to get its centre.
<g transform = "translate(100, 100) rotate(45 60 60)">
See transform documentation of the rotate function for more information.
The accepted answer works if you are drawing the rectangle starting at point (0,0) which was the OP case. However for me it was not!
Here is what worked for me:
To get the rectangle coordinates i used $('#rectID').getBBox()
method, should return [rect-height , rect-width , rect-y , rect x ]
The center point is ( rect-x + (rect-width/2) , rect-y + (rect-height/2) )
Here is a snippet i used on the browser console:
var coord = $('#elemID')[0].getBBox();
coord.x + (coord.width/2) +' '+ coord.y + (coord.height/2)
origin
x = x + width / 2
y = y + height / 2
here
x is 10
y is 10
width is 120
height is 120
<g transform = "translate(100, 100) rotate(45 70 70)">
I know this is an old post but if there are people out there who are looking make the values modifiable outside the group element
const centerX=100;
const centerY=100;
const firstAngle=45;
const secondAngle=60;
const thirdAngle =60;
<g transform ={`translate(${centerX}, ${centerY}) rotate(${firstAngle} ${secondAngle},
${thirdAngle})`}>
Nothing you just need to write the following code with the element in javascript:
element.rotate(angle Degree);

Resources