wenay-react2 1.0.19 → 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 +10 -7
- 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 -107
- package/lib/common/src/components/Dnd/Resizable.js +3 -3
- package/lib/common/src/components/Input.js +3 -3
- package/lib/common/src/components/Menu/RightMenu.js +17 -18
- package/lib/common/src/components/Menu/StickerMenu.d.ts +1 -3
- 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 +34 -52
- 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 +6 -5
- package/lib/common/src/components/Parameters.js +8 -7
- package/lib/common/src/components/ParametersEngine.js +36 -59
- 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 -9
- 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 +77 -26
- package/lib/common/src/logs/logs.d.ts +5 -2
- package/lib/common/src/logs/logs.js +18 -34
- package/lib/common/src/logs/logs3.d.ts +9 -9
- package/lib/common/src/logs/logs3.js +48 -39
- 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 +49 -25
- package/lib/common/src/menu/menuMouse.d.ts +2 -1
- package/lib/common/src/menu/menuMouse.js +5 -4
- package/lib/common/src/menu/menuR.d.ts +4 -3
- package/lib/common/src/menu/menuR.js +42 -38
- 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 +189 -118
- 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 +25 -19
- 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 +32 -24
- 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 +15 -15
- package/lib/common/updateBy.d.ts +21 -2
- package/lib/common/updateBy.js +66 -131
- 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,21 +317,40 @@ 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;
|
|
317
|
-
|
|
326
|
+
let minPt = null;
|
|
327
|
+
let maxPt = null;
|
|
328
|
+
const flush = () => {
|
|
329
|
+
if (!minPt || !maxPt)
|
|
330
|
+
return;
|
|
331
|
+
const a = minPt.x <= maxPt.x ? minPt : maxPt;
|
|
332
|
+
const b = a === minPt ? maxPt : minPt;
|
|
333
|
+
result.push(a);
|
|
334
|
+
if (b !== a)
|
|
335
|
+
result.push(b);
|
|
336
|
+
};
|
|
337
|
+
for (let i = start; i < end; i++) {
|
|
338
|
+
const pt = data[i];
|
|
318
339
|
const px = Math.round(xToPixX(pt.x, transform, panel));
|
|
319
340
|
if (px !== lastPixX) {
|
|
320
|
-
|
|
341
|
+
flush();
|
|
342
|
+
minPt = pt;
|
|
343
|
+
maxPt = pt;
|
|
321
344
|
lastPixX = px;
|
|
322
345
|
}
|
|
346
|
+
else {
|
|
347
|
+
if (pt.y < minPt.y)
|
|
348
|
+
minPt = pt;
|
|
349
|
+
if (pt.y > maxPt.y)
|
|
350
|
+
maxPt = pt;
|
|
351
|
+
}
|
|
323
352
|
}
|
|
353
|
+
flush();
|
|
324
354
|
if (result.length < 2)
|
|
325
355
|
return;
|
|
326
356
|
ctx.save();
|
|
@@ -362,19 +392,18 @@ export function createRenderer() {
|
|
|
362
392
|
}
|
|
363
393
|
function drawBarChart(ctx, data, panel, transform, style) {
|
|
364
394
|
const barColor = style.barColor ?? '#66cc66';
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
const visible = data.filter((pt) => pt.x >= xMinVisible && pt.x <= xMaxVisible);
|
|
369
|
-
if (!visible.length)
|
|
395
|
+
const [xMinVisible, xMaxVisible] = visibleXRange(panel.width, transform);
|
|
396
|
+
const [start, end] = visibleRange(data, xMinVisible, xMaxVisible);
|
|
397
|
+
if (end === start)
|
|
370
398
|
return;
|
|
371
399
|
ctx.save();
|
|
372
400
|
ctx.fillStyle = barColor;
|
|
373
401
|
const barWidth = 5;
|
|
374
|
-
|
|
402
|
+
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
403
|
+
for (let i = start; i < end; i++) {
|
|
404
|
+
const pt = data[i];
|
|
375
405
|
const px = xToPixX(pt.x, transform, panel);
|
|
376
406
|
const py = yToPixY(pt.y, panel, transform);
|
|
377
|
-
const pyBase = yToPixY(panel.verticalRange.minY, panel, transform);
|
|
378
407
|
ctx.fillRect(px - barWidth / 2, py, barWidth, pyBase - py);
|
|
379
408
|
}
|
|
380
409
|
ctx.restore();
|
|
@@ -469,11 +498,14 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
469
498
|
const localX = e.clientX - rect.left;
|
|
470
499
|
const localY = e.clientY - rect.top;
|
|
471
500
|
crosshairPos = { x: localX, y: localY };
|
|
472
|
-
//
|
|
501
|
+
// Check the boundary between panels (for resize)
|
|
473
502
|
const panels = getPanels();
|
|
474
503
|
for (const p of panels) {
|
|
475
504
|
if (!p.resizable)
|
|
476
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;
|
|
477
509
|
const bottom = p.top + p.height;
|
|
478
510
|
if (Math.abs(localY - bottom) < 5) {
|
|
479
511
|
dragMode = DragMode.ResizePanel;
|
|
@@ -482,15 +514,15 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
482
514
|
return;
|
|
483
515
|
}
|
|
484
516
|
}
|
|
485
|
-
//
|
|
517
|
+
// Find panel
|
|
486
518
|
activePanel = findPanel(localX, localY, panels);
|
|
487
519
|
if (!activePanel) {
|
|
488
520
|
dragMode = DragMode.None;
|
|
489
521
|
return;
|
|
490
522
|
}
|
|
491
|
-
const axisRightX = activePanel.left + activePanel.width -
|
|
523
|
+
const axisRightX = activePanel.left + activePanel.width - AXIS_THICKNESS;
|
|
492
524
|
if (localX >= axisRightX) {
|
|
493
|
-
//
|
|
525
|
+
// Scale by Y if autoFocusY=false
|
|
494
526
|
dragMode = DragMode.ScaleY;
|
|
495
527
|
}
|
|
496
528
|
else {
|
|
@@ -513,11 +545,13 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
513
545
|
lastX = e.clientX;
|
|
514
546
|
lastY = e.clientY;
|
|
515
547
|
const t = getTransform();
|
|
516
|
-
//
|
|
548
|
+
// Panel resize mode
|
|
517
549
|
if (dragMode === DragMode.ResizePanel && resizingPanelId) {
|
|
518
550
|
// deltaPx = dy
|
|
519
|
-
const { height: cH } = getContainerSize();
|
|
551
|
+
const { width: cW, height: cH } = getContainerSize();
|
|
520
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);
|
|
521
555
|
onTransformChanged();
|
|
522
556
|
return;
|
|
523
557
|
}
|
|
@@ -540,12 +574,6 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
540
574
|
}
|
|
541
575
|
onTransformChanged();
|
|
542
576
|
}
|
|
543
|
-
function onMouseUp(e) {
|
|
544
|
-
isMouseDown = false;
|
|
545
|
-
dragMode = DragMode.None;
|
|
546
|
-
activePanel = null;
|
|
547
|
-
resizingPanelId = null;
|
|
548
|
-
}
|
|
549
577
|
function onGlobalMouseUp(e) {
|
|
550
578
|
if (isMouseDown) {
|
|
551
579
|
isMouseDown = false;
|
|
@@ -574,7 +602,7 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
574
602
|
const p = findPanel(localX, localY, getPanels());
|
|
575
603
|
if (!p)
|
|
576
604
|
return;
|
|
577
|
-
const axisRightX = p.left + p.width -
|
|
605
|
+
const axisRightX = p.left + p.width - AXIS_THICKNESS;
|
|
578
606
|
if (localX >= axisRightX) {
|
|
579
607
|
onToggleAutoFocusY(p);
|
|
580
608
|
onTransformChanged();
|
|
@@ -618,7 +646,7 @@ export function createInteraction(canvas, getTransform, setTransform, getPanels,
|
|
|
618
646
|
};
|
|
619
647
|
}
|
|
620
648
|
/**
|
|
621
|
-
*
|
|
649
|
+
* Engine factory function
|
|
622
650
|
*/
|
|
623
651
|
export function createChartEngine(canvas) {
|
|
624
652
|
const ctx = canvas.getContext('2d');
|
|
@@ -637,7 +665,22 @@ export function createChartEngine(canvas) {
|
|
|
637
665
|
let resizeObserver = null;
|
|
638
666
|
let containerWidth = 0;
|
|
639
667
|
let containerHeight = 0;
|
|
640
|
-
|
|
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
|
+
}
|
|
641
684
|
function setTransform(t) {
|
|
642
685
|
transform = t;
|
|
643
686
|
}
|
|
@@ -653,13 +696,27 @@ export function createChartEngine(canvas) {
|
|
|
653
696
|
function getContainerSize() {
|
|
654
697
|
return { width: containerWidth, height: containerHeight };
|
|
655
698
|
}
|
|
656
|
-
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);
|
|
657
703
|
function updatePanels() {
|
|
658
|
-
const x1 = transform
|
|
659
|
-
const x2 = transform.offsetX + (canvas.width - 40) / transform.scaleX;
|
|
704
|
+
const [x1, x2] = visibleXRange(canvas.width, transform);
|
|
660
705
|
for (const p of panelManager.panels) {
|
|
661
706
|
if (p.autoFocusY) {
|
|
662
|
-
|
|
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
|
+
}
|
|
663
720
|
p.verticalRange.minY = minY;
|
|
664
721
|
p.verticalRange.maxY = maxY;
|
|
665
722
|
}
|
|
@@ -675,12 +732,19 @@ export function createChartEngine(canvas) {
|
|
|
675
732
|
function renderLoop() {
|
|
676
733
|
if (destroyed)
|
|
677
734
|
return;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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
|
+
}
|
|
684
748
|
}
|
|
685
749
|
animationFrameId = requestAnimationFrame(renderLoop);
|
|
686
750
|
}
|
|
@@ -692,14 +756,15 @@ export function createChartEngine(canvas) {
|
|
|
692
756
|
destroyed = true;
|
|
693
757
|
cancelAnimationFrame(animationFrameId);
|
|
694
758
|
interaction.destroy();
|
|
695
|
-
if (resizeObserver
|
|
696
|
-
resizeObserver.unobserve(containerEl);
|
|
759
|
+
if (resizeObserver) {
|
|
697
760
|
resizeObserver.disconnect();
|
|
698
761
|
}
|
|
699
762
|
containerEl = null;
|
|
700
763
|
resizeObserver = null;
|
|
701
764
|
}
|
|
702
765
|
function attachToContainer(container) {
|
|
766
|
+
if (resizeObserver)
|
|
767
|
+
resizeObserver.disconnect();
|
|
703
768
|
containerEl = container;
|
|
704
769
|
resizeObserver = new ResizeObserver((entries) => {
|
|
705
770
|
for (const entry of entries) {
|
|
@@ -709,6 +774,7 @@ export function createChartEngine(canvas) {
|
|
|
709
774
|
if (canvas.width !== w || canvas.height !== h) {
|
|
710
775
|
canvas.width = w;
|
|
711
776
|
canvas.height = h;
|
|
777
|
+
invalidate(); // changing canvas size clears the bitmap, so redraw is required
|
|
712
778
|
}
|
|
713
779
|
containerWidth = w;
|
|
714
780
|
containerHeight = h;
|
|
@@ -720,10 +786,14 @@ export function createChartEngine(canvas) {
|
|
|
720
786
|
resizeObserver.observe(containerEl);
|
|
721
787
|
}
|
|
722
788
|
function createDataSetFn(params) {
|
|
723
|
-
|
|
789
|
+
const ds = dataModel.addDataSet(params);
|
|
790
|
+
const addDataOrig = ds.addData;
|
|
791
|
+
ds.addData = (p) => { addDataOrig(p); invalidate(); };
|
|
792
|
+
return ds;
|
|
724
793
|
}
|
|
725
794
|
function addPanelFn(config) {
|
|
726
795
|
panelManager.addPanel(config);
|
|
796
|
+
invalidate();
|
|
727
797
|
}
|
|
728
798
|
return {
|
|
729
799
|
init,
|
|
@@ -738,7 +808,7 @@ export function createChartEngine(canvas) {
|
|
|
738
808
|
};
|
|
739
809
|
}
|
|
740
810
|
/**
|
|
741
|
-
*
|
|
811
|
+
* Data generation example
|
|
742
812
|
*/
|
|
743
813
|
export function generateIncrementalData(startX, count, startY, maxDelta) {
|
|
744
814
|
const arr = [];
|
|
@@ -755,9 +825,9 @@ export function generateIncrementalData(startX, count, startY, maxDelta) {
|
|
|
755
825
|
return arr;
|
|
756
826
|
}
|
|
757
827
|
/**
|
|
758
|
-
*
|
|
828
|
+
* MyChart React component example
|
|
759
829
|
*/
|
|
760
|
-
export const MyChartEngine = () => {
|
|
830
|
+
export const MyChartEngine = ({ style }) => {
|
|
761
831
|
const containerRef = useRef(null);
|
|
762
832
|
const canvasRef = useRef(null);
|
|
763
833
|
const engineRef = useRef(null);
|
|
@@ -770,7 +840,7 @@ export const MyChartEngine = () => {
|
|
|
770
840
|
engineRef.current = engine;
|
|
771
841
|
engine.attachToContainer(container);
|
|
772
842
|
engine.init();
|
|
773
|
-
//
|
|
843
|
+
// Create DataSets
|
|
774
844
|
const lineData = generateIncrementalData(0, 50, 100, 10);
|
|
775
845
|
const lineDS = engine.createDataSet({
|
|
776
846
|
id: 'line1',
|
|
@@ -785,8 +855,8 @@ export const MyChartEngine = () => {
|
|
|
785
855
|
data: barData,
|
|
786
856
|
style: { barColor: '#66aa66' }
|
|
787
857
|
});
|
|
788
|
-
//
|
|
789
|
-
//
|
|
858
|
+
// Add panels and specify heightPct
|
|
859
|
+
// The last panel automatically takes the remaining space up to 100%.
|
|
790
860
|
engine.addPanel({
|
|
791
861
|
id: 'mainPanel',
|
|
792
862
|
dataSets: [lineDS],
|
|
@@ -801,7 +871,7 @@ export const MyChartEngine = () => {
|
|
|
801
871
|
autoFocusY: true,
|
|
802
872
|
resizable: true
|
|
803
873
|
});
|
|
804
|
-
//
|
|
874
|
+
// Simulate adding data
|
|
805
875
|
let lineX = lineData[lineData.length - 1].x;
|
|
806
876
|
let lineY = lineData[lineData.length - 1].y;
|
|
807
877
|
let barX = barData[barData.length - 1].x;
|
|
@@ -819,7 +889,7 @@ export const MyChartEngine = () => {
|
|
|
819
889
|
barY = 0;
|
|
820
890
|
barX++;
|
|
821
891
|
barDS.addData({ x: barX, y: barY });
|
|
822
|
-
},
|
|
892
|
+
}, 50); // demo stream; 1ms flooded the dataset at max timer rate
|
|
823
893
|
return () => {
|
|
824
894
|
clearInterval(intervalId);
|
|
825
895
|
engine.destroy();
|
|
@@ -829,6 +899,7 @@ export const MyChartEngine = () => {
|
|
|
829
899
|
width: '100%',
|
|
830
900
|
height: '600px',
|
|
831
901
|
border: '1px solid #ccc',
|
|
832
|
-
position: 'relative'
|
|
902
|
+
position: 'relative',
|
|
903
|
+
...style
|
|
833
904
|
}, children: _jsx("canvas", { ref: canvasRef }) }));
|
|
834
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
|
}
|