vccs 0.4.1 → 0.6.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 (56) hide show
  1. package/dist/cartesian/axis/YAxis.d.ts +5 -5
  2. package/dist/cartesian/cartesian-axis/CartesianAxis.d.ts +12 -1
  3. package/dist/cartesian/cartesianPositionToCSSTranslate.d.ts +15 -0
  4. package/dist/cartesian/getCartesianPosition.d.ts +45 -0
  5. package/dist/cartesian/index.d.ts +1 -0
  6. package/dist/cartesian/type.d.ts +8 -0
  7. package/dist/chart/{RechartsWrapper.d.ts → ChartsWrapper.d.ts} +16 -1
  8. package/dist/chart/Surface.d.ts +2 -2
  9. package/dist/chart/generateCategoricalChart.d.ts +4 -0
  10. package/dist/components/Tooltip.d.ts +1 -1
  11. package/dist/components/legend/hooks/useLegend.d.ts +3 -0
  12. package/dist/components/legend/type.d.ts +18 -2
  13. package/dist/components/legend/utils.d.ts +9 -0
  14. package/dist/container/Surface.d.ts +3 -3
  15. package/dist/es/cartesian/axis/XAxis.mjs +11 -5
  16. package/dist/es/cartesian/axis/YAxis.mjs +62 -9
  17. package/dist/es/cartesian/cartesian-axis/CartesianAxis.mjs +6 -1
  18. package/dist/es/cartesian/cartesianPositionToCSSTranslate.mjs +28 -0
  19. package/dist/es/cartesian/getCartesianPosition.mjs +176 -0
  20. package/dist/es/chart/{RechartsWrapper.mjs → ChartsWrapper.mjs} +26 -6
  21. package/dist/es/chart/Sankey.mjs +2 -2
  22. package/dist/es/chart/SunburstChart.mjs +2 -2
  23. package/dist/es/chart/Surface.vue_vue_type_script_setup_true_lang.mjs +7 -1
  24. package/dist/es/chart/Treemap.mjs +2 -2
  25. package/dist/es/chart/generateCategoricalChart.mjs +53 -49
  26. package/dist/es/components/legend/Legend.mjs +5 -3
  27. package/dist/es/components/legend/hooks/useLegend.mjs +50 -9
  28. package/dist/es/components/legend/type.mjs +9 -1
  29. package/dist/es/components/legend/utils.mjs +12 -1
  30. package/dist/es/container/ResponsiveContainer.vue_vue_type_script_setup_true_lang.mjs +2 -11
  31. package/dist/es/container/Surface.mjs +5 -1
  32. package/dist/es/hooks/useResponsiveSize.mjs +27 -0
  33. package/dist/es/hooks/useRoundedSize.mjs +31 -0
  34. package/dist/es/shape/Sector.mjs +1 -1
  35. package/dist/es/state/cartesianAxisSlice.mjs +13 -2
  36. package/dist/es/state/legendSlice.mjs +2 -0
  37. package/dist/es/state/selectors/axisSelectors.mjs +3 -3
  38. package/dist/es/state/selectors/barSelectors.mjs +1 -1
  39. package/dist/es/state/selectors/pieSelectors.mjs +1 -1
  40. package/dist/es/state/selectors/polarAxisSelectors.mjs +1 -1
  41. package/dist/es/state/selectors/radialBarSelectors.mjs +1 -1
  42. package/dist/es/state/selectors/selectChartOffset.mjs +7 -4
  43. package/dist/es/state/selectors/selectLegendArea.mjs +19 -0
  44. package/dist/es/state/selectors/tooltipSelectors.mjs +1 -1
  45. package/dist/es/state/touchEventsMiddleware.mjs +1 -1
  46. package/dist/es/utils/YAxisUtils.mjs +29 -0
  47. package/dist/es/utils/chart.mjs +1 -1
  48. package/dist/es/utils/legend.mjs +23 -1
  49. package/dist/hooks/useResponsiveSize.d.ts +22 -0
  50. package/dist/hooks/useRoundedSize.d.ts +22 -0
  51. package/dist/state/cartesianAxisSlice.d.ts +11 -3
  52. package/dist/state/legendSlice.d.ts +3 -0
  53. package/dist/state/selectors/selectLegendArea.d.ts +7 -0
  54. package/dist/types/axis.d.ts +11 -1
  55. package/dist/utils/YAxisUtils.d.ts +20 -0
  56. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ import { isNumber, isPercent } from "../utils/validate.mjs";
