@katlux/block-charts 0.1.1-alpha.93 → 0.1.1-alpha.95

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 (24) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/KAreaChart/KAreaChart.d.vue.ts +2 -0
  3. package/dist/runtime/components/KAreaChart/KAreaChart.vue +53 -25
  4. package/dist/runtime/components/KAreaChart/KAreaChart.vue.d.ts +2 -0
  5. package/dist/runtime/components/KBarChart/KBarChart.d.vue.ts +2 -0
  6. package/dist/runtime/components/KBarChart/KBarChart.vue +89 -36
  7. package/dist/runtime/components/KBarChart/KBarChart.vue.d.ts +2 -0
  8. package/dist/runtime/components/KHeatMap/KHeatMap.vue +7 -3
  9. package/dist/runtime/components/KLineChart/KLineChart.d.vue.ts +2 -0
  10. package/dist/runtime/components/KLineChart/KLineChart.vue +45 -23
  11. package/dist/runtime/components/KLineChart/KLineChart.vue.d.ts +2 -0
  12. package/dist/runtime/components/KPieChart/KPieChart.vue +7 -3
  13. package/dist/runtime/components/KScatterChart/KScatterChart.d.vue.ts +3 -1
  14. package/dist/runtime/components/KScatterChart/KScatterChart.vue +59 -13
  15. package/dist/runtime/components/KScatterChart/KScatterChart.vue.d.ts +3 -1
  16. package/dist/runtime/composables/useChartAnimation.js +7 -1
  17. package/dist/runtime/composables/useChartData.d.ts +1 -0
  18. package/dist/runtime/composables/useChartData.js +53 -22
  19. package/dist/runtime/composables/useChartExport.js +10 -3
  20. package/dist/runtime/composables/useChartHitTest.d.ts +1 -1
  21. package/dist/runtime/composables/useChartHitTest.js +3 -2
  22. package/dist/runtime/composables/useChartViewport.d.ts +3 -0
  23. package/dist/runtime/composables/useChartViewport.js +29 -7
  24. package/package.json +3 -3
@@ -2,7 +2,7 @@
2
2
  .k-chart-wrapper(
3
3
  ref="wrapperRef"
4
4
  :class="{ 'k-chart--grabbing': isDragging, 'k-chart--pannable': canPan }"
5
- @wheel.prevent="onWheel"
5
+ @wheel="onWheel"
6
6
  @mousedown="onMouseDown"
7
7
  @mousemove="onMouseMove"
8
8
  @mouseup="onMouseUp"
@@ -22,7 +22,7 @@
22
22
  :height="height"
23
23
  )
24
24
  defs
25
- clipPath(id="plot-clip")
25
+ clipPath(:id="'plot-clip-' + clipId")
26
26
  rect(
27
27
  :x="axes.plotLeft"
28
28
  :y="axes.plotTop"
@@ -30,7 +30,7 @@
30
30
  :height="axes.plotHeight.value"
31
31
  )
32
32
  // Dynamic Interaction Layer: Repositions one set of circles to the nearest point
