Добавлен Area график

This commit is contained in:
goodmice 2022-07-02 13:14:43 +05:00
parent cd3c873368
commit 1402e6ce67
5 changed files with 77 additions and 5 deletions

View File

@ -9,6 +9,7 @@ import { isDev, usePartialProps } from '@utils'
import D3MouseZone from './D3MouseZone' import D3MouseZone from './D3MouseZone'
import { import {
renderArea,
renderLine, renderLine,
renderPoint, renderPoint,
renderNeedle renderNeedle
@ -42,9 +43,9 @@ const defaultOffsets: ChartOffset = {
right: 10, right: 10,
} }
const getGroupClass = (key: string | number) => `chart-id-${key}` export const getGroupClass = (key: string | number) => `chart-id-${key}`
const getByAccessor = <DataType extends Record<any, any>, R>(accessor: keyof DataType | ((d: DataType) => R)): ((d: DataType) => R) => { export const getByAccessor = <DataType extends Record<any, any>, R>(accessor: keyof DataType | ((d: DataType) => R)): ((d: DataType) => R) => {
if (typeof accessor === 'function') if (typeof accessor === 'function')
return accessor return accessor
return (d) => d[accessor] return (d) => d[accessor]
@ -98,7 +99,6 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
...other ...other
}: D3ChartProps<DataType>) => { }: D3ChartProps<DataType>) => {
const xAxisConfig = usePartialProps<ChartAxis<DataType>>(_xAxisConfig, getDefaultXAxisConfig) const xAxisConfig = usePartialProps<ChartAxis<DataType>>(_xAxisConfig, getDefaultXAxisConfig)
const offset = usePartialProps(_offset, defaultOffsets) const offset = usePartialProps(_offset, defaultOffsets)
const [svgRef, setSvgRef] = useState<SVGSVGElement | null>(null) const [svgRef, setSvgRef] = useState<SVGSVGElement | null>(null)
@ -285,6 +285,9 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
case 'point': case 'point':
chartData = renderPoint<DataType>(xAxis, yAxis, chart, chartData) chartData = renderPoint<DataType>(xAxis, yAxis, chart, chartData)
break break
case 'area':
chartData = renderArea<DataType>(xAxis, yAxis, chart, chartData)
break
default: default:
break break
} }

View File

@ -1,5 +1,5 @@
import { CSSProperties, memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react' 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 * as d3 from 'd3'
import { isDev } from '@utils' import { isDev } from '@utils'
@ -40,7 +40,7 @@ const makeDefaultRender = <DataType,>(): D3RenderFunction<DataType> => (data, mo
case 'needle': Icon = BarChartOutlined; break case 'needle': Icon = BarChartOutlined; break
case 'line': Icon = LineChartOutlined; break case 'line': Icon = LineChartOutlined; break
case 'point': Icon = DotChartOutlined; break case 'point': Icon = DotChartOutlined; break
// case 'area': Icon = AreaChartOutlined; break case 'area': Icon = AreaChartOutlined; break
// case 'dot': Icon = DotChartOutLined; break // case 'dot': Icon = DotChartOutLined; break
} }
@ -110,6 +110,7 @@ const getTouchedElements = <DataType,>(
): d3.Selection<any, DataType, any, any> => { ): d3.Selection<any, DataType, any, any> => {
let nodes: d3.Selection<any, any, any, any> let nodes: d3.Selection<any, any, any, any>
switch (chart.type) { switch (chart.type) {
case 'area':
case 'line': case 'line':
case 'point': { case 'point': {
const tag = chart.point?.shape ?? 'circle' const tag = chart.point?.shape ?? 'circle'

View File

@ -0,0 +1,57 @@
import * as d3 from 'd3'
import { ChartRegistry } from '@components/d3/types'
import { makePointsOptimizator } from '@utils'
export const renderArea = <DataType extends Record<string, unknown>>(
xAxis: (value: any) => number,
yAxis: (value: any) => number,
chart: ChartRegistry<DataType>,
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<DataType>((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
}

View File

@ -1,3 +1,4 @@
export * from './area'
export * from './line' export * from './line'
export * from './needle' export * from './needle'
export * from './points' export * from './points'

View File

@ -38,6 +38,15 @@ export type BaseChartDataset<DataType> = {
dash?: string | number | [string | number, string | number] 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 = { export type LineChartDataset = {
type: 'line' type: 'line'
nullValues?: 'skip' | 'gap' | 'none' nullValues?: 'skip' | 'gap' | 'none'
@ -49,6 +58,7 @@ export type NeedleChartDataset = {
} }
export type ChartDataset<DataType> = BaseChartDataset<DataType> & ( export type ChartDataset<DataType> = BaseChartDataset<DataType> & (
AreaChartDataset |
LineChartDataset | LineChartDataset |
NeedleChartDataset | NeedleChartDataset |
PointChartDataset PointChartDataset