@katlux/block-charts 0.1.0-beta.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/module.d.mts +6 -0
  2. package/dist/module.json +12 -0
  3. package/dist/module.mjs +23 -0
  4. package/dist/runtime/components/KAreaChart/KAreaChart.d.vue.ts +53 -0
  5. package/dist/runtime/components/KAreaChart/KAreaChart.vue +382 -0
  6. package/dist/runtime/components/KAreaChart/KAreaChart.vue.d.ts +53 -0
  7. package/dist/runtime/components/KBarChart/KBarChart.d.vue.ts +55 -0
  8. package/dist/runtime/components/KBarChart/KBarChart.vue +398 -0
  9. package/dist/runtime/components/KBarChart/KBarChart.vue.d.ts +55 -0
  10. package/dist/runtime/components/KHeatMap/KHeatMap.d.vue.ts +36 -0
  11. package/dist/runtime/components/KHeatMap/KHeatMap.vue +263 -0
  12. package/dist/runtime/components/KHeatMap/KHeatMap.vue.d.ts +36 -0
  13. package/dist/runtime/components/KLineChart/KLineChart.d.vue.ts +51 -0
  14. package/dist/runtime/components/KLineChart/KLineChart.vue +407 -0
  15. package/dist/runtime/components/KLineChart/KLineChart.vue.d.ts +51 -0
  16. package/dist/runtime/components/KPieChart/KPieChart.d.vue.ts +32 -0
  17. package/dist/runtime/components/KPieChart/KPieChart.vue +273 -0
  18. package/dist/runtime/components/KPieChart/KPieChart.vue.d.ts +32 -0
  19. package/dist/runtime/components/KScatterChart/KScatterChart.d.vue.ts +55 -0
  20. package/dist/runtime/components/KScatterChart/KScatterChart.vue +356 -0
  21. package/dist/runtime/components/KScatterChart/KScatterChart.vue.d.ts +55 -0
  22. package/dist/runtime/composables/useChartAnimation.d.ts +9 -0
  23. package/dist/runtime/composables/useChartAnimation.js +32 -0
  24. package/dist/runtime/composables/useChartAxes.d.ts +40 -0
  25. package/dist/runtime/composables/useChartAxes.js +58 -0
  26. package/dist/runtime/composables/useChartCanvas.d.ts +11 -0
  27. package/dist/runtime/composables/useChartCanvas.js +47 -0
  28. package/dist/runtime/composables/useChartData.d.ts +14 -0
  29. package/dist/runtime/composables/useChartData.js +45 -0
  30. package/dist/runtime/composables/useChartExport.d.ts +5 -0
  31. package/dist/runtime/composables/useChartExport.js +32 -0
  32. package/dist/runtime/composables/useChartHitTest.d.ts +11 -0
  33. package/dist/runtime/composables/useChartHitTest.js +39 -0
  34. package/dist/runtime/composables/useChartSvg.d.ts +17 -0
  35. package/dist/runtime/composables/useChartSvg.js +22 -0
  36. package/dist/runtime/composables/useChartViewport.d.ts +25 -0
  37. package/dist/runtime/composables/useChartViewport.js +92 -0
  38. package/dist/types.d.mts +3 -0
  39. package/package.json +7 -3
  40. package/build.config.ts +0 -4
  41. package/src/module.ts +0 -25
  42. package/src/runtime/components/KAreaChart/KAreaChart.vue +0 -410
  43. package/src/runtime/components/KBarChart/KBarChart.vue +0 -427
  44. package/src/runtime/components/KHeatMap/KHeatMap.vue +0 -301
  45. package/src/runtime/components/KLineChart/KLineChart.vue +0 -493
  46. package/src/runtime/components/KPieChart/KPieChart.vue +0 -307
  47. package/src/runtime/components/KScatterChart/KScatterChart.vue +0 -375
  48. package/src/runtime/composables/useChartAnimation.ts +0 -45
  49. package/src/runtime/composables/useChartAxes.ts +0 -105
  50. package/src/runtime/composables/useChartCanvas.ts +0 -67
  51. package/src/runtime/composables/useChartData.ts +0 -79
  52. package/src/runtime/composables/useChartExport.ts +0 -40
  53. package/src/runtime/composables/useChartHitTest.ts +0 -71
  54. package/src/runtime/composables/useChartSvg.ts +0 -45
  55. package/src/runtime/composables/useChartViewport.ts +0 -140
