From 2ec03e88c95c558fa975e85cb94de6c0c87bd071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Tue, 19 Oct 2021 19:43:12 +0200 Subject: [PATCH] [LineChart] Added ability to specify x and y keys as functions (#11) --- packages/charts/src/Line/Line.tsx | 22 ++++---- packages/charts/src/LineChart/LineChart.tsx | 62 +++++++++++++-------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/packages/charts/src/Line/Line.tsx b/packages/charts/src/Line/Line.tsx index 3daa9af6e593..d46330059862 100644 --- a/packages/charts/src/Line/Line.tsx +++ b/packages/charts/src/Line/Line.tsx @@ -4,8 +4,8 @@ import * as d3 from 'd3'; import ChartContext from '../ChartContext'; import Scatter from '../Scatter/Scatter'; -function points(data, xKey) { - return data.map((d) => ({ [xKey]: d.data[xKey], y: d[1] })); +function points(data, xValueSelector) { + return data.map((d) => ({ x: xValueSelector(d.data), y: d[1] })); } const Line = (props) => { @@ -19,9 +19,9 @@ const Line = (props) => { markerShape: markerShapeContext, smoothed: smoothedContext, stacked, - xKey, + xValueSelector, xScale, - yKey, + yValueSelector, yScale, } = useContext(ChartContext) as any; @@ -55,27 +55,27 @@ const Line = (props) => { linePath = d3 .line() // @ts-ignore TODO: Fix me - .x((d) => xScale(d.data[xKey])) + .x((d) => xScale(xValueSelector(d.data))) .y((d) => -yScale(d[1])); areaPath = d3 .area() // @ts-ignore TODO: Fix me - .x((d) => xScale(d.data[xKey])) + .x((d) => xScale(xValueSelector(d.data))) .y0((d) => -yScale(d[0])) .y1((d) => -yScale(d[1])); - pointData = points(chartData, xKey); + pointData = points(chartData, xValueSelector); } else { linePath = d3 .line() - .x((d) => xScale(d[xKey])) - .y((d) => -yScale(d[yKey])); + .x((d) => xScale(xValueSelector(d))) + .y((d) => -yScale(yValueSelector(d))); areaPath = d3 .area() - .x((d) => xScale(d[xKey])) - .y1((d) => -yScale(d[yKey])) + .x((d) => xScale(xValueSelector(d))) + .y1((d) => -yScale(yValueSelector(d))) .y0(-yScale(yScale.domain()[0])); } diff --git a/packages/charts/src/LineChart/LineChart.tsx b/packages/charts/src/LineChart/LineChart.tsx index 596b67b7e4bb..98525cd5b2ca 100644 --- a/packages/charts/src/LineChart/LineChart.tsx +++ b/packages/charts/src/LineChart/LineChart.tsx @@ -9,11 +9,6 @@ import useThrottle from '../hooks/useThrottle'; import useTicks from '../hooks/useTicks'; import { getExtent, getMaxDataSetLength, stringRatioToNumber } from '../utils'; -interface ChartData { - x: X; - y: Y; -} - interface Margin { bottom?: number; left?: number; @@ -32,7 +27,9 @@ type MarkerShape = | 'wye' | 'none'; -export interface LineChartProps { +type Scale = 'linear' | 'time' | 'log' | 'point' | 'pow' | 'sqrt' | 'utc'; + +export interface LineChartProps { /** * The keys to use when stacking the data. */ @@ -44,7 +41,7 @@ export interface LineChartProps { /** * The data to use for the chart. */ - data: ChartData[] | ChartData[][]; + data: Record[] | Record[][]; /** * The fill color to use for the area. */ @@ -113,38 +110,37 @@ export interface LineChartProps { /** * Override the calculated domain of the x axis. */ - xDomain?: X[]; + xDomain?: [X] | [X, X]; /** - * The key to use for the x axis. + * The key to use for the x axis or the function to access . */ - - xKey?: string; + xValueSelector?: keyof Record | ((r: Record) => X); /** * The scale type to use for the x axis. */ - xScaleType?: 'linear' | 'time' | 'log' | 'point' | 'pow' | 'sqrt' | 'utc'; + xScaleType?: Scale; /** * Override the calculated domain of the y axis. * By default, the domain starts at zero. Set the value to null to calculate the true domain. */ - yDomain?: Y[]; + yDomain?: [Y] | [Y, Y]; /** * The key to use for the y axis. */ - yKey?: string; + yValueSelector?: keyof Record | ((r: Record) => Y); /** * The scale type to use for the y axis. */ - yScaleType?: 'linear' | 'time' | 'log' | 'point' | 'pow' | 'sqrt' | 'utc'; + yScaleType?: Scale; } type LineChartComponent = ( props: LineChartProps & React.RefAttributes, ) => JSX.Element; -const LineChart = React.forwardRef(function LineChart( - props: LineChartProps, - ref: React.Ref, +const LineChart = React.forwardRef(function LineChart( + props: LineChartProps, + ref: React.ForwardedRef, ) { const { keys, @@ -166,10 +162,10 @@ const LineChart = React.forwardRef(function LineChart( smoothed = false, stacked = false, xDomain: xDomainProp, - xKey = 'x', + xValueSelector, xScaleType = 'linear', yDomain: yDomainProp = [0], - yKey = 'y', + yValueSelector, yScaleType = 'linear', ...other } = props; @@ -211,8 +207,26 @@ const LineChart = React.forwardRef(function LineChart( marginBottom, } = dimensions; - const xDomain = getExtent(data, (d) => d[xKey], xDomainProp); - const yDomain = getExtent(data, (d) => d[yKey], yDomainProp); + let xGetter: (record: Record) => X; + if (typeof xValueSelector === 'string') { + xGetter = (record: Record) => record[xValueSelector as keyof Record] as unknown as X; + } else if (typeof xValueSelector === 'function') { + xGetter = xValueSelector; + } else { + xGetter = (record: Record) => (record as any).x as X; + } + + let yGetter: (record: Record) => Y; + if (typeof yValueSelector === 'string') { + yGetter = (record: Record) => record[yValueSelector as keyof Record] as unknown as Y; + } else if (typeof yValueSelector === 'function') { + yGetter = yValueSelector; + } else { + yGetter = (record: Record) => (record as any).y as Y; + } + + const xDomain = getExtent(data, (d: Record) => xGetter(d), xDomainProp); + const yDomain = getExtent(data, (d: Record) => yGetter(d), yDomainProp); const xRange = [0, boundedWidth]; const yRange = [0, boundedHeight]; const maxXTicks = getMaxDataSetLength(data) - 1; @@ -268,11 +282,11 @@ const LineChart = React.forwardRef(function LineChart( stacked, mousePosition, smoothed, - xKey, + xValueSelector: xGetter, xScale, xScaleType, xTicks, - yKey, + yValueSelector: yGetter, yScale, yScaleType, yTicks,