@gravity-ui/charts 1.18.2 → 1.19.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 (102) hide show
  1. package/dist/cjs/components/AxisY/AxisY.js +7 -5
  2. package/dist/cjs/components/AxisY/prepare-axis-data.js +8 -5
  3. package/dist/cjs/components/AxisY/types.d.ts +1 -1
  4. package/dist/cjs/components/AxisY/utils.js +1 -1
  5. package/dist/cjs/components/ChartInner/index.js +20 -26
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +2 -2
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.js +38 -30
  8. package/dist/cjs/components/ChartInner/utils.d.ts +1 -0
  9. package/dist/cjs/components/ChartInner/utils.js +21 -0
  10. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  11. package/dist/cjs/components/Tooltip/ChartTooltipContent.js +3 -2
  12. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +1 -0
  13. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
  14. package/dist/cjs/constants/chart-types.d.ts +1 -0
  15. package/dist/cjs/constants/chart-types.js +1 -0
  16. package/dist/cjs/constants/defaults/series-options.js +8 -0
  17. package/dist/cjs/hooks/useAxisScales/index.js +47 -8
  18. package/dist/cjs/hooks/useChartOptions/tooltip.js +1 -1
  19. package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +1 -1
  20. package/dist/cjs/hooks/useChartOptions/x-axis.js +15 -4
  21. package/dist/cjs/hooks/useChartOptions/y-axis.js +15 -7
  22. package/dist/cjs/hooks/useSeries/prepare-heatmap.d.ts +11 -0
  23. package/dist/cjs/hooks/useSeries/prepare-heatmap.js +37 -0
  24. package/dist/cjs/hooks/useSeries/prepareSeries.js +9 -0
  25. package/dist/cjs/hooks/useSeries/types.d.ts +14 -2
  26. package/dist/cjs/hooks/useShapes/heatmap/index.d.ts +13 -0
  27. package/dist/cjs/hooks/useShapes/heatmap/index.js +74 -0
  28. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
  29. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +97 -0
  30. package/dist/cjs/hooks/useShapes/heatmap/types.d.ts +24 -0
  31. package/dist/cjs/hooks/useShapes/heatmap/types.js +1 -0
  32. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  33. package/dist/cjs/hooks/useShapes/index.js +15 -0
  34. package/dist/cjs/hooks/useShapes/styles.css +4 -0
  35. package/dist/cjs/hooks/useTooltip/index.js +11 -1
  36. package/dist/cjs/hooks/utils/bar-y.d.ts +0 -5
  37. package/dist/cjs/hooks/utils/bar-y.js +2 -29
  38. package/dist/cjs/hooks/utils/get-band-size.d.ts +5 -0
  39. package/dist/cjs/hooks/utils/get-band-size.js +29 -0
  40. package/dist/cjs/types/chart/axis.d.ts +3 -1
  41. package/dist/cjs/types/chart/heatmap.d.ts +47 -0
  42. package/dist/cjs/types/chart/heatmap.js +1 -0
  43. package/dist/cjs/types/chart/series.d.ts +19 -2
  44. package/dist/cjs/types/chart/tooltip.d.ts +7 -1
  45. package/dist/cjs/types/index.d.ts +1 -0
  46. package/dist/cjs/types/index.js +1 -0
  47. package/dist/cjs/utils/chart/color.js +3 -2
  48. package/dist/cjs/utils/chart/get-closest-data.js +18 -1
  49. package/dist/cjs/utils/chart/index.js +10 -13
  50. package/dist/cjs/utils/chart/series/waterfall.d.ts +1 -1
  51. package/dist/cjs/utils/chart/series/waterfall.js +3 -3
  52. package/dist/esm/components/AxisY/AxisY.js +7 -5
  53. package/dist/esm/components/AxisY/prepare-axis-data.js +8 -5
  54. package/dist/esm/components/AxisY/types.d.ts +1 -1
  55. package/dist/esm/components/AxisY/utils.js +1 -1
  56. package/dist/esm/components/ChartInner/index.js +20 -26
  57. package/dist/esm/components/ChartInner/useChartInnerProps.js +38 -30
  58. package/dist/esm/components/ChartInner/utils.d.ts +1 -0
  59. package/dist/esm/components/ChartInner/utils.js +21 -0
  60. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  61. package/dist/esm/components/Tooltip/ChartTooltipContent.js +3 -2
  62. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -0
  63. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
  64. package/dist/esm/constants/chart-types.d.ts +1 -0
  65. package/dist/esm/constants/chart-types.js +1 -0
  66. package/dist/esm/constants/defaults/series-options.js +8 -0
  67. package/dist/esm/hooks/useAxisScales/index.js +47 -8
  68. package/dist/esm/hooks/useChartOptions/tooltip.js +1 -1
  69. package/dist/esm/hooks/useChartOptions/x-axis.d.ts +1 -1
  70. package/dist/esm/hooks/useChartOptions/x-axis.js +15 -4
  71. package/dist/esm/hooks/useChartOptions/y-axis.js +15 -7
  72. package/dist/esm/hooks/useSeries/prepare-heatmap.d.ts +11 -0
  73. package/dist/esm/hooks/useSeries/prepare-heatmap.js +37 -0
  74. package/dist/esm/hooks/useSeries/prepareSeries.js +9 -0
  75. package/dist/esm/hooks/useSeries/types.d.ts +14 -2
  76. package/dist/esm/hooks/useShapes/heatmap/index.d.ts +13 -0
  77. package/dist/esm/hooks/useShapes/heatmap/index.js +74 -0
  78. package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
  79. package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +97 -0
  80. package/dist/esm/hooks/useShapes/heatmap/types.d.ts +24 -0
  81. package/dist/esm/hooks/useShapes/heatmap/types.js +1 -0
  82. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  83. package/dist/esm/hooks/useShapes/index.js +15 -0
  84. package/dist/esm/hooks/useShapes/styles.css +4 -0
  85. package/dist/esm/hooks/useTooltip/index.js +11 -1
  86. package/dist/esm/hooks/utils/bar-y.d.ts +0 -5
  87. package/dist/esm/hooks/utils/bar-y.js +2 -29
  88. package/dist/esm/hooks/utils/get-band-size.d.ts +5 -0
  89. package/dist/esm/hooks/utils/get-band-size.js +29 -0
  90. package/dist/esm/types/chart/axis.d.ts +3 -1
  91. package/dist/esm/types/chart/heatmap.d.ts +47 -0
  92. package/dist/esm/types/chart/heatmap.js +1 -0
  93. package/dist/esm/types/chart/series.d.ts +19 -2
  94. package/dist/esm/types/chart/tooltip.d.ts +7 -1
  95. package/dist/esm/types/index.d.ts +1 -0
  96. package/dist/esm/types/index.js +1 -0
  97. package/dist/esm/utils/chart/color.js +3 -2
  98. package/dist/esm/utils/chart/get-closest-data.js +18 -1
  99. package/dist/esm/utils/chart/index.js +10 -13
  100. package/dist/esm/utils/chart/series/waterfall.d.ts +1 -1
  101. package/dist/esm/utils/chart/series/waterfall.js +3 -3
  102. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import get from 'lodash/get';
