@katlux/block-charts 0.1.1-alpha.95 → 0.1.1-alpha.96
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.
- package/dist/module.json +1 -1
- package/dist/runtime/components/KAreaChart/KAreaChart.vue +7 -6
- package/dist/runtime/components/KBarChart/KBarChart.vue +29 -7
- package/dist/runtime/components/KHeatMap/KHeatMap.vue +10 -2
- package/dist/runtime/components/KLineChart/KLineChart.vue +8 -7
- package/dist/runtime/components/KScatterChart/KScatterChart.d.vue.ts +1 -1
- package/dist/runtime/components/KScatterChart/KScatterChart.vue +26 -12
- package/dist/runtime/components/KScatterChart/KScatterChart.vue.d.ts +1 -1
- package/dist/runtime/composables/useChartAxes.js +1 -0
- package/dist/runtime/composables/useChartCanvas.js +18 -0
- package/dist/runtime/composables/useChartData.d.ts +2 -2
- package/dist/runtime/composables/useChartData.js +11 -2
- package/dist/runtime/composables/useChartViewport.js +1 -0
- package/package.json +3 -3
package/dist/module.json
CHANGED
|
@@ -140,13 +140,14 @@ const xFieldRef = computed(() => props.xField);
|
|
|
140
140
|
const yFieldRef = computed(() => props.yField);
|
|
141
141
|
const xAxisType = computed(() => {
|
|
142
142
|
if (!items.value.length) return "linear";
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
if (typeof
|
|
146
|
-
|
|
143
|
+
const sample = items.value.find((r) => r?.[props.xField] !== void 0 && r?.[props.xField] !== null)?.[props.xField];
|
|
144
|
+
if (sample === void 0 || sample === null) return "linear";
|
|
145
|
+
if (typeof sample === "number") return "linear";
|
|
146
|
+
if (typeof sample === "string") {
|
|
147
|
+
const cleaned = cleanNumber(sample, NaN);
|
|
147
148
|
if (Number.isFinite(cleaned)) return "linear";
|
|
148
|
-
const parsed = Date.parse(
|
|
149
|
-
if (!isNaN(parsed) && !/^\d+$/.test(
|
|
149
|
+
const parsed = Date.parse(sample);
|
|
150
|
+
if (!isNaN(parsed) && !/^\d+$/.test(sample.trim()) && /[-/T:]|[A-Za-z]/.test(sample)) return "datetime";
|
|
150
151
|
return "category";
|
|
151
152
|
}
|
|
152
153
|
return "linear";
|
|
@@ -60,7 +60,7 @@ import { useChartData } from "../../composables/useChartData";
|
|
|
60
60
|
import { useChartViewport } from "../../composables/useChartViewport";
|
|
61
61
|
import { useChartAnimation, easeOut } from "../../composables/useChartAnimation";
|
|
62
62
|
import { useChartExport } from "../../composables/useChartExport";
|
|
63
|
-
import { cleanNumber, safeMax } from "../../composables/chartUtils";
|
|
63
|
+
import { cleanNumber, safeMax, safeMin } from "../../composables/chartUtils";
|
|
64
64
|
const clipId = useId();
|
|
65
65
|
const props = defineProps({
|
|
66
66
|
dataProvider: { type: null, required: false },
|
|
@@ -133,16 +133,38 @@ const plotWidth = computed(() => Math.max(0, width.value - PAD.left - PAD.right)
|
|
|
133
133
|
const plotHeight = computed(() => Math.max(0, height.value - PAD.top - PAD.bottom));
|
|
134
134
|
const categories = computed(() => [...new Set(items.value.map((r) => String(r[props.categoryField])))]);
|
|
135
135
|
const groups = computed(() => [...new Set(items.value.map((r) => String(r[props.groupField] ?? "default")))]);
|
|
136
|
+
const categoryMap = computed(() => {
|
|
137
|
+
const map = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const item of items.value) {
|
|
139
|
+
const cat = String(item[props.categoryField]);
|
|
140
|
+
let list = map.get(cat);
|
|
141
|
+
if (!list) {
|
|
142
|
+
list = [];
|
|
143
|
+
map.set(cat, list);
|
|
144
|
+
}
|
|
145
|
+
list.push(item);
|
|
146
|
+
}
|
|
147
|
+
return map;
|
|
148
|
+
});
|
|
136
149
|
const minValue = computed(() => {
|
|
150
|
+
if (props.mode === "stacked") {
|
|
151
|
+
const negSums = categories.value.map((cat) => {
|
|
152
|
+
const catItems = categoryMap.value.get(cat) || [];
|
|
153
|
+
return catItems.reduce((acc, r) => acc + Math.min(0, cleanNumber(r[props.valueField])), 0);
|
|
154
|
+
});
|
|
155
|
+
const min2 = Math.min(0, safeMin(negSums, 0));
|
|
156
|
+
return isFinite(min2) ? min2 : 0;
|
|
157
|
+
}
|
|
137
158
|
const vals = items.value.map((r) => cleanNumber(r[props.valueField]));
|
|
138
|
-
const min = Math.min(0,
|
|
159
|
+
const min = Math.min(0, safeMin(vals, 0));
|
|
139
160
|
return isFinite(min) ? min : 0;
|
|
140
161
|
});
|
|
141
162
|
const maxValue = computed(() => {
|
|
142
163
|
if (props.mode === "stacked") {
|
|
143
|
-
const sums = categories.value.map(
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
const sums = categories.value.map((cat) => {
|
|
165
|
+
const catItems = categoryMap.value.get(cat) || [];
|
|
166
|
+
return catItems.reduce((acc, r) => acc + Math.max(0, cleanNumber(r[props.valueField])), 0);
|
|
167
|
+
});
|
|
146
168
|
return safeMax(sums, 10);
|
|
147
169
|
}
|
|
148
170
|
const vals = items.value.map((r) => cleanNumber(r[props.valueField]));
|
|
@@ -227,7 +249,7 @@ const draw = () => {
|
|
|
227
249
|
c.rect(plotLeft, plotTop, plotWidth.value, plotHeight.value);
|
|
228
250
|
c.clip();
|
|
229
251
|
visibleCategories.value.forEach((cat, catIdx) => {
|
|
230
|
-
const catItems =
|
|
252
|
+
const catItems = categoryMap.value.get(cat) || [];
|
|
231
253
|
const catY = plotTop + catIdx * catHeight;
|
|
232
254
|
if (props.mode === "stacked") {
|
|
233
255
|
let xOffsetPos = 0;
|
|
@@ -350,7 +372,7 @@ const draw = () => {
|
|
|
350
372
|
c.rect(plotLeft, plotTop, plotWidth.value, plotHeight.value);
|
|
351
373
|
c.clip();
|
|
352
374
|
visibleCategories.value.forEach((cat, catIdx) => {
|
|
353
|
-
const catItems =
|
|
375
|
+
const catItems = categoryMap.value.get(cat) || [];
|
|
354
376
|
const catX = plotLeft + catIdx * catWidth;
|
|
355
377
|
if (props.mode === "stacked") {
|
|
356
378
|
let yOffsetPos = 0;
|
|
@@ -105,9 +105,17 @@ const hoveredCell = computed(() => {
|
|
|
105
105
|
if (hoveredIndex.value === -1) return null;
|
|
106
106
|
return renderedCells.value[hoveredIndex.value];
|
|
107
107
|
});
|
|
108
|
+
const parseHexColor = (colorStr, fallback) => {
|
|
109
|
+
let hex = (colorStr || fallback).replace("#", "").trim();
|
|
110
|
+
if (hex.length === 3) {
|
|
111
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
112
|
+
}
|
|
113
|
+
const num = parseInt(hex.padEnd(6, "0").slice(0, 6), 16);
|
|
114
|
+
return isNaN(num) ? parseInt(fallback.replace("#", ""), 16) : num;
|
|
115
|
+
};
|
|
108
116
|
const lerp = (t) => {
|
|
109
|
-
const low =
|
|
110
|
-
const high =
|
|
117
|
+
const low = parseHexColor(props.colorLow, "1e1e2e");
|
|
118
|
+
const high = parseHexColor(props.colorHigh, "6366f1");
|
|
111
119
|
const lr = low >> 16 & 255;
|
|
112
120
|
const lg = low >> 8 & 255;
|
|
113
121
|
const lb = low & 255;
|
|
@@ -139,13 +139,14 @@ const {
|
|
|
139
139
|
} = viewport;
|
|
140
140
|
const xAxisType = computed(() => {
|
|
141
141
|
if (!items.value.length) return "linear";
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
if (typeof
|
|
145
|
-
|
|
142
|
+
const sample = items.value.find((r) => r?.[props.xField] !== void 0 && r?.[props.xField] !== null)?.[props.xField];
|
|
143
|
+
if (sample === void 0 || sample === null) return "linear";
|
|
144
|
+
if (typeof sample === "number") return "linear";
|
|
145
|
+
if (typeof sample === "string") {
|
|
146
|
+
const cleaned = cleanNumber(sample, NaN);
|
|
146
147
|
if (Number.isFinite(cleaned)) return "linear";
|
|
147
|
-
const parsed = Date.parse(
|
|
148
|
-
if (!isNaN(parsed) && !/^\d+$/.test(
|
|
148
|
+
const parsed = Date.parse(sample);
|
|
149
|
+
if (!isNaN(parsed) && !/^\d+$/.test(sample.trim()) && /[-/T:]|[A-Za-z]/.test(sample)) return "datetime";
|
|
149
150
|
return "category";
|
|
150
151
|
}
|
|
151
152
|
return "linear";
|
|
@@ -387,7 +388,7 @@ const drawLine = (c, data, color, progress2) => {
|
|
|
387
388
|
c.restore();
|
|
388
389
|
};
|
|
389
390
|
watch(progress, draw);
|
|
390
|
-
watch([scale, panOffset,
|
|
391
|
+
watch([scale, panOffset, resizeTick, width, height], draw);
|
|
391
392
|
watch(items, () => {
|
|
392
393
|
if (props.animated) animate();
|
|
393
394
|
else draw();
|
|
@@ -50,8 +50,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
50
50
|
showLegend: boolean;
|
|
51
51
|
xAxisTitle: string;
|
|
52
52
|
yAxisTitle: string;
|
|
53
|
-
sizeField: string;
|
|
54
53
|
labelField: string;
|
|
54
|
+
sizeField: string;
|
|
55
55
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
56
56
|
declare const _default: typeof __VLS_export;
|
|
57
57
|
export default _default;
|
|
@@ -129,7 +129,8 @@ const xFieldRef = computed(() => props.xField);
|
|
|
129
129
|
const yFieldRef = computed(() => props.yField);
|
|
130
130
|
const xAxisType = computed(() => {
|
|
131
131
|
if (!items.value.length) return "numeric";
|
|
132
|
-
const sample = items.value[0][props.xField];
|
|
132
|
+
const sample = items.value.find((r) => r?.[props.xField] !== void 0 && r?.[props.xField] !== null)?.[props.xField];
|
|
133
|
+
if (sample === void 0 || sample === null) return "numeric";
|
|
133
134
|
if (typeof sample === "number") return "numeric";
|
|
134
135
|
if (typeof sample === "string") {
|
|
135
136
|
const cleaned = cleanNumber(sample, NaN);
|
|
@@ -181,11 +182,25 @@ const axes = useChartAxes({
|
|
|
181
182
|
const maxSize = computed(() => safeMax(items.value.map((r) => cleanNumber(r[props.sizeField] ?? 1, 1)), 1));
|
|
182
183
|
const seriesMap = computed(() => {
|
|
183
184
|
const map = /* @__PURE__ */ new Map();
|
|
184
|
-
|
|
185
|
+
const maxS = maxSize.value || 1;
|
|
186
|
+
items.value.forEach((item, idx) => {
|
|
185
187
|
const key = String(item[props.seriesField] ?? "default");
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
let list = map.get(key);
|
|
189
|
+
if (!list) {
|
|
190
|
+
list = [];
|
|
191
|
+
map.set(key, list);
|
|
192
|
+
}
|
|
193
|
+
const sizeRaw = cleanNumber(item[props.sizeField] ?? 1, 1);
|
|
194
|
+
const r = 4 + sizeRaw / maxS * 16;
|
|
195
|
+
list.push({
|
|
196
|
+
item,
|
|
197
|
+
index: idx,
|
|
198
|
+
xVal: resolveX(item, idx),
|
|
199
|
+
yVal: parseValue(item[props.yField]),
|
|
200
|
+
sizeRaw,
|
|
201
|
+
r
|
|
202
|
+
});
|
|
203
|
+
});
|
|
189
204
|
return map;
|
|
190
205
|
});
|
|
191
206
|
const hoveredPoint = computed(() => {
|
|
@@ -291,14 +306,13 @@ const draw = () => {
|
|
|
291
306
|
c.rect(axes.plotLeft, axes.plotTop, axes.plotWidth.value, axes.plotHeight.value);
|
|
292
307
|
c.clip();
|
|
293
308
|
let seriesIdx = 0;
|
|
294
|
-
for (const [,
|
|
309
|
+
for (const [, points] of seriesMap.value) {
|
|
295
310
|
const color = props.colors[seriesIdx % props.colors.length];
|
|
296
|
-
|
|
297
|
-
const x = axes.toX(
|
|
298
|
-
const y = axes.toY(
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const isHovered = items.value[hoveredIndex.value] === item;
|
|
311
|
+
points.forEach((pt) => {
|
|
312
|
+
const x = axes.toX(pt.xVal);
|
|
313
|
+
const y = axes.toY(pt.yVal);
|
|
314
|
+
const r = pt.r;
|
|
315
|
+
const isHovered = items.value[hoveredIndex.value] === pt.item;
|
|
302
316
|
const alpha = p;
|
|
303
317
|
c.save();
|
|
304
318
|
c.globalAlpha = alpha * (isHovered ? 1 : 0.75);
|
|
@@ -50,8 +50,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
50
50
|
showLegend: boolean;
|
|
51
51
|
xAxisTitle: string;
|
|
52
52
|
yAxisTitle: string;
|
|
53
|
-
sizeField: string;
|
|
54
53
|
labelField: string;
|
|
54
|
+
sizeField: string;
|
|
55
55
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
56
56
|
declare const _default: typeof __VLS_export;
|
|
57
57
|
export default _default;
|
|
@@ -69,6 +69,7 @@ export function useChartAxes(opts) {
|
|
|
69
69
|
return plotTop + plotHeight.value - (value - yMin.value) / range * plotHeight.value;
|
|
70
70
|
};
|
|
71
71
|
const fromX = (px) => {
|
|
72
|
+
if (plotWidth.value <= 0) return xMin.value;
|
|
72
73
|
const range = xMax.value - xMin.value;
|
|
73
74
|
return xMin.value + (px - plotLeft) / plotWidth.value * range;
|
|
74
75
|
};
|
|
@@ -26,6 +26,22 @@ export function useChartCanvas() {
|
|
|
26
26
|
resizeTick.value++;
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
+
let dprMediaQuery = null;
|
|
30
|
+
const handleDprChange = () => {
|
|
31
|
+
if (canvas.value) {
|
|
32
|
+
const rect = canvas.value.getBoundingClientRect();
|
|
33
|
+
const w = rect.width || canvas.value.clientWidth || width.value;
|
|
34
|
+
const h = rect.height || canvas.value.clientHeight || height.value;
|
|
35
|
+
applySize(canvas.value, w, h);
|
|
36
|
+
}
|
|
37
|
+
updateDprWatcher();
|
|
38
|
+
};
|
|
39
|
+
const updateDprWatcher = () => {
|
|
40
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
41
|
+
dprMediaQuery?.removeEventListener?.("change", handleDprChange);
|
|
42
|
+
dprMediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio ?? 1}dppx)`);
|
|
43
|
+
dprMediaQuery.addEventListener?.("change", handleDprChange);
|
|
44
|
+
};
|
|
29
45
|
const setupCanvas = (el) => {
|
|
30
46
|
canvas.value = el;
|
|
31
47
|
ctx.value = el.getContext("2d");
|
|
@@ -33,6 +49,7 @@ export function useChartCanvas() {
|
|
|
33
49
|
const initialW = rect.width || el.clientWidth || 400;
|
|
34
50
|
const initialH = rect.height || el.clientHeight || 400;
|
|
35
51
|
applySize(el, initialW, initialH);
|
|
52
|
+
updateDprWatcher();
|
|
36
53
|
if (typeof ResizeObserver !== "undefined") {
|
|
37
54
|
resizeObserver?.disconnect();
|
|
38
55
|
resizeObserver = new ResizeObserver((entries) => {
|
|
@@ -57,6 +74,7 @@ export function useChartCanvas() {
|
|
|
57
74
|
};
|
|
58
75
|
onUnmounted(() => {
|
|
59
76
|
resizeObserver?.disconnect();
|
|
77
|
+
dprMediaQuery?.removeEventListener?.("change", handleDprChange);
|
|
60
78
|
});
|
|
61
79
|
return { canvas, ctx, width, height, dpr, resizeTick, clear, setupCanvas };
|
|
62
80
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Ref } from 'vue';
|
|
1
|
+
import { type Ref, type ShallowRef } from 'vue';
|
|
2
2
|
import type { ADataProvider } from '@katlux/providers';
|
|
3
3
|
export interface IChartDataContext<T = any> {
|
|
4
|
-
items:
|
|
4
|
+
items: ShallowRef<T[]>;
|
|
5
5
|
loading: Ref<boolean>;
|
|
6
6
|
error: Ref<string | null>;
|
|
7
7
|
reload: () => Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, watch, onMounted, onUnmounted, onScopeDispose, getCurrentScope, getCurrentInstance, isRef } from "vue";
|
|
1
|
+
import { shallowRef, 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;
|
|
@@ -6,6 +6,15 @@ export function extractProviderItems(provider) {
|
|
|
6
6
|
const hasActiveFilter = Boolean(
|
|
7
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
8
|
);
|
|
9
|
+
const rowMap = isRef(instance._rowMap) ? instance._rowMap.value : instance._rowMap;
|
|
10
|
+
if (Array.isArray(rowMap)) {
|
|
11
|
+
if (hasActiveFilter) {
|
|
12
|
+
return rowMap;
|
|
13
|
+
}
|
|
14
|
+
if (rowMap.length > 0) {
|
|
15
|
+
return rowMap;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
9
18
|
const filtered = isRef(instance.filteredData) ? instance.filteredData.value : instance.filteredData;
|
|
10
19
|
if (Array.isArray(filtered)) {
|
|
11
20
|
if (hasActiveFilter) {
|
|
@@ -33,7 +42,7 @@ export function extractProviderItems(provider) {
|
|
|
33
42
|
return [];
|
|
34
43
|
}
|
|
35
44
|
export function useChartData(dataProvider) {
|
|
36
|
-
const items =
|
|
45
|
+
const items = shallowRef([]);
|
|
37
46
|
const loading = ref(false);
|
|
38
47
|
const error = ref(null);
|
|
39
48
|
const updateItems = () => {
|
|
@@ -37,6 +37,7 @@ export function useChartViewport(options) {
|
|
|
37
37
|
panOffset.value = { x: 0, y: 0 };
|
|
38
38
|
};
|
|
39
39
|
const zoomTo = (newScale, anchorX) => {
|
|
40
|
+
if (!isZoomable()) return;
|
|
40
41
|
const prev = scale.value;
|
|
41
42
|
const clamped = Math.min(Math.max(newScale, MIN_SCALE), maxZoom.value);
|
|
42
43
|
if (anchorX !== void 0 && prev !== clamped) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@katlux/block-charts",
|
|
3
|
-
"version": "0.1.1-alpha.
|
|
3
|
+
"version": "0.1.1-alpha.96",
|
|
4
4
|
"description": "Pre-built charting block components for Katlux toolkit",
|
|
5
5
|
"author": "Katlux",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"./components/*": "./dist/runtime/components/*"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@katlux/providers": "^0.1.1-alpha.
|
|
27
|
-
"@katlux/toolkit": "^0.1.1-alpha.
|
|
26
|
+
"@katlux/providers": "^0.1.1-alpha.105",
|
|
27
|
+
"@katlux/toolkit": "^0.1.1-alpha.119",
|
|
28
28
|
"@nuxt/kit": "^3.12.0",
|
|
29
29
|
"pug": "^3.0.0",
|
|
30
30
|
"sass": "^1.80.0"
|