@ggterm/core 0.2.11 → 0.2.15

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/index.js CHANGED
@@ -1564,6 +1564,82 @@ function renderGeomFreqpoly(data, _geom, aes, scales, canvas) {
1564
1564
  }
1565
1565
  }
1566
1566
  }
1567
+ function renderGeomDensity(data, geom, aes, scales, canvas) {
1568
+ if (data.length < 2)
1569
+ return;
1570
+ const plotLeft = Math.round(scales.x.range[0]);
1571
+ const plotRight = Math.round(scales.x.range[1]);
1572
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
1573
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
1574
+ const groups = new Map;
1575
+ const groupField = aes.group || aes.color || aes.fill;
1576
+ if (groupField) {
1577
+ for (const row of data) {
1578
+ const key = String(row[groupField] ?? "default");
1579
+ if (!groups.has(key))
1580
+ groups.set(key, []);
1581
+ groups.get(key).push(row);
1582
+ }
1583
+ } else {
1584
+ groups.set("default", data);
1585
+ }
1586
+ let baseline = Math.round(scales.y.map(0));
1587
+ baseline = Math.max(plotTop, Math.min(plotBottom, baseline));
1588
+ const alpha = geom.params.alpha ?? 0.3;
1589
+ const fillChar = "░";
1590
+ for (const [groupKey, groupData] of groups) {
1591
+ if (groupData.length < 2)
1592
+ continue;
1593
+ const sorted = [...groupData].sort((a, b) => {
1594
+ const ax = Number(a.x) || 0;
1595
+ const bx = Number(b.x) || 0;
1596
+ return ax - bx;
1597
+ });
1598
+ let baseColor = scales.color?.map(groupKey) ?? DEFAULT_POINT_COLOR;
1599
+ const fillColor = {
1600
+ r: Math.round(baseColor.r * alpha),
1601
+ g: Math.round(baseColor.g * alpha),
1602
+ b: Math.round(baseColor.b * alpha),
1603
+ a: baseColor.a
1604
+ };
1605
+ for (let i = 0;i < sorted.length - 1; i++) {
1606
+ const row1 = sorted[i];
1607
+ const row2 = sorted[i + 1];
1608
+ const density1 = Number(row1.y ?? row1.density) || 0;
1609
+ const density2 = Number(row2.y ?? row2.density) || 0;
1610
+ const x1 = Math.round(scales.x.map(row1.x));
1611
+ const y1 = Math.round(scales.y.map(density1));
1612
+ const x2 = Math.round(scales.x.map(row2.x));
1613
+ const y2 = Math.round(scales.y.map(density2));
1614
+ for (let x = x1;x <= x2; x++) {
1615
+ if (x < plotLeft || x > plotRight)
1616
+ continue;
1617
+ const t = x2 !== x1 ? (x - x1) / (x2 - x1) : 0;
1618
+ const yInterp = Math.round(y1 + (y2 - y1) * t);
1619
+ const top = Math.min(yInterp, baseline);
1620
+ const bottom = Math.max(yInterp, baseline);
1621
+ for (let y = top;y <= bottom; y++) {
1622
+ if (y >= plotTop && y <= plotBottom) {
1623
+ canvas.drawChar(x, y, fillChar, fillColor);
1624
+ }
1625
+ }
1626
+ }
1627
+ }
1628
+ for (let i = 0;i < sorted.length - 1; i++) {
1629
+ const row1 = sorted[i];
1630
+ const row2 = sorted[i + 1];
1631
+ const density1 = Number(row1.y ?? row1.density) || 0;
1632
+ const density2 = Number(row2.y ?? row2.density) || 0;
1633
+ const x1 = Math.round(scales.x.map(row1.x));
1634
+ const y1 = Math.round(scales.y.map(density1));
1635
+ const x2 = Math.round(scales.x.map(row2.x));
1636
+ const y2 = Math.round(scales.y.map(density2));
1637
+ if (x1 >= plotLeft && x1 <= plotRight && x2 >= plotLeft && x2 <= plotRight && y1 >= plotTop && y1 <= plotBottom && y2 >= plotTop && y2 <= plotBottom) {
1638
+ drawLine(canvas, x1, y1, x2, y2, baseColor);
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1567
1643
  function renderGeomBoxplot(data, geom, _aes, scales, canvas) {
1568
1644
  const widthFactor = geom.params.width ?? 0.75;
1569
1645
  let boxWidth;
@@ -2208,6 +2284,78 @@ function renderGeomLinerange(data, geom, aes, scales, canvas) {
2208
2284
  }
2209
2285
  }
2210
2286
  }
2287
+ function renderGeomCrossbar(data, geom, aes, scales, canvas) {
2288
+ const width = geom.params.width ?? 0.5;
2289
+ const fatten = geom.params.fatten ?? 2.5;
2290
+ const plotLeft = Math.round(scales.x.range[0]);
2291
+ const plotRight = Math.round(scales.x.range[1]);
2292
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2293
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2294
+ const halfWidth = Math.max(2, Math.round(width * 4));
2295
+ for (const row of data) {
2296
+ const xVal = row[aes.x];
2297
+ const yVal = row[aes.y];
2298
+ const ymin = row["ymin"];
2299
+ const ymax = row["ymax"];
2300
+ if (xVal === null || xVal === undefined || ymin === undefined || ymax === undefined) {
2301
+ continue;
2302
+ }
2303
+ const cx = Math.round(scales.x.map(xVal));
2304
+ const cy = yVal !== null && yVal !== undefined ? Math.round(scales.y.map(yVal)) : null;
2305
+ const cyMin = Math.round(scales.y.map(ymin));
2306
+ const cyMax = Math.round(scales.y.map(ymax));
2307
+ const color = getPointColor(row, aes, scales.color);
2308
+ const top = Math.min(cyMin, cyMax);
2309
+ const bottom = Math.max(cyMin, cyMax);
2310
+ const left = cx - halfWidth;
2311
+ const right = cx + halfWidth;
2312
+ for (let y = top;y <= bottom; y++) {
2313
+ if (y >= plotTop && y <= plotBottom) {
2314
+ if (left >= plotLeft && left <= plotRight) {
2315
+ canvas.drawChar(left, y, "│", color);
2316
+ }
2317
+ if (right >= plotLeft && right <= plotRight) {
2318
+ canvas.drawChar(right, y, "│", color);
2319
+ }
2320
+ }
2321
+ }
2322
+ for (let x = left;x <= right; x++) {
2323
+ if (x >= plotLeft && x <= plotRight) {
2324
+ if (top >= plotTop && top <= plotBottom) {
2325
+ canvas.drawChar(x, top, "─", color);
2326
+ }
2327
+ if (bottom >= plotTop && bottom <= plotBottom) {
2328
+ canvas.drawChar(x, bottom, "─", color);
2329
+ }
2330
+ }
2331
+ }
2332
+ if (left >= plotLeft && left <= plotRight) {
2333
+ if (top >= plotTop && top <= plotBottom)
2334
+ canvas.drawChar(left, top, "┌", color);
2335
+ if (bottom >= plotTop && bottom <= plotBottom)
2336
+ canvas.drawChar(left, bottom, "└", color);
2337
+ }
2338
+ if (right >= plotLeft && right <= plotRight) {
2339
+ if (top >= plotTop && top <= plotBottom)
2340
+ canvas.drawChar(right, top, "┐", color);
2341
+ if (bottom >= plotTop && bottom <= plotBottom)
2342
+ canvas.drawChar(right, bottom, "┘", color);
2343
+ }
2344
+ if (cy !== null && cy >= plotTop && cy <= plotBottom) {
2345
+ const fattenLines = Math.max(1, Math.round(fatten / 2));
2346
+ for (let dy = -fattenLines + 1;dy < fattenLines; dy++) {
2347
+ const lineY = cy + dy;
2348
+ if (lineY >= plotTop && lineY <= plotBottom && lineY >= top && lineY <= bottom) {
2349
+ for (let x = left + 1;x < right; x++) {
2350
+ if (x >= plotLeft && x <= plotRight) {
2351
+ canvas.drawChar(x, lineY, "━", color);
2352
+ }
2353
+ }
2354
+ }
2355
+ }
2356
+ }
2357
+ }
2358
+ }
2211
2359
  function renderGeomPointrange(data, geom, aes, scales, canvas) {
2212
2360
  renderGeomLinerange(data, geom, aes, scales, canvas);
2213
2361
  const plotLeft = Math.round(scales.x.range[0]);
@@ -2287,78 +2435,1205 @@ function renderGeomSmooth(data, geom, _aes, scales, canvas) {
2287
2435
  drawLine(canvas, x1, y1, x2, y2, baseColor);
2288
2436
  }
2289
2437
  }
2290
- function parseColor(color) {
2291
- if (color.startsWith("#")) {
2292
- const hex = color.slice(1);
2293
- if (hex.length === 3) {
2438
+ function parseColor(color) {
2439
+ if (color.startsWith("#")) {
2440
+ const hex = color.slice(1);
2441
+ if (hex.length === 3) {
2442
+ return {
2443
+ r: parseInt(hex[0] + hex[0], 16),
2444
+ g: parseInt(hex[1] + hex[1], 16),
2445
+ b: parseInt(hex[2] + hex[2], 16),
2446
+ a: 1
2447
+ };
2448
+ } else if (hex.length === 6) {
2449
+ return {
2450
+ r: parseInt(hex.slice(0, 2), 16),
2451
+ g: parseInt(hex.slice(2, 4), 16),
2452
+ b: parseInt(hex.slice(4, 6), 16),
2453
+ a: 1
2454
+ };
2455
+ }
2456
+ }
2457
+ return { r: 128, g: 128, b: 128, a: 1 };
2458
+ }
2459
+ function renderGeomBeeswarm(data, geom, aes, scales, canvas) {
2460
+ const alpha = geom.params.alpha ?? 1;
2461
+ const fixedColor = geom.params.color;
2462
+ const shape = getPointShape(geom.params.shape);
2463
+ const plotLeft = Math.round(scales.x.range[0]);
2464
+ const plotRight = Math.round(scales.x.range[1]);
2465
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2466
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2467
+ const defaultColors = [
2468
+ { r: 79, g: 169, b: 238, a: 1 },
2469
+ { r: 238, g: 136, b: 102, a: 1 },
2470
+ { r: 102, g: 204, b: 153, a: 1 },
2471
+ { r: 204, g: 102, b: 204, a: 1 },
2472
+ { r: 255, g: 200, b: 87, a: 1 },
2473
+ { r: 138, g: 201, b: 222, a: 1 },
2474
+ { r: 255, g: 153, b: 153, a: 1 },
2475
+ { r: 170, g: 170, b: 170, a: 1 }
2476
+ ];
2477
+ const categories = new Set;
2478
+ for (const row of data) {
2479
+ categories.add(String(row.xOriginal ?? row[aes.x] ?? "default"));
2480
+ }
2481
+ const categoryList = [...categories];
2482
+ for (const row of data) {
2483
+ const xVal = row.x;
2484
+ const yVal = row.y;
2485
+ if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
2486
+ continue;
2487
+ }
2488
+ const numGroups = categoryList.length;
2489
+ const xRange = plotRight - plotLeft;
2490
+ const xNormalized = (Number(xVal) + 0.5) / numGroups;
2491
+ const cx = Math.round(plotLeft + xNormalized * xRange);
2492
+ const cy = Math.round(scales.y.map(yVal));
2493
+ let color;
2494
+ if (fixedColor) {
2495
+ color = parseColorToRgba(fixedColor);
2496
+ } else if (scales.color && aes.color) {
2497
+ color = getPointColor(row, aes, scales.color);
2498
+ } else {
2499
+ const category = String(row.xOriginal ?? row[aes.x] ?? "default");
2500
+ const categoryIdx = categoryList.indexOf(category);
2501
+ color = defaultColors[categoryIdx % defaultColors.length];
2502
+ }
2503
+ if (alpha < 1) {
2504
+ color = { ...color, a: alpha };
2505
+ }
2506
+ if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
2507
+ canvas.drawChar(cx, cy, shape, color);
2508
+ }
2509
+ }
2510
+ }
2511
+ function renderGeomDumbbell(data, geom, aes, scales, canvas) {
2512
+ const lineColor = parseColorToRgba(geom.params.lineColor ?? "#666666");
2513
+ const alpha = geom.params.alpha ?? 1;
2514
+ const shape = getPointShape(geom.params.shape);
2515
+ const plotLeft = Math.round(scales.x.range[0]);
2516
+ const plotRight = Math.round(scales.x.range[1]);
2517
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2518
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2519
+ const defaultColors = [
2520
+ { r: 79, g: 169, b: 238, a: 1 },
2521
+ { r: 238, g: 136, b: 102, a: 1 },
2522
+ { r: 102, g: 204, b: 153, a: 1 },
2523
+ { r: 204, g: 102, b: 204, a: 1 }
2524
+ ];
2525
+ for (let i = 0;i < data.length; i++) {
2526
+ const row = data[i];
2527
+ const xVal = row[aes.x];
2528
+ const xendVal = row["xend"] ?? row[aes.x];
2529
+ const yVal = row[aes.y];
2530
+ if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
2531
+ continue;
2532
+ }
2533
+ const x1 = Math.round(scales.x.map(xVal));
2534
+ const x2 = Math.round(scales.x.map(xendVal));
2535
+ const cy = Math.round(scales.y.map(yVal));
2536
+ let startColor;
2537
+ let endColor;
2538
+ if (geom.params.color) {
2539
+ startColor = parseColorToRgba(geom.params.color);
2540
+ } else if (scales.color && aes.color) {
2541
+ startColor = getPointColor(row, aes, scales.color);
2542
+ } else {
2543
+ startColor = defaultColors[0];
2544
+ }
2545
+ if (geom.params.colorEnd) {
2546
+ endColor = parseColorToRgba(geom.params.colorEnd);
2547
+ } else {
2548
+ endColor = geom.params.color ? startColor : defaultColors[1];
2549
+ }
2550
+ if (alpha < 1) {
2551
+ startColor = { ...startColor, a: alpha };
2552
+ endColor = { ...endColor, a: alpha };
2553
+ }
2554
+ if (cy >= plotTop && cy <= plotBottom) {
2555
+ const left = Math.max(plotLeft, Math.min(x1, x2));
2556
+ const right = Math.min(plotRight, Math.max(x1, x2));
2557
+ for (let x = left;x <= right; x++) {
2558
+ canvas.drawChar(x, cy, "─", lineColor);
2559
+ }
2560
+ }
2561
+ if (x1 >= plotLeft && x1 <= plotRight && cy >= plotTop && cy <= plotBottom) {
2562
+ canvas.drawChar(x1, cy, shape, startColor);
2563
+ }
2564
+ if (x2 >= plotLeft && x2 <= plotRight && cy >= plotTop && cy <= plotBottom) {
2565
+ canvas.drawChar(x2, cy, shape, endColor);
2566
+ }
2567
+ }
2568
+ }
2569
+ function renderGeomLollipop(data, geom, aes, scales, canvas) {
2570
+ const alpha = geom.params.alpha ?? 1;
2571
+ const baseline = geom.params.baseline ?? 0;
2572
+ const direction = geom.params.direction ?? "vertical";
2573
+ const shape = getPointShape(geom.params.shape);
2574
+ const plotLeft = Math.round(scales.x.range[0]);
2575
+ const plotRight = Math.round(scales.x.range[1]);
2576
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2577
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2578
+ const defaultColors = [
2579
+ { r: 79, g: 169, b: 238, a: 1 },
2580
+ { r: 238, g: 136, b: 102, a: 1 },
2581
+ { r: 102, g: 204, b: 153, a: 1 },
2582
+ { r: 204, g: 102, b: 204, a: 1 },
2583
+ { r: 255, g: 200, b: 87, a: 1 },
2584
+ { r: 138, g: 201, b: 222, a: 1 }
2585
+ ];
2586
+ const xValues = [...new Set(data.map((row) => row[aes.x]))];
2587
+ for (let i = 0;i < data.length; i++) {
2588
+ const row = data[i];
2589
+ const xVal = row[aes.x];
2590
+ const yVal = row[aes.y];
2591
+ if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
2592
+ continue;
2593
+ }
2594
+ const cx = Math.round(scales.x.map(xVal));
2595
+ const cy = Math.round(scales.y.map(yVal));
2596
+ let color;
2597
+ if (geom.params.color) {
2598
+ color = parseColorToRgba(geom.params.color);
2599
+ } else if (scales.color && aes.color) {
2600
+ color = getPointColor(row, aes, scales.color);
2601
+ } else {
2602
+ const categoryIdx = xValues.indexOf(xVal);
2603
+ color = defaultColors[categoryIdx % defaultColors.length];
2604
+ }
2605
+ let lineColor;
2606
+ if (geom.params.lineColor) {
2607
+ lineColor = parseColorToRgba(geom.params.lineColor);
2608
+ } else {
2609
+ lineColor = {
2610
+ r: Math.round(color.r * 0.7),
2611
+ g: Math.round(color.g * 0.7),
2612
+ b: Math.round(color.b * 0.7),
2613
+ a: color.a
2614
+ };
2615
+ }
2616
+ if (alpha < 1) {
2617
+ color = { ...color, a: alpha };
2618
+ lineColor = { ...lineColor, a: alpha };
2619
+ }
2620
+ if (direction === "vertical") {
2621
+ let baselineY = Math.round(scales.y.map(baseline));
2622
+ baselineY = Math.max(plotTop, Math.min(plotBottom, baselineY));
2623
+ if (cx >= plotLeft && cx <= plotRight) {
2624
+ const top = Math.min(cy, baselineY);
2625
+ const bottom = Math.max(cy, baselineY);
2626
+ for (let y = top;y <= bottom; y++) {
2627
+ if (y >= plotTop && y <= plotBottom) {
2628
+ canvas.drawChar(cx, y, "│", lineColor);
2629
+ }
2630
+ }
2631
+ }
2632
+ if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
2633
+ canvas.drawChar(cx, cy, shape, color);
2634
+ }
2635
+ } else {
2636
+ let baselineX = Math.round(scales.x.map(baseline));
2637
+ baselineX = Math.max(plotLeft, Math.min(plotRight, baselineX));
2638
+ if (cy >= plotTop && cy <= plotBottom) {
2639
+ const left = Math.min(cx, baselineX);
2640
+ const right = Math.max(cx, baselineX);
2641
+ for (let x = left;x <= right; x++) {
2642
+ if (x >= plotLeft && x <= plotRight) {
2643
+ canvas.drawChar(x, cy, "─", lineColor);
2644
+ }
2645
+ }
2646
+ }
2647
+ if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
2648
+ canvas.drawChar(cx, cy, shape, color);
2649
+ }
2650
+ }
2651
+ }
2652
+ }
2653
+ function renderGeomWaffle(data, geom, aes, scales, canvas) {
2654
+ const rows = geom.params.rows ?? 10;
2655
+ const cols = geom.params.cols ?? 10;
2656
+ const fillChar = geom.params.fill_char ?? "█";
2657
+ const emptyChar = geom.params.empty_char ?? "░";
2658
+ const showLegend = geom.params.show_legend ?? true;
2659
+ const flip = geom.params.flip ?? false;
2660
+ const gap = geom.params.gap ?? 0;
2661
+ const plotLeft = Math.round(scales.x.range[0]);
2662
+ const plotRight = Math.round(scales.x.range[1]);
2663
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2664
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2665
+ const defaultColors = [
2666
+ { r: 79, g: 169, b: 238, a: 1 },
2667
+ { r: 238, g: 136, b: 102, a: 1 },
2668
+ { r: 102, g: 204, b: 153, a: 1 },
2669
+ { r: 204, g: 102, b: 204, a: 1 },
2670
+ { r: 255, g: 200, b: 87, a: 1 },
2671
+ { r: 138, g: 201, b: 222, a: 1 },
2672
+ { r: 255, g: 153, b: 153, a: 1 },
2673
+ { r: 170, g: 170, b: 170, a: 1 }
2674
+ ];
2675
+ const fillField = aes.fill || aes.color || "category";
2676
+ const valueField = aes.y || "value";
2677
+ const categories = new Map;
2678
+ let totalValue = 0;
2679
+ for (const row of data) {
2680
+ const cat = String(row[fillField] ?? "default");
2681
+ const val = Number(row[valueField]) || 1;
2682
+ categories.set(cat, (categories.get(cat) ?? 0) + val);
2683
+ totalValue += val;
2684
+ }
2685
+ const cellsPerCategory = [];
2686
+ const categoryList = [...categories.keys()];
2687
+ let cellsAssigned = 0;
2688
+ for (let i = 0;i < categoryList.length; i++) {
2689
+ const cat = categoryList[i];
2690
+ const val = categories.get(cat);
2691
+ const proportion = val / totalValue;
2692
+ const cells = Math.round(proportion * rows * cols);
2693
+ const color = scales.color?.map(cat) ?? defaultColors[i % defaultColors.length];
2694
+ cellsPerCategory.push({ category: cat, cells, color });
2695
+ cellsAssigned += cells;
2696
+ }
2697
+ if (cellsAssigned < rows * cols && cellsPerCategory.length > 0) {
2698
+ cellsPerCategory[0].cells += rows * cols - cellsAssigned;
2699
+ }
2700
+ const grid = [];
2701
+ for (const { cells, color } of cellsPerCategory) {
2702
+ for (let i = 0;i < cells; i++) {
2703
+ grid.push({ char: fillChar, color });
2704
+ }
2705
+ }
2706
+ const emptyColor = { r: 80, g: 80, b: 80, a: 0.3 };
2707
+ while (grid.length < rows * cols) {
2708
+ grid.push({ char: emptyChar, color: emptyColor });
2709
+ }
2710
+ const availableWidth = plotRight - plotLeft - (showLegend ? 15 : 0);
2711
+ const availableHeight = plotBottom - plotTop;
2712
+ const cellWidth = Math.max(1, Math.floor(availableWidth / cols)) + gap;
2713
+ const cellHeight = Math.max(1, Math.floor(availableHeight / rows)) + gap;
2714
+ for (let row = 0;row < rows; row++) {
2715
+ for (let col = 0;col < cols; col++) {
2716
+ let idx;
2717
+ if (flip) {
2718
+ idx = row * cols + col;
2719
+ } else {
2720
+ idx = col * rows + (rows - 1 - row);
2721
+ }
2722
+ if (idx >= grid.length)
2723
+ continue;
2724
+ const cell = grid[idx];
2725
+ const x = plotLeft + col * cellWidth;
2726
+ const y = plotTop + row * cellHeight;
2727
+ if (x >= plotLeft && x < plotRight - (showLegend ? 15 : 0) && y >= plotTop && y <= plotBottom) {
2728
+ canvas.drawChar(x, y, cell.char, cell.color);
2729
+ }
2730
+ }
2731
+ }
2732
+ if (showLegend) {
2733
+ const legendX = plotRight - 12;
2734
+ let legendY = plotTop;
2735
+ for (let i = 0;i < cellsPerCategory.length && legendY < plotBottom; i++) {
2736
+ const { category, cells, color } = cellsPerCategory[i];
2737
+ const pct = Math.round(cells / (rows * cols) * 100);
2738
+ const label = `${category.slice(0, 6)} ${pct}%`;
2739
+ canvas.drawChar(legendX, legendY, "█", color);
2740
+ canvas.drawString(legendX + 2, legendY, label, { r: 180, g: 180, b: 180, a: 1 });
2741
+ legendY += 2;
2742
+ }
2743
+ }
2744
+ }
2745
+ function renderGeomSparkline(data, geom, aes, scales, canvas) {
2746
+ const sparkType = geom.params.sparkType ?? "bar";
2747
+ const width = geom.params.width ?? 20;
2748
+ const showMinmax = geom.params.show_minmax ?? false;
2749
+ const normalize = geom.params.normalize ?? true;
2750
+ const minColor = parseColorToRgba(geom.params.min_color ?? "#e74c3c");
2751
+ const maxColor = parseColorToRgba(geom.params.max_color ?? "#2ecc71");
2752
+ const plotLeft = Math.round(scales.x.range[0]);
2753
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2754
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2755
+ const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
2756
+ const defaultColor = { r: 79, g: 169, b: 238, a: 1 };
2757
+ const groupField = aes.group || aes.color;
2758
+ const groups = new Map;
2759
+ if (groupField) {
2760
+ for (const row of data) {
2761
+ const key = String(row[groupField] ?? "default");
2762
+ if (!groups.has(key))
2763
+ groups.set(key, []);
2764
+ groups.get(key).push(row);
2765
+ }
2766
+ } else {
2767
+ groups.set("default", [...data]);
2768
+ }
2769
+ let currentY = plotTop;
2770
+ for (const [groupKey, groupData] of groups) {
2771
+ const sorted = aes.x ? [...groupData].sort((a, b) => Number(a[aes.x]) - Number(b[aes.x])) : groupData;
2772
+ const values = sorted.map((row) => Number(row[aes.y]) || 0);
2773
+ if (values.length === 0)
2774
+ continue;
2775
+ const minVal = Math.min(...values);
2776
+ const maxVal = Math.max(...values);
2777
+ const minIdx = values.indexOf(minVal);
2778
+ const maxIdx = values.indexOf(maxVal);
2779
+ const range = maxVal - minVal || 1;
2780
+ const color = scales.color?.map(groupKey) ?? defaultColor;
2781
+ const sparkValues = [];
2782
+ if (values.length <= width) {
2783
+ sparkValues.push(...values);
2784
+ } else {
2785
+ for (let i = 0;i < width; i++) {
2786
+ const idx = Math.floor(i * values.length / width);
2787
+ sparkValues.push(values[idx]);
2788
+ }
2789
+ }
2790
+ for (let i = 0;i < sparkValues.length; i++) {
2791
+ const val = sparkValues[i];
2792
+ const normalized = normalize ? (val - minVal) / range : val / (maxVal || 1);
2793
+ const charIdx = Math.min(7, Math.max(0, Math.floor(normalized * 8)));
2794
+ const char = sparkType === "dot" ? "•" : SPARK_CHARS[charIdx];
2795
+ const x = plotLeft + i;
2796
+ const y = currentY;
2797
+ let pointColor = color;
2798
+ if (showMinmax) {
2799
+ const origIdx = Math.floor(i * values.length / sparkValues.length);
2800
+ if (origIdx === minIdx)
2801
+ pointColor = minColor;
2802
+ else if (origIdx === maxIdx)
2803
+ pointColor = maxColor;
2804
+ }
2805
+ if (x < plotLeft + width && y >= plotTop && y <= plotBottom) {
2806
+ canvas.drawChar(x, y, char, pointColor);
2807
+ }
2808
+ }
2809
+ if (groupField && groups.size > 1) {
2810
+ const labelX = plotLeft + width + 1;
2811
+ canvas.drawString(labelX, currentY, groupKey.slice(0, 8), { r: 180, g: 180, b: 180, a: 1 });
2812
+ }
2813
+ currentY += 2;
2814
+ }
2815
+ }
2816
+ function renderGeomBullet(data, geom, aes, scales, canvas) {
2817
+ const width = geom.params.width ?? 40;
2818
+ const targetChar = geom.params.target_char ?? "│";
2819
+ const barChar = geom.params.bar_char ?? "█";
2820
+ const rangeChars = geom.params.range_chars ?? ["░", "▒", "▓"];
2821
+ const showValues = geom.params.show_values ?? true;
2822
+ const targetColor = parseColorToRgba(geom.params.target_color ?? "#e74c3c");
2823
+ const plotLeft = Math.round(scales.x.range[0]);
2824
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2825
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2826
+ const defaultColors = [
2827
+ { r: 79, g: 169, b: 238, a: 1 },
2828
+ { r: 238, g: 136, b: 102, a: 1 },
2829
+ { r: 102, g: 204, b: 153, a: 1 }
2830
+ ];
2831
+ const rangeColors = [
2832
+ { r: 60, g: 60, b: 60, a: 1 },
2833
+ { r: 90, g: 90, b: 90, a: 1 },
2834
+ { r: 120, g: 120, b: 120, a: 1 }
2835
+ ];
2836
+ let currentY = plotTop;
2837
+ for (let i = 0;i < data.length; i++) {
2838
+ const row = data[i];
2839
+ const label = aes.x ? String(row[aes.x]).slice(0, 10) : `Item ${i + 1}`;
2840
+ const value = Number(row[aes.y]) || 0;
2841
+ const target = Number(row["target"]) || null;
2842
+ const maxValue = Number(row["max"]) || Math.max(value, target || 0) * 1.2;
2843
+ const ranges = row["ranges"] ?? [maxValue * 0.6, maxValue * 0.8, maxValue];
2844
+ const color = scales.color?.map(label) ?? defaultColors[i % defaultColors.length];
2845
+ const labelWidth = 12;
2846
+ canvas.drawString(plotLeft, currentY, label.padEnd(labelWidth), { r: 180, g: 180, b: 180, a: 1 });
2847
+ const barStart = plotLeft + labelWidth;
2848
+ const barWidth = Math.min(width, scales.x.range[1] - barStart - (showValues ? 8 : 0));
2849
+ for (let r = ranges.length - 1;r >= 0; r--) {
2850
+ const rangeWidth = Math.round(ranges[r] / maxValue * barWidth);
2851
+ for (let x = 0;x < rangeWidth; x++) {
2852
+ if (barStart + x <= scales.x.range[1]) {
2853
+ canvas.drawChar(barStart + x, currentY, rangeChars[r], rangeColors[r]);
2854
+ }
2855
+ }
2856
+ }
2857
+ const valueWidth = Math.round(value / maxValue * barWidth);
2858
+ for (let x = 0;x < valueWidth; x++) {
2859
+ if (barStart + x <= scales.x.range[1]) {
2860
+ canvas.drawChar(barStart + x, currentY, barChar, color);
2861
+ }
2862
+ }
2863
+ if (target !== null) {
2864
+ const targetX = barStart + Math.round(target / maxValue * barWidth);
2865
+ if (targetX >= barStart && targetX <= barStart + barWidth) {
2866
+ canvas.drawChar(targetX, currentY, targetChar, targetColor);
2867
+ }
2868
+ }
2869
+ if (showValues) {
2870
+ const valueStr = value.toFixed(0);
2871
+ canvas.drawString(barStart + barWidth + 2, currentY, valueStr, { r: 180, g: 180, b: 180, a: 1 });
2872
+ }
2873
+ currentY += 2;
2874
+ if (currentY > plotBottom)
2875
+ break;
2876
+ }
2877
+ }
2878
+ function renderGeomBraille(data, geom, aes, scales, canvas) {
2879
+ const brailleType = geom.params.brailleType ?? "point";
2880
+ const fill = geom.params.fill ?? false;
2881
+ const alpha = geom.params.alpha ?? 1;
2882
+ const BRAILLE_BASE = 10240;
2883
+ const DOTS = [
2884
+ [1, 2, 4, 64],
2885
+ [8, 16, 32, 128]
2886
+ ];
2887
+ const plotLeft = Math.round(scales.x.range[0]);
2888
+ const plotRight = Math.round(scales.x.range[1]);
2889
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2890
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
2891
+ const plotWidth = plotRight - plotLeft;
2892
+ const plotHeight = plotBottom - plotTop;
2893
+ const brailleWidth = plotWidth;
2894
+ const brailleHeight = plotHeight;
2895
+ const buffer = [];
2896
+ for (let y = 0;y < brailleHeight; y++) {
2897
+ buffer[y] = new Array(brailleWidth).fill(0);
2898
+ }
2899
+ const defaultColor = { r: 79, g: 169, b: 238, a: 1 };
2900
+ const color = geom.params.color ? parseColorToRgba(geom.params.color) : defaultColor;
2901
+ const finalColor = alpha < 1 ? { ...color, a: alpha } : color;
2902
+ const sorted = aes.x ? [...data].sort((a, b) => Number(a[aes.x]) - Number(b[aes.x])) : data;
2903
+ const setDot = (canvasX, canvasY) => {
2904
+ const subX = (canvasX - plotLeft) * 2;
2905
+ const subY = (canvasY - plotTop) * 4;
2906
+ const cellX = Math.floor(subX / 2);
2907
+ const cellY = Math.floor(subY / 4);
2908
+ const dotCol = subX % 2;
2909
+ const dotRow = subY % 4;
2910
+ if (cellX >= 0 && cellX < brailleWidth && cellY >= 0 && cellY < brailleHeight) {
2911
+ if (dotCol >= 0 && dotCol < 2 && dotRow >= 0 && dotRow < 4) {
2912
+ buffer[cellY][cellX] |= DOTS[dotCol][dotRow];
2913
+ }
2914
+ }
2915
+ };
2916
+ let prevCx = null;
2917
+ let prevCy = null;
2918
+ for (const row of sorted) {
2919
+ const xVal = row[aes.x];
2920
+ const yVal = row[aes.y];
2921
+ if (xVal === null || xVal === undefined || yVal === null || yVal === undefined) {
2922
+ prevCx = null;
2923
+ prevCy = null;
2924
+ continue;
2925
+ }
2926
+ const cx = Math.round(scales.x.map(xVal));
2927
+ const cy = Math.round(scales.y.map(yVal));
2928
+ if (brailleType === "line" && prevCx !== null && prevCy !== null) {
2929
+ const dx = Math.abs(cx - prevCx);
2930
+ const dy = Math.abs(cy - prevCy);
2931
+ const sx = prevCx < cx ? 1 : -1;
2932
+ const sy = prevCy < cy ? 1 : -1;
2933
+ let err = dx - dy;
2934
+ let x = prevCx;
2935
+ let y = prevCy;
2936
+ while (true) {
2937
+ if (x >= plotLeft && x < plotRight && y >= plotTop && y < plotBottom) {
2938
+ setDot(x, y);
2939
+ if (fill) {
2940
+ for (let fy = y;fy < plotBottom; fy++) {
2941
+ setDot(x, fy);
2942
+ }
2943
+ }
2944
+ }
2945
+ if (x === cx && y === cy)
2946
+ break;
2947
+ const e2 = 2 * err;
2948
+ if (e2 > -dy) {
2949
+ err -= dy;
2950
+ x += sx;
2951
+ }
2952
+ if (e2 < dx) {
2953
+ err += dx;
2954
+ y += sy;
2955
+ }
2956
+ }
2957
+ } else {
2958
+ if (cx >= plotLeft && cx < plotRight && cy >= plotTop && cy < plotBottom) {
2959
+ setDot(cx, cy);
2960
+ }
2961
+ }
2962
+ prevCx = cx;
2963
+ prevCy = cy;
2964
+ }
2965
+ for (let y = 0;y < brailleHeight; y++) {
2966
+ for (let x = 0;x < brailleWidth; x++) {
2967
+ if (buffer[y][x] > 0) {
2968
+ const char = String.fromCharCode(BRAILLE_BASE + buffer[y][x]);
2969
+ canvas.drawChar(plotLeft + x, plotTop + y, char, finalColor);
2970
+ }
2971
+ }
2972
+ }
2973
+ }
2974
+ function renderGeomCalendar(data, geom, aes, scales, canvas) {
2975
+ if (data.length === 0)
2976
+ return;
2977
+ const cellChar = geom.params.cell_char ?? "█";
2978
+ const emptyColor = parseColorToRgba(geom.params.empty_color ?? "#161b22");
2979
+ const fillColor = parseColorToRgba(geom.params.fill_color ?? "#39d353");
2980
+ const levels = geom.params.levels ?? 5;
2981
+ const showDays = geom.params.show_days ?? true;
2982
+ const showMonths = geom.params.show_months ?? true;
2983
+ const weekStart = geom.params.week_start ?? 0;
2984
+ const dateField = aes.x;
2985
+ const valueField = aes.fill || aes.y || "value";
2986
+ const entries = [];
2987
+ let minValue = Infinity;
2988
+ let maxValue = -Infinity;
2989
+ for (const row of data) {
2990
+ const dateVal = row[dateField];
2991
+ const val = Number(row[valueField]) || 0;
2992
+ let date;
2993
+ if (dateVal instanceof Date) {
2994
+ date = dateVal;
2995
+ } else if (typeof dateVal === "string" || typeof dateVal === "number") {
2996
+ date = new Date(dateVal);
2997
+ } else {
2998
+ continue;
2999
+ }
3000
+ if (isNaN(date.getTime()))
3001
+ continue;
3002
+ entries.push({ date, value: val });
3003
+ if (val < minValue)
3004
+ minValue = val;
3005
+ if (val > maxValue)
3006
+ maxValue = val;
3007
+ }
3008
+ if (entries.length === 0)
3009
+ return;
3010
+ entries.sort((a, b) => a.date.getTime() - b.date.getTime());
3011
+ const startDate = new Date(entries[0].date);
3012
+ const endDate = new Date(entries[entries.length - 1].date);
3013
+ startDate.setDate(startDate.getDate() - (startDate.getDay() - weekStart + 7) % 7);
3014
+ const valueMap = new Map;
3015
+ for (const entry of entries) {
3016
+ const key = entry.date.toISOString().slice(0, 10);
3017
+ valueMap.set(key, (valueMap.get(key) || 0) + entry.value);
3018
+ }
3019
+ const plotLeft = Math.round(scales.x.range[0]);
3020
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
3021
+ const dayLabels = ["S", "M", "T", "W", "T", "F", "S"];
3022
+ const monthLabels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
3023
+ const msPerDay = 24 * 60 * 60 * 1000;
3024
+ const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / msPerDay) + 7;
3025
+ const numWeeks = Math.ceil(totalDays / 7);
3026
+ const getColor = (value) => {
3027
+ if (value === 0 || maxValue === minValue)
3028
+ return emptyColor;
3029
+ const t = Math.min(1, Math.max(0, (value - minValue) / (maxValue - minValue)));
3030
+ const level = Math.floor(t * (levels - 1));
3031
+ const levelT = level / (levels - 1);
3032
+ return {
3033
+ r: Math.round(emptyColor.r + (fillColor.r - emptyColor.r) * levelT),
3034
+ g: Math.round(emptyColor.g + (fillColor.g - emptyColor.g) * levelT),
3035
+ b: Math.round(emptyColor.b + (fillColor.b - emptyColor.b) * levelT),
3036
+ a: 1
3037
+ };
3038
+ };
3039
+ const xOffset = showDays ? 3 : 0;
3040
+ const yOffset = showMonths ? 2 : 0;
3041
+ if (showDays) {
3042
+ for (let d = 0;d < 7; d++) {
3043
+ const dayIndex = (d + weekStart) % 7;
3044
+ const y = plotTop + yOffset + d;
3045
+ canvas.drawChar(plotLeft, y, dayLabels[dayIndex], { r: 128, g: 128, b: 128, a: 1 });
3046
+ }
3047
+ }
3048
+ let lastMonth = -1;
3049
+ const currentDate = new Date(startDate);
3050
+ for (let week = 0;week < numWeeks; week++) {
3051
+ const x = plotLeft + xOffset + week * 2;
3052
+ if (showMonths && currentDate.getMonth() !== lastMonth) {
3053
+ const monthLabel = monthLabels[currentDate.getMonth()];
3054
+ for (let i = 0;i < monthLabel.length; i++) {
3055
+ canvas.drawChar(x + i, plotTop, monthLabel[i], { r: 128, g: 128, b: 128, a: 1 });
3056
+ }
3057
+ lastMonth = currentDate.getMonth();
3058
+ }
3059
+ for (let day = 0;day < 7; day++) {
3060
+ const dayDate = new Date(currentDate);
3061
+ dayDate.setDate(dayDate.getDate() + day);
3062
+ if (dayDate > endDate)
3063
+ break;
3064
+ const key = dayDate.toISOString().slice(0, 10);
3065
+ const value = valueMap.get(key) || 0;
3066
+ const color = getColor(value);
3067
+ const y = plotTop + yOffset + day;
3068
+ canvas.drawChar(x, y, cellChar, color);
3069
+ }
3070
+ currentDate.setDate(currentDate.getDate() + 7);
3071
+ }
3072
+ }
3073
+ function renderGeomFlame(data, geom, aes, scales, canvas) {
3074
+ if (data.length === 0)
3075
+ return;
3076
+ const style = geom.params.style ?? "flame";
3077
+ const palette = geom.params.palette ?? "warm";
3078
+ const showLabels = geom.params.show_labels ?? true;
3079
+ const minLabelWidth = geom.params.min_label_width ?? 10;
3080
+ const barChar = geom.params.bar_char ?? "█";
3081
+ const plotLeft = Math.round(scales.x.range[0]);
3082
+ const plotRight = Math.round(scales.x.range[1]);
3083
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
3084
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
3085
+ const plotWidth = plotRight - plotLeft;
3086
+ const plotHeight = plotBottom - plotTop;
3087
+ const nameField = aes.x || "name";
3088
+ const valueField = aes.fill || "value";
3089
+ const depthField = aes.y || "depth";
3090
+ const startField = "start";
3091
+ const frames = [];
3092
+ let totalValue = 0;
3093
+ let maxDepth = 0;
3094
+ for (const row of data) {
3095
+ const name = String(row[nameField] || "");
3096
+ const value = Number(row[valueField]) || 0;
3097
+ const depth = Number(row[depthField]) || 0;
3098
+ const start = row[startField] !== undefined ? Number(row[startField]) : -1;
3099
+ frames.push({ name, value, depth, start, width: 0 });
3100
+ if (depth === 0)
3101
+ totalValue += value;
3102
+ if (depth > maxDepth)
3103
+ maxDepth = depth;
3104
+ }
3105
+ if (totalValue === 0)
3106
+ return;
3107
+ for (const frame of frames) {
3108
+ frame.width = frame.value / totalValue * plotWidth;
3109
+ }
3110
+ frames.sort((a, b) => {
3111
+ if (a.depth !== b.depth)
3112
+ return a.depth - b.depth;
3113
+ if (a.start !== b.start)
3114
+ return a.start - b.start;
3115
+ return a.name.localeCompare(b.name);
3116
+ });
3117
+ const depthOffsets = new Array(maxDepth + 1).fill(0);
3118
+ for (const frame of frames) {
3119
+ if (frame.start < 0) {
3120
+ frame.start = depthOffsets[frame.depth];
3121
+ }
3122
+ depthOffsets[frame.depth] = frame.start + frame.width;
3123
+ }
3124
+ const getPaletteColor = (name) => {
3125
+ let hash = 0;
3126
+ for (let i = 0;i < name.length; i++) {
3127
+ hash = (hash << 5) - hash + name.charCodeAt(i);
3128
+ hash = hash & hash;
3129
+ }
3130
+ const hue = Math.abs(hash) % 360;
3131
+ let h;
3132
+ if (palette === "warm") {
3133
+ h = hue % 60 + 0;
3134
+ } else if (palette === "cool") {
3135
+ h = hue % 60 + 180;
3136
+ } else {
3137
+ h = hue % 40 + 10;
3138
+ }
3139
+ const s = 0.7;
3140
+ const l = 0.5;
3141
+ const c = (1 - Math.abs(2 * l - 1)) * s;
3142
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
3143
+ const m = l - c / 2;
3144
+ let r = 0, g = 0, b = 0;
3145
+ if (h < 60) {
3146
+ r = c;
3147
+ g = x;
3148
+ b = 0;
3149
+ } else if (h < 120) {
3150
+ r = x;
3151
+ g = c;
3152
+ b = 0;
3153
+ } else if (h < 180) {
3154
+ r = 0;
3155
+ g = c;
3156
+ b = x;
3157
+ } else if (h < 240) {
3158
+ r = 0;
3159
+ g = x;
3160
+ b = c;
3161
+ } else if (h < 300) {
3162
+ r = x;
3163
+ g = 0;
3164
+ b = c;
3165
+ } else {
3166
+ r = c;
3167
+ g = 0;
3168
+ b = x;
3169
+ }
3170
+ return {
3171
+ r: Math.round((r + m) * 255),
3172
+ g: Math.round((g + m) * 255),
3173
+ b: Math.round((b + m) * 255),
3174
+ a: 1
3175
+ };
3176
+ };
3177
+ const rowHeight = Math.max(1, Math.floor(plotHeight / (maxDepth + 1)));
3178
+ for (const frame of frames) {
3179
+ const x1 = plotLeft + Math.round(frame.start);
3180
+ const x2 = plotLeft + Math.round(frame.start + frame.width);
3181
+ const width = x2 - x1;
3182
+ if (width < 1)
3183
+ continue;
3184
+ let y;
3185
+ if (style === "icicle") {
3186
+ y = plotTop + frame.depth * rowHeight;
3187
+ } else {
3188
+ y = plotBottom - (frame.depth + 1) * rowHeight;
3189
+ }
3190
+ const color = getPaletteColor(frame.name);
3191
+ for (let row = 0;row < rowHeight; row++) {
3192
+ for (let col = x1;col < x2; col++) {
3193
+ if (col >= plotLeft && col < plotRight && y + row >= plotTop && y + row < plotBottom) {
3194
+ canvas.drawChar(col, y + row, barChar, color);
3195
+ }
3196
+ }
3197
+ }
3198
+ if (showLabels && width >= minLabelWidth) {
3199
+ const label = frame.name.slice(0, width - 2);
3200
+ const labelX = x1 + 1;
3201
+ const labelY = y + Math.floor(rowHeight / 2);
3202
+ const textColor = { r: 255, g: 255, b: 255, a: 1 };
3203
+ for (let i = 0;i < label.length; i++) {
3204
+ if (labelX + i < x2 - 1) {
3205
+ canvas.drawChar(labelX + i, labelY, label[i], textColor);
3206
+ }
3207
+ }
3208
+ }
3209
+ }
3210
+ }
3211
+ function renderGeomCorrmat(data, geom, aes, scales, canvas) {
3212
+ if (data.length === 0)
3213
+ return;
3214
+ const showValues = geom.params.show_values ?? true;
3215
+ const decimals = geom.params.decimals ?? 2;
3216
+ const positiveColor = parseColorToRgba(geom.params.positive_color ?? "#2166ac");
3217
+ const negativeColor = parseColorToRgba(geom.params.negative_color ?? "#b2182b");
3218
+ const neutralColor = parseColorToRgba(geom.params.neutral_color ?? "#f7f7f7");
3219
+ const lowerTriangle = geom.params.lower_triangle ?? false;
3220
+ const upperTriangle = geom.params.upper_triangle ?? false;
3221
+ const plotLeft = Math.round(scales.x.range[0]);
3222
+ const plotRight = Math.round(scales.x.range[1]);
3223
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
3224
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
3225
+ const xField = aes.x || "var1";
3226
+ const yField = aes.y || "var2";
3227
+ const valueField = aes.fill || "correlation";
3228
+ const xVars = new Set;
3229
+ const yVars = new Set;
3230
+ const corrMap = new Map;
3231
+ for (const row of data) {
3232
+ const x = String(row[xField] || "");
3233
+ const y = String(row[yField] || "");
3234
+ const r = Number(row[valueField]) || 0;
3235
+ xVars.add(x);
3236
+ yVars.add(y);
3237
+ corrMap.set(`${x}|${y}`, r);
3238
+ }
3239
+ const xList = [...xVars];
3240
+ const yList = [...yVars];
3241
+ const numX = xList.length;
3242
+ const numY = yList.length;
3243
+ if (numX === 0 || numY === 0)
3244
+ return;
3245
+ const cellWidth = Math.max(4, Math.floor((plotRight - plotLeft) / numX));
3246
+ const cellHeight = Math.max(2, Math.floor((plotBottom - plotTop) / numY));
3247
+ const getColor = (r) => {
3248
+ const t = (r + 1) / 2;
3249
+ if (t < 0.5) {
3250
+ const s = t * 2;
2294
3251
  return {
2295
- r: parseInt(hex[0] + hex[0], 16),
2296
- g: parseInt(hex[1] + hex[1], 16),
2297
- b: parseInt(hex[2] + hex[2], 16),
3252
+ r: Math.round(negativeColor.r + (neutralColor.r - negativeColor.r) * s),
3253
+ g: Math.round(negativeColor.g + (neutralColor.g - negativeColor.g) * s),
3254
+ b: Math.round(negativeColor.b + (neutralColor.b - negativeColor.b) * s),
2298
3255
  a: 1
2299
3256
  };
2300
- } else if (hex.length === 6) {
3257
+ } else {
3258
+ const s = (t - 0.5) * 2;
2301
3259
  return {
2302
- r: parseInt(hex.slice(0, 2), 16),
2303
- g: parseInt(hex.slice(2, 4), 16),
2304
- b: parseInt(hex.slice(4, 6), 16),
3260
+ r: Math.round(neutralColor.r + (positiveColor.r - neutralColor.r) * s),
3261
+ g: Math.round(neutralColor.g + (positiveColor.g - neutralColor.g) * s),
3262
+ b: Math.round(neutralColor.b + (positiveColor.b - neutralColor.b) * s),
2305
3263
  a: 1
2306
3264
  };
2307
3265
  }
3266
+ };
3267
+ for (let i = 0;i < numX; i++) {
3268
+ for (let j = 0;j < numY; j++) {
3269
+ if (lowerTriangle && i < j)
3270
+ continue;
3271
+ if (upperTriangle && i > j)
3272
+ continue;
3273
+ const x = xList[i];
3274
+ const y = yList[j];
3275
+ const key = `${x}|${y}`;
3276
+ const r = corrMap.get(key) ?? corrMap.get(`${y}|${x}`) ?? (i === j ? 1 : 0);
3277
+ const color = getColor(r);
3278
+ const cellX = plotLeft + i * cellWidth;
3279
+ const cellY = plotTop + j * cellHeight;
3280
+ for (let cy = 0;cy < cellHeight; cy++) {
3281
+ for (let cx = 0;cx < cellWidth; cx++) {
3282
+ if (cellX + cx < plotRight && cellY + cy < plotBottom) {
3283
+ canvas.drawChar(cellX + cx, cellY + cy, "█", color);
3284
+ }
3285
+ }
3286
+ }
3287
+ if (showValues && cellWidth >= 4) {
3288
+ const valueStr = r.toFixed(decimals);
3289
+ const textX = cellX + Math.floor((cellWidth - valueStr.length) / 2);
3290
+ const textY = cellY + Math.floor(cellHeight / 2);
3291
+ const brightness = (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
3292
+ const textColor = brightness > 128 ? { r: 0, g: 0, b: 0, a: 1 } : { r: 255, g: 255, b: 255, a: 1 };
3293
+ for (let k = 0;k < valueStr.length; k++) {
3294
+ if (textX + k < cellX + cellWidth && textX + k < plotRight) {
3295
+ canvas.drawChar(textX + k, textY, valueStr[k], textColor);
3296
+ }
3297
+ }
3298
+ }
3299
+ }
2308
3300
  }
2309
- return { r: 128, g: 128, b: 128, a: 1 };
2310
3301
  }
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);
3302
+ function renderGeomSankey(data, geom, aes, scales, canvas) {
3303
+ if (data.length === 0)
3304
+ return;
3305
+ const nodeWidth = geom.params.node_width ?? 3;
3306
+ const nodePadding = geom.params.node_padding ?? 2;
3307
+ const nodeChar = geom.params.node_char ?? "█";
3308
+ const showLabels = geom.params.show_labels ?? true;
3309
+ const showValues = geom.params.show_values ?? false;
3310
+ const minFlowWidth = geom.params.min_flow_width ?? 1;
2315
3311
  const plotLeft = Math.round(scales.x.range[0]);
2316
3312
  const plotRight = Math.round(scales.x.range[1]);
2317
3313
  const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
2318
3314
  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;
3315
+ const plotHeight = plotBottom - plotTop;
3316
+ const sourceField = aes.x || "source";
3317
+ const targetField = aes.y || "target";
3318
+ const valueField = aes.fill || "value";
3319
+ const nodes = new Map;
3320
+ const links = [];
3321
+ const sourceNodes = new Set;
3322
+ const targetNodes = new Set;
2330
3323
  for (const row of data) {
2331
- categories.add(String(row.xOriginal ?? row[aes.x] ?? "default"));
3324
+ const source = String(row[sourceField] ?? "");
3325
+ const target = String(row[targetField] ?? "");
3326
+ const value = Number(row[valueField]) || 0;
3327
+ if (!source || !target)
3328
+ continue;
3329
+ sourceNodes.add(source);
3330
+ targetNodes.add(target);
3331
+ links.push({ source, target, value });
3332
+ if (!nodes.has(source)) {
3333
+ nodes.set(source, { name: source, value: 0, column: 0, y: 0, height: 0 });
3334
+ }
3335
+ if (!nodes.has(target)) {
3336
+ nodes.set(target, { name: target, value: 0, column: 1, y: 0, height: 0 });
3337
+ }
3338
+ nodes.get(source).value += value;
3339
+ nodes.get(target).value += value;
3340
+ }
3341
+ for (const [name, node] of nodes) {
3342
+ if (sourceNodes.has(name) && !targetNodes.has(name)) {
3343
+ node.column = 0;
3344
+ } else if (targetNodes.has(name) && !sourceNodes.has(name)) {
3345
+ node.column = 1;
3346
+ } else {
3347
+ node.column = 0;
3348
+ }
2332
3349
  }
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) {
3350
+ const columns = [[], []];
3351
+ for (const [, node] of nodes) {
3352
+ if (!columns[node.column])
3353
+ columns[node.column] = [];
3354
+ columns[node.column].push(node);
3355
+ }
3356
+ const maxColumnValue = Math.max(columns[0]?.reduce((sum, n) => sum + n.value, 0) || 0, columns[1]?.reduce((sum, n) => sum + n.value, 0) || 0);
3357
+ if (maxColumnValue === 0)
3358
+ return;
3359
+ const availableHeight = plotHeight - nodePadding * Math.max(columns[0]?.length || 0, columns[1]?.length || 0);
3360
+ for (let col = 0;col < 2; col++) {
3361
+ if (!columns[col])
2338
3362
  continue;
3363
+ let currentY = plotTop;
3364
+ columns[col].sort((a, b) => b.value - a.value);
3365
+ for (const node of columns[col]) {
3366
+ node.height = Math.max(1, Math.round(node.value / maxColumnValue * availableHeight));
3367
+ node.y = currentY;
3368
+ currentY += node.height + nodePadding;
2339
3369
  }
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);
3370
+ }
3371
+ const colorPalette = [
3372
+ { r: 79, g: 169, b: 238, a: 1 },
3373
+ { r: 240, g: 128, b: 60, a: 1 },
3374
+ { r: 102, g: 194, b: 114, a: 1 },
3375
+ { r: 218, g: 98, b: 125, a: 1 },
3376
+ { r: 169, g: 140, b: 204, a: 1 },
3377
+ { r: 255, g: 204, b: 102, a: 1 }
3378
+ ];
3379
+ const nodeColors = new Map;
3380
+ let colorIdx = 0;
3381
+ for (const [name] of nodes) {
3382
+ nodeColors.set(name, colorPalette[colorIdx % colorPalette.length]);
3383
+ colorIdx++;
3384
+ }
3385
+ const labelSpace = showLabels ? 8 : 0;
3386
+ const col0X = plotLeft + labelSpace;
3387
+ const col1X = plotRight - nodeWidth - labelSpace;
3388
+ for (const [name, node] of nodes) {
3389
+ const x = node.column === 0 ? col0X : col1X;
3390
+ const color = nodeColors.get(name);
3391
+ for (let dy = 0;dy < node.height; dy++) {
3392
+ for (let dx = 0;dx < nodeWidth; dx++) {
3393
+ canvas.drawChar(x + dx, node.y + dy, nodeChar, color);
3394
+ }
3395
+ }
3396
+ if (showLabels) {
3397
+ const labelX = node.column === 0 ? x - name.length - 1 : x + nodeWidth + 1;
3398
+ const labelY = node.y + Math.floor(node.height / 2);
3399
+ const labelColor = { r: 180, g: 180, b: 180, a: 1 };
3400
+ for (let i = 0;i < name.length && labelX + i >= plotLeft && labelX + i < plotRight; i++) {
3401
+ canvas.drawChar(labelX + i, labelY, name[i], labelColor);
3402
+ }
3403
+ }
3404
+ }
3405
+ for (const link of links) {
3406
+ const sourceNode = nodes.get(link.source);
3407
+ const targetNode = nodes.get(link.target);
3408
+ if (!sourceNode || !targetNode)
3409
+ continue;
3410
+ const flowWidth = Math.max(minFlowWidth, Math.round(link.value / maxColumnValue * (plotHeight / 4)));
3411
+ const sourceColor = nodeColors.get(link.source);
3412
+ const x1 = col0X + nodeWidth;
3413
+ const x2 = col1X;
3414
+ const y1 = sourceNode.y + Math.floor(sourceNode.height / 2);
3415
+ const y2 = targetNode.y + Math.floor(targetNode.height / 2);
3416
+ const steps = Math.abs(x2 - x1);
3417
+ for (let i = 0;i <= steps; i++) {
3418
+ const t = i / steps;
3419
+ const x = Math.round(x1 + (x2 - x1) * t);
3420
+ const y = Math.round(y1 + (y2 - y1) * (3 * t * t - 2 * t * t * t));
3421
+ const halfWidth = Math.floor(flowWidth / 2);
3422
+ for (let dy = -halfWidth;dy <= halfWidth; dy++) {
3423
+ const flowY = y + dy;
3424
+ if (flowY >= plotTop && flowY < plotBottom && x >= plotLeft && x < plotRight) {
3425
+ const flowColor = {
3426
+ r: sourceColor.r,
3427
+ g: sourceColor.g,
3428
+ b: sourceColor.b,
3429
+ a: 0.4
3430
+ };
3431
+ const char = dy === 0 ? "─" : "░";
3432
+ canvas.drawChar(x, flowY, char, flowColor);
3433
+ }
3434
+ }
3435
+ }
3436
+ if (showValues) {
3437
+ const midX = Math.round((x1 + x2) / 2);
3438
+ const midY = Math.round((y1 + y2) / 2);
3439
+ const valueStr = String(link.value);
3440
+ const textColor = { r: 200, g: 200, b: 200, a: 1 };
3441
+ for (let i = 0;i < valueStr.length; i++) {
3442
+ canvas.drawChar(midX - Math.floor(valueStr.length / 2) + i, midY, valueStr[i], textColor);
3443
+ }
3444
+ }
3445
+ }
3446
+ }
3447
+ function renderGeomTreemap(data, geom, aes, scales, canvas) {
3448
+ if (data.length === 0)
3449
+ return;
3450
+ const showLabels = geom.params.show_labels ?? true;
3451
+ const showValues = geom.params.show_values ?? false;
3452
+ const border = geom.params.border ?? true;
3453
+ const minLabelSize = geom.params.min_label_size ?? 4;
3454
+ const fillChar = geom.params.fill_char ?? "█";
3455
+ const plotLeft = Math.round(scales.x.range[0]);
3456
+ const plotRight = Math.round(scales.x.range[1]);
3457
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
3458
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
3459
+ const labelField = aes.x || "name";
3460
+ const valueField = aes.fill || aes.y || "value";
3461
+ const parentField = aes.group || "parent";
3462
+ const idField = aes.x || "id";
3463
+ const nodeMap = new Map;
3464
+ const roots = [];
3465
+ for (const row of data) {
3466
+ const id = String(row[idField] ?? row[labelField] ?? "");
3467
+ const label = String(row[labelField] ?? id);
3468
+ const value = Number(row[valueField]) || 0;
3469
+ const parent = row[parentField] ? String(row[parentField]) : undefined;
3470
+ const node = {
3471
+ id,
3472
+ label,
3473
+ value,
3474
+ parent,
3475
+ children: [],
3476
+ x: 0,
3477
+ y: 0,
3478
+ width: 0,
3479
+ height: 0
3480
+ };
3481
+ nodeMap.set(id, node);
3482
+ }
3483
+ for (const [, node] of nodeMap) {
3484
+ if (node.parent && nodeMap.has(node.parent)) {
3485
+ nodeMap.get(node.parent).children.push(node);
2350
3486
  } else {
2351
- const category = String(row.xOriginal ?? row[aes.x] ?? "default");
2352
- const categoryIdx = categoryList.indexOf(category);
2353
- color = defaultColors[categoryIdx % defaultColors.length];
3487
+ roots.push(node);
2354
3488
  }
2355
- if (alpha < 1) {
2356
- color = { ...color, a: alpha };
3489
+ }
3490
+ if (roots.length === 0) {
3491
+ for (const [, node] of nodeMap) {
3492
+ roots.push(node);
2357
3493
  }
2358
- if (cx >= plotLeft && cx <= plotRight && cy >= plotTop && cy <= plotBottom) {
2359
- canvas.drawChar(cx, cy, shape, color);
3494
+ }
3495
+ function calculateValue(node) {
3496
+ if (node.children.length === 0) {
3497
+ return node.value;
3498
+ }
3499
+ const childSum = node.children.reduce((sum, child) => sum + calculateValue(child), 0);
3500
+ return node.value || childSum;
3501
+ }
3502
+ for (const root of roots) {
3503
+ root.value = calculateValue(root);
3504
+ }
3505
+ const validRoots = roots.filter((n) => n.value > 0).sort((a, b) => b.value - a.value);
3506
+ if (validRoots.length === 0)
3507
+ return;
3508
+ function squarify(nodes, x, y, width, height) {
3509
+ if (nodes.length === 0 || width <= 0 || height <= 0)
3510
+ return;
3511
+ const totalValue = nodes.reduce((sum, n) => sum + n.value, 0);
3512
+ if (totalValue === 0)
3513
+ return;
3514
+ if (nodes.length === 1) {
3515
+ const node = nodes[0];
3516
+ node.x = x;
3517
+ node.y = y;
3518
+ node.width = width;
3519
+ node.height = height;
3520
+ return;
3521
+ }
3522
+ const isHorizontal = width > height;
3523
+ let currentPos = isHorizontal ? x : y;
3524
+ const totalSize = isHorizontal ? width : height;
3525
+ for (const node of nodes) {
3526
+ const fraction = node.value / totalValue;
3527
+ const size = Math.round(totalSize * fraction);
3528
+ if (isHorizontal) {
3529
+ node.x = currentPos;
3530
+ node.y = y;
3531
+ node.width = Math.max(1, size);
3532
+ node.height = height;
3533
+ currentPos += node.width;
3534
+ } else {
3535
+ node.x = x;
3536
+ node.y = currentPos;
3537
+ node.width = width;
3538
+ node.height = Math.max(1, size);
3539
+ currentPos += node.height;
3540
+ }
3541
+ }
3542
+ }
3543
+ squarify(validRoots, plotLeft, plotTop, plotRight - plotLeft, plotBottom - plotTop);
3544
+ const colorPalette = [
3545
+ { r: 66, g: 133, b: 244, a: 1 },
3546
+ { r: 234, g: 67, b: 53, a: 1 },
3547
+ { r: 251, g: 188, b: 5, a: 1 },
3548
+ { r: 52, g: 168, b: 83, a: 1 },
3549
+ { r: 154, g: 102, b: 255, a: 1 },
3550
+ { r: 255, g: 109, b: 0, a: 1 },
3551
+ { r: 0, g: 188, b: 212, a: 1 },
3552
+ { r: 233, g: 30, b: 99, a: 1 }
3553
+ ];
3554
+ function renderNode(node, colorIndex, depth) {
3555
+ if (node.width <= 0 || node.height <= 0)
3556
+ return;
3557
+ const baseColor = colorPalette[colorIndex % colorPalette.length];
3558
+ const depthFactor = Math.max(0.5, 1 - depth * 0.15);
3559
+ const color = {
3560
+ r: Math.round(baseColor.r * depthFactor),
3561
+ g: Math.round(baseColor.g * depthFactor),
3562
+ b: Math.round(baseColor.b * depthFactor),
3563
+ a: 1
3564
+ };
3565
+ for (let dy = 0;dy < node.height; dy++) {
3566
+ for (let dx = 0;dx < node.width; dx++) {
3567
+ const px = node.x + dx;
3568
+ const py = node.y + dy;
3569
+ if (px >= plotLeft && px < plotRight && py >= plotTop && py < plotBottom) {
3570
+ canvas.drawChar(px, py, fillChar, color);
3571
+ }
3572
+ }
3573
+ }
3574
+ if (border && node.width >= 2 && node.height >= 2) {
3575
+ const borderColor = { r: 40, g: 40, b: 40, a: 1 };
3576
+ for (let dx = 0;dx < node.width; dx++) {
3577
+ const px = node.x + dx;
3578
+ if (px >= plotLeft && px < plotRight) {
3579
+ if (node.y >= plotTop)
3580
+ canvas.drawChar(px, node.y, "─", borderColor);
3581
+ if (node.y + node.height - 1 < plotBottom)
3582
+ canvas.drawChar(px, node.y + node.height - 1, "─", borderColor);
3583
+ }
3584
+ }
3585
+ for (let dy = 0;dy < node.height; dy++) {
3586
+ const py = node.y + dy;
3587
+ if (py >= plotTop && py < plotBottom) {
3588
+ if (node.x >= plotLeft)
3589
+ canvas.drawChar(node.x, py, "│", borderColor);
3590
+ if (node.x + node.width - 1 < plotRight)
3591
+ canvas.drawChar(node.x + node.width - 1, py, "│", borderColor);
3592
+ }
3593
+ }
3594
+ if (node.x >= plotLeft && node.y >= plotTop)
3595
+ canvas.drawChar(node.x, node.y, "┌", borderColor);
3596
+ if (node.x + node.width - 1 < plotRight && node.y >= plotTop)
3597
+ canvas.drawChar(node.x + node.width - 1, node.y, "┐", borderColor);
3598
+ if (node.x >= plotLeft && node.y + node.height - 1 < plotBottom)
3599
+ canvas.drawChar(node.x, node.y + node.height - 1, "└", borderColor);
3600
+ if (node.x + node.width - 1 < plotRight && node.y + node.height - 1 < plotBottom)
3601
+ canvas.drawChar(node.x + node.width - 1, node.y + node.height - 1, "┘", borderColor);
3602
+ }
3603
+ if (showLabels && node.width >= minLabelSize && node.height >= 1) {
3604
+ const label = node.label.substring(0, node.width - 2);
3605
+ const labelX = node.x + 1;
3606
+ const labelY = node.y + Math.floor(node.height / 2);
3607
+ const brightness = (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
3608
+ const textColor = brightness > 128 ? { r: 0, g: 0, b: 0, a: 1 } : { r: 255, g: 255, b: 255, a: 1 };
3609
+ for (let i = 0;i < label.length; i++) {
3610
+ const px = labelX + i;
3611
+ if (px >= plotLeft && px < node.x + node.width - 1 && px < plotRight) {
3612
+ canvas.drawChar(px, labelY, label[i], textColor);
3613
+ }
3614
+ }
3615
+ if (showValues && node.height >= 3) {
3616
+ const valueStr = String(node.value);
3617
+ const valueY = labelY + 1;
3618
+ for (let i = 0;i < valueStr.length; i++) {
3619
+ const px = labelX + i;
3620
+ if (px >= plotLeft && px < node.x + node.width - 1 && px < plotRight && valueY < plotBottom) {
3621
+ canvas.drawChar(px, valueY, valueStr[i], textColor);
3622
+ }
3623
+ }
3624
+ }
3625
+ }
3626
+ if (node.children.length > 0) {
3627
+ const childrenSorted = [...node.children].sort((a, b) => b.value - a.value);
3628
+ squarify(childrenSorted, node.x + (border ? 1 : 0), node.y + (border ? 1 : 0), node.width - (border ? 2 : 0), node.height - (border ? 2 : 0));
3629
+ for (let i = 0;i < childrenSorted.length; i++) {
3630
+ renderNode(childrenSorted[i], colorIndex, depth + 1);
3631
+ }
2360
3632
  }
2361
3633
  }
3634
+ for (let i = 0;i < validRoots.length; i++) {
3635
+ renderNode(validRoots[i], i, 0);
3636
+ }
2362
3637
  }
2363
3638
  function renderGeom(data, geom, aes, scales, canvas, coordType) {
2364
3639
  switch (geom.type) {
@@ -2397,6 +3672,9 @@ function renderGeom(data, geom, aes, scales, canvas, coordType) {
2397
3672
  case "freqpoly":
2398
3673
  renderGeomFreqpoly(data, geom, aes, scales, canvas);
2399
3674
  break;
3675
+ case "density":
3676
+ renderGeomDensity(data, geom, aes, scales, canvas);
3677
+ break;
2400
3678
  case "boxplot":
2401
3679
  renderGeomBoxplot(data, geom, aes, scales, canvas);
2402
3680
  break;
@@ -2435,6 +3713,9 @@ function renderGeom(data, geom, aes, scales, canvas, coordType) {
2435
3713
  case "linerange":
2436
3714
  renderGeomLinerange(data, geom, aes, scales, canvas);
2437
3715
  break;
3716
+ case "crossbar":
3717
+ renderGeomCrossbar(data, geom, aes, scales, canvas);
3718
+ break;
2438
3719
  case "pointrange":
2439
3720
  renderGeomPointrange(data, geom, aes, scales, canvas);
2440
3721
  break;
@@ -2445,6 +3726,40 @@ function renderGeom(data, geom, aes, scales, canvas, coordType) {
2445
3726
  case "quasirandom":
2446
3727
  renderGeomBeeswarm(data, geom, aes, scales, canvas);
2447
3728
  break;
3729
+ case "dumbbell":
3730
+ renderGeomDumbbell(data, geom, aes, scales, canvas);
3731
+ break;
3732
+ case "lollipop":
3733
+ renderGeomLollipop(data, geom, aes, scales, canvas);
3734
+ break;
3735
+ case "waffle":
3736
+ renderGeomWaffle(data, geom, aes, scales, canvas);
3737
+ break;
3738
+ case "sparkline":
3739
+ renderGeomSparkline(data, geom, aes, scales, canvas);
3740
+ break;
3741
+ case "bullet":
3742
+ renderGeomBullet(data, geom, aes, scales, canvas);
3743
+ break;
3744
+ case "braille":
3745
+ renderGeomBraille(data, geom, aes, scales, canvas);
3746
+ break;
3747
+ case "calendar":
3748
+ renderGeomCalendar(data, geom, aes, scales, canvas);
3749
+ break;
3750
+ case "flame":
3751
+ case "icicle":
3752
+ renderGeomFlame(data, geom, aes, scales, canvas);
3753
+ break;
3754
+ case "corrmat":
3755
+ renderGeomCorrmat(data, geom, aes, scales, canvas);
3756
+ break;
3757
+ case "sankey":
3758
+ renderGeomSankey(data, geom, aes, scales, canvas);
3759
+ break;
3760
+ case "treemap":
3761
+ renderGeomTreemap(data, geom, aes, scales, canvas);
3762
+ break;
2448
3763
  default:
2449
3764
  break;
2450
3765
  }
@@ -5450,6 +6765,26 @@ function geom_freqpoly(options = {}) {
5450
6765
  };
5451
6766
  }
5452
6767
 
6768
+ // src/geoms/density.ts
6769
+ function geom_density(options = {}) {
6770
+ return {
6771
+ type: "density",
6772
+ stat: "density",
6773
+ position: "identity",
6774
+ params: {
6775
+ n: options.n ?? 512,
6776
+ bw: options.bw,
6777
+ kernel: options.kernel ?? "gaussian",
6778
+ adjust: options.adjust ?? 1,
6779
+ alpha: options.alpha ?? 0.3,
6780
+ color: options.color,
6781
+ fill: options.fill,
6782
+ linewidth: options.linewidth ?? 1,
6783
+ linetype: options.linetype ?? "solid"
6784
+ }
6785
+ };
6786
+ }
6787
+
5453
6788
  // src/geoms/boxplot.ts
5454
6789
  function geom_boxplot(options = {}) {
5455
6790
  return {
@@ -5817,9 +7152,240 @@ function geom_quasirandom(options = {}) {
5817
7152
  return geom_beeswarm({ ...options, method: "center" });
5818
7153
  }
5819
7154
 
7155
+ // src/geoms/dumbbell.ts
7156
+ function geom_dumbbell(options = {}) {
7157
+ return {
7158
+ type: "dumbbell",
7159
+ stat: "identity",
7160
+ position: "identity",
7161
+ params: {
7162
+ size: options.size ?? 2,
7163
+ sizeEnd: options.sizeEnd ?? options.size ?? 2,
7164
+ color: options.color,
7165
+ colorEnd: options.colorEnd ?? options.color,
7166
+ lineColor: options.lineColor ?? "#666666",
7167
+ lineWidth: options.lineWidth ?? 1,
7168
+ alpha: options.alpha ?? 1,
7169
+ shape: options.shape ?? "circle"
7170
+ }
7171
+ };
7172
+ }
7173
+
7174
+ // src/geoms/lollipop.ts
7175
+ function geom_lollipop(options = {}) {
7176
+ return {
7177
+ type: "lollipop",
7178
+ stat: "identity",
7179
+ position: "identity",
7180
+ params: {
7181
+ size: options.size ?? 2,
7182
+ color: options.color,
7183
+ lineColor: options.lineColor,
7184
+ lineWidth: options.lineWidth ?? 1,
7185
+ alpha: options.alpha ?? 1,
7186
+ shape: options.shape ?? "circle",
7187
+ direction: options.direction ?? "vertical",
7188
+ baseline: options.baseline ?? 0
7189
+ }
7190
+ };
7191
+ }
7192
+
7193
+ // src/geoms/waffle.ts
7194
+ function geom_waffle(options = {}) {
7195
+ return {
7196
+ type: "waffle",
7197
+ stat: "identity",
7198
+ position: "identity",
7199
+ params: {
7200
+ rows: options.rows ?? 10,
7201
+ cols: options.cols ?? 10,
7202
+ n_total: options.n_total ?? 100,
7203
+ fill_char: options.fill_char ?? "█",
7204
+ empty_char: options.empty_char ?? "░",
7205
+ alpha: options.alpha ?? 1,
7206
+ show_legend: options.show_legend ?? true,
7207
+ flip: options.flip ?? false,
7208
+ gap: options.gap ?? 0
7209
+ }
7210
+ };
7211
+ }
7212
+
7213
+ // src/geoms/sparkline.ts
7214
+ function geom_sparkline(options = {}) {
7215
+ return {
7216
+ type: "sparkline",
7217
+ stat: "identity",
7218
+ position: "identity",
7219
+ params: {
7220
+ sparkType: options.type ?? "bar",
7221
+ width: options.width ?? 20,
7222
+ height: options.height ?? 1,
7223
+ show_minmax: options.show_minmax ?? false,
7224
+ color: options.color,
7225
+ min_color: options.min_color ?? "#e74c3c",
7226
+ max_color: options.max_color ?? "#2ecc71",
7227
+ normalize: options.normalize ?? true
7228
+ }
7229
+ };
7230
+ }
7231
+ var SPARK_BARS, SPARK_DOTS;
7232
+ var init_sparkline = __esm(() => {
7233
+ SPARK_BARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
7234
+ SPARK_DOTS = ["⠀", "⢀", "⢠", "⢰", "⢸", "⣸", "⣾", "⣿"];
7235
+ });
7236
+
7237
+ // src/geoms/bullet.ts
7238
+ function geom_bullet(options = {}) {
7239
+ return {
7240
+ type: "bullet",
7241
+ stat: "identity",
7242
+ position: "identity",
7243
+ params: {
7244
+ width: options.width ?? 40,
7245
+ height: options.height ?? 1,
7246
+ target_char: options.target_char ?? "│",
7247
+ bar_char: options.bar_char ?? "█",
7248
+ range_chars: options.range_chars ?? ["░", "▒", "▓"],
7249
+ show_values: options.show_values ?? true,
7250
+ color: options.color,
7251
+ target_color: options.target_color ?? "#e74c3c",
7252
+ orientation: options.orientation ?? "horizontal"
7253
+ }
7254
+ };
7255
+ }
7256
+
7257
+ // src/geoms/braille.ts
7258
+ function geom_braille(options = {}) {
7259
+ return {
7260
+ type: "braille",
7261
+ stat: "identity",
7262
+ position: "identity",
7263
+ params: {
7264
+ brailleType: options.type ?? "point",
7265
+ color: options.color,
7266
+ fill: options.fill ?? false,
7267
+ alpha: options.alpha ?? 1,
7268
+ dot_size: options.dot_size ?? 1
7269
+ }
7270
+ };
7271
+ }
7272
+ var BRAILLE_BASE = 10240, BRAILLE_DOTS;
7273
+ var init_braille = __esm(() => {
7274
+ BRAILLE_DOTS = [
7275
+ [1, 2, 4, 64],
7276
+ [8, 16, 32, 128]
7277
+ ];
7278
+ });
7279
+
7280
+ // src/geoms/calendar.ts
7281
+ function geom_calendar(options = {}) {
7282
+ return {
7283
+ type: "calendar",
7284
+ stat: "identity",
7285
+ position: "identity",
7286
+ params: {
7287
+ cell_char: options.cell_char ?? "█",
7288
+ empty_char: options.empty_char ?? "░",
7289
+ empty_color: options.empty_color ?? "#161b22",
7290
+ fill_color: options.fill_color ?? "#39d353",
7291
+ show_months: options.show_months ?? true,
7292
+ show_days: options.show_days ?? true,
7293
+ week_start: options.week_start ?? 0,
7294
+ levels: options.levels ?? 5
7295
+ }
7296
+ };
7297
+ }
7298
+
7299
+ // src/geoms/flame.ts
7300
+ function geom_flame(options = {}) {
7301
+ return {
7302
+ type: "flame",
7303
+ stat: "identity",
7304
+ position: "identity",
7305
+ params: {
7306
+ style: options.style ?? "flame",
7307
+ palette: options.palette ?? "warm",
7308
+ show_labels: options.show_labels ?? true,
7309
+ min_label_width: options.min_label_width ?? 10,
7310
+ sort: options.sort ?? "alpha",
7311
+ bar_char: options.bar_char ?? "█"
7312
+ }
7313
+ };
7314
+ }
7315
+ function geom_icicle(options = {}) {
7316
+ return geom_flame({ ...options, style: "icicle" });
7317
+ }
7318
+
7319
+ // src/geoms/corrmat.ts
7320
+ function geom_corrmat(options = {}) {
7321
+ return {
7322
+ type: "corrmat",
7323
+ stat: "identity",
7324
+ position: "identity",
7325
+ params: {
7326
+ show_values: options.show_values ?? true,
7327
+ decimals: options.decimals ?? 2,
7328
+ show_significance: options.show_significance ?? false,
7329
+ sig_threshold: options.sig_threshold ?? 0.05,
7330
+ sig_marker: options.sig_marker ?? "*",
7331
+ positive_color: options.positive_color ?? "#2166ac",
7332
+ negative_color: options.negative_color ?? "#b2182b",
7333
+ neutral_color: options.neutral_color ?? "#f7f7f7",
7334
+ lower_triangle: options.lower_triangle ?? false,
7335
+ upper_triangle: options.upper_triangle ?? false,
7336
+ show_diagonal: options.show_diagonal ?? true,
7337
+ method: options.method ?? "pearson"
7338
+ }
7339
+ };
7340
+ }
7341
+
7342
+ // src/geoms/sankey.ts
7343
+ function geom_sankey(options = {}) {
7344
+ return {
7345
+ type: "sankey",
7346
+ stat: "identity",
7347
+ position: "identity",
7348
+ params: {
7349
+ node_width: options.node_width ?? 3,
7350
+ node_padding: options.node_padding ?? 2,
7351
+ node_char: options.node_char ?? "█",
7352
+ flow_char: options.flow_char ?? "─",
7353
+ show_labels: options.show_labels ?? true,
7354
+ show_values: options.show_values ?? false,
7355
+ align: options.align ?? "justify",
7356
+ color_by: options.color_by ?? "auto",
7357
+ min_flow_width: options.min_flow_width ?? 1,
7358
+ flow_gap: options.flow_gap ?? 0
7359
+ }
7360
+ };
7361
+ }
7362
+
7363
+ // src/geoms/treemap.ts
7364
+ function geom_treemap(options = {}) {
7365
+ return {
7366
+ type: "treemap",
7367
+ stat: "identity",
7368
+ position: "identity",
7369
+ params: {
7370
+ algorithm: options.algorithm ?? "squarify",
7371
+ show_labels: options.show_labels ?? true,
7372
+ show_values: options.show_values ?? false,
7373
+ border: options.border ?? true,
7374
+ padding: options.padding ?? 0,
7375
+ min_label_size: options.min_label_size ?? 4,
7376
+ color_by: options.color_by ?? "value",
7377
+ fill_char: options.fill_char ?? "█",
7378
+ max_depth: options.max_depth,
7379
+ aspect_ratio: options.aspect_ratio ?? 1.618
7380
+ }
7381
+ };
7382
+ }
7383
+
5820
7384
  // src/geoms/index.ts
5821
7385
  var init_geoms = __esm(() => {
5822
7386
  init_ridgeline();
7387
+ init_sparkline();
7388
+ init_braille();
5823
7389
  });
5824
7390
 
5825
7391
  // src/stats/index.ts
@@ -10322,13 +11888,17 @@ __export(exports_src, {
10322
11888
  getColorEscape: () => getColorEscape,
10323
11889
  getCapabilities: () => getCapabilities,
10324
11890
  getAvailablePalettes: () => getAvailablePalettes,
11891
+ geom_waffle: () => geom_waffle,
10325
11892
  geom_vline: () => geom_vline,
10326
11893
  geom_violin: () => geom_violin,
11894
+ geom_treemap: () => geom_treemap,
10327
11895
  geom_tile: () => geom_tile,
10328
11896
  geom_text: () => geom_text,
10329
11897
  geom_step: () => geom_step,
11898
+ geom_sparkline: () => geom_sparkline,
10330
11899
  geom_smooth: () => geom_smooth,
10331
11900
  geom_segment: () => geom_segment,
11901
+ geom_sankey: () => geom_sankey,
10332
11902
  geom_rug: () => geom_rug,
10333
11903
  geom_ridgeline: () => geom_ridgeline,
10334
11904
  geom_ribbon: () => geom_ribbon,
@@ -10340,21 +11910,30 @@ __export(exports_src, {
10340
11910
  geom_pointrange: () => geom_pointrange,
10341
11911
  geom_point: () => geom_point,
10342
11912
  geom_path: () => geom_path,
11913
+ geom_lollipop: () => geom_lollipop,
10343
11914
  geom_linerange: () => geom_linerange,
10344
11915
  geom_line: () => geom_line,
10345
11916
  geom_label: () => geom_label,
10346
11917
  geom_joy: () => geom_joy,
11918
+ geom_icicle: () => geom_icicle,
10347
11919
  geom_hline: () => geom_hline,
10348
11920
  geom_histogram: () => geom_histogram,
10349
11921
  geom_freqpoly: () => geom_freqpoly,
11922
+ geom_flame: () => geom_flame,
10350
11923
  geom_errorbarh: () => geom_errorbarh,
10351
11924
  geom_errorbar: () => geom_errorbar,
11925
+ geom_dumbbell: () => geom_dumbbell,
10352
11926
  geom_density_2d: () => geom_density_2d,
11927
+ geom_density: () => geom_density,
10353
11928
  geom_curve: () => geom_curve,
10354
11929
  geom_crossbar: () => geom_crossbar,
11930
+ geom_corrmat: () => geom_corrmat,
10355
11931
  geom_contour_filled: () => geom_contour_filled,
10356
11932
  geom_contour: () => geom_contour,
10357
11933
  geom_col: () => geom_col,
11934
+ geom_calendar: () => geom_calendar,
11935
+ geom_bullet: () => geom_bullet,
11936
+ geom_braille: () => geom_braille,
10358
11937
  geom_boxplot: () => geom_boxplot,
10359
11938
  geom_bin2d: () => geom_bin2d,
10360
11939
  geom_beeswarm: () => geom_beeswarm,
@@ -10426,6 +12005,8 @@ __export(exports_src, {
10426
12005
  annotate: () => annotate,
10427
12006
  TerminalCanvas: () => TerminalCanvas,
10428
12007
  StreamingPlot: () => StreamingPlot,
12008
+ SPARK_DOTS: () => SPARK_DOTS,
12009
+ SPARK_BARS: () => SPARK_BARS,
10429
12010
  SHAPE_CHARS: () => SHAPE_CHARS,
10430
12011
  RollingAggregator: () => RollingAggregator,
10431
12012
  RendererChain: () => RendererChain,
@@ -10445,6 +12026,8 @@ __export(exports_src, {
10445
12026
  DEFAULT_BG: () => DEFAULT_BG,
10446
12027
  CanvasDiff: () => CanvasDiff,
10447
12028
  Binner: () => Binner,
12029
+ BRAILLE_DOTS: () => BRAILLE_DOTS,
12030
+ BRAILLE_BASE: () => BRAILLE_BASE,
10448
12031
  ANSI_16_COLORS: () => ANSI_16_COLORS
10449
12032
  });
10450
12033
  var init_src = __esm(() => {
@@ -10574,13 +12157,17 @@ export {
10574
12157
  getColorEscape,
10575
12158
  getCapabilities,
10576
12159
  getAvailablePalettes,
12160
+ geom_waffle,
10577
12161
  geom_vline,
10578
12162
  geom_violin,
12163
+ geom_treemap,
10579
12164
  geom_tile,
10580
12165
  geom_text,
10581
12166
  geom_step,
12167
+ geom_sparkline,
10582
12168
  geom_smooth,
10583
12169
  geom_segment,
12170
+ geom_sankey,
10584
12171
  geom_rug,
10585
12172
  geom_ridgeline,
10586
12173
  geom_ribbon,
@@ -10592,21 +12179,30 @@ export {
10592
12179
  geom_pointrange,
10593
12180
  geom_point,
10594
12181
  geom_path,
12182
+ geom_lollipop,
10595
12183
  geom_linerange,
10596
12184
  geom_line,
10597
12185
  geom_label,
10598
12186
  geom_joy,
12187
+ geom_icicle,
10599
12188
  geom_hline,
10600
12189
  geom_histogram,
10601
12190
  geom_freqpoly,
12191
+ geom_flame,
10602
12192
  geom_errorbarh,
10603
12193
  geom_errorbar,
12194
+ geom_dumbbell,
10604
12195
  geom_density_2d,
12196
+ geom_density,
10605
12197
  geom_curve,
10606
12198
  geom_crossbar,
12199
+ geom_corrmat,
10607
12200
  geom_contour_filled,
10608
12201
  geom_contour,
10609
12202
  geom_col,
12203
+ geom_calendar,
12204
+ geom_bullet,
12205
+ geom_braille,
10610
12206
  geom_boxplot,
10611
12207
  geom_bin2d,
10612
12208
  geom_beeswarm,
@@ -10678,6 +12274,8 @@ export {
10678
12274
  annotate,
10679
12275
  TerminalCanvas,
10680
12276
  StreamingPlot,
12277
+ SPARK_DOTS,
12278
+ SPARK_BARS,
10681
12279
  SHAPE_CHARS,
10682
12280
  RollingAggregator,
10683
12281
  RendererChain,
@@ -10697,5 +12295,7 @@ export {
10697
12295
  DEFAULT_BG,
10698
12296
  CanvasDiff,
10699
12297
  Binner,
12298
+ BRAILLE_DOTS,
12299
+ BRAILLE_BASE,
10700
12300
  ANSI_16_COLORS
10701
12301
  };