Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions packages/visx-xychart/src/components/XYChart.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint jsx-a11y/mouse-events-have-key-events: 'off', @typescript-eslint/no-explicit-any: 'off' */
import React, { useContext, useEffect } from 'react';
import ParentSize from '@visx/responsive/lib/components/ParentSize';
import { AxisScaleOutput } from '@visx/axis';
import { AxisScale, AxisScaleOutput } from '@visx/axis';
import { ScaleConfig } from '@visx/scale';

import DataContext from '../context/DataContext';
import { Margin, EventHandlerParams } from '../types';
import { DataRegistryEntry } from '../types/data';
import useEventEmitter from '../hooks/useEventEmitter';
import EventEmitterProvider from '../providers/EventEmitterProvider';
import TooltipContext from '../context/TooltipContext';
Expand Down Expand Up @@ -35,14 +36,18 @@ export type XYChartProps<
height?: number;
/** Margin to apply around the outside. */
margin?: Margin;
/** XYChart data to be rendered in Series. */
data?:
| DataRegistryEntry<AxisScale, AxisScale, Datum>
| DataRegistryEntry<AxisScale, AxisScale, Datum>[];
/** XYChart children (Series, Tooltip, etc.). */
children: React.ReactNode;
/** If DataContext is not available, XYChart will wrap itself in a DataProvider and set this as the theme. */
theme?: DataProviderProps<XScaleConfig, YScaleConfig>['theme'];
theme?: DataProviderProps<XScaleConfig, YScaleConfig, Datum>['theme'];
/** If DataContext is not available, XYChart will wrap itself in a DataProvider and set this as the xScale config. */
xScale?: DataProviderProps<XScaleConfig, YScaleConfig>['xScale'];
xScale?: DataProviderProps<XScaleConfig, YScaleConfig, Datum>['xScale'];
/** If DataContext is not available, XYChart will wrap itself in a DataProvider and set this as the yScale config. */
yScale?: DataProviderProps<XScaleConfig, YScaleConfig>['yScale'];
yScale?: DataProviderProps<XScaleConfig, YScaleConfig, Datum>['yScale'];
/* If DataContext is not available, XYChart will wrap itself in a DataProvider and set this as horizontal. Determines whether Series will be plotted horizontally (e.g., horizontal bars). By default this will try to be inferred based on scale types. */
horizontal?: boolean | 'auto';
/** Callback invoked for onPointerMove events for the nearest Datum to the PointerEvent _for each Series with pointerEvents={true}_. */
Expand Down Expand Up @@ -85,6 +90,7 @@ export default function XYChart<
accessibilityLabel = 'XYChart',
captureEvents = true,
children,
data,
height,
horizontal,
margin = DEFAULT_MARGIN,
Expand Down Expand Up @@ -127,6 +133,7 @@ export default function XYChart<
}
return (
<DataProvider
data={data}
xScale={xScale}
yScale={yScale}
theme={theme}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext, useCallback, useMemo } from 'react';
import { AxisScale } from '@visx/axis';
import { ScaleInput } from '@visx/scale';
import Area, { AreaProps } from '@visx/shape/lib/shapes/Area';
import LinePath, { LinePathProps } from '@visx/shape/lib/shapes/LinePath';
import DataContext from '../../../context/DataContext';
Expand Down Expand Up @@ -48,15 +49,18 @@ function BaseAreaSeries<XScale extends AxisScale, YScale extends AxisScale, Datu
onPointerUp,
enableEvents = true,
renderLine = true,
xAccessor,
xAccessor: _xAccessor,
x0Accessor,
xScale,
yAccessor,
yAccessor: _yAccessor,
y0Accessor,
yScale,
...areaProps
}: BaseAreaSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
const { colorScale, theme, horizontal } = useContext(DataContext);
const { colorScale, dataRegistry, theme, horizontal } = useContext(DataContext);

