@gravity-ui/charts 1.49.0 → 1.50.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 (55) hide show
  1. package/dist/cjs/components/AxisX/prepare-axis-data.js +2 -1
  2. package/dist/cjs/components/AxisY/types.d.ts +2 -1
  3. package/dist/cjs/components/ChartInner/utils/zoom.js +1 -1
  4. package/dist/cjs/core/series/prepare-area.js +1 -0
  5. package/dist/cjs/core/series/prepare-bar-x.js +1 -0
  6. package/dist/cjs/core/series/prepare-bar-y.d.ts +1 -0
  7. package/dist/cjs/core/series/prepare-bar-y.js +1 -0
  8. package/dist/cjs/core/series/types.d.ts +3 -0
  9. package/dist/cjs/core/shapes/bar-y/prepare-data.js +11 -9
  10. package/dist/cjs/core/shapes/funnel/prepare-data.js +39 -18
  11. package/dist/cjs/core/shapes/funnel/types.d.ts +2 -1
  12. package/dist/cjs/core/shapes/utils.d.ts +6 -0
  13. package/dist/cjs/core/shapes/utils.js +9 -0
  14. package/dist/cjs/core/types/chart/funnel.d.ts +1 -1
  15. package/dist/cjs/core/types/chart/series.d.ts +5 -5
  16. package/dist/cjs/core/types/chart/tooltip.d.ts +3 -2
  17. package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
  18. package/dist/cjs/core/types/css.d.ts +2 -0
  19. package/dist/cjs/core/types/css.js +1 -0
  20. package/dist/cjs/core/types/renderer.d.ts +15 -0
  21. package/dist/cjs/core/types/renderer.js +1 -0
  22. package/dist/cjs/core/utils/text.d.ts +2 -1
  23. package/dist/cjs/core/zoom/zoom.js +10 -2
  24. package/dist/cjs/hooks/useShapes/funnel/index.js +5 -2
  25. package/dist/cjs/hooks/useShapes/utils.d.ts +0 -7
  26. package/dist/cjs/hooks/useShapes/utils.js +0 -11
  27. package/dist/cjs/types/chart-ui.d.ts +2 -1
  28. package/dist/esm/components/AxisX/prepare-axis-data.js +2 -1
  29. package/dist/esm/components/AxisY/types.d.ts +2 -1
  30. package/dist/esm/components/ChartInner/utils/zoom.js +1 -1
  31. package/dist/esm/core/series/prepare-area.js +1 -0
  32. package/dist/esm/core/series/prepare-bar-x.js +1 -0
  33. package/dist/esm/core/series/prepare-bar-y.d.ts +1 -0
  34. package/dist/esm/core/series/prepare-bar-y.js +1 -0
  35. package/dist/esm/core/series/types.d.ts +3 -0
  36. package/dist/esm/core/shapes/bar-y/prepare-data.js +11 -9
  37. package/dist/esm/core/shapes/funnel/prepare-data.js +39 -18
  38. package/dist/esm/core/shapes/funnel/types.d.ts +2 -1
  39. package/dist/esm/core/shapes/utils.d.ts +6 -0
  40. package/dist/esm/core/shapes/utils.js +9 -0
  41. package/dist/esm/core/types/chart/funnel.d.ts +1 -1
  42. package/dist/esm/core/types/chart/series.d.ts +5 -5
  43. package/dist/esm/core/types/chart/tooltip.d.ts +3 -2
  44. package/dist/esm/core/types/chart/zoom.d.ts +1 -1
  45. package/dist/esm/core/types/css.d.ts +2 -0
  46. package/dist/esm/core/types/css.js +1 -0
  47. package/dist/esm/core/types/renderer.d.ts +15 -0
  48. package/dist/esm/core/types/renderer.js +1 -0
  49. package/dist/esm/core/utils/text.d.ts +2 -1
  50. package/dist/esm/core/zoom/zoom.js +10 -2
  51. package/dist/esm/hooks/useShapes/funnel/index.js +5 -2
  52. package/dist/esm/hooks/useShapes/utils.d.ts +0 -7
  53. package/dist/esm/hooks/useShapes/utils.js +0 -11
  54. package/dist/esm/types/chart-ui.d.ts +2 -1
  55. package/package.json +2 -1
@@ -26,7 +26,8 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
26
26
  else {
27
27
  textMaxWidth = Math.min(axis.labels.height / calculateSin(a) - textSize.height * calculateSin(90 - a),
28
28
  // leftmost label: may extend into the left Y-axis area but not into the left margin
29
- (boundsOffsetLeft - chartMarginLeft + left) / calculateSin(a));
29
+ // subtract textSize.height / 2 to account for the height component in the x calculation
30
+ (boundsOffsetLeft - chartMarginLeft + left) / calculateSin(a) - textSize.height / 2);
30
31
  }
