@gravity-ui/charts 1.46.1 → 1.47.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 (149) hide show
  1. package/dist/cjs/components/ChartInner/index.js +1 -0
  2. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +1 -0
  3. package/dist/cjs/components/ChartInner/useChartInnerProps.js +12 -5
  4. package/dist/cjs/core/brush/index.d.ts +2 -0
  5. package/dist/cjs/core/brush/index.js +2 -0
  6. package/dist/cjs/{hooks/useBrush → core/brush}/types.d.ts +2 -2
  7. package/dist/{esm/hooks/useBrush → cjs/core/brush}/utils.d.ts +1 -1
  8. package/dist/cjs/core/chart/index.d.ts +1 -0
  9. package/dist/cjs/core/chart/index.js +1 -0
  10. package/dist/cjs/core/chart/types.d.ts +8 -0
  11. package/dist/cjs/core/index.d.ts +3 -0
  12. package/dist/cjs/core/index.js +3 -0
  13. package/dist/cjs/core/layout/chart-dimensions.d.ts +1 -1
  14. package/dist/cjs/core/range-slider/index.d.ts +2 -0
  15. package/dist/cjs/core/range-slider/index.js +2 -0
  16. package/dist/cjs/core/range-slider/types.d.ts +4 -0
  17. package/dist/cjs/{hooks/useRangeSlider → core/range-slider}/utils.d.ts +5 -5
  18. package/dist/cjs/{hooks/useRangeSlider → core/range-slider}/utils.js +1 -1
  19. package/dist/cjs/core/scales/x-scale.d.ts +2 -2
  20. package/dist/cjs/core/scales/y-scale.js +21 -0
  21. package/dist/cjs/core/series/prepare-legend.d.ts +1 -1
  22. package/dist/cjs/core/shapes/area/prepare-data.js +6 -1
  23. package/dist/cjs/core/shapes/area/renderer.js +8 -14
  24. package/dist/cjs/core/shapes/area/types.d.ts +6 -5
  25. package/dist/cjs/core/shapes/bar-x/renderer.js +6 -12
  26. package/dist/cjs/core/shapes/bar-y/renderer.js +6 -12
  27. package/dist/cjs/core/shapes/data-labels.d.ts +15 -0
  28. package/dist/cjs/core/shapes/data-labels.js +15 -0
  29. package/dist/cjs/core/shapes/funnel/renderer.js +6 -11
  30. package/dist/cjs/core/shapes/heatmap/prepare-data.js +1 -0
  31. package/dist/cjs/core/shapes/heatmap/renderer.js +6 -11
  32. package/dist/cjs/core/shapes/heatmap/types.d.ts +1 -0
  33. package/dist/cjs/core/shapes/line/prepare-data.js +9 -1
  34. package/dist/cjs/core/shapes/line/renderer.js +7 -13
  35. package/dist/cjs/core/shapes/line/types.d.ts +5 -4
  36. package/dist/cjs/core/shapes/radar/renderer.js +8 -12
  37. package/dist/cjs/core/shapes/sankey/renderer.js +6 -12
  38. package/dist/cjs/core/shapes/utils.d.ts +24 -0
  39. package/dist/cjs/core/shapes/utils.js +48 -0
  40. package/dist/cjs/core/shapes/waterfall/renderer.js +6 -12
  41. package/dist/cjs/core/shapes/x-range/renderer.js +7 -13
  42. package/dist/cjs/core/types/chart/base.d.ts +17 -3
  43. package/dist/cjs/core/types/chart/tooltip.d.ts +3 -3
  44. package/dist/cjs/core/types/formatter.d.ts +1 -40
  45. package/dist/cjs/core/utils/format.d.ts +2 -2
  46. package/dist/cjs/core/utils/get-closest-data.js +13 -8
  47. package/dist/cjs/core/zoom/index.d.ts +2 -0
  48. package/dist/cjs/core/zoom/index.js +2 -0
  49. package/dist/{esm/hooks/useZoom → cjs/core/zoom}/utils.d.ts +3 -3
  50. package/dist/{esm/hooks/useZoom → cjs/core/zoom}/utils.js +1 -1
  51. package/dist/cjs/core/zoom/zoom.d.ts +3 -3
  52. package/dist/cjs/hooks/index.d.ts +2 -2
  53. package/dist/cjs/hooks/index.js +2 -2
  54. package/dist/cjs/hooks/types.d.ts +2 -8
  55. package/dist/cjs/hooks/useBrush/index.d.ts +1 -1
  56. package/dist/cjs/hooks/useBrush/index.js +1 -1
  57. package/dist/cjs/hooks/useRangeSlider/index.js +3 -3
  58. package/dist/cjs/hooks/useRangeSlider/types.d.ts +5 -7
  59. package/dist/cjs/hooks/useShapes/index.d.ts +1 -1
  60. package/dist/cjs/hooks/useShapes/utils.d.ts +1 -1
  61. package/dist/cjs/hooks/useZoom/index.d.ts +1 -1
  62. package/dist/cjs/hooks/useZoom/index.js +1 -1
  63. package/dist/cjs/index.d.ts +1 -0
  64. package/dist/cjs/index.js +1 -0
  65. package/dist/cjs/libs/format-number/index.js +82 -14
  66. package/dist/cjs/libs/format-number/presets.d.ts +40 -0
  67. package/dist/cjs/libs/format-number/presets.js +66 -0
  68. package/dist/cjs/libs/format-number/types.d.ts +82 -3
  69. package/dist/esm/components/ChartInner/index.js +1 -0
  70. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +1 -0
  71. package/dist/esm/components/ChartInner/useChartInnerProps.js +12 -5
  72. package/dist/esm/core/brush/index.d.ts +2 -0
  73. package/dist/esm/core/brush/index.js +2 -0
  74. package/dist/esm/{hooks/useBrush → core/brush}/types.d.ts +2 -2
  75. package/dist/esm/core/brush/types.js +1 -0
  76. package/dist/{cjs/hooks/useBrush → esm/core/brush}/utils.d.ts +1 -1
  77. package/dist/esm/core/chart/index.d.ts +1 -0
  78. package/dist/esm/core/chart/index.js +1 -0
  79. package/dist/esm/core/chart/types.d.ts +8 -0
  80. package/dist/esm/core/chart/types.js +1 -0
  81. package/dist/esm/core/index.d.ts +3 -0
  82. package/dist/esm/core/index.js +3 -0
  83. package/dist/esm/core/layout/chart-dimensions.d.ts +1 -1
  84. package/dist/esm/core/range-slider/index.d.ts +2 -0
  85. package/dist/esm/core/range-slider/index.js +2 -0
  86. package/dist/esm/core/range-slider/types.d.ts +4 -0
  87. package/dist/esm/core/range-slider/types.js +1 -0
  88. package/dist/esm/{hooks/useRangeSlider → core/range-slider}/utils.d.ts +5 -5
  89. package/dist/esm/{hooks/useRangeSlider → core/range-slider}/utils.js +1 -1
  90. package/dist/esm/core/scales/x-scale.d.ts +2 -2
  91. package/dist/esm/core/scales/y-scale.js +21 -0
  92. package/dist/esm/core/series/prepare-legend.d.ts +1 -1
  93. package/dist/esm/core/shapes/area/prepare-data.js +6 -1
  94. package/dist/esm/core/shapes/area/renderer.js +8 -14
  95. package/dist/esm/core/shapes/area/types.d.ts +6 -5
  96. package/dist/esm/core/shapes/bar-x/renderer.js +6 -12
  97. package/dist/esm/core/shapes/bar-y/renderer.js +6 -12
  98. package/dist/esm/core/shapes/data-labels.d.ts +15 -0
  99. package/dist/esm/core/shapes/data-labels.js +15 -0
  100. package/dist/esm/core/shapes/funnel/renderer.js +6 -11
  101. package/dist/esm/core/shapes/heatmap/prepare-data.js +1 -0
  102. package/dist/esm/core/shapes/heatmap/renderer.js +6 -11
  103. package/dist/esm/core/shapes/heatmap/types.d.ts +1 -0
  104. package/dist/esm/core/shapes/line/prepare-data.js +9 -1
  105. package/dist/esm/core/shapes/line/renderer.js +7 -13
  106. package/dist/esm/core/shapes/line/types.d.ts +5 -4
  107. package/dist/esm/core/shapes/radar/renderer.js +8 -12
  108. package/dist/esm/core/shapes/sankey/renderer.js +6 -12
  109. package/dist/esm/core/shapes/utils.d.ts +24 -0
  110. package/dist/esm/core/shapes/utils.js +48 -0
  111. package/dist/esm/core/shapes/waterfall/renderer.js +6 -12
  112. package/dist/esm/core/shapes/x-range/renderer.js +7 -13
  113. package/dist/esm/core/types/chart/base.d.ts +17 -3
  114. package/dist/esm/core/types/chart/tooltip.d.ts +3 -3
  115. package/dist/esm/core/types/formatter.d.ts +1 -40
  116. package/dist/esm/core/utils/format.d.ts +2 -2
  117. package/dist/esm/core/utils/get-closest-data.js +13 -8
  118. package/dist/esm/core/zoom/index.d.ts +2 -0
  119. package/dist/esm/core/zoom/index.js +2 -0
  120. package/dist/esm/core/zoom/types.js +1 -0
  121. package/dist/{cjs/hooks/useZoom → esm/core/zoom}/utils.d.ts +3 -3
  122. package/dist/{cjs/hooks/useZoom → esm/core/zoom}/utils.js +1 -1
  123. package/dist/esm/core/zoom/zoom.d.ts +3 -3
  124. package/dist/esm/hooks/index.d.ts +2 -2
  125. package/dist/esm/hooks/index.js +2 -2
  126. package/dist/esm/hooks/types.d.ts +2 -8
  127. package/dist/esm/hooks/useBrush/index.d.ts +1 -1
  128. package/dist/esm/hooks/useBrush/index.js +1 -1
  129. package/dist/esm/hooks/useRangeSlider/index.js +3 -3
  130. package/dist/esm/hooks/useRangeSlider/types.d.ts +5 -7
  131. package/dist/esm/hooks/useShapes/index.d.ts +1 -1
  132. package/dist/esm/hooks/useShapes/utils.d.ts +1 -1
  133. package/dist/esm/hooks/useZoom/index.d.ts +1 -1
  134. package/dist/esm/hooks/useZoom/index.js +1 -1
  135. package/dist/esm/index.d.ts +1 -0
  136. package/dist/esm/index.js +1 -0
  137. package/dist/esm/libs/format-number/index.js +82 -14
  138. package/dist/esm/libs/format-number/presets.d.ts +40 -0
  139. package/dist/esm/libs/format-number/presets.js +66 -0
  140. package/dist/esm/libs/format-number/types.d.ts +82 -3
  141. package/package.json +1 -1
  142. /package/dist/cjs/{hooks/useBrush → core/brush}/types.js +0 -0
  143. /package/dist/cjs/{hooks/useBrush → core/brush}/utils.js +0 -0
  144. /package/dist/cjs/{hooks/useZoom → core/chart}/types.js +0 -0
  145. /package/dist/{esm/hooks/useBrush → cjs/core/range-slider}/types.js +0 -0
  146. /package/dist/cjs/{hooks/useZoom → core/zoom}/types.d.ts +0 -0
  147. /package/dist/{esm/hooks/useZoom → cjs/core/zoom}/types.js +0 -0
  148. /package/dist/esm/{hooks/useBrush → core/brush}/utils.js +0 -0
  149. /package/dist/esm/{hooks/useZoom → core/zoom}/types.d.ts +0 -0
