wenay-react2 1.0.20 → 1.0.21
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/README.md +383 -382
- package/lib/common/api.d.ts +28 -0
- package/lib/common/api.js +41 -16
- package/lib/common/src/components/Buttons/MiniButton.d.ts +1 -0
- package/lib/common/src/components/Buttons/MiniButton.js +2 -2
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +1 -0
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.js +7 -6
- package/lib/common/src/components/Dnd/RNDFunc.js +28 -17
- package/lib/common/src/components/Dnd/RNDFunc3.d.ts +27 -17
- package/lib/common/src/components/Dnd/RNDFunc3.js +107 -105
- package/lib/common/src/components/Dnd/Resizable.js +3 -3
- package/lib/common/src/components/Menu/RightMenu.js +11 -17
- package/lib/common/src/components/Menu/StickerMenu.js +42 -28
- package/lib/common/src/components/Menu/index.d.ts +1 -1
- package/lib/common/src/components/Menu/index.js +1 -1
- package/lib/common/src/components/Modal/LeftModal.d.ts +14 -12
- package/lib/common/src/components/Modal/LeftModal.js +30 -16
- package/lib/common/src/components/Modal/Modal.d.ts +26 -12
- package/lib/common/src/components/Modal/Modal.js +66 -89
- package/lib/common/src/components/Modal/ModalContextProvider.d.ts +20 -3
- package/lib/common/src/components/Modal/ModalContextProvider.js +35 -8
- package/lib/common/src/components/MyResizeObserver.js +15 -18
- package/lib/common/src/components/Other.js +3 -2
- package/lib/common/src/components/Parameters.js +7 -6
- package/lib/common/src/components/ParametersEngine.js +25 -54
- package/lib/common/src/grid/agGrid4/agGrid4.d.ts +53 -0
- package/lib/common/src/grid/agGrid4/agGrid4.js +132 -0
- package/lib/common/src/grid/agGrid4/core.d.ts +56 -0
- package/lib/common/src/grid/agGrid4/core.js +127 -0
- package/lib/common/src/grid/agGrid4/index.d.ts +6 -0
- package/lib/common/src/grid/agGrid4/index.js +3 -0
- package/lib/common/src/grid/agGrid4/theme.d.ts +7 -0
- package/lib/common/src/grid/agGrid4/theme.js +18 -0
- package/lib/common/src/hooks/useAddDownAnyKey.d.ts +21 -2
- package/lib/common/src/hooks/useAddDownAnyKey.js +46 -8
- package/lib/common/src/hooks/useDraggable.d.ts +20 -2
- package/lib/common/src/hooks/useDraggable.js +146 -115
- package/lib/common/src/hooks/useOutside.d.ts +24 -4
- package/lib/common/src/hooks/useOutside.js +76 -27
- package/lib/common/src/logs/logs.d.ts +5 -2
- package/lib/common/src/logs/logs.js +15 -31
- package/lib/common/src/logs/logs3.d.ts +9 -9
- package/lib/common/src/logs/logs3.js +47 -38
- package/lib/common/src/logs/miniLogs.d.ts +3 -3
- package/lib/common/src/logs/miniLogs.js +28 -36
- package/lib/common/src/menu/menu.d.ts +18 -18
- package/lib/common/src/menu/menu.js +48 -24
- package/lib/common/src/menu/menuMouse.js +4 -1
- package/lib/common/src/menu/menuR.d.ts +2 -2
- package/lib/common/src/menu/menuR.js +34 -34
- package/lib/common/src/myChart/1/myChart.d.ts +5 -5
- package/lib/common/src/myChart/1/myChart.js +58 -47
- package/lib/common/src/myChart/1/myChartTest.js +8 -4
- package/lib/common/src/myChart/chartEngine/chartEngineReact.d.ts +24 -27
- package/lib/common/src/myChart/chartEngine/chartEngineReact.js +166 -117
- package/lib/common/src/styles/styleGrid.d.ts +2 -1
- package/lib/common/src/styles/styleGrid.js +10 -12
- package/lib/common/src/styles/tokens.d.ts +39 -0
- package/lib/common/src/styles/tokens.js +40 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +7 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.js +19 -12
- package/lib/common/src/utils/arrayPromise.d.ts +6 -0
- package/lib/common/src/utils/arrayPromise.js +7 -1
- package/lib/common/src/utils/cache.js +24 -21
- package/lib/common/src/utils/inputAutoStep.d.ts +1 -1
- package/lib/common/src/utils/inputAutoStep.js +30 -19
- package/lib/common/src/utils/mapMemory.js +9 -10
- package/lib/common/updateBy.d.ts +21 -2
- package/lib/common/updateBy.js +66 -31
- package/lib/index.js +0 -2
- package/lib/style/menuRight.css +13 -26
- package/lib/style/style.css +51 -39
- package/lib/style/tokens.css +34 -0
- package/package.json +2 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/*************************************************************
|
|
3
3
|
* chartEngine.tsx
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* Updated example where:
|
|
5
|
+
* - Panel width = 100% of Canvas
|
|
6
|
+
* - Panel height is defined in percent (heightPct)
|
|
7
|
+
* - The last panel always fills up to 100%
|
|
8
8
|
*************************************************************/
|
|
9
9
|
import { useRef, useEffect } from 'react';
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* DataSet factory
|
|
12
12
|
*/
|
|
13
13
|
export function createDataSet(params) {
|
|
14
14
|
const { id, type = 'line', data = [], style = {}, chunkSize = 100 } = params;
|
|
@@ -22,23 +22,24 @@ export function createDataSet(params) {
|
|
|
22
22
|
const mergedStyle = { ...defaultStyle, ...style };
|
|
23
23
|
let internalData = data.slice();
|
|
24
24
|
let minMaxChunks = [];
|
|
25
|
+
function computeChunk(chunkIndex) {
|
|
26
|
+
const start = chunkIndex * chunkSize;
|
|
27
|
+
const end = Math.min(start + chunkSize, internalData.length);
|
|
28
|
+
let minY = Infinity;
|
|
29
|
+
let maxY = -Infinity;
|
|
30
|
+
for (let i = start; i < end; i++) {
|
|
31
|
+
const y = internalData[i].y;
|
|
32
|
+
if (y < minY)
|
|
33
|
+
minY = y;
|
|
34
|
+
if (y > maxY)
|
|
35
|
+
maxY = y;
|
|
36
|
+
}
|
|
37
|
+
return { xStart: internalData[start].x, xEnd: internalData[end - 1].x, minY, maxY };
|
|
38
|
+
}
|
|
25
39
|
function buildMinMaxChunks() {
|
|
26
40
|
minMaxChunks = [];
|
|
27
|
-
|
|
28
|
-
|
|
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 });
|
|
41
|
+
for (let c = 0; c * chunkSize < internalData.length; c++) {
|
|
42
|
+
minMaxChunks.push(computeChunk(c));
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
buildMinMaxChunks();
|
|
@@ -64,26 +65,15 @@ export function createDataSet(params) {
|
|
|
64
65
|
}
|
|
65
66
|
function addData(newPoints) {
|
|
66
67
|
const arr = Array.isArray(newPoints) ? newPoints : [newPoints];
|
|
68
|
+
if (arr.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
const oldLength = internalData.length;
|
|
67
71
|
internalData.push(...arr);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 };
|
|
72
|
+
// recompute only the tail: previously crossing a chunk boundary rebuilt ALL chunks (O(n) per boundary)
|
|
73
|
+
const firstChunk = Math.floor(oldLength / chunkSize);
|
|
74
|
+
const lastChunk = Math.floor((internalData.length - 1) / chunkSize);
|
|
75
|
+
for (let c = firstChunk; c <= lastChunk; c++) {
|
|
76
|
+
minMaxChunks[c] = computeChunk(c);
|
|
87
77
|
}
|
|
88
78
|
}
|
|
89
79
|
return {
|
|
@@ -92,7 +82,8 @@ export function createDataSet(params) {
|
|
|
92
82
|
data: internalData,
|
|
93
83
|
style: mergedStyle,
|
|
94
84
|
chunkSize,
|
|
95
|
-
|
|
85
|
+
// buildMinMaxChunks reassigns the local array, so use a getter instead of a stale snapshot
|
|
86
|
+
get minMaxChunks() { return minMaxChunks; },
|
|
96
87
|
getMinMaxInRange,
|
|
97
88
|
addData
|
|
98
89
|
};
|
|
@@ -140,7 +131,7 @@ export function createPanelManager() {
|
|
|
140
131
|
top: 0,
|
|
141
132
|
width: 0,
|
|
142
133
|
height: 0,
|
|
143
|
-
heightPct: config.heightPct ?? 20, //
|
|
134
|
+
heightPct: config.heightPct ?? 20, // default 20% (or any other value)
|
|
144
135
|
dataSets: config.dataSets,
|
|
145
136
|
verticalRange: { minY: 0, maxY: 1 },
|
|
146
137
|
autoFocusY: config.autoFocusY !== false,
|
|
@@ -150,28 +141,28 @@ export function createPanelManager() {
|
|
|
150
141
|
}
|
|
151
142
|
/**
|
|
152
143
|
* layoutPanels:
|
|
153
|
-
* -
|
|
154
|
-
* -
|
|
155
|
-
* -
|
|
144
|
+
* - Sum heightPct for all panels except the last one.
|
|
145
|
+
* - Last panel = 100% - sum of previous panels (if the sum is below 100).
|
|
146
|
+
* - Convert percents to px and calculate top/height.
|
|
156
147
|
*/
|
|
157
148
|
function layoutPanels(containerWidth, containerHeight) {
|
|
158
|
-
//
|
|
149
|
+
// Percent sum for all panels except the last one
|
|
159
150
|
if (panels.length === 0)
|
|
160
151
|
return;
|
|
161
|
-
//
|
|
152
|
+
// Calculate the sum of assigned percents (except the last one)
|
|
162
153
|
let totalAssignedPct = 0;
|
|
163
154
|
for (let i = 0; i < panels.length - 1; i++) {
|
|
164
155
|
totalAssignedPct += panels[i].heightPct;
|
|
165
156
|
}
|
|
166
157
|
if (totalAssignedPct > 100)
|
|
167
|
-
totalAssignedPct = 100; //
|
|
168
|
-
//
|
|
158
|
+
totalAssignedPct = 100; // clamp
|
|
159
|
+
// Give the remaining space to the last panel
|
|
169
160
|
const lastPanel = panels[panels.length - 1];
|
|
170
161
|
lastPanel.heightPct = 100 - totalAssignedPct;
|
|
171
|
-
//
|
|
162
|
+
// Now calculate top/height
|
|
172
163
|
let currentTopPx = 0;
|
|
173
164
|
for (const p of panels) {
|
|
174
|
-
//
|
|
165
|
+
// Convert percent to px
|
|
175
166
|
const hPx = (p.heightPct / 100) * containerHeight;
|
|
176
167
|
p.left = 0;
|
|
177
168
|
p.top = currentTopPx;
|
|
@@ -181,21 +172,21 @@ export function createPanelManager() {
|
|
|
181
172
|
}
|
|
182
173
|
}
|
|
183
174
|
/**
|
|
184
|
-
* resizePanel:
|
|
185
|
-
* deltaPx
|
|
175
|
+
* resizePanel: change one panel height percent while dragging.
|
|
176
|
+
* deltaPx is the height change in px, converted to percent.
|
|
186
177
|
*/
|
|
187
178
|
function resizePanel(panelId, deltaPx, containerHeight) {
|
|
188
179
|
const idx = panels.findIndex((p) => p.id === panelId);
|
|
189
|
-
if (idx < 0
|
|
180
|
+
if (idx < 0)
|
|
190
181
|
return;
|
|
191
182
|
const p = panels[idx];
|
|
192
|
-
//
|
|
183
|
+
// Current percent
|
|
193
184
|
const oldPct = p.heightPct;
|
|
194
185
|
// px -> pct
|
|
195
186
|
const deltaPct = (deltaPx / containerHeight) * 100;
|
|
196
187
|
const newPct = p.heightPct + deltaPct;
|
|
197
|
-
//
|
|
198
|
-
if (newPct < 5) { //
|
|
188
|
+
// Clamp to avoid negative values
|
|
189
|
+
if (newPct < 5) { // Minimum 5% (arbitrary)
|
|
199
190
|
p.heightPct = 5;
|
|
200
191
|
}
|
|
201
192
|
else if (newPct > 95) {
|
|
@@ -212,21 +203,41 @@ export function createPanelManager() {
|
|
|
212
203
|
resizePanel
|
|
213
204
|
};
|
|
214
205
|
}
|
|
206
|
+
// Right Y-axis width, a single file-level constant
|
|
207
|
+
const AXIS_THICKNESS = 40;
|
|
208
|
+
// Visible world-X range for a drawing area of the given pixel width
|
|
209
|
+
function visibleXRange(width, transform) {
|
|
210
|
+
return [transform.offsetX, transform.offsetX + (width - AXIS_THICKNESS) / transform.scaleX];
|
|
211
|
+
}
|
|
212
|
+
// Data is sorted by x: find visible-range boundaries with binary search,
|
|
213
|
+
// without filter allocations on each frame. Returns [start, end) by indexes.
|
|
214
|
+
function visibleRange(data, xMin, xMax) {
|
|
215
|
+
let lo = 0, hi = data.length;
|
|
216
|
+
while (lo < hi) {
|
|
217
|
+
const mid = (lo + hi) >> 1;
|
|
218
|
+
if (data[mid].x < xMin)
|
|
219
|
+
lo = mid + 1;
|
|
220
|
+
else
|
|
221
|
+
hi = mid;
|
|
222
|
+
}
|
|
223
|
+
const start = lo;
|
|
224
|
+
hi = data.length;
|
|
225
|
+
while (lo < hi) {
|
|
226
|
+
const mid = (lo + hi) >> 1;
|
|
227
|
+
if (data[mid].x <= xMax)
|
|
228
|
+
lo = mid + 1;
|
|
229
|
+
else
|
|
230
|
+
hi = mid;
|
|
231
|
+
}
|
|
232
|
+
return [start, lo];
|
|
233
|
+
}
|
|
215
234
|
export function createRenderer() {
|
|
216
|
-
const AXIS_THICKNESS = 40;
|
|
217
235
|
function getNiceTicks(minVal, maxVal, count) {
|
|
218
236
|
const range = maxVal - minVal || 1;
|
|
219
237
|
const roughStep = range / count;
|
|
220
238
|
const mag = Math.pow(10, Math.floor(Math.log10(roughStep)));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (norm < 2)
|
|
224
|
-
step = 1;
|
|
225
|
-
else if (norm < 5)
|
|
226
|
-
step = 2;
|
|
227
|
-
else
|
|
228
|
-
step = 5;
|
|
229
|
-
step *= mag;
|
|
239
|
+
const norm = roughStep / mag;
|
|
240
|
+
const step = (norm < 2 ? 1 : norm < 5 ? 2 : 5) * mag;
|
|
230
241
|
const niceMin = Math.floor(minVal / step) * step;
|
|
231
242
|
const niceMax = Math.ceil(maxVal / step) * step;
|
|
232
243
|
const ticks = [];
|
|
@@ -240,7 +251,7 @@ export function createRenderer() {
|
|
|
240
251
|
}
|
|
241
252
|
function yToPixY(yVal, panel, transform) {
|
|
242
253
|
// autoFocusY => [minY, maxY]
|
|
243
|
-
//
|
|
254
|
+
// otherwise offsetY/scaleY
|
|
244
255
|
if (panel.autoFocusY) {
|
|
245
256
|
const { minY, maxY } = panel.verticalRange;
|
|
246
257
|
const range = maxY - minY || 1;
|
|
@@ -306,11 +317,9 @@ export function createRenderer() {
|
|
|
306
317
|
const fillColor = style.fillColor ?? 'rgba(34,153,221,0.2)';
|
|
307
318
|
const gradientFill = style.gradientFill ?? true;
|
|
308
319
|
const lineWidth = style.lineWidth ?? 2;
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
const visible = data.filter((pt) => pt.x >= xMinVisible && pt.x <= xMaxVisible);
|
|
313
|
-
if (visible.length < 2)
|
|
320
|
+
const [xMinVisible, xMaxVisible] = visibleXRange(panel.width, transform);
|
|
321
|
+
const [start, end] = visibleRange(data, xMinVisible, xMaxVisible);
|
|
322
|
+
if (end - start < 2)
|
|
314
323
|
return;
|
|
315
324
|
const result = [];
|
|
316
325
|
let lastPixX = -1;
|
|
@@ -325,7 +334,8 @@ export function createRenderer() {
|
|
|
325
334
|
if (b !== a)
|
|
326
335
|
result.push(b);
|
|
327
336
|
};
|
|
328
|
-
for (
|
|
337
|
+
for (let i = start; i < end; i++) {
|
|
338
|
+
const pt = data[i];
|
|
329
339
|
const px = Math.round(xToPixX(pt.x, transform, panel));
|
|
330
340
|
if (px !== lastPixX) {
|
|
331
341
|
flush();
|
|
@@ -382,19 +392,18 @@ export function createRenderer() {
|
|
|
382
392
|
}
|
|
383
393
|
function drawBarChart(ctx, data, panel, transform, style) {
|
|
384
394
|
const barColor = style.barColor ?? '#66cc66';
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
const visible = data.filter((pt) => pt.x >= xMinVisible && pt.x <= xMaxVisible);
|
|
389
|
-
if (!visible.length)
|
|
395
|
+
const [xMinVisible, xMaxVisible] = visibleXRange(panel.width, transform);
|
|
396
|
+
const [start, end] = visibleRange(data, xMinVisible, xMaxVisible);
|
|
397
|
+
if (end === start)
|
|
390
398
|
return;
|
|
391
399
|
ctx.save();
|
|
392
400
|
ctx.fillStyle = barColor;
|
|
393
401
|
const barWidth = 5;
|
|
394
|
-
|
|
402
|
+
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
403
|
+
for (let i = start; i < end; i++) {
|
|
404
|
+
const pt = data[i];
|
|
395
405
|
const px = xToPixX(pt.x, transform, panel);
|
|
396
406
|
const py = yToPixY(pt.y, panel, transform);
|
|
397
|
-
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
398
407
|
ctx.fillRect(px - barWidth / 2, py, barWidth, pyBase - py);
|
|
399
408
|
}
|
|
400
409
|
ctx.restore();
|
|
@@ -489,11 +498,14 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
489
498
|
const localX = e.clientX - rect.left;
|
|
490
499
|
const localY = e.clientY - rect.top;
|
|
491
500
|
crosshairPos = { x: localX, y: localY };
|
|
492
|
-
//
|
|
501
|
+
// Check the boundary between panels (for resize)
|
|
493
502
|
const panels = getPanels();
|
|
494
503
|
for (const p of panels) {
|
|
495
504
|
if (!p.resizable)
|
|
496
505
|
continue;
|
|
506
|
+
// last panel takes the leftover height in layoutPanels - its bottom border is not draggable
|
|
507
|
+
if (p === panels[panels.length - 1])
|
|
508
|
+
continue;
|
|
497
509
|
const bottom = p.top + p.height;
|
|
498
510
|
if (Math.abs(localY - bottom) < 5) {
|
|
499
511
|
dragMode = DragMode.ResizePanel;
|
|
@@ -502,15 +514,15 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
502
514
|
return;
|
|
503
515
|
}
|
|
504
516
|
}
|
|
505
|
-
//
|
|
517
|
+
// Find panel
|
|
506
518
|
activePanel = findPanel(localX, localY, panels);
|
|
507
519
|
if (!activePanel) {
|
|
508
520
|
dragMode = DragMode.None;
|
|
509
521
|
return;
|
|
510
522
|
}
|
|
511
|
-
const axisRightX = activePanel.left + activePanel.width -
|
|
523
|
+
const axisRightX = activePanel.left + activePanel.width - AXIS_THICKNESS;
|
|
512
524
|
if (localX >= axisRightX) {
|
|
513
|
-
//
|
|
525
|
+
// Scale by Y if autoFocusY=false
|
|
514
526
|
dragMode = DragMode.ScaleY;
|
|
515
527
|
}
|
|
516
528
|
else {
|
|
@@ -533,11 +545,13 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
533
545
|
lastX = e.clientX;
|
|
534
546
|
lastY = e.clientY;
|
|
535
547
|
const t = getTransform();
|
|
536
|
-
//
|
|
548
|
+
// Panel resize mode
|
|
537
549
|
if (dragMode === DragMode.ResizePanel && resizingPanelId) {
|
|
538
550
|
// deltaPx = dy
|
|
539
|
-
const { height: cH } = getContainerSize();
|
|
551
|
+
const { width: cW, height: cH } = getContainerSize();
|
|
540
552
|
panelManager.resizePanel(resizingPanelId, dy, cH);
|
|
553
|
+
// recalc px top/height right away - previously it only happened on container resize
|
|
554
|
+
panelManager.layoutPanels(cW, cH);
|
|
541
555
|
onTransformChanged();
|
|
542
556
|
return;
|
|
543
557
|
}
|
|
@@ -560,12 +574,6 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
560
574
|
}
|
|
561
575
|
onTransformChanged();
|
|
562
576
|
}
|
|
563
|
-
function onMouseUp(e) {
|
|
564
|
-
isMouseDown = false;
|
|
565
|
-
dragMode = DragMode.None;
|
|
566
|
-
activePanel = null;
|
|
567
|
-
resizingPanelId = null;
|
|
568
|
-
}
|
|
569
577
|
function onGlobalMouseUp(e) {
|
|
570
578
|
if (isMouseDown) {
|
|
571
579
|
isMouseDown = false;
|
|
@@ -594,7 +602,7 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
594
602
|
const p = findPanel(localX, localY, getPanels());
|
|
595
603
|
if (!p)
|
|
596
604
|
return;
|
|
597
|
-
const axisRightX = p.left + p.width -
|
|
605
|
+
const axisRightX = p.left + p.width - AXIS_THICKNESS;
|
|
598
606
|
if (localX >= axisRightX) {
|
|
599
607
|
onToggleAutoFocusY(p);
|
|
600
608
|
onTransformChanged();
|
|
@@ -638,7 +646,7 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
638
646
|
};
|
|
639
647
|
}
|
|
640
648
|
/**
|
|
641
|
-
*
|
|
649
|
+
* Engine factory function
|
|
642
650
|
*/
|
|
643
651
|
export function createChartEngine(canvas) {
|
|
644
652
|
const ctx = canvas.getContext('2d');
|
|
@@ -657,7 +665,22 @@ export function createChartEngine(canvas) {
|
|
|
657
665
|
let resizeObserver = null;
|
|
658
666
|
let containerWidth = 0;
|
|
659
667
|
let containerHeight = 0;
|
|
660
|
-
|
|
668
|
+
// Dirty flag: render only when something changes. Invalidation hooks cover interaction
|
|
669
|
+
// (including crosshair), addData/addPanel/resize; stamp in renderLoop catches direct
|
|
670
|
+
// mutations through dataModel/panelManager (data lengths, panel geometry, canvas size).
|
|
671
|
+
let needsRender = true;
|
|
672
|
+
let lastStamp = NaN;
|
|
673
|
+
function invalidate() { needsRender = true; }
|
|
674
|
+
function frameStamp() {
|
|
675
|
+
const panels = panelManager.panels;
|
|
676
|
+
let s = panels.length + canvas.width * 3 + canvas.height * 5;
|
|
677
|
+
for (const p of panels) {
|
|
678
|
+
s += p.top * 7 + p.height * 13 + (p.autoFocusY ? 1 : 0);
|
|
679
|
+
for (const ds of p.dataSets)
|
|
680
|
+
s += ds.data.length * 31;
|
|
681
|
+
}
|
|
682
|
+
return s;
|
|
683
|
+
}
|
|
661
684
|
function setTransform(t) {
|
|
662
685
|
transform = t;
|
|
663
686
|
}
|
|
@@ -673,13 +696,27 @@ export function createChartEngine(canvas) {
|
|
|
673
696
|
function getContainerSize() {
|
|
674
697
|
return { width: containerWidth, height: containerHeight };
|
|
675
698
|
}
|
|
676
|
-
const interaction = createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
699
|
+
const interaction = createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
700
|
+
// invalidate is enough: renderLoop runs updatePanels once per dirty frame,
|
|
701
|
+
// calling it here doubled the min/max scan on every mouse event
|
|
702
|
+
invalidate, (p) => toggleAutoFocusY(p), panelManager, getContainerSize);
|
|
677
703
|
function updatePanels() {
|
|
678
|
-
const x1 = transform
|
|
679
|
-
const x2 = transform.offsetX + (canvas.width - 40) / transform.scaleX;
|
|
704
|
+
const [x1, x2] = visibleXRange(canvas.width, transform);
|
|
680
705
|
for (const p of panelManager.panels) {
|
|
681
706
|
if (p.autoFocusY) {
|
|
682
|
-
|
|
707
|
+
let minY = Infinity;
|
|
708
|
+
let maxY = -Infinity;
|
|
709
|
+
for (const ds of p.dataSets) {
|
|
710
|
+
const r = ds.getMinMaxInRange(x1, x2);
|
|
711
|
+
if (r.minY < minY)
|
|
712
|
+
minY = r.minY;
|
|
713
|
+
if (r.maxY > maxY)
|
|
714
|
+
maxY = r.maxY;
|
|
715
|
+
}
|
|
716
|
+
if (minY === Infinity || maxY === -Infinity) {
|
|
717
|
+
minY = 0;
|
|
718
|
+
maxY = 1;
|
|
719
|
+
}
|
|
683
720
|
p.verticalRange.minY = minY;
|
|
684
721
|
p.verticalRange.maxY = maxY;
|
|
685
722
|
}
|
|
@@ -695,12 +732,19 @@ export function createChartEngine(canvas) {
|
|
|
695
732
|
function renderLoop() {
|
|
696
733
|
if (destroyed)
|
|
697
734
|
return;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
735
|
+
const stamp = frameStamp();
|
|
736
|
+
if (stamp !== lastStamp) {
|
|
737
|
+
lastStamp = stamp;
|
|
738
|
+
needsRender = true;
|
|
739
|
+
}
|
|
740
|
+
if (needsRender) {
|
|
741
|
+
needsRender = false;
|
|
742
|
+
updatePanels();
|
|
743
|
+
const [xMin, xMax] = visibleXRange(canvas.width, transform);
|
|
744
|
+
const crosshair = interaction.getCrosshairPos();
|
|
745
|
+
for (const p of panelManager.panels) {
|
|
746
|
+
renderer.drawPanel(ctx, p, transform, { xMin, xMax }, crosshair, true);
|
|
747
|
+
}
|
|
704
748
|
}
|
|
705
749
|
animationFrameId = requestAnimationFrame(renderLoop);
|
|
706
750
|
}
|
|
@@ -712,8 +756,7 @@ export function createChartEngine(canvas) {
|
|
|
712
756
|
destroyed = true;
|
|
713
757
|
cancelAnimationFrame(animationFrameId);
|
|
714
758
|
interaction.destroy();
|
|
715
|
-
if (resizeObserver
|
|
716
|
-
resizeObserver.unobserve(containerEl);
|
|
759
|
+
if (resizeObserver) {
|
|
717
760
|
resizeObserver.disconnect();
|
|
718
761
|
}
|
|
719
762
|
containerEl = null;
|
|
@@ -731,6 +774,7 @@ export function createChartEngine(canvas) {
|
|
|
731
774
|
if (canvas.width !== w || canvas.height !== h) {
|
|
732
775
|
canvas.width = w;
|
|
733
776
|
canvas.height = h;
|
|
777
|
+
invalidate(); // changing canvas size clears the bitmap, so redraw is required
|
|
734
778
|
}
|
|
735
779
|
containerWidth = w;
|
|
736
780
|
containerHeight = h;
|
|
@@ -742,10 +786,14 @@ export function createChartEngine(canvas) {
|
|
|
742
786
|
resizeObserver.observe(containerEl);
|
|
743
787
|
}
|
|
744
788
|
function createDataSetFn(params) {
|
|
745
|
-
|
|
789
|
+
const ds = dataModel.addDataSet(params);
|
|
790
|
+
const addDataOrig = ds.addData;
|
|
791
|
+
ds.addData = (p) => { addDataOrig(p); invalidate(); };
|
|
792
|
+
return ds;
|
|
746
793
|
}
|
|
747
794
|
function addPanelFn(config) {
|
|
748
795
|
panelManager.addPanel(config);
|
|
796
|
+
invalidate();
|
|
749
797
|
}
|
|
750
798
|
return {
|
|
751
799
|
init,
|
|
@@ -760,7 +808,7 @@ export function createChartEngine(canvas) {
|
|
|
760
808
|
};
|
|
761
809
|
}
|
|
762
810
|
/**
|
|
763
|
-
*
|
|
811
|
+
* Data generation example
|
|
764
812
|
*/
|
|
765
813
|
export function generateIncrementalData(startX, count, startY, maxDelta) {
|
|
766
814
|
const arr = [];
|
|
@@ -777,9 +825,9 @@ export function generateIncrementalData(startX, count, startY, maxDelta) {
|
|
|
777
825
|
return arr;
|
|
778
826
|
}
|
|
779
827
|
/**
|
|
780
|
-
*
|
|
828
|
+
* MyChart React component example
|
|
781
829
|
*/
|
|
782
|
-
export const MyChartEngine = () => {
|
|
830
|
+
export const MyChartEngine = ({ style }) => {
|
|
783
831
|
const containerRef = useRef(null);
|
|
784
832
|
const canvasRef = useRef(null);
|
|
785
833
|
const engineRef = useRef(null);
|
|
@@ -792,7 +840,7 @@ export const MyChartEngine = () => {
|
|
|
792
840
|
engineRef.current = engine;
|
|
793
841
|
engine.attachToContainer(container);
|
|
794
842
|
engine.init();
|
|
795
|
-
//
|
|
843
|
+
// Create DataSets
|
|
796
844
|
const lineData = generateIncrementalData(0, 50, 100, 10);
|
|
797
845
|
const lineDS = engine.createDataSet({
|
|
798
846
|
id: 'line1',
|
|
@@ -807,8 +855,8 @@ export const MyChartEngine = () => {
|
|
|
807
855
|
data: barData,
|
|
808
856
|
style: { barColor: '#66aa66' }
|
|
809
857
|
});
|
|
810
|
-
//
|
|
811
|
-
//
|
|
858
|
+
// Add panels and specify heightPct
|
|
859
|
+
// The last panel automatically takes the remaining space up to 100%.
|
|
812
860
|
engine.addPanel({
|
|
813
861
|
id: 'mainPanel',
|
|
814
862
|
dataSets: [lineDS],
|
|
@@ -823,7 +871,7 @@ export const MyChartEngine = () => {
|
|
|
823
871
|
autoFocusY: true,
|
|
824
872
|
resizable: true
|
|
825
873
|
});
|
|
826
|
-
//
|
|
874
|
+
// Simulate adding data
|
|
827
875
|
let lineX = lineData[lineData.length - 1].x;
|
|
828
876
|
let lineY = lineData[lineData.length - 1].y;
|
|
829
877
|
let barX = barData[barData.length - 1].x;
|
|
@@ -841,7 +889,7 @@ export const MyChartEngine = () => {
|
|
|
841
889
|
barY = 0;
|
|
842
890
|
barX++;
|
|
843
891
|
barDS.addData({ x: barX, y: barY });
|
|
844
|
-
},
|
|
892
|
+
}, 50); // demo stream; 1ms flooded the dataset at max timer rate
|
|
845
893
|
return () => {
|
|
846
894
|
clearInterval(intervalId);
|
|
847
895
|
engine.destroy();
|
|
@@ -851,6 +899,7 @@ export const MyChartEngine = () => {
|
|
|
851
899
|
width: '100%',
|
|
852
900
|
height: '600px',
|
|
853
901
|
border: '1px solid #ccc',
|
|
854
|
-
position: 'relative'
|
|
902
|
+
position: 'relative',
|
|
903
|
+
...style
|
|
855
904
|
}, children: _jsx("canvas", { ref: canvasRef }) }));
|
|
856
905
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
1
2
|
import { CellClassParams } from "ag-grid-community";
|
|
2
3
|
import type { Theme, ThemeDefaultParams } from "ag-grid-community";
|
|
3
4
|
import { provideGlobalGridOptions } from "ag-grid-community";
|
|
@@ -17,4 +18,4 @@ export declare function StyleCSSHeadGridEdit(name: string, rules: string): void;
|
|
|
17
18
|
export declare function StyleCSSHeadGrid(): void;
|
|
18
19
|
export type tCallFuncAgGrid<T> = (params: CellClassParams & {
|
|
19
20
|
data: T;
|
|
20
|
-
}) =>
|
|
21
|
+
}) => CSSProperties | Record<string, any>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { colorSchemeDarkBlue, iconSetMaterial, provideGlobalGridOptions, themeAlpine } from "ag-grid-community";
|
|
2
2
|
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
|
3
3
|
export { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
|
4
|
+
import { tokens } from "./tokens";
|
|
4
5
|
// import 'ag-grid-community/styles/ag-grid.css';
|
|
5
6
|
// import 'ag-grid-community/styles/ag-theme-alpine.css';
|
|
6
7
|
// Register all community features
|
|
@@ -9,14 +10,7 @@ export function GridStyleDefault() {
|
|
|
9
10
|
const theme = themeAlpine
|
|
10
11
|
.withPart(colorSchemeDarkBlue)
|
|
11
12
|
.withPart(iconSetMaterial)
|
|
12
|
-
.withParams({
|
|
13
|
-
'fontFamily': 'Roboto',
|
|
14
|
-
'textColor': 'rgba(214,214,214,0.9)',
|
|
15
|
-
'tabTextColor': 'rgba(227,227,227,0.9)',
|
|
16
|
-
'fontSize': '10px',
|
|
17
|
-
'spacing': '3px',
|
|
18
|
-
'backgroundColor': 'rgb(19,24,33)'
|
|
19
|
-
});
|
|
13
|
+
.withParams({ ...tokens.grid });
|
|
20
14
|
// Mark all grids as using legacy themes
|
|
21
15
|
provideGlobalGridOptions({ theme: theme });
|
|
22
16
|
return { theme, provideGlobalGridOptions };
|
|
@@ -38,13 +32,17 @@ export const StyleGridDefault = {
|
|
|
38
32
|
};
|
|
39
33
|
export function StyleCSSHeadGridEdit(name, rules) {
|
|
40
34
|
let style = document.createElement('style');
|
|
41
|
-
style
|
|
42
|
-
document.getElementsByTagName('head')[0].appendChild(style);
|
|
35
|
+
document.head.appendChild(style);
|
|
43
36
|
style.sheet?.insertRule(name + "{" + rules + "}", 0);
|
|
44
37
|
}
|
|
38
|
+
let headGridStylesApplied = false;
|
|
45
39
|
export function StyleCSSHeadGrid() {
|
|
46
|
-
//
|
|
40
|
+
// idempotent: repeated calls used to append a new <style> to <head> every time
|
|
41
|
+
if (headGridStylesApplied)
|
|
42
|
+
return;
|
|
43
|
+
headGridStylesApplied = true;
|
|
44
|
+
// Reduce side padding for headers
|
|
47
45
|
StyleCSSHeadGridEdit('.ag-theme-alpine-dark .ag-theme-alpine .ag-header-cell, .ag-theme-alpine-dark .ag-header-group-cell', "padding-left: 3px; padding-right: 3px;");
|
|
48
|
-
//
|
|
46
|
+
// Center header content
|
|
49
47
|
StyleCSSHeadGridEdit('.ag-header-cell-label', 'justify-content: center');
|
|
50
48
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const tokens: {
|
|
2
|
+
readonly color: {
|
|
3
|
+
readonly bgDark: "#131821";
|
|
4
|
+
readonly bgLight: "#17202e";
|
|
5
|
+
readonly scrollbarTrack: "rgba(255, 255, 255, 0)";
|
|
6
|
+
readonly scrollbarThumb: "#1d262c";
|
|
7
|
+
readonly scrollbarThumbAlt: "#4c4562";
|
|
8
|
+
readonly textBase: "#c4c4c4";
|
|
9
|
+
readonly textTheme: "#5D9FFA";
|
|
10
|
+
readonly borderCommon: "rgb(50, 62, 71)";
|
|
11
|
+
};
|
|
12
|
+
readonly menu: {
|
|
13
|
+
readonly bgColor: "rgba(12, 12, 12, 0.91)";
|
|
14
|
+
readonly blur: "10px";
|
|
15
|
+
readonly border: "1px solid rgba(255, 255, 255, 0.41)";
|
|
16
|
+
readonly activeBackground: "rgba(255, 255, 255, 0.23)";
|
|
17
|
+
readonly itemColor: "#fff";
|
|
18
|
+
readonly itemHoverColor: "#101010";
|
|
19
|
+
readonly itemHoverBgColor: "#fff";
|
|
20
|
+
};
|
|
21
|
+
readonly font: {
|
|
22
|
+
readonly family: "Roboto";
|
|
23
|
+
readonly sizeBase: "12px";
|
|
24
|
+
};
|
|
25
|
+
readonly zIndex: {
|
|
26
|
+
/** CSS variable --wenay-z-modal; fallback in ModalProvider */
|
|
27
|
+
readonly modal: 9999;
|
|
28
|
+
};
|
|
29
|
+
/** ag-grid theme params shared by GridStyleDefault (legacy) and agGrid4 buildAgTheme */
|
|
30
|
+
readonly grid: {
|
|
31
|
+
readonly fontFamily: "Roboto";
|
|
32
|
+
readonly textColor: "rgba(214,214,214,0.9)";
|
|
33
|
+
readonly tabTextColor: "rgba(227,227,227,0.9)";
|
|
34
|
+
readonly fontSize: "10px";
|
|
35
|
+
readonly spacing: "3px";
|
|
36
|
+
readonly backgroundColor: "rgb(19,24,33)";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export type Tokens = typeof tokens;
|