wenay-react2 1.0.1
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/lib/common/api.d.ts +17 -0
- package/lib/common/api.js +24 -0
- package/lib/common/src/components/Buttons/MiniButton.d.ts +17 -0
- package/lib/common/src/components/Buttons/MiniButton.js +18 -0
- package/lib/common/src/components/Buttons/index.d.ts +1 -0
- package/lib/common/src/components/Buttons/index.js +1 -0
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +1 -0
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.js +31 -0
- package/lib/common/src/components/Dnd/RNDFunc.d.ts +13 -0
- package/lib/common/src/components/Dnd/RNDFunc.js +111 -0
- package/lib/common/src/components/Dnd/RNDFunc3.d.ts +81 -0
- package/lib/common/src/components/Dnd/RNDFunc3.js +380 -0
- package/lib/common/src/components/Dnd/Resizable.d.ts +15 -0
- package/lib/common/src/components/Dnd/Resizable.js +36 -0
- package/lib/common/src/components/Dnd/index.d.ts +4 -0
- package/lib/common/src/components/Dnd/index.js +4 -0
- package/lib/common/src/components/Input.d.ts +28 -0
- package/lib/common/src/components/Input.js +30 -0
- package/lib/common/src/components/Menu/RightMenu.d.ts +22 -0
- package/lib/common/src/components/Menu/RightMenu.js +179 -0
- package/lib/common/src/components/Menu/StickerMenu.d.ts +5 -0
- package/lib/common/src/components/Menu/StickerMenu.js +110 -0
- package/lib/common/src/components/Menu/index.d.ts +2 -0
- package/lib/common/src/components/Menu/index.js +2 -0
- package/lib/common/src/components/Modal/LeftModal.d.ts +58 -0
- package/lib/common/src/components/Modal/LeftModal.js +284 -0
- package/lib/common/src/components/Modal/Modal.d.ts +31 -0
- package/lib/common/src/components/Modal/Modal.js +104 -0
- package/lib/common/src/components/Modal/index.d.ts +2 -0
- package/lib/common/src/components/Modal/index.js +2 -0
- package/lib/common/src/components/MyResizeObserver.d.ts +10 -0
- package/lib/common/src/components/MyResizeObserver.js +94 -0
- package/lib/common/src/components/Other.d.ts +9 -0
- package/lib/common/src/components/Other.js +31 -0
- package/lib/common/src/components/Parameters.d.ts +10 -0
- package/lib/common/src/components/Parameters.js +24 -0
- package/lib/common/src/components/ParametersEngine.d.ts +8 -0
- package/lib/common/src/components/ParametersEngine.js +373 -0
- package/lib/common/src/components/index.d.ts +9 -0
- package/lib/common/src/components/index.js +9 -0
- package/lib/common/src/hooks/index.d.ts +3 -0
- package/lib/common/src/hooks/index.js +3 -0
- package/lib/common/src/hooks/useAddDownAnyKey.d.ts +5 -0
- package/lib/common/src/hooks/useAddDownAnyKey.js +22 -0
- package/lib/common/src/hooks/useDraggable.d.ts +15 -0
- package/lib/common/src/hooks/useDraggable.js +134 -0
- package/lib/common/src/hooks/useOutside.d.ts +40 -0
- package/lib/common/src/hooks/useOutside.js +68 -0
- package/lib/common/src/logs/logs.d.ts +163 -0
- package/lib/common/src/logs/logs.js +249 -0
- package/lib/common/src/logs/logs3.d.ts +63 -0
- package/lib/common/src/logs/logs3.js +245 -0
- package/lib/common/src/logs/miniLogs.d.ts +5 -0
- package/lib/common/src/logs/miniLogs.js +51 -0
- package/lib/common/src/menu/menu.d.ts +72 -0
- package/lib/common/src/menu/menu.js +230 -0
- package/lib/common/src/menu/menuMouse.d.ts +21 -0
- package/lib/common/src/menu/menuMouse.js +32 -0
- package/lib/common/src/menu/menuR.d.ts +17 -0
- package/lib/common/src/menu/menuR.js +116 -0
- package/lib/common/src/myChart/1/myChart.d.ts +40 -0
- package/lib/common/src/myChart/1/myChart.js +306 -0
- package/lib/common/src/myChart/1/myChartTest.d.ts +1 -0
- package/lib/common/src/myChart/1/myChartTest.js +45 -0
- package/lib/common/src/myChart/chartEngine/chartEngineReact.d.ts +164 -0
- package/lib/common/src/myChart/chartEngine/chartEngineReact.js +834 -0
- package/lib/common/src/styles/index.d.ts +1 -0
- package/lib/common/src/styles/index.js +1 -0
- package/lib/common/src/styles/styleGrid.d.ts +20 -0
- package/lib/common/src/styles/styleGrid.js +50 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +42 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.js +97 -0
- package/lib/common/src/utils/arrayPromise.d.ts +5 -0
- package/lib/common/src/utils/arrayPromise.js +16 -0
- package/lib/common/src/utils/cache.d.ts +31 -0
- package/lib/common/src/utils/cache.js +119 -0
- package/lib/common/src/utils/index.d.ts +6 -0
- package/lib/common/src/utils/index.js +6 -0
- package/lib/common/src/utils/inputAutoStep.d.ts +4 -0
- package/lib/common/src/utils/inputAutoStep.js +79 -0
- package/lib/common/src/utils/mapMemory.d.ts +26 -0
- package/lib/common/src/utils/mapMemory.js +72 -0
- package/lib/common/src/utils/pageVisibilityContext.d.ts +5 -0
- package/lib/common/src/utils/pageVisibilityContext.js +18 -0
- package/lib/common/updateBy.d.ts +12 -0
- package/lib/common/updateBy.js +88 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -0
- package/lib/style/menuRight.css +139 -0
- package/lib/style/style.css +334 -0
- package/lib/tsconfig.json +6 -0
- package/package.json +50 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*************************************************************
|
|
3
|
+
* chartEngine.tsx
|
|
4
|
+
* Обновлённый пример, где:
|
|
5
|
+
* - Ширина панелей = 100% от Canvas
|
|
6
|
+
* - Высота панелей задаётся в процентах (heightPct)
|
|
7
|
+
* - Последняя панель всегда "дотягивает" до 100%
|
|
8
|
+
*************************************************************/
|
|
9
|
+
import { useRef, useEffect } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* Фабрика DataSet
|
|
12
|
+
*/
|
|
13
|
+
export function createDataSet(params) {
|
|
14
|
+
const { id, type = 'line', data = [], style = {}, chunkSize = 100 } = params;
|
|
15
|
+
const defaultStyle = {
|
|
16
|
+
strokeColor: '#2299dd',
|
|
17
|
+
fillColor: 'rgba(34,153,221,0.2)',
|
|
18
|
+
barColor: '#66cc66',
|
|
19
|
+
lineWidth: 2,
|
|
20
|
+
gradientFill: true
|
|
21
|
+
};
|
|
22
|
+
const mergedStyle = { ...defaultStyle, ...style };
|
|
23
|
+
let internalData = data.slice();
|
|
24
|
+
let minMaxChunks = [];
|
|
25
|
+
function buildMinMaxChunks() {
|
|
26
|
+
minMaxChunks = [];
|
|
27
|
+
if (internalData.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
for (let i = 0; i < internalData.length; i += chunkSize) {
|
|
30
|
+
const chunkData = internalData.slice(i, i + chunkSize);
|
|
31
|
+
let minY = Infinity;
|
|
32
|
+
let maxY = -Infinity;
|
|
33
|
+
const xStart = chunkData[0].x;
|
|
34
|
+
const xEnd = chunkData[chunkData.length - 1].x;
|
|
35
|
+
for (const dp of chunkData) {
|
|
36
|
+
if (dp.y < minY)
|
|
37
|
+
minY = dp.y;
|
|
38
|
+
if (dp.y > maxY)
|
|
39
|
+
maxY = dp.y;
|
|
40
|
+
}
|
|
41
|
+
minMaxChunks.push({ xStart, xEnd, minY, maxY });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
buildMinMaxChunks();
|
|
45
|
+
function getMinMaxInRange(rangeX1, rangeX2) {
|
|
46
|
+
if (internalData.length === 0) {
|
|
47
|
+
return { minY: 0, maxY: 1 };
|
|
48
|
+
}
|
|
49
|
+
let overallMin = Infinity;
|
|
50
|
+
let overallMax = -Infinity;
|
|
51
|
+
for (const chunk of minMaxChunks) {
|
|
52
|
+
if (chunk.xEnd < rangeX1 || chunk.xStart > rangeX2)
|
|
53
|
+
continue;
|
|
54
|
+
if (chunk.minY < overallMin)
|
|
55
|
+
overallMin = chunk.minY;
|
|
56
|
+
if (chunk.maxY > overallMax)
|
|
57
|
+
overallMax = chunk.maxY;
|
|
58
|
+
}
|
|
59
|
+
if (overallMin === Infinity || overallMax === -Infinity) {
|
|
60
|
+
overallMin = 0;
|
|
61
|
+
overallMax = 1;
|
|
62
|
+
}
|
|
63
|
+
return { minY: overallMin, maxY: overallMax };
|
|
64
|
+
}
|
|
65
|
+
function addData(newPoints) {
|
|
66
|
+
const arr = Array.isArray(newPoints) ? newPoints : [newPoints];
|
|
67
|
+
internalData.push(...arr);
|
|
68
|
+
const remainder = internalData.length % chunkSize;
|
|
69
|
+
if (remainder <= arr.length) {
|
|
70
|
+
buildMinMaxChunks();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const lastChunkIndex = Math.floor((internalData.length - 1) / chunkSize);
|
|
74
|
+
const startIndex = lastChunkIndex * chunkSize;
|
|
75
|
+
const chunkData = internalData.slice(startIndex, startIndex + chunkSize);
|
|
76
|
+
let minY = Infinity;
|
|
77
|
+
let maxY = -Infinity;
|
|
78
|
+
for (const d of chunkData) {
|
|
79
|
+
if (d.y < minY)
|
|
80
|
+
minY = d.y;
|
|
81
|
+
if (d.y > maxY)
|
|
82
|
+
maxY = d.y;
|
|
83
|
+
}
|
|
84
|
+
const xStart = chunkData[0].x;
|
|
85
|
+
const xEnd = chunkData[chunkData.length - 1].x;
|
|
86
|
+
minMaxChunks[lastChunkIndex] = { xStart, xEnd, minY, maxY };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
type,
|
|
92
|
+
data: internalData,
|
|
93
|
+
style: mergedStyle,
|
|
94
|
+
chunkSize,
|
|
95
|
+
minMaxChunks,
|
|
96
|
+
getMinMaxInRange,
|
|
97
|
+
addData
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function createDataModel() {
|
|
101
|
+
const dataSets = [];
|
|
102
|
+
function addDataSet(params) {
|
|
103
|
+
const ds = createDataSet(params);
|
|
104
|
+
dataSets.push(ds);
|
|
105
|
+
return ds;
|
|
106
|
+
}
|
|
107
|
+
function getAllDataSets() {
|
|
108
|
+
return dataSets;
|
|
109
|
+
}
|
|
110
|
+
function getGlobalMinMaxY(x1, x2, filterFn) {
|
|
111
|
+
let globalMin = Infinity;
|
|
112
|
+
let globalMax = -Infinity;
|
|
113
|
+
for (const ds of dataSets) {
|
|
114
|
+
if (filterFn && !filterFn(ds))
|
|
115
|
+
continue;
|
|
116
|
+
const { minY, maxY } = ds.getMinMaxInRange(x1, x2);
|
|
117
|
+
if (minY < globalMin)
|
|
118
|
+
globalMin = minY;
|
|
119
|
+
if (maxY > globalMax)
|
|
120
|
+
globalMax = maxY;
|
|
121
|
+
}
|
|
122
|
+
if (globalMin === Infinity || globalMax === -Infinity) {
|
|
123
|
+
globalMin = 0;
|
|
124
|
+
globalMax = 1;
|
|
125
|
+
}
|
|
126
|
+
return { minY: globalMin, maxY: globalMax };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
addDataSet,
|
|
130
|
+
getAllDataSets,
|
|
131
|
+
getGlobalMinMaxY
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function createPanelManager() {
|
|
135
|
+
const panels = [];
|
|
136
|
+
function addPanel(config) {
|
|
137
|
+
const p = {
|
|
138
|
+
id: config.id,
|
|
139
|
+
left: 0,
|
|
140
|
+
top: 0,
|
|
141
|
+
width: 0,
|
|
142
|
+
height: 0,
|
|
143
|
+
heightPct: config.heightPct ?? 20, // по умолчанию 20% (или любой другой)
|
|
144
|
+
dataSets: config.dataSets,
|
|
145
|
+
verticalRange: { minY: 0, maxY: 1 },
|
|
146
|
+
autoFocusY: config.autoFocusY !== false,
|
|
147
|
+
resizable: config.resizable ?? false
|
|
148
|
+
};
|
|
149
|
+
panels.push(p);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* layoutPanels:
|
|
153
|
+
* - Суммируем heightPct всех панелей (кроме последней).
|
|
154
|
+
* - Последняя панель = 100% - сумма предыдущих (если вдруг сумма < 100).
|
|
155
|
+
* - Переводим проценты в px, рассчитываем top/height.
|
|
156
|
+
*/
|
|
157
|
+
function layoutPanels(containerWidth, containerHeight) {
|
|
158
|
+
// Сумма процентов у всех панелей, кроме последней
|
|
159
|
+
if (panels.length === 0)
|
|
160
|
+
return;
|
|
161
|
+
// Рассчитываем сумму “заданных” процентов (кроме последней)
|
|
162
|
+
let totalAssignedPct = 0;
|
|
163
|
+
for (let i = 0; i < panels.length - 1; i++) {
|
|
164
|
+
totalAssignedPct += panels[i].heightPct;
|
|
165
|
+
}
|
|
166
|
+
if (totalAssignedPct > 100)
|
|
167
|
+
totalAssignedPct = 100; // ограничим
|
|
168
|
+
// Последней панели даём остаток
|
|
169
|
+
const lastPanel = panels[panels.length - 1];
|
|
170
|
+
lastPanel.heightPct = 100 - totalAssignedPct;
|
|
171
|
+
// Теперь вычисляем top/height
|
|
172
|
+
let currentTopPx = 0;
|
|
173
|
+
for (const p of panels) {
|
|
174
|
+
// Преобразуем процент в px
|
|
175
|
+
const hPx = (p.heightPct / 100) * containerHeight;
|
|
176
|
+
p.left = 0;
|
|
177
|
+
p.top = currentTopPx;
|
|
178
|
+
p.width = containerWidth;
|
|
179
|
+
p.height = hPx;
|
|
180
|
+
currentTopPx += hPx;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* resizePanel: меняем процент высоты одной панели при “перетаскивании”.
|
|
185
|
+
* deltaPx — изменение высоты в px, превращаем в проценты.
|
|
186
|
+
*/
|
|
187
|
+
function resizePanel(panelId, deltaPx, containerHeight) {
|
|
188
|
+
const idx = panels.findIndex((p) => p.id === panelId);
|
|
189
|
+
if (idx < 0 || idx >= panels.length)
|
|
190
|
+
return;
|
|
191
|
+
const p = panels[idx];
|
|
192
|
+
// Текущий процент
|
|
193
|
+
const oldPct = p.heightPct;
|
|
194
|
+
// px -> pct
|
|
195
|
+
const deltaPct = (deltaPx / containerHeight) * 100;
|
|
196
|
+
const newPct = p.heightPct + deltaPct;
|
|
197
|
+
// Ограничим чтобы не уходило в отрицательные значения
|
|
198
|
+
if (newPct < 5) { // Минимум 5% (условно)
|
|
199
|
+
p.heightPct = 5;
|
|
200
|
+
}
|
|
201
|
+
else if (newPct > 95) {
|
|
202
|
+
p.heightPct = 95;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
p.heightPct = newPct;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
panels,
|
|
210
|
+
addPanel,
|
|
211
|
+
layoutPanels,
|
|
212
|
+
resizePanel
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function createRenderer() {
|
|
216
|
+
const AXIS_THICKNESS = 40;
|
|
217
|
+
function getNiceTicks(minVal, maxVal, count) {
|
|
218
|
+
const range = maxVal - minVal || 1;
|
|
219
|
+
const roughStep = range / count;
|
|
220
|
+
const mag = Math.pow(10, Math.floor(Math.log10(roughStep)));
|
|
221
|
+
let norm = roughStep / mag;
|
|
222
|
+
let step = 1;
|
|
223
|
+
if (norm < 2)
|
|
224
|
+
step = 1;
|
|
225
|
+
else if (norm < 5)
|
|
226
|
+
step = 2;
|
|
227
|
+
else
|
|
228
|
+
step = 5;
|
|
229
|
+
step *= mag;
|
|
230
|
+
const niceMin = Math.floor(minVal / step) * step;
|
|
231
|
+
const niceMax = Math.ceil(maxVal / step) * step;
|
|
232
|
+
const ticks = [];
|
|
233
|
+
for (let val = niceMin; val <= niceMax; val += step) {
|
|
234
|
+
ticks.push(val);
|
|
235
|
+
}
|
|
236
|
+
return ticks;
|
|
237
|
+
}
|
|
238
|
+
function xToPixX(xVal, transform, panel) {
|
|
239
|
+
return panel.left + (xVal - transform.offsetX) * transform.scaleX;
|
|
240
|
+
}
|
|
241
|
+
function yToPixY(yVal, panel, transform) {
|
|
242
|
+
// autoFocusY => [minY, maxY]
|
|
243
|
+
// иначе offsetY/scaleY
|
|
244
|
+
if (panel.autoFocusY) {
|
|
245
|
+
const { minY, maxY } = panel.verticalRange;
|
|
246
|
+
const range = maxY - minY || 1;
|
|
247
|
+
const ratio = (yVal - minY) / range;
|
|
248
|
+
return panel.top + panel.height - ratio * panel.height;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const offsetY = transform.offsetY ?? 0;
|
|
252
|
+
const scaleY = transform.scaleY ?? 1;
|
|
253
|
+
return panel.top + panel.height - (yVal - offsetY) * scaleY;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function drawAxesAndTicks(ctx, panel, transform, xMin, xMax) {
|
|
257
|
+
ctx.save();
|
|
258
|
+
ctx.strokeStyle = '#666';
|
|
259
|
+
ctx.fillStyle = '#666';
|
|
260
|
+
ctx.lineWidth = 1;
|
|
261
|
+
const yAxisX = panel.left + panel.width - AXIS_THICKNESS / 2;
|
|
262
|
+
ctx.beginPath();
|
|
263
|
+
ctx.moveTo(yAxisX, panel.top);
|
|
264
|
+
ctx.lineTo(yAxisX, panel.top + panel.height);
|
|
265
|
+
ctx.stroke();
|
|
266
|
+
ctx.beginPath();
|
|
267
|
+
ctx.moveTo(panel.left, panel.top + panel.height - 0.5);
|
|
268
|
+
ctx.lineTo(panel.left + panel.width, panel.top + panel.height - 0.5);
|
|
269
|
+
ctx.stroke();
|
|
270
|
+
// X tics
|
|
271
|
+
const xTicks = getNiceTicks(xMin, xMax, 6);
|
|
272
|
+
xTicks.forEach((val) => {
|
|
273
|
+
const xPix = xToPixX(val, transform, panel);
|
|
274
|
+
const baseY = panel.top + panel.height;
|
|
275
|
+
ctx.beginPath();
|
|
276
|
+
ctx.moveTo(xPix, baseY - 5);
|
|
277
|
+
ctx.lineTo(xPix, baseY);
|
|
278
|
+
ctx.stroke();
|
|
279
|
+
ctx.fillText(Math.round(val).toString(), xPix - 5, baseY + 12);
|
|
280
|
+
});
|
|
281
|
+
// Y tics
|
|
282
|
+
let minY, maxY;
|
|
283
|
+
if (panel.autoFocusY) {
|
|
284
|
+
minY = panel.verticalRange.minY;
|
|
285
|
+
maxY = panel.verticalRange.maxY;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const offsetY = transform.offsetY ?? 0;
|
|
289
|
+
const scaleY = transform.scaleY ?? 1;
|
|
290
|
+
minY = offsetY;
|
|
291
|
+
maxY = offsetY + panel.height / scaleY;
|
|
292
|
+
}
|
|
293
|
+
const yTicks = getNiceTicks(minY, maxY, 5);
|
|
294
|
+
yTicks.forEach((val) => {
|
|
295
|
+
const pixY = yToPixY(val, panel, transform);
|
|
296
|
+
ctx.beginPath();
|
|
297
|
+
ctx.moveTo(yAxisX - 5, pixY);
|
|
298
|
+
ctx.lineTo(yAxisX, pixY);
|
|
299
|
+
ctx.stroke();
|
|
300
|
+
ctx.fillText(Math.round(val).toString(), yAxisX + 2, pixY + 4);
|
|
301
|
+
});
|
|
302
|
+
ctx.restore();
|
|
303
|
+
}
|
|
304
|
+
function drawLineChartLOD(ctx, data, panel, transform, style) {
|
|
305
|
+
const strokeColor = style.strokeColor ?? '#2299dd';
|
|
306
|
+
const fillColor = style.fillColor ?? 'rgba(34,153,221,0.2)';
|
|
307
|
+
const gradientFill = style.gradientFill ?? true;
|
|
308
|
+
const lineWidth = style.lineWidth ?? 2;
|
|
309
|
+
const AXIS_THICKNESS = 40;
|
|
310
|
+
const xMinVisible = transform.offsetX;
|
|
311
|
+
const xMaxVisible = transform.offsetX + (panel.width - AXIS_THICKNESS) / transform.scaleX;
|
|
312
|
+
const visible = data.filter((pt) => pt.x >= xMinVisible && pt.x <= xMaxVisible);
|
|
313
|
+
if (visible.length < 2)
|
|
314
|
+
return;
|
|
315
|
+
const result = [];
|
|
316
|
+
let lastPixX = -1;
|
|
317
|
+
for (const pt of visible) {
|
|
318
|
+
const px = Math.round(xToPixX(pt.x, transform, panel));
|
|
319
|
+
if (px !== lastPixX) {
|
|
320
|
+
result.push(pt);
|
|
321
|
+
lastPixX = px;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (result.length < 2)
|
|
325
|
+
return;
|
|
326
|
+
ctx.save();
|
|
327
|
+
ctx.strokeStyle = strokeColor;
|
|
328
|
+
ctx.lineWidth = lineWidth;
|
|
329
|
+
ctx.beginPath();
|
|
330
|
+
let first = true;
|
|
331
|
+
for (const r of result) {
|
|
332
|
+
const px = xToPixX(r.x, transform, panel);
|
|
333
|
+
const py = yToPixY(r.y, panel, transform);
|
|
334
|
+
if (first) {
|
|
335
|
+
ctx.moveTo(px, py);
|
|
336
|
+
first = false;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
ctx.lineTo(px, py);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
ctx.stroke();
|
|
343
|
+
if (gradientFill) {
|
|
344
|
+
const lastPt = result[result.length - 1];
|
|
345
|
+
const pxLast = xToPixX(lastPt.x, transform, panel);
|
|
346
|
+
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
347
|
+
ctx.lineTo(pxLast, pyBase);
|
|
348
|
+
const firstPt = result[0];
|
|
349
|
+
const pxFirst = xToPixX(firstPt.x, transform, panel);
|
|
350
|
+
ctx.lineTo(pxFirst, pyBase);
|
|
351
|
+
ctx.closePath();
|
|
352
|
+
const { minY, maxY } = panel.verticalRange;
|
|
353
|
+
const yPixMin = yToPixY(minY, panel, transform);
|
|
354
|
+
const yPixMax = yToPixY(maxY, panel, transform);
|
|
355
|
+
const grad = ctx.createLinearGradient(0, yPixMin, 0, yPixMax);
|
|
356
|
+
grad.addColorStop(0, fillColor);
|
|
357
|
+
grad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
358
|
+
ctx.fillStyle = grad;
|
|
359
|
+
ctx.fill();
|
|
360
|
+
}
|
|
361
|
+
ctx.restore();
|
|
362
|
+
}
|
|
363
|
+
function drawBarChart(ctx, data, panel, transform, style) {
|
|
364
|
+
const barColor = style.barColor ?? '#66cc66';
|
|
365
|
+
const AXIS_THICKNESS = 40;
|
|
366
|
+
const xMinVisible = transform.offsetX;
|
|
367
|
+
const xMaxVisible = transform.offsetX + (panel.width - AXIS_THICKNESS) / transform.scaleX;
|
|
368
|
+
const visible = data.filter((pt) => pt.x >= xMinVisible && pt.x <= xMaxVisible);
|
|
369
|
+
if (!visible.length)
|
|
370
|
+
return;
|
|
371
|
+
ctx.save();
|
|
372
|
+
ctx.fillStyle = barColor;
|
|
373
|
+
const barWidth = 5;
|
|
374
|
+
for (const pt of visible) {
|
|
375
|
+
const px = xToPixX(pt.x, transform, panel);
|
|
376
|
+
const py = yToPixY(pt.y, panel, transform);
|
|
377
|
+
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
378
|
+
ctx.fillRect(px - barWidth / 2, py, barWidth, pyBase - py);
|
|
379
|
+
}
|
|
380
|
+
ctx.restore();
|
|
381
|
+
}
|
|
382
|
+
function drawCrosshair(ctx, panel, crosshair, transform) {
|
|
383
|
+
ctx.save();
|
|
384
|
+
ctx.strokeStyle = '#888';
|
|
385
|
+
ctx.setLineDash([4, 4]);
|
|
386
|
+
ctx.beginPath();
|
|
387
|
+
ctx.moveTo(crosshair.x, panel.top);
|
|
388
|
+
ctx.lineTo(crosshair.x, panel.top + panel.height);
|
|
389
|
+
ctx.stroke();
|
|
390
|
+
ctx.beginPath();
|
|
391
|
+
ctx.moveTo(panel.left, crosshair.y);
|
|
392
|
+
ctx.lineTo(panel.left + panel.width, crosshair.y);
|
|
393
|
+
ctx.stroke();
|
|
394
|
+
ctx.setLineDash([]);
|
|
395
|
+
ctx.font = '12px sans-serif';
|
|
396
|
+
ctx.fillStyle = '#333';
|
|
397
|
+
const worldX = (crosshair.x - panel.left) / transform.scaleX + transform.offsetX;
|
|
398
|
+
let worldY = 0;
|
|
399
|
+
if (panel.autoFocusY) {
|
|
400
|
+
const { minY, maxY } = panel.verticalRange;
|
|
401
|
+
const range = maxY - minY || 1;
|
|
402
|
+
const ratio = (panel.top + panel.height - crosshair.y) / panel.height;
|
|
403
|
+
worldY = minY + ratio * range;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
const offsetY = transform.offsetY ?? 0;
|
|
407
|
+
const scaleY = transform.scaleY ?? 1;
|
|
408
|
+
worldY = offsetY + (panel.top + panel.height - crosshair.y) / scaleY;
|
|
409
|
+
}
|
|
410
|
+
const labelX = Math.round(worldX).toString();
|
|
411
|
+
ctx.fillText(labelX, crosshair.x - 10, panel.top + panel.height - 8);
|
|
412
|
+
const labelY = Math.round(worldY).toString();
|
|
413
|
+
const rightX = panel.left + panel.width - 38;
|
|
414
|
+
ctx.fillRect(rightX, crosshair.y - 8, 38, 16);
|
|
415
|
+
ctx.fillStyle = '#fff';
|
|
416
|
+
ctx.fillText(labelY, rightX + 3, crosshair.y + 4);
|
|
417
|
+
ctx.restore();
|
|
418
|
+
}
|
|
419
|
+
function drawPanel(ctx, panel, transform, globalTimeRange, crosshair, isYRightAxis) {
|
|
420
|
+
ctx.clearRect(panel.left, panel.top, panel.width, panel.height);
|
|
421
|
+
ctx.save();
|
|
422
|
+
ctx.beginPath();
|
|
423
|
+
ctx.rect(panel.left, panel.top, panel.width, panel.height);
|
|
424
|
+
ctx.clip();
|
|
425
|
+
drawAxesAndTicks(ctx, panel, transform, globalTimeRange.xMin, globalTimeRange.xMax);
|
|
426
|
+
for (const ds of panel.dataSets) {
|
|
427
|
+
if (ds.type === 'line') {
|
|
428
|
+
drawLineChartLOD(ctx, ds.data, panel, transform, ds.style);
|
|
429
|
+
}
|
|
430
|
+
else if (ds.type === 'bar') {
|
|
431
|
+
drawBarChart(ctx, ds.data, panel, transform, ds.style);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (crosshair) {
|
|
435
|
+
if (crosshair.x >= panel.left &&
|
|
436
|
+
crosshair.x <= panel.left + panel.width &&
|
|
437
|
+
crosshair.y >= panel.top &&
|
|
438
|
+
crosshair.y <= panel.top + panel.height) {
|
|
439
|
+
drawCrosshair(ctx, panel, crosshair, transform);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
ctx.restore();
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
drawPanel
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
export function createInteraction(canvas, getTransform, setTransform, getPanels, onTransformChanged, onToggleAutoFocusY, panelManager, getContainerSize) {
|
|
449
|
+
let crosshairPos = null;
|
|
450
|
+
let DragMode;
|
|
451
|
+
(function (DragMode) {
|
|
452
|
+
DragMode[DragMode["None"] = 0] = "None";
|
|
453
|
+
DragMode[DragMode["Pan"] = 1] = "Pan";
|
|
454
|
+
DragMode[DragMode["ScaleY"] = 2] = "ScaleY";
|
|
455
|
+
DragMode[DragMode["ResizePanel"] = 3] = "ResizePanel";
|
|
456
|
+
})(DragMode || (DragMode = {}));
|
|
457
|
+
let dragMode = DragMode.None;
|
|
458
|
+
let isMouseDown = false;
|
|
459
|
+
let lastX = 0;
|
|
460
|
+
let lastY = 0;
|
|
461
|
+
let activePanel = null;
|
|
462
|
+
let attached = false;
|
|
463
|
+
let resizingPanelId = null;
|
|
464
|
+
function onMouseDown(e) {
|
|
465
|
+
isMouseDown = true;
|
|
466
|
+
lastX = e.clientX;
|
|
467
|
+
lastY = e.clientY;
|
|
468
|
+
const rect = canvas.getBoundingClientRect();
|
|
469
|
+
const localX = e.clientX - rect.left;
|
|
470
|
+
const localY = e.clientY - rect.top;
|
|
471
|
+
crosshairPos = { x: localX, y: localY };
|
|
472
|
+
// Проверка границы между панелями (для ресайза)
|
|
473
|
+
const panels = getPanels();
|
|
474
|
+
for (const p of panels) {
|
|
475
|
+
if (!p.resizable)
|
|
476
|
+
continue;
|
|
477
|
+
const bottom = p.top + p.height;
|
|
478
|
+
if (Math.abs(localY - bottom) < 5) {
|
|
479
|
+
dragMode = DragMode.ResizePanel;
|
|
480
|
+
resizingPanelId = p.id;
|
|
481
|
+
onTransformChanged();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Ищем панель
|
|
486
|
+
activePanel = findPanel(localX, localY, panels);
|
|
487
|
+
if (!activePanel) {
|
|
488
|
+
dragMode = DragMode.None;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const axisRightX = activePanel.left + activePanel.width - 40;
|
|
492
|
+
if (localX >= axisRightX) {
|
|
493
|
+
// масштаб по Y, если autoFocusY=false
|
|
494
|
+
dragMode = DragMode.ScaleY;
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
// pan
|
|
498
|
+
dragMode = DragMode.Pan;
|
|
499
|
+
}
|
|
500
|
+
onTransformChanged();
|
|
501
|
+
}
|
|
502
|
+
function onMouseMove(e) {
|
|
503
|
+
const rect = canvas.getBoundingClientRect();
|
|
504
|
+
const localX = e.clientX - rect.left;
|
|
505
|
+
const localY = e.clientY - rect.top;
|
|
506
|
+
crosshairPos = { x: localX, y: localY };
|
|
507
|
+
if (!isMouseDown) {
|
|
508
|
+
onTransformChanged();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const dx = e.clientX - lastX;
|
|
512
|
+
const dy = e.clientY - lastY;
|
|
513
|
+
lastX = e.clientX;
|
|
514
|
+
lastY = e.clientY;
|
|
515
|
+
const t = getTransform();
|
|
516
|
+
// Режим ресайза панели
|
|
517
|
+
if (dragMode === DragMode.ResizePanel && resizingPanelId) {
|
|
518
|
+
// deltaPx = dy
|
|
519
|
+
const { height: cH } = getContainerSize();
|
|
520
|
+
panelManager.resizePanel(resizingPanelId, dy, cH);
|
|
521
|
+
onTransformChanged();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (!activePanel)
|
|
525
|
+
return;
|
|
526
|
+
if (dragMode === DragMode.Pan) {
|
|
527
|
+
const newOffsetX = t.offsetX - dx / t.scaleX;
|
|
528
|
+
setTransform({ ...t, offsetX: newOffsetX });
|
|
529
|
+
}
|
|
530
|
+
else if (dragMode === DragMode.ScaleY) {
|
|
531
|
+
if (!activePanel.autoFocusY) {
|
|
532
|
+
const oldScaleY = t.scaleY ?? 1;
|
|
533
|
+
const factor = (dy < 0) ? 1.02 : 0.98;
|
|
534
|
+
const newScaleY = oldScaleY * factor;
|
|
535
|
+
const offsetY = t.offsetY ?? 0;
|
|
536
|
+
const worldY = offsetY + (activePanel.top + activePanel.height - localY) / oldScaleY;
|
|
537
|
+
const newOffsetY = worldY - (activePanel.top + activePanel.height - localY) / newScaleY;
|
|
538
|
+
setTransform({ ...t, scaleY: newScaleY, offsetY: newOffsetY });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
onTransformChanged();
|
|
542
|
+
}
|
|
543
|
+
function onMouseUp(e) {
|
|
544
|
+
isMouseDown = false;
|
|
545
|
+
dragMode = DragMode.None;
|
|
546
|
+
activePanel = null;
|
|
547
|
+
resizingPanelId = null;
|
|
548
|
+
}
|
|
549
|
+
function onGlobalMouseUp(e) {
|
|
550
|
+
if (isMouseDown) {
|
|
551
|
+
isMouseDown = false;
|
|
552
|
+
dragMode = DragMode.None;
|
|
553
|
+
activePanel = null;
|
|
554
|
+
resizingPanelId = null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function onWheel(e) {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
const t = getTransform();
|
|
560
|
+
const rect = canvas.getBoundingClientRect();
|
|
561
|
+
const localX = e.clientX - rect.left;
|
|
562
|
+
const worldX = t.offsetX + (localX - (activePanel?.left ?? 0)) / t.scaleX;
|
|
563
|
+
const delta = e.deltaY < 0 ? 1.1 : 0.9;
|
|
564
|
+
let newScaleX = t.scaleX * delta;
|
|
565
|
+
newScaleX = Math.max(newScaleX, 0.0001);
|
|
566
|
+
const newOffsetX = worldX - (localX - (activePanel?.left ?? 0)) / newScaleX;
|
|
567
|
+
setTransform({ ...t, offsetX: newOffsetX, scaleX: newScaleX });
|
|
568
|
+
onTransformChanged();
|
|
569
|
+
}
|
|
570
|
+
function onDblClick(e) {
|
|
571
|
+
const rect = canvas.getBoundingClientRect();
|
|
572
|
+
const localX = e.clientX - rect.left;
|
|
573
|
+
const localY = e.clientY - rect.top;
|
|
574
|
+
const p = findPanel(localX, localY, getPanels());
|
|
575
|
+
if (!p)
|
|
576
|
+
return;
|
|
577
|
+
const axisRightX = p.left + p.width - 40;
|
|
578
|
+
if (localX >= axisRightX) {
|
|
579
|
+
onToggleAutoFocusY(p);
|
|
580
|
+
onTransformChanged();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function findPanel(x, y, panels) {
|
|
584
|
+
for (const p of panels) {
|
|
585
|
+
if (x >= p.left && x <= p.left + p.width && y >= p.top && y <= p.top + p.height) {
|
|
586
|
+
return p;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
function initEvents(canvasEl) {
|
|
592
|
+
if (attached)
|
|
593
|
+
return;
|
|
594
|
+
attached = true;
|
|
595
|
+
canvasEl.addEventListener('mousedown', onMouseDown);
|
|
596
|
+
canvasEl.addEventListener('mousemove', onMouseMove);
|
|
597
|
+
canvasEl.addEventListener('wheel', onWheel, { passive: false });
|
|
598
|
+
canvasEl.addEventListener('dblclick', onDblClick);
|
|
599
|
+
document.addEventListener('mouseup', onGlobalMouseUp);
|
|
600
|
+
}
|
|
601
|
+
function getCrosshairPos() {
|
|
602
|
+
return crosshairPos;
|
|
603
|
+
}
|
|
604
|
+
function destroy() {
|
|
605
|
+
if (!attached)
|
|
606
|
+
return;
|
|
607
|
+
attached = false;
|
|
608
|
+
canvas.removeEventListener('mousedown', onMouseDown);
|
|
609
|
+
canvas.removeEventListener('mousemove', onMouseMove);
|
|
610
|
+
canvas.removeEventListener('wheel', onWheel);
|
|
611
|
+
canvas.removeEventListener('dblclick', onDblClick);
|
|
612
|
+
document.removeEventListener('mouseup', onGlobalMouseUp);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
initEvents,
|
|
616
|
+
getCrosshairPos,
|
|
617
|
+
destroy
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Фабричная функция движка
|
|
622
|
+
*/
|
|
623
|
+
export function createChartEngine(canvas) {
|
|
624
|
+
const ctx = canvas.getContext('2d');
|
|
625
|
+
const dataModel = createDataModel();
|
|
626
|
+
const panelManager = createPanelManager();
|
|
627
|
+
const renderer = createRenderer();
|
|
628
|
+
let transform = {
|
|
629
|
+
offsetX: 0,
|
|
630
|
+
scaleX: 1,
|
|
631
|
+
offsetY: 0,
|
|
632
|
+
scaleY: 1
|
|
633
|
+
};
|
|
634
|
+
let destroyed = false;
|
|
635
|
+
let animationFrameId = 0;
|
|
636
|
+
let containerEl = null;
|
|
637
|
+
let resizeObserver = null;
|
|
638
|
+
let containerWidth = 0;
|
|
639
|
+
let containerHeight = 0;
|
|
640
|
+
const isYRightAxis = true;
|
|
641
|
+
function setTransform(t) {
|
|
642
|
+
transform = t;
|
|
643
|
+
}
|
|
644
|
+
function getTransform() {
|
|
645
|
+
return transform;
|
|
646
|
+
}
|
|
647
|
+
function getPanels() {
|
|
648
|
+
return panelManager.panels;
|
|
649
|
+
}
|
|
650
|
+
function toggleAutoFocusY(p) {
|
|
651
|
+
p.autoFocusY = !p.autoFocusY;
|
|
652
|
+
}
|
|
653
|
+
function getContainerSize() {
|
|
654
|
+
return { width: containerWidth, height: containerHeight };
|
|
655
|
+
}
|
|
656
|
+
const interaction = createInteraction(canvas, getTransform, setTransform, getPanels, () => updatePanels(), (p) => toggleAutoFocusY(p), panelManager, getContainerSize);
|
|
657
|
+
function updatePanels() {
|
|
658
|
+
const x1 = transform.offsetX;
|
|
659
|
+
const x2 = transform.offsetX + (canvas.width - 40) / transform.scaleX;
|
|
660
|
+
for (const p of panelManager.panels) {
|
|
661
|
+
if (p.autoFocusY) {
|
|
662
|
+
const { minY, maxY } = dataModel.getGlobalMinMaxY(x1, x2, (ds) => p.dataSets.includes(ds));
|
|
663
|
+
p.verticalRange.minY = minY;
|
|
664
|
+
p.verticalRange.maxY = maxY;
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
const offsetY = transform.offsetY ?? 0;
|
|
668
|
+
const scaleY = transform.scaleY ?? 1;
|
|
669
|
+
const y2 = offsetY + p.height / scaleY;
|
|
670
|
+
p.verticalRange.minY = offsetY;
|
|
671
|
+
p.verticalRange.maxY = y2;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function renderLoop() {
|
|
676
|
+
if (destroyed)
|
|
677
|
+
return;
|
|
678
|
+
updatePanels();
|
|
679
|
+
const xMin = transform.offsetX;
|
|
680
|
+
const xMax = transform.offsetX + (canvas.width - 40) / transform.scaleX;
|
|
681
|
+
const crosshair = interaction.getCrosshairPos();
|
|
682
|
+
for (const p of panelManager.panels) {
|
|
683
|
+
renderer.drawPanel(ctx, p, transform, { xMin, xMax }, crosshair, isYRightAxis);
|
|
684
|
+
}
|
|
685
|
+
animationFrameId = requestAnimationFrame(renderLoop);
|
|
686
|
+
}
|
|
687
|
+
function init() {
|
|
688
|
+
interaction.initEvents(canvas);
|
|
689
|
+
renderLoop();
|
|
690
|
+
}
|
|
691
|
+
function destroy() {
|
|
692
|
+
destroyed = true;
|
|
693
|
+
cancelAnimationFrame(animationFrameId);
|
|
694
|
+
interaction.destroy();
|
|
695
|
+
if (resizeObserver && containerEl) {
|
|
696
|
+
resizeObserver.unobserve(containerEl);
|
|
697
|
+
resizeObserver.disconnect();
|
|
698
|
+
}
|
|
699
|
+
containerEl = null;
|
|
700
|
+
resizeObserver = null;
|
|
701
|
+
}
|
|
702
|
+
function attachToContainer(container) {
|
|
703
|
+
containerEl = container;
|
|
704
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
705
|
+
for (const entry of entries) {
|
|
706
|
+
if (entry.target === containerEl) {
|
|
707
|
+
const w = Math.floor(entry.contentRect.width);
|
|
708
|
+
const h = Math.floor(entry.contentRect.height);
|
|
709
|
+
if (canvas.width !== w || canvas.height !== h) {
|
|
710
|
+
canvas.width = w;
|
|
711
|
+
canvas.height = h;
|
|
712
|
+
}
|
|
713
|
+
containerWidth = w;
|
|
714
|
+
containerHeight = h;
|
|
715
|
+
// layoutPanels
|
|
716
|
+
panelManager.layoutPanels(w, h);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
resizeObserver.observe(containerEl);
|
|
721
|
+
}
|
|
722
|
+
function createDataSetFn(params) {
|
|
723
|
+
return dataModel.addDataSet(params);
|
|
724
|
+
}
|
|
725
|
+
function addPanelFn(config) {
|
|
726
|
+
panelManager.addPanel(config);
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
init,
|
|
730
|
+
destroy,
|
|
731
|
+
attachToContainer,
|
|
732
|
+
createDataSet: createDataSetFn,
|
|
733
|
+
addPanel: addPanelFn,
|
|
734
|
+
canvas,
|
|
735
|
+
dataModel,
|
|
736
|
+
panelManager,
|
|
737
|
+
renderer
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Пример генерации данных
|
|
742
|
+
*/
|
|
743
|
+
export function generateIncrementalData(startX, count, startY, maxDelta) {
|
|
744
|
+
const arr = [];
|
|
745
|
+
let currentY = startY;
|
|
746
|
+
let currentX = startX;
|
|
747
|
+
for (let i = 0; i < count; i++) {
|
|
748
|
+
const delta = (Math.random() - 0.5) * 2 * maxDelta;
|
|
749
|
+
currentY += delta;
|
|
750
|
+
if (currentY < 0)
|
|
751
|
+
currentY = 0;
|
|
752
|
+
arr.push({ x: currentX, y: currentY });
|
|
753
|
+
currentX++;
|
|
754
|
+
}
|
|
755
|
+
return arr;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Пример React-компонента MyChart
|
|
759
|
+
*/
|
|
760
|
+
export const MyChartEngine = () => {
|
|
761
|
+
const containerRef = useRef(null);
|
|
762
|
+
const canvasRef = useRef(null);
|
|
763
|
+
const engineRef = useRef(null);
|
|
764
|
+
useEffect(() => {
|
|
765
|
+
const container = containerRef.current;
|
|
766
|
+
const canvas = canvasRef.current;
|
|
767
|
+
if (!container || !canvas)
|
|
768
|
+
return;
|
|
769
|
+
const engine = createChartEngine(canvas);
|
|
770
|
+
engineRef.current = engine;
|
|
771
|
+
engine.attachToContainer(container);
|
|
772
|
+
engine.init();
|
|
773
|
+
// Создаём DataSets
|
|
774
|
+
const lineData = generateIncrementalData(0, 50, 100, 10);
|
|
775
|
+
const lineDS = engine.createDataSet({
|
|
776
|
+
id: 'line1',
|
|
777
|
+
type: 'line',
|
|
778
|
+
data: lineData,
|
|
779
|
+
style: { strokeColor: '#FF0000' }
|
|
780
|
+
});
|
|
781
|
+
const barData = generateIncrementalData(0, 50, 50, 5);
|
|
782
|
+
const barDS = engine.createDataSet({
|
|
783
|
+
id: 'bar1',
|
|
784
|
+
type: 'bar',
|
|
785
|
+
data: barData,
|
|
786
|
+
style: { barColor: '#66aa66' }
|
|
787
|
+
});
|
|
788
|
+
// Добавляем панели — указываем heightPct
|
|
789
|
+
// Последняя панель автоматически заберёт остаток до 100%.
|
|
790
|
+
engine.addPanel({
|
|
791
|
+
id: 'mainPanel',
|
|
792
|
+
dataSets: [lineDS],
|
|
793
|
+
heightPct: 60, // 60%
|
|
794
|
+
autoFocusY: true,
|
|
795
|
+
resizable: true
|
|
796
|
+
});
|
|
797
|
+
engine.addPanel({
|
|
798
|
+
id: 'bottomPanel',
|
|
799
|
+
dataSets: [barDS],
|
|
800
|
+
heightPct: 30, // 30%
|
|
801
|
+
autoFocusY: true,
|
|
802
|
+
resizable: true
|
|
803
|
+
});
|
|
804
|
+
// Имитация добавления
|
|
805
|
+
let lineX = lineData[lineData.length - 1].x;
|
|
806
|
+
let lineY = lineData[lineData.length - 1].y;
|
|
807
|
+
let barX = barData[barData.length - 1].x;
|
|
808
|
+
let barY = barData[barData.length - 1].y;
|
|
809
|
+
const intervalId = setInterval(() => {
|
|
810
|
+
const dLine = (Math.random() - 0.5) * 10;
|
|
811
|
+
lineY += dLine;
|
|
812
|
+
if (lineY < 0)
|
|
813
|
+
lineY = 0;
|
|
814
|
+
lineX++;
|
|
815
|
+
lineDS.addData({ x: lineX, y: lineY });
|
|
816
|
+
const dBar = (Math.random() - 0.5) * 5;
|
|
817
|
+
barY += dBar;
|
|
818
|
+
if (barY < 0)
|
|
819
|
+
barY = 0;
|
|
820
|
+
barX++;
|
|
821
|
+
barDS.addData({ x: barX, y: barY });
|
|
822
|
+
}, 1);
|
|
823
|
+
return () => {
|
|
824
|
+
clearInterval(intervalId);
|
|
825
|
+
engine.destroy();
|
|
826
|
+
};
|
|
827
|
+
}, []);
|
|
828
|
+
return (_jsx("div", { ref: containerRef, style: {
|
|
829
|
+
width: '100%',
|
|
830
|
+
height: '600px',
|
|
831
|
+
border: '1px solid #ccc',
|
|
832
|
+
position: 'relative'
|
|
833
|
+
}, children: _jsx("canvas", { ref: canvasRef }) }));
|
|
834
|
+
};
|