@katlux/block-charts 0.1.1-alpha.87 → 0.1.1-alpha.89

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.
@@ -2,7 +2,6 @@
2
2
  .k-chart-wrapper(
3
3
  ref="wrapperRef"
4
4
  :class="{ 'k-chart--grabbing': isDragging, 'k-chart--pannable': canPan }"
5
- :style="{ cursor: isDragging ? 'grabbing' : canPan ? 'grab' : 'default' }"
6
5
  @wheel.prevent="onWheel"
7
6
  @mousedown="onMouseDown"
8
7
  @mousemove="onMouseMove"
@@ -39,7 +38,6 @@
39
38
  :cy="pt.y"
40
39
  :r="10"
41
40
  fill="transparent"
42
- style="cursor: pointer"
43
41
  @click="emit('click-point', pt.item, pt.index)"
44
42
  )
45
43
  // Visible indicators
@@ -57,7 +55,7 @@
57
55
  // Tooltip
58
56
  .k-chart-tooltip(
59
57
  v-if="tooltipState.visible && !isLoading"
60
- :style="{ left: tooltipState.x + 'px', top: tooltipState.y + 'px' }"
58
+ :style="tooltipStyle"
61
59
  )
62
60
  slot(name="tooltip" :item="tooltipState.item" :index="tooltipState.index")
63
61
  span {{ tooltipState.content }}
@@ -92,7 +90,7 @@ const wrapperRef = ref(null);
92
90
  const containerRef = ref(null);
93
91
  const canvasRef = ref(null);
94
92
  const svgRef = ref(null);
95
- const { ctx, width, height, clear, setupCanvas } = useChartCanvas();
93
+ const { ctx, width, height, resizeTick, clear, setupCanvas } = useChartCanvas();
96
94
  const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg();
97
95
  const dataRef = computed(() => props.dataProvider);
98
96
  const { items, loading } = useChartData(dataRef);
@@ -101,6 +99,10 @@ const { exportPng, exportSvg: exportSvgFile } = useChartExport();
101
99
  const isLoading = computed(() => {
102
100
  return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
103
101
  });
102
+ const tooltipStyle = computed(() => ({
103
+ left: `${tooltipState.value.x}px`,
104
+ top: `${tooltipState.value.y}px`
105
+ }));
104
106
  const maxZoomRef = computed(() => props.maxZoom);
105
107
  const plotWidthRef = computed(() => Math.max(0, width.value - 75));
106
108
  const plotLeftRef = computed(() => 55);
@@ -120,8 +122,45 @@ const {
120
122
  onMouseUp,
121
123
  onMouseLeave: onViewportMouseLeave
122
124
  } = viewport;
