@ggterm/core 0.2.10 → 0.2.12
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/dist/cli-plot.js +965 -1
- package/dist/cli.js +920 -0
- package/dist/geoms/beeswarm.d.ts +60 -0
- package/dist/geoms/beeswarm.d.ts.map +1 -0
- package/dist/geoms/braille.d.ts +39 -0
- package/dist/geoms/braille.d.ts.map +1 -0
- package/dist/geoms/bullet.d.ts +37 -0
- package/dist/geoms/bullet.d.ts.map +1 -0
- package/dist/geoms/dumbbell.d.ts +36 -0
- package/dist/geoms/dumbbell.d.ts.map +1 -0
- package/dist/geoms/index.d.ts +7 -0
- package/dist/geoms/index.d.ts.map +1 -1
- package/dist/geoms/lollipop.d.ts +35 -0
- package/dist/geoms/lollipop.d.ts.map +1 -0
- package/dist/geoms/sparkline.d.ts +36 -0
- package/dist/geoms/sparkline.d.ts.map +1 -0
- package/dist/geoms/waffle.d.ts +36 -0
- package/dist/geoms/waffle.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +948 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -1
- package/dist/pipeline/render-geoms.d.ts +36 -0
- package/dist/pipeline/render-geoms.d.ts.map +1 -1
- package/dist/stats/beeswarm.d.ts +38 -0
- package/dist/stats/beeswarm.d.ts.map +1 -0
- package/dist/stats/index.d.ts +2 -0
- package/dist/stats/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2308,6 +2308,521 @@ function parseColor(color) {
|
|
|
2308
2308
|
}
|
|
2309
2309
|
return { r: 128, g: 128, b: 128, a: 1 };
|
|
2310
2310
|
}
|
|
2311
|
+
function renderGeomBeeswarm(data, geom, aes, scales, canvas) {
|
|
2312
|
+
const alpha = geom.params.alpha ?? 1;
|
|
2313
|
+
const fixedColor = geom.params.color;
|
|
2314
|
+
const shape = getPointShape(geom.params.shape);
|
|
2315
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2316
|
+
const plotRight = Math.round(scales.x.range[1]);
|
|
2317
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2318
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2319
|
+
const defaultColors = [
|
|
2320
|
+
{ r: 79, g: 169, b: 238, a: 1 },
|
|
2321
|
+
{ r: 238, g: 136, b: 102, a: 1 },
|
|
2322
|
+
{ r: 102, g: 204, b: 153, a: 1 },
|
|
2323
|
+
{ r: 204, g: 102, b: 204, a: 1 },
|
|
2324
|
+
{ r: 255, g: 200, b: 87, a: 1 },
|
|
2325
|
+
{ r: 138, g: 201, b: 222, a: 1 },
|
|
2326
|
+
{ r: 255, g: 153, b: 153, a: 1 },
|
|
2327
|
+
{ r: 170, g: 170, b: 170, a: 1 }
|
|
2328
|
+
];
|
|
2329
|
+
const categories = new Set;
|
|
2330
|
+
for (const row of data) {
|
|
2331
|
+
categories.add(String(row.xOriginal ?? row[aes.x] ?? "default"));
|
|
2332
|
+
}
|
|
2333
|
+
const categoryList = [...categories];
|
|
2334
|
+
for (const row of data) {
|
|
2335
|
+
const xVal = row.x;
|
|
2336
|
+
const yVal = row.y;
|
|
2337
|
+
if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
const numGroups = categoryList.length;
|
|
2341
|
+
const xRange = plotRight - plotLeft;
|
|
2342
|
+
const xNormalized = (Number(xVal) + 0.5) / numGroups;
|
|
2343
|
+
const cx = Math.round(plotLeft + xNormalized * xRange);
|
|
2344
|
+
const cy = Math.round(scales.y.map(yVal));
|
|
2345
|
+
let color;
|
|
2346
|
+
if (fixedColor) {
|
|
2347
|
+
color = parseColorToRgba(fixedColor);
|
|
2348
|
+
} else if (scales.color && aes.color) {
|
|
2349
|
+
color = getPointColor(row, aes, scales.color);
|
|
2350
|
+
} else {
|
|
2351
|
+
const category = String(row.xOriginal ?? row[aes.x] ?? "default");
|
|
2352
|
+
const categoryIdx = categoryList.indexOf(category);
|
|
2353
|
+
color = defaultColors[categoryIdx % defaultColors.length];
|
|
2354
|
+
}
|
|
2355
|
+
if (alpha < 1) {
|
|
2356
|
+
color = { ...color, a: alpha };
|
|
2357
|
+
}
|
|
2358
|
+
if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
|
|
2359
|
+
canvas.drawChar(cx, cy, shape, color);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
function renderGeomDumbbell(data, geom, aes, scales, canvas) {
|
|
2364
|
+
const lineColor = parseColorToRgba(geom.params.lineColor ?? "#666666");
|
|
2365
|
+
const alpha = geom.params.alpha ?? 1;
|
|
2366
|
+
const shape = getPointShape(geom.params.shape);
|
|
2367
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2368
|
+
const plotRight = Math.round(scales.x.range[1]);
|
|
2369
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2370
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2371
|
+
const defaultColors = [
|
|
2372
|
+
{ r: 79, g: 169, b: 238, a: 1 },
|
|
2373
|
+
{ r: 238, g: 136, b: 102, a: 1 },
|
|
2374
|
+
{ r: 102, g: 204, b: 153, a: 1 },
|
|
2375
|
+
{ r: 204, g: 102, b: 204, a: 1 }
|
|
2376
|
+
];
|
|
2377
|
+
for (let i = 0;i < data.length; i++) {
|
|
2378
|
+
const row = data[i];
|
|
2379
|
+
const xVal = row[aes.x];
|
|
2380
|
+
const xendVal = row["xend"] ?? row[aes.x];
|
|
2381
|
+
const yVal = row[aes.y];
|
|
2382
|
+
if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
const x1 = Math.round(scales.x.map(xVal));
|
|
2386
|
+
const x2 = Math.round(scales.x.map(xendVal));
|
|
2387
|
+
const cy = Math.round(scales.y.map(yVal));
|
|
2388
|
+
let startColor;
|
|
2389
|
+
let endColor;
|
|
2390
|
+
if (geom.params.color) {
|
|
2391
|
+
startColor = parseColorToRgba(geom.params.color);
|
|
2392
|
+
} else if (scales.color && aes.color) {
|
|
2393
|
+
startColor = getPointColor(row, aes, scales.color);
|
|
2394
|
+
} else {
|
|
2395
|
+
startColor = defaultColors[0];
|
|
2396
|
+
}
|
|
2397
|
+
if (geom.params.colorEnd) {
|
|
2398
|
+
endColor = parseColorToRgba(geom.params.colorEnd);
|
|
2399
|
+
} else {
|
|
2400
|
+
endColor = geom.params.color ? startColor : defaultColors[1];
|
|
2401
|
+
}
|
|
2402
|
+
if (alpha < 1) {
|
|
2403
|
+
startColor = { ...startColor, a: alpha };
|
|
2404
|
+
endColor = { ...endColor, a: alpha };
|
|
2405
|
+
}
|
|
2406
|
+
if (cy >= plotTop && cy <= plotBottom) {
|
|
2407
|
+
const left = Math.max(plotLeft, Math.min(x1, x2));
|
|
2408
|
+
const right = Math.min(plotRight, Math.max(x1, x2));
|
|
2409
|
+
for (let x = left;x <= right; x++) {
|
|
2410
|
+
canvas.drawChar(x, cy, "─", lineColor);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (x1 >= plotLeft && x1 <= plotRight && cy >= plotTop && cy <= plotBottom) {
|
|
2414
|
+
canvas.drawChar(x1, cy, shape, startColor);
|
|
2415
|
+
}
|
|
2416
|
+
if (x2 >= plotLeft && x2 <= plotRight && cy >= plotTop && cy <= plotBottom) {
|
|
2417
|
+
canvas.drawChar(x2, cy, shape, endColor);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
function renderGeomLollipop(data, geom, aes, scales, canvas) {
|
|
2422
|
+
const alpha = geom.params.alpha ?? 1;
|
|
2423
|
+
const baseline = geom.params.baseline ?? 0;
|
|
2424
|
+
const direction = geom.params.direction ?? "vertical";
|
|
2425
|
+
const shape = getPointShape(geom.params.shape);
|
|
2426
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2427
|
+
const plotRight = Math.round(scales.x.range[1]);
|
|
2428
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2429
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2430
|
+
const defaultColors = [
|
|
2431
|
+
{ r: 79, g: 169, b: 238, a: 1 },
|
|
2432
|
+
{ r: 238, g: 136, b: 102, a: 1 },
|
|
2433
|
+
{ r: 102, g: 204, b: 153, a: 1 },
|
|
2434
|
+
{ r: 204, g: 102, b: 204, a: 1 },
|
|
2435
|
+
{ r: 255, g: 200, b: 87, a: 1 },
|
|
2436
|
+
{ r: 138, g: 201, b: 222, a: 1 }
|
|
2437
|
+
];
|
|
2438
|
+
const xValues = [...new Set(data.map((row) => row[aes.x]))];
|
|
2439
|
+
for (let i = 0;i < data.length; i++) {
|
|
2440
|
+
const row = data[i];
|
|
2441
|
+
const xVal = row[aes.x];
|
|
2442
|
+
const yVal = row[aes.y];
|
|
2443
|
+
if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
const cx = Math.round(scales.x.map(xVal));
|
|
2447
|
+
const cy = Math.round(scales.y.map(yVal));
|
|
2448
|
+
let color;
|
|
2449
|
+
if (geom.params.color) {
|
|
2450
|
+
color = parseColorToRgba(geom.params.color);
|
|
2451
|
+
} else if (scales.color && aes.color) {
|
|
2452
|
+
color = getPointColor(row, aes, scales.color);
|
|
2453
|
+
} else {
|
|
2454
|
+
const categoryIdx = xValues.indexOf(xVal);
|
|
2455
|
+
color = defaultColors[categoryIdx % defaultColors.length];
|
|
2456
|
+
}
|
|
2457
|
+
let lineColor;
|
|
2458
|
+
if (geom.params.lineColor) {
|
|
2459
|
+
lineColor = parseColorToRgba(geom.params.lineColor);
|
|
2460
|
+
} else {
|
|
2461
|
+
lineColor = {
|
|
2462
|
+
r: Math.round(color.r * 0.7),
|
|
2463
|
+
g: Math.round(color.g * 0.7),
|
|
2464
|
+
b: Math.round(color.b * 0.7),
|
|
2465
|
+
a: color.a
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
if (alpha < 1) {
|
|
2469
|
+
color = { ...color, a: alpha };
|
|
2470
|
+
lineColor = { ...lineColor, a: alpha };
|
|
2471
|
+
}
|
|
2472
|
+
if (direction === "vertical") {
|
|
2473
|
+
let baselineY = Math.round(scales.y.map(baseline));
|
|
2474
|
+
baselineY = Math.max(plotTop, Math.min(plotBottom, baselineY));
|
|
2475
|
+
if (cx >= plotLeft && cx <= plotRight) {
|
|
2476
|
+
const top = Math.min(cy, baselineY);
|
|
2477
|
+
const bottom = Math.max(cy, baselineY);
|
|
2478
|
+
for (let y = top;y <= bottom; y++) {
|
|
2479
|
+
if (y >= plotTop && y <= plotBottom) {
|
|
2480
|
+
canvas.drawChar(cx, y, "│", lineColor);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
|
|
2485
|
+
canvas.drawChar(cx, cy, shape, color);
|
|
2486
|
+
}
|
|
2487
|
+
} else {
|
|
2488
|
+
let baselineX = Math.round(scales.x.map(baseline));
|
|
2489
|
+
baselineX = Math.max(plotLeft, Math.min(plotRight, baselineX));
|
|
2490
|
+
if (cy >= plotTop && cy <= plotBottom) {
|
|
2491
|
+
const left = Math.min(cx, baselineX);
|
|
2492
|
+
const right = Math.max(cx, baselineX);
|
|
2493
|
+
for (let x = left;x <= right; x++) {
|
|
2494
|
+
if (x >= plotLeft && x <= plotRight) {
|
|
2495
|
+
canvas.drawChar(x, cy, "─", lineColor);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
|
|
2500
|
+
canvas.drawChar(cx, cy, shape, color);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function renderGeomWaffle(data, geom, aes, scales, canvas) {
|
|
2506
|
+
const rows = geom.params.rows ?? 10;
|
|
2507
|
+
const cols = geom.params.cols ?? 10;
|
|
2508
|
+
const fillChar = geom.params.fill_char ?? "█";
|
|
2509
|
+
const emptyChar = geom.params.empty_char ?? "░";
|
|
2510
|
+
const showLegend = geom.params.show_legend ?? true;
|
|
2511
|
+
const flip = geom.params.flip ?? false;
|
|
2512
|
+
const gap = geom.params.gap ?? 0;
|
|
2513
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2514
|
+
const plotRight = Math.round(scales.x.range[1]);
|
|
2515
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2516
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2517
|
+
const defaultColors = [
|
|
2518
|
+
{ r: 79, g: 169, b: 238, a: 1 },
|
|
2519
|
+
{ r: 238, g: 136, b: 102, a: 1 },
|
|
2520
|
+
{ r: 102, g: 204, b: 153, a: 1 },
|
|
2521
|
+
{ r: 204, g: 102, b: 204, a: 1 },
|
|
2522
|
+
{ r: 255, g: 200, b: 87, a: 1 },
|
|
2523
|
+
{ r: 138, g: 201, b: 222, a: 1 },
|
|
2524
|
+
{ r: 255, g: 153, b: 153, a: 1 },
|
|
2525
|
+
{ r: 170, g: 170, b: 170, a: 1 }
|
|
2526
|
+
];
|
|
2527
|
+
const fillField = aes.fill || aes.color || "category";
|
|
2528
|
+
const valueField = aes.y || "value";
|
|
2529
|
+
const categories = new Map;
|
|
2530
|
+
let totalValue = 0;
|
|
2531
|
+
for (const row of data) {
|
|
2532
|
+
const cat = String(row[fillField] ?? "default");
|
|
2533
|
+
const val = Number(row[valueField]) || 1;
|
|
2534
|
+
categories.set(cat, (categories.get(cat) ?? 0) + val);
|
|
2535
|
+
totalValue += val;
|
|
2536
|
+
}
|
|
2537
|
+
const cellsPerCategory = [];
|
|
2538
|
+
const categoryList = [...categories.keys()];
|
|
2539
|
+
let cellsAssigned = 0;
|
|
2540
|
+
for (let i = 0;i < categoryList.length; i++) {
|
|
2541
|
+
const cat = categoryList[i];
|
|
2542
|
+
const val = categories.get(cat);
|
|
2543
|
+
const proportion = val / totalValue;
|
|
2544
|
+
const cells = Math.round(proportion * rows * cols);
|
|
2545
|
+
const color = scales.color?.map(cat) ?? defaultColors[i % defaultColors.length];
|
|
2546
|
+
cellsPerCategory.push({ category: cat, cells, color });
|
|
2547
|
+
cellsAssigned += cells;
|
|
2548
|
+
}
|
|
2549
|
+
if (cellsAssigned < rows * cols && cellsPerCategory.length > 0) {
|
|
2550
|
+
cellsPerCategory[0].cells += rows * cols - cellsAssigned;
|
|
2551
|
+
}
|
|
2552
|
+
const grid = [];
|
|
2553
|
+
for (const { cells, color } of cellsPerCategory) {
|
|
2554
|
+
for (let i = 0;i < cells; i++) {
|
|
2555
|
+
grid.push({ char: fillChar, color });
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
const emptyColor = { r: 80, g: 80, b: 80, a: 0.3 };
|
|
2559
|
+
while (grid.length < rows * cols) {
|
|
2560
|
+
grid.push({ char: emptyChar, color: emptyColor });
|
|
2561
|
+
}
|
|
2562
|
+
const availableWidth = plotRight - plotLeft - (showLegend ? 15 : 0);
|
|
2563
|
+
const availableHeight = plotBottom - plotTop;
|
|
2564
|
+
const cellWidth = Math.max(1, Math.floor(availableWidth / cols)) + gap;
|
|
2565
|
+
const cellHeight = Math.max(1, Math.floor(availableHeight / rows)) + gap;
|
|
2566
|
+
for (let row = 0;row < rows; row++) {
|
|
2567
|
+
for (let col = 0;col < cols; col++) {
|
|
2568
|
+
let idx;
|
|
2569
|
+
if (flip) {
|
|
2570
|
+
idx = row * cols + col;
|
|
2571
|
+
} else {
|
|
2572
|
+
idx = col * rows + (rows - 1 - row);
|
|
2573
|
+
}
|
|
2574
|
+
if (idx >= grid.length)
|
|
2575
|
+
continue;
|
|
2576
|
+
const cell = grid[idx];
|
|
2577
|
+
const x = plotLeft + col * cellWidth;
|
|
2578
|
+
const y = plotTop + row * cellHeight;
|
|
2579
|
+
if (x >= plotLeft && x < plotRight - (showLegend ? 15 : 0) && y >= plotTop && y <= plotBottom) {
|
|
2580
|
+
canvas.drawChar(x, y, cell.char, cell.color);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (showLegend) {
|
|
2585
|
+
const legendX = plotRight - 12;
|
|
2586
|
+
let legendY = plotTop;
|
|
2587
|
+
for (let i = 0;i < cellsPerCategory.length && legendY < plotBottom; i++) {
|
|
2588
|
+
const { category, cells, color } = cellsPerCategory[i];
|
|
2589
|
+
const pct = Math.round(cells / (rows * cols) * 100);
|
|
2590
|
+
const label = `${category.slice(0, 6)} ${pct}%`;
|
|
2591
|
+
canvas.drawChar(legendX, legendY, "█", color);
|
|
2592
|
+
canvas.drawString(legendX + 2, legendY, label, { r: 180, g: 180, b: 180, a: 1 });
|
|
2593
|
+
legendY += 2;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
function renderGeomSparkline(data, geom, aes, scales, canvas) {
|
|
2598
|
+
const sparkType = geom.params.sparkType ?? "bar";
|
|
2599
|
+
const width = geom.params.width ?? 20;
|
|
2600
|
+
const showMinmax = geom.params.show_minmax ?? false;
|
|
2601
|
+
const normalize = geom.params.normalize ?? true;
|
|
2602
|
+
const minColor = parseColorToRgba(geom.params.min_color ?? "#e74c3c");
|
|
2603
|
+
const maxColor = parseColorToRgba(geom.params.max_color ?? "#2ecc71");
|
|
2604
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2605
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2606
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2607
|
+
const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
2608
|
+
const defaultColor = { r: 79, g: 169, b: 238, a: 1 };
|
|
2609
|
+
const groupField = aes.group || aes.color;
|
|
2610
|
+
const groups = new Map;
|
|
2611
|
+
if (groupField) {
|
|
2612
|
+
for (const row of data) {
|
|
2613
|
+
const key = String(row[groupField] ?? "default");
|
|
2614
|
+
if (!groups.has(key))
|
|
2615
|
+
groups.set(key, []);
|
|
2616
|
+
groups.get(key).push(row);
|
|
2617
|
+
}
|
|
2618
|
+
} else {
|
|
2619
|
+
groups.set("default", [...data]);
|
|
2620
|
+
}
|
|
2621
|
+
let currentY = plotTop;
|
|
2622
|
+
for (const [groupKey, groupData] of groups) {
|
|
2623
|
+
const sorted = aes.x ? [...groupData].sort((a, b) => Number(a[aes.x]) - Number(b[aes.x])) : groupData;
|
|
2624
|
+
const values = sorted.map((row) => Number(row[aes.y]) || 0);
|
|
2625
|
+
if (values.length === 0)
|
|
2626
|
+
continue;
|
|
2627
|
+
const minVal = Math.min(...values);
|
|
2628
|
+
const maxVal = Math.max(...values);
|
|
2629
|
+
const minIdx = values.indexOf(minVal);
|
|
2630
|
+
const maxIdx = values.indexOf(maxVal);
|
|
2631
|
+
const range = maxVal - minVal || 1;
|
|
2632
|
+
const color = scales.color?.map(groupKey) ?? defaultColor;
|
|
2633
|
+
const sparkValues = [];
|
|
2634
|
+
if (values.length <= width) {
|
|
2635
|
+
sparkValues.push(...values);
|
|
2636
|
+
} else {
|
|
2637
|
+
for (let i = 0;i < width; i++) {
|
|
2638
|
+
const idx = Math.floor(i * values.length / width);
|
|
2639
|
+
sparkValues.push(values[idx]);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
for (let i = 0;i < sparkValues.length; i++) {
|
|
2643
|
+
const val = sparkValues[i];
|
|
2644
|
+
const normalized = normalize ? (val - minVal) / range : val / (maxVal || 1);
|
|
2645
|
+
const charIdx = Math.min(7, Math.max(0, Math.floor(normalized * 8)));
|
|
2646
|
+
const char = sparkType === "dot" ? "•" : SPARK_CHARS[charIdx];
|
|
2647
|
+
const x = plotLeft + i;
|
|
2648
|
+
const y = currentY;
|
|
2649
|
+
let pointColor = color;
|
|
2650
|
+
if (showMinmax) {
|
|
2651
|
+
const origIdx = Math.floor(i * values.length / sparkValues.length);
|
|
2652
|
+
if (origIdx === minIdx)
|
|
2653
|
+
pointColor = minColor;
|
|
2654
|
+
else if (origIdx === maxIdx)
|
|
2655
|
+
pointColor = maxColor;
|
|
2656
|
+
}
|
|
2657
|
+
if (x < plotLeft + width && y >= plotTop && y <= plotBottom) {
|
|
2658
|
+
canvas.drawChar(x, y, char, pointColor);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
if (groupField && groups.size > 1) {
|
|
2662
|
+
const labelX = plotLeft + width + 1;
|
|
2663
|
+
canvas.drawString(labelX, currentY, groupKey.slice(0, 8), { r: 180, g: 180, b: 180, a: 1 });
|
|
2664
|
+
}
|
|
2665
|
+
currentY += 2;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
function renderGeomBullet(data, geom, aes, scales, canvas) {
|
|
2669
|
+
const width = geom.params.width ?? 40;
|
|
2670
|
+
const targetChar = geom.params.target_char ?? "│";
|
|
2671
|
+
const barChar = geom.params.bar_char ?? "█";
|
|
2672
|
+
const rangeChars = geom.params.range_chars ?? ["░", "▒", "▓"];
|
|
2673
|
+
const showValues = geom.params.show_values ?? true;
|
|
2674
|
+
const targetColor = parseColorToRgba(geom.params.target_color ?? "#e74c3c");
|
|
2675
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2676
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2677
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2678
|
+
const defaultColors = [
|
|
2679
|
+
{ r: 79, g: 169, b: 238, a: 1 },
|
|
2680
|
+
{ r: 238, g: 136, b: 102, a: 1 },
|
|
2681
|
+
{ r: 102, g: 204, b: 153, a: 1 }
|
|
2682
|
+
];
|
|
2683
|
+
const rangeColors = [
|
|
2684
|
+
{ r: 60, g: 60, b: 60, a: 1 },
|
|
2685
|
+
{ r: 90, g: 90, b: 90, a: 1 },
|
|
2686
|
+
{ r: 120, g: 120, b: 120, a: 1 }
|
|
2687
|
+
];
|
|
2688
|
+
let currentY = plotTop;
|
|
2689
|
+
for (let i = 0;i < data.length; i++) {
|
|
2690
|
+
const row = data[i];
|
|
2691
|
+
const label = aes.x ? String(row[aes.x]).slice(0, 10) : `Item ${i + 1}`;
|
|
2692
|
+
const value = Number(row[aes.y]) || 0;
|
|
2693
|
+
const target = Number(row["target"]) || null;
|
|
2694
|
+
const maxValue = Number(row["max"]) || Math.max(value, target || 0) * 1.2;
|
|
2695
|
+
const ranges = row["ranges"] ?? [maxValue * 0.6, maxValue * 0.8, maxValue];
|
|
2696
|
+
const color = scales.color?.map(label) ?? defaultColors[i % defaultColors.length];
|
|
2697
|
+
const labelWidth = 12;
|
|
2698
|
+
canvas.drawString(plotLeft, currentY, label.padEnd(labelWidth), { r: 180, g: 180, b: 180, a: 1 });
|
|
2699
|
+
const barStart = plotLeft + labelWidth;
|
|
2700
|
+
const barWidth = Math.min(width, scales.x.range[1] - barStart - (showValues ? 8 : 0));
|
|
2701
|
+
for (let r = ranges.length - 1;r >= 0; r--) {
|
|
2702
|
+
const rangeWidth = Math.round(ranges[r] / maxValue * barWidth);
|
|
2703
|
+
for (let x = 0;x < rangeWidth; x++) {
|
|
2704
|
+
if (barStart + x <= scales.x.range[1]) {
|
|
2705
|
+
canvas.drawChar(barStart + x, currentY, rangeChars[r], rangeColors[r]);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
const valueWidth = Math.round(value / maxValue * barWidth);
|
|
2710
|
+
for (let x = 0;x < valueWidth; x++) {
|
|
2711
|
+
if (barStart + x <= scales.x.range[1]) {
|
|
2712
|
+
canvas.drawChar(barStart + x, currentY, barChar, color);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (target !== null) {
|
|
2716
|
+
const targetX = barStart + Math.round(target / maxValue * barWidth);
|
|
2717
|
+
if (targetX >= barStart && targetX <= barStart + barWidth) {
|
|
2718
|
+
canvas.drawChar(targetX, currentY, targetChar, targetColor);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
if (showValues) {
|
|
2722
|
+
const valueStr = value.toFixed(0);
|
|
2723
|
+
canvas.drawString(barStart + barWidth + 2, currentY, valueStr, { r: 180, g: 180, b: 180, a: 1 });
|
|
2724
|
+
}
|
|
2725
|
+
currentY += 2;
|
|
2726
|
+
if (currentY > plotBottom)
|
|
2727
|
+
break;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
function renderGeomBraille(data, geom, aes, scales, canvas) {
|
|
2731
|
+
const brailleType = geom.params.brailleType ?? "point";
|
|
2732
|
+
const fill = geom.params.fill ?? false;
|
|
2733
|
+
const alpha = geom.params.alpha ?? 1;
|
|
2734
|
+
const BRAILLE_BASE = 10240;
|
|
2735
|
+
const DOTS = [
|
|
2736
|
+
[1, 2, 4, 64],
|
|
2737
|
+
[8, 16, 32, 128]
|
|
2738
|
+
];
|
|
2739
|
+
const plotLeft = Math.round(scales.x.range[0]);
|
|
2740
|
+
const plotRight = Math.round(scales.x.range[1]);
|
|
2741
|
+
const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
|
|
2742
|
+
const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
|
|
2743
|
+
const plotWidth = plotRight - plotLeft;
|
|
2744
|
+
const plotHeight = plotBottom - plotTop;
|
|
2745
|
+
const brailleWidth = plotWidth;
|
|
2746
|
+
const brailleHeight = plotHeight;
|
|
2747
|
+
const buffer = [];
|
|
2748
|
+
for (let y = 0;y < brailleHeight; y++) {
|
|
2749
|
+
buffer[y] = new Array(brailleWidth).fill(0);
|
|
2750
|
+
}
|
|
2751
|
+
const defaultColor = { r: 79, g: 169, b: 238, a: 1 };
|
|
2752
|
+
const color = geom.params.color ? parseColorToRgba(geom.params.color) : defaultColor;
|
|
2753
|
+
const finalColor = alpha < 1 ? { ...color, a: alpha } : color;
|
|
2754
|
+
const sorted = aes.x ? [...data].sort((a, b) => Number(a[aes.x]) - Number(b[aes.x])) : data;
|
|
2755
|
+
const setDot = (canvasX, canvasY) => {
|
|
2756
|
+
const subX = (canvasX - plotLeft) * 2;
|
|
2757
|
+
const subY = (canvasY - plotTop) * 4;
|
|
2758
|
+
const cellX = Math.floor(subX / 2);
|
|
2759
|
+
const cellY = Math.floor(subY / 4);
|
|
2760
|
+
const dotCol = subX % 2;
|
|
2761
|
+
const dotRow = subY % 4;
|
|
2762
|
+
if (cellX >= 0 && cellX < brailleWidth && cellY >= 0 && cellY < brailleHeight) {
|
|
2763
|
+
if (dotCol >= 0 && dotCol < 2 && dotRow >= 0 && dotRow < 4) {
|
|
2764
|
+
buffer[cellY][cellX] |= DOTS[dotCol][dotRow];
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
let prevCx = null;
|
|
2769
|
+
let prevCy = null;
|
|
2770
|
+
for (const row of sorted) {
|
|
2771
|
+
const xVal = row[aes.x];
|
|
2772
|
+
const yVal = row[aes.y];
|
|
2773
|
+
if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
|
|
2774
|
+
prevCx = null;
|
|
2775
|
+
prevCy = null;
|
|
2776
|
+
continue;
|
|
2777
|
+
}
|
|
2778
|
+
const cx = Math.round(scales.x.map(xVal));
|
|
2779
|
+
const cy = Math.round(scales.y.map(yVal));
|
|
2780
|
+
if (brailleType === "line" && prevCx !== null && prevCy !== null) {
|
|
2781
|
+
const dx = Math.abs(cx - prevCx);
|
|
2782
|
+
const dy = Math.abs(cy - prevCy);
|
|
2783
|
+
const sx = prevCx < cx ? 1 : -1;
|
|
2784
|
+
const sy = prevCy < cy ? 1 : -1;
|
|
2785
|
+
let err = dx - dy;
|
|
2786
|
+
let x = prevCx;
|
|
2787
|
+
let y = prevCy;
|
|
2788
|
+
while (true) {
|
|
2789
|
+
if (x >= plotLeft && x < plotRight && y >= plotTop && y < plotBottom) {
|
|
2790
|
+
setDot(x, y);
|
|
2791
|
+
if (fill) {
|
|
2792
|
+
for (let fy = y;fy < plotBottom; fy++) {
|
|
2793
|
+
setDot(x, fy);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
if (x === cx && y === cy)
|
|
2798
|
+
break;
|
|
2799
|
+
const e2 = 2 * err;
|
|
2800
|
+
if (e2 > -dy) {
|
|
2801
|
+
err -= dy;
|
|
2802
|
+
x += sx;
|
|
2803
|
+
}
|
|
2804
|
+
if (e2 < dx) {
|
|
2805
|
+
err += dx;
|
|
2806
|
+
y += sy;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
} else {
|
|
2810
|
+
if (cx >= plotLeft && cx < plotRight && cy >= plotTop && cy < plotBottom) {
|
|
2811
|
+
setDot(cx, cy);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
prevCx = cx;
|
|
2815
|
+
prevCy = cy;
|
|
2816
|
+
}
|
|
2817
|
+
for (let y = 0;y < brailleHeight; y++) {
|
|
2818
|
+
for (let x = 0;x < brailleWidth; x++) {
|
|
2819
|
+
if (buffer[y][x] > 0) {
|
|
2820
|
+
const char = String.fromCharCode(BRAILLE_BASE + buffer[y][x]);
|
|
2821
|
+
canvas.drawChar(plotLeft + x, plotTop + y, char, finalColor);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2311
2826
|
function renderGeom(data, geom, aes, scales, canvas, coordType) {
|
|
2312
2827
|
switch (geom.type) {
|
|
2313
2828
|
case "point":
|
|
@@ -2389,6 +2904,28 @@ function renderGeom(data, geom, aes, scales, canvas, coordType) {
|
|
|
2389
2904
|
case "smooth":
|
|
2390
2905
|
renderGeomSmooth(data, geom, aes, scales, canvas);
|
|
2391
2906
|
break;
|
|
2907
|
+
case "beeswarm":
|
|
2908
|
+
case "quasirandom":
|
|
2909
|
+
renderGeomBeeswarm(data, geom, aes, scales, canvas);
|
|
2910
|
+
break;
|
|
2911
|
+
case "dumbbell":
|
|
2912
|
+
renderGeomDumbbell(data, geom, aes, scales, canvas);
|
|
2913
|
+
break;
|
|
2914
|
+
case "lollipop":
|
|
2915
|
+
renderGeomLollipop(data, geom, aes, scales, canvas);
|
|
2916
|
+
break;
|
|
2917
|
+
case "waffle":
|
|
2918
|
+
renderGeomWaffle(data, geom, aes, scales, canvas);
|
|
2919
|
+
break;
|
|
2920
|
+
case "sparkline":
|
|
2921
|
+
renderGeomSparkline(data, geom, aes, scales, canvas);
|
|
2922
|
+
break;
|
|
2923
|
+
case "bullet":
|
|
2924
|
+
renderGeomBullet(data, geom, aes, scales, canvas);
|
|
2925
|
+
break;
|
|
2926
|
+
case "braille":
|
|
2927
|
+
renderGeomBraille(data, geom, aes, scales, canvas);
|
|
2928
|
+
break;
|
|
2392
2929
|
default:
|
|
2393
2930
|
break;
|
|
2394
2931
|
}
|
|
@@ -3516,6 +4053,224 @@ function stat_xdensity(params = {}) {
|
|
|
3516
4053
|
};
|
|
3517
4054
|
}
|
|
3518
4055
|
|
|
4056
|
+
// src/stats/beeswarm.ts
|
|
4057
|
+
function swarmArrange(yValues, params) {
|
|
4058
|
+
const n = yValues.length;
|
|
4059
|
+
if (n === 0)
|
|
4060
|
+
return { offsets: [], indices: [] };
|
|
4061
|
+
const cex = params.cex ?? 1;
|
|
4062
|
+
const spacing = params.spacing ?? 1;
|
|
4063
|
+
const side = params.side ?? 0;
|
|
4064
|
+
const yRange = Math.max(...yValues) - Math.min(...yValues);
|
|
4065
|
+
const pointSize = yRange / Math.max(n, 10) * cex * spacing;
|
|
4066
|
+
let indices = yValues.map((_, i) => i);
|
|
4067
|
+
const priority = params.priority ?? "ascending";
|
|
4068
|
+
switch (priority) {
|
|
4069
|
+
case "ascending":
|
|
4070
|
+
indices.sort((a, b) => yValues[a] - yValues[b]);
|
|
4071
|
+
break;
|
|
4072
|
+
case "descending":
|
|
4073
|
+
indices.sort((a, b) => yValues[b] - yValues[a]);
|
|
4074
|
+
break;
|
|
4075
|
+
case "random":
|
|
4076
|
+
for (let i = indices.length - 1;i > 0; i--) {
|
|
4077
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
4078
|
+
[indices[i], indices[j]] = [indices[j], indices[i]];
|
|
4079
|
+
}
|
|
4080
|
+
break;
|
|
4081
|
+
case "density":
|
|
4082
|
+
const median = yValues.slice().sort((a, b) => a - b)[Math.floor(n / 2)];
|
|
4083
|
+
indices.sort((a, b) => Math.abs(yValues[a] - median) - Math.abs(yValues[b] - median));
|
|
4084
|
+
break;
|
|
4085
|
+
}
|
|
4086
|
+
const placed = [];
|
|
4087
|
+
const offsets = new Array(n).fill(0);
|
|
4088
|
+
for (const idx of indices) {
|
|
4089
|
+
const y = yValues[idx];
|
|
4090
|
+
let bestOffset = 0;
|
|
4091
|
+
if (placed.length > 0) {
|
|
4092
|
+
const nearby = placed.filter((p) => Math.abs(p.y - y) < pointSize * 2);
|
|
4093
|
+
if (nearby.length > 0) {
|
|
4094
|
+
const maxOffset = nearby.length * pointSize;
|
|
4095
|
+
let foundSpot = false;
|
|
4096
|
+
for (let tryOffset = 0;tryOffset <= maxOffset && !foundSpot; tryOffset += pointSize * 0.5) {
|
|
4097
|
+
if (side >= 0) {
|
|
4098
|
+
let collision = false;
|
|
4099
|
+
for (const p of nearby) {
|
|
4100
|
+
const dx = tryOffset - p.x;
|
|
4101
|
+
const dy = y - p.y;
|
|
4102
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
4103
|
+
if (dist < pointSize) {
|
|
4104
|
+
collision = true;
|
|
4105
|
+
break;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
if (!collision) {
|
|
4109
|
+
bestOffset = tryOffset;
|
|
4110
|
+
foundSpot = true;
|
|
4111
|
+
break;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
if (side <= 0 && tryOffset > 0 && !foundSpot) {
|
|
4115
|
+
let collision = false;
|
|
4116
|
+
for (const p of nearby) {
|
|
4117
|
+
const dx = -tryOffset - p.x;
|
|
4118
|
+
const dy = y - p.y;
|
|
4119
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
4120
|
+
if (dist < pointSize) {
|
|
4121
|
+
collision = true;
|
|
4122
|
+
break;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
if (!collision) {
|
|
4126
|
+
bestOffset = -tryOffset;
|
|
4127
|
+
foundSpot = true;
|
|
4128
|
+
break;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
offsets[idx] = bestOffset;
|
|
4135
|
+
placed.push({ y, x: bestOffset });
|
|
4136
|
+
}
|
|
4137
|
+
const maxAbsOffset = Math.max(...offsets.map(Math.abs), 0.001);
|
|
4138
|
+
const dodge = params.dodge ?? 0.8;
|
|
4139
|
+
const scale = dodge * 0.5 / maxAbsOffset;
|
|
4140
|
+
for (let i = 0;i < offsets.length; i++) {
|
|
4141
|
+
offsets[i] *= scale;
|
|
4142
|
+
}
|
|
4143
|
+
return { offsets, indices };
|
|
4144
|
+
}
|
|
4145
|
+
function centerArrange(yValues, params) {
|
|
4146
|
+
const n = yValues.length;
|
|
4147
|
+
if (n === 0)
|
|
4148
|
+
return { offsets: [], indices: [] };
|
|
4149
|
+
const dodge = params.dodge ?? 0.8;
|
|
4150
|
+
const side = params.side ?? 0;
|
|
4151
|
+
const indices = yValues.map((_, i) => i);
|
|
4152
|
+
indices.sort((a, b) => yValues[a] - yValues[b]);
|
|
4153
|
+
const offsets = new Array(n).fill(0);
|
|
4154
|
+
const maxOffset = dodge * 0.4;
|
|
4155
|
+
for (let i = 0;i < indices.length; i++) {
|
|
4156
|
+
const idx = indices[i];
|
|
4157
|
+
const layer = Math.floor(i / 2) + 1;
|
|
4158
|
+
const offset = layer / Math.ceil(n / 2) * maxOffset;
|
|
4159
|
+
if (side === 0) {
|
|
4160
|
+
offsets[idx] = i % 2 === 0 ? offset : -offset;
|
|
4161
|
+
} else {
|
|
4162
|
+
offsets[idx] = side * offset;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
return { offsets, indices };
|
|
4166
|
+
}
|
|
4167
|
+
function squareArrange(yValues, params) {
|
|
4168
|
+
const n = yValues.length;
|
|
4169
|
+
if (n === 0)
|
|
4170
|
+
return { offsets: [], indices: [] };
|
|
4171
|
+
const dodge = params.dodge ?? 0.8;
|
|
4172
|
+
const side = params.side ?? 0;
|
|
4173
|
+
const indices = yValues.map((_, i) => i);
|
|
4174
|
+
indices.sort((a, b) => yValues[a] - yValues[b]);
|
|
4175
|
+
const offsets = new Array(n).fill(0);
|
|
4176
|
+
const yRange = Math.max(...yValues) - Math.min(...yValues);
|
|
4177
|
+
const binSize = yRange / Math.max(Math.sqrt(n), 3);
|
|
4178
|
+
const bins = new Map;
|
|
4179
|
+
for (let i = 0;i < indices.length; i++) {
|
|
4180
|
+
const idx = indices[i];
|
|
4181
|
+
const y = yValues[idx];
|
|
4182
|
+
const binKey = Math.floor(y / binSize);
|
|
4183
|
+
if (!bins.has(binKey)) {
|
|
4184
|
+
bins.set(binKey, []);
|
|
4185
|
+
}
|
|
4186
|
+
bins.get(binKey).push(idx);
|
|
4187
|
+
}
|
|
4188
|
+
for (const binIndices of bins.values()) {
|
|
4189
|
+
const binN = binIndices.length;
|
|
4190
|
+
const maxOffset = dodge * 0.4;
|
|
4191
|
+
for (let i = 0;i < binN; i++) {
|
|
4192
|
+
const idx = binIndices[i];
|
|
4193
|
+
const offset = (i - (binN - 1) / 2) * (maxOffset * 2 / Math.max(binN - 1, 1));
|
|
4194
|
+
if (side === 0) {
|
|
4195
|
+
offsets[idx] = offset;
|
|
4196
|
+
} else if (side > 0) {
|
|
4197
|
+
offsets[idx] = Math.abs(offset);
|
|
4198
|
+
} else {
|
|
4199
|
+
offsets[idx] = -Math.abs(offset);
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
return { offsets, indices };
|
|
4204
|
+
}
|
|
4205
|
+
function computeBeeswarm(data, _xField, yField, groupKey, groupIndex, params = {}) {
|
|
4206
|
+
const yValues = [];
|
|
4207
|
+
const originalRows = [];
|
|
4208
|
+
for (const row of data) {
|
|
4209
|
+
const yVal = row[yField];
|
|
4210
|
+
if (yVal === null || yVal === undefined)
|
|
4211
|
+
continue;
|
|
4212
|
+
const numY = Number(yVal);
|
|
4213
|
+
if (isNaN(numY))
|
|
4214
|
+
continue;
|
|
4215
|
+
yValues.push(numY);
|
|
4216
|
+
originalRows.push(row);
|
|
4217
|
+
}
|
|
4218
|
+
if (yValues.length === 0)
|
|
4219
|
+
return [];
|
|
4220
|
+
const method = params.method ?? "swarm";
|
|
4221
|
+
let result;
|
|
4222
|
+
switch (method) {
|
|
4223
|
+
case "center":
|
|
4224
|
+
result = centerArrange(yValues, params);
|
|
4225
|
+
break;
|
|
4226
|
+
case "square":
|
|
4227
|
+
result = squareArrange(yValues, params);
|
|
4228
|
+
break;
|
|
4229
|
+
case "swarm":
|
|
4230
|
+
default:
|
|
4231
|
+
result = swarmArrange(yValues, params);
|
|
4232
|
+
}
|
|
4233
|
+
const output = [];
|
|
4234
|
+
for (let i = 0;i < yValues.length; i++) {
|
|
4235
|
+
const originalRow = originalRows[i];
|
|
4236
|
+
output.push({
|
|
4237
|
+
x: groupIndex + result.offsets[i],
|
|
4238
|
+
y: yValues[i],
|
|
4239
|
+
xOriginal: groupKey,
|
|
4240
|
+
yOriginal: yValues[i],
|
|
4241
|
+
xOffset: result.offsets[i],
|
|
4242
|
+
...originalRow
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
return output;
|
|
4246
|
+
}
|
|
4247
|
+
function stat_beeswarm(params = {}) {
|
|
4248
|
+
return {
|
|
4249
|
+
type: "beeswarm",
|
|
4250
|
+
compute(data, aes) {
|
|
4251
|
+
const groups = new Map;
|
|
4252
|
+
const groupOrder = [];
|
|
4253
|
+
for (const row of data) {
|
|
4254
|
+
const groupKey = String(row[aes.x] ?? "default");
|
|
4255
|
+
if (!groups.has(groupKey)) {
|
|
4256
|
+
groups.set(groupKey, []);
|
|
4257
|
+
groupOrder.push(groupKey);
|
|
4258
|
+
}
|
|
4259
|
+
groups.get(groupKey).push(row);
|
|
4260
|
+
}
|
|
4261
|
+
const result = [];
|
|
4262
|
+
let groupIndex = 0;
|
|
4263
|
+
for (const groupKey of groupOrder) {
|
|
4264
|
+
const groupData = groups.get(groupKey);
|
|
4265
|
+
const swarmResult = computeBeeswarm(groupData, aes.x, aes.y, groupKey, groupIndex, params);
|
|
4266
|
+
result.push(...swarmResult);
|
|
4267
|
+
groupIndex++;
|
|
4268
|
+
}
|
|
4269
|
+
return result;
|
|
4270
|
+
}
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
|
|
3519
4274
|
// src/stats/smooth.ts
|
|
3520
4275
|
function linearRegression(xs, ys) {
|
|
3521
4276
|
const n = xs.length;
|
|
@@ -4395,6 +5150,15 @@ function applyStatTransform(data, geom, aes) {
|
|
|
4395
5150
|
adjust: geom.params.adjust
|
|
4396
5151
|
});
|
|
4397
5152
|
return xdensityStat.compute(data, aes);
|
|
5153
|
+
} else if (geom.stat === "beeswarm") {
|
|
5154
|
+
const beeswarmStat = stat_beeswarm({
|
|
5155
|
+
method: geom.params.method,
|
|
5156
|
+
cex: geom.params.cex,
|
|
5157
|
+
side: geom.params.side,
|
|
5158
|
+
priority: geom.params.priority,
|
|
5159
|
+
dodge: geom.params.dodge
|
|
5160
|
+
});
|
|
5161
|
+
return beeswarmStat.compute(data, aes);
|
|
4398
5162
|
} else if (geom.stat === "smooth") {
|
|
4399
5163
|
const smoothStat = stat_smooth({
|
|
4400
5164
|
method: geom.params.method,
|
|
@@ -4521,6 +5285,10 @@ function renderToCanvas(spec, options) {
|
|
|
4521
5285
|
scaleData = applyStatTransform(spec.data, geom, spec.aes);
|
|
4522
5286
|
scaleAes = { ...spec.aes, x: "x", y: "y" };
|
|
4523
5287
|
break;
|
|
5288
|
+
} else if (geom.stat === "beeswarm") {
|
|
5289
|
+
scaleData = spec.data;
|
|
5290
|
+
scaleAes = spec.aes;
|
|
5291
|
+
break;
|
|
4524
5292
|
}
|
|
4525
5293
|
}
|
|
4526
5294
|
scaleData = applyCoordTransform(scaleData, scaleAes, spec.coord);
|
|
@@ -4561,6 +5329,8 @@ function renderToCanvas(spec, options) {
|
|
|
4561
5329
|
geomAes = { ...spec.aes, x: "x", y: "y" };
|
|
4562
5330
|
} else if (geom.stat === "xdensity") {
|
|
4563
5331
|
geomAes = { ...spec.aes, x: "x", y: "y" };
|
|
5332
|
+
} else if (geom.stat === "beeswarm") {
|
|
5333
|
+
geomAes = { ...spec.aes, x: "x", y: "y" };
|
|
4564
5334
|
}
|
|
4565
5335
|
geomData = applyCoordTransform(geomData, geomAes, spec.coord);
|
|
4566
5336
|
}
|
|
@@ -5505,9 +6275,159 @@ var init_ridgeline = __esm(() => {
|
|
|
5505
6275
|
geom_joy = geom_ridgeline;
|
|
5506
6276
|
});
|
|
5507
6277
|
|
|
6278
|
+
// src/geoms/beeswarm.ts
|
|
6279
|
+
function geom_beeswarm(options = {}) {
|
|
6280
|
+
return {
|
|
6281
|
+
type: "beeswarm",
|
|
6282
|
+
stat: "beeswarm",
|
|
6283
|
+
position: "identity",
|
|
6284
|
+
params: {
|
|
6285
|
+
method: options.method ?? "swarm",
|
|
6286
|
+
size: options.size ?? 1,
|
|
6287
|
+
cex: options.cex ?? 1,
|
|
6288
|
+
alpha: options.alpha ?? 1,
|
|
6289
|
+
color: options.color,
|
|
6290
|
+
shape: options.shape ?? "circle",
|
|
6291
|
+
side: options.side ?? 0,
|
|
6292
|
+
priority: options.priority ?? "ascending",
|
|
6293
|
+
dodge: options.dodge ?? 0.8
|
|
6294
|
+
}
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6297
|
+
function geom_quasirandom(options = {}) {
|
|
6298
|
+
return geom_beeswarm({ ...options, method: "center" });
|
|
6299
|
+
}
|
|
6300
|
+
|
|
6301
|
+
// src/geoms/dumbbell.ts
|
|
6302
|
+
function geom_dumbbell(options = {}) {
|
|
6303
|
+
return {
|
|
6304
|
+
type: "dumbbell",
|
|
6305
|
+
stat: "identity",
|
|
6306
|
+
position: "identity",
|
|
6307
|
+
params: {
|
|
6308
|
+
size: options.size ?? 2,
|
|
6309
|
+
sizeEnd: options.sizeEnd ?? options.size ?? 2,
|
|
6310
|
+
color: options.color,
|
|
6311
|
+
colorEnd: options.colorEnd ?? options.color,
|
|
6312
|
+
lineColor: options.lineColor ?? "#666666",
|
|
6313
|
+
lineWidth: options.lineWidth ?? 1,
|
|
6314
|
+
alpha: options.alpha ?? 1,
|
|
6315
|
+
shape: options.shape ?? "circle"
|
|
6316
|
+
}
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
// src/geoms/lollipop.ts
|
|
6321
|
+
function geom_lollipop(options = {}) {
|
|
6322
|
+
return {
|
|
6323
|
+
type: "lollipop",
|
|
6324
|
+
stat: "identity",
|
|
6325
|
+
position: "identity",
|
|
6326
|
+
params: {
|
|
6327
|
+
size: options.size ?? 2,
|
|
6328
|
+
color: options.color,
|
|
6329
|
+
lineColor: options.lineColor,
|
|
6330
|
+
lineWidth: options.lineWidth ?? 1,
|
|
6331
|
+
alpha: options.alpha ?? 1,
|
|
6332
|
+
shape: options.shape ?? "circle",
|
|
6333
|
+
direction: options.direction ?? "vertical",
|
|
6334
|
+
baseline: options.baseline ?? 0
|
|
6335
|
+
}
|
|
6336
|
+
};
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
// src/geoms/waffle.ts
|
|
6340
|
+
function geom_waffle(options = {}) {
|
|
6341
|
+
return {
|
|
6342
|
+
type: "waffle",
|
|
6343
|
+
stat: "identity",
|
|
6344
|
+
position: "identity",
|
|
6345
|
+
params: {
|
|
6346
|
+
rows: options.rows ?? 10,
|
|
6347
|
+
cols: options.cols ?? 10,
|
|
6348
|
+
n_total: options.n_total ?? 100,
|
|
6349
|
+
fill_char: options.fill_char ?? "█",
|
|
6350
|
+
empty_char: options.empty_char ?? "░",
|
|
6351
|
+
alpha: options.alpha ?? 1,
|
|
6352
|
+
show_legend: options.show_legend ?? true,
|
|
6353
|
+
flip: options.flip ?? false,
|
|
6354
|
+
gap: options.gap ?? 0
|
|
6355
|
+
}
|
|
6356
|
+
};
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
// src/geoms/sparkline.ts
|
|
6360
|
+
function geom_sparkline(options = {}) {
|
|
6361
|
+
return {
|
|
6362
|
+
type: "sparkline",
|
|
6363
|
+
stat: "identity",
|
|
6364
|
+
position: "identity",
|
|
6365
|
+
params: {
|
|
6366
|
+
sparkType: options.type ?? "bar",
|
|
6367
|
+
width: options.width ?? 20,
|
|
6368
|
+
height: options.height ?? 1,
|
|
6369
|
+
show_minmax: options.show_minmax ?? false,
|
|
6370
|
+
color: options.color,
|
|
6371
|
+
min_color: options.min_color ?? "#e74c3c",
|
|
6372
|
+
max_color: options.max_color ?? "#2ecc71",
|
|
6373
|
+
normalize: options.normalize ?? true
|
|
6374
|
+
}
|
|
6375
|
+
};
|
|
6376
|
+
}
|
|
6377
|
+
var SPARK_BARS, SPARK_DOTS;
|
|
6378
|
+
var init_sparkline = __esm(() => {
|
|
6379
|
+
SPARK_BARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
6380
|
+
SPARK_DOTS = ["⠀", "⢀", "⢠", "⢰", "⢸", "⣸", "⣾", "⣿"];
|
|
6381
|
+
});
|
|
6382
|
+
|
|
6383
|
+
// src/geoms/bullet.ts
|
|
6384
|
+
function geom_bullet(options = {}) {
|
|
6385
|
+
return {
|
|
6386
|
+
type: "bullet",
|
|
6387
|
+
stat: "identity",
|
|
6388
|
+
position: "identity",
|
|
6389
|
+
params: {
|
|
6390
|
+
width: options.width ?? 40,
|
|
6391
|
+
height: options.height ?? 1,
|
|
6392
|
+
target_char: options.target_char ?? "│",
|
|
6393
|
+
bar_char: options.bar_char ?? "█",
|
|
6394
|
+
range_chars: options.range_chars ?? ["░", "▒", "▓"],
|
|
6395
|
+
show_values: options.show_values ?? true,
|
|
6396
|
+
color: options.color,
|
|
6397
|
+
target_color: options.target_color ?? "#e74c3c",
|
|
6398
|
+
orientation: options.orientation ?? "horizontal"
|
|
6399
|
+
}
|
|
6400
|
+
};
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
// src/geoms/braille.ts
|
|
6404
|
+
function geom_braille(options = {}) {
|
|
6405
|
+
return {
|
|
6406
|
+
type: "braille",
|
|
6407
|
+
stat: "identity",
|
|
6408
|
+
position: "identity",
|
|
6409
|
+
params: {
|
|
6410
|
+
brailleType: options.type ?? "point",
|
|
6411
|
+
color: options.color,
|
|
6412
|
+
fill: options.fill ?? false,
|
|
6413
|
+
alpha: options.alpha ?? 1,
|
|
6414
|
+
dot_size: options.dot_size ?? 1
|
|
6415
|
+
}
|
|
6416
|
+
};
|
|
6417
|
+
}
|
|
6418
|
+
var BRAILLE_BASE = 10240, BRAILLE_DOTS;
|
|
6419
|
+
var init_braille = __esm(() => {
|
|
6420
|
+
BRAILLE_DOTS = [
|
|
6421
|
+
[1, 2, 4, 64],
|
|
6422
|
+
[8, 16, 32, 128]
|
|
6423
|
+
];
|
|
6424
|
+
});
|
|
6425
|
+
|
|
5508
6426
|
// src/geoms/index.ts
|
|
5509
6427
|
var init_geoms = __esm(() => {
|
|
5510
6428
|
init_ridgeline();
|
|
6429
|
+
init_sparkline();
|
|
6430
|
+
init_braille();
|
|
5511
6431
|
});
|
|
5512
6432
|
|
|
5513
6433
|
// src/stats/index.ts
|
|
@@ -9913,6 +10833,7 @@ __export(exports_src, {
|
|
|
9913
10833
|
stat_boxplot: () => stat_boxplot,
|
|
9914
10834
|
stat_bin2d: () => stat_bin2d,
|
|
9915
10835
|
stat_bin: () => stat_bin,
|
|
10836
|
+
stat_beeswarm: () => stat_beeswarm,
|
|
9916
10837
|
startREPL: () => startREPL,
|
|
9917
10838
|
selectRenderer: () => selectRenderer,
|
|
9918
10839
|
selectColorMode: () => selectColorMode,
|
|
@@ -10009,11 +10930,13 @@ __export(exports_src, {
|
|
|
10009
10930
|
getColorEscape: () => getColorEscape,
|
|
10010
10931
|
getCapabilities: () => getCapabilities,
|
|
10011
10932
|
getAvailablePalettes: () => getAvailablePalettes,
|
|
10933
|
+
geom_waffle: () => geom_waffle,
|
|
10012
10934
|
geom_vline: () => geom_vline,
|
|
10013
10935
|
geom_violin: () => geom_violin,
|
|
10014
10936
|
geom_tile: () => geom_tile,
|
|
10015
10937
|
geom_text: () => geom_text,
|
|
10016
10938
|
geom_step: () => geom_step,
|
|
10939
|
+
geom_sparkline: () => geom_sparkline,
|
|
10017
10940
|
geom_smooth: () => geom_smooth,
|
|
10018
10941
|
geom_segment: () => geom_segment,
|
|
10019
10942
|
geom_rug: () => geom_rug,
|
|
@@ -10021,11 +10944,13 @@ __export(exports_src, {
|
|
|
10021
10944
|
geom_ribbon: () => geom_ribbon,
|
|
10022
10945
|
geom_rect: () => geom_rect,
|
|
10023
10946
|
geom_raster: () => geom_raster,
|
|
10947
|
+
geom_quasirandom: () => geom_quasirandom,
|
|
10024
10948
|
geom_qq_line: () => geom_qq_line,
|
|
10025
10949
|
geom_qq: () => geom_qq,
|
|
10026
10950
|
geom_pointrange: () => geom_pointrange,
|
|
10027
10951
|
geom_point: () => geom_point,
|
|
10028
10952
|
geom_path: () => geom_path,
|
|
10953
|
+
geom_lollipop: () => geom_lollipop,
|
|
10029
10954
|
geom_linerange: () => geom_linerange,
|
|
10030
10955
|
geom_line: () => geom_line,
|
|
10031
10956
|
geom_label: () => geom_label,
|
|
@@ -10035,14 +10960,18 @@ __export(exports_src, {
|
|
|
10035
10960
|
geom_freqpoly: () => geom_freqpoly,
|
|
10036
10961
|
geom_errorbarh: () => geom_errorbarh,
|
|
10037
10962
|
geom_errorbar: () => geom_errorbar,
|
|
10963
|
+
geom_dumbbell: () => geom_dumbbell,
|
|
10038
10964
|
geom_density_2d: () => geom_density_2d,
|
|
10039
10965
|
geom_curve: () => geom_curve,
|
|
10040
10966
|
geom_crossbar: () => geom_crossbar,
|
|
10041
10967
|
geom_contour_filled: () => geom_contour_filled,
|
|
10042
10968
|
geom_contour: () => geom_contour,
|
|
10043
10969
|
geom_col: () => geom_col,
|
|
10970
|
+
geom_bullet: () => geom_bullet,
|
|
10971
|
+
geom_braille: () => geom_braille,
|
|
10044
10972
|
geom_boxplot: () => geom_boxplot,
|
|
10045
10973
|
geom_bin2d: () => geom_bin2d,
|
|
10974
|
+
geom_beeswarm: () => geom_beeswarm,
|
|
10046
10975
|
geom_bar: () => geom_bar,
|
|
10047
10976
|
geom_area: () => geom_area,
|
|
10048
10977
|
geom_abline: () => geom_abline,
|
|
@@ -10089,6 +11018,7 @@ __export(exports_src, {
|
|
|
10089
11018
|
computeBoxplotStats: () => computeBoxplotStats,
|
|
10090
11019
|
computeBins2d: () => computeBins2d,
|
|
10091
11020
|
computeBins: () => computeBins,
|
|
11021
|
+
computeBeeswarm: () => computeBeeswarm,
|
|
10092
11022
|
colorDistance: () => colorDistance,
|
|
10093
11023
|
clearCapabilityCache: () => clearCapabilityCache,
|
|
10094
11024
|
calculatePanelLayouts: () => calculatePanelLayouts,
|
|
@@ -10110,6 +11040,8 @@ __export(exports_src, {
|
|
|
10110
11040
|
annotate: () => annotate,
|
|
10111
11041
|
TerminalCanvas: () => TerminalCanvas,
|
|
10112
11042
|
StreamingPlot: () => StreamingPlot,
|
|
11043
|
+
SPARK_DOTS: () => SPARK_DOTS,
|
|
11044
|
+
SPARK_BARS: () => SPARK_BARS,
|
|
10113
11045
|
SHAPE_CHARS: () => SHAPE_CHARS,
|
|
10114
11046
|
RollingAggregator: () => RollingAggregator,
|
|
10115
11047
|
RendererChain: () => RendererChain,
|
|
@@ -10129,6 +11061,8 @@ __export(exports_src, {
|
|
|
10129
11061
|
DEFAULT_BG: () => DEFAULT_BG,
|
|
10130
11062
|
CanvasDiff: () => CanvasDiff,
|
|
10131
11063
|
Binner: () => Binner,
|
|
11064
|
+
BRAILLE_DOTS: () => BRAILLE_DOTS,
|
|
11065
|
+
BRAILLE_BASE: () => BRAILLE_BASE,
|
|
10132
11066
|
ANSI_16_COLORS: () => ANSI_16_COLORS
|
|
10133
11067
|
});
|
|
10134
11068
|
var init_src = __esm(() => {
|
|
@@ -10161,6 +11095,7 @@ export {
|
|
|
10161
11095
|
stat_boxplot,
|
|
10162
11096
|
stat_bin2d,
|
|
10163
11097
|
stat_bin,
|
|
11098
|
+
stat_beeswarm,
|
|
10164
11099
|
startREPL,
|
|
10165
11100
|
selectRenderer,
|
|
10166
11101
|
selectColorMode,
|
|
@@ -10257,11 +11192,13 @@ export {
|
|
|
10257
11192
|
getColorEscape,
|
|
10258
11193
|
getCapabilities,
|
|
10259
11194
|
getAvailablePalettes,
|
|
11195
|
+
geom_waffle,
|
|
10260
11196
|
geom_vline,
|
|
10261
11197
|
geom_violin,
|
|
10262
11198
|
geom_tile,
|
|
10263
11199
|
geom_text,
|
|
10264
11200
|
geom_step,
|
|
11201
|
+
geom_sparkline,
|
|
10265
11202
|
geom_smooth,
|
|
10266
11203
|
geom_segment,
|
|
10267
11204
|
geom_rug,
|
|
@@ -10269,11 +11206,13 @@ export {
|
|
|
10269
11206
|
geom_ribbon,
|
|
10270
11207
|
geom_rect,
|
|
10271
11208
|
geom_raster,
|
|
11209
|
+
geom_quasirandom,
|
|
10272
11210
|
geom_qq_line,
|
|
10273
11211
|
geom_qq,
|
|
10274
11212
|
geom_pointrange,
|
|
10275
11213
|
geom_point,
|
|
10276
11214
|
geom_path,
|
|
11215
|
+
geom_lollipop,
|
|
10277
11216
|
geom_linerange,
|
|
10278
11217
|
geom_line,
|
|
10279
11218
|
geom_label,
|
|
@@ -10283,14 +11222,18 @@ export {
|
|
|
10283
11222
|
geom_freqpoly,
|
|
10284
11223
|
geom_errorbarh,
|
|
10285
11224
|
geom_errorbar,
|
|
11225
|
+
geom_dumbbell,
|
|
10286
11226
|
geom_density_2d,
|
|
10287
11227
|
geom_curve,
|
|
10288
11228
|
geom_crossbar,
|
|
10289
11229
|
geom_contour_filled,
|
|
10290
11230
|
geom_contour,
|
|
10291
11231
|
geom_col,
|
|
11232
|
+
geom_bullet,
|
|
11233
|
+
geom_braille,
|
|
10292
11234
|
geom_boxplot,
|
|
10293
11235
|
geom_bin2d,
|
|
11236
|
+
geom_beeswarm,
|
|
10294
11237
|
geom_bar,
|
|
10295
11238
|
geom_area,
|
|
10296
11239
|
geom_abline,
|
|
@@ -10337,6 +11280,7 @@ export {
|
|
|
10337
11280
|
computeBoxplotStats,
|
|
10338
11281
|
computeBins2d,
|
|
10339
11282
|
computeBins,
|
|
11283
|
+
computeBeeswarm,
|
|
10340
11284
|
colorDistance,
|
|
10341
11285
|
clearCapabilityCache,
|
|
10342
11286
|
calculatePanelLayouts,
|
|
@@ -10358,6 +11302,8 @@ export {
|
|
|
10358
11302
|
annotate,
|
|
10359
11303
|
TerminalCanvas,
|
|
10360
11304
|
StreamingPlot,
|
|
11305
|
+
SPARK_DOTS,
|
|
11306
|
+
SPARK_BARS,
|
|
10361
11307
|
SHAPE_CHARS,
|
|
10362
11308
|
RollingAggregator,
|
|
10363
11309
|
RendererChain,
|
|
@@ -10377,5 +11323,7 @@ export {
|
|
|
10377
11323
|
DEFAULT_BG,
|
|
10378
11324
|
CanvasDiff,
|
|
10379
11325
|
Binner,
|
|
11326
|
+
BRAILLE_DOTS,
|
|
11327
|
+
BRAILLE_BASE,
|
|
10380
11328
|
ANSI_16_COLORS
|
|
10381
11329
|
};
|