@gravity-ui/charts 1.51.7 → 1.52.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 (199) hide show
  1. package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +3 -2
  2. package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +1 -3
  3. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -2
  4. package/dist/cjs/components/ChartInner/useDefaultState.d.ts +3 -2
  5. package/dist/cjs/components/index.d.ts +1 -0
  6. package/dist/cjs/components/index.js +1 -0
  7. package/dist/cjs/core/series/plugin.d.ts +47 -2
  8. package/dist/cjs/core/series/types.d.ts +4 -0
  9. package/dist/cjs/core/shapes/area/prepare-data.js +29 -19
  10. package/dist/cjs/core/shapes/area/renderer.d.ts +0 -5
  11. package/dist/cjs/core/shapes/area/renderer.js +0 -75
  12. package/dist/cjs/core/shapes/area/types.d.ts +2 -8
  13. package/dist/cjs/core/shapes/bar-x/prepare-data.js +18 -7
  14. package/dist/cjs/core/shapes/bar-x/renderer.d.ts +0 -1
  15. package/dist/cjs/core/shapes/bar-x/renderer.js +0 -18
  16. package/dist/cjs/core/shapes/bar-x/types.d.ts +2 -1
  17. package/dist/cjs/core/shapes/bar-y/get-tooltip-data.js +4 -2
  18. package/dist/cjs/core/shapes/bar-y/prepare-data.js +8 -2
  19. package/dist/cjs/core/shapes/funnel/prepare-data.js +121 -68
  20. package/dist/cjs/core/shapes/heatmap/prepare-data.js +11 -2
  21. package/dist/cjs/core/shapes/line/prepare-data.js +27 -17
  22. package/dist/cjs/core/shapes/line/renderer.d.ts +0 -5
  23. package/dist/cjs/core/shapes/line/renderer.js +0 -75
  24. package/dist/cjs/core/shapes/line/types.d.ts +2 -8
  25. package/dist/cjs/core/shapes/marker.d.ts +30 -0
  26. package/dist/cjs/core/shapes/marker.js +68 -0
  27. package/dist/cjs/core/shapes/pie/prepare-data.js +24 -9
  28. package/dist/cjs/core/shapes/radar/prepare-data.js +3 -0
  29. package/dist/cjs/core/shapes/sankey/prepare-data.js +10 -1
  30. package/dist/cjs/core/shapes/scatter/prepare-data.js +8 -1
  31. package/dist/cjs/core/shapes/scatter/renderer.js +3 -2
  32. package/dist/cjs/core/shapes/scatter/types.d.ts +1 -1
  33. package/dist/cjs/core/shapes/treemap/prepare-data.js +9 -1
  34. package/dist/cjs/core/shapes/types.d.ts +35 -0
  35. package/dist/cjs/core/shapes/waterfall/prepare-data.js +5 -2
  36. package/dist/cjs/core/shapes/x-range/prepare-data.js +7 -2
  37. package/dist/cjs/core/types/chart/base.d.ts +22 -2
  38. package/dist/cjs/core/types/chart/funnel.d.ts +25 -1
  39. package/dist/cjs/core/types/chart/tooltip.d.ts +6 -1
  40. package/dist/cjs/core/utils/data-labels.d.ts +34 -0
  41. package/dist/cjs/core/utils/data-labels.js +26 -0
  42. package/dist/cjs/core/utils/get-closest-data.d.ts +2 -2
  43. package/dist/cjs/core/utils/get-closest-data.js +14 -34
  44. package/dist/cjs/core/utils/tooltip-helpers.d.ts +16 -0
  45. package/dist/cjs/core/utils/tooltip-helpers.js +12 -0
  46. package/dist/cjs/hooks/useShapes/AnnotationLayer.d.ts +9 -0
  47. package/dist/cjs/hooks/useShapes/AnnotationLayer.js +17 -0
  48. package/dist/cjs/hooks/useShapes/HoverMarkerLayer.d.ts +10 -0
  49. package/dist/cjs/hooks/useShapes/HoverMarkerLayer.js +22 -0
  50. package/dist/cjs/hooks/useShapes/MarkerLayer.d.ts +7 -0
  51. package/dist/cjs/hooks/useShapes/MarkerLayer.js +12 -0
  52. package/dist/cjs/hooks/useShapes/SeriesShapes.d.ts +18 -0
  53. package/dist/cjs/hooks/useShapes/SeriesShapes.js +32 -0
  54. package/dist/cjs/hooks/useShapes/index.d.ts +5 -18
  55. package/dist/cjs/hooks/useShapes/index.js +39 -229
  56. package/dist/cjs/index.d.ts +0 -1
  57. package/dist/cjs/index.js +0 -1
  58. package/dist/cjs/plugins/area/index.js +42 -0
  59. package/dist/cjs/plugins/bar-x/index.js +42 -0
  60. package/dist/cjs/plugins/bar-y/index.js +26 -0
  61. package/dist/cjs/plugins/funnel/index.js +18 -0
  62. package/dist/cjs/plugins/funnel/prepare.js +17 -12
  63. package/dist/cjs/plugins/heatmap/index.js +23 -0
  64. package/dist/cjs/plugins/line/index.js +28 -0
  65. package/dist/cjs/plugins/pie/index.js +18 -0
  66. package/dist/cjs/plugins/radar/index.js +18 -0
  67. package/dist/cjs/plugins/sankey/index.js +18 -0
  68. package/dist/cjs/plugins/scatter/index.js +26 -0
  69. package/dist/cjs/plugins/treemap/index.js +18 -0
  70. package/dist/cjs/plugins/waterfall/index.js +39 -0
  71. package/dist/cjs/plugins/x-range/index.js +25 -0
  72. package/dist/cjs/setup-jsdom.d.ts +0 -1
  73. package/dist/cjs/setup-jsdom.js +1 -1
  74. package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +3 -2
  75. package/dist/esm/components/ChartInner/useChartInnerHandlers.js +1 -3
  76. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -2
  77. package/dist/esm/components/ChartInner/useDefaultState.d.ts +3 -2
  78. package/dist/esm/components/index.d.ts +1 -0
  79. package/dist/esm/components/index.js +1 -0
  80. package/dist/esm/core/series/plugin.d.ts +47 -2
  81. package/dist/esm/core/series/types.d.ts +4 -0
  82. package/dist/esm/core/shapes/area/prepare-data.js +29 -19
  83. package/dist/esm/core/shapes/area/renderer.d.ts +0 -5
  84. package/dist/esm/core/shapes/area/renderer.js +0 -75
  85. package/dist/esm/core/shapes/area/types.d.ts +2 -8
  86. package/dist/esm/core/shapes/bar-x/prepare-data.js +18 -7
  87. package/dist/esm/core/shapes/bar-x/renderer.d.ts +0 -1
  88. package/dist/esm/core/shapes/bar-x/renderer.js +0 -18
  89. package/dist/esm/core/shapes/bar-x/types.d.ts +2 -1
  90. package/dist/esm/core/shapes/bar-y/get-tooltip-data.js +4 -2
  91. package/dist/esm/core/shapes/bar-y/prepare-data.js +8 -2
  92. package/dist/esm/core/shapes/funnel/prepare-data.js +121 -68
  93. package/dist/esm/core/shapes/heatmap/prepare-data.js +11 -2
  94. package/dist/esm/core/shapes/line/prepare-data.js +27 -17
  95. package/dist/esm/core/shapes/line/renderer.d.ts +0 -5
  96. package/dist/esm/core/shapes/line/renderer.js +0 -75
  97. package/dist/esm/core/shapes/line/types.d.ts +2 -8
  98. package/dist/esm/core/shapes/marker.d.ts +30 -0
  99. package/dist/esm/core/shapes/marker.js +68 -0
  100. package/dist/esm/core/shapes/pie/prepare-data.js +24 -9
  101. package/dist/esm/core/shapes/radar/prepare-data.js +3 -0
  102. package/dist/esm/core/shapes/sankey/prepare-data.js +10 -1
  103. package/dist/esm/core/shapes/scatter/prepare-data.js +8 -1
  104. package/dist/esm/core/shapes/scatter/renderer.js +3 -2
  105. package/dist/esm/core/shapes/scatter/types.d.ts +1 -1
  106. package/dist/esm/core/shapes/treemap/prepare-data.js +9 -1
  107. package/dist/esm/core/shapes/types.d.ts +35 -0
  108. package/dist/esm/core/shapes/waterfall/prepare-data.js +5 -2
  109. package/dist/esm/core/shapes/x-range/prepare-data.js +7 -2
  110. package/dist/esm/core/types/chart/base.d.ts +22 -2
  111. package/dist/esm/core/types/chart/funnel.d.ts +25 -1
  112. package/dist/esm/core/types/chart/tooltip.d.ts +6 -1
  113. package/dist/esm/core/utils/data-labels.d.ts +34 -0
  114. package/dist/esm/core/utils/data-labels.js +26 -0
  115. package/dist/esm/core/utils/get-closest-data.d.ts +2 -2
  116. package/dist/esm/core/utils/get-closest-data.js +14 -34
  117. package/dist/esm/core/utils/tooltip-helpers.d.ts +16 -0
  118. package/dist/esm/core/utils/tooltip-helpers.js +12 -0
  119. package/dist/esm/hooks/useShapes/AnnotationLayer.d.ts +9 -0
  120. package/dist/esm/hooks/useShapes/AnnotationLayer.js +17 -0
  121. package/dist/esm/hooks/useShapes/HoverMarkerLayer.d.ts +10 -0
  122. package/dist/esm/hooks/useShapes/HoverMarkerLayer.js +22 -0
  123. package/dist/esm/hooks/useShapes/MarkerLayer.d.ts +7 -0
  124. package/dist/esm/hooks/useShapes/MarkerLayer.js +12 -0
  125. package/dist/esm/hooks/useShapes/SeriesShapes.d.ts +18 -0
  126. package/dist/esm/hooks/useShapes/SeriesShapes.js +32 -0
  127. package/dist/esm/hooks/useShapes/index.d.ts +5 -18
  128. package/dist/esm/hooks/useShapes/index.js +39 -229
  129. package/dist/esm/index.d.ts +0 -1
  130. package/dist/esm/index.js +0 -1
  131. package/dist/esm/plugins/area/index.js +42 -0
  132. package/dist/esm/plugins/bar-x/index.js +42 -0
  133. package/dist/esm/plugins/bar-y/index.js +26 -0
  134. package/dist/esm/plugins/funnel/index.js +18 -0
  135. package/dist/esm/plugins/funnel/prepare.js +17 -12
  136. package/dist/esm/plugins/heatmap/index.js +23 -0
  137. package/dist/esm/plugins/line/index.js +28 -0
  138. package/dist/esm/plugins/pie/index.js +18 -0
  139. package/dist/esm/plugins/radar/index.js +18 -0
  140. package/dist/esm/plugins/sankey/index.js +18 -0
  141. package/dist/esm/plugins/scatter/index.js +26 -0
  142. package/dist/esm/plugins/treemap/index.js +18 -0
  143. package/dist/esm/plugins/waterfall/index.js +39 -0
  144. package/dist/esm/plugins/x-range/index.js +25 -0
  145. package/dist/esm/setup-jsdom.d.ts +0 -1
  146. package/dist/esm/setup-jsdom.js +1 -1
  147. package/package.json +2 -3
  148. package/dist/cjs/hooks/useShapes/area/index.d.ts +0 -15
  149. package/dist/cjs/hooks/useShapes/area/index.js +0 -52
  150. package/dist/cjs/hooks/useShapes/bar-x/index.d.ts +0 -16
  151. package/dist/cjs/hooks/useShapes/bar-x/index.js +0 -45
  152. package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +0 -13
  153. package/dist/cjs/hooks/useShapes/bar-y/index.js +0 -19
  154. package/dist/cjs/hooks/useShapes/funnel/index.d.ts +0 -13
  155. package/dist/cjs/hooks/useShapes/funnel/index.js +0 -21
  156. package/dist/cjs/hooks/useShapes/heatmap/index.d.ts +0 -13
  157. package/dist/cjs/hooks/useShapes/heatmap/index.js +0 -20
  158. package/dist/cjs/hooks/useShapes/line/index.d.ts +0 -15
  159. package/dist/cjs/hooks/useShapes/line/index.js +0 -38
  160. package/dist/cjs/hooks/useShapes/pie/index.d.ts +0 -12
  161. package/dist/cjs/hooks/useShapes/pie/index.js +0 -20
  162. package/dist/cjs/hooks/useShapes/radar/index.d.ts +0 -12
  163. package/dist/cjs/hooks/useShapes/radar/index.js +0 -19
  164. package/dist/cjs/hooks/useShapes/sankey/index.d.ts +0 -12
  165. package/dist/cjs/hooks/useShapes/sankey/index.js +0 -18
  166. package/dist/cjs/hooks/useShapes/scatter/index.d.ts +0 -13
  167. package/dist/cjs/hooks/useShapes/scatter/index.js +0 -22
  168. package/dist/cjs/hooks/useShapes/treemap/index.d.ts +0 -12
  169. package/dist/cjs/hooks/useShapes/treemap/index.js +0 -18
  170. package/dist/cjs/hooks/useShapes/waterfall/index.d.ts +0 -14
  171. package/dist/cjs/hooks/useShapes/waterfall/index.js +0 -31
  172. package/dist/cjs/hooks/useShapes/x-range/index.d.ts +0 -14
  173. package/dist/cjs/hooks/useShapes/x-range/index.js +0 -20
  174. package/dist/esm/hooks/useShapes/area/index.d.ts +0 -15
  175. package/dist/esm/hooks/useShapes/area/index.js +0 -52
  176. package/dist/esm/hooks/useShapes/bar-x/index.d.ts +0 -16
  177. package/dist/esm/hooks/useShapes/bar-x/index.js +0 -45
  178. package/dist/esm/hooks/useShapes/bar-y/index.d.ts +0 -13
  179. package/dist/esm/hooks/useShapes/bar-y/index.js +0 -19
  180. package/dist/esm/hooks/useShapes/funnel/index.d.ts +0 -13
  181. package/dist/esm/hooks/useShapes/funnel/index.js +0 -21
  182. package/dist/esm/hooks/useShapes/heatmap/index.d.ts +0 -13
  183. package/dist/esm/hooks/useShapes/heatmap/index.js +0 -20
  184. package/dist/esm/hooks/useShapes/line/index.d.ts +0 -15
  185. package/dist/esm/hooks/useShapes/line/index.js +0 -38
  186. package/dist/esm/hooks/useShapes/pie/index.d.ts +0 -12
  187. package/dist/esm/hooks/useShapes/pie/index.js +0 -20
  188. package/dist/esm/hooks/useShapes/radar/index.d.ts +0 -12
  189. package/dist/esm/hooks/useShapes/radar/index.js +0 -19
  190. package/dist/esm/hooks/useShapes/sankey/index.d.ts +0 -12
  191. package/dist/esm/hooks/useShapes/sankey/index.js +0 -18
  192. package/dist/esm/hooks/useShapes/scatter/index.d.ts +0 -13
  193. package/dist/esm/hooks/useShapes/scatter/index.js +0 -22
  194. package/dist/esm/hooks/useShapes/treemap/index.d.ts +0 -12
  195. package/dist/esm/hooks/useShapes/treemap/index.js +0 -18
  196. package/dist/esm/hooks/useShapes/waterfall/index.d.ts +0 -14
  197. package/dist/esm/hooks/useShapes/waterfall/index.js +0 -31
  198. package/dist/esm/hooks/useShapes/x-range/index.d.ts +0 -14
  199. package/dist/esm/hooks/useShapes/x-range/index.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { path } from 'd3-path';
