На странице Наработка добавлен сброс даты и выделение данных при наведении

This commit is contained in:
ts_salikhov 2022-10-12 16:45:34 +04:00
parent 6455be0891
commit 116de6e912
3 changed files with 25 additions and 30 deletions

View File

@ -1,4 +1,4 @@
import { memo, useEffect, useMemo, useRef } from 'react' import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { useElementSize } from 'usehooks-ts' import { useElementSize } from 'usehooks-ts'
import { Property } from 'csstype' import { Property } from 'csstype'
import * as d3 from 'd3' import * as d3 from 'd3'
@ -20,7 +20,7 @@ export type D3HorizontalChartProps = {
height?: Property.Height height?: Property.Height
data: PercentChartDataType[] data: PercentChartDataType[]
offset?: Partial<ChartOffset> offset?: Partial<ChartOffset>
afterDraw?: (d: d3.Selection<SVGGElement, unknown, null, undefined>) => void afterDraw?: (d: d3.Selection<d3.BaseType, PercentChartDataType, d3.BaseType, unknown>) => void
} }
const defaultOffset = { top: 50, right: 100, bottom: 50, left: 100 } const defaultOffset = { top: 50, right: 100, bottom: 50, left: 100 }
@ -37,7 +37,7 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
const [divRef, { width, height }] = useElementSize() const [divRef, { width, height }] = useElementSize()
const rootRef = useRef<SVGGElement | null>(null) const rootRef = useRef<SVGGElement | null>(null)
const root = useMemo(() => rootRef.current ? d3.select(rootRef.current) : null, [rootRef.current]) const root = useCallback(() => d3.select(rootRef.current), [rootRef.current])
const inlineWidth = useMemo(() => width - offset.left - offset.right, [width]) const inlineWidth = useMemo(() => width - offset.left - offset.right, [width])
const inlineHeight = useMemo(() => height - offset.top - offset.bottom, [height]) const inlineHeight = useMemo(() => height - offset.top - offset.bottom, [height])
@ -50,15 +50,15 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
const xAxisTop = d3.axisTop(xScale).tickFormat((d) => `${d}%`).ticks(4).tickSize(-inlineHeight) const xAxisTop = d3.axisTop(xScale).tickFormat((d) => `${d}%`).ticks(4).tickSize(-inlineHeight)
const xAxisBottom = d3.axisBottom(xScale).tickFormat((d) => `${d}%`).ticks(4) const xAxisBottom = d3.axisBottom(xScale).tickFormat((d) => `${d}%`).ticks(4)
root.selectChild<SVGGElement>('.axis.x.bottom').call(xAxisBottom) root().selectChild<SVGGElement>('.axis.x.bottom').call(xAxisBottom)
root.selectChild<SVGGElement>('.axis.x.top').call(xAxisTop) root().selectChild<SVGGElement>('.axis.x.top').call(xAxisTop)
.selectAll('.tick') .selectAll('.tick')
.attr('class', 'tick grid-line') .attr('class', 'tick grid-line')
}, [root, width, height, xScale, inlineHeight]) }, [root, width, height, xScale, inlineHeight])
useEffect(() => { /// Отрисовываем ось Y слева useEffect(() => { /// Отрисовываем ось Y слева
if (width < 100 || height < 100 || !root) return if (width < 100 || height < 100 || !root) return
root.selectChild<SVGGElement>('.axis.y.left').call(d3.axisLeft(yScale)) root().selectChild<SVGGElement>('.axis.y.left').call(d3.axisLeft(yScale))
}, [root, width, height, yScale]) }, [root, width, height, yScale])
useEffect(() => { useEffect(() => {
@ -66,10 +66,10 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
const delay = d3.transition().duration(500).ease(d3.easeLinear) const delay = d3.transition().duration(500).ease(d3.easeLinear)
const rects = root.selectChild('.data').selectAll('rect').data(data) const rects = root().selectChild('.data').selectAll('rect').data(data)
rects.enter().append('rect') rects.enter().append('rect')
rects.exit().remove() rects.exit().remove()
root.selectChild<SVGGElement>('.data') root().selectChild<SVGGElement>('.data')
.selectAll<SVGRectElement, PercentChartDataType>('rect') .selectAll<SVGRectElement, PercentChartDataType>('rect')
.attr('fill', (d) => d.color || 'black') .attr('fill', (d) => d.color || 'black')
.attr('y', (d) => yScale(d.name) ?? null) .attr('y', (d) => yScale(d.name) ?? null)
@ -77,14 +77,14 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
.transition(delay) .transition(delay)
.attr('width', (d) => d.percent > 0 ? xScale(d.percent) : 0) .attr('width', (d) => d.percent > 0 ? xScale(d.percent) : 0)
afterDraw?.(root) afterDraw?.(root().selectChild('.data').selectAll('rect'))
}, [data, width, height, root, yScale, xScale, afterDraw]) }, [data, width, height, root, yScale, xScale, afterDraw])
return ( return (
<LoaderPortal show={false} style={{ width: givenWidth, height: givenHeight }}> <LoaderPortal show={false} style={{ width: givenWidth, height: givenHeight }}>
<div ref={divRef} style={{ width: '100%', height: '100%' }}> <div ref={divRef} style={{ width: '100%', height: '100%' }}>
<svg id={'d3-horizontal-chart'} width={'100%'} height={'100%'}> <svg width={'100%'} height={'100%'}>
<g ref={rootRef} transform={`translate(${offset.left}, ${offset.top})`}> <g ref={rootRef} transform={`translate(${offset.left}, ${offset.top})`}>
<g className={'axis x top'}></g> <g className={'axis x top'}></g>
<g className={'axis x bottom'} transform={`translate(0, ${inlineHeight})`}></g> <g className={'axis x bottom'} transform={`translate(0, ${inlineHeight})`}></g>

View File

@ -21,6 +21,8 @@ const subsystemColors = [
'#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d', '#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d',
] ]
const tableStyle = { background: '#FAFAFA', fontSize: '16px', fontWeight: '600', transition: 'all 0.2s ease-out' }
const tableColumns = [ const tableColumns = [
makeColumn('Цвет', 'color', { width: 60, render: (backgroundColor) => ( makeColumn('Цвет', 'color', { width: 60, render: (backgroundColor) => (
<div className={'table_color'} style={{ backgroundColor }} /> <div className={'table_color'} style={{ backgroundColor }} />
@ -62,37 +64,25 @@ export const OperationTime = memo(() => {
const onRow = useCallback((item) => { const onRow = useCallback((item) => {
const out = { const out = {
onMouseEnter: () => { onMouseEnter: () => setSelectedSubsystem(item.subsystemName),
setSelectedSubsystem(item.subsystemName) onMouseLeave: () => setSelectedSubsystem(null),
},
onMouseLeave: () => {
setSelectedSubsystem(null)
},
} }
if (item.subsystemName === selectedSubsystem) { if (item.subsystemName === selectedSubsystem) {
out.style = { background: '#FAFAFA', fontSize: '16px', fontWeight: '600', transition: 'all 0.2s ease-out' } out.style = tableStyle
} }
return out return out
}, [selectedSubsystem]) }, [selectedSubsystem])
const onMouseOver = useCallback((_, d) => { const onMouseOver = useCallback((_, d) => setSelectedSubsystem(d.name), [])
setSelectedSubsystem(d.name)
}, [])
const onMouseOut = useCallback(() => { const onMouseOut = useCallback(() => setSelectedSubsystem(null), [])
setSelectedSubsystem(null)
}, [])
const afterDraw = useCallback(function(selection) { const afterDraw = useCallback(selection => {
selection.selectAll('rect') selection.on('mouseover', onMouseOver)
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut) .on('mouseout', onMouseOut)
.transition(200) .classed('selected_graph_column', d => d.name === selectedSubsystem)
.style('stroke-width', d => d.name === selectedSubsystem ? '2' : '0')
.style('stroke', d => d.name === selectedSubsystem ? 'black' : '')
.style('stroke-opacity', '0.4')
}, [onMouseOver, onMouseOut, selectedSubsystem]) }, [onMouseOver, onMouseOut, selectedSubsystem])
useEffect(() => { useEffect(() => {

View File

@ -1,3 +1,8 @@
.table_color { .table_color {
padding: 5px 0; padding: 5px 0;
} }
.selected_graph_column {
filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, .6));
transition: all 0.2s ease-out
}