Related
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
the drawing algorithm which I currently use:
a_max = Pi*2 (float)(num_segments - 1.0f)/(float)num_segments;
for (unsigned int i = 0; i<=num_segments;i++)
{
const float a = (float)i / (float)num_segments * a_max;
SetPixel(centre.x + cos(a) *radius, centre.y +sin(a) *radius);
}
Works fine, but it starts drawing at (centre.x+radius, centre.y). I would like to have it to start at the top , because I want to draw a compass and zero degree is at the top, not on the right, so that I don't have to make a hacky solution.
Try rotating 90 degrees to the left before you start drawing, this should solve it for you.
A compass not only starts at "north" instead of "east" but also goes clockwise instead of counter-clockwise.
For this case, just swap sin(a) and cos(a):
x = centre.x + sin(a) * radius
y = centre.y + cos(a) * radius
I am looking for a function that given start angle, end angle, moving clockwise or anticlockwise, hitting angle and will return if such a movement hits the hitting angle or not.
So for example, if we move from 270 degrees to 50 degrees in a clockwise direction, it will hit the hitting angle = 0 or 360 degree position, but it will not hit if we move in an anticlockwise direction.
Another example is that if we move from 80 degrees to 100 degrees clockwise, it will hit hitting angle = 90 but not in a anticlockwise direction.
First, you don't really need to explicitly pass the direction of travel. You just need a way to specify a particular arc of angle. So you can just adopt the convention that the angle should increase as it moves from the start to the end angle. To specify the inverse of an arc, just pass the start and end in reverse order.
So, let's define a function that works if the angles are in order from 0 to 360 degrees - no modulo required:
bool IsBetween(int low, int high, int target)
{
return (target >= low && target <= high);
}
That will work only if low is not larger than high. If it is (because the angle is sweeping across the 360 degree line), then we can just split the arc into two sections, one from the start angle to 360, and one from 0 to the finish angle.
bool SweepHits(int start, int finish, int target)
{
// check for the simple case
if (start <= finish) return IsBetween(start, finish, target);
// straddling 360 degrees - break into two sections
return IsBetween(start, 360, target) || IsBetween(0, finish, target);
}
As a side note, I think your initial examples are maybe wrong? If we start at 270 degrees and proceed to 50 going clockwise, we will NOT hit 0 degrees (same if you move from 80 to 100 degrees clockwise, you won't hit 90 degrees. This answer assumes that is a mistake on your part.
To clarify, this answer has angles starting from 0 degrees and going to 360 degrees counterclockwise.
UPDATED:
This now works regardless of whether the start angle is less or more than the end angle.
public static bool DidWeHitAngle(double hittingAngle, double startAngle, double endAngle, bool clockwise)
{
var minAngle = Math.Min(startAngle, endAngle);
var maxAngle = Math.Max(startAngle, endAngle);
var dir = minAngle == startAngle ? clockwise : !clockwise;
if (hittingAngle < minAngle || hittingAngle > maxAngle)
{
if (dir)
return true;
}
else
{
if (!dir)
return true;
}
return false;
}
Short question: using SVG path, we can draw 99.99% of a circle and it shows up, but when it is 99.99999999% of a circle, then the circle won't show up. How can it be fixed?
The following SVG path can draw 99.99% of a circle: (try it below and see if you see 4 arcs or only 2 arcs, but note that if it is IE, it is rendered in VML, not SVG, but have the similar issue)
var paper = Raphael(0, 0, 300, 800);
// Note that there are supposed to be 4 arcs drawn, but you may see only 1, 2, or 3 arcs depending on which browser you use
paper.path("M 100 100 a 50 50 0 1 0 35 85").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is about 62.5% of a circle, and it shows on most any browsers
paper.path("M 100 210 a 50 50 0 1 0 0.0001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't show anything if it is IE 8's VML, but will show if it is Chrome or Firefox's SVG. On IE 8, it needs to be 0.01 to show
paper.path("M 100 320 a 50 50 0 1 0 0.0000001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't draw anything at all, unless you change the 0.0000001 to 0.0001 on Chrome or Firefox... Safari will show it though...
paper.path("M 100 430 a 50 50 0 1 0 0 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is 100% of a circle... even Safari won't show it
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
M 100 100 a 50 50 0 1 0 0.00001 0
But when it is 99.99999999% of a circle, then nothing will show at all?
M 100 100 a 50 50 0 1 0 0.00000001 0
And that's the same with 100% of a circle (it is still an arc, isn't it, just a very complete arc)
M 100 100 a 50 50 0 1 0 0 0
How can that be fixed? The reason is I use a function to draw a percentage of an arc, and if I need to "special case" a 99.9999% or 100% arc to use the circle function, that'd be kind of silly.
Again, a test case is above
(and if it is VML on IE 8, even the second circle won't show... you have to change it to 0.01)
Update:
This is because I am rendering an arc for a score in our system, so 3.3 points get 1/3 of a circle. 0.5 gets half a circle, and 9.9 points get 99% of a circle. But what if there are scores that are 9.99 in our system? Do I have to check whether it is close to 99.999% of a circle, and use an arc function or a circle function accordingly? Then what about a score of 9.9987? Which one to use? It is ridiculous to need to know what kind of scores will map to a "too complete circle" and switch to a circle function, and when it is "a certain 99.9%" of a circle or a 9.9987 score, then use the arc function.
I know it's a bit late in the game, but I remembered this question from when it was new and I had a similar dillemma, and I accidently found the "right" solution, if anyone is still looking for one:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,0 (r * 2),0
a r,r 0 1,0 -(r * 2),0
"
/>
In other words, this:
<circle cx="100" cy="100" r="75" />
can be achieved as a path with this:
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
The trick is to have two arcs, the second one picking up where the first left off and using the negative diameter to get back to the original arc start point.
The reason it can't be done as a full circle in one arc (and I'm just speculating) is because you would be telling it to draw an arc from itself (let's say 150,150) to itself (150,150), which it renders as "oh, I'm already there, no arc necessary!".
The benefits of the solution I'm offering are:
it's easy to translate from a circle directly to a path, and
there is no overlap in the two arc lines (which may cause issues if you are using markers or patterns, etc). It's a clean continuous line, albeit drawn in two pieces.
None of this would matter if they would just allow textpaths to accept shapes. But I think they are avoiding that solution since shape elements like circle don't technically have a "start" point.
snippet demo:
circle, path {
fill: none;
stroke-width: 5;
stroke-opacity: .5;
}
circle {
stroke: red;
}
path {
stroke: yellow;
}
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="220px" height="220px">
<circle cx="100" cy="100" r="75" />
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
</svg>
Update:
If you are using the path for a textPath reference and you are wanting the text to render on the outer edge of the arc, you would use the exact same method but change the sweep-flag from 0 to 1 so that it treats the outside of the path as the surface instead of the inside (think of 1,0 as someone sitting at the center and drawing a circle around themselves, while 1,1 as someone walking around the center at radius distance and dragging their chalk beside them, if that's any help). Here is the code as above but with the change:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,1 (r * 2),0
a r,r 0 1,1 -(r * 2),0
"
/>
Same for XAML's arc. Just close the 99.99% arc with a Z and you've got a circle!
In reference to Anthony’s solution, here is a function to get the path:
function circlePath(cx, cy, r){
return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}
A totally different approach:
Instead of fiddling with paths to specify an arc in svg, you can also take a circle element and specify a stroke-dasharray, in pseudo code:
with $score between 0..1, and pi = 3.141592653589793238
$length = $score * 2 * pi * $r
$max = 7 * $r (i.e. well above 2*pi*r)
<circle r="$r" stroke-dasharray="$length $max" />
Its simplicity is the main advantage over the multiple-arc-path method (e.g. when scripting you only plug in one value and you're done for any arc length)
The arc starts at the rightmost point, and can be shifted around using a rotate transform.
Note: Firefox has an odd bug where rotations over 90 degrees or more are ignored. So to start the arc from the top, use:
<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />
Building upon Anthony and Anton's answers I incorporated the ability to rotate the generated circle without affecting it's overall appearance. This is useful if you're using the path for an animation and you need to control where it begins.
function(cx, cy, r, deg){
var theta = deg*Math.PI/180,
dx = r*Math.cos(theta),
dy = -r*Math.sin(theta);
return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}
i made a jsfiddle to do it in here:
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
console.log(describeArc(255,255,220,134,136))
link
all you need to do is to change the input of console.log and get the result in console
For those like me who were looking for an ellipse attributes to path conversion:
const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0 Z`
Adobe Illustrator uses bezier curves like SVG, and for circles it creates four points. You can create a circle with two elliptical arc commands...but then for a circle in SVG I would use a <circle /> :)
Written as a function, it looks like this:
function getPath(cx,cy,r){
return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}
It's a good idea that using two arc command to draw a full circle.
usually, I use ellipse or circle element to draw a full circle.
Another way would be to use two Cubic Bezier Curves. That's for iOS folks using pocketSVG which doesn't recognize svg arc parameter.
C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)
The last set of coordinates here (x,y) are where you want the line to end. The other two are control points. (x1,y1) is the control point for the start of your curve, and (x2,y2) for the end point of your curve.
<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />
These answers are much too complicated.
A simpler way to do this without creating two arcs or convert to different coordinate systems..
This assumes your canvas area has width w and height h.
`M${w*0.5 + radius},${h*0.5}
A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`
Just use the "long arc" flag, so the full flag is filled. Then make the arcs 99.9999% the full circle. Visually it is the same. Avoid the sweep flag by just starting the circle at the rightmost point in the circle (one radius directly horizontal from the center).
By reading HSL/HSV color theory, I get the impression that hue component is a cyclical attribute that repeats every 360 degrees and can be changed independently of saturation and lightness/value. Correct me if I am wrong, but these statements logically follow the previous definition:
Rotating hue by 360 degrees yields the same color
Rotating hue by 180 degrees twice yields the original color
Rotating hue by 180 degrees followed by -180 degrees yields the original color
However, only the option 1 is correct. Rotating hue 4 times by +90 degrees yields a color that isn't even remotely similar to the original.
Furthermore, using -webkit-filter and SVG's
<filter><feColorMatrix in="SourceGraphic" type="hueRotate" values="..." /></filter>
don't produce the same result for the same rotation. On the other hand, colors produced by SVG filters are consistent across browsers.
Is there any "hidden" property of hue rotation that makes the operation not associative?
Examples of both webkit filters and SVGs can be found here: http://jsfiddle.net/maros_urbanec/ARsjb/5/
In both CSS and SVG filters, there is no conversion into HSV or HSL - the hueRotation shorthands are using a linear matrix approximation in RGB space to perform the hue rotation. This doesn't conserve saturation or brightness very well for small rotations and highly saturated colors - as you're seeing.
A true hue rotation, would first convert the input RGB color to HSL, adjust the H and then convert back to RGB. Filters don't do this. And this conversion can't be accurately approximated with a linear matrix, so while the hue is accurately changed(mostly), the saturation and brightness goes all over the place. These effects are non-linear, so adding smaller ops together results in different colors vs. doing one big operation.
(The difference between huerotation in SVG and CSS filters could be due to using different color spaces (sRGB vs. linearRGB) - these should be the same.)
Update: I got interested enough to go and do a manual comparison. As you can see, filters do a terrible job of hue rotating pure colors in the 0 to 180 degree range. This image compares a manual hue rotation done by plugging in hsl colors manually (outer ring) vs. a filter hue rotation on the base color (inner ring)
But, they do a better job at less pure colors like hsl(0,50%,75%) as you can see.
codepen link in case you want to play: http://codepen.io/mullany/pen/fwHrd
Michael's answer is awesome, and I wish I had seen it before; but since I need to not only understand they're damn wierd but also in which way (I want to work around their logic so I need the maths), I've coded a hue-rotate implementation in Javascript (which was mostly taken from reading Firefox's source code), which emulates the hue-rotate that Webkit/Blink/Gecko use.
Again, the whole point here is just to understand what results it produces.
function calculate() {
// Get the RGB and angle to work with.
var color = document.getElementById('color').value;
if (! /^[0-9A-F]{6}$/i.test(color)) return alert('Bad color!');
var angle = document.getElementById('angle').value;
if (! /^-?[0-9]+$/i.test(angle)) return alert('Bad angle!');
var r = parseInt(color.substr(0, 2), 16);
var g = parseInt(color.substr(2, 2), 16);
var b = parseInt(color.substr(4, 2), 16);
var angle = (parseInt(angle) % 360 + 360) % 360;
// Hold your breath because what follows isn't flowers.
var matrix = [ // Just remember this is the identity matrix for
1, 0, 0, // Reds
0, 1, 0, // Greens
0, 0, 1 // Blues
];
// Luminance coefficients.
var lumR = 0.2126;
var lumG = 0.7152;
var lumB = 0.0722;
// Hue rotate coefficients.
var hueRotateR = 0.143;
var hueRotateG = 0.140;
var hueRotateB = 0.283;
var cos = Math.cos(angle * Math.PI / 180);
var sin = Math.sin(angle * Math.PI / 180);
matrix[0] = lumR + (1 - lumR) * cos - lumR * sin;
matrix[1] = lumG - lumG * cos - lumG * sin;
matrix[2] = lumB - lumB * cos + (1 - lumB) * sin;
matrix[3] = lumR - lumR * cos + hueRotateR * sin;
matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin;
matrix[5] = lumB - lumB * cos - hueRotateB * sin;
matrix[6] = lumR - lumR * cos - (1 - lumR) * sin;
matrix[7] = lumG - lumG * cos + lumG * sin;
matrix[8] = lumB + (1 - lumB) * cos + lumB * sin;
function clamp(num) {
return Math.round(Math.max(0, Math.min(255, num)));
}
var R = clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b);
var G = clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b);
var B = clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b);
// Output the result
var result = 'The original color, rgb(' + [r,g,b] + '), '
+ 'when rotated by ' + angle + ' degrees '
+ 'by the devil\'s logic, gives you '
+ 'rgb(' + [R,G,B] + '). If I got it right.';
document.getElementById('result').innerText = result;
}
// Listen for Enter key press.
['color', 'angle'].forEach(function(i) {
document.getElementById(i).onkeypress = function(event) {
var e = event || window.event, c = e.which || e.keyCode;
if (c == '13') return calculate();
}
});
body {
font: 14px sans-serif;
padding: 6px 8px;
}
input {
width: 64px;
}
<p>
This algorithm emulates the wierd, nonsensical and completely
idiotic <code>hue-rotate</code> CSS filter. I wanted to know
how it worked, because it is out of touch with any definition
of "hue" I've ever seen; the results it produces are stupid
and I believe it was coded under extreme influence of meth,
alcohol and caffeine, by a scientologist listening to Death Metal.
</p>
<span>#</span>
<input type="text" id="color" placeholder="RRGGBB">
<input type="text" id="angle" placeholder="degrees">
<button onclick="calculate()">Calculate</button>
<p id="result"></p>
The snippet was taken from this answer.
tl;dr Error from converting colors from floats (inside the filter) to bytes (everywhere else).
So it's a bit more complicated than that, the spec provides a good formula for hue rotation matrices, for instance the one for 180 degrees is (excluding alpha and shifts):
-0.5747 1.4304 0.1444
0.4252 0.4304 0.1444
0.4252 1.4304 -0.8556
Note, if you multiply that by itself you get (to four decimal places):
0.9999 0.0001 0.0000
0.0000 1.0 0.0
0.0000 0.0000 1.0
which is very close to the identity matrix, or a null transformation.
That would be perfect, except that the browser is converting back to RGB between each filter. Look what happens when we hue-rotate bright red:
-0.5747 1.4304 0.1444 1 -0.5747
0.4252 0.4304 0.1444 * 0 = 0.4252
0.4252 1.4304 -0.8556 0 0.4252
We get a color that's impossible to represent in RGB with values from 0 to 255. So it gets bound and rounded to 0 0.4235 0.4235 during the RGB conversion, and when it's rotated again we end up with a dark desaturated red, 0.6667 0.2431 0.2431 instead of the bright pure red we started with.