Dual Axes Charts

Sometimes you need to plot two things on the same timeline that simply do not belong to the same scale. Revenue and headcount. Inflation and GDP. Price and spread. If you force them onto one axis, one series becomes unreadable, and the chart turns into a polite lie.

A dual-axis chart solves this by keeping a shared X axis (time) while providing two independent Y axes, so each metric is shown in its natural units. The goal is not to pretend the metrics are comparable. The goal is to compare when they move and how their dynamics relate.

In RareCharts, DualAxes is exactly that: a time-based chart with two vertical scales.

To illustrate a real-world use case, this example uses a finance-native story: a convergence (arbitrage) strategy based on the idea that two similar instruments that temporarily diverged in price will later converge again.

One concrete version of this story exists in government bonds, where a newly issued “on-the-run” Treasury can trade slightly richer than the previous “off-the-run” issue for a period of time. Traders may position for convergence by going long the cheaper issue and hedging the richer one, expecting the price gap to compress as the market normalizes.

In the chart, the right axis (Y1) shows the price index of the two instruments over time. The left axis (Y2) shows the spread between them. Prices and spread are different categories, so they get different axes. The chart stays readable, and the relationship stays visible.

Axis behavior and formatting

Both axes are configured independently. You can set titles (y1Title, y2Title), control tick formatting (y1TickFormat, y2TickFormat), and, when needed, override the visible ranges (y1Domain, y2Domain) to keep the chart stable and comparable across screenshots, reports, or multiple panels.

Axis titles are meant to be terse units (PRICE, N · K HLX). A title that would run past its axis margin is trimmed with an ellipsis — measured by rendered width, so a short label that fits is left alone — with the full text on hover and a one-time console hint. Set axisTitleMaxLength to cap it explicitly, or widen margin if you need more room.

In this example, the spread axis uses a signed format and treats very small values as clean zero, so you do not get the infamous +0.00 noise that makes charts look broken even when the data is fine.

Interaction

Dual Axes supports cursor inspection through a crosshair and a tooltip. The tooltip is fully customizable through tooltipFormat ({ date, points }), so you can present values in your product’s language instead of whatever generic tooltip someone thought was “good enough”.

Built-in timeframe controls

Because DualAxes is inherently time-based, it can also render the shared timeframe switcher in its own chart header:

const chart = new RareCharts.DualAxes('#chart', {
    timeframes: true,
    defaultTimeframe: '1Y',
});

chart.setData(series);

Available pieces:

  • timeframes renders the built-in range buttons in the chart header.
  • defaultTimeframe selects the initial visible range and active button.
  • navigator: true renders the built-in overview strip with a brush under the main chart.
  • setView([start, end]) sets any custom visible date window.
  • getView() returns the currently visible [start, end].
  • onViewChange(fn) lets external UI or a linked navigator stay synchronized.

Example:

const chart = new RareCharts.DualAxes('#chart', {
    timeframes: true,
    defaultTimeframe: '1Y',
    navigator: true,
});

Dual-axis charts are often implemented as a hack: two scales, mismatched formatting, confusing labels, and tooltips that quietly mix units. This component exists to make the dual-axis case predictable, explicit, and safe for real reporting: independent scales, consistent structure, and controlled formatting.

Annotations

DualAxes supports the full annotation API — vertical date markers (point and range) and horizontal reference levels (line and band) on either Y axis. Horizontal entries take an explicit axis: 'y1' | 'y2' so the value is mapped to the correct scale. See the dedicated annotations page for the full API.

Dual Axes chart options

Common options shared by all chart types (title, subtitle, legend, legendPosition, source, theme) are documented on the Settings page.

