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

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.
@@ -0,0 +1,2 @@
1
+ export * from "/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module";
2
+ export { default } from "/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module";
@@ -0,0 +1,2 @@
1
+ export * from "/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module";
2
+ export { default } from "/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module";
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@katlux/block-charts",
3
+ "configKey": "katluxCharts",
4
+ "compatibility": {
5
+ "nuxt": "^3.0.0"
6
+ },
7
+ "version": "0.1.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ import { createJiti } from "file:///home/cagatay/Desktop/projects/KatluxShowcase/node_modules/jiti/lib/jiti.mjs";
2
+
3
+ const jiti = createJiti(import.meta.url, {
4
+ "interopDefault": true,
5
+ "alias": {
6
+ "@katlux/block-charts": "/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts"
7
+ },
8
+ "transformOptions": {
9
+ "babel": {
10
+ "plugins": []
11
+ }
12
+ }
13
+ })
14
+
15
+ /** @type {import("/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module")} */
16
+ const _module = await jiti.import("/home/cagatay/Desktop/projects/KatluxShowcase/packages/katlux-block-charts/src/module.ts");
17
+
18
+ export default _module?.default ?? _module;
@@ -0,0 +1,13 @@
1
+ import type { ModuleHooks, ModuleRuntimeHooks, ModuleRuntimeConfig, ModulePublicRuntimeConfig } from './module.mjs'
2
+
3
+ declare module '#app' {
4
+ interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
5
+ }
6
+
7
+ declare module '@nuxt/schema' {
8
+ interface NuxtHooks extends ModuleHooks {}
9
+ interface RuntimeConfig extends ModuleRuntimeConfig {}
10
+ interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
11
+ }
12
+
13
+ export * from "./module.mjs"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@katlux/block-charts",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0-beta.1",
4
4
  "description": "Pre-built charting block components for Katlux toolkit",
5
5
  "author": "Katlux",
6
6
  "license": "MIT",
@@ -27,5 +27,8 @@
27
27
  "@nuxt/kit": "^3.20.1",
28
28
  "pug": "^3.0.0",
29
29
  "sass": "^1.80.0"
30
- }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ]
31
34
  }