31
32
  if (textSize.width > textMaxWidth) {
32
33
  rowText = await getTextWithElipsis({
@@ -1,5 +1,6 @@
1
1
  import type { DashStyle } from 'src/core/constants';
2
2
  import type { AxisPlotShape } from '../../core/types/chart/axis';
3
+ import type { CSSProperties } from '../../core/types/css';
3
4
  import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
4
5
  import type { TextRowData } from '../types';
5
6
  export type AxisSvgLabelData = {
@@ -42,7 +43,7 @@ export type SvgAxisTitleData = {
42
43
  export type HtmlAxisTitleData = {
43
44
  html: true;
44
45
  content: string;
45
- style: BaseTextStyle & React.CSSProperties;
46
+ style: BaseTextStyle & CSSProperties;
46
47
  size: {
47
48
  width: number;
48
49
  height: number;
@@ -13,7 +13,7 @@ function mapSeriesTypeToZoomType(seriesType) {
13
13
  return [ZOOM_TYPE.X];
14
14
  }
15
15
  case SERIES_TYPE.BarY: {
16
- return [ZOOM_TYPE.Y];
16
+ return [ZOOM_TYPE.Y, ZOOM_TYPE.XY];
17
17
  }
18
18
  case SERIES_TYPE.Line: {
19
19
  return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
@@ -63,6 +63,7 @@ export function prepareArea(args) {
63
63
  data: prepareSeriesData(series),
64
64
  stacking: series.stacking,
65
65
  stackId: getSeriesStackId(series),
66
+ valueAxis: 'y',
66
67
  dataLabels: {
67
68
  enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
68
69
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_f = series.dataLabels) === null || _f === void 0 ? void 0 : _f.style),
@@ -37,6 +37,7 @@ export function prepareBarXSeries(args) {
37
37
  data: prepareSeriesData(series),
38
38
  stacking: series.stacking,
39
39
  stackId: getSeriesStackId(series),
40
+ valueAxis: 'y',
40
41
  dataLabels: {
41
42
  enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
42
43
  inside: dataLabelsInside,
@@ -12,6 +12,7 @@ export declare function prepareBarYSeries(args: PrepareBarYSeriesArgs): Promise<
12
12
  data: BarYSeriesData[];
13
13
  stackId: string;
14
14
  stacking: BarYSeries["stacking"];
15
+ valueAxis: "x";
15
16
  dataLabels: {
16
17
  padding: number;
17
18
  enabled: boolean;
@@ -64,6 +64,7 @@ export function prepareBarYSeries(args) {
64
64
  data: prepareSeriesData(series),
65
65
  stacking: series.stacking,
66
66
  stackId: getSeriesStackId(series),
67
+ valueAxis: 'x',
67
68
  dataLabels: await prepareDataLabels(series),
68
69
  cursor: get(series, 'cursor', null),
69
70
  borderRadius: (_g = (_e = series.borderRadius) !== null && _e !== void 0 ? _e : (_f = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _f === void 0 ? void 0 : _f.borderRadius) !== null && _g !== void 0 ? _g : 0,
@@ -134,6 +134,7 @@ export type PreparedBarXSeries = {
134
134
  data: BarXSeriesData[];
135
135
  stackId: string;
136
136
  stacking: BarXSeries['stacking'];
137
+ valueAxis: 'y';
137
138
  dataLabels: {
138
139
  enabled: boolean;
139
140
  inside: boolean;
@@ -151,6 +152,7 @@ export type PreparedBarYSeries = {
151
152
  data: BarYSeriesData[];
152
153
  stackId: string;
153
154
  stacking: BarYSeries['stacking'];
155
+ valueAxis: 'x';
154
156
  dataLabels: {
155
157
  padding: number;
156
158
  enabled: boolean;
@@ -252,6 +254,7 @@ export type PreparedAreaSeries = {
252
254
  data: AreaSeriesData[];
253
255
  stacking: AreaSeries['stacking'];
254
256
  stackId: string;
257
+ valueAxis: 'y';
255
258
  lineWidth: number;
256
259
  opacity: number;
257
260
  nullMode: AreaSeries['nullMode'];
@@ -9,7 +9,6 @@ export async function prepareBarYData(args) {
9
9
  const stackGap = seriesOptions['bar-y'].stackGap;
10
10
  const xLinearScale = xScale;
11
11
  const yLinearScale = yScale;
12
- const [baseRangeValue] = xLinearScale.range();
13
12
  if (!yLinearScale) {
14
13
  return {
15
14
  shapes: [],
@@ -92,19 +91,22 @@ export async function prepareBarYData(args) {
92
91
  const borderWidth = barSize > s.borderWidth * 2 ? s.borderWidth : 0;
93
92
  const isFirstInStack = xValueIndex === 0;
94
93
  const isLastStackItem = xValueIndex === sortedData.length - 1;
94
+ const extendsRight = xLinearScale(xValue) > baseValue;
95
95
  // Calculate position with border compensation
96
96
  // Border extends halfBorder outward from the shape, so we need to adjust position
97
- let itemX = xValue > baseRangeValue ? positiveStack : negativeStack - width;
97
+ let itemX = extendsRight ? positiveStack : negativeStack - width;
98
98
  itemX += itemStackGap;
99
99
  const halfBorder = borderWidth / 2;
100
- if (isFirstInStack && xValue > 0) {
101
- // Positive bar: border extends left, so shift position left by halfBorder
102
- // to keep the visual left edge at the zero line
100
+ if (isFirstInStack && extendsRight) {
101
+ // Bar extends right from base, border extends outward to the
102
+ // left shift left by halfBorder to keep the visual left
103
+ // edge at the zero line.
103
104
  itemX -= halfBorder;
104
105
  }
105
- else if (isFirstInStack && xValue < 0) {
106
- // Negative bar: border extends right, so shift position right by halfBorder
107
- // to keep the visual right edge at the zero line
106
+ else if (isFirstInStack && !extendsRight && xValue !== 0) {
107
+ // Bar extends left from base, border extends outward to the
108
+ // right shift right by halfBorder to keep the visual
109
+ // right edge at the zero line.
108
110
  itemX += halfBorder;
109
111
  }
110
112
  const item = {
@@ -121,7 +123,7 @@ export async function prepareBarYData(args) {
121
123
  isLastStackItem,
122
124
  };
123
125
  stackItems.push(item);
124
- if (xValue > baseRangeValue) {
126
+ if (extendsRight) {
125
127
  positiveStack += width;
126
128
  }
127
129
  else {
@@ -1,5 +1,5 @@
1
1
  import { path } from 'd3-path';
2
- import { calculateNumericProperty, getFormattedValue, getTextSizeFn } from '../../utils';
2
+ import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, } from '../../utils';
3
3
  function getLineConnectorPaths(args) {
4
4
  const { points } = args;
5
5
  const leftPath = path();
@@ -23,6 +23,7 @@ export async function prepareFunnelData(args) {
23
23
  const { series, boundsWidth, boundsHeight } = args;
24
24
  const items = [];
25
25
  const svgLabels = [];
26
+ const htmlLabels = [];
26
27
  const connectors = [];
27
28
  const maxValue = Math.max(...series.map((s) => s.data.value));
28
29
  const itemBandSpace = boundsHeight / series.length;
@@ -42,36 +43,55 @@ export async function prepareFunnelData(args) {
42
43
  if (s.dataLabels.enabled) {
43
44
  const d = s.data;
44
45
  const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
45
- const labelSize = await getTextSize(labelContent);
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);
46
57
  let x;
47
58
  switch (s.dataLabels.align) {
48
59
  case 'left': {
49
60
  x = 0;
50
- segmentLeftOffset = Math.max(segmentLeftOffset, labelSize.width);
61
+ segmentLeftOffset = Math.max(segmentLeftOffset, width);
51
62
  break;
52
63
  }
53
64
  case 'right': {
54
- x = boundsWidth - labelSize.width;
55
- segmentRightOffset = Math.max(segmentRightOffset, labelSize.width);
65
+ x = boundsWidth - width;
66
+ segmentRightOffset = Math.max(segmentRightOffset, width);
56
67
  break;
57
68
  }
58
69
  case 'center': {
59
- x = boundsWidth / 2 - labelSize.width / 2;
70
+ x = boundsWidth / 2 - width / 2;
60
71
  break;
61
72
  }
62
73
  }
63
- svgLabels.push({
64
- x,
65
- y: getSegmentY(index) +
66
- itemHeight / 2 -
67
- labelSize.height / 2 +
68
- labelSize.hangingOffset,
69
- text: labelContent,
70
- style: s.dataLabels.style,
71
- size: labelSize,
72
- textAnchor: 'start',
73
- series: s,
74
- });
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
+ });
83
+ }
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
+ });
94
+ }
75
95
  }
76
96
  }
77
97
  const segmentMaxWidth = boundsWidth - segmentLeftOffset - segmentRightOffset;
@@ -117,6 +137,7 @@ export async function prepareFunnelData(args) {
117
137
  type: 'funnel',
118
138
  items,
119
139
  svgLabels,
140
+ htmlLabels,
120
141
  connectors,
121
142
  };
122
143
  return data;
@@ -1,5 +1,5 @@
1
1
  import type { Path } from 'd3-path';
2
- import type { FunnelSeriesData, LabelData } from '../../../types';
2
+ import type { FunnelSeriesData, HtmlItem, LabelData } from '../../../types';
3
3
  import type { DashStyle } from '../../constants';
4
4
  import type { PreparedFunnelSeries } from '../../series/types';
5
5
  export type FunnelItemData = {
@@ -29,4 +29,5 @@ export type PreparedFunnelData = {
29
29
  items: FunnelItemData[];
30
30
  connectors: FunnelConnectorData[];
31
31
  svgLabels: LabelData[];
32
+ htmlLabels: HtmlItem[];
32
33
  };
@@ -2,6 +2,7 @@ import type { BaseType } from 'd3-selection';
2
2
  import type { PreparedXAxis, PreparedYAxis } from '../axes/types';
3
3
  import type { ChartScale } from '../scales/types';
4
4
  import type { BasicInactiveState } from '../types';
5
+ import type { ZoomState } from '../zoom/types';
5
6
  export declare function getXValue(args: {
6
7
  point: {
7
8
  x?: number | string | null;
@@ -74,3 +75,8 @@ export declare function getClipPathIdByBounds(args: {
74
75
  clipPathId: string;
75
76
  bounds?: 'horizontal';
76
77
  }): string;
78
+ export declare function getSeriesClipPathId(args: {
79
+ clipPathId: string;
80
+ yAxis: PreparedYAxis[];
81
+ zoomState?: Partial<ZoomState>;
82
+ }): string;
@@ -184,3 +184,12 @@ export function getClipPathIdByBounds(args) {
184
184
  const { bounds, clipPathId } = args;
185
185
  return bounds ? `${clipPathId}-${bounds}` : clipPathId;
186
186
  }
187
+ export function getSeriesClipPathId(args) {
188
+ const { clipPathId, yAxis, zoomState } = args;
189
+ const hasMinOrMax = yAxis.some((axis) => typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number');
190
+ const hasZoom = zoomState && Object.keys(zoomState).length > 0;
191
+ if (!hasZoom && !hasMinOrMax) {
192
+ return `${clipPathId}-horizontal`;
193
+ }
194
+ return clipPathId;
195
+ }
@@ -39,7 +39,7 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
39
39
  /** Opacity for the connector area. */
40
40
  areaOpacity?: number;
41
41
  };
42
- dataLabels?: Omit<BaseDataLabels, 'html' | 'allowOverlap'> & {
42
+ dataLabels?: Omit<BaseDataLabels, 'allowOverlap'> & {
43
43
  /** Horizontal alignment of the data labels. */
44
44
  align?: 'left' | 'center' | 'right';
45
45
  };
@@ -1,6 +1,6 @@
1
- import type React from 'react';
2
1
  import type { DashStyle, LineCap, LineJoin } from '../../constants';
3
2
  import type { MeaningfulAny } from '../misc';
3
+ import type { SVGTextAttributes } from '../renderer';
4
4
  import type { ChartAnnotationSeriesOptions } from './annotation';
5
5
  import type { AreaSeries, AreaSeriesData } from './area';
6
6
  import type { BarXSeries, BarXSeriesData } from './bar-x';
@@ -40,7 +40,7 @@ export interface BasicHoverState {
40
40
  export interface BasicInactiveState {
41
41
  /**
42
42
  * Enable separate styles for the inactive series.
43
- * @default true
43
+ * @default false for most series types; true for radar
44
44
  */
45
45
  enabled?: boolean;
46
46
  /**
@@ -55,7 +55,7 @@ export interface ChartSeriesOptions {
55
55
  /** Enable or disable the data labels */
56
56
  enabled?: boolean;
57
57
  /** Callback function to render the data label */
58
- renderer?: (args: DataLabelRendererData) => React.SVGTextElementAttributes<SVGTextElement>;
58
+ renderer?: (args: DataLabelRendererData) => SVGTextAttributes;
59
59
  };
60
60
  'bar-x'?: {
61
61
  /**
@@ -207,12 +207,12 @@ export interface ChartSeriesOptions {
207
207
  dashStyle?: DashStyle;
208
208
  /**
209
209
  * Options for line cap style
210
- * @default 'round' when dashStyle is not 'solid', 'none' when dashStyle is not 'solid'
210
+ * @default 'round' when dashStyle is 'Solid', 'none' when dashStyle is not 'Solid'
211
211
  */
212
212
  linecap?: `${LineCap}`;
213
213
  /**
214
214
  * Defines the shape to be used at the corners of the line
215
- * @default 'round' when dashStyle is not 'solid', 'unset' when dashStyle is not 'solid'
215
+ * @default 'round' when dashStyle is 'Solid', 'unset' when dashStyle is not 'Solid'
216
216
  */
217
217
  linejoin?: `${LineJoin}`;
218
218
  /** Default annotation settings for all line data points */
@@ -1,6 +1,7 @@
1
1
  import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
2
2
  import type { DateTimeLabelFormats } from '../../utils/time';
3
3
  import type { MeaningfulAny } from '../misc';
4
+ import type { RendererElement } from '../renderer';
4
5
  import type { AreaSeries, AreaSeriesData } from './area';
5
6
  import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
6
7
  import type { BarXSeries, BarXSeriesData } from './bar-x';
@@ -132,7 +133,7 @@ export type ChartTooltipSortComparator<T = MeaningfulAny> = (a: TooltipDataChunk
132
133
  export interface ChartTooltip<T = MeaningfulAny> {
133
134
  enabled?: boolean;
134
135
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
135
- renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
136
+ renderer?: (args: ChartTooltipRendererArgs<T>) => RendererElement | null;
136
137
  /**
137
138
  * Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
138
139
  * It is useful in cases where you need to display additional information, but keep the general format of the tooltip.
@@ -157,7 +158,7 @@ export interface ChartTooltip<T = MeaningfulAny> {
157
158
  * `<tr class="${className}"><td>${name}</td><td>${value}</td></tr>`
158
159
  * ```
159
160
  */
160
- rowRenderer?: ((args: ChartTooltipRowRendererArgs) => React.ReactElement | string) | null;
161
+ rowRenderer?: ((args: ChartTooltipRowRendererArgs) => RendererElement | string) | null;
161
162
  pin?: {
162
163
  enabled?: boolean;
163
164
  modifierKey?: 'altKey' | 'metaKey';
@@ -19,8 +19,8 @@ export interface ChartZoom {
19
19
  * Supported zoom types by series type:
20
20
  * - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
21
21
  * - `BarX`: `x`, `xy`
22
+ * - `BarY`: `y`, `xy`
22
23
  * - `XRange`: `x`
23
- * - `BarY`: `y`
24
24
  *
25
25
  * Default zoom type by series type:
26
26
  * - `BarY`: `y`
@@ -0,0 +1,2 @@
1
+ import type { Properties } from 'csstype';
2
+ export type CSSProperties = Properties;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Framework-agnostic renderer return type.
3
+ * Structurally compatible with React.ReactElement — callers in React contexts
4
+ * may narrow to it without changes.
5
+ */
6
+ export interface RendererElement {
7
+ type: unknown;
8
+ props: unknown;
9
+ key: unknown;
10
+ }
11
+ /**
12
+ * SVG text element attributes map.
13
+ * Replaces React.SVGTextElementAttributes<SVGTextElement> in core types.
14
+ */
15
+ export type SVGTextAttributes = object;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import type { Selection } from 'd3-selection';
2
2
  import type { BaseTextStyle, MeaningfulAny } from '../../types';
3
+ import type { CSSProperties } from '../types/css';
3
4
  /**
4
5
  * Approximate ratio of descenders relative to the full font em height.
5
6
  * Based on the Chromium hanging baseline algorithm where hanging offset ≈ ascent × 0.2.
@@ -12,7 +13,7 @@ export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTe
12
13
  export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, MeaningfulAny, unknown>, maxWidth: ((datum: T) => number) | number, currentWidth?: (datum: T) => number): void;
13
14
  export declare function getLabelsSize({ labels, style, rotation, html, }: {
14
15
  labels: string[];
15
- style?: BaseTextStyle & React.CSSProperties;
16
+ style?: BaseTextStyle & CSSProperties;
16
17
  rotation?: number;
17
18
  html?: boolean;
18
19
  }): Promise<{
@@ -60,6 +60,14 @@ export function getZoomedSeriesData(args) {
60
60
  if (!isPreparedZoomableSeries(seriesItem)) {
61
61
  return;
62
62
  }
63
+ // For stacked series the chart-space position of each point is the
64
+ // cumulative stack sum, not its individual value, so the value-axis
65
+ // filter would drop segments that still need to participate in the
66
+ // stack. Skip it and let the axis range / clip handle visibility.
67
+ const isStacked = 'stacking' in seriesItem && Boolean(seriesItem.stacking);
68
+ const stackedValueAxis = isStacked && 'valueAxis' in seriesItem ? seriesItem.valueAxis : undefined;
69
+ const skipXFilter = stackedValueAxis === 'x';
70
+ const skipYFilter = stackedValueAxis === 'y';
63
71
  seriesItem.data.forEach((point, i) => {
64
72
  var _a, _b;
65
73
  const prevPoint = seriesItem.data[i - 1];
@@ -67,7 +75,7 @@ export function getZoomedSeriesData(args) {
67
75
  let inXRange = true;
68
76
  let inYRange = true;
69
77
  prevPointInRange = currentPointInRange;
70
- if (zoomState.x) {
78
+ if (zoomState.x && !skipXFilter) {
71
79
  const [xMin, xMax] = zoomState.x;
72
80
  if ('x0' in point && 'x1' in point) {
73
81
  const isStartInRange = isValueInRange({
@@ -94,7 +102,7 @@ export function getZoomedSeriesData(args) {
94
102
  });
95
103
  }
96
104
  }
97
- if (zoomState.y) {
105
+ if (zoomState.y && !skipYFilter) {
98
106
  const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
99
107
  ? seriesItem.yAxis
100
108
  : 0;
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import { renderFunnel } from '../../../core/shapes/funnel/renderer';
3
3
  import { block } from '../../../utils';
4
+ import { HtmlLayer } from '../HtmlLayer';
4
5
  export { prepareFunnelData } from '../../../core/shapes/funnel/prepare-data';
5
6
  export * from '../../../core/shapes/funnel/types';
6
7
  const b = block('funnel');
7
8
  export const FunnelSeriesShapes = (args) => {
8
- const { dispatcher, preparedData, seriesOptions } = args;
9
+ const { dispatcher, htmlLayout, preparedData, seriesOptions } = args;
9
10
  const ref = React.useRef(null);
10
11
  React.useEffect(() => {
11
12
  if (!ref.current) {
@@ -13,6 +14,8 @@ export const FunnelSeriesShapes = (args) => {
13
14
  }
14
15
  return renderFunnel({ plot: ref.current }, preparedData, seriesOptions, dispatcher);
15
16
  }, [dispatcher, preparedData, seriesOptions]);
17
+ const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.htmlLabels }), [preparedData.htmlLabels]);
16
18
  return (React.createElement(React.Fragment, null,
17
- React.createElement("g", { ref: ref, className: b() })));
19
+ React.createElement("g", { ref: ref, className: b() }),
20
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
18
21
  };
@@ -1,8 +1 @@
1
- import type { ZoomState } from '../../core/zoom/types';
2
- import type { PreparedYAxis } from '../useAxis/types';
3
1
  export * from '../../core/shapes/utils';
4
- export declare function getSeriesClipPathId(args: {
5
- clipPathId: string;
6
- yAxis: PreparedYAxis[];
7
- zoomState?: Partial<ZoomState>;
8
- }): string;
@@ -1,12 +1 @@
1
1
  export * from '../../core/shapes/utils';
2
- export function getSeriesClipPathId(args) {
3
- const { clipPathId, yAxis, zoomState } = args;
4
- const hasMinOrMax = yAxis.some((axis) => {
5
- return typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number';
6
- });
7
- const hasZoom = zoomState && Object.keys(zoomState).length > 0;
8
- if (!hasZoom && !hasMinOrMax) {
9
- return `${clipPathId}-horizontal`;
10
- }
11
- return clipPathId;
12
- }
@@ -1,4 +1,5 @@
1
1
  import type { BaseTextStyle } from '../core/types/chart/base';
2
+ import type { CSSProperties } from '../core/types/css';
2
3
  export interface LabelData {
3
4
  text: string;
4
5
  x: number;
@@ -23,7 +24,7 @@ export interface HtmlItem {
23
24
  width: number;
24
25
  height: number;
25
26
  };
26
- style?: BaseTextStyle & React.CSSProperties;
27
+ style?: BaseTextStyle & CSSProperties;
27
28
  /** Coordinate space for positioning: 'plot' uses the plot area origin, 'chart' uses the full chart origin. Defaults to 'plot'. */
28
29
  scope?: 'plot' | 'chart';
29
30
  }
@@ -26,7 +26,8 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
26
26
  else {
27
27
  textMaxWidth = Math.min(axis.labels.height / calculateSin(a) - textSize.height * calculateSin(90 - a),
28
28
  // leftmost label: may extend into the left Y-axis area but not into the left margin
29
- (boundsOffsetLeft - chartMarginLeft + left) / calculateSin(a));
29
+ // subtract textSize.height / 2 to account for the height component in the x calculation
30
+ (boundsOffsetLeft - chartMarginLeft + left) / calculateSin(a) - textSize.height / 2);
30
31
  }
31
32
  if (textSize.width > textMaxWidth) {
32
33
  rowText = await getTextWithElipsis({
@@ -1,5 +1,6 @@
1
1
  import type { DashStyle } from 'src/core/constants';
2
2
  import type { AxisPlotShape } from '../../core/types/chart/axis';
3
+ import type { CSSProperties } from '../../core/types/css';
3
4
  import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
4
5
  import type { TextRowData } from '../types';
5
6
  export type AxisSvgLabelData = {
@@ -42,7 +43,7 @@ export type SvgAxisTitleData = {
42
43
  export type HtmlAxisTitleData = {
43
44
  html: true;
44
45
  content: string;
45
- style: BaseTextStyle & React.CSSProperties;
46
+ style: BaseTextStyle & CSSProperties;
46
47
  size: {
47
48
  width: number;
48
49
  height: number;
@@ -13,7 +13,7 @@ function mapSeriesTypeToZoomType(seriesType) {
13
13
  return [ZOOM_TYPE.X];
14
14
  }
15
15
  case SERIES_TYPE.BarY: {
16
- return [ZOOM_TYPE.Y];
16
+ return [ZOOM_TYPE.Y, ZOOM_TYPE.XY];
17
17
  }
18
18
  case SERIES_TYPE.Line: {
19
19
  return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
@@ -63,6 +63,7 @@ export function prepareArea(args) {
63
63
  data: prepareSeriesData(series),
64
64
  stacking: series.stacking,
65
65
  stackId: getSeriesStackId(series),
66
+ valueAxis: 'y',
66
67
  dataLabels: {
67
68
  enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
68
69
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_f = series.dataLabels) === null || _f === void 0 ? void 0 : _f.style),
@@ -37,6 +37,7 @@ export function prepareBarXSeries(args) {
37
37
  data: prepareSeriesData(series),
38
38
  stacking: series.stacking,
39
39
  stackId: getSeriesStackId(series),
40
+ valueAxis: 'y',
40
41
  dataLabels: {
41
42
  enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
42
43
  inside: dataLabelsInside,
@@ -12,6 +12,7 @@ export declare function prepareBarYSeries(args: PrepareBarYSeriesArgs): Promise<
12
12
  data: BarYSeriesData[];
13
13
  stackId: string;
14
14
  stacking: BarYSeries["stacking"];
15
+ valueAxis: "x";
15
16
  dataLabels: {
16
17
  padding: number;
17
18
  enabled: boolean;
@@ -64,6 +64,7 @@ export function prepareBarYSeries(args) {
64
64
  data: prepareSeriesData(series),
65
65
  stacking: series.stacking,
66
66
  stackId: getSeriesStackId(series),
67
+ valueAxis: 'x',
67
68
  dataLabels: await prepareDataLabels(series),
68
69
  cursor: get(series, 'cursor', null),
69
70
  borderRadius: (_g = (_e = series.borderRadius) !== null && _e !== void 0 ? _e : (_f = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _f === void 0 ? void 0 : _f.borderRadius) !== null && _g !== void 0 ? _g : 0,
@@ -134,6 +134,7 @@ export type PreparedBarXSeries = {
134
134
  data: BarXSeriesData[];
135
135
  stackId: string;
136
136
  stacking: BarXSeries['stacking'];
137
+ valueAxis: 'y';
137
138
  dataLabels: {
138
139
  enabled: boolean;
139
140
  inside: boolean;
@@ -151,6 +152,7 @@ export type PreparedBarYSeries = {
151
152
  data: BarYSeriesData[];
152
153
  stackId: string;
153
154
  stacking: BarYSeries['stacking'];
155
+ valueAxis: 'x';
154
156
  dataLabels: {
155
157
  padding: number;
156
158
  enabled: boolean;
@@ -252,6 +254,7 @@ export type PreparedAreaSeries = {
252
254
  data: AreaSeriesData[];
253
255
  stacking: AreaSeries['stacking'];
254
256
  stackId: string;
257
+ valueAxis: 'y';
255
258
  lineWidth: number;
256
259
  opacity: number;
257
260
  nullMode: AreaSeries['nullMode'];
@@ -9,7 +9,6 @@ export async function prepareBarYData(args) {
9
9
  const stackGap = seriesOptions['bar-y'].stackGap;
10
10
  const xLinearScale = xScale;
11
11
  const yLinearScale = yScale;
12
- const [baseRangeValue] = xLinearScale.range();
13
12
  if (!yLinearScale) {
14
13
  return {
15
14
  shapes: [],
@@ -92,19 +91,22 @@ export async function prepareBarYData(args) {
92
91
  const borderWidth = barSize > s.borderWidth * 2 ? s.borderWidth : 0;
93
92
  const isFirstInStack = xValueIndex === 0;
94
93
  const isLastStackItem = xValueIndex === sortedData.length - 1;
94
+ const extendsRight = xLinearScale(xValue) > baseValue;
95
95
  // Calculate position with border compensation
96
96
  // Border extends halfBorder outward from the shape, so we need to adjust position
97
- let itemX = xValue > baseRangeValue ? positiveStack : negativeStack - width;
97
+ let itemX = extendsRight ? positiveStack : negativeStack - width;
98
98
  itemX += itemStackGap;
99
99
  const halfBorder = borderWidth / 2;
100
- if (isFirstInStack && xValue > 0) {
101
- // Positive bar: border extends left, so shift position left by halfBorder
102
- // to keep the visual left edge at the zero line
100
+ if (isFirstInStack && extendsRight) {
101
+ // Bar extends right from base, border extends outward to the
102
+ // left shift left by halfBorder to keep the visual left
103
+ // edge at the zero line.
103
104
  itemX -= halfBorder;
104
105
  }
105
- else if (isFirstInStack && xValue < 0) {
106
- // Negative bar: border extends right, so shift position right by halfBorder
107
- // to keep the visual right edge at the zero line
106
+ else if (isFirstInStack && !extendsRight && xValue !== 0) {
107
+ // Bar extends left from base, border extends outward to the
108
+ // right shift right by halfBorder to keep the visual
109
+ // right edge at the zero line.
108
110
  itemX += halfBorder;
109
111
  }
110
112
  const item = {
@@ -121,7 +123,7 @@ export async function prepareBarYData(args) {
121
123
  isLastStackItem,
122
124
  };
123
125
  stackItems.push(item);
124
- if (xValue > baseRangeValue) {
126
+ if (extendsRight) {
125
127
  positiveStack += width;
126
128
  }
127
129
  else {
@@ -1,5 +1,5 @@
1
1
  import { path } from 'd3-path';
2
- import { calculateNumericProperty, getFormattedValue, getTextSizeFn } from '../../utils';
2
+ import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, } from '../../utils';
3
3
  function getLineConnectorPaths(args) {
4
4
  const { points } = args;
5
5
  const leftPath = path();
@@ -23,6 +23,7 @@ export async function prepareFunnelData(args) {
23
23
  const { series, boundsWidth, boundsHeight } = args;
24
24
  const items = [];
25
25
  const svgLabels = [];
26
+ const htmlLabels = [];
26
27
  const connectors = [];
27
28
  const maxValue = Math.max(...series.map((s) => s.data.value));
28
29
  const itemBandSpace = boundsHeight / series.length;
@@ -42,36 +43,55 @@ export async function prepareFunnelData(args) {
42
43
  if (s.dataLabels.enabled) {
43
44
  const d = s.data;
44
45
  const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
45
- const labelSize = await getTextSize(labelContent);
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);
46
57
  let x;
47
58
  switch (s.dataLabels.align) {
48
59
  case 'left': {
49
60
  x = 0;
50
- segmentLeftOffset = Math.max(segmentLeftOffset, labelSize.width);
61
+ segmentLeftOffset = Math.max(segmentLeftOffset, width);
51
62
  break;
52
63
  }
53
64
  case 'right': {
54
- x = boundsWidth - labelSize.width;
55
- segmentRightOffset = Math.max(segmentRightOffset, labelSize.width);
65
+ x = boundsWidth - width;
66
+ segmentRightOffset = Math.max(segmentRightOffset, width);
56
67
  break;
57
68
  }
58
69
  case 'center': {
59
- x = boundsWidth / 2 - labelSize.width / 2;
70
+ x = boundsWidth / 2 - width / 2;
60
71
  break;
61
72
  }
62
73
  }
63
- svgLabels.push({
64
- x,
65
- y: getSegmentY(index) +
66
- itemHeight / 2 -
67
- labelSize.height / 2 +
68
- labelSize.hangingOffset,
69
- text: labelContent,
70
- style: s.dataLabels.style,
71
- size: labelSize,
72
- textAnchor: 'start',
73
- series: s,
74
- });
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
+ });
83
+ }
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
+ });
94
+ }
75
95
  }
76
96
  }
77
97
  const segmentMaxWidth = boundsWidth - segmentLeftOffset - segmentRightOffset;
@@ -117,6 +137,7 @@ export async function prepareFunnelData(args) {
117
137
  type: 'funnel',
118
138
  items,
119
139
  svgLabels,
140
+ htmlLabels,
120
141
  connectors,
121
142
  };
122
143
  return data;
@@ -1,5 +1,5 @@
1
1
  import type { Path } from 'd3-path';
2
- import type { FunnelSeriesData, LabelData } from '../../../types';
2
+ import type { FunnelSeriesData, HtmlItem, LabelData } from '../../../types';
3
3
  import type { DashStyle } from '../../constants';
4
4
  import type { PreparedFunnelSeries } from '../../series/types';
5
5
  export type FunnelItemData = {
@@ -29,4 +29,5 @@ export type PreparedFunnelData = {
29
29
  items: FunnelItemData[];
30
30
  connectors: FunnelConnectorData[];
31
31
  svgLabels: LabelData[];
32
+ htmlLabels: HtmlItem[];
32
33
  };
@@ -2,6 +2,7 @@ import type { BaseType } from 'd3-selection';
2
2
  import type { PreparedXAxis, PreparedYAxis } from '../axes/types';
3
3
  import type { ChartScale } from '../scales/types';
4
4
  import type { BasicInactiveState } from '../types';
5
+ import type { ZoomState } from '../zoom/types';
5
6
  export declare function getXValue(args: {
6
7
  point: {
7
8
  x?: number | string | null;
@@ -74,3 +75,8 @@ export declare function getClipPathIdByBounds(args: {
74
75
  clipPathId: string;
75
76
  bounds?: 'horizontal';
76
77
  }): string;
78
+ export declare function getSeriesClipPathId(args: {
79
+ clipPathId: string;
80
+ yAxis: PreparedYAxis[];
81
+ zoomState?: Partial<ZoomState>;
82
+ }): string;
@@ -184,3 +184,12 @@ export function getClipPathIdByBounds(args) {
184
184
  const { bounds, clipPathId } = args;
185
185
  return bounds ? `${clipPathId}-${bounds}` : clipPathId;
186
186
  }
187
+ export function getSeriesClipPathId(args) {
188
+ const { clipPathId, yAxis, zoomState } = args;
189
+ const hasMinOrMax = yAxis.some((axis) => typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number');
190
+ const hasZoom = zoomState && Object.keys(zoomState).length > 0;
191
+ if (!hasZoom && !hasMinOrMax) {
192
+ return `${clipPathId}-horizontal`;
193
+ }
194
+ return clipPathId;
195
+ }
@@ -39,7 +39,7 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
39
39
  /** Opacity for the connector area. */
40
40
  areaOpacity?: number;
41
41
  };
42
- dataLabels?: Omit<BaseDataLabels, 'html' | 'allowOverlap'> & {
42
+ dataLabels?: Omit<BaseDataLabels, 'allowOverlap'> & {
43
43
  /** Horizontal alignment of the data labels. */
44
44
  align?: 'left' | 'center' | 'right';
45
45
  };
@@ -1,6 +1,6 @@
1
- import type React from 'react';
2
1
  import type { DashStyle, LineCap, LineJoin } from '../../constants';
3
2
  import type { MeaningfulAny } from '../misc';
3
+ import type { SVGTextAttributes } from '../renderer';
4
4
  import type { ChartAnnotationSeriesOptions } from './annotation';
5
5
  import type { AreaSeries, AreaSeriesData } from './area';
6
6
  import type { BarXSeries, BarXSeriesData } from './bar-x';
@@ -40,7 +40,7 @@ export interface BasicHoverState {
40
40
  export interface BasicInactiveState {
41
41
  /**
42
42
  * Enable separate styles for the inactive series.
43
- * @default true
43
+ * @default false for most series types; true for radar
44
44
  */
45
45
  enabled?: boolean;
46
46
  /**
@@ -55,7 +55,7 @@ export interface ChartSeriesOptions {
55
55
  /** Enable or disable the data labels */
56
56
  enabled?: boolean;
57
57
  /** Callback function to render the data label */
58
- renderer?: (args: DataLabelRendererData) => React.SVGTextElementAttributes<SVGTextElement>;
58
+ renderer?: (args: DataLabelRendererData) => SVGTextAttributes;
59
59
  };
60
60
  'bar-x'?: {
61
61
  /**
@@ -207,12 +207,12 @@ export interface ChartSeriesOptions {
207
207
  dashStyle?: DashStyle;
208
208
  /**
209
209
  * Options for line cap style
210
- * @default 'round' when dashStyle is not 'solid', 'none' when dashStyle is not 'solid'
210
+ * @default 'round' when dashStyle is 'Solid', 'none' when dashStyle is not 'Solid'
211
211
  */
212
212
  linecap?: `${LineCap}`;
213
213
  /**
214
214
  * Defines the shape to be used at the corners of the line
215
- * @default 'round' when dashStyle is not 'solid', 'unset' when dashStyle is not 'solid'
215
+ * @default 'round' when dashStyle is 'Solid', 'unset' when dashStyle is not 'Solid'
216
216
  */
217
217
  linejoin?: `${LineJoin}`;
218
218
  /** Default annotation settings for all line data points */
@@ -1,6 +1,7 @@
1
1
  import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
2
2
  import type { DateTimeLabelFormats } from '../../utils/time';
3
3
  import type { MeaningfulAny } from '../misc';
4
+ import type { RendererElement } from '../renderer';
4
5
  import type { AreaSeries, AreaSeriesData } from './area';
5
6
  import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
6
7
  import type { BarXSeries, BarXSeriesData } from './bar-x';
@@ -132,7 +133,7 @@ export type ChartTooltipSortComparator<T = MeaningfulAny> = (a: TooltipDataChunk
132
133
  export interface ChartTooltip<T = MeaningfulAny> {
133
134
  enabled?: boolean;
134
135
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
135
- renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
136
+ renderer?: (args: ChartTooltipRendererArgs<T>) => RendererElement | null;
136
137
  /**
137
138
  * Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
138
139
  * It is useful in cases where you need to display additional information, but keep the general format of the tooltip.
@@ -157,7 +158,7 @@ export interface ChartTooltip<T = MeaningfulAny> {
157
158
  * `<tr class="${className}"><td>${name}</td><td>${value}</td></tr>`
158
159
  * ```
159
160
  */
160
- rowRenderer?: ((args: ChartTooltipRowRendererArgs) => React.ReactElement | string) | null;
161
+ rowRenderer?: ((args: ChartTooltipRowRendererArgs) => RendererElement | string) | null;
161
162
  pin?: {
162
163
  enabled?: boolean;
163
164
  modifierKey?: 'altKey' | 'metaKey';
@@ -19,8 +19,8 @@ export interface ChartZoom {
19
19
  * Supported zoom types by series type:
20
20
  * - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
21
21
  * - `BarX`: `x`, `xy`
22
+ * - `BarY`: `y`, `xy`
22
23
  * - `XRange`: `x`
23
- * - `BarY`: `y`
24
24
  *
25
25
  * Default zoom type by series type:
26
26
  * - `BarY`: `y`
@@ -0,0 +1,2 @@
1
+ import type { Properties } from 'csstype';
2
+ export type CSSProperties = Properties;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Framework-agnostic renderer return type.
3
+ * Structurally compatible with React.ReactElement — callers in React contexts
4
+ * may narrow to it without changes.
5
+ */
6
+ export interface RendererElement {
7
+ type: unknown;
8
+ props: unknown;
9
+ key: unknown;
10
+ }
11
+ /**
12
+ * SVG text element attributes map.
13
+ * Replaces React.SVGTextElementAttributes<SVGTextElement> in core types.
14
+ */
15
+ export type SVGTextAttributes = object;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import type { Selection } from 'd3-selection';
2
2
  import type { BaseTextStyle, MeaningfulAny } from '../../types';
3
+ import type { CSSProperties } from '../types/css';
3
4
  /**
4
5
  * Approximate ratio of descenders relative to the full font em height.
5
6
  * Based on the Chromium hanging baseline algorithm where hanging offset ≈ ascent × 0.2.
@@ -12,7 +13,7 @@ export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTe
12
13
  export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, MeaningfulAny, unknown>, maxWidth: ((datum: T) => number) | number, currentWidth?: (datum: T) => number): void;
13
14
  export declare function getLabelsSize({ labels, style, rotation, html, }: {
14
15
  labels: string[];
15
- style?: BaseTextStyle & React.CSSProperties;
16
+ style?: BaseTextStyle & CSSProperties;
16
17
  rotation?: number;
17
18
  html?: boolean;
18
19
  }): Promise<{
@@ -60,6 +60,14 @@ export function getZoomedSeriesData(args) {
60
60
  if (!isPreparedZoomableSeries(seriesItem)) {
61
61
  return;
62
62
  }
63
+ // For stacked series the chart-space position of each point is the
64
+ // cumulative stack sum, not its individual value, so the value-axis
65
+ // filter would drop segments that still need to participate in the
66
+ // stack. Skip it and let the axis range / clip handle visibility.
67
+ const isStacked = 'stacking' in seriesItem && Boolean(seriesItem.stacking);
68
+ const stackedValueAxis = isStacked && 'valueAxis' in seriesItem ? seriesItem.valueAxis : undefined;
69
+ const skipXFilter = stackedValueAxis === 'x';
70
+ const skipYFilter = stackedValueAxis === 'y';
63
71
  seriesItem.data.forEach((point, i) => {
64
72
  var _a, _b;
65
73
  const prevPoint = seriesItem.data[i - 1];
@@ -67,7 +75,7 @@ export function getZoomedSeriesData(args) {
67
75
  let inXRange = true;
68
76
  let inYRange = true;
69
77
  prevPointInRange = currentPointInRange;
70
- if (zoomState.x) {
78
+ if (zoomState.x && !skipXFilter) {
71
79
  const [xMin, xMax] = zoomState.x;
72
80
  if ('x0' in point && 'x1' in point) {
73
81
  const isStartInRange = isValueInRange({
@@ -94,7 +102,7 @@ export function getZoomedSeriesData(args) {
94
102
  });
95
103
  }
96
104
  }
97
- if (zoomState.y) {
105
+ if (zoomState.y && !skipYFilter) {
98
106
  const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
99
107
  ? seriesItem.yAxis
100
108
  : 0;
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import { renderFunnel } from '../../../core/shapes/funnel/renderer';
3
3
  import { block } from '../../../utils';
4
+ import { HtmlLayer } from '../HtmlLayer';
4
5
  export { prepareFunnelData } from '../../../core/shapes/funnel/prepare-data';
5
6
  export * from '../../../core/shapes/funnel/types';
6
7
  const b = block('funnel');
7
8
  export const FunnelSeriesShapes = (args) => {
8
- const { dispatcher, preparedData, seriesOptions } = args;
9
+ const { dispatcher, htmlLayout, preparedData, seriesOptions } = args;
9
10
  const ref = React.useRef(null);
10
11
  React.useEffect(() => {
11
12
  if (!ref.current) {
@@ -13,6 +14,8 @@ export const FunnelSeriesShapes = (args) => {
13
14
  }
14
15
  return renderFunnel({ plot: ref.current }, preparedData, seriesOptions, dispatcher);
15
16
  }, [dispatcher, preparedData, seriesOptions]);
17
+ const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.htmlLabels }), [preparedData.htmlLabels]);
16
18
  return (React.createElement(React.Fragment, null,
17
- React.createElement("g", { ref: ref, className: b() })));
19
+ React.createElement("g", { ref: ref, className: b() }),
20
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
18
21
  };
@@ -1,8 +1 @@
1
- import type { ZoomState } from '../../core/zoom/types';
2
- import type { PreparedYAxis } from '../useAxis/types';
3
1
  export * from '../../core/shapes/utils';
4
- export declare function getSeriesClipPathId(args: {
5
- clipPathId: string;
6
- yAxis: PreparedYAxis[];
7
- zoomState?: Partial<ZoomState>;
8
- }): string;
@@ -1,12 +1 @@
1
1
  export * from '../../core/shapes/utils';
2
- export function getSeriesClipPathId(args) {
3
- const { clipPathId, yAxis, zoomState } = args;
4
- const hasMinOrMax = yAxis.some((axis) => {
5
- return typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number';
6
- });
7
- const hasZoom = zoomState && Object.keys(zoomState).length > 0;
8
- if (!hasZoom && !hasMinOrMax) {
9
- return `${clipPathId}-horizontal`;
10
- }
11
- return clipPathId;
12
- }
@@ -1,4 +1,5 @@
1
1
  import type { BaseTextStyle } from '../core/types/chart/base';
2
+ import type { CSSProperties } from '../core/types/css';
2
3
  export interface LabelData {
3
4
  text: string;
4
5
  x: number;
@@ -23,7 +24,7 @@ export interface HtmlItem {
23
24
  width: number;
24
25
  height: number;
25
26
  };
26
- style?: BaseTextStyle & React.CSSProperties;
27
+ style?: BaseTextStyle & CSSProperties;
27
28
  /** Coordinate space for positioning: 'plot' uses the plot area origin, 'chart' uses the full chart origin. Defaults to 'plot'. */
28
29
  scope?: 'plot' | 'chart';
29
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.49.0",
3
+ "version": "1.50.0",
4
4
  "description": "A flexible JavaScript library for data visualization and chart rendering using React",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",
@@ -71,6 +71,7 @@
71
71
  "@gravity-ui/date-utils": "^2.5.4",
72
72
  "@gravity-ui/i18n": "^1.6.0",
73
73
  "@gravity-ui/icons": "^2.15.0",
74
+ "csstype": "^3.2.3",
74
75
  "d3-array": "^3.2.4",
75
76
  "d3-axis": "^3.0.0",
76
77
  "d3-brush": "^3.0.0",