@x-plat/design-system 0.5.8 → 0.5.9

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.
@@ -27,6 +27,39 @@ var getPalette = (palettes, index, key) => {
27
27
  return palettes[(index + offset) % palettes.length];
28
28
  };
29
29
  var PADDING = { top: 20, right: 20, bottom: 30, left: 40 };
30
+ var toSmoothPath = (points) => {
31
+ if (points.length < 2) return "";
32
+ const p = points;
33
+ let d = `M ${p[0].x} ${p[0].y}`;
34
+ for (let i = 0; i < p.length - 1; i++) {
35
+ const p0 = p[Math.max(0, i - 1)];
36
+ const p1 = p[i];
37
+ const p2 = p[i + 1];
38
+ const p3 = p[Math.min(p.length - 1, i + 2)];
39
+ const cp1x = p1.x + (p2.x - p0.x) / 6;
40
+ const cp1y = p1.y + (p2.y - p0.y) / 6;
41
+ const cp2x = p2.x - (p3.x - p1.x) / 6;
42
+ const cp2y = p2.y - (p3.y - p1.y) / 6;
43
+ d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
44
+ }
45
+ return d;
46
+ };
47
+ var useChartSize = (ref) => {
48
+ const [size, setSize] = React.useState({ width: 0, height: 0 });
49
+ React.useEffect(() => {
50
+ const el = ref.current;
51
+ if (!el) return;
52
+ const observer = new ResizeObserver((entries) => {
53
+ const entry = entries[0];
54
+ if (!entry) return;
55
+ const { width, height } = entry.contentRect;
56
+ setSize({ width: Math.floor(width), height: Math.floor(height) });
57
+ });
58
+ observer.observe(el);
59
+ return () => observer.disconnect();
60
+ }, [ref]);
61
+ return size;
62
+ };
30
63
  var useChartTooltip = (enabled) => {
31
64
  const [tooltip, setTooltip] = React.useState({
32
65
  visible: false,
@@ -61,15 +94,15 @@ var useChartTooltip = (enabled) => {
61
94
  }, []);
62
95
  return { tooltip, show, hide, move, containerRef };
63
96
  };
64
- var LineChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
97
+ var LineChart = React.memo(({ data, labels, width, height, onHover, onMove, onLeave }) => {
65
98
  const entries = React.useMemo(() => Object.entries(data), [data]);
66
99
  const maxVal = React.useMemo(() => {
67
100
  const allValues = entries.flatMap(([, v]) => v);
68
101
  return Math.max(...allValues) * 1.2 || 1;
69
102
  }, [entries]);
70
103
  const count = labels.length;
71
- const chartW = 600 - PADDING.left - PADDING.right;
72
- const chartH = 300 - PADDING.top - PADDING.bottom;
104
+ const chartW = width - PADDING.left - PADDING.right;
105
+ const chartH = height - PADDING.top - PADDING.bottom;
73
106
  const seriesPoints = React.useMemo(
74
107
  () => entries.map(
75
108
  ([, values]) => values.map((v, i) => ({
@@ -80,18 +113,18 @@ var LineChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
80
113
  ),
81
114
  [entries, count, chartW, chartH, maxVal]
82
115
  );
83
- return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 600 300", className: "chart-svg", children: [
116
+ return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
84
117
  [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
85
118
  const y = PADDING.top + (1 - ratio) * chartH;
86
119
  const val = Math.round(maxVal * ratio);
87
120
  return /* @__PURE__ */ jsxs("g", { children: [
88
- /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: 600 - PADDING.right, y2: y, className: "chart-grid" }),
121
+ /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
89
122
  /* @__PURE__ */ jsx("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
90
123
  ] }, ratio);
91
124
  }),
92
125
  labels.map((label, i) => {
93
126
  const x = PADDING.left + i / (count - 1 || 1) * chartW;
94
- return /* @__PURE__ */ jsx("text", { x, y: 300 - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
127
+ return /* @__PURE__ */ jsx("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
95
128
  }),
96
129
  entries.map(([key], di) => {
97
130
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
@@ -126,7 +159,73 @@ var LineChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
126
159
  ] });
127
160
  });
128
161
  LineChart.displayName = "LineChart";
129
- var BarChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
162
+ var CurveChart = React.memo(({ data, labels, width, height, onHover, onMove, onLeave }) => {
163
+ const entries = React.useMemo(() => Object.entries(data), [data]);
164
+ const maxVal = React.useMemo(() => {
165
+ const allValues = entries.flatMap(([, v]) => v);
166
+ return Math.max(...allValues) * 1.2 || 1;
167
+ }, [entries]);
168
+ const count = labels.length;
169
+ const chartW = width - PADDING.left - PADDING.right;
170
+ const chartH = height - PADDING.top - PADDING.bottom;
171
+ const seriesPoints = React.useMemo(
172
+ () => entries.map(
173
+ ([, values]) => values.map((v, i) => ({
174
+ x: PADDING.left + i / (count - 1 || 1) * chartW,
175
+ y: PADDING.top + (1 - v / maxVal) * chartH,
176
+ v
177
+ }))
178
+ ),
179
+ [entries, count, chartW, chartH, maxVal]
180
+ );
181
+ return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
182
+ [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
183
+ const y = PADDING.top + (1 - ratio) * chartH;
184
+ const val = Math.round(maxVal * ratio);
185
+ return /* @__PURE__ */ jsxs("g", { children: [
186
+ /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
187
+ /* @__PURE__ */ jsx("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
188
+ ] }, ratio);
189
+ }),
190
+ labels.map((label, i) => {
191
+ const x = PADDING.left + i / (count - 1 || 1) * chartW;
192
+ return /* @__PURE__ */ jsx("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
193
+ }),
194
+ entries.map(([key], di) => {
195
+ const palette = getPalette(LINE_BAR_PALETTES, di, key);
196
+ const color = palette[2];
197
+ const areaColor = palette[0];
198
+ const points = seriesPoints[di];
199
+ const gradientId = `curve-gradient-${di}`;
200
+ const linePath = toSmoothPath(points);
201
+ const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
202
+ return /* @__PURE__ */ jsxs("g", { children: [
203
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
204
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
205
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
206
+ ] }) }),
207
+ /* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradientId})` }),
208
+ /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" }),
209
+ points.map((p, i) => /* @__PURE__ */ jsx(
210
+ "circle",
211
+ {
212
+ cx: p.x,
213
+ cy: p.y,
214
+ r: "4",
215
+ fill: color,
216
+ className: "chart-point",
217
+ onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${p.v}`),
218
+ onMouseMove: onMove,
219
+ onMouseLeave: onLeave
220
+ },
221
+ i
222
+ ))
223
+ ] }, di);
224
+ })
225
+ ] });
226
+ });
227
+ CurveChart.displayName = "CurveChart";
228
+ var BarChart = React.memo(({ data, labels, width, height, onHover, onMove, onLeave }) => {
130
229
  const entries = React.useMemo(() => Object.entries(data), [data]);
131
230
  const maxVal = React.useMemo(() => {
132
231
  const allValues = entries.flatMap(([, v]) => v);
@@ -134,8 +233,8 @@ var BarChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
134
233
  }, [entries]);
135
234
  const count = labels.length;
136
235
  const groupCount = entries.length;
137
- const chartW = 600 - PADDING.left - PADDING.right;
138
- const chartH = 300 - PADDING.top - PADDING.bottom;
236
+ const chartW = width - PADDING.left - PADDING.right;
237
+ const chartH = height - PADDING.top - PADDING.bottom;
139
238
  const groupW = chartW / count;
140
239
  const barW = Math.min(32, groupW * 0.7 / groupCount);
141
240
  const bars = React.useMemo(
@@ -149,16 +248,16 @@ var BarChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
149
248
  ),
150
249
  [entries, maxVal, chartH, groupW, barW, groupCount]
151
250
  );
152
- return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 600 300", className: "chart-svg", children: [
251
+ return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
153
252
  [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
154
253
  const y = PADDING.top + (1 - ratio) * chartH;
155
254
  const val = Math.round(maxVal * ratio);
156
255
  return /* @__PURE__ */ jsxs("g", { children: [
157
- /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: 600 - PADDING.right, y2: y, className: "chart-grid" }),
256
+ /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
158
257
  /* @__PURE__ */ jsx("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
159
258
  ] }, ratio);
160
259
  }),
161
- labels.map((label, i) => /* @__PURE__ */ jsx("text", { x: PADDING.left + groupW * i + groupW / 2, y: 300 - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i)),
260
+ labels.map((label, i) => /* @__PURE__ */ jsx("text", { x: PADDING.left + groupW * i + groupW / 2, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i)),
162
261
  entries.map(([key], di) => {
163
262
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
164
263
  const color = palette[2];
@@ -169,7 +268,7 @@ var BarChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
169
268
  y: b.y,
170
269
  width: b.w,
171
270
  height: b.h,
172
- rx: "2",
271
+ rx: Math.min(4, b.w / 2),
173
272
  fill: color,
174
273
  className: "chart-bar",
175
274
  onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
@@ -183,14 +282,15 @@ var BarChart = React.memo(({ data, labels, onHover, onMove, onLeave }) => {
183
282
  });
184
283
  BarChart.displayName = "BarChart";
185
284
  var PieDonutChart = React.memo(
186
- ({ data, labels, isDoughnut, onHover, onMove, onLeave }) => {
285
+ ({ data, labels, width, height, isDoughnut, onHover, onMove, onLeave }) => {
187
286
  const entries = React.useMemo(() => Object.entries(data), [data]);
188
287
  const values = React.useMemo(() => entries.flatMap(([, v]) => v), [entries]);
189
288
  const total = React.useMemo(() => values.reduce((a, b) => a + b, 0) || 1, [values]);
190
- const cx = 150;
191
- const cy = 150;
192
- const r = 120;
193
- const innerR = isDoughnut ? 60 : 0;
289
+ const size = Math.min(width, height);
290
+ const cx = size / 2;
291
+ const cy = size / 2;
292
+ const r = size * 0.4;
293
+ const innerR = isDoughnut ? r * 0.5 : 0;
194
294
  const firstKey = entries[0]?.[0] ?? "";
195
295
  const colorOffset = hashString(firstKey);
196
296
  const sliceData = React.useMemo(() => {
@@ -221,8 +321,8 @@ var PieDonutChart = React.memo(
221
321
  angle0 = endAngle;
222
322
  return { d, lx, ly, v, pct, angle, label: labels[i] || `${i + 1}` };
223
323
  });
224
- }, [values, total, innerR, labels]);
225
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 300 300", className: "chart-svg chart-pie", children: sliceData.map((s, i) => /* @__PURE__ */ jsxs("g", { children: [
324
+ }, [values, total, cx, cy, r, innerR, labels]);
325
+ return /* @__PURE__ */ jsx("svg", { viewBox: `0 0 ${size} ${size}`, className: "chart-svg chart-pie", children: sliceData.map((s, i) => /* @__PURE__ */ jsxs("g", { children: [
226
326
  /* @__PURE__ */ jsx(
227
327
  "path",
228
328
  {
@@ -239,22 +339,42 @@ var PieDonutChart = React.memo(
239
339
  }
240
340
  );
241
341
  PieDonutChart.displayName = "PieDonutChart";
342
+ var TooltipBubble = ({ x, y, containerWidth, children }) => {
343
+ const ref = React.useRef(null);
344
+ const [adjustedX, setAdjustedX] = React.useState(x);
345
+ React.useEffect(() => {
346
+ const el = ref.current;
347
+ if (!el) return;
348
+ const w = el.offsetWidth;
349
+ const half = w / 2;
350
+ const margin = 8;
351
+ let nx = x;
352
+ if (x - half < margin) nx = half + margin;
353
+ else if (x + half > containerWidth - margin) nx = containerWidth - half - margin;
354
+ setAdjustedX(nx);
355
+ }, [x, containerWidth]);
356
+ return /* @__PURE__ */ jsx(
357
+ "div",
358
+ {
359
+ ref,
360
+ className: "chart-tooltip",
361
+ style: { left: adjustedX, top: y },
362
+ children
363
+ }
364
+ );
365
+ };
242
366
  var Chart = (props) => {
243
367
  const { type, data, labels, tooltip: showTooltip = true } = props;
244
368
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
369
+ const { width, height } = useChartSize(containerRef);
370
+ const ready = width > 0 && height > 0;
245
371
  return /* @__PURE__ */ jsxs("div", { className: "lib-xplat-chart", ref: containerRef, children: [
246
- type === "line" && /* @__PURE__ */ jsx(LineChart, { data, labels, onHover: show, onMove: move, onLeave: hide }),
247
- type === "bar" && /* @__PURE__ */ jsx(BarChart, { data, labels, onHover: show, onMove: move, onLeave: hide }),
248
- type === "pie" && /* @__PURE__ */ jsx(PieDonutChart, { data, labels, onHover: show, onMove: move, onLeave: hide }),
249
- type === "doughnut" && /* @__PURE__ */ jsx(PieDonutChart, { data, labels, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
250
- tooltip.visible && /* @__PURE__ */ jsx(
251
- "div",
252
- {
253
- className: "chart-tooltip",
254
- style: { left: tooltip.x, top: tooltip.y },
255
- children: tooltip.content
256
- }
257
- )
372
+ ready && type === "line" && /* @__PURE__ */ jsx(LineChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
373
+ ready && type === "curve" && /* @__PURE__ */ jsx(CurveChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
374
+ ready && type === "bar" && /* @__PURE__ */ jsx(BarChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
375
+ ready && type === "pie" && /* @__PURE__ */ jsx(PieDonutChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
376
+ ready && type === "doughnut" && /* @__PURE__ */ jsx(PieDonutChart, { data, labels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
377
+ tooltip.visible && /* @__PURE__ */ jsx(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
258
378
  ] });
259
379
  };
260
380
  Chart.displayName = "Chart";