Skip to content

Commit

Permalink
new(xy-chart): add hook to context for full scale modification, not j…
Browse files Browse the repository at this point in the history
…ust domain
  • Loading branch information
williaster committed Aug 12, 2020
1 parent 9f3875a commit 855313c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 49 deletions.
37 changes: 21 additions & 16 deletions packages/vx-demo/src/sandboxes/vx-chart-poc/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ import Stack from './src/components/series/Stack';

type DataKeys = 'austin' | 'sf' | 'ny';

const data = cityTemperature.slice(100, 100 + 12).map(({ date, ...d }) => ({
...d,
// current format is like `20200105` which you can't form a valid date from
// @TODO PR soon!
date: `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`,
key: date,
})) as CityTemperature[];
const data = cityTemperature.slice(100, 100 + 16);

// @TODO wip updating data, not currently used
// const halfData = data.slice(0, Math.floor(data.length / 2));
Expand Down Expand Up @@ -129,7 +123,17 @@ export default function Example() {
const [renderTooltipInPortal, setRenderTooltipInPortal] = useState(true);
const [visibleSeries, setVisibleSeries] = useState<
('line' | 'bar' | 'groupedbar' | 'stackedbar')[]
>(['stackedbar']);
>(['bar']);
const canSnapTooltipToDataX =
(visibleSeries.includes('groupedbar') && renderHorizontally) ||
(visibleSeries.includes('stackedbar') && !renderHorizontally) ||
visibleSeries.includes('bar');

const canSnapTooltipToDataY =
(visibleSeries.includes('groupedbar') && !renderHorizontally) ||
(visibleSeries.includes('stackedbar') && renderHorizontally) ||
visibleSeries.includes('bar');

const dateScaleConfig: ScaleConfig<string> = useMemo(() => ({ type: 'band', padding: 0.2 }), []);
const temperatureScaleConfig: ScaleConfig<number> = useMemo(
() => ({
Expand Down Expand Up @@ -262,8 +266,8 @@ export default function Example() {
/>
</XYChart>
<Tooltip
snapToDataX={snapTooltipToDataX}
snapToDataY={snapTooltipToDataY}
snapToDataX={snapTooltipToDataX && canSnapTooltipToDataX}
snapToDataY={snapTooltipToDataY && canSnapTooltipToDataY}
renderTooltip={renderTooltip}
renderInPortal={renderTooltipInPortal}
/>
Expand All @@ -279,7 +283,6 @@ export default function Example() {
<button
onClick={() => {
setDataMultiplier(5 * Math.random());
// setCurrData(currData === data ? halfData : data);
}}
>
Update data
Expand Down Expand Up @@ -349,6 +352,7 @@ export default function Example() {
<label>
<input
type="checkbox"
disabled={!canSnapTooltipToDataX}
checked={snapTooltipToDataX}
onChange={() => setSnapTooltipToDataX(!snapTooltipToDataX)}
/>
Expand All @@ -357,6 +361,7 @@ export default function Example() {
<label>
<input
type="checkbox"
disabled={!canSnapTooltipToDataY}
checked={snapTooltipToDataY}
onChange={() => setSnapTooltipToDataY(!snapTooltipToDataY)}
/>
Expand Down Expand Up @@ -505,11 +510,11 @@ export default function Example() {
/>{' '}
right
</label>
{/* <br />
<label>
<input type="checkbox" onChange={() => setAutoWidth(!autoWidth)} checked={autoWidth} />{' '}
responsive width
</label> */}
<br />
<label>
<input type="checkbox" onChange={() => setAutoWidth(!autoWidth)} checked={autoWidth} />{' '}
responsive width
</label>
</div>
<div>
<strong>series</strong>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
RegisterData,
Margin,
DatumWithKey,
ScaleType,
} from '../../types';
import ChartContext from '../../context/ChartContext';
import createScale from '../../createScale';
Expand Down Expand Up @@ -69,7 +70,7 @@ export default class ChartProvider<
}

/** Adds data to the registry and to combined data if it supports events. */
registerData: RegisterData = dataToRegister => {
registerData: RegisterData<XScaleInput, YScaleInput, Datum> = dataToRegister => {
this.setState(state => {
const nextState = {
...state,
Expand Down Expand Up @@ -156,38 +157,35 @@ export default class ChartProvider<

if (width == null || height == null) return;

let xDomain = combinedData.map(({ key, datum }) =>
dataRegistry[key]?.xAccessor(datum),
) as XScaleInput[];

let yDomain = combinedData.map(({ key, datum }) =>
dataRegistry[key]?.yAccessor(datum),
) as YScaleInput[];

// apply any updates to the domain from the registry
Object.values(dataRegistry).forEach(registry => {
if (registry.xDomain) xDomain = registry.xDomain(xDomain);
if (registry.yDomain) yDomain = registry.yDomain(yDomain);
});

const xScale = createScale<XScaleInput>({
data: xDomain,
let xScale = createScale<XScaleInput>({
data: combinedData.map(({ key, datum }) =>
dataRegistry[key]?.xAccessor(datum),
) as XScaleInput[],
scaleConfig: xScaleConfig,
range: [margin.left, width - margin.right],
});
}) as ScaleType<XScaleInput, number>;

const yScale = createScale<YScaleInput>({
data: yDomain,
let yScale = createScale<YScaleInput>({
data: combinedData.map(({ key, datum }) =>
dataRegistry[key]?.yAccessor(datum),
) as YScaleInput[],
scaleConfig: yScaleConfig,
range: [height - margin.bottom, margin.top],
});
}) as ScaleType<YScaleInput, number>;

const colorScale = scaleOrdinal({
domain: Object.keys(dataRegistry),
range: theme.colors,
...colorScaleConfig,
});

// apply any updates to the scales from the registry
// @TODO this order currently overrides any changes from x/yScaleConfig
Object.values(dataRegistry).forEach(registry => {
if (registry.xScale) xScale = registry.xScale(xScale);
if (registry.yScale) yScale = registry.yScale(yScale);
});

return { xScale, yScale, colorScale };
};

Expand All @@ -199,6 +197,7 @@ export default class ChartProvider<
}
};

/** */
findNearestData = (event: React.MouseEvent | React.TouchEvent) => {
const { width, height, margin, xScale, yScale, dataRegistry } = this.state;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import BarStack from '@vx/shape/lib/shapes/BarStack';
import BarStackHorizontal from '@vx/shape/lib/shapes/BarStackHorizontal';
import { BarStack as BarStackType } from '@vx/shape/lib/types';
import ChartContext from '../../context/ChartContext';
import { DataRegistry, ChartContext as ChartContextType, NearestDatumArgs } from '../../types';
import {
DataRegistry,
ChartContext as ChartContextType,
NearestDatumArgs,
ScaleType,
} from '../../types';

import BarSeries from './BarSeries';
import findNearestDatumY from '../../util/findNearestDatumY';
Expand All @@ -13,6 +18,10 @@ import AnimatedBars from './AnimatedBars';

const STACK_ACCESSOR = d => d.stack;

type CombinedData<XScaleInput, YScaleInput> = {
[dataKey: string]: XScaleInput | YScaleInput | number;
} & { stack: XScaleInput | YScaleInput; positiveSum: number; negativeSum: number };

export type GroupProps = {
horizontal?: boolean;
children: typeof BarSeries;
Expand All @@ -38,7 +47,7 @@ export default function Stack<Datum, XScaleInput, YScaleInput>({
// override the findNearestDatum logic
const findNearestDatum = useCallback(
(args: NearestDatumArgs<Datum, XScaleInput, YScaleInput>) => {
if (!stacks.current) return;
if (!stacks.current) return null;

const nearestDatum = horizontal
? findNearestDatumY<Datum, XScaleInput, YScaleInput>(args)
Expand Down Expand Up @@ -84,8 +93,10 @@ export default function Stack<Datum, XScaleInput, YScaleInput>({
);

// group all child data by stack value, this format is needed by BarStack
const combinedData: { [key: string]: number }[] = useMemo(() => {
const dataByStackValue = {};
const combinedData: CombinedData<XScaleInput, YScaleInput>[] = useMemo(() => {
const dataByStackValue: {
[stackValue: string]: CombinedData<XScaleInput, YScaleInput>;
} = {};
React.Children.forEach(children, child => {
const { dataKey, data = [], xAccessor, yAccessor } = child.props;

Expand Down Expand Up @@ -127,8 +138,17 @@ export default function Stack<Datum, XScaleInput, YScaleInput>({

// only need to update the domain for one of the keys
if (comprehensiveDomain.length > 0 && dataKeys.indexOf(key) === 0) {
dataToRegister[key][horizontal ? 'xDomain' : 'yDomain'] = domain =>
extent([...domain, ...comprehensiveDomain], d => d);
if (horizontal) {
dataToRegister[key].xScale = (scale: ScaleType<XScaleInput, number>) =>
scale.domain(
extent<number | XScaleInput>([...scale.domain(), ...comprehensiveDomain], d => d),
);
} else {
dataToRegister[key].yScale = (scale: ScaleType<YScaleInput, number>) =>
scale.domain(
extent<number | YScaleInput>([...scale.domain(), ...comprehensiveDomain], d => d),
);
}
}
});

Expand Down
14 changes: 8 additions & 6 deletions packages/vx-demo/src/sandboxes/vx-chart-poc/src/types/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ export interface DataRegistry<Datum = unknown, XScaleInput = unknown, YScaleInpu
yAccessor: (d: Datum) => YScaleInput;
/** whether the entry supports mouse events. */
mouseEvents?: boolean;
/** Optionally update the domain of the xScale. */
xDomain?: (domain: XScaleInput[]) => XScaleInput[];
/** Optionally update the domain of the yScale. */
yDomain?: (domain: YScaleInput[]) => YScaleInput[];
/** Optionally update the xScale. */
xScale?: (xScale: ScaleType<XScaleInput, number>) => ScaleType<XScaleInput, number>;
/** Optionally update the yScale. */
yScale?: (yScale: ScaleType<YScaleInput, number>) => ScaleType<YScaleInput, number>;
/** Optionally override logic for finding the nearest data point to a mouse event. */
findNearestDatum?: FindNearestDatum<Datum, XScaleInput, YScaleInput>;
/** Legend shape */
legendShape?: LegendShape;
};
}

export type RegisterData = (data: DataRegistry) => void;
export type RegisterData<Datum, XScaleInput, YScaleInput> = (
data: DataRegistry<Datum, XScaleInput, YScaleInput>,
) => void;

export type DatumWithKey<Datum = unknown> = { datum: Datum; key: string; index: number };

Expand Down Expand Up @@ -57,7 +59,7 @@ export interface ChartContext<
height: number | null;
margin: Margin;
dataRegistry: DataRegistry<Datum, XScaleInput, YScaleInput>;
registerData: RegisterData;
registerData: RegisterData<Datum, XScaleInput, YScaleInput>;
unregisterData: (keyOrKeys: string | string[]) => void;
setChartDimensions: (dims: { width: number; height: number; margin: Margin }) => void;
findNearestData: (
Expand Down

0 comments on commit 855313c

Please sign in to comment.