Reputation: 16908
I need to draw a circle with 4 sectors. I'm trying to draw a sector like this:
<path d="M200, 200, l 100,-100 a180,180 0 0,0 -127.27,127.27 z"/>
I got the -127.27, 127.27 from the equations:
x=cos(angle) * radius
y=sin(angle) * radius
My angle is 135, and my radius is 180.
Here's a codepen of what i get. The blue one is the one I'm talking about here, the black one is what I'm trying with different numbers.
Why am i not getting a proper 1/4 of a circle? What am i missing?
Upvotes: 12
Views: 28162
Reputation: 82316
The easiest way is to write a function to draw a circle with N sectors, that is nicely general and will cover the special case 4.
<!DOCTYPE html>
<html>
<head>
<title>Draw a circle with N sectors</title>
</head>
<body>
<script>
// Function to create an SVG sector
function createSector(cx, cy, r, startAngle, endAngle, color) {
const x1 = cx + r * Math.cos(startAngle);
const y1 = cy + r * Math.sin(startAngle);
const x2 = cx + r * Math.cos(endAngle);
const y2 = cy + r * Math.sin(endAngle);
const largeArcFlag = endAngle - startAngle <= Math.PI ? 0 : 1;
return `<path d="M${cx},${cy} L${x1},${y1} A${r},${r} 0 ${largeArcFlag},1 ${x2},${y2} Z" fill="${color}" stroke-width="0" stroke="none" />`;
}
// Main function to generate SVG
function generateSVG(n) {
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("width", "400");
svg.setAttribute("height", "400");
// svg.setAttribute("shape-rendering", "crispEdges");
// svg.setAttribute("shape-rendering", "geometricPrecision");
const cx = 200;
const cy = 200;
const r = 150;
const sectorAngle = (2 * Math.PI) / n;
for (let i = 0; i < n; i++)
{
// const startAngle = i * sectorAngle;
// const endAngle = startAngle + sectorAngle;
const overlap = 0.01; // Adjust as needed
const startAngle = i * sectorAngle - overlap;
const endAngle = startAngle + sectorAngle + overlap;
const color = `hsl(${(i * 360) / n}, 70%, 60%)`;
const sector = createSector(cx, cy, r, startAngle, endAngle, color);
svg.innerHTML += sector;
}
document.body.appendChild(svg);
}
// Generate an example with 4 sectors
generateSVG(4);
// Generate an example with 12 sectors
generateSVG(12);
// Generate an example with 360 sectors
generateSVG(360);
</script>
</body>
</html>
Upvotes: 0
Reputation: 11
This sample uses HTML + some JavaScript. It works as I expected :) Hope it helps.
<!DOCTYPE html>
<html>
<body>
<h2>SVG demo</h2>
<svg xmlns="http://www.w3.org/2000/svg" style="width:220px; height:220px;">
<path d="" id="arc1" fill="none" stroke="blue" stroke-width="0" />
<path d="" id="arc2" fill="none" stroke="blue" stroke-width="0" />
<path d="" id="arc3" fill="none" stroke="blue" stroke-width="0" />
<path d="" id="arc4" fill="none" stroke="blue" stroke-width="0" />
</svg>
<script>
function drawArc(id, cx, cy, radius, max, fromangle, toangle, color){
var circle = document.getElementById(id);
var e = circle.getAttribute("d");
var d = " M "+ cx + " " + cy;
for (var i = fromangle; i<=toangle; i++) {
var radians= i * (Math.PI / 180); //convert degree to radian
var x = cx + Math.cos(radians) * radius;
var y = cy + Math.sin(radians) * radius;
d += " L "+x + " " + y;
}
d += " L "+ cx + " " + cy;
circle.setAttribute("d", d);
circle.setAttribute("fill", color);
circle.setAttribute("stroke", color);
}
drawArc('arc1', 110, 110, 100, 360, 0, 90, '#ff0000');
drawArc('arc2', 110, 110, 100, 360, 90, 180, '#ffa500');
drawArc('arc3', 110, 110, 100, 360, 180, 270, '#ffff00');
drawArc('arc4', 110, 110, 100, 360, 270, 360, '#80c000');
</script>
</body>
</html>
Upvotes: 1
Reputation: 1854
This is the example where I create svg from JavaScript. I wrote it inspired by @zmechanic answer. One sector has gradient. To execute it just create an html document and copy the content.
<!DOCTYPE html>
<html>
<body>
<div id="svgRoot"></div>
<script type="text/javascript">
const DIAMETER = 200;
const SVG_SIZE = DIAMETER + 12;
const STROKE = "black";
const STROKE_WIDTH = "2";
const getSectorPath = (x, y, outerDiameter, a1, a2) => {
const degtorad = Math.PI / 180;
const cr = outerDiameter / 2;
const cx1 = Math.cos(degtorad * a2) * cr + x;
const cy1 = -Math.sin(degtorad * a2) * cr + y;
const cx2 = Math.cos(degtorad * a1) * cr + x;
const cy2 = -Math.sin(degtorad * a1) * cr + y;
return `M${x} ${y} ${cx1} ${cy1} A${cr} ${cr} 0 0 1 ${cx2} ${cy2}Z`;
};
const svgRoot = document.getElementById("svgRoot");
const pieChartSvgString = `<svg width="${SVG_SIZE}" height="${SVG_SIZE}">
<defs>
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#DD5E89;stop-opacity:1" />
<stop offset="100%" style="stop-color:#F7BB97;stop-opacity:1" />
</linearGradient>
</defs>
<g fill="url(#gradient1)">
<path
stroke="${STROKE}"
strokeWidth="${STROKE_WIDTH}"
d="${getSectorPath(SVG_SIZE / 2, SVG_SIZE / 2 - 5, DIAMETER, 45, 135)}"
/>
</g>
<path
stroke="${STROKE}"
strokeWidth="${STROKE_WIDTH}"
d="${getSectorPath(SVG_SIZE / 2, SVG_SIZE / 2, DIAMETER, 135, 225)}"
fill="#00ff00"
/>
<path
stroke="${STROKE}"
strokeWidth="${STROKE_WIDTH}"
d="${getSectorPath(SVG_SIZE / 2, SVG_SIZE / 2, DIAMETER, 225, 315)}"
fill="#0000ff"
/>
<path
stroke="${STROKE}"
strokeWidth="${STROKE_WIDTH}"
d="${getSectorPath(SVG_SIZE / 2, SVG_SIZE / 2, DIAMETER, 315, 45)}"
fill="#ffff00"
/>
</svg>`;
const svgNode = document.createRange().createContextualFragment(pieChartSvgString);
svgRoot.appendChild(svgNode);
</script>
</body>
</html>
Upvotes: 1
Reputation: 1990
Here is the function which does programmatic generation of SVG
sector path I used in React
component:
getSectorPath(x, y, outerDiameter, a1, a2) {
const degtorad = Math.PI / 180;
const halfOuterDiameter = outerDiameter / 2;
const cr = halfOuterDiameter - 5;
const cx1 = (Math.cos(degtorad * a2) * cr) + x;
const cy1 = (-Math.sin(degtorad * a2) * cr) + y;
const cx2 = (Math.cos(degtorad * a1) * cr) + x;
const cy2 = (-Math.sin(degtorad * a1) * cr) + y;
return "M" + x + " " + y + " " + cx1 + " " + cy1 + " A" + cr + " " + cr + " 0 0 1 " + cx2 + " " + cy2 + "Z";
}
and here is the usage:
<svg width={outerDiameter} height={outerDiameter}>
<path d={this.getSectorPath(outerDiameter / 2, outerDiameter / 2, outerDiameter, 45, 135)} fill="#f00"/>
<path d={this.getSectorPath(outerDiameter / 2, outerDiameter / 2, outerDiameter, 135, 225)} fill="#f00"/>
<path d={this.getSectorPath(outerDiameter / 2, outerDiameter / 2, outerDiameter, 225, 315)} fill="#f00"/>
<path d={this.getSectorPath(outerDiameter / 2, outerDiameter / 2, outerDiameter, 315, 45)} fill="#f00"/>
</svg>
Upvotes: 9
Reputation: 24587
The numbers don't make much sense. You start by moving to (200,200)
, then draw a straight line to (300,100)
(length: 141 units) followed by a circular arc ending at (172.73,227.27)
(radius 180 units). Shouldn't the length of the straight line segment at least be equal to the radius of the circle?
You're making life terribly difficult for yourself. If you want to draw four circular segments, a good first step would be to use a <g>
element to move the coordinate system to the centre of the circle. Then you can create all four segments using near-identical code.
Here's an example:
<svg width="200" height="200" viewBox="0 0 200 200">
<g transform="translate(100,100)" stroke="#000" stroke-width="2">
<path d="M0 0-70 70A99 99 0 0 1-70-70Z" fill="#f00"/>
<path d="M0 0-70-70A99 99 0 0 1 70-70Z" fill="#080"/>
<path d="M0 0 70-70A99 99 0 0 1 70 70Z" fill="#dd0"/>
<path d="M0 0 70 70A99 99 0 0 1-70 70Z" fill="#04e"/>
</g>
</svg>
If you want a circle with a different radius, replace 99
with the radius you want, and replace 70
with this value times sqrt(0.5).
M0 0-70 70
Move to (0,0)
, then draw a straight line to (-70,70)
(the L
is implied).
A99 99 0 0 1-70-70
Draw an elliptical arc from this point to (-70,-70)
with rx=rx=99
, x-axis-rotation=0
, large-arc-flag=1
, and sweep-flag=0
. (The last two parameters are described here).
Z
Close the path.
Upvotes: 32
Reputation: 7536
i'm realy lazy, so what i do when i need to draw arcs, is i use the following script: i create a unit vector:
var p = svgElem.createSVGPoint()
p.x = 0
p.y = 1
then i cerate a matrix for my rotations:
var m = svgElem.createSVGMatrix()
and finally i rotate the unit vector and translate/scale it to where i want it.
var p2 = p.matrixTransform(m.rotate(45))
p2.x = cx + p2.x*rx
p2.y = cy + p2.y*ry
and now i can either console.log(p2.x,p2.y)
if i want to hardcode the segment, or you can create the segments from script.
here is a basic example (i know, for a simple case like the one above, this is not necessary, but its an easy general solution, which has help me a lot in the past years...)
var svgElem=document.getElementById("svg");
var cx=100;
var cy=100;
var rx=90;
var ry=90;
var p = svgElem.createSVGPoint();
p.x = 0;
p.y = 1;
var m = svgElem.createSVGMatrix();
var p2 = p.matrixTransform(m.rotate(45));
p2.x = cx + p2.x*rx;
p2.y = cy + p2.y*ry;
console.log(p2.x,p2.y);
var path = document.createElementNS("http://www.w3.org/2000/svg","path");
svgElem.appendChild(path);
var d="M"+cx+" "+(cy+ry)+"A"+rx+" "+ry+" 0 0 1"+p2.x+" "+p2.y+"L"+cx+" "+cy+"z";
path.setAttribute("d",d)
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
</svg>
Upvotes: 12