webdevpuneet.com

Scatter Plot — HTML CSS JS SVG Scatter Chart

Scatter Plot · Charts · Plain HTML, CSS & JS · Live preview

CategoryCharts

Copy & paste

Source Code

<div class="scp-card">
  <div class="scp-head">
    <h3>Study hours vs. exam score</h3>
    <label class="scp-toggle"><input type="checkbox" id="scpTrend" checked> Trend line</label>
  </div>
  <svg class="scp-svg" id="scpSvg" viewBox="0 0 360 240" role="img" aria-label="Scatter plot"></svg>
  <div class="scp-tip" id="scpTip" hidden></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Data-to-pixel scaling
sx()/sy() map [x, y] data into the padded plot area, with y inverted so up means more.
Least-squares trend line
A real linear regression computes the best-fit slope and intercept from the data.
Toggleable overlay
A checkbox shows/hides the trend line, which is drawn under the points so dots stay readable.
Gridlines and axes
Horizontal gridlines and solid x/y axes frame the plot for easy reading.
Per-point tooltips
Hovering any point shows its exact x and y values at the cursor.
Event delegation
One mousemove listener handles every point via closest().
Configurable axis ranges
XMAX/YMAX constants set the domains; change them for any data range.
Data-driven & no library
Renders entirely from a DATA array of [x, y] pairs — zero dependencies.

About this UI Snippet

Scatter Plot — SVG Point Plot with a Least-Squares Best-Fit Trend Line

Screenshot of the Scatter Plot snippet rendered live

A scatter plot reveals the relationship between two numeric variables — whether they correlate, how tightly, and in which direction. It's the chart you reach for to answer "does X affect Y?" This snippet builds a complete scatter plot in plain HTML, CSS, SVG, and vanilla JavaScript, including an optional least-squares trend line computed from the data itself, gridlines, axes, and hover tooltips — with no charting library.

Plotting points in pixel space

Each data point is an [x, y] pair, and two scaling functions turn data into pixels: sx(x) maps the x value across the padded plot width, and sy(y) maps the y value up the height — inverted, because SVG's y grows downward while the chart's grows upward. Every point becomes a small SVG circle at the scaled coordinates. Because points are real DOM nodes, each one is individually hoverable, which is what lets the tooltip report the exact values behind any dot.

A real best-fit line, not a hand-drawn one

The trend line is computed with least-squares linear regression — the standard method for fitting a straight line to scattered points. fit() accumulates the sums of x, y, x·y, and x² across the dataset, then solves for the slope and intercept with the closed-form formulas. The result is the line that minimises the squared vertical distance to every point: the genuine statistical trend, not an eyeballed approximation. Drawing it from the regression means it updates correctly for any data you feed in.

Toggleable and drawn underneath

A checkbox toggles the trend line on and off, re-rendering the plot. The line is drawn before the points so the dots sit on top of it, keeping them readable and hoverable where the line crosses through the cloud. The line uses a dashed stroke in a contrasting colour so it reads clearly as an overlay rather than as data.

Gridlines, axes, and tooltips

Faint horizontal gridlines and solid x/y axes frame the plot so positions are easy to judge. Hovering any point shows a tooltip with its exact x and y values, positioned at the cursor via getBoundingClientRect, with a single delegated mousemove listener handling every point through closest('.scp-pt'). The tooltip is pointer-events: none so it never steals its own hover.

Data-driven and drop-in

The plot renders from a DATA array of [x, y] pairs, with the axis maxima set by two constants. Swap in your own pairs and the points, scaling, and regression line all follow. Because it's dependency-free SVG, it's crisp at any size, themeable with CSS, and a clear reference for both the data-to-pixel scaling and the linear-regression math that power correlation visualisations.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA scatter plot renders with seventeen points and a best-fit trend line through them.
  2. 2
    Toggle the trend lineUse the checkbox to show or hide the least-squares regression line.
  3. 3
    Hover a pointMove over any dot to see its exact x and y values in a tooltip.
  4. 4
    Swap in your dataReplace the DATA array with your own [x, y] pairs and set XMAX/YMAX to your axis ranges.
  5. 5
    Restyle itChange point size, colour, the trend-line dash pattern, or the gridline density.
  6. 6
    Wire to an APIFetch your paired values, map them into [x, y] form, and call render() to draw the live chart.

Real-world uses

Common Use Cases

Correlation analysis
Show whether two metrics move together, with a real least-squares trend line that can be toggled on and off over the points.
A/B tests and experiments
Plot an input against an outcome to spot a pattern, and use a bubble chart when a third variable needs size.
Scientific and lab results
Visualise paired measurements with axes and gridlines, and hover any point to read its exact x and y values.
Pricing and demand curves
Map price against units sold to see the shape of demand, with the trend line revealing the slope.
Education and performance data
Relate study time to scores or training hours to output, and check the trend before drawing conclusions from a line chart widget of averages.
Related: UV Index Meter
See the UV Index Meter for a related charts pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It uses least-squares linear regression. fit() accumulates the sums of x, y, x·y, and x² across all points, then applies the closed-form formulas for slope m = (n·Σxy − Σx·Σy) / (n·Σx² − (Σx)²) and intercept b = (Σy − m·Σx) / n. The result is the line that minimises the total squared vertical distance to the points — the genuine statistical best fit, recomputed from whatever data you supply.

Draw order in SVG is paint order — later elements sit on top. Drawing the trend line first means the data points render over it, so where the line passes through the cloud the dots stay visible and hoverable rather than being hidden behind the line. The dashed stroke and contrasting colour further distinguish the line as an overlay rather than data.

Set the XMAX and YMAX constants to the maximum values of your two variables (or a bit above, for headroom). The scaling functions divide by these to map data into the plot area. If your data has a non-zero minimum, subtract the min before scaling and divide by the range (max − min) so the plot uses the full width and height.

As written it assumes a 0-based origin. For negative values, shift the domain: compute min and max, then map (value − min) / (max − min) across the axis, and draw the zero line wherever 0 falls in that range. The point and line drawing stay the same — only the scaling functions change to account for the offset origin.

In React, hold the data and trend-toggle in useState and render circles from .map(), or run render() in a useEffect with a ref; in Vue, use v-for or a template ref with onMounted; in Angular, use *ngFor or ViewChild with ngAfterViewInit. The scaling and regression math is framework-agnostic and ports unchanged — only state and event wiring move into the framework.