Related
I have an SVG path defined using a quadratic path:
M x11 y11 Q cx cy x12 y12
This intersects with another line:
M x21 y21 L x22 y22
I am using a library which determines the intersection point of two SVG paths, xintersection yintersection. What I want to do now is draw the quadratic path only up to the intersection point, but following the same curve as though the full path were being drawn, i.e.:
M x11 y11 Q cxModified cyModified xintersection yintersection
How do I determine what the new control point, cxModified cyModified, should be?
As commented by #ccprog bezier.js can do this.
You need to convert your svg <path> elements to bezier.js objects like so:
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curveEl.getAttribute('d'));
This won't work for horizontal or vertical lines, you need to give them a slight x or y slant.
Bezier.fromSVG() can convert yout <path> d attribute to a bezier curve object (quadratic or cubic) - but it expects absolute commands. Shorthand commands like sor t won't be normalized automatically.
Then you can retrieve intersection t values (0-1) via intersects() method and call split(t) to get the curve command coordinates for left and right segment:
let intersections = curve.intersects(line);
let splitSegments = curve.split(intersections);
let left = splitSegments.left.points;
let right = splitSegments.right.points;
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curve1.getAttribute('d'));
// get intersection t value 0-1
let intersectT = curve.intersects(line);
let splitSegments = curve.split(intersectT);
// render
let dLeft = pointsToD(splitSegments.left.points);
left.setAttribute("d", dLeft);
let dRight = pointsToD(splitSegments.right.points);
right.setAttribute("d", dRight);
/**
* convert intersection result to
* svg d attribute
*/
function pointsToD(points) {
let d = `M ${points[0].x} ${points[0].y} `;
// cubic or quadratic bezier
let command = points.length > 3 ? "C" : "Q";
points.shift();
d +=
command +
points
.map((val) => {
return `${val.x} ${val.y}`;
})
.join(" ");
return d;
}
svg{
width:20em;
border:1px solid #ccc;
}
path{
fill:none;
}
<svg id="svg" viewBox="0 0 200 200">
<path id="line1" d="M 100 20 L 99 180" fill="none" stroke="#ccc" stroke-width="5" />
<path id="curve1" d="M 48 84 Q 100 187 166 37" fill="none" stroke="#ccc" stroke-width="5" />
<!-- result -->
<path id="left" d="" fill="none" stroke="green" stroke-width="2" />
<path id="right" d="" fill="none" stroke="red" stroke-width="2" />
</svg>
<script src="https://cdn.jsdelivr.net/npm/bezier-js#1.0.1/bezier.js"></script>
Below different transform values of same path
Path with first transform value.
<path id="cropMarkMask" class="bleedPath" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" width="357.21000000000004" height="315.02055178728943" aWidth="0" aHeight="0" x="71.39499999999998" y="92.48972410635528" aX="0" aY="0" fill="none" stroke="#ff0000" style="pointer-events: none; display: block;" display="none" transform="translate(107.11599999999999,128.2063399552543) scale(0.63504,0.6138000000000001)"></path>
Path with second transform value.
<path id="safeMarkMask" class="bleedPath" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" width="357.21000000000004" height="315.02055178728943" aWidth="0" aHeight="0" x="71.39499999999998" y="92.48972410635528" aX="0" aY="0" fill="none" stroke="#2B8F49" stroke-width="2" stroke-dasharray="5,5" style="pointer-events: none; display: block;" display="none" transform="translate(142.837,163.92295580415333) scale(0.47628000000000004,0.43380000000000013)"></path>
This is not exactly answering your question but I think you'll find it useful. I draw 2 rectangles, one for each heart: the transformed bounding box. The code is giving you (please open the console) the size and the position of those boxes so that you can calculate the difference you need:
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink"
const svg = document.querySelector("svg")
let heart_bbox = heart.getBBox();
// untransformed heart
let W0 = heart_bbox.width;
let H0 = heart_bbox.height;
let x0 = heart_bbox.x;
let y0 = heart_bbox.y;
// red heart
let bbox1 = {
width: W0 * 0.635,
height: H0 * 0.614,
x : x0 + 107.116,
y : y0 + 128.206
}
console.log("red heart",bbox1);
// green heart
let bbox2 = {
width: W0 * 0.476,
height: H0 * 0.434,
x : x0 + 142.837,
y : y0 + 163.923
}
console.log("green heart",bbox2);
drawRect(bbox1, svg)
drawRect(bbox2, svg)
function drawRect(o, parent) {
let rect = document.createElementNS(SVG_NS, 'rect');
for (let name in o) {
if (o.hasOwnProperty(name)) {
rect.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(rect);
return rect;
}
rect{stroke:#d9d9d9; fill:none;}
<svg viewBox = "-10 -10 500 500">
<defs>
<path id="heart" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" />
</defs>
<use xlink:href ="#heart" fill="none" stroke="#ff0000" transform="translate(107.11599999999999,128.2063399552543) scale(0.63504,0.6138000000000001)"></use>
<use xlink:href ="#heart" fill="none" stroke="#2B8F49" vector-effect="non-scaling-stroke" stroke-dasharray="5,5" transform="translate(142.837,163.92295580415333) scale(0.47628000000000004,0.43380000000000013)"></path>
</svg>
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
There is a path element of "M 1050000 80 A 40 40 0 1 0 1050000 40", getBBox() result width and height are: 0.0000711679458618164, 0.00007629334868397564
If move the path 10000 pixels towards left, become "M 1040000 80 A 40 40 0 1 0 1040000 40", then getBBox() result width and height are: 74.625, 79.99937438964844
The element's width and height are not changed but getBBox() give different results.
What's the reason and how to avoid this issue ? getBoundingClientRect() ?
Testing snippets:
function $$(id){return document.getElementById(id);}
var b1 = $$("p1").getBBox();
$$("r1").innerHTML=('p1: width:'+b1.width +', height:'+ b1.height);
//0.0000711679458618164, 0.00007629334868397564
var b2 = $$("p2").getBBox();
$$("r2").innerHTML=('p2: width:'+b2.width +', height:'+ b2.height);
//74.625, 79.99937438964844
<svg width="110" height="110" viewBox="1050000 0 110 110">
<path id="p1" d="M 1050000 80 A 40 40 0 1 0 1050000 40" stroke="#880000" stroke-width="1" fill="none"></path>
</svg>
<svg width="110" height="110" viewBox="1040000 0 110 110">
<path id="p2" d="M 1040000 80 A 40 40 0 1 0 1040000 40" stroke="#880000" stroke-width="1" fill="none"></path>
</svg>
<p id="r1"></p>
<p id="r2"></p>
Has confirmed this is a bug of chromium: issue 678162. It's originally caused by the code bug of Skia engine. The fixing is committed.
Only some versions of Chrome has this bug. As my testing, Chrome 52 is ok, Chrome 55 has bug.
On my case, I just need to multiple the wrong bbox by 0x100000.
0.0000711679458618164 * 0x100000 == 74.625
0.00007629334868397564 * 0x100000 == 79.99937438964844
Finally I use a custom function to calculate bbox for chrome.
This function cannot handle the nested svg(s) or nested transforms.
function bbox (element) {
var svg = element.farthestViewportElement,
mtr = element.getScreenCTM().inverse(),
bcr = element.getBoundingClientRect(),
x1 = bcr.left, x2 = bcr.right,
y1 = bcr.top, y2 = bcr.bottom,
pts = [[x1,y1],[x1,y2],[x2,y1],[x2,y2]],
pt = svg.createSVGPoint(), pt2,
bb = {}, u;
if(x1===0 && x2===0 && y1===0 && y2===0){
return {x:0,y:0,width:0,height:0};
}
for(var i=0; i < 4; i++){
pt.x = pts[i][0];
pt.y = pts[i][1];
pt2 = pt.matrixTransform(mtr);
if(bb.xmin === u){
bb.xmin = bb.xmax = pt2.x;
bb.ymin = bb.ymax = pt2.y;
}else{
if(bb.xmin > pt2.x){
bb.xmin = pt2.x;
}else if(bb.xmax < pt2.x){
bb.xmax = pt2.x;
}
if(bb.ymin > pt2.y){
bb.ymin = pt2.y;
}else if(bb.ymax < pt2.y){
bb.ymax = pt2.y;
}
}
}
return {
x: bb.xmin,
y: bb.ymin,
width: bb.xmax - bb.xmin,
height: bb.ymax - bb.ymin
};
}
I'm not sure how to approach drawing a hollow circle in SVG.
I would like a ring shape filled with a colour and then have a black outline.
The way I thought about doing it was have 2 circles, one with a smaller radius than the other. The problem is when I fill them, how do I make the smaller circle take the same fill colour as what it sits on?
Just use fill="none" and then only the stroke (outline) will be drawn.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="none" />
</svg>
Or this if you want two colours:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="3" fill="none" />
<circle cx="100" cy="50" r="39" stroke="red" stroke-width="2" fill="none" />
</svg>
MDragon00's answer works, but the inner and outer circles are not perfectly aligned (e.g. centered).
I modified his approach a little, using 4 semi-circle arcs (2 outer and 2 inner in reversed direction) to get the alignment exactly right.
<svg width="100" height="100">
<path d="M 50 10 A 40 40 0 1 0 50 90 A 40 40 0 1 0 50 10 Z M 50 30 A 20 20 0 1 1 50 70 A 20 20 0 1 1 50 30 Z" fill="#0000dd" stroke="#00aaff" stroke-width="3" />
</svg>
<!--
Using this path definition as d:
M centerX (centerY-outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY+outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY-outerRadius)
Z
M centerX (centerY-innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY+innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY-innerRadius)
Z
-->
Thanks to Chasbeen, I figured out how to make a true ring/donut in SVG. Note that the outer circle actually isn't closed, which is only apparent when you use a stroke. Very useful when you have many concentric rings, especially if they're interactive (say, with CSS hover commands).
For the draw command...
M cx, cy // Move to center of ring
m 0, -outerRadius // Move to top of ring
a outerRadius, outerRadius, 0, 1, 0, 1, 0 // Draw outer arc, but don't close it
Z // default fill-rule:even-odd will help create the empty innards
m 0 outerRadius-innerRadius // Move to top point of inner radius
a innerRadius, innerRadius, 0, 1, 1, -1, 0 // Draw inner arc, but don't close it
Z // Close the inner ring. Actually will still work without, but inner ring will have one unit missing in stroke
JSFiddle - Contains several rings and CSS to simulate interactivity. Note the downside that there's a single pixel missing at the starting point (at the top), which is only there if you add a stroke on.
Edit:
Found this SO answer (and better yet, this answer) which describes how to get the empty innards in general
You can do this as per the SVG spec by using a path with two components and fill-rule="evenodd". The two components are semi-circular arcs which join to form a circle (in the "d" attribute below, they each end with a 'z'). The area inside the inner circle does not count as part of the shape, hence interactivity is good.
To decode the below a little, the "340 260" is the top middle of the outer circle, the "290 290" is the radius of the outer circle (twice), the "340 840" is the bottom middle of the outer circle, the "340 492" is the top middle of the inner circle, the "58 58" is the radius of the inner circle (twice) and the "340 608" is the bottom middle of the inner circle.
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M340 260A290 290 0 0 1 340 840A290 290 0 0 1 340 260zM340 492A58 58 0 0 1 340 608A58 58 0 0 1 340 492z" stroke-width="4" stroke="rgb(0,0,0)" fill="rgb(0,0,255)">
<title>This will only display on the donut</title>
</path>
</svg>
This is the classic donut shape
I'm not sure if you are trying to achieve this with standard SVG or JavaScript that produces SVG
The objective can be achieved by including a relative "moveto" command in a single path definition
And click "donut holes" on the right side of the interactive examples.
At the very least you can see the path definition that made the red donut.
Here's a routine to create a bezier arc which is as close as makes no odds to a circle. You need four of them in a path for a complete circle.
BezierCurve BezierArc(double ox, double oy, double r, double thetaa, double thetab)
{
double theta;
double cpx[4];
double cpy[4];
int i;
int sign = 1;
while (thetaa > thetab)
thetab += 2 * Pi;
theta = thetab - thetaa;
if (theta > Pi)
{
theta = 2 * Pi - theta;
sign = -1;
}
cpx[0] = 1;
cpy[0] = 0;
cpx[1] = 1;
cpy[1] = 4.0 / 3.0 * tan(theta / 4);
cpx[2] = cos(theta) + cpy[1] * sin(theta);
cpy[2] = sin(theta) - cpy[1] * cos(theta);
cpx[3] = cos(theta);
cpy[3] = sin(theta);
cpy[1] *= sign;
cpy[2] *= sign;
cpy[3] *= sign;
for (i = 0; i < 4; i++)
{
double xp = cpx[i] * cos(thetaa) + cpy[i] * -sin(thetaa);
double yp = cpx[i] * sin(thetaa) + cpy[i] * cos(thetaa);
cpx[i] = xp;
cpy[i] = yp;
cpx[i] *= r;
cpy[i] *= r;
cpx[i] += ox;
cpy[i] += oy;
}
return BezierCurve({cpx[0], cpy[0]},{cpx[1], cpy[1]}, {cpx[2], cpy[2]}, {cpx[3], cpy[3]});
}