2
+ import { getPercentValue } from "../utils/data.mjs";
3
+ //#region src/cartesian/getCartesianPosition.ts
4
+ function cartesianViewBoxToTrapezoid(box) {
5
+ if (!box) return;
6
+ return {
7
+ x: box.x,
8
+ y: box.y,
9
+ upperWidth: "upperWidth" in box ? box.upperWidth : box.width,
10
+ lowerWidth: "lowerWidth" in box ? box.lowerWidth : box.width,
11
+ width: box.width,
12
+ height: box.height
13
+ };
14
+ }
15
+ /**
16
+ * Calculates the position and alignment for a generic element in a Cartesian coordinate system.
17
+ *
18
+ * @param options - The options including viewBox, position, and offset.
19
+ * @returns The calculated x, y, alignment and size.
20
+ */
21
+ function getCartesianPosition(options) {
22
+ const { viewBox, position, offset = 0, parentViewBox, clamp } = options;
23
+ const { x, y, height, upperWidth, lowerWidth } = cartesianViewBoxToTrapezoid(viewBox);
24
+ const upperX = x;
25
+ const lowerX = x + (upperWidth - lowerWidth) / 2;
26
+ const middleX = (upperX + lowerX) / 2;
27
+ const midHeightWidth = (upperWidth + lowerWidth) / 2;
28
+ const centerX = upperX + upperWidth / 2;
29
+ const verticalSign = height >= 0 ? 1 : -1;
30
+ const verticalOffset = verticalSign * offset;
31
+ const verticalEnd = verticalSign > 0 ? "end" : "start";
32
+ const verticalStart = verticalSign > 0 ? "start" : "end";
33
+ const horizontalSign = upperWidth >= 0 ? 1 : -1;
34
+ const horizontalOffset = horizontalSign * offset;
35
+ const horizontalEnd = horizontalSign > 0 ? "end" : "start";
36
+ const horizontalStart = horizontalSign > 0 ? "start" : "end";
37
+ if (position === "top") {
38
+ const result = {
39
+ x: upperX + upperWidth / 2,
40
+ y: y - verticalOffset,
41
+ horizontalAnchor: "middle",
42
+ verticalAnchor: verticalEnd
43
+ };
44
+ if (clamp && parentViewBox) {
45
+ result.height = Math.max(y - parentViewBox.y, 0);
46
+ result.width = upperWidth;
47
+ }
48
+ return result;
49
+ }
50
+ if (position === "bottom") {
51
+ const result = {
52
+ x: lowerX + lowerWidth / 2,
53
+ y: y + height + verticalOffset,
54
+ horizontalAnchor: "middle",
55
+ verticalAnchor: verticalStart
56
+ };
57
+ if (clamp && parentViewBox) {
58
+ result.height = Math.max(parentViewBox.y + parentViewBox.height - (y + height), 0);
59
+ result.width = lowerWidth;
60
+ }
61
+ return result;
62
+ }
63
+ if (position === "left") {
64
+ const result = {
65
+ x: middleX - horizontalOffset,
66
+ y: y + height / 2,
67
+ horizontalAnchor: horizontalEnd,
68
+ verticalAnchor: "middle"
69
+ };
70
+ if (clamp && parentViewBox) {
71
+ result.width = Math.max(result.x - parentViewBox.x, 0);
72
+ result.height = height;
73
+ }
74
+ return result;
75
+ }
76
+ if (position === "right") {
77
+ const result = {
78
+ x: middleX + midHeightWidth + horizontalOffset,
79
+ y: y + height / 2,
80
+ horizontalAnchor: horizontalStart,
81
+ verticalAnchor: "middle"
82
+ };
83
+ if (clamp && parentViewBox) {
84
+ result.width = Math.max(parentViewBox.x + parentViewBox.width - result.x, 0);
85
+ result.height = height;
86
+ }
87
+ return result;
88
+ }
89
+ const sizeAttrs = clamp && parentViewBox ? {
90
+ width: midHeightWidth,
91
+ height
92
+ } : {};
93
+ if (position === "insideLeft") return {
94
+ x: middleX + horizontalOffset,
95
+ y: y + height / 2,
96
+ horizontalAnchor: horizontalStart,
97
+ verticalAnchor: "middle",
98
+ ...sizeAttrs
99
+ };
100
+ if (position === "insideRight") return {
101
+ x: middleX + midHeightWidth - horizontalOffset,
102
+ y: y + height / 2,
103
+ horizontalAnchor: horizontalEnd,
104
+ verticalAnchor: "middle",
105
+ ...sizeAttrs
106
+ };
107
+ if (position === "insideTop") return {
108
+ x: upperX + upperWidth / 2,
109
+ y: y + verticalOffset,
110
+ horizontalAnchor: "middle",
111
+ verticalAnchor: verticalStart,
112
+ ...sizeAttrs
113
+ };
114
+ if (position === "insideBottom") return {
115
+ x: lowerX + lowerWidth / 2,
116
+ y: y + height - verticalOffset,
117
+ horizontalAnchor: "middle",
118
+ verticalAnchor: verticalEnd,
119
+ ...sizeAttrs
120
+ };
121
+ if (position === "insideTopLeft") return {
122
+ x: upperX + horizontalOffset,
123
+ y: y + verticalOffset,
124
+ horizontalAnchor: horizontalStart,
125
+ verticalAnchor: verticalStart,
126
+ ...sizeAttrs
127
+ };
128
+ if (position === "insideTopRight") return {
129
+ x: upperX + upperWidth - horizontalOffset,
130
+ y: y + verticalOffset,
131
+ horizontalAnchor: horizontalEnd,
132
+ verticalAnchor: verticalStart,
133
+ ...sizeAttrs
134
+ };
135
+ if (position === "insideBottomLeft") return {
136
+ x: lowerX + horizontalOffset,
137
+ y: y + height - verticalOffset,
138
+ horizontalAnchor: horizontalStart,
139
+ verticalAnchor: verticalEnd,
140
+ ...sizeAttrs
141
+ };
142
+ if (position === "insideBottomRight") return {
143
+ x: lowerX + lowerWidth - horizontalOffset,
144
+ y: y + height - verticalOffset,
145
+ horizontalAnchor: horizontalEnd,
146
+ verticalAnchor: verticalEnd,
147
+ ...sizeAttrs
148
+ };
149
+ if (!!position && typeof position === "object" && (isNumber(position.x) || isPercent(position.x)) && (isNumber(position.y) || isPercent(position.y))) return {
150
+ x: x + getPercentValue(position.x, midHeightWidth),
151
+ y: y + getPercentValue(position.y, height),
152
+ horizontalAnchor: "end",
153
+ verticalAnchor: "end",
154
+ ...sizeAttrs
155
+ };
156
+ return {
157
+ x: centerX,
158
+ y: y + height / 2,
159
+ horizontalAnchor: "middle",
160
+ verticalAnchor: "middle",
161
+ ...sizeAttrs
162
+ };
163
+ }
164
+ var absolutePositions = [
165
+ "top",
166
+ "left",
167
+ "right",
168
+ "bottom"
169
+ ];
170
+ function isOutsidePosition(position) {
171
+ if (position == null) return false;
172
+ if (typeof position === "object") return true;
173
+ return absolutePositions.includes(position);
174
+ }
175
+ //#endregion
176
+ export { getCartesianPosition, isOutsidePosition };
@@ -10,10 +10,11 @@ import { useSynchronisedEventsFromOtherCharts } from "../events/useChartSynchron
10
10
  import { useReportScale } from "../state/utils/useReportScale.mjs";