123
- const xFieldRef = computed(() => props.xField);
124
- const yFieldRef = computed(() => props.yField);
125
+ const xAxisType = computed(() => {
126
+ if (!items.value.length) return "linear";
127
+ const firstVal = items.value[0]?.[props.xField];
128
+ if (typeof firstVal === "number") return "linear";
129
+ if (typeof firstVal === "string") {
130
+ const parsed = Date.parse(firstVal);
131
+ if (!isNaN(parsed) && !/^\d+$/.test(firstVal.trim())) return "datetime";
132
+ const num = Number(firstVal);
133
+ if (!isNaN(num)) return "linear";
134
+ return "category";
135
+ }
136
+ return "linear";
137
+ });
138
+ const categoryList = computed(() => {
139
+ if (xAxisType.value !== "category") return [];
140
+ const seen = /* @__PURE__ */ new Set();
141
+ const list = [];
142
+ for (const item of items.value) {
143
+ const val = String(item[props.xField] ?? "");
144
+ if (!seen.has(val)) {
145
+ seen.add(val);
146
+ list.push(val);
147
+ }
148
+ }
149
+ return list;
150
+ });
151
+ const categoryIndexMap = computed(() => {
152
+ const map = {};
153
+ categoryList.value.forEach((cat, i) => {
154
+ map[cat] = i;
155
+ });
156
+ return map;
157
+ });
158
+ const resolveX = (item, index = 0) => {
159
+ if (!item) return index;
160
+ const raw = item[props.xField];
161
+ if (xAxisType.value === "category") return categoryIndexMap.value[String(raw)] ?? index;
162
+ return parseValue(raw, index);
163
+ };
125
164
  const seriesMap = computed(() => {
126
165
  const map = /* @__PURE__ */ new Map();
127
166
  for (const item of items.value) {
@@ -131,6 +170,24 @@ const seriesMap = computed(() => {
131
170
  }
132
171
  return map;
133
172
  });
173
+ const sortedSeriesMap = computed(() => {
174
+ const map = /* @__PURE__ */ new Map();
175
+ const itemIndexMap = /* @__PURE__ */ new Map();
176
+ for (let i = 0; i < items.value.length; i++) {
177
+ itemIndexMap.set(items.value[i], i);
178
+ }
179
+ for (const [key, seriesItems] of seriesMap.value) {
180
+ const enriched = seriesItems.map((item, idx) => ({
181
+ item,
182
+ index: itemIndexMap.get(item) ?? idx,
183
+ xVal: resolveX(item, idx)
184
+ })).sort((a, b) => a.xVal - b.xVal);
185
+ map.set(key, enriched);
186
+ }
187
+ return map;
188
+ });
189
+ const xFieldRef = computed(() => props.xField);
190
+ const yFieldRef = computed(() => props.yField);
134
191
  const axes = useChartAxes({
135
192
  items,
136
193
  xField: xFieldRef,
@@ -139,13 +196,14 @@ const axes = useChartAxes({
139
196
  height,
140
197
  scale,
141
198
  panOffset,
142
- padding: { top: 20, right: 20, bottom: 40, left: 55 }
199
+ padding: { top: 20, right: 20, bottom: 40, left: 55 },
200
+ xValueGetter: (r, i) => resolveX(r, i)
143
201
  });
144
202
  const draw = () => {
145
203
  if (!ctx.value) return;
146
204
  clear(props.backgroundColor);
147
205
  const c = ctx.value;
148
- const p = easeOut(progress.value);
206
+ const p = props.animated ? easeOut(progress.value) : 1;
149
207
  drawGrid(c);
150
208
  drawAxes(c);
151
209
  c.save();
@@ -153,9 +211,9 @@ const draw = () => {
153
211
  c.rect(axes.plotLeft, axes.plotTop, axes.plotWidth.value, axes.plotHeight.value);
154
212
  c.clip();
155
213
  let seriesIdx = 0;
156
- for (const [, seriesItems] of seriesMap.value) {
214
+ for (const [, seriesData] of sortedSeriesMap.value) {
157
215
  const color = props.colors[seriesIdx % props.colors.length];
158
- drawLine(c, seriesItems, color, p);
216
+ drawLine(c, seriesData, color, p);
159
217
  seriesIdx++;
160
218
  }
161
219
  c.restore();
@@ -220,13 +278,36 @@ const drawAxes = (c) => {
220
278
  c.fillText(val.toFixed(1), axes.plotLeft - 8, y + 4);
221
279
  }
222
280
  c.textAlign = "center";
223
- const xSteps = 5;
224
- for (let i = 0; i <= xSteps; i++) {
225
- const val = axes.xMin.value + (axes.xMax.value - axes.xMin.value) * (i / xSteps);
226
- const x = axes.toX(val);
227
- let label = val.toFixed(1);
228
- if (val > 1e12) label = new Date(val).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
229
- c.fillText(label, x, axes.plotTop + axes.plotHeight.value + 16);
281
+ if (xAxisType.value === "category") {
282
+ const count = categoryList.value.length;
283
+ if (count > 0) {
284
+ const xSteps = Math.min(10, count - 1);
285
+ const stepSize = xSteps > 0 ? (count - 1) / xSteps : 1;
286
+ for (let i = 0; i <= xSteps; i++) {
287
+ const idx = Math.round(i * stepSize);
288
+ const x = axes.toX(idx);
289
+ const label = categoryList.value[idx] ?? "";
290
+ c.fillText(label, x, axes.plotTop + axes.plotHeight.value + 16);
291
+ }
292
+ }
293
+ } else {
294
+ const xSteps = 5;
295
+ for (let i = 0; i <= xSteps; i++) {
296
+ const val = axes.xMin.value + (axes.xMax.value - axes.xMin.value) * (i / xSteps);
297
+ const x = axes.toX(val);
298
+ let label;
299
+ if (xAxisType.value === "datetime") {
300
+ const allMidnight = items.value.every((r) => {
301
+ const ts = parseValue(r[props.xField]);
302
+ const d = new Date(ts);
303
+ return d.getHours() === 0 && d.getMinutes() === 0;
304
+ });
305
+ label = allMidnight ? new Date(val).toLocaleDateString([], { month: "short", day: "numeric" }) : new Date(val).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
306
+ } else {
307
+ label = val.toFixed(1);
308
+ }
309
+ c.fillText(label, x, axes.plotTop + axes.plotHeight.value + 16);
310
+ }
230
311
  }
231
312
  c.beginPath();
232
313
  c.moveTo(axes.plotLeft, axes.plotTop + axes.plotHeight.value);
@@ -234,7 +315,7 @@ const drawAxes = (c) => {
234
315
  c.stroke();
235
316
  c.beginPath();
236
317
  c.moveTo(axes.plotLeft, axes.plotTop);
237
- c.lineTo(axes.plotLeft, axes.plotTop + axes.plotHeight.value);
318
+ c.lineTo(axes.plotLeft + axes.plotHeight.value);
238
319
  c.stroke();
239
320
  c.fillStyle = "rgba(120,120,140,0.8)";
240
321
  if (props.xAxisTitle) {
@@ -253,8 +334,7 @@ const drawAxes = (c) => {
253
334
  };
254
335
  const drawLine = (c, data, color, progress2) => {
255
336
  if (!data.length) return;
256
- const sorted = [...data].sort((a, b) => parseValue(a[props.xField]) - parseValue(b[props.xField]));
257
- const totalPts = sorted.length;
337
+ const totalPts = data.length;
258
338
  const drawCount = Math.max(1, Math.floor(totalPts * progress2));
259
339
  c.save();
260
340
  c.strokeStyle = color;
@@ -262,18 +342,20 @@ const drawLine = (c, data, color, progress2) => {
262
342
  c.lineJoin = "round";
263
343
  c.lineCap = "round";
264
344
  c.beginPath();
265
- sorted.slice(0, drawCount).forEach((item, i) => {
266
- const x = axes.toX(parseValue(item[props.xField]));
267
- const y = axes.toY(parseValue(item[props.yField]));
345
+ for (let i = 0; i < drawCount; i++) {
346
+ const pt = data[i];
347
+ const x = axes.toX(pt.xVal);
348
+ const y = axes.toY(parseValue(pt.item[props.yField]));
268
349
  if (i === 0) c.moveTo(x, y);
269
350
  else c.lineTo(x, y);
270
- });
351
+ }
271
352
  c.stroke();
353
+ const currentHoveredIdx = hoveredIndex.value;
272
354
  for (let i = 0; i < drawCount; i++) {
273
- const item = sorted[i];
274
- const x = axes.toX(parseValue(item[props.xField]));
275
- const y = axes.toY(parseValue(item[props.yField]));
276
- const isHovered = hoveredIndex.value === items.value.indexOf(item);
355
+ const pt = data[i];
356
+ const x = axes.toX(pt.xVal);
357
+ const y = axes.toY(parseValue(pt.item[props.yField]));
358
+ const isHovered = currentHoveredIdx !== -1 && pt.index === currentHoveredIdx;
277
359
  c.beginPath();
278
360
  c.arc(x, y, isHovered ? 5 : 3, 0, Math.PI * 2);
279
361
  c.fillStyle = isHovered ? "#fff" : color;
@@ -284,10 +366,12 @@ const drawLine = (c, data, color, progress2) => {
284
366
  }
285
367
  c.restore();
286
368
  };
287
- watch([items, scale, panOffset, hoveredIndex, progress], draw, { deep: true });
369
+ watch(progress, draw);
370
+ watch([scale, panOffset, hoveredIndex, resizeTick, width, height], draw);
288
371
  watch(items, () => {
289
372
  if (props.animated) animate();
290
- }, { deep: true });
373
+ else draw();
374
+ });
291
375
  watch(scale, (v) => emit("zoom-change", v));
292
376
  watch(panOffset, (v) => emit("pan-change", v), { deep: true });
293
377
  onMounted(async () => {
@@ -300,20 +384,24 @@ onMounted(async () => {
300
384
  });
301
385
  const hoveredPoints = computed(() => {
302
386
  if (hoveredIndex.value === -1) return [];
387
+ if (hoveredIndex.value >= items.value.length) {
388
+ setHovered(-1);
389
+ return [];
390
+ }
303
391
  const item = items.value[hoveredIndex.value];
304
- const xVal = parseValue(item[props.xField]);
392
+ const xVal = resolveX(item);
305
393
  const xPx = axes.toX(xVal);
306
394
  const pts = [];
307
395
  let seriesIdx = 0;
308
- for (const [key, seriesItems] of seriesMap.value) {
309
- const seriesItem = seriesItems.find((it) => parseValue(it[props.xField]) === xVal);
310
- if (seriesItem) {
396
+ for (const [, seriesList] of sortedSeriesMap.value) {
397
+ const found = seriesList.find((pt) => pt.xVal === xVal);
398
+ if (found) {
311
399
  pts.push({
312
400
  x: xPx,
313
- y: axes.toY(parseValue(seriesItem[props.yField])),
401
+ y: axes.toY(parseValue(found.item[props.yField])),
314
402
  color: props.colors[seriesIdx % props.colors.length],
315
- item: seriesItem,
316
- index: items.value.indexOf(seriesItem)
403
+ item: found.item,
404
+ index: found.index
317
405
  });
318
406
  }
319
407
  seriesIdx++;
@@ -331,18 +419,31 @@ const onMouseMove = (e) => {
331
419
  if (!rect) return;
332
420
  const mouseX = e.clientX - rect.left;
333
421
  const mouseY = e.clientY - rect.top;
334
- const nearest = findNearestX(items.value, mouseX, props.xField, axes);
422
+ if (mouseX < axes.plotLeft || mouseX > axes.plotLeft + axes.plotWidth.value || mouseY < axes.plotTop || mouseY > axes.plotTop + axes.plotHeight.value) {
423
+ setHovered(-1);
424
+ hideTooltip();
425
+ return;
426
+ }
427
+ const nearest = findNearestX(mouseX, items.value, axes.toX, (r, i) => resolveX(r, i));
335
428
  if (nearest) {
336
429
  setHovered(nearest.index);
337
- showTooltip(mouseX + 10, mouseY - 20, `${nearest.item[props.xField]}: ${nearest.item[props.yField]}`, nearest.item, nearest.index);
338
430
  emit("hover-point", nearest.item, nearest.index);
431
+ const xPx = axes.toX(resolveX(nearest.item, nearest.index));
432
+ const yPx = axes.toY(parseValue(nearest.item[props.yField]));
433
+ showTooltip(
434
+ xPx,
435
+ yPx - 10,
436
+ `${props.xField}: ${nearest.item[props.xField]} | ${props.yField}: ${nearest.item[props.yField]}`,
437
+ nearest.item,
438
+ nearest.index
439
+ );
339
440
  } else {
340
441
  setHovered(-1);
341
442
  hideTooltip();
342
443
  }
343
444
  };
344
445
  const onMouseLeave = (e) => {
345
- onViewportMouseLeave(e);
446
+ onViewportMouseLeave();
346
447
  setHovered(-1);
347
448
  hideTooltip();
348
449
  };
@@ -351,7 +452,7 @@ defineExpose({
351
452
  zoomOut: viewport.zoomOut,
352
453
  resetZoom: viewport.resetZoom,
353
454
  exportPng: () => canvasRef.value && exportPng(canvasRef.value, "line-chart.png"),
354
- exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, "line-chart.svg")
455
+ exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, "line-chart.svg", canvasRef.value)
355
456
  });
356
457
  </script>
357
458
 
@@ -359,6 +460,13 @@ defineExpose({
359
460
  .k-chart-wrapper {
360
461
  position: relative;
361
462
  user-select: none;
463
+ cursor: default;
464
+ }
465
+ .k-chart-wrapper.k-chart--pannable {
466
+ cursor: grab;
467
+ }
468
+ .k-chart-wrapper.k-chart--grabbing {
469
+ cursor: grabbing;
362
470
  }
363
471
  .k-chart-wrapper .k-chart-controls {
364
472
  position: absolute;
@@ -9,14 +9,13 @@
9
9
  canvas(ref="canvasRef" v-show="!isLoading")
10
10
  svg.k-chart-svg(v-show="!isLoading" ref="svgRef" :width="width" :height="height")
11
11
  g.k-chart-hits
12
- // Hit detection now handled via angle calculation in onMouseMove
12
+ // Hit detection handled via angle calculation in onMouseMove
13
13
 
14
14
  // Dynamic Interaction Layer
15
15
  g.k-chart-interaction(v-if="hoveredIndex !== -1 && hoveredSeg")
16
16
  path.k-chart-hit-proxy(
17
17
  :d="hoveredSeg.d"
18
18
  fill="transparent"
19
- style="cursor: pointer"
20
19
  @click="emit('click-point', hoveredSeg.item, hoveredIndex)"
21
20
  )
22
21
  path(
@@ -26,7 +25,7 @@
26
25
  stroke-width="3"
27
26
  pointer-events="none"
28
27
  )
29
- .k-chart-tooltip(v-if="tooltipState.visible && !isLoading" :style="{ left: tooltipState.x + 'px', top: tooltipState.y + 'px' }")
28
+ .k-chart-tooltip(v-if="tooltipState.visible && !isLoading" :style="tooltipStyle")
30
29
  slot(name="tooltip" :item="tooltipState.item" :index="tooltipState.index")
31
30
  span {{ tooltipState.content }}
32
31
  </template>
@@ -38,6 +37,7 @@ import { useChartSvg } from "../../composables/useChartSvg";
38
37
  import { useChartData } from "../../composables/useChartData";
39
38
  import { useChartAnimation, easeOut } from "../../composables/useChartAnimation";
40
39
  import { useChartExport } from "../../composables/useChartExport";
40
+ import { cleanNumber } from "../../composables/chartUtils";
41
41
  const props = defineProps({
42
42
  dataProvider: { type: null, required: false },
43
43
  labelField: { type: String, required: false, default: "label" },
@@ -53,7 +53,7 @@ const emit = defineEmits(["click-point", "hover-point"]);
53
53
  const containerRef = ref(null);
54
54
  const canvasRef = ref(null);
55
55
  const svgRef = ref(null);
56
- const { ctx, width, height, clear, setupCanvas } = useChartCanvas();
56
+ const { ctx, width, height, resizeTick, clear, setupCanvas } = useChartCanvas();
57
57
  const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg();
58
58
  const dataRef = computed(() => props.dataProvider);
59
59
  const { items } = useChartData(dataRef);
@@ -62,8 +62,12 @@ const { exportPng, exportSvg: exportSvgFile } = useChartExport();
62
62
  const isLoading = computed(() => {
63
63
  return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
64
64
  });
65
+ const tooltipStyle = computed(() => ({
66
+ left: `${tooltipState.value.x}px`,
67
+ top: `${tooltipState.value.y}px`
68
+ }));
65
69
  const hitSegments = ref([]);
66
- const total = computed(() => items.value.reduce((s, r) => s + Number(r[props.valueField]), 0));
70
+ const total = computed(() => items.value.reduce((s, r) => s + Math.max(0, cleanNumber(r[props.valueField])), 0));
67
71
  const hoveredSeg = computed(() => {
68
72
  if (hoveredIndex.value === -1) return null;
69
73
  return hitSegments.value[hoveredIndex.value];
@@ -88,7 +92,7 @@ const draw = () => {
88
92
  if (!ctx.value) return;
89
93
  clear(props.backgroundColor);
90
94
  const c = ctx.value;
91
- const p = easeOut(progress.value);
95
+ const p = props.animated ? easeOut(progress.value) : 1;
92
96
  const segs = [];
93
97
  const cx = width.value / 2;
94
98
  const cy = props.showLegend ? (height.value - 50) / 2 : height.value / 2;
@@ -96,7 +100,8 @@ const draw = () => {
96
100
  let startAngle = -Math.PI / 2;
97
101
  const tot = total.value || 1;
98
102
  items.value.forEach((item, i) => {
99
- const slice = Number(item[props.valueField]) / tot * Math.PI * 2 * p;
103
+ const val = Math.max(0, cleanNumber(item[props.valueField]));
104
+ const slice = val / tot * Math.PI * 2 * p;
100
105
  const endAngle = startAngle + slice;
101
106
  const color = props.colors[i % props.colors.length];
102
107
  const isHovered = hoveredIndex.value === i;
@@ -113,13 +118,13 @@ const draw = () => {
113
118
  const lr = r * 0.7;
114
119
  const lx = cx + lr * Math.cos(midAngle);
115
120
  const ly = cy + lr * Math.sin(midAngle);
116
- if (p > 0.5) {
121
+ if (p > 0.5 && val > 0) {
117
122
  c.save();
118
123
  c.fillStyle = "#fff";
119
124
  c.font = "bold 11px system-ui, sans-serif";
120
125
  c.textAlign = "center";
121
126
  c.textBaseline = "middle";
122
- const pct = (Number(item[props.valueField]) / tot * 100).toFixed(0) + "%";
127
+ const pct = (val / tot * 100).toFixed(0) + "%";
123
128
  c.fillText(pct, lx, ly);
124
129
  c.restore();
125
130
  }
@@ -128,41 +133,32 @@ const draw = () => {
128
133
  cx: cx + r * 0.75 * Math.cos(startAngle + slice / 2),
129
134
  cy: cy + r * 0.75 * Math.sin(startAngle + slice / 2),
130
135
  item,
131
- label: `${item[props.labelField]}: ${item[props.valueField]}`,
136
+ label: `${item[props.labelField]}: ${val}`,
132
137
  startAngle,
133
138
  endAngle
134
139
  });
135
140
  startAngle = endAngle;
136
141
  });
137
142
  if (props.donut) {
143
+ const ri = r * 0.5;
138
144
  c.save();
139
145
  c.beginPath();
140
- c.arc(cx, cy, r * 0.5, 0, Math.PI * 2);
141
- c.fillStyle = "#000";
142
- c.globalCompositeOperation = "destination-out";
146
+ c.arc(cx, cy, ri, 0, Math.PI * 2);
147
+ c.fillStyle = props.backgroundColor || "#ffffff";
143
148
  c.fill();
144
- c.globalCompositeOperation = "source-over";
145
149
  c.restore();
146
150
  }
147
151
  hitSegments.value = segs;
148
152
  if (props.showLegend) {
149
153
  drawLegend(c);
150
154
  }
151
- if (props.title) {
152
- c.save();
153
- c.fillStyle = "rgba(120,120,140,0.9)";
154
- c.font = "bold 16px system-ui, sans-serif";
155
- c.textAlign = "center";
156
- c.fillText(props.title, width.value / 2, 30);
157
- c.restore();
158
- }
159
155
  };
160
156
  const drawLegend = (c) => {
161
157
  c.save();
162
158
  c.font = "11px system-ui, sans-serif";
163
159
  c.textAlign = "left";
164
160
  c.textBaseline = "middle";
165
- const keys = items.value.map((it) => String(it[props.labelField]));
161
+ const keys = items.value.map((r) => String(r[props.labelField]));
166
162
  let xOffset = 20;
167
163
  let yOffset = height.value - 25;
168
164
  keys.forEach((key, i) => {
@@ -180,10 +176,12 @@ const drawLegend = (c) => {
180
176
  });
181
177
  c.restore();
182
178
  };
183
- watch([items, hoveredIndex, progress, () => props.donut], draw, { deep: true });
179
+ watch(progress, draw);
180
+ watch([hoveredIndex, resizeTick, width, height, () => props.donut], draw);
184
181
  watch(items, () => {
185
182
  if (props.animated) animate();
186
- }, { deep: true });
183
+ else draw();
184
+ });
187
185
  onMounted(async () => {
188
186
  await nextTick();
189
187
  if (containerRef.value && canvasRef.value) {
@@ -225,7 +223,7 @@ const onMouseLeave = () => {
225
223
  };
226
224
  defineExpose({
227
225
  exportPng: () => canvasRef.value && exportPng(canvasRef.value, "pie-chart.png"),
228
- exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, "pie-chart.svg")
226
+ exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, "pie-chart.svg", canvasRef.value)
229
227
  });
230
228
  </script>
231
229
 
@@ -233,6 +231,7 @@ defineExpose({
233
231
  .k-chart-wrapper {
234
232
  position: relative;
235
233
  user-select: none;
234
+ cursor: default;
236
235
  }
237
236
  .k-chart-wrapper .k-chart-inner {
238
237
  width: 100%;