package/build.config.ts DELETED
@@ -1,4 +0,0 @@
1
- export default {
2
- failOnWarn: false,
3
- externals: ['#app', '@katlux/toolkit', '@katlux/providers']
4
- }
package/src/module.ts DELETED
@@ -1,25 +0,0 @@
1
- import { defineNuxtModule, createResolver, addComponentsDir } from '@nuxt/kit'
2
-
3
- export interface ModuleOptions { }
4
-
5
- export default defineNuxtModule<ModuleOptions>({
6
- meta: {
7
- name: '@katlux/block-charts',
8
- configKey: 'katluxCharts',
9
- compatibility: {
10
- nuxt: '^3.0.0'
11
- }
12
- },
13
- defaults: {},
14
- async setup(options, nuxt) {
15
- const resolver = createResolver(import.meta.url)
16
-
17
- // Add components directory
18
- addComponentsDir({
19
- path: resolver.resolve('./runtime/components'),
20
- pathPrefix: false,
21
- prefix: '',
22
- global: true
23
- })
24
- }
25
- })
@@ -1,410 +0,0 @@
1
- <template lang="pug">
2
- .k-chart-wrapper(
3
- :class="{ 'k-chart--grabbing': isDragging, 'k-chart--pannable': canPan }"
4
- :style="{ cursor: isDragging ? 'grabbing' : canPan ? 'grab' : 'default' }"
5
- @wheel.prevent="onWheel"
6
- @mousedown="onMouseDown"
7
- @mousemove="onMouseMove"
8
- @mouseup="onMouseUp"
9
- @mouseleave="onMouseLeave"
10
- )
11
- .k-chart-controls(v-if="zoomable")
12
- KButton(size="small" @click="zoomIn" title="Zoom In") +
13
- KButton(size="small" @click="zoomOut" title="Zoom Out") −
14
- KButton(size="small" @click="resetZoom" title="Reset Zoom") ↺
15
- .k-chart-inner(ref="containerRef")
16
- KLoader(:loading="isLoading" overlay)
17
- canvas(ref="canvasRef" v-show="!isLoading")
18
- svg.k-chart-svg(
19
- v-show="!isLoading"
20
- ref="svgRef"
21
- :width="width"
22
- :height="height"
23
- )
24
- defs
25
- clipPath(id="plot-clip")
26
- rect(
27
- :x="axes.plotLeft"
28
- :y="axes.plotTop"
29
- :width="axes.plotWidth.value"
30
- :height="axes.plotHeight.value"
31
- )
32
- g.k-chart-hits
33
- // No more rect v-for here.
34
-
35
- // Dynamic Interaction Layer
36
- g.k-chart-interaction(v-if="hoveredIndex !== -1" clip-path="url(#plot-clip)")
37
- circle.k-chart-hit-proxy(
38
- v-for="(pt, i) in hoveredPoints"
39
- :key="i"
40
- :cx="pt.x"
41
- :cy="pt.y"
42
- :r="10"
43
- fill="transparent"
44
- style="cursor: pointer"
45
- @click="emit('click-point', pt.item, pt.index)"
46
- )
47
- circle(
48
- v-for="(pt, i) in hoveredPoints"
49
- :key="'v' + i"
50
- :cx="pt.x"
51
- :cy="pt.y"
52
- :r="5"
53
- :fill="pt.color"
54
- stroke="#fff"
55
- stroke-width="2"
56
- pointer-events="none"
57
- )
58
- .k-chart-tooltip(
59
- v-if="tooltipState.visible && !isLoading"
60
- :style="{ left: tooltipState.x + 'px', top: tooltipState.y + 'px' }"
61
- )
62
- slot(name="tooltip" :item="tooltipState.item" :index="tooltipState.index")
63
- span {{ tooltipState.content }}
64
- </template>
65
-
66
- <script lang="ts" setup>
67
- import { ref, watch, computed, onMounted, nextTick } from 'vue'
68
- import { useChartCanvas } from '../../composables/useChartCanvas'
69
- import { useChartSvg } from '../../composables/useChartSvg'
70
- import { useChartData } from '../../composables/useChartData'
71
- import { useChartAxes, parseValue } from '../../composables/useChartAxes'
72
- import { useChartViewport } from '../../composables/useChartViewport'
73
- import { useChartAnimation, easeOut } from '../../composables/useChartAnimation'
74
- import { useChartExport } from '../../composables/useChartExport'
75
- import { useChartHitTest } from '../../composables/useChartHitTest'
76
-
77
- const props = withDefaults(defineProps<{
78
- dataProvider?: any
79
- xField?: string
80
- yField?: string
81
- seriesField?: string
82
- animated?: boolean
83
- zoomable?: boolean
84
- maxZoom?: number
85
- stacked?: boolean
86
- colors?: string[]
87
- backgroundColor?: string
88
- showLegend?: boolean
89
- xAxisTitle?: string
90
- yAxisTitle?: string
91
- }>(), {
92
- xField: 'x',
93
- yField: 'y',
94
- seriesField: 'series',
95
- animated: true,
96
- zoomable: true,
97
- maxZoom: 10,
98
- stacked: false,
99
- colors: () => ['#6366f1', '#22d3ee', '#f59e0b', '#10b981', '#f43f5e', '#a855f7'],
100
- backgroundColor: '#ffffff',
101
- showLegend: true,
102
- xAxisTitle: '',
103
- yAxisTitle: ''
104
- })
105
-
106
- const emit = defineEmits<{
107
- 'click-point': [item: any, index: number]
108
- 'hover-point': [item: any, index: number]
109
- 'zoom-change': [scale: number]
110
- 'pan-change': [offset: { x: number; y: number }]
111
- }>()
112
-
113
- const containerRef = ref<HTMLElement | null>(null)
114
- const canvasRef = ref<HTMLCanvasElement | null>(null)
115
- const svgRef = ref<SVGSVGElement | null>(null)
116
-
117
- const { ctx, width, height, clear, setupCanvas } = useChartCanvas()
118
- const { hoveredIndex, tooltipState, showTooltip, hideTooltip, setHovered } = useChartSvg()
119
- const dataRef = computed(() => props.dataProvider)
120
- const { items } = useChartData(dataRef)
121
- const { progress, animate } = useChartAnimation()
122
- const { exportPng, exportSvg: exportSvgFile } = useChartExport()
123
-
124
- const isLoading = computed(() => {
125
- return props.dataProvider?.loading?.value || (props.dataProvider as any)?.initialLoad?.value || false
126
- })
127
-
128
- const maxZoomRef = computed(() => props.maxZoom)
129
- const plotWidthRef = computed(() => Math.max(0, width.value - 75))
130
- const plotLeftRef = computed(() => 55)
131
- const viewport = useChartViewport({ maxZoom: maxZoomRef, plotWidth: plotWidthRef, plotLeft: plotLeftRef })
132
- const { findNearestX } = useChartHitTest()
133
-
134
- const {
135
- scale, panOffset, isDragging, canPan,
136
- zoomIn, zoomOut, resetZoom,
137
- onWheel, onMouseDown, onMouseMove: onViewportMouseMove, onMouseUp, onMouseLeave: onViewportMouseLeave
138
- } = viewport
139
-
140
- const xFieldRef = computed(() => props.xField)
141
- const yFieldRef = computed(() => props.yField)
142
-
143
- const seriesMap = computed(() => {
144
- const map = new Map<string, any[]>()
145
- for (const item of items.value) {
146
- const key = String(item[props.seriesField] ?? 'default')
147
- if (!map.has(key)) map.set(key, [])
148
- map.get(key)!.push(item)
149
- }
150
- return map
151
- })
152
-
153
- const axes = useChartAxes({ items, xField: xFieldRef, yField: yFieldRef, width, height, scale, panOffset, padding: { top: 20, right: 20, bottom: 40, left: 55 } })
154
-
155
- // hitPoints removed to save memory and avoid DOM nodes
156
-
157
- const draw = () => {
158
- if (!ctx.value) return
159
- clear(props.backgroundColor)
160
- const c = ctx.value
161
- const p = easeOut(progress.value)
162
-
163
- // Draw grid
164
- c.save()
165
- c.strokeStyle = 'rgba(100,100,120,0.1)'
166
- c.lineWidth = 1
167
- for (let i = 0; i <= 6; i++) {
168
- const y = axes.plotTop + (axes.plotHeight.value / 6) * i
169
- c.beginPath()
170
- c.moveTo(axes.plotLeft, y)
171
- c.lineTo(axes.plotLeft + axes.plotWidth.value, y)
172
- c.stroke()
173
- }
174
- for (let i = 0; i <= 6; i++) {
175
- const x = axes.plotLeft + (axes.plotWidth.value / 6) * i
176
- c.beginPath()
177
- c.moveTo(x, axes.plotTop)
178
- c.lineTo(x, axes.plotTop + axes.plotHeight.value)
179
- c.stroke()
180
- }
181
- c.restore()
182
-
183
- // Axes labels & lines
184
- c.save()
185
- c.fillStyle = 'rgba(150,150,170,0.8)'
186
- c.font = '11px system-ui, sans-serif'
187
- c.textAlign = 'right'
188
- for (let i = 0; i <= 5; i++) {
189
- const val = axes.yMin.value + (axes.yMax.value - axes.yMin.value) * (i / 5)
190
- c.fillText(val.toFixed(1), axes.plotLeft - 8, axes.toY(val) + 4)
191
- }
192
- c.textAlign = 'center'
193
- for (let i = 0; i <= 5; i++) {
194
- const val = axes.xMin.value + (axes.xMax.value - axes.xMin.value) * (i / 5)
195
- const x = axes.toX(val)
196
- let label = val.toFixed(1)
197
- if (val > 1000000000000) label = new Date(val).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
198
- c.fillText(label, x, axes.plotTop + axes.plotHeight.value + 16)
199
- }
200
-
201
- c.beginPath()
202
- c.moveTo(axes.plotLeft, axes.plotTop)
203
- c.lineTo(axes.plotLeft, axes.plotTop + axes.plotHeight.value)
204
- c.stroke()
205
-
206
- if (props.xAxisTitle) {
207
- c.fillStyle = 'rgba(120,120,140,0.8)'
208
- c.textAlign = 'center'
209
- c.fillText(props.xAxisTitle, axes.plotLeft + axes.plotWidth.value / 2, axes.plotTop + axes.plotHeight.value + 34)
210
- }
211
- if (props.yAxisTitle) {
212
- c.save()
213
- c.fillStyle = 'rgba(120,120,140,0.8)'
214
- c.translate(axes.plotLeft - 40, axes.plotTop + axes.plotHeight.value / 2)
215
- c.rotate(-Math.PI / 2)
216
- c.textAlign = 'center'
217
- c.fillText(props.yAxisTitle, 0, 0)
218
- c.restore()
219
- }
220
-
221
- c.restore()
222
-
223
- // Clipping for plot area
224
- c.save()
225
- c.beginPath()
226
- c.rect(axes.plotLeft, axes.plotTop, axes.plotWidth.value, axes.plotHeight.value)
227
- c.clip()
228
-
229
- // Draw filled areas per series
230
- let seriesIdx = 0
231
- for (const [key, seriesItems] of seriesMap.value) {
232
- const color = props.colors[seriesIdx % props.colors.length]
233
- const sorted = [...seriesItems].sort((a, b) => parseValue(a[props.xField]) - parseValue(b[props.xField]))
234
- const drawCount = Math.max(1, Math.floor(sorted.length * p))
235
- const pts = sorted.slice(0, drawCount)
236
-
237
- c.save()
238
- // Fill
239
- c.beginPath()
240
- pts.forEach((item, i) => {
241
- const x = axes.toX(parseValue(item[props.xField]))
242
- const y = axes.toY(parseValue(item[props.yField]))
243
- if (i === 0) c.moveTo(x, y)
244
- else c.lineTo(x, y)
245
- })
246
- // Close to baseline
247
- if (pts.length > 0) {
248
- const lastX = axes.toX(parseValue(pts[pts.length - 1][props.xField]))
249
- const firstX = axes.toX(parseValue(pts[0][props.xField]))
250
- const baselineY = axes.plotTop + axes.plotHeight.value
251
- c.lineTo(lastX, baselineY)
252
- c.lineTo(firstX, baselineY)
253
- c.closePath()
254
- }
255
- c.fillStyle = color + '33'
256
- c.fill()
257
-
258
- // Line stroke
259
- c.beginPath()
260
- pts.forEach((item, i) => {
261
- const x = axes.toX(parseValue(item[props.xField]))
262
- const y = axes.toY(parseValue(item[props.yField]))
263
- if (i === 0) c.moveTo(x, y)
264
- else c.lineTo(x, y)
265
- })
266
- c.strokeStyle = color
267
- c.lineWidth = 2
268
- c.lineJoin = 'round'
269
- c.stroke()
270
- c.restore()
271
-
272
- seriesIdx++
273
- }
274
- c.restore()
275
-
276
- if (props.showLegend) {
277
- drawLegend(c)
278
- }
279
- }
280
-
281
- const drawLegend = (c: CanvasRenderingContext2D) => {
282
- c.save()
283
- c.font = '11px system-ui, sans-serif'
284
- c.textAlign = 'left'
285
- c.textBaseline = 'middle'
286
-
287
- const keys = Array.from(seriesMap.value.keys())
288
- let xOffset = axes.plotLeft
289
- const yPos = axes.plotTop + axes.plotHeight.value + 28
290
-
291
- keys.forEach((key, i) => {
292
- const color = props.colors[i % props.colors.length]
293
- c.fillStyle = color
294
- c.fillRect(xOffset, yPos - 4, 10, 8)
295
-
296
- c.fillStyle = 'rgba(150,150,170,0.9)'
297
- const label = key === 'default' ? 'Series' : key
298
- c.fillText(label, xOffset + 15, yPos)
299
- xOffset += c.measureText(label).width + 35
300
- })
301
- c.restore()
302
- }
303
-
304
- watch([items, scale, panOffset, hoveredIndex, progress], draw, { deep: true })
305
- watch(items, () => { if (props.animated) animate() }, { deep: true })
306
- watch(scale, v => emit('zoom-change', v))
307
- watch(panOffset, v => emit('pan-change', v), { deep: true })
308
-
309
- // Points for all series at current X
310
- const hoveredPoints = computed(() => {
311
- if (hoveredIndex.value === -1) return []
312
- const item = items.value[hoveredIndex.value]
313
- const xVal = parseValue(item[props.xField])
314
- const xPx = axes.toX(xVal)
315
-
316
- const pts: any[] = []
317
- let seriesIdx = 0
318
- for (const [key, seriesItems] of seriesMap.value) {
319
- const seriesItem = seriesItems.find(it => parseValue(it[props.xField]) === xVal)
320
- if (seriesItem) {
321
- pts.push({
322
- x: xPx,
323
- y: axes.toY(parseValue(seriesItem[props.yField])),
324
- color: props.colors[seriesIdx % props.colors.length],
325
- item: seriesItem,
326
- index: items.value.indexOf(seriesItem)
327
- })
328
- }
329
- seriesIdx++
330
- }
331
- return pts
332
- })
333
-
334
- onMounted(async () => {
335
- await nextTick()
336
- if (containerRef.value && canvasRef.value) {
337
- setupCanvas(canvasRef.value)
338
- if (props.animated) animate()
339
- else draw()
340
- }
341
- })
342
-
343
- const onMouseMove = (e: MouseEvent) => {
344
- viewport.onMouseMove(e)
345
- if (isDragging.value) {
346
- setHovered(-1)
347
- hideTooltip()
348
- return
349
- }
350
-
351
- const rect = containerRef.value?.getBoundingClientRect()
352
- if (!rect) return
353
- const mouseX = e.clientX - rect.left
354
- const mouseY = e.clientY - rect.top
355
- const nearest = findNearestX(items.value, mouseX, props.xField, axes)
356
- if (nearest) {
357
- setHovered(nearest.index)
358
- showTooltip(mouseX + 10, mouseY - 20, `${nearest.item[props.xField]}: ${nearest.item[props.yField]}`, nearest.item, nearest.index)
359
- emit('hover-point', nearest.item, nearest.index)
360
- } else {
361
- setHovered(-1)
362
- hideTooltip()
363
- }
364
- }
365
-
366
- const onMouseLeave = (e: MouseEvent) => {
367
- onViewportMouseLeave(e)
368
- setHovered(-1)
369
- hideTooltip()
370
- }
371
-
372
- defineExpose({
373
- zoomIn: viewport.zoomIn, zoomOut: viewport.zoomOut, resetZoom: viewport.resetZoom,
374
- exportPng: () => canvasRef.value && exportPng(canvasRef.value, 'area-chart.png'),
375
- exportSvg: () => svgRef.value && exportSvgFile(svgRef.value, 'area-chart.svg')
376
- })
377
- </script>
378
-
379
- <style lang="scss" scoped>
380
- .k-chart-wrapper {
381
- position: relative;
382
- user-select: none;
383
- .k-chart-controls { position: absolute; top: 8px; right: 8px; z-index: 10; display: flex; gap: 4px; }
384
- .k-chart-btn { display: none; }
385
- .k-chart-inner {
386
- width: 100%;
387
- height: 400px;
388
- position: relative;
389
- canvas, .k-chart-svg {
390
- position: absolute;
391
- top: 0;
392
- left: 0;
393
- width: 100%;
394
- height: 100%;
395
- display: block;
396
- }
397
- .k-chart-svg {
398
- overflow: visible;
399
- }
400
- }
401
- .k-chart-svg {
402
- pointer-events: none;
403
- .k-chart-hit-proxy {
404
- pointer-events: all;
405
- cursor: pointer;
406
- }
407
- }
408
- .k-chart-tooltip { position: absolute; background: var(--bg-color-elevated, #1e1e2e); color: var(--text-color-primary, #cdd6f4); border: 1px solid var(--border-color-medium, #45475a); border-radius: 8px; padding: 6px 10px; font-size: 12px; pointer-events: none; z-index: 20; white-space: nowrap; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
409
- }
410
- </style>