@gravity-ui/charts 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cjs/components/ChartInner/index.js +37 -16
  2. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +9 -0
  3. package/dist/cjs/components/Tooltip/ChartTooltipContent.js +11 -0
  4. package/dist/cjs/components/Tooltip/DefaultContent.d.ts +3 -4
  5. package/dist/cjs/components/Tooltip/DefaultContent.js +10 -10
  6. package/dist/cjs/components/Tooltip/index.js +3 -11
  7. package/dist/cjs/components/index.d.ts +1 -1
  8. package/dist/cjs/components/index.js +1 -0
  9. package/dist/cjs/constants/index.d.ts +1 -0
  10. package/dist/cjs/constants/index.js +1 -0
  11. package/dist/cjs/constants/misc.d.ts +1 -0
  12. package/dist/cjs/constants/misc.js +7 -0
  13. package/dist/cjs/types/chart/chart.d.ts +6 -7
  14. package/dist/cjs/types/chart/tooltip.d.ts +8 -0
  15. package/dist/cjs/utils/d3-dispatcher.d.ts +5 -0
  16. package/dist/cjs/utils/d3-dispatcher.js +6 -1
  17. package/dist/esm/components/ChartInner/index.js +37 -16
  18. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +9 -0
  19. package/dist/esm/components/Tooltip/ChartTooltipContent.js +11 -0
  20. package/dist/esm/components/Tooltip/DefaultContent.d.ts +3 -4
  21. package/dist/esm/components/Tooltip/DefaultContent.js +10 -10
  22. package/dist/esm/components/Tooltip/index.js +3 -11
  23. package/dist/esm/components/index.d.ts +1 -1
  24. package/dist/esm/components/index.js +1 -0
  25. package/dist/esm/constants/index.d.ts +1 -0
  26. package/dist/esm/constants/index.js +1 -0
  27. package/dist/esm/constants/misc.d.ts +1 -0
  28. package/dist/esm/constants/misc.js +7 -0
  29. package/dist/esm/types/chart/chart.d.ts +6 -7
  30. package/dist/esm/types/chart/tooltip.d.ts +8 -0
  31. package/dist/esm/utils/d3-dispatcher.d.ts +5 -0
  32. package/dist/esm/utils/d3-dispatcher.js +6 -1
  33. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import { pointer } from 'd3';
3
3
  import throttle from 'lodash/throttle';
4
+ import { IS_TOUCH_ENABLED } from '../../constants';
4
5
  import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes, } from '../../hooks';
5
6
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
6
7
  import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
7
8
  import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
8
9
  import { useSplit } from '../../hooks/useSplit';
9
- import { block, getD3Dispatcher } from '../../utils';
10
+ import { EventType, block, getD3Dispatcher } from '../../utils';
10
11
  import { getClosestPoints } from '../../utils/chart/get-closest-data';
11
12
  import { AxisX, AxisY } from '../Axis';
12
13
  import { Legend } from '../Legend';