@@ -1,13 +1,14 @@
1
1
  import type { AreaSeriesData, HtmlItem, LabelData } from '../../../types';
2
2
  import type { AnnotationAnchor, PreparedAnnotation, PreparedAreaSeries } from '../../series/types';
3
3
  export type PointData = {
4
- y0: number;
5
- x: number;
6
- y: number | null;
7
- data: AreaSeriesData;
8
- series: PreparedAreaSeries;
9
4
  annotation?: PreparedAnnotation;
10
5
  color?: string;
6
+ data: AreaSeriesData;
7
+ hiddenInLine?: boolean;
8
+ series: PreparedAreaSeries;
9
+ x: number;
10
+ y: number | null;
11
+ y0: number;
11
12
  };
12
13
  export type MarkerPointData = PointData & {
13
14
  y: number;
@@ -4,6 +4,7 @@ import get from 'lodash/get';
4
4
  import { block } from '../../../utils';
5
5
  import { filterOverlappingLabels } from '../../utils';
6
6
  import { renderAnnotations } from '../annotation';
7
+ import { renderDataLabels } from '../data-labels';
7
8
  import { getRectPath } from '../utils';
8
9
  const b = block('bar-x');
9
10
  export function renderBarX(elements, preparedData, seriesOptions, allowOverlapDataLabels, dispatcher) {
@@ -37,18 +38,11 @@ export function renderBarX(elements, preparedData, seriesOptions, allowOverlapDa
37
38
  if (!allowOverlapDataLabels) {
38
39
  dataLabels = filterOverlappingLabels(dataLabels);
39
40
  }
40
- const labelSelection = svgElement
41
- .selectAll('text')
42
- .data(dataLabels)
43
- .join('text')
44
- .html((d) => d.text)
45
- .attr('class', b('label'))
46
- .attr('x', (d) => d.x)
47
- .attr('y', (d) => d.y)
48
- .attr('text-anchor', (d) => d.textAnchor)
49
- .style('font-size', (d) => d.style.fontSize)
50
- .style('font-weight', (d) => d.style.fontWeight || null)
51
- .style('fill', (d) => d.style.fontColor || null);
41
+ const labelSelection = renderDataLabels({
42
+ container: svgElement,
43
+ data: dataLabels,
44
+ className: b('label'),
45
+ });
52
46
  const annotationAnchors = [];
53
47
  for (const d of preparedData) {
54
48
  if (d.annotation) {
@@ -2,6 +2,7 @@ import { color } from 'd3-color';
2
2
  import { select } from 'd3-selection';
3
3
  import get from 'lodash/get';
4
4
  import { block } from '../../../utils';
5
+ import { renderDataLabels } from '../data-labels';
5
6
  import { getAdjustedRectBorderPath, getAdjustedRectPath } from './utils';
6
7
  const b = block('bar-y');
7
8
  export function renderBarY(elements, preparedData, seriesOptions, dispatcher) {
@@ -31,18 +32,11 @@ export function renderBarY(elements, preparedData, seriesOptions, dispatcher) {
31
32
  .attr('fill-rule', 'evenodd')
32
33
  .attr('opacity', (d) => d.data.opacity || null)
33
34
  .attr('pointer-events', 'none');
34
- const labelSelection = svgElement
35
- .selectAll('text')
36
- .data(dataLabels)
37
- .join('text')
38
- .html((d) => d.text)
39
- .attr('class', b('label'))
40
- .attr('x', (d) => d.x)
41
- .attr('y', (d) => d.y)
42
- .attr('text-anchor', (d) => d.textAnchor)
43
- .style('font-size', (d) => d.style.fontSize)
44
- .style('font-weight', (d) => d.style.fontWeight || null)
45
- .style('fill', (d) => d.style.fontColor || null);
35
+ const labelSelection = renderDataLabels({
36
+ container: svgElement,
37
+ data: dataLabels,
38
+ className: b('label'),
39
+ });
46
40
  const hoverOptions = get(seriesOptions, 'bar-y.states.hover');
47
41
  const inactiveOptions = get(seriesOptions, 'bar-y.states.inactive');
48
42
  function handleShapeHover(data) {
@@ -0,0 +1,15 @@
1
+ import type { Selection } from 'd3-selection';
2
+ import type { BaseTextStyle } from '../types/chart/base';
3
+ type RenderableLabelData = {
4
+ text: string;
5
+ x: number;
6
+ y: number;
7
+ textAnchor: 'start' | 'end' | 'middle';
8
+ style: BaseTextStyle;
9
+ };
10
+ export declare function renderDataLabels<T extends RenderableLabelData>(args: {
11
+ container: Selection<SVGGElement, unknown, null, undefined>;
12
+ data: T[];
13
+ className: string;
14
+ }): Selection<SVGTextElement, T, SVGGElement, unknown>;
15
+ export {};
@@ -0,0 +1,15 @@
1
+ export function renderDataLabels(args) {
2
+ const { container, data, className } = args;
3
+ return container
4
+ .selectAll('text')
5
+ .data(data)
6
+ .join('text')
7
+ .html((d) => d.text)
8
+ .attr('class', className)
9
+ .attr('x', (d) => d.x)
10
+ .attr('y', (d) => d.y)
11
+ .attr('text-anchor', (d) => d.textAnchor)
12
+ .style('font-size', (d) => d.style.fontSize)
13
+ .style('font-weight', (d) => d.style.fontWeight || null)
14
+ .style('fill', (d) => d.style.fontColor || null);
15
+ }
@@ -2,6 +2,7 @@ import { color } from 'd3-color';
2
2
  import { select } from 'd3-selection';
3
3
  import { block } from '../../../utils';
4
4
  import { getLineDashArray } from '../../utils';
5
+ import { renderDataLabels } from '../data-labels';
5
6
  const b = block('funnel');
6
7
  export function renderFunnel(elements, preparedData, seriesOptions, dispatcher) {
7
8
  var _a, _b;
@@ -44,17 +45,11 @@ export function renderFunnel(elements, preparedData, seriesOptions, dispatcher)
44
45
  connectorLines.append('path').attr('d', (d) => d.linePath[0].toString());
45
46
  connectorLines.append('path').attr('d', (d) => d.linePath[1].toString());
46
47
  // dataLabels
47
- svgElement
48
- .selectAll('text')
49
- .data(preparedData.svgLabels)
50
- .join('text')
51
- .html((d) => d.text)
52
- .attr('class', b('label'))
53
- .attr('x', (d) => d.x)
54
- .attr('y', (d) => d.y)
55
- .style('font-size', (d) => d.style.fontSize)
56
- .style('font-weight', (d) => d.style.fontWeight || null)
57
- .style('fill', (d) => d.style.fontColor || null);
48
+ renderDataLabels({
49
+ container: svgElement,
50
+ data: preparedData.svgLabels,
51
+ className: b('label'),
52
+ });
58
53
  function handleShapeHover(data) {
59
54
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
60
55
  if (hoverEnabled) {
@@ -84,6 +84,7 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
84
84
  x: item.x + item.width / 2 - size.width / 2,
85
85
  y: item.y + item.height / 2 - size.height / 2 + size.hangingOffset,
86
86
  text,
87
+ textAnchor: 'start',
87
88
  style: series.dataLabels.style,
88
89
  });
89
90
  }
@@ -1,6 +1,7 @@
1
1
  import { color } from 'd3-color';
2
2
  import { select } from 'd3-selection';
3
3
  import { block } from '../../../utils';
4
+ import { renderDataLabels } from '../data-labels';
4
5
  const b = block('heatmap');
5
6
  export function renderHeatmap(elements, preparedData, seriesOptions, dispatcher) {
6
7
  var _a, _b;
@@ -20,17 +21,11 @@ export function renderHeatmap(elements, preparedData, seriesOptions, dispatcher)
20
21
  .attr('stroke', (d) => d.borderColor)
21
22
  .attr('stroke-width', (d) => d.borderWidth);
22
23
  // dataLabels
23
- svgElement
24
- .selectAll('text')
25
- .data(preparedData.labels)
26
- .join('text')
27
- .html((d) => d.text)
28
- .attr('class', b('label'))
29
- .attr('x', (d) => d.x)
30
- .attr('y', (d) => d.y)
31
- .style('font-size', (d) => d.style.fontSize)
32
- .style('font-weight', (d) => d.style.fontWeight || null)
33
- .style('fill', (d) => d.style.fontColor || null);
24
+ renderDataLabels({
25
+ container: svgElement,
26
+ data: preparedData.labels,
27
+ className: b('label'),
28
+ });
34
29
  function handleShapeHover(data) {
35
30
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
36
31
  if (hoverEnabled) {
@@ -14,6 +14,7 @@ export type HeatmapLabel = {
14
14
  x: number;
15
15
  y: number;
16
16
  text: string;
17
+ textAnchor: 'start' | 'end' | 'middle';
17
18
  style: BaseTextStyle;
18
19
  };
19
20
  export type PreparedHeatmapData = {
@@ -1,7 +1,7 @@
1
1
  import { prepareAnnotation } from '../../series/prepare-annotation';
2
2
  import { filterOverlappingLabels, getLabelsSize, getTextSizeFn } from '../../utils';
3
3
  import { getFormattedValue } from '../../utils/format';
4
- import { getXValue, getYValue } from '../utils';
4
+ import { getXValue, getYValue, markHiddenPointsOutOfYRange } from '../utils';
5
5
  async function getHtmlLabel(point, series, xMax) {
6
6
  var _a;
7
7
  const content = String((_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y);
@@ -129,6 +129,14 @@ export const prepareLineData = async (args) => {
129
129
  }
130
130
  return result;
131
131
  }, []);
132
+ markHiddenPointsOutOfYRange({
133
+ points,
134
+ yScale: seriesYScale,
135
+ yAxisTop,
136
+ axisMin: seriesYAxis.min,
137
+ axisMax: seriesYAxis.max,
138
+ getDataY: (p) => p.data.y,
139
+ });
132
140
  const result = {
133
141
  annotations,
134
142
  points,
@@ -5,6 +5,7 @@ import get from 'lodash/get';
5
5
  import { block } from '../../../utils';
6
6
  import { getLineDashArray } from '../../utils';
7
7
  import { renderAnnotations } from '../annotation';
8
+ import { renderDataLabels } from '../data-labels';
8
9
  import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
9
10
  import { setActiveState } from '../utils';
10
11
  const b = block('line');
@@ -17,7 +18,7 @@ export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
17
18
  const hoverOptions = get(seriesOptions, 'line.states.hover');
18
19
  const inactiveOptions = get(seriesOptions, 'line.states.inactive');
19
20
  const line = lineGenerator()
20
- .defined((d) => d.y !== null && d.x !== null)
21
+ .defined((d) => d.y !== null && d.x !== null && !d.hiddenInLine)
21
22
  .x((d) => d.x)
22
23
  .y((d) => d.y);
23
24
  plotSvgElement.selectAll('*').remove();
@@ -38,18 +39,11 @@ export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
38
39
  const dataLabels = preparedData.reduce((acc, d) => {
39
40
  return acc.concat(d.svgLabels);
40
41
  }, []);
41
- const labelsSelection = plotSvgElement
42
- .selectAll('text')
43
- .data(dataLabels)
44
- .join('text')
45
- .html((d) => d.text)
46
- .attr('class', b('label'))
47
- .attr('x', (d) => d.x)
48
- .attr('y', (d) => d.y)
49
- .attr('text-anchor', (d) => d.textAnchor)
50
- .style('font-size', (d) => d.style.fontSize)
51
- .style('font-weight', (d) => d.style.fontWeight || null)
52
- .style('fill', (d) => d.style.fontColor || null);
42
+ const labelsSelection = renderDataLabels({
43
+ container: plotSvgElement,
44
+ data: dataLabels,
45
+ className: b('label'),
46
+ });
53
47
  const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
54
48
  const markerSelection = markersSvgElement
55
49
  .selectAll('marker')
@@ -2,12 +2,13 @@ import type { HtmlItem, LabelData, LineSeriesData, LineSeriesLineBaseStyle } fro
2
2
  import type { DashStyle, LineCap, LineJoin } from '../../constants';
3
3
  import type { AnnotationAnchor, PreparedAnnotation, PreparedLineSeries } from '../../series/types';
4
4
  export type PointData = {
5
- x: number | null;
6
- y: number | null;
7
- data: LineSeriesData;
8
- series: PreparedLineSeries;
9
5
  annotation?: PreparedAnnotation;
10
6
  color?: string;
7
+ data: LineSeriesData;
8
+ hiddenInLine?: boolean;
9
+ series: PreparedLineSeries;
10
+ x: number | null;
11
+ y: number | null;
11
12
  };
12
13
  export type MarkerPointData = PointData & {
13
14
  y: number;
@@ -3,6 +3,7 @@ import { select } from 'd3-selection';
3
3
  import { line } from 'd3-shape';
4
4
  import get from 'lodash/get';
5
5
  import { block } from '../../../utils';
6
+ import { renderDataLabels } from '../../shapes/data-labels';
6
7
  import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../../shapes/marker';
7
8
  import { setActiveState } from '../../shapes/utils';
8
9
  const b = block('radar');
@@ -59,18 +60,13 @@ export function renderRadar(elements, preparedData, seriesOptions, dispatcher) {
59
60
  .join('g')
60
61
  .call(renderMarker);
61
62
  // Render labels
62
- radarSelection
63
- .selectAll('text')
64
- .data((radarData) => radarData.labels)
65
- .join('text')
66
- .html((d) => d.text)
67
- .attr('class', b('label'))
68
- .attr('x', (d) => d.x)
69
- .attr('y', (d) => d.y)
70
- .attr('text-anchor', (d) => d.textAnchor)
71
- .style('font-size', (d) => d.style.fontSize)
72
- .style('font-weight', (d) => d.style.fontWeight || null)
73
- .style('fill', (d) => d.style.fontColor || null);
63
+ radarSelection.each(function (radarData) {
64
+ renderDataLabels({
65
+ container: select(this),
66
+ data: radarData.labels,
67
+ className: b('label'),
68
+ });
69
+ });
74
70
  // Handle hover events
75
71
  const eventName = `hover-shape.radar`;
76
72
  const hoverOptions = get(seriesOptions, 'radar.states.hover');
@@ -1,5 +1,6 @@
1
1
  import { select } from 'd3-selection';
2
2
  import { block } from '../../../utils';
3
+ import { renderDataLabels } from '../data-labels';
3
4
  const b = block('sankey');
4
5
  export function renderSankey(elements, preparedData, _seriesOptions, dispatcher) {
5
6
  const svgElement = select(elements.plot);
@@ -28,18 +29,11 @@ export function renderSankey(elements, preparedData, _seriesOptions, dispatcher)
28
29
  .attr('stroke', (d) => d.color)
29
30
  .attr('stroke-width', (d) => d.strokeWidth);
30
31
  // dataLabels
31
- svgElement
32
- .append('g')
33
- .selectAll()
34
- .data(preparedData.labels)
35
- .join('text')
36
- .html((d) => d.text)
37
- .attr('class', b('label'))
38
- .attr('x', (d) => d.x)
39
- .attr('y', (d) => d.y)
40
- .attr('dy', '0.35em')
41
- .attr('text-anchor', (d) => d.textAnchor)
42
- .attr('fill', (d) => { var _a; return (_a = d.style.fontColor) !== null && _a !== void 0 ? _a : null; });
32
+ renderDataLabels({
33
+ container: svgElement.append('g'),
34
+ data: preparedData.labels,
35
+ className: b('label'),
36
+ }).attr('dy', '0.35em');
43
37
  const eventName = `hover-shape.sankey`;
44
38
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on(eventName, (_data) => {
45
39
  // no-op hover handler
@@ -22,6 +22,30 @@ export declare function getYValue(args: {
22
22
  yAxis: PreparedYAxis;
23
23
  yScale: ChartScale;
24
24
  }): number | null;
25
+ /**
26
+ * Hides out-of-range points from line/area path generators via `hiddenInLine`.
27
+ * Neighbors of in-range points are kept as anchors so the path retains its
28
+ * slope at the plot edges instead of stopping abruptly at the visible point.
29
+ *
30
+ * Pixel check alone is not enough: a degenerate/clamped scale domain (see the
31
+ * y-scale.ts guard) can map out-of-range data into the plot rectangle, leaving
32
+ * phantom hover targets. Pass `axisMin`/`axisMax` + `getDataY` to also reject
33
+ * points whose raw value is outside the user's intended range.
34
+ *
35
+ * Stacked shapes must NOT pass the data check — `point.data.y` is the unstacked
36
+ * value and doesn't reflect where the point actually lands on the plot.
37
+ */
38
+ export declare function markHiddenPointsOutOfYRange<P extends {
39
+ y: number | null;
40
+ hiddenInLine?: boolean;
41
+ }>(args: {
42
+ points: P[];
43
+ yScale: ChartScale;
44
+ yAxisTop: number;
45
+ axisMin?: number;
46
+ axisMax?: number;
47
+ getDataY?: (point: P) => unknown;
48
+ }): void;
25
49
  export declare function shapeKey(d: unknown): string | number;
26
50
  export declare function setActiveState<T extends {
27
51
  active?: boolean;
@@ -53,6 +53,54 @@ export function getYValue(args) {
53
53
  }
54
54
  return point.y === null ? null : yLinearScale(point.y);
55
55
  }
56
+ // Slack for d3 scale rounding (a `500` edge can come back as `499.9999`).
57
+ // Half a pixel matches the ±0.5 of a 1px centered stroke and far exceeds any
58
+ // plausible float error.
59
+ const Y_RANGE_PIXEL_TOLERANCE = 0.5;
60
+ /**
61
+ * Hides out-of-range points from line/area path generators via `hiddenInLine`.
62
+ * Neighbors of in-range points are kept as anchors so the path retains its
63
+ * slope at the plot edges instead of stopping abruptly at the visible point.
64
+ *
65
+ * Pixel check alone is not enough: a degenerate/clamped scale domain (see the
66
+ * y-scale.ts guard) can map out-of-range data into the plot rectangle, leaving
67
+ * phantom hover targets. Pass `axisMin`/`axisMax` + `getDataY` to also reject
68
+ * points whose raw value is outside the user's intended range.
69
+ *
70
+ * Stacked shapes must NOT pass the data check — `point.data.y` is the unstacked
71
+ * value and doesn't reflect where the point actually lands on the plot.
72
+ */
73
+ export function markHiddenPointsOutOfYRange(args) {
74
+ const { points, yScale, yAxisTop, axisMin, axisMax, getDataY } = args;
75
+ const [yRangeA, yRangeB] = yScale.range();
76
+ const yMinPx = yAxisTop + Math.min(yRangeA, yRangeB);
77
+ const yMaxPx = yAxisTop + Math.max(yRangeA, yRangeB);
78
+ const hasDataBoundsCheck = Boolean(getDataY) && (typeof axisMin === 'number' || typeof axisMax === 'number');
79
+ const isInRange = (point) => {
80
+ if (point.y === null) {
81
+ return false;
82
+ }
83
+ if (hasDataBoundsCheck) {
84
+ const dataY = getDataY === null || getDataY === void 0 ? void 0 : getDataY(point);
85
+ if (typeof dataY === 'number') {
86
+ if (typeof axisMin === 'number' && dataY < axisMin) {
87
+ return false;
88
+ }
89
+ if (typeof axisMax === 'number' && dataY > axisMax) {
90
+ return false;
91
+ }
92
+ }
93
+ }
94
+ return (point.y >= yMinPx - Y_RANGE_PIXEL_TOLERANCE &&
95
+ point.y <= yMaxPx + Y_RANGE_PIXEL_TOLERANCE);
96
+ };
97
+ const inRange = points.map(isInRange);
98
+ for (let idx = 0; idx < points.length; idx++) {
99
+ if (!inRange[idx] && !inRange[idx - 1] && !inRange[idx + 1]) {
100
+ points[idx].hiddenInLine = true;
101
+ }
102
+ }
103
+ }
56
104
  export function shapeKey(d) {
57
105
  return d.id || -1;
58
106
  }
@@ -5,6 +5,7 @@ import get from 'lodash/get';
5
5
  import { block } from '../../../utils';
6
6
  import { DASH_STYLE } from '../../constants';
7
7
  import { filterOverlappingLabels, getLineDashArray, getWaterfallPointColor } from '../../utils';
8
+ import { renderDataLabels } from '../data-labels';
8
9
  const b = block('waterfall');
9
10
  export function renderWaterfall(elements, preparedData, seriesOptions, allowOverlapDataLabels, dispatcher) {
10
11
  const svgElement = select(elements.plot);
@@ -28,18 +29,11 @@ export function renderWaterfall(elements, preparedData, seriesOptions, allowOver
28
29
  if (!allowOverlapDataLabels) {
29
30
  dataLabels = filterOverlappingLabels(dataLabels);
30
31
  }
31
- const labelSelection = svgElement
32
- .selectAll('text')
33
- .data(dataLabels)
34
- .join('text')
35
- .html((d) => d.text)
36
- .attr('class', b('label'))
37
- .attr('x', (d) => d.x)
38
- .attr('y', (d) => d.y)
39
- .attr('text-anchor', (d) => d.textAnchor)
40
- .style('font-size', (d) => d.style.fontSize)
41
- .style('font-weight', (d) => d.style.fontWeight || null)
42
- .style('fill', (d) => d.style.fontColor || null);
32
+ const labelSelection = renderDataLabels({
33
+ container: svgElement,
34
+ data: dataLabels,
35
+ className: b('label'),
36
+ });
43
37
  // Add the connector line between bars
44
38
  svgElement
45
39
  .selectAll(connectorSelector)
@@ -4,6 +4,7 @@ import get from 'lodash/get';
4
4
  import { block } from '../../../utils';
5
5
  import { getRectPath } from '../../shapes/utils';
6
6
  import { getLineDashArray } from '../../utils';
7
+ import { renderDataLabels } from '../data-labels';
7
8
  const b = block('x-range');
8
9
  export function renderXRange(elements, preparedData, seriesOptions, dispatcher) {
9
10
  const svgElement = select(elements.plot);
@@ -48,20 +49,13 @@ export function renderXRange(elements, preparedData, seriesOptions, dispatcher)
48
49
  .attr('opacity', (d) => { var _a; return (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity; })
49
50
  .attr('pointer-events', 'none');
50
51
  const svgLabels = preparedData.flatMap((d) => d.svgLabels);
51
- svgElement
52
- .selectAll(`text.${b('label')}`)
53
- .data(svgLabels)
54
- .join('text')
55
- .attr('class', b('label'))
56
- .attr('x', (d) => d.x)
57
- .attr('y', (d) => d.y)
58
- .attr('text-anchor', (d) => d.textAnchor)
52
+ renderDataLabels({
53
+ container: svgElement,
54
+ data: svgLabels,
55
+ className: b('label'),
56
+ })
59
57
  .attr('dominant-baseline', 'central')
60
- .attr('pointer-events', 'none')
61
- .style('font-size', (d) => d.style.fontSize)
62
- .style('font-weight', (d) => d.style.fontWeight || null)
63
- .style('fill', (d) => d.style.fontColor || null)
64
- .html((d) => d.text);
58
+ .attr('pointer-events', 'none');
65
59
  const hoverOptions = get(seriesOptions, 'x-range.states.hover');
66
60
  const inactiveOptions = get(seriesOptions, 'x-range.states.inactive');
67
61
  function handleShapeHover(data) {
@@ -12,7 +12,6 @@ export type CustomFormat = {
12
12
  type: 'custom';
13
13
  formatter: (args: {
14
14
  value: unknown;
15
- formattedValue?: string;
16
15
  }) => string;
17
16
  };
18
17
  /**
@@ -20,7 +19,10 @@ export type CustomFormat = {
20
19
  *
21
20
  * - `{ type: 'number' }` — numeric formatting with optional precision, units, percent display, etc.
22
21
  * See [FormatNumberOptions](https://gravity-ui.github.io/charts/pages/api/Utilities/interfaces/FormatNumberOptions.html) for all available options.
23
- * - `{ type: 'date' }` — date/time formatting
22
+ * - `{ type: 'date' }` — date/time formatting.
23
+ * - `{ type: 'custom' }` — user-defined formatter function. Receives the raw `value`
24
+ * and returns the display string. Use it when the built-in number/date formatters
25
+ * are not enough (e.g. bytes → KB/MB/GB, currency with locale, etc.).
24
26
  * @example
25
27
  * // Two decimal places, shown as percent
26
28
  * { type: 'number', precision: 2, format: 'percent' }
@@ -30,8 +32,20 @@ export type CustomFormat = {
30
32
  * @example
31
33
  * // Date value (Unix ms) formatted as "17 October 2025"
32
34
  * { type: 'date', format: 'DD MMMM YYYY' }
35
+ * @example
36
+ * // Bytes → human-readable size
37
+ * {
38
+ * type: 'custom',
39
+ * formatter: ({value}) => {
40
+ * const bytes = Number(value);
41
+ * if (!Number.isFinite(bytes)) return String(value);
42
+ * const units = ['B', 'KB', 'MB', 'GB', 'TB'];
43
+ * const i = Math.min(units.length - 1, Math.floor(Math.log(Math.abs(bytes) || 1) / Math.log(1024)));
44
+ * return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`;
45
+ * },
46
+ * }
33
47
  */
34
- export type ValueFormat = NumberFormat | DateFormat;
48
+ export type ValueFormat = NumberFormat | DateFormat | CustomFormat;
35
49
  export interface BaseDataLabels {
36
50
  /**
37
51
  * Enable or disable the data labels
@@ -5,7 +5,7 @@ import type { AreaSeries, AreaSeriesData } from './area';
5
5
  import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
6
6
  import type { BarXSeries, BarXSeriesData } from './bar-x';
7
7
  import type { BarYSeries, BarYSeriesData } from './bar-y';
8
- import type { CustomFormat, ValueFormat } from './base';
8
+ import type { ValueFormat } from './base';
9
9
  import type { FunnelSeries, FunnelSeriesData } from './funnel';
10
10
  import type { HeatmapSeries, HeatmapSeriesData } from './heatmap';
11
11
  import type { LineSeries, LineSeriesData } from './line';
@@ -107,7 +107,7 @@ export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
107
107
  xAxis?: ChartXAxis | null;
108
108
  yAxis?: ChartYAxis;
109
109
  /** Formatting settings for tooltip header row (includes computed default). */
110
- headerFormat?: ValueFormat | CustomFormat;
110
+ headerFormat?: ValueFormat;
111
111
  }
112
112
  export interface ChartTooltipTotalsAggregationArgs<T = MeaningfulAny> extends ChartTooltipRendererArgs<T> {
113
113
  }
@@ -167,7 +167,7 @@ export interface ChartTooltip<T = MeaningfulAny> {
167
167
  /** Formatting settings for tooltip value. */
168
168
  valueFormat?: ValueFormat;
169
169
  /** Formatting settings for tooltip header row. */
170
- headerFormat?: ValueFormat | CustomFormat;
170
+ headerFormat?: ValueFormat;
171
171
  /** Settings for totals block in tooltip */
172
172
  totals?: {
173
173
  /**
@@ -1,40 +1 @@
1
- export interface FormatOptions {
2
- /**
3
- * Number of decimal places to display.
4
- * Use `'auto'` to determine precision automatically based on the value magnitude.
5
- */
6
- precision?: number | 'auto';
7
- /** When `true`, inserts a thousands separator (e.g. `1 500 000`). */
8
- showRankDelimiter?: boolean;
9
- /**
10
- * BCP 47 language tag used for locale-aware formatting (e.g. `'en'`, `'ru'`).
11
- * Defaults to the application locale when omitted.
12
- */
13
- lang?: string;
14
- /** Internal rendering hint for axis label layout. Not intended for public use. */
15
- labelMode?: string;
16
- }
17
- export interface FormatNumberOptions extends FormatOptions {
18
- /**
19
- * Display mode for the numeric value.
20
- * - `'number'` — plain number (default).
21
- * - `'percent'` — value is multiplied by 100 and rendered with a `%` suffix.
22
- */
23
- format?: 'number' | 'percent';
24
- /** Factor applied to the value before formatting. For example, `multiplier: 1000` converts seconds to milliseconds. */
25
- multiplier?: number;
26
- /** String prepended to the formatted value (e.g. `'$'`). */
27
- prefix?: string;
28
- /** String appended to the formatted value (e.g. `' USD'`). */
29
- postfix?: string;
30
- /**
31
- * Compact unit suffix applied to large numbers.
32
- * - `'auto'` — picks the most appropriate unit automatically (`k`, `m`, `b`, `t`).
33
- * - `'k'` — thousands (÷ 1 000).
34
- * - `'m'` — millions (÷ 1 000 000).
35
- * - `'b'` — billions (÷ 1 000 000 000).
36
- * - `'t'` — trillions (÷ 1 000 000 000 000).
37
- * - `null` — no unit suffix.
38
- */
39
- unit?: 'auto' | 'k' | 'm' | 'b' | 't' | null;
40
- }
1
+ export type { FormatNumberOptions, FormatOptions, FormatUnitScale, FormatUnitScaleEntry, } from '../../libs/format-number/types';
@@ -1,9 +1,9 @@
1
1
  import type { AxisDomain } from 'd3-axis';
2
- import type { CustomFormat, ValueFormat } from '../../types';
2
+ import type { ValueFormat } from '../../types';
3
3
  import type { PreparedAxis } from '../axes/types';
4
4
  export declare function getFormattedValue(args: {
5
5
  value: string | number | undefined | null;
6
- format?: ValueFormat | CustomFormat;
6
+ format?: ValueFormat;
7
7
  }): string;
8
8
  export declare function formatAxisTickLabel(args: {
9
9
  axis: PreparedAxis;