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.
Files changed (61) hide show
  1. package/LICENSE +3 -3
  2. package/README.md +1 -0
  3. package/dist/Accordion/Accordion.svelte +53 -53
  4. package/dist/Button/Button.svelte +111 -144
  5. package/dist/Button/Button.svelte.d.ts +1 -34
  6. package/dist/ColorPicker/ColorPicker.svelte +205 -207
  7. package/dist/FileAttach/FileAttach.svelte +103 -103
  8. package/dist/Graph/Graph.svelte +270 -270
  9. package/dist/Input/Input.svelte +240 -239
  10. package/dist/Loader.svelte +12 -12
  11. package/dist/MessageModal.svelte +54 -54
  12. package/dist/ProgressBar/ProgressBar.svelte +48 -48
  13. package/dist/Select/Select.svelte +189 -191
  14. package/dist/Slider/Slider.svelte +260 -260
  15. package/dist/Switch/Switch.svelte +84 -83
  16. package/dist/Table/Table.svelte +275 -276
  17. package/dist/TextField/TextField.svelte +22 -22
  18. package/dist/index.d.ts +0 -11
  19. package/dist/index.js +0 -11
  20. package/dist/{appIcons → libIcons}/ButtonAdd.svelte +10 -10
  21. package/dist/{appIcons → libIcons}/ButtonDelete.svelte +13 -13
  22. package/dist/{appIcons → libIcons}/LoaderRotate.svelte +9 -9
  23. package/dist/options.d.ts +11 -11
  24. package/dist/options.js +27 -27
  25. package/dist/types.d.ts +1 -1
  26. package/package.json +48 -47
  27. package/dist/Accordion/AccordionProps.svelte +0 -70
  28. package/dist/Accordion/AccordionProps.svelte.d.ts +0 -10
  29. package/dist/Button/ButtonProps.svelte +0 -200
  30. package/dist/Button/ButtonProps.svelte.d.ts +0 -10
  31. package/dist/ColorPicker/ColorPickerProps.svelte +0 -100
  32. package/dist/ColorPicker/ColorPickerProps.svelte.d.ts +0 -10
  33. package/dist/Graph/GraphProps.svelte +0 -56
  34. package/dist/Graph/GraphProps.svelte.d.ts +0 -10
  35. package/dist/Input/InputProps.svelte +0 -221
  36. package/dist/Input/InputProps.svelte.d.ts +0 -10
  37. package/dist/ProgressBar/ProgressBarProps.svelte +0 -145
  38. package/dist/ProgressBar/ProgressBarProps.svelte.d.ts +0 -10
  39. package/dist/Select/SelectProps.svelte +0 -260
  40. package/dist/Select/SelectProps.svelte.d.ts +0 -10
  41. package/dist/Slider/SliderProps.svelte +0 -161
  42. package/dist/Slider/SliderProps.svelte.d.ts +0 -10
  43. package/dist/Switch/SwitchProps.svelte +0 -144
  44. package/dist/Switch/SwitchProps.svelte.d.ts +0 -10
  45. package/dist/Table/TableProps.svelte +0 -286
  46. package/dist/Table/TableProps.svelte.d.ts +0 -10
  47. package/dist/TextField/TextFieldProps.svelte +0 -92
  48. package/dist/TextField/TextFieldProps.svelte.d.ts +0 -10
  49. package/dist/locales/CircleFlagsEn.svelte +0 -14
  50. package/dist/locales/CircleFlagsEn.svelte.d.ts +0 -26
  51. package/dist/locales/CircleFlagsRu.svelte +0 -8
  52. package/dist/locales/CircleFlagsRu.svelte.d.ts +0 -26
  53. package/dist/locales/CircleFlagsZh.svelte +0 -8
  54. package/dist/locales/CircleFlagsZh.svelte.d.ts +0 -26
  55. package/dist/locales/i18n.d.ts +0 -10
  56. package/dist/locales/i18n.js +0 -36
  57. package/dist/locales/translations.d.ts +0 -7
  58. package/dist/locales/translations.js +0 -450
  59. /package/dist/{appIcons → libIcons}/ButtonAdd.svelte.d.ts +0 -0
  60. /package/dist/{appIcons → libIcons}/ButtonDelete.svelte.d.ts +0 -0
  61. /package/dist/{appIcons → libIcons}/LoaderRotate.svelte.d.ts +0 -0
@@ -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>