2
2
  import { getTickValues } from '../../components/AxisY/utils';
3
- import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
4
- import { calculateNumericProperty, formatAxisTickLabel, getClosestPointsRange, getDefaultDateFormat, getDefaultMinYAxisValue, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getScaleTicks, getTextSizeFn, isAxisRelatedSeries, wrapText, } from '../../utils';
3
+ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, SeriesType, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
4
+ import { calculateNumericProperty, formatAxisTickLabel, getDefaultDateFormat, getDefaultMinYAxisValue, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, wrapText, } from '../../utils';
5
5
  import { createYScale } from '../useAxisScales';
6
6
  import { getAxisCategories, prepareAxisPlotLabel } from './utils';
7
7
  const getAxisLabelMaxWidth = async (args) => {
@@ -20,8 +20,7 @@ const getAxisLabelMaxWidth = async (args) => {
20
20
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
21
21
  const labelLineHeight = (await getTextSize('Tmp')).height;
22
22
  const tickValues = getTickValues({ axis, scale, labelLineHeight, series: seriesData });
23
- const ticks = getScaleTicks(scale);
24
- const tickStep = getClosestPointsRange(axis, ticks);
23
+ const tickStep = getMinSpaceBetween(tickValues, (d) => Number(d.value));
25
24
  if (axis.type === 'datetime' && !axis.labels.dateFormat) {
26
25
  axis.labels.dateFormat = getDefaultDateFormat(tickStep);
27
26
  }
@@ -38,6 +37,12 @@ const getAxisLabelMaxWidth = async (args) => {
38
37
  });
39
38
  return { height: size.maxHeight, width: size.maxWidth };
40
39
  };
40
+ function getMaxPaddingBySeries({ series }) {
41
+ if (series.some((s) => s.type === SeriesType.Heatmap)) {
42
+ return 0;
43
+ }
44
+ return 0.05;
45
+ }
41
46
  export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxis, }) => {
42
47
  const axisByPlot = [];
43
48
  const axisItems = yAxis || [{}];
@@ -72,6 +77,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
72
77
  })).slice(0, titleMaxRowsCount);
73
78
  const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