const xAccessor: (d: Datum) => ScaleInput<XScale> = _xAccessor ?? dataRegistry.get(dataKey).xAccessor;
const yAccessor: (d: Datum) => ScaleInput<YScale> = _yAccessor ?? dataRegistry.get(dataKey).yAccessor;
const getScaledX0 = useMemo(
() => (x0Accessor ? getScaledValueFactory(xScale, x0Accessor) : undefined),
[xScale, x0Accessor],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ function BaseAreaStack<XScale extends AxisScale, YScale extends AxisScale, Datum
const findNearestDatum = useCallback(
(params: NearestDatumArgs<XScale, YScale, AreaStackDatum>): NearestDatumReturnType<Datum> => {
const childData = seriesChildren.find((child) => child.props.dataKey === params.dataKey)
?.props?.data;
?.props?.data ?? dataRegistry.get(params.dataKey);
return childData ? findNearestStackDatum(params, childData, horizontal) : null;
},
[seriesChildren, horizontal],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ export default function BaseBarGroup<

// register all child data
useEffect(() => {
const dataToRegister = barSeriesChildren.map((child) => {
barSeriesChildren.forEach((child) => {
const { dataKey: key, data, xAccessor, yAccessor } = child.props;
return { key, data, xAccessor, yAccessor };
const dataToRegister = { key, data, xAccessor, yAccessor };
if (data && xAccessor && yAccessor) registerData(dataToRegister);
});

registerData(dataToRegister);
return () => unregisterData(dataKeys);
}, [registerData, unregisterData, barSeriesChildren, dataKeys]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext, useCallback, useMemo } from 'react';
import { AxisScale } from '@visx/axis';
import { ScaleInput } from '@visx/scale';
import DataContext from '../../../context/DataContext';
import { Bar, BarsProps, SeriesProps } from '../../../types';
import withRegisteredData, { WithRegisteredDataProps } from '../../../enhancers/withRegisteredData';
Expand Down Expand Up @@ -46,19 +47,23 @@ function BaseBarSeries<XScale extends AxisScale, YScale extends AxisScale, Datum
onPointerOut,
onPointerUp,
enableEvents = true,
xAccessor,
xAccessor: _xAccessor,
xScale,
yAccessor,
yAccessor: _yAccessor,
yScale,
...barComponentProps
}: BaseBarSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
const {
colorScale,
dataRegistry,
horizontal,
theme,
innerWidth = 0,
innerHeight = 0,
} = useContext(DataContext);

const xAccessor: (d: Datum) => ScaleInput<XScale> = _xAccessor ?? dataRegistry.get(dataKey).xAccessor;
const yAccessor: (d: Datum) => ScaleInput<YScale> = _yAccessor ?? dataRegistry.get(dataKey).yAccessor;
const getScaledX = useCallback(getScaledValueFactory(xScale, xAccessor), [xScale, xAccessor]);
const getScaledY = useCallback(getScaledValueFactory(yScale, yAccessor), [yScale, yAccessor]);
const scaleBandwidth = getScaleBandwidth(horizontal ? yScale : xScale);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function BaseBarStack<
params: NearestDatumArgs<XScale, YScale, BarStackDatum<XScale, YScale>>,
): NearestDatumReturnType<Datum> => {
const childData = seriesChildren.find((child) => child.props.dataKey === params.dataKey)
?.props?.data;
?.props?.data ?? dataRegistry.get(params.dataKey);
return childData ? findNearestStackDatum(params, childData, horizontal) : null;
},
[seriesChildren, horizontal],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext, useCallback, useMemo } from 'react';
import { AxisScale } from '@visx/axis';
import { ScaleInput } from '@visx/scale';
import DataContext from '../../../context/DataContext';
import { GlyphProps, GlyphsProps, SeriesProps } from '../../../types';
import withRegisteredData, { WithRegisteredDataProps } from '../../../enhancers/withRegisteredData';
Expand Down Expand Up @@ -37,12 +38,15 @@ export function BaseGlyphSeries<
enableEvents = true,
renderGlyphs,
size = 8,
xAccessor,
xAccessor: _xAccessor,
xScale,
yAccessor,
yAccessor: _yAccessor,
yScale,
}: BaseGlyphSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
const { colorScale, theme, horizontal } = useContext(DataContext);
const { colorScale, dataRegistry, theme, horizontal } = useContext(DataContext);

