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.
Files changed (92) hide show
  1. package/lib/common/api.d.ts +17 -0
  2. package/lib/common/api.js +24 -0
  3. package/lib/common/src/components/Buttons/MiniButton.d.ts +17 -0
  4. package/lib/common/src/components/Buttons/MiniButton.js +18 -0
  5. package/lib/common/src/components/Buttons/index.d.ts +1 -0
  6. package/lib/common/src/components/Buttons/index.js +1 -0
  7. package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +1 -0
  8. package/lib/common/src/components/Dnd/DraggableOutlineDiv.js +31 -0
  9. package/lib/common/src/components/Dnd/RNDFunc.d.ts +13 -0
  10. package/lib/common/src/components/Dnd/RNDFunc.js +111 -0
  11. package/lib/common/src/components/Dnd/RNDFunc3.d.ts +81 -0
  12. package/lib/common/src/components/Dnd/RNDFunc3.js +380 -0
  13. package/lib/common/src/components/Dnd/Resizable.d.ts +15 -0
  14. package/lib/common/src/components/Dnd/Resizable.js +36 -0
  15. package/lib/common/src/components/Dnd/index.d.ts +4 -0
  16. package/lib/common/src/components/Dnd/index.js +4 -0
  17. package/lib/common/src/components/Input.d.ts +28 -0
  18. package/lib/common/src/components/Input.js +30 -0
  19. package/lib/common/src/components/Menu/RightMenu.d.ts +22 -0
  20. package/lib/common/src/components/Menu/RightMenu.js +179 -0
  21. package/lib/common/src/components/Menu/StickerMenu.d.ts +5 -0
  22. package/lib/common/src/components/Menu/StickerMenu.js +110 -0
  23. package/lib/common/src/components/Menu/index.d.ts +2 -0
  24. package/lib/common/src/components/Menu/index.js +2 -0
  25. package/lib/common/src/components/Modal/LeftModal.d.ts +58 -0
  26. package/lib/common/src/components/Modal/LeftModal.js +284 -0
  27. package/lib/common/src/components/Modal/Modal.d.ts +31 -0
  28. package/lib/common/src/components/Modal/Modal.js +104 -0
  29. package/lib/common/src/components/Modal/index.d.ts +2 -0
  30. package/lib/common/src/components/Modal/index.js +2 -0
  31. package/lib/common/src/components/MyResizeObserver.d.ts +10 -0
  32. package/lib/common/src/components/MyResizeObserver.js +94 -0
  33. package/lib/common/src/components/Other.d.ts +9 -0
  34. package/lib/common/src/components/Other.js +31 -0
  35. package/lib/common/src/components/Parameters.d.ts +10 -0
  36. package/lib/common/src/components/Parameters.js +24 -0
  37. package/lib/common/src/components/ParametersEngine.d.ts +8 -0
  38. package/lib/common/src/components/ParametersEngine.js +373 -0
  39. package/lib/common/src/components/index.d.ts +9 -0
  40. package/lib/common/src/components/index.js +9 -0
  41. package/lib/common/src/hooks/index.d.ts +3 -0
  42. package/lib/common/src/hooks/index.js +3 -0
  43. package/lib/common/src/hooks/useAddDownAnyKey.d.ts +5 -0
  44. package/lib/common/src/hooks/useAddDownAnyKey.js +22 -0
  45. package/lib/common/src/hooks/useDraggable.d.ts +15 -0
  46. package/lib/common/src/hooks/useDraggable.js +134 -0
  47. package/lib/common/src/hooks/useOutside.d.ts +40 -0
  48. package/lib/common/src/hooks/useOutside.js +68 -0
  49. package/lib/common/src/logs/logs.d.ts +163 -0
  50. package/lib/common/src/logs/logs.js +249 -0
  51. package/lib/common/src/logs/logs3.d.ts +63 -0
  52. package/lib/common/src/logs/logs3.js +245 -0
  53. package/lib/common/src/logs/miniLogs.d.ts +5 -0
  54. package/lib/common/src/logs/miniLogs.js +51 -0
  55. package/lib/common/src/menu/menu.d.ts +72 -0
  56. package/lib/common/src/menu/menu.js +230 -0
  57. package/lib/common/src/menu/menuMouse.d.ts +21 -0
  58. package/lib/common/src/menu/menuMouse.js +32 -0
  59. package/lib/common/src/menu/menuR.d.ts +17 -0
  60. package/lib/common/src/menu/menuR.js +116 -0
  61. package/lib/common/src/myChart/1/myChart.d.ts +40 -0
  62. package/lib/common/src/myChart/1/myChart.js +306 -0
  63. package/lib/common/src/myChart/1/myChartTest.d.ts +1 -0
  64. package/lib/common/src/myChart/1/myChartTest.js +45 -0
  65. package/lib/common/src/myChart/chartEngine/chartEngineReact.d.ts +164 -0
  66. package/lib/common/src/myChart/chartEngine/chartEngineReact.js +834 -0
  67. package/lib/common/src/styles/index.d.ts +1 -0
  68. package/lib/common/src/styles/index.js +1 -0
  69. package/lib/common/src/styles/styleGrid.d.ts +20 -0
  70. package/lib/common/src/styles/styleGrid.js +50 -0
  71. package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +42 -0
  72. package/lib/common/src/utils/applyTransactionAsyncUpdate.js +97 -0
  73. package/lib/common/src/utils/arrayPromise.d.ts +5 -0
  74. package/lib/common/src/utils/arrayPromise.js +16 -0
  75. package/lib/common/src/utils/cache.d.ts +31 -0
  76. package/lib/common/src/utils/cache.js +119 -0
  77. package/lib/common/src/utils/index.d.ts +6 -0
  78. package/lib/common/src/utils/index.js +6 -0
  79. package/lib/common/src/utils/inputAutoStep.d.ts +4 -0
  80. package/lib/common/src/utils/inputAutoStep.js +79 -0
  81. package/lib/common/src/utils/mapMemory.d.ts +26 -0
  82. package/lib/common/src/utils/mapMemory.js +72 -0
  83. package/lib/common/src/utils/pageVisibilityContext.d.ts +5 -0
  84. package/lib/common/src/utils/pageVisibilityContext.js +18 -0
  85. package/lib/common/updateBy.d.ts +12 -0
  86. package/lib/common/updateBy.js +88 -0
  87. package/lib/index.d.ts +1 -0
  88. package/lib/index.js +3 -0
  89. package/lib/style/menuRight.css +139 -0
  90. package/lib/style/style.css +334 -0
  91. package/lib/tsconfig.json +6 -0
  92. 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
+ };