@@ -76,17 +77,17 @@ export const ChartInner = (props) => {
76
77
  const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
77
78
  React.useEffect(() => {
78
79
  if (clickHandler) {
79
- dispatcher.on('click-chart', clickHandler);
80
+ dispatcher.on(EventType.CLICK_CHART, clickHandler);
80
81
  }
81
82
  if (pointerMoveHandler) {
82
- dispatcher.on('hover-shape.chart', (...args) => {
83
- const [hoverData, _position, event] = args;
84
- pointerMoveHandler(hoverData, event);
83
+ dispatcher.on(EventType.POINTERMOVE_CHART, (...args) => {
84
+ const [handlerData, event] = args;
85
+ pointerMoveHandler(handlerData, event);
85
86
  });
86
87
  }
87
88
  return () => {
88
- dispatcher.on('click-chart', null);
89
- dispatcher.on('hover-shape.chart', null);
89
+ dispatcher.on(EventType.CLICK_CHART, null);
90
+ dispatcher.on(EventType.POINTERMOVE_CHART, null);
90
91
  };
91
92
  }, [dispatcher, clickHandler, pointerMoveHandler]);
92
93
  const boundsOffsetTop = chart.margin.top;
@@ -95,25 +96,45 @@ export const ChartInner = (props) => {
95
96
  const isOutsideBounds = React.useCallback((x, y) => {
96
97
  return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
97
98
  }, [boundsHeight, boundsWidth]);
98
- const handlePointerMove = (event) => {
99
- const [pointerX, pointerY] = pointer(event, svgRef.current);
99
+ const handleMove = ([pointerX, pointerY], event) => {
100
100
  const x = pointerX - boundsOffsetLeft;
101
101
  const y = pointerY - boundsOffsetTop;
102
102
  if (isOutsideBounds(x, y)) {
103
- dispatcher.call('hover-shape', {}, undefined, undefined, event);
103
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
104
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
104
105
  return;
105
106
  }
106
107
  const closest = getClosestPoints({
107
108
  position: [x, y],
108
109
  shapesData,
109
110
  });
110
- dispatcher.call('hover-shape', event.target, closest, [pointerX, pointerY], event);
111
+ dispatcher.call(EventType.HOVER_SHAPE, event.target, closest, [pointerX, pointerY]);
112
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
113
+ hovered: closest,
114
+ xAxis,
115
+ yAxis: yAxis[0],
116
+ }, event);
117
+ };
118
+ const handleMouseMove = (event) => {
119
+ const [pointerX, pointerY] = pointer(event, svgRef.current);
120
+ handleMove([pointerX, pointerY], event);
121
+ };
122
+ const throttledHandleMouseMove = IS_TOUCH_ENABLED
123
+ ? undefined
124
+ : throttle(handleMouseMove, THROTTLE_DELAY);
125
+ const handleMouseLeave = (event) => {
126
+ throttledHandleMouseMove === null || throttledHandleMouseMove === void 0 ? void 0 : throttledHandleMouseMove.cancel();
127
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
128
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
111
129
  };
112
- const throttledHandlePointerMove = throttle(handlePointerMove, THROTTLE_DELAY);
113
- const handlePointerLeave = (event) => {
114
- throttledHandlePointerMove.cancel();
115
- dispatcher.call('hover-shape', {}, undefined, undefined, event);
130
+ const handleTouchMove = (event) => {
131
+ const touch = event.touches[0];
132
+ const [pointerX, pointerY] = pointer(touch, svgRef.current);
133
+ handleMove([pointerX, pointerY], event);
116
134
  };
135
+ const throttledHandleTouchMove = IS_TOUCH_ENABLED
136
+ ? throttle(handleTouchMove, THROTTLE_DELAY)
137
+ : undefined;
117
138
  const handleChartClick = React.useCallback((event) => {
118
139
  const [pointerX, pointerY] = pointer(event, svgRef.current);
119
140
  const x = pointerX - boundsOffsetLeft;
@@ -132,7 +153,7 @@ export const ChartInner = (props) => {
132
153
  dispatcher.call('click-chart', undefined, { point: selected.data, series: selected.series }, event);
133
154
  }, [boundsOffsetLeft, boundsOffsetTop, dispatcher, isOutsideBounds, shapesData]);
134
155
  return (React.createElement(React.Fragment, null,
135
- React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onPointerMove: throttledHandlePointerMove, onPointerLeave: handlePointerLeave, onClick: handleChartClick },
156
+ React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
136
157
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
137
158
  React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
138
159
  return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
3
+ export type ChartTooltipContentProps = {
4
+ hovered?: TooltipDataChunk[];
5
+ xAxis?: ChartXAxis;
6
+ yAxis?: ChartYAxis;
7
+ renderer?: ChartTooltip['renderer'];
8
+ };
9
+ export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import isNil from 'lodash/isNil';
3
+ import { DefaultContent } from './DefaultContent';
4
+ export const ChartTooltipContent = (props) => {
5
+ const { hovered, xAxis, yAxis, renderer } = props;
6
+ if (!hovered) {
7
+ return null;
8
+ }
9
+ const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
10
+ return isNil(customTooltip) ? (React.createElement(DefaultContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
11
+ };
@@ -1,10 +1,9 @@
1
1
  import React from 'react';
2
- import type { PreparedAxis } from '../../hooks';
3
- import type { TooltipDataChunk } from '../../types';
2
+ import type { ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
4
3
  type Props = {
5
4
  hovered: TooltipDataChunk[];
6
- xAxis: PreparedAxis;
7
- yAxis: PreparedAxis;
5
+ xAxis?: ChartXAxis;
6
+ yAxis?: ChartYAxis;
8
7
  };
9
8
  export declare const DefaultContent: ({ hovered, xAxis, yAxis }: Props) => React.JSX.Element;
10
9
  export {};
@@ -5,8 +5,8 @@ import { formatNumber } from '../../libs';
5
5
  import { block, getDataCategoryValue, getWaterfallPointSubtotal } from '../../utils';
6
6
  const b = block('d3-tooltip');
7
7
  const DEFAULT_DATE_FORMAT = 'DD.MM.YY';
8
- const getRowData = (fieldName, axis, data) => {
9
- switch (axis.type) {
8
+ const getRowData = (fieldName, data, axis) => {
9
+ switch (axis === null || axis === void 0 ? void 0 : axis.type) {
10
10
  case 'category': {
11
11
  const categories = get(axis, 'categories', []);
12
12
  return getDataCategoryValue({ axisDirection: fieldName, categories, data });
@@ -25,17 +25,17 @@ const getRowData = (fieldName, axis, data) => {
25
25
  }
26
26
  }
27
27
  };
28
- const getXRowData = (xAxis, data) => getRowData('x', xAxis, data);
29
- const getYRowData = (yAxis, data) => getRowData('y', yAxis, data);
28
+ const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
29
+ const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
30
30
  const getMeasureValue = (data, xAxis, yAxis) => {
31
31
  var _a, _b;
32
32
  if (data.every((item) => ['pie', 'treemap', 'waterfall'].includes(item.series.type))) {
33
33
  return null;
34
34
  }
35
35
  if (data.some((item) => item.series.type === 'bar-y')) {
36
- return getYRowData(yAxis, (_a = data[0]) === null || _a === void 0 ? void 0 : _a.data);
36
+ return getYRowData((_a = data[0]) === null || _a === void 0 ? void 0 : _a.data, yAxis);
37
37
  }
38
- return getXRowData(xAxis, (_b = data[0]) === null || _b === void 0 ? void 0 : _b.data);
38
+ return getXRowData((_b = data[0]) === null || _b === void 0 ? void 0 : _b.data, xAxis);
39
39
  };
40
40
  export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
41
41
  const measureValue = getMeasureValue(hovered, xAxis, yAxis);
@@ -52,7 +52,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
52
52
  const value = (React.createElement(React.Fragment, null,
53
53
  series.name,
54
54
  ": ",
55
- getYRowData(yAxis, data)));
55
+ getYRowData(data, yAxis)));
56
56
  return (React.createElement("div", { key: id, className: b('content-row') },
57
57
  React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
58
58
  React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
@@ -63,12 +63,12 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
63
63
  return (React.createElement("div", { key: `${id}_${get(data, 'x')}` },
64
64
  !isTotal && (React.createElement(React.Fragment, null,
65
65
  React.createElement("div", { key: id, className: b('content-row') },
66
- React.createElement("b", null, getXRowData(xAxis, data))),
66
+ React.createElement("b", null, getXRowData(data, xAxis))),
67
67
  React.createElement("div", { className: b('content-row') },
68
68
  React.createElement("span", null,
69
69
  series.name,
70
70
  "\u00A0"),
71
- React.createElement("span", null, getYRowData(yAxis, data))))),
71
+ React.createElement("span", null, getYRowData(data, yAxis))))),
72
72
  React.createElement("div", { key: id, className: b('content-row') },
73
73
  isTotal ? 'Total' : 'Subtotal',
74
74
  ": ",
@@ -78,7 +78,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
78
78
  const value = (React.createElement(React.Fragment, null,
79
79
  series.name,
80
80
  ": ",
81
- getXRowData(xAxis, data)));
81
+ getXRowData(data, xAxis)));
82
82
  return (React.createElement("div", { key: id, className: b('content-row') },
83
83
  React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
84
84
  React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Popup, useVirtualElementRef } from '@gravity-ui/uikit';
3
- import isNil from 'lodash/isNil';
4
3
  import { useTooltip } from '../../hooks';
5
4
  import { block } from '../../utils';
6
- import { DefaultContent } from './DefaultContent';
5
+ import { ChartTooltipContent } from './ChartTooltipContent';
7
6
  import './styles.css';
8
7
  const b = block('d3-tooltip');
9
8
  export const Tooltip = (props) => {
@@ -13,17 +12,10 @@ export const Tooltip = (props) => {
13
12
  const left = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[0]) || 0) + containerRect.left;
14
13
  const top = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[1]) || 0) + containerRect.top;
15
14
  const anchorRef = useVirtualElementRef({ rect: { top, left } });
16
- const content = React.useMemo(() => {
17
- var _a;
18
- if (!hovered) {
19
- return null;
20
- }
21
- const customTooltip = (_a = tooltip.renderer) === null || _a === void 0 ? void 0 : _a.call(tooltip, { hovered });
22
- return isNil(customTooltip) ? (React.createElement(DefaultContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
23
- }, [hovered, tooltip, xAxis, yAxis]);
24
15
  React.useEffect(() => {
25
16
  window.dispatchEvent(new CustomEvent('scroll'));
26
17
  }, [left, top]);
27
18
  return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { className: b(), open: true, anchorRef: anchorRef, offset: [0, 20], placement: ['right', 'left', 'top', 'bottom'], modifiers: [{ name: 'preventOverflow', options: { padding: 10, altAxis: true } }] },
28
- React.createElement("div", { className: b('content') }, content))) : null;
19
+ React.createElement("div", { className: b('content') },
20
+ React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer })))) : null;
29
21
  };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ChartData } from '../types';
3
+ export * from './Tooltip/ChartTooltipContent';
3
4
  export type ChartRef = {
4
5
  reflow: () => void;
5
6
  };
@@ -15,4 +16,3 @@ export type ChartProps = {
15
16
  }) => void;
16
17
  };
