Date: 2026-10-16
Status: investigation and proposed direction, not an approved API or an implementation plan.
The long-term goal is to retain mode 2 and remove mode 1. Faceted data becomes the general representation. Shared, ordered X data becomes an optimized case within that representation.
During migration, mode 2 needs useful built-in behavior for ordered time series. Users must retain custom spatial lookup for unordered data, heatmaps, and other specialized charts.
The proposed design separates four concerns:
One global “all series share X” flag cannot represent charts with several shared-X groups or mixed ordered and unordered series.
These observations refer to the source and local prototypes at the time of this investigation.
| Area | Mode 1 | Mode 2 |
|---|---|---|
| Data | Shared, ordered X array and aligned Y arrays | Per-series facets, with optional reference sharing |
| Default X ordering | Ascending | Facets default to sorted: 0 |
| Cursor candidate | Binary search over shared X | No built-in nearest-X candidate in updateCursor() |
| Hover refinement | Built-in null skipping, proximity, and bias | Specialized charts supply cursor.dataIdx |
| Default drag selection | X only | X and Y |
| Y scan after an X change | Visible X interval | Full participating facets |
Mode 2 does revisit automatic Y scales after a change to the primary X scale. It does not restrict those scans to the visible X interval. As a result, unchanged full-data extrema usually produce unchanged Y bounds.
The local tick-aware ranging prototype supports mode-2 linear numeric scales. That work changes the calculation of bounds and ticks, not the selection of data for scans.
Relevant source locations:
src/uPlot.js: updateCursor(), default cursor.dataIdx, setScales(), and scanScaleInternal().src/opts.js: xSeriesOpts, xySeriesOpts, and facet defaults.dist/uPlot.d.ts: Mode, Series.Sorted, cursor indices, and facet declarations.Positional pairing identifies coordinates that belong to the same observations or shapes. A range dependency specifies which selected coordinate window constrains another coordinate’s automatic range.
These concepts are distinct. A positional pair does not necessarily need automatic range adjustments in either direction.
Ordinary XY facets already describe positional pairs:
series A: time ↔ temperature
series B: time ↔ pressure
The arrows represent positional relationships, not a direction of automatic range adjustment.
An X-only zoom can select observations within the time window. The temperature and pressure scales can then independently range their contributing values.
A single pairedWith property on a scale cannot represent all relationships:
series A: timeA ↔ temperature
series B: timeB ↔ temperature
Here, one temperature scale aggregates contributions from two positional pairs. Each contribution must respect its own X window.
The relationship belongs to the series. The aggregate range belongs to the scale.
The proposed default infers ordinary XY relationships from facets. Specialized charts can supply interval or spatial semantics. X and Y describe logical coordinate roles, not fixed screen directions, so rotated charts retain the same relationships.
Count, color, and size facets do not become positional counterparts merely because they occur in the same series.
The following policies describe possible behavior, not proposed configuration names:
| Policy | X-only zoom | Y-only zoom |
|---|---|---|
| Independent | Preserve Y | Preserve X |
| X controls automatic Y | Fit Y to the X window | Preserve X |
| Y controls automatic X | Preserve Y | Fit X to the Y window |
| Reciprocal | Fit Y to the X window | Fit X to the Y window |
X-controlled automatic Y matches common time-series behavior. Reciprocal adjustment can serve scatterplots, but needs a bounded update rule.
The proposed rule permits one propagation step per zoom operation:
Automatic adjustments do not become new zoom requests. Otherwise, reciprocal adjustments can repeatedly shrink the selected population.
An explicit XY zoom keeps both requested bounds authoritative. Neither axis replaces the other axis’s requested bounds through automatic fitting.
This relationship differs from scale.from. A derived scale follows another scale’s range. A positional counterpart calculates a range from a selected population.
| Data | Contribution selection |
|---|---|
| Ordered X | Binary-search the X interval, then scan corresponding Y values |
| Shared ordered X | Reuse the interval calculation for the group |
| Unordered X | Filter observations by X, or query an external index |
| Custom shapes | Use the plugin’s positional bounds and intersection rules |
A heatmap cell occupies both an X interval and a Y interval. Selection can mean intersection with the X window, rather than containment of xMax. Its Y-range contribution includes both Y edges.
Native nearest-X hover does not supply these interval semantics. The plugin needs a way to provide contributions without an unnecessary core scan.
An X group contains series that share all three properties:
For example:
[
null,
[xsA, ys1],
[xsA, ys2],
[xsB, ys3],
]
The first two series share one group, provided their X scales and ordering agree. The third series uses another group.
Each ordered group can share one cursor search and one calculation of its visible index interval. An independently sampled, ordered series can use the same algorithms without shared X references.
Reference sharing reduces work. It does not determine whether native binary-search hover is available.
Group construction needs no comparison of array contents and no copies of numeric data. Equal contents in different arrays remain separate groups. Separate typed-array views also remain separate unless they are the same object.
setData(), series addition, and series removal must refresh group membership. In-place data changes retain the existing requirement to notify uPlot. Conflicting ordering declarations must not silently share a search result.
The current default refiner directly accesses mode-1 arrays:
let xValues = data[0];
let yValues = data[seriesIdx];
The proposed refiner receives the relevant X array, Y array, and index window instead. Null skipping, proximity, and bias can then use one implementation for both input forms.
The proposed cursor flow is:
Explicit cursor.dataIdx callbacks retain control. A heatmap with a spatial index must not pay for an unused native search. The exact callback contract for optional native candidates remains open.
cursor.idxs becomes the authoritative collection of per-series results. For compatibility, cursor.idx can represent the primary group’s candidate. It cannot imply a shared index across unrelated groups.
The initial proposal requires explicitly ordered mode-2 X facets. A mode-1 compatibility adapter supplies sorted: 1 automatically.
A later breaking release can make ascending order the default for X facets, with sorted: 0 as an explicit unordered declaration. This default remains undecided. Applying it immediately to existing mode-2 charts can silently break scatterplots.
sorted: -1 needs descending-search support. Descending data order is different from a reversed scale direction.
Ordered X values need not be unique. Duplicate values need a documented tie rule and inclusive window boundaries. A nearest-X candidate does not identify the correct cell among multiple heatmap cells at the same X.
Positional relationships and directional policies determine which contributions affect an automatic range:
Series positional relationships
+ directional range policy
+ explicit zoom request
→ eligible data contributions
→ aggregate extrema per target scale
→ range policy and tick-aware layout
Ordered X groups optimize contribution selection for visible-window Y ranging:
Requested X bounds
→ visible index interval per ordered X group
→ participating Y-facet extrema
→ aggregate extrema per Y scale
The group structure supports the range dependency. It does not define the dependency or its default behavior.
Cursor lookup and visible-window ranging remain separate features. Native hover must not silently change existing zoom or autoscale behavior.
The first ranging target is the mode-1-equivalent case: shared, ordered X. Independent ordered groups can subsequently use the same per-group path.
Two cache rules are essential:
minAbs, not unrelated groups.A Y scale can combine facets from different X groups. Mixed ordered and unordered contributors need a defined policy. The implementation must not silently combine visible-window extrema with full-data extrema under an unclear default.
The proposed long-term default is X-only drag selection. Scatterplots and heatmaps explicitly select XY drag.
Drag behavior must not change because setData() introduces or removes shared array references. Reference sharing is an optimization fact, not a reliable statement of chart intent.
The first cursor prototype retains current drag and autoscale defaults. Changes to those defaults require a separate compatibility decision.
A shared internal representation can avoid maintaining two complete implementations until the final migration.
The proposed sequence is:
The adapter does not copy numeric arrays or allocate objects per point. During migration, public u.data behavior needs a compatibility boundary separate from internal normalization.
Remaining mode-1 assumptions include:
valToIdx() and posToIdx(), which currently assume a primary X array.These decisions do not need to block the first cursor prototype.
Scope: native mode-2 hover for explicitly ordered X, with one candidate search per X group.
The prototype retains existing drag and autoscale defaults. It preserves explicit spatial lookup and does not change public data layout.
Required regression coverage:
setData(), series addition, and series removal.Performance checks count searches as well as elapsed time. They must demonstrate one candidate search per eligible group, not one per series. Refinement remains per series and can require additional work around gaps.
This prototype provides a useful feature independently of the full migration. It also establishes the shared structure needed for visible-window Y ranging.
The directional range proposal needs separate regression coverage:
xMax lies outside the selected window.dataIdx callback receive?No new configuration names or migration deadlines are selected by this document.