Drawing Arbitrary Angle Arcs in SVG with Text Labels and Gradient Colors
This article explains how to draw SVG arcs of any angle, position and rotate text labels along the arcs, generate gradient colors using both RGBA and HSL methods, and assemble a reusable JavaScript class that creates interactive circular arc charts with click events.
The author, a front‑end engineer, revisits a question from SegmentFault about creating a circular selector with dates, showing both Canvas and SVG solutions and focusing on the SVG implementation to help readers understand SVG path arc commands.
SVG arcs are defined by seven parameters: rx (horizontal radius), ry (vertical radius), x-axis-rotation , large-arc-flag , sweep-flag , x and y (end point). For a circular arc the radii are equal, reducing the required parameters to start/end points, radius, rotation, large‑arc flag, and sweep direction.
The article provides a concise SVG path command template:
M X0,Y0 A rx ry xAxisRotation largeArcFlag sweepFlag X1,Y1and an example drawing a 30° arc with center (100,100) and radius 50.
To label the arcs, the text element is used. The label’s position is calculated from the midpoint of the arc, and the text is rotated to align with the tangent using transform="rotate(α x y)" . Because rotation defaults to the text’s lower‑left corner, an additional offset moves the text half its width/height to centre it.
A helper method SVG.prototype.appendCircleArcText encapsulates this logic.
For gradient colors, the author first shows an RGBA‑based approach that interpolates opacity, then improves it with an HSL‑based function that varies lightness to produce a smooth gradient. Example code:
function gradientColor(len, color) {
color = color || [260, 59.8, 64.9];
const delta = 28 / len;
return new Array(len).fill(0).map((_, i) => {
const c = [...color];
c[2] += i * delta;
c[1] += '%';
c[2] += '%';
return `hsl(${c.join()})`;
});
}The final solution combines all pieces into a reusable SVG class that can create groups ( g ) containing an arc and its label, apply click handlers, and render interactive circular charts for different categories (year, month, week, day). The full class definition is provided:
class SVG {
constructor(width, height) {
this.width = width;
this.height = height;
this.s = SVG.createSVG(width, height);
}
appendCircleArc(circle = {cx:100,cy:100,r:100}, angel = {start:0,end:90}, attrs = {fill:"none"}) {
const largeArcFlag = Number(angel > 180);
this.appendItem(SVG.arc({
largeArcFlag,
rx: circle.r,
ry: circle.r,
startX: circle.cx - circle.r * Math.sin(angel.start/180*Math.PI),
startY: circle.cy - circle.r * Math.cos(angel.start/180*Math.PI),
endX: circle.cx - circle.r * Math.sin(angel.end/180*Math.PI),
endY: circle.cy - circle.r * Math.cos(angel.end/180*Math.PI)
}, attrs));
return this;
}
appendCircleArcText(text, circle = {cx:100,cy:100,r:100}, angel = {start:0,end:90}, width = 16, attrs = {}) {
angel = angel.start + (angel.end - angel.start)/2;
const posX = circle.cx - circle.r * Math.sin(angel/180*Math.PI);
const posY = circle.cx - circle.r * Math.cos(angel/180*Math.PI);
const circleArcText = SVG.text(text, {
...attrs,
x: posX,
y: posY,
fontSize: width,
transform: "rotate(-" + angel + " " + posX + " " + posY + ")"
});
this.appendItem(circleArcText);
return this;
}
render() { return this.s; }
renderTo(DOM = document.body) {
DOM.innerHTML = this.s.outerHTML;
const texts = Array.from(DOM.querySelectorAll('text'));
texts.forEach(text => {
const transform = text.getAttribute('transform');
if (transform) text.removeAttribute('transform');
const {width, height} = text.getBoundingClientRect();
[['x', text.getAttribute('x')/1 - width/2],
['y', text.getAttribute('y')/1 + height/2],
['transform', transform || '']].forEach(([name, value]) => text.setAttribute(name, value));
});
return this;
}
appendItem(item) { this.s.appendChild(item); return this; }
static arc({rx=50, ry=50, xAxisRotation=0, largeArcFlag=0, sweepFlag=0, startX=0, startY=0, endX=0, endY=0}, attrs = {}) {
attrs.d = `M ${startX},${startY} A ${rx} ${ry} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${endX},${endY}`;
const path = document.createElement('path');
for (let i in attrs) {
path.setAttribute(i.replace(/[A-Z]/g, o => `-${o}`), attrs[i]);
}
return path;
}
static text(text = '', attrs = {}) {
const t = document.createElement('text');
t.innerHTML = text;
for (let i in attrs) {
t.setAttribute(i.replace(/[A-Z]/g, o => `-${o}`), attrs[i]);
}
return t;
}
static g = class extends SVG {
constructor(attrs = {}) { super(); this.s = document.createElement('g'); for (let i in attrs) { this.s.setAttribute(i.replace(/[A-Z]/g, o => `-${o}`), attrs[i]); } }
};
static createSVG(width, height) {
const s = document.createElement('svg');
s.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
s.setAttribute('width', width);
s.setAttribute('height', height);
s.setAttribute('viewBox', `0 0 ${width} ${height}`);
return s;
}
}Using the class, the article builds a multi‑layer circular chart for years, months, weeks and days, attaches click events to each segment, and demonstrates the final interactive output via online previews.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.