From f2a69020fe3af87c4ec8435ab3410b4f15178051 Mon Sep 17 00:00:00 2001 From: "Mr.Dr.Professor Patrick" Date: Fri, 27 Oct 2023 12:22:18 +0200 Subject: [PATCH] refactor(D3 plugin): pointer position calculation fixes (#337) --- src/plugins/d3/renderer/D3Widget.tsx | 14 +++-------- src/plugins/d3/renderer/components/Chart.tsx | 11 +++------ .../components/Tooltip/TooltipTriggerArea.tsx | 24 +++++++++++-------- .../d3/renderer/components/Tooltip/index.tsx | 9 ++++--- .../d3/renderer/components/styles.scss | 1 + .../d3/renderer/hooks/useShapes/index.tsx | 10 -------- .../d3/renderer/hooks/useShapes/pie.tsx | 14 +++-------- .../hooks/useShapes/scatter/index.tsx | 13 +++------- 8 files changed, 33 insertions(+), 63 deletions(-) diff --git a/src/plugins/d3/renderer/D3Widget.tsx b/src/plugins/d3/renderer/D3Widget.tsx index 23d777ff..acaed1d8 100644 --- a/src/plugins/d3/renderer/D3Widget.tsx +++ b/src/plugins/d3/renderer/D3Widget.tsx @@ -9,8 +9,6 @@ import {getRandomCKId} from '../../../utils'; import {Chart} from './components'; type ChartDimensions = { - top: number; - left: number; width: number; height: number; }; @@ -37,8 +35,8 @@ const D3Widget = React.forwardRef {dimensions?.width && dimensions?.height && ( - + )} ); diff --git a/src/plugins/d3/renderer/components/Chart.tsx b/src/plugins/d3/renderer/components/Chart.tsx index 9020b810..9959ab54 100644 --- a/src/plugins/d3/renderer/components/Chart.tsx +++ b/src/plugins/d3/renderer/components/Chart.tsx @@ -26,8 +26,6 @@ import './styles.scss'; const b = block('d3'); type Props = { - top: number; - left: number; width: number; height: number; data: ChartKitWidgetData; @@ -35,8 +33,8 @@ type Props = { export const Chart = (props: Props) => { // FIXME: add data validation - const {top, left, width, height, data} = props; - const svgRef = React.createRef(); + const {width, height, data} = props; + const svgRef = React.useRef(null); const dispatcher = React.useMemo(() => { return getD3Dispatcher(); }, []); @@ -86,8 +84,6 @@ export const Chart = (props: Props) => { }); const {hovered, pointerPosition} = useTooltip({dispatcher, tooltip}); const {shapes, shapesData} = useShapes({ - top, - left, boundsWidth, boundsHeight, dispatcher, @@ -136,8 +132,6 @@ export const Chart = (props: Props) => { boundsWidth={boundsWidth} boundsHeight={boundsHeight} dispatcher={dispatcher} - offsetLeft={left} - offsetTop={top} shapesData={shapesData} svgContainer={svgRef.current} /> @@ -157,6 +151,7 @@ export const Chart = (props: Props) => { ; - offsetTop: number; - offsetLeft: number; shapesData: ShapeData[]; svgContainer: SVGSVGElement | null; }; @@ -52,7 +50,7 @@ function getBarXShapeData(args: { container, } = args; const barWidthOffset = (shapesData[0] as PreparedBarXData).width / 2; - const xPosition = pointerX - left - barWidthOffset - window.pageXOffset; + const xPosition = pointerX - left - barWidthOffset; const xDataIndex = bisector((d: {x: number; data: ShapeData}) => d.x).center(xData, xPosition); const xNodes = Array.from(container?.querySelectorAll(`[x="${xData[xDataIndex]?.x}"]`) || []); @@ -61,7 +59,7 @@ function getBarXShapeData(args: { } if (xNodes.length > 1 && xNodes.every(isNodeContainsData)) { - const yPosition = pointerY - top - window.pageYOffset; + const yPosition = pointerY - top; const xyNode = xNodes.find((node, i) => { const {y, height} = extractD3DataFromNode(node) as PreparedBarXData; if (i === xNodes.length - 1) { @@ -101,8 +99,7 @@ function getLineShapesData(args: {xData: XLineData[]; point: number[]}) { } export const TooltipTriggerArea = (args: Args) => { - const {boundsWidth, boundsHeight, dispatcher, offsetTop, offsetLeft, shapesData, svgContainer} = - args; + const {boundsWidth, boundsHeight, dispatcher, shapesData, svgContainer} = args; const rectRef = React.useRef(null); const calculationType = React.useMemo(() => { return getCalculationType(shapesData); @@ -132,7 +129,14 @@ export const TooltipTriggerArea = (args: Args) => { }, [shapesData]); const handleXprimaryMouseMove: React.MouseEventHandler = (e) => { - const {left, top} = rectRef.current?.getBoundingClientRect() || {left: 0, top: 0}; + const {left: ownLeft, top: ownTop} = rectRef.current?.getBoundingClientRect() || { + left: 0, + top: 0, + }; + const {left: containerLeft, top: containerTop} = svgContainer?.getBoundingClientRect() || { + left: 0, + top: 0, + }; const [pointerX, pointerY] = pointer(e, svgContainer); const hoverShapeData = []; @@ -140,8 +144,8 @@ export const TooltipTriggerArea = (args: Args) => { ...getBarXShapeData({ shapesData, point: [pointerX, pointerY], - left, - top, + left: ownLeft - containerLeft, + top: ownTop - containerTop, xData: xBarData, container: rectRef.current?.parentElement, }), @@ -149,7 +153,7 @@ export const TooltipTriggerArea = (args: Args) => { ); if (hoverShapeData.length) { - const position: PointerPosition = [pointerX - offsetLeft, pointerY - offsetTop]; + const position: PointerPosition = [pointerX, pointerY]; dispatcher.call('hover-shape', e.target, hoverShapeData, position); } }; diff --git a/src/plugins/d3/renderer/components/Tooltip/index.tsx b/src/plugins/d3/renderer/components/Tooltip/index.tsx index 298e537e..415d11db 100644 --- a/src/plugins/d3/renderer/components/Tooltip/index.tsx +++ b/src/plugins/d3/renderer/components/Tooltip/index.tsx @@ -16,6 +16,7 @@ const POINTER_OFFSET_X = 20; type TooltipProps = { dispatcher: Dispatch; tooltip: PreparedTooltip; + svgContainer: SVGSVGElement | null; xAxis: PreparedAxis; yAxis: PreparedAxis; hovered?: TooltipDataChunk[]; @@ -23,7 +24,7 @@ type TooltipProps = { }; export const Tooltip = (props: TooltipProps) => { - const {tooltip, xAxis, yAxis, hovered, pointerPosition} = props; + const {tooltip, svgContainer, xAxis, yAxis, hovered, pointerPosition} = props; const ref = React.useRef(null); const size = React.useMemo(() => { if (ref.current && hovered) { @@ -37,8 +38,10 @@ export const Tooltip = (props: TooltipProps) => { if (hovered && pointerPosition && size) { const {clientWidth} = document.documentElement; const {width, height} = size; + const rect = svgContainer?.getBoundingClientRect() || {left: 0, top: 0}; const [pointerLeft, pointetTop] = pointerPosition; - const outOfRightBoudary = pointerLeft + width + POINTER_OFFSET_X >= clientWidth; + const outOfRightBoudary = + pointerLeft + width + rect.left + POINTER_OFFSET_X >= clientWidth; const outOfTopBoundary = pointetTop - height / 2 <= 0; const left = outOfRightBoudary ? pointerLeft - width - POINTER_OFFSET_X @@ -50,7 +53,7 @@ export const Tooltip = (props: TooltipProps) => { } return undefined; - }, [hovered, pointerPosition, size]); + }, [hovered, pointerPosition, size, svgContainer]); const content = React.useMemo(() => { if (!hovered) { return null; diff --git a/src/plugins/d3/renderer/components/styles.scss b/src/plugins/d3/renderer/components/styles.scss index 68d40194..599156a9 100644 --- a/src/plugins/d3/renderer/components/styles.scss +++ b/src/plugins/d3/renderer/components/styles.scss @@ -84,4 +84,5 @@ border-radius: 3px; box-shadow: 0 2px 12px var(--g-color-sfx-shadow); z-index: 100001; + text-wrap: nowrap; } diff --git a/src/plugins/d3/renderer/hooks/useShapes/index.tsx b/src/plugins/d3/renderer/hooks/useShapes/index.tsx index 0d5c978e..c0c2af16 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/index.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/index.tsx @@ -28,8 +28,6 @@ export type {PreparedScatterData} from './scatter'; export type ShapeData = PreparedBarXData | PreparedScatterData | PreparedLineData; type Args = { - top: number; - left: number; boundsWidth: number; boundsHeight: number; dispatcher: Dispatch; @@ -44,8 +42,6 @@ type Args = { export const useShapes = (args: Args) => { const { - top, - left, boundsWidth, boundsHeight, dispatcher, @@ -121,8 +117,6 @@ export const useShapes = (args: Args) => { { boundsWidth={boundsWidth} boundsHeight={boundsHeight} dispatcher={dispatcher} - top={top} - left={left} series={pieSeries} seriesOptions={seriesOptions} svgContainer={svgContainer} @@ -170,8 +162,6 @@ export const useShapes = (args: Args) => { yAxis, yScale, svgContainer, - left, - top, ]); return {shapes: shapesComponents.shapes, shapesData: shapesComponents.shapesData}; diff --git a/src/plugins/d3/renderer/hooks/useShapes/pie.tsx b/src/plugins/d3/renderer/hooks/useShapes/pie.tsx index 6d212801..9df9bf4b 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/pie.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/pie.tsx @@ -22,8 +22,6 @@ type PreparePieSeriesArgs = { boundsWidth: number; boundsHeight: number; dispatcher: Dispatch; - top: number; - left: number; series: PreparedPieSeries[]; seriesOptions: PreparedSeriesOptions; svgContainer: SVGSVGElement | null; @@ -91,8 +89,7 @@ const isNodeContainsPieData = ( }; export function PieSeriesComponent(args: PreparePieSeriesArgs) { - const {boundsWidth, boundsHeight, dispatcher, top, left, series, seriesOptions, svgContainer} = - args; + const {boundsWidth, boundsHeight, dispatcher, series, seriesOptions, svgContainer} = args; const ref = React.useRef(null); const [x, y] = getCenter(boundsWidth, boundsHeight, series[0]?.center); @@ -230,12 +227,7 @@ export function PieSeriesComponent(args: PreparePieSeriesArgs) { const [pointerX, pointerY] = pointer(e, svgContainer); const segmentData = extractD3DataFromNode(segment).data; - dispatcher.call( - 'hover-shape', - {}, - [segmentData], - [pointerX - left, pointerY - top], - ); + dispatcher.call('hover-shape', {}, [segmentData], [pointerX, pointerY]); }) .on('mouseleave', () => { dispatcher.call('hover-shape', {}, undefined); @@ -285,7 +277,7 @@ export function PieSeriesComponent(args: PreparePieSeriesArgs) { return () => { dispatcher.on(eventName, null); }; - }, [boundsWidth, boundsHeight, dispatcher, top, left, series, seriesOptions, svgContainer]); + }, [boundsWidth, boundsHeight, dispatcher, series, seriesOptions, svgContainer]); return ; } diff --git a/src/plugins/d3/renderer/hooks/useShapes/scatter/index.tsx b/src/plugins/d3/renderer/hooks/useShapes/scatter/index.tsx index 5f7930a4..8dd669f9 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/scatter/index.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/scatter/index.tsx @@ -16,8 +16,6 @@ export type {PreparedScatterData} from './prepare-data'; type ScatterSeriesShapeProps = { dispatcher: Dispatch; - top: number; - left: number; preparedData: PreparedScatterData[]; seriesOptions: PreparedSeriesOptions; svgContainer: SVGSVGElement | null; @@ -37,7 +35,7 @@ const isNodeContainsScatterData = (node?: Element): node is NodeWithD3Data(null); React.useEffect(() => { @@ -72,12 +70,7 @@ export function ScatterSeriesShape(props: ScatterSeriesShapeProps) { const [pointerX, pointerY] = pointer(e, svgContainer); const segmentData = extractD3DataFromNode(point); - dispatcher.call( - 'hover-shape', - {}, - [segmentData], - [pointerX - left, pointerY - top], - ); + dispatcher.call('hover-shape', {}, [segmentData], [pointerX, pointerY]); }) .on('mouseleave', () => { dispatcher.call('hover-shape', {}, undefined); @@ -143,7 +136,7 @@ export function ScatterSeriesShape(props: ScatterSeriesShapeProps) { return () => { dispatcher.on('hover-shape.scatter', null); }; - }, [dispatcher, top, left, preparedData, seriesOptions, svgContainer]); + }, [dispatcher, preparedData, seriesOptions, svgContainer]); return ; }