Как вычислить путь SVG для дуги (круга)

учитывая окружность с центром в (200,200), радиус 25, Как нарисовать дугу от 270 до 135 градусов и ту, которая идет от 270 до 45 градусов?

0 градусов означает, что он находится прямо на оси x (правая сторона) (что означает, что это 3-часовое положение) 270 градусов означает, что это 12 часов, и 90 означает, что положении "6 часов"

в более общем плане, что такое путь для дуги для части круга с

x, y, r, d1, d2, direction

смысл

center (x,y), radius r, degree_start, degree_end, direction

12 ответов


расширение на @ wdebeaum отличный ответ, Вот метод для генерации дугообразного пути:

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;       
}

использовать

document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 180));

и в вашем html

<path id="arc1" fill="none" stroke="#446688" stroke-width="20" />

Live demo


вы хотите использовать эллиптические Arc command. К сожалению для вас, это требует, чтобы вы указали Декартовые координаты (x, y) начальной и конечной точек, а не полярные координаты (радиус, угол), которые у вас есть, поэтому вам нужно сделать некоторую математику. Вот функция JavaScript, которая должна работать (хотя я ее не тестировал), и которая, я надеюсь, довольно понятна:

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  var angleInRadians = angleInDegrees * Math.PI / 180.0;
  var x = centerX + radius * Math.cos(angleInRadians);
  var y = centerY + radius * Math.sin(angleInRadians);
  return [x,y];
}

какие углы соответствуют, какие позиции часов будут зависеть в системе координат; просто замените и/или отрицайте термины sin / cos по мере необходимости.

команда arc имеет следующие параметры:

rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y

для первого примера:

rx=ry=25 и x-axis-rotation=0, так как вы хотите круг, а не эллипс. Вы можете вычислить обе начальные координаты (которые вы должны Move to) и конечные координаты (x, y), используя функцию выше, давая (200, 175) и около (182.322, 217.678), соответственно. С учетом эти ограничения до сих пор на самом деле есть четыре дуги, которые можно нарисовать, поэтому два флага выбирают один из них. Я предполагаю, что вы, вероятно, хотите нарисовать небольшую дугу (что означает large-arc-flag=0), в направлении уменьшения угла (значение sweep-flag=0). Все вместе, путь SVG:

M 200 175 A 25 25 0 0 0 182.322 217.678

для второго примера (предполагая, что вы имеете в виду одно и то же направление и, следовательно, большую дугу), путь SVG:

M 200 175 A 25 25 0 1 0 217.678 217.678

опять же, я не проверял это.

(редактировать 2016-06-01) если, как @clocksmith, вам интересно, почему они выбрали этот API, посмотрите на замечание:. Они описывают две возможные параметризации дуги:" параметризация конечной точки "(та, которую они выбрали) и" параметризация центра " (которая похожа на то, что использует вопрос). В описании "параметризации конечной точки" говорится:

одним из преимуществ параметризации конечной точки является то, что она позволяет согласованный синтаксис пути в все команды пути заканчиваются координатами новой "текущей точки".

таким образом, в основном это побочный эффект дуг, рассматриваемых как часть большего пути, а не их собственного отдельного объекта. Я полагаю, что если ваш SVG-рендеринг неполный, он может просто пропустить любые компоненты пути, которые он не знает, как визуализировать, пока он знает, сколько аргументов они принимают. Или, может быть, он позволяет параллельную визуализацию различных фрагментов пути со многими компонентами. Или возможно, они сделали это, чтобы убедиться, что ошибки округления не накапливаются по длине сложного пути.

примечания по реализации также полезны для исходного вопроса, поскольку у них есть более математический псевдокод для преобразования между двумя параметризациями (что я не понял, когда впервые написал этот ответ).


Я немного изменил ответ opsb и сделал заливку в поддержку сектора круга. http://codepen.io/anon/pen/AkoGx

JS

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 arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

    var d = [
        "M", start.x, start.y, 
        "A", radius, radius, 0, arcSweep, 0, end.x, end.y,
        "L", x,y,
        "L", start.x, start.y
    ].join(" ");

    return d;       
}

document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 220));

HTML-код

<svg>
  <path id="arc1" fill="orange" stroke="#446688" stroke-width="0" />
</svg>

Я хотел прокомментировать ответ @Ahtenus, в частности, комментарий Ray Hulha о том, что codepen не показывает никакой дуги, но моя репутация недостаточно высока.

причина этого codepen не работает в том, что его html неисправен с шириной штриха нуля.

