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- and y- 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 around vx
Bar, Line, Point, Area, AreaDifference, StackedArea, StackedBar, GroupedBar, BoxPlot, ViolinPlot, Interval
- Exports
CrossHair component for use with Tooltip
- Exports
XAxis and YAxis components from vx
- Supports
horizontal and vertical reference lines
- Supports
x- and y-gridlines
- Supports
brush functionality (pre @vx/brush)
- Supports styles with
LinearGradient, Patterns, and a chart theme via props
- Wraps individual points in
FocusBlur handlers that are a11y 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 a Portal to fix z-index stacking context problem
- easy creation of
Legends
- better support for overlays / annotations (points + regions)
- integration with
@vx primitives like brush, zoom, and drag
canvas support β vx is currently mostly svg based (this likely requires updates in other vx packages)
- animation βΒ
@data-ui does not support animation. while this may not need to be fully baked in we should expose hooks to animate xy-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 for DataSeries) currently require data, so this is more consistent with separate package APIs
- Series can use any custom logic they need for computing the
x- and y- extent from their data, and the Chart container can simply collect these
- additionally this allows more advanced functionality like
horizontal orientation to be pushed to the Series-level without requiring it to be implemented by all Series and the Chart container doesn't need to have any knowledge of it
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 provide x- and y- scales; this makes that easy.
Cons π
Series may require custom logic to compute x- and y- extent from their data (e.g., a bar stack) which the Chart needs to be aware of in this model
- still requires
key accessors at the Series-level for Series 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 the Chart and Series level; these use standard react events such as onClick, onTouchMove, etc.
-
Custom event syntax β β disfavored
Some libraries like Victory 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, but vx 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 robust 16.3 context API. Therefore chart styles and shared scales are passed via props + React.cloneElement. Combined with hooks, using context 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 chart width + margin
yScale that accounts for data range of all chart series and chart height + margin
tooltipData + tooltipCoords, when applicable
In addition to chart width + height, the Chart container must have knowledge of all data (+ annotation) values to compute scales 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>
)
cc @hshoff @techniq @kristw
Motivation
vxpackages 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-chartis built on top ofvxand was designed to make it easier to create common charts with less work. To solve this use-case within thevxecosystem, and consolidate these efforts,@data-ui/xy-chartis being deprecated and we plan to port its functionality to a newvxpackage@vx/xy-chart.Goals of this RFC
XYChartAPIFeatures
To have feature parity with
@data-ui/xychart, the new package should support the following:x-andy- scalesacross all data seriesXYChartacross all data series*Serieswhich are mostly wrappers aroundvxBar,Line,Point,Area,AreaDifference,StackedArea,StackedBar,GroupedBar,BoxPlot,ViolinPlot,IntervalCrossHaircomponent for use withTooltipXAxisandYAxiscomponents fromvxhorizontalandverticalreference linesx-andy-gridlinesbrushfunctionality (pre@vx/brush)LinearGradient,Patterns, and a chart theme viapropsFocusBlurhandlers that area11yaccessible / tab-ableWe would also like to add additional support for the following:
New near-term features
TypeScriptdatumshape +x/ydata accessors (currently requires{ x, y }datumshape)hookssupportNew mid-term features
Tooltipin aPortalto fix z-index stacking context problemLegends@vxprimitives likebrush,zoom, anddragcanvassupport βvxis currently mostlysvgbased (this likely requires updates in othervxpackages)@data-uidoes not support animation. while this may not need to be fully baked in we should expose hooks to animatexy-chartAPI
@techniq has done some great research on declarative
reactchart APIs here. Generally they are composable:However there are some key differences:
dataprovided at theDataSerieslevel vs thechartcontainer levelDataSerieslevel β β favoredPros π
datato the series that will visually represent it@vx/shape's (the basis forDataSeries) currently require data, so this is more consistent with separate package APIsx-andy-extent from theirdata, and theChartcontainer can simply collect thesehorizontalorientation to be pushed to theSeries-level without requiring it to be implemented by allSeriesand theChartcontainer doesn't need to have any knowledge of itCons π
Chartcontainer needs a way to access the data across all seriesChartcontainer level β β disfavoredPros π
Chartneeds access to all data in order to providex-andy-scales; this makes that easy.Cons π
Seriesmay require custom logic to computex-andy-extent from theirdata(e.g., a bar stack) which theChartneeds to be aware of in this modelkeyaccessors at theSeries-level forSeriesdataDataSeriesto 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-visand@data-uiexpose mouse and touch events at both theChartandSerieslevel; these use standardreactevents such asonClick,onTouchMove, etc.Custom event syntax β β disfavored
Some libraries like
Victoryhave their own custom event system with non-standard syntax and selection language.reacthooksI've not been able to find any
reactvis libraries which exposehooks. Feels like an opportunity on top of a render / component API πImplementation
We'd like to improve upon the following limitations of
@data-uiv1implementation:Written in TypeScript
@data-uiwas written in JavaScript, butvxis now a TypeScript project and typings will be similarly useful for@data-ui.Use
reactcontextover cloning children@data-uiwas implemented before the new / more robust16.3contextAPI. Therefore chartstylesand sharedscalesare passed viaprops +React.cloneElement. Combined withhooks, usingcontextshould open up a whole new set of API possibilities (see below).What is kept in context
The function of the
XYChartwrapper largely equates to managing shared state across the elements of a chart, which components can leverage as needed. . This includestheme+stylesxScalethat accounts for data range of all chart series and chartwidth+marginyScalethat accounts for data range of all chart series and chartheight+margintooltipData+tooltipCoords, when applicableIn addition to chart
width+height, theChartcontainer must have knowledge of alldata(+annotation) values to computescales appropriately. Rather than having theChartintrospect props fromchildDataSeriesorAnnotations(which can get gnarly) we propose thatDataSeriesandAnnotationsregister theirdata,xValues, andyValuesincontext.This pushes the logic of
data <> x/y extentmapping toDataSeriesrather than theChart, and allows theChartto leverage these values to compute scales properly. It could look something likeUnknowns
I'm unsure if there are major performance implications of using hooks β‘ π
Proposed API
The same functionality could be provided in a
componentAPI:cc @hshoff @techniq @kristw