-
Notifications
You must be signed in to change notification settings - Fork 745
Description
Motivation
vx
packages are (by design) low-level and modular which maximizes their flexibility, but also requires more work to create even simple charts. My library @data-ui/xy-chart
is built on top of vx
and was designed to make it easier to create common charts with less work. To solve this use-case within the vx
ecosystem, and consolidate these efforts, @data-ui/xy-chart
is being deprecated and we plan to port its functionality to a new vx
package @vx/xy-chart
.
Goals of this RFC
- align on near- and mid-term features
- align on an
XYChart
API - align on a general direction for implementation
Features
To have feature parity with @data-ui/xychart
, the new package should support the following:
- Computes and provides
x-
andy- scales
across all data series - Handles mouse events for the
XYChart
across all data series- Positions tooltips and provides tooltip data
- Supports programmatic tooltip control
- Mouse events can be defined at chart or series level, can be disabled at series level
- Supports the following
*Series
which are mostly wrappers aroundvx
Bar
,Line
,Point
,Area
,AreaDifference
,StackedArea
,StackedBar
,GroupedBar
,BoxPlot
,ViolinPlot
,Interval
- Exports
CrossHair
component for use withTooltip
- Exports
XAxis
andYAxis
components fromvx
- Supports
horizontal
andvertical
reference lines
- Supports
x-
andy-gridlines
- Supports
brush
functionality (pre@vx/brush
) - Supports styles with
LinearGradient
,Patterns
, and a chart theme viaprops
- Wraps individual points in
FocusBlur
handlers that area11y
accessible / tab-able
We would also like to add additional support for the following:
New near-term features
- re-written in
TypeScript
- responsive by default
- supports arbitrary
datum
shape +x
/y
data accessors (currently requires{ x, y }
datum
shape) - first-class
hooks
support
New mid-term features
- optionally render
Tooltip
in aPortal
to fix z-index stacking context problem - easy creation of
Legend
s - better support for overlays / annotations (points + regions)
- integration with
@vx
primitives likebrush
,zoom
, anddrag
canvas
support –vx
is currently mostlysvg
based (this likely requires updates in othervx
packages)- animation –
@data-ui
does not support animation. while this may not need to be fully baked in we should expose hooks to animatexy-chart
API
@techniq has done some great research on declarative react
chart APIs here. Generally they are composable:
<Chart {...}>
<Axis {...} />
<Gridlines {...} />
<Legend />
<DataSeries {...} />
<DataSeries {...} />
</Chart>
However there are some key differences:
data
provided at the DataSeries
level vs the chart
container level
DataSeries
level – ✅ favored
() => <Chart><DataSeries data={...} /></Chart>
Pros 👍
- it's more "natural" to directly link
data
to the series that will visually represent it @vx/shape
's (the basis forDataSeries
) currently require data, so this is more consistent with separate package APIs- Series can use any custom logic they need for computing the
x-
andy-
extent from theirdata
, and theChart
container can simply collect these- additionally this allows more advanced functionality like
horizontal
orientation to be pushed to theSeries
-level without requiring it to be implemented by allSeries
and theChart
container doesn't need to have any knowledge of it
- additionally this allows more advanced functionality like
Cons 👎
Chart
container needs a way to access the data across all series
Chart
container level – ❌ disfavored
() => <Chart data={...}><DataSeries /></Chart>
Pros 👍
- ultimately the
Chart
needs access to all data in order to providex-
andy-
scales; this makes that easy.
Cons 👎
Series
may require custom logic to computex-
andy-
extent from theirdata
(e.g., a bar stack) which theChart
needs to be aware of in this model- still requires
key
accessors at theSeries
-level forSeries
data - forces all
DataSeries
to have the same data length (or be filled with empty values)
Mouse and Touch events
Mouse and touch events are handled in several ways
-
Standard mouse events – ✅ favored
react-vis
and@data-ui
expose mouse and touch events at both theChart
andSeries
level; these use standardreact
events such asonClick
,onTouchMove
, etc. -
Custom event syntax – ❌ disfavored
Some libraries likeVictory
have their own custom event system with non-standard syntax and selection language.
react
hooks
I've not been able to find any react
vis libraries which expose hooks
. Feels like an opportunity on top of a render / component API 😏
Implementation
We'd like to improve upon the following limitations of @data-ui
v1
implementation:
-
Written in TypeScript
@data-ui
was written in JavaScript, butvx
is now a TypeScript project and typings will be similarly useful for@data-ui
. -
Use
react
context
over cloning children
@data-ui
was implemented before the new / more robust16.3
context
API. Therefore chartstyles
and sharedscales
are passed viaprop
s +React.cloneElement
. Combined withhooks
, usingcontext
should open up a whole new set of API possibilities (see below).
What is kept in context
The function of the XYChart
wrapper largely equates to managing shared state across the elements of a chart, which components can leverage as needed. . This includes
- chart
theme
+styles
xScale
that accounts for data range of all chart series and chartwidth
+margin
yScale
that accounts for data range of all chart series and chartheight
+margin
tooltipData
+tooltipCoords
, when applicable
In addition to chart width
+ height
, the Chart
container must have knowledge of all data
(+ annotation
) values to compute scale
s appropriately. Rather than having the Chart
introspect props from child
DataSeries
or Annotations
(which can get gnarly) we propose that DataSeries
and Annotations
register their data
, xValues
, and yValues
in context
.
This pushes the logic of data <> x/y extent
mapping to DataSeries
rather than the Chart
, and allows the Chart
to leverage these values to compute scales properly. It could look something like
// in e.g., <LineSeries {...props} />
const { key, data, xAccessor, yAccessor } = props;
const { registerData } = useContext(XYChart);
registerData({
dataKey: key,
xValues: data.map(d => xAccessor(d)),
yValues: data.map(d => yAccessor(d)),
// other interesting things to do at this level
mouseEvents: false,
legendItemRenderer,
});
Unknowns
I'm unsure if there are major performance implications of using hooks ⚡ 🐌
Proposed API
// all of these items have access to the same Chart `context` which includes
// theme, dataRegistry, xScale, yScale, colorScale, tooltipData, tooltipCoords
const { ChartProvider, XYChart, Legend, Tooltip } = useChart({ theme, scaleConfig, ... }));
() => (
{/* context provider */}
<ChartProvider>
{/**
* Chart renders `svg` container and computes `x-` and `y-scale`s using the
* data registry context. It is either passed `width`/`height`, or it uses
* `@vx/responsive` for auto-sizing
*/}
<XYChart>
{/**
* DataSeries register their data on mount. When `x-` and `y-scale`s
* are computed and available in context they render data.
*/}
<LineSeries key={dataRegistryKey} data={...} />
{/**
* Axes use `x-` or `y-scale`s from context based on orientation.
*/}
<Axis orientation="left" />
{/* Custom axis component could use `scale`s from context */}
<CustomAxis />
</XYChart>
{/**
* Tooltip is `html`-based so should be rendered outside the chart `svg`
* It has access to `tooltipData` and `tooltipCoords` from context.
*/}
<Tooltip renderInPortal={boolean} />
{/**
* Legend is `html`-based so should be rendered outside the chart `svg`.
* It has access to all series via `dataRegistry` from context,
* or we could add a legend renderer registry.
*/}
<Legend />
</ChartProvider>
)
The same functionality could be provided in a component
API:
import { ChartProvider, XYChart, Legend, Tooltip } from '@vx/xy-chart`
() => (
<ChartProvider theme={...} {...scaleConfig} >
<XYChart {...} />
<Legend {...} />
<Tooltip {...} />
</ChartProvider>
)