poe-svelte-ui-lib 1.0.1 → 1.0.4
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/LICENSE +3 -3
- package/README.md +1 -0
- package/dist/Accordion/Accordion.svelte +53 -53
- package/dist/Button/Button.svelte +111 -144
- package/dist/Button/Button.svelte.d.ts +1 -34
- package/dist/ColorPicker/ColorPicker.svelte +205 -207
- package/dist/FileAttach/FileAttach.svelte +103 -103
- package/dist/Graph/Graph.svelte +270 -270
- package/dist/Input/Input.svelte +240 -239
- package/dist/Loader.svelte +12 -12
- package/dist/MessageModal.svelte +54 -54
- package/dist/ProgressBar/ProgressBar.svelte +48 -48
- package/dist/Select/Select.svelte +189 -191
- package/dist/Slider/Slider.svelte +260 -260
- package/dist/Switch/Switch.svelte +84 -83
- package/dist/Table/Table.svelte +275 -276
- package/dist/TextField/TextField.svelte +22 -22
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -11
- package/dist/{appIcons → libIcons}/ButtonAdd.svelte +10 -10
- package/dist/{appIcons → libIcons}/ButtonDelete.svelte +13 -13
- package/dist/{appIcons → libIcons}/LoaderRotate.svelte +9 -9
- package/dist/options.d.ts +11 -11
- package/dist/options.js +27 -27
- package/dist/types.d.ts +1 -1
- package/package.json +48 -47
- package/dist/Accordion/AccordionProps.svelte +0 -70
- package/dist/Accordion/AccordionProps.svelte.d.ts +0 -10
- package/dist/Button/ButtonProps.svelte +0 -200
- package/dist/Button/ButtonProps.svelte.d.ts +0 -10
- package/dist/ColorPicker/ColorPickerProps.svelte +0 -100
- package/dist/ColorPicker/ColorPickerProps.svelte.d.ts +0 -10
- package/dist/Graph/GraphProps.svelte +0 -56
- package/dist/Graph/GraphProps.svelte.d.ts +0 -10
- package/dist/Input/InputProps.svelte +0 -221
- package/dist/Input/InputProps.svelte.d.ts +0 -10
- package/dist/ProgressBar/ProgressBarProps.svelte +0 -145
- package/dist/ProgressBar/ProgressBarProps.svelte.d.ts +0 -10
- package/dist/Select/SelectProps.svelte +0 -260
- package/dist/Select/SelectProps.svelte.d.ts +0 -10
- package/dist/Slider/SliderProps.svelte +0 -161
- package/dist/Slider/SliderProps.svelte.d.ts +0 -10
- package/dist/Switch/SwitchProps.svelte +0 -144
- package/dist/Switch/SwitchProps.svelte.d.ts +0 -10
- package/dist/Table/TableProps.svelte +0 -286
- package/dist/Table/TableProps.svelte.d.ts +0 -10
- package/dist/TextField/TextFieldProps.svelte +0 -92
- package/dist/TextField/TextFieldProps.svelte.d.ts +0 -10
- package/dist/locales/CircleFlagsEn.svelte +0 -14
- package/dist/locales/CircleFlagsEn.svelte.d.ts +0 -26
- package/dist/locales/CircleFlagsRu.svelte +0 -8
- package/dist/locales/CircleFlagsRu.svelte.d.ts +0 -26
- package/dist/locales/CircleFlagsZh.svelte +0 -8
- package/dist/locales/CircleFlagsZh.svelte.d.ts +0 -26
- package/dist/locales/i18n.d.ts +0 -10
- package/dist/locales/i18n.js +0 -36
- package/dist/locales/translations.d.ts +0 -7
- package/dist/locales/translations.js +0 -450
- /package/dist/{appIcons → libIcons}/ButtonAdd.svelte.d.ts +0 -0
- /package/dist/{appIcons → libIcons}/ButtonDelete.svelte.d.ts +0 -0
- /package/dist/{appIcons → libIcons}/LoaderRotate.svelte.d.ts +0 -0
package/dist/Graph/Graph.svelte
CHANGED
|
@@ -1,270 +1,270 @@
|
|
|
1
|
-
<!-- $lib/ElementsUI/Graph.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
import { onMount } from 'svelte'
|
|
4
|
-
import Select from '../Select/Select.svelte'
|
|
5
|
-
import type { IGraphDataObject, IGraphProps, ISelectOption } from '../types'
|
|
6
|
-
|
|
7
|
-
/* Инициализация пропсов с дефолтными значениями */
|
|
8
|
-
let {
|
|
9
|
-
id = { name: '', value: crypto.randomUUID() },
|
|
10
|
-
wrapperClass = '',
|
|
11
|
-
label = { name: '', class: '' },
|
|
12
|
-
streamingData = { data: [], timestamp: Date.now() },
|
|
13
|
-
isTest = false,
|
|
14
|
-
}: IGraphProps = $props()
|
|
15
|
-
|
|
16
|
-
/* Состояние компонента */
|
|
17
|
-
let graphData = $state<{ id: string; points: { x: number; y: number }[]; color: string; name: string }[]>([])
|
|
18
|
-
let currentValues = $state<number[]>([])
|
|
19
|
-
let container: HTMLDivElement
|
|
20
|
-
let canvas: HTMLCanvasElement
|
|
21
|
-
let ctx: CanvasRenderingContext2D
|
|
22
|
-
let width = $state(600)
|
|
23
|
-
let height = $state(125)
|
|
24
|
-
|
|
25
|
-
/* Константы и настройки */
|
|
26
|
-
const REFRESH_OPTIONS: ISelectOption[] = [
|
|
27
|
-
{ id: 'RefreshOption-AUTO', name: 'AUTO', value: 0, class: '' },
|
|
28
|
-
{ id: 'RefreshOption-10', name: '10', value: 10, class: '' },
|
|
29
|
-
{ id: 'RefreshOption-25', name: '25', value: 25, class: '' },
|
|
30
|
-
{ id: 'RefreshOption-50', name: '50', value: 50, class: '' },
|
|
31
|
-
{ id: 'RefreshOption-100', name: '100', value: 100, class: '' },
|
|
32
|
-
{ id: 'RefreshOption-250', name: '250', value: 250, class: '' },
|
|
33
|
-
{ id: 'RefreshOption-500', name: '500', value: 500, class: '' },
|
|
34
|
-
{ id: 'RefreshOption-1000', name: '1000', value: 1000, class: '' },
|
|
35
|
-
{ id: 'RefreshOption-5000', name: '5000', value: 5000, class: '' },
|
|
36
|
-
]
|
|
37
|
-
// const REFRESH_OPTIONS = [10, 25, 50, 100, 250, 500, 1000, 5000]
|
|
38
|
-
const SCALE_OPTIONS: ISelectOption[] = [
|
|
39
|
-
{ id: 'ScaleOption-50', name: '50', value: 50, class: '' },
|
|
40
|
-
{ id: 'ScaleOption-100', name: '100', value: 100, class: '' },
|
|
41
|
-
{ id: 'ScaleOption-500', name: '500', value: 500, class: '' },
|
|
42
|
-
{ id: 'ScaleOption-1000', name: '1000', value: 1000, class: '' },
|
|
43
|
-
{ id: 'ScaleOption-2000', name: '2000', value: 2000, class: '' },
|
|
44
|
-
]
|
|
45
|
-
// const SCALE_OPTIONS = [50, 100, 500, 1000, 2000]
|
|
46
|
-
let selectedRefreshRate = $state(0)
|
|
47
|
-
let selectedScale = $state(100)
|
|
48
|
-
const maxDataPoints = $derived(selectedRefreshRate == 0 ? 20 : 100)
|
|
49
|
-
const defaultColors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899']
|
|
50
|
-
|
|
51
|
-
let previousDataTimestamp: number = $state(0)
|
|
52
|
-
|
|
53
|
-
/* Инициализация данных графиков */
|
|
54
|
-
const initializeGraphData = () => {
|
|
55
|
-
if (typeof streamingData === 'string') {
|
|
56
|
-
streamingData = JSON.parse(streamingData)
|
|
57
|
-
}
|
|
58
|
-
if (!streamingData.data || streamingData.data.length === 0) {
|
|
59
|
-
graphData = []
|
|
60
|
-
currentValues = []
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
const newGraphData = (streamingData.data as IGraphDataObject[]).slice(0, 6).map((d, i) => {
|
|
64
|
-
const existingData = graphData.find((g) => g.name === d.name) || graphData[i]
|
|
65
|
-
return {
|
|
66
|
-
id: existingData?.id || crypto.randomUUID(),
|
|
67
|
-
points: existingData?.points || [],
|
|
68
|
-
color: d.color || defaultColors[i % defaultColors.length],
|
|
69
|
-
name: d.name || `Value ${i}`,
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
if (JSON.stringify(graphData) !== JSON.stringify(newGraphData)) {
|
|
73
|
-
graphData = newGraphData
|
|
74
|
-
currentValues = (streamingData.data as IGraphDataObject[]).map((d) => d.value || 0)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/* Вызываем инициализацию при монтировании и при изменении streamingData */
|
|
79
|
-
onMount(initializeGraphData)
|
|
80
|
-
$effect(() => {
|
|
81
|
-
initializeGraphData()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
/* Обработка входящих данных */
|
|
85
|
-
let intervalId: ReturnType<typeof setInterval>
|
|
86
|
-
$effect(() => {
|
|
87
|
-
clearInterval(intervalId)
|
|
88
|
-
if (selectedRefreshRate > 0 && streamingData.data && streamingData.data.length > 0) {
|
|
89
|
-
intervalId = setInterval(() => {
|
|
90
|
-
let newValues
|
|
91
|
-
if (isTest) newValues = graphData.map(() => Math.random() * 100 - 50)
|
|
92
|
-
else newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
|
|
93
|
-
const now = Date.now()
|
|
94
|
-
newValues.forEach((value, i) => {
|
|
95
|
-
if (!graphData[i]) return
|
|
96
|
-
graphData[i].points.push({ x: now, y: value })
|
|
97
|
-
if (graphData[i].points.length > maxDataPoints) {
|
|
98
|
-
graphData[i].points.shift()
|
|
99
|
-
}
|
|
100
|
-
currentValues[i] = value
|
|
101
|
-
})
|
|
102
|
-
drawAllGraphs()
|
|
103
|
-
}, selectedRefreshRate)
|
|
104
|
-
} else if (selectedRefreshRate == 0 && streamingData.data && streamingData.data.length > 0 && !isTest) {
|
|
105
|
-
intervalId = setInterval(() => {
|
|
106
|
-
if (previousDataTimestamp < (streamingData.timestamp ?? Date.now())) {
|
|
107
|
-
let newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
|
|
108
|
-
|
|
109
|
-
newValues.forEach((value, i) => {
|
|
110
|
-
if (!graphData[i]) return
|
|
111
|
-
graphData[i].points.push({ x: streamingData.timestamp ?? Date.now(), y: value })
|
|
112
|
-
if (graphData[i].points.length > maxDataPoints) {
|
|
113
|
-
graphData[i].points.shift()
|
|
114
|
-
}
|
|
115
|
-
currentValues[i] = value
|
|
116
|
-
})
|
|
117
|
-
drawAllGraphs()
|
|
118
|
-
previousDataTimestamp = streamingData.timestamp ?? Date.now()
|
|
119
|
-
}
|
|
120
|
-
}, 10)
|
|
121
|
-
}
|
|
122
|
-
console.log(streamingData.data)
|
|
123
|
-
return () => clearInterval(intervalId)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
let resizeObserver: ResizeObserver
|
|
127
|
-
$effect(() => {
|
|
128
|
-
if (!container || !canvas) return
|
|
129
|
-
const dpr = window.devicePixelRatio || 1
|
|
130
|
-
resizeObserver = new ResizeObserver(() => {
|
|
131
|
-
const rect = container.getBoundingClientRect()
|
|
132
|
-
width = rect.width
|
|
133
|
-
height = rect.height
|
|
134
|
-
canvas.width = width * dpr
|
|
135
|
-
canvas.height = height * dpr
|
|
136
|
-
canvas.style.width = `${width}px`
|
|
137
|
-
canvas.style.height = `${height}px`
|
|
138
|
-
ctx = canvas.getContext('2d')!
|
|
139
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
140
|
-
drawAllGraphs()
|
|
141
|
-
})
|
|
142
|
-
resizeObserver.observe(container)
|
|
143
|
-
return () => resizeObserver.disconnect()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const drawAllGraphs = () => {
|
|
147
|
-
if (!ctx) return
|
|
148
|
-
ctx.clearRect(0, 0, width, height)
|
|
149
|
-
|
|
150
|
-
const padding = {
|
|
151
|
-
top: 10,
|
|
152
|
-
right: 10,
|
|
153
|
-
bottom: 20,
|
|
154
|
-
left: 35,
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const graphWidth = width - padding.left - padding.right
|
|
158
|
-
const graphHeight = height - padding.top - padding.bottom
|
|
159
|
-
|
|
160
|
-
const allPoints = graphData.flatMap((g) => g.points)
|
|
161
|
-
const minX = Math.min(...allPoints.map((p) => p.x))
|
|
162
|
-
const maxX = Math.max(...allPoints.map((p) => p.x))
|
|
163
|
-
const timeSpan = maxX - minX || 1
|
|
164
|
-
|
|
165
|
-
// Функции преобразования координат с учетом отступов
|
|
166
|
-
const getX = (x: number) => padding.left + ((x - minX) / timeSpan) * graphWidth
|
|
167
|
-
const getY = (y: number) => padding.top + graphHeight - (((y / selectedScale) * graphHeight) / 2 + graphHeight / 2)
|
|
168
|
-
|
|
169
|
-
/* Сетка X */
|
|
170
|
-
ctx.strokeStyle = '#777'
|
|
171
|
-
ctx.fillStyle = '#777'
|
|
172
|
-
ctx.lineWidth = 0.5
|
|
173
|
-
ctx.font = '10px monospace'
|
|
174
|
-
ctx.textAlign = 'center'
|
|
175
|
-
|
|
176
|
-
const now = Date.now()
|
|
177
|
-
for (let i = 0; i <= 10; i++) {
|
|
178
|
-
const t = minX + (i / 10) * timeSpan
|
|
179
|
-
const x = getX(t)
|
|
180
|
-
const secondsAgo = ((t - now) / 1000).toFixed(0)
|
|
181
|
-
ctx.beginPath()
|
|
182
|
-
ctx.moveTo(x, padding.top)
|
|
183
|
-
ctx.lineTo(x, height - padding.bottom)
|
|
184
|
-
ctx.stroke()
|
|
185
|
-
ctx.textBaseline = 'top'
|
|
186
|
-
ctx.fillText(`${secondsAgo}s`, x, height - padding.bottom + 2)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/* Сетка Y */
|
|
190
|
-
ctx.textAlign = 'right'
|
|
191
|
-
ctx.textBaseline = 'middle'
|
|
192
|
-
const ySteps = 8
|
|
193
|
-
for (let i = 0; i <= ySteps; i++) {
|
|
194
|
-
const yVal = selectedScale - (i * 2 * selectedScale) / ySteps
|
|
195
|
-
const y = getY(yVal)
|
|
196
|
-
ctx.beginPath()
|
|
197
|
-
ctx.moveTo(padding.left, y)
|
|
198
|
-
ctx.lineTo(width - padding.right, y)
|
|
199
|
-
ctx.stroke()
|
|
200
|
-
ctx.fillText(`${yVal.toFixed(0)}`, padding.left - 5, y)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/* Отрисовка графиков */
|
|
204
|
-
ctx.lineWidth = 2
|
|
205
|
-
graphData.forEach(({ points, color }) => {
|
|
206
|
-
if (points.length < 2) return
|
|
207
|
-
ctx.strokeStyle = color
|
|
208
|
-
ctx.beginPath()
|
|
209
|
-
ctx.moveTo(getX(points[0].x), getY(points[0].y))
|
|
210
|
-
for (let i = 1; i < points.length - 1; i++) {
|
|
211
|
-
const p1 = points[i]
|
|
212
|
-
const p2 = points[i + 1]
|
|
213
|
-
const xc2 = (p1.x + p2.x) / 2
|
|
214
|
-
const yc2 = (p1.y + p2.y) / 2
|
|
215
|
-
ctx.quadraticCurveTo(getX(p1.x), getY(p1.y), getX(xc2), getY(yc2))
|
|
216
|
-
}
|
|
217
|
-
const last = points[points.length - 1]
|
|
218
|
-
ctx.lineTo(getX(last.x), getY(last.y))
|
|
219
|
-
ctx.stroke()
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
</script>
|
|
223
|
-
|
|
224
|
-
<div id={id.value} class={`relative flex w-full flex-col items-center justify-center ${wrapperClass}`}>
|
|
225
|
-
{#if label.name}
|
|
226
|
-
<h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
|
|
227
|
-
{/if}
|
|
228
|
-
|
|
229
|
-
<div class="flex w-full flex-row gap-4">
|
|
230
|
-
<!-- График -->
|
|
231
|
-
<div bind:this={container} class="h-64 flex-grow overflow-hidden rounded-md border border-gray-200">
|
|
232
|
-
<canvas class="h-full w-full bg-[var(--back-color)]" bind:this={canvas}></canvas>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<!-- Панель настроек -->
|
|
236
|
-
<div class="flex w-48 flex-col gap-2">
|
|
237
|
-
<!-- Развертка по горизонтали -->
|
|
238
|
-
<Select
|
|
239
|
-
label={{ name: 'Refresh rate', class: '' }}
|
|
240
|
-
options={REFRESH_OPTIONS}
|
|
241
|
-
value={REFRESH_OPTIONS.find((o) => o.value == selectedRefreshRate)}
|
|
242
|
-
onUpdate={(value) => (selectedRefreshRate = value.value as number)}
|
|
243
|
-
/>
|
|
244
|
-
|
|
245
|
-
<!-- Масштаб по вертикали -->
|
|
246
|
-
<Select
|
|
247
|
-
label={{ name: 'Scale' }}
|
|
248
|
-
options={SCALE_OPTIONS}
|
|
249
|
-
value={REFRESH_OPTIONS.find((o) => o.value == selectedScale)}
|
|
250
|
-
onUpdate={(value) => (selectedScale = value.value as number)}
|
|
251
|
-
/>
|
|
252
|
-
|
|
253
|
-
<!-- Переменные и их значение -->
|
|
254
|
-
<div>
|
|
255
|
-
<h5 class="px-4">Values</h5>
|
|
256
|
-
<table class="w-full font-mono text-sm">
|
|
257
|
-
<tbody>
|
|
258
|
-
{#each graphData as data, i (i)}
|
|
259
|
-
<tr>
|
|
260
|
-
<td><div class="mr-2 h-4 w-4 rounded-full" style="background-color: {data.color}"></div></td>
|
|
261
|
-
<td class="w-24 truncate text-left font-semibold">{(streamingData.data as IGraphDataObject[])?.[i]?.name}</td>
|
|
262
|
-
<td class="w-16 text-right">{currentValues[i].toFixed(2)}</td>
|
|
263
|
-
</tr>
|
|
264
|
-
{/each}
|
|
265
|
-
</tbody>
|
|
266
|
-
</table>
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
1
|
+
<!-- $lib/ElementsUI/Graph.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { onMount } from 'svelte'
|
|
4
|
+
import Select from '../Select/Select.svelte'
|
|
5
|
+
import type { IGraphDataObject, IGraphProps, ISelectOption } from '../types'
|
|
6
|
+
|
|
7
|
+
/* Инициализация пропсов с дефолтными значениями */
|
|
8
|
+
let {
|
|
9
|
+
id = { name: '', value: crypto.randomUUID() },
|
|
10
|
+
wrapperClass = '',
|
|
11
|
+
label = { name: '', class: '' },
|
|
12
|
+
streamingData = { data: [], timestamp: Date.now() },
|
|
13
|
+
isTest = false,
|
|
14
|
+
}: IGraphProps = $props()
|
|
15
|
+
|
|
16
|
+
/* Состояние компонента */
|
|
17
|
+
let graphData = $state<{ id: string; points: { x: number; y: number }[]; color: string; name: string }[]>([])
|
|
18
|
+
let currentValues = $state<number[]>([])
|
|
19
|
+
let container: HTMLDivElement
|
|
20
|
+
let canvas: HTMLCanvasElement
|
|
21
|
+
let ctx: CanvasRenderingContext2D
|
|
22
|
+
let width = $state(600)
|
|
23
|
+
let height = $state(125)
|
|
24
|
+
|
|
25
|
+
/* Константы и настройки */
|
|
26
|
+
const REFRESH_OPTIONS: ISelectOption[] = [
|
|
27
|
+
{ id: 'RefreshOption-AUTO', name: 'AUTO', value: 0, class: '' },
|
|
28
|
+
{ id: 'RefreshOption-10', name: '10', value: 10, class: '' },
|
|
29
|
+
{ id: 'RefreshOption-25', name: '25', value: 25, class: '' },
|
|
30
|
+
{ id: 'RefreshOption-50', name: '50', value: 50, class: '' },
|
|
31
|
+
{ id: 'RefreshOption-100', name: '100', value: 100, class: '' },
|
|
32
|
+
{ id: 'RefreshOption-250', name: '250', value: 250, class: '' },
|
|
33
|
+
{ id: 'RefreshOption-500', name: '500', value: 500, class: '' },
|
|
34
|
+
{ id: 'RefreshOption-1000', name: '1000', value: 1000, class: '' },
|
|
35
|
+
{ id: 'RefreshOption-5000', name: '5000', value: 5000, class: '' },
|
|
36
|
+
]
|
|
37
|
+
// const REFRESH_OPTIONS = [10, 25, 50, 100, 250, 500, 1000, 5000]
|
|
38
|
+
const SCALE_OPTIONS: ISelectOption[] = [
|
|
39
|
+
{ id: 'ScaleOption-50', name: '50', value: 50, class: '' },
|
|
40
|
+
{ id: 'ScaleOption-100', name: '100', value: 100, class: '' },
|
|
41
|
+
{ id: 'ScaleOption-500', name: '500', value: 500, class: '' },
|
|
42
|
+
{ id: 'ScaleOption-1000', name: '1000', value: 1000, class: '' },
|
|
43
|
+
{ id: 'ScaleOption-2000', name: '2000', value: 2000, class: '' },
|
|
44
|
+
]
|
|
45
|
+
// const SCALE_OPTIONS = [50, 100, 500, 1000, 2000]
|
|
46
|
+
let selectedRefreshRate = $state(0)
|
|
47
|
+
let selectedScale = $state(100)
|
|
48
|
+
const maxDataPoints = $derived(selectedRefreshRate == 0 ? 20 : 100)
|
|
49
|
+
const defaultColors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899']
|
|
50
|
+
|
|
51
|
+
let previousDataTimestamp: number = $state(0)
|
|
52
|
+
|
|
53
|
+
/* Инициализация данных графиков */
|
|
54
|
+
const initializeGraphData = () => {
|
|
55
|
+
if (typeof streamingData === 'string') {
|
|
56
|
+
streamingData = JSON.parse(streamingData)
|
|
57
|
+
}
|
|
58
|
+
if (!streamingData.data || streamingData.data.length === 0) {
|
|
59
|
+
graphData = []
|
|
60
|
+
currentValues = []
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
const newGraphData = (streamingData.data as IGraphDataObject[]).slice(0, 6).map((d, i) => {
|
|
64
|
+
const existingData = graphData.find((g) => g.name === d.name) || graphData[i]
|
|
65
|
+
return {
|
|
66
|
+
id: existingData?.id || crypto.randomUUID(),
|
|
67
|
+
points: existingData?.points || [],
|
|
68
|
+
color: d.color || defaultColors[i % defaultColors.length],
|
|
69
|
+
name: d.name || `Value ${i}`,
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
if (JSON.stringify(graphData) !== JSON.stringify(newGraphData)) {
|
|
73
|
+
graphData = newGraphData
|
|
74
|
+
currentValues = (streamingData.data as IGraphDataObject[]).map((d) => d.value || 0)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Вызываем инициализацию при монтировании и при изменении streamingData */
|
|
79
|
+
onMount(initializeGraphData)
|
|
80
|
+
$effect(() => {
|
|
81
|
+
initializeGraphData()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
/* Обработка входящих данных */
|
|
85
|
+
let intervalId: ReturnType<typeof setInterval>
|
|
86
|
+
$effect(() => {
|
|
87
|
+
clearInterval(intervalId)
|
|
88
|
+
if (selectedRefreshRate > 0 && streamingData.data && streamingData.data.length > 0) {
|
|
89
|
+
intervalId = setInterval(() => {
|
|
90
|
+
let newValues
|
|
91
|
+
if (isTest) newValues = graphData.map(() => Math.random() * 100 - 50)
|
|
92
|
+
else newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
|
|
93
|
+
const now = Date.now()
|
|
94
|
+
newValues.forEach((value, i) => {
|
|
95
|
+
if (!graphData[i]) return
|
|
96
|
+
graphData[i].points.push({ x: now, y: value })
|
|
97
|
+
if (graphData[i].points.length > maxDataPoints) {
|
|
98
|
+
graphData[i].points.shift()
|
|
99
|
+
}
|
|
100
|
+
currentValues[i] = value
|
|
101
|
+
})
|
|
102
|
+
drawAllGraphs()
|
|
103
|
+
}, selectedRefreshRate)
|
|
104
|
+
} else if (selectedRefreshRate == 0 && streamingData.data && streamingData.data.length > 0 && !isTest) {
|
|
105
|
+
intervalId = setInterval(() => {
|
|
106
|
+
if (previousDataTimestamp < (streamingData.timestamp ?? Date.now())) {
|
|
107
|
+
let newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
|
|
108
|
+
|
|
109
|
+
newValues.forEach((value, i) => {
|
|
110
|
+
if (!graphData[i]) return
|
|
111
|
+
graphData[i].points.push({ x: streamingData.timestamp ?? Date.now(), y: value })
|
|
112
|
+
if (graphData[i].points.length > maxDataPoints) {
|
|
113
|
+
graphData[i].points.shift()
|
|
114
|
+
}
|
|
115
|
+
currentValues[i] = value
|
|
116
|
+
})
|
|
117
|
+
drawAllGraphs()
|
|
118
|
+
previousDataTimestamp = streamingData.timestamp ?? Date.now()
|
|
119
|
+
}
|
|
120
|
+
}, 10)
|
|
121
|
+
}
|
|
122
|
+
console.log(streamingData.data)
|
|
123
|
+
return () => clearInterval(intervalId)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
let resizeObserver: ResizeObserver
|
|
127
|
+
$effect(() => {
|
|
128
|
+
if (!container || !canvas) return
|
|
129
|
+
const dpr = window.devicePixelRatio || 1
|
|
130
|
+
resizeObserver = new ResizeObserver(() => {
|
|
131
|
+
const rect = container.getBoundingClientRect()
|
|
132
|
+
width = rect.width
|
|
133
|
+
height = rect.height
|
|
134
|
+
canvas.width = width * dpr
|
|
135
|
+
canvas.height = height * dpr
|
|
136
|
+
canvas.style.width = `${width}px`
|
|
137
|
+
canvas.style.height = `${height}px`
|
|
138
|
+
ctx = canvas.getContext('2d')!
|
|
139
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
140
|
+
drawAllGraphs()
|
|
141
|
+
})
|
|
142
|
+
resizeObserver.observe(container)
|
|
143
|
+
return () => resizeObserver.disconnect()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const drawAllGraphs = () => {
|
|
147
|
+
if (!ctx) return
|
|
148
|
+
ctx.clearRect(0, 0, width, height)
|
|
149
|
+
|
|
150
|
+
const padding = {
|
|
151
|
+
top: 10,
|
|
152
|
+
right: 10,
|
|
153
|
+
bottom: 20,
|
|
154
|
+
left: 35,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const graphWidth = width - padding.left - padding.right
|
|
158
|
+
const graphHeight = height - padding.top - padding.bottom
|
|
159
|
+
|
|
160
|
+
const allPoints = graphData.flatMap((g) => g.points)
|
|
161
|
+
const minX = Math.min(...allPoints.map((p) => p.x))
|
|
162
|
+
const maxX = Math.max(...allPoints.map((p) => p.x))
|
|
163
|
+
const timeSpan = maxX - minX || 1
|
|
164
|
+
|
|
165
|
+
// Функции преобразования координат с учетом отступов
|
|
166
|
+
const getX = (x: number) => padding.left + ((x - minX) / timeSpan) * graphWidth
|
|
167
|
+
const getY = (y: number) => padding.top + graphHeight - (((y / selectedScale) * graphHeight) / 2 + graphHeight / 2)
|
|
168
|
+
|
|
169
|
+
/* Сетка X */
|
|
170
|
+
ctx.strokeStyle = '#777'
|
|
171
|
+
ctx.fillStyle = '#777'
|
|
172
|
+
ctx.lineWidth = 0.5
|
|
173
|
+
ctx.font = '10px monospace'
|
|
174
|
+
ctx.textAlign = 'center'
|
|
175
|
+
|
|
176
|
+
const now = Date.now()
|
|
177
|
+
for (let i = 0; i <= 10; i++) {
|
|
178
|
+
const t = minX + (i / 10) * timeSpan
|
|
179
|
+
const x = getX(t)
|
|
180
|
+
const secondsAgo = ((t - now) / 1000).toFixed(0)
|
|
181
|
+
ctx.beginPath()
|
|
182
|
+
ctx.moveTo(x, padding.top)
|
|
183
|
+
ctx.lineTo(x, height - padding.bottom)
|
|
184
|
+
ctx.stroke()
|
|
185
|
+
ctx.textBaseline = 'top'
|
|
186
|
+
ctx.fillText(`${secondsAgo}s`, x, height - padding.bottom + 2)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Сетка Y */
|
|
190
|
+
ctx.textAlign = 'right'
|
|
191
|
+
ctx.textBaseline = 'middle'
|
|
192
|
+
const ySteps = 8
|
|
193
|
+
for (let i = 0; i <= ySteps; i++) {
|
|
194
|
+
const yVal = selectedScale - (i * 2 * selectedScale) / ySteps
|
|
195
|
+
const y = getY(yVal)
|
|
196
|
+
ctx.beginPath()
|
|
197
|
+
ctx.moveTo(padding.left, y)
|
|
198
|
+
ctx.lineTo(width - padding.right, y)
|
|
199
|
+
ctx.stroke()
|
|
200
|
+
ctx.fillText(`${yVal.toFixed(0)}`, padding.left - 5, y)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Отрисовка графиков */
|
|
204
|
+
ctx.lineWidth = 2
|
|
205
|
+
graphData.forEach(({ points, color }) => {
|
|
206
|
+
if (points.length < 2) return
|
|
207
|
+
ctx.strokeStyle = color
|
|
208
|
+
ctx.beginPath()
|
|
209
|
+
ctx.moveTo(getX(points[0].x), getY(points[0].y))
|
|
210
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
211
|
+
const p1 = points[i]
|
|
212
|
+
const p2 = points[i + 1]
|
|
213
|
+
const xc2 = (p1.x + p2.x) / 2
|
|
214
|
+
const yc2 = (p1.y + p2.y) / 2
|
|
215
|
+
ctx.quadraticCurveTo(getX(p1.x), getY(p1.y), getX(xc2), getY(yc2))
|
|
216
|
+
}
|
|
217
|
+
const last = points[points.length - 1]
|
|
218
|
+
ctx.lineTo(getX(last.x), getY(last.y))
|
|
219
|
+
ctx.stroke()
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<div id={id.value} class={`relative flex w-full flex-col items-center justify-center ${wrapperClass}`}>
|
|
225
|
+
{#if label.name}
|
|
226
|
+
<h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
|
|
227
|
+
{/if}
|
|
228
|
+
|
|
229
|
+
<div class="flex w-full flex-row gap-4">
|
|
230
|
+
<!-- График -->
|
|
231
|
+
<div bind:this={container} class="h-64 flex-grow overflow-hidden rounded-md border border-gray-200">
|
|
232
|
+
<canvas class="h-full w-full bg-[var(--back-color)]" bind:this={canvas}></canvas>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<!-- Панель настроек -->
|
|
236
|
+
<div class="flex w-48 flex-col gap-2">
|
|
237
|
+
<!-- Развертка по горизонтали -->
|
|
238
|
+
<Select
|
|
239
|
+
label={{ name: 'Refresh rate', class: '' }}
|
|
240
|
+
options={REFRESH_OPTIONS}
|
|
241
|
+
value={REFRESH_OPTIONS.find((o) => o.value == selectedRefreshRate)}
|
|
242
|
+
onUpdate={(value) => (selectedRefreshRate = value.value as number)}
|
|
243
|
+
/>
|
|
244
|
+
|
|
245
|
+
<!-- Масштаб по вертикали -->
|
|
246
|
+
<Select
|
|
247
|
+
label={{ name: 'Scale' }}
|
|
248
|
+
options={SCALE_OPTIONS}
|
|
249
|
+
value={REFRESH_OPTIONS.find((o) => o.value == selectedScale)}
|
|
250
|
+
onUpdate={(value) => (selectedScale = value.value as number)}
|
|
251
|
+
/>
|
|
252
|
+
|
|
253
|
+
<!-- Переменные и их значение -->
|
|
254
|
+
<div>
|
|
255
|
+
<h5 class="px-4">Values</h5>
|
|
256
|
+
<table class="w-full font-mono text-sm">
|
|
257
|
+
<tbody>
|
|
258
|
+
{#each graphData as data, i (i)}
|
|
259
|
+
<tr>
|
|
260
|
+
<td><div class="mr-2 h-4 w-4 rounded-full" style="background-color: {data.color}"></div></td>
|
|
261
|
+
<td class="w-24 truncate text-left font-semibold">{(streamingData.data as IGraphDataObject[])?.[i]?.name}</td>
|
|
262
|
+
<td class="w-16 text-right">{currentValues[i].toFixed(2)}</td>
|
|
263
|
+
</tr>
|
|
264
|
+
{/each}
|
|
265
|
+
</tbody>
|
|
266
|
+
</table>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|