Як обчислити шлях SVG для дуги (кола)


191

Враховуючи коло з центром у (200200), радіусом 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

Відповіді:


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" />

Демонстраційна демонстрація


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.

Команда дуги має такі параметри:

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

Знову ж таки, я цього не перевіряв.

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

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

Таким чином, це побічний ефект дуг, що розглядається як частина більшого шляху, а не їх окремий об'єкт. Я вважаю, що якщо ваш SVG-рендер не завершений, він може просто пропустити будь-які компоненти контуру, він не знає, як відобразити, доки він знає, скільки аргументів вони беруть. А може, це дозволяє паралельне відображення різних фрагментів шляху з багатьма компонентами. Або, можливо, вони зробили це, щоб переконатися, що помилки округлення не накопичувались по довжині складного шляху.

Примітки щодо реалізації також корисні для початкового запитання, оскільки вони мають більше математичного псевдокоду для перетворення між двома параметрами (що я не усвідомлював, коли вперше написав цю відповідь).


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>

5
Схоже, що посилання на codepen для мене не працює (Chrome)
Ray Hulha,

Дуга намальована поза межами SVG. Збільшити висоту SVG та змінити centerX, centerYнаприклад , до 100.
А.Акрам

Ви також можете встановити вікно перегляду явно в батьківському елементі svg. НаприкладviewBox="0 0 500 500"
Håken Lid

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>

і ось ти йдеш!


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/AbodyLinuxUser/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

Образ і якийсь 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))

Ви можете мати більш детальне пояснення в цій публікації, яку я написав.


3

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

Розділіть масив тире на два елементи та масштабуйте їх діапазон до потрібного кута. Кут пуску можна регулювати за допомогою stroke-dashoffset.

Ні одного косинуса не видно.

Повний приклад з поясненнями: https://codepen.io/mjurczyk/pen/wvBKOvP


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

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


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

2
Можна, мабуть, зробити приклад зрозумілішим, використовуючи літеральні шаблони ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.