@gravity-ui/charts 1.18.2 → 1.20.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 (108) 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 +57 -31
  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/i18n/keysets/en.json +2 -1
  41. package/dist/cjs/i18n/keysets/ru.json +2 -1
  42. package/dist/cjs/types/chart/axis.d.ts +3 -1
  43. package/dist/cjs/types/chart/heatmap.d.ts +47 -0
  44. package/dist/cjs/types/chart/heatmap.js +1 -0
  45. package/dist/cjs/types/chart/series.d.ts +19 -2
  46. package/dist/cjs/types/chart/tooltip.d.ts +7 -1
  47. package/dist/cjs/types/index.d.ts +1 -0
  48. package/dist/cjs/types/index.js +1 -0
  49. package/dist/cjs/utils/chart/color.js +3 -2
  50. package/dist/cjs/utils/chart/get-closest-data.js +18 -1
  51. package/dist/cjs/utils/chart/index.js +10 -13
  52. package/dist/cjs/utils/chart/series/waterfall.d.ts +1 -1
  53. package/dist/cjs/utils/chart/series/waterfall.js +3 -3
  54. package/dist/cjs/validation/validate-axes.js +31 -1
  55. package/dist/esm/components/AxisY/AxisY.js +7 -5
  56. package/dist/esm/components/AxisY/prepare-axis-data.js +8 -5
  57. package/dist/esm/components/AxisY/types.d.ts +1 -1
  58. package/dist/esm/components/AxisY/utils.js +1 -1
  59. package/dist/esm/components/ChartInner/index.js +20 -26
  60. package/dist/esm/components/ChartInner/useChartInnerProps.js +57 -31
  61. package/dist/esm/components/ChartInner/utils.d.ts +1 -0
  62. package/dist/esm/components/ChartInner/utils.js +21 -0
  63. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  64. package/dist/esm/components/Tooltip/ChartTooltipContent.js +3 -2
  65. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -0
  66. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
  67. package/dist/esm/constants/chart-types.d.ts +1 -0
  68. package/dist/esm/constants/chart-types.js +1 -0
  69. package/dist/esm/constants/defaults/series-options.js +8 -0
  70. package/dist/esm/hooks/useAxisScales/index.js +47 -8
  71. package/dist/esm/hooks/useChartOptions/tooltip.js +1 -1
  72. package/dist/esm/hooks/useChartOptions/x-axis.d.ts +1 -1
  73. package/dist/esm/hooks/useChartOptions/x-axis.js +15 -4
  74. package/dist/esm/hooks/useChartOptions/y-axis.js +15 -7
  75. package/dist/esm/hooks/useSeries/prepare-heatmap.d.ts +11 -0
  76. package/dist/esm/hooks/useSeries/prepare-heatmap.js +37 -0
  77. package/dist/esm/hooks/useSeries/prepareSeries.js +9 -0
  78. package/dist/esm/hooks/useSeries/types.d.ts +14 -2
  79. package/dist/esm/hooks/useShapes/heatmap/index.d.ts +13 -0
  80. package/dist/esm/hooks/useShapes/heatmap/index.js +74 -0
  81. package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
  82. package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +97 -0
  83. package/dist/esm/hooks/useShapes/heatmap/types.d.ts +24 -0
  84. package/dist/esm/hooks/useShapes/heatmap/types.js +1 -0
  85. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  86. package/dist/esm/hooks/useShapes/index.js +15 -0
  87. package/dist/esm/hooks/useShapes/styles.css +4 -0
  88. package/dist/esm/hooks/useTooltip/index.js +11 -1
  89. package/dist/esm/hooks/utils/bar-y.d.ts +0 -5
  90. package/dist/esm/hooks/utils/bar-y.js +2 -29
  91. package/dist/esm/hooks/utils/get-band-size.d.ts +5 -0
  92. package/dist/esm/hooks/utils/get-band-size.js +29 -0
  93. package/dist/esm/i18n/keysets/en.json +2 -1
  94. package/dist/esm/i18n/keysets/ru.json +2 -1
  95. package/dist/esm/types/chart/axis.d.ts +3 -1
  96. package/dist/esm/types/chart/heatmap.d.ts +47 -0
  97. package/dist/esm/types/chart/heatmap.js +1 -0
  98. package/dist/esm/types/chart/series.d.ts +19 -2
  99. package/dist/esm/types/chart/tooltip.d.ts +7 -1
  100. package/dist/esm/types/index.d.ts +1 -0
  101. package/dist/esm/types/index.js +1 -0
  102. package/dist/esm/utils/chart/color.js +3 -2
  103. package/dist/esm/utils/chart/get-closest-data.js +18 -1
  104. package/dist/esm/utils/chart/index.js +10 -13
  105. package/dist/esm/utils/chart/series/waterfall.d.ts +1 -1
  106. package/dist/esm/utils/chart/series/waterfall.js +3 -3
  107. package/dist/esm/validation/validate-axes.js +31 -1
  108. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
