@gravity-ui/charts 1.18.1 → 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 (108) hide show
  1. package/dist/cjs/components/AxisY/AxisY.js +7 -5
  2. package/dist/cjs/components/AxisY/prepare-axis-data.js +9 -6
  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/Legend/index.js +1 -1
  11. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  12. package/dist/cjs/components/Tooltip/ChartTooltipContent.js +3 -2
  13. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +1 -0
  14. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
  15. package/dist/cjs/constants/chart-types.d.ts +1 -0
  16. package/dist/cjs/constants/chart-types.js +1 -0
  17. package/dist/cjs/constants/defaults/series-options.js +8 -0
  18. package/dist/cjs/hooks/useAxisScales/index.js +47 -8
  19. package/dist/cjs/hooks/useChartOptions/tooltip.js +1 -1
  20. package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +1 -1
  21. package/dist/cjs/hooks/useChartOptions/x-axis.js +15 -4
  22. package/dist/cjs/hooks/useChartOptions/y-axis.js +15 -7
  23. package/dist/cjs/hooks/useSeries/prepare-heatmap.d.ts +11 -0
  24. package/dist/cjs/hooks/useSeries/prepare-heatmap.js +37 -0
  25. package/dist/cjs/hooks/useSeries/prepareSeries.js +9 -0
  26. package/dist/cjs/hooks/useSeries/types.d.ts +14 -2
  27. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +7 -4
  28. package/dist/cjs/hooks/useShapes/heatmap/index.d.ts +13 -0
  29. package/dist/cjs/hooks/useShapes/heatmap/index.js +74 -0
  30. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
  31. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +97 -0
  32. package/dist/cjs/hooks/useShapes/heatmap/types.d.ts +24 -0
  33. package/dist/cjs/hooks/useShapes/heatmap/types.js +1 -0
  34. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  35. package/dist/cjs/hooks/useShapes/index.js +15 -0
  36. package/dist/cjs/hooks/useShapes/styles.css +4 -0
  37. package/dist/cjs/hooks/useTooltip/index.js +11 -1
  38. package/dist/cjs/hooks/utils/bar-y.d.ts +0 -5
  39. package/dist/cjs/hooks/utils/bar-y.js +2 -29
  40. package/dist/cjs/hooks/utils/get-band-size.d.ts +5 -0
  41. package/dist/cjs/hooks/utils/get-band-size.js +29 -0
  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/index.js +4 -1
  55. package/dist/esm/components/AxisY/AxisY.js +7 -5
  56. package/dist/esm/components/AxisY/prepare-axis-data.js +9 -6
  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 +38 -30
  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/Legend/index.js +1 -1
  64. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  65. package/dist/esm/components/Tooltip/ChartTooltipContent.js +3 -2
  66. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -0
  67. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
  68. package/dist/esm/constants/chart-types.d.ts +1 -0
  69. package/dist/esm/constants/chart-types.js +1 -0
  70. package/dist/esm/constants/defaults/series-options.js +8 -0
  71. package/dist/esm/hooks/useAxisScales/index.js +47 -8
  72. package/dist/esm/hooks/useChartOptions/tooltip.js +1 -1
  73. package/dist/esm/hooks/useChartOptions/x-axis.d.ts +1 -1
  74. package/dist/esm/hooks/useChartOptions/x-axis.js +15 -4
  75. package/dist/esm/hooks/useChartOptions/y-axis.js +15 -7
  76. package/dist/esm/hooks/useSeries/prepare-heatmap.d.ts +11 -0
  77. package/dist/esm/hooks/useSeries/prepare-heatmap.js +37 -0
  78. package/dist/esm/hooks/useSeries/prepareSeries.js +9 -0
  79. package/dist/esm/hooks/useSeries/types.d.ts +14 -2
  80. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +7 -4
  81. package/dist/esm/hooks/useShapes/heatmap/index.d.ts +13 -0
  82. package/dist/esm/hooks/useShapes/heatmap/index.js +74 -0
  83. package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
  84. package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +97 -0
  85. package/dist/esm/hooks/useShapes/heatmap/types.d.ts +24 -0
  86. package/dist/esm/hooks/useShapes/heatmap/types.js +1 -0
  87. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  88. package/dist/esm/hooks/useShapes/index.js +15 -0
  89. package/dist/esm/hooks/useShapes/styles.css +4 -0
  90. package/dist/esm/hooks/useTooltip/index.js +11 -1
  91. package/dist/esm/hooks/utils/bar-y.d.ts +0 -5
  92. package/dist/esm/hooks/utils/bar-y.js +2 -29
  93. package/dist/esm/hooks/utils/get-band-size.d.ts +5 -0
  94. package/dist/esm/hooks/utils/get-band-size.js +29 -0
  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/index.js +4 -1
  108. package/package.json +1 -1
