Skip to content

Commit c8b9b50

Browse files
authored
Merge pull request #187 from teamEPYC/alevikx-patch-63
Create arcText.js
2 parents ec39efb + 215ab3c commit c8b9b50

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

IDTA/arcText.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
(() => {
2+
// ---------- Constants ----------
3+
const SVG_NS = 'http://www.w3.org/2000/svg';
4+
5+
// Map data-text-align to <textPath startOffset> and <text text-anchor>
6+
const TEXT_ALIGN = {
7+
start: { startOffset: '0%', anchor: 'start' },
8+
center: { startOffset: '50%', anchor: 'middle' },
9+
end: { startOffset: '100%', anchor: 'end' },
10+
};
11+
12+
// ---------- Utilities ----------
13+
const toRadians = deg => (deg * Math.PI) / 180;
14+
const normAngle = deg => ((deg % 360) + 360) % 360; // normalize into [0, 360)
15+
16+
function buildArcPath(cx, cy, r, startDeg, endDeg) {
17+
// Ensure we draw a partial arc; SVG can't do a 360° sweep in one go
18+
const sweepDeg = ((endDeg - startDeg + 360) % 360) || 359.999;
19+
const largeArcFlag = sweepDeg > 180 ? 1 : 0;
20+
const sweepFlag = 1; // clockwise
21+
22+
const a1 = toRadians(startDeg);
23+
const a2 = toRadians(startDeg + sweepDeg);
24+
25+
const x1 = cx + r * Math.cos(a1);
26+
const y1 = cy + r * Math.sin(a1);
27+
const x2 = cx + r * Math.cos(a2);
28+
const y2 = cy + r * Math.sin(a2);
29+
30+
return `M ${x1} ${y1} A ${r} ${r} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
31+
}
32+
33+
function createSvgEl(name) {
34+
return document.createElementNS(SVG_NS, name);
35+
}
36+
37+
// ---------- Render ----------
38+
function renderArc(el) {
39+
// --- Size & geometry ---
40+
const width = Math.max(1, el.clientWidth);
41+
const height = Math.max(1, el.clientHeight);
42+
const cx = width / 2;
43+
const cy = height / 2;
44+
const rOuter = Math.min(cx, cy) - 1; // leave 1px padding
45+
46+
// --- Inputs from data-attributes ---
47+
const startRaw = Number(el.dataset.start);
48+
const endRaw = Number(el.dataset.end);
49+
const text = el.dataset.text ?? '';
50+
const strokeW = Number(el.dataset.strokeWidth) || 0;
51+
52+
const startDeg = Number.isFinite(startRaw) ? normAngle(startRaw) : 220;
53+
let endDeg = Number.isFinite(endRaw) ? normAngle(endRaw) : 360;
54+
if (endDeg === startDeg) endDeg = (endDeg + 359.999) % 360; // avoid full circle
55+
56+
const alignKey = (el.dataset.textAlign || 'start').toLowerCase();
57+
const { startOffset, anchor } = TEXT_ALIGN[alignKey] || TEXT_ALIGN.start;
58+
59+
// --- Create or reuse SVG structure ---
60+
let svg = el.querySelector('svg[data-arc-el]');
61+
let strokePath, textGuidePath, textEl, textPath;
62+
63+
if (!svg) {
64+
// Clear any existing content (so inner text doesn't double-render)
65+
el.innerHTML = '';
66+
67+
svg = createSvgEl('svg');
68+
svg.setAttribute('data-arc-el', '');
69+
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
70+
svg.style.width = '100%';
71+
svg.style.height = '100%';
72+
73+
// Visible arc path (styled via CSS; color from .arc-stroke)
74+
strokePath = createSvgEl('path');
75+
strokePath.setAttribute('class', 'arc-stroke');
76+
strokePath.setAttribute('fill', 'none');
77+
strokePath.setAttribute('stroke-linecap', 'round');
78+
svg.appendChild(strokePath);
79+
80+
// Invisible guide path for text
81+
const guideId = `arc-guide-${Math.random().toString(36).slice(2)}`;
82+
textGuidePath = createSvgEl('path');
83+
textGuidePath.setAttribute('id', guideId);
84+
textGuidePath.setAttribute('class', 'arc-textpath');
85+
textGuidePath.setAttribute('fill', 'none');
86+
textGuidePath.setAttribute('stroke', 'none');
87+
svg.appendChild(textGuidePath);
88+
89+
// Text + textPath following the guide
90+
textEl = createSvgEl('text');
91+
textEl.setAttribute('xml:space', 'preserve'); // keep multiple spaces
92+
textEl.setAttribute('text-anchor', anchor);
93+
94+
textPath = createSvgEl('textPath');
95+
textPath.setAttribute('xml:space', 'preserve');
96+
textPath.setAttribute('href', `#${guideId}`);
97+
textPath.setAttribute('startOffset', startOffset);
98+
textEl.appendChild(textPath);
99+
100+
svg.appendChild(textEl);
101+
el.appendChild(svg);
102+
} else {
103+
strokePath = svg.querySelector('.arc-stroke');
104+
textGuidePath = svg.querySelector('.arc-textpath');
105+
textEl = svg.querySelector('text');
106+
textPath = svg.querySelector('textPath');
107+
108+
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
109+
textEl.setAttribute('text-anchor', anchor);
110+
textPath.setAttribute('startOffset', startOffset);
111+
}
112+
113+
// --- Inset for the text path (CSS var -> font-size fallback) ---
114+
const cs = getComputedStyle(el);
115+
const cssInset = parseFloat(cs.getPropertyValue('--text-inset'));
116+
const textFontSize = parseFloat(getComputedStyle(textEl).fontSize) || 12;
117+
const inset = Number.isFinite(cssInset) ? cssInset : textFontSize;
118+
119+
// --- Apply geometry & content ---
120+
strokePath.setAttribute('stroke-width', String(strokeW));
121+
strokePath.setAttribute('d', buildArcPath(cx, cy, rOuter, startDeg, endDeg));
122+
123+
const rText = Math.max(1, rOuter - inset);
124+
textGuidePath.setAttribute('d', buildArcPath(cx, cy, rText, startDeg, endDeg));
125+
126+
textPath.textContent = text;
127+
}
128+
129+
// ---------- Init ----------
130+
function init() {
131+
const els = document.querySelectorAll('[data-arc="true"]');
132+
els.forEach((el) => {
133+
renderArc(el);
134+
// Responsive re-render
135+
new ResizeObserver(() => renderArc(el)).observe(el);
136+
});
137+
}
138+
139+
if (document.readyState === 'loading') {
140+
document.addEventListener('DOMContentLoaded', init);
141+
} else {
142+
init();
143+
}
144+
})();

0 commit comments

Comments
 (0)