A Pure CSS & JavaScript Approach to Radar Charts
Radar charts—often called spider charts—are a distinctive way to visualize multivariate data. Rather than plotting points against traditional X and Y axes, each category gets its own radial axis extending from the center of a circular field. Connecting the plotted values produces a geometric shape that reveals relationships at a glance. The inherently polygonal nature of these charts makes them a natural fit for CSS, particularly the polygon() function. While libraries like Chart.js and D3.js offer ready-made solutions, building one from scratch with core HTML, CSS, and JavaScript is both feasible and instructive—especially for single-chart projects where a full library overhead isn't justified.
It's worth noting that radar charts have limitations. When multiple data series are overlaid, the resulting web of polygons can become hard to read compared to simpler chart types. The order of categories around the circle influences the visual pattern, and comparisons only hold if scales are consistent across series.
Core Structural Components
The layout of a radar chart boils down to three distinct layers:
- Grids: The spider web of concentric guide lines and radial axes.
- Graphs: The filled polygons representing actual data values.
- Labels: Text identifiers marking each category's axis.
These can be represented in HTML as three separate wrapper elements, each containing its respective component. The wrappers are then overlaid on the same grid area, creating the stacked visual effect.
Setting Up Base Styles
Start by defining a few CSS custom properties for colors. With CSS Grid, all three layers can be placed on the same grid-area using minimal code. Give each child a fixed width and rely on aspect-ratio: 1 to maintain a perfectly square canvas per layer.
Each layer is stacked and centered. The graphs and grids are sized at 100% of the container, while labels receive explicit positioning so they can be placed by JavaScript later. Adding temporary borders or subtle background colors helps visualize the stacking order during development.
Mathematics for Plotting Vertices
Calculating the coordinates for a radar chart's vertices doesn't require deep geometry knowledge. The key assumption is that one vertex (the first data category) sits directly at the top of the center—so its angle, θ, is 90deg (π/2 radians). Subsequent vertices are spaced at equal angular intervals around the center.
For a chart with n sides, each vertex's angle is computed as:
angle = (90deg - (2 * PI * i) / n)
From trigonometry, the coordinates are derived:
x = radius * cos(angle) y = radius * sin(angle)
These coordinates are typically multiplied by -1 on the y-axis (or the entire angle) to control rotation direction. Values are then converted to percentages relative to the container's size for CSS formatting.
Drawing with JavaScript
The logic works in three stages—grid first, then graphs, then labels—using plain HTML elements rather than the Canvas API to keep things simple.
Constructing the Grid
Create a set of angular gradients and positioned vertices. The grid polygon uses clip-path: polygon() on a pseudo-element, while the guide lines are drawn with conic-gradient(). The angles are mapped to color stops, creating precise spokes.
Each vertex is placed around a central point, using percentages. For example, with five sides the resulting vertices resemble the coordinates for a path from the top clockwise.
The grid's clip-path receives the calculated polygon points, while its background uses the conic-gradient() with hairline stops to delineate each axis. For a five-side chart, the computed vertices might look like:
47.06% 0%, 2.86% 57.0%, 20.0% 100%, ...
Overlaying Data Graphs
Adding graphs is straightforward once the grid is functional. For each data series, increment the unit counter and push its numerical values (as percentages) into the percents array. The script automatically generates a new polygon for each series, centered with place-self: center, colored with the specified gradient, and given a subtle drop shadow for depth.
Each graph renders as a new div within the graphs wrapper. The number of graphs is dynamically matched to the data provided.
Positioning Labels
Label placement relies on the vertex coordinates, text dimensions, and desired spacing. All labels are relatively positioned, and the JavaScript assigns inset-inline-start and inset-block-start inline styles.
The horizontal position logic checks whether a label is on the left or right half of the chart. If the vertex's x-coordinate is under 50%, the label shifts left by its known offsetWidth plus a padding gap. Otherwise, it shifts right similarly, aligning the outer edge relative to the vertex.
Vertically, labels sit flush with their vertex y-coordinates, with adjustments for particular cases like bottom-anchored labels to avoid collisions. The first vertex (at the top) is always given a top-middle placement instead of a custom offset.
Adapting to Different Sizes
With the math encapsulated in the script, tuning the chart for different numbers of data categories is trivial. Increase or decrease the sides variable, and the script updates the grid lines, graph points, and label coordinates accordingly. A three-sided chart runs smoothly with just that single change, as does a seven-sided one. The markup and CSS need no additional modifications, safeguarding the component's reusability.



