Как рассчитать SVG Path для дуги (круга) - PullRequest
169 голосов
/ 21 апреля 2011

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

Ответы [ 13 ]

328 голосов
/ 27 августа 2013

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

Живая демоверсия

121 голосов
/ 21 апреля 2011

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

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

17 голосов
/ 04 июля 2014

Я слегка изменил ответ 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 голосов
/ 04 апреля 2017

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

и вот, пожалуйста!

4 голосов
/ 09 августа 2016

Я хотел бы прокомментировать ответ @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));
3 голосов
/ 03 мая 2018

Изображение и немного 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 из 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))

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

3 голосов
/ 13 января 2017
Ответы

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

@ Джитин исправил проблему 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>
2 голосов
/ 11 января 2018

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;
};
2 голосов
/ 29 августа 2016

Небольшое изменение ответа @ 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));
1 голос
/ 22 октября 2014

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...