Skip to content

Commit

Permalink
refactor(D3 plugin): pointer position calculation fixes (#337)
Browse files Browse the repository at this point in the history
  • Loading branch information
korvin89 authored Oct 27, 2023
1 parent 8ad2f09 commit f2a6902
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 63 deletions.
14 changes: 3 additions & 11 deletions src/plugins/d3/renderer/D3Widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {getRandomCKId} from '../../../utils';
import {Chart} from './components';

type ChartDimensions = {
top: number;
left: number;
width: number;
height: number;
};
Expand All @@ -37,8 +35,8 @@ const D3Widget = React.forwardRef<ChartKitWidgetRef | undefined, ChartKitProps<'
const parentElement = ref.current?.parentElement;

if (parentElement) {
const {top, left, width, height} = parentElement.getBoundingClientRect();
setDimensions({top, left, width, height});
const {width, height} = parentElement.getBoundingClientRect();
setDimensions({width, height});
}
}, []);

Expand Down Expand Up @@ -85,13 +83,7 @@ const D3Widget = React.forwardRef<ChartKitWidgetRef | undefined, ChartKitProps<'
}}
>
{dimensions?.width && dimensions?.height && (
<Chart
top={dimensions?.top || 0}
left={dimensions.left || 0}
width={dimensions.width}
height={dimensions.height}
data={data}
/>
<Chart width={dimensions.width} height={dimensions.height} data={data} />
)}
</div>
);
Expand Down
11 changes: 3 additions & 8 deletions src/plugins/d3/renderer/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ import './styles.scss';
const b = block('d3');

type Props = {
top: number;
left: number;
width: number;
height: number;
data: ChartKitWidgetData;
};

