ink-hud 0.1.0
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/LICENSE +21 -0
- package/README.md +284 -0
- package/dist/index.cjs +3012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1510 -0
- package/dist/index.d.ts +1510 -0
- package/dist/index.js +2951 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2951 @@
|
|
|
1
|
+
import React6, { createContext, useMemo, useContext, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import tinygradient from 'tinygradient';
|
|
4
|
+
import { useStdout, Box, Text, useFocus, useInput } from 'ink';
|
|
5
|
+
|
|
6
|
+
// src/core/renderer.ts
|
|
7
|
+
var Renderer = class {
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Common implementations shared by all renderers
|
|
10
|
+
// ============================================================
|
|
11
|
+
/**
|
|
12
|
+
* Create a blank Canvas
|
|
13
|
+
* @param width - Canvas width (pixels)
|
|
14
|
+
* @param height - Canvas height (pixels)
|
|
15
|
+
* @returns 2D pixel array
|
|
16
|
+
*/
|
|
17
|
+
createCanvas(width, height) {
|
|
18
|
+
return Array.from(
|
|
19
|
+
{ length: height },
|
|
20
|
+
() => Array.from({ length: width }, () => ({ active: false }))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set a single pixel on the Canvas
|
|
25
|
+
* @param canvas - Canvas
|
|
26
|
+
* @param x - x coordinate
|
|
27
|
+
* @param y - y coordinate
|
|
28
|
+
* @param pixel - Pixel properties (active, color, etc.)
|
|
29
|
+
*/
|
|
30
|
+
setPixel(canvas, x, y, pixel = { active: true }) {
|
|
31
|
+
const row = canvas[y];
|
|
32
|
+
if (y >= 0 && y < canvas.length && row && x >= 0 && x < row.length) {
|
|
33
|
+
const current = row[x];
|
|
34
|
+
if (current) {
|
|
35
|
+
Object.assign(current, pixel);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Draw line segments (Bresenham's algorithm)
|
|
41
|
+
*/
|
|
42
|
+
drawLine(canvas, x0, y0, x1, y1, pixel = { active: true }) {
|
|
43
|
+
const dx = Math.abs(x1 - x0);
|
|
44
|
+
const dy = Math.abs(y1 - y0);
|
|
45
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
46
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
47
|
+
let err = dx - dy;
|
|
48
|
+
let x = Math.round(x0);
|
|
49
|
+
let y = Math.round(y0);
|
|
50
|
+
const endX = Math.round(x1);
|
|
51
|
+
const endY = Math.round(y1);
|
|
52
|
+
while (true) {
|
|
53
|
+
this.setPixel(canvas, x, y, pixel);
|
|
54
|
+
if (x === endX && y === endY) break;
|
|
55
|
+
const e2 = 2 * err;
|
|
56
|
+
if (e2 > -dy) {
|
|
57
|
+
err -= dy;
|
|
58
|
+
x += sx;
|
|
59
|
+
}
|
|
60
|
+
if (e2 < dx) {
|
|
61
|
+
err += dx;
|
|
62
|
+
y += sy;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Draw circle or ring (Midpoint Circle algorithm)
|
|
68
|
+
*/
|
|
69
|
+
drawCircle(canvas, centerX, centerY, radius, filled = false, pixel = { active: true }) {
|
|
70
|
+
if (filled) {
|
|
71
|
+
for (let y = -radius; y <= radius; y++) {
|
|
72
|
+
const width = Math.sqrt(radius * radius - y * y);
|
|
73
|
+
for (let x = -width; x <= width; x++) {
|
|
74
|
+
this.setPixel(canvas, Math.round(centerX + x), Math.round(centerY + y), pixel);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
let x = 0;
|
|
79
|
+
let y = radius;
|
|
80
|
+
let d = 1 - radius;
|
|
81
|
+
while (x <= y) {
|
|
82
|
+
this.setPixel(canvas, centerX + x, centerY + y, pixel);
|
|
83
|
+
this.setPixel(canvas, centerX - x, centerY + y, pixel);
|
|
84
|
+
this.setPixel(canvas, centerX + x, centerY - y, pixel);
|
|
85
|
+
this.setPixel(canvas, centerX - x, centerY - y, pixel);
|
|
86
|
+
this.setPixel(canvas, centerX + y, centerY + x, pixel);
|
|
87
|
+
this.setPixel(canvas, centerX - y, centerY + x, pixel);
|
|
88
|
+
this.setPixel(canvas, centerX + y, centerY - x, pixel);
|
|
89
|
+
this.setPixel(canvas, centerX - y, centerY - x, pixel);
|
|
90
|
+
x++;
|
|
91
|
+
if (d < 0) {
|
|
92
|
+
d += 2 * x + 1;
|
|
93
|
+
} else {
|
|
94
|
+
y--;
|
|
95
|
+
d += 2 * (x - y) + 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Draw arc (Parametric equation)
|
|
102
|
+
*/
|
|
103
|
+
drawArc(canvas, centerX, centerY, radius, startAngle, endAngle, thickness = 1, pixel = { active: true }) {
|
|
104
|
+
let start = startAngle;
|
|
105
|
+
let end = endAngle;
|
|
106
|
+
if (start > end) {
|
|
107
|
+
[start, end] = [end, start];
|
|
108
|
+
}
|
|
109
|
+
const angleStep = 1 / (radius * 2);
|
|
110
|
+
for (let r = Math.max(0, radius - thickness + 1); r <= radius; r++) {
|
|
111
|
+
for (let theta = start; theta <= end; theta += angleStep) {
|
|
112
|
+
const x = centerX + r * Math.cos(theta);
|
|
113
|
+
const y = centerY + r * Math.sin(theta);
|
|
114
|
+
this.setPixel(canvas, Math.round(x), Math.round(y), pixel);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Draw rectangle
|
|
120
|
+
*/
|
|
121
|
+
drawRect(canvas, x, y, width, height, filled = false, pixel = { active: true }) {
|
|
122
|
+
if (filled) {
|
|
123
|
+
for (let py = y; py < y + height; py++) {
|
|
124
|
+
for (let px = x; px < x + width; px++) {
|
|
125
|
+
this.setPixel(canvas, px, py, pixel);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
for (let px = x; px < x + width; px++) {
|
|
130
|
+
this.setPixel(canvas, px, y + height - 1, pixel);
|
|
131
|
+
}
|
|
132
|
+
for (let py = y; py < y + height; py++) {
|
|
133
|
+
this.setPixel(canvas, x, py, pixel);
|
|
134
|
+
}
|
|
135
|
+
for (let py = y; py < y + height; py++) {
|
|
136
|
+
this.setPixel(canvas, x + width - 1, py, pixel);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ============================================================
|
|
141
|
+
// Helper methods
|
|
142
|
+
// ============================================================
|
|
143
|
+
/**
|
|
144
|
+
* Get renderer resolution
|
|
145
|
+
*/
|
|
146
|
+
getResolution() {
|
|
147
|
+
return this.getMetadata().resolution;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get renderer name
|
|
151
|
+
*/
|
|
152
|
+
getName() {
|
|
153
|
+
return this.getMetadata().name;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Calculate number of character rows and columns needed to render specified dimensions
|
|
157
|
+
*/
|
|
158
|
+
calculateCharDimensions(pixelWidth, pixelHeight) {
|
|
159
|
+
const res = this.getResolution();
|
|
160
|
+
return {
|
|
161
|
+
cols: Math.ceil(pixelWidth / res.horizontal),
|
|
162
|
+
rows: Math.ceil(pixelHeight / res.vertical)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/core/braille.ts
|
|
168
|
+
var BRAILLE_BASE = 10240;
|
|
169
|
+
var DOT_WEIGHTS = [1, 2, 4, 8, 16, 32, 64, 128];
|
|
170
|
+
var BrailleRenderer = class extends Renderer {
|
|
171
|
+
getMetadata() {
|
|
172
|
+
return {
|
|
173
|
+
name: "braille",
|
|
174
|
+
displayName: "Braille",
|
|
175
|
+
description: "Braille character (\u28FF) 2x4 matrix, 8x resolution",
|
|
176
|
+
resolution: {
|
|
177
|
+
horizontal: 2,
|
|
178
|
+
vertical: 4
|
|
179
|
+
},
|
|
180
|
+
requiresUtf8: true,
|
|
181
|
+
requiresUnicode: true,
|
|
182
|
+
minScore: 80
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// ============================================================
|
|
186
|
+
// Braille-specific private methods
|
|
187
|
+
// ============================================================
|
|
188
|
+
createBrailleChar(dots) {
|
|
189
|
+
let code = BRAILLE_BASE;
|
|
190
|
+
for (let i = 0; i < 8 && i < DOT_WEIGHTS.length; i++) {
|
|
191
|
+
if (dots[i]) {
|
|
192
|
+
const weight = DOT_WEIGHTS[i];
|
|
193
|
+
if (weight !== void 0) {
|
|
194
|
+
code += weight;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return String.fromCharCode(code);
|
|
199
|
+
}
|
|
200
|
+
pixelToDotIndex(x, y) {
|
|
201
|
+
if (x === 0) {
|
|
202
|
+
return y === 3 ? 6 : y;
|
|
203
|
+
}
|
|
204
|
+
return y === 3 ? 7 : y + 3;
|
|
205
|
+
}
|
|
206
|
+
fillBrailleDots(pixels, cx, cy, width, height) {
|
|
207
|
+
const dots = Array(8).fill(false);
|
|
208
|
+
for (let py = 0; py < 4; py++) {
|
|
209
|
+
for (let px = 0; px < 2; px++) {
|
|
210
|
+
const pixelY = cy * 4 + py;
|
|
211
|
+
const pixelX = cx * 2 + px;
|
|
212
|
+
if (pixelY < height && pixelX < width) {
|
|
213
|
+
dots[this.pixelToDotIndex(px, py)] = pixels[pixelY]?.[pixelX]?.active ?? false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return dots;
|
|
218
|
+
}
|
|
219
|
+
resolveColor(pixels, cx, cy, width, height) {
|
|
220
|
+
const counts = /* @__PURE__ */ new Map();
|
|
221
|
+
for (let py = 0; py < 4; py++) {
|
|
222
|
+
for (let px = 0; px < 2; px++) {
|
|
223
|
+
const pixelY = cy * 4 + py;
|
|
224
|
+
const pixelX = cx * 2 + px;
|
|
225
|
+
if (pixelY < height && pixelX < width) {
|
|
226
|
+
const pixel = pixels[pixelY]?.[pixelX];
|
|
227
|
+
if (pixel?.active && pixel.color) {
|
|
228
|
+
counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (counts.size === 0) return void 0;
|
|
234
|
+
let maxCount = 0;
|
|
235
|
+
let dominantColor;
|
|
236
|
+
for (const [color, count] of counts) {
|
|
237
|
+
if (count > maxCount) {
|
|
238
|
+
maxCount = count;
|
|
239
|
+
dominantColor = color;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return dominantColor;
|
|
243
|
+
}
|
|
244
|
+
// ============================================================
|
|
245
|
+
// Methods that subclasses must implement
|
|
246
|
+
// ============================================================
|
|
247
|
+
renderCanvas(pixels, width, height) {
|
|
248
|
+
const charWidth = Math.ceil(width / 2);
|
|
249
|
+
const charHeight = Math.ceil(height / 4);
|
|
250
|
+
const lines = [];
|
|
251
|
+
for (let cy = 0; cy < charHeight; cy++) {
|
|
252
|
+
const lineSegments = [];
|
|
253
|
+
let buffer = "";
|
|
254
|
+
let bufferColor;
|
|
255
|
+
for (let cx = 0; cx < charWidth; cx++) {
|
|
256
|
+
const dots = this.fillBrailleDots(pixels, cx, cy, width, height);
|
|
257
|
+
const char = this.createBrailleChar(dots);
|
|
258
|
+
const color = this.resolveColor(pixels, cx, cy, width, height);
|
|
259
|
+
if (buffer && bufferColor !== color) {
|
|
260
|
+
lineSegments.push(
|
|
261
|
+
bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
|
|
262
|
+
);
|
|
263
|
+
buffer = "";
|
|
264
|
+
}
|
|
265
|
+
bufferColor = color;
|
|
266
|
+
buffer += char;
|
|
267
|
+
}
|
|
268
|
+
if (buffer) {
|
|
269
|
+
lineSegments.push(
|
|
270
|
+
bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
lines.push(lineSegments);
|
|
274
|
+
}
|
|
275
|
+
return lines;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/core/block.ts
|
|
280
|
+
var BlockRenderer = class _BlockRenderer extends Renderer {
|
|
281
|
+
// 9x9 Lookup table: [leftHeight][rightHeight] -> char
|
|
282
|
+
// Index 0-8 represents filled pixel height of column
|
|
283
|
+
static BLOCK_LUT = [
|
|
284
|
+
// R=0 1 2 3 4 5 6 7 8 (L Row Index)
|
|
285
|
+
[" ", "\u2597", "\u2597", "\u2597", "\u2597", "\u2597", "\u2597", "\u2590", "\u2590"],
|
|
286
|
+
// L=0
|
|
287
|
+
["\u2596", "\u2582", "\u2582", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585"],
|
|
288
|
+
// L=1: Avg 1-1.5 -> ▂
|
|
289
|
+
["\u2596", "\u2582", "\u2582", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585"],
|
|
290
|
+
// L=2
|
|
291
|
+
["\u2596", "\u2583", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585", "\u2585"],
|
|
292
|
+
// L=3: Avg 1.5-3.5
|
|
293
|
+
["\u2596", "\u2583", "\u2583", "\u2584", "\u2584", "\u2585", "\u2585", "\u2586", "\u2586"],
|
|
294
|
+
// L=4
|
|
295
|
+
["\u2596", "\u2584", "\u2584", "\u2585", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587"],
|
|
296
|
+
// L=5
|
|
297
|
+
["\u2596", "\u2584", "\u2584", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587"],
|
|
298
|
+
// L=6
|
|
299
|
+
["\u258C", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587", "\u2587", "\u2588"],
|
|
300
|
+
// L=7
|
|
301
|
+
["\u258C", "\u2585", "\u2585", "\u2586", "\u2586", "\u2587", "\u2587", "\u2588", "\u2588"]
|
|
302
|
+
// L=8
|
|
303
|
+
];
|
|
304
|
+
getMetadata() {
|
|
305
|
+
return {
|
|
306
|
+
name: "block",
|
|
307
|
+
displayName: "Block Elements",
|
|
308
|
+
description: "Block Elements character (\u2588) 2x8 matrix, vertical 8x resolution",
|
|
309
|
+
resolution: {
|
|
310
|
+
horizontal: 2,
|
|
311
|
+
vertical: 8
|
|
312
|
+
},
|
|
313
|
+
requiresUtf8: true,
|
|
314
|
+
requiresUnicode: true,
|
|
315
|
+
minScore: 30
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
// ============================================================
|
|
319
|
+
// Block-specific private methods
|
|
320
|
+
// ============================================================
|
|
321
|
+
/**
|
|
322
|
+
* Determine primary color
|
|
323
|
+
*/
|
|
324
|
+
resolveColor(pixels, startX, startY, width, height) {
|
|
325
|
+
const counts = /* @__PURE__ */ new Map();
|
|
326
|
+
for (let py = 0; py < 8; py++) {
|
|
327
|
+
for (let px = 0; px < 2; px++) {
|
|
328
|
+
const y = startY + py;
|
|
329
|
+
const x = startX + px;
|
|
330
|
+
if (y >= height || x >= width) continue;
|
|
331
|
+
const pixel = pixels[y]?.[x];
|
|
332
|
+
if (pixel?.active && pixel.color) {
|
|
333
|
+
counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (counts.size === 0) return void 0;
|
|
338
|
+
let maxCount = 0;
|
|
339
|
+
let dominantColor;
|
|
340
|
+
for (const [color, count] of counts) {
|
|
341
|
+
if (count > maxCount) {
|
|
342
|
+
maxCount = count;
|
|
343
|
+
dominantColor = color;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return dominantColor;
|
|
347
|
+
}
|
|
348
|
+
// ============================================================
|
|
349
|
+
// Methods that subclasses must implement
|
|
350
|
+
// ============================================================
|
|
351
|
+
renderCanvas(pixels, width, height) {
|
|
352
|
+
const charWidth = Math.ceil(width / 2);
|
|
353
|
+
const charHeight = Math.ceil(height / 8);
|
|
354
|
+
const lines = [];
|
|
355
|
+
for (let cy = 0; cy < charHeight; cy++) {
|
|
356
|
+
const lineSegments = [];
|
|
357
|
+
let buffer = "";
|
|
358
|
+
let bufferFg;
|
|
359
|
+
let bufferBg;
|
|
360
|
+
let bufferColorKey;
|
|
361
|
+
for (let cx = 0; cx < charWidth; cx++) {
|
|
362
|
+
const startY = cy * 8;
|
|
363
|
+
const leftX = cx * 2;
|
|
364
|
+
const rightX = cx * 2 + 1;
|
|
365
|
+
const analyzeColumn = (colX) => {
|
|
366
|
+
let count = 0;
|
|
367
|
+
let sumY = 0;
|
|
368
|
+
for (let py = 0; py < 8; py++) {
|
|
369
|
+
const y = startY + py;
|
|
370
|
+
if (y >= height) break;
|
|
371
|
+
if (pixels[y]?.[colX]?.active) {
|
|
372
|
+
count++;
|
|
373
|
+
sumY += py;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const gravity = count > 0 ? sumY / count : 3.5;
|
|
377
|
+
return { count, gravity };
|
|
378
|
+
};
|
|
379
|
+
const left = analyzeColumn(leftX);
|
|
380
|
+
const right = analyzeColumn(rightX);
|
|
381
|
+
const isLeftTop = left.count > 0 && left.gravity < 3.5;
|
|
382
|
+
const isRightTop = right.count > 0 && right.gravity < 3.5;
|
|
383
|
+
const useInverse = isLeftTop && (isRightTop || right.count === 0) || isRightTop && (isLeftTop || left.count === 0);
|
|
384
|
+
let char = " ";
|
|
385
|
+
let isInverted = false;
|
|
386
|
+
const pixelColor = this.resolveColor(pixels, leftX, startY, width, height);
|
|
387
|
+
if (useInverse) {
|
|
388
|
+
const invLeft = 8 - left.count;
|
|
389
|
+
const invRight = 8 - right.count;
|
|
390
|
+
char = _BlockRenderer.BLOCK_LUT[invLeft]?.[invRight] ?? " ";
|
|
391
|
+
isInverted = true;
|
|
392
|
+
} else {
|
|
393
|
+
const finalLeft = Math.min(8, Math.max(0, left.count));
|
|
394
|
+
const finalRight = Math.min(8, Math.max(0, right.count));
|
|
395
|
+
char = _BlockRenderer.BLOCK_LUT[finalLeft]?.[finalRight] ?? " ";
|
|
396
|
+
}
|
|
397
|
+
let fgColor;
|
|
398
|
+
let bgColor;
|
|
399
|
+
if (char !== " ") {
|
|
400
|
+
if (isInverted) {
|
|
401
|
+
fgColor = "black";
|
|
402
|
+
bgColor = pixelColor;
|
|
403
|
+
} else {
|
|
404
|
+
fgColor = pixelColor;
|
|
405
|
+
bgColor = void 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const colorKey = `${fgColor}|${bgColor}`;
|
|
409
|
+
if (buffer && bufferColorKey !== colorKey) {
|
|
410
|
+
lineSegments.push({
|
|
411
|
+
text: buffer,
|
|
412
|
+
...bufferFg ? { color: bufferFg } : {},
|
|
413
|
+
...bufferBg ? { backgroundColor: bufferBg } : {}
|
|
414
|
+
});
|
|
415
|
+
buffer = "";
|
|
416
|
+
}
|
|
417
|
+
bufferColorKey = colorKey;
|
|
418
|
+
bufferFg = fgColor;
|
|
419
|
+
bufferBg = bgColor;
|
|
420
|
+
buffer += char;
|
|
421
|
+
}
|
|
422
|
+
if (buffer) {
|
|
423
|
+
lineSegments.push({
|
|
424
|
+
text: buffer,
|
|
425
|
+
...bufferFg ? { color: bufferFg } : {},
|
|
426
|
+
...bufferBg ? { backgroundColor: bufferBg } : {}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
lines.push(lineSegments);
|
|
430
|
+
}
|
|
431
|
+
return lines;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/core/ascii.ts
|
|
436
|
+
var AsciiRenderer = class extends Renderer {
|
|
437
|
+
getMetadata() {
|
|
438
|
+
return {
|
|
439
|
+
name: "ascii",
|
|
440
|
+
displayName: "ASCII",
|
|
441
|
+
description: "ASCII character (. _ - ' / \\ |) 1x3 resolution, maximum compatibility",
|
|
442
|
+
resolution: {
|
|
443
|
+
horizontal: 1,
|
|
444
|
+
vertical: 3
|
|
445
|
+
},
|
|
446
|
+
requiresUtf8: false,
|
|
447
|
+
requiresUnicode: false,
|
|
448
|
+
minScore: 0
|
|
449
|
+
// No requirements, usable in any terminal
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
// ============================================================
|
|
453
|
+
// ASCII-specific private methods
|
|
454
|
+
// ============================================================
|
|
455
|
+
/**
|
|
456
|
+
* Check if the pixel at the specified position is set
|
|
457
|
+
*/
|
|
458
|
+
isPixelSet(pixels, x, y) {
|
|
459
|
+
const row = pixels[y];
|
|
460
|
+
if (!row) return false;
|
|
461
|
+
return row[x]?.active ?? false;
|
|
462
|
+
}
|
|
463
|
+
getCellPositions(pixels, x, baseY) {
|
|
464
|
+
const positions = [];
|
|
465
|
+
for (let offset = 0; offset < 3; offset++) {
|
|
466
|
+
if (this.isPixelSet(pixels, x, baseY + offset)) {
|
|
467
|
+
positions.push(offset);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return positions;
|
|
471
|
+
}
|
|
472
|
+
getNeighborPosition(pixels, x, baseY) {
|
|
473
|
+
const positions = this.getCellPositions(pixels, x, baseY);
|
|
474
|
+
if (positions.length === 0) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
const sum = positions.reduce((total, value) => total + value, 0);
|
|
478
|
+
return sum / positions.length;
|
|
479
|
+
}
|
|
480
|
+
selectMultiPixelChar(hasAnyLeft, hasAnyRight) {
|
|
481
|
+
return hasAnyLeft || hasAnyRight ? "+" : "|";
|
|
482
|
+
}
|
|
483
|
+
selectHorizontalChar(pos) {
|
|
484
|
+
if (pos === 0) {
|
|
485
|
+
return "'";
|
|
486
|
+
}
|
|
487
|
+
if (pos === 1) {
|
|
488
|
+
return "-";
|
|
489
|
+
}
|
|
490
|
+
return "_";
|
|
491
|
+
}
|
|
492
|
+
selectDiagonalChar(params) {
|
|
493
|
+
const { pos, hasAnyLeft, hasAnyRight, leftPos, rightPos } = params;
|
|
494
|
+
if (hasAnyLeft && leftPos !== null && leftPos !== pos) {
|
|
495
|
+
return pos < leftPos ? "/" : "\\";
|
|
496
|
+
}
|
|
497
|
+
if (hasAnyRight && rightPos !== null && rightPos !== pos) {
|
|
498
|
+
return pos > rightPos ? "/" : "\\";
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
selectIsolatedChar(pos) {
|
|
503
|
+
if (pos === 0) {
|
|
504
|
+
return '"';
|
|
505
|
+
}
|
|
506
|
+
if (pos === 1) {
|
|
507
|
+
return "+";
|
|
508
|
+
}
|
|
509
|
+
return ".";
|
|
510
|
+
}
|
|
511
|
+
selectSinglePixelChar(params) {
|
|
512
|
+
const { pos, hasSameHorizontal, hasAnyLeft, hasAnyRight, leftPos, rightPos } = params;
|
|
513
|
+
if (hasSameHorizontal) {
|
|
514
|
+
return this.selectHorizontalChar(pos);
|
|
515
|
+
}
|
|
516
|
+
const diagonal = this.selectDiagonalChar({
|
|
517
|
+
pos,
|
|
518
|
+
hasAnyLeft,
|
|
519
|
+
hasAnyRight,
|
|
520
|
+
leftPos,
|
|
521
|
+
rightPos
|
|
522
|
+
});
|
|
523
|
+
if (diagonal) {
|
|
524
|
+
return diagonal;
|
|
525
|
+
}
|
|
526
|
+
return this.selectIsolatedChar(pos);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Intelligently select ASCII character
|
|
530
|
+
* Select appropriate character based on 1x3 vertical pixels and adjacent column connections
|
|
531
|
+
*/
|
|
532
|
+
selectAsciiChar(pixels, x, baseY) {
|
|
533
|
+
const positions = this.getCellPositions(pixels, x, baseY);
|
|
534
|
+
if (positions.length === 0) {
|
|
535
|
+
return " ";
|
|
536
|
+
}
|
|
537
|
+
const leftPos = this.getNeighborPosition(pixels, x - 1, baseY);
|
|
538
|
+
const rightPos = this.getNeighborPosition(pixels, x + 1, baseY);
|
|
539
|
+
const hasAnyLeft = leftPos !== null;
|
|
540
|
+
const hasAnyRight = rightPos !== null;
|
|
541
|
+
if (positions.length >= 2) {
|
|
542
|
+
return this.selectMultiPixelChar(hasAnyLeft, hasAnyRight);
|
|
543
|
+
}
|
|
544
|
+
const pos = positions[0] ?? 1;
|
|
545
|
+
const hasLeftSame = this.isPixelSet(pixels, x - 1, baseY + pos);
|
|
546
|
+
const hasRightSame = this.isPixelSet(pixels, x + 1, baseY + pos);
|
|
547
|
+
const hasSameHorizontal = hasLeftSame || hasRightSame;
|
|
548
|
+
return this.selectSinglePixelChar({
|
|
549
|
+
pos,
|
|
550
|
+
hasSameHorizontal,
|
|
551
|
+
hasAnyLeft,
|
|
552
|
+
hasAnyRight,
|
|
553
|
+
leftPos,
|
|
554
|
+
rightPos
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Determine the primary color for this character cell
|
|
559
|
+
*/
|
|
560
|
+
resolveColor(pixels, x, baseY) {
|
|
561
|
+
const counts = /* @__PURE__ */ new Map();
|
|
562
|
+
for (let offset = 0; offset < 3; offset++) {
|
|
563
|
+
const y = baseY + offset;
|
|
564
|
+
if (y >= pixels.length) continue;
|
|
565
|
+
const pixel = pixels[y]?.[x];
|
|
566
|
+
if (pixel?.active && pixel.color) {
|
|
567
|
+
counts.set(pixel.color, (counts.get(pixel.color) ?? 0) + 1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (counts.size === 0) return void 0;
|
|
571
|
+
let maxCount = 0;
|
|
572
|
+
let dominantColor;
|
|
573
|
+
for (const [color, count] of counts) {
|
|
574
|
+
if (count > maxCount) {
|
|
575
|
+
maxCount = count;
|
|
576
|
+
dominantColor = color;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return dominantColor;
|
|
580
|
+
}
|
|
581
|
+
// ============================================================
|
|
582
|
+
// Methods that subclasses must implement
|
|
583
|
+
// ============================================================
|
|
584
|
+
renderCanvas(pixels, width, height) {
|
|
585
|
+
const lines = [];
|
|
586
|
+
const charHeight = Math.ceil(height / 3);
|
|
587
|
+
for (let cy = 0; cy < charHeight; cy++) {
|
|
588
|
+
const lineSegments = [];
|
|
589
|
+
const baseY = cy * 3;
|
|
590
|
+
let buffer = "";
|
|
591
|
+
let bufferColor;
|
|
592
|
+
for (let x = 0; x < width; x++) {
|
|
593
|
+
const char = this.selectAsciiChar(pixels, x, baseY);
|
|
594
|
+
const color = char === " " ? void 0 : this.resolveColor(pixels, x, baseY);
|
|
595
|
+
if (buffer && bufferColor !== color) {
|
|
596
|
+
lineSegments.push(
|
|
597
|
+
bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
|
|
598
|
+
);
|
|
599
|
+
buffer = "";
|
|
600
|
+
}
|
|
601
|
+
bufferColor = color;
|
|
602
|
+
buffer += char;
|
|
603
|
+
}
|
|
604
|
+
if (buffer) {
|
|
605
|
+
lineSegments.push(
|
|
606
|
+
bufferColor ? { text: buffer, color: bufferColor } : { text: buffer }
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
lines.push(lineSegments);
|
|
610
|
+
}
|
|
611
|
+
return lines;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/detect/terminal.ts
|
|
616
|
+
var TerminalDetector = class _TerminalDetector {
|
|
617
|
+
/** Terminal whitelist supporting Braille characters (lowercase) */
|
|
618
|
+
static BRAILLE_SUPPORTED_TERMINALS = [
|
|
619
|
+
"iterm",
|
|
620
|
+
"warp",
|
|
621
|
+
"alacritty",
|
|
622
|
+
"kitty",
|
|
623
|
+
"wezterm",
|
|
624
|
+
"hyper",
|
|
625
|
+
"tabby",
|
|
626
|
+
"rio"
|
|
627
|
+
];
|
|
628
|
+
/** Environment variable information */
|
|
629
|
+
envInfo;
|
|
630
|
+
constructor(env = process.env) {
|
|
631
|
+
this.envInfo = this.extractEnvInfo(env);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Extract relevant information from environment variables
|
|
635
|
+
* @param env - Environment variable object
|
|
636
|
+
* @returns Environment information
|
|
637
|
+
*/
|
|
638
|
+
extractEnvInfo(env) {
|
|
639
|
+
const info = {};
|
|
640
|
+
if (env.LANG !== void 0) {
|
|
641
|
+
info.LANG = env.LANG;
|
|
642
|
+
}
|
|
643
|
+
if (env.TERM !== void 0) {
|
|
644
|
+
info.TERM = env.TERM;
|
|
645
|
+
}
|
|
646
|
+
if (env.COLORTERM !== void 0) {
|
|
647
|
+
info.COLORTERM = env.COLORTERM;
|
|
648
|
+
}
|
|
649
|
+
if (env.TERM_PROGRAM !== void 0) {
|
|
650
|
+
info.TERM_PROGRAM = env.TERM_PROGRAM;
|
|
651
|
+
}
|
|
652
|
+
if (env.TERM_PROGRAM_VERSION !== void 0) {
|
|
653
|
+
info.TERM_PROGRAM_VERSION = env.TERM_PROGRAM_VERSION;
|
|
654
|
+
}
|
|
655
|
+
return info;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Detect UTF-8 support
|
|
659
|
+
* @returns Whether UTF-8 is supported
|
|
660
|
+
*/
|
|
661
|
+
checkUtf8Support() {
|
|
662
|
+
const lang = this.envInfo.LANG || "";
|
|
663
|
+
return lang.toUpperCase().includes("UTF-8") || lang.toUpperCase().includes("UTF8");
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Detect Unicode support
|
|
667
|
+
* UTF-8 terminals usually support Unicode
|
|
668
|
+
* @returns Whether Unicode is supported
|
|
669
|
+
*/
|
|
670
|
+
checkUnicodeSupport() {
|
|
671
|
+
return this.checkUtf8Support();
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Detect Braille character support
|
|
675
|
+
* Determine based on terminal program whitelist
|
|
676
|
+
* @returns Whether Braille characters are supported
|
|
677
|
+
*/
|
|
678
|
+
checkBrailleSupport() {
|
|
679
|
+
const termProgram = (this.envInfo.TERM_PROGRAM || "").toLowerCase();
|
|
680
|
+
return _TerminalDetector.BRAILLE_SUPPORTED_TERMINALS.some(
|
|
681
|
+
(supportedTerm) => termProgram.includes(supportedTerm)
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Detect Block Elements character support
|
|
686
|
+
* Most terminals supporting Unicode also support Block Elements
|
|
687
|
+
* @returns Whether Block Elements are supported
|
|
688
|
+
*/
|
|
689
|
+
checkBlockElementsSupport() {
|
|
690
|
+
return this.checkUnicodeSupport();
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Detect color support (16 colors or more)
|
|
694
|
+
* @returns Whether colors are supported
|
|
695
|
+
*/
|
|
696
|
+
checkColorSupport() {
|
|
697
|
+
const term = this.envInfo.TERM || "";
|
|
698
|
+
const colorterm = this.envInfo.COLORTERM || "";
|
|
699
|
+
if (term.includes("color") || term.includes("256color")) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
if (colorterm.length > 0) {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Detect true color support (24-bit RGB)
|
|
709
|
+
* @returns Whether true color is supported
|
|
710
|
+
*/
|
|
711
|
+
checkTrueColorSupport() {
|
|
712
|
+
const colorterm = this.envInfo.COLORTERM || "";
|
|
713
|
+
return colorterm.toLowerCase() === "truecolor" || colorterm === "24bit";
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Calculate comprehensive terminal capability score (0-100)
|
|
717
|
+
* @returns Score
|
|
718
|
+
*/
|
|
719
|
+
calculateScore() {
|
|
720
|
+
let score = 0;
|
|
721
|
+
if (this.checkUtf8Support()) {
|
|
722
|
+
score += 20;
|
|
723
|
+
}
|
|
724
|
+
if (this.checkUnicodeSupport()) {
|
|
725
|
+
score += 10;
|
|
726
|
+
}
|
|
727
|
+
if (this.checkBrailleSupport()) {
|
|
728
|
+
score += 30;
|
|
729
|
+
}
|
|
730
|
+
if (this.checkBlockElementsSupport()) {
|
|
731
|
+
score += 10;
|
|
732
|
+
}
|
|
733
|
+
if (this.checkColorSupport()) {
|
|
734
|
+
score += 15;
|
|
735
|
+
}
|
|
736
|
+
if (this.checkTrueColorSupport()) {
|
|
737
|
+
score += 15;
|
|
738
|
+
}
|
|
739
|
+
return Math.min(score, 100);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Detect terminal capabilities
|
|
743
|
+
* @returns Terminal capability information
|
|
744
|
+
*/
|
|
745
|
+
detect() {
|
|
746
|
+
const supportsUtf8 = this.checkUtf8Support();
|
|
747
|
+
const supportsUnicode = this.checkUnicodeSupport();
|
|
748
|
+
const supportsBraille = this.checkBrailleSupport();
|
|
749
|
+
const supportsBlockElements = this.checkBlockElementsSupport();
|
|
750
|
+
const supportsColor = this.checkColorSupport();
|
|
751
|
+
const supportsTrueColor = this.checkTrueColorSupport();
|
|
752
|
+
const score = this.calculateScore();
|
|
753
|
+
return {
|
|
754
|
+
supportsUtf8,
|
|
755
|
+
supportsUnicode,
|
|
756
|
+
supportsBraille,
|
|
757
|
+
supportsBlockElements,
|
|
758
|
+
supportsColor,
|
|
759
|
+
supportsTrueColor,
|
|
760
|
+
score
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Get environment information
|
|
765
|
+
* @returns Environment information
|
|
766
|
+
*/
|
|
767
|
+
getEnvironmentInfo() {
|
|
768
|
+
return { ...this.envInfo };
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
var terminalDetector = new TerminalDetector();
|
|
772
|
+
|
|
773
|
+
// src/detect/selector.ts
|
|
774
|
+
var RendererSelector = class {
|
|
775
|
+
/** Terminal detector */
|
|
776
|
+
detector;
|
|
777
|
+
constructor(detector = new TerminalDetector()) {
|
|
778
|
+
this.detector = detector;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Creates a renderer instance of the specified type
|
|
782
|
+
* @param type - Renderer type
|
|
783
|
+
* @returns Renderer instance
|
|
784
|
+
*/
|
|
785
|
+
createRenderer(type) {
|
|
786
|
+
switch (type) {
|
|
787
|
+
case "braille":
|
|
788
|
+
return new BrailleRenderer();
|
|
789
|
+
case "block":
|
|
790
|
+
return new BlockRenderer();
|
|
791
|
+
case "ascii":
|
|
792
|
+
return new AsciiRenderer();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Get renderer by type
|
|
797
|
+
* @param type - Renderer type
|
|
798
|
+
* @returns Renderer instance
|
|
799
|
+
*/
|
|
800
|
+
getRenderer(type) {
|
|
801
|
+
return this.createRenderer(type);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Check if the renderer meets terminal capability requirements
|
|
805
|
+
* @param renderer - Renderer instance
|
|
806
|
+
* @param capabilities - Terminal capabilities
|
|
807
|
+
* @returns Whether requirements are met
|
|
808
|
+
*/
|
|
809
|
+
isRendererSupported(renderer, capabilities) {
|
|
810
|
+
const metadata = renderer.getMetadata();
|
|
811
|
+
if (capabilities.score < metadata.minScore) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
if (metadata.requiresUtf8 && !capabilities.supportsUtf8) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
if (metadata.requiresUnicode && !capabilities.supportsUnicode) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
const rendererName = metadata.name;
|
|
821
|
+
if (rendererName === "braille" && !capabilities.supportsBraille) {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
if (rendererName === "block" && !capabilities.supportsBlockElements) {
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Automatically select the best renderer
|
|
831
|
+
*
|
|
832
|
+
* Try in the order of the priority chain, returning the first renderer that meets terminal capability requirements
|
|
833
|
+
* If none are satisfied, fallback to ASCII
|
|
834
|
+
*
|
|
835
|
+
* @param preferredChain - Priority chain (default: ['braille', 'block', 'ascii'])
|
|
836
|
+
* @returns Selected renderer instance
|
|
837
|
+
*/
|
|
838
|
+
selectBest(preferredChain = ["braille", "block", "ascii"]) {
|
|
839
|
+
const capabilities = this.detector.detect();
|
|
840
|
+
for (const rendererType of preferredChain) {
|
|
841
|
+
const renderer = this.getRenderer(rendererType);
|
|
842
|
+
if (this.isRendererSupported(renderer, capabilities)) {
|
|
843
|
+
return renderer;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return this.getRenderer("ascii");
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get terminal capability information
|
|
850
|
+
* @returns Terminal capabilities
|
|
851
|
+
*/
|
|
852
|
+
getTerminalCapabilities() {
|
|
853
|
+
return this.detector.detect();
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
var rendererSelector = new RendererSelector();
|
|
857
|
+
var defaultSelector = new RendererSelector();
|
|
858
|
+
var defaultContext = {
|
|
859
|
+
selector: defaultSelector,
|
|
860
|
+
getCapabilities: () => defaultSelector.getTerminalCapabilities(),
|
|
861
|
+
getRenderer: (type) => defaultSelector.getRenderer(type),
|
|
862
|
+
selectBest: (chain) => defaultSelector.selectBest(chain)
|
|
863
|
+
};
|
|
864
|
+
var InkHudContext = createContext(defaultContext);
|
|
865
|
+
var InkHudProvider = ({
|
|
866
|
+
detector,
|
|
867
|
+
forceRenderer,
|
|
868
|
+
children
|
|
869
|
+
}) => {
|
|
870
|
+
const value = useMemo(() => {
|
|
871
|
+
const selector = detector ? new RendererSelector(detector) : defaultSelector;
|
|
872
|
+
return {
|
|
873
|
+
selector,
|
|
874
|
+
getCapabilities: () => selector.getTerminalCapabilities(),
|
|
875
|
+
getRenderer: (type) => selector.getRenderer(type),
|
|
876
|
+
selectBest: (chain) => {
|
|
877
|
+
if (forceRenderer) {
|
|
878
|
+
return selector.getRenderer(forceRenderer);
|
|
879
|
+
}
|
|
880
|
+
return selector.selectBest(chain);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}, [detector, forceRenderer]);
|
|
884
|
+
return /* @__PURE__ */ React6.createElement(InkHudContext.Provider, { value }, children);
|
|
885
|
+
};
|
|
886
|
+
function useInkHud() {
|
|
887
|
+
return useContext(InkHudContext);
|
|
888
|
+
}
|
|
889
|
+
function useRendererSelector() {
|
|
890
|
+
return useInkHud().selector;
|
|
891
|
+
}
|
|
892
|
+
function createGradient(colors, steps) {
|
|
893
|
+
if (colors.length === 0) {
|
|
894
|
+
return Array(steps).fill((text) => text);
|
|
895
|
+
}
|
|
896
|
+
if (colors.length === 1) {
|
|
897
|
+
const color = colors[0];
|
|
898
|
+
if (color) {
|
|
899
|
+
const colorFn = (text) => chalk.hex(color)(text);
|
|
900
|
+
return Array(steps).fill(colorFn);
|
|
901
|
+
}
|
|
902
|
+
return Array(steps).fill((text) => text);
|
|
903
|
+
}
|
|
904
|
+
const gradient = tinygradient(colors);
|
|
905
|
+
const rgbColors = gradient.rgb(steps);
|
|
906
|
+
return rgbColors.map((color) => {
|
|
907
|
+
const hex = color.toHex();
|
|
908
|
+
return (text) => chalk.hex(hex)(text);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
var ONE_DARK_PALETTES = {
|
|
912
|
+
/** One Dark - Classic theme */
|
|
913
|
+
standard: [
|
|
914
|
+
"#61afef",
|
|
915
|
+
// blue
|
|
916
|
+
"#98c379",
|
|
917
|
+
// green
|
|
918
|
+
"#e5c07b",
|
|
919
|
+
// yellow
|
|
920
|
+
"#c678dd",
|
|
921
|
+
// purple
|
|
922
|
+
"#e06c75",
|
|
923
|
+
// red
|
|
924
|
+
"#56b6c2",
|
|
925
|
+
// cyan
|
|
926
|
+
"#d19a66",
|
|
927
|
+
// orange
|
|
928
|
+
"#abb2bf"
|
|
929
|
+
// gray
|
|
930
|
+
],
|
|
931
|
+
/** One Dark Vivid - More vibrant variant */
|
|
932
|
+
vivid: [
|
|
933
|
+
"#61afef",
|
|
934
|
+
// blue
|
|
935
|
+
"#98c379",
|
|
936
|
+
// green
|
|
937
|
+
"#e5c07b",
|
|
938
|
+
// yellow
|
|
939
|
+
"#c678dd",
|
|
940
|
+
// purple
|
|
941
|
+
"#e06c75",
|
|
942
|
+
// red
|
|
943
|
+
"#56b6c2",
|
|
944
|
+
// cyan
|
|
945
|
+
"#be5046",
|
|
946
|
+
// dark red
|
|
947
|
+
"#d19a66",
|
|
948
|
+
// orange
|
|
949
|
+
"#528bff"
|
|
950
|
+
// bright blue
|
|
951
|
+
]
|
|
952
|
+
};
|
|
953
|
+
function assignColors(seriesCount, palette = "one-dark") {
|
|
954
|
+
if (seriesCount === 0) {
|
|
955
|
+
return [];
|
|
956
|
+
}
|
|
957
|
+
let baseColors;
|
|
958
|
+
if (Array.isArray(palette)) {
|
|
959
|
+
baseColors = palette;
|
|
960
|
+
} else {
|
|
961
|
+
switch (palette) {
|
|
962
|
+
case "one-dark-vivid":
|
|
963
|
+
baseColors = ONE_DARK_PALETTES.vivid;
|
|
964
|
+
break;
|
|
965
|
+
default:
|
|
966
|
+
baseColors = ONE_DARK_PALETTES.standard;
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (seriesCount <= baseColors.length) {
|
|
971
|
+
return baseColors.slice(0, seriesCount);
|
|
972
|
+
}
|
|
973
|
+
const gradient = tinygradient(baseColors);
|
|
974
|
+
return gradient.rgb(seriesCount).map((c) => c.toHex());
|
|
975
|
+
}
|
|
976
|
+
function colorToChalk(color) {
|
|
977
|
+
if (color.startsWith("#")) {
|
|
978
|
+
return (text) => chalk.hex(color)(text);
|
|
979
|
+
}
|
|
980
|
+
if (typeof chalk[color] === "function") {
|
|
981
|
+
return (text) => chalk[color](text);
|
|
982
|
+
}
|
|
983
|
+
return (text) => text;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/theme/ThemeContext.tsx
|
|
987
|
+
var ONE_DARK_THEME = {
|
|
988
|
+
name: "one-dark",
|
|
989
|
+
palette: ONE_DARK_PALETTES.standard,
|
|
990
|
+
semantic: {
|
|
991
|
+
success: "#98c379",
|
|
992
|
+
// green
|
|
993
|
+
error: "#e06c75",
|
|
994
|
+
// red
|
|
995
|
+
warning: "#e5c07b",
|
|
996
|
+
// yellow
|
|
997
|
+
info: "#61afef",
|
|
998
|
+
// blue
|
|
999
|
+
muted: "#5c6370",
|
|
1000
|
+
// gray
|
|
1001
|
+
text: "#abb2bf",
|
|
1002
|
+
// light gray
|
|
1003
|
+
textSecondary: "#5c6370"
|
|
1004
|
+
// dark gray
|
|
1005
|
+
},
|
|
1006
|
+
heatmapGradient: [
|
|
1007
|
+
"#282c34",
|
|
1008
|
+
// background (dark)
|
|
1009
|
+
"#56b6c2",
|
|
1010
|
+
// cyan
|
|
1011
|
+
"#98c379",
|
|
1012
|
+
// green
|
|
1013
|
+
"#e5c07b",
|
|
1014
|
+
// yellow
|
|
1015
|
+
"#61afef"
|
|
1016
|
+
// blue (light)
|
|
1017
|
+
]
|
|
1018
|
+
};
|
|
1019
|
+
var ThemeContext = createContext(ONE_DARK_THEME);
|
|
1020
|
+
var ThemeProvider = ({ theme: customTheme, children }) => {
|
|
1021
|
+
const mergedTheme = useMemo(() => {
|
|
1022
|
+
if (!customTheme) {
|
|
1023
|
+
return ONE_DARK_THEME;
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
...ONE_DARK_THEME,
|
|
1027
|
+
...customTheme,
|
|
1028
|
+
semantic: {
|
|
1029
|
+
...ONE_DARK_THEME.semantic,
|
|
1030
|
+
...customTheme.semantic
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}, [customTheme]);
|
|
1034
|
+
return /* @__PURE__ */ React6.createElement(ThemeContext.Provider, { value: mergedTheme }, children);
|
|
1035
|
+
};
|
|
1036
|
+
function useTheme() {
|
|
1037
|
+
return useContext(ThemeContext);
|
|
1038
|
+
}
|
|
1039
|
+
function useSemanticColors() {
|
|
1040
|
+
const theme = useTheme();
|
|
1041
|
+
return theme.semantic;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/utils/scale.ts
|
|
1045
|
+
function linearScale(value, domain, range) {
|
|
1046
|
+
const [domainMin, domainMax] = domain;
|
|
1047
|
+
const [rangeMin, rangeMax] = range;
|
|
1048
|
+
if (domainMax === domainMin) {
|
|
1049
|
+
return rangeMin;
|
|
1050
|
+
}
|
|
1051
|
+
const ratio = (value - domainMin) / (domainMax - domainMin);
|
|
1052
|
+
return ratio * (rangeMax - rangeMin) + rangeMin;
|
|
1053
|
+
}
|
|
1054
|
+
function normalize(data) {
|
|
1055
|
+
if (data.length === 0) {
|
|
1056
|
+
return [];
|
|
1057
|
+
}
|
|
1058
|
+
const min = Math.min(...data);
|
|
1059
|
+
const max = Math.max(...data);
|
|
1060
|
+
if (max === min) {
|
|
1061
|
+
return data.map(() => 0.5);
|
|
1062
|
+
}
|
|
1063
|
+
return data.map((value) => linearScale(value, [min, max], [0, 1]));
|
|
1064
|
+
}
|
|
1065
|
+
function scaleToRange(data, range) {
|
|
1066
|
+
if (data.length === 0) {
|
|
1067
|
+
return [];
|
|
1068
|
+
}
|
|
1069
|
+
const min = Math.min(...data);
|
|
1070
|
+
const max = Math.max(...data);
|
|
1071
|
+
if (max === min) {
|
|
1072
|
+
const midPoint = (range[0] + range[1]) / 2;
|
|
1073
|
+
return data.map(() => midPoint);
|
|
1074
|
+
}
|
|
1075
|
+
return data.map((value) => linearScale(value, [min, max], range));
|
|
1076
|
+
}
|
|
1077
|
+
function clamp(value, min, max) {
|
|
1078
|
+
return Math.min(Math.max(value, min), max);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/utils/geometry.ts
|
|
1082
|
+
function midpointCircle(centerX, centerY, radius) {
|
|
1083
|
+
const points = [];
|
|
1084
|
+
let x = 0;
|
|
1085
|
+
let y = radius;
|
|
1086
|
+
let d = 1 - radius;
|
|
1087
|
+
while (x <= y) {
|
|
1088
|
+
points.push(
|
|
1089
|
+
[centerX + x, centerY + y],
|
|
1090
|
+
[centerX - x, centerY + y],
|
|
1091
|
+
[centerX + x, centerY - y],
|
|
1092
|
+
[centerX - x, centerY - y],
|
|
1093
|
+
[centerX + y, centerY + x],
|
|
1094
|
+
[centerX - y, centerY + x],
|
|
1095
|
+
[centerX + y, centerY - x],
|
|
1096
|
+
[centerX - y, centerY - x]
|
|
1097
|
+
);
|
|
1098
|
+
x++;
|
|
1099
|
+
if (d < 0) {
|
|
1100
|
+
d += 2 * x + 1;
|
|
1101
|
+
} else {
|
|
1102
|
+
y--;
|
|
1103
|
+
d += 2 * (x - y) + 1;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return points;
|
|
1107
|
+
}
|
|
1108
|
+
function pointOnArc(centerX, centerY, radius, angle) {
|
|
1109
|
+
const x = centerX + radius * Math.cos(angle);
|
|
1110
|
+
const y = centerY + radius * Math.sin(angle);
|
|
1111
|
+
return [x, y];
|
|
1112
|
+
}
|
|
1113
|
+
function arcPoints(centerX, centerY, radius, startAngle, endAngle, steps) {
|
|
1114
|
+
const points = [];
|
|
1115
|
+
let start = startAngle;
|
|
1116
|
+
let end = endAngle;
|
|
1117
|
+
if (start > end) {
|
|
1118
|
+
[start, end] = [end, start];
|
|
1119
|
+
}
|
|
1120
|
+
const angleRange = end - start;
|
|
1121
|
+
const stepCount = steps ?? Math.max(10, Math.ceil(radius * angleRange));
|
|
1122
|
+
const angleStep = angleRange / stepCount;
|
|
1123
|
+
for (let i = 0; i <= stepCount; i++) {
|
|
1124
|
+
const angle = start + i * angleStep;
|
|
1125
|
+
points.push(pointOnArc(centerX, centerY, radius, angle));
|
|
1126
|
+
}
|
|
1127
|
+
return points;
|
|
1128
|
+
}
|
|
1129
|
+
function degreesToRadians(degrees) {
|
|
1130
|
+
return degrees * Math.PI / 180;
|
|
1131
|
+
}
|
|
1132
|
+
function radiansToDegrees(radians) {
|
|
1133
|
+
return radians * 180 / Math.PI;
|
|
1134
|
+
}
|
|
1135
|
+
function distanceBetweenPoints(x1, y1, x2, y2) {
|
|
1136
|
+
const dx = x2 - x1;
|
|
1137
|
+
const dy = y2 - y1;
|
|
1138
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/utils/downsampling.ts
|
|
1142
|
+
function computeAveragePoint(data, startIndexInclusive, endIndexExclusive) {
|
|
1143
|
+
const count = endIndexExclusive - startIndexInclusive;
|
|
1144
|
+
if (count <= 0) {
|
|
1145
|
+
return { x: 0, y: 0 };
|
|
1146
|
+
}
|
|
1147
|
+
let sumX = 0;
|
|
1148
|
+
let sumY = 0;
|
|
1149
|
+
for (let i = startIndexInclusive; i < endIndexExclusive; i++) {
|
|
1150
|
+
sumX += i;
|
|
1151
|
+
sumY += data[i] ?? 0;
|
|
1152
|
+
}
|
|
1153
|
+
return { x: sumX / count, y: sumY / count };
|
|
1154
|
+
}
|
|
1155
|
+
function triangleArea(prevX, prevY, avgX, avgY, pointX, pointY) {
|
|
1156
|
+
return Math.abs((prevX - avgX) * (pointY - prevY) - (prevX - pointX) * (avgY - prevY));
|
|
1157
|
+
}
|
|
1158
|
+
function findLargestTrianglePointIndex(data, bucketStart, bucketEnd, prevX, prevY, avgX, avgY) {
|
|
1159
|
+
let maxArea = -1;
|
|
1160
|
+
let maxIdx = bucketStart;
|
|
1161
|
+
for (let i = bucketStart; i < bucketEnd; i++) {
|
|
1162
|
+
const area = triangleArea(prevX, prevY, avgX, avgY, i, data[i] ?? 0);
|
|
1163
|
+
if (area > maxArea) {
|
|
1164
|
+
maxArea = area;
|
|
1165
|
+
maxIdx = i;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return maxIdx;
|
|
1169
|
+
}
|
|
1170
|
+
function lttb(data, threshold) {
|
|
1171
|
+
if (data.length <= threshold) {
|
|
1172
|
+
return data;
|
|
1173
|
+
}
|
|
1174
|
+
if (threshold <= 2) {
|
|
1175
|
+
return [data[0] ?? 0, data[data.length - 1] ?? 0];
|
|
1176
|
+
}
|
|
1177
|
+
const sampled = [data[0] ?? 0];
|
|
1178
|
+
let prevIndex = 0;
|
|
1179
|
+
const bucketSize = (data.length - 2) / (threshold - 2);
|
|
1180
|
+
for (let i = 0; i < threshold - 2; i++) {
|
|
1181
|
+
const bucketStart = Math.floor(i * bucketSize) + 1;
|
|
1182
|
+
const bucketEnd = Math.floor((i + 1) * bucketSize) + 1;
|
|
1183
|
+
const nextBucketStart = bucketEnd;
|
|
1184
|
+
const nextBucketEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, data.length);
|
|
1185
|
+
const { x: avgX, y: avgY } = computeAveragePoint(data, nextBucketStart, nextBucketEnd);
|
|
1186
|
+
const prevX = prevIndex;
|
|
1187
|
+
const prevY = data[prevIndex] ?? 0;
|
|
1188
|
+
const maxIdx = findLargestTrianglePointIndex(
|
|
1189
|
+
data,
|
|
1190
|
+
bucketStart,
|
|
1191
|
+
bucketEnd,
|
|
1192
|
+
prevX,
|
|
1193
|
+
prevY,
|
|
1194
|
+
avgX,
|
|
1195
|
+
avgY
|
|
1196
|
+
);
|
|
1197
|
+
sampled.push(data[maxIdx] ?? 0);
|
|
1198
|
+
prevIndex = maxIdx;
|
|
1199
|
+
}
|
|
1200
|
+
sampled.push(data[data.length - 1] ?? 0);
|
|
1201
|
+
return sampled;
|
|
1202
|
+
}
|
|
1203
|
+
function fixedIntervalDownsampling(data, threshold) {
|
|
1204
|
+
if (data.length <= threshold) {
|
|
1205
|
+
return data;
|
|
1206
|
+
}
|
|
1207
|
+
const sampled = [];
|
|
1208
|
+
const step = (data.length - 1) / (threshold - 1);
|
|
1209
|
+
for (let i = 0; i < threshold; i++) {
|
|
1210
|
+
const index = Math.round(i * step);
|
|
1211
|
+
sampled.push(data[index] ?? 0);
|
|
1212
|
+
}
|
|
1213
|
+
return sampled;
|
|
1214
|
+
}
|
|
1215
|
+
function averageDownsampling(data, threshold) {
|
|
1216
|
+
if (data.length <= threshold) {
|
|
1217
|
+
return data;
|
|
1218
|
+
}
|
|
1219
|
+
const sampled = [];
|
|
1220
|
+
const bucketSize = data.length / threshold;
|
|
1221
|
+
for (let i = 0; i < threshold; i++) {
|
|
1222
|
+
const bucketStart = Math.floor(i * bucketSize);
|
|
1223
|
+
const bucketEnd = Math.floor((i + 1) * bucketSize);
|
|
1224
|
+
let sum = 0;
|
|
1225
|
+
let count = 0;
|
|
1226
|
+
for (let j = bucketStart; j < bucketEnd; j++) {
|
|
1227
|
+
sum += data[j] ?? 0;
|
|
1228
|
+
count++;
|
|
1229
|
+
}
|
|
1230
|
+
sampled.push(count > 0 ? sum / count : 0);
|
|
1231
|
+
}
|
|
1232
|
+
return sampled;
|
|
1233
|
+
}
|
|
1234
|
+
function minMaxDownsampling(data, threshold) {
|
|
1235
|
+
if (data.length <= threshold) {
|
|
1236
|
+
return data;
|
|
1237
|
+
}
|
|
1238
|
+
const sampled = [];
|
|
1239
|
+
const bucketSize = data.length / threshold;
|
|
1240
|
+
for (let i = 0; i < threshold; i++) {
|
|
1241
|
+
const bucketStart = Math.floor(i * bucketSize);
|
|
1242
|
+
const bucketEnd = Math.floor((i + 1) * bucketSize);
|
|
1243
|
+
let min = Number.POSITIVE_INFINITY;
|
|
1244
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
1245
|
+
for (let j = bucketStart; j < bucketEnd; j++) {
|
|
1246
|
+
const value = data[j] ?? 0;
|
|
1247
|
+
if (value < min) min = value;
|
|
1248
|
+
if (value > max) max = value;
|
|
1249
|
+
}
|
|
1250
|
+
sampled.push(min, max);
|
|
1251
|
+
}
|
|
1252
|
+
return sampled;
|
|
1253
|
+
}
|
|
1254
|
+
function easeInOutQuad(t) {
|
|
1255
|
+
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
1256
|
+
}
|
|
1257
|
+
function easeLinear(t) {
|
|
1258
|
+
return t;
|
|
1259
|
+
}
|
|
1260
|
+
function easeOutCubic(t) {
|
|
1261
|
+
const t1 = t - 1;
|
|
1262
|
+
return t1 * t1 * t1 + 1;
|
|
1263
|
+
}
|
|
1264
|
+
function easeInCubic(t) {
|
|
1265
|
+
return t * t * t;
|
|
1266
|
+
}
|
|
1267
|
+
function useSmooth(targetValue, duration = 300, easingFn = easeInOutQuad) {
|
|
1268
|
+
const [currentValue, setCurrentValue] = useState(targetValue);
|
|
1269
|
+
const startValueRef = useRef(targetValue);
|
|
1270
|
+
const startTimeRef = useRef(null);
|
|
1271
|
+
const timerRef = useRef(null);
|
|
1272
|
+
useEffect(() => {
|
|
1273
|
+
if (targetValue === currentValue) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
startValueRef.current = currentValue;
|
|
1277
|
+
startTimeRef.current = Date.now();
|
|
1278
|
+
const frameInterval = 1e3 / 30;
|
|
1279
|
+
const animate = () => {
|
|
1280
|
+
const now = Date.now();
|
|
1281
|
+
const elapsed = now - (startTimeRef.current ?? now);
|
|
1282
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1283
|
+
const easedProgress = easingFn(progress);
|
|
1284
|
+
const newValue = startValueRef.current + (targetValue - startValueRef.current) * easedProgress;
|
|
1285
|
+
setCurrentValue(newValue);
|
|
1286
|
+
if (progress < 1) {
|
|
1287
|
+
timerRef.current = setTimeout(animate, frameInterval);
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
timerRef.current = setTimeout(animate, frameInterval);
|
|
1291
|
+
return () => {
|
|
1292
|
+
if (timerRef.current !== null) {
|
|
1293
|
+
clearTimeout(timerRef.current);
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
}, [targetValue, duration, easingFn, currentValue]);
|
|
1297
|
+
return currentValue;
|
|
1298
|
+
}
|
|
1299
|
+
function useSmoothArray(targetData, duration = 300, easingFn = easeInOutQuad) {
|
|
1300
|
+
const [currentData, setCurrentData] = useState(targetData);
|
|
1301
|
+
const startDataRef = useRef(targetData);
|
|
1302
|
+
const startTimeRef = useRef(null);
|
|
1303
|
+
const timerRef = useRef(null);
|
|
1304
|
+
useEffect(() => {
|
|
1305
|
+
if (targetData.length !== currentData.length) {
|
|
1306
|
+
setCurrentData(targetData);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const hasChanged = targetData.some((value, i) => value !== currentData[i]);
|
|
1310
|
+
if (!hasChanged) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
startDataRef.current = currentData;
|
|
1314
|
+
startTimeRef.current = Date.now();
|
|
1315
|
+
const frameInterval = 1e3 / 30;
|
|
1316
|
+
const animate = () => {
|
|
1317
|
+
const now = Date.now();
|
|
1318
|
+
const elapsed = now - (startTimeRef.current ?? now);
|
|
1319
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1320
|
+
const easedProgress = easingFn(progress);
|
|
1321
|
+
const newData = targetData.map((target, i) => {
|
|
1322
|
+
const start = startDataRef.current[i] ?? 0;
|
|
1323
|
+
return start + (target - start) * easedProgress;
|
|
1324
|
+
});
|
|
1325
|
+
setCurrentData(newData);
|
|
1326
|
+
if (progress < 1) {
|
|
1327
|
+
timerRef.current = setTimeout(animate, frameInterval);
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
timerRef.current = setTimeout(animate, frameInterval);
|
|
1331
|
+
return () => {
|
|
1332
|
+
if (timerRef.current !== null) {
|
|
1333
|
+
clearTimeout(timerRef.current);
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
}, [targetData, duration, easingFn, currentData]);
|
|
1337
|
+
return currentData;
|
|
1338
|
+
}
|
|
1339
|
+
function useThrottle(value, fps = 30) {
|
|
1340
|
+
const [throttledValue, setThrottledValue] = useState(value);
|
|
1341
|
+
const lastUpdateRef = useRef(Date.now());
|
|
1342
|
+
useEffect(() => {
|
|
1343
|
+
const now = Date.now();
|
|
1344
|
+
const interval = 1e3 / fps;
|
|
1345
|
+
const elapsed = now - lastUpdateRef.current;
|
|
1346
|
+
if (elapsed >= interval) {
|
|
1347
|
+
setThrottledValue(value);
|
|
1348
|
+
lastUpdateRef.current = now;
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const timeoutId = setTimeout(() => {
|
|
1352
|
+
setThrottledValue(value);
|
|
1353
|
+
lastUpdateRef.current = Date.now();
|
|
1354
|
+
}, interval - elapsed);
|
|
1355
|
+
return () => clearTimeout(timeoutId);
|
|
1356
|
+
}, [value, fps]);
|
|
1357
|
+
return throttledValue;
|
|
1358
|
+
}
|
|
1359
|
+
var GridContext = createContext({
|
|
1360
|
+
columns: 12,
|
|
1361
|
+
gap: 0,
|
|
1362
|
+
totalWidth: 80
|
|
1363
|
+
// Fallback
|
|
1364
|
+
});
|
|
1365
|
+
var GridItemContext = createContext(null);
|
|
1366
|
+
var Grid = ({
|
|
1367
|
+
columns = 12,
|
|
1368
|
+
gap = 0,
|
|
1369
|
+
width: propsWidth,
|
|
1370
|
+
widthOffset = 0,
|
|
1371
|
+
rowHeight,
|
|
1372
|
+
children
|
|
1373
|
+
}) => {
|
|
1374
|
+
const { stdout } = useStdout();
|
|
1375
|
+
const [terminalWidth, setTerminalWidth] = useState(stdout ? stdout.columns : 80);
|
|
1376
|
+
useEffect(() => {
|
|
1377
|
+
if (!stdout) return;
|
|
1378
|
+
const onResize = () => setTerminalWidth(stdout.columns);
|
|
1379
|
+
stdout.on("resize", onResize);
|
|
1380
|
+
return () => {
|
|
1381
|
+
stdout.off("resize", onResize);
|
|
1382
|
+
};
|
|
1383
|
+
}, [stdout]);
|
|
1384
|
+
const defaultWidth = terminalWidth > 2 ? terminalWidth - 2 : terminalWidth;
|
|
1385
|
+
const totalWidth = propsWidth ?? Math.max(0, defaultWidth - widthOffset);
|
|
1386
|
+
return /* @__PURE__ */ React6.createElement(GridContext.Provider, { value: { columns, gap, totalWidth, rowHeight } }, /* @__PURE__ */ React6.createElement(
|
|
1387
|
+
Box,
|
|
1388
|
+
{
|
|
1389
|
+
flexDirection: "row",
|
|
1390
|
+
flexWrap: "wrap",
|
|
1391
|
+
columnGap: gap,
|
|
1392
|
+
rowGap: gap,
|
|
1393
|
+
width: totalWidth
|
|
1394
|
+
},
|
|
1395
|
+
children
|
|
1396
|
+
));
|
|
1397
|
+
};
|
|
1398
|
+
var GridItem = ({
|
|
1399
|
+
span = 1,
|
|
1400
|
+
height,
|
|
1401
|
+
minHeight,
|
|
1402
|
+
overflow,
|
|
1403
|
+
children
|
|
1404
|
+
}) => {
|
|
1405
|
+
const { columns, gap, totalWidth, rowHeight: contextRowHeight } = useContext(GridContext);
|
|
1406
|
+
const totalGapWidth = Math.max(0, (columns - 1) * gap);
|
|
1407
|
+
const availableWidth = Math.max(0, totalWidth - totalGapWidth);
|
|
1408
|
+
const colWidth = availableWidth / columns;
|
|
1409
|
+
const itemGapWidth = Math.max(0, (span - 1) * gap);
|
|
1410
|
+
const basisWidth = Math.floor(colWidth * span + itemGapWidth);
|
|
1411
|
+
const effectiveHeight = height ?? contextRowHeight;
|
|
1412
|
+
return /* @__PURE__ */ React6.createElement(GridItemContext.Provider, { value: { width: basisWidth, height: effectiveHeight } }, /* @__PURE__ */ React6.createElement(
|
|
1413
|
+
Box,
|
|
1414
|
+
{
|
|
1415
|
+
flexGrow: 1,
|
|
1416
|
+
flexShrink: 1,
|
|
1417
|
+
flexBasis: basisWidth,
|
|
1418
|
+
flexDirection: "column",
|
|
1419
|
+
...effectiveHeight !== void 0 && { height: effectiveHeight },
|
|
1420
|
+
...minHeight !== void 0 && { minHeight },
|
|
1421
|
+
...overflow !== void 0 && { overflow }
|
|
1422
|
+
},
|
|
1423
|
+
children
|
|
1424
|
+
));
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/components/common/chartUtils.ts
|
|
1428
|
+
function resolveSeriesColors(series, colors, palette) {
|
|
1429
|
+
if (series.length === 0) {
|
|
1430
|
+
return [];
|
|
1431
|
+
}
|
|
1432
|
+
if (colors && colors.length >= series.length) {
|
|
1433
|
+
return colors.slice(0, series.length);
|
|
1434
|
+
}
|
|
1435
|
+
if (colors && colors.length > 0) {
|
|
1436
|
+
return assignColors(series.length, colors);
|
|
1437
|
+
}
|
|
1438
|
+
return assignColors(series.length, palette);
|
|
1439
|
+
}
|
|
1440
|
+
function useChartLayout(props, config) {
|
|
1441
|
+
const {
|
|
1442
|
+
minWidth = 10,
|
|
1443
|
+
widthOffset = 0,
|
|
1444
|
+
heightOffset = 0,
|
|
1445
|
+
showXAxis = true,
|
|
1446
|
+
showYAxis = true,
|
|
1447
|
+
showLegend = true,
|
|
1448
|
+
legendPosition = "right",
|
|
1449
|
+
xAxisLabel,
|
|
1450
|
+
yTickCount = 5,
|
|
1451
|
+
yTickFormat,
|
|
1452
|
+
defaultWidth = 60,
|
|
1453
|
+
min,
|
|
1454
|
+
max
|
|
1455
|
+
} = config;
|
|
1456
|
+
const gridContext = useContext(GridItemContext);
|
|
1457
|
+
const totalHeight = props.height ?? (typeof gridContext?.height === "number" ? gridContext.height : 15);
|
|
1458
|
+
let totalWidth = props.width;
|
|
1459
|
+
if (totalWidth === void 0) {
|
|
1460
|
+
if (gridContext?.width) {
|
|
1461
|
+
totalWidth = Math.max(minWidth, gridContext.width - widthOffset);
|
|
1462
|
+
} else {
|
|
1463
|
+
totalWidth = defaultWidth;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const yAxisWidth = showYAxis ? getYAxisLabelWidth({
|
|
1467
|
+
min,
|
|
1468
|
+
max,
|
|
1469
|
+
tickCount: yTickCount,
|
|
1470
|
+
tickFormat: yTickFormat ?? defaultTickFormat
|
|
1471
|
+
}) : 0;
|
|
1472
|
+
const effectiveXAxisHeight = showXAxis ? 1 + (xAxisLabel ? 1 : 0) : 0;
|
|
1473
|
+
let legendWidth = 0;
|
|
1474
|
+
let legendHeight = 0;
|
|
1475
|
+
if (showLegend) {
|
|
1476
|
+
if (legendPosition === "right") {
|
|
1477
|
+
legendWidth = 20;
|
|
1478
|
+
} else {
|
|
1479
|
+
legendHeight = 2;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
const yAxisSpacing = showYAxis ? 1 : 0;
|
|
1483
|
+
const legendSpacing = showLegend && legendPosition === "right" ? 2 : 0;
|
|
1484
|
+
const plotWidth = Math.max(
|
|
1485
|
+
1,
|
|
1486
|
+
totalWidth - yAxisWidth - yAxisSpacing - legendWidth - legendSpacing
|
|
1487
|
+
);
|
|
1488
|
+
const plotHeight = Math.max(
|
|
1489
|
+
1,
|
|
1490
|
+
totalHeight - heightOffset - legendHeight - effectiveXAxisHeight
|
|
1491
|
+
);
|
|
1492
|
+
return {
|
|
1493
|
+
totalWidth,
|
|
1494
|
+
totalHeight,
|
|
1495
|
+
plotWidth,
|
|
1496
|
+
plotHeight,
|
|
1497
|
+
yAxisWidth,
|
|
1498
|
+
legendHeight,
|
|
1499
|
+
legendWidth,
|
|
1500
|
+
xAxisHeight: effectiveXAxisHeight
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function useChartLayoutSimple(props, min, max) {
|
|
1504
|
+
const {
|
|
1505
|
+
width: propsWidth,
|
|
1506
|
+
height: propsHeight,
|
|
1507
|
+
showAxis = true,
|
|
1508
|
+
showXAxis,
|
|
1509
|
+
showYAxis,
|
|
1510
|
+
showLegend = true,
|
|
1511
|
+
legendPosition = "right",
|
|
1512
|
+
xAxisLabel,
|
|
1513
|
+
yAxisLabel,
|
|
1514
|
+
yTickCount = 5,
|
|
1515
|
+
yTickFormat,
|
|
1516
|
+
widthOffset = 0,
|
|
1517
|
+
heightOffset = 0
|
|
1518
|
+
} = props;
|
|
1519
|
+
const renderXAxis = showXAxis ?? showAxis;
|
|
1520
|
+
const renderYAxis = showYAxis ?? showAxis;
|
|
1521
|
+
return useChartLayout(
|
|
1522
|
+
{
|
|
1523
|
+
...propsWidth !== void 0 && { width: propsWidth },
|
|
1524
|
+
...propsHeight !== void 0 && { height: propsHeight }
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
widthOffset,
|
|
1528
|
+
heightOffset,
|
|
1529
|
+
showXAxis: renderXAxis,
|
|
1530
|
+
showYAxis: renderYAxis,
|
|
1531
|
+
showLegend,
|
|
1532
|
+
legendPosition,
|
|
1533
|
+
...xAxisLabel && { xAxisLabel },
|
|
1534
|
+
...yAxisLabel && { yAxisLabel },
|
|
1535
|
+
yTickCount,
|
|
1536
|
+
...yTickFormat && { yTickFormat },
|
|
1537
|
+
min,
|
|
1538
|
+
max
|
|
1539
|
+
}
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
function buildSeriesInputParams(series, data, seriesName) {
|
|
1543
|
+
const params = {};
|
|
1544
|
+
if (series !== void 0) {
|
|
1545
|
+
params.series = series;
|
|
1546
|
+
}
|
|
1547
|
+
if (data !== void 0) {
|
|
1548
|
+
params.data = data;
|
|
1549
|
+
}
|
|
1550
|
+
if (seriesName !== void 0) {
|
|
1551
|
+
params.seriesName = seriesName;
|
|
1552
|
+
}
|
|
1553
|
+
return params;
|
|
1554
|
+
}
|
|
1555
|
+
function resolveSeriesInput(params) {
|
|
1556
|
+
const { series, data, seriesName } = params;
|
|
1557
|
+
if (series && series.length > 0) {
|
|
1558
|
+
return series;
|
|
1559
|
+
}
|
|
1560
|
+
if (data && data.length > 0) {
|
|
1561
|
+
return [
|
|
1562
|
+
{
|
|
1563
|
+
name: seriesName ?? "Series",
|
|
1564
|
+
data
|
|
1565
|
+
}
|
|
1566
|
+
];
|
|
1567
|
+
}
|
|
1568
|
+
return [];
|
|
1569
|
+
}
|
|
1570
|
+
function computeSeriesExtent(series) {
|
|
1571
|
+
let min = 0;
|
|
1572
|
+
let max = 0;
|
|
1573
|
+
let maxLength = 0;
|
|
1574
|
+
let hasValue = false;
|
|
1575
|
+
for (const item of series) {
|
|
1576
|
+
maxLength = Math.max(maxLength, item.data.length);
|
|
1577
|
+
for (const value of item.data) {
|
|
1578
|
+
if (!hasValue) {
|
|
1579
|
+
min = value;
|
|
1580
|
+
max = value;
|
|
1581
|
+
hasValue = true;
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
min = Math.min(min, value);
|
|
1585
|
+
max = Math.max(max, value);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (!hasValue) {
|
|
1589
|
+
return { min: 0, max: 0, maxLength };
|
|
1590
|
+
}
|
|
1591
|
+
return { min, max, maxLength };
|
|
1592
|
+
}
|
|
1593
|
+
function getPixelDimensions(renderer, width, height) {
|
|
1594
|
+
const resolution = renderer.getResolution();
|
|
1595
|
+
return {
|
|
1596
|
+
pixelWidth: width * resolution.horizontal,
|
|
1597
|
+
pixelHeight: height * resolution.vertical
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function defaultTickFormat(value) {
|
|
1601
|
+
const absValue = Math.abs(value);
|
|
1602
|
+
const sign = value < 0 ? "-" : "";
|
|
1603
|
+
if (absValue >= 1e9) {
|
|
1604
|
+
const v = absValue / 1e9;
|
|
1605
|
+
return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}b`;
|
|
1606
|
+
}
|
|
1607
|
+
if (absValue >= 1e6) {
|
|
1608
|
+
const v = absValue / 1e6;
|
|
1609
|
+
return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}m`;
|
|
1610
|
+
}
|
|
1611
|
+
if (absValue >= 1e3) {
|
|
1612
|
+
const v = absValue / 1e3;
|
|
1613
|
+
return `${sign}${Number.isInteger(v) ? v : v.toFixed(1)}k`;
|
|
1614
|
+
}
|
|
1615
|
+
return Math.round(value).toString();
|
|
1616
|
+
}
|
|
1617
|
+
function getYAxisLabelWidth(params) {
|
|
1618
|
+
const { min, max, tickCount, tickFormat = defaultTickFormat } = params;
|
|
1619
|
+
if (tickCount <= 0) {
|
|
1620
|
+
return 0;
|
|
1621
|
+
}
|
|
1622
|
+
if (max === min) {
|
|
1623
|
+
return tickFormat(min).length;
|
|
1624
|
+
}
|
|
1625
|
+
const step = (max - min) / (tickCount - 1);
|
|
1626
|
+
let maxLength = 0;
|
|
1627
|
+
for (let i = 0; i < tickCount; i++) {
|
|
1628
|
+
const value = min + i * step;
|
|
1629
|
+
maxLength = Math.max(maxLength, tickFormat(value).length);
|
|
1630
|
+
}
|
|
1631
|
+
return maxLength;
|
|
1632
|
+
}
|
|
1633
|
+
function computeBaselineY(params) {
|
|
1634
|
+
const { min, max, pixelHeight } = params;
|
|
1635
|
+
if (max === min) {
|
|
1636
|
+
return Math.round((pixelHeight - 1) / 2);
|
|
1637
|
+
}
|
|
1638
|
+
const baselineValue = min <= 0 && max >= 0 ? 0 : min > 0 ? min : max;
|
|
1639
|
+
return Math.round(linearScale(baselineValue, [min, max], [pixelHeight - 1, 0]));
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/components/common/Axis.tsx
|
|
1643
|
+
var Axis = ({
|
|
1644
|
+
type,
|
|
1645
|
+
min,
|
|
1646
|
+
max,
|
|
1647
|
+
tickCount = 5,
|
|
1648
|
+
tickFormat = defaultTickFormat,
|
|
1649
|
+
label,
|
|
1650
|
+
length,
|
|
1651
|
+
color = "gray",
|
|
1652
|
+
showGrid: _showGrid = false,
|
|
1653
|
+
integerScale = false
|
|
1654
|
+
}) => {
|
|
1655
|
+
const ticks = useMemo(() => {
|
|
1656
|
+
if (tickCount <= 0) {
|
|
1657
|
+
return [];
|
|
1658
|
+
}
|
|
1659
|
+
if (max === min) {
|
|
1660
|
+
return [{ value: min, position: 0 }];
|
|
1661
|
+
}
|
|
1662
|
+
let effectiveTickCount = tickCount;
|
|
1663
|
+
let step = (max - min) / (tickCount - 1);
|
|
1664
|
+
if (integerScale) {
|
|
1665
|
+
const range = max - min;
|
|
1666
|
+
if (range < tickCount - 1) {
|
|
1667
|
+
effectiveTickCount = range + 1;
|
|
1668
|
+
step = 1;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return Array.from({ length: effectiveTickCount }, (_, i) => {
|
|
1672
|
+
let value = min + i * step;
|
|
1673
|
+
if (integerScale) {
|
|
1674
|
+
value = Math.round(value);
|
|
1675
|
+
}
|
|
1676
|
+
const position = (value - min) / (max - min) * length;
|
|
1677
|
+
return { value, position };
|
|
1678
|
+
});
|
|
1679
|
+
}, [min, max, tickCount, length, integerScale]);
|
|
1680
|
+
if (type === "x") {
|
|
1681
|
+
const xConfig = (() => {
|
|
1682
|
+
let previousEnd = 0;
|
|
1683
|
+
const elements = [];
|
|
1684
|
+
ticks.forEach((tick, i) => {
|
|
1685
|
+
const tickLabel = tickFormat(tick.value);
|
|
1686
|
+
const labelWidth = tickLabel.length;
|
|
1687
|
+
const intendedStart = Math.floor(tick.position - labelWidth / 2);
|
|
1688
|
+
const actualStart = Math.max(previousEnd, intendedStart);
|
|
1689
|
+
const spaces = Math.max(0, actualStart - previousEnd);
|
|
1690
|
+
if (spaces > 0) {
|
|
1691
|
+
elements.push(/* @__PURE__ */ React6.createElement(Text, { key: `space-${i}` }, " ".repeat(spaces)));
|
|
1692
|
+
}
|
|
1693
|
+
elements.push(
|
|
1694
|
+
/* @__PURE__ */ React6.createElement(Text, { key: `tick-${i}`, color, wrap: "truncate" }, tickLabel)
|
|
1695
|
+
);
|
|
1696
|
+
previousEnd = actualStart + labelWidth;
|
|
1697
|
+
});
|
|
1698
|
+
return elements;
|
|
1699
|
+
})();
|
|
1700
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row" }, xConfig), label && /* @__PURE__ */ React6.createElement(Box, { justifyContent: "center", marginTop: 0 }, /* @__PURE__ */ React6.createElement(Text, { color, dimColor: true }, label)));
|
|
1701
|
+
}
|
|
1702
|
+
return /* @__PURE__ */ React6.createElement(
|
|
1703
|
+
Box,
|
|
1704
|
+
{
|
|
1705
|
+
flexDirection: "column",
|
|
1706
|
+
alignItems: "flex-end",
|
|
1707
|
+
height: length,
|
|
1708
|
+
justifyContent: "space-between"
|
|
1709
|
+
},
|
|
1710
|
+
ticks.slice().reverse().map((tick, i) => /* @__PURE__ */ React6.createElement(Box, { key: `tick-${i}` }, /* @__PURE__ */ React6.createElement(Text, { color, wrap: "truncate" }, tickFormat(tick.value))))
|
|
1711
|
+
);
|
|
1712
|
+
};
|
|
1713
|
+
var Legend = ({
|
|
1714
|
+
items,
|
|
1715
|
+
position = "horizontal",
|
|
1716
|
+
color,
|
|
1717
|
+
gap = 2
|
|
1718
|
+
}) => {
|
|
1719
|
+
if (items.length === 0) {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: position === "horizontal" ? "row" : "column", gap }, items.map((item, i) => /* @__PURE__ */ React6.createElement(Box, { key: `legend-${i}`, gap: 1 }, /* @__PURE__ */ React6.createElement(Text, { color: color ?? item.color }, item.symbol || "\u25CF"), /* @__PURE__ */ React6.createElement(Text, { ...color && { color } }, item.name))));
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
// src/components/common/ChartContainer.tsx
|
|
1726
|
+
var ChartContainer = ({
|
|
1727
|
+
layout,
|
|
1728
|
+
showXAxis = true,
|
|
1729
|
+
showYAxis = true,
|
|
1730
|
+
xAxisConfig,
|
|
1731
|
+
yAxisConfig,
|
|
1732
|
+
showLegend = true,
|
|
1733
|
+
legendPosition = "right",
|
|
1734
|
+
legendItems = [],
|
|
1735
|
+
children
|
|
1736
|
+
}) => {
|
|
1737
|
+
const { totalWidth, plotWidth, plotHeight, yAxisWidth } = layout;
|
|
1738
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column", width: totalWidth }, showLegend && legendPosition === "top" && /* @__PURE__ */ React6.createElement(Box, { marginBottom: 1, marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6.createElement(Legend, { items: legendItems, position: "horizontal" })), /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row" }, showYAxis && yAxisConfig && /* @__PURE__ */ React6.createElement(Box, { marginRight: 1, width: yAxisWidth }, /* @__PURE__ */ React6.createElement(Axis, { type: "y", length: plotHeight, ...yAxisConfig })), /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column" }, children), showLegend && legendPosition === "right" && /* @__PURE__ */ React6.createElement(Box, { marginLeft: 2 }, /* @__PURE__ */ React6.createElement(Legend, { items: legendItems, position: "vertical" }))), showXAxis && xAxisConfig && /* @__PURE__ */ React6.createElement(Box, { marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6.createElement(Axis, { type: "x", length: plotWidth, ...xAxisConfig })), showLegend && legendPosition === "bottom" && /* @__PURE__ */ React6.createElement(Box, { marginTop: 1, marginLeft: showYAxis ? yAxisWidth + 1 : 0 }, /* @__PURE__ */ React6.createElement(Legend, { items: legendItems, position: "horizontal" })));
|
|
1739
|
+
};
|
|
1740
|
+
function useChartCore(props) {
|
|
1741
|
+
const { series: seriesProp, data, seriesName, colors: colorsProp, colorPalette } = props;
|
|
1742
|
+
const series = useMemo(
|
|
1743
|
+
() => resolveSeriesInput(buildSeriesInputParams(seriesProp, data, seriesName)),
|
|
1744
|
+
[seriesProp, data, seriesName]
|
|
1745
|
+
);
|
|
1746
|
+
const { min, max, maxLength } = useMemo(() => computeSeriesExtent(series), [series]);
|
|
1747
|
+
const colors = useMemo(
|
|
1748
|
+
() => resolveSeriesColors(series, colorsProp, colorPalette),
|
|
1749
|
+
[series, colorsProp, colorPalette]
|
|
1750
|
+
);
|
|
1751
|
+
const legendItems = useMemo(
|
|
1752
|
+
() => series.map((item, i) => ({
|
|
1753
|
+
name: item.name,
|
|
1754
|
+
color: item.color ?? colors[i] ?? "cyan",
|
|
1755
|
+
symbol: "\u25CF"
|
|
1756
|
+
})),
|
|
1757
|
+
[series, colors]
|
|
1758
|
+
);
|
|
1759
|
+
return {
|
|
1760
|
+
series,
|
|
1761
|
+
min,
|
|
1762
|
+
max,
|
|
1763
|
+
maxLength,
|
|
1764
|
+
colors,
|
|
1765
|
+
legendItems
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
var DEFAULT_RENDERER_CHAIN = ["braille", "block", "ascii"];
|
|
1769
|
+
var BAR_CHART_RENDERER_CHAIN = ["block", "braille", "ascii"];
|
|
1770
|
+
function useChartRenderer(props, defaultChain = DEFAULT_RENDERER_CHAIN) {
|
|
1771
|
+
const { getRenderer, selectBest } = useInkHud();
|
|
1772
|
+
const { renderer: preferredRenderer, rendererChain = defaultChain } = props;
|
|
1773
|
+
return useMemo(() => {
|
|
1774
|
+
if (preferredRenderer) {
|
|
1775
|
+
return getRenderer(preferredRenderer);
|
|
1776
|
+
}
|
|
1777
|
+
return selectBest(rendererChain);
|
|
1778
|
+
}, [preferredRenderer, rendererChain, getRenderer, selectBest]);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// src/components/LineChart.tsx
|
|
1782
|
+
var LineChart = (props) => {
|
|
1783
|
+
const {
|
|
1784
|
+
showLegend = true,
|
|
1785
|
+
showAxis = true,
|
|
1786
|
+
showXAxis,
|
|
1787
|
+
showYAxis,
|
|
1788
|
+
legendPosition = "right",
|
|
1789
|
+
xAxisLabel,
|
|
1790
|
+
yAxisLabel,
|
|
1791
|
+
xTickCount = 5,
|
|
1792
|
+
yTickCount = 5,
|
|
1793
|
+
xTickFormat,
|
|
1794
|
+
yTickFormat,
|
|
1795
|
+
rendererChain = DEFAULT_RENDERER_CHAIN,
|
|
1796
|
+
xIntegerScale = true,
|
|
1797
|
+
yIntegerScale = false
|
|
1798
|
+
} = props;
|
|
1799
|
+
const renderXAxis = showXAxis ?? showAxis;
|
|
1800
|
+
const renderYAxis = showYAxis ?? showAxis;
|
|
1801
|
+
const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
|
|
1802
|
+
const renderer = useChartRenderer(props, rendererChain);
|
|
1803
|
+
const layout = useChartLayoutSimple(props, min, max);
|
|
1804
|
+
const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
|
|
1805
|
+
const coloredLines = useMemo(
|
|
1806
|
+
() => renderLineChartCanvas({
|
|
1807
|
+
renderer,
|
|
1808
|
+
series,
|
|
1809
|
+
canvasWidth,
|
|
1810
|
+
canvasHeight,
|
|
1811
|
+
min,
|
|
1812
|
+
max,
|
|
1813
|
+
colors
|
|
1814
|
+
}),
|
|
1815
|
+
[renderer, series, canvasWidth, canvasHeight, min, max, colors]
|
|
1816
|
+
);
|
|
1817
|
+
if (coloredLines.length === 0) {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
const yAxisConfig = {
|
|
1821
|
+
min,
|
|
1822
|
+
max,
|
|
1823
|
+
tickCount: yTickCount,
|
|
1824
|
+
tickFormat: yTickFormat ?? defaultTickFormat,
|
|
1825
|
+
...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
|
|
1826
|
+
...yAxisLabel ? { label: yAxisLabel } : {}
|
|
1827
|
+
};
|
|
1828
|
+
const xAxisConfig = {
|
|
1829
|
+
min: 0,
|
|
1830
|
+
max: Math.max(0, maxLength - 1),
|
|
1831
|
+
tickCount: xTickCount,
|
|
1832
|
+
tickFormat: xTickFormat ?? defaultTickFormat,
|
|
1833
|
+
...xIntegerScale !== void 0 && { integerScale: xIntegerScale },
|
|
1834
|
+
...xAxisLabel ? { label: xAxisLabel } : {}
|
|
1835
|
+
};
|
|
1836
|
+
return /* @__PURE__ */ React6.createElement(
|
|
1837
|
+
ChartContainer,
|
|
1838
|
+
{
|
|
1839
|
+
layout,
|
|
1840
|
+
showLegend,
|
|
1841
|
+
legendPosition,
|
|
1842
|
+
legendItems,
|
|
1843
|
+
showXAxis: renderXAxis,
|
|
1844
|
+
showYAxis: renderYAxis,
|
|
1845
|
+
xAxisConfig,
|
|
1846
|
+
yAxisConfig
|
|
1847
|
+
},
|
|
1848
|
+
coloredLines.map((line, i) => /* @__PURE__ */ React6.createElement(Text, { key: i }, line.map((seg, j) => /* @__PURE__ */ React6.createElement(
|
|
1849
|
+
Text,
|
|
1850
|
+
{
|
|
1851
|
+
key: j,
|
|
1852
|
+
...seg.color ? { color: seg.color } : {},
|
|
1853
|
+
...seg.backgroundColor ? { backgroundColor: seg.backgroundColor } : {}
|
|
1854
|
+
},
|
|
1855
|
+
seg.text
|
|
1856
|
+
))))
|
|
1857
|
+
);
|
|
1858
|
+
};
|
|
1859
|
+
function renderLineChartCanvas({
|
|
1860
|
+
renderer,
|
|
1861
|
+
series,
|
|
1862
|
+
canvasWidth,
|
|
1863
|
+
canvasHeight,
|
|
1864
|
+
min,
|
|
1865
|
+
max,
|
|
1866
|
+
colors
|
|
1867
|
+
}) {
|
|
1868
|
+
const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
|
|
1869
|
+
if (pixelWidth <= 0 || pixelHeight <= 0 || series.length === 0) {
|
|
1870
|
+
return [];
|
|
1871
|
+
}
|
|
1872
|
+
const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
|
|
1873
|
+
for (let si = 0; si < series.length; si++) {
|
|
1874
|
+
const s = series[si];
|
|
1875
|
+
if (!s) continue;
|
|
1876
|
+
const { data } = s;
|
|
1877
|
+
const color = s.color ?? colors[si] ?? "cyan";
|
|
1878
|
+
if (data.length === 0) continue;
|
|
1879
|
+
const xScale = data.length > 1 ? (pixelWidth - 1) / (data.length - 1) : 0;
|
|
1880
|
+
const scaleY = (value) => {
|
|
1881
|
+
if (max === min) {
|
|
1882
|
+
return Math.round((pixelHeight - 1) / 2);
|
|
1883
|
+
}
|
|
1884
|
+
return Math.round(linearScale(value, [min, max], [pixelHeight - 1, 0]));
|
|
1885
|
+
};
|
|
1886
|
+
for (let i = 0; i < data.length - 1; i++) {
|
|
1887
|
+
const currVal = data[i];
|
|
1888
|
+
const nextVal = data[i + 1];
|
|
1889
|
+
if (currVal === void 0 || nextVal === void 0) continue;
|
|
1890
|
+
const x0 = Math.round(i * xScale);
|
|
1891
|
+
const y0 = scaleY(currVal);
|
|
1892
|
+
const x1 = Math.round((i + 1) * xScale);
|
|
1893
|
+
const y1 = scaleY(nextVal);
|
|
1894
|
+
renderer.drawLine(canvas, x0, y0, x1, y1, { active: true, color });
|
|
1895
|
+
}
|
|
1896
|
+
if (data.length === 1) {
|
|
1897
|
+
const val = data[0];
|
|
1898
|
+
if (val !== void 0) {
|
|
1899
|
+
const x = Math.round(pixelWidth / 2);
|
|
1900
|
+
const y = scaleY(val);
|
|
1901
|
+
renderer.setPixel(canvas, x, y, { active: true, color });
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
|
|
1906
|
+
}
|
|
1907
|
+
function fillVerticalLine(renderer, canvas, x, y1, y2, color) {
|
|
1908
|
+
const fillStart = Math.min(y1, y2);
|
|
1909
|
+
const fillEnd = Math.max(y1, y2);
|
|
1910
|
+
for (let fy = fillStart; fy <= fillEnd; fy++) {
|
|
1911
|
+
renderer.setPixel(canvas, x, fy, {
|
|
1912
|
+
active: true,
|
|
1913
|
+
...color ? { color } : {}
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
function renderAreaChartCanvas(params) {
|
|
1918
|
+
const { renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors } = params;
|
|
1919
|
+
if (series.length === 0 || maxLength === 0) {
|
|
1920
|
+
return [];
|
|
1921
|
+
}
|
|
1922
|
+
const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
|
|
1923
|
+
const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
|
|
1924
|
+
const baselineY = computeBaselineY({ min, max, pixelHeight });
|
|
1925
|
+
const xStep = maxLength > 1 ? (pixelWidth - 1) / (maxLength - 1) : 0;
|
|
1926
|
+
const scaleValue = (value) => {
|
|
1927
|
+
if (max === min) {
|
|
1928
|
+
return Math.round((pixelHeight - 1) / 2);
|
|
1929
|
+
}
|
|
1930
|
+
return Math.round(linearScale(value, [min, max], [pixelHeight - 1, 0]));
|
|
1931
|
+
};
|
|
1932
|
+
const sortedData = series.map((s, i) => {
|
|
1933
|
+
const maxVal = Math.max(...s.data);
|
|
1934
|
+
return { series: s, color: colors[i], maxVal };
|
|
1935
|
+
}).sort((a, b) => b.maxVal - a.maxVal);
|
|
1936
|
+
for (const { series: item, color } of sortedData) {
|
|
1937
|
+
let prevPoint = null;
|
|
1938
|
+
for (let idx = 0; idx < item.data.length; idx++) {
|
|
1939
|
+
const value = item.data[idx];
|
|
1940
|
+
if (value === void 0) continue;
|
|
1941
|
+
const point = { x: Math.round(idx * xStep), y: scaleValue(value) };
|
|
1942
|
+
if (prevPoint) {
|
|
1943
|
+
const startX = Math.min(prevPoint.x, point.x);
|
|
1944
|
+
const endX = Math.max(prevPoint.x, point.x);
|
|
1945
|
+
for (let px = startX; px <= endX; px++) {
|
|
1946
|
+
const t = endX === startX ? 0 : (px - prevPoint.x) / (point.x - prevPoint.x);
|
|
1947
|
+
const py = Math.floor(prevPoint.y + (point.y - prevPoint.y) * t);
|
|
1948
|
+
fillVerticalLine(renderer, canvas, px, py, baselineY, color);
|
|
1949
|
+
}
|
|
1950
|
+
renderer.drawLine(canvas, prevPoint.x, prevPoint.y, point.x, point.y, {
|
|
1951
|
+
active: true,
|
|
1952
|
+
...color ? { color } : {}
|
|
1953
|
+
});
|
|
1954
|
+
} else {
|
|
1955
|
+
fillVerticalLine(renderer, canvas, point.x, point.y, baselineY, color);
|
|
1956
|
+
}
|
|
1957
|
+
prevPoint = point;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
|
|
1961
|
+
}
|
|
1962
|
+
var AreaChart = (props) => {
|
|
1963
|
+
const {
|
|
1964
|
+
showLegend = true,
|
|
1965
|
+
showAxis = true,
|
|
1966
|
+
showXAxis,
|
|
1967
|
+
showYAxis,
|
|
1968
|
+
legendPosition = "right",
|
|
1969
|
+
xAxisLabel,
|
|
1970
|
+
yAxisLabel,
|
|
1971
|
+
xTickCount = 5,
|
|
1972
|
+
yTickCount = 5,
|
|
1973
|
+
xTickFormat,
|
|
1974
|
+
yTickFormat,
|
|
1975
|
+
rendererChain = DEFAULT_RENDERER_CHAIN,
|
|
1976
|
+
xIntegerScale = true,
|
|
1977
|
+
yIntegerScale = false
|
|
1978
|
+
} = props;
|
|
1979
|
+
const renderXAxis = showXAxis ?? showAxis;
|
|
1980
|
+
const renderYAxis = showYAxis ?? showAxis;
|
|
1981
|
+
const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
|
|
1982
|
+
const renderer = useChartRenderer(props, rendererChain);
|
|
1983
|
+
const layout = useChartLayoutSimple(props, min, max);
|
|
1984
|
+
const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
|
|
1985
|
+
const coloredLines = useMemo(
|
|
1986
|
+
() => renderAreaChartCanvas({
|
|
1987
|
+
renderer,
|
|
1988
|
+
series,
|
|
1989
|
+
canvasWidth,
|
|
1990
|
+
canvasHeight,
|
|
1991
|
+
min,
|
|
1992
|
+
max,
|
|
1993
|
+
maxLength,
|
|
1994
|
+
colors
|
|
1995
|
+
}),
|
|
1996
|
+
[renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors]
|
|
1997
|
+
);
|
|
1998
|
+
if (coloredLines.length === 0) {
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
const yAxisConfig = {
|
|
2002
|
+
min,
|
|
2003
|
+
max,
|
|
2004
|
+
tickCount: yTickCount,
|
|
2005
|
+
tickFormat: yTickFormat ?? defaultTickFormat,
|
|
2006
|
+
...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
|
|
2007
|
+
...yAxisLabel ? { label: yAxisLabel } : {}
|
|
2008
|
+
};
|
|
2009
|
+
const xAxisConfig = {
|
|
2010
|
+
min: 0,
|
|
2011
|
+
max: Math.max(0, maxLength - 1),
|
|
2012
|
+
tickCount: xTickCount,
|
|
2013
|
+
tickFormat: xTickFormat ?? defaultTickFormat,
|
|
2014
|
+
...xIntegerScale !== void 0 && { integerScale: xIntegerScale },
|
|
2015
|
+
...xAxisLabel ? { label: xAxisLabel } : {}
|
|
2016
|
+
};
|
|
2017
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2018
|
+
ChartContainer,
|
|
2019
|
+
{
|
|
2020
|
+
layout,
|
|
2021
|
+
showXAxis: renderXAxis,
|
|
2022
|
+
showYAxis: renderYAxis,
|
|
2023
|
+
xAxisConfig,
|
|
2024
|
+
yAxisConfig,
|
|
2025
|
+
showLegend,
|
|
2026
|
+
legendPosition,
|
|
2027
|
+
legendItems
|
|
2028
|
+
},
|
|
2029
|
+
coloredLines.map((segments, i) => /* @__PURE__ */ React6.createElement(Text, { key: `chart-line-${i}` }, segments.map(
|
|
2030
|
+
(segment, j) => segment.color ? /* @__PURE__ */ React6.createElement(Text, { key: `seg-${i}-${j}`, color: segment.color }, segment.text) : /* @__PURE__ */ React6.createElement(Text, { key: `seg-${i}-${j}` }, segment.text)
|
|
2031
|
+
)))
|
|
2032
|
+
);
|
|
2033
|
+
};
|
|
2034
|
+
var alignDown = (val, alignment) => Math.floor(val / alignment) * alignment;
|
|
2035
|
+
function computeVerticalLayout(params) {
|
|
2036
|
+
const { pixelWidth, categoryCount, seriesCount, alignment } = params;
|
|
2037
|
+
const maxGroupWidth = alignDown(
|
|
2038
|
+
Math.max(alignment, Math.floor(pixelWidth / Math.max(1, categoryCount))),
|
|
2039
|
+
alignment
|
|
2040
|
+
);
|
|
2041
|
+
const groupWidth = maxGroupWidth;
|
|
2042
|
+
const calculateForPadding = (p) => {
|
|
2043
|
+
const available = groupWidth - p * 2;
|
|
2044
|
+
const minNeed = seriesCount * alignment;
|
|
2045
|
+
if (available < minNeed) return null;
|
|
2046
|
+
const gap = available >= seriesCount * alignment + (seriesCount - 1) * alignment ? alignment : 0;
|
|
2047
|
+
const totalGap = gap * (seriesCount - 1);
|
|
2048
|
+
const remaining = available - totalGap;
|
|
2049
|
+
let barWidth = Math.floor(remaining / seriesCount);
|
|
2050
|
+
barWidth = alignDown(barWidth, alignment);
|
|
2051
|
+
if (barWidth < alignment) return null;
|
|
2052
|
+
return { groupWidth, barWidth, barGap: gap, groupPadding: p };
|
|
2053
|
+
};
|
|
2054
|
+
const paddingStandard = alignment;
|
|
2055
|
+
const layoutStandard = calculateForPadding(paddingStandard);
|
|
2056
|
+
const layoutCompact = calculateForPadding(0);
|
|
2057
|
+
if (!layoutStandard) {
|
|
2058
|
+
return layoutCompact ?? { groupWidth, barWidth: alignment, barGap: 0, groupPadding: 0 };
|
|
2059
|
+
}
|
|
2060
|
+
if (!layoutCompact) {
|
|
2061
|
+
return layoutStandard;
|
|
2062
|
+
}
|
|
2063
|
+
if (layoutCompact.barWidth > layoutStandard.barWidth) {
|
|
2064
|
+
return layoutCompact;
|
|
2065
|
+
}
|
|
2066
|
+
return layoutStandard;
|
|
2067
|
+
}
|
|
2068
|
+
function computeHorizontalLayout(params) {
|
|
2069
|
+
const { pixelHeight, categoryCount, seriesCount, alignment } = params;
|
|
2070
|
+
const maxGroupHeight = alignDown(
|
|
2071
|
+
Math.max(alignment, Math.floor(pixelHeight / Math.max(1, categoryCount))),
|
|
2072
|
+
alignment
|
|
2073
|
+
);
|
|
2074
|
+
if (alignment >= 4) {
|
|
2075
|
+
const stackHeight = seriesCount * alignment;
|
|
2076
|
+
const groupHeight = Math.max(alignment, stackHeight);
|
|
2077
|
+
return {
|
|
2078
|
+
groupHeight,
|
|
2079
|
+
// Strictly tight
|
|
2080
|
+
barHeight: alignment,
|
|
2081
|
+
// Min thickness (1 block char)
|
|
2082
|
+
barGap: 0,
|
|
2083
|
+
// No gaps
|
|
2084
|
+
groupPadding: 0
|
|
2085
|
+
// No padding
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
const padding = alignment;
|
|
2089
|
+
const available = maxGroupHeight - padding * 2;
|
|
2090
|
+
if (available > seriesCount * alignment * 1.5) {
|
|
2091
|
+
const gap = alignment;
|
|
2092
|
+
const barHeight = alignDown(
|
|
2093
|
+
Math.floor((available - gap * (seriesCount - 1)) / seriesCount),
|
|
2094
|
+
alignment
|
|
2095
|
+
);
|
|
2096
|
+
return { groupHeight: maxGroupHeight, barHeight, barGap: gap, groupPadding: padding };
|
|
2097
|
+
}
|
|
2098
|
+
const availableCompact = maxGroupHeight;
|
|
2099
|
+
const barHeightCompact = alignDown(Math.floor(availableCompact / seriesCount), alignment);
|
|
2100
|
+
return { groupHeight: maxGroupHeight, barHeight: barHeightCompact, barGap: 0, groupPadding: 0 };
|
|
2101
|
+
}
|
|
2102
|
+
function renderVertical(params) {
|
|
2103
|
+
const { renderer, series, width, height, min, max, maxLength, colors } = params;
|
|
2104
|
+
const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, width, height);
|
|
2105
|
+
const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
|
|
2106
|
+
const resolution = renderer.getResolution();
|
|
2107
|
+
const alignment = resolution.horizontal;
|
|
2108
|
+
const { groupWidth, barWidth, barGap, groupPadding } = computeVerticalLayout({
|
|
2109
|
+
pixelWidth,
|
|
2110
|
+
categoryCount: maxLength,
|
|
2111
|
+
seriesCount: series.length,
|
|
2112
|
+
alignment
|
|
2113
|
+
});
|
|
2114
|
+
const baselineY = computeBaselineY({ min, max, pixelHeight });
|
|
2115
|
+
const scaleValue = (val) => {
|
|
2116
|
+
if (max === min) return Math.round((pixelHeight - 1) / 2);
|
|
2117
|
+
return Math.round(linearScale(val, [min, max], [pixelHeight - 1, 0]));
|
|
2118
|
+
};
|
|
2119
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2120
|
+
const groupLeft = i * groupWidth + groupPadding;
|
|
2121
|
+
for (let j = 0; j < series.length; j++) {
|
|
2122
|
+
const val = series[j]?.data[i] ?? 0;
|
|
2123
|
+
const yVal = scaleValue(val);
|
|
2124
|
+
const x = groupLeft + j * (barWidth + barGap);
|
|
2125
|
+
const xEnd = Math.min(pixelWidth - 1, x + barWidth - 1);
|
|
2126
|
+
const yStart = Math.min(yVal, baselineY);
|
|
2127
|
+
const yEnd = Math.max(yVal, baselineY);
|
|
2128
|
+
const color = colors[j];
|
|
2129
|
+
for (let yy = yStart; yy <= yEnd; yy++) {
|
|
2130
|
+
for (let xx = x; xx <= xEnd; xx++) {
|
|
2131
|
+
renderer.setPixel(canvas, xx, yy, {
|
|
2132
|
+
active: true,
|
|
2133
|
+
...color ? { color } : {}
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
|
|
2140
|
+
}
|
|
2141
|
+
function renderHorizontal(params) {
|
|
2142
|
+
const { renderer, series, width, height, min, max, maxLength, colors } = params;
|
|
2143
|
+
const resolution = renderer.getResolution();
|
|
2144
|
+
const pixelWidth = width * resolution.horizontal;
|
|
2145
|
+
const pixelHeight = height * resolution.vertical;
|
|
2146
|
+
const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
|
|
2147
|
+
const alignment = resolution.vertical;
|
|
2148
|
+
const { groupHeight, barHeight, barGap, groupPadding } = computeHorizontalLayout({
|
|
2149
|
+
pixelHeight,
|
|
2150
|
+
categoryCount: maxLength,
|
|
2151
|
+
seriesCount: series.length,
|
|
2152
|
+
alignment
|
|
2153
|
+
});
|
|
2154
|
+
const scaleValue = (val) => {
|
|
2155
|
+
if (max === min) return Math.round((pixelWidth - 1) / 2);
|
|
2156
|
+
return Math.round(linearScale(val, [min, max], [0, pixelWidth - 1]));
|
|
2157
|
+
};
|
|
2158
|
+
let baselineVal = 0;
|
|
2159
|
+
if (min > 0) baselineVal = min;
|
|
2160
|
+
else if (max < 0) baselineVal = max;
|
|
2161
|
+
const baselineX = scaleValue(baselineVal);
|
|
2162
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2163
|
+
const groupTop = i * groupHeight + groupPadding;
|
|
2164
|
+
for (let j = 0; j < series.length; j++) {
|
|
2165
|
+
const val = series[j]?.data[i] ?? 0;
|
|
2166
|
+
const xVal = scaleValue(val);
|
|
2167
|
+
const y = groupTop + j * (barHeight + barGap);
|
|
2168
|
+
const yEnd = Math.min(pixelHeight - 1, y + barHeight - 1);
|
|
2169
|
+
if (y >= pixelHeight) continue;
|
|
2170
|
+
const xStart = Math.min(xVal, baselineX);
|
|
2171
|
+
const xEndFill = Math.max(xVal, baselineX);
|
|
2172
|
+
const color = colors[j];
|
|
2173
|
+
for (let yy = y; yy <= yEnd; yy++) {
|
|
2174
|
+
for (let xx = xStart; xx <= xEndFill; xx++) {
|
|
2175
|
+
renderer.setPixel(canvas, xx, yy, {
|
|
2176
|
+
active: true,
|
|
2177
|
+
...color ? { color } : {}
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
|
|
2184
|
+
}
|
|
2185
|
+
var BarChart = (props) => {
|
|
2186
|
+
const {
|
|
2187
|
+
showLegend = true,
|
|
2188
|
+
showAxis = true,
|
|
2189
|
+
showXAxis,
|
|
2190
|
+
showYAxis,
|
|
2191
|
+
legendPosition = "right",
|
|
2192
|
+
orientation = "vertical",
|
|
2193
|
+
xAxisLabel,
|
|
2194
|
+
yAxisLabel,
|
|
2195
|
+
xTickCount = 5,
|
|
2196
|
+
yTickCount = 5,
|
|
2197
|
+
xTickFormat,
|
|
2198
|
+
yTickFormat,
|
|
2199
|
+
rendererChain = BAR_CHART_RENDERER_CHAIN,
|
|
2200
|
+
xIntegerScale,
|
|
2201
|
+
yIntegerScale
|
|
2202
|
+
} = props;
|
|
2203
|
+
const renderXAxis = showXAxis ?? showAxis;
|
|
2204
|
+
const renderYAxis = showYAxis ?? showAxis;
|
|
2205
|
+
const { series, min, max, maxLength, colors, legendItems } = useChartCore(props);
|
|
2206
|
+
const renderer = useChartRenderer(props, rendererChain);
|
|
2207
|
+
const layout = useChartLayoutSimple(props, min, max);
|
|
2208
|
+
const { plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
|
|
2209
|
+
const coloredLines = useMemo(() => {
|
|
2210
|
+
if (series.length === 0 || maxLength === 0) return [];
|
|
2211
|
+
if (orientation === "horizontal") {
|
|
2212
|
+
return renderHorizontal({
|
|
2213
|
+
renderer,
|
|
2214
|
+
series,
|
|
2215
|
+
width: canvasWidth,
|
|
2216
|
+
height: canvasHeight,
|
|
2217
|
+
min,
|
|
2218
|
+
max,
|
|
2219
|
+
maxLength,
|
|
2220
|
+
colors
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
return renderVertical({
|
|
2224
|
+
renderer,
|
|
2225
|
+
series,
|
|
2226
|
+
width: canvasWidth,
|
|
2227
|
+
height: canvasHeight,
|
|
2228
|
+
min,
|
|
2229
|
+
max,
|
|
2230
|
+
maxLength,
|
|
2231
|
+
colors
|
|
2232
|
+
});
|
|
2233
|
+
}, [renderer, series, canvasWidth, canvasHeight, min, max, maxLength, colors, orientation]);
|
|
2234
|
+
if (coloredLines.length === 0) return null;
|
|
2235
|
+
const yAxisConfig = {
|
|
2236
|
+
min,
|
|
2237
|
+
max,
|
|
2238
|
+
tickCount: yTickCount,
|
|
2239
|
+
tickFormat: yTickFormat ?? defaultTickFormat,
|
|
2240
|
+
...yIntegerScale !== void 0 && { integerScale: yIntegerScale },
|
|
2241
|
+
...yAxisLabel ? { label: yAxisLabel } : {}
|
|
2242
|
+
};
|
|
2243
|
+
const xAxisConfig = {
|
|
2244
|
+
min: 0,
|
|
2245
|
+
max: Math.max(0, maxLength - 1),
|
|
2246
|
+
tickCount: xTickCount,
|
|
2247
|
+
tickFormat: xTickFormat ?? defaultTickFormat,
|
|
2248
|
+
integerScale: xIntegerScale ?? true,
|
|
2249
|
+
...xAxisLabel ? { label: xAxisLabel } : {}
|
|
2250
|
+
};
|
|
2251
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2252
|
+
ChartContainer,
|
|
2253
|
+
{
|
|
2254
|
+
layout,
|
|
2255
|
+
showXAxis: renderXAxis,
|
|
2256
|
+
showYAxis: renderYAxis,
|
|
2257
|
+
xAxisConfig,
|
|
2258
|
+
yAxisConfig,
|
|
2259
|
+
showLegend,
|
|
2260
|
+
legendPosition,
|
|
2261
|
+
legendItems
|
|
2262
|
+
},
|
|
2263
|
+
coloredLines.map((segments, i) => /* @__PURE__ */ React6.createElement(Text, { key: `chart-line-${i}` }, segments.map((segment, j) => /* @__PURE__ */ React6.createElement(
|
|
2264
|
+
Text,
|
|
2265
|
+
{
|
|
2266
|
+
key: `seg-${i}-${j}`,
|
|
2267
|
+
...segment.color ? { color: segment.color } : {},
|
|
2268
|
+
...segment.backgroundColor ? { backgroundColor: segment.backgroundColor } : {}
|
|
2269
|
+
},
|
|
2270
|
+
segment.text
|
|
2271
|
+
))))
|
|
2272
|
+
);
|
|
2273
|
+
};
|
|
2274
|
+
function resolveDataItems(data, labels) {
|
|
2275
|
+
if (!data || data.length === 0) {
|
|
2276
|
+
return [];
|
|
2277
|
+
}
|
|
2278
|
+
if (typeof data[0] === "number") {
|
|
2279
|
+
return data.map((value, i) => ({
|
|
2280
|
+
name: labels?.[i] ?? `Item ${i + 1}`,
|
|
2281
|
+
value
|
|
2282
|
+
}));
|
|
2283
|
+
}
|
|
2284
|
+
return data;
|
|
2285
|
+
}
|
|
2286
|
+
var TWO_PI = Math.PI * 2;
|
|
2287
|
+
var START_ANGLE = -Math.PI / 2;
|
|
2288
|
+
function resolveRadius(pixelWidth, pixelHeight, customRadius) {
|
|
2289
|
+
const centerX = Math.floor(pixelWidth / 2);
|
|
2290
|
+
const centerY = Math.floor(pixelHeight / 2);
|
|
2291
|
+
const maxRadius = Math.max(
|
|
2292
|
+
0,
|
|
2293
|
+
Math.min(Math.floor(pixelWidth / 2) - 1, Math.floor(pixelHeight / 2) - 1)
|
|
2294
|
+
);
|
|
2295
|
+
const radius = customRadius ?? maxRadius;
|
|
2296
|
+
return { centerX, centerY, radius };
|
|
2297
|
+
}
|
|
2298
|
+
function buildAngleStops(data, total) {
|
|
2299
|
+
if (total <= 0) {
|
|
2300
|
+
return [];
|
|
2301
|
+
}
|
|
2302
|
+
const stops = [];
|
|
2303
|
+
let current = 0;
|
|
2304
|
+
data.forEach((item, index) => {
|
|
2305
|
+
if (item.value <= 0) {
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
current += item.value / total * TWO_PI;
|
|
2309
|
+
stops.push({ index, end: current });
|
|
2310
|
+
});
|
|
2311
|
+
return stops;
|
|
2312
|
+
}
|
|
2313
|
+
function resolveSliceIndex(angle, stops) {
|
|
2314
|
+
if (stops.length === 0) {
|
|
2315
|
+
return null;
|
|
2316
|
+
}
|
|
2317
|
+
for (const stop of stops) {
|
|
2318
|
+
if (angle <= stop.end) {
|
|
2319
|
+
return stop.index;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return stops[stops.length - 1]?.index ?? null;
|
|
2323
|
+
}
|
|
2324
|
+
var PieChart = ({
|
|
2325
|
+
data: dataProp,
|
|
2326
|
+
labels,
|
|
2327
|
+
width,
|
|
2328
|
+
height,
|
|
2329
|
+
radius: customRadius,
|
|
2330
|
+
aspectRatio,
|
|
2331
|
+
donutRatio = 0,
|
|
2332
|
+
showLabels = false,
|
|
2333
|
+
showLegend = true,
|
|
2334
|
+
legendPosition = "right",
|
|
2335
|
+
colors,
|
|
2336
|
+
colorPalette,
|
|
2337
|
+
renderer: preferredRenderer,
|
|
2338
|
+
rendererChain = ["braille", "block", "ascii"],
|
|
2339
|
+
heightOffset = 0,
|
|
2340
|
+
widthOffset = 0
|
|
2341
|
+
}) => {
|
|
2342
|
+
const data = useMemo(() => resolveDataItems(dataProp, labels), [dataProp, labels]);
|
|
2343
|
+
const layout = useChartLayoutSimple(
|
|
2344
|
+
{
|
|
2345
|
+
...width !== void 0 && { width },
|
|
2346
|
+
...height !== void 0 && { height },
|
|
2347
|
+
showAxis: false,
|
|
2348
|
+
showLegend,
|
|
2349
|
+
legendPosition,
|
|
2350
|
+
widthOffset,
|
|
2351
|
+
heightOffset
|
|
2352
|
+
},
|
|
2353
|
+
0,
|
|
2354
|
+
0
|
|
2355
|
+
);
|
|
2356
|
+
const { totalWidth, plotWidth: canvasWidth, plotHeight: canvasHeight } = layout;
|
|
2357
|
+
const { getRenderer, selectBest } = useInkHud();
|
|
2358
|
+
const renderer = useMemo(() => {
|
|
2359
|
+
if (preferredRenderer) {
|
|
2360
|
+
return getRenderer(preferredRenderer);
|
|
2361
|
+
}
|
|
2362
|
+
return selectBest(rendererChain);
|
|
2363
|
+
}, [preferredRenderer, rendererChain, getRenderer, selectBest]);
|
|
2364
|
+
const ratio = useMemo(() => {
|
|
2365
|
+
if (aspectRatio !== void 0) return aspectRatio;
|
|
2366
|
+
const resolution = renderer.getResolution();
|
|
2367
|
+
return 2 * (resolution.horizontal / resolution.vertical);
|
|
2368
|
+
}, [aspectRatio, renderer]);
|
|
2369
|
+
const itemColors = useMemo(() => {
|
|
2370
|
+
if (colors && colors.length >= data.length) {
|
|
2371
|
+
return colors;
|
|
2372
|
+
}
|
|
2373
|
+
if (colors && colors.length > 0) {
|
|
2374
|
+
return assignColors(data.length, colors);
|
|
2375
|
+
}
|
|
2376
|
+
return assignColors(data.length, colorPalette);
|
|
2377
|
+
}, [data.length, colors, colorPalette]);
|
|
2378
|
+
const { total, percentages } = useMemo(() => {
|
|
2379
|
+
const sum = data.reduce((acc, item) => acc + item.value, 0);
|
|
2380
|
+
const pcts = data.map((item) => sum > 0 ? item.value / sum * 100 : 0);
|
|
2381
|
+
return { total: sum, percentages: pcts };
|
|
2382
|
+
}, [data]);
|
|
2383
|
+
const coloredLines = useMemo(() => {
|
|
2384
|
+
if (data.length === 0 || total <= 0) {
|
|
2385
|
+
return [];
|
|
2386
|
+
}
|
|
2387
|
+
const { pixelWidth, pixelHeight } = getPixelDimensions(renderer, canvasWidth, canvasHeight);
|
|
2388
|
+
const canvas = renderer.createCanvas(pixelWidth, pixelHeight);
|
|
2389
|
+
const {
|
|
2390
|
+
centerX,
|
|
2391
|
+
centerY,
|
|
2392
|
+
radius: outerRadius
|
|
2393
|
+
} = resolveRadius(pixelWidth, pixelHeight, customRadius);
|
|
2394
|
+
const innerRadius = outerRadius * donutRatio;
|
|
2395
|
+
const angleStops = buildAngleStops(data, total);
|
|
2396
|
+
for (let y = 0; y < pixelHeight; y++) {
|
|
2397
|
+
const dy = (y - centerY) * ratio;
|
|
2398
|
+
for (let x = 0; x < pixelWidth; x++) {
|
|
2399
|
+
const dx = x - centerX;
|
|
2400
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2401
|
+
if (distance > outerRadius || distance < innerRadius) {
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
const angle = (Math.atan2(dy, dx) - START_ANGLE + TWO_PI) % TWO_PI;
|
|
2405
|
+
const sliceIndex = resolveSliceIndex(angle, angleStops);
|
|
2406
|
+
if (sliceIndex !== null) {
|
|
2407
|
+
const color = data[sliceIndex]?.color ?? itemColors[sliceIndex];
|
|
2408
|
+
if (color) {
|
|
2409
|
+
renderer.setPixel(canvas, x, y, { active: true, color });
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return renderer.renderCanvas(canvas, pixelWidth, pixelHeight);
|
|
2415
|
+
}, [
|
|
2416
|
+
data,
|
|
2417
|
+
total,
|
|
2418
|
+
renderer,
|
|
2419
|
+
canvasWidth,
|
|
2420
|
+
canvasHeight,
|
|
2421
|
+
customRadius,
|
|
2422
|
+
ratio,
|
|
2423
|
+
donutRatio,
|
|
2424
|
+
itemColors
|
|
2425
|
+
]);
|
|
2426
|
+
const legendItems = useMemo(() => {
|
|
2427
|
+
return data.map((item, i) => ({
|
|
2428
|
+
name: showLabels ? `${item.name} (${percentages[i]?.toFixed(1)}%)` : item.name,
|
|
2429
|
+
color: item.color ?? itemColors[i] ?? "cyan",
|
|
2430
|
+
symbol: "\u25CF"
|
|
2431
|
+
}));
|
|
2432
|
+
}, [data, itemColors, showLabels, percentages]);
|
|
2433
|
+
if (coloredLines.length === 0) {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column", width: totalWidth, alignItems: "center" }, /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column" }, coloredLines.map((segments, i) => /* @__PURE__ */ React6.createElement(Text, { key: `chart-line-${i}` }, segments.map((segment, j) => /* @__PURE__ */ React6.createElement(
|
|
2437
|
+
Text,
|
|
2438
|
+
{
|
|
2439
|
+
key: `seg-${i}-${j}`,
|
|
2440
|
+
...segment.color ? { color: segment.color } : {},
|
|
2441
|
+
...segment.backgroundColor ? { backgroundColor: segment.backgroundColor } : {}
|
|
2442
|
+
},
|
|
2443
|
+
segment.text
|
|
2444
|
+
))))), showLegend && legendPosition === "right" && /* @__PURE__ */ React6.createElement(Box, { marginLeft: 2 }, /* @__PURE__ */ React6.createElement(Legend, { items: legendItems, position: "vertical" }))), showLegend && legendPosition === "bottom" && /* @__PURE__ */ React6.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Legend, { items: legendItems, position: "horizontal" })));
|
|
2445
|
+
};
|
|
2446
|
+
var SPARK_LEVELS_BLOCK = [" ", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
2447
|
+
var SPARK_LEVELS_BRAILLE = ["\u2800", "\u2840", "\u28C0", "\u28C4", "\u28E4", "\u28E6", "\u28F6", "\u28F7", "\u28FF"];
|
|
2448
|
+
var SPARK_LEVELS_ASCII = [" ", ".", ":", "-", "=", "+", "*", "#", "%", "@"];
|
|
2449
|
+
var Sparkline = ({
|
|
2450
|
+
data,
|
|
2451
|
+
width: propsWidth,
|
|
2452
|
+
min: userMin,
|
|
2453
|
+
max: userMax,
|
|
2454
|
+
color,
|
|
2455
|
+
variant = "block"
|
|
2456
|
+
}) => {
|
|
2457
|
+
const gridContext = useContext(GridItemContext);
|
|
2458
|
+
const effectiveWidth = propsWidth ?? gridContext?.width;
|
|
2459
|
+
const text = useMemo(() => {
|
|
2460
|
+
if (!data || data.length === 0) return "";
|
|
2461
|
+
let processedData = data;
|
|
2462
|
+
if (effectiveWidth && data.length > effectiveWidth) {
|
|
2463
|
+
processedData = lttb(data, effectiveWidth);
|
|
2464
|
+
}
|
|
2465
|
+
const min = userMin ?? Math.min(...processedData);
|
|
2466
|
+
let max = userMax ?? Math.max(...processedData);
|
|
2467
|
+
if (max === min) {
|
|
2468
|
+
max = min + 1;
|
|
2469
|
+
}
|
|
2470
|
+
const levels = variant === "braille" ? SPARK_LEVELS_BRAILLE : variant === "ascii" ? SPARK_LEVELS_ASCII : SPARK_LEVELS_BLOCK;
|
|
2471
|
+
return processedData.map((v) => {
|
|
2472
|
+
const value = Math.max(min, Math.min(max, v));
|
|
2473
|
+
const normalized = (value - min) / (max - min);
|
|
2474
|
+
const index = Math.round(normalized * (levels.length - 1));
|
|
2475
|
+
return levels[index];
|
|
2476
|
+
}).join("");
|
|
2477
|
+
}, [data, effectiveWidth, userMin, userMax, variant]);
|
|
2478
|
+
return /* @__PURE__ */ React6.createElement(Text, { ...color ? { color } : {} }, text);
|
|
2479
|
+
};
|
|
2480
|
+
var Panel = ({
|
|
2481
|
+
title,
|
|
2482
|
+
titleAlignment = "left",
|
|
2483
|
+
borderStyle = "round",
|
|
2484
|
+
borderColor,
|
|
2485
|
+
padding = 0,
|
|
2486
|
+
width,
|
|
2487
|
+
height,
|
|
2488
|
+
children
|
|
2489
|
+
}) => {
|
|
2490
|
+
const gridContext = React6.useContext(GridItemContext);
|
|
2491
|
+
const effectiveWidth = width ?? gridContext?.width;
|
|
2492
|
+
const effectiveHeight = height ?? gridContext?.height;
|
|
2493
|
+
const borderOverhead = borderStyle ? 2 : 0;
|
|
2494
|
+
const paddingOverhead = padding * 2;
|
|
2495
|
+
const totalOverhead = borderOverhead + paddingOverhead;
|
|
2496
|
+
const innerWidth = typeof effectiveWidth === "number" ? Math.max(0, effectiveWidth - totalOverhead) : void 0;
|
|
2497
|
+
const innerHeight = typeof effectiveHeight === "number" ? Math.max(0, effectiveHeight - totalOverhead) : void 0;
|
|
2498
|
+
const boxProps = {
|
|
2499
|
+
borderStyle,
|
|
2500
|
+
flexDirection: "column",
|
|
2501
|
+
...effectiveWidth !== void 0 && { width: effectiveWidth },
|
|
2502
|
+
...effectiveHeight !== void 0 && { height: effectiveHeight },
|
|
2503
|
+
...borderColor !== void 0 && { borderColor }
|
|
2504
|
+
};
|
|
2505
|
+
const childContext = React6.useMemo(
|
|
2506
|
+
() => ({
|
|
2507
|
+
...innerWidth !== void 0 ? { width: innerWidth } : {},
|
|
2508
|
+
height: innerHeight
|
|
2509
|
+
}),
|
|
2510
|
+
[innerWidth, innerHeight]
|
|
2511
|
+
);
|
|
2512
|
+
return /* @__PURE__ */ React6.createElement(GridItemContext.Provider, { value: childContext }, /* @__PURE__ */ React6.createElement(Box, { ...boxProps }, title && /* @__PURE__ */ React6.createElement(
|
|
2513
|
+
Box,
|
|
2514
|
+
{
|
|
2515
|
+
position: "absolute",
|
|
2516
|
+
marginTop: -1,
|
|
2517
|
+
width: "100%",
|
|
2518
|
+
paddingX: 2,
|
|
2519
|
+
justifyContent: titleAlignment === "center" ? "center" : titleAlignment === "right" ? "flex-end" : "flex-start"
|
|
2520
|
+
},
|
|
2521
|
+
/* @__PURE__ */ React6.createElement(Text, { bold: true, color: borderColor || "white" }, " ", title, " ")
|
|
2522
|
+
), /* @__PURE__ */ React6.createElement(Box, { padding, flexDirection: "column", flexGrow: 1 }, children)));
|
|
2523
|
+
};
|
|
2524
|
+
var CHAR_SETS = {
|
|
2525
|
+
unicode: { fill: "\u2588", empty: "\u2591" },
|
|
2526
|
+
ascii: { fill: "#", empty: "-" }
|
|
2527
|
+
};
|
|
2528
|
+
var Gauge = ({
|
|
2529
|
+
value,
|
|
2530
|
+
min = 0,
|
|
2531
|
+
max = 100,
|
|
2532
|
+
width = 20,
|
|
2533
|
+
color,
|
|
2534
|
+
emptyColor,
|
|
2535
|
+
showPercent = true,
|
|
2536
|
+
variant = "unicode",
|
|
2537
|
+
fillChar,
|
|
2538
|
+
emptyChar,
|
|
2539
|
+
label
|
|
2540
|
+
}) => {
|
|
2541
|
+
const theme = useTheme();
|
|
2542
|
+
const effectiveColor = color ?? theme.semantic.success;
|
|
2543
|
+
const effectiveEmptyColor = emptyColor ?? theme.semantic.muted;
|
|
2544
|
+
const charSet = CHAR_SETS[variant];
|
|
2545
|
+
const effectiveFillChar = fillChar ?? charSet.fill;
|
|
2546
|
+
const effectiveEmptyChar = emptyChar ?? charSet.empty;
|
|
2547
|
+
const clampedValue = Math.min(Math.max(value, min), max);
|
|
2548
|
+
const range = max - min;
|
|
2549
|
+
const ratio = range === 0 ? 0 : (clampedValue - min) / range;
|
|
2550
|
+
const percent = Math.round(ratio * 100);
|
|
2551
|
+
const filledLength = Math.round(ratio * width);
|
|
2552
|
+
const emptyLength = width - filledLength;
|
|
2553
|
+
const filledStr = effectiveFillChar.repeat(filledLength);
|
|
2554
|
+
const emptyStr = effectiveEmptyChar.repeat(emptyLength);
|
|
2555
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row" }, label && /* @__PURE__ */ React6.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React6.createElement(Text, null, label)), /* @__PURE__ */ React6.createElement(Text, { color: effectiveColor }, filledStr), /* @__PURE__ */ React6.createElement(Text, { color: effectiveEmptyColor }, emptyStr), showPercent && /* @__PURE__ */ React6.createElement(Box, { marginLeft: 1 }, /* @__PURE__ */ React6.createElement(Text, null, percent, "%")));
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2558
|
+
// src/components/BigNumber/font.ts
|
|
2559
|
+
var BLOCK_FONT = {
|
|
2560
|
+
"0": ["\u2588\u2580\u2588", "\u2588 \u2588", "\u2588\u2584\u2588"],
|
|
2561
|
+
"1": [" \u2588 ", " \u2588 ", " \u2588 "],
|
|
2562
|
+
"2": ["\u2580\u2580\u2588", " \u2580\u2584", "\u2588\u2584\u2584"],
|
|
2563
|
+
"3": ["\u2580\u2580\u2588", " \u2580\u2584", "\u2584\u2584\u2588"],
|
|
2564
|
+
"4": ["\u2588 \u2588", "\u2588\u2584\u2588", " \u2588"],
|
|
2565
|
+
"5": ["\u2588\u2580\u2580", "\u2580\u2580\u2584", "\u2584\u2584\u2588"],
|
|
2566
|
+
"6": ["\u2588\u2580\u2580", "\u2588\u2584\u2584", "\u2588\u2584\u2588"],
|
|
2567
|
+
"7": ["\u2580\u2580\u2588", " \u2588", " \u2588"],
|
|
2568
|
+
"8": ["\u2588\u2580\u2588", "\u2588\u2580\u2588", "\u2588\u2584\u2588"],
|
|
2569
|
+
"9": ["\u2588\u2580\u2588", "\u2580\u2580\u2588", " \u2588"],
|
|
2570
|
+
".": [" ", " ", " \u2584 "],
|
|
2571
|
+
",": [" ", " ", " \u2599 "],
|
|
2572
|
+
"%": ["\u2588 ", " \u2588 ", " \u2588"],
|
|
2573
|
+
"+": [" ", " \u253C ", " "],
|
|
2574
|
+
"-": [" ", " \u2500 ", " "]
|
|
2575
|
+
};
|
|
2576
|
+
var BRAILLE_FONT = {
|
|
2577
|
+
"0": ["\u28F0\u28C6", "\u2847\u28B8", "\u2819\u281B"],
|
|
2578
|
+
"1": ["\u2880\u2846", "\u2800\u2847", "\u2800\u2807"],
|
|
2579
|
+
"2": ["\u2824\u28E4", "\u2880\u2864", "\u2813\u2812"],
|
|
2580
|
+
"3": ["\u2824\u28E4", "\u2800\u2864", "\u2812\u281A"],
|
|
2581
|
+
"4": ["\u2846\u28B8", "\u2813\u28BA", "\u2800\u28B8"],
|
|
2582
|
+
"5": ["\u2816\u2836", "\u2812\u28B2", "\u2812\u281A"],
|
|
2583
|
+
"6": ["\u28B0\u2846", "\u2816\u28B2", "\u2813\u281A"],
|
|
2584
|
+
"7": ["\u2824\u28E4", "\u2800\u2870", "\u2800\u2847"],
|
|
2585
|
+
"8": ["\u28B0\u2846", "\u2816\u2876", "\u2813\u281A"],
|
|
2586
|
+
"9": ["\u28B0\u2846", "\u2813\u28BA", "\u2800\u28B8"],
|
|
2587
|
+
".": ["\u2800\u2800", "\u2800\u2800", "\u2800\u2804"],
|
|
2588
|
+
",": ["\u2800\u2800", "\u2800\u2800", "\u2800\u2822"],
|
|
2589
|
+
"%": ["\u2801\u2800", "\u2800\u2802", "\u2800\u2808"],
|
|
2590
|
+
"+": ["\u2800\u2800", "\u2810\u2812", "\u2800\u2800"],
|
|
2591
|
+
"-": ["\u2800\u2800", "\u2810\u2812", "\u2800\u2800"]
|
|
2592
|
+
};
|
|
2593
|
+
var ASCII_FONT = {
|
|
2594
|
+
"0": ["+~+", "| |", "+~+"],
|
|
2595
|
+
"1": [" | ", " | ", " | "],
|
|
2596
|
+
"2": ["~~+", "+-+", "+~~"],
|
|
2597
|
+
"3": ["~~+", " ~+", "~~+"],
|
|
2598
|
+
"4": ["+ +", "+-+", " +"],
|
|
2599
|
+
"5": ["+~~", "+~+", "~~+"],
|
|
2600
|
+
"6": ["+~~", "+~+", "+~+"],
|
|
2601
|
+
"7": ["~~+", " +", " +"],
|
|
2602
|
+
"8": ["+~+", "+~+", "+~+"],
|
|
2603
|
+
"9": ["+~+", "+~+", " +"],
|
|
2604
|
+
".": [" ", " ", " . "],
|
|
2605
|
+
",": [" ", " ", " , "],
|
|
2606
|
+
"%": ["* ", " * ", " *"],
|
|
2607
|
+
"+": [" ", " + ", " "],
|
|
2608
|
+
"-": [" ", " - ", " "]
|
|
2609
|
+
};
|
|
2610
|
+
var FONTS = {
|
|
2611
|
+
block: BLOCK_FONT,
|
|
2612
|
+
braille: BRAILLE_FONT,
|
|
2613
|
+
ascii: ASCII_FONT
|
|
2614
|
+
};
|
|
2615
|
+
var UNKNOWN = {
|
|
2616
|
+
block: [" ", " ? ", " "],
|
|
2617
|
+
braille: ["\u2800\u2800", "\u2800\u2826", "\u2800\u2800"],
|
|
2618
|
+
ascii: [" ", " ? ", " "]
|
|
2619
|
+
};
|
|
2620
|
+
function getBigChar(char, style = "block") {
|
|
2621
|
+
return FONTS[style][char] || UNKNOWN[style];
|
|
2622
|
+
}
|
|
2623
|
+
function renderBigString(text, style = "block") {
|
|
2624
|
+
const rows = ["", "", ""];
|
|
2625
|
+
for (const char of text) {
|
|
2626
|
+
const matrix = getBigChar(char, style);
|
|
2627
|
+
rows[0] += `${matrix[0]} `;
|
|
2628
|
+
rows[1] += `${matrix[1]} `;
|
|
2629
|
+
rows[2] += `${matrix[2]} `;
|
|
2630
|
+
}
|
|
2631
|
+
return rows;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// src/components/BigNumber.tsx
|
|
2635
|
+
var TREND_ARROWS = {
|
|
2636
|
+
unicode: { up: "\u25B2", down: "\u25BC", neutral: "\u2500" },
|
|
2637
|
+
ascii: { up: "^", down: "v", neutral: "-" }
|
|
2638
|
+
};
|
|
2639
|
+
var BigNumber = ({
|
|
2640
|
+
value,
|
|
2641
|
+
label,
|
|
2642
|
+
color = "white",
|
|
2643
|
+
trendDirection,
|
|
2644
|
+
trendLabel,
|
|
2645
|
+
variant = "unicode",
|
|
2646
|
+
fontStyle = "block",
|
|
2647
|
+
align = "center"
|
|
2648
|
+
}) => {
|
|
2649
|
+
const bigLines = useMemo(() => renderBigString(String(value), fontStyle), [value, fontStyle]);
|
|
2650
|
+
const theme = useTheme();
|
|
2651
|
+
const arrows = TREND_ARROWS[variant];
|
|
2652
|
+
let trendColor = theme.semantic.muted;
|
|
2653
|
+
let trendArrow = "";
|
|
2654
|
+
if (trendDirection === "up") {
|
|
2655
|
+
trendColor = theme.semantic.success;
|
|
2656
|
+
trendArrow = arrows.up;
|
|
2657
|
+
} else if (trendDirection === "down") {
|
|
2658
|
+
trendColor = theme.semantic.error;
|
|
2659
|
+
trendArrow = arrows.down;
|
|
2660
|
+
} else if (trendDirection === "neutral") {
|
|
2661
|
+
trendArrow = arrows.neutral;
|
|
2662
|
+
}
|
|
2663
|
+
const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
|
|
2664
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column", alignItems }, /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column", marginBottom: 1 }, bigLines.map((line, i) => /* @__PURE__ */ React6.createElement(Text, { key: i, color }, line))), /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row", gap: 1 }, label && /* @__PURE__ */ React6.createElement(Text, { color: theme.semantic.textSecondary }, label), (trendLabel || trendArrow) && /* @__PURE__ */ React6.createElement(Text, { color: trendColor }, trendArrow, " ", trendLabel)));
|
|
2665
|
+
};
|
|
2666
|
+
var CHAR_SETS2 = {
|
|
2667
|
+
unicode: "\u25A0",
|
|
2668
|
+
ascii: "#"
|
|
2669
|
+
};
|
|
2670
|
+
var findMinMax = (data) => {
|
|
2671
|
+
let minVal = Number.POSITIVE_INFINITY;
|
|
2672
|
+
let maxVal = Number.NEGATIVE_INFINITY;
|
|
2673
|
+
for (const row of data) {
|
|
2674
|
+
for (const val of row) {
|
|
2675
|
+
if (val < minVal) minVal = val;
|
|
2676
|
+
if (val > maxVal) maxVal = val;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
return { min: minVal, max: maxVal };
|
|
2680
|
+
};
|
|
2681
|
+
var Heatmap = ({ data, colors, variant = "unicode", char }) => {
|
|
2682
|
+
const theme = useTheme();
|
|
2683
|
+
const effectiveColors = colors ?? theme.heatmapGradient;
|
|
2684
|
+
const effectiveChar = char ?? CHAR_SETS2[variant];
|
|
2685
|
+
const { min, max } = useMemo(() => {
|
|
2686
|
+
const { min: minVal, max: maxVal } = findMinMax(data);
|
|
2687
|
+
if (minVal === Number.POSITIVE_INFINITY) return { min: 0, max: 0 };
|
|
2688
|
+
return { min: minVal, max: maxVal > minVal ? maxVal : minVal + 1 };
|
|
2689
|
+
}, [data]);
|
|
2690
|
+
const steps = effectiveColors.length;
|
|
2691
|
+
const gradient = useMemo(
|
|
2692
|
+
() => createGradient(effectiveColors, steps),
|
|
2693
|
+
[effectiveColors, steps]
|
|
2694
|
+
);
|
|
2695
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "column" }, data.map((row, rowIndex) => /* @__PURE__ */ React6.createElement(Box, { key: rowIndex, flexDirection: "row" }, row.map((val, colIndex) => {
|
|
2696
|
+
const normalized = max === min ? 0 : (val - min) / (max - min);
|
|
2697
|
+
let stepIndex = Math.floor(normalized * steps);
|
|
2698
|
+
if (stepIndex >= steps) stepIndex = steps - 1;
|
|
2699
|
+
const colorFn = gradient[stepIndex];
|
|
2700
|
+
const renderedChar = colorFn ? colorFn(effectiveChar) : effectiveChar;
|
|
2701
|
+
return /* @__PURE__ */ React6.createElement(Text, { key: `${rowIndex}-${colIndex}` }, renderedChar, " ");
|
|
2702
|
+
}))));
|
|
2703
|
+
};
|
|
2704
|
+
function parseLogLine(line) {
|
|
2705
|
+
const timeRegex = /\[?(\d{2,4}-\d{2}-\d{2}\s)?(\d{2}:\d{2}:\d{2})\]?/;
|
|
2706
|
+
const timeMatch = line.match(timeRegex);
|
|
2707
|
+
let timestamp = timeMatch ? timeMatch[0] : void 0;
|
|
2708
|
+
let content = line;
|
|
2709
|
+
if (timestamp) {
|
|
2710
|
+
content = content.replace(timestamp, "").trim();
|
|
2711
|
+
timestamp = timestamp.replace(/^\[|\]$/g, "");
|
|
2712
|
+
}
|
|
2713
|
+
const levelRegex = /(info|warn|warning|error|err|success|debug)/i;
|
|
2714
|
+
const levelMatch = content.match(levelRegex);
|
|
2715
|
+
let level = "unknown";
|
|
2716
|
+
if (levelMatch) {
|
|
2717
|
+
const lvl = levelMatch[0].toLowerCase();
|
|
2718
|
+
if (lvl.includes("error") || lvl.includes("err")) level = "error";
|
|
2719
|
+
else if (lvl.includes("warn")) level = "warn";
|
|
2720
|
+
else if (lvl.includes("info")) level = "info";
|
|
2721
|
+
else if (lvl.includes("success")) level = "success";
|
|
2722
|
+
else if (lvl.includes("debug")) level = "debug";
|
|
2723
|
+
content = content.replace(new RegExp(`\\[?${levelMatch[0]}\\]?`, "i"), "").trim();
|
|
2724
|
+
content = content.replace(/^[:\-\s]+/, "");
|
|
2725
|
+
}
|
|
2726
|
+
return {
|
|
2727
|
+
...timestamp ? { timestamp } : {},
|
|
2728
|
+
level,
|
|
2729
|
+
message: content || line,
|
|
2730
|
+
raw: line
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
var LogLine = ({ parsed, semantic }) => {
|
|
2734
|
+
const { timestamp, level, message } = parsed;
|
|
2735
|
+
const getStyle = (lvl) => {
|
|
2736
|
+
switch (lvl) {
|
|
2737
|
+
case "error":
|
|
2738
|
+
return { color: semantic.error, badge: "\u2716 ERROR", icon: "\u2716" };
|
|
2739
|
+
case "warn":
|
|
2740
|
+
return { color: semantic.warning, badge: "\u26A0 WARN ", icon: "\u26A0" };
|
|
2741
|
+
case "success":
|
|
2742
|
+
return { color: semantic.success, badge: "\u2714 SUCCESS", icon: "\u2714" };
|
|
2743
|
+
case "debug":
|
|
2744
|
+
return { color: semantic.muted, badge: "\u2699 DEBUG", icon: "\u2699" };
|
|
2745
|
+
default:
|
|
2746
|
+
return { color: semantic.info, badge: "\u2139 INFO ", icon: "\u2139" };
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
const style = getStyle(level);
|
|
2750
|
+
return /* @__PURE__ */ React6.createElement(Box, { flexDirection: "row", width: "100%" }, timestamp && /* @__PURE__ */ React6.createElement(Box, { marginRight: 1, width: 10 }, /* @__PURE__ */ React6.createElement(Text, { dimColor: true, wrap: "truncate" }, timestamp)), /* @__PURE__ */ React6.createElement(Box, { marginRight: 1, width: 9 }, level === "unknown" ? /* @__PURE__ */ React6.createElement(Text, { color: semantic.muted }, "\u2022") : /* @__PURE__ */ React6.createElement(Text, { color: style.color, bold: true }, level === "error" || level === "warn" ? `${style.icon} ${level.toUpperCase()}` : level.toUpperCase())), /* @__PURE__ */ React6.createElement(Box, { flexGrow: 1 }, level === "error" ? /* @__PURE__ */ React6.createElement(Text, { color: style.color, wrap: "truncate-end" }, message) : /* @__PURE__ */ React6.createElement(Text, { color: semantic.text, wrap: "truncate-end" }, message)));
|
|
2751
|
+
};
|
|
2752
|
+
var LogStream = ({ logs, maxLines = 100, height, width }) => {
|
|
2753
|
+
const theme = useTheme();
|
|
2754
|
+
const gridContext = useContext(GridItemContext);
|
|
2755
|
+
const effectiveHeight = height ?? (typeof gridContext?.height === "number" ? gridContext.height : void 0);
|
|
2756
|
+
const effectiveWidth = width ?? gridContext?.width;
|
|
2757
|
+
const recentLogs = useMemo(() => {
|
|
2758
|
+
const start = Math.max(0, logs.length - maxLines);
|
|
2759
|
+
return logs.slice(start);
|
|
2760
|
+
}, [logs, maxLines]);
|
|
2761
|
+
const displayLogs = useMemo(() => {
|
|
2762
|
+
if (effectiveHeight && effectiveHeight > 0) {
|
|
2763
|
+
const start = Math.max(0, recentLogs.length - effectiveHeight);
|
|
2764
|
+
return recentLogs.slice(start);
|
|
2765
|
+
}
|
|
2766
|
+
return recentLogs;
|
|
2767
|
+
}, [recentLogs, effectiveHeight]);
|
|
2768
|
+
const items = displayLogs.map((line, index) => {
|
|
2769
|
+
const parsed = parseLogLine(line);
|
|
2770
|
+
return /* @__PURE__ */ React6.createElement(LogLine, { key: index, parsed, semantic: theme.semantic });
|
|
2771
|
+
});
|
|
2772
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2773
|
+
Box,
|
|
2774
|
+
{
|
|
2775
|
+
flexDirection: "column",
|
|
2776
|
+
justifyContent: "flex-end",
|
|
2777
|
+
flexGrow: 1,
|
|
2778
|
+
...effectiveHeight !== void 0 && { height: effectiveHeight },
|
|
2779
|
+
...effectiveWidth !== void 0 && { width: effectiveWidth }
|
|
2780
|
+
},
|
|
2781
|
+
items
|
|
2782
|
+
);
|
|
2783
|
+
};
|
|
2784
|
+
var ALIGN_MAP = {
|
|
2785
|
+
left: "flex-start",
|
|
2786
|
+
right: "flex-end",
|
|
2787
|
+
center: "center"
|
|
2788
|
+
};
|
|
2789
|
+
var SortableHeaderCell = ({
|
|
2790
|
+
column,
|
|
2791
|
+
width,
|
|
2792
|
+
isSorted,
|
|
2793
|
+
sortDirection,
|
|
2794
|
+
onSort,
|
|
2795
|
+
align,
|
|
2796
|
+
autoFocus
|
|
2797
|
+
}) => {
|
|
2798
|
+
const theme = useTheme();
|
|
2799
|
+
const semantic = theme.semantic;
|
|
2800
|
+
const { isFocused } = useFocus({ autoFocus: !!autoFocus });
|
|
2801
|
+
useInput((input, key) => {
|
|
2802
|
+
if (isFocused && (key.return || input === " ")) {
|
|
2803
|
+
onSort?.(column);
|
|
2804
|
+
}
|
|
2805
|
+
});
|
|
2806
|
+
let indicator = "";
|
|
2807
|
+
if (isSorted) {
|
|
2808
|
+
indicator = sortDirection === "asc" ? " \u25B2" : " \u25BC";
|
|
2809
|
+
}
|
|
2810
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2811
|
+
Box,
|
|
2812
|
+
{
|
|
2813
|
+
width,
|
|
2814
|
+
justifyContent: align,
|
|
2815
|
+
flexShrink: 0,
|
|
2816
|
+
paddingX: 1,
|
|
2817
|
+
...isFocused ? { borderStyle: "single" } : {},
|
|
2818
|
+
borderColor: semantic.info,
|
|
2819
|
+
marginTop: isFocused ? -1 : 0
|
|
2820
|
+
},
|
|
2821
|
+
/* @__PURE__ */ React6.createElement(
|
|
2822
|
+
Text,
|
|
2823
|
+
{
|
|
2824
|
+
bold: true,
|
|
2825
|
+
color: isFocused ? semantic.info : semantic.success,
|
|
2826
|
+
underline: isFocused,
|
|
2827
|
+
wrap: "truncate-end"
|
|
2828
|
+
},
|
|
2829
|
+
column.header,
|
|
2830
|
+
indicator
|
|
2831
|
+
)
|
|
2832
|
+
);
|
|
2833
|
+
};
|
|
2834
|
+
var getCellContentLength = (item, col) => {
|
|
2835
|
+
let content = "";
|
|
2836
|
+
if (typeof col.accessor === "function") {
|
|
2837
|
+
const result = col.accessor(item);
|
|
2838
|
+
if (typeof result === "string" || typeof result === "number") {
|
|
2839
|
+
content = String(result);
|
|
2840
|
+
}
|
|
2841
|
+
} else {
|
|
2842
|
+
const val = item[col.accessor];
|
|
2843
|
+
if (val !== void 0 && val !== null) {
|
|
2844
|
+
content = String(val);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
return content.length;
|
|
2848
|
+
};
|
|
2849
|
+
var calculateWidths = (columns, data) => {
|
|
2850
|
+
return columns.map((col) => {
|
|
2851
|
+
if (col.width) return col.width;
|
|
2852
|
+
let max = col.header.length + 2;
|
|
2853
|
+
for (const item of data) {
|
|
2854
|
+
max = Math.max(max, getCellContentLength(item, col));
|
|
2855
|
+
}
|
|
2856
|
+
return max + 2;
|
|
2857
|
+
});
|
|
2858
|
+
};
|
|
2859
|
+
var Table = ({
|
|
2860
|
+
data,
|
|
2861
|
+
columns,
|
|
2862
|
+
sortColumn,
|
|
2863
|
+
sortDirection = "asc",
|
|
2864
|
+
zebra = false,
|
|
2865
|
+
onSort
|
|
2866
|
+
}) => {
|
|
2867
|
+
const gridContext = useContext(GridItemContext);
|
|
2868
|
+
const availableWidth = gridContext?.width;
|
|
2869
|
+
const contentWidths = useMemo(() => calculateWidths(columns, data), [data, columns]);
|
|
2870
|
+
const totalContentWidth = contentWidths.reduce((a, b) => a + b, 0);
|
|
2871
|
+
const finalColumnWidths = useMemo(() => {
|
|
2872
|
+
if (!availableWidth) {
|
|
2873
|
+
return contentWidths;
|
|
2874
|
+
}
|
|
2875
|
+
const scale = availableWidth / totalContentWidth;
|
|
2876
|
+
let allocated = 0;
|
|
2877
|
+
return contentWidths.map((w, i) => {
|
|
2878
|
+
if (i === contentWidths.length - 1) {
|
|
2879
|
+
return Math.max(1, availableWidth - allocated);
|
|
2880
|
+
}
|
|
2881
|
+
const newW = Math.floor(w * scale);
|
|
2882
|
+
allocated += newW;
|
|
2883
|
+
return Math.max(1, newW);
|
|
2884
|
+
});
|
|
2885
|
+
}, [availableWidth, totalContentWidth, contentWidths]);
|
|
2886
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2887
|
+
Box,
|
|
2888
|
+
{
|
|
2889
|
+
flexDirection: "column",
|
|
2890
|
+
...availableWidth !== void 0 && { width: availableWidth }
|
|
2891
|
+
},
|
|
2892
|
+
/* @__PURE__ */ React6.createElement(
|
|
2893
|
+
Box,
|
|
2894
|
+
{
|
|
2895
|
+
borderStyle: "single",
|
|
2896
|
+
borderTop: false,
|
|
2897
|
+
borderLeft: false,
|
|
2898
|
+
borderRight: false,
|
|
2899
|
+
borderBottom: true,
|
|
2900
|
+
flexDirection: "row"
|
|
2901
|
+
},
|
|
2902
|
+
columns.map((col, i) => {
|
|
2903
|
+
const width = finalColumnWidths[i] ?? 0;
|
|
2904
|
+
const isSorted = sortColumn === i || sortColumn === col.header;
|
|
2905
|
+
const justifyContent = col.align ? ALIGN_MAP[col.align] : "flex-start";
|
|
2906
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2907
|
+
SortableHeaderCell,
|
|
2908
|
+
{
|
|
2909
|
+
key: i,
|
|
2910
|
+
column: col,
|
|
2911
|
+
width,
|
|
2912
|
+
isSorted,
|
|
2913
|
+
sortDirection,
|
|
2914
|
+
align: justifyContent,
|
|
2915
|
+
onSort: () => onSort?.(col, i),
|
|
2916
|
+
autoFocus: i === 0
|
|
2917
|
+
}
|
|
2918
|
+
);
|
|
2919
|
+
})
|
|
2920
|
+
),
|
|
2921
|
+
data.map((item, rowIndex) => {
|
|
2922
|
+
const isZebra = zebra && rowIndex % 2 === 1;
|
|
2923
|
+
return /* @__PURE__ */ React6.createElement(Box, { key: rowIndex, flexDirection: "row" }, columns.map((col, colIndex) => {
|
|
2924
|
+
const width = finalColumnWidths[colIndex] ?? 0;
|
|
2925
|
+
let content;
|
|
2926
|
+
if (typeof col.accessor === "function") {
|
|
2927
|
+
content = col.accessor(item);
|
|
2928
|
+
} else {
|
|
2929
|
+
const val = item[col.accessor];
|
|
2930
|
+
content = val !== void 0 && val !== null ? String(val) : "";
|
|
2931
|
+
}
|
|
2932
|
+
const justifyContent = col.align ? ALIGN_MAP[col.align] : "flex-start";
|
|
2933
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2934
|
+
Box,
|
|
2935
|
+
{
|
|
2936
|
+
key: colIndex,
|
|
2937
|
+
width,
|
|
2938
|
+
justifyContent,
|
|
2939
|
+
flexShrink: 0,
|
|
2940
|
+
paddingX: 1
|
|
2941
|
+
},
|
|
2942
|
+
/* @__PURE__ */ React6.createElement(Text, { dimColor: isZebra, wrap: "truncate-end" }, content)
|
|
2943
|
+
);
|
|
2944
|
+
}));
|
|
2945
|
+
})
|
|
2946
|
+
);
|
|
2947
|
+
};
|
|
2948
|
+
|
|
2949
|
+
export { AreaChart, AsciiRenderer, Axis, BarChart, BigNumber, BlockRenderer, BrailleRenderer, Gauge, Grid, GridItem, Heatmap, InkHudProvider, Legend, LineChart, LogStream, ONE_DARK_THEME, Panel, PieChart, Renderer, RendererSelector, Sparkline, Table, TerminalDetector, ThemeProvider, arcPoints, assignColors, averageDownsampling, clamp, colorToChalk, createGradient, degreesToRadians, distanceBetweenPoints, easeInCubic, easeInOutQuad, easeLinear, easeOutCubic, fixedIntervalDownsampling, linearScale, lttb, midpointCircle, minMaxDownsampling, normalize, pointOnArc, radiansToDegrees, rendererSelector, scaleToRange, terminalDetector, useInkHud, useRendererSelector, useSemanticColors, useSmooth, useSmoothArray, useTheme, useThrottle };
|
|
2950
|
+
//# sourceMappingURL=index.js.map
|
|
2951
|
+
//# sourceMappingURL=index.js.map
|