@mekari/pixel3-chart 0.0.1-dev.0

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,624 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-QZ7VFGWC.mjs";
4
+
5
+ // src/modules/chart.hooks.ts
6
+ import { computed, watch, ref, shallowRef, toRefs, onMounted, onUnmounted } from "vue";
7
+ import { getUniqueId } from "@mekari/pixel3-utils";
8
+ import { chartSlotRecipe } from "@mekari/pixel3-styled-system/recipes";
9
+ import { token } from "@mekari/pixel3-styled-system/tokens";
10
+ import { merge } from "lodash-es";
11
+ import Color from "color";
12
+ import Chart from "chart.js/auto";
13
+ import ChartDataLabels from "chartjs-plugin-datalabels";
14
+ function useChart(props, emit) {
15
+ const {
16
+ id,
17
+ title,
18
+ type,
19
+ widthChart,
20
+ heightChart,
21
+ widthContainer,
22
+ heightContainer,
23
+ data,
24
+ options,
25
+ colorPattern,
26
+ colorStart,
27
+ colorRatio,
28
+ legendPosition,
29
+ legendDirection,
30
+ tooltipMarginTop,
31
+ tooltipMarginLeft,
32
+ isShowDataLabels,
33
+ isHorizontal,
34
+ isStacked,
35
+ isArea,
36
+ isDebug,
37
+ isShowBackgroundHover,
38
+ isShowGaugePointer,
39
+ isMixedChart,
40
+ tooltipOptions
41
+ } = toRefs(props);
42
+ const chart = shallowRef(null);
43
+ const tooltip = ref({
44
+ top: 0,
45
+ left: 0,
46
+ opacity: 0,
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ data: null
49
+ });
50
+ const legend = ref([]);
51
+ const baseColor = ref();
52
+ const chartContainerNode = ref();
53
+ const chartLegendNode = ref();
54
+ const chartCanvasNode = ref();
55
+ const getId = id.value || getUniqueId("", "chart").value;
56
+ const isHorizontalBarChart = computed(() => {
57
+ return type.value === "bar" && isHorizontal.value;
58
+ });
59
+ const isCircularChart = computed(() => {
60
+ return type.value === "pie" || type.value === "doughnut";
61
+ });
62
+ const getLegendPosition = computed(() => {
63
+ let position = "column";
64
+ if (legendPosition.value === "right") {
65
+ position = "row";
66
+ }
67
+ if (legendPosition.value === "top") {
68
+ position = "column-reverse";
69
+ }
70
+ if (legendPosition.value === "left") {
71
+ position = "row-reverse";
72
+ }
73
+ return position;
74
+ });
75
+ const getLegendDirection = computed(() => {
76
+ let direction = "flex-start";
77
+ if (legendDirection.value === "center") {
78
+ direction = "center";
79
+ }
80
+ if (legendDirection.value === "right") {
81
+ direction = "flex-end";
82
+ }
83
+ return direction;
84
+ });
85
+ const getOptions = computed(() => {
86
+ const basicOptions = {
87
+ maintainAspectRatio: false,
88
+ responsive: true,
89
+ animation: true,
90
+ cutout: type.value === "doughnut" ? 70 : 0,
91
+ ...isHorizontalBarChart.value && {
92
+ indexAxis: "y"
93
+ },
94
+ interaction: {
95
+ intersect: !isArea.value
96
+ },
97
+ plugins: {
98
+ PluginHtmlLegend: true,
99
+ PluginHoverBackgroundColors: isShowBackgroundHover.value,
100
+ gaugeChartPointer: isShowGaugePointer.value,
101
+ title: {
102
+ display: false
103
+ },
104
+ legend: {
105
+ display: false
106
+ },
107
+ tooltip: {
108
+ enabled: false,
109
+ position: "average",
110
+ external: handleExternalTooltip
111
+ },
112
+ filler: {
113
+ propagate: false
114
+ },
115
+ datalabels: {
116
+ display: isShowDataLabels.value,
117
+ color: "#626B79",
118
+ clamp: true,
119
+ font: {
120
+ size: 10
121
+ },
122
+ labels: {
123
+ value: {
124
+ anchor: "end",
125
+ align: "end"
126
+ }
127
+ }
128
+ }
129
+ },
130
+ elements: {
131
+ bar: {
132
+ backgroundColor: getListColors.value[1],
133
+ borderRadius: "4",
134
+ ...isStacked.value && {
135
+ borderWidth: {
136
+ top: !isHorizontal.value ? 2 : 0,
137
+ right: isHorizontal.value ? 2 : 0
138
+ },
139
+ borderColor: "#FFFFFF"
140
+ }
141
+ },
142
+ line: {
143
+ borderColor: isArea.value ? "#FFFFFF" : getListColors.value[1],
144
+ borderWidth: 2,
145
+ tension: isArea.value ? 0.4 : 0
146
+ },
147
+ point: {
148
+ radius: isArea.value || type.value === "radar" ? 0 : 6,
149
+ borderWidth: 2,
150
+ hoverRadius: isArea.value || type.value === "radar" ? 0 : 6,
151
+ hoverBorderWidth: 2
152
+ },
153
+ arc: {
154
+ backgroundColor: getListColors.value[1],
155
+ borderColor: "#FFFFFF",
156
+ hoverBorderColor: "#FFFFFF"
157
+ }
158
+ },
159
+ scales: {
160
+ x: {
161
+ stacked: isStacked.value,
162
+ border: {
163
+ display: false
164
+ },
165
+ grid: {
166
+ display: false,
167
+ drawOnChartArea: false,
168
+ drawTicks: false
169
+ },
170
+ ticks: {
171
+ display: !isCircularChart.value && type.value !== "radar",
172
+ color: "#626B79"
173
+ }
174
+ },
175
+ y: {
176
+ stacked: isStacked.value,
177
+ border: {
178
+ display: false
179
+ },
180
+ grid: {
181
+ display: !isHorizontal.value,
182
+ drawOnChartArea: !isCircularChart.value && type.value !== "radar",
183
+ drawTicks: false,
184
+ color: "#D0D6DD"
185
+ },
186
+ ticks: {
187
+ display: !isCircularChart.value && type.value !== "radar",
188
+ beginAtZero: true,
189
+ color: "#626B79",
190
+ padding: 8
191
+ }
192
+ },
193
+ ...type.value === "radar" && {
194
+ r: {
195
+ angleLines: {
196
+ display: true,
197
+ color: "#D0D6DD"
198
+ },
199
+ grid: {
200
+ color: "#D0D6DD"
201
+ },
202
+ ticks: {
203
+ color: "#626B79"
204
+ },
205
+ pointLabels: {
206
+ color: "#626B79"
207
+ }
208
+ }
209
+ }
210
+ }
211
+ };
212
+ return merge(basicOptions, options.value);
213
+ });
214
+ const getDataset = computed(() => {
215
+ const datasets = data.value.datasets || [];
216
+ if (type.value === "bar") {
217
+ datasets.forEach((item, index) => {
218
+ item.backgroundColor = item.backgroundColor || handleColorPattern(index);
219
+ item.barPercentage = item.barPercentage || 0.68;
220
+ item.categoryPercentage = item.categoryPercentage || 1;
221
+ });
222
+ }
223
+ if (isCircularChart.value) {
224
+ const backgroundColor = [];
225
+ datasets.forEach((item) => {
226
+ item.data.forEach((_, index) => {
227
+ return backgroundColor.push(handleColorPattern(index));
228
+ });
229
+ item.backgroundColor = item.backgroundColor || backgroundColor;
230
+ });
231
+ }
232
+ if (isArea.value && (type.value === "line" || isMixedChart.value)) {
233
+ datasets.forEach((item, index) => {
234
+ item.fill = "start";
235
+ item.backgroundColor = item.backgroundColor || handleColorPattern(index);
236
+ });
237
+ }
238
+ if (!isArea.value && (type.value === "line" || type.value === "radar" || isMixedChart.value)) {
239
+ const isFading = type.value === "radar";
240
+ datasets.forEach((item, index) => {
241
+ item.backgroundColor = item.backgroundColor || handleColorPattern(index, isFading);
242
+ item.borderColor = item.borderColor || handleColorPattern(index);
243
+ item.pointBackgroundColor = item.pointBackgroundColor || "#FFFFFF";
244
+ });
245
+ }
246
+ return datasets;
247
+ });
248
+ const getListColors = computed(() => {
249
+ return {
250
+ 1: token("colors.sky.400"),
251
+ 2: token("colors.sky.100"),
252
+ 3: token("colors.teal.400"),
253
+ 4: token("colors.teal.100"),
254
+ 5: token("colors.violet.400"),
255
+ 6: token("colors.violet.100"),
256
+ 7: token("colors.amber.400"),
257
+ 8: token("colors.amber.100"),
258
+ 9: token("colors.rose.400"),
259
+ 10: token("colors.rose.100"),
260
+ 11: token("colors.stone.400"),
261
+ 12: token("colors.stone.100"),
262
+ 13: token("colors.lime.400"),
263
+ 14: token("colors.lime.100"),
264
+ 15: token("colors.pink.400"),
265
+ 16: token("colors.pink.100"),
266
+ 17: token("colors.apricot.400"),
267
+ 18: token("colors.apricot.100"),
268
+ 19: token("colors.aqua.400"),
269
+ 20: token("colors.aqua.100"),
270
+ 21: token("colors.leaf.400"),
271
+ 22: token("colors.leaf.100"),
272
+ 23: token("colors.fuchsia.400"),
273
+ 24: token("colors.fuchsia.100")
274
+ };
275
+ });
276
+ const rootAttrs = computed(() => {
277
+ return {
278
+ "data-pixel-component": "MpChart",
279
+ id: getId,
280
+ class: chartSlotRecipe().root,
281
+ style: {
282
+ width: widthContainer.value
283
+ }
284
+ };
285
+ });
286
+ const chartHeaderAttrs = computed(() => {
287
+ return {
288
+ class: chartSlotRecipe().chartHeader
289
+ };
290
+ });
291
+ const chartWrapperAttrs = computed(() => {
292
+ return {
293
+ class: chartSlotRecipe().chartWrapper,
294
+ style: {
295
+ flexDirection: getLegendPosition.value
296
+ }
297
+ };
298
+ });
299
+ const chartContainerAttrs = computed(() => {
300
+ return {
301
+ ref: chartContainerNode,
302
+ id: `chart-container--${getId}`,
303
+ class: chartSlotRecipe().chartContainer,
304
+ style: {
305
+ width: widthContainer.value,
306
+ height: heightContainer.value
307
+ }
308
+ };
309
+ });
310
+ const canvasContainerAttrs = computed(() => {
311
+ return {
312
+ id: `canvas-container--${getId}`,
313
+ class: chartSlotRecipe().canvasContainer,
314
+ style: {
315
+ width: widthChart.value,
316
+ height: heightChart.value
317
+ }
318
+ };
319
+ });
320
+ const canvasAttrs = computed(() => {
321
+ return {
322
+ ref: chartCanvasNode,
323
+ id: `canvas-${getId}`,
324
+ ariaLabel: `Piel Chart ${title.value}`,
325
+ role: "img"
326
+ };
327
+ });
328
+ const chartLegendAttrs = computed(() => {
329
+ return {
330
+ ref: chartLegendNode,
331
+ id: `chart-legend--${getId}`,
332
+ class: chartSlotRecipe().chartLegend,
333
+ style: {
334
+ justifyContent: getLegendDirection.value,
335
+ marginTop: legendPosition.value === "bottom" ? token("spacing.4") : "0",
336
+ marginBottom: legendPosition.value === "top" ? token("spacing.4") : "0"
337
+ }
338
+ };
339
+ });
340
+ const legendWrapperAttrs = /* @__PURE__ */ __name((item, index) => {
341
+ return {
342
+ key: index,
343
+ class: chartSlotRecipe().legendWrapper,
344
+ onClick: () => handleClickLegend(item)
345
+ };
346
+ }, "legendWrapperAttrs");
347
+ const legendBoxAttrs = /* @__PURE__ */ __name((item) => {
348
+ return {
349
+ class: chartSlotRecipe().legendBox,
350
+ style: {
351
+ width: token("sizes.3"),
352
+ height: token("sizes.3"),
353
+ background: item.fillStyle
354
+ }
355
+ };
356
+ }, "legendBoxAttrs");
357
+ const chartTooltipAttrs = computed(() => {
358
+ return {
359
+ class: chartSlotRecipe().chartTooltip,
360
+ style: {
361
+ top: `${tooltip.value.top}px`,
362
+ left: `${tooltip.value.left}px`,
363
+ opacity: tooltip.value.opacity
364
+ }
365
+ };
366
+ });
367
+ const tooltipWrapperAttrs = computed(() => {
368
+ return {
369
+ class: chartSlotRecipe().tooltipWrapper
370
+ };
371
+ });
372
+ const tooltipRowAttrs = computed(() => {
373
+ return {
374
+ class: chartSlotRecipe().tooltipRow
375
+ };
376
+ });
377
+ const tooltipItemAttrs = computed(() => {
378
+ return {
379
+ class: chartSlotRecipe().tooltipItem
380
+ };
381
+ });
382
+ const tooltipBoxAttrs = computed(() => {
383
+ var _a, _b, _c, _d;
384
+ return {
385
+ class: chartSlotRecipe().tooltipBox,
386
+ style: {
387
+ backgroundColor: type.value === "line" ? (_b = (_a = tooltip.value.data) == null ? void 0 : _a.labelColors[0]) == null ? void 0 : _b.borderColor : (_d = (_c = tooltip.value.data) == null ? void 0 : _c.labelColors[0]) == null ? void 0 : _d.backgroundColor
388
+ }
389
+ };
390
+ });
391
+ function createChart() {
392
+ if (!chartCanvasNode.value)
393
+ return;
394
+ chart.value = new Chart(chartCanvasNode.value, {
395
+ type: type.value,
396
+ data: {
397
+ labels: typeof data.value.labels === "undefined" ? [] : [...data.value.labels],
398
+ datasets: getDataset.value
399
+ },
400
+ options: getOptions.value,
401
+ plugins: [ChartDataLabels, handleExternalLegend(), hoverBackgroundColors(), gaugeChartPointer()]
402
+ });
403
+ if (isDebug.value) {
404
+ console.log(`[MpChart] The context chart of ${getId} is:`, chart.value);
405
+ }
406
+ }
407
+ __name(createChart, "createChart");
408
+ function destroyChart() {
409
+ var _a;
410
+ (_a = chart.value) == null ? void 0 : _a.destroy();
411
+ }
412
+ __name(destroyChart, "destroyChart");
413
+ function updateChart() {
414
+ destroyChart();
415
+ createChart();
416
+ }
417
+ __name(updateChart, "updateChart");
418
+ function handleExternalTooltip(context) {
419
+ const {
420
+ tooltip: tooltipCtx
421
+ } = context;
422
+ const legendHeight = chartLegendNode.value ? chartLegendNode.value.clientHeight + 16 : tooltipMarginTop.value;
423
+ const extendTopValue = legendPosition.value === "top" ? legendHeight.value : 0;
424
+ const legendWidth = chartLegendNode.value ? chartLegendNode.value.clientWidth : tooltipMarginLeft.value;
425
+ const extendLeftValue = legendPosition.value === "left" ? legendWidth.value : 0;
426
+ const tooltipLeftPosition = tooltipCtx.x - chartContainerNode.value.scrollLeft;
427
+ const tooltipTopPosition = tooltipCtx.y > chartContainerNode.value.scrollTop ? tooltipCtx.caretY - chartContainerNode.value.scrollTop : tooltipCtx.caretY;
428
+ if (tooltipCtx.opacity === 1) {
429
+ tooltip.value = {
430
+ opacity: tooltipCtx.opacity,
431
+ left: tooltipLeftPosition + extendLeftValue + tooltipOptions.value.offsetLeft,
432
+ top: tooltipTopPosition + extendTopValue + tooltipOptions.value.offsetTop,
433
+ data: tooltipCtx
434
+ };
435
+ emit("show-tooltip", context);
436
+ } else {
437
+ tooltip.value.opacity = 0;
438
+ emit("hide-tooltip", context);
439
+ }
440
+ if (isDebug.value) {
441
+ console.log(`[MpChart] The context tooltip of ${getId} is:`, tooltip.value);
442
+ }
443
+ }
444
+ __name(handleExternalTooltip, "handleExternalTooltip");
445
+ function gaugeChartPointer() {
446
+ return {
447
+ id: "gaugeChartPointer",
448
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
449
+ afterDatasetsDraw(chart2, args, options2) {
450
+ const {
451
+ ctx,
452
+ data: data2
453
+ } = chart2;
454
+ const pointerIndexStart = options2.pointerIndexStart || 1;
455
+ const xCenter = chart2.getDatasetMeta(0).data[pointerIndexStart].x;
456
+ const yCenter = chart2.getDatasetMeta(0).data[pointerIndexStart].y;
457
+ const innerRadius = chart2.getDatasetMeta(0).data[pointerIndexStart].innerRadius;
458
+ const outerRadius = chart2.getDatasetMeta(0).data[pointerIndexStart].outerRadius;
459
+ const circumference = chart2.getDatasetMeta(0).data[pointerIndexStart].circumference;
460
+ const halfCircle = circumference / Math.PI / data2.datasets[0].data[pointerIndexStart];
461
+ const pointerRadius = outerRadius - innerRadius - 10;
462
+ const pointerColor = options2.pointerColor || "black";
463
+ const pointerValue = options2.pointerValue || 1;
464
+ const pointerRatio = options2.pointerRatio || 1.47;
465
+ const pointerPosition = halfCircle * pointerValue;
466
+ const pointerAngle = Math.PI * (pointerPosition + pointerRatio);
467
+ ctx.save();
468
+ ctx.translate(xCenter, yCenter);
469
+ ctx.rotate(pointerAngle);
470
+ ctx.beginPath();
471
+ ctx.strokeStyle = "white";
472
+ ctx.fillStyle = pointerColor;
473
+ ctx.lineWidth = 8;
474
+ ctx.shadowColor = "rgba(0, 0, 0, 0.08)";
475
+ ctx.shadowBlur = 8;
476
+ ctx.shadowOffsetX = 6;
477
+ ctx.shadowOffsetY = 6;
478
+ ctx.beginPath();
479
+ ctx.roundRect(0, -outerRadius + 6, pointerRadius, pointerRadius, 100);
480
+ ctx.fill();
481
+ ctx.stroke();
482
+ ctx.restore();
483
+ }
484
+ };
485
+ }
486
+ __name(gaugeChartPointer, "gaugeChartPointer");
487
+ function hoverBackgroundColors() {
488
+ return {
489
+ id: "PluginHoverBackgroundColors",
490
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
491
+ beforeDatasetsDraw(chart2) {
492
+ const {
493
+ ctx,
494
+ tooltip: tooltip2,
495
+ chartArea: {
496
+ top,
497
+ height
498
+ },
499
+ scales: {
500
+ x
501
+ }
502
+ } = chart2;
503
+ if (tooltip2._active[0]) {
504
+ const index = tooltip2._active[0].index;
505
+ const leftStart = x._gridLineItems[index + 1] ? x._gridLineItems[index + 1].x1 : 0;
506
+ const newWidth = x._gridLineItems[0].x1 - x._gridLineItems[1].x1;
507
+ ctx.fillStyle = "rgba(236, 240, 242, 0.6)";
508
+ ctx.fillRect(leftStart, top, newWidth, height);
509
+ }
510
+ }
511
+ };
512
+ }
513
+ __name(hoverBackgroundColors, "hoverBackgroundColors");
514
+ function handleExternalLegend() {
515
+ return {
516
+ id: "PluginHtmlLegend",
517
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
518
+ afterUpdate(chart2) {
519
+ var _a, _b, _c, _d;
520
+ const items = (_d = (_c = (_b = (_a = chart2 == null ? void 0 : chart2.options) == null ? void 0 : _a.plugins) == null ? void 0 : _b.legend) == null ? void 0 : _c.labels) == null ? void 0 : _d.generateLabels(chart2);
521
+ items.forEach((item) => {
522
+ item.chart = chart2;
523
+ item.click = () => {
524
+ if (isCircularChart.value) {
525
+ chart2.toggleDataVisibility(item.index);
526
+ } else {
527
+ chart2.setDatasetVisibility(item.datasetIndex, !chart2.isDatasetVisible(item.datasetIndex));
528
+ }
529
+ chart2.update();
530
+ };
531
+ });
532
+ legend.value = items;
533
+ if (isDebug.value) {
534
+ console.log(`[MpChart] The context legend of ${getId} is:`, legend.value);
535
+ }
536
+ }
537
+ };
538
+ }
539
+ __name(handleExternalLegend, "handleExternalLegend");
540
+ function handleColorPattern(idx, isFading) {
541
+ let color = "";
542
+ const startIndex = colorStart.value - 1;
543
+ const indexColors = Object.keys(getListColors.value);
544
+ const odds = indexColors.filter((number) => {
545
+ return Number(number) % 2 !== 0;
546
+ });
547
+ if (colorPattern.value === "") {
548
+ color = getListColors.value[Number(indexColors[startIndex + idx])];
549
+ }
550
+ if (colorPattern.value === "categorical") {
551
+ const oddsLength = odds.length - startIndex;
552
+ if (idx < oddsLength) {
553
+ color = getListColors.value[Number(odds[startIndex + idx])];
554
+ } else {
555
+ const multiplier = Math.floor(idx / oddsLength);
556
+ const darkenRatio = (colorRatio.value * multiplier).toFixed(1);
557
+ baseColor.value = getListColors.value[Number(odds[startIndex + idx - oddsLength * multiplier])];
558
+ color = Color(baseColor.value).darken(Number(darkenRatio)).hex();
559
+ }
560
+ }
561
+ if (colorPattern.value === "comparison") {
562
+ color = getListColors.value[Number(indexColors[startIndex + idx])];
563
+ }
564
+ if (colorPattern.value === "dark-shades" || colorPattern.value === "light-shades") {
565
+ if (idx === 0) {
566
+ color = getListColors.value[Number(indexColors[startIndex])];
567
+ } else {
568
+ if (colorPattern.value === "dark-shades") {
569
+ color = Color(baseColor.value).darken(colorRatio.value).hex();
570
+ }
571
+ if (colorPattern.value === "light-shades") {
572
+ color = Color(baseColor.value).lighten(colorRatio.value).hex();
573
+ }
574
+ }
575
+ baseColor.value = color;
576
+ }
577
+ color = isFading ? Color(color).fade(0.5).string() : color;
578
+ return color;
579
+ }
580
+ __name(handleColorPattern, "handleColorPattern");
581
+ function handleClickLegend(item) {
582
+ item.click();
583
+ emit("click-legend", item);
584
+ }
585
+ __name(handleClickLegend, "handleClickLegend");
586
+ watch([() => data.value, () => options.value, () => type.value], ([newData, newOptions, newType]) => {
587
+ if (newData || newOptions || newType) {
588
+ updateChart();
589
+ }
590
+ }, {
591
+ deep: true
592
+ });
593
+ onMounted(() => {
594
+ createChart();
595
+ chartContainerNode.value.scrollTo(0, chartContainerNode.value.scrollHeight);
596
+ });
597
+ onUnmounted(() => {
598
+ destroyChart();
599
+ });
600
+ return {
601
+ rootAttrs,
602
+ chartHeaderAttrs,
603
+ chartWrapperAttrs,
604
+ chartContainerAttrs,
605
+ canvasContainerAttrs,
606
+ canvasAttrs,
607
+ chartLegendAttrs,
608
+ legendWrapperAttrs,
609
+ legendBoxAttrs,
610
+ chartTooltipAttrs,
611
+ tooltipWrapperAttrs,
612
+ tooltipRowAttrs,
613
+ tooltipItemAttrs,
614
+ tooltipBoxAttrs,
615
+ chart,
616
+ legend,
617
+ tooltip
618
+ };
619
+ }
620
+ __name(useChart, "useChart");
621
+
622
+ export {
623
+ useChart
624
+ };
@@ -0,0 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ export {
5
+ __name
6
+ };