Skip to content

Commit

Permalink
feat: tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
korvin89 committed Sep 21, 2023
1 parent ed1289e commit bc2dd16
Show file tree
Hide file tree
Showing 31 changed files with 923 additions and 391 deletions.
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export {CHARTKIT_SCROLLABLE_NODE_CLASSNAME} from './common';
export * from './widget-data';
5 changes: 5 additions & 0 deletions src/constants/widget-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const TooltipDataChunkType = {
BAR_X: 0,
PIE: 1,
SCATTER: 2,
} as const;
8 changes: 4 additions & 4 deletions src/plugins/d3/__stories__/bar-x/DatetimeAxis.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ const Template: Story = () => {
x: Number(new Date(2022, 10, 10)),
y: 100,
},
{
x: Number(new Date(2023, 2, 5)),
y: 80,
},
// {
// x: Number(new Date(2023, 2, 5)),
// y: 80,
// },
],
name: 'AB',
},
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/__stories__/pie/BasicDonut.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const Template: Story = () => {
},
title: {text: 'Basic donut'},
legend: {enabled: false},
tooltip: {enabled: false},
tooltip: {enabled: true},
};

if (!shown) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/__stories__/pie/BasicPie.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const Template: Story = () => {
},
title: {text: 'Basic pie'},
legend: {enabled: true},
tooltip: {enabled: false},
tooltip: {enabled: true},
};

if (!shown) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/__stories__/pie/Styled.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Template: Story = () => {
},
title: {text: 'Styled pies'},
legend: {enabled: false},
tooltip: {enabled: false},
tooltip: {enabled: true},
};

