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

191

Учитывая окружность в центре (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
nonopolarity
источник

Ответы:

381

В продолжение замечательного ответа @ 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 демо

opsb
источник
9
Это супер! Обратите внимание, что arcSweepпеременная фактически управляет параметром large-arc-flagsvg A. В приведенном выше коде значение sweep-flagпараметра всегда равно нулю. arcSweepвероятно, следует переименовать в нечто подобное longArc.
Стивен Гросмарк
Спасибо @PocketLogic, обновили (наконец) согласно вашему предложению.
opsb
2
Действительно полезно, спасибо. Единственное, что я обнаружил, это то, что логика largeArc не работает, если вы используете отрицательные углы. Это работает от -360 до +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Я упускаю момент, почему стандарт не определяет дуги одинаково.
polkovnikov.ph
4
и не забудьте вырезать небольшое количество длины дуги, например: endAngle - 0.0001если нет, полная дуга не будет отрисована.
saike
128

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

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

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

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

rx= ry= 25 и x-axis-rotation= 0, так как вы хотите круг, а не эллипс. Вы можете вычислить как начальные координаты (которые вы должны Mдобавить), так и конечные координаты (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 неполон, он может просто пропустить любые компоненты пути, которые он не знает, как отобразить, если он знает, сколько аргументов они принимают. Или, возможно, он позволяет выполнять параллельный рендеринг различных фрагментов пути со многими компонентами. Или, возможно, они сделали это, чтобы избежать ошибок округления по всей длине сложного пути.

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

wdebeaum
источник
1
выглядит хорошо, попробую дальше. тот факт, что если это «больше» от дуги (когда дуга больше половины окружности) и ее large-arc-flagнужно переключать с 0 на 1, может привести к некоторой ошибке.
неполярность
тьфу, почему это API для дуг SVG?
часовщик
17

Я немного изменил ответ 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
источник
5
Ссылка на кодовый блок не работает для меня (Chrome)
Рэй Хулха,
Дуга рисуется вне границ SVG. Увеличьте высоту SVG и измените centerX, centerYнапример , до 100.
А.Акрам
Вы также можете явно установить окно просмотра в родительском элементе svg. НапримерviewBox="0 0 500 500"
Хокен Лид
7

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

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

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>

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

SumNeuron
источник
5

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

Хасан Юсеф
источник
4

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

Причина, по которой этот код не работает, состоит в том, что его 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));
Alex
источник
3

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

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

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

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

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

<svg viewBox="0 0 500 500">
    <path fill="red" d="
    M 250 250
    A 100 100 0 0 0 450 250
    Z"/> 
</svg>

введите описание изображения здесь

Вы установите начальную точку с 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))

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

GM
источник
3

Примечание для тех, кто ищет ответы (кем я тоже был) - если использование дуги не является обязательным , гораздо более простым решением для рисования круга является использование stroke-dasharraySVG <circle>.

Разделите массив штрихов на два элемента и масштабируйте их диапазон до нужного угла. Начальный угол можно отрегулировать с помощью stroke-dashoffset.

Ни одного косинуса в поле зрения.

Полный пример с объяснениями: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
источник
2

Оригинальная функция polarToCartesian от 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);

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

Wiley
источник
2

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

Версия 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;
};
MRE
источник
2
Возможно, вы могли бы прояснить пример, используя литералы шаблонов ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Рой Принс
0

Компонент 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;
парень
источник
0

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

https://jsfiddle.net/tyw6nfee/

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

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
Джавад Бат
источник
1
Я сделал некоторые изменения в вашем фрагменте, чтобы вывести визуальный результат. Посмотрите: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0