<path> 節點有五個直線命令。第一個命令是“移動到”或 M,上面已描述過。它接受兩個引數,一個 x 座標 (x) 和一個 y 座標 (y) 以移動到。如果游標已經在頁面上的某個位置,則不會繪製線條來連線這兩個位置。“移動到”命令出現在路徑的開頭,用於指定繪圖的起始位置。例如:
M x y
(or)
m dx dy
在下面的例子中,只有一個點在 (10, 10)。但是請注意,如果路徑只是正常繪製,它不會顯示。例如:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<path d="M10 10" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<circle cx="10" cy="10" r="3" fill="red" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
有三個命令用於繪製線條。最通用的是“畫線到”命令,用 L 呼叫。L 接受兩個引數——x 和 y 座標——並從當前位置繪製一條線到新位置。
L x y
(or)
l dx dy
有兩種縮寫形式用於繪製水平線和垂直線。H 繪製水平線,V 繪製垂直線。這兩個命令都只接受一個引數,因為它們只沿一個方向移動。
H x
(or)
h dx
V y
(or)
v dy
一個簡單的開始是繪製一個形狀。我們將從一個矩形開始(與使用 <rect> 元素可以更輕鬆地建立的矩形型別相同)。它只由水平線和垂直線組成。
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 10 H 90 V 90 H 10 L 10 10" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<circle cx="10" cy="10" r="3" fill="red" />
<circle cx="90" cy="90" r="3" fill="red" />
<circle cx="90" cy="10" r="3" fill="red" />
<circle cx="10" cy="90" r="3" fill="red" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
我們可以透過使用“閉合路徑”命令(用 Z 呼叫)來稍微縮短上面的路徑宣告。此命令從當前位置繪製一條直線回到第一個未閉合點(如果存在,則為最後一個 Z 命令後的第一個點,否則為路徑中的第一個點),並以線連線閉合路徑。它通常放在路徑節點的末尾,但並非總是如此。大寫和小寫命令之間沒有區別。
Z
(or)
z
所以我們上面的路徑可以縮短為
<path d="M 10 10 H 90 V 90 H 10 Z" fill="transparent" stroke="black" />
這些命令的相對形式也可以用來繪製相同的圖片。相對命令透過使用小寫字母呼叫,它們不是將游標移動到精確的座標,而是相對於其最後位置移動。例如,由於我們的矩形是 80×80,<path> 元素可以寫成
<path d="M 10 10 h 80 v 80 h -80 Z" fill="transparent" stroke="black" />
路徑將移動到點 (10, 10),然後向右水平移動 80 個點,然後向下 80 個點,然後向左 80 個點,然後返回到起點。
在這些示例中,使用 <polygon> 或 <polyline> 元素可能更直觀。然而,路徑在 SVG 繪圖中經常使用,開發者可能更習慣使用它們。使用其中一個並沒有真正的效能損失或增益。
三次曲線 C 是一種稍微複雜的曲線。三次貝塞爾曲線為每個點接受兩個控制點。因此,要建立三次貝塞爾曲線,需要指定三組座標。
C x1 y1, x2 y2, x y
(or)
c dx1 dy1, dx2 dy2, dx dy
最後一組座標 (x, y) 指定線條應在哪裡結束。另外兩個是控制點。(x1, y1) 是曲線起點的控制點,(x2, y2) 是曲線終點的控制點。控制點本質上描述了從每個點開始的線的斜率。然後,貝塞爾函式建立一條平滑曲線,從線起點建立的斜率過渡到另一端的斜率。
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 10 C 20 20, 40 20, 50 10" stroke="black" fill="transparent" />
<path d="M 70 10 C 70 20, 110 20, 110 10" stroke="black" fill="transparent" />
<path
d="M 130 10 C 120 20, 180 20, 170 10"
stroke="black"
fill="transparent" />
<path d="M 10 60 C 20 80, 40 80, 50 60" stroke="black" fill="transparent" />
<path d="M 70 60 C 70 80, 110 80, 110 60" stroke="black" fill="transparent" />
<path
d="M 130 60 C 120 80, 180 80, 170 60"
stroke="black"
fill="transparent" />
<path
d="M 10 110 C 20 140, 40 140, 50 110"
stroke="black"
fill="transparent" />
<path
d="M 70 110 C 70 140, 110 140, 110 110"
stroke="black"
fill="transparent" />
<path
d="M 130 110 C 120 140, 180 140, 170 110"
stroke="black"
fill="transparent" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference"></g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
// prettier-ignore
const points = [
[[10, 10], [20, 20], [40, 20], [50, 10]],
[[70, 10], [70, 20], [110, 20], [110, 10]],
[[130, 10], [120, 20], [180, 20], [170, 10]],
[[10, 60], [20, 80], [40, 80], [50, 60]],
[[70, 60], [70, 80], [110, 80], [110, 60]],
[[130, 60], [120, 80], [180, 80], [170, 60]],
[[10, 110], [20, 140], [40, 140], [50, 110]],
[[70, 110], [70, 140], [110, 140], [110, 110]],
[[130, 110], [120, 140], [180, 140], [170, 110]],
];
for (const curvePoints of points) {
for (const p of curvePoints) {
const circle = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle",
);
circle.setAttribute("cx", p[0]);
circle.setAttribute("cy", p[1]);
circle.setAttribute("r", 1.5);
circle.setAttribute("fill", "red");
g.appendChild(circle);
}
const line1 = document.createElementNS("http://www.w3.org/2000/svg", "line");
line1.setAttribute("x1", curvePoints[0][0]);
line1.setAttribute("y1", curvePoints[0][1]);
line1.setAttribute("x2", curvePoints[1][0]);
line1.setAttribute("y2", curvePoints[1][1]);
line1.setAttribute("stroke", "red");
g.appendChild(line1);
const line2 = document.createElementNS("http://www.w3.org/2000/svg", "line");
line2.setAttribute("x1", curvePoints[2][0]);
line2.setAttribute("y1", curvePoints[2][1]);
line2.setAttribute("x2", curvePoints[3][0]);
line2.setAttribute("y2", curvePoints[3][1]);
line2.setAttribute("stroke", "red");
g.appendChild(line2);
}
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
上面的例子建立了九條三次貝塞爾曲線。隨著曲線向右移動,控制點在水平方向上散開。隨著曲線向下移動,它們與端點之間的距離變得更遠。這裡需要注意的是,曲線沿第一個控制點的方向開始,然後彎曲,使其沿第二個控制點的方向到達。
可以將多條貝塞爾曲線串聯起來,以建立擴充套件的平滑形狀。通常,點一側的控制點將是另一側使用的控制點的反射,以保持斜率恆定。在這種情況下,可以使用三次貝塞爾曲線的快捷版本,用命令 S(或 s)表示。
S x2 y2, x y
(or)
s dx2 dy2, dx dy
S 生成與之前相同型別的曲線——但如果它跟隨另一個 S 命令或 C 命令,則假定第一個控制點是先前使用的控制點的反射。如果 S 命令不跟隨另一個 S 或 C 命令,則將游標的當前位置用作第一個控制點。結果與 Q 命令使用相同引數生成的結果不同,但相似。
下面顯示了這種語法的一個示例,左圖中紅色顯示了指定的控制點,藍色顯示了推斷的控制點。
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path
d="M 10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"
stroke="black"
fill="transparent" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<line x1="10" y1="80" x2="40" y2="10" stroke="red" />
<line x1="65" y1="10" x2="95" y2="80" stroke="red" />
<line x1="95" y1="80" x2="125" y2="150" stroke="blue" />
<line x1="150" y1="150" x2="180" y2="80" stroke="red" />
<circle cx="10" cy="80" r="3" fill="red" />
<circle cx="40" cy="10" r="3" fill="red" />
<circle cx="65" cy="10" r="3" fill="red" />
<circle cx="95" cy="80" r="3" fill="red" />
<circle cx="125" cy="150" r="3" fill="blue" />
<circle cx="150" cy="150" r="3" fill="red" />
<circle cx="180" cy="80" r="3" fill="red" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
另一種貝塞爾曲線,用 Q 呼叫的二次曲線,實際上比三次曲線更簡單。它需要一個控制點,該控制點確定曲線在起點和終點的斜率。它接受兩個引數:控制點和曲線的終點。
注意: q 的座標增量都相對於前一個點(也就是說,dx 和 dy 不相對於 dx1 和 dy1)。
Q x1 y1, x y
(or)
q dx1 dy1, dx dy
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 80 Q 95 10 180 80" stroke="black" fill="transparent" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<line x1="10" y1="80" x2="95" y2="10" stroke="red" />
<line x1="95" y1="10" x2="180" y2="80" stroke="red" />
<circle cx="10" cy="80" r="3" fill="red" />
<circle cx="180" cy="80" r="3" fill="red" />
<circle cx="95" cy="10" r="3" fill="red" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
與三次貝塞爾曲線一樣,有一個快捷方式用於將多個二次貝塞爾曲線串聯起來,用 T 呼叫。
T x y
(or)
t dx dy
此快捷方式檢視先前使用的控制點並從中推斷出一個新控制點。這意味著在第一個控制點之後,可以透過僅指定終點來建立相當複雜的形狀。
這僅在之前的命令是 Q 或 T 命令時有效。否則,控制點被假定與前一個點相同,並且只會繪製線條。
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path
d="M 10 80 Q 52.5 10, 95 80 T 180 80"
stroke="black"
fill="transparent" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<line x1="10" y1="80" x2="52.5" y2="10" stroke="red" />
<line x1="52.5" y1="10" x2="95" y2="80" stroke="red" />
<line x1="95" y1="80" x2="137.5" y2="150" stroke="blue" />
<line x1="137.5" y1="150" x2="180" y2="80" stroke="blue" />
<circle cx="10" cy="80" r="3" fill="red" />
<circle cx="52.5" cy="10" r="3" fill="red" />
<circle cx="95" cy="80" r="3" fill="red" />
<circle cx="137.5" cy="150" r="3" fill="blue" />
<circle cx="180" cy="80" r="3" fill="blue" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
兩種曲線都會產生相似的結果,儘管三次曲線在曲線的具體外觀上提供了更大的自由度。決定使用哪種曲線取決於具體情況以及線條的對稱性。
另一種可以使用 SVG 建立的曲線是弧線,透過 A 命令呼叫。弧線是圓形或橢圓的一部分。
對於給定的 x 半徑和 y 半徑,可以連線任意兩個點的橢圓有兩個(只要它們在圓的半徑內)。沿著這兩個圓中的任何一個,有兩條可能的路徑可以連線這些點——因此在任何情況下,都有四條可能的弧線可用。
正因為如此,弧線需要相當多的引數
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
在開始時,弧線元素接受 x 半徑和 y 半徑的兩個引數。如果需要,請參閱 <ellipse> 及其行為。最後兩個引數指定繪製終點的 x 和 y 座標。這四個值共同定義了弧線的基本結構。
第三個引數描述了弧線的旋轉。這最好透過一個例子來解釋
<svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
<path
d="M 10 315
L 110 215
A 30 50 0 0 1 162.55 162.45
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10"
stroke="black"
fill="green"
stroke-width="2"
fill-opacity="0.5" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<ellipse
cx="136.225"
cy="188.275"
rx="30"
ry="50"
stroke="red"
fill="none" />
<ellipse
cx="193.5"
cy="131.5"
rx="30"
ry="50"
stroke="red"
fill="none"
transform="rotate(-45)"
transform-origin="193.5 131.5" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
該示例顯示一個對角線穿過頁面的 <path> 元素。在其中心,有兩個橢圓形弧線被切出(x 半徑 = 30,y 半徑 = 50)。在第一個中,x 軸旋轉保持為 0,因此弧線圍繞的橢圓(顯示為灰色)是垂直方向的。然而,對於第二個弧線,x 軸旋轉設定為 -45 度。這旋轉了橢圓,使其與路徑方向的小軸對齊,如示例影像中的第二個橢圓所示。
對於上圖中未旋轉的橢圓,只有兩個不同的弧線,而不是四個可供選擇,因為從弧線起點到終點繪製的線穿過橢圓的中心。在一個稍作修改的例子中,可以看到形成四個不同弧線的兩個橢圓
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320">
<path
d="M 10 315
L 110 215
A 36 60 0 0 1 150.71 170.29
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10"
stroke="black"
fill="green"
stroke-width="2"
fill-opacity="0.5" />
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<circle cx="150.71" cy="170.29" r="3" fill="red" />
<circle cx="110" cy="215" r="3" fill="red" />
<ellipse
cx="144.931"
cy="229.512"
rx="36"
ry="60"
fill="transparent"
stroke="red" />
<ellipse
cx="115.779"
cy="155.778"
rx="36"
ry="60"
fill="transparent"
stroke="red" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
請注意,每個藍色橢圓都由兩個弧線形成,具體取決於順時針或逆時針方向。每個橢圓都有一條短弧線和一條長弧線。這兩個橢圓只是彼此的映象。它們沿著從起點到終點形成的線翻轉。
如果起點到終點之間的距離超出了橢圓的 x 和 y 半徑所能達到的範圍,則橢圓的半徑將被最小程度地擴充套件,以便它能夠到達起點到終點。此頁面底部的互動式程式碼筆很好地演示了這一點。要確定橢圓的半徑是否足夠大以需要擴充套件,需要解決一個方程組,例如 Wolfram Alpha 上的這個。此計算用於未旋轉的橢圓,起點到終點為 (110, 215) → (150.71, 170.29)。解 (x, y) 是橢圓的中心。如果橢圓的半徑太小,解將是 虛數。第二次計算用於未旋轉的橢圓,起點到終點為 (110, 215) → (162.55, 162.45)。解有一個小的虛數分量,因為橢圓剛好被擴充套件了。
上面提到的四種不同的路徑由接下來的兩個引數標誌決定。如前所述,仍然有兩個可能的橢圓可供路徑圍繞,並且在兩個橢圓上都有兩條不同的可能路徑,總共有四條可能的路徑。第一個引數是 large-arc-flag。它決定弧線應大於還是小於 180 度;最終,這個標誌決定了弧線將圍繞給定圓的哪個方向行駛。第二個引數是 sweep-flag。它決定弧線應該以正角度還是負角度開始移動,這實際上選擇將圍繞兩個圓中的哪一個行駛。下面的示例顯示了所有四種可能的組合,以及每種情況的兩個圓。
<svg width="360" height="360" xmlns="http://www.w3.org/2000/svg">
<path
d="M 100 100
A 45 45, 0, 0, 0, 145 145
L 145 100 Z"
fill="#00FF00A0"
stroke="black"
stroke-width="2" />
<path
d="M 250 100
A 45 45, 0, 1, 0, 295 145
L 295 100 Z"
fill="#FF0000A0"
stroke="black"
stroke-width="2" />
<path
d="M 100 250
A 45 45, 0, 0, 1, 145 295
L 145 250 Z"
fill="#FF00FFA0"
stroke="black"
stroke-width="2" />
<path
d="M 250 250
A 45 45, 0, 1, 1, 295 295
L 295 250 Z"
fill="#0000FFA0"
stroke="black"
stroke-width="2" />
<path
d="M 45 45 L 345 45 L 345 345 L 45 345 Z M 195 45 L 195 345 M 45 195 L 345 195"
fill="none"
stroke="black" />
<text x="140" y="20" font-size="20" fill="black">Large arc flag</text>
<text
x="-15"
y="195"
font-size="20"
fill="black"
transform="rotate(-90)"
transform-origin="20 195">
Sweep flag
</text>
<text x="120" y="40" font-size="20" fill="black">0</text>
<text x="270" y="40" font-size="20" fill="black">1</text>
<text x="30" y="120" font-size="20" fill="black">0</text>
<text x="30" y="270" font-size="20" fill="black">1</text>
</svg>
<svg style="display:none" xmlns="http://www.w3.org/2000/svg">
<g id="reference">
<circle cx="145" cy="100" r="45" stroke="#888888E0" fill="none" />
<circle cx="100" cy="145" r="45" stroke="#888888E0" fill="none" />
<circle cx="295" cy="100" r="45" stroke="#888888E0" fill="none" />
<circle cx="250" cy="145" r="45" stroke="#888888E0" fill="none" />
<circle cx="145" cy="250" r="45" stroke="#888888E0" fill="none" />
<circle cx="100" cy="295" r="45" stroke="#888888E0" fill="none" />
<circle cx="295" cy="250" r="45" stroke="#888888E0" fill="none" />
<circle cx="250" cy="295" r="45" stroke="#888888E0" fill="none" />
</g>
</svg>
<button>Show/hide reference points and lines</button>
const g = document.querySelector("#reference");
const svg = document.querySelector("svg");
const button = document.querySelector("button");
let isHidden = true;
button.addEventListener("click", () => {
isHidden = !isHidden;
if (isHidden) {
svg.querySelector("#reference").remove();
} else {
svg.appendChild(g.cloneNode(true));
}
});
弧線是建立圖形中圓形或橢圓形部分的簡單方法。例如,餅圖的每個部分都需要不同的弧線。
如果從 <canvas> 切換到 SVG,弧線可能是最難學習的部分,但它們也更強大。完整的圓形和橢圓是 SVG 弧線難以繪製的唯一形狀。因為圍繞圓的任何路徑的起點和終點是同一個點,所以可以選擇的圓的數量是無限的,並且實際路徑是未定義的。可以透過使路徑的起點和終點稍微偏斜,然後用另一個路徑段連線它們來近似它們。例如,可以透過為每個半圓使用一個弧線來製作一個圓。在這一點上,通常更容易使用真正的 <circle> 或 <ellipse> 節點。這個互動式演示可能有助於理解 SVG 弧線背後的概念。
<script src="https://cdn.rawgit.com/lingtalfi/simpledrag/master/simpledrag.js"></script>
<div class="ui">
<div class="controls">
Radius X: <input id="rx" type="range" min="0" max="500" /><br />
Radius Y: <input id="ry" type="range" min="0" max="500" /><br />
Rotation:
<input id="rot" type="range" min="0" max="360" value="0" /><br />
Large arc flag: <input id="laf" type="checkbox" /><br />
Sweep flag: <input id="sf" type="checkbox" /><br />
Arc command: <span id="arc-value"></span><br />
</div>
<div class="results">
mouse: pageX <span id="page-x"></span>, pageY <span id="page-y"></span
><br />
A: <span id="ax-value"></span>, <span id="ay-value"></span><br />
B: <span id="bx-value"></span>, <span id="by-value"></span><br />
m: <span id="m-value"></span><br />
b(A): <span id="ba-value"></span><br />
b(B): <span id="bb-value"></span><br />
contextWidth: <span id="cw-value"></span><br />
</div>
</div>
<svg width="100%" height="100%" id="svg-context">
<path id="arc2" d="" fill="none" stroke="green" stroke-width="2"></path>
<path id="arc3" d="" fill="none" stroke="green" stroke-width="2"></path>
<path id="arc4" d="" fill="none" stroke="green" stroke-width="2"></path>
<path
id="arc"
d="M100 100 A 100 100 0 1 0 200 100"
fill="none"
stroke="red"
stroke-width="4"></path>
<line
id="line0"
x1="0"
y1="0"
x2="0"
y2="0"
fill="none"
stroke="black"
stroke-width="2"></line>
<line
id="line"
x1="0"
y1="0"
x2="0"
y2="0"
fill="none"
stroke="black"
stroke-width="2"></line>
<line
id="line2"
x1="0"
y1="0"
x2="0"
y2="0"
fill="none"
stroke="black"
stroke-width="2"></line>
<circle
id="circle1"
cx="100"
cy="100"
r="5"
fill="red"
stroke="red"
stroke-width="2"></circle>
<circle
id="circle2"
cx="200"
cy="100"
r="5"
fill="red"
stroke="red"
stroke-width="2"></circle>
</svg>
body {
position: fixed;
width: 100%;
height: 100%;
background: #eeeeee;
}
.ui {
display: flex;
}
.ui > div {
margin: 0 10px;
}
.ui .controls input {
vertical-align: middle;
}
#circle1,
#circle2 {
cursor: pointer;
}
svg {
background: #dddddd;
}
const svgContext = document.getElementById("svg-context");
let rect = svgContext.getBoundingClientRect(); // helper to enclose mouse coordinates into svg box
const pageXEl = document.getElementById("page-x");
const pageYEl = document.getElementById("page-y");
const mEl = document.getElementById("m-value");
const rxEl = document.getElementById("rx");
const ryEl = document.getElementById("ry");
const rotEl = document.getElementById("rot");
const lafEl = document.getElementById("laf");
const sfEl = document.getElementById("sf");
const axEl = document.getElementById("ax-value");
const ayEl = document.getElementById("ay-value");
const bxEl = document.getElementById("bx-value");
const byEl = document.getElementById("by-value");
const baEl = document.getElementById("ba-value");
const bbEl = document.getElementById("bb-value");
const circle1 = document.getElementById("circle1");
const circle2 = document.getElementById("circle2");
const line = document.getElementById("line");
const line0 = document.getElementById("line0");
const line2 = document.getElementById("line2");
const cwEl = document.getElementById("cw-value");
const arcCmdEl = document.getElementById("arc-value");
const arcEl = document.getElementById("arc");
const arc2El = document.getElementById("arc2");
const arc3El = document.getElementById("arc3");
const arc4El = document.getElementById("arc4");
function updatePaths(pageX, pageY) {
pageXEl.textContent = pageX;
pageYEl.textContent = pageY;
// line between two points
line.setAttribute("x1", circle1.getAttribute("cx"));
line.setAttribute("y1", circle1.getAttribute("cy"));
line.setAttribute("x2", circle2.getAttribute("cx"));
line.setAttribute("y2", circle2.getAttribute("cy"));
axEl.textContent = circle1.getAttribute("cx");
ayEl.textContent = circle1.getAttribute("cy");
bxEl.textContent = circle2.getAttribute("cx");
byEl.textContent = circle2.getAttribute("cy");
// y = mx + b
let m, b, run; // m = rise/run = (y2-y1) / (x2-x1)
if (circle1.getAttribute("cx") <= circle2.getAttribute("cx")) {
run = circle2.getAttribute("cx") - circle1.getAttribute("cx");
if (run !== 0) {
m = (circle2.getAttribute("cy") - circle1.getAttribute("cy")) / run;
}
} else {
run = circle1.getAttribute("cx") - circle2.getAttribute("cx");
if (run !== 0) {
m = (circle1.getAttribute("cy") - circle2.getAttribute("cy")) / run;
}
}
if (run !== 0) {
// b = y - mx
b = circle1.getAttribute("cy") - m * circle1.getAttribute("cx");
b2 = circle2.getAttribute("cy") - m * circle2.getAttribute("cx");
baEl.textContent = b;
bbEl.textContent = b2;
mEl.textContent = m;
// draw segment from the left vertical axis (x=0) to the left most point (A or B).
// x=0 ----> y = b
let leftMost, rightMost;
if (circle1.getAttribute("cx") <= circle2.getAttribute("cx")) {
leftMost = circle1;
rightMost = circle2;
} else {
leftMost = circle2;
rightMost = circle1;
}
line0.setAttribute("x1", 0);
line0.setAttribute("y1", b);
line0.setAttribute("x2", leftMost.getAttribute("cx"));
line0.setAttribute("y2", leftMost.getAttribute("cy"));
// draw segment from point B to the right vertical axis (x=rect.width)
// representing the end of the svg box.
// y = mx + b
const y = m * rect.width + b;
line2.setAttribute("x1", rightMost.getAttribute("cx"));
line2.setAttribute("y1", rightMost.getAttribute("cy"));
line2.setAttribute("x2", rect.width);
line2.setAttribute("y2", y);
// now update the arc
const arcCmd = getArcCommand(
leftMost,
rightMost,
lafEl.checked,
sfEl.checked,
);
arcCmdEl.textContent = arcCmd;
arcEl.setAttribute("d", arcCmd);
// now update the other helper arcs
const combo = [
[true, true],
[true, false],
[false, true],
[false, false],
].filter(
(item) => !(item[0] === lafEl.checked && item[1] === sfEl.checked),
);
arc2El.setAttribute(
"d",
getArcCommand(leftMost, rightMost, combo[0][0], combo[0][1]),
);
arc3El.setAttribute(
"d",
getArcCommand(leftMost, rightMost, combo[1][0], combo[1][1]),
);
arc4El.setAttribute(
"d",
getArcCommand(leftMost, rightMost, combo[2][0], combo[2][1]),
);
}
}
function getArcCommand(leftMost, rightMost, lafChecked, sfChecked) {
return `M${leftMost.getAttribute("cx")} ${leftMost.getAttribute("cy")} A ${rxEl.value} ${ryEl.value} ${rotEl.value} ${lafChecked ? "1" : "0"} ${sfChecked ? "1" : "0"} ${rightMost.getAttribute("cx")} ${rightMost.getAttribute("cy")}`;
}
function updateScreen() {
rect = svgContext.getBoundingClientRect();
cwEl.textContent = rect.width;
}
circle1.sdrag((el, pageX, startX, pageY, startY) => {
pageX -= rect.left;
pageY -= rect.top;
el.setAttribute("cx", pageX);
el.setAttribute("cy", pageY);
updatePaths(pageX, pageY);
});
circle2.sdrag((el, pageX, startX, pageY, startY) => {
pageX -= rect.left;
pageY -= rect.top;
el.setAttribute("cx", pageX);
el.setAttribute("cy", pageY);
updatePaths(pageX, pageY);
});
window.addEventListener("resize", updateScreen);
// sliders
["rx", "ry", "rot"].forEach((id) => {
document.getElementById(id).addEventListener("input", (e) => {
updatePaths();
});
});
// checkboxes
["laf", "sf"].forEach((id) => {
document.getElementById(id).addEventListener("change", (e) => {
updatePaths();
});
});
updatePaths();
updateScreen();