if (!shown) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/__stories__/scatter/BigLegend.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const shapeData = (): ChartKitWidgetData => {
itemDistance: number('Item distance', 20, undefined, 'legend'),
},
series: {
data: generateSeriesData(number('Amount of series', 100, undefined, 'legend')),
data: generateSeriesData(number('Amount of series', 1000, undefined, 'legend')),
},
xAxis: {
labels: {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/__stories__/scatter/Timestamp.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const shapeData = (data: Record<string, any>[]): ChartKitWidgetData<string> => {
],
tooltip: {
renderer: ({hovered}) => {
const d = hovered.data as ScatterSeriesData<string>;
const d = hovered[0].data as ScatterSeriesData<string>;
return <div style={{color: d.custom}}>{dateTime({input: d.x}).format('LL')}</div>;
},
},
Expand Down
56 changes: 34 additions & 22 deletions src/plugins/d3/renderer/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {block} from '../../../../utils/cn';
import {
useAxisScales,
useChartDimensions,
useChartEvents,
// useChartEvents,
useChartOptions,
useSeries,
useShapes,
Expand All @@ -17,7 +17,7 @@ import {AxisY} from './AxisY';
import {AxisX} from './AxisX';
import {Legend} from './Legend';
import {Title} from './Title';
import {Tooltip} from './Tooltip';
import {Tooltip, TooltipArea} from './Tooltip';

import './styles.scss';

Expand All @@ -35,19 +35,24 @@ export const Chart = (props: Props) => {
// FIXME: add data validation
const {top, left, width, height, data} = props;
const svgRef = React.createRef<SVGSVGElement>();
const {chartHovered, handleMouseEnter, handleMouseLeave} = useChartEvents();
const {chart, title, tooltip, xAxis, yAxis} = useChartOptions({
data,
});
const {legendItems, legendConfig, preparedSeries, preparedLegend, handleLegendItemClick} =
useSeries({
chartWidth: width,
chartHeight: height,
chartMargin: chart.margin,
series: data.series,
legend: data.legend,
preparedYAxis: yAxis,
});
const {
legendItems,
legendConfig,
preparedSeries,
preparedSeriesOptions,
preparedLegend,
handleLegendItemClick,
} = useSeries({
chartWidth: width,
chartHeight: height,
chartMargin: chart.margin,
series: data.series,
legend: data.legend,
preparedYAxis: yAxis,
});
const {boundsWidth, boundsHeight} = useChartDimensions({
hasAxisRelatedSeries: data.series.data.some(isAxisRelatedSeries),
width,
Expand All @@ -67,13 +72,14 @@ export const Chart = (props: Props) => {
const {hovered, pointerPosition, handleSeriesMouseMove, handleSeriesMouseLeave} = useTooltip({
tooltip,
});
const {shapes} = useShapes({
const {shapes, shapesData} = useShapes({
top,
left,
boundsWidth,
boundsHeight,
hovered,
series: preparedSeries,
seriesOptions: data.series.options,
seriesOptions: preparedSeriesOptions,
xAxis,
xScale,
yAxis,
Expand All @@ -85,14 +91,7 @@ export const Chart = (props: Props) => {

return (
<React.Fragment>
<svg
ref={svgRef}
className={b({hovered: chartHovered})}
width={width}
height={height}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<svg ref={svgRef} className={b()} width={width} height={height}>
{title && <Title {...title} chartWidth={width} />}
<g
width={boundsWidth}
Expand All @@ -119,6 +118,19 @@ export const Chart = (props: Props) => {
</React.Fragment>
)}
{shapes}
{tooltip?.enabled && Boolean(shapesData.length) && (
<TooltipArea
boundsWidth={boundsWidth}
boundsHeight={boundsHeight}
offsetLeft={left}
offsetTop={top}
shapesData={shapesData}
svgContainer={svgRef.current}
xScale={xScale}
onSeriesMouseMove={handleSeriesMouseMove}
onSeriesMouseLeave={handleSeriesMouseLeave}
/>
)}
</g>
{preparedLegend.enabled && (
<Legend
Expand Down
12 changes: 10 additions & 2 deletions src/plugins/d3/renderer/components/Tooltip/DefaultContent.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import get from 'lodash/get';
import {dateTime} from '@gravity-ui/date-utils';
import type {ChartKitWidgetSeriesData, TooltipHoveredData} from '../../../../../types';
import type {ChartKitWidgetSeriesData, TooltipDataChunkNext} from '../../../../../types';
import {formatNumber} from '../../../../shared';
import type {PreparedAxis} from '../../hooks';
import {getDataCategoryValue} from '../../utils';

type Props = {
hovered: TooltipHoveredData;
hovered: TooltipDataChunkNext;
xAxis: PreparedAxis;
yAxis: PreparedAxis;
};
Expand Down Expand Up @@ -75,6 +75,14 @@ export const DefaultContent = ({hovered, xAxis, yAxis}: Props) => {
</div>
);
}
case 'pie': {
return (
<div>
<span>{series.name || series.innerName}&nbsp;</span>
<span>{data.value}</span>
</div>
);
}
default: {
return null;
}
Expand Down
151 changes: 151 additions & 0 deletions src/plugins/d3/renderer/components/Tooltip/TooltipArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import throttle from 'lodash/throttle';
import {Delaunay, bisector, pointer, sort} from 'd3';

import type {
ChartScale,
OnSeriesMouseMove,
OnSeriesMouseLeave,
ShapeData,
PreparedBarXData,
PreparedScatterData,
} from '../../hooks';

type Args = {
boundsWidth: number;
boundsHeight: number;
offsetTop: number;
offsetLeft: number;
shapesData: ShapeData[];
svgContainer: SVGSVGElement | null;
xScale?: ChartScale;
onSeriesMouseMove?: OnSeriesMouseMove;
onSeriesMouseLeave?: OnSeriesMouseLeave;
};

type CalculationType = 'x-primary' | 'delaunay';

// https://d3js.org/d3-selection/joining#selection_data
const isNodeContainsData = (node?: Element): node is Element & {__data__: ShapeData} => {
return Boolean(node && '__data__' in node);
};

const getCalculationType = (shapesData: ShapeData[]): CalculationType => {
if (shapesData.every((d) => d.series.type === 'bar-x')) {
return 'x-primary';
}

if (shapesData.every((d) => d.series.type === 'scatter')) {
return 'delaunay';
}

throw new Error('This type of series does not supported for tooltip yet');
};

export const TooltipArea = (args: Args) => {
const {
boundsWidth,
boundsHeight,
offsetTop,
offsetLeft,
shapesData,
svgContainer,
xScale,
onSeriesMouseMove,
onSeriesMouseLeave,
} = args;
const rectRef = React.useRef<SVGRectElement>(null);
const calculationType = React.useMemo(() => {
return getCalculationType(shapesData);
}, [shapesData]);
const xData = React.useMemo(() => {
return calculationType === 'x-primary'
? sort(new Set((shapesData as PreparedBarXData[]).map((d) => d.x)))
: [];
}, [shapesData, calculationType]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const delaunay = React.useMemo(() => {
return calculationType === 'delaunay'
? new Delaunay(
new Float64Array(
(shapesData as PreparedScatterData[]).map((d) => [d.cx, d.cy]).flat(),
),
)
: undefined;
}, [shapesData, calculationType]);

const handleXprimaryMouseMove: React.MouseEventHandler<SVGRectElement> = (e) => {
const {left, top} = rectRef.current?.getBoundingClientRect() || {left: 0, top: 0};
const [x, y] = pointer(e, svgContainer);
const isXLinearOrTimeScale = xScale && 'invert' in xScale;
const xPosition = x - left - (isXLinearOrTimeScale ? 0 : offsetLeft);
const xDataIndex = bisector((d) => d).center(xData, xPosition);
const xNodes = Array.from(
rectRef.current?.parentElement?.querySelectorAll(`[x="${xData[xDataIndex]}"]`) || [],
);

if (xNodes.length === 1 && isNodeContainsData(xNodes[0])) {
onSeriesMouseMove?.({
hovered: [xNodes[0].__data__],
pointerPosition: [x - offsetLeft, y - offsetTop],
});
} else if (xNodes.length > 1 && xNodes.every(isNodeContainsData)) {
const yData = xNodes.map((node) => (node.__data__ as PreparedBarXData).y);
const yPosition = y - top;
const yDataIndex = bisector((d) => d).center(yData, yPosition);

if (xNodes[yDataIndex]) {
xNodes.reverse();
onSeriesMouseMove?.({
hovered: [xNodes[yDataIndex].__data__],
pointerPosition: [x - offsetLeft, y - offsetTop],
});
}
}
};

const handleDelaunayMouseMove: React.MouseEventHandler<SVGRectElement> = (e) => {
console.log(document.elementFromPoint(e.clientX, e.clientY));
// const {left, top} = rectRef.current?.getBoundingClientRect() || {left: 0, top: 0};
// const [x, y] = pointer(e, svgContainer);
// const dataIndex = delaunay?.find(x - left, y - top, prevIndex) || -1;
// if (shapesData[dataIndex]) {
// prevIndex = dataIndex;
// // console.log(shapesData[dataIndex]);
// onSeriesMouseMove?.({
// hovered: [shapesData[dataIndex]],
// pointerPosition: [x - offsetLeft, y - offsetTop],
// });
// }
};

const handleMouseMove: React.MouseEventHandler<SVGRectElement> = (e) => {
switch (calculationType) {
case 'x-primary': {
handleXprimaryMouseMove(e);
return;
}
case 'delaunay': {
handleDelaunayMouseMove(e);
}
}
};

const throttledHandleMouseMove = throttle(handleMouseMove, 50);

const handleMouseLeave = () => {
throttledHandleMouseMove.cancel();
onSeriesMouseLeave?.();
};

return (
<rect
ref={rectRef}
width={boundsWidth}
height={boundsHeight}
fill="transparent"
onMouseMove={throttledHandleMouseMove}
onMouseLeave={handleMouseLeave}
/>
);
};
9 changes: 6 additions & 3 deletions src/plugins/d3/renderer/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import React from 'react';
import isNil from 'lodash/isNil';

import type {TooltipHoveredData} from '../../../../../types/widget-data';
import type {TooltipDataChunkNext} from '../../../../../types/widget-data';
import {block} from '../../../../../utils/cn';

import type {PointerPosition, PreparedAxis, PreparedTooltip} from '../../hooks';
import {DefaultContent} from './DefaultContent';

export * from './TooltipArea';

const b = block('d3-tooltip');
const POINTER_OFFSET_X = 20;

type TooltipProps = {
tooltip: PreparedTooltip;
xAxis: PreparedAxis;
yAxis: PreparedAxis;
hovered?: TooltipHoveredData;
hovered?: TooltipDataChunkNext[];
pointerPosition?: PointerPosition;
};

Expand All @@ -27,6 +29,7 @@ export const Tooltip = (props: TooltipProps) => {
return {width, height};
}
return undefined;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hovered, pointerPosition]);
const position = React.useMemo(() => {
if (hovered && pointerPosition && size) {
Expand Down Expand Up @@ -54,7 +57,7 @@ export const Tooltip = (props: TooltipProps) => {
const customTooltip = tooltip.renderer?.({hovered});

return isNil(customTooltip) ? (
<DefaultContent hovered={hovered} xAxis={xAxis} yAxis={yAxis} />
<DefaultContent hovered={hovered[0]} xAxis={xAxis} yAxis={yAxis} />
) : (
customTooltip
);
Expand Down
Loading

0 comments on commit bc2dd16

Please sign in to comment.