33
- g.k-chart-interaction(v-if="hoveredIndex !== -1" clip-path="url(#plot-clip)")
33
+ g.k-chart-interaction(v-if="hoveredIndex !== -1" :clip-path="'url(#plot-clip-' + clipId + ')'")
34
34
  circle.k-chart-hit-proxy(
35
35
  v-for="(pt, i) in hoveredPoints"
36
36
  :key="i"
@@ -62,7 +62,7 @@
62
62
  </template>
63
63
 
64
64
  <script setup>
65
- import { ref, watch, computed, onMounted, onUnmounted, nextTick } from "vue";
65
+ import { ref, watch, computed, onMounted, onUnmounted, nextTick, useId, isRef } from "vue";
66
66
  import { useChartCanvas } from "../../composables/useChartCanvas";
67
67
  import { useChartSvg } from "../../composables/useChartSvg";
68
68
  import { useChartData } from "../../composables/useChartData";
@@ -71,6 +71,8 @@ import { useChartViewport } from "../../composables/useChartViewport";
71
71
  import { useChartAnimation, easeOut } from "../../composables/useChartAnimation";
72
72
  import { useChartExport } from "../../composables/useChartExport";
73
73
  import { useChartHitTest } from "../../composables/useChartHitTest";
74
+ import { cleanNumber } from "../../composables/chartUtils";
75
+ const clipId = useId();
74
76
  const props = defineProps({
75
77
  dataProvider: { type: null, required: false },
76
78
  xField: { type: String, required: false, default: "x" },
@@ -78,6 +80,7 @@ const props = defineProps({
78
80
  seriesField: { type: String, required: false, default: "series" },
79
81
  animated: { type: Boolean, required: false, default: true },
80
82
  zoomable: { type: Boolean, required: false, default: true },
83
+ wheelNavigation: { type: Boolean, required: false, default: true },
81
84
  maxZoom: { type: Number, required: false, default: 10 },
82
85
  colors: { type: Array, required: false, default: () => ["#6366f1", "#22d3ee", "#f59e0b", "#10b981", "#f43f5e", "#a855f7", "#14b8a6", "#fb923c"] },
83
86
  backgroundColor: { type: String, required: false, default: "#ffffff" },
@@ -93,11 +96,15 @@ const svgRef = ref(null);
93
96
  const { ctx, width, height, resizeTick, clear, setupCanvas } = useChartCanvas();
94
97
  const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg();
95
98
  const dataRef = computed(() => props.dataProvider);
96
- const { items, loading } = useChartData(dataRef);
99
+ const { items, loading: dataLoading } = useChartData(dataRef);
97
100
  const { progress, animate } = useChartAnimation();
98
101
  const { exportPng, exportSvg: exportSvgFile } = useChartExport();
99
102
  const isLoading = computed(() => {
100
- return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
103
+ const dp = props.dataProvider;
104
+ if (!dp) return dataLoading.value;
105
+ const rawLoading = dp.loading !== void 0 ? isRef(dp.loading) ? dp.loading.value : dp.loading?.value ?? dp.loading : false;
106
+ const rawInitial = dp.initialLoad !== void 0 ? isRef(dp.initialLoad) ? dp.initialLoad.value : dp.initialLoad?.value ?? dp.initialLoad : false;
107
+ return Boolean(dataLoading.value || rawLoading || rawInitial);
101
108
  });
102
109
  const tooltipStyle = computed(() => ({
103
110
  left: `${tooltipState.value.x}px`,
@@ -106,7 +113,15 @@ const tooltipStyle = computed(() => ({
106
113
  const maxZoomRef = computed(() => props.maxZoom);
107
114
  const plotWidthRef = computed(() => Math.max(0, width.value - 75));
108
115
  const plotLeftRef = computed(() => 55);
109
- const viewport = useChartViewport({ maxZoom: maxZoomRef, plotWidth: plotWidthRef, plotLeft: plotLeftRef });
116
+ const zoomableRef = computed(() => props.zoomable);
117
+ const wheelNavRef = computed(() => props.wheelNavigation);
118
+ const viewport = useChartViewport({
119
+ maxZoom: maxZoomRef,
120
+ plotWidth: plotWidthRef,
121
+ plotLeft: plotLeftRef,
122
+ zoomable: zoomableRef,
123
+ wheelNavigation: wheelNavRef
124
+ });
110
125
  const { findNearestX } = useChartHitTest();
111
126
  const {
112
127
  scale,
@@ -127,14 +142,24 @@ const xAxisType = computed(() => {
127
142
  const firstVal = items.value[0]?.[props.xField];
128
143
  if (typeof firstVal === "number") return "linear";
129
144
  if (typeof firstVal === "string") {
145
+ const cleaned = cleanNumber(firstVal, NaN);
146
+ if (Number.isFinite(cleaned)) return "linear";
130
147
  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";
148
+ if (!isNaN(parsed) && !/^\d+$/.test(firstVal.trim()) && /[-/T:]|[A-Za-z]/.test(firstVal)) return "datetime";
134
149
  return "category";
135
150
  }
136
151
  return "linear";
137
152
  });
153
+ const isAllMidnight = computed(() => {
154
+ if (xAxisType.value !== "datetime" || !items.value.length) return false;
155
+ const checkCount = Math.min(items.value.length, 100);
156
+ for (let i = 0; i < checkCount; i++) {
157
+ const ts = parseValue(items.value[i][props.xField]);
158
+ const d = new Date(ts);
159
+ if (d.getHours() !== 0 || d.getMinutes() !== 0) return false;
160
+ }
161
+ return true;
162
+ });
138
163
  const categoryList = computed(() => {
139
164
  if (xAxisType.value !== "category") return [];
140
165
  const seen = /* @__PURE__ */ new Set();
@@ -297,12 +322,7 @@ const drawAxes = (c) => {
297
322
  const x = axes.toX(val);
298
323
  let label;
299
324
  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" });
325
+ label = isAllMidnight.value ? new Date(val).toLocaleDateString([], { month: "short", day: "numeric" }) : new Date(val).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
306
326
  } else {
307
327
  label = val.toFixed(1);
308
328
  }
@@ -373,7 +393,7 @@ watch(items, () => {
373
393
  else draw();
374
394
  });
375
395
  watch(scale, (v) => emit("zoom-change", v));
376
- watch(panOffset, (v) => emit("pan-change", v), { deep: true });
396
+ watch(() => [panOffset.value.x, panOffset.value.y], ([x, y]) => emit("pan-change", { x, y }));
377
397
  onMounted(async () => {
378
398
  await nextTick();
379
399
  if (containerRef.value && canvasRef.value) {
@@ -382,13 +402,15 @@ onMounted(async () => {
382
402
  else draw();
383
403
  }
384
404
  });
385
- const hoveredPoints = computed(() => {
386
- if (hoveredIndex.value === -1) return [];
387
- if (hoveredIndex.value >= items.value.length) {
405
+ watch(() => items.value.length, (newLen) => {
406
+ if (hoveredIndex.value >= newLen) {
388
407
  setHovered(-1);
389
- return [];
390
408
  }
409
+ });
410
+ const hoveredPoints = computed(() => {
411
+ if (hoveredIndex.value === -1 || hoveredIndex.value >= items.value.length) return [];
391
412
  const item = items.value[hoveredIndex.value];
413
+ if (!item) return [];
392
414
  const xVal = resolveX(item);
393
415
  const xPx = axes.toX(xVal);
394
416
  const pts = [];
@@ -424,7 +446,7 @@ const onMouseMove = (e) => {
424
446
  hideTooltip();
425
447
  return;
426
448
  }
427
- const nearest = findNearestX(mouseX, items.value, axes.toX, (r, i) => resolveX(r, i));
449
+ const nearest = findNearestX(items.value, mouseX, props.xField, axes, (r, i) => resolveX(r, i));
428
450
  if (nearest) {
429
451
  setHovered(nearest.index);
430
452
  emit("hover-point", nearest.item, nearest.index);
@@ -443,7 +465,7 @@ const onMouseMove = (e) => {
443
465
  }
444
466
  };
445
467
  const onMouseLeave = (e) => {
446
- onViewportMouseLeave();
468
+ onViewportMouseLeave(e);
447
469
  setHovered(-1);
448
470
  hideTooltip();
449
471
  };
@@ -5,6 +5,7 @@ type __VLS_Props = {
5
5
  seriesField?: string;
6
6
  animated?: boolean;
7
7
  zoomable?: boolean;
8
+ wheelNavigation?: boolean;
8
9
  maxZoom?: number;
9
10
  colors?: string[];
10
11
  backgroundColor?: string;
@@ -40,6 +41,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
40
41
  seriesField: string;
41
42
  animated: boolean;
42
43
  zoomable: boolean;
44
+ wheelNavigation: boolean;
43
45
  maxZoom: number;
44
46
  colors: string[];
45
47
  backgroundColor: string;
@@ -31,7 +31,7 @@
31
31
  </template>
32
32
 
33
33
  <script setup>
34
- import { ref, watch, computed, onMounted, nextTick } from "vue";
34
+ import { ref, watch, computed, onMounted, nextTick, isRef } from "vue";
35
35
  import { useChartCanvas } from "../../composables/useChartCanvas";
36
36
  import { useChartSvg } from "../../composables/useChartSvg";
37
37
  import { useChartData } from "../../composables/useChartData";
@@ -56,11 +56,15 @@ const svgRef = ref(null);
56
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
- const { items } = useChartData(dataRef);
59
+ const { items, loading: dataLoading } = useChartData(dataRef);
60
60
  const { progress, animate } = useChartAnimation();
61
61
  const { exportPng, exportSvg: exportSvgFile } = useChartExport();
62
62
  const isLoading = computed(() => {
63
- return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
63
+ const dp = props.dataProvider;
64
+ if (!dp) return dataLoading.value;
65
+ const rawLoading = dp.loading !== void 0 ? isRef(dp.loading) ? dp.loading.value : dp.loading?.value ?? dp.loading : false;
66
+ const rawInitial = dp.initialLoad !== void 0 ? isRef(dp.initialLoad) ? dp.initialLoad.value : dp.initialLoad?.value ?? dp.initialLoad : false;
67
+ return Boolean(dataLoading.value || rawLoading || rawInitial);
64
68
  });
65
69
  const tooltipStyle = computed(() => ({
66
70
  left: `${tooltipState.value.x}px`,
@@ -7,6 +7,7 @@ type __VLS_Props = {
7
7
  labelField?: string;
8
8
  animated?: boolean;
9
9
  zoomable?: boolean;
10
+ wheelNavigation?: boolean;
10
11
  maxZoom?: number;
11
12
  colors?: string[];
12
13
  backgroundColor?: string;
@@ -42,14 +43,15 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
42
43
  seriesField: string;
43
44
  animated: boolean;
44
45
  zoomable: boolean;
46
+ wheelNavigation: boolean;
45
47
  maxZoom: number;
46
48
  colors: string[];
47
49
  backgroundColor: string;
48
50
  showLegend: boolean;
49
51
  xAxisTitle: string;
50
52
  yAxisTitle: string;
51
- labelField: string;
52
53
  sizeField: string;
54
+ labelField: string;
53
55
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
54
56
  declare const _default: typeof __VLS_export;
55
57
  export default _default;
@@ -1,7 +1,7 @@
1
1
  <template lang="pug">
2
2
  .k-chart-wrapper(
3
3
  :class="{ 'k-chart--grabbing': isDragging, 'k-chart--pannable': canPan }"
4
- @wheel.prevent="onWheel"
4
+ @wheel="onWheel"
5
5
  @mousedown="onMouseDown"
6
6
  @mousemove="onMouseMove"
7
7
  @mouseup="onMouseUp"
@@ -16,7 +16,7 @@
16
16
  canvas(ref="canvasRef" v-show="!isLoading")
17
17
  svg.k-chart-svg(v-show="!isLoading" ref="svgRef" :width="width" :height="height")
18
18
  defs
19
- clipPath(id="plot-clip")
19
+ clipPath(:id="'plot-clip-' + clipId")
20
20
  rect(
21
21
  :x="axes.plotLeft"
22
22
  :y="axes.plotTop"
@@ -26,7 +26,7 @@
26
26
  g.k-chart-hits
27
27
 
28
28
  // Dynamic Interaction Layer
29
- g.k-chart-interaction(v-if="hoveredIndex !== -1 && !isLoading && hoveredPoint" clip-path="url(#plot-clip)")
29
+ g.k-chart-interaction(v-if="hoveredIndex !== -1 && !isLoading && hoveredPoint" :clip-path="'url(#plot-clip-' + clipId + ')'")
30
30
  circle.k-chart-hit-proxy(
31
31
  :cx="hoveredPoint.x"
32
32
  :cy="hoveredPoint.y"
@@ -49,7 +49,7 @@
49
49
  </template>
50
50
 
51
51
  <script setup>
52
- import { ref, watch, computed, onMounted, nextTick } from "vue";
52
+ import { ref, watch, computed, onMounted, nextTick, useId, isRef } from "vue";
53
53
  import { useChartCanvas } from "../../composables/useChartCanvas";
54
54
  import { useChartSvg } from "../../composables/useChartSvg";
55
55
  import { useChartData } from "../../composables/useChartData";
@@ -59,6 +59,7 @@ import { useChartAnimation, easeOut } from "../../composables/useChartAnimation"
59
59
  import { useChartExport } from "../../composables/useChartExport";
60
60
  import { useChartHitTest } from "../../composables/useChartHitTest";
61
61
  import { cleanNumber, safeMax } from "../../composables/chartUtils";
62
+ const clipId = useId();
62
63
  const props = defineProps({
63
64
  dataProvider: { type: null, required: false },
64
65
  xField: { type: String, required: false, default: "x" },
@@ -68,6 +69,7 @@ const props = defineProps({
68
69
  labelField: { type: String, required: false, default: "label" },
69
70
  animated: { type: Boolean, required: false, default: true },
70
71
  zoomable: { type: Boolean, required: false, default: true },
72
+ wheelNavigation: { type: Boolean, required: false, default: true },
71
73
  maxZoom: { type: Number, required: false, default: 20 },
72
74
  colors: { type: Array, required: false, default: () => ["#6366f1", "#22d3ee", "#f59e0b", "#10b981", "#f43f5e", "#a855f7", "#14b8a6", "#fb923c"] },
73
75
  backgroundColor: { type: String, required: false, default: "#ffffff" },
@@ -82,11 +84,15 @@ const svgRef = ref(null);
82
84
  const { ctx, width, height, resizeTick, clear, setupCanvas } = useChartCanvas();
83
85
  const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg();
84
86
  const dataRef = computed(() => props.dataProvider);
85
- const { items } = useChartData(dataRef);
87
+ const { items, loading: dataLoading } = useChartData(dataRef);
86
88
  const { progress, animate } = useChartAnimation();
87
89
  const { exportPng, exportSvg: exportSvgFile } = useChartExport();
88
90
  const isLoading = computed(() => {
89
- return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
91
+ const dp = props.dataProvider;
92
+ if (!dp) return dataLoading.value;
93
+ const rawLoading = dp.loading !== void 0 ? isRef(dp.loading) ? dp.loading.value : dp.loading?.value ?? dp.loading : false;
94
+ const rawInitial = dp.initialLoad !== void 0 ? isRef(dp.initialLoad) ? dp.initialLoad.value : dp.initialLoad?.value ?? dp.initialLoad : false;
95
+ return Boolean(dataLoading.value || rawLoading || rawInitial);
90
96
  });
91
97
  const tooltipStyle = computed(() => ({
92
98
  left: `${tooltipState.value.x}px`,
@@ -95,7 +101,15 @@ const tooltipStyle = computed(() => ({
95
101
  const maxZoomRef = computed(() => props.maxZoom);
96
102
  const plotWidthRef = computed(() => Math.max(0, width.value - 75));
97
103
  const plotLeftRef = computed(() => 55);
98
- const viewport = useChartViewport({ maxZoom: maxZoomRef, plotWidth: plotWidthRef, plotLeft: plotLeftRef });
104
+ const zoomableRef = computed(() => props.zoomable);
105
+ const wheelNavRef = computed(() => props.wheelNavigation);
106
+ const viewport = useChartViewport({
107
+ maxZoom: maxZoomRef,
108
+ plotWidth: plotWidthRef,
109
+ plotLeft: plotLeftRef,
110
+ zoomable: zoomableRef,
111
+ wheelNavigation: wheelNavRef
112
+ });
99
113
  const { findNearestEuclidean } = useChartHitTest();
100
114
  const {
101
115
  scale,
@@ -118,12 +132,24 @@ const xAxisType = computed(() => {
118
132
  const sample = items.value[0][props.xField];
119
133
  if (typeof sample === "number") return "numeric";
120
134
  if (typeof sample === "string") {
135
+ const cleaned = cleanNumber(sample, NaN);
136
+ if (Number.isFinite(cleaned)) return "numeric";
121
137
  const ts = Date.parse(sample);
122
- if (!isNaN(ts) && isNaN(Number(sample))) return "datetime";
138
+ if (!isNaN(ts) && !/^\d+$/.test(sample.trim()) && /[-/T:]|[A-Za-z]/.test(sample)) return "datetime";
123
139
  return "category";
124
140
  }
125
141
  return "numeric";
126
142
  });
143
+ const isAllMidnight = computed(() => {
144
+ if (xAxisType.value !== "datetime" || !items.value.length) return false;
145
+ const checkCount = Math.min(items.value.length, 100);
146
+ for (let i = 0; i < checkCount; i++) {
147
+ const ts = parseValue(items.value[i][props.xField]);
148
+ const d = new Date(ts);
149
+ if (d.getHours() !== 0 || d.getMinutes() !== 0) return false;
150
+ }
151
+ return true;
152
+ });
127
153
  const categoryList = computed(() => {
128
154
  if (xAxisType.value !== "category") return [];
129
155
  return [...new Set(items.value.map((r) => String(r[props.xField])))];
@@ -213,9 +239,29 @@ const draw = () => {
213
239
  c.fillText(val.toFixed(1), axes.plotLeft - 8, axes.toY(val) + 4);
214
240
  }
215
241
  c.textAlign = "center";
216
- for (let i = 0; i <= 5; i++) {
217
- const val = axes.xMin.value + (axes.xMax.value - axes.xMin.value) * (i / 5);
218
- c.fillText(val.toFixed(1), axes.toX(val), axes.plotTop + axes.plotHeight.value + 16);
242
+ if (xAxisType.value === "category") {
243
+ const count = categoryList.value.length;
244
+ if (count > 0) {
245
+ const xSteps = Math.min(10, count - 1);
246
+ const stepSize = xSteps > 0 ? (count - 1) / xSteps : 1;
247
+ for (let i = 0; i <= xSteps; i++) {
248
+ const idx = Math.round(i * stepSize);
249
+ const x = axes.toX(idx);
250
+ const label = categoryList.value[idx] ?? "";
251
+ c.fillText(label, x, axes.plotTop + axes.plotHeight.value + 16);
252
+ }
253
+ }
254
+ } else {
255
+ for (let i = 0; i <= 5; i++) {
256
+ const val = axes.xMin.value + (axes.xMax.value - axes.xMin.value) * (i / 5);
257
+ let label;
258
+ if (xAxisType.value === "datetime") {
259
+ label = isAllMidnight.value ? new Date(val).toLocaleDateString([], { month: "short", day: "numeric" }) : new Date(val).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
260
+ } else {
261
+ label = val.toFixed(1);
262
+ }
263
+ c.fillText(label, axes.toX(val), axes.plotTop + axes.plotHeight.value + 16);
264
+ }
219
265
  }
220
266
  c.strokeStyle = "rgba(150,150,170,0.5)";
221
267
  c.beginPath();
@@ -302,7 +348,7 @@ watch(items, () => {
302
348
  else draw();
303
349
  });
304
350
  watch(scale, (v) => emit("zoom-change", v));
305
- watch(panOffset, (v) => emit("pan-change", v), { deep: true });
351
+ watch(() => [panOffset.value.x, panOffset.value.y], ([x, y]) => emit("pan-change", { x, y }));
306
352
  onMounted(async () => {
307
353
  await nextTick();
308
354
  if (containerRef.value && canvasRef.value) {
@@ -322,7 +368,7 @@ const onMouseMove = (e) => {
322
368
  if (!rect) return;
323
369
  const mx = e.clientX - rect.left;
324
370
  const my = e.clientY - rect.top;
325
- const nearest = findNearestEuclidean(items.value, mx, my, axes, props.xField, props.yField, 20);
371
+ const nearest = findNearestEuclidean(items.value, mx, my, axes, props.xField, props.yField, 20, (item, i) => resolveX(item, i));
326
372
  if (nearest) {
327
373
  setHovered(nearest.index);
328
374
  showTooltip(mx + 10, my - 10, `${nearest.item[props.xField]}: ${nearest.item[props.yField]}`, nearest.item, nearest.index);
@@ -7,6 +7,7 @@ type __VLS_Props = {
7
7
  labelField?: string;
8
8
  animated?: boolean;
9
9
  zoomable?: boolean;
10
+ wheelNavigation?: boolean;
10
11
  maxZoom?: number;
11
12
  colors?: string[];
12
13
  backgroundColor?: string;
@@ -42,14 +43,15 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
42
43
  seriesField: string;
43
44
  animated: boolean;
44
45
  zoomable: boolean;
46
+ wheelNavigation: boolean;
45
47
  maxZoom: number;
46
48
  colors: string[];
47
49
  backgroundColor: string;
48
50
  showLegend: boolean;
49
51
  xAxisTitle: string;
50
52
  yAxisTitle: string;
51
- labelField: string;
52
53
  sizeField: string;
54
+ labelField: string;
53
55
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
54
56
  declare const _default: typeof __VLS_export;
55
57
  export default _default;
@@ -1,4 +1,4 @@
1
- import { ref } from "vue";
1
+ import { ref, onScopeDispose } from "vue";
2
2
  export function useChartAnimation(duration = 600) {
3
3
  const progress = ref(0);
4
4
  let rafId = null;
@@ -25,6 +25,12 @@ export function useChartAnimation(duration = 600) {
25
25
  rafId = null;
26
26
  }
27
27
  };
28
+ try {
29
+ onScopeDispose(() => {
30
+ stop();
31
+ });
32
+ } catch {
33
+ }
28
34
  return { progress, animate, stop };
29
35
  }
30
36
  export function easeOut(t) {
@@ -15,5 +15,6 @@ export declare function extractProviderItems<T = any>(provider?: ADataProvider |
15
15
  /**
16
16
  * Provider-agnostic data watcher for Katlux chart components.
17
17
  * Consumes data through Katlux DataProvider instances (Server-side or Client-side).
18
+ * Uses provider.subscribe() instead of Vue watch() so it works with __v_skip providers.
18
19
  */
19
20
  export declare function useChartData<T = any>(dataProvider: Ref<ADataProvider | null | undefined> | ADataProvider | null | undefined): IChartDataContext<T>;
@@ -1,14 +1,25 @@
1
- import { ref, watch, onMounted, getCurrentInstance, isRef } from "vue";
1
+ import { ref, watch, onMounted, onUnmounted, onScopeDispose, getCurrentScope, getCurrentInstance, isRef } from "vue";
2
2
  export function extractProviderItems(provider) {
3
3
  if (!provider) return [];
4
4
  const instance = isRef(provider) ? provider.value : provider;
5
5
  if (!instance) return [];
6
+ const hasActiveFilter = Boolean(
7
+ instance.filter && (typeof instance.filter === "object" && Object.keys(instance.filter).length > 0 || typeof instance.hasFilter === "boolean" && instance.hasFilter || typeof instance.hasFilter === "function" && instance.hasFilter())
8
+ );
6
9
  const filtered = isRef(instance.filteredData) ? instance.filteredData.value : instance.filteredData;
7
- if (Array.isArray(filtered) && filtered.length > 0) {
8
- return filtered;
10
+ if (Array.isArray(filtered)) {
11
+ if (hasActiveFilter) {
12
+ return filtered;
13
+ }
14
+ if (filtered.length > 0) {
15
+ return filtered;
16
+ }
9
17
  }
10
18
  const all = isRef(instance.allData) ? instance.allData.value : instance.allData;
11
19
  if (Array.isArray(all) && all.length > 0) {
20
+ if (hasActiveFilter && Array.isArray(filtered)) {
21
+ return [];
22
+ }
12
23
  return all;
13
24
  }
14
25
  let raw = isRef(instance.pageData) ? instance.pageData.value : instance.pageData;
@@ -29,24 +40,49 @@ export function useChartData(dataProvider) {
29
40
  const provider = isRef(dataProvider) ? dataProvider.value : dataProvider;
30
41
  if (provider) {
31
42
  items.value = extractProviderItems(provider);
43
+ const rawLoading = provider.loading !== void 0 ? isRef(provider.loading) ? provider.loading.value : provider.loading?.value ?? provider.loading : false;
44
+ const rawInitial = provider.initialLoad !== void 0 ? isRef(provider.initialLoad) ? provider.initialLoad.value : provider.initialLoad?.value ?? provider.initialLoad : false;
45
+ loading.value = Boolean(rawLoading || rawInitial);
32
46
  } else {
33
47
  items.value = [];
48
+ loading.value = false;
34
49
  }
35
50
  };
36
- watch(
37
- () => {
38
- const provider = isRef(dataProvider) ? dataProvider.value : dataProvider;
39
- if (!provider) return null;
40
- const filtered = isRef(provider.filteredData) ? provider.filteredData.value : provider.filteredData;
41
- const all = isRef(provider.allData) ? provider.allData.value : provider.allData;
42
- const pd = isRef(provider.pageData) ? provider.pageData.value : provider.pageData;
43
- return { provider, filtered, all, pd };
44
- },
45
- () => {
46
- updateItems();
47
- },
48
- { immediate: true, deep: false }
49
- );
51
+ let unsubscribe = null;
52
+ const setupSubscription = () => {
53
+ if (unsubscribe) {
54
+ unsubscribe();
55
+ unsubscribe = null;
56
+ }
57
+ const isServer = typeof window === "undefined" || typeof import.meta !== "undefined" && Boolean(import.meta.server);
58
+ const provider = isRef(dataProvider) ? dataProvider.value : dataProvider;
59
+ if (!isServer && provider?.subscribe) {
60
+ unsubscribe = provider.subscribe(() => updateItems());
61
+ }
62
+ updateItems();
63
+ };
64
+ if (isRef(dataProvider)) {
65
+ watch(dataProvider, setupSubscription, { immediate: true });
66
+ } else {
67
+ setupSubscription();
68
+ }
69
+ if (getCurrentInstance()) {
70
+ onMounted(updateItems);
71
+ onUnmounted(() => {
72
+ if (unsubscribe) {
73
+ unsubscribe();
74
+ unsubscribe = null;
75
+ }
76
+ });
77
+ }
78
+ if (getCurrentScope()) {
79
+ onScopeDispose(() => {
80
+ if (unsubscribe) {
81
+ unsubscribe();
82
+ unsubscribe = null;
83
+ }
84
+ });
85
+ }
50
86
  const reload = async () => {
51
87
  const provider = isRef(dataProvider) ? dataProvider.value : dataProvider;
52
88
  if (!provider) return;
@@ -65,10 +101,5 @@ export function useChartData(dataProvider) {
65
101
  loading.value = false;
66
102
  }
67
103
  };
68
- if (getCurrentInstance()) {
69
- onMounted(updateItems);
70
- } else {
71
- updateItems();
72
- }
73
104
  return { items, loading, error, reload };
74
105
  }
@@ -28,8 +28,10 @@ export function useChartExport() {
28
28
  imageEl.setAttribute("href", dataUrl);
29
29
  imageEl.setAttribute("x", "0");
30
30
  imageEl.setAttribute("y", "0");
31
- const w = canvas.style.width ? parseFloat(canvas.style.width) : canvas.width || svgEl.clientWidth || 400;
32
- const h = canvas.style.height ? parseFloat(canvas.style.height) : canvas.height || svgEl.clientHeight || 400;
31
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
32
+ const svgBox = svgEl.viewBox?.baseVal;
33
+ const w = svgBox && svgBox.width > 0 ? svgBox.width : canvas.style.width ? parseFloat(canvas.style.width) : svgEl.clientWidth || canvas.width / dpr || 400;
34
+ const h = svgBox && svgBox.height > 0 ? svgBox.height : canvas.style.height ? parseFloat(canvas.style.height) : svgEl.clientHeight || canvas.height / dpr || 400;
33
35
  imageEl.setAttribute("width", String(w));
34
36
  imageEl.setAttribute("height", String(h));
35
37
  clone.insertBefore(imageEl, clone.firstChild);
@@ -39,7 +41,12 @@ export function useChartExport() {
39
41
  const blob = new Blob([svgStr], { type: "image/svg+xml;charset=utf-8" });
40
42
  const url = URL.createObjectURL(blob);
41
43
  download(url, filename);
42
- URL.revokeObjectURL(url);
44
+ setTimeout(() => {
45
+ try {
46
+ URL.revokeObjectURL(url);
47
+ } catch {
48
+ }
49
+ }, 1e3);
43
50
  } catch (e) {
44
51
  console.error("[KChart] SVG export failed:", e);
45
52
  }
@@ -4,7 +4,7 @@ export declare function useChartHitTest(): {
4
4
  item: any;
5
5
  index: number;
6
6
  } | null;
7
- findNearestEuclidean: (items: any[], mouseX: number, mouseY: number, axes: IChartAxesContext, xField: string, yField: string, threshold?: number) => {
7
+ findNearestEuclidean: (items: any[], mouseX: number, mouseY: number, axes: IChartAxesContext, xField: string, yField: string, threshold?: number, xValueGetter?: (item: any, index: number) => number) => {
8
8
  item: any;
9
9
  index: number;
10
10
  } | null;
@@ -16,12 +16,13 @@ export function useChartHitTest() {
16
16
  }
17
17
  return { item: items[nearestIdx], index: nearestIdx };
18
18
  };
19
- const findNearestEuclidean = (items, mouseX, mouseY, axes, xField, yField, threshold = 20) => {
19
+ const findNearestEuclidean = (items, mouseX, mouseY, axes, xField, yField, threshold = 20, xValueGetter) => {
20
20
  if (!items.length) return null;
21
21
  let nearestIdx = -1;
22
22
  let minDistanceSq = threshold * threshold;
23
23
  items.forEach((item, i) => {
24
- const px = axes.toX(parseValue(item[xField]));
24
+ const xVal = xValueGetter ? xValueGetter(item, i) : parseValue(item[xField], i);
25
+ const px = axes.toX(xVal);
25
26
  const py = axes.toY(parseValue(item[yField]));
26
27
  const dx = mouseX - px;
27
28
  const dy = mouseY - py;
@@ -9,6 +9,7 @@ export interface IViewportState {
9
9
  canPan: Ref<boolean>;
10
10
  }
11
11
  export interface IChartViewportContext extends IViewportState {
12
+ zoomTo: (newScale: number, anchorX?: number) => void;
12
13
  zoomIn: () => void;
13
14
  zoomOut: () => void;
14
15
  resetZoom: () => void;
@@ -22,4 +23,6 @@ export declare function useChartViewport(options: {
22
23
  maxZoom?: Ref<number>;
23
24
  plotWidth: Ref<number>;
24
25
  plotLeft?: Ref<number>;
26
+ zoomable?: Ref<boolean> | boolean;
27
+ wheelNavigation?: Ref<boolean> | boolean;
25
28
  }): IChartViewportContext;