Ivan
Ivan

Reputation: 16908

SVG Draw a circle with 4 sectors

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

Answers (6)

Stefan Steiger
Stefan Steiger

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

vu-pt
vu-pt

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

Michael Klishevich
Michael Klishevich

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

zmechanic
zmechanic

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

r3mainer
r3mainer

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).

Path data breakdown:

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

Holger Will
Holger Will

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

Related Questions