17
18
  export declare const Chart: React.ForwardRefExoticComponent<ChartProps & React.RefAttributes<ChartRef>>;
18
- export {};
@@ -5,6 +5,7 @@ import { i18nFactory } from '../i18n';
5
5
  import { getUniqId } from '../utils';
6
6
  import { validateData } from '../validation';
7
7
  import { ChartInner } from './ChartInner';
8
+ export * from './Tooltip/ChartTooltipContent';
8
9
  export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
9
10
  const { data, lang, onResize } = props;
10
11
  const validatedData = React.useRef();
@@ -1,4 +1,5 @@
1
1
  export * from './defaults';
2
+ export * from './misc';
2
3
  export declare const SeriesType: {
3
4
  readonly Area: "area";
4
5
  readonly BarX: "bar-x";
@@ -1,4 +1,5 @@
1
1
  export * from './defaults';
2
+ export * from './misc';
2
3
  export const SeriesType = {
3
4
  Area: 'area',
4
5
  BarX: 'bar-x',
@@ -0,0 +1 @@
1
+ export declare const IS_TOUCH_ENABLED: boolean;
@@ -0,0 +1,7 @@
1
+ function isTouchEnabled() {
2
+ if (typeof window !== 'object') {
3
+ return false;
4
+ }
5
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
6
+ }
7
+ export const IS_TOUCH_ENABLED = isTouchEnabled();
@@ -1,19 +1,18 @@
1
1
  import type { MeaningfulAny } from '../misc';
2
+ import type { ChartTooltipRendererData } from './tooltip';
2
3
  export type ChartMargin = {
3
4
  top: number;
4
5
  right: number;
5
6
  bottom: number;
6
7
  left: number;
7
8
  };
8
- type ChartEventData = {
9
- point: MeaningfulAny;
10
- series: MeaningfulAny;
11
- };
12
9
  export type ChartOptions = {
13
10
  margin?: Partial<ChartMargin>;
14
11
  events?: {
15
- click?: (data: ChartEventData, event: PointerEvent) => void;
16
- pointermove?: (data: ChartEventData | undefined, event: PointerEvent) => void;
12
+ click?: (data: {
13
+ point: MeaningfulAny;
14
+ series: MeaningfulAny;
15
+ }, event: PointerEvent) => void;
16
+ pointermove?: (data: ChartTooltipRendererData | undefined, event: PointerEvent) => void;
17
17
  };
18
18
  };
19
- export {};
@@ -1,5 +1,6 @@
1
1
  import type { MeaningfulAny } from '../misc';
2
2
  import type { AreaSeries, AreaSeriesData } from './area';
3
+ import type { ChartXAxis, ChartYAxis } from './axis';
3
4
  import type { BarXSeries, BarXSeriesData } from './bar-x';
4
5
  import type { BarYSeries, BarYSeriesData } from './bar-y';
5
6
  import type { LineSeries, LineSeriesData } from './line';
@@ -58,10 +59,17 @@ export type TooltipDataChunkWaterfall<T = MeaningfulAny> = {
58
59
  export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkWaterfall<T>) & {
59
60
  closest?: boolean;
60
61
  };
62
+ export type ChartTooltipRendererData<T = MeaningfulAny> = {
63
+ hovered: TooltipDataChunk<T>[];
64
+ xAxis?: ChartXAxis;
65
+ yAxis?: ChartYAxis;
66
+ };
61
67
  export type ChartTooltip<T = MeaningfulAny> = {
62
68
  enabled?: boolean;
63
69
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
64
70
  renderer?: (args: {
65
71
  hovered: TooltipDataChunk<T>[];
72
+ xAxis?: ChartXAxis;
73
+ yAxis?: ChartYAxis;
66
74
  }) => React.ReactElement | null;
67
75
  };
@@ -1 +1,6 @@
1
+ export declare const EventType: {
2
+ CLICK_CHART: string;
3
+ HOVER_SHAPE: string;
4
+ POINTERMOVE_CHART: string;
5
+ };
1
6
  export declare const getD3Dispatcher: () => import("d3-dispatch").Dispatch<object>;
@@ -1,4 +1,9 @@
1
1
  import { dispatch } from 'd3';
2
+ export const EventType = {
3
+ CLICK_CHART: 'click-chart',
4
+ HOVER_SHAPE: 'hover-shape',
5
+ POINTERMOVE_CHART: 'pointermove-chart',
6
+ };
2
7
  export const getD3Dispatcher = () => {
3
- return dispatch('hover-shape', 'click-chart');
8
+ return dispatch(EventType.CLICK_CHART, EventType.HOVER_SHAPE, EventType.POINTERMOVE_CHART);
4
9
  };
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import { pointer } from 'd3';
3
3
  import throttle from 'lodash/throttle';
4
+ import { IS_TOUCH_ENABLED } from '../../constants';
4
5
  import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes, } from '../../hooks';
5
6
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
6
7
  import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
7
8
  import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
8
9
  import { useSplit } from '../../hooks/useSplit';
9
- import { block, getD3Dispatcher } from '../../utils';
10
+ import { EventType, block, getD3Dispatcher } from '../../utils';
10
11
  import { getClosestPoints } from '../../utils/chart/get-closest-data';
11
12
  import { AxisX, AxisY } from '../Axis';
12
13
  import { Legend } from '../Legend';
@@ -76,17 +77,17 @@ export const ChartInner = (props) => {
76
77
  const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
77
78
  React.useEffect(() => {
78
79
  if (clickHandler) {
79
- dispatcher.on('click-chart', clickHandler);
80
+ dispatcher.on(EventType.CLICK_CHART, clickHandler);
80
81
  }
81
82
  if (pointerMoveHandler) {
82
- dispatcher.on('hover-shape.chart', (...args) => {
83
- const [hoverData, _position, event] = args;
84
- pointerMoveHandler(hoverData, event);
83
+ dispatcher.on(EventType.POINTERMOVE_CHART, (...args) => {
84
+ const [handlerData, event] = args;
85
+ pointerMoveHandler(handlerData, event);
85
86
  });
86
87
  }
87
88
  return () => {
88
- dispatcher.on('click-chart', null);
89
- dispatcher.on('hover-shape.chart', null);
89
+ dispatcher.on(EventType.CLICK_CHART, null);
90
+ dispatcher.on(EventType.POINTERMOVE_CHART, null);
90
91
  };
91
92
  }, [dispatcher, clickHandler, pointerMoveHandler]);
92
93
  const boundsOffsetTop = chart.margin.top;
@@ -95,25 +96,45 @@ export const ChartInner = (props) => {
95
96
  const isOutsideBounds = React.useCallback((x, y) => {
96
97
  return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
97
98
  }, [boundsHeight, boundsWidth]);
98
- const handlePointerMove = (event) => {
99
- const [pointerX, pointerY] = pointer(event, svgRef.current);
99
+ const handleMove = ([pointerX, pointerY], event) => {
100
100
  const x = pointerX - boundsOffsetLeft;
101
101
  const y = pointerY - boundsOffsetTop;
102
102
  if (isOutsideBounds(x, y)) {
103
- dispatcher.call('hover-shape', {}, undefined, undefined, event);
103
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
104
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
104
105
  return;
105
106
  }
106
107
  const closest = getClosestPoints({
107
108
  position: [x, y],
108
109
  shapesData,
109
110
  });
110
- dispatcher.call('hover-shape', event.target, closest, [pointerX, pointerY], event);
111
+ dispatcher.call(EventType.HOVER_SHAPE, event.target, closest, [pointerX, pointerY]);
112
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
113
+ hovered: closest,
114
+ xAxis,
115
+ yAxis: yAxis[0],
116
+ }, event);
117
+ };
118
+ const handleMouseMove = (event) => {
119
+ const [pointerX, pointerY] = pointer(event, svgRef.current);
120
+ handleMove([pointerX, pointerY], event);
121
+ };
122
+ const throttledHandleMouseMove = IS_TOUCH_ENABLED
123
+ ? undefined
124
+ : throttle(handleMouseMove, THROTTLE_DELAY);
125
+ const handleMouseLeave = (event) => {
126
+ throttledHandleMouseMove === null || throttledHandleMouseMove === void 0 ? void 0 : throttledHandleMouseMove.cancel();
127
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
128
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
111
129
  };
112
- const throttledHandlePointerMove = throttle(handlePointerMove, THROTTLE_DELAY);
113
- const handlePointerLeave = (event) => {
114
- throttledHandlePointerMove.cancel();
115
- dispatcher.call('hover-shape', {}, undefined, undefined, event);
130
+ const handleTouchMove = (event) => {
131
+ const touch = event.touches[0];
132
+ const [pointerX, pointerY] = pointer(touch, svgRef.current);
133
+ handleMove([pointerX, pointerY], event);
116
134
  };
135
+ const throttledHandleTouchMove = IS_TOUCH_ENABLED
136
+ ? throttle(handleTouchMove, THROTTLE_DELAY)
137
+ : undefined;
117
138
  const handleChartClick = React.useCallback((event) => {
118
139
  const [pointerX, pointerY] = pointer(event, svgRef.current);
119
140
  const x = pointerX - boundsOffsetLeft;
@@ -132,7 +153,7 @@ export const ChartInner = (props) => {
132
153
  dispatcher.call('click-chart', undefined, { point: selected.data, series: selected.series }, event);
133
154
  }, [boundsOffsetLeft, boundsOffsetTop, dispatcher, isOutsideBounds, shapesData]);
134
155
  return (React.createElement(React.Fragment, null,
135
- React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onPointerMove: throttledHandlePointerMove, onPointerLeave: handlePointerLeave, onClick: handleChartClick },
156
+ React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
136
157
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
137
158
  React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
138
159
  return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
3
+ export type ChartTooltipContentProps = {
4
+ hovered?: TooltipDataChunk[];
5
+ xAxis?: ChartXAxis;
6
+ yAxis?: ChartYAxis;
7
+ renderer?: ChartTooltip['renderer'];
8
+ };
9
+ export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import isNil from 'lodash/isNil';
3
+ import { DefaultContent } from './DefaultContent';
4
+ export const ChartTooltipContent = (props) => {
5
+ const { hovered, xAxis, yAxis, renderer } = props;
6
+ if (!hovered) {
7
+ return null;
8
+ }
9
+ const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
10
+ return isNil(customTooltip) ? (React.createElement(DefaultContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
11
+ };
@@ -1,10 +1,9 @@
1
1
  import React from 'react';
2
- import type { PreparedAxis } from '../../hooks';
3
- import type { TooltipDataChunk } from '../../types';
2
+ import type { ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
4
3
  type Props = {
5
4
  hovered: TooltipDataChunk[];
6
- xAxis: PreparedAxis;
7
- yAxis: PreparedAxis;
5
+ xAxis?: ChartXAxis;
6
+ yAxis?: ChartYAxis;
8
7
  };
9
8
  export declare const DefaultContent: ({ hovered, xAxis, yAxis }: Props) => React.JSX.Element;
10
9
  export {};
@@ -5,8 +5,8 @@ import { formatNumber } from '../../libs';
5
5
  import { block, getDataCategoryValue, getWaterfallPointSubtotal } from '../../utils';
6
6
  const b = block('d3-tooltip');
7
7
  const DEFAULT_DATE_FORMAT = 'DD.MM.YY';
8
- const getRowData = (fieldName, axis, data) => {
9
- switch (axis.type) {
8
+ const getRowData = (fieldName, data, axis) => {
9
+ switch (axis === null || axis === void 0 ? void 0 : axis.type) {
10
10
  case 'category': {
11
11
  const categories = get(axis, 'categories', []);
12
12
  return getDataCategoryValue({ axisDirection: fieldName, categories, data });
@@ -25,17 +25,17 @@ const getRowData = (fieldName, axis, data) => {
25
25
  }
26
26
  }
27
27
  };
28
- const getXRowData = (xAxis, data) => getRowData('x', xAxis, data);
29
- const getYRowData = (yAxis, data) => getRowData('y', yAxis, data);
28
+ const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
29
+ const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
30
30
  const getMeasureValue = (data, xAxis, yAxis) => {
31
31
  var _a, _b;
32
32
  if (data.every((item) => ['pie', 'treemap', 'waterfall'].includes(item.series.type))) {
33
33
  return null;
34
34
  }
35
35
  if (data.some((item) => item.series.type === 'bar-y')) {
36
- return getYRowData(yAxis, (_a = data[0]) === null || _a === void 0 ? void 0 : _a.data);
36
+ return getYRowData((_a = data[0]) === null || _a === void 0 ? void 0 : _a.data, yAxis);
37
37
  }
38
- return getXRowData(xAxis, (_b = data[0]) === null || _b === void 0 ? void 0 : _b.data);
38
+ return getXRowData((_b = data[0]) === null || _b === void 0 ? void 0 : _b.data, xAxis);
39
39
  };
40
40
  export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
41
41
  const measureValue = getMeasureValue(hovered, xAxis, yAxis);
@@ -52,7 +52,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
52
52
  const value = (React.createElement(React.Fragment, null,
53
53
  series.name,
54
54
  ": ",
55
- getYRowData(yAxis, data)));
55
+ getYRowData(data, yAxis)));
56
56
  return (React.createElement("div", { key: id, className: b('content-row') },
57
57
  React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
58
58
  React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
@@ -63,12 +63,12 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
63
63
  return (React.createElement("div", { key: `${id}_${get(data, 'x')}` },
64
64
  !isTotal && (React.createElement(React.Fragment, null,
65
65
  React.createElement("div", { key: id, className: b('content-row') },
66
- React.createElement("b", null, getXRowData(xAxis, data))),
66
+ React.createElement("b", null, getXRowData(data, xAxis))),
67
67
  React.createElement("div", { className: b('content-row') },
68
68
  React.createElement("span", null,
69
69
  series.name,
70
70
  "\u00A0"),
71
- React.createElement("span", null, getYRowData(yAxis, data))))),
71
+ React.createElement("span", null, getYRowData(data, yAxis))))),
72
72
  React.createElement("div", { key: id, className: b('content-row') },
73
73
  isTotal ? 'Total' : 'Subtotal',
74
74
  ": ",
@@ -78,7 +78,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
78
78
  const value = (React.createElement(React.Fragment, null,
79
79
  series.name,
80
80
  ": ",
81
- getXRowData(xAxis, data)));
81
+ getXRowData(data, xAxis)));
82
82
  return (React.createElement("div", { key: id, className: b('content-row') },
83
83
  React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
84
84
  React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Popup, useVirtualElementRef } from '@gravity-ui/uikit';
3
- import isNil from 'lodash/isNil';
4
3
  import { useTooltip } from '../../hooks';
5
4
  import { block } from '../../utils';
6
- import { DefaultContent } from './DefaultContent';
5
+ import { ChartTooltipContent } from './ChartTooltipContent';
7
6
  import './styles.css';
8
7
  const b = block('d3-tooltip');
9
8
  export const Tooltip = (props) => {
@@ -13,17 +12,10 @@ export const Tooltip = (props) => {
13
12
  const left = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[0]) || 0) + containerRect.left;
14
13
  const top = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[1]) || 0) + containerRect.top;
15
14
  const anchorRef = useVirtualElementRef({ rect: { top, left } });
16
- const content = React.useMemo(() => {
17
- var _a;
18
- if (!hovered) {
19
- return null;
20
- }
21
- const customTooltip = (_a = tooltip.renderer) === null || _a === void 0 ? void 0 : _a.call(tooltip, { hovered });
22
- return isNil(customTooltip) ? (React.createElement(DefaultContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
23
- }, [hovered, tooltip, xAxis, yAxis]);
24
15
  React.useEffect(() => {
25
16
  window.dispatchEvent(new CustomEvent('scroll'));
26
17
  }, [left, top]);
27
18
  return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { className: b(), open: true, anchorRef: anchorRef, offset: [0, 20], placement: ['right', 'left', 'top', 'bottom'], modifiers: [{ name: 'preventOverflow', options: { padding: 10, altAxis: true } }] },
28
- React.createElement("div", { className: b('content') }, content))) : null;
19
+ React.createElement("div", { className: b('content') },
20
+ React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer })))) : null;
29
21
  };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ChartData } from '../types';
3
+ export * from './Tooltip/ChartTooltipContent';
3
4
  export type ChartRef = {
4
5
  reflow: () => void;
5
6
  };
@@ -15,4 +16,3 @@ export type ChartProps = {
15
16
  }) => void;
16
17
  };
17
18
  export declare const Chart: React.ForwardRefExoticComponent<ChartProps & React.RefAttributes<ChartRef>>;
18
- export {};
@@ -5,6 +5,7 @@ import { i18nFactory } from '../i18n';
5
5
  import { getUniqId } from '../utils';
6
6
  import { validateData } from '../validation';
7
7
  import { ChartInner } from './ChartInner';
8
+ export * from './Tooltip/ChartTooltipContent';
8
9
  export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
9
10
  const { data, lang, onResize } = props;
10
11
  const validatedData = React.useRef();
@@ -1,4 +1,5 @@
1
1
  export * from './defaults';
2
+ export * from './misc';
2
3
  export declare const SeriesType: {
3
4
  readonly Area: "area";
4
5
  readonly BarX: "bar-x";
@@ -1,4 +1,5 @@
1
1
  export * from './defaults';
2
+ export * from './misc';
2
3
  export const SeriesType = {
3
4
  Area: 'area',
4
5
  BarX: 'bar-x',
@@ -0,0 +1 @@
1
+ export declare const IS_TOUCH_ENABLED: boolean;
@@ -0,0 +1,7 @@
1
+ function isTouchEnabled() {
2
+ if (typeof window !== 'object') {
3
+ return false;
4
+ }
5
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
6
+ }
7
+ export const IS_TOUCH_ENABLED = isTouchEnabled();
@@ -1,19 +1,18 @@
1
1
  import type { MeaningfulAny } from '../misc';
2
+ import type { ChartTooltipRendererData } from './tooltip';
2
3
  export type ChartMargin = {
3
4
  top: number;
4
5
  right: number;
5
6
  bottom: number;
6
7
  left: number;
7
8
  };
8
- type ChartEventData = {
9
- point: MeaningfulAny;
10
- series: MeaningfulAny;
11
- };
12
9
  export type ChartOptions = {
13
10
  margin?: Partial<ChartMargin>;
14
11
  events?: {
15
- click?: (data: ChartEventData, event: PointerEvent) => void;
16
- pointermove?: (data: ChartEventData | undefined, event: PointerEvent) => void;
12
+ click?: (data: {
13
+ point: MeaningfulAny;
14
+ series: MeaningfulAny;
15
+ }, event: PointerEvent) => void;
16
+ pointermove?: (data: ChartTooltipRendererData | undefined, event: PointerEvent) => void;
17
17
  };
18
18
  };
19
- export {};
@@ -1,5 +1,6 @@
1
1
  import type { MeaningfulAny } from '../misc';
2
2
  import type { AreaSeries, AreaSeriesData } from './area';
3
+ import type { ChartXAxis, ChartYAxis } from './axis';
3
4
  import type { BarXSeries, BarXSeriesData } from './bar-x';
4
5
  import type { BarYSeries, BarYSeriesData } from './bar-y';
5
6
  import type { LineSeries, LineSeriesData } from './line';
@@ -58,10 +59,17 @@ export type TooltipDataChunkWaterfall<T = MeaningfulAny> = {
58
59
  export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkWaterfall<T>) & {
59
60
  closest?: boolean;
60
61
  };
62
+ export type ChartTooltipRendererData<T = MeaningfulAny> = {
63
+ hovered: TooltipDataChunk<T>[];
64
+ xAxis?: ChartXAxis;
65
+ yAxis?: ChartYAxis;
66
+ };
61
67
  export type ChartTooltip<T = MeaningfulAny> = {
62
68
  enabled?: boolean;
63
69
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
64
70
  renderer?: (args: {
65
71
  hovered: TooltipDataChunk<T>[];
72
+ xAxis?: ChartXAxis;
73
+ yAxis?: ChartYAxis;
66
74
  }) => React.ReactElement | null;
67
75
  };
@@ -1 +1,6 @@
1
+ export declare const EventType: {
2
+ CLICK_CHART: string;
3
+ HOVER_SHAPE: string;
4
+ POINTERMOVE_CHART: string;
5
+ };
1
6
  export declare const getD3Dispatcher: () => import("d3-dispatch").Dispatch<object>;
@@ -1,4 +1,9 @@
1
1
  import { dispatch } from 'd3';
2
+ export const EventType = {
3
+ CLICK_CHART: 'click-chart',
4
+ HOVER_SHAPE: 'hover-shape',
5
+ POINTERMOVE_CHART: 'pointermove-chart',
6
+ };
2
7
  export const getD3Dispatcher = () => {
3
- return dispatch('hover-shape', 'click-chart');
8
+ return dispatch(EventType.CLICK_CHART, EventType.HOVER_SHAPE, EventType.POINTERMOVE_CHART);
4
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",