Time scale
The time scale (or horizontal axis) maps timestamps to canvas coordinates, calculates index intervals, extrapolates future boundaries, and renders adaptive time tick gridlines.
Under the Hood: Coordinate Mapping
Backtestx Charts uses a slot-based layout model on the horizontal axis. Every candlestick or bar occupies an index slot of width candleWidth separated by candleGap:
Bar Index to X Pixel (barToX)
Translates a bar's integer index i into a horizontal canvas pixel coordinate. It adds half the candle's width to center the drawing shape on its vertical gridline:
x = offset + i * slot + candleWidth / 2
X Pixel to Bar Index (xToBar)
Translates a horizontal canvas offset pixel back to the nearest integer bar index. Used for mouse crosshair tracking and snap grids:
i = Math.round((x - offset - candleWidth / 2) / slot)
Future & Past Extrapolation
Unlike static plots, Backtestx Charts allows users to drag past the boundaries of the historical dataset. The time scale generates extrapolated ticks dynamically using the sample interval step:
- Sample Step Discovery: Computes the smallest positive difference between historical bars to find the interval step (
intervalMs) in milliseconds. - Past Index Extrapolation (i < 0): Ticks printed to the left of the initial historical candle are calculated as:
time = bars[0].time + i * intervalMs. - Future Index Extrapolation (i >= len): Ticks printed inside the right-hand margin are calculated as:
time = bars[len - 1].time + (i - (len - 1)) * intervalMs.
Adaptive Axis Labeling
To prevent grid labels from overlapping when zooming, the axis ticks adjust dynamically using window.TimeScale._getAdaptiveLabel(chart, date, prevDate):
Boundary Detection Engine
When a label crosses a temporal boundary (such as moving into a new day, month, or year), it is marked as a boundary label. Ticks that flag as boundaries are rendered in **bold** and a high-contrast theme color to establish clear visual context.
| Timeframe Type | Normal Label Format | Boundary Label Format |
|---|---|---|
| Intraday (1m, 5m, 1h) | HH:MM | DD Mon (e.g. 12 Jun) / Year (bold) |
| Daily / Weekly / Monthly | DD | Mon (e.g. Jun) / Year (bold) |
Horizontal Scroll Clamping
To prevent the user from dragging the chart completely off-screen, the engine applies structural clamping limits:
Past Boundary Limit
80% visible bars offset limit
Prevents dragging historical bars too far to the right. A minimum of 20% of the historical candles remain visible on screen.
Future Boundary Limit
90% visible bars offset limit
Limits how far to the left the user can drag the latest candles. A minimum buffer of 10% of visible slots (with at least 100 future empty slots) is maintained.
Pluggable Horizontal Scales
Backtestx Charts supports swap-in horizontal axis mappings via the ChartingAPI horizontal scale registry. This is essential for rendering non-time-series layouts:
Custom Scaling API
Register custom scales using the registry interface:
barToX: (chart, i) => { ... },
xToBar: (chart, x) => { ... },
getVisibleRange: (chart) => { ... },
drawTimeScale: (chart, ctx, chartH) => { ... },
drawTimeBadge: (chart, ctx, chartH) => { ... }
});
Built-in Pluggable Scales
- YieldScale (
yield-curve): Spaces discrete maturity terms (1M, 3M, 2Y, 10Y, 30Y) evenly across the horizontal axis, mapping yield vertices to coordinates. - StrikeScale (
strike-price): Maps option curves horizontally by numerical strike price points ($90.00, $100.00, etc.) rather than timestamps.