Я все исправила и второй пример здесь : http://codepen.io/AnotherLinuxUser/pen/QEJmkN.

html:

<svg>
    <path id="theSvgArc"/>
    <path id="theSvgArc2"/>
</svg>

соответствующий CSS :

svg {
    width  : 500px;
    height : 500px;
}

path {
    stroke-width : 5;
    stroke       : lime;
    fill         : #151515;
}

javascript:

document.getElementById("theSvgArc").setAttribute("d", describeArc(150, 150, 100, 0, 180));
document.getElementById("theSvgArc2").setAttribute("d", describeArc(300, 150, 100, 45, 190));

@opsb отвечает аккуратно, но центральная точка не является точной, более того, как отметил @Jithin, если угол 360, то ничего не рисуется вообще.

@Jithin Исправлена проблема 360, но если вы выбрали менее 360 градусов, то вы получите линию, закрывающую дуговой цикл, что не требуется.

я исправил это и добавил некоторую анимацию в код ниже:

function myArc(cx, cy, radius, max){       
       var circle = document.getElementById("arc");
        var e = circle.getAttribute("d");
        var d = " M "+ (cx + radius) + " " + cy;
        var angle=0;
        window.timer = window.setInterval(
        function() {
            var radians= angle * (Math.PI / 180);  // convert degree to radians
            var x = cx + Math.cos(radians) * radius;  
            var y = cy + Math.sin(radians) * radius;
           
            d += " L "+x + " " + y;
            circle.setAttribute("d", d)
            if(angle==max)window.clearInterval(window.timer);
            angle++;
        }
      ,5)
 }     

  myArc(110, 110, 100, 360);
    
<svg xmlns="http://www.w3.org/2000/svg" style="width:220; height:220;"> 
    <path d="" id="arc" fill="none" stroke="red" stroke-width="2" />
</svg>

исходная полартокартезианская функция wdebeaum верна:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

изменение начальной и конечной точек с помощью:

var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);

сбивает с толку (для меня), потому что это изменит флаг развертки. Использование:

var start = polarToCartesian(x, y, radius, startAngle);
var end = polarToCartesian(x, y, radius, endAngle);

С флагом развертки = " 0 "рисует" нормальные " против часовой стрелки дуги, что, я думаю, более прямолинейно. См.https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths


небольшая модификация ответа @opsb. Мы не можем нарисовать полный круг с помощью этого метода. т. е. если мы дадим (0, 360), он вообще ничего не нарисует. Поэтому небольшая модификация сделана, чтобы исправить это. Было бы полезно отображать оценки, которые иногда достигают 100%.

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 endAngleOriginal = endAngle;
    if(endAngleOriginal - startAngle === 360){
        endAngle = 359;
    }

    var start = polarToCartesian(x, y, radius, endAngle);
    var end = polarToCartesian(x, y, radius, startAngle);

    var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

    if(endAngleOriginal - startAngle === 360){
        var d = [
              "M", start.x, start.y, 
              "A", radius, radius, 0, arcSweep, 0, end.x, end.y, "z"
        ].join(" ");
    }
    else{
      var d = [
          "M", start.x, start.y, 
          "A", radius, radius, 0, arcSweep, 0, end.x, end.y
      ].join(" ");
    }

    return d;       
}

document.getElementById("arc1").setAttribute("d", describeArc(120, 120, 100, 0, 359));

Это старый вопрос, но я нашел код полезным и сэкономил мне три минуты мышления:) поэтому я добавляю небольшое расширение в @opsb'ы ответ.

Если вы хотите преобразовать эту дугу в срез (для заполнения), мы можем немного изменить код:

function describeArc(x, y, radius, spread, startAngle, endAngle){
    var innerStart = polarToCartesian(x, y, radius, endAngle);
  	var innerEnd = polarToCartesian(x, y, radius, startAngle);
    var outerStart = polarToCartesian(x, y, radius + spread, endAngle);
    var outerEnd = polarToCartesian(x, y, radius + spread, startAngle);

    var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

    var d = [
        "M", outerStart.x, outerStart.y,
        "A", radius + spread, radius + spread, 0, largeArcFlag, 0, outerEnd.x, outerEnd.y,
        "L", innerEnd.x, innerEnd.y, 
        "A", radius, radius, 0, largeArcFlag, 1, innerStart.x, innerStart.y, 
        "L", outerStart.x, outerStart.y, "Z"
    ].join(" ");

    return d;
}

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))
  };
}

var path = describeArc(150, 150, 50, 30, 0, 50)
document.getElementById("p").innerHTML = path
document.getElementById("path").setAttribute('d',path)
<p id="p">
</p>
<svg width="300" height="300" style="border:1px gray solid">
  <path id="path" fill="blue" stroke="cyan"></path>
</svg>

и там вы идете!


вы можете использовать код JSFiddle, который я сделал для ответа выше:

https://jsfiddle.net/tyw6nfee/

все, что вам нужно сделать, это изменить последней консоли.войти код и дать ему свой собственный параметр:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))

изображение и некоторые Python

просто уточнить лучше и предложить другое решение. Arc [A команда] использовать текущее положение в качестве отправной точки, так что вы должны использовать Moveto [M] команда первая.

потом параметры Arc следующие:

rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, xf, yf

если мы определим, например, следующий svg-файл:

<svg width="5cm" height="5cm" viewBox="0 0 500 500"

xmlns="http://www.w3.org/2000/svg" version="1.1">

следующий код даст вам это результат:

<g stroke="none" fill="lime">
<path d="
M 250 250
A 100 100 0 0 0 450 250
Z"/> 
</g>

enter image description here

вы установите начальную точку с M конечная точка с параметрами xf и yf of A.

мы ищем круги, поэтому мы устанавливаем rx равна ry делая это в основном теперь он будет пытаться найти весь круг радиуса rx, которые пересекают начальную и конечную точку.

import numpy as np

def write_svgarc(xcenter,ycenter,r,startangle,endangle,output='arc.svg'):
    if startangle > endangle: 
        raise ValueError("startangle must be smaller than endangle")

    if endangle - startangle < 360:
        large_arc_flag = 0
        radiansconversion = np.pi/180.
        xstartpoint = xcenter + r*np.cos(startangle*radiansconversion)
        ystartpoint = ycenter - r*np.sin(startangle*radiansconversion)
        xendpoint = xcenter + r*np.cos(endangle*radiansconversion)
        yendpoint = ycenter - r*np.sin(endangle*radiansconversion)
        #If we want to plot angles larger than 180 degrees we need this
        if endangle - startangle > 180: large_arc_flag = 1
        with open(output,'a') as f:
            f.write(r"""<path d=" """)
            f.write("M %s %s" %(xstartpoint,ystartpoint))
            f.write("A %s %s 0 %s 0 %s %s" 
                    %(r,r,large_arc_flag,xendpoint,yendpoint))
            f.write("L %s %s" %(xcenter,ycenter))
            f.write(r"""Z"/>""" )

    else:
        with open(output,'a') as f:
            f.write(r"""<circle cx="%s" cy="%s" r="%s"/>"""
                    %(xcenter,ycenter,r))

вы можете иметь более подробное объяснение в этот пост это я написал.


компонент ReactJS на основе выбранного ответа:

import React from 'react';

const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
    const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;

    return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
    };
};

const describeSlice = (x, y, radius, startAngle, endAngle) => {

    const start = polarToCartesian(x, y, radius, endAngle);
    const end = polarToCartesian(x, y, radius, startAngle);

    const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

    const d = [
        "M", 0, 0, start.x, start.y,
        "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
    ].join(" ");

    return d;
};

const path = (degrees = 90, radius = 10) => {
    return describeSlice(0, 0, radius, 0, degrees) + 'Z';
};

export const Arc = (props) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
    <g transform="translate(150,150)" stroke="#000" strokeWidth="2">
        <path d={path(props.degrees, props.radius)} fill="#333"/>
    </g>

</svg>;

export default Arc;

версия ES6:

const angleInRadians = angleInDegrees => (angleInDegrees - 90) * (Math.PI / 180.0);

const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
    const a = angleInRadians(angleInDegrees);
    return {
        x: centerX + (radius * Math.cos(a)),
        y: centerY + (radius * Math.sin(a)),
    };
};

const arc = (x, y, radius, startAngle, endAngle) => {
    const fullCircle = endAngle - startAngle === 360;
    const start = polarToCartesian(x, y, radius, endAngle - 0.01);
    const end = polarToCartesian(x, y, radius, startAngle);
    const arcSweep = endAngle - startAngle <= 180 ? '0' : '1';

    const d = [
        'M', start.x, start.y,
        'A', radius, radius, 0, arcSweep, 0, end.x, end.y,
    ].join(' ');

    if (fullCircle) d.push('z');
    return d;
};