Skip to content

Commit 29f6f51

Browse files
committed
VueUiSparkHistogram add component
1 parent cf2f8db commit 29f6f51

File tree

9 files changed

+483
-6
lines changed

9 files changed

+483
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
[![MadeWithVueJs.com shield](https://madewithvuejs.com/storage/repo-shields/4526-shield.svg)](https://madewithvuejs.com/p/vue-data-ui/shield-link)
1313
[![Socket Badge](https://socket.dev/api/badge/npm/package/vue-data-ui)](https://socket.dev/npm/package/vue-data-ui)
1414
![GitHub issues](https://img.shields.io/github/issues/graphieros/vue-data-ui)
15-
![Static Badge](https://img.shields.io/badge/components-25-green)
15+
![Static Badge](https://img.shields.io/badge/components-26-green)
1616

1717
[Interactive documentation](https://vue-data-ui.graphieros.com/)
1818

@@ -41,6 +41,7 @@ Available components:
4141
- [VueUiSparkline](https://vue-data-ui.graphieros.com/docs#vue-ui-sparkline)
4242
- [VueUiSparkbar](https://vue-data-ui.graphieros.com/docs#vue-ui-sparkbar)
4343
- [VueUiSparkstackbar](https://vue-data-ui.graphieros.com/docs#vue-ui-sparkstackbar)
44+
- [VueUiSparkHistogram](https://vue-data-ui.graphieros.com/docs#vue-ui-sparkhistogram)
4445

4546
## Tables
4647
- [VueUiTable](https://vue-data-ui.graphieros.com/docs#vue-ui-table)

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vue-data-ui",
33
"private": false,
4-
"version": "1.8.8",
4+
"version": "1.8.9",
55
"type": "module",
66
"description": "A user-empowering data visualization Vue components library",
77
"keywords": [

src/App.vue

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import SmileyTest from "./components/vue-ui-smiley.vue";
2525
import RelationTest from "./components/vue-ui-relation-circle.vue";
2626
import ThermoTest from "./components/vue-ui-thermometer.vue";
2727
import StackTest from "./components/vue-ui-sparkstackbar.vue";
28+
import HistoTest from "./components/vue-ui-sparkhistogram.vue";
2829
2930
const dataset = ref([
3031
{
@@ -3342,6 +3343,156 @@ const stackDataset = ref([
33423343
},
33433344
]);
33443345
3346+
const histoConfig = ref({
3347+
"style": {
3348+
"backgroundColor":"#FFFFFF",
3349+
"fontFamily":"inherit",
3350+
"layout": {
3351+
"height": 96,
3352+
"width": 640,
3353+
"padding": {
3354+
"top": 24,
3355+
"right": 0,
3356+
"left": 0,
3357+
"bottom": 36
3358+
}
3359+
},
3360+
"bars": {
3361+
"strokeWidth": 0,
3362+
"colors": {
3363+
"positive": "#3366cc",
3364+
"negative": "#dc3912",
3365+
"gradient": {
3366+
"show": true
3367+
}
3368+
},
3369+
"borderRadius": 24,
3370+
"gap": 12
3371+
},
3372+
"labels": {
3373+
"value": {
3374+
"fontSize": 14,
3375+
"color":"#2D353C",
3376+
"bold": true,
3377+
"rounding": 1,
3378+
"prefix":"",
3379+
"suffix":""
3380+
},
3381+
"valueLabel": {
3382+
"fontSize": 14,
3383+
"color":"#2D353C",
3384+
"bold": false,
3385+
"rounding": 0
3386+
},
3387+
"timeLabel": {
3388+
"fontSize": 12,
3389+
"color":"#2D353C",
3390+
"bold": false
3391+
}
3392+
},
3393+
"title": {
3394+
"textAlign": "left",
3395+
"text": "Title",
3396+
"color": "#2D353C",
3397+
"fontSize": 16,
3398+
"bold": true,
3399+
"margin": "0 0 6px 0",
3400+
"subtitle": {
3401+
"color": "#A1A1A1",
3402+
"text": "Subitle",
3403+
"fontSize": 12,
3404+
"bold": false
3405+
}
3406+
}
3407+
}
3408+
});
3409+
3410+
const histoDataset = ref([
3411+
{
3412+
value: 1.2,
3413+
valueLabel: "20%",
3414+
timeLabel: "09:00",
3415+
intensity: 0.2,
3416+
},
3417+
{
3418+
value: 1.3,
3419+
valueLabel: "50%",
3420+
timeLabel: "10:00",
3421+
intensity: 0.5,
3422+
3423+
},
3424+
{
3425+
value: 1.1,
3426+
valueLabel: "60%",
3427+
timeLabel: "11:00",
3428+
intensity: 0.6,
3429+
3430+
},
3431+
{
3432+
value: 0.8,
3433+
valueLabel: "70%",
3434+
timeLabel: "12:00",
3435+
intensity: 0.7,
3436+
3437+
},
3438+
{
3439+
value: 2,
3440+
valueLabel: "100%",
3441+
timeLabel: "13:00",
3442+
intensity: 1,
3443+
3444+
},
3445+
{
3446+
value: 2.1,
3447+
valueLabel: "100%",
3448+
timeLabel: "14:00",
3449+
intensity: 1,
3450+
3451+
},
3452+
{
3453+
value: 2.3,
3454+
valueLabel: "80%",
3455+
timeLabel: "15:00",
3456+
intensity: 0.8,
3457+
3458+
},
3459+
{
3460+
value: 2.1,
3461+
valueLabel: "70%",
3462+
timeLabel: "16:00",
3463+
intensity: 0.7,
3464+
3465+
},
3466+
{
3467+
value: 0.9,
3468+
valueLabel: "60%",
3469+
timeLabel: "17:00",
3470+
intensity: 0.6,
3471+
3472+
},
3473+
{
3474+
value: 0.7,
3475+
valueLabel: "50%",
3476+
timeLabel: "18:00",
3477+
intensity: 0.5,
3478+
3479+
},
3480+
{
3481+
value: 0.3,
3482+
valueLabel: "30%",
3483+
timeLabel: "19:00",
3484+
intensity: 0.3,
3485+
3486+
},
3487+
{
3488+
value: 0.2,
3489+
valueLabel: "20%",
3490+
timeLabel: "20:00",
3491+
intensity: 0.2,
3492+
3493+
},
3494+
]);
3495+
33453496
const showLocalTest = ref(false);
33463497
</script>
33473498

@@ -3379,6 +3530,11 @@ const showLocalTest = ref(false);
33793530
<button @click="printRelation">PRINT RELATION CIRCLE</button>
33803531
<button @click="printThermo">PRINT THERMO</button>
33813532

3533+
<div style="max-width: 1000px; margin: 0 auto; margin-bottom: 48px">
3534+
<VueUiSparkHistogram v-if="!showLocalTest" :dataset="histoDataset" :config="histoConfig"/>
3535+
<HistoTest v-if="showLocalTest" :dataset="histoDataset" :config="histoConfig"/>
3536+
</div>
3537+
33823538
<div style="max-width: 1000px; margin: 0 auto; margin-bottom: 48px">
33833539
<VueUiCandlestick
33843540
ref="candlestick"
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<script setup>
2+
import { ref, computed } from "vue";
3+
import { treeShake, shiftHue, opacity, convertConfigColors, palette, convertColorToHex } from "../lib";
4+
import mainConfig from "../default_configs.json";
5+
6+
const props = defineProps({
7+
config: {
8+
type: Object,
9+
default() {
10+
return {}
11+
}
12+
},
13+
dataset: {
14+
type: Array,
15+
default() {
16+
return [];
17+
}
18+
}
19+
});
20+
21+
const uid = ref(`vue-ui-sparkhistogram-${Math.random()}`);
22+
const defaultConfig = ref(mainConfig.vue_ui_sparkhistogram);
23+
24+
const histoConfig = computed(() => {
25+
if(!Object.keys(props.config || {}).length) {
26+
return defaultConfig.value;
27+
}
28+
const reconciled = treeShake({
29+
defaultConfig: defaultConfig.value,
30+
userConfig: props.config
31+
});
32+
return convertConfigColors(reconciled);
33+
});
34+
35+
const drawingArea = computed(() => {
36+
const height = histoConfig.value.style.layout.height;
37+
const width = histoConfig.value.style.layout.width;
38+
const top = histoConfig.value.style.layout.padding.top;
39+
const bottom = height - histoConfig.value.style.layout.padding.bottom;
40+
const left = histoConfig.value.style.layout.padding.left;
41+
const right = width - histoConfig.value.style.layout.padding.right;
42+
const centerY = top + ((height - top - histoConfig.value.style.layout.padding.bottom) / 2);
43+
const drawingHeight = height - histoConfig.value.style.layout.padding.top - histoConfig.value.style.layout.padding.bottom;
44+
const drawingWidth = width - histoConfig.value.style.layout.padding.left - histoConfig.value.style.layout.padding.right;
45+
return {
46+
bottom,
47+
centerY,
48+
drawingHeight,
49+
drawingWidth,
50+
height,
51+
left,
52+
right,
53+
top,
54+
width,
55+
}
56+
});
57+
58+
const maxVal = computed(() => {
59+
return Math.max(...props.dataset.map(ds => Math.abs(ds.value)))
60+
});
61+
62+
function toMax(val) {
63+
return Math.abs(val) / maxVal.value;
64+
}
65+
66+
// value, valueLabel, timeLabel
67+
68+
const computedDataset = computed(() => {
69+
return props.dataset.map((dp,i) => {
70+
const proportion = toMax(dp.value);
71+
const height = drawingArea.value.drawingHeight * proportion;
72+
const unitWidth = drawingArea.value.drawingWidth / props.dataset.length;
73+
const gap = unitWidth * (histoConfig.value.style.bars.gap / 100)
74+
const width = unitWidth - gap;
75+
const y = drawingArea.value.centerY - height / 2;
76+
const x = drawingArea.value.left + (gap / 2 + (i * unitWidth));
77+
const intensity = typeof dp.intensity === 'undefined' ? 100 : Math.round(dp.intensity * 100);
78+
const color = dp.value >= 0 ? `${histoConfig.value.style.bars.colors.positive}${opacity[intensity]}` : `${histoConfig.value.style.bars.colors.negative}${opacity[intensity]}`;
79+
const stroke = dp.value >= 0 ? histoConfig.value.style.bars.colors.positive : histoConfig.value.style.bars.colors.negative;
80+
const gradient = dp.value >= 0 ? `url(#gradient_positive_${i}_${uid.value})` : `url(#gradient_negative_${i}_${uid.value})`;
81+
const textAnchor = x + width / 2;
82+
return {
83+
...dp,
84+
color,
85+
gradient,
86+
height,
87+
intensity,
88+
proportion,
89+
stroke,
90+
textAnchor,
91+
width,
92+
x,
93+
y
94+
}
95+
})
96+
})
97+
98+
</script>
99+
100+
<template>
101+
<div :style="`width:100%;background:${histoConfig.style.backgroundColor};font-family:${histoConfig.style.fontFamily}`">
102+
<!-- TITLE -->
103+
<div v-if="histoConfig.style.title.text" :style="`width:calc(100% - 12px);background:${histoConfig.style.backgroundColor};margin:0 auto;margin:${histoConfig.style.title.margin};padding: 0 6px;text-align:${histoConfig.style.title.textAlign}`">
104+
<div :style="`font-size:${histoConfig.style.title.fontSize}px;color:${histoConfig.style.title.color};font-weight:${histoConfig.style.title.bold ? 'bold' : 'normal'}`">
105+
{{ histoConfig.style.title.text }}
106+
</div>
107+
<div v-if="histoConfig.style.title.subtitle.text" :style="`font-size:${histoConfig.style.title.subtitle.fontSize}px;color:${histoConfig.style.title.subtitle.color};font-weight:${histoConfig.style.title.subtitle.bold ? 'bold' : 'normal'}`">
108+
{{ histoConfig.style.title.subtitle.text }}
109+
</div>
110+
</div>
111+
112+
<svg :viewBox="`0 0 ${drawingArea.width} ${drawingArea.height}`">
113+
114+
<defs>
115+
<radialGradient v-for="(posGrad, i) in computedDataset" :id="`gradient_positive_${i}_${uid}`" cy="50%" cx="50%" r="50%" fx="50%" fy="50%">
116+
<stop offset="0%" :stop-color="`${shiftHue(histoConfig.style.bars.colors.positive, 0.05)}${opacity[posGrad.intensity]}`"/>
117+
<stop offset="100%" :stop-color="`${histoConfig.style.bars.colors.positive}${opacity[posGrad.intensity]}`"/>
118+
</radialGradient>
119+
120+
<radialGradient v-for="(negGrad, i) in computedDataset" :id="`gradient_negative_${i}_${uid}`" cy="50%" cx="50%" r="50%" fx="50%" fy="50%">
121+
<stop offset="0%" :stop-color="`${shiftHue(histoConfig.style.bars.colors.negative, 0.05)}${opacity[negGrad.intensity]}`"/>
122+
<stop offset="100%" :stop-color="`${histoConfig.style.bars.colors.negative}${opacity[negGrad.intensity]}`"/>
123+
</radialGradient>
124+
</defs>
125+
126+
127+
<rect v-for="rect in computedDataset"
128+
:x="rect.x"
129+
:y="rect.y"
130+
:height="rect.height"
131+
:width="rect.width"
132+
:fill="histoConfig.style.bars.colors.gradient.show ? rect.gradient : rect.color"
133+
:stroke="rect.stroke"
134+
:stroke-width="histoConfig.style.bars.strokeWidth"
135+
:rx="`${histoConfig.style.bars.borderRadius * rect.proportion / 12}%`"
136+
/>
137+
138+
<text v-for="val in computedDataset"
139+
text-anchor="middle"
140+
:x="val.textAnchor"
141+
:y="val.y - histoConfig.style.labels.value.fontSize / 3"
142+
:font-size="histoConfig.style.labels.value.fontSize"
143+
:font-weight="histoConfig.style.labels.value.bold ? 'bold' : 'normal'"
144+
:fill="histoConfig.style.labels.value.color"
145+
>
146+
{{ histoConfig.style.labels.value.prefix }}{{ isNaN(val.value) ? '' : Number(val.value.toFixed(histoConfig.style.labels.value.rounding)).toLocaleString() }}{{ histoConfig.style.labels.value.suffix }}
147+
</text>
148+
149+
<g v-for="label in computedDataset">
150+
<text
151+
v-if="label.valueLabel"
152+
:x="label.textAnchor"
153+
:y="label.y + label.height + histoConfig.style.labels.valueLabel.fontSize"
154+
:font-size="histoConfig.style.labels.valueLabel.fontSize"
155+
text-anchor="middle"
156+
:fill="histoConfig.style.labels.valueLabel.color"
157+
>
158+
{{ label.valueLabel }}
159+
</text>
160+
</g>
161+
162+
<g v-for="time in computedDataset">
163+
<text
164+
v-if="time.timeLabel"
165+
:x="time.textAnchor"
166+
:y="drawingArea.height - histoConfig.style.labels.timeLabel.fontSize / 2"
167+
:font-size="histoConfig.style.labels.timeLabel.fontSize"
168+
:fill="histoConfig.style.labels.timeLabel.color"
169+
text-anchor="middle"
170+
>
171+
{{ time.timeLabel }}
172+
</text>
173+
</g>
174+
175+
</svg>
176+
</div>
177+
</template>

0 commit comments

Comments
 (0)