diff --git a/src/components/d3/D3Chart.tsx b/src/components/d3/D3Chart.tsx index b175b0d..c7938cf 100644 --- a/src/components/d3/D3Chart.tsx +++ b/src/components/d3/D3Chart.tsx @@ -9,6 +9,7 @@ import { isDev, usePartialProps } from '@utils' import D3MouseZone from './D3MouseZone' import { + renderArea, renderLine, renderPoint, renderNeedle @@ -42,9 +43,9 @@ const defaultOffsets: ChartOffset = { right: 10, } -const getGroupClass = (key: string | number) => `chart-id-${key}` +export const getGroupClass = (key: string | number) => `chart-id-${key}` -const getByAccessor = , R>(accessor: keyof DataType | ((d: DataType) => R)): ((d: DataType) => R) => { +export const getByAccessor = , R>(accessor: keyof DataType | ((d: DataType) => R)): ((d: DataType) => R) => { if (typeof accessor === 'function') return accessor return (d) => d[accessor] @@ -98,7 +99,6 @@ const _D3Chart = >({ ...other }: D3ChartProps) => { const xAxisConfig = usePartialProps>(_xAxisConfig, getDefaultXAxisConfig) - const offset = usePartialProps(_offset, defaultOffsets) const [svgRef, setSvgRef] = useState(null) @@ -285,6 +285,9 @@ const _D3Chart = >({ case 'point': chartData = renderPoint(xAxis, yAxis, chart, chartData) break + case 'area': + chartData = renderArea(xAxis, yAxis, chart, chartData) + break default: break } diff --git a/src/components/d3/plugins/D3Tooltip.tsx b/src/components/d3/plugins/D3Tooltip.tsx index 6b6bc7a..7c94dde 100644 --- a/src/components/d3/plugins/D3Tooltip.tsx +++ b/src/components/d3/plugins/D3Tooltip.tsx @@ -1,5 +1,5 @@ import { CSSProperties, memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react' -import { BarChartOutlined, DotChartOutlined, LineChartOutlined } from '@ant-design/icons' +import { AreaChartOutlined, BarChartOutlined, DotChartOutlined, LineChartOutlined } from '@ant-design/icons' import * as d3 from 'd3' import { isDev } from '@utils' @@ -40,7 +40,7 @@ const makeDefaultRender = (): D3RenderFunction => (data, mo case 'needle': Icon = BarChartOutlined; break case 'line': Icon = LineChartOutlined; break case 'point': Icon = DotChartOutlined; break - // case 'area': Icon = AreaChartOutlined; break + case 'area': Icon = AreaChartOutlined; break // case 'dot': Icon = DotChartOutLined; break } @@ -110,6 +110,7 @@ const getTouchedElements = ( ): d3.Selection => { let nodes: d3.Selection switch (chart.type) { + case 'area': case 'line': case 'point': { const tag = chart.point?.shape ?? 'circle' diff --git a/src/components/d3/renders/area.ts b/src/components/d3/renders/area.ts new file mode 100644 index 0000000..73965cd --- /dev/null +++ b/src/components/d3/renders/area.ts @@ -0,0 +1,57 @@ +import * as d3 from 'd3' + +import { ChartRegistry } from '@components/d3/types' +import { makePointsOptimizator } from '@utils' + +export const renderArea = >( + xAxis: (value: any) => number, + yAxis: (value: any) => number, + chart: ChartRegistry, + data: DataType[] +): DataType[] => { + if (chart.type !== 'area') return data + + let area = d3.area() + + if (chart.y0) { + area = area.y0(chart.y0) + .y1(d => yAxis(chart.y(d))) + } else { + area = area.y(d => yAxis(chart.y(d))) + } + + if (chart.x0) { + area = area.x0(chart.x0) + .x1(d => xAxis(chart.x(d))) + } else { + area = area.x(d => xAxis(chart.x(d))) + } + + switch (chart.nullValues || 'skip') { + case 'gap': + area = area.defined(d => (chart.y(d) ?? null) !== null && !Number.isNaN(chart.y(d))) + break + case 'skip': + data = data.filter(chart.y) + break + default: + break + } + + if (chart.optimization) { + const optimize = makePointsOptimizator((a, b) => chart.y(a) === chart.y(b)) + data = optimize(data) + } + + + if (chart().selectAll('path').empty()) + chart().append('path') + + chart().selectAll('path') + .transition() + .duration(chart.animDurationMs || 0) + .attr('d', area(data as any)) + .attr('stroke-dasharray', chart.dash ? String(chart.dash) : null) + + return data +} diff --git a/src/components/d3/renders/index.ts b/src/components/d3/renders/index.ts index 0cddad8..3ba343e 100644 --- a/src/components/d3/renders/index.ts +++ b/src/components/d3/renders/index.ts @@ -1,3 +1,4 @@ +export * from './area' export * from './line' export * from './needle' export * from './points' diff --git a/src/components/d3/types.ts b/src/components/d3/types.ts index 6c22536..084d3a4 100644 --- a/src/components/d3/types.ts +++ b/src/components/d3/types.ts @@ -38,6 +38,15 @@ export type BaseChartDataset = { dash?: string | number | [string | number, string | number] } +export type AreaChartDataset = { + type: 'area' + x0?: number + y0?: number + areaColor?: Property.Color + nullValues?: 'skip' | 'gap' | 'none' + optimization?: boolean +} + export type LineChartDataset = { type: 'line' nullValues?: 'skip' | 'gap' | 'none' @@ -49,6 +58,7 @@ export type NeedleChartDataset = { } export type ChartDataset = BaseChartDataset & ( + AreaChartDataset | LineChartDataset | NeedleChartDataset | PointChartDataset