const xAccessor: (d: Datum) => ScaleInput<XScale> = _xAccessor ?? dataRegistry.get(dataKey).xAccessor;
const yAccessor: (d: Datum) => ScaleInput<YScale> = _yAccessor ?? dataRegistry.get(dataKey).yAccessor;
const getScaledX = useCallback(getScaledValueFactory(xScale, xAccessor), [xScale, xAccessor]);
const getScaledY = useCallback(getScaledValueFactory(yScale, yAccessor), [yScale, yAccessor]);
const color = colorScale?.(dataKey) ?? theme?.colors?.[0] ?? '#222';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext, useCallback } from 'react';
import LinePath, { LinePathProps } from '@visx/shape/lib/shapes/LinePath';
import { AxisScale } from '@visx/axis';
import { ScaleInput } from '@visx/scale';
import DataContext from '../../../context/DataContext';
import { GlyphsProps, SeriesProps } from '../../../types';
import withRegisteredData, { WithRegisteredDataProps } from '../../../enhancers/withRegisteredData';
Expand Down Expand Up @@ -32,14 +33,16 @@ function BaseLineSeries<XScale extends AxisScale, YScale extends AxisScale, Datu
onPointerOut,
onPointerUp,
enableEvents = true,
xAccessor,
xAccessor: _xAccessor,
xScale,
yAccessor,
yAccessor: _yAccessor,
yScale,
PathComponent = 'path',
...lineProps
}: BaseLineSeriesProps<XScale, YScale, Datum> & WithRegisteredDataProps<XScale, YScale, Datum>) {
const { colorScale, theme } = useContext(DataContext);
const { colorScale, dataRegistry, theme } = useContext(DataContext);
const xAccessor: (d: Datum) => ScaleInput<XScale> = _xAccessor ?? dataRegistry.get(dataKey).xAccessor;
const yAccessor: (d: Datum) => ScaleInput<YScale> = _yAccessor ?? dataRegistry.get(dataKey).yAccessor;
const getScaledX = useCallback(getScaledValueFactory(xScale, xAccessor), [xScale, xAccessor]);
const getScaledY = useCallback(getScaledValueFactory(yScale, yAccessor), [yScale, yAccessor]);
const isDefined = useCallback(
Expand Down
4 changes: 3 additions & 1 deletion packages/visx-xychart/src/enhancers/withRegisteredData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export default function withRegisteredData<
>;

useEffect(() => {
if (dataRegistry) dataRegistry.registerData({ key: dataKey, data, xAccessor, yAccessor });
if (data && xAccessor && yAccessor && dataRegistry) {
dataRegistry.registerData({ key: dataKey, data, xAccessor, yAccessor });
}
return () => dataRegistry?.unregisterData(dataKey);
}, [dataRegistry, dataKey, data, xAccessor, yAccessor]);

Expand Down
30 changes: 23 additions & 7 deletions packages/visx-xychart/src/hooks/useStackedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { StackPathConfig } from '@visx/shape';
import { extent } from 'd3-array';
import { AxisScale } from '@visx/axis';
import DataContext from '../context/DataContext';
import { CombinedStackData, DataContextType, SeriesProps } from '../types';
import {
CombinedStackData,
DataContextType,
DataRegistryEntry,
SeriesProps,
} from '../types';
import getBarStackRegistryData from '../utils/getBarStackRegistryData';
import combineBarStackData from '../utils/combineBarStackData';
import getChildrenAndGrandchildrenWithProps from '../utils/getChildrenAndGrandchildrenWithProps';
Expand All @@ -23,7 +28,7 @@ export default function useStackedData<
>({ children, order, offset }: UseStackedData<Datum>) {
type StackDatum = SeriesPoint<CombinedStackData<XScale, YScale>>;

const { horizontal, registerData, unregisterData } = useContext(
const { horizontal, dataRegistry, registerData, unregisterData } = useContext(
DataContext,
) as unknown as DataContextType<XScale, YScale, StackDatum>;

Expand All @@ -34,17 +39,28 @@ export default function useStackedData<
[children],
);

// extract data keys from child series
const dataKeys: string[] = useMemo(
() => seriesChildren.filter((child) => child.props.dataKey).map((child) => child.props.dataKey),
const stackedSeries: DataRegistryEntry<XScale, YScale, Datum>[] = useMemo(
() => seriesChildren
.filter((child) => child.props.dataKey)
.map((child) => (
'data' in child.props
? {key: child.props.dataKey, ...child.props}
: dataRegistry.get(child.props.dataKey)
)),
[seriesChildren],
);

// extract data keys from child series
const dataKeys = useMemo(
() => stackedSeries.map((series) => series.key),
[stackedSeries],
);

// group all child data by stack value { [x | y]: { [dataKey]: value } }
// this format is needed by d3Stack
const combinedData = useMemo(
() => combineBarStackData<XScale, YScale, Datum>(seriesChildren, horizontal),
[horizontal, seriesChildren],
() => combineBarStackData<XScale, YScale, Datum>(stackedSeries, horizontal),
[stackedSeries, horizontal],
);

// stack data
Expand Down
15 changes: 13 additions & 2 deletions packages/visx-xychart/src/providers/DataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import { ScaleConfig, ScaleConfigToD3Scale } from '@visx/scale';
import React, { useContext, useMemo } from 'react';
import createOrdinalScale from '@visx/scale/lib/scales/ordinal';
import { AxisScaleOutput } from '@visx/axis';
import { AxisScale, AxisScaleOutput } from '@visx/axis';
import { XYChartTheme } from '../types';
import { DataRegistryEntry } from '../types/data';
import ThemeContext from '../context/ThemeContext';
import DataContext from '../context/DataContext';
import useDataRegistry from '../hooks/useDataRegistry';
Expand All @@ -15,6 +16,7 @@ import isDiscreteScale from '../utils/isDiscreteScale';
export type DataProviderProps<
XScaleConfig extends ScaleConfig<AxisScaleOutput, any, any>,
YScaleConfig extends ScaleConfig<AxisScaleOutput, any, any>,
Datum extends object,
> = {
/* Optionally define the initial dimensions. */
initialDimensions?: Partial<Dimensions>;
Expand All @@ -24,6 +26,10 @@ export type DataProviderProps<
xScale: XScaleConfig;
/* y-scale configuration whose shape depends on scale type. */
yScale: YScaleConfig;
/** XYChart data to be rendered in Series. */
data?:
| DataRegistryEntry<AxisScale, AxisScale, Datum>
| DataRegistryEntry<AxisScale, AxisScale, Datum>[];
/* Any React children. */
children: React.ReactNode;
/* Determines whether Series will be plotted horizontally (e.g., horizontal bars). By default this will try to be inferred based on scale types. */
Expand All @@ -39,9 +45,10 @@ export default function DataProvider<
theme: propsTheme,
xScale: xScaleConfig,
yScale: yScaleConfig,
data,
children,
horizontal: initialHorizontal = 'auto',
}: DataProviderProps<XScaleConfig, YScaleConfig>) {
}: DataProviderProps<XScaleConfig, YScaleConfig, Datum>) {
// `DataProvider` provides a theme so that `ThemeProvider` is not strictly needed.
// `props.theme` takes precedent over `context.theme`, which has a default even if
// a ThemeProvider is not present.
Expand All @@ -56,6 +63,10 @@ export default function DataProvider<

const dataRegistry = useDataRegistry<XScale, YScale, Datum>();

useMemo(() => {
if (data) dataRegistry.registerData(data)
}, [data]);

const { xScale, yScale }: { xScale?: XScale; yScale?: YScale } = useScales({
dataRegistry,
xScaleConfig,
Expand Down
6 changes: 3 additions & 3 deletions packages/visx-xychart/src/types/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export type SeriesProps<
/** Required data key for the Series, should be unique across all series. */
dataKey: string;
/** Data for the Series. */
data: Datum[];
data?: Datum[];
/** Given a Datum, returns the x-scale value. */
xAccessor: (d: Datum) => ScaleInput<XScale>;
xAccessor?: (d: Datum) => ScaleInput<XScale>;
/** Given a Datum, returns the y-scale value. */
yAccessor: (d: Datum) => ScaleInput<YScale>;
yAccessor?: (d: Datum) => ScaleInput<YScale>;
/**
* Callback invoked for onPointerMove events for the nearest Datum to the PointerEvent.
* By default XYChart will capture and emit PointerEvents, invoking this function for
Expand Down
10 changes: 5 additions & 5 deletions packages/visx-xychart/src/utils/combineBarStackData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { AxisScale } from '@visx/axis';
import { CombinedStackData, SeriesProps } from '../types';
import { CombinedStackData, DataRegistryEntry, SeriesProps } from '../types';

/** Returns the value which forms a stack group. */
export const getStackValue = <XScale extends AxisScale, YScale extends AxisScale>(
Expand All @@ -17,15 +17,15 @@ export default function combineBarStackData<
YScale extends AxisScale,
Datum extends object,
>(
seriesChildren: React.ReactElement<SeriesProps<XScale, YScale, Datum>>[],
stackedSeries: DataRegistryEntry<XScale, YScale, Datum>[],
horizontal?: boolean,
): CombinedStackData<XScale, YScale>[] {
const dataByStackValue: {
[stackValue: string]: CombinedStackData<XScale, YScale>;
} = {};

seriesChildren.forEach((child) => {
const { dataKey, data, xAccessor, yAccessor } = child.props;
stackedSeries.forEach((series) => {
const { key, data, xAccessor, yAccessor } = series;

// this should exist but double check
if (!xAccessor || !yAccessor) return;
Expand All @@ -39,7 +39,7 @@ export default function combineBarStackData<
if (!dataByStackValue[stackKey]) {
dataByStackValue[stackKey] = { stack, positiveSum: 0, negativeSum: 0 };
}
dataByStackValue[stackKey][dataKey] = numericValue;
dataByStackValue[stackKey][key] = numericValue;
dataByStackValue[stackKey][numericValue >= 0 ? 'positiveSum' : 'negativeSum'] += numericValue;
});
});
Expand Down