Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
575 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
packages/vx-demo/src/sandboxes/vx-xychart/CustomChartBackground.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React, { useContext } from 'react'; | ||
import { PatternLines } from '@vx/pattern'; | ||
import { DataContext } from '@vx/xychart'; | ||
|
||
const patternId = 'xy-chart-pattern'; | ||
|
||
export default function CustomChartBackground() { | ||
const { theme, margin, width, height } = useContext(DataContext); | ||
const textStyles = { ...theme?.axisStyles.x.bottom.axisLabel, textAnchor: 'start' }; | ||
|
||
// early return values not available in context | ||
if (width == null || height == null || margin == null || theme == null) return null; | ||
|
||
return ( | ||
<> | ||
<PatternLines | ||
id={patternId} | ||
width={10} | ||
height={10} | ||
orientation={['diagonal']} | ||
stroke={theme?.gridStyles?.stroke} | ||
strokeWidth={1} | ||
/> | ||
<rect x={0} y={0} width={width} height={height} fill={theme?.backgroundColor ?? '#fff'} /> | ||
<rect | ||
x={margin.left} | ||
y={margin.top} | ||
width={width - margin.left - margin.right} | ||
height={height - margin.top - margin.bottom} | ||
fill={`url(#${patternId})`} | ||
/> | ||
<text x={margin.left} y={height - margin.top + margin.bottom / 2} {...textStyles}> | ||
width {width}px | ||
</text> | ||
<g transform={`translate(${margin.left / 1.3}, ${height - margin.bottom})rotate(-90)`}> | ||
<text {...textStyles}>height {height}px</text> | ||
</g> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { AxisScale } from '@vx/axis'; | ||
import { DataRegistryEntry } from '../types/data'; | ||
|
||
/** A class for holding data entries */ | ||
export default class DataRegistry< | ||
XScale extends AxisScale, | ||
YScale extends AxisScale, | ||
Datum = unknown | ||
> { | ||
private registry: { [key: string]: DataRegistryEntry<XScale, YScale, Datum> } = {}; | ||
|
||
private registryKeys: string[] = []; | ||
|
||
/** Add one or more entries to the registry. */ | ||
public registerData( | ||
entryOrEntries: | ||
| DataRegistryEntry<XScale, YScale, Datum> | ||
| DataRegistryEntry<XScale, YScale, Datum>[], | ||
) { | ||
const entries = Array.isArray(entryOrEntries) ? entryOrEntries : [entryOrEntries]; | ||
entries.forEach(currEntry => { | ||
if (currEntry.key in this.registry && this.registry[currEntry.key] != null) { | ||
console.debug('Overriding data registry key', currEntry.key); | ||
this.registryKeys = this.registryKeys.filter(key => key !== currEntry.key); | ||
} | ||
this.registry[currEntry.key] = currEntry; | ||
this.registryKeys.push(currEntry.key); | ||
}); | ||
} | ||
|
||
/** Remove one or more entries to the registry. */ | ||
public unregisterData(keyOrKeys: string | string[]) { | ||
const keys = Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys]; | ||
keys.forEach(currKey => { | ||
delete this.registry[currKey]; | ||
this.registryKeys = this.registryKeys.filter(key => key !== currKey); | ||
}); | ||
} | ||
|
||
/** Returns all data registry entries. This value is not constant between calls. */ | ||
public entries() { | ||
return Object.values(this.registry); | ||
} | ||
|
||
/** Returns a specific entity from the registry, if it exists. */ | ||
public get(key: string) { | ||
return this.registry[key]; | ||
} | ||
|
||
/** | ||
* Returns the current registry keys. | ||
* This value is constant between calls if the keys themselves have not changed. | ||
*/ | ||
public keys() { | ||
return this.registryKeys; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,38 @@ | ||
import React, { useContext } from 'react'; | ||
import ThemeContext from '../context/ThemeContext'; | ||
import React, { useContext, useEffect } from 'react'; | ||
import ParentSize from '@vx/responsive/lib/components/ParentSize'; | ||
|
||
export default function XYChart() { | ||
const theme = useContext(ThemeContext); | ||
return ( | ||
<div | ||
style={{ | ||
width: '100%', | ||
height: 400, | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
background: theme.backgroundColor, | ||
border: `1px solid ${theme?.gridStyles?.stroke}`, | ||
...theme.htmlLabelStyles, | ||
}} | ||
> | ||
XYChart | ||
</div> | ||
); | ||
import DataContext from '../context/DataContext'; | ||
import { Margin } from '../types'; | ||
|
||
const DEFAULT_MARGIN = { top: 50, right: 50, bottom: 50, left: 50 }; | ||
|
||
type Props = { | ||
events?: boolean; | ||
width?: number; | ||
height?: number; | ||
margin?: Margin; | ||
children: React.ReactNode; | ||
}; | ||
|
||
export default function XYChart(props: Props) { | ||
const { children, width, height, margin = DEFAULT_MARGIN } = props; | ||
const { setDimensions } = useContext(DataContext); | ||
|
||
// update dimensions in context | ||
useEffect(() => { | ||
if (setDimensions && width != null && height != null && width > 0 && height > 0) { | ||
setDimensions({ width, height, margin }); | ||
} | ||
}, [setDimensions, width, height, margin]); | ||
|
||
// if width and height aren't both provided, wrap in auto-sizer + preserve passed dims | ||
if (width == null || height == null) { | ||
return <ParentSize>{dims => <XYChart {...dims} {...props} />}</ParentSize>; | ||
} | ||
|
||
return width > 0 && height > 0 ? ( | ||
<svg width={width} height={height}> | ||
{children} | ||
</svg> | ||
) : null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React, { useContext, useCallback, useEffect } from 'react'; | ||
import LinePath from '@vx/shape/lib/shapes/LinePath'; | ||
import { ScaleConfig, ScaleConfigToD3Scale, ScaleInput } from '@vx/scale'; | ||
import { AxisScaleOutput } from '@vx/axis'; | ||
import DataContext from '../../context/DataContext'; | ||
import isValidNumber from '../../typeguards/isValidNumber'; | ||
|
||
type LineSeriesProps< | ||
XScaleConfig extends ScaleConfig<AxisScaleOutput>, | ||
YScaleConfig extends ScaleConfig<AxisScaleOutput>, | ||
Datum | ||
> = { | ||
dataKey: string; | ||
data: Datum[]; | ||
xAccessor: ( | ||
d: Datum, | ||
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
ScaleInput<ScaleConfigToD3Scale<XScaleConfig, AxisScaleOutput, any, any>>; | ||
yAccessor: ( | ||
d: Datum, | ||
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
ScaleInput<ScaleConfigToD3Scale<YScaleConfig, AxisScaleOutput, any, any>>; | ||
}; | ||
|
||
export default function LineSeries< | ||
XScaleConfig extends ScaleConfig<AxisScaleOutput>, | ||
YScaleConfig extends ScaleConfig<AxisScaleOutput>, | ||
Datum | ||
>({ | ||
data, | ||
xAccessor, | ||
yAccessor, | ||
dataKey, | ||
...lineProps | ||
}: LineSeriesProps<XScaleConfig, YScaleConfig, Datum>) { | ||
const { xScale, yScale, colorScale, dataRegistry } = useContext(DataContext); | ||
|
||
// register data on mount | ||
// @TODO(chris) make this easier with HOC | ||
useEffect(() => { | ||
if (dataRegistry) dataRegistry.registerData({ key: dataKey, data, xAccessor, yAccessor }); | ||
return () => dataRegistry?.unregisterData(dataKey); | ||
}, [dataRegistry, dataKey, data, xAccessor, yAccessor]); | ||
|
||
const getScaledX = useCallback( | ||
(d: Datum) => { | ||
const x = xScale?.(xAccessor(d)); | ||
return isValidNumber(x) | ||
? x + (xScale && 'bandwidth' in xScale ? xScale?.bandwidth?.() ?? 0 : 0) / 2 | ||
: NaN; | ||
}, | ||
[xScale, xAccessor], | ||
); | ||
|
||
const getScaledY = useCallback( | ||
(d: Datum) => { | ||
const y = yScale?.(yAccessor(d)); | ||
return isValidNumber(y) | ||
? y + (yScale && 'bandwidth' in yScale ? yScale?.bandwidth?.() ?? 0 : 0) / 2 | ||
: NaN; | ||
}, | ||
[yScale, yAccessor], | ||
); | ||
|
||
if (!data || !xAccessor || !yAccessor) return null; | ||
|
||
const color = colorScale?.(dataKey) ?? '#222'; | ||
|
||
return ( | ||
<LinePath | ||
data={data} | ||
x={getScaledX} | ||
y={getScaledY} | ||
stroke={color} | ||
strokeWidth={2} | ||
{...lineProps} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import React from 'react'; | ||
import { AxisScale } from '@vx/axis'; | ||
import { DataContextType } from '../types'; | ||
|
||
type AnyDataContext = DataContextType<AxisScale, AxisScale, any>; | ||
|
||
/** Utilities for infering context generics */ | ||
export type InferXScaleConfig<X extends AnyDataContext> = X extends DataContextType< | ||
infer T, | ||
any, | ||
any | ||
> | ||
? T | ||
: AxisScale; | ||
|
||
export type InferYScaleConfig<X extends AnyDataContext> = X extends DataContextType< | ||
any, | ||
infer T, | ||
any | ||
> | ||
? T | ||
: AxisScale; | ||
|
||
export type InferDatum<X extends AnyDataContext> = X extends DataContextType<any, any, infer T> | ||
? T | ||
: any; | ||
|
||
export type InferDataContext<C extends AnyDataContext = AnyDataContext> = DataContextType< | ||
InferXScaleConfig<C>, | ||
InferYScaleConfig<C>, | ||
InferDatum<C> | ||
>; | ||
|
||
const DataContext = React.createContext<Partial<InferDataContext>>({}); | ||
|
||
export default DataContext; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { AxisScale } from '@vx/axis'; | ||
import { useMemo } from 'react'; | ||
import DataRegistry from '../classes/DataRegistry'; | ||
|
||
/** Hook that returns a constant instance of a DataRegistry. */ | ||
export default function useDataRegistry< | ||
XScale extends AxisScale, | ||
YScale extends AxisScale, | ||
Datum = unknown | ||
>() { | ||
return useMemo(() => new DataRegistry<XScale, YScale, Datum>(), []); | ||
} |
Oops, something went wrong.