Добавлен механизм автоматического скалирования графиков на группах вертикальных графиков

This commit is contained in:
goodmice 2022-07-11 14:04:24 +05:00
parent d5a7d947c6
commit 719180392e

View File

@ -27,16 +27,32 @@ import D3MouseZone from './D3MouseZone'
import { getByAccessor, getChartClass, getGroupClass, getTicks } from './functions' import { getByAccessor, getChartClass, getGroupClass, getTicks } from './functions'
import { renderArea, renderLine, renderNeedle, renderPoint } from './renders' import { renderArea, renderLine, renderNeedle, renderPoint } from './renders'
const roundTo = (v: number, to: number = 50) => {
if (to == 0) return v
if (v < 0) return Math.round(v / to) * to
return Math.ceil(v / to) * to
}
const calculateDomain = (mm: MinMax, round: number = 100): Required<MinMax> => {
let min = roundTo(mm.min ?? 0, round)
let max = roundTo(mm.max ?? round, round)
if (min - max < round) {
const mid = (min + max) / 2
min = mid - round
max = mid + round
}
return { min, max }
}
type AxisScale = d3.ScaleTime<number, number, never> | d3.ScaleLinear<number, number, never> type AxisScale = d3.ScaleTime<number, number, never> | d3.ScaleLinear<number, number, never>
type ExtendedChartDataset<DataType> = ChartDataset<DataType> & { type ExtendedChartDataset<DataType> = ChartDataset<DataType> & {
yDomain: MinMax xDomain: MinMax
hideXAxis?: boolean hideXAxis?: boolean
} }
type ExtendedChartRegistry<DataType> = ExtendedChartDataset<DataType> & { type ExtendedChartRegistry<DataType> = ExtendedChartDataset<DataType> & {
(): d3.Selection<SVGGElement, DataType, any, any> (): d3.Selection<SVGGElement, DataType, any, any>
scale: d3.ScaleLinear<number, number>
y: (value: any) => number y: (value: any) => number
x: (value: any) => number x: (value: any) => number
} }
@ -110,7 +126,7 @@ export type ChartSizes = ChartOffset & {
} }
const axisHeight = 20 const axisHeight = 20
const space = 20 const space = 30
const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
width: givenWidth = '100%', width: givenWidth = '100%',
@ -188,6 +204,32 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
} }
), [chartArea, axesArea]) ), [chartArea, axesArea])
const chartDomains: Record<string, {
scale: d3.ScaleLinear<number, number>,
domain: Required<MinMax>,
}>[] = useMemo(() => {
return groups.map((group) => {
const out = group.charts.map((chart) => {
const mm = { ...chart.xDomain }
let domain: Required<MinMax> = { min: 0, max: 100 }
if (mm.min && mm.max) {
domain = mm as Required<MinMax>
} else if (data) {
const [min, max] = d3.extent(data, chart.x)
domain = calculateDomain({ min, max, ...mm }, 100)
}
return [chart.key, {
scale: d3.scaleLinear().domain([domain.min, domain.max]),
domain,
}]
})
return Object.fromEntries(out)
})
}, [groups, data])
console.log(chartDomains)
useEffect(() => { useEffect(() => {
if (isDev()) { if (isDev()) {
datasetGroups.forEach((sets, i) => { datasetGroups.forEach((sets, i) => {
@ -228,15 +270,6 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
group.charts[chartIdx]().selectAll('*').remove() group.charts[chartIdx]().selectAll('*').remove()
} }
const yDomain = {
min: 0,
max: 10,
...dataset.yDomain,
}
const scale = d3.scaleLinear()
.domain([yDomain.min, yDomain.max])
// Пересоздаём график // Пересоздаём график
const newChart: ExtendedChartRegistry<DataType> = Object.assign( const newChart: ExtendedChartRegistry<DataType> = Object.assign(
() => group().select('.' + getChartClass(dataset.key)) as d3.Selection<SVGGElement, DataType, any, unknown>, () => group().select('.' + getChartClass(dataset.key)) as d3.Selection<SVGGElement, DataType, any, unknown>,
@ -248,8 +281,6 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
animDurationMs, animDurationMs,
...dataset, ...dataset,
yAxis: dataset.yAxis ?? yAxisConfig, yAxis: dataset.yAxis ?? yAxisConfig,
yDomain,
scale,
y: getByAccessor(dataset.yAxis.accessor ?? yAxisConfig.accessor), y: getByAccessor(dataset.yAxis.accessor ?? yAxisConfig.accessor),
x: getByAccessor(dataset.xAxis?.accessor), x: getByAccessor(dataset.xAxis?.accessor),
} }
@ -288,7 +319,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
.attr('class', (g) => `charts-group ${getGroupClass(g.key)}`) .attr('class', (g) => `charts-group ${getGroupClass(g.key)}`)
.attr('transform', (g) => `translate(${sizes.groupLeft(g.key)}, 0)`) .attr('transform', (g) => `translate(${sizes.groupLeft(g.key)}, 0)`)
actualAxesGroups.each(function(group) { actualAxesGroups.each(function(group, i) {
const groupAxes = d3.select(this) const groupAxes = d3.select(this)
const chartsData = group.charts.filter((chart) => !chart.hideXAxis) const chartsData = group.charts.filter((chart) => !chart.hideXAxis)
const charts = groupAxes.selectChildren().data(chartsData) const charts = groupAxes.selectChildren().data(chartsData)
@ -302,17 +333,18 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
.style('color', (d) => d.color ?? null) .style('color', (d) => d.color ?? null)
actualCharts.each(function (chart, j) { actualCharts.each(function (chart, j) {
let axis = d3.axisTop(chart.scale.range([0, sizes.groupWidth])) let axis = d3.axisTop(chartDomains[i][chart.key].scale.range([0, sizes.groupWidth]))
const domain = chartDomains[i][chart.key].domain
if (j === chartsData.length - 1) { if (j === chartsData.length - 1) {
axis = axis axis = axis
.ticks(5) .ticks(5)
.tickSize(-sizes.chartsHeight) .tickSize(-sizes.chartsHeight)
.tickFormat((d, i) => i === 0 || i === 5 ? String(d) : '') .tickFormat((d, i) => i === 0 || i === 5 ? String(d) : '')
.tickValues(getTicks(chart.yDomain, 5)) .tickValues(getTicks(domain, 5))
} else { } else {
axis = axis.ticks(1) axis = axis.ticks(1)
.tickValues(getTicks(chart.yDomain, 1)) .tickValues(getTicks(domain, 1))
} }
d3.select(this).call(axis as any) d3.select(this).call(axis as any)
@ -335,7 +367,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
.attr('stroke', j === chartsData.length - 1 ? 'gray' : 'currentColor') .attr('stroke', j === chartsData.length - 1 ? 'gray' : 'currentColor')
}) })
}) })
}, [groups, groupScales, sizes, space]) }, [groups, groupScales, sizes, space, chartDomains])
useEffect(() => { // Рисуем ось Y useEffect(() => { // Рисуем ось Y
if (!yAxis) return if (!yAxis) return
@ -361,7 +393,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
useEffect(() => { useEffect(() => {
if (!data || !yAxis) return if (!data || !yAxis) return
groups.forEach((group) => { groups.forEach((group, i) => {
group() group()
.attr('transform', `translate(${sizes.groupLeft(group.key)}, 0)`) .attr('transform', `translate(${sizes.groupLeft(group.key)}, 0)`)
.attr('clip-path', `url(#chart-clip)`) .attr('clip-path', `url(#chart-clip)`)
@ -377,7 +409,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
let chartData = data let chartData = data
if (!chartData) return if (!chartData) return
const xAxis = chart.scale.range([0, sizes.groupWidth]) const xAxis = chartDomains[i][chart.key].scale.range([0, sizes.groupWidth])
switch (chart.type) { switch (chart.type) {
case 'needle': case 'needle':
@ -397,12 +429,12 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
} }
if (chart.point) if (chart.point)
renderPoint<DataType>(chart.scale, yAxis, chart, chartData, true) renderPoint<DataType>(xAxis, yAxis, chart, chartData, true)
chart.afterDraw?.(chart) chart.afterDraw?.(chart)
}) })
}) })
}, [data, groups, groupScales, height, offset, sizes]) }, [data, groups, groupScales, height, offset, sizes, chartDomains])
return ( return (
<LoaderPortal <LoaderPortal