74
79
  const axisType = get(axisItem, 'type', DEFAULT_AXIS_TYPE);
80
+ const shouldHideGrid = axisItem.visible === false || seriesData.some((s) => s.type === SeriesType.Heatmap);
75
81
  const preparedAxis = {
76
82
  type: axisType,
77
83
  labels: {
@@ -104,10 +110,12 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
104
110
  },
105
111
  min: (_c = get(axisItem, 'min')) !== null && _c !== void 0 ? _c : getDefaultMinYAxisValue(seriesData),
106
112
  max: get(axisItem, 'max'),
107
- maxPadding: get(axisItem, 'maxPadding', 0.05),
113
+ maxPadding: get(axisItem, 'maxPadding', getMaxPaddingBySeries({ series: seriesData })),
108
114
  grid: {
109
- enabled: get(axisItem, 'grid.enabled', firstPlotAxis ||
110
- (!firstPlotAxis && !((_d = axisByPlot[plotIndex][0].visible) !== null && _d !== void 0 ? _d : true))),
115
+ enabled: shouldHideGrid
116
+ ? false
117
+ : get(axisItem, 'grid.enabled', firstPlotAxis ||
118
+ (!firstPlotAxis && !((_d = axisByPlot[plotIndex][0].visible) !== null && _d !== void 0 ? _d : true))),
111
119
  },
112
120
  ticks: {
113
121
  pixelInterval: ((_e = axisItem.ticks) === null || _e === void 0 ? void 0 : _e.interval)
@@ -0,0 +1,11 @@
1
+ import type { ScaleOrdinal } from 'd3';
2
+ import type { ChartSeriesOptions, HeatmapSeries } from '../../types';
3
+ import type { PreparedLegend, PreparedSeries } from './types';
4
+ type PrepareHeatmapSeriesArgs = {
5
+ colorScale: ScaleOrdinal<string, string>;
6
+ series: HeatmapSeries[];
7
+ legend: PreparedLegend;
8
+ seriesOptions?: ChartSeriesOptions;
9
+ };
10
+ export declare function prepareHeatmapSeries(args: PrepareHeatmapSeriesArgs): PreparedSeries[];
11
+ export {};
@@ -0,0 +1,37 @@
1
+ import get from 'lodash/get';
2
+ import { DEFAULT_DATALABELS_STYLE } from '../../constants';
3
+ import { getUniqId } from '../../utils';
4
+ import { DEFAULT_DATALABELS_PADDING } from './constants';
5
+ import { prepareLegendSymbol } from './utils';
6
+ export function prepareHeatmapSeries(args) {
7
+ const { colorScale, series: seriesList, seriesOptions, legend } = args;
8
+ return seriesList.map((series) => {
9
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
10
+ const name = series.name || '';
11
+ const color = series.color || colorScale(name);
12
+ return {
13
+ type: series.type,
14
+ color,
15
+ name,
16
+ id: getUniqId(),
17
+ visible: get(series, 'visible', true),
18
+ legend: {
19
+ enabled: get(series, 'legend.enabled', legend.enabled),
20
+ symbol: prepareLegendSymbol(series),
21
+ },
22
+ data: series.data,
23
+ dataLabels: {
24
+ enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
25
+ style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
26
+ padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
27
+ html: (_d = (_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.html) !== null && _d !== void 0 ? _d : false,
28
+ format: (_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.format,
29
+ },
30
+ cursor: get(series, 'cursor', null),
31
+ yAxis: get(series, 'yAxis', 0),
32
+ tooltip: series.tooltip,
33
+ borderColor: (_h = (_f = series.borderColor) !== null && _f !== void 0 ? _f : (_g = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.heatmap) === null || _g === void 0 ? void 0 : _g.borderColor) !== null && _h !== void 0 ? _h : 'var(--gcharts-shape-border-color)',
34
+ borderWidth: (_l = (_j = series.borderWidth) !== null && _j !== void 0 ? _j : (_k = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.heatmap) === null || _k === void 0 ? void 0 : _k.borderWidth) !== null && _l !== void 0 ? _l : 0,
35
+ };
36
+ }, []);
37
+ }
@@ -2,6 +2,7 @@ import { ChartError } from '../../libs';
2
2
  import { prepareArea } from './prepare-area';
3
3
  import { prepareBarXSeries } from './prepare-bar-x';
4
4
  import { prepareBarYSeries } from './prepare-bar-y';
5
+ import { prepareHeatmapSeries } from './prepare-heatmap';
5
6
  import { prepareLineSeries } from './prepare-line';
6
7
  import { preparePieSeries } from './prepare-pie';
7
8
  import { prepareRadarSeries } from './prepare-radar';
@@ -90,6 +91,14 @@ export async function prepareSeries(args) {
90
91
  colors,
91
92
  });
92
93
  }
94
+ case 'heatmap': {
95
+ return await prepareHeatmapSeries({
96
+ series: series,
97
+ legend,
98
+ colorScale,
99
+ seriesOptions,
100
+ });
101
+ }
93
102
  default: {
94
103
  throw new ChartError({
95
104
  message: `Series type "${type}" does not support data preparation for series that do not support the presence of axes`,
@@ -1,5 +1,5 @@
1
1
  import type { DashStyle, LayoutAlgorithm, LineCap, SeriesOptionsDefaults, SymbolType } from '../../constants';
2
- import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ChartSeries, ConnectorCurve, ConnectorShape, LineSeries, LineSeriesData, PathLegendSymbolOptions, PieSeries, PieSeriesData, RadarSeries, RadarSeriesCategory, RadarSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, ValueFormat, WaterfallSeries, WaterfallSeriesData } from '../../types';
2
+ import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ChartSeries, ConnectorCurve, ConnectorShape, HeatmapSeries, HeatmapSeriesData, LineSeries, LineSeriesData, PathLegendSymbolOptions, PieSeries, PieSeriesData, RadarSeries, RadarSeriesCategory, RadarSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, ValueFormat, WaterfallSeries, WaterfallSeriesData } from '../../types';
3
3
  export type RectLegendSymbol = {
4
4
  shape: 'rect';
5
5
  } & Required<RectLegendSymbolOptions>;
@@ -137,6 +137,18 @@ export type PreparedBarYSeries = {
137
137
  borderWidth: number;
138
138
  borderColor: string;
139
139
  } & BasePreparedSeries;
140
+ export type PreparedHeatmapSeries = {
141
+ type: HeatmapSeries['type'];
142
+ data: HeatmapSeriesData[];
143
+ dataLabels: {
144
+ enabled: boolean;
145
+ style: BaseTextStyle;
146
+ html: boolean;
147
+ format?: ValueFormat;
148
+ };
149
+ borderWidth: number;
150
+ borderColor: string;
151
+ } & BasePreparedSeries;
140
152
  export type PreparedPieSeries = {
141
153
  type: PieSeries['type'];
142
154
  data: PieSeriesData;
@@ -313,7 +325,7 @@ export type PreparedRadarSeries = {
313
325
  };
314
326
  };
315
327
  } & BasePreparedSeries;
316
- export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries | PreparedAreaSeries | PreparedTreemapSeries | PreparedWaterfallSeries | PreparedSankeySeries | PreparedRadarSeries;
328
+ export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries | PreparedAreaSeries | PreparedTreemapSeries | PreparedWaterfallSeries | PreparedSankeySeries | PreparedRadarSeries | PreparedHeatmapSeries;
317
329
  export type PreparedSeriesOptions = SeriesOptionsDefaults;
318
330
  export type StackedSeries = BarXSeries | AreaSeries | BarYSeries;
319
331
  export {};
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { PreparedSeriesOptions } from '../../useSeries/types';
4
+ import type { PreparedHeatmapData } from './types';
5
+ export { prepareHeatmapData } from './prepare-data';
6
+ export * from './types';
7
+ type Args = {
8
+ dispatcher: Dispatch<object>;
9
+ preparedData: PreparedHeatmapData;
10
+ seriesOptions: PreparedSeriesOptions;
11
+ htmlLayout: HTMLElement | null;
12
+ };
13
+ export declare const HeatmapSeriesShapes: (args: Args) => React.JSX.Element;
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import { color, select } from 'd3';
3
+ import { block } from '../../../utils';
4
+ import { HtmlLayer } from '../HtmlLayer';
5
+ export { prepareHeatmapData } from './prepare-data';
6
+ export * from './types';
7
+ const b = block('heatmap');
8
+ export const HeatmapSeriesShapes = (args) => {
9
+ const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
10
+ const hoveredDataRef = React.useRef(null);
11
+ const ref = React.useRef(null);
12
+ React.useEffect(() => {
13
+ var _a, _b;
14
+ if (!ref.current) {
15
+ return () => { };
16
+ }
17
+ const svgElement = select(ref.current);
18
+ const hoverOptions = (_b = (_a = seriesOptions.heatmap) === null || _a === void 0 ? void 0 : _a.states) === null || _b === void 0 ? void 0 : _b.hover;
19
+ svgElement.selectAll('*').remove();
20
+ // heatmap cells
21
+ const cellsSelection = svgElement
22
+ .selectAll('rect')
23
+ .data(preparedData.items)
24
+ .join('rect')
25
+ .attr('x', (d) => d.x)
26
+ .attr('y', (d) => d.y)
27
+ .attr('height', (d) => d.height)
28
+ .attr('width', (d) => d.width)
29
+ .attr('fill', (d) => d.color)
30
+ .attr('stroke', (d) => d.borderColor)
31
+ .attr('stroke-width', (d) => d.borderWidth);
32
+ // dataLabels
33
+ svgElement
34
+ .selectAll('text')
35
+ .data(preparedData.labels)
36
+ .join('text')
37
+ .text((d) => d.text)
38
+ .attr('class', b('label'))
39
+ .attr('x', (d) => d.x)
40
+ .attr('y', (d) => d.y)
41
+ .style('font-size', (d) => d.style.fontSize)
42
+ .style('font-weight', (d) => d.style.fontWeight || null)
43
+ .style('fill', (d) => d.style.fontColor || null);
44
+ function handleShapeHover(data) {
45
+ hoveredDataRef.current = data;
46
+ const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
47
+ if (hoverEnabled) {
48
+ const hovered = data === null || data === void 0 ? void 0 : data.reduce((acc, d) => {
49
+ acc.add(d.data);
50
+ return acc;
51
+ }, new Set());
52
+ cellsSelection.attr('fill', (d) => {
53
+ var _a;
54
+ const fillColor = d.color;
55
+ if (hovered === null || hovered === void 0 ? void 0 : hovered.has(d.data)) {
56
+ return (((_a = color(fillColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions.brightness).toString()) ||
57
+ fillColor);
58
+ }
59
+ return fillColor;
60
+ });
61
+ }
62
+ }
63
+ if (hoveredDataRef.current !== null) {
64
+ handleShapeHover(hoveredDataRef.current);
65
+ }
66
+ dispatcher.on('hover-shape.heatmap', handleShapeHover);
67
+ return () => {
68
+ dispatcher.on('hover-shape.heatmap', null);
69
+ };
70
+ }, [dispatcher, preparedData, seriesOptions]);
71
+ return (React.createElement(React.Fragment, null,
72
+ React.createElement("g", { ref: ref, className: b() }),
73
+ React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
74
+ };
@@ -0,0 +1,13 @@
1
+ import type { ChartScale } from '../../../hooks/useAxisScales';
2
+ import type { PreparedAxis } from '../../../hooks/useChartOptions/types';
3
+ import type { PreparedHeatmapSeries } from '../../useSeries/types';
4
+ import type { PreparedHeatmapData } from './types';
5
+ type PrepareHeatmapDataArgs = {
6
+ series: PreparedHeatmapSeries;
7
+ xAxis: PreparedAxis;
8
+ yAxis: PreparedAxis;
9
+ xScale: ChartScale;
10
+ yScale: ChartScale;
11
+ };
12
+ export declare function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale, }: PrepareHeatmapDataArgs): Promise<PreparedHeatmapData>;
13
+ export {};
@@ -0,0 +1,97 @@
1
+ import { getBandSize } from '../../../hooks/utils/get-band-size';
2
+ import { getDomainDataXBySeries, getDomainDataYBySeries, getFormattedValue, getLabelsSize, getTextSizeFn, getTextWithElipsis, isBandScale, } from '../../../utils';
3
+ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale, }) {
4
+ var _a, _b, _c, _d, _e;
5
+ const yDomainData = getDomainDataYBySeries([series]);
6
+ const bandHeight = getBandSize({ domain: yDomainData, scale: yScale });
7
+ const yAxisCategories = (_a = yAxis.categories) !== null && _a !== void 0 ? _a : [];
8
+ const xDomainData = getDomainDataXBySeries([series]);
9
+ const bandWidth = getBandSize({ domain: xDomainData, scale: xScale });
10
+ const xAxisCategories = (_b = xAxis.categories) !== null && _b !== void 0 ? _b : [];
11
+ const heatmapItems = series.data.map((d) => {
12
+ var _a, _b, _c;
13
+ let x = 0;
14
+ if (isBandScale(xScale)) {
15
+ x = (_a = xScale(xAxisCategories[d.x])) !== null && _a !== void 0 ? _a : 0;
16
+ }
17
+ else {
18
+ const scale = xScale;
19
+ x = scale(d.x) - bandWidth / 2;
20
+ }
21
+ let y = 0;
22
+ if (isBandScale(yScale)) {
23
+ y = (_b = yScale(yAxisCategories[d.y])) !== null && _b !== void 0 ? _b : 0;
24
+ }
25
+ else {
26
+ const scale = yScale;
27
+ y = scale(d.y) - bandHeight / 2;
28
+ }
29
+ const item = {
30
+ x,
31
+ y,
32
+ color: (_c = d.color) !== null && _c !== void 0 ? _c : series.color,
33
+ width: bandWidth,
34
+ height: bandHeight,
35
+ borderColor: series.borderColor,
36
+ borderWidth: series.borderWidth,
37
+ data: d,
38
+ };
39
+ return item;
40
+ });
41
+ const svgDataLabels = [];
42
+ const htmlDataLabels = [];
43
+ if (series.dataLabels.enabled) {
44
+ if (series.dataLabels.html) {
45
+ for (let i = 0; i < heatmapItems.length; i++) {
46
+ const item = heatmapItems[i];
47
+ const labelContent = (_c = item.data.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: item.data.value, format: series.dataLabels.format });
48
+ if (labelContent) {
49
+ const dataLabelsStyle = Object.assign(Object.assign({}, series.dataLabels.style), { maxWidth: `${item.width}px`, maxHeight: `${item.height}px`, overflow: 'hidden' });
50
+ const { maxHeight: height, maxWidth: width } = (_d = (await getLabelsSize({
51
+ labels: [labelContent],
52
+ style: dataLabelsStyle,
53
+ html: true,
54
+ }))) !== null && _d !== void 0 ? _d : {};
55
+ const size = { width, height };
56
+ htmlDataLabels.push({
57
+ x: item.x + item.width / 2 - size.width / 2,
58
+ y: item.y + item.height / 2 - size.height / 2,
59
+ content: labelContent,
60
+ style: dataLabelsStyle,
61
+ size,
62
+ });
63
+ }
64
+ }
65
+ }
66
+ else {
67
+ const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
68
+ for (let i = 0; i < heatmapItems.length; i++) {
69
+ const item = heatmapItems[i];
70
+ const labelContent = (_e = item.data.label) !== null && _e !== void 0 ? _e : getFormattedValue({ value: item.data.value, format: series.dataLabels.format });
71
+ if (labelContent) {
72
+ const size = await getTextSize(labelContent);
73
+ const text = await getTextWithElipsis({
74
+ text: labelContent,
75
+ getTextWidth: (s) => getTextSize(s).then((value) => value.width),
76
+ maxWidth: item.width,
77
+ });
78
+ if (text) {
79
+ svgDataLabels.push({
80
+ x: item.x + item.width / 2 - size.width / 2,
81
+ y: item.y + item.height / 2 - size.height / 2,
82
+ text,
83
+ style: series.dataLabels.style,
84
+ });
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ const preparedData = {
91
+ series: series,
92
+ htmlElements: htmlDataLabels,
93
+ items: heatmapItems,
94
+ labels: svgDataLabels,
95
+ };
96
+ return preparedData;
97
+ }
@@ -0,0 +1,24 @@
1
+ import type { BaseTextStyle, HeatmapSeriesData, HtmlItem } from '../../../types';
2
+ import type { PreparedHeatmapSeries } from '../../useSeries/types';
3
+ export type HeatmapItem = {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ color: string;
9
+ borderColor: string | null;
10
+ borderWidth: number | null;
11
+ data: HeatmapSeriesData;
12
+ };
13
+ export type HeatmapLabel = {
14
+ x: number;
15
+ y: number;
16
+ text: string;
17
+ style: BaseTextStyle;
18
+ };
19
+ export type PreparedHeatmapData = {
20
+ series: PreparedHeatmapSeries;
21
+ items: HeatmapItem[];
22
+ htmlElements: HtmlItem[];
23
+ labels: HeatmapLabel[];
24
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -6,6 +6,7 @@ import type { PreparedAxis } from '../useChartOptions/types';
6
6
  import type { PreparedAreaData } from './area/types';
7
7
  import type { PreparedBarXData } from './bar-x';
8
8
  import type { PreparedBarYData } from './bar-y/types';
9
+ import type { PreparedHeatmapData } from './heatmap';
9
10
  import type { PreparedLineData } from './line/types';
10
11
  import type { PreparedPieData } from './pie/types';
11
12
  import type { PreparedRadarData } from './radar/types';
@@ -15,7 +16,7 @@ export type { PreparedBarXData } from './bar-x';
15
16
  export type { PreparedScatterData } from './scatter/types';
16
17
  import type { PreparedWaterfallData } from './waterfall';
17
18
  import './styles.css';
18
- export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData;
19
+ export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData;
19
20
  type Args = {
20
21
  boundsWidth: number;
21
22
  boundsHeight: number;
@@ -6,6 +6,7 @@ import { AreaSeriesShapes } from './area';
6
6
  import { prepareAreaData } from './area/prepare-data';
7
7
  import { BarXSeriesShapes, prepareBarXData } from './bar-x';
8
8
  import { BarYSeriesShapes, prepareBarYData } from './bar-y';
9
+ import { HeatmapSeriesShapes, prepareHeatmapData } from './heatmap';
9
10
  import { LineSeriesShapes } from './line';
10
11
  import { prepareLineData } from './line/prepare-data';
11
12
  import { PieSeriesShapes } from './pie';
@@ -174,6 +175,20 @@ export const useShapes = (args) => {
174
175
  shapesData.push(...preparedData);
175
176
  break;
176
177
  }
178
+ case 'heatmap': {
179
+ if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale[0])) {
180
+ const preparedData = await prepareHeatmapData({
181
+ series: chartSeries[0],
182
+ xAxis,
183
+ xScale,
184
+ yAxis: yAxis[0],
185
+ yScale: yScale[0],
186
+ });
187
+ shapes.push(React.createElement(HeatmapSeriesShapes, { key: "heatmap", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout }));
188
+ shapesData.push(preparedData);
189
+ }
190
+ break;
191
+ }
177
192
  default: {
178
193
  throw new ChartError({
179
194
  message: `The display method is not defined for a series with type "${seriesType}"`,
@@ -36,4 +36,8 @@
36
36
 
37
37
  .gcharts-waterfall__connector {
38
38
  stroke: var(--g-color-line-generic-active);
39
+ }
40
+
41
+ .gcharts-heatmap__label {
42
+ dominant-baseline: text-before-edge;
39
43
  }
@@ -1,10 +1,20 @@
1
1
  import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
2
3
  export const useTooltip = ({ dispatcher, tooltip }) => {
3
4
  const [{ hovered, pointerPosition }, setTooltipState] = React.useState({});
5
+ const prevHovered = React.useRef(hovered);
4
6
  React.useEffect(() => {
5
7
  if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) {
6
8
  dispatcher.on('hover-shape.tooltip', (nextHovered, nextPointerPosition) => {
7
- setTooltipState({ hovered: nextHovered, pointerPosition: nextPointerPosition });
9
+ const isHoveredChanged = !isEqual(prevHovered.current, nextHovered);
10
+ const newTooltipState = {
11
+ pointerPosition: nextPointerPosition,
12
+ hovered: isHoveredChanged ? nextHovered : prevHovered.current,
13
+ };
14
+ if (isHoveredChanged) {
15
+ prevHovered.current = nextHovered;
16
+ }
17
+ setTooltipState(newTooltipState);
8
18
  });
9
19
  }
10
20
  return () => {
@@ -1,4 +1,3 @@
1
- import type { AxisDomain, AxisScale } from 'd3';
2
1
  import type { BarYSeries, BarYSeriesData } from '../../types';
3
2
  import type { ChartScale } from '../useAxisScales';
4
3
  import type { PreparedAxis } from '../useChartOptions/types';
@@ -7,10 +6,6 @@ export declare function groupBarYDataByYValue<T extends BarYSeries | PreparedBar
7
6
  data: BarYSeriesData;
8
7
  series: T;
9
8
  }[]>>;
10
- export declare function getBandSize({ domain, scale, }: {
11
- domain: AxisDomain[];
12
- scale: AxisScale<AxisDomain> | undefined;
13
- }): number;
14
9
  export declare function getBarYLayout(args: {
15
10
  plotHeight: number;
16
11
  seriesOptions: PreparedSeriesOptions;
@@ -1,8 +1,9 @@
1
1
  import { max } from 'd3';
2
2
  import get from 'lodash/get';
3
- import { getDataCategoryValue, isBandScale } from '../../utils';
3
+ import { getDataCategoryValue } from '../../utils';
4
4
  import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
5
5
  import { getSeriesStackId } from '../useSeries/utils';
6
+ import { getBandSize } from './get-band-size';
6
7
  export function groupBarYDataByYValue(series, yAxis) {
7
8
  const data = {};
8
9
  series.forEach((s) => {
@@ -27,34 +28,6 @@ export function groupBarYDataByYValue(series, yAxis) {
27
28
  });
28
29
  return data;
29
30
  }
30
- export function getBandSize({ domain, scale, }) {
31
- if (!scale || !domain.length) {
32
- return 0;
33
- }
34
- if (isBandScale(scale)) {
35
- return scale.bandwidth();
36
- }
37
- const range = scale.range();
38
- const plotHeight = Math.abs(range[0] - range[1]);
39
- if (domain.length === 1) {
40
- return plotHeight;
41
- }
42
- // for the numeric or datetime axes, you first need to count domain.length + 1,
43
- // since the extreme points are located not in the center of the bar, but along the edges of the axes
44
- let bandWidth = plotHeight / (domain.length - 1);
45
- domain.forEach((current, index) => {
46
- if (index > 0) {
47
- const prev = domain[index - 1];
48
- const prevY = scale(prev);
49
- const currentY = scale(current);
50
- if (typeof prevY === 'number' && typeof currentY === 'number') {
51
- const distance = Math.abs(prevY - currentY);
52
- bandWidth = Math.min(bandWidth, distance);
53
- }
54
- }
55
- });
56
- return bandWidth;
57
- }
58
31
  export function getBarYLayout(args) {
59
32
  const { groupedData, seriesOptions, scale } = args;
60
33
  const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
@@ -0,0 +1,5 @@
1
+ import type { AxisDomain, AxisScale } from 'd3';
2
+ export declare function getBandSize({ domain, scale, }: {
3
+ domain: AxisDomain[];
4
+ scale: AxisScale<AxisDomain> | undefined;
5
+ }): number;
@@ -0,0 +1,29 @@
1
+ import { isBandScale } from '../../utils';
2
+ export function getBandSize({ domain, scale, }) {
3
+ if (!scale || !domain.length) {
4
+ return 0;
5
+ }
6
+ if (isBandScale(scale)) {
7
+ return scale.bandwidth();
8
+ }
9
+ const range = scale.range();
10
+ const plotHeight = Math.abs(range[0] - range[1]);
11
+ if (domain.length === 1) {
12
+ return plotHeight;
13
+ }
14
+ // for the numeric or datetime axes, you first need to count domain.length + 1,
15
+ // since the extreme points are located not in the center of the bar, but along the edges of the axes
16
+ let bandWidth = plotHeight / (domain.length - 1);
17
+ domain.forEach((current, index) => {
18
+ if (index > 0) {
19
+ const prev = domain[index - 1];
20
+ const prevY = scale(prev);
21
+ const currentY = scale(current);
22
+ if (typeof prevY === 'number' && typeof currentY === 'number') {
23
+ const distance = Math.abs(prevY - currentY);
24
+ bandWidth = Math.min(bandWidth, distance);
25
+ }
26
+ }
27
+ });
28
+ return bandWidth;
29
+ }
@@ -69,7 +69,9 @@ export interface ChartAxis {
69
69
  min?: number;
70
70
  /** The maximum value of the axis. If undefined the max value is automatically calculate. */
71
71
  max?: number;
72
- /** The grid lines settings. */
72
+ /** The grid lines settings.
73
+ * Unavailable for some visualizations, such as a heatmap.
74
+ */
73
75
  grid?: {
74
76
  /** Enable or disable the grid lines.
75
77
  * @default true
@@ -0,0 +1,47 @@
1
+ import type { SeriesType } from '../../constants';
2
+ import type { MeaningfulAny } from '../misc';
3
+ import type { BaseSeries, BaseSeriesData } from './base';
4
+ import type { ChartLegend, RectLegendSymbolOptions } from './legend';
5
+ export interface HeatmapSeriesData<T = MeaningfulAny> extends BaseSeriesData<T> {
6
+ /**
7
+ * The `x` value of the heatmap cell. Depending on the context, it may represents:
8
+ * - numeric value (for `linear` x axis)
9
+ * - timestamp value (for `datetime` x axis)
10
+ * - x axis category value (for `category` x axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `xAxis.categories`
11
+ */
12
+ x?: number;
13
+ /**
14
+ * The `y` value of the heatmap cell. Depending on the context, it may represents:
15
+ * - numeric value (for `linear` y axis)
16
+ * - timestamp value (for `datetime` y axis)
17
+ * - y axis category value (for `category` y axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `yAxis[0].categories`
18
+ */
19
+ y?: string | number;
20
+ /** Value of the heatmap cell */
21
+ value?: number;
22
+ /** Data label value of the heatmap cell. If not specified, the value is used. */
23
+ label?: string;
24
+ }
25
+ export interface HeatmapSeries<T = MeaningfulAny> extends BaseSeries {
26
+ type: typeof SeriesType.Heatmap;
27
+ data: HeatmapSeriesData<T>[];
28
+ /** The name of the series (used in legend, tooltip etc) */
29
+ name: string;
30
+ /** The main color of the series (hex, rgba) */
31
+ color?: string;
32
+ dataLabels?: BaseSeries['dataLabels'];
33
+ /** Individual series legend options. Has higher priority than legend options in widget data */
34
+ legend?: ChartLegend & {
35
+ symbol?: RectLegendSymbolOptions;
36
+ };
37
+ /**
38
+ * The width of the border surrounding each cell.
39
+ *
40
+ * @default 0
41
+ */
42
+ borderWidth?: number;
43
+ /**
44
+ * The color of the border surrounding each cell.
45
+ */
46
+ borderColor?: string;
47
+ }
@@ -0,0 +1 @@
1
+ export {};