export const Chart = (props: Props) => {
// FIXME: add data validation
const {top, left, width, height, data} = props;
const svgRef = React.createRef<SVGSVGElement>();
const {width, height, data} = props;
const svgRef = React.useRef<SVGSVGElement>(null);
const dispatcher = React.useMemo(() => {
return getD3Dispatcher();
}, []);
Expand Down Expand Up @@ -86,8 +84,6 @@ export const Chart = (props: Props) => {
});
const {hovered, pointerPosition} = useTooltip({dispatcher, tooltip});
const {shapes, shapesData} = useShapes({
top,
left,
boundsWidth,
boundsHeight,
dispatcher,
Expand Down Expand Up @@ -136,8 +132,6 @@ export const Chart = (props: Props) => {
boundsWidth={boundsWidth}
boundsHeight={boundsHeight}
dispatcher={dispatcher}
offsetLeft={left}
offsetTop={top}
shapesData={shapesData}
svgContainer={svgRef.current}
/>
Expand All @@ -157,6 +151,7 @@ export const Chart = (props: Props) => {
<Tooltip
dispatcher={dispatcher}
tooltip={tooltip}
svgContainer={svgRef.current}
xAxis={xAxis}
yAxis={yAxis[0]}
hovered={hovered}
Expand Down
24 changes: 14 additions & 10 deletions src/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ type Args = {
boundsWidth: number;
boundsHeight: number;
dispatcher: Dispatch<object>;
offsetTop: number;
offsetLeft: number;
shapesData: ShapeData[];
svgContainer: SVGSVGElement | null;
};
Expand Down Expand Up @@ -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}"]`) || []);

Expand All @@ -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) {
Expand Down Expand Up @@ -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<SVGRectElement>(null);
const calculationType = React.useMemo(() => {
return getCalculationType(shapesData);
Expand Down Expand Up @@ -132,24 +129,31 @@ export const TooltipTriggerArea = (args: Args) => {
}, [shapesData]);

const handleXprimaryMouseMove: React.MouseEventHandler<SVGRectElement> = (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 = [];

hoverShapeData?.push(
...getBarXShapeData({
shapesData,
point: [pointerX, pointerY],
left,
top,
left: ownLeft - containerLeft,
top: ownTop - containerTop,
xData: xBarData,
container: rectRef.current?.parentElement,
}),
...getLineShapesData({xData: xLineData, point: [pointerX, pointerY]}),
);

if (hoverShapeData.length) {
const position: PointerPosition = [pointerX - offsetLeft, pointerY - offsetTop];
const position: PointerPosition = [pointerX, pointerY];
dispatcher.call('hover-shape', e.target, hoverShapeData, position);
}
};
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/d3/renderer/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ const POINTER_OFFSET_X = 20;
type TooltipProps = {
dispatcher: Dispatch<object>;
tooltip: PreparedTooltip;
svgContainer: SVGSVGElement | null;
xAxis: PreparedAxis;
yAxis: PreparedAxis;
hovered?: TooltipDataChunk[];
pointerPosition?: PointerPosition;
};

export const Tooltip = (props: TooltipProps) => {
const {tooltip, xAxis, yAxis, hovered, pointerPosition} = props;
const {tooltip, svgContainer, xAxis, yAxis, hovered, pointerPosition} = props;
const ref = React.useRef<HTMLDivElement>(null);
const size = React.useMemo(() => {
if (ref.current && hovered) {
Expand All @@ -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
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/components/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@
border-radius: 3px;
box-shadow: 0 2px 12px var(--g-color-sfx-shadow);
z-index: 100001;
text-wrap: nowrap;
}
10 changes: 0 additions & 10 deletions src/plugins/d3/renderer/hooks/useShapes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<object>;
Expand All @@ -44,8 +42,6 @@ type Args = {

export const useShapes = (args: Args) => {
const {
top,
left,
boundsWidth,
boundsHeight,
dispatcher,
Expand Down Expand Up @@ -121,8 +117,6 @@ export const useShapes = (args: Args) => {
<ScatterSeriesShape
key="scatter"
dispatcher={dispatcher}
top={top}
left={left}
preparedData={preparedData}
seriesOptions={seriesOptions}
svgContainer={svgContainer}
Expand All @@ -144,8 +138,6 @@ export const useShapes = (args: Args) => {
boundsWidth={boundsWidth}
boundsHeight={boundsHeight}
dispatcher={dispatcher}
top={top}
left={left}
series={pieSeries}
seriesOptions={seriesOptions}
svgContainer={svgContainer}
Expand All @@ -170,8 +162,6 @@ export const useShapes = (args: Args) => {
yAxis,
yScale,
svgContainer,
left,
top,
]);

return {shapes: shapesComponents.shapes, shapesData: shapesComponents.shapesData};
Expand Down
14 changes: 3 additions & 11 deletions src/plugins/d3/renderer/hooks/useShapes/pie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ type PreparePieSeriesArgs = {
boundsWidth: number;
boundsHeight: number;
dispatcher: Dispatch<object>;
top: number;
left: number;
series: PreparedPieSeries[];
seriesOptions: PreparedSeriesOptions;
svgContainer: SVGSVGElement | null;
Expand Down Expand Up @@ -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<SVGGElement>(null);
const [x, y] = getCenter(boundsWidth, boundsHeight, series[0]?.center);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 <g ref={ref} className={b()} transform={`translate(${x}, ${y})`} />;
}
13 changes: 3 additions & 10 deletions src/plugins/d3/renderer/hooks/useShapes/scatter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export type {PreparedScatterData} from './prepare-data';

type ScatterSeriesShapeProps = {
dispatcher: Dispatch<object>;
top: number;
left: number;
preparedData: PreparedScatterData[];
seriesOptions: PreparedSeriesOptions;
svgContainer: SVGSVGElement | null;
Expand All @@ -37,7 +35,7 @@ const isNodeContainsScatterData = (node?: Element): node is NodeWithD3Data<Prepa
};

export function ScatterSeriesShape(props: ScatterSeriesShapeProps) {
const {dispatcher, top, left, preparedData, seriesOptions, svgContainer} = props;
const {dispatcher, preparedData, seriesOptions, svgContainer} = props;
const ref = React.useRef<SVGGElement>(null);

React.useEffect(() => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 <g ref={ref} className={b()} />;
}

0 comments on commit f2a6902

Please sign in to comment.