11
11
  import { providePortalRaw } from "./TooltipPortalContext.mjs";
12
12
  import { provideLegendPortalRaw } from "./LegendPortalContext.mjs";
13
- import { createVNode, defineComponent, ref } from "vue";
14
- //#region src/chart/RechartsWrapper.tsx
15
- var RechartsWrapper = /* @__PURE__ */ defineComponent({
16
- name: "RechartsWrapper",
13
+ import { createVNode, defineComponent, ref, watch } from "vue";
14
+ import { useResizeObserver } from "@vueuse/core";
15
+ //#region src/chart/ChartsWrapper.tsx
16
+ var ChartsWrapper = /* @__PURE__ */ defineComponent({
17
+ name: "ChartsWrapper",
17
18
  props: {
18
19
  class: classProp,
19
20
  height: {
@@ -28,9 +29,14 @@ var RechartsWrapper = /* @__PURE__ */ defineComponent({
28
29
  onMouseLeave: { type: Function },
29
30
  onMouseMove: { type: Function },
30
31
  onMouseUp: { type: Function },
32
+ onResize: { type: Function },
31
33
  onTouchEnd: { type: Function },
32
34
  onTouchMove: { type: Function },
33
35
  onTouchStart: { type: Function },
36
+ responsive: {
37
+ type: Boolean,
38
+ default: false
39
+ },
34
40
  style: { type: [
35
41
  String,
36
42
  Object,
@@ -49,11 +55,20 @@ var RechartsWrapper = /* @__PURE__ */ defineComponent({
49
55
  const legendPortal = ref(null);
50
56
  providePortalRaw(tooltipPortal);
51
57
  provideLegendPortalRaw(legendPortal);
58
+ const wrapperEl = ref(null);
52
59
  const innerRef = (node) => {
53
60
  scaleRef.value = node;
54
61
  tooltipPortal.value = node;
55
62
  legendPortal.value = node;
63
+ wrapperEl.value = node;
56
64
  };
65
+ watch(() => props.responsive, (responsive, _, onCleanup) => {
66
+ if (!responsive) return;
67
+ onCleanup(useResizeObserver(wrapperEl, (entries) => {
68
+ const { width, height } = entries[0].contentRect;
69
+ props.onResize?.(width, height);
70
+ }).stop);
71
+ }, { immediate: true });
57
72
  const myOnClick = (e) => {
58
73
  const chartPointer = getChartPointer(e);
59
74
  if (chartPointer) dispatch(mouseClickAction(chartPointer));
@@ -136,7 +151,12 @@ var RechartsWrapper = /* @__PURE__ */ defineComponent({
136
151
  };
137
152
  return () => createVNode("div", {
138
153
  "class": ["v-charts-wrapper", props.class],
139
- "style": [{
154
+ "style": [props.responsive ? {
155
+ position: "relative",
156
+ cursor: "default",
157
+ width: "100%",
158
+ height: "100%"
159
+ } : {
140
160
  position: "relative",
141
161
  cursor: "default",
142
162
  width: `${props.width}px`,
@@ -161,4 +181,4 @@ var RechartsWrapper = /* @__PURE__ */ defineComponent({
161
181
  }
162
182
  });
163
183
  //#endregion
164
- export { RechartsWrapper };
184
+ export { ChartsWrapper };
@@ -3,7 +3,7 @@ import { useAppDispatch } from "../state/hooks.mjs";
3
3
  import { Layer } from "../container/Layer.mjs";
4
4
  import { Animate } from "../animation/Animate.mjs";
5
5
  import { createRechartsStore } from "../state/store.mjs";
6
- import { RechartsWrapper } from "./RechartsWrapper.mjs";
6
+ import { ChartsWrapper } from "./ChartsWrapper.mjs";
7
7
  import Surface from "../container/Surface.mjs";
8
8
  import { computeSankeyLayout, linkPathGenerator } from "./sankeyUtils.mjs";
9
9
  import { Fragment, computed, createVNode, defineComponent, watchEffect } from "vue";
@@ -318,7 +318,7 @@ var Sankey = /* @__PURE__ */ defineComponent({
318
318
  provideStore({ store: createRechartsStore({ options: sankeyOptions }, "Sankey") });
319
319
  return () => {
320
320
  if (!props.data || !props.data.nodes || props.data.nodes.length === 0) return null;
321
- return createVNode(RechartsWrapper, {
321
+ return createVNode(ChartsWrapper, {
322
322
  "width": props.width,
323
323
  "height": props.height
324
324
  }, { default: () => [createVNode(SankeyInner, props, {
@@ -3,7 +3,7 @@ import { addTooltipEntrySettings, mouseLeaveItem, removeTooltipEntrySettings, se
3
3
  import { useAppDispatch } from "../state/hooks.mjs";
4
4
  import { Layer } from "../container/Layer.mjs";
5
5
  import { createRechartsStore } from "../state/store.mjs";
6
- import { RechartsWrapper } from "./RechartsWrapper.mjs";
6
+ import { ChartsWrapper } from "./ChartsWrapper.mjs";
7
7
  import Surface from "../container/Surface.mjs";
8
8
  import { Sector } from "../shape/Sector.mjs";
9
9
  import { computeSunburstLayout } from "./sunburstUtils.mjs";
@@ -218,7 +218,7 @@ var SunburstChart = /* @__PURE__ */ defineComponent({
218
218
  provideStore({ store: createRechartsStore({ options: sunburstOptions }, "SunburstChart") });
219
219
  return () => {
220
220
  if (!props.data?.children || props.data.children.length === 0) return null;
221
- return createVNode(RechartsWrapper, {
221
+ return createVNode(ChartsWrapper, {
222
222
  "width": props.width,
223
223
  "height": props.height
224
224
  }, { default: () => [createVNode(SunburstInner, props, { content: slots.content }), slots.default?.()] });
@@ -17,7 +17,13 @@ var Surface_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ define
17
17
  height: {},
18
18
  viewBox: {},
19
19
  class: {},
20
- style: {},
20
+ style: { type: [
21
+ Boolean,
22
+ null,
23
+ String,
24
+ Object,
25
+ Array
26
+ ] },
21
27
  title: {},
22
28
  desc: {}
23
29
  },
@@ -4,7 +4,7 @@ import { Layer } from "../container/Layer.mjs";
4
4
  import { getStringSize } from "../utils/attrs.mjs";
5
5
  import { Animate } from "../animation/Animate.mjs";
6
6
  import { createRechartsStore } from "../state/store.mjs";
7
- import { RechartsWrapper } from "./RechartsWrapper.mjs";
7
+ import { ChartsWrapper } from "./ChartsWrapper.mjs";
8
8
  import Surface from "../container/Surface.mjs";
9
9
  import { computeTreemapLayout } from "./treemapUtils.mjs";
10
10
  import { Fragment, computed, createTextVNode, createVNode, defineComponent, ref, watchEffect } from "vue";
@@ -368,7 +368,7 @@ var Treemap = /* @__PURE__ */ defineComponent({
368
368
  provideStore({ store: createRechartsStore({ options: treemapOptions }, "Treemap") });
369
369
  return () => {
370
370
  if (!props.data || props.data.length === 0) return null;
371
- return createVNode(RechartsWrapper, {
371
+ return createVNode(ChartsWrapper, {
372
372
  "width": props.width,
373
373
  "height": props.height
374
374
  }, { default: () => [createVNode(TreemapInner, props, { content: slots.content }), slots.default?.()] });
@@ -1,16 +1,16 @@
1
1
  import { classProp } from "../types/common.mjs";
2
- import { validateWidthHeight } from "../utils/validate.mjs";
3
2
  import { provideClipPathId } from "./provideClipPathId.mjs";
4
3
  import { createRechartsStore } from "../state/store.mjs";
5
4
  import Surface_default from "./Surface.mjs";
6
5
  import { ChartDataContextProvider } from "../context/ChartDataContextProvider.mjs";
7
6
  import ClipPath_default from "../container/ClipPath.mjs";
8
- import { RechartsWrapper } from "./RechartsWrapper.mjs";
7
+ import { ChartsWrapper } from "./ChartsWrapper.mjs";
9
8
  import { FULL_WIDTH_AND_HEIGHT } from "./const.mjs";
10
9
  import { ReportMainChartProps } from "../state/ReportMainChartProps.mjs";
11
10
  import ReportChartProps_default from "../state/ReportChartProps.mjs";
12
11
  import { applyDefaultProps } from "../utils/props.mjs";
13
12
  import { ReportPolarOptions } from "../state/ReportPolarOptions.mjs";
13
+ import { useResponsiveSize } from "../hooks/useResponsiveSize.mjs";
14
14
  import { Fragment, createVNode, defineComponent, mergeProps } from "vue";
15
15
  import { provideStore } from "@reduxjs/vue-redux";
16
16
  //#region src/chart/generateCategoricalChart.tsx
@@ -63,6 +63,10 @@ var CategoricalProps = {
63
63
  },
64
64
  maxBarSize: { type: Number },
65
65
  outerRadius: { type: [Number, String] },
66
+ responsive: {
67
+ type: Boolean,
68
+ default: false
69
+ },
66
70
  reverseStackOrder: {
67
71
  type: Boolean,
68
72
  default: false
@@ -102,38 +106,43 @@ function generateCategoricalChart({ chartName, defaultProps = {}, defaultTooltip
102
106
  eventEmitter: void 0
103
107
  } }, props.id ?? chartName) });
104
108
  const clipPathId = provideClipPathId(props);
109
+ const { effectiveWidth, effectiveHeight, hasValidSize, handleResize } = useResponsiveSize(props);
110
+ function renderPolarOptions(isPolarChart) {
111
+ if (!isPolarChart) return null;
112
+ return createVNode(ReportPolarOptions, {
113
+ "cx": props.cx ?? "50%",
114
+ "cy": props.cy ?? "50%",
115
+ "startAngle": props.startAngle ?? defaultProps.startAngle ?? 90,
116
+ "endAngle": props.endAngle ?? defaultProps.endAngle ?? -270,
117
+ "innerRadius": props.innerRadius ?? 0,
118
+ "outerRadius": props.outerRadius ?? "80%"
119
+ }, null);
120
+ }
105
121
  return () => {
106
- const { compact, width, height, title, desc, ...rest } = props;
122
+ const { compact, width, height, title, desc, responsive, ...rest } = props;
107
123
  const attributes = { ...attrs };
108
- if (!validateWidthHeight({
109
- width,
110
- height
111
- })) return null;
112
124
  const layout = props.layout ?? defaultProps.layout ?? defaultLayout;
113
125
  const isPolarChart = layout === "centric" || layout === "radial";
114
- if (compact) return createVNode(Fragment, null, [
115
- createVNode(ChartDataContextProvider, { "chartData": props.data }, null),
116
- createVNode(ReportMainChartProps, {
117
- "width": width,
118
- "height": height,
119
- "layout": layout,
120
- "margin": props.margin ?? defaultMargin
121
- }, null),
122
- isPolarChart && createVNode(ReportPolarOptions, {
123
- "cx": props.cx ?? "50%",
124
- "cy": props.cy ?? "50%",
125
- "startAngle": props.startAngle ?? defaultProps.startAngle ?? 90,
126
- "endAngle": props.endAngle ?? defaultProps.endAngle ?? -270,
127
- "innerRadius": props.innerRadius ?? 0,
128
- "outerRadius": props.outerRadius ?? "80%"
129
- }, null),
130
- createVNode(Surface_default, mergeProps(attrs, rest, {
131
- "width": width,
132
- "height": height,
133
- "title": title,
134
- "desc": desc
135
- }), { default: () => [createVNode(ClipPath_default, { "clipPathId": clipPathId }, null), slots.default?.()] })
136
- ]);
126
+ if (compact) {
127
+ if (!hasValidSize.value) return null;
128
+ return createVNode(Fragment, null, [
129
+ createVNode(ChartDataContextProvider, { "chartData": props.data }, null),
130
+ createVNode(ReportMainChartProps, {
131
+ "width": effectiveWidth.value,
132
+ "height": effectiveHeight.value,
133
+ "layout": layout,
134
+ "margin": props.margin
135
+ }, null),
136
+ renderPolarOptions(isPolarChart),
137
+ createVNode(Surface_default, mergeProps(attrs, rest, {
138
+ "width": effectiveWidth.value,
139
+ "height": effectiveHeight.value,
140
+ "title": title,
141
+ "desc": desc
142
+ }), { default: () => [createVNode(ClipPath_default, { "clipPathId": clipPathId }, null), slots.default?.()] })
143
+ ]);
144
+ }
145
+ if (!responsive && !hasValidSize.value) return null;
137
146
  if (props.accessibilityLayer) {
138
147
  attributes.tabindex = props.tabIndex ?? 0;
139
148
  attributes.role = props.role ?? "application";
@@ -143,34 +152,29 @@ function generateCategoricalChart({ chartName, defaultProps = {}, defaultTooltip
143
152
  for (const [key, value] of Object.entries(attributes)) if (key.startsWith("on") && typeof value === "function") eventHandlerAttrs[key] = value;
144
153
  else svgAttributes[key] = value;
145
154
  return createVNode(Fragment, null, [
146
- createVNode(ChartDataContextProvider, { "chartData": props.data }, null),
147
- createVNode(ReportMainChartProps, {
148
- "width": width,
149
- "height": height,
155
+ hasValidSize.value && createVNode(ChartDataContextProvider, { "chartData": props.data }, null),
156
+ hasValidSize.value && createVNode(ReportMainChartProps, {
157
+ "width": effectiveWidth.value,
158
+ "height": effectiveHeight.value,
150
159
  "layout": layout,
151
160
  "margin": props.margin ?? defaultMargin
152
161
  }, null),
153
- isPolarChart && createVNode(ReportPolarOptions, {
154
- "cx": props.cx ?? "50%",
155
- "cy": props.cy ?? "50%",
156
- "startAngle": props.startAngle ?? defaultProps.startAngle ?? 90,
157
- "endAngle": props.endAngle ?? defaultProps.endAngle ?? -270,
158
- "innerRadius": props.innerRadius ?? 0,
159
- "outerRadius": props.outerRadius ?? "80%"
160
- }, null),
161
- createVNode(RechartsWrapper, mergeProps({
162
+ hasValidSize.value && renderPolarOptions(isPolarChart),
163
+ createVNode(ChartsWrapper, mergeProps({
164
+ "responsive": responsive,
165
+ "onResize": handleResize,
162
166
  "style": props.style,
163
167
  "class": props.class,
164
- "width": width,
165
- "height": height
166
- }, eventHandlerAttrs), { default: () => [createVNode(Surface_default, mergeProps(svgAttributes, {
167
- "width": width,
168
- "height": height,
168
+ "width": effectiveWidth.value,
169
+ "height": effectiveHeight.value
170
+ }, eventHandlerAttrs), { default: () => [hasValidSize.value && createVNode(Surface_default, mergeProps(svgAttributes, {
171
+ "width": effectiveWidth.value,
172
+ "height": effectiveHeight.value,
169
173
  "title": title,
170
174
  "desc": desc,
171
175
  "style": FULL_WIDTH_AND_HEIGHT
172
176
  }), { default: () => [createVNode(ClipPath_default, { "clipPathId": clipPathId }, null), slots.default?.()] }), slots.tooltip?.()] }),
173
- createVNode(ReportChartProps_default, props, null)
177
+ hasValidSize.value && createVNode(ReportChartProps_default, props, null)
174
178
  ]);
175
179
  };
176
180
  }
@@ -12,7 +12,7 @@ var Legend_default = /* @__PURE__ */ defineComponent({
12
12
  name: "Legend",
13
13
  props: LegendVueProps,
14
14
  setup(props, { slots }) {
15
- const { legendRef, processedPayload, outerStyle, legendPortal, syncSettings, syncSize } = useLegend(props);
15
+ const { legendRef, processedPayload, outerStyle, legendPortal, resolvedLayout, positionViewBox, syncSettings, syncSize } = useLegend(props);
16
16
  const { getItemStyle, getSvgStyle, formatValue, handleClick, handleMouseEnter, handleMouseLeave } = useLegendContent(props);
17
17
  watchEffect(() => {
18
18
  syncSettings();
@@ -20,7 +20,8 @@ var Legend_default = /* @__PURE__ */ defineComponent({
20
20
  });
21
21
  const renderDefaultContent = () => {
22
22
  if (!processedPayload.value || processedPayload.value.length === 0) return null;
23
- const { layout = "horizontal", align = "center", iconSize = 14 } = props;
23
+ const { align = "center", iconSize = 14 } = props;
24
+ const layout = resolvedLayout.value;
24
25
  const finalStyle = {
25
26
  padding: 0,
26
27
  margin: 0,
@@ -73,13 +74,14 @@ var Legend_default = /* @__PURE__ */ defineComponent({
73
74
  if (!processedPayload.value || processedPayload.value.length === 0) return null;
74
75
  const contentProps = {
75
76
  ...props,
77
+ layout: resolvedLayout.value,
76
78
  payload: processedPayload.value
77
79
  };
78
80
  if (slots.content) return slots.content(contentProps);
79
81
  return renderDefaultContent();
80
82
  };
81
83
  return () => {
82
- if (legendPortal.value == null) return null;
84
+ if (legendPortal.value == null || props.position != null && positionViewBox.value == null) return null;
83
85
  const legendElement = createVNode("div", {
84
86
  "class": "v-charts-legend-wrapper",
85
87
  "style": outerStyle.value,
@@ -1,10 +1,13 @@
1
1
  import { selectLegendPayload } from "../../../state/selectors/legendSelectors.mjs";
2
+ import { getCartesianPosition, isOutsidePosition } from "../../../cartesian/getCartesianPosition.mjs";
2
3
  import { useAppDispatch, useAppSelector } from "../../../state/hooks.mjs";
3
- import { useChartHeight, useChartWidth, useMargin } from "../../../context/chartLayoutContext.mjs";
4
+ import { useChartHeight, useChartWidth, useMargin, useViewBox } from "../../../context/chartLayoutContext.mjs";
4
5
  import { setLegendSettings, setLegendSize } from "../../../state/legendSlice.mjs";
5
6
  import { useLegendPortal } from "../../../chart/LegendPortalContext.mjs";
7
+ import { selectLegendArea } from "../../../state/selectors/selectLegendArea.mjs";
6
8
  import { getUniqPayload } from "../../../utils/payload/getUniqPayload.mjs";
7
- import { defaultUniqBy, getDefaultPosition, getWidthOrHeight } from "../utils.mjs";
9
+ import { cartesianPositionToCSSTranslate } from "../../../cartesian/cartesianPositionToCSSTranslate.mjs";
10
+ import { defaultUniqBy, getDefaultPosition, getLayoutForPosition, getOutsidePositionOffset, getWidthOrHeight } from "../utils.mjs";
8
11
  import { computed, ref } from "vue";
9
12
  import { sortBy } from "es-toolkit/compat";
10
13
  import { useElementBounding } from "@vueuse/core";
@@ -16,14 +19,22 @@ function useLegend(props) {
16
19
  const margin = useMargin();
17
20
  const chartWidth = useChartWidth();
18
21
  const chartHeight = useChartHeight();
22
+ const viewBox = useViewBox();
23
+ const legendArea = useAppSelector(selectLegendArea);
19
24
  const legendRef = ref();
20
25
  const { width: boundingWidth, height: boundingHeight } = useElementBounding(legendRef);
26
+ const resolvedLayout = computed(() => props.layout && props.layout !== "auto" ? props.layout : getLayoutForPosition(props.position));
21
27
  const maxWidth = computed(() => chartWidth.value - (margin.value.left || 0) - (margin.value.right || 0));
22
- const widthOrHeight = computed(() => getWidthOrHeight(props.layout, props.height, props.width, maxWidth.value));
28
+ const widthOrHeight = computed(() => getWidthOrHeight(resolvedLayout.value, props.height, props.width, maxWidth.value));
23
29
  const boundingBox = computed(() => ({
24
30
  width: boundingWidth.value,
25
31
  height: boundingHeight.value
26
32
  }));
33
+ const positionViewBox = computed(() => {
34
+ if (props.position == null) return null;
35
+ return isOutsidePosition(props.position) ? legendArea.value : viewBox.value;
36
+ });
37
+ const shouldReportDimensions = computed(() => props.portal == null && (props.position == null || isOutsidePosition(props.position)));
27
38
  const processedPayload = computed(() => {
28
39
  if (!contextPayload.value || contextPayload.value.length === 0) return [];
29
40
  let finalPayload = getUniqPayload(contextPayload.value, props.payloadUniqBy, defaultUniqBy);
@@ -33,30 +44,58 @@ function useLegend(props) {
33
44
  }
34
45
  return Array.from(finalPayload);
35
46
  });
47
+ const positionStyle = computed(() => {
48
+ if (props.position == null || positionViewBox.value == null) return;
49
+ const positionResult = getCartesianPosition({
50
+ viewBox: positionViewBox.value,
51
+ position: props.position,
52
+ offset: props.offset ?? 0
53
+ });
54
+ const outsidePositionOffset = getOutsidePositionOffset(props.position, props.offset ?? 0, boundingBox.value);
55
+ const positionMaxWidth = resolvedLayout.value === "vertical" ? (positionViewBox.value.width ?? 0) / 2 : positionViewBox.value.width ?? 0;
56
+ const positionMaxHeight = resolvedLayout.value === "horizontal" ? (positionViewBox.value.height ?? 0) / 2 : positionViewBox.value.height ?? 0;
57
+ return {
58
+ width: "max-content",
59
+ height: "max-content",
60
+ maxWidth: `${positionMaxWidth}px`,
61
+ maxHeight: `${positionMaxHeight}px`,
62
+ overflowY: "auto",
63
+ top: `${positionResult.y + (outsidePositionOffset.top ?? 0)}px`,
64
+ left: `${positionResult.x + (outsidePositionOffset.left ?? 0)}px`,
65
+ transform: cartesianPositionToCSSTranslate(positionResult.horizontalAnchor, positionResult.verticalAnchor)
66
+ };
67
+ });
36
68
  const outerStyle = computed(() => {
37
69
  const userStyle = props.wrapperStyle ? { ...props.wrapperStyle } : {};
38
70
  if (props.portal) return userStyle;
39
71
  const baseStyle = {
40
72
  position: "absolute",
41
- width: widthOrHeight.value?.width ? `${widthOrHeight.value.width}px` : props.width ? `${props.width}px` : "auto",
42
- height: widthOrHeight.value?.height ? `${widthOrHeight.value.height}px` : props.height ? `${props.height}px` : "auto"
73
+ width: widthOrHeight.value?.width || props.width || "auto",
74
+ height: widthOrHeight.value?.height || props.height || "auto"
43
75
  };
44
- const positionStyle = getDefaultPosition(userStyle, props, margin.value, chartWidth.value, chartHeight.value, boundingBox.value);
76
+ const calculatedPositionStyle = positionStyle.value ?? getDefaultPosition(userStyle, {
77
+ layout: resolvedLayout.value,
78
+ align: props.align,
79
+ verticalAlign: props.verticalAlign
80
+ }, margin.value, chartWidth.value, chartHeight.value, boundingBox.value);
45
81
  return {
46
82
  ...baseStyle,
47
- ...positionStyle,
83
+ ...calculatedPositionStyle,
48
84
  ...userStyle
49
85
  };
50
86
  });
51
87
  const legendPortal = computed(() => props.portal ?? legendPortalFromContext?.value);
52
88
  const syncSettings = () => {
53
89
  dispatch(setLegendSettings({
54
- layout: props.layout,
90
+ layout: resolvedLayout.value,
55
91
  align: props.align,
56
- verticalAlign: props.verticalAlign
92
+ verticalAlign: props.verticalAlign,
93
+ position: props.position,
94
+ offset: props.offset
57
95
  }));
58
96
  };
59
97
  const syncSize = () => {
98
+ if (!shouldReportDimensions.value) return;
60
99
  dispatch(setLegendSize({
61
100
  width: boundingBox.value.width,
62
101
  height: boundingBox.value.height
@@ -69,6 +108,8 @@ function useLegend(props) {
69
108
  processedPayload,
70
109
  outerStyle,
71
110
  legendPortal,
111
+ resolvedLayout,
112
+ positionViewBox,
72
113
  syncSettings,
73
114
  syncSize
74
115
  };
@@ -2,7 +2,7 @@
2
2
  var LegendVueProps = {
3
3
  layout: {
4
4
  type: String,
5
- default: "horizontal"
5
+ default: "auto"
6
6
  },
7
7
  align: {
8
8
  type: String,
@@ -12,6 +12,14 @@ var LegendVueProps = {
12
12
  type: String,
13
13
  default: "bottom"
14
14
  },
15
+ position: {
16
+ type: [String, Object],
17
+ default: void 0
18
+ },
19
+ offset: {
20
+ type: Number,
21
+ default: 0
22
+ },
15
23
  width: Number,
16
24
  height: Number,
17
25
  iconSize: {
@@ -1,5 +1,16 @@
1
1
  import { isNumber } from "../../utils/validate.mjs";
2
2
  //#region src/components/legend/utils.ts
3
+ function getLayoutForPosition(position) {
4
+ if (position === "left" || position === "right" || position === "insideLeft" || position === "insideRight") return "vertical";
5
+ return "horizontal";
6
+ }
7
+ function getOutsidePositionOffset(position, offset, box) {
8
+ if (position === "top") return { top: box.height + offset };
9
+ if (position === "bottom") return { top: -box.height - offset };
10
+ if (position === "left") return { left: box.width + offset };
11
+ if (position === "right") return { left: -box.width - offset };
12
+ return {};
13
+ }
3
14
  function getDefaultPosition(style, props, margin, chartWidth, chartHeight, box) {
4
15
  const { layout, align, verticalAlign } = props;
5
16
  let hPos = {};
@@ -22,4 +33,4 @@ function defaultUniqBy(entry) {
22
33
  return entry.value;
23
34
  }
24
35
  //#endregion
25
- export { defaultUniqBy, getDefaultPosition, getWidthOrHeight };
36
+ export { defaultUniqBy, getDefaultPosition, getLayoutForPosition, getOutsidePositionOffset, getWidthOrHeight };