@john-guerra/d3-zoomable-axis
v0.0.5
Published
A d3 axis you can drag to zoom: combines a d3 axis with a dual-handle range control. Pure-d3 idiom + optional reactive-widget input.
Maintainers
Readme
d3-zoomable-axis
A d3 axis you can drag to zoom. It combines a d3 axis
(ticks, labels, domain line) with a dual-handle range control: drag the handles to select a
[lo, hi] sub-range, which it emits in data space. One scale, one element — the axis is
the zoom control, so it lines up with your chart by construction (no pixel-offset hacks).
▶ Live interactive demo — drag the axis handles to zoom a penguins scatterplot in your browser.
npm:
@john-guerra/d3-zoomable-axis· status: early (0.0.x)
Two entry points
The package ships two independent layers — import only what you need:
| Import | What you get |
| --- | --- |
| @john-guerra/d3-zoomable-axis | Core d3 component — zoomableAxis{Bottom,Top,Left,Right}, a d3-idiom factory applied via selection.call(...), built on d3-brush. Chainable accessors, d3-dispatch events. Dependency-light. |
| @john-guerra/d3-zoomable-axis/input | Accessible reactive widget — zoomableAxisInput, a DOM element with .value [lo, hi] that dispatches input (reactivewidgets.org / Observable view()). Native <input type=range> handles, a scented distribution overlay, drag-to-pan, editable value badges, and a live settings panel. |
Splitting the entries keeps the core free of the widget's optional peer
(reactive-widget-helper) and density deps (fast-kde, d3-shape) — those are only pulled
in when you import /input.
Accessibility is a property of the widget layer: its handles are real
<input type="range"> elements — keyboard-operable (arrows / Page / Home / End),
screen-reader announced (named sliders with aria-valuetext), and standard HTML form inputs.
The core zoomableAxis* factories are d3-brush-based (pointer/touch; not keyboard-accessible).
Install
npm install @john-guerra/d3-zoomable-axis
# for the widget layer, also install its optional peer:
npm install reactive-widget-helperWhen bundling, the d3-* submodules (and, for the widget, fast-kde) come along as regular
dependencies — nothing extra to do.
From a CDN (script tag) — core only
The UMD bundle is the core layer and is peer-global: to keep it small it does not
embed d3, so load d3 first. The factories merge into the shared global d3:
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/@john-guerra/d3-zoomable-axis"></script>
<script>
// factories are merged into the shared global `d3`
const slider = d3.zoomableAxisBottom(d3.scaleLinear().domain([24, 92]).range([0, 600]));
</script>The widget layer is ESM-only; import it from a bundler, or over ESM from
https://cdn.jsdelivr.net/npm/@john-guerra/d3-zoomable-axis/src/input.js.
Core component (d3 idiom)
import * as d3 from "d3";
import { zoomableAxisBottom } from "@john-guerra/d3-zoomable-axis";
const x = d3.scaleLinear().domain([24, 92]).range([0, 600]);
const slider = zoomableAxisBottom(x)
.step(1)
.ticks(10)
.value([30, 60]) // initial [lo, hi] in data space
.on("input", (v) => chart.zoomX(v)) // fires while dragging
.on("end", (v) => persist(v));
d3.select("svg").append("g")
.attr("transform", "translate(20,40)")
.call(slider);
slider.value(); // -> [30, 60]
slider.value([40, 70]); // set + re-render, no eventFactories: zoomableAxisBottom, zoomableAxisTop, zoomableAxisLeft, zoomableAxisRight
(orientation mirrors d3-axis). Accessors: scale, value, step, handleSize, ticks,
tickArguments, tickValues, tickFormat, tickSize, tickSizeInner, tickSizeOuter,
tickPadding, on. Events (d3-dispatch): start, input, end — each receives [lo, hi]
in data space (inverted from pixels, snapped to step). Imperative slider.move(g, [lo,hi])
sets and emits; slider.value([lo,hi]) sets silently.
Handles: the core is
d3-brush-based, so its resize handles are invisible hit-areas by default — only the selection is drawn. Style.selection/.handle(or set.handleSize(...)) to draw them. The widget layer below ships visible, accessible handles out of the box.
Reactive widget (Observable / reactivewidgets.org)
import { zoomableAxisInput } from "@john-guerra/d3-zoomable-axis/input";
const weeks = view(zoomableAxisInput([24, 92], {
orient: "bottom", step: 1, length: 600, value: [30, 60],
label: "Weeks", units: "wk",
}));
// `weeks` is reactive [lo, hi]; the element has .value and dispatches "input".Drag a handle to resize an end, drag the band between the handles to pan the window, or
double-click a value badge to type an exact value (an OS-native number / date /
datetime-local / time picker, via the inputType option). Every handle is a
keyboard-focusable native slider.
Key options: orient, step, length, value, label, units, format, inputType
("number" | "date" | "time" | "datetime-local"), ticks, thickness, margin, and
scent (below). Events: input (DOM, on the element) plus start / input / end / scent
via el.on(...).
Scented distribution
Pass scent to draw the data distribution along the axis (a
scented widget — see where the data is
dense before you zoom). A ⚙ settings panel lets you retune it live.
zoomableAxisInput([170, 235], {
orient: "bottom", length: 600, value: [190, 215],
scent: {
values: flipperLengths, // number[]
type: "histogram", // "histogram" | "violin" | "area"
bins: 28,
direction: "out", // "out" = away from the plot, "in" = toward it
},
});scent options: type, bins, size, direction ("out" | "in"; histogram + area only —
side is an alias), style ("kde" | "bars"), color, colorSelected, KDE tunables
(bandwidth, adjust, pad), curve (a d3-shape curve factory or name string), controls
(the ⚙ panel — on by default; false to hide), and persistKey (remember tuned params in
localStorage). Panel edits also fire a scent event (el.on("scent", params => …)).
See it live: john-guerra.github.io/d3ZoomableAxis
(a landing page + demo that import the published npm package from a CDN). The source is in
examples/demo.html — live zoom, dynamic-query filtering, and a
brush-synced example (and examples/test-local.html for an
offline smoke test).
Design & background
- docs/DESIGN.md — architecture, API, packaging.
- docs/d3-api-style.md — the d3 reusable-component idiom reference.
- docs/history-direct-manipulation-rangesliders.md — Shneiderman & Plaisant dynamic-query range sliders and design takeaways.
License
MIT © John Alexis Guerra Gómez

