-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSparkLine.vue
147 lines (141 loc) · 3.49 KB
/
SparkLine.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<template>
<div>
<svg :viewBox="`0 0 ${width} ${height}`" :preserveAspectRatio="preserveAspectRatio">
<defs>
<defs>
<filter
id="glow"
x="-100%"
y="-100%"
width="350%"
height="350%"
color-interpolation-filters="sRGB"
>
<feGaussianBlur stdDeviation="1.8" result="coloredBlur" />
<feOffset dx="-1" dy="-1" result="offsetblur"></feOffset>
<feFlood id="glowAlpha" flood-color="#666" flood-opacity="0.8"></feFlood>
<feComposite in2="offsetblur" operator="in"></feComposite>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
</defs>
<g>
<path
:d="`${linePath} ${endPath}`"
class="sfill"
style="
stroke: none;
stroke-width: 0;
fill-opacity: 0.2;
fill: #007fff;
pointer-events: auto;
"
/>
<path
:d="linePath"
class="sline"
style="
stroke: slategray;
stroke-width: 2;
stroke-linejoin: round;
stroke-linecap: round;
fill: none;
"
/>
</g>
<g>
<circle :cx="pt[pt.length - 1].x - 2" :cy="pt[pt.length - 1].y" :r="3" style="fill: red" />
</g>
</svg>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
interface Props {
width?: number
height?: number
preserveAspectRatio?: string
cdata?: number
limit?: number
margin?: number
smooth?: number
}
interface Point {
x: number
y: number
}
const props = withDefaults(defineProps<Props>(), {
width: 170,
height: 60,
preserveAspectRatio: 'none',
cdata: 0,
limit: 20,
margin: 4,
smooth: 0.2
})
const lineData = reactive<number[]>([])
const linePath = ref<string>('')
const endPath = ref<string>('')
const prev = ref<Point | null>(null)
const pt = ref<Point[]>([])
// SVG curve path
const curve = (p: Point) => {
let res
if (!prev.value) {
res = [p.x, p.y]
} else {
const len = (p.x - prev.value.x) * props.smooth
res = ['C', prev.value.x + len, prev.value.y, p.x - len, p.y, p.x, p.y]
}
prev.value = p
return res
}
// Generate SVG path to draw sparkline chart
const getDataPoints = () => {
const len = lineData.length
const max = Math.max(...lineData)
const min = Math.min(...lineData)
const vfactor = (props.height - props.margin * 2) / (max - min || 2)
const hfactor = (props.width - props.margin * 2) / ((props.limit || len) - (len > 1 ? 1 : 0))
pt.value = lineData.map((d, i) => ({
x: i * hfactor,
y: max === min ? 1 : (max - d) * vfactor + props.margin
}))
linePath.value = `M${pt.value
.map((p) => curve(p))
.reduce((a, b) => a.concat(b))
.join(' ')}`
endPath.value = [
' L' + pt.value[pt.value.length - 1].x,
props.height - 0,
0,
props.height - 0,
0,
pt.value[0].y
].join(' ')
}
// Watch realtime price and update lineData array
watch(
() => props.cdata,
(value: number) => {
prev.value = null
const l = lineData.length
if (l === 0) {
lineData.push(0)
} else {
if (l === 1 && lineData[0] === 0) {
lineData.pop()
}
lineData.push(value)
if (l > props.limit - 1) {
lineData.shift()
}
}
getDataPoints()
},
{ immediate: true }
)
</script>