@@ -43,11 +43,13 @@ export const AxisY = (props) => {
43
43
  .attr('y', (d) => d.y)
44
44
  .attr('text-anchor', 'start');
45
45
  }
46
- svgElement
47
- .append('path')
48
- .attr('class', b('domain'))
49
- .attr('d', lineGenerator([preparedAxisData.domain.start, preparedAxisData.domain.end]))
50
- .style('stroke', preparedAxisData.domain.lineColor);
46
+ if (preparedAxisData.domain) {
47
+ svgElement
48
+ .append('path')
49
+ .attr('class', b('domain'))
50
+ .attr('d', lineGenerator([preparedAxisData.domain.start, preparedAxisData.domain.end]))
51
+ .style('stroke', preparedAxisData.domain.lineColor);
52
+ }
51
53
  const tickClassName = b('tick');
52
54
  const ticks = svgElement
53
55
  .selectAll(`.${tickClassName}`)
@@ -86,11 +86,14 @@ export async function prepareAxisData({ axis, split, scale, top: topOffset, widt
86
86
  const axisPlotTopPosition = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
87
87
  const axisHeight = ((_b = split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
88
88
  const domainX = axis.position === 'left' ? 0 : width;
89
- const domain = {
90
- start: [domainX, axisPlotTopPosition],
91
- end: [domainX, axisPlotTopPosition + axisHeight],
92
- lineColor: (_c = axis.lineColor) !== null && _c !== void 0 ? _c : '',
93
- };
89
+ let domain = null;
90
+ if (axis.visible) {
91
+ domain = {
92
+ start: [domainX, axisPlotTopPosition],
93
+ end: [domainX, axisPlotTopPosition + axisHeight],
94
+ lineColor: (_c = axis.lineColor) !== null && _c !== void 0 ? _c : '',
95
+ };
96
+ }
94
97
  const ticks = [];
95
98
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
96
99
  const labelLineHeight = (await getTextSize('Tmp')).height;
@@ -226,7 +229,7 @@ export async function prepareAxisData({ axis, split, scale, top: topOffset, widt
226
229
  content: titleContent,
227
230
  style: axis.title.style,
228
231
  size: rotatedTitleSize,
229
- x: x,
232
+ x,
230
233
  y: axisPlotTopPosition + y,
231
234
  rotate: rotateAngle,
232
235
  offset: -(originalTextSize.height / titleContent.length) * (titleContent.length - 1),
@@ -74,7 +74,7 @@ export type AxisDomainData = {
74
74
  export type AxisYData = {
75
75
  id: string;
76
76
  title: AxisTitleData | null;
77
- domain: AxisDomainData;
77
+ domain: AxisDomainData | null;
78
78
  ticks: AxisTickData[];
79
79
  plotLines: AxisPlotLineData[];
80
80
  plotBands: AxisPlotBandData[];
@@ -15,8 +15,8 @@ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
15
15
  }
16
16
  const getScaleTicks = () => {
17
17
  var _a;
18
+ const domainData = getDomainDataYBySeries(series);
18
19
  if (series.some((s) => s.type === 'bar-y')) {
19
- const domainData = getDomainDataYBySeries(series);
20
20
  if (domainData.length < 3) {
21
21
  return domainData;
22
22
  }
@@ -13,6 +13,7 @@ import { Tooltip } from '../Tooltip';
13
13
  import { useChartInnerHandlers } from './useChartInnerHandlers';
14
14
  import { useChartInnerProps } from './useChartInnerProps';
15
15
  import { useChartInnerState } from './useChartInnerState';
16
+ import { useAsyncState } from './utils';
16
17
  import './styles.css';
17
18
  const b = block('chart');
18
19
  export const ChartInner = (props) => {
@@ -82,34 +83,27 @@ export const ChartInner = (props) => {
82
83
  unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
83
84
  }
84
85
  }, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
85
- const [yAxisDataItems, setYAxisDataItems] = React.useState([]);
86
- const countedRef = React.useRef(0);
87
- React.useEffect(() => {
88
- countedRef.current++;
89
- (async function () {
90
- const currentRun = countedRef.current;
91
- const items = [];
92
- for (let i = 0; i < yAxis.length; i++) {
93
- const axis = yAxis[i];
94
- const scale = yScale === null || yScale === void 0 ? void 0 : yScale[i];
95
- if (scale) {
96
- const axisData = await prepareAxisData({
97
- axis,
98
- scale,
99
- top: boundsOffsetTop,
100
- width: boundsWidth,
101
- height: boundsHeight,
102
- split: preparedSplit,
103
- series: preparedSeries,
104
- });
105
- items.push(axisData);
106
- }
107
- }
108
- if (countedRef.current === currentRun) {
109
- setYAxisDataItems(items);
86
+ const setYAxisDataItems = React.useCallback(async () => {
87
+ const items = [];
88
+ for (let i = 0; i < yAxis.length; i++) {
89
+ const axis = yAxis[i];
90
+ const scale = yScale === null || yScale === void 0 ? void 0 : yScale[i];
91
+ if (scale) {
92
+ const axisData = await prepareAxisData({
93
+ axis,
94
+ scale,
95
+ top: boundsOffsetTop,
96
+ width: boundsWidth,
97
+ height: boundsHeight,
98
+ split: preparedSplit,
99
+ series: preparedSeries.filter((s) => s.visible),
100
+ });
101
+ items.push(axisData);
110
102
  }
111
- })();
103
+ }
104
+ return items;
112
105
  }, [boundsHeight, boundsOffsetTop, boundsWidth, preparedSeries, preparedSplit, yAxis, yScale]);
106
+ const yAxisDataItems = useAsyncState([], setYAxisDataItems);
113
107
  return (React.createElement("div", { className: b() },
114
108
  React.createElement("svg", { ref: svgRef, width: width, height: height,
115
109
  // We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
@@ -41,10 +41,10 @@ export declare function useChartInnerProps(props: Props): {
41
41
  prevWidth: number | undefined;
42
42
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
43
43
  shapesData: import("../../hooks").ShapeData[];
44
- title: (import("../..").ChartTitle & {
44
+ title: (import("../../types").ChartTitle & {
45
45
  height: number;
46
46
  }) | undefined;
47
- tooltip: import("../..").ChartTooltip<any> & {
47
+ tooltip: import("../../types").ChartTooltip<any> & {
48
48
  enabled: boolean;
49
49
  throttle: number;
50
50
  };
@@ -8,7 +8,7 @@ import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
8
8
  import { getActiveLegendItems } from '../../hooks/useSeries/utils';
9
9
  import { useZoom } from '../../hooks/useZoom';
10
10
  import { getSortedSeriesData, getZoomedSeriesData } from '../../utils';
11
- import { hasAtLeastOneSeriesDataPerPlot } from './utils';
11
+ import { hasAtLeastOneSeriesDataPerPlot, useAsyncState } from './utils';
12
12
  export function useChartInnerProps(props) {
13
13
  var _a;
14
14
  const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
@@ -38,44 +38,52 @@ export function useChartInnerProps(props) {
38
38
  zoomState,
39
39
  });
40
40
  }, [data.xAxis, data.yAxis, sortedSeriesData, zoomState]);
41
- const [xAxis, setXAxis] = React.useState(null);
42
- React.useEffect(() => {
43
- setXAxis(null);
44
- getPreparedXAxis({
41
+ const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
42
+ colors,
43
+ legend: data.legend,
44
+ originalSeriesData: sortedSeriesData,
45
+ seriesData: zoomedSeriesData,
46
+ seriesOptions: data.series.options,
47
+ });
48
+ const setAxes = React.useCallback(async () => {
49
+ const seriesData = preparedSeries.filter((s) => s.visible);
50
+ const xAxis = await getPreparedXAxis({
45
51
  xAxis: data.xAxis,
46
52
  width,
47
- seriesData: zoomedSeriesData,
53
+ seriesData,
48
54
  seriesOptions: preparedSeriesOptions,
49
- }).then((val) => setXAxis(val));
50
- }, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
51
- const estimatedBoundsHeight = React.useMemo(() => {
55
+ });
56
+ let estimatedBoundsHeight = height;
52
57
  if (xAxis) {
53
- return (height -
54
- xAxis.title.height +
55
- xAxis.title.margin +
56
- xAxis.labels.margin +
57
- parseInt(xAxis.labels.style.fontSize, 10));
58
+ estimatedBoundsHeight =
59
+ height -
60
+ (xAxis.title.height +
61
+ xAxis.title.margin +
62
+ xAxis.labels.margin +
63
+ xAxis.labels.height +
64
+ (preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
65
+ chart.margin.top +
66
+ chart.margin.bottom);
58
67
  }
59
- return 0;
60
- }, [height, xAxis]);
61
- const [yAxis, setYAxis] = React.useState([]);
62
- React.useEffect(() => {
63
- setYAxis([]);
64
- getPreparedYAxis({
68
+ const yAxis = await getPreparedYAxis({
65
69
  height,
66
70
  boundsHeight: estimatedBoundsHeight,
67
71
  width,
68
- seriesData: zoomedSeriesData,
72
+ seriesData,
69
73
  yAxis: data.yAxis,
70
- }).then((val) => setYAxis(val));
71
- }, [data.yAxis, estimatedBoundsHeight, height, width, zoomedSeriesData]);
72
- const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
73
- colors,
74
- legend: data.legend,
75
- originalSeriesData: sortedSeriesData,
76
- seriesData: zoomedSeriesData,
77
- seriesOptions: data.series.options,
78
- });
74
+ });
75
+ return { xAxis, yAxis };
76
+ }, [
77
+ chart.margin,
78
+ data.xAxis,
79
+ data.yAxis,
80
+ height,
81
+ preparedLegend,
82
+ preparedSeries,
83
+ preparedSeriesOptions,
84
+ width,
85
+ ]);
86
+ const { xAxis, yAxis } = useAsyncState({ xAxis: null, yAxis: [] }, setAxes);
79
87
  const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
80
88
  const { preparedSeries: preparedShapesSeries } = useShapeSeries({
81
89
  colors,
@@ -1,3 +1,4 @@
1
1
  import type { PreparedAxis } from '../../hooks/useChartOptions/types';
2
2
  import type { ChartSeries } from '../../types';
3
3
  export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
4
+ export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
1
3
  export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
2
4
  const hasDataMap = new Map();
3
5
  yAxes.forEach((yAxis) => {
@@ -26,3 +28,22 @@ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
26
28
  });
27
29
  return [...hasDataMap.values()].every((hasData) => hasData);
28
30
  }
31
+ export function useAsyncState(value, setState) {
32
+ const [stateValue, setValue] = React.useState(value);
33
+ const countedRef = React.useRef(0);
34
+ const prevValue = React.useRef(value);
35
+ const ready = React.useRef(false);
36
+ React.useEffect(() => {
37
+ countedRef.current++;
38
+ (async function () {
39
+ const currentRun = countedRef.current;
40
+ const newValue = await setState();
41
+ ready.current = true;
42
+ if (countedRef.current === currentRun && !isEqual(prevValue.current, newValue)) {
43
+ setValue(newValue);
44
+ prevValue.current = newValue;
45
+ }
46
+ })();
47
+ }, [setState]);
48
+ return stateValue;
49
+ }
@@ -85,7 +85,7 @@ function renderLegendSymbol(args) {
85
85
  case 'path': {
86
86
  const y = legendLineHeight / 2;
87
87
  const points = [
88
- { x: x, y },
88
+ { x, y },
89
89
  { x: x + d.symbol.width, y },
90
90
  ];
91
91
  element
@@ -12,4 +12,4 @@ export interface ChartTooltipContentProps {
12
12
  yAxis?: ChartYAxis;
13
13
  qa?: string;
14
14
  }
15
- export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null;
15
+ export declare const ChartTooltipContent: React.MemoExoticComponent<(props: ChartTooltipContentProps) => React.JSX.Element | null>;
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import isNil from 'lodash/isNil';
3
3
  import { DefaultTooltipContent } from './DefaultTooltipContent';
4
- export const ChartTooltipContent = (props) => {
4
+ export const ChartTooltipContent = React.memo((props) => {
5
5
  const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, qa, } = props;
6
6
  if (!hovered) {
7
7
  return null;
8
8
  }
9
9
  const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
10
10
  return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, pinned: pinned, rowRenderer: rowRenderer, totals: totals, valueFormat: valueFormat, headerFormat: headerFormat, xAxis: xAxis, yAxis: yAxis, qa: qa })) : (customTooltip);
11
- };
11
+ });
12
+ ChartTooltipContent.displayName = 'ChartTooltipContent';
@@ -150,6 +150,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
150
150
  });
151
151
  }
152
152
  case 'pie':
153
+ case 'heatmap':
153
154
  case 'treemap': {
154
155
  const seriesData = data;
155
156
  const formattedValue = getFormattedValue({
@@ -39,7 +39,7 @@ export function getDefaultValueFormat({ axis, closestPointsRange, }) {
39
39
  }
40
40
  export const getMeasureValue = ({ data, xAxis, yAxis, headerFormat, }) => {
41
41
  var _a, _b, _c, _d, _e, _f;
42
- if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
42
+ if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'heatmap'].includes(item.series.type))) {
43
43
  return null;
44
44
  }
45
45
  if (data.some((item) => item.series.type === 'radar')) {
@@ -78,6 +78,7 @@ export function getHoveredValues(args) {
78
78
  }
79
79
  case 'pie':
80
80
  case 'radar':
81
+ case 'heatmap':
81
82
  case 'treemap': {
82
83
  const seriesData = data;
83
84
  return seriesData.value;
@@ -9,4 +9,5 @@ export declare const SeriesType: {
9
9
  readonly Waterfall: "waterfall";
10
10
  readonly Sankey: "sankey";
11
11
  readonly Radar: "radar";
12
+ readonly Heatmap: "heatmap";
12
13
  };
@@ -9,4 +9,5 @@ export const SeriesType = {
9
9
  Waterfall: 'waterfall',
10
10
  Sankey: 'sankey',
11
11
  Radar: 'radar',
12
+ Heatmap: 'heatmap',
12
13
  };
@@ -117,4 +117,12 @@ export const seriesOptionsDefaults = {
117
117
  },
118
118
  },
119
119
  },
120
+ heatmap: {
121
+ states: {
122
+ hover: {
123
+ enabled: true,
124
+ brightness: 0.3,
125
+ },
126
+ },
127
+ },
120
128
  };
@@ -3,8 +3,8 @@ import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
5
5
  import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
- import { getBandSize } from '../utils';
7
6
  import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
7
+ import { getBandSize } from '../utils/get-band-size';
8
8
  const X_AXIS_ZOOM_PADDING = 0.02;
9
9
  function validateArrayData(data) {
10
10
  let hasNumberAndNullValues;
@@ -59,6 +59,10 @@ function getYScaleRange(args) {
59
59
  }
60
60
  }
61
61
  }
62
+ function isSeriesWithYAxisOffset(series) {
63
+ const types = [SeriesType.BarY, SeriesType.Heatmap];
64
+ return series.some((s) => types.includes(s.type));
65
+ }
62
66
  // eslint-disable-next-line complexity
63
67
  export function createYScale(args) {
64
68
  const { axis, boundsHeight, series } = args;
@@ -89,9 +93,9 @@ export function createYScale(args) {
89
93
  const scaleFn = axis.type === 'logarithmic' ? scaleLog : scaleLinear;
90
94
  const scale = scaleFn().domain([yMin, yMax]).range(range);
91
95
  let offsetMin = 0;
92
- let offsetMax = boundsHeight * axis.maxPadding;
93
- const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
94
- if (barYSeries.length) {
96
+ // We should ignore padding if we are drawing only one point on the plot.
97
+ let offsetMax = yMin === yMax ? 0 : boundsHeight * axis.maxPadding;
98
+ if (isSeriesWithYAxisOffset(series)) {
95
99
  if (domain.length > 1) {
96
100
  const bandWidth = getBandSize({
97
101
  scale: scale,
@@ -138,8 +142,7 @@ export function createYScale(args) {
138
142
  const scale = scaleUtc().domain([yMin, yMax]).range(range);
139
143
  let offsetMin = 0;
140
144
  let offsetMax = boundsHeight * axis.maxPadding;
141
- const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
142
- if (barYSeries.length) {
145
+ if (isSeriesWithYAxisOffset(series)) {
143
146
  if (Object.keys(domain).length > 1) {
144
147
  const bandWidth = getBandSize({
145
148
  scale: scale,
@@ -175,6 +178,10 @@ function calculateXAxisPadding(series) {
175
178
  });
176
179
  return result;
177
180
  }
181
+ function isSeriesWithXAxisOffset(series) {
182
+ const types = [SeriesType.Heatmap];
183
+ return series.some((s) => types.includes(s.type));
184
+ }
178
185
  function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
179
186
  const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
180
187
  const xRange = [0, boundsWidth - maxPadding];
@@ -252,7 +259,23 @@ export function createXScale(args) {
252
259
  }
253
260
  const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
254
261
  const scale = scaleFn().domain([xMin, xMax]).range(range);
255
- if (!hasZoomX) {
262
+ let offsetMin = 0;
263
+ let offsetMax = 0;
264
+ const hasOffset = isSeriesWithXAxisOffset(series);
265
+ if (hasOffset) {
266
+ if (domainData.length > 1) {
267
+ const bandWidth = getBandSize({
268
+ scale: scale,
269
+ domain: domainData,
270
+ });
271
+ offsetMin += bandWidth / 2;
272
+ offsetMax += bandWidth / 2;
273
+ }
274
+ }
275
+ const domainOffsetMin = Math.abs(scale.invert(offsetMin) - scale.invert(0));
276
+ const domainOffsetMax = Math.abs(scale.invert(offsetMax) - scale.invert(0));
277
+ scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
278
+ if (!hasZoomX && !hasOffset) {
256
279
  // 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
257
280
  scale.nice(Math.max(10, domainData.length));
258
281
  }
@@ -288,7 +311,23 @@ export function createXScale(args) {
288
311
  const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
289
312
  domain = [xMin, xMax];
290
313
  const scale = scaleUtc().domain(domain).range(range);
291
- if (!hasZoomX) {
314
+ let offsetMin = 0;
315
+ let offsetMax = 0;
316
+ const hasOffset = isSeriesWithXAxisOffset(series);
317
+ if (hasOffset) {
318
+ if (domainData.length > 1) {
319
+ const bandWidth = getBandSize({
320
+ scale: scale,
321
+ domain: domainData,
322
+ });
323
+ offsetMin += bandWidth / 2;
324
+ offsetMax += bandWidth / 2;
325
+ }
326
+ }
327
+ const domainOffsetMin = Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
328
+ const domainOffsetMax = Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
329
+ scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
330
+ if (!hasZoomX && !hasOffset) {
292
331
  // 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
293
332
  scale.nice(Math.max(10, domainData.length));
294
333
  }
@@ -2,7 +2,7 @@ import get from 'lodash/get';
2
2
  import { getDefaultValueFormat } from '../../components/Tooltip/DefaultTooltipContent/utils';
3
3
  import { getDomainDataXBySeries, getDomainDataYBySeries, getMinSpaceBetween } from '../../utils';
4
4
  function getDefaultHeaderFormat({ seriesData, yAxes, xAxis, }) {
5
- if (seriesData.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'radar'].includes(item.type))) {
5
+ if (seriesData.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'radar', 'heatmap'].includes(item.type))) {
6
6
  return undefined;
7
7
  }
8
8
  if (seriesData.some((item) => item.type === 'bar-y')) {
@@ -6,4 +6,4 @@ export declare const getPreparedXAxis: ({ xAxis, seriesData, seriesOptions, widt
6
6
  seriesData: ChartSeries[];
7
7
  seriesOptions: PreparedSeriesOptions;
8
8
  width: number;
9
- }) => Promise<PreparedAxis>;
9
+ }) => Promise<PreparedAxis | null>;
@@ -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
+ }