- import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
- import { calculateCos, calculateNumericProperty, formatAxisTickLabel, getAxisItems, getClosestPointsRange, getDefaultDateFormat, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, hasOverlappingLabels, wrapText, } from '../../utils';
2
+ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, SeriesType, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
+ import { calculateCos, calculateNumericProperty, formatAxisTickLabel, getAxisItems, getClosestPointsRange, getDefaultDateFormat, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, hasOverlappingLabels, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createXScale } from '../useAxisScales';
5
5
  import { getAxisCategories, prepareAxisPlotLabel } from './utils';
6
6
  async function setLabelSettings({ axis, seriesData, seriesOptions, width, autoRotation = true, }) {
@@ -49,8 +49,18 @@ async function setLabelSettings({ axis, seriesData, seriesOptions, width, autoRo
49
49
  axis.labels.height = Math.min(maxHeight, labelsHeight);
50
50
  axis.labels.rotation = rotation;
51
51
  }
52
+ function getMaxPaddingBySeries({ series }) {
53
+ if (series.some((s) => s.type === SeriesType.Heatmap)) {
54
+ return 0;
55
+ }
56
+ return 0.01;
57
+ }
52
58
  export const getPreparedXAxis = async ({ xAxis, seriesData, seriesOptions, width, }) => {
53
59
  var _a, _b, _c, _d, _e;
60
+ const hasAxisRelatedSeries = seriesData.some(isAxisRelatedSeries);
61
+ if (!hasAxisRelatedSeries) {
62
+ return Promise.resolve(null);
63
+ }
54
64
  const titleText = get(xAxis, 'title.text', '');
55
65
  const titleStyle = Object.assign(Object.assign({}, xAxisTitleDefaults.style), get(xAxis, 'title.style'));
56
66
  const titleMaxRowsCount = get(xAxis, 'title.maxRowCount', xAxisTitleDefaults.maxRowCount);
@@ -70,6 +80,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, seriesOptions, width
70
80
  const labelsLineHeight = labelsHtml
71
81
  ? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
72
82
  : getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
83
+ const shouldHideGrid = seriesData.some((s) => s.type === SeriesType.Heatmap);
73
84
  const preparedXAxis = {
74
85
  type: get(xAxis, 'type', 'linear'),
75
86
  labels: {
@@ -100,9 +111,9 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, seriesOptions, width
100
111
  },
101
112
  min: get(xAxis, 'min'),
102
113
  max: get(xAxis, 'max'),
103
- maxPadding: get(xAxis, 'maxPadding', 0.01),
114
+ maxPadding: get(xAxis, 'maxPadding', getMaxPaddingBySeries({ series: seriesData })),
104
115
  grid: {
105
- enabled: get(xAxis, 'grid.enabled', true),
116
+ enabled: shouldHideGrid ? false : get(xAxis, 'grid.enabled', true),
106
117
  },
107
118
  ticks: {
108
119
  pixelInterval: ((_c = xAxis === null || xAxis === void 0 ? void 0 : xAxis.ticks) === null || _c === void 0 ? void 0 : _c.interval)
@@ -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
+ }
@@ -18,7 +18,8 @@
18
18
  "label_invalid-tooltip-totals-aggregation-type-str": "It seems you are trying to use inappropriate value for built-in \"tooltip.totals.aggregation\". Available values: [{{values}}].",
19
19
  "label_invalid-axis-type": "It seems you are trying to use inappropriate type for \"{{key}}\" axis. Available types: [{{values}}].",
20
20
  "label_invalid-axis-labels-html-type": "It seems you are trying to use inappropriate type for \"labels.html\" property. Only boolean is allowed.",
21
- "label_invalid-axis-labels-html-not-supported-axis-type": "It seems you are trying to use \"labels.html\" property for an axis with an unsupported type. This property is supported only for \"category\" axis."
21
+ "label_invalid-axis-labels-html-not-supported-axis-type": "It seems you are trying to use \"labels.html\" property for an axis with an unsupported type. This property is supported only for \"category\" axis.",
22
+ "label_duplicate-axis-categories": "It seems you have duplicate value \"{{duplicate}}\" found in {{key}}[{{axisIndex}}]."
22
23
  },
23
24
  "tooltip": {
24
25
  "label_totals_sum": "Sum",
@@ -18,7 +18,8 @@
18
18
  "label_invalid-tooltip-totals-aggregation-type-str": "Похоже, что вы пытаетесь использовать некорректное значение для встроенной агрегации \"tooltip.totals.aggregation\". Доступные значения: [{{values}}].",
19
19
  "label_invalid-axis-type": "Похоже, что вы пытаетесь использовать некорректный тип для оси \"{{key}}\". Доступные типы: [{{values}}].",
20
20
  "label_invalid-axis-labels-html-type": "Похоже, что вы пытаетесь использовать некорректный тип для свойства \"labels.html\". Допускается только использование булевых значений.",
21
- "label_invalid-axis-labels-html-not-supported-axis-type": "Похоже, что вы пытаетесь использовать свойство \"labels.html\" для оси с неподдерживаемым типом. Это свойство поддерживается только для оси типа \"category\"."
21
+ "label_invalid-axis-labels-html-not-supported-axis-type": "Похоже, что вы пытаетесь использовать свойство \"labels.html\" для оси с неподдерживаемым типом. Это свойство поддерживается только для оси типа \"category\".",
22
+ "label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}]."
22
23
  },
23
24
  "tooltip": {
24
25
  "label_totals_sum": "Сумма",