@@ -0,0 +1,273 @@
1
+ <template lang="pug">
2
+ .k-chart-wrapper
3
+ .k-chart-inner(
4
+ ref="containerRef"
5
+ @mousemove="onMouseMove"
6
+ @mouseleave="onMouseLeave"
7
+ )
8
+ KLoader(:loading="isLoading" overlay)
9
+ canvas(ref="canvasRef" v-show="!isLoading")
10
+ svg.k-chart-svg(v-show="!isLoading" ref="svgRef" :width="width" :height="height")
11
+ g.k-chart-hits
12
+ // Hit detection now handled via angle calculation in onMouseMove
13
+
14
+ // Dynamic Interaction Layer
15
+ g.k-chart-interaction(v-if="hoveredIndex !== -1 && hoveredSeg")
16
+ path.k-chart-hit-proxy(
17
+ :d="hoveredSeg.d"
18
+ fill="transparent"
19
+ style="cursor: pointer"
20
+ @click="emit('click-point', hoveredSeg.item, hoveredIndex)"
21
+ )
22
+ path(
23
+ :d="hoveredSeg.d"
24
+ fill="transparent"
25
+ stroke="#fff"
26
+ stroke-width="3"
27
+ pointer-events="none"
28
+ )
29
+ .k-chart-tooltip(v-if="tooltipState.visible && !isLoading" :style="{ left: tooltipState.x + 'px', top: tooltipState.y + 'px' }")
30
+ slot(name="tooltip" :item="tooltipState.item" :index="tooltipState.index")
31
+ span {{ tooltipState.content }}
32
+ </template>
33
+
34
+ <script setup>
35
+ import { ref, watch, computed, onMounted, nextTick } from "vue";
36
+ import { useChartCanvas } from "../../composables/useChartCanvas";
37
+ import { useChartSvg } from "../../composables/useChartSvg";
38
+ import { useChartData } from "../../composables/useChartData";
39
+ import { useChartAnimation, easeOut } from "../../composables/useChartAnimation";
40
+ import { useChartExport } from "../../composables/useChartExport";
41
+ const props = defineProps({
42
+ dataProvider: { type: null, required: false },
43
+ labelField: { type: String, required: false, default: "label" },
44
+ valueField: { type: String, required: false, default: "value" },
45
+ donut: { type: Boolean, required: false, default: false },
46
+ animated: { type: Boolean, required: false, default: true },
47
+ colors: { type: Array, required: false, default: () => ["#6366f1", "#22d3ee", "#f59e0b", "#10b981", "#f43f5e", "#a855f7", "#14b8a6", "#fb923c"] },
48
+ backgroundColor: { type: String, required: false, default: "#ffffff" },
49
+ showLegend: { type: Boolean, required: false, default: true },
50
+ title: { type: String, required: false, default: "" }
51
+ });
52
+ const emit = defineEmits(["click-point", "hover-point"]);
53
+ const containerRef = ref(null);
54
+ const canvasRef = ref(null);
55
+ const svgRef = ref(null);
56
+ const { ctx, width, height, clear, setupCanvas } = useChartCanvas();
57
+ const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg();
58
+ const dataRef = computed(() => props.dataProvider);
59
+ const { items } = useChartData(dataRef);
60
+ const { progress, animate } = useChartAnimation();
61
+ const { exportPng, exportSvg: exportSvgFile } = useChartExport();
62
+ const isLoading = computed(() => {
63
+ return props.dataProvider?.loading?.value || props.dataProvider?.initialLoad?.value || false;
64
+ });
65
+ const hitSegments = ref([]);
66
+ const total = computed(() => items.value.reduce((s, r) => s + Number(r[props.valueField]), 0));
67
+ const hoveredSeg = computed(() => {
68
+ if (hoveredIndex.value === -1) return null;
69
+ return hitSegments.value[hoveredIndex.value];
70
+ });
71
+ const arcPath = (cx, cy, r, startAngle, endAngle) => {
72
+ const x1 = cx + r * Math.cos(startAngle);
73
+ const y1 = cy + r * Math.sin(startAngle);
74
+ const x2 = cx + r * Math.cos(endAngle);
75
+ const y2 = cy + r * Math.sin(endAngle);
76
+ const large = endAngle - startAngle > Math.PI ? 1 : 0;
77
+ if (props.donut) {
78
+ const ri = r * 0.5;
79
+ const xi1 = cx + ri * Math.cos(endAngle);
80
+ const yi1 = cy + ri * Math.sin(endAngle);
81
+ const xi2 = cx + ri * Math.cos(startAngle);
82
+ const yi2 = cy + ri * Math.sin(startAngle);
83
+ return `M ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} L ${xi1} ${yi1} A ${ri} ${ri} 0 ${large} 0 ${xi2} ${yi2} Z`;
84
+ }
85
+ return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
86
+ };
87
+ const draw = () => {
88
+ if (!ctx.value) return;
89
+ clear(props.backgroundColor);
90
+ const c = ctx.value;
91
+ const p = easeOut(progress.value);
92
+ const segs = [];
93
+ const cx = width.value / 2;
94
+ const cy = props.showLegend ? (height.value - 50) / 2 : height.value / 2;
95
+ const r = Math.min(width.value - 40, props.showLegend ? height.value - 100 : height.value - 40) / 2;
96
+ let startAngle = -Math.PI / 2;
97
+ const tot = total.value || 1;
98
+ items.value.forEach((item, i) => {
99
+ const slice = Number(item[props.valueField]) / tot * Math.PI * 2 * p;
100
+ const endAngle = startAngle + slice;
101
+ const color = props.colors[i % props.colors.length];
102
+ const isHovered = hoveredIndex.value === i;
103
+ const rr = isHovered ? r + 8 : r;
104
+ c.save();
105
+ c.beginPath();
106
+ c.moveTo(cx, cy);
107
+ c.arc(cx, cy, rr, startAngle, endAngle);
108
+ c.closePath();
109
+ c.fillStyle = color;
110
+ c.fill();
111
+ c.restore();
112
+ const midAngle = startAngle + slice / 2;
113
+ const lr = r * 0.7;
114
+ const lx = cx + lr * Math.cos(midAngle);
115
+ const ly = cy + lr * Math.sin(midAngle);
116
+ if (p > 0.5) {
117
+ c.save();
118
+ c.fillStyle = "#fff";
119
+ c.font = "bold 11px system-ui, sans-serif";
120
+ c.textAlign = "center";
121
+ c.textBaseline = "middle";
122
+ const pct = (Number(item[props.valueField]) / tot * 100).toFixed(0) + "%";
123
+ c.fillText(pct, lx, ly);
124
+ c.restore();
125
+ }
126
+ segs.push({
127
+ d: arcPath(cx, cy, rr, startAngle, endAngle),
128
+ cx: cx + r * 0.75 * Math.cos(startAngle + slice / 2),
129
+ cy: cy + r * 0.75 * Math.sin(startAngle + slice / 2),
130
+ item,
131
+ label: `${item[props.labelField]}: ${item[props.valueField]}`,
132
+ startAngle,
133
+ endAngle
134
+ });
135
+ startAngle = endAngle;
136
+ });
137
+ if (props.donut) {
138
+ c.save();
139
+ c.beginPath();
140
+ c.arc(cx, cy, r * 0.5, 0, Math.PI * 2);
141
+ c.fillStyle = "#000";
142
+ c.globalCompositeOperation = "destination-out";
143
+ c.fill();
144
+ c.globalCompositeOperation = "source-over";
145
+ c.restore();
146
+ }
147
+ hitSegments.value = segs;
148
+ if (props.showLegend) {
149
+ drawLegend(c);
150
+ }
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
+ };
160
+ const drawLegend = (c) => {
161
+ c.save();
162
+ c.font = "11px system-ui, sans-serif";
163
+ c.textAlign = "left";
164
+ c.textBaseline = "middle";
165
+ const keys = items.value.map((it) => String(it[props.labelField]));
166
+ let xOffset = 20;
167
+ let yOffset = height.value - 25;
168
+ keys.forEach((key, i) => {
169
+ const color = props.colors[i % props.colors.length];
170
+ c.fillStyle = color;
171
+ c.fillRect(xOffset, yOffset - 4, 10, 8);
172
+ c.fillStyle = "rgba(150,150,170,0.9)";
173
+ c.fillText(key, xOffset + 15, yOffset);
174
+ const textWidth = c.measureText(key).width;
175
+ xOffset += textWidth + 35;
176
+ if (xOffset > width.value - 50) {
177
+ xOffset = 20;
178
+ yOffset -= 15;
179
+ }
180
+ });
181
+ c.restore();
182
+ };
183
+ watch([items, hoveredIndex, progress, () => props.donut], draw, { deep: true });
184
+ watch(items, () => {
185
+ if (props.animated) animate();
186
+ }, { deep: true });
187
+ onMounted(async () => {
188
+ await nextTick();
189
+ if (containerRef.value && canvasRef.value) {
190
+ setupCanvas(canvasRef.value);
191
+ if (props.animated) animate();
192
+ else draw();
193
+ }
194
+ });
195
+ const onMouseMove = (e) => {
196
+ const rect = containerRef.value?.getBoundingClientRect();
197
+ if (!rect) return;
198
+ const cx = width.value / 2;
199
+ const cy = props.showLegend ? (height.value - 50) / 2 : height.value / 2;
200
+ const r = Math.min(width.value - 40, props.showLegend ? height.value - 100 : height.value - 40) / 2;
201
+ const mx = e.clientX - rect.left;
202
+ const my = e.clientY - rect.top;
203
+ const dx = mx - cx;
204
+ const dy = my - cy;
205
+ const dist = Math.sqrt(dx * dx + dy * dy);
206
+ const inRadius = dist <= r + 10 && (!props.donut || dist >= r * 0.5 - 5);
207
+ if (inRadius) {
208
+ let angle = Math.atan2(dy, dx);
209
+ if (angle < -Math.PI / 2) angle += Math.PI * 2;
210
+ const idx = hitSegments.value.findIndex((seg) => angle >= seg.startAngle && angle < seg.endAngle);
211
+ if (idx !== -1) {
212
+ const seg = hitSegments.value[idx];
213
+ setHovered(idx);
214
+ showTooltip(mx + 10, my - 10, seg.label, seg.item, idx);
215
+ emit("hover-point", seg.item, idx);
216
+ return;
217
+ }
218
+ }
219
+ setHovered(-1);
220
+ hideTooltip();
221
+ };
222
+ const onMouseLeave = () => {
223
+ setHovered(-1);
224
+ hideTooltip();
225
+ };
226
+ defineExpose({
227
+ exportPng: () => canvasRef.value && exportPng(canvasRef.value, "pie-chart.png"),
228
+ exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, "pie-chart.svg")
229
+ });
230
+ </script>
231
+
232
+ <style scoped>
233
+ .k-chart-wrapper {
234
+ position: relative;
235
+ user-select: none;
236
+ }
237
+ .k-chart-wrapper .k-chart-inner {
238
+ width: 100%;
239
+ height: 400px;
240
+ position: relative;
241
+ }
242
+ .k-chart-wrapper .k-chart-inner canvas, .k-chart-wrapper .k-chart-inner .k-chart-svg {
243
+ position: absolute;
244
+ top: 0;
245
+ left: 0;
246
+ width: 100%;
247
+ height: 100%;
248
+ display: block;
249
+ }
250
+ .k-chart-wrapper .k-chart-inner .k-chart-svg {
251
+ overflow: visible;
252
+ }
253
+ .k-chart-wrapper .k-chart-svg {
254
+ pointer-events: none;
255
+ }
256
+ .k-chart-wrapper .k-chart-svg .k-chart-hit-proxy {
257
+ pointer-events: all;
258
+ cursor: pointer;
259
+ }
260
+ .k-chart-wrapper .k-chart-tooltip {
261
+ position: absolute;
262
+ background: var(--bg-color-elevated, #1e1e2e);
263
+ color: var(--text-color-primary, #cdd6f4);
264
+ border: 1px solid var(--border-color-medium, #45475a);
265
+ border-radius: 8px;
266
+ padding: 6px 10px;
267
+ font-size: 12px;
268
+ pointer-events: none;
269
+ z-index: 20;
270
+ white-space: nowrap;
271
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
272
+ }
273
+ </style>
@@ -0,0 +1,32 @@
1
+ type __VLS_Props = {
2
+ dataProvider?: any;
3
+ labelField?: string;
4
+ valueField?: string;
5
+ donut?: boolean;
6
+ animated?: boolean;
7
+ colors?: string[];
8
+ backgroundColor?: string;
9
+ showLegend?: boolean;
10
+ title?: string;
11
+ };
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
13
+ exportPng: () => void | null;
14
+ exportSvg: () => void | null;
15
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "click-point": (item: any, index: number) => any;
17
+ "hover-point": (item: any, index: number) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ "onClick-point"?: ((item: any, index: number) => any) | undefined;
20
+ "onHover-point"?: ((item: any, index: number) => any) | undefined;
21
+ }>, {
22
+ title: string;
23
+ animated: boolean;
24
+ colors: string[];
25
+ backgroundColor: string;
26
+ showLegend: boolean;
27
+ valueField: string;
28
+ labelField: string;
29
+ donut: boolean;
30
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
31
+ declare const _default: typeof __VLS_export;
32
+ export default _default;
@@ -0,0 +1,55 @@
1
+ type __VLS_Props = {
2
+ dataProvider?: any;
3
+ xField?: string;
4
+ yField?: string;
5
+ seriesField?: string;
6
+ sizeField?: string;
7
+ labelField?: string;
8
+ animated?: boolean;
9
+ zoomable?: boolean;
10
+ maxZoom?: number;
11
+ colors?: string[];
12
+ backgroundColor?: string;
13
+ showLegend?: boolean;
14
+ xAxisTitle?: string;
15
+ yAxisTitle?: string;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
18
+ zoomIn: () => void;
19
+ zoomOut: () => void;
20
+ resetZoom: () => void;
21
+ exportPng: () => void | null;
22
+ exportSvg: () => void | null;
23
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
+ "click-point": (item: any, index: number) => any;
25
+ "hover-point": (item: any, index: number) => any;
26
+ "zoom-change": (scale: number) => any;
27
+ "pan-change": (offset: {
28
+ x: number;
29
+ y: number;
30
+ }) => any;
31
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
32
+ "onClick-point"?: ((item: any, index: number) => any) | undefined;
33
+ "onHover-point"?: ((item: any, index: number) => any) | undefined;
34
+ "onZoom-change"?: ((scale: number) => any) | undefined;
35
+ "onPan-change"?: ((offset: {
36
+ x: number;
37
+ y: number;
38
+ }) => any) | undefined;
39
+ }>, {
40
+ xField: string;
41
+ yField: string;
42
+ seriesField: string;
43
+ animated: boolean;
44
+ zoomable: boolean;
45
+ maxZoom: number;
46
+ colors: string[];
47
+ backgroundColor: string;
48
+ showLegend: boolean;
49
+ xAxisTitle: string;
50
+ yAxisTitle: string;
51
+ labelField: string;
52
+ sizeField: string;
53
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;