2
- import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, } from '../../utils';
2
+ import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, isPointDataLabelEnabled, } from '../../utils';
3
3
  function getLineConnectorPaths(args) {
4
4
  const { points } = args;
5
5
  const leftPath = path();
@@ -19,7 +19,7 @@ function getAreaConnectorPath(args) {
19
19
  return p;
20
20
  }
21
21
  export async function prepareFunnelData(args) {
22
- var _a, _b, _c, _d, _e;
22
+ var _a, _b, _c, _d;
23
23
  const { series, boundsWidth, boundsHeight } = args;
24
24
  const items = [];
25
25
  const svgLabels = [];
@@ -33,75 +33,59 @@ export async function prepareFunnelData(args) {
33
33
  })) !== null && _b !== void 0 ? _b : 0;
34
34
  const itemHeight = (boundsHeight - connectorHeight * (series.length - 1)) / series.length;
35
35
  const getTextSize = getTextSizeFn({ style: series[0].dataLabels.style });
36
- const getSegmentY = (index) => {
37
- return index * (itemHeight + connectorHeight);
38
- };
39
- let segmentLeftOffset = 0;
40
- let segmentRightOffset = 0;
36
+ const getSegmentY = (index) => index * (itemHeight + connectorHeight);
37
+ // measure labels and accumulate max outside-label widths per side.
38
+ let rawLeftOffset = 0;
39
+ let rawRightOffset = 0;
40
+ const labelInfos = [];
41
41
  for (let index = 0; index < series.length; index++) {
42
42
  const s = series[index];
43
- if (s.dataLabels.enabled) {
44
- const d = s.data;
45
- const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
46
- const { width, height, hangingOffset } = s.dataLabels.html
47
- ? await getLabelsSize({
48
- labels: [labelContent],
49
- style: s.dataLabels.style,
50
- html: true,
51
- }).then((size) => ({
52
- width: size.maxWidth,
53
- height: size.maxHeight,
54
- hangingOffset: 0,
55
- }))
56
- : await getTextSize(labelContent);
57
- let x;
58
- switch (s.dataLabels.align) {
59
- case 'left': {
60
- x = 0;
61
- segmentLeftOffset = Math.max(segmentLeftOffset, width);
62
- break;
63
- }
64
- case 'right': {
65
- x = boundsWidth - width;
66
- segmentRightOffset = Math.max(segmentRightOffset, width);
67
- break;
68
- }
69
- case 'center': {
70
- x = boundsWidth / 2 - width / 2;
71
- break;
72
- }
73
- }
74
- const y = getSegmentY(index) + itemHeight / 2 - height / 2 + hangingOffset;
75
- if (s.dataLabels.html) {
76
- htmlLabels.push({
77
- x,
78
- y,
79
- content: labelContent,
80
- size: { width, height },
81
- style: s.dataLabels.style,
82
- });
43
+ if (!isPointDataLabelEnabled({ data: s.data, series: s })) {
44
+ labelInfos.push(null);
45
+ continue;
46
+ }
47
+ const d = s.data;
48
+ const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
49
+ const { width, height, hangingOffset } = s.dataLabels.html
50
+ ? await getLabelsSize({
51
+ labels: [labelContent],
52
+ style: s.dataLabels.style,
53
+ html: true,
54
+ }).then((size) => ({
55
+ width: size.maxWidth,
56
+ height: size.maxHeight,
57
+ hangingOffset: 0,
58
+ }))
59
+ : await getTextSize(labelContent);
60
+ labelInfos.push({ text: labelContent, width, height, hangingOffset, series: s });
61
+ const { inside, align, padding } = s.dataLabels;
62
+ if (!inside) {
63
+ // Minimum offset so this label stays within the plot boundary.
64
+ // Accounts for the fact that narrower segments are already indented from the edge.
65
+ const ratio = s.data.value / maxValue;
66
+ const minOffset = (2 * (width + padding) - boundsWidth * (1 - ratio)) / (1 + ratio);
67
+ if (align === 'left') {
68
+ rawLeftOffset = Math.max(rawLeftOffset, minOffset);
83
69
  }
84
- else {
85
- svgLabels.push({
86
- x,
87
- y,
88
- text: labelContent,
89
- style: s.dataLabels.style,
90
- size: { width, height, hangingOffset },
91
- textAnchor: 'start',
92
- series: s,
93
- });
70
+ else if (align === 'right') {
71
+ rawRightOffset = Math.max(rawRightOffset, minOffset);
94
72
  }
95
73
  }
96
74
  }
75
+ // reserveSpace=true → inset only the labelled side so labels don't overlap segments.
76
+ // reserveSpace=false → no inset; labels overlap segments.
77
+ const { reserveSpace } = series[0].dataLabels;
78
+ const segmentLeftOffset = reserveSpace ? rawLeftOffset : 0;
79
+ const segmentRightOffset = reserveSpace ? rawRightOffset : 0;
80
+ // compute shapes and label positions in a single pass.
81
+ // centerX is constant across all segments — hoist it out of the loop.
97
82
  const segmentMaxWidth = boundsWidth - segmentLeftOffset - segmentRightOffset;
98
- const isTrapezoid = ((_d = series[0]) === null || _d === void 0 ? void 0 : _d.shape) === 'trapezoid';
83
+ const isTrapezoid = series[0].shape === 'trapezoid';
84
+ const centerX = segmentLeftOffset + segmentMaxWidth / 2;
99
85
  const getItemWidth = (index) => (segmentMaxWidth * series[index].data.value) / maxValue;
100
86
  for (let index = 0; index < series.length; index++) {
101
87
  const s = series[index];
102
- const d = s.data;
103
88
  const itemWidth = getItemWidth(index);
104
- const centerX = segmentLeftOffset + segmentMaxWidth / 2;
105
89
  const segmentY = getSegmentY(index);
106
90
  const isLastSegment = index === series.length - 1;
107
91
  const bottomWidth = isTrapezoid && !isLastSegment ? getItemWidth(index + 1) : itemWidth;
@@ -111,7 +95,7 @@ export async function prepareFunnelData(args) {
111
95
  [centerX + bottomWidth / 2, segmentY + itemHeight],
112
96
  [centerX - bottomWidth / 2, segmentY + itemHeight],
113
97
  ];
114
- const funnelSegment = {
98
+ const item = {
115
99
  x: centerX - itemWidth / 2,
116
100
  y: segmentY,
117
101
  width: itemWidth,
@@ -119,22 +103,22 @@ export async function prepareFunnelData(args) {
119
103
  points,
120
104
  color: s.color,
121
105
  series: s,
122
- data: d,
106
+ data: s.data,
123
107
  borderColor: '',
124
108
  borderWidth: 0,
125
109
  cursor: s.cursor,
126
110
  };
127
- items.push(funnelSegment);
111
+ items.push(item);
128
112
  const prevSeries = series[index - 1];
129
113
  const prevItem = items[index - 1];
130
- if (prevSeries && prevItem && ((_e = prevSeries.connectors) === null || _e === void 0 ? void 0 : _e.enabled)) {
114
+ if (prevSeries && prevItem && ((_d = prevSeries.connectors) === null || _d === void 0 ? void 0 : _d.enabled)) {
131
115
  // Use the actual bottom corners of the previous segment (points[3]/[2]) so that
132
116
  // trapezoid segments (whose bottom edge differs from the top edge) are handled correctly.
133
117
  const connectorPoints = [
134
118
  prevItem.points[3],
135
119
  prevItem.points[2],
136
- funnelSegment.points[1],
137
- funnelSegment.points[0],
120
+ item.points[1],
121
+ item.points[0],
138
122
  ];
139
123
  connectors.push({
140
124
  linePath: getLineConnectorPaths({ points: connectorPoints }),
@@ -147,13 +131,82 @@ export async function prepareFunnelData(args) {
147
131
  dashStyle: prevSeries.connectors.lineDashStyle,
148
132
  });
149
133
  }
134
+ const info = labelInfos[index];
135
+ if (!info)
136
+ continue;
137
+ const { text, width, height, hangingOffset } = info;
138
+ const { anchor, inside, padding } = s.dataLabels;
139
+ const y = segmentY + itemHeight / 2 - height / 2 + hangingOffset;
140
+ let x;
141
+ if (inside) {
142
+ switch (s.dataLabels.align) {
143
+ case 'left':
144
+ x = item.x + padding;
145
+ break;
146
+ case 'right':
147
+ x = item.x + item.width - width - padding;
148
+ break;
149
+ default:
150
+ x = item.x + item.width / 2 - width / 2;
151
+ break;
152
+ }
153
+ }
154
+ else {
155
+ switch (s.dataLabels.align) {
156
+ case 'left': {
157
+ const edge = 0;
158
+ if (anchor === 'plot') {
159
+ x = edge;
160
+ }
161
+ else {
162
+ x = Math.max(edge, item.x - width - padding);
163
+ }
164
+ break;
165
+ }
166
+ case 'right': {
167
+ const edge = boundsWidth - width;
168
+ if (anchor === 'plot') {
169
+ x = edge;
170
+ }
171
+ else {
172
+ x = Math.min(edge, item.x + item.width + padding);
173
+ }
174
+ break;
175
+ }
176
+ default:
177
+ x = boundsWidth / 2 - width / 2;
178
+ break;
179
+ }
180
+ }
181
+ if (s.dataLabels.html) {
182
+ htmlLabels.push({
183
+ x,
184
+ y,
185
+ content: text,
186
+ size: { width, height },
187
+ style: s.dataLabels.style,
188
+ });
189
+ }
190
+ else {
191
+ svgLabels.push({
192
+ x,
193
+ y,
194
+ text,
195
+ style: s.dataLabels.style,
196
+ size: { width, height, hangingOffset },
197
+ textAnchor: 'start',
198
+ series: s,
199
+ });
200
+ }
150
201
  }
151
- const data = {
202
+ return {
152
203
  type: 'funnel',
153
204
  items,
154
205
  svgLabels,
155
206
  htmlLabels,
156
207
  connectors,
208
+ markers: [],
209
+ getHoverMarkers: () => [],
210
+ annotations: [],
157
211
  };
158
- return data;
159
212
  }
@@ -1,4 +1,4 @@
1
- import { getDomainDataXBySeries, getDomainDataYBySeries, getFormattedValue, getLabelsSize, getTextSizeFn, getTextWithElipsis, isBandScale, } from '../../utils';
1
+ import { getDomainDataXBySeries, getDomainDataYBySeries, getFormattedValue, getLabelsSize, getTextSizeFn, getTextWithElipsis, isBandScale, isPointDataLabelEnabled, shouldPrepareSeriesDataLabels, } from '../../utils';
2
2
  import { getBandSize } from '../../utils/band-size';
3
3
  export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale, }) {
4
4
  var _a, _b, _c, _d, _e;
@@ -44,10 +44,13 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
44
44
  }, []);
45
45
  const svgDataLabels = [];
46
46
  const htmlDataLabels = [];
47
- if (series.dataLabels.enabled) {
47
+ if (shouldPrepareSeriesDataLabels(series)) {
48
48
  if (series.dataLabels.html) {
49
49
  for (let i = 0; i < heatmapItems.length; i++) {
50
50
  const item = heatmapItems[i];
51
+ if (!isPointDataLabelEnabled({ data: item.data, series })) {
52
+ continue;
53
+ }
51
54
  const labelContent = (_c = item.data.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: item.data.value, format: series.dataLabels.format });
52
55
  if (labelContent) {
53
56
  const dataLabelsStyle = Object.assign(Object.assign({}, series.dataLabels.style), { maxWidth: `${item.width}px`, maxHeight: `${item.height}px`, overflow: 'hidden' });
@@ -71,6 +74,9 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
71
74
  const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
72
75
  for (let i = 0; i < heatmapItems.length; i++) {
73
76
  const item = heatmapItems[i];
77
+ if (!isPointDataLabelEnabled({ data: item.data, series })) {
78
+ continue;
79
+ }
74
80
  const labelContent = (_e = item.data.label) !== null && _e !== void 0 ? _e : getFormattedValue({ value: item.data.value, format: series.dataLabels.format });
75
81
  if (labelContent) {
76
82
  const size = await getTextSize(labelContent);
@@ -97,6 +103,9 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
97
103
  htmlLabels: htmlDataLabels,
98
104
  items: heatmapItems,
99
105
  labels: svgDataLabels,
106
+ markers: [],
107
+ getHoverMarkers: () => [],
108
+ annotations: [],
100
109
  };
101
110
  return preparedData;
102
111
  }
@@ -1,5 +1,6 @@
1
1
  import { prepareAnnotation } from '../../series/prepare-annotation';
2
2
  import { filterOverlappingLabels, preparePointDataLabels } from '../../utils';
3
+ import { buildHoverMarkerGetter } from '../marker';
3
4
  import { getXValue, getYValue, markHiddenPointsOutOfYRange } from '../utils';
4
5
  export const prepareLineData = async (args) => {
5
6
  var _a, _b, _c, _d, _e, _f;
@@ -58,44 +59,53 @@ export const prepareLineData = async (args) => {
58
59
  svgLabels = filterOverlappingLabels(svgLabels, otherLayers.map((l) => l.svgLabels).flat());
59
60
  htmlElements = filterOverlappingLabels(htmlElements, otherLayers.map((l) => l.htmlLabels).flat());
60
61
  }
61
- let markers = [];
62
+ markHiddenPointsOutOfYRange({
63
+ points,
64
+ yScale: seriesYScale,
65
+ yAxisTop,
66
+ axisMin: seriesYAxis.min,
67
+ axisMax: seriesYAxis.max,
68
+ getDataY: (p) => p.data.y,
69
+ });
70
+ const normalState = s.marker.states.normal;
62
71
  const hasPerPointNormalMarkers = s.data.some((d) => { var _a, _b, _c; return (_c = (_b = (_a = d.marker) === null || _a === void 0 ? void 0 : _a.states) === null || _b === void 0 ? void 0 : _b.normal) === null || _c === void 0 ? void 0 : _c.enabled; });
63
- if (s.marker.states.normal.enabled || hasPerPointNormalMarkers) {
64
- markers = points.reduce((result, p) => {
65
- var _a, _b, _c, _d;
66
- if (p.y === null || p.x === null) {
72
+ const markers = s.marker.states.normal.enabled || hasPerPointNormalMarkers
73
+ ? points.reduce((result, p) => {
74
+ var _a, _b, _c, _d, _e;
75
+ if (p.y === null || p.x === null || p.hiddenInLine) {
67
76
  return result;
68
77
  }
69
78
  const pointNormalEnabled = (_d = (_c = (_b = (_a = p.data.marker) === null || _a === void 0 ? void 0 : _a.states) === null || _b === void 0 ? void 0 : _b.normal) === null || _c === void 0 ? void 0 : _c.enabled) !== null && _d !== void 0 ? _d : false;
70
79
  if (s.marker.states.normal.enabled || pointNormalEnabled) {
71
80
  result.push({
72
- point: p,
81
+ cx: p.x,
82
+ cy: p.y,
83
+ radius: normalState.radius,
84
+ symbolType: normalState.symbol,
85
+ fill: (_e = p.color) !== null && _e !== void 0 ? _e : s.color,
86
+ stroke: normalState.borderColor,
87
+ strokeWidth: normalState.borderWidth,
88
+ opacity: 1,
73
89
  active: true,
74
- hovered: false,
75
90
  clipped: isOutsideBounds(p.x, p.y),
91
+ series: { id: s.id },
92
+ data: p.data,
76
93
  });
77
94
  }
78
95
  return result;
79
- }, []);
80
- }
96
+ }, [])
97
+ : [];
81
98
  const annotations = points.reduce((result, p) => {
82
99
  if (p.annotation && p.x !== null && p.y !== null) {
83
100
  result.push({ annotation: p.annotation, x: p.x, y: p.y });
84
101
  }
85
102
  return result;
86
103
  }, []);
87
- markHiddenPointsOutOfYRange({
88
- points,
89
- yScale: seriesYScale,
90
- yAxisTop,
91
- axisMin: seriesYAxis.min,
92
- axisMax: seriesYAxis.max,
93
- getDataY: (p) => p.data.y,
94
- });
95
104
  const result = {
96
105
  annotations,
97
106
  points,
98
107
  markers,
108
+ getHoverMarkers: buildHoverMarkerGetter(points, s),
99
109
  svgLabels: svgLabels,
100
110
  series: s,
101
111
  hovered: false,
@@ -3,9 +3,4 @@ import type { PreparedSeriesOptions } from '../../series/types';
3
3
  import type { PreparedLineData } from './types';
4
4
  export declare function renderLine(elements: {
5
5
  plot: SVGGElement;
6
- markers: SVGGElement;
7
- hoverMarkers: SVGGElement;
8
- annotations: SVGGElement;
9
- boundsWidth: number;
10
- boundsHeight: number;
11
6
  }, preparedData: PreparedLineData[], seriesOptions: PreparedSeriesOptions, dispatcher?: Dispatch<object>): () => void;
@@ -4,17 +4,12 @@ import { line as lineGenerator } from 'd3-shape';
4
4
  import get from 'lodash/get';
5
5
  import { block } from '../../../utils';
6
6
  import { getLineDashArray } from '../../utils';
7
- import { renderAnnotations } from '../annotation';
8
7
  import { renderDataLabels } from '../data-labels';
9
- import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
10
8
  import { setActiveState } from '../utils';
11
9
  const b = block('line');
12
10
  export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
13
11
  var _a, _b;
14
12
  const plotSvgElement = select(elements.plot);
15
- const markersSvgElement = select(elements.markers);
16
- const hoverMarkersSvgElement = select(elements.hoverMarkers);
17
- const annotationsSvgElement = select(elements.annotations);
18
13
  const hoverOptions = get(seriesOptions, 'line.states.hover');
19
14
  const inactiveOptions = get(seriesOptions, 'line.states.inactive');
20
15
  const line = lineGenerator()
@@ -22,7 +17,6 @@ export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
22
17
  .x((d) => d.x)
23
18
  .y((d) => d.y);
24
19
  plotSvgElement.selectAll('*').remove();
25
- markersSvgElement.selectAll('*').remove();
26
20
  const lineSelection = plotSvgElement
27
21
  .selectAll('path')
28
22
  .data(preparedData)
@@ -44,23 +38,10 @@ export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
44
38
  data: dataLabels,
45
39
  className: b('label'),
46
40
  });
47
- const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
48
- const markerSelection = markersSvgElement
49
- .selectAll('marker')
50
- .data(markers)
51
- .join('g')
52
- .call(renderMarker);
53
- renderAnnotations({
54
- anchors: preparedData.flatMap((d) => d.annotations),
55
- container: annotationsSvgElement,
56
- plotHeight: elements.boundsHeight,
57
- plotWidth: elements.boundsWidth,
58
- });
59
41
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
60
42
  const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
61
43
  function handleShapeHover(data) {
62
44
  const selected = (data === null || data === void 0 ? void 0 : data.filter((d) => d.series.type === 'line')) || [];
63
- const selectedDataItems = selected.map((d) => d.data);
64
45
  const selectedSeriesIds = selected.map((d) => { var _a; return (_a = d.series) === null || _a === void 0 ? void 0 : _a.id; });
65
46
  lineSelection.datum((d, index, list) => {
66
47
  const elementSelection = select(list[index]);
@@ -96,62 +77,6 @@ export function renderLine(elements, preparedData, seriesOptions, dispatcher) {
96
77
  datum: d,
97
78
  });
98
79
  });
99
- markerSelection.datum((d, index, list) => {
100
- const elementSelection = select(list[index]);
101
- const hovered = Boolean(hoverEnabled && selectedDataItems.includes(d.point.data));
102
- if (d.hovered !== hovered) {
103
- d.hovered = hovered;
104
- elementSelection.attr('visibility', getMarkerVisibility(d));
105
- selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
106
- selectMarkerSymbol(elementSelection).call(setMarker, hovered ? 'hover' : 'normal');
107
- }
108
- if (d.point.series.marker.states.normal.enabled) {
109
- const isActive = Boolean(!inactiveEnabled ||
110
- !selectedSeriesIds.length ||
111
- selectedSeriesIds.includes(d.point.series.id));
112
- setActiveState({
113
- element: list[index],
114
- state: inactiveOptions,
115
- active: isActive,
116
- datum: d,
117
- });
118
- }
119
- return d;
120
- });
121
- hoverMarkersSvgElement.selectAll('*').remove();
122
- if (hoverEnabled && selected.length > 0) {
123
- const hoverOnlyMarkers = [];
124
- for (const chunk of selected) {
125
- const seriesData = preparedData.find((pd) => pd.id === chunk.series.id);
126
- if (!seriesData) {
127
- continue;
128
- }
129
- const { series } = seriesData;
130
- if (series.marker.states.normal.enabled || !series.marker.states.hover.enabled) {
131
- continue;
132
- }
133
- const point = seriesData.points.find((p) => p.data === chunk.data);
134
- if (!point || point.x === null || point.y === null) {
135
- continue;
136
- }
137
- hoverOnlyMarkers.push({
138
- point: point,
139
- active: true,
140
- hovered: true,
141
- clipped: false,
142
- });
143
- }
144
- if (hoverOnlyMarkers.length > 0) {
145
- hoverMarkersSvgElement
146
- .selectAll('g')
147
- .data(hoverOnlyMarkers)
148
- .join('g')
149
- .call(renderMarker)
150
- .each((_d, i, nodes) => {
151
- selectMarkerSymbol(select(nodes[i])).call(setMarker, 'hover');
152
- });
153
- }
154
- }
155
80
  }
156
81
  const eventName = `hover-shape.line-${(_b = (_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : 'unknown'}`;
157
82
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on(eventName, handleShapeHover);
@@ -1,7 +1,7 @@
1
1
  import type { LabelData, LineSeriesData, LineSeriesLineBaseStyle } from '../../../types';
2
2
  import type { DashStyle, LineCap, LineJoin } from '../../constants';
3
3
  import type { AnnotationAnchor, PreparedAnnotation, PreparedLineSeries } from '../../series/types';
4
- import type { SeriesShapeData } from '../types';
4
+ import type { MarkerItem, SeriesShapeData } from '../types';
5
5
  export type PointData = {
6
6
  annotation?: PreparedAnnotation;
7
7
  color?: string;
@@ -15,17 +15,11 @@ export type MarkerPointData = PointData & {
15
15
  y: number;
16
16
  x: number;
17
17
  };
18
- export type MarkerData = {
19
- point: MarkerPointData;
20
- active: boolean;
21
- hovered: boolean;
22
- clipped: boolean;
23
- };
24
18
  export type PreparedLineData = {
25
19
  annotations: AnnotationAnchor[];
26
20
  id: string;
27
21
  points: PointData[];
28
- markers: MarkerData[];
22
+ markers: MarkerItem[];
29
23
  series: PreparedLineSeries;
30
24
  hovered: boolean;
31
25
  active: boolean;
@@ -1,5 +1,6 @@
1
1
  import type { BaseType, Selection } from 'd3-selection';
2
2
  import { SymbolType } from '../constants';
3
+ import type { MarkerItem } from './types';
3
4
  export interface BaseMarkerData {
4
5
  point: {
5
6
  x: number;
@@ -43,3 +44,32 @@ export declare function setMarker<T extends BaseType, D extends BaseMarkerData>(
43
44
  export declare function getMarkerSymbol(type: `${SymbolType}` | undefined, radius: number): string | null;
44
45
  export declare function selectMarkerHalo<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
45
46
  export declare function selectMarkerSymbol<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
47
+ export declare function renderMarkers(container: Selection<SVGGElement, unknown, null, undefined>, markers: MarkerItem[]): void;
48
+ export declare function renderHoverMarkers(container: Selection<SVGGElement, unknown, null, undefined>, hoverMarkers: MarkerItem[]): void;
49
+ interface HoverMarkerPoint {
50
+ data: unknown;
51
+ x: number | null;
52
+ y: number | null;
53
+ hiddenInLine?: boolean;
54
+ color?: string;
55
+ }
56
+ interface HoverMarkerSeries {
57
+ id: string;
58
+ color: string;
59
+ marker: {
60
+ states: {
61
+ normal: {
62
+ enabled: boolean;
63
+ symbol: `${SymbolType}`;
64
+ };
65
+ hover: {
66
+ enabled: boolean;
67
+ radius: number;
68
+ borderColor: string;
69
+ borderWidth: number;
70
+ };
71
+ };
72
+ };
73
+ }
74
+ export declare function buildHoverMarkerGetter(points: HoverMarkerPoint[], series: HoverMarkerSeries): (hoveredData: unknown[]) => MarkerItem[];
75
+ export {};
@@ -80,3 +80,71 @@ export function selectMarkerHalo(parentSelection) {
80
80
  export function selectMarkerSymbol(parentSelection) {
81
81
  return parentSelection.select(`.${symbolClassName}`);
82
82
  }
83
+ export function renderMarkers(container, markers) {
84
+ container.selectAll('*').remove();
85
+ const selection = container
86
+ .selectAll('g')
87
+ .data(markers)
88
+ .join('g')
89
+ .attr('class', b('wrapper'))
90
+ .attr('transform', (d) => `translate(${d.cx},${d.cy})`);
91
+ selection
92
+ .append('path')
93
+ .attr('class', b('symbol'))
94
+ .attr('d', (d) => d.clipped ? null : getMarkerSymbol(d.symbolType, d.radius + d.strokeWidth))
95
+ .attr('fill', (d) => d.fill)
96
+ .attr('stroke', (d) => d.stroke)
97
+ .attr('stroke-width', (d) => d.strokeWidth);
98
+ }
99
+ export function renderHoverMarkers(container, hoverMarkers) {
100
+ container.selectAll('*').remove();
101
+ if (hoverMarkers.length === 0)
102
+ return;
103
+ container
104
+ .selectAll('g')
105
+ .data(hoverMarkers)
106
+ .join('g')
107
+ .attr('class', b('wrapper'))
108
+ .attr('transform', (d) => `translate(${d.cx},${d.cy})`)
109
+ .append('path')
110
+ .attr('class', b('symbol'))
111
+ .attr('d', (d) => getMarkerSymbol(d.symbolType, d.radius + d.strokeWidth))
112
+ .attr('fill', (d) => d.fill)
113
+ .attr('stroke', (d) => d.stroke)
114
+ .attr('stroke-width', (d) => d.strokeWidth);
115
+ }
116
+ export function buildHoverMarkerGetter(points, series) {
117
+ const { normal: normalState, hover: hoverState } = series.marker.states;
118
+ if (normalState.enabled || !hoverState.enabled)
119
+ return () => [];
120
+ const pointByData = new Map();
121
+ for (const p of points) {
122
+ if (p.x !== null && p.y !== null && !p.hiddenInLine) {
123
+ pointByData.set(p.data, p);
124
+ }
125
+ }
126
+ return (hoveredData) => {
127
+ var _a;
128
+ const items = [];
129
+ for (const rawData of hoveredData) {
130
+ const point = pointByData.get(rawData);
131
+ if (!point || point.x === null || point.y === null)
132
+ continue;
133
+ items.push({
134
+ cx: point.x,
135
+ cy: point.y,
136
+ radius: hoverState.radius,
137
+ symbolType: normalState.symbol,
138
+ fill: (_a = point.color) !== null && _a !== void 0 ? _a : series.color,
139
+ stroke: hoverState.borderColor,
140
+ strokeWidth: hoverState.borderWidth,
141
+ opacity: 1,
142
+ active: true,
143
+ clipped: false,
144
+ series: { id: series.id },
145
+ data: rawData,
146
+ });
147
+ }
148
+ return items;
149
+ };
150
+ }