Rysowanie okręgu ze ścieżką łuku SVG


146

Krótkie pytanie: używając ścieżki SVG możemy narysować 99,99% okręgu i to się pojawia, ale gdy jest to 99,999999999% okręgu, to koło się nie pojawi. Jak to naprawić?

Następująca ścieżka SVG może narysować 99,99% okręgu: (wypróbuj na http://jsfiddle.net/DFhUF/1381/ i zobacz, czy widzisz 4 łuki, czy tylko 2 łuki, ale zwróć uwagę, że jeśli to IE, to jest renderowane w VML, a nie SVG, ale mają podobny problem)

M 100 100 a 50 50 0 1 0 0.00001 0

Ale kiedy jest to 99,999999999% koła, to w ogóle nic się nie pokaże?

M 100 100 a 50 50 0 1 0 0.00000001 0    

I tak samo jest ze 100% okręgu (to nadal jest łuk, prawda, tylko bardzo pełny łuk)

M 100 100 a 50 50 0 1 0 0 0 

Jak można to naprawić? Powodem jest to, że używam funkcji do rysowania procentu łuku, a jeśli potrzebuję „specjalnego przypadku” 99,9999% lub 100% łuku, aby użyć funkcji okręgu, to byłoby trochę głupie.

Ponownie, przypadek testowy na jsfiddle przy użyciu RaphaelJS znajduje się pod adresem http://jsfiddle.net/DFhUF/1381/
(a jeśli jest to VML w IE 8, nawet drugie kółko się nie pokaże ... musisz zmienić to na 0,01)


Aktualizacja:

Dzieje się tak, ponieważ w naszym systemie renderuję łuk dla wyniku, więc 3,3 punktu otrzymuje 1/3 koła. 0,5 daje pół koła, a 9,9 punktów to 99% koła. Ale co, jeśli w naszym systemie są wyniki 9,99? Czy muszę sprawdzić, czy jest blisko 99,999% koła i odpowiednio użyć arcfunkcji lub circlefunkcji? A co z wynikiem 9,9987? Którego użyć? To śmieszne, gdy trzeba wiedzieć, jakiego rodzaju wyniki będą mapowane na „zbyt pełne koło” i przełączać się na funkcję koła, a kiedy jest to „pewne 99,9%” koła lub 9,9987, należy użyć funkcji łuku .


@minitech oczywiście działa ... to wtedy 99% koła (tak z grubsza mówiąc). Chodzi o to, że może rysować 98%, 99%, 99,99% koła, ale nie 99,9999999% ani 100%
niepolarność.

1
Oba te linki prowadzą do tego samego i działa dobrze w Safari.
Mark Bessey,

tak, ten sam link, po prostu chcę, aby ludzie wcześniej widzieli przypadek testowy, więc dodaję link na początku pytania. Właściwe safari to zrobi, jak miło ... Chrome i Firefox nie ... trochę dziwne, ponieważ Safari i Chrome to Webkit ... ale czy silnik SVG zależy od Webkita?
nonopolarity

@Marcin wygląda dobrze, jak? czy widzisz 4 łuki czy 2 łuki? czy nawet spojrzałeś na kod?
nonopolarity

2
uaktualniony jsfiddle z Raphael włączone jako biblioteki, aby ominąć krzyż pochodzenia Błąd ładowania Raphael.js: jsfiddle.net/DFhUF/1381
ericsoco

Odpowiedzi:


35

To samo dotyczy łuku XAML. Po prostu zamknij łuk 99,99% za pomocą Zi masz koło!


więc czy możesz zamknąć trzecią skrzynkę w próbce jsfiddle i sprawić, by działała?
nonopolarity

z obniżeniem wartości do 0,0001, tak. problem wydaje się związany z implementacją WebKit w różnych przeglądarkach, a nie z samym SVG. Ale w ten sposób tworzysz okrąg w komponencie SVG / XAML Arc.
Todd Main

Ahhh, oszukiwanie, zawsze najlepsze rozwiązanie. Nadal trzeba obsłużyć 100% przypadek, ale po prostu „zaokrąglając w dół” do 99,999%, a nie całą oddzielną gałąź kodu.
SimonR

Jakieś demo? Ta odpowiedź nie spełnia
Medet Tleukabiluly

@MedetTleukabiluly masz łuk powyżej w pytaniu, dodaj zna końcu i gotowe. Być może trzeba będzie usunąć zera, na przykład w najnowszym Chrome musiałem napisać, 0.0001aby to działało. Jeśli szukasz miejsca na ścieżkę, spójrz na Paths na MDN .
TWiStErRob

415

Wiem, że w grze jest trochę za późno, ale zapamiętałem to pytanie z czasów, gdy było nowe i miałem podobny dylemat i przypadkowo znalazłem „właściwe” rozwiązanie, jeśli ktoś jeszcze takiego szuka:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,0 (r * 2),0
    a r,r 0 1,0 -(r * 2),0
    "
/>

Innymi słowy, to:

<circle cx="100" cy="100" r="75" />

można osiągnąć jako ścieżkę za pomocą tego:

  <path 
        d="
        M 100, 100
        m -75, 0
        a 75,75 0 1,0 150,0
        a 75,75 0 1,0 -150,0
        "
  />

Sztuczka polega na tym, aby mieć dwa łuki, drugi wychwytujący w miejscu, w którym skończył się pierwszy i używając ujemnej średnicy, aby wrócić do pierwotnego punktu początkowego łuku.

Powodem, dla którego nie można tego zrobić jako pełnego koła w jednym łuku (i tylko spekuluję), jest to, że kazałbyś mu narysować łuk od siebie samego (powiedzmy 150,150) do siebie (150,150), który renderuje jako „och, już tam jestem, łuk nie jest potrzebny!”.

Zalety oferowanego przeze mnie rozwiązania to:

  1. łatwo jest przetłumaczyć z okręgu bezpośrednio na ścieżkę i
  2. dwie linie łuków nie nakładają się (co może powodować problemy, jeśli używasz markerów lub wzorów itp.). To czysta, ciągła linia, choć narysowana w dwóch częściach.

Nic z tego nie miałoby znaczenia, gdyby po prostu zezwalały ścieżkom tekstowym na akceptowanie kształtów. Ale myślę, że unikają tego rozwiązania, ponieważ elementy kształtu, takie jak koło, nie mają technicznie punktu „początkowego”.

Demo jsfiddle: http://jsfiddle.net/crazytonyi/mNt2g/

Aktualizacja:

Jeśli używasz ścieżki jako textPathodniesienia i chcesz, aby tekst był renderowany na zewnętrznej krawędzi łuku, użyłbyś dokładnie tej samej metody, ale zmień flagę przeciągnięcia z 0 na 1, tak aby traktowała zewnętrzną stronę łuku ścieżka jako powierzchnia zamiast wnętrza (pomyśl o 1,0kimś siedzącym w środku i rysującym okrąg wokół siebie, podczas 1,1gdy ktoś spaceruje po środku w promieniu i ciągnie obok siebie kredę, jeśli to w ogóle pomoże). Oto kod jak powyżej, ale ze zmianą:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,1 (r * 2),0
    a r,r 0 1,1 -(r * 2),0
    "
/>

1
+1, dzięki za formuły. Oczywiście pierwsze dwa polecenia można by skonsolidować.
John Gietzen,

+1 za ogólną przejrzystość i praktyczność rozwiązania, ale szczególnie w przypadku „elementów kształtu, takich jak okrąg, technicznie rzecz biorąc, nie mają punktu„ początkowego ”” - tak jasny sposób wyrażenia problemu.
David John Welsh

11
Myślę, że problem nie polega na tym, że „och, już tam jestem, łuk nie jest potrzebny!”, Ponieważ podając 1 jako flagę dużego łuku, wyraźnie mówisz silnikowi, że chcesz, aby poszedł dalej. Prawdziwym problemem jest to, że istnieje nieskończenie wiele okręgów, które spełniają ograniczenia przejścia przez twój punkt. To wyjaśnia, dlaczego jeśli chcesz łuku 360 *, silnik nie wie, co robić. Powodem, dla którego to nie działa dla 359,999, jest to, że jest to obliczenie niestabilne numerycznie i chociaż technicznie istnieje dokładnie jedno kółko, które pasuje do żądania, prawdopodobnie i tak nie byłby to ten, którego chciałeś
qbolec

@qbolec - Masz rację, myślałem w kategoriach wyłączenia dużego łuku i odchylenia, a łuk nie ma celu. Lub przynajmniej, że nawet przy dużym łuku i przeciągnięciu umożliwiało istnienie pewnego fundamentalnego projektu, który definiował łuk jako krzywą między dwoma punktami (tak, że gdy jeden punkt został użyty dwukrotnie, zobaczył go jako zwinięty pojedynczy punkt). wizja łuku, który potencjalnie rysuje 360 ​​okręgów wokół jednego punktu, ma sens, chociaż zwinąłby się do jednego koła, gdyby zdefiniowano środek, co rodzi pytanie, czy pojedynczy łuk mógłby wykonać pełny okrąg, gdyby istniał środek?
Anthony

1
„Elementy kształtu, takie jak okrąg, technicznie rzecz biorąc, nie mają„ punktu początkowego ”. To nie jest prawdą. SVG określa absolutnie, gdzie znajduje się punkt początkowy wszystkich kształtów. Musi to zrobić, aby tablice kreskowe działały na kształtach.
Paul LeBeau

17

W odniesieniu do rozwiązania Anthony'ego, oto funkcja do uzyskania ścieżki:

function circlePath(cx, cy, r){
    return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}

10

Zupełnie inne podejście:

Zamiast majstrować przy ścieżkach, aby określić łuk w svg, możesz również wziąć element okręgu i określić tablicę kresek obrysów w pseudokodzie:

with $score between 0..1, and pi = 3.141592653589793238

$length = $score * 2 * pi * $r
$max = 7 * $r  (i.e. well above 2*pi*r)

<circle r="$r" stroke-dasharray="$length $max" />

Jego prostota jest główną zaletą w stosunku do metody z wieloma ścieżkami łuku (np. Podczas tworzenia skryptów podłączasz tylko jedną wartość i gotowe dla dowolnej długości łuku)

Łuk zaczyna się w skrajnym prawym punkcie i można go przesuwać za pomocą funkcji obracania.

Uwaga: Firefox ma dziwny błąd, w którym obrót o ponad 90 stopni lub więcej jest ignorowany. Aby więc rozpocząć łuk od góry, użyj:

<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />

należy pamiętać, że to rozwiązanie ma zastosowanie tylko w przypadkach, w których nie używasz żadnego rodzaju wypełnienia i potrzebujesz tylko segmentu łukowego bez odchyleń. W momencie, gdy chcesz narysować sektory, potrzebujesz nawet nowego węzła ścieżki svg, który w przeciwnym razie mógłby być obsługiwany w jednym znaczniku ścieżki.
mxfh

@mxfh niezupełnie, jeśli weźmiesz szerokość obrysu dwukrotnie większą od wartości r, otrzymasz idealne sektory.
mvds

Dzięki stroke-dasharray="0 10cm 5cm 1000cm"temu można uniknąć transformacji
stenci

@stenci tak, ale wadą jest to, że nie możesz mieć jednego parametru w dasharray do skalowania od 0% do 100% wypełnienia (co było jednym z moich celów)
mvds,

5

Adobe Illustrator używa krzywych Beziera, takich jak SVG, a dla okręgów tworzy cztery punkty. Możesz utworzyć okrąg za pomocą dwóch poleceń dotyczących łuku eliptycznego ... ale wtedy dla okręgu w SVG użyłbym <circle />:)


„Możesz utworzyć okrąg za pomocą dwóch poleceń dotyczących łuku eliptycznego”? co masz na myśli? Nie możesz po prostu użyć jednego? Przynajmniej przez przejście od (0,0) do (0,01, 0), aby coś renderować. Aby skorzystać z circle, zapoznaj się z aktualizacją w pytaniu.
nonopolarity

Nie, myślę, że nie możesz użyć tylko jednego. Pokazałeś, że nie możesz i masz podobny problem z łukami HTML5 Canvas.
Phrogz

masz na myśli, używając arcdo tworzenia pełnego koła, używając lewego i prawego łuku? Więc łuk nie może narysować pełnego koła ... aby to zrobić arcpotrzebne są dwie ścieżki
nonopolarity

2
@ 動靜 能量 Prawidłowo, potrzebne są dwie ścieżki łuku (lub brzydka przeróbka jednego łuku przechodzącego przez większość drogi, a następnie polecenie najbliższej ścieżki do linii prostej do końca).
Phrogz

1
Illustrator jest do bani. Nie możesz zrobić koła z krzywymi Beziera. Tylko coś zbliżonego do koła, ale nadal nie jest to koło.
adius

4

Dobrym pomysłem jest użycie dwóch poleceń łuku, aby narysować pełne koło.

zwykle używam elipsy lub koła, aby narysować pełne koło.


5
Głównym ograniczeniem SVG jest to, że elementów kształtu nie można używać w ścieżkach tekstowych. Jest to prawdopodobnie główna motywacja dla każdego, kto odwiedza to pytanie.
Anthony

1
@Anthony mogą teraz. Firefox obsługuje funkcję textPath dla dowolnego kształtu. Podejrzewam, że Chrome też.
Robert Longson,

@RobertLongson - Czy możesz podać przykład lub linki do przykładu? Ciekawe, jak decyduje, gdzie zaczyna się ścieżka tekstowa i czy jest na zewnątrz, czy wewnątrz (itp.), Kiedy jest rysowana bez tradycyjnego punktu początkowego.
Anthony

@Anthony Zobacz SVG 2 specyfikacja np w3.org/TR/SVG2/shapes.html#CircleElement również nowy textPath atrybut side
Robert Longson

4

Opierając się na odpowiedziach Anthony'ego i Antona, włączyłem możliwość obracania wygenerowanego koła bez wpływu na jego ogólny wygląd. Jest to przydatne, jeśli używasz ścieżki do animacji i musisz kontrolować, gdzie się zaczyna.

function(cx, cy, r, deg){
    var theta = deg*Math.PI/180,
        dx = r*Math.cos(theta),
        dy = -r*Math.sin(theta);
    return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}

Właśnie tego potrzebowałem. Używałem wtyczki greensock morph, aby zmienić ścieżkę koła na ścieżkę prostokątną i potrzebowałem niewielkiego obrotu, aby wyglądał dobrze.
RoboKozo

3

Dla takich jak ja, którzy szukali atrybutów elipsy do konwersji ścieżki:

const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0`

2

Zapisany jako funkcja wygląda tak:

function getPath(cx,cy,r){
  return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}

2
To świetna odpowiedź! Tylko mały dodatek: ta ścieżka jest rysowana na wschód, północ, zachód, południe. Jeśli chcesz, aby koło się zachowywać dokładnie jak <circle>, trzeba zmienić kierunek na wschód, południe, zachód, północ: "M" + cx + "," + cy + "m" + (r) + ",0" + "a" + r + "," + r + " 0 1,1 " + (-r * 2) + ",0" + "a" + r + "," + r + " 0 1,1 " + (r * 2) + ",0" Zaletą jest to, że stroke-dashoffseti startOffsetteraz działają w ten sam sposób.
loominade

2

Te odpowiedzi są zbyt skomplikowane.

Prostszy sposób na zrobienie tego bez tworzenia dwóch łuków lub konwertowania do różnych układów współrzędnych.

Zakłada się, że obszar roboczy ma szerokość wi wysokość h.

`M${w*0.5 + radius},${h*0.5}
 A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`

Po prostu użyj flagi „długi łuk”, aby cała flaga była wypełniona. Następnie wykonaj łuki 99,9999% pełnego koła. Wizualnie jest tak samo. Unikaj flagi zamiatania, zaczynając okrąg w najbardziej prawym punkcie okręgu (jeden promień bezpośrednio poziomo od środka).


1

Innym sposobem byłoby użycie dwóch sześciennych krzywych Beziera. To dla użytkowników iOS używających pocketSVG, który nie rozpoznaje parametru arc svg.

C x1 y1, x2 y2, xy (lub c dx1 dy1, dx2 dy2, dx dy)

Sześcienna krzywa Beziera

Ostatni zestaw współrzędnych tutaj (x, y) to miejsce, w którym linia ma się kończyć. Pozostałe dwa to punkty kontrolne. (x1, y1) to punkt kontrolny dla początku twojej krzywej, a (x2, y2) dla punktu końcowego twojej krzywej.

<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />

1

Zrobiłem jsfiddle, aby to zrobić tutaj:

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;       
}
console.log(describeArc(255,255,220,134,136))

połączyć

wszystko, co musisz zrobić, to zmienić dane wejściowe console.log i uzyskać wynik w konsoli


To było dokładnie to, czego szukałem. Najlepsza odpowiedź tutaj! upvote this guy
Måns Dahlström
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.