Option Type Default Description
Layout
height number 280 Chart height in px.
margin object Inner padding {top, right, bottom, left}.
Right defaults to 92, left to 64 to give both axes room.
xPad number 8 Extra horizontal padding on both ends of the X scale.
timeframes boolean | array Built-in timeframe buttons rendered in the chart header. Pass true to use the default set ['1M', '3M', '6M', '1Y', '2Y', 'ALL'], or pass a custom array.
defaultTimeframe string | object Initial active timeframe used on first render when no explicit view has been set.
defaultView array Initial visible date range as [from, to]. An explicit alternative to defaultTimeframe; clamped to the data extent.
navigator boolean | object Built-in overview strip with brush selection under the chart. Pass true for defaults or an object with overview options.
Right axis — Y1

Y1 is drawn on the right side.

y1Domain [min, max] auto Override the Y1 scale domain.
Useful for keeping charts comparable across snapshots.
y1Ticks number 4 Tick count on Y1.
y1TickFormat function ,.2f (value) => string — Y1 tick labels.
y1LabelsOnly boolean true Show only tick labels; suppress the axis line.
y1Title string Axis title, rendered along the right edge.
Left axis — Y2

Y2 is drawn on the left side. Grid lines and the zero baseline are keyed to Y2.

y2Domain [min, max] auto Override the Y2 scale domain.
y2Ticks number 4 Tick count on Y2.
y2TickFormat function +.2f (value) => string — Y2 tick labels.
y2LabelsOnly boolean true Show only tick labels; suppress the axis line.
y2Title string Axis title, rendered along the left edge.
X axis
xTickFormat function '%m/%d' (date) => string — X tick labels.
Lines
curve string 'linear' D3 curve type for line series: 'linear', 'monotone', 'step', etc.
curveTension number 0 Tension for the 'cardinal' curve, 01.
strokeDash string SVG stroke-dasharray applied to all line series globally.
area boolean false Fill area under line series.
areaOpacity number 0.12 Area fill opacity.
areaBaseline 'zero' | 'min' | number 'zero' Area baseline anchor.
Bars
barOpacity number 0.35 Bar fill opacity.
barWidthRatio number 0.65 Bar width as a fraction of the time step width.
barGrouping 'overlap' | 'cluster' 'overlap' How multiple bar series are arranged. 'cluster' places them side by side.
Visibility
showGrid boolean true Show horizontal grid lines.
showXAxis boolean true Show the X (date) axis at the bottom. Hiding it also collapses the bottom margin, so the plot runs flush.
showY1Axis boolean true Show the Y1 axis on the right. The right margin collapses once endLabels is off and no y1Title is set — they all share that gutter. An explicit margin always wins.
showY2Axis boolean true Show the Y2 axis on the left. The left margin collapses unless a y2Title is set.
End labels and markers
endLabels boolean true Show last-value labels at the right edge for line series.
endLabelsAxis 'y1' | 'y2' 'y1' Which axis to use when formatting end labels.
markers boolean false Point markers at each data sample on line series.
markerShape string 'circle' Marker shape: 'circle', 'square', 'diamond'.
markerSize number 4 Marker radius in px.
Interaction
crosshair boolean true Vertical tracker with dots at data points and a tooltip on hover.
tooltipFormat function ({ date, points }) => html — where points is [{name, value, color, fmt}].
Annotations
annotations array Event markers and reference levels. Horizontal entries accept axis: 'y1' | 'y2' to choose the scale. See Annotations.
annotationLabelHeight number 22 Pixels reserved above the chart for vertical annotation labels.
Animation
animate boolean true Animate on first render.
duration number 650 Animation duration in ms.
ease string 'cubicOut' Easing: 'cubicOut', 'cubicInOut', 'linear'.
Per-series fields

Each object in the series array passed to setData():

name string 'Series N' Series name — used in legend and tooltip.
axis 'y1' | 'y2' 'y1' Which Y axis to plot against.
type 'line' | 'bar' 'line' Rendering type for this series.
color CSS color theme palette Series color.
strokeWidth number 2 Line thickness in px (lines only).
strokeDash string SVG dash pattern for this series only.
curve string global curve Curve override for this series.
area boolean global area Fill area under this series.
areaOpacity number global areaOpacity Per-series area opacity.
areaBaseline string | number global areaBaseline Per-series area baseline.
values array [{date, value}, ...] — the data points.