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

Для окружности с центром в (200 200), радиусом 25, как я рисую дугу от 270 градусов до 135 градусов и одну от 270 до 45 градусов?

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

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

x, y, r, d1, d2, direction

значение

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

Ответ 1

Развернувшись на @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" />

Живая демонстрация

Ответ 2

Вы хотите использовать команду эллиптическая A rc. К сожалению, для вас это требует, чтобы вы определили декартовы координаты (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 по мере необходимости.

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

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

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

rx= ry= 25 и x-axis-rotation= 0, так как вам нужен круг, а не эллипс. Вы можете вычислить как начальные координаты (которые вы должны M ove 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

Опять же, я не тестировал их.

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

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

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

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

Ответ 3

Я немного изменил ответ 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>

Ответ 4

Я хотел прокомментировать ответ @Ahtenus, в частности, на комментарий Рэя Хулха, в котором говорится, что 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));

Ответ 5

Это старый вопрос, но я нашел код полезным и сохранил мне три минуты мышления:) Итак, я добавляю небольшое расширение в @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>

Ответ 6

Ответы @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>

Ответ 7

Изображение и немного Python

Просто чтобы лучше уточнить и предложить другое решение. A Arc [ A ] использует текущую позицию в качестве начальной точки, поэтому Moveto нужно использовать 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 из 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))

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

Ответ 8

Небольшая модификация ответа @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));

Ответ 9

Версия 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;
};

Ответ 10

Оригинальная функция polarToCartesian по wdebeaum правильна:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

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

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

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

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

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

Ответ 11

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

https://jsfiddle.net/tyw6nfee/

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

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

Ответ 12

// putr following above class
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;       
}

// put following in render
 <Svg width={150} height={150}>
            <Text style={{position:'absolute', top:50,left:60}}>24</Text> 
            <Text style={{position:'absolute', top:70,left:50}}>Rooms</Text> 
  <Path
d={showArc}
fill="transparent"
stroke="green"
strokeWidth="15"
/>
 <Path
d={showArc1}
fill="transparent"
stroke={Colors.lightOrange}
strokeWidth="15"
/>
<Path
d={showArc2}
fill="transparent"
stroke="gray"
strokeWidth="15"
/> 
</Svg>

Ответ 13

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;