forked from ddrilling/asb_cloud_front
Compare commits
6 Commits
dev
...
feature/ad
Author | SHA1 | Date | |
---|---|---|---|
73ae239148 | |||
f896a22f1d | |||
|
17c388c0b7 | ||
|
f481a212c5 | ||
|
aeb53eecb0 | ||
|
a924cd0ff1 |
@ -93,6 +93,11 @@ export type D3ChartProps<DataType extends BaseDataType> = React.DetailedHTMLProp
|
|||||||
/** Параметры блока легенды */
|
/** Параметры блока легенды */
|
||||||
legend?: BasePluginSettings & D3LegendSettings
|
legend?: BasePluginSettings & D3LegendSettings
|
||||||
}
|
}
|
||||||
|
/** Добавление зума графику */
|
||||||
|
zoom?: {
|
||||||
|
/** Массив коэффициентов приближения k0 - минимальный k1 - максимальный коэффициент */
|
||||||
|
scaleExtent: [k0: number, k1: number]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDefaultXAxisConfig = <DataType extends BaseDataType>(): ChartAxis<DataType> => ({
|
const getDefaultXAxisConfig = <DataType extends BaseDataType>(): ChartAxis<DataType> => ({
|
||||||
@ -110,11 +115,12 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
height: givenHeight = '100%',
|
height: givenHeight = '100%',
|
||||||
loading,
|
loading,
|
||||||
offset: _offset,
|
offset: _offset,
|
||||||
animDurationMs = 200,
|
animDurationMs = 20,
|
||||||
backgroundColor = 'transparent',
|
backgroundColor = 'transparent',
|
||||||
ticks,
|
ticks,
|
||||||
plugins,
|
plugins,
|
||||||
dash,
|
dash,
|
||||||
|
zoom,
|
||||||
...other
|
...other
|
||||||
}: D3ChartProps<DataType>) => {
|
}: D3ChartProps<DataType>) => {
|
||||||
const xAxisConfig = usePartialProps<ChartAxis<DataType>>(_xAxisConfig, getDefaultXAxisConfig)
|
const xAxisConfig = usePartialProps<ChartAxis<DataType>>(_xAxisConfig, getDefaultXAxisConfig)
|
||||||
@ -124,6 +130,7 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
const [xAxisRef, setXAxisRef] = useState<SVGGElement | null>(null)
|
const [xAxisRef, setXAxisRef] = useState<SVGGElement | null>(null)
|
||||||
const [yAxisRef, setYAxisRef] = useState<SVGGElement | null>(null)
|
const [yAxisRef, setYAxisRef] = useState<SVGGElement | null>(null)
|
||||||
const [chartAreaRef, setChartAreaRef] = useState<SVGGElement | null>(null)
|
const [chartAreaRef, setChartAreaRef] = useState<SVGGElement | null>(null)
|
||||||
|
const [currentZoomState, setCurrentZoomState] = useState<d3.ZoomTransform | null>(null)
|
||||||
|
|
||||||
const xAxisArea = useCallback(() => d3.select(xAxisRef), [xAxisRef])
|
const xAxisArea = useCallback(() => d3.select(xAxisRef), [xAxisRef])
|
||||||
const yAxisArea = useCallback(() => d3.select(yAxisRef), [yAxisRef])
|
const yAxisArea = useCallback(() => d3.select(yAxisRef), [yAxisRef])
|
||||||
@ -159,8 +166,13 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
xAxis.domain([domain?.x?.min ?? minX, domain?.x?.max ?? maxX])
|
xAxis.domain([domain?.x?.min ?? minX, domain?.x?.max ?? maxX])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentZoomState) {
|
||||||
|
const newXScale = currentZoomState.rescaleX(xAxis)
|
||||||
|
xAxis.domain(newXScale.domain())
|
||||||
|
}
|
||||||
|
|
||||||
return xAxis
|
return xAxis
|
||||||
}, [xAxisConfig, data, domain, width, offset])
|
}, [xAxisConfig, data, domain, width, offset, currentZoomState])
|
||||||
|
|
||||||
const yAxis = useMemo(() => {
|
const yAxis = useMemo(() => {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
@ -200,8 +212,13 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
|
|
||||||
yAxis.range([height - offset.top - offset.bottom, 0])
|
yAxis.range([height - offset.top - offset.bottom, 0])
|
||||||
|
|
||||||
|
if (currentZoomState) {
|
||||||
|
const newYScale = currentZoomState.rescaleY(yAxis)
|
||||||
|
yAxis.domain(newYScale.domain())
|
||||||
|
}
|
||||||
|
|
||||||
return yAxis
|
return yAxis
|
||||||
}, [charts, data, domain, height, offset])
|
}, [charts, data, domain, height, offset, currentZoomState])
|
||||||
|
|
||||||
const nTicks = {
|
const nTicks = {
|
||||||
color: 'lightgray',
|
color: 'lightgray',
|
||||||
@ -348,6 +365,20 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
redrawCharts()
|
redrawCharts()
|
||||||
}, [redrawCharts])
|
}, [redrawCharts])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!svgRef || !zoom) return
|
||||||
|
const zoomBehavior = d3.zoom<SVGSVGElement, unknown>()
|
||||||
|
.scaleExtent(zoom.scaleExtent)
|
||||||
|
.translateExtent([[0, 0], [width - offset.left - offset.right, height - offset.top - offset.bottom]])
|
||||||
|
.extent([[0, 0], [width - offset.left - offset.right, height - offset.top - offset.bottom]])
|
||||||
|
.on('zoom', () => {
|
||||||
|
const zoomState = d3.zoomTransform(svgRef)
|
||||||
|
setCurrentZoomState(zoomState)
|
||||||
|
})
|
||||||
|
|
||||||
|
d3.select(svgRef).call(zoomBehavior)
|
||||||
|
}, [svgRef, zoom, width, height, offset])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal
|
<LoaderPortal
|
||||||
show={loading}
|
show={loading}
|
||||||
@ -366,7 +397,17 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
<svg ref={setSvgRef} width={'100%'} height={'100%'}>
|
<svg ref={setSvgRef} width={'100%'} height={'100%'}>
|
||||||
<g ref={setXAxisRef} className={'axis x'} transform={`translate(${offset.left}, ${height - offset.bottom})`} />
|
<g ref={setXAxisRef} className={'axis x'} transform={`translate(${offset.left}, ${height - offset.bottom})`} />
|
||||||
<g ref={setYAxisRef} className={'axis y'} transform={`translate(${offset.left}, ${offset.top})`} />
|
<g ref={setYAxisRef} className={'axis y'} transform={`translate(${offset.left}, ${offset.top})`} />
|
||||||
<g ref={setChartAreaRef} className={'chart-area'} transform={`translate(${offset.left}, ${offset.top})`}>
|
<defs>
|
||||||
|
<clipPath id={'clipZoomData'}>
|
||||||
|
<rect
|
||||||
|
x={0}
|
||||||
|
y={0}
|
||||||
|
width={Math.max(width - offset.left - offset.right, 0)}
|
||||||
|
height={Math.max(height - offset.top - offset.bottom, 0)}
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clipPath={'url(#clipZoomData)'} ref={setChartAreaRef} className={'chart-area'} transform={`translate(${offset.left}, ${offset.top})`}>
|
||||||
<rect
|
<rect
|
||||||
width={Math.max(width - offset.left - offset.right, 0)}
|
width={Math.max(width - offset.left - offset.right, 0)}
|
||||||
height={Math.max(height - offset.top - offset.bottom, 0)}
|
height={Math.max(height - offset.top - offset.bottom, 0)}
|
||||||
@ -376,7 +417,7 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
<D3MouseZone width={width} height={height} offset={offset}>
|
<D3MouseZone width={width} height={height} offset={offset}>
|
||||||
<D3Cursor {...plugins?.cursor} />
|
<D3Cursor {...plugins?.cursor} />
|
||||||
<D3Legend<DataType> charts={charts} {...plugins?.legend} />
|
<D3Legend<DataType> charts={charts} {...plugins?.legend} />
|
||||||
<D3Tooltip<DataType> charts={charts} {...plugins?.tooltip} />
|
<D3Tooltip<DataType> charts={charts} {...plugins?.tooltip} zoomState={currentZoomState}/>
|
||||||
</D3MouseZone>
|
</D3MouseZone>
|
||||||
</svg>
|
</svg>
|
||||||
</D3ContextMenu>
|
</D3ContextMenu>
|
||||||
|
@ -76,6 +76,7 @@ export const makeDefaultRender = <DataType extends BaseDataType>(): D3RenderFunc
|
|||||||
|
|
||||||
export type D3TooltipProps<DataType extends BaseDataType> = Partial<D3TooltipSettings<DataType>> & {
|
export type D3TooltipProps<DataType extends BaseDataType> = Partial<D3TooltipSettings<DataType>> & {
|
||||||
charts: ChartRegistry<DataType>[],
|
charts: ChartRegistry<DataType>[],
|
||||||
|
zoomState?: d3.ZoomTransform | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
function _D3Tooltip<DataType extends BaseDataType>({
|
function _D3Tooltip<DataType extends BaseDataType>({
|
||||||
@ -86,7 +87,8 @@ function _D3Tooltip<DataType extends BaseDataType>({
|
|||||||
position: _position = 'bottom',
|
position: _position = 'bottom',
|
||||||
className = '',
|
className = '',
|
||||||
style: _style = {},
|
style: _style = {},
|
||||||
limit = 2
|
limit = 2,
|
||||||
|
zoomState,
|
||||||
}: D3TooltipProps<DataType>) {
|
}: D3TooltipProps<DataType>) {
|
||||||
const { mouseState, zoneRect, subscribe } = useD3MouseZone()
|
const { mouseState, zoneRect, subscribe } = useD3MouseZone()
|
||||||
const [tooltipBody, setTooltipBody] = useState<any>()
|
const [tooltipBody, setTooltipBody] = useState<any>()
|
||||||
@ -94,6 +96,7 @@ function _D3Tooltip<DataType extends BaseDataType>({
|
|||||||
const [position, setPosition] = useState<D3TooltipPosition>(_position ?? 'bottom')
|
const [position, setPosition] = useState<D3TooltipPosition>(_position ?? 'bottom')
|
||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
const [fixed, setFixed] = useState(false)
|
const [fixed, setFixed] = useState(false)
|
||||||
|
const [currentZoom, setCurrentZoom] = useState<d3.ZoomTransform | null>(null);
|
||||||
|
|
||||||
const tooltipRef = useRef<HTMLDivElement>(null)
|
const tooltipRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ function _D3Tooltip<DataType extends BaseDataType>({
|
|||||||
}, [subscribe, visible])
|
}, [subscribe, visible])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!tooltipRef.current || !zoneRect || fixed) return
|
if (!tooltipRef.current || !zoneRect || fixed || !visible) return
|
||||||
const rect = tooltipRef.current.getBoundingClientRect()
|
const rect = tooltipRef.current.getBoundingClientRect()
|
||||||
|
|
||||||
if (!mouseState.visible) return
|
if (!mouseState.visible) return
|
||||||
@ -121,8 +124,9 @@ function _D3Tooltip<DataType extends BaseDataType>({
|
|||||||
const offsetX = -rect.width / 2 // По центру
|
const offsetX = -rect.width / 2 // По центру
|
||||||
const offsetY = 15 // Чуть выше курсора
|
const offsetY = 15 // Чуть выше курсора
|
||||||
|
|
||||||
const left = Math.max(10, Math.min(zoneRect.width - rect.width - 10, mouseState.x + offsetX))
|
const left = mouseState.x + offsetX
|
||||||
let top = mouseState.y - offsetY - rect.height
|
let top = mouseState.y - offsetY - rect.height
|
||||||
|
|
||||||
setPosition(top <= 0 ? 'top' : 'bottom')
|
setPosition(top <= 0 ? 'top' : 'bottom')
|
||||||
if (top <= 0) top = mouseState.y + offsetY
|
if (top <= 0) top = mouseState.y + offsetY
|
||||||
|
|
||||||
@ -131,7 +135,34 @@ function _D3Tooltip<DataType extends BaseDataType>({
|
|||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
}))
|
}))
|
||||||
}, [tooltipRef.current, mouseState, zoneRect, fixed])
|
}, [tooltipRef.current, mouseState, zoneRect, fixed, visible])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!zoomState) return
|
||||||
|
|
||||||
|
setCurrentZoom(zoomState)
|
||||||
|
|
||||||
|
if (!fixed) {
|
||||||
|
setVisible(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetX = Number(style.left) + Number(width) + 7
|
||||||
|
|
||||||
|
if (zoneRect && ((offsetX <= zoneRect.left) || (offsetX > zoneRect.right)) || zoomState.k !== currentZoom?.k) {
|
||||||
|
setVisible(false)
|
||||||
|
setFixed(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const distanceMoveX = currentZoom ? currentZoom.x - zoomState.x : 1
|
||||||
|
|
||||||
|
setStyle((prevStyle) => ({
|
||||||
|
...prevStyle,
|
||||||
|
left: prevStyle?.left ? +prevStyle.left - distanceMoveX : 0,
|
||||||
|
}))
|
||||||
|
}, [zoomState, fixed])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fixed) return
|
if (fixed) return
|
||||||
|
@ -100,6 +100,10 @@ export const OperationsChart = memo(({ data, yDomain, height, category, onDomain
|
|||||||
},
|
},
|
||||||
}), [category])
|
}), [category])
|
||||||
|
|
||||||
|
const zoom = useMemo(() => ({
|
||||||
|
scaleExtent: [1, 5]
|
||||||
|
}), [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<D3Chart
|
<D3Chart
|
||||||
xAxis={xAxis}
|
xAxis={xAxis}
|
||||||
@ -109,6 +113,7 @@ export const OperationsChart = memo(({ data, yDomain, height, category, onDomain
|
|||||||
height={height}
|
height={height}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
ticks={ticks}
|
ticks={ticks}
|
||||||
|
zoom={zoom}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user