@tuicomponents/chart 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3656 @@
1
+ // src/chart.ts
2
+ import {
3
+ BaseTuiComponent,
4
+ measureLines,
5
+ registry
6
+ } from "@tuicomponents/core";
7
+ import { zodToJsonSchema } from "zod-to-json-schema";
8
+
9
+ // src/schema.ts
10
+ import { z } from "zod";
11
+ var chartTypeSchema = z.enum([
12
+ "bar",
13
+ // Horizontal bars
14
+ "bar-vertical",
15
+ // Vertical bars (column chart)
16
+ "bar-stacked",
17
+ // Stacked horizontal bars
18
+ "bar-stacked-vertical",
19
+ // Stacked vertical bars
20
+ "line",
21
+ // Line chart (height blocks)
22
+ "area",
23
+ // Filled area
24
+ "area-stacked",
25
+ // Stacked area
26
+ "scatter",
27
+ // 2D scatter plot with X/Y axes
28
+ "pie",
29
+ // Pie chart (circular)
30
+ "donut",
31
+ // Donut chart (pie with inner radius)
32
+ "heatmap"
33
+ // 2D grid with intensity shading
34
+ ]);
35
+ var barStyleSchema = z.enum([
36
+ "block",
37
+ // ████████
38
+ "shaded",
39
+ // ▓▓▓▓▓▓▓▓
40
+ "light",
41
+ // ░░░░░░░░
42
+ "hash",
43
+ // ########
44
+ "equals",
45
+ // ========
46
+ "arrow"
47
+ // >>>>>>>>
48
+ ]);
49
+ var lineStyleSchema = z.enum([
50
+ "blocks",
51
+ // Height blocks: ▁▂▃▄▅▆▇█ (default)
52
+ "braille",
53
+ // Braille dots (high resolution)
54
+ "dots"
55
+ // Point markers: · • ●
56
+ ]);
57
+ var scatterStyleSchema = z.enum([
58
+ "dots",
59
+ // Simple character dots (●, ■, ▲)
60
+ "braille"
61
+ // High-resolution braille positioning
62
+ ]);
63
+ var scatterMarkerSchema = z.enum([
64
+ "circle",
65
+ // ●
66
+ "square",
67
+ // ■
68
+ "triangle",
69
+ // ▲
70
+ "diamond",
71
+ // ◆
72
+ "plus"
73
+ // +
74
+ ]);
75
+ var heatmapStyleSchema = z.enum([
76
+ "blocks",
77
+ // ░▒▓█ (4 intensity levels)
78
+ "ascii",
79
+ // . : * # (4 levels, markdown-friendly)
80
+ "numeric"
81
+ // Show actual values in cells
82
+ ]);
83
+ var valueFormatSchema = z.enum([
84
+ "number",
85
+ // Regular number
86
+ "percent",
87
+ // Percentage
88
+ "compact",
89
+ // K/M/B suffixes
90
+ "currency"
91
+ // Dollar prefix
92
+ ]);
93
+ var legendPositionSchema = z.enum([
94
+ "none",
95
+ // No legend
96
+ "top",
97
+ // Above chart
98
+ "bottom",
99
+ // Below chart
100
+ "right",
101
+ // To the right
102
+ "inline"
103
+ // Inline with data
104
+ ]);
105
+ var dataPointSchema = z.object({
106
+ /**
107
+ * Category or x-value.
108
+ * Can be a string (categorical) or number (continuous).
109
+ */
110
+ x: z.union([z.string(), z.number()]),
111
+ /**
112
+ * Numeric y-value.
113
+ */
114
+ y: z.number(),
115
+ /**
116
+ * Optional custom label for this data point.
117
+ */
118
+ label: z.string().optional()
119
+ });
120
+ var dataSeriesSchema = z.object({
121
+ /**
122
+ * Series name (used in legend).
123
+ */
124
+ name: z.string(),
125
+ /**
126
+ * Data points in this series.
127
+ */
128
+ data: z.array(dataPointSchema),
129
+ /**
130
+ * Optional style for this series.
131
+ */
132
+ style: barStyleSchema.optional()
133
+ });
134
+ var axisConfigSchema = z.object({
135
+ /**
136
+ * Axis title/label.
137
+ */
138
+ label: z.string().optional(),
139
+ /**
140
+ * Explicit minimum value (auto-computed if not set).
141
+ */
142
+ min: z.number().optional(),
143
+ /**
144
+ * Explicit maximum value (auto-computed if not set).
145
+ */
146
+ max: z.number().optional(),
147
+ /**
148
+ * Number of tick marks.
149
+ * @default 5
150
+ */
151
+ tickCount: z.number().int().positive().default(5),
152
+ /**
153
+ * Whether to show tick marks.
154
+ * @default true
155
+ */
156
+ showTicks: z.boolean().default(true),
157
+ /**
158
+ * Value format for tick labels.
159
+ * @default "number"
160
+ */
161
+ format: valueFormatSchema.default("number"),
162
+ /**
163
+ * Number of decimal places.
164
+ */
165
+ decimals: z.number().int().nonnegative().optional()
166
+ });
167
+ var legendConfigSchema = z.object({
168
+ /**
169
+ * Legend position.
170
+ * @default "bottom"
171
+ */
172
+ position: legendPositionSchema.default("bottom"),
173
+ /**
174
+ * Whether to use boxed style.
175
+ * @default false
176
+ */
177
+ boxed: z.boolean().default(false)
178
+ });
179
+ var gridConfigSchema = z.object({
180
+ /**
181
+ * Show horizontal grid lines.
182
+ * @default false
183
+ */
184
+ horizontal: z.boolean().default(false),
185
+ /**
186
+ * Show vertical grid lines.
187
+ * @default false
188
+ */
189
+ vertical: z.boolean().default(false),
190
+ /**
191
+ * Grid character.
192
+ * @default "·"
193
+ */
194
+ char: z.string().default("\xB7")
195
+ });
196
+ var chartInputSchema = z.object({
197
+ /**
198
+ * Chart type.
199
+ */
200
+ type: chartTypeSchema,
201
+ /**
202
+ * Data series to display.
203
+ */
204
+ series: z.array(dataSeriesSchema).min(1),
205
+ /**
206
+ * Chart title.
207
+ */
208
+ title: z.string().optional(),
209
+ /**
210
+ * X-axis configuration.
211
+ */
212
+ xAxis: axisConfigSchema.optional(),
213
+ /**
214
+ * Y-axis configuration.
215
+ */
216
+ yAxis: axisConfigSchema.optional(),
217
+ /**
218
+ * Legend configuration.
219
+ */
220
+ legend: legendConfigSchema.optional(),
221
+ /**
222
+ * Grid configuration.
223
+ */
224
+ grid: gridConfigSchema.optional(),
225
+ /**
226
+ * Chart width in characters.
227
+ * @default 40
228
+ */
229
+ width: z.number().int().positive().default(40),
230
+ /**
231
+ * Chart height in lines.
232
+ * @default 10
233
+ */
234
+ height: z.number().int().positive().default(10),
235
+ /**
236
+ * Show values on data points.
237
+ * @default false
238
+ */
239
+ showValues: z.boolean().default(false),
240
+ /**
241
+ * Show axes.
242
+ * @default true
243
+ */
244
+ showAxes: z.boolean().default(true),
245
+ /**
246
+ * Line/area rendering style.
247
+ * @default "blocks"
248
+ */
249
+ lineStyle: lineStyleSchema.default("blocks"),
250
+ /**
251
+ * Default bar style for series without explicit style.
252
+ * @default "block"
253
+ */
254
+ barStyle: barStyleSchema.default("block"),
255
+ /**
256
+ * Scatter plot rendering style.
257
+ * @default "dots"
258
+ */
259
+ scatterStyle: scatterStyleSchema.default("dots"),
260
+ /**
261
+ * Heatmap rendering style.
262
+ * @default "blocks"
263
+ */
264
+ heatmapStyle: heatmapStyleSchema.default("blocks"),
265
+ /**
266
+ * Center label for donut charts.
267
+ * Displayed in the center of the donut.
268
+ */
269
+ centerLabel: z.string().optional(),
270
+ /**
271
+ * Inner radius ratio for donut charts (0-0.9).
272
+ * 0 = pie chart, 0.5 = typical donut.
273
+ * @default 0.5
274
+ */
275
+ innerRadius: z.number().min(0).max(0.9).default(0.5)
276
+ });
277
+
278
+ // src/layout/bar.ts
279
+ import { getStringWidth } from "@tuicomponents/core";
280
+
281
+ // src/core/scaling.ts
282
+ var NICE_INTERVALS = [1, 2, 2.5, 5, 10];
283
+ function findNiceStep(rawStep) {
284
+ if (rawStep === 0) return 1;
285
+ const magnitude = Math.pow(10, Math.floor(Math.log10(Math.abs(rawStep))));
286
+ const normalized = rawStep / magnitude;
287
+ for (const interval of NICE_INTERVALS) {
288
+ if (interval >= normalized) {
289
+ return interval * magnitude;
290
+ }
291
+ }
292
+ return 10 * magnitude;
293
+ }
294
+ function floorToStep(value, step) {
295
+ return Math.floor(value / step) * step;
296
+ }
297
+ function ceilToStep(value, step) {
298
+ return Math.ceil(value / step) * step;
299
+ }
300
+ function computeNiceTicks(options) {
301
+ const {
302
+ dataMin,
303
+ dataMax,
304
+ tickCount = 5,
305
+ includeZero = true,
306
+ forceMin,
307
+ forceMax
308
+ } = options;
309
+ if (dataMin === dataMax && forceMin === void 0 && forceMax === void 0) {
310
+ const center = dataMin;
311
+ const half = center === 0 ? 5 : Math.abs(center) * 0.5;
312
+ const ticks2 = [center - half, center, center + half];
313
+ return {
314
+ min: ticks2[0] ?? center - half,
315
+ max: ticks2[2] ?? center + half,
316
+ step: half,
317
+ ticks: ticks2
318
+ };
319
+ }
320
+ let effectiveMin = dataMin;
321
+ let effectiveMax = dataMax;
322
+ if (includeZero) {
323
+ if (effectiveMin > 0) effectiveMin = 0;
324
+ if (effectiveMax < 0) effectiveMax = 0;
325
+ }
326
+ if (forceMin !== void 0) effectiveMin = forceMin;
327
+ if (forceMax !== void 0) effectiveMax = forceMax;
328
+ const range = effectiveMax - effectiveMin;
329
+ const rawStep = range / Math.max(1, tickCount - 1);
330
+ const niceStep = findNiceStep(rawStep);
331
+ let niceMin = forceMin ?? floorToStep(effectiveMin, niceStep);
332
+ let niceMax = forceMax ?? ceilToStep(effectiveMax, niceStep);
333
+ if (niceMin > dataMin) niceMin -= niceStep;
334
+ if (niceMax < dataMax) niceMax += niceStep;
335
+ const ticks = [];
336
+ const epsilon = niceStep * 1e-10;
337
+ for (let value = niceMin; value <= niceMax + epsilon; value += niceStep) {
338
+ const roundedValue = Math.round(value * 1e12) / 1e12;
339
+ ticks.push(roundedValue);
340
+ }
341
+ return {
342
+ min: niceMin,
343
+ max: niceMax,
344
+ step: niceStep,
345
+ ticks
346
+ };
347
+ }
348
+ function formatTickValue(value, format = "number", decimals) {
349
+ switch (format) {
350
+ case "percent":
351
+ return `${(value * 100).toFixed(decimals ?? 0)}%`;
352
+ case "compact": {
353
+ const abs = Math.abs(value);
354
+ if (abs >= 1e9) {
355
+ return `${(value / 1e9).toFixed(decimals ?? 1)}B`;
356
+ }
357
+ if (abs >= 1e6) {
358
+ return `${(value / 1e6).toFixed(decimals ?? 1)}M`;
359
+ }
360
+ if (abs >= 1e3) {
361
+ return `${(value / 1e3).toFixed(decimals ?? 1)}K`;
362
+ }
363
+ return value.toFixed(decimals ?? 0);
364
+ }
365
+ case "currency":
366
+ return `$${value.toLocaleString("en-US", {
367
+ minimumFractionDigits: decimals ?? 0,
368
+ maximumFractionDigits: decimals ?? 0
369
+ })}`;
370
+ case "number":
371
+ default:
372
+ if (decimals !== void 0) {
373
+ return value.toFixed(decimals);
374
+ }
375
+ if (Number.isInteger(value)) {
376
+ return value.toLocaleString("en-US");
377
+ }
378
+ return value.toLocaleString("en-US", {
379
+ minimumFractionDigits: 0,
380
+ maximumFractionDigits: 2
381
+ });
382
+ }
383
+ }
384
+ function scaleValue(value, min, max, size) {
385
+ if (max === min) return size / 2;
386
+ return (value - min) / (max - min) * size;
387
+ }
388
+ function unscaleValue(position, min, max, size) {
389
+ if (size === 0) return min;
390
+ return min + position / size * (max - min);
391
+ }
392
+
393
+ // src/core/chars.ts
394
+ var AXIS_CHARS = {
395
+ /** Y-axis line (│) */
396
+ vertical: "\u2502",
397
+ /** X-axis line (─) */
398
+ horizontal: "\u2500",
399
+ /** Origin corner (└) */
400
+ origin: "\u2514",
401
+ /** Y-axis tick (├) - points right */
402
+ yTick: "\u251C",
403
+ /** X-axis tick (┬) - points up */
404
+ xTick: "\u252C",
405
+ /** Grid intersection (┼) */
406
+ cross: "\u253C",
407
+ /** Y-axis with grid (┤) - points left */
408
+ yTickLeft: "\u2524",
409
+ /** X-axis tick (┴) - points down */
410
+ xTickDown: "\u2534"
411
+ };
412
+ var HEIGHT_BLOCKS = [
413
+ "\u2581",
414
+ // 1/8
415
+ "\u2582",
416
+ // 2/8
417
+ "\u2583",
418
+ // 3/8
419
+ "\u2584",
420
+ // 4/8
421
+ "\u2585",
422
+ // 5/8
423
+ "\u2586",
424
+ // 6/8
425
+ "\u2587",
426
+ // 7/8
427
+ "\u2588"
428
+ // 8/8 (full)
429
+ ];
430
+ var BAR_CHARS = {
431
+ /** Solid block █ */
432
+ block: "\u2588",
433
+ /** Dark shade ▓ */
434
+ shaded: "\u2593",
435
+ /** Light shade ░ */
436
+ light: "\u2591",
437
+ /** Hash # */
438
+ hash: "#",
439
+ /** Equals = */
440
+ equals: "=",
441
+ /** Arrow > */
442
+ arrow: ">"
443
+ };
444
+ var BRAILLE_BASE = 10240;
445
+ var BRAILLE_DOTS = {
446
+ topLeft: 1,
447
+ upperLeft: 2,
448
+ lowerLeft: 3,
449
+ bottomLeft: 7,
450
+ topRight: 4,
451
+ upperRight: 5,
452
+ lowerRight: 6,
453
+ bottomRight: 8
454
+ };
455
+ function valueToBlock(normalized) {
456
+ if (normalized <= 0) return " ";
457
+ const index = Math.min(7, Math.floor(normalized * 8));
458
+ return HEIGHT_BLOCKS[index] ?? "\u2588";
459
+ }
460
+ function getBarChar(style) {
461
+ return BAR_CHARS[style];
462
+ }
463
+ function toBrailleChar(dots) {
464
+ let pattern = 0;
465
+ for (const dot of dots) {
466
+ if (dot >= 1 && dot <= 8) {
467
+ pattern |= 1 << dot - 1;
468
+ }
469
+ }
470
+ return String.fromCharCode(BRAILLE_BASE + pattern);
471
+ }
472
+ var SERIES_STYLES = [
473
+ { char: "\u2588", useBackticks: false },
474
+ // Solid, plain
475
+ { char: "\u2588", useBackticks: true },
476
+ // Solid, highlighted
477
+ { char: "\u2593", useBackticks: false },
478
+ // Shaded, plain
479
+ { char: "\u2591", useBackticks: true }
480
+ // Light, highlighted
481
+ ];
482
+ var SCATTER_MARKERS = {
483
+ circle: "\u25CF",
484
+ square: "\u25A0",
485
+ triangle: "\u25B2",
486
+ diamond: "\u25C6",
487
+ plus: "+"
488
+ };
489
+ var SCATTER_MARKER_SEQUENCE = ["\u25CF", "\u25A0", "\u25B2", "\u25C6", "+"];
490
+ var HEATMAP_BLOCKS = [" ", "\u2591", "\u2592", "\u2593", "\u2588"];
491
+ var HEATMAP_ASCII = [" ", ".", ":", "*", "#"];
492
+ function valueToHeatmapChar(normalized, style) {
493
+ const chars = style === "blocks" ? HEATMAP_BLOCKS : HEATMAP_ASCII;
494
+ if (normalized <= 0) return chars[0];
495
+ if (normalized >= 1) return chars[chars.length - 1] ?? " ";
496
+ const index = Math.min(
497
+ chars.length - 1,
498
+ Math.floor(normalized * chars.length)
499
+ );
500
+ return chars[index] ?? " ";
501
+ }
502
+ var LINE_CHARS = {
503
+ /** Point marker */
504
+ point: "\u25CF",
505
+ /** Horizontal line */
506
+ horizontal: "\u2500",
507
+ /** Vertical line */
508
+ vertical: "\u2502",
509
+ /** Rising diagonal (approximation) */
510
+ rising: "\u2571",
511
+ /** Falling diagonal (approximation) */
512
+ falling: "\u2572"
513
+ };
514
+ var BRAILLE_DOT_MAP = {
515
+ "0,0": 1,
516
+ "0,1": 2,
517
+ "0,2": 3,
518
+ "0,3": 7,
519
+ "1,0": 4,
520
+ "1,1": 5,
521
+ "1,2": 6,
522
+ "1,3": 8
523
+ };
524
+ var BrailleCanvas = class {
525
+ /** Width in character cells */
526
+ width;
527
+ /** Height in character cells */
528
+ height;
529
+ /** Dot grid (width*2 x height*4) */
530
+ dots;
531
+ /** Series index for each dot (for coloring) */
532
+ seriesIndices;
533
+ constructor(width, height) {
534
+ this.width = width;
535
+ this.height = height;
536
+ const dotWidth = width * 2;
537
+ const dotHeight = height * 4;
538
+ this.dots = Array.from(
539
+ { length: dotHeight },
540
+ () => Array.from({ length: dotWidth }, () => false)
541
+ );
542
+ this.seriesIndices = Array.from(
543
+ { length: dotHeight },
544
+ () => Array.from({ length: dotWidth }, () => null)
545
+ );
546
+ }
547
+ /**
548
+ * Set a dot at the given dot coordinates.
549
+ */
550
+ setDot(dotX, dotY, seriesIndex = 0) {
551
+ if (dotX >= 0 && dotX < this.width * 2 && dotY >= 0 && dotY < this.height * 4) {
552
+ const dotRow = this.dots[dotY];
553
+ const seriesRow = this.seriesIndices[dotY];
554
+ if (dotRow && seriesRow) {
555
+ dotRow[dotX] = true;
556
+ seriesRow[dotX] = seriesIndex;
557
+ }
558
+ }
559
+ }
560
+ /**
561
+ * Draw a line between two points using Bresenham's algorithm.
562
+ * Coordinates are in dot space (width*2 x height*4).
563
+ */
564
+ drawLine(x0, y0, x1, y1, seriesIndex = 0) {
565
+ const dx = Math.abs(x1 - x0);
566
+ const dy = Math.abs(y1 - y0);
567
+ const sx = x0 < x1 ? 1 : -1;
568
+ const sy = y0 < y1 ? 1 : -1;
569
+ let err = dx - dy;
570
+ let x = x0;
571
+ let y = y0;
572
+ while (true) {
573
+ this.setDot(x, y, seriesIndex);
574
+ if (x === x1 && y === y1) break;
575
+ const e2 = 2 * err;
576
+ if (e2 > -dy) {
577
+ err -= dy;
578
+ x += sx;
579
+ }
580
+ if (e2 < dx) {
581
+ err += dx;
582
+ y += sy;
583
+ }
584
+ }
585
+ }
586
+ /**
587
+ * Draw a point marker (fills more dots for visibility).
588
+ */
589
+ drawPoint(dotX, dotY, seriesIndex = 0) {
590
+ for (let dy = -1; dy <= 1; dy++) {
591
+ for (let dx = -1; dx <= 1; dx++) {
592
+ this.setDot(dotX + dx, dotY + dy, seriesIndex);
593
+ }
594
+ }
595
+ }
596
+ /**
597
+ * Draw a circle outline using the midpoint circle algorithm.
598
+ * Coordinates are in dot space (width*2 x height*4).
599
+ *
600
+ * @param centerX - Center X in dot coordinates
601
+ * @param centerY - Center Y in dot coordinates
602
+ * @param radius - Radius in dot units
603
+ * @param seriesIndex - Series index for coloring
604
+ */
605
+ drawCircle(centerX, centerY, radius, seriesIndex = 0) {
606
+ if (radius <= 0) return;
607
+ let x = radius;
608
+ let y = 0;
609
+ let err = 1 - radius;
610
+ while (x >= y) {
611
+ this.setDot(centerX + x, centerY + y, seriesIndex);
612
+ this.setDot(centerX - x, centerY + y, seriesIndex);
613
+ this.setDot(centerX + x, centerY - y, seriesIndex);
614
+ this.setDot(centerX - x, centerY - y, seriesIndex);
615
+ this.setDot(centerX + y, centerY + x, seriesIndex);
616
+ this.setDot(centerX - y, centerY + x, seriesIndex);
617
+ this.setDot(centerX + y, centerY - x, seriesIndex);
618
+ this.setDot(centerX - y, centerY - x, seriesIndex);
619
+ y++;
620
+ if (err < 0) {
621
+ err += 2 * y + 1;
622
+ } else {
623
+ x--;
624
+ err += 2 * (y - x) + 1;
625
+ }
626
+ }
627
+ }
628
+ /**
629
+ * Draw an arc (portion of a circle).
630
+ * Angles are in radians, 0 = top (12 o'clock), increasing clockwise.
631
+ *
632
+ * @param centerX - Center X in dot coordinates
633
+ * @param centerY - Center Y in dot coordinates
634
+ * @param radius - Radius in dot units
635
+ * @param startAngle - Start angle in radians (0 = top)
636
+ * @param endAngle - End angle in radians
637
+ * @param seriesIndex - Series index for coloring
638
+ */
639
+ drawArc(centerX, centerY, radius, startAngle, endAngle, seriesIndex = 0) {
640
+ if (radius <= 0) return;
641
+ const twoPi = Math.PI * 2;
642
+ startAngle = (startAngle % twoPi + twoPi) % twoPi;
643
+ endAngle = (endAngle % twoPi + twoPi) % twoPi;
644
+ const arcLength = endAngle > startAngle ? endAngle - startAngle : twoPi - startAngle + endAngle;
645
+ const steps = Math.max(8, Math.ceil(radius * arcLength * 0.5));
646
+ for (let i = 0; i <= steps; i++) {
647
+ const t = i / steps;
648
+ let angle;
649
+ if (endAngle > startAngle) {
650
+ angle = startAngle + t * (endAngle - startAngle);
651
+ } else {
652
+ angle = startAngle + t * (twoPi - startAngle + endAngle);
653
+ if (angle >= twoPi) angle -= twoPi;
654
+ }
655
+ const mathAngle = angle - Math.PI / 2;
656
+ const x = Math.round(centerX + radius * Math.cos(mathAngle));
657
+ const y = Math.round(centerY + radius * Math.sin(mathAngle));
658
+ this.setDot(x, y, seriesIndex);
659
+ }
660
+ }
661
+ /**
662
+ * Fill a wedge (pie slice) from center to edge.
663
+ * Angles are in radians, 0 = top (12 o'clock), increasing clockwise.
664
+ *
665
+ * @param centerX - Center X in dot coordinates
666
+ * @param centerY - Center Y in dot coordinates
667
+ * @param radius - Outer radius in dot units
668
+ * @param startAngle - Start angle in radians (0 = top)
669
+ * @param endAngle - End angle in radians
670
+ * @param seriesIndex - Series index for coloring
671
+ * @param innerRadius - Inner radius for donut (0 for pie)
672
+ */
673
+ fillWedge(centerX, centerY, radius, startAngle, endAngle, seriesIndex = 0, innerRadius = 0) {
674
+ if (radius <= 0) return;
675
+ const twoPi = Math.PI * 2;
676
+ startAngle = (startAngle % twoPi + twoPi) % twoPi;
677
+ endAngle = (endAngle % twoPi + twoPi) % twoPi;
678
+ const arcLength = endAngle > startAngle ? endAngle - startAngle : twoPi - startAngle + endAngle;
679
+ const angleSteps = Math.max(8, Math.ceil(radius * arcLength * 0.3));
680
+ for (let i = 0; i <= angleSteps; i++) {
681
+ const t = i / angleSteps;
682
+ let angle;
683
+ if (endAngle > startAngle) {
684
+ angle = startAngle + t * (endAngle - startAngle);
685
+ } else {
686
+ angle = startAngle + t * (twoPi - startAngle + endAngle);
687
+ if (angle >= twoPi) angle -= twoPi;
688
+ }
689
+ const mathAngle = angle - Math.PI / 2;
690
+ const cos = Math.cos(mathAngle);
691
+ const sin = Math.sin(mathAngle);
692
+ const startR = Math.ceil(innerRadius);
693
+ for (let r = startR; r <= radius; r++) {
694
+ const x = Math.round(centerX + r * cos);
695
+ const y = Math.round(centerY + r * sin);
696
+ this.setDot(x, y, seriesIndex);
697
+ }
698
+ }
699
+ }
700
+ /**
701
+ * Get the braille character at the given character cell.
702
+ */
703
+ getChar(charX, charY) {
704
+ const dots = [];
705
+ const dotBaseX = charX * 2;
706
+ const dotBaseY = charY * 4;
707
+ for (let dy = 0; dy < 4; dy++) {
708
+ for (let dx = 0; dx < 2; dx++) {
709
+ const dotX = dotBaseX + dx;
710
+ const dotY = dotBaseY + dy;
711
+ const dotRow = this.dots[dotY];
712
+ if (dotY >= 0 && dotY < this.height * 4 && dotX >= 0 && dotX < this.width * 2 && dotRow?.[dotX]) {
713
+ const dotNum = BRAILLE_DOT_MAP[`${String(dx)},${String(dy)}`];
714
+ if (dotNum) {
715
+ dots.push(dotNum);
716
+ }
717
+ }
718
+ }
719
+ }
720
+ if (dots.length === 0) {
721
+ return " ";
722
+ }
723
+ return toBrailleChar(dots);
724
+ }
725
+ /**
726
+ * Get the dominant series index for a character cell.
727
+ */
728
+ getSeriesIndex(charX, charY) {
729
+ const counts = /* @__PURE__ */ new Map();
730
+ const dotBaseX = charX * 2;
731
+ const dotBaseY = charY * 4;
732
+ for (let dy = 0; dy < 4; dy++) {
733
+ for (let dx = 0; dx < 2; dx++) {
734
+ const dotX = dotBaseX + dx;
735
+ const dotY = dotBaseY + dy;
736
+ if (dotY >= 0 && dotY < this.height * 4 && dotX >= 0 && dotX < this.width * 2) {
737
+ const seriesRow = this.seriesIndices[dotY];
738
+ const idx = seriesRow?.[dotX];
739
+ if (idx !== null && idx !== void 0) {
740
+ counts.set(idx, (counts.get(idx) ?? 0) + 1);
741
+ }
742
+ }
743
+ }
744
+ }
745
+ let maxCount = 0;
746
+ let maxIndex = null;
747
+ for (const [idx, count] of counts) {
748
+ if (count > maxCount) {
749
+ maxCount = count;
750
+ maxIndex = idx;
751
+ }
752
+ }
753
+ return maxIndex;
754
+ }
755
+ /**
756
+ * Render the entire canvas to a 2D array of characters.
757
+ */
758
+ render() {
759
+ const chars = [];
760
+ const indices = [];
761
+ for (let y = 0; y < this.height; y++) {
762
+ const row = [];
763
+ const indexRow = [];
764
+ for (let x = 0; x < this.width; x++) {
765
+ row.push(this.getChar(x, y));
766
+ indexRow.push(this.getSeriesIndex(x, y));
767
+ }
768
+ chars.push(row);
769
+ indices.push(indexRow);
770
+ }
771
+ return { chars, seriesIndices: indices };
772
+ }
773
+ };
774
+
775
+ // src/layout/bar.ts
776
+ function computeBarLayout(input) {
777
+ const isVertical = input.type === "bar-vertical";
778
+ const series = input.series;
779
+ const allValues = [];
780
+ const categorySet = /* @__PURE__ */ new Set();
781
+ for (const s of series) {
782
+ for (const point of s.data) {
783
+ allValues.push(point.y);
784
+ categorySet.add(String(point.x));
785
+ }
786
+ }
787
+ const categories = Array.from(categorySet);
788
+ const dataMin = Math.min(0, ...allValues);
789
+ const dataMax = Math.max(...allValues);
790
+ const yScale = computeNiceTicks({
791
+ dataMin,
792
+ dataMax,
793
+ tickCount: input.yAxis?.tickCount ?? 5,
794
+ includeZero: true,
795
+ forceMin: input.yAxis?.min,
796
+ forceMax: input.yAxis?.max
797
+ });
798
+ let maxLabelWidth = 0;
799
+ for (const category of categories) {
800
+ const width = getStringWidth(category);
801
+ if (width > maxLabelWidth) {
802
+ maxLabelWidth = width;
803
+ }
804
+ }
805
+ const barAreaSize = isVertical ? input.height - 2 : input.width - maxLabelWidth - 3;
806
+ const bars = [];
807
+ const formattedValues = [];
808
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
809
+ const s = series[seriesIndex];
810
+ if (!s) continue;
811
+ const styleIndex = seriesIndex % SERIES_STYLES.length;
812
+ const styleInfo = SERIES_STYLES[styleIndex];
813
+ if (!styleInfo) continue;
814
+ const barChar = s.style ? getBarChar(s.style) : styleInfo.char;
815
+ const useBackticks = s.style ? false : styleInfo.useBackticks;
816
+ for (let pointIndex = 0; pointIndex < s.data.length; pointIndex++) {
817
+ const point = s.data[pointIndex];
818
+ if (!point) continue;
819
+ const value = point.y;
820
+ const label = point.label ?? String(point.x);
821
+ const normalizedValue = scaleValue(
822
+ value,
823
+ yScale.min,
824
+ yScale.max,
825
+ barAreaSize
826
+ );
827
+ const length = Math.max(0, Math.round(normalizedValue));
828
+ const format = input.yAxis?.format ?? "number";
829
+ const decimals = input.yAxis?.decimals;
830
+ const formattedValue = formatTickValue(value, format, decimals);
831
+ formattedValues.push(formattedValue);
832
+ const percentage = yScale.max !== yScale.min ? (value - yScale.min) / (yScale.max - yScale.min) * 100 : 0;
833
+ bars.push({
834
+ seriesIndex,
835
+ pointIndex,
836
+ label,
837
+ value,
838
+ length,
839
+ formattedValue,
840
+ barChar,
841
+ useBackticks,
842
+ percentage
843
+ });
844
+ }
845
+ }
846
+ let maxValueWidth = 0;
847
+ for (const formatted of formattedValues) {
848
+ const width = getStringWidth(formatted);
849
+ if (width > maxValueWidth) {
850
+ maxValueWidth = width;
851
+ }
852
+ }
853
+ return {
854
+ type: isVertical ? "bar-vertical" : "bar",
855
+ bars,
856
+ categories,
857
+ maxLabelWidth,
858
+ maxValueWidth,
859
+ barAreaSize,
860
+ yScale,
861
+ showValues: input.showValues,
862
+ width: input.width,
863
+ height: input.height
864
+ };
865
+ }
866
+ function groupBarsByCategory(layout) {
867
+ const groups = /* @__PURE__ */ new Map();
868
+ for (const bar of layout.bars) {
869
+ const existing = groups.get(bar.label);
870
+ if (existing) {
871
+ existing.push(bar);
872
+ } else {
873
+ groups.set(bar.label, [bar]);
874
+ }
875
+ }
876
+ return groups;
877
+ }
878
+
879
+ // src/layout/stacked-bar.ts
880
+ import { getStringWidth as getStringWidth2 } from "@tuicomponents/core";
881
+ function computeStackedBarLayout(input) {
882
+ const isVertical = input.type === "bar-stacked-vertical";
883
+ const series = input.series;
884
+ const categoryTotals = /* @__PURE__ */ new Map();
885
+ const categoryData = /* @__PURE__ */ new Map();
886
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
887
+ const s = series[seriesIndex];
888
+ if (!s) continue;
889
+ for (const point of s.data) {
890
+ const category = String(point.x);
891
+ const value = point.y;
892
+ const currentTotal = categoryTotals.get(category) ?? 0;
893
+ categoryTotals.set(category, currentTotal + value);
894
+ let seriesMap = categoryData.get(category);
895
+ if (!seriesMap) {
896
+ seriesMap = /* @__PURE__ */ new Map();
897
+ categoryData.set(category, seriesMap);
898
+ }
899
+ seriesMap.set(seriesIndex, value);
900
+ }
901
+ }
902
+ const categories = Array.from(categoryTotals.keys());
903
+ const maxTotal = Math.max(...categoryTotals.values());
904
+ const yScale = computeNiceTicks({
905
+ dataMin: 0,
906
+ dataMax: maxTotal,
907
+ tickCount: input.yAxis?.tickCount ?? 5,
908
+ includeZero: true,
909
+ forceMin: input.yAxis?.min,
910
+ forceMax: input.yAxis?.max
911
+ });
912
+ let maxLabelWidth = 0;
913
+ for (const category of categories) {
914
+ const width = getStringWidth2(category);
915
+ if (width > maxLabelWidth) {
916
+ maxLabelWidth = width;
917
+ }
918
+ }
919
+ const barAreaSize = isVertical ? input.height - 2 : input.width - maxLabelWidth - 3;
920
+ const stacks = [];
921
+ let maxValueWidth = 0;
922
+ for (const category of categories) {
923
+ const seriesMap = categoryData.get(category);
924
+ const total = categoryTotals.get(category);
925
+ if (!seriesMap || total === void 0) continue;
926
+ const segments = [];
927
+ let cumulativeValue = 0;
928
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
929
+ const value = seriesMap.get(seriesIndex) ?? 0;
930
+ if (value <= 0) continue;
931
+ const styleIndex = seriesIndex % SERIES_STYLES.length;
932
+ const styleInfo = SERIES_STYLES[styleIndex];
933
+ if (!styleInfo) continue;
934
+ const s = series[seriesIndex];
935
+ if (!s) continue;
936
+ const barChar = s.style ? getBarChar(s.style) : styleInfo.char;
937
+ const useBackticks = s.style ? false : styleInfo.useBackticks;
938
+ const startPos = scaleValue(
939
+ cumulativeValue,
940
+ yScale.min,
941
+ yScale.max,
942
+ barAreaSize
943
+ );
944
+ cumulativeValue += value;
945
+ const endPos = scaleValue(
946
+ cumulativeValue,
947
+ yScale.min,
948
+ yScale.max,
949
+ barAreaSize
950
+ );
951
+ const length = Math.max(1, Math.round(endPos - startPos));
952
+ const format = input.yAxis?.format ?? "number";
953
+ const decimals = input.yAxis?.decimals;
954
+ const formattedValue = formatTickValue(value, format, decimals);
955
+ const valueWidth = getStringWidth2(formattedValue);
956
+ if (valueWidth > maxValueWidth) {
957
+ maxValueWidth = valueWidth;
958
+ }
959
+ segments.push({
960
+ seriesIndex,
961
+ pointIndex: categories.indexOf(category),
962
+ label: category,
963
+ value,
964
+ length,
965
+ formattedValue,
966
+ barChar,
967
+ useBackticks,
968
+ percentage: total > 0 ? value / total * 100 : 0
969
+ });
970
+ }
971
+ stacks.push({
972
+ label: category,
973
+ segments,
974
+ total
975
+ });
976
+ }
977
+ const seriesNames = series.map((s) => s.name);
978
+ const seriesStyles = series.map((s, i) => {
979
+ const styleIndex = i % SERIES_STYLES.length;
980
+ const styleInfo = SERIES_STYLES[styleIndex];
981
+ if (!styleInfo) return null;
982
+ return {
983
+ char: s.style ? getBarChar(s.style) : styleInfo.char,
984
+ useBackticks: s.style ? false : styleInfo.useBackticks
985
+ };
986
+ }).filter(
987
+ (style) => style !== null
988
+ );
989
+ return {
990
+ type: isVertical ? "bar-stacked-vertical" : "bar-stacked",
991
+ stacks,
992
+ seriesNames,
993
+ seriesStyles,
994
+ maxLabelWidth,
995
+ maxValueWidth,
996
+ barAreaSize,
997
+ yScale,
998
+ showValues: input.showValues,
999
+ width: input.width,
1000
+ height: input.height
1001
+ };
1002
+ }
1003
+
1004
+ // src/layout/line.ts
1005
+ import { getStringWidth as getStringWidth3 } from "@tuicomponents/core";
1006
+ var LINE_DRAW = {
1007
+ horizontal: "\u2500",
1008
+ point: "\u25CF",
1009
+ rise: "\u2571",
1010
+ fall: "\u2572"
1011
+ };
1012
+ function computeLineLayout(input) {
1013
+ const series = input.series;
1014
+ const lineStyle = input.lineStyle;
1015
+ const allValues = [];
1016
+ const categorySet = /* @__PURE__ */ new Set();
1017
+ for (const s of series) {
1018
+ for (const point of s.data) {
1019
+ allValues.push(point.y);
1020
+ categorySet.add(String(point.x));
1021
+ }
1022
+ }
1023
+ const categories = Array.from(categorySet);
1024
+ const seriesNames = series.map((s) => s.name);
1025
+ const dataMin = Math.min(...allValues);
1026
+ const dataMax = Math.max(...allValues);
1027
+ const yScale = computeNiceTicks({
1028
+ dataMin,
1029
+ dataMax,
1030
+ tickCount: input.yAxis?.tickCount ?? 5,
1031
+ includeZero: input.yAxis?.min === void 0,
1032
+ forceMin: input.yAxis?.min,
1033
+ forceMax: input.yAxis?.max
1034
+ });
1035
+ let maxXLabelWidth = 0;
1036
+ for (const category of categories) {
1037
+ const width = getStringWidth3(category);
1038
+ if (width > maxXLabelWidth) {
1039
+ maxXLabelWidth = width;
1040
+ }
1041
+ }
1042
+ let yAxisWidth = 0;
1043
+ const format = input.yAxis?.format ?? "number";
1044
+ const decimals = input.yAxis?.decimals;
1045
+ for (const tick of yScale.ticks) {
1046
+ const label = formatTickValue(tick, format, decimals);
1047
+ const width = getStringWidth3(label);
1048
+ if (width > yAxisWidth) {
1049
+ yAxisWidth = width;
1050
+ }
1051
+ }
1052
+ yAxisWidth += 2;
1053
+ const chartHeight = input.height - 2;
1054
+ if (lineStyle === "braille") {
1055
+ return computeBrailleLayout(
1056
+ input,
1057
+ categories,
1058
+ seriesNames,
1059
+ series,
1060
+ yScale,
1061
+ yAxisWidth,
1062
+ chartHeight,
1063
+ format,
1064
+ decimals
1065
+ );
1066
+ } else {
1067
+ return computeBlocksLayout(
1068
+ input,
1069
+ categories,
1070
+ seriesNames,
1071
+ series,
1072
+ yScale,
1073
+ yAxisWidth,
1074
+ chartHeight,
1075
+ format,
1076
+ decimals,
1077
+ lineStyle
1078
+ );
1079
+ }
1080
+ }
1081
+ function computeBlocksLayout(input, categories, seriesNames, series, yScale, yAxisWidth, chartHeight, format, decimals, lineStyle) {
1082
+ const chartWidth = categories.length * 2 - 1;
1083
+ const points = [];
1084
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
1085
+ const s = series[seriesIndex];
1086
+ if (!s) continue;
1087
+ const seriesPoints = [];
1088
+ for (const point of s.data) {
1089
+ const categoryIndex = categories.indexOf(String(point.x));
1090
+ const colPos = categoryIndex * 2;
1091
+ const normalizedY = scaleValue(point.y, yScale.min, yScale.max, 1);
1092
+ const clampedY = Math.max(0, Math.min(1, normalizedY));
1093
+ seriesPoints.push({
1094
+ x: colPos,
1095
+ value: point.y,
1096
+ normalizedY: clampedY,
1097
+ seriesIndex
1098
+ });
1099
+ }
1100
+ seriesPoints.sort((a, b) => a.x - b.x);
1101
+ points.push(seriesPoints);
1102
+ }
1103
+ const grid = [];
1104
+ const seriesGrid = [];
1105
+ for (let row = 0; row < chartHeight; row++) {
1106
+ grid.push(Array(chartWidth).fill(" "));
1107
+ seriesGrid.push(Array(chartWidth).fill(null));
1108
+ }
1109
+ for (let seriesIndex = 0; seriesIndex < points.length; seriesIndex++) {
1110
+ const seriesPoints = points[seriesIndex];
1111
+ if (!seriesPoints) continue;
1112
+ for (let i = 0; i < seriesPoints.length; i++) {
1113
+ const p = seriesPoints[i];
1114
+ if (!p) continue;
1115
+ const row = Math.floor((1 - p.normalizedY) * (chartHeight - 1e-3));
1116
+ const col = p.x;
1117
+ if (row >= 0 && row < chartHeight && col >= 0 && col < chartWidth) {
1118
+ const gridRow = grid[row];
1119
+ const seriesGridRow = seriesGrid[row];
1120
+ if (!gridRow || !seriesGridRow) continue;
1121
+ gridRow[col] = lineStyle === "dots" ? "\u25CF" : LINE_DRAW.point;
1122
+ seriesGridRow[col] = seriesIndex;
1123
+ if (lineStyle !== "dots" && i < seriesPoints.length - 1) {
1124
+ const nextP = seriesPoints[i + 1];
1125
+ if (!nextP) continue;
1126
+ const nextRow = Math.floor(
1127
+ (1 - nextP.normalizedY) * (chartHeight - 1e-3)
1128
+ );
1129
+ const nextCol = nextP.x;
1130
+ if (nextCol > col + 1) {
1131
+ const rowDiff = nextRow - row;
1132
+ const colDiff = nextCol - col;
1133
+ for (let c = col + 1; c < nextCol; c++) {
1134
+ const t = (c - col) / colDiff;
1135
+ const interpRow = Math.round(row + rowDiff * t);
1136
+ const interpGridRow = grid[interpRow];
1137
+ const interpSeriesGridRow = seriesGrid[interpRow];
1138
+ if (interpRow >= 0 && interpRow < chartHeight && interpGridRow?.[c] === " ") {
1139
+ if (rowDiff < 0) {
1140
+ interpGridRow[c] = LINE_DRAW.rise;
1141
+ } else if (rowDiff > 0) {
1142
+ interpGridRow[c] = LINE_DRAW.fall;
1143
+ } else {
1144
+ interpGridRow[c] = LINE_DRAW.horizontal;
1145
+ }
1146
+ if (interpSeriesGridRow) {
1147
+ interpSeriesGridRow[c] = seriesIndex;
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ const rows = [];
1157
+ for (let rowIndex = 0; rowIndex < chartHeight; rowIndex++) {
1158
+ const rowBottom = 1 - (rowIndex + 1) / chartHeight;
1159
+ const rowTop = 1 - rowIndex / chartHeight;
1160
+ let yLabel;
1161
+ let yValue = yScale.min + rowTop * (yScale.max - yScale.min);
1162
+ for (const tick of yScale.ticks) {
1163
+ const tickNormalized = (tick - yScale.min) / (yScale.max - yScale.min);
1164
+ if (tickNormalized > rowBottom && tickNormalized <= rowTop) {
1165
+ yLabel = formatTickValue(tick, format, decimals);
1166
+ yValue = tick;
1167
+ break;
1168
+ }
1169
+ }
1170
+ const chars = grid[rowIndex];
1171
+ const seriesIndices = seriesGrid[rowIndex];
1172
+ if (!chars || !seriesIndices) continue;
1173
+ const useBackticks = seriesIndices.map((idx) => {
1174
+ if (idx === null) return false;
1175
+ const styleIndex = idx % SERIES_STYLES.length;
1176
+ return SERIES_STYLES[styleIndex]?.useBackticks ?? false;
1177
+ });
1178
+ rows.push({ yLabel, yValue, chars, useBackticks, seriesIndices });
1179
+ }
1180
+ return {
1181
+ type: "line",
1182
+ lineStyle: input.lineStyle,
1183
+ categories,
1184
+ seriesNames,
1185
+ points,
1186
+ rows,
1187
+ yScale,
1188
+ maxXLabelWidth: Math.max(...categories.map((c) => getStringWidth3(c))),
1189
+ yAxisWidth,
1190
+ width: input.width,
1191
+ height: input.height,
1192
+ showValues: input.showValues
1193
+ };
1194
+ }
1195
+ function computeBrailleLayout(input, categories, seriesNames, series, yScale, yAxisWidth, chartHeight, format, decimals) {
1196
+ const charsPerCategory = 3;
1197
+ const chartWidth = categories.length * charsPerCategory;
1198
+ const points = [];
1199
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
1200
+ const s = series[seriesIndex];
1201
+ if (!s) continue;
1202
+ const seriesPoints = [];
1203
+ for (const point of s.data) {
1204
+ const categoryIndex = categories.indexOf(String(point.x));
1205
+ const colPos = categoryIndex * charsPerCategory + Math.floor(charsPerCategory / 2);
1206
+ const normalizedY = scaleValue(point.y, yScale.min, yScale.max, 1);
1207
+ const clampedY = Math.max(0, Math.min(1, normalizedY));
1208
+ seriesPoints.push({
1209
+ x: colPos,
1210
+ value: point.y,
1211
+ normalizedY: clampedY,
1212
+ seriesIndex
1213
+ });
1214
+ }
1215
+ seriesPoints.sort((a, b) => a.x - b.x);
1216
+ points.push(seriesPoints);
1217
+ }
1218
+ const canvas = new BrailleCanvas(chartWidth, chartHeight);
1219
+ for (let seriesIndex = 0; seriesIndex < points.length; seriesIndex++) {
1220
+ const seriesPoints = points[seriesIndex];
1221
+ if (!seriesPoints) continue;
1222
+ for (let i = 0; i < seriesPoints.length; i++) {
1223
+ const p = seriesPoints[i];
1224
+ if (!p) continue;
1225
+ const dotX = p.x * 2 + 1;
1226
+ const dotY = Math.round((1 - p.normalizedY) * (chartHeight * 4 - 1));
1227
+ if (i < seriesPoints.length - 1) {
1228
+ const nextP = seriesPoints[i + 1];
1229
+ if (nextP) {
1230
+ const nextDotX = nextP.x * 2 + 1;
1231
+ const nextDotY = Math.round(
1232
+ (1 - nextP.normalizedY) * (chartHeight * 4 - 1)
1233
+ );
1234
+ canvas.drawLine(dotX, dotY, nextDotX, nextDotY, seriesIndex);
1235
+ }
1236
+ }
1237
+ canvas.drawPoint(dotX, dotY, seriesIndex);
1238
+ }
1239
+ }
1240
+ const rendered = canvas.render();
1241
+ const rows = [];
1242
+ for (let rowIndex = 0; rowIndex < chartHeight; rowIndex++) {
1243
+ const rowBottom = 1 - (rowIndex + 1) / chartHeight;
1244
+ const rowTop = 1 - rowIndex / chartHeight;
1245
+ let yLabel;
1246
+ let yValue = yScale.min + rowTop * (yScale.max - yScale.min);
1247
+ for (const tick of yScale.ticks) {
1248
+ const tickNormalized = (tick - yScale.min) / (yScale.max - yScale.min);
1249
+ if (tickNormalized > rowBottom && tickNormalized <= rowTop) {
1250
+ yLabel = formatTickValue(tick, format, decimals);
1251
+ yValue = tick;
1252
+ break;
1253
+ }
1254
+ }
1255
+ const chars = rendered.chars[rowIndex] ?? [];
1256
+ const seriesIndices = rendered.seriesIndices[rowIndex] ?? [];
1257
+ const useBackticks = seriesIndices.map((idx) => {
1258
+ if (idx === null) return false;
1259
+ const styleIndex = idx % SERIES_STYLES.length;
1260
+ return SERIES_STYLES[styleIndex]?.useBackticks ?? false;
1261
+ });
1262
+ rows.push({ yLabel, yValue, chars, useBackticks, seriesIndices });
1263
+ }
1264
+ return {
1265
+ type: "line",
1266
+ lineStyle: input.lineStyle,
1267
+ categories,
1268
+ seriesNames,
1269
+ points,
1270
+ rows,
1271
+ yScale,
1272
+ maxXLabelWidth: Math.max(...categories.map((c) => getStringWidth3(c))),
1273
+ yAxisWidth,
1274
+ width: input.width,
1275
+ height: input.height,
1276
+ showValues: input.showValues
1277
+ };
1278
+ }
1279
+
1280
+ // src/layout/area.ts
1281
+ import { getStringWidth as getStringWidth4 } from "@tuicomponents/core";
1282
+ function computeAreaLayout(input) {
1283
+ const isStacked = input.type === "area-stacked";
1284
+ const series = input.series;
1285
+ const lineStyle = input.lineStyle;
1286
+ const categoryData = /* @__PURE__ */ new Map();
1287
+ const categories = [];
1288
+ for (const s of series) {
1289
+ for (const point of s.data) {
1290
+ const category = String(point.x);
1291
+ if (!categoryData.has(category)) {
1292
+ categoryData.set(
1293
+ category,
1294
+ new Array(series.length).fill(0)
1295
+ );
1296
+ categories.push(category);
1297
+ }
1298
+ }
1299
+ }
1300
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
1301
+ const s = series[seriesIndex];
1302
+ if (!s) continue;
1303
+ for (const point of s.data) {
1304
+ const category = String(point.x);
1305
+ const values = categoryData.get(category);
1306
+ if (!values) continue;
1307
+ values[seriesIndex] = point.y;
1308
+ }
1309
+ }
1310
+ let maxValue = 0;
1311
+ const columns = [];
1312
+ for (let i = 0; i < categories.length; i++) {
1313
+ const category = categories[i];
1314
+ if (!category) continue;
1315
+ const values = categoryData.get(category);
1316
+ if (!values) continue;
1317
+ const cumulativeValues = [];
1318
+ let cumulative = 0;
1319
+ for (const value of values) {
1320
+ if (isStacked) {
1321
+ cumulative += value;
1322
+ cumulativeValues.push(cumulative);
1323
+ } else {
1324
+ cumulativeValues.push(value);
1325
+ if (value > cumulative) cumulative = value;
1326
+ }
1327
+ }
1328
+ if (cumulative > maxValue) maxValue = cumulative;
1329
+ columns.push({
1330
+ x: i,
1331
+ label: category,
1332
+ cumulativeValues,
1333
+ normalizedHeights: []
1334
+ // Fill in after we have scale
1335
+ });
1336
+ }
1337
+ const yScale = computeNiceTicks({
1338
+ dataMin: 0,
1339
+ dataMax: maxValue,
1340
+ tickCount: input.yAxis?.tickCount ?? 5,
1341
+ includeZero: true,
1342
+ forceMin: input.yAxis?.min,
1343
+ forceMax: input.yAxis?.max
1344
+ });
1345
+ for (const column of columns) {
1346
+ column.normalizedHeights = column.cumulativeValues.map(
1347
+ (v) => scaleValue(v, yScale.min, yScale.max, 1)
1348
+ );
1349
+ }
1350
+ let yAxisWidth = 0;
1351
+ const format = input.yAxis?.format ?? "number";
1352
+ const decimals = input.yAxis?.decimals;
1353
+ for (const tick of yScale.ticks) {
1354
+ const label = formatTickValue(tick, format, decimals);
1355
+ const width = getStringWidth4(label);
1356
+ if (width > yAxisWidth) {
1357
+ yAxisWidth = width;
1358
+ }
1359
+ }
1360
+ yAxisWidth += 2;
1361
+ const seriesNames = series.map((s) => s.name);
1362
+ const seriesStyles = series.map((_, i) => {
1363
+ const styleIndex = i % SERIES_STYLES.length;
1364
+ const style = SERIES_STYLES[styleIndex];
1365
+ if (!style) throw new Error(`Invalid style index: ${String(styleIndex)}`);
1366
+ return style;
1367
+ });
1368
+ const chartHeight = input.height - 2;
1369
+ const rows = [];
1370
+ for (let rowIndex = 0; rowIndex < chartHeight; rowIndex++) {
1371
+ const rowBottom = 1 - (rowIndex + 1) / chartHeight;
1372
+ const rowTop = 1 - rowIndex / chartHeight;
1373
+ let yLabel;
1374
+ for (const tick of yScale.ticks) {
1375
+ const tickNormalized = (tick - yScale.min) / (yScale.max - yScale.min);
1376
+ if (tickNormalized > rowBottom && tickNormalized <= rowTop) {
1377
+ yLabel = formatTickValue(tick, format, decimals);
1378
+ break;
1379
+ }
1380
+ }
1381
+ const chars = [];
1382
+ const useBackticks = [];
1383
+ const fillChars = [];
1384
+ for (const column of columns) {
1385
+ let activeSeriesIndex = -1;
1386
+ let fillLevel = 0;
1387
+ for (let seriesIndex = column.normalizedHeights.length - 1; seriesIndex >= 0; seriesIndex--) {
1388
+ const height = column.normalizedHeights[seriesIndex];
1389
+ if (height === void 0) continue;
1390
+ const prevHeight = seriesIndex > 0 ? column.normalizedHeights[seriesIndex - 1] ?? 0 : 0;
1391
+ if (height > rowBottom && prevHeight < rowTop) {
1392
+ activeSeriesIndex = seriesIndex;
1393
+ const effectiveTop = Math.min(height, rowTop);
1394
+ const effectiveBottom = Math.max(prevHeight, rowBottom);
1395
+ fillLevel = (effectiveTop - effectiveBottom) / (rowTop - rowBottom);
1396
+ break;
1397
+ }
1398
+ }
1399
+ if (activeSeriesIndex >= 0) {
1400
+ const char = valueToBlock(fillLevel);
1401
+ const style = seriesStyles[activeSeriesIndex];
1402
+ if (!style) {
1403
+ chars.push(" ");
1404
+ useBackticks.push(false);
1405
+ fillChars.push(" ");
1406
+ } else {
1407
+ chars.push(char);
1408
+ useBackticks.push(style.useBackticks);
1409
+ fillChars.push(style.char);
1410
+ }
1411
+ } else {
1412
+ chars.push(" ");
1413
+ useBackticks.push(false);
1414
+ fillChars.push(" ");
1415
+ }
1416
+ }
1417
+ rows.push({
1418
+ yLabel,
1419
+ chars,
1420
+ useBackticks,
1421
+ fillChars
1422
+ });
1423
+ }
1424
+ return {
1425
+ type: isStacked ? "area-stacked" : "area",
1426
+ lineStyle,
1427
+ categories,
1428
+ seriesNames,
1429
+ seriesStyles,
1430
+ columns,
1431
+ rows,
1432
+ yScale,
1433
+ yAxisWidth,
1434
+ width: input.width,
1435
+ height: input.height,
1436
+ showValues: input.showValues
1437
+ };
1438
+ }
1439
+
1440
+ // src/layout/scatter.ts
1441
+ import { getStringWidth as getStringWidth5 } from "@tuicomponents/core";
1442
+ function computeScatterLayout(input) {
1443
+ const series = input.series;
1444
+ const scatterStyle = input.scatterStyle;
1445
+ const allXValues = [];
1446
+ const allYValues = [];
1447
+ for (const s of series) {
1448
+ for (const point of s.data) {
1449
+ const xVal = typeof point.x === "number" ? point.x : parseFloat(point.x);
1450
+ if (!isNaN(xVal)) {
1451
+ allXValues.push(xVal);
1452
+ }
1453
+ allYValues.push(point.y);
1454
+ }
1455
+ }
1456
+ if (allXValues.length === 0 || allYValues.length === 0) {
1457
+ return {
1458
+ type: "scatter",
1459
+ scatterStyle,
1460
+ seriesNames: series.map((s) => s.name),
1461
+ points: [],
1462
+ xScale: { min: 0, max: 100, step: 20, ticks: [0, 20, 40, 60, 80, 100] },
1463
+ yScale: { min: 0, max: 100, step: 20, ticks: [0, 20, 40, 60, 80, 100] },
1464
+ yAxisWidth: 6,
1465
+ chartWidth: input.width - 6,
1466
+ chartHeight: input.height - 2,
1467
+ width: input.width,
1468
+ height: input.height
1469
+ };
1470
+ }
1471
+ const seriesNames = series.map((s) => s.name);
1472
+ const xDataMin = Math.min(...allXValues);
1473
+ const xDataMax = Math.max(...allXValues);
1474
+ const yDataMin = Math.min(...allYValues);
1475
+ const yDataMax = Math.max(...allYValues);
1476
+ const xScale = computeNiceTicks({
1477
+ dataMin: xDataMin,
1478
+ dataMax: xDataMax,
1479
+ tickCount: input.xAxis?.tickCount ?? 5,
1480
+ includeZero: input.xAxis?.min === void 0,
1481
+ forceMin: input.xAxis?.min,
1482
+ forceMax: input.xAxis?.max
1483
+ });
1484
+ const yScale = computeNiceTicks({
1485
+ dataMin: yDataMin,
1486
+ dataMax: yDataMax,
1487
+ tickCount: input.yAxis?.tickCount ?? 5,
1488
+ includeZero: input.yAxis?.min === void 0,
1489
+ forceMin: input.yAxis?.min,
1490
+ forceMax: input.yAxis?.max
1491
+ });
1492
+ const yFormat = input.yAxis?.format ?? "number";
1493
+ const yDecimals = input.yAxis?.decimals;
1494
+ let yAxisWidth = 0;
1495
+ for (const tick of yScale.ticks) {
1496
+ const label = formatTickValue(tick, yFormat, yDecimals);
1497
+ const width = getStringWidth5(label);
1498
+ if (width > yAxisWidth) {
1499
+ yAxisWidth = width;
1500
+ }
1501
+ }
1502
+ yAxisWidth += 2;
1503
+ const chartHeight = input.height - 2;
1504
+ const chartWidth = input.width - yAxisWidth - 1;
1505
+ const points = [];
1506
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
1507
+ const s = series[seriesIndex];
1508
+ if (!s) continue;
1509
+ for (const point of s.data) {
1510
+ const xVal = typeof point.x === "number" ? point.x : parseFloat(point.x);
1511
+ if (isNaN(xVal)) continue;
1512
+ const normalizedX = scaleValue(xVal, xScale.min, xScale.max, 1);
1513
+ const normalizedY = scaleValue(point.y, yScale.min, yScale.max, 1);
1514
+ const charX = Math.round(
1515
+ Math.max(0, Math.min(1, normalizedX)) * (chartWidth - 1)
1516
+ );
1517
+ const charY = Math.round(
1518
+ (1 - Math.max(0, Math.min(1, normalizedY))) * (chartHeight - 1)
1519
+ );
1520
+ points.push({
1521
+ dataX: xVal,
1522
+ dataY: point.y,
1523
+ charX,
1524
+ charY,
1525
+ seriesIndex
1526
+ });
1527
+ }
1528
+ }
1529
+ if (scatterStyle === "braille") {
1530
+ return computeBrailleScatterLayout(
1531
+ input,
1532
+ seriesNames,
1533
+ points,
1534
+ xScale,
1535
+ yScale,
1536
+ yAxisWidth,
1537
+ chartWidth,
1538
+ chartHeight
1539
+ );
1540
+ } else {
1541
+ return computeDotsScatterLayout(
1542
+ input,
1543
+ seriesNames,
1544
+ points,
1545
+ xScale,
1546
+ yScale,
1547
+ yAxisWidth,
1548
+ chartWidth,
1549
+ chartHeight
1550
+ );
1551
+ }
1552
+ }
1553
+ function computeDotsScatterLayout(input, seriesNames, points, xScale, yScale, yAxisWidth, chartWidth, chartHeight) {
1554
+ const grid = [];
1555
+ const seriesIndices = [];
1556
+ for (let row = 0; row < chartHeight; row++) {
1557
+ grid.push(Array(chartWidth).fill(" "));
1558
+ seriesIndices.push(Array(chartWidth).fill(null));
1559
+ }
1560
+ for (const point of points) {
1561
+ if (point.charY >= 0 && point.charY < chartHeight && point.charX >= 0 && point.charX < chartWidth) {
1562
+ const marker = SCATTER_MARKER_SEQUENCE[point.seriesIndex % SCATTER_MARKER_SEQUENCE.length];
1563
+ if (!marker) continue;
1564
+ const gridRow = grid[point.charY];
1565
+ const seriesRow = seriesIndices[point.charY];
1566
+ if (!gridRow || !seriesRow) continue;
1567
+ gridRow[point.charX] = marker;
1568
+ seriesRow[point.charX] = point.seriesIndex;
1569
+ }
1570
+ }
1571
+ return {
1572
+ type: "scatter",
1573
+ scatterStyle: "dots",
1574
+ seriesNames,
1575
+ points,
1576
+ xScale,
1577
+ yScale,
1578
+ yAxisWidth,
1579
+ chartWidth,
1580
+ chartHeight,
1581
+ width: input.width,
1582
+ height: input.height,
1583
+ grid,
1584
+ seriesIndices
1585
+ };
1586
+ }
1587
+ function computeBrailleScatterLayout(input, seriesNames, points, xScale, yScale, yAxisWidth, chartWidth, chartHeight) {
1588
+ const canvas = new BrailleCanvas(chartWidth, chartHeight);
1589
+ for (const point of points) {
1590
+ const dotX = Math.round(
1591
+ scaleValue(point.dataX, xScale.min, xScale.max, chartWidth * 2 - 1)
1592
+ );
1593
+ const normalizedY = scaleValue(point.dataY, yScale.min, yScale.max, 1);
1594
+ const dotY = Math.round((1 - normalizedY) * (chartHeight * 4 - 1));
1595
+ canvas.drawPoint(dotX, dotY, point.seriesIndex);
1596
+ }
1597
+ const rendered = canvas.render();
1598
+ return {
1599
+ type: "scatter",
1600
+ scatterStyle: "braille",
1601
+ seriesNames,
1602
+ points,
1603
+ xScale,
1604
+ yScale,
1605
+ yAxisWidth,
1606
+ chartWidth,
1607
+ chartHeight,
1608
+ width: input.width,
1609
+ height: input.height,
1610
+ brailleChars: rendered.chars,
1611
+ brailleSeriesIndices: rendered.seriesIndices
1612
+ };
1613
+ }
1614
+
1615
+ // src/layout/pie.ts
1616
+ function computePieLayout(input) {
1617
+ const type = input.type;
1618
+ const series = input.series;
1619
+ const slicesData = [];
1620
+ for (const s of series) {
1621
+ for (const point of s.data) {
1622
+ const label = point.label ?? String(point.x);
1623
+ if (point.y > 0) {
1624
+ slicesData.push({ label, value: point.y });
1625
+ }
1626
+ }
1627
+ }
1628
+ const total = slicesData.reduce((sum, s) => sum + s.value, 0);
1629
+ if (total === 0 || slicesData.length === 0) {
1630
+ return {
1631
+ type,
1632
+ slices: [],
1633
+ total: 0,
1634
+ radius: 0,
1635
+ centerX: Math.floor(input.width / 2),
1636
+ centerY: Math.floor(input.height / 2),
1637
+ innerRadius: 0,
1638
+ ...input.centerLabel !== void 0 && {
1639
+ centerLabel: input.centerLabel
1640
+ },
1641
+ width: input.width,
1642
+ height: input.height,
1643
+ brailleChars: [],
1644
+ brailleSeriesIndices: [],
1645
+ seriesStyles: SERIES_STYLES
1646
+ };
1647
+ }
1648
+ const chartHeight = input.height - 2;
1649
+ const chartWidth = input.width;
1650
+ const maxRadiusY = Math.floor(chartHeight / 2);
1651
+ const maxRadiusX = Math.floor(chartWidth / 2);
1652
+ const radius = Math.min(maxRadiusX, maxRadiusY);
1653
+ const centerX = Math.floor(chartWidth / 2);
1654
+ const centerY = Math.floor(chartHeight / 2);
1655
+ const innerRadiusRatio = type === "donut" ? input.innerRadius : 0;
1656
+ const slices = [];
1657
+ let currentAngle = 0;
1658
+ for (let i = 0; i < slicesData.length; i++) {
1659
+ const data = slicesData[i];
1660
+ if (!data) continue;
1661
+ const percentage = data.value / total * 100;
1662
+ const arcAngle = data.value / total * Math.PI * 2;
1663
+ const style = SERIES_STYLES[i % SERIES_STYLES.length];
1664
+ if (!style) continue;
1665
+ slices.push({
1666
+ label: data.label,
1667
+ value: data.value,
1668
+ percentage,
1669
+ startAngle: currentAngle,
1670
+ endAngle: currentAngle + arcAngle,
1671
+ barChar: style.char,
1672
+ useBackticks: style.useBackticks,
1673
+ seriesIndex: i
1674
+ });
1675
+ currentAngle += arcAngle;
1676
+ }
1677
+ const canvas = new BrailleCanvas(chartWidth, chartHeight);
1678
+ const dotCenterX = centerX * 2;
1679
+ const dotCenterY = centerY * 4;
1680
+ const dotRadiusX = radius * 2;
1681
+ const dotRadiusY = radius * 4;
1682
+ const dotRadius = Math.min(dotRadiusX, dotRadiusY);
1683
+ const _dotInnerRadius = dotRadius * innerRadiusRatio;
1684
+ for (const slice of slices) {
1685
+ drawEllipticalWedge(
1686
+ canvas,
1687
+ dotCenterX,
1688
+ dotCenterY,
1689
+ dotRadiusX,
1690
+ dotRadiusY,
1691
+ slice.startAngle,
1692
+ slice.endAngle,
1693
+ slice.seriesIndex,
1694
+ dotRadiusX * innerRadiusRatio,
1695
+ dotRadiusY * innerRadiusRatio
1696
+ );
1697
+ }
1698
+ const rendered = canvas.render();
1699
+ return {
1700
+ type,
1701
+ slices,
1702
+ total,
1703
+ radius,
1704
+ centerX,
1705
+ centerY,
1706
+ innerRadius: radius * innerRadiusRatio,
1707
+ ...input.centerLabel !== void 0 && { centerLabel: input.centerLabel },
1708
+ width: input.width,
1709
+ height: input.height,
1710
+ brailleChars: rendered.chars,
1711
+ brailleSeriesIndices: rendered.seriesIndices,
1712
+ seriesStyles: SERIES_STYLES
1713
+ };
1714
+ }
1715
+ function drawEllipticalWedge(canvas, centerX, centerY, radiusX, radiusY, startAngle, endAngle, seriesIndex, innerRadiusX, innerRadiusY) {
1716
+ if (radiusX <= 0 || radiusY <= 0) return;
1717
+ const twoPi = Math.PI * 2;
1718
+ startAngle = (startAngle % twoPi + twoPi) % twoPi;
1719
+ endAngle = (endAngle % twoPi + twoPi) % twoPi;
1720
+ const arcLength = endAngle > startAngle ? endAngle - startAngle : twoPi - startAngle + endAngle;
1721
+ const avgRadius = (radiusX + radiusY) / 2;
1722
+ const angleSteps = Math.max(16, Math.ceil(avgRadius * arcLength * 0.4));
1723
+ for (let i = 0; i <= angleSteps; i++) {
1724
+ const t = i / angleSteps;
1725
+ let angle;
1726
+ if (endAngle > startAngle) {
1727
+ angle = startAngle + t * (endAngle - startAngle);
1728
+ } else {
1729
+ angle = startAngle + t * (twoPi - startAngle + endAngle);
1730
+ if (angle >= twoPi) angle -= twoPi;
1731
+ }
1732
+ const mathAngle = angle - Math.PI / 2;
1733
+ const cos = Math.cos(mathAngle);
1734
+ const sin = Math.sin(mathAngle);
1735
+ const maxR = Math.max(radiusX, radiusY);
1736
+ const innerR = Math.max(innerRadiusX, innerRadiusY);
1737
+ for (let r = Math.ceil(innerR); r <= maxR; r++) {
1738
+ const scaleX = radiusX / maxR;
1739
+ const scaleY = radiusY / maxR;
1740
+ const x = Math.round(centerX + r * cos * scaleX);
1741
+ const y = Math.round(centerY + r * sin * scaleY);
1742
+ canvas.setDot(x, y, seriesIndex);
1743
+ }
1744
+ }
1745
+ }
1746
+
1747
+ // src/layout/heatmap.ts
1748
+ import { getStringWidth as getStringWidth6 } from "@tuicomponents/core";
1749
+ function computeHeatmapLayout(input) {
1750
+ const series = input.series;
1751
+ const heatmapStyle = input.heatmapStyle;
1752
+ const rowLabelSet = /* @__PURE__ */ new Set();
1753
+ const colLabelSet = /* @__PURE__ */ new Set();
1754
+ const valueMap = /* @__PURE__ */ new Map();
1755
+ for (const s of series) {
1756
+ for (const point of s.data) {
1757
+ const colLabel = String(point.x);
1758
+ const rowLabel = point.label ?? s.name;
1759
+ const value = point.y;
1760
+ rowLabelSet.add(rowLabel);
1761
+ colLabelSet.add(colLabel);
1762
+ valueMap.set(JSON.stringify([rowLabel, colLabel]), value);
1763
+ }
1764
+ }
1765
+ const rowLabels = Array.from(rowLabelSet);
1766
+ const colLabels = Array.from(colLabelSet);
1767
+ const values = Array.from(valueMap.values());
1768
+ const minValue = values.length > 0 ? Math.min(...values) : 0;
1769
+ const maxValue = values.length > 0 ? Math.max(...values) : 1;
1770
+ const valueRange = { min: minValue, max: maxValue };
1771
+ let rowLabelWidth = 0;
1772
+ for (const label of rowLabels) {
1773
+ const width = getStringWidth6(label);
1774
+ if (width > rowLabelWidth) {
1775
+ rowLabelWidth = width;
1776
+ }
1777
+ }
1778
+ rowLabelWidth += 2;
1779
+ let cellWidth;
1780
+ if (heatmapStyle === "numeric") {
1781
+ const maxValueWidth = Math.max(
1782
+ getStringWidth6(formatValue(minValue)),
1783
+ getStringWidth6(formatValue(maxValue))
1784
+ );
1785
+ cellWidth = Math.max(3, maxValueWidth + 1);
1786
+ } else {
1787
+ cellWidth = 2;
1788
+ }
1789
+ let colLabelWidth = cellWidth;
1790
+ for (const label of colLabels) {
1791
+ const width = getStringWidth6(label);
1792
+ if (width > colLabelWidth) {
1793
+ colLabelWidth = width;
1794
+ }
1795
+ }
1796
+ const cells = [];
1797
+ for (let rowIdx = 0; rowIdx < rowLabels.length; rowIdx++) {
1798
+ const rowLabel = rowLabels[rowIdx];
1799
+ if (!rowLabel) continue;
1800
+ const rowCells = [];
1801
+ for (let colIdx = 0; colIdx < colLabels.length; colIdx++) {
1802
+ const colLabel = colLabels[colIdx];
1803
+ if (!colLabel) continue;
1804
+ const key = JSON.stringify([rowLabel, colLabel]);
1805
+ const value = valueMap.get(key) ?? 0;
1806
+ const range = maxValue - minValue;
1807
+ const normalizedValue = range > 0 ? (value - minValue) / range : 0.5;
1808
+ let displayChar;
1809
+ if (heatmapStyle === "numeric") {
1810
+ displayChar = formatValue(value);
1811
+ } else {
1812
+ const baseChar = valueToHeatmapChar(
1813
+ normalizedValue,
1814
+ heatmapStyle === "blocks" ? "blocks" : "ascii"
1815
+ );
1816
+ displayChar = baseChar.repeat(cellWidth);
1817
+ }
1818
+ rowCells.push({
1819
+ row: rowIdx,
1820
+ col: colIdx,
1821
+ value,
1822
+ normalizedValue,
1823
+ displayChar
1824
+ });
1825
+ }
1826
+ cells.push(rowCells);
1827
+ }
1828
+ return {
1829
+ type: "heatmap",
1830
+ heatmapStyle,
1831
+ rowLabels,
1832
+ colLabels,
1833
+ cells,
1834
+ valueRange,
1835
+ cellWidth,
1836
+ colLabelWidth,
1837
+ rowLabelWidth,
1838
+ width: input.width,
1839
+ height: input.height
1840
+ };
1841
+ }
1842
+ function formatValue(value) {
1843
+ if (Number.isInteger(value)) {
1844
+ return String(value);
1845
+ }
1846
+ if (Math.abs(value) >= 100) {
1847
+ return value.toFixed(0);
1848
+ }
1849
+ if (Math.abs(value) >= 10) {
1850
+ return value.toFixed(1);
1851
+ }
1852
+ return value.toFixed(2);
1853
+ }
1854
+
1855
+ // src/renderers/ansi.ts
1856
+ import { padToWidth as padToWidth2 } from "@tuicomponents/core";
1857
+
1858
+ // src/core/legend.ts
1859
+ import { getStringWidth as getStringWidth7 } from "@tuicomponents/core";
1860
+ function getLegendItemWidth(item) {
1861
+ const baseWidth = getStringWidth7(item.symbol) + 1 + getStringWidth7(item.name);
1862
+ if (item.useBackticks) {
1863
+ return baseWidth + 1;
1864
+ }
1865
+ return baseWidth;
1866
+ }
1867
+ function formatLegendItem(item, forMarkdown) {
1868
+ if (forMarkdown && item.useBackticks) {
1869
+ return ` \`${item.symbol}\` ${item.name}`;
1870
+ }
1871
+ return `${item.symbol} ${item.name}`;
1872
+ }
1873
+ function computeLegendLayout(options) {
1874
+ const { items, position, boxed = false, maxWidth = 80 } = options;
1875
+ if (position === "none" || items.length === 0) {
1876
+ return {
1877
+ position,
1878
+ boxed,
1879
+ rows: [],
1880
+ totalWidth: 0,
1881
+ totalHeight: 0
1882
+ };
1883
+ }
1884
+ const itemWidths = items.map((item) => getLegendItemWidth(item));
1885
+ const _separator = " ";
1886
+ const separatorWidth = 2;
1887
+ const rows = [];
1888
+ let currentRow = [];
1889
+ let currentWidth = 0;
1890
+ for (let i = 0; i < items.length; i++) {
1891
+ const item = items[i];
1892
+ const itemWidth = itemWidths[i];
1893
+ if (!item || itemWidth === void 0) continue;
1894
+ const widthWithSeparator = currentRow.length > 0 ? itemWidth + separatorWidth : itemWidth;
1895
+ if (currentWidth + widthWithSeparator > maxWidth && currentRow.length > 0) {
1896
+ rows.push({ items: currentRow, width: currentWidth });
1897
+ currentRow = [item];
1898
+ currentWidth = itemWidth;
1899
+ } else {
1900
+ currentRow.push(item);
1901
+ currentWidth += widthWithSeparator;
1902
+ }
1903
+ }
1904
+ if (currentRow.length > 0) {
1905
+ rows.push({ items: currentRow, width: currentWidth });
1906
+ }
1907
+ const totalWidth = Math.max(...rows.map((r) => r.width));
1908
+ const totalHeight = rows.length + (boxed ? 2 : 0);
1909
+ return {
1910
+ position,
1911
+ boxed,
1912
+ rows,
1913
+ totalWidth,
1914
+ totalHeight
1915
+ };
1916
+ }
1917
+ function renderLegendRow(row, forMarkdown) {
1918
+ return row.items.map((item) => formatLegendItem(item, forMarkdown)).join(" ");
1919
+ }
1920
+
1921
+ // src/core/axis-renderer.ts
1922
+ import { padToWidth } from "@tuicomponents/core";
1923
+ function computeYAxisRow(row, config) {
1924
+ const {
1925
+ scale,
1926
+ chartHeight,
1927
+ labelWidth,
1928
+ format = "number",
1929
+ decimals
1930
+ } = config;
1931
+ const rowBottom = row / chartHeight;
1932
+ const rowTop = (row + 1) / chartHeight;
1933
+ let label = " ".repeat(labelWidth);
1934
+ let hasTick = false;
1935
+ for (const tick of scale.ticks) {
1936
+ const tickNorm = (tick - scale.min) / (scale.max - scale.min);
1937
+ if (tickNorm < 1e-3) continue;
1938
+ const epsilon = 1e-3;
1939
+ if (tickNorm > rowBottom - epsilon && tickNorm <= rowTop) {
1940
+ label = padToWidth(formatTickValue(tick, format, decimals), labelWidth - 1) + " ";
1941
+ hasTick = true;
1942
+ break;
1943
+ }
1944
+ }
1945
+ const axisChar = hasTick ? AXIS_CHARS.yTickLeft : AXIS_CHARS.vertical;
1946
+ return { label, hasTick, axisChar };
1947
+ }
1948
+ function renderXAxis(config) {
1949
+ const {
1950
+ categories,
1951
+ barWidth,
1952
+ yAxisWidth,
1953
+ chartWidth,
1954
+ minValue,
1955
+ format = "number",
1956
+ decimals
1957
+ } = config;
1958
+ const zeroLabel = padToWidth(formatTickValue(minValue, format, decimals), yAxisWidth - 1) + " ";
1959
+ let xAxisLine = "";
1960
+ for (let i = 0; i < categories.length; i++) {
1961
+ const tickPos = Math.floor(barWidth / 2);
1962
+ const beforeTick = AXIS_CHARS.horizontal.repeat(tickPos);
1963
+ const afterTick = AXIS_CHARS.horizontal.repeat(barWidth - tickPos - 1);
1964
+ xAxisLine += beforeTick + AXIS_CHARS.xTick + afterTick;
1965
+ if (i < categories.length - 1) {
1966
+ xAxisLine += AXIS_CHARS.horizontal;
1967
+ }
1968
+ }
1969
+ const remaining = chartWidth - yAxisWidth - xAxisLine.length - 1;
1970
+ if (remaining > 0) {
1971
+ xAxisLine += AXIS_CHARS.horizontal.repeat(remaining);
1972
+ }
1973
+ const fullAxisLine = zeroLabel + AXIS_CHARS.origin + xAxisLine;
1974
+ let labelLine = "";
1975
+ for (let i = 0; i < categories.length; i++) {
1976
+ const label = categories[i];
1977
+ if (!label) continue;
1978
+ const tickPos = Math.floor(barWidth / 2);
1979
+ const labelStart = Math.max(0, tickPos - Math.floor(label.length / 2));
1980
+ const labelEnd = labelStart + label.length;
1981
+ const paddingBefore = " ".repeat(labelStart);
1982
+ const paddingAfter = " ".repeat(Math.max(0, barWidth - labelEnd));
1983
+ labelLine += paddingBefore + label + paddingAfter;
1984
+ if (i < categories.length - 1) {
1985
+ labelLine += " ";
1986
+ }
1987
+ }
1988
+ const fullLabelLine = " ".repeat(yAxisWidth + 1) + labelLine;
1989
+ return {
1990
+ axisLine: fullAxisLine,
1991
+ labelLine: fullLabelLine
1992
+ };
1993
+ }
1994
+ function buildChartRow(row, content, config) {
1995
+ const { label, axisChar } = computeYAxisRow(row, config);
1996
+ return `${label}${axisChar}${content}`;
1997
+ }
1998
+
1999
+ // src/renderers/ansi.ts
2000
+ function renderLegendRowAnsi(row, theme) {
2001
+ return row.items.map((item) => {
2002
+ const symbol = item.useBackticks && theme ? theme.semantic.secondary(item.symbol) : theme ? theme.semantic.primary(item.symbol) : item.symbol;
2003
+ return `${symbol} ${item.name}`;
2004
+ }).join(" ");
2005
+ }
2006
+ function renderBarChartAnsi(layout, options) {
2007
+ const { theme } = options;
2008
+ const lines = [];
2009
+ for (const bar of layout.bars) {
2010
+ const label = padToWidth2(bar.label, layout.maxLabelWidth);
2011
+ const coloredLabel = theme ? theme.semantic.header(label) : label;
2012
+ const barStr = bar.barChar.repeat(bar.length);
2013
+ const coloredBar = theme ? theme.semantic.primary(barStr) : barStr;
2014
+ const coloredValue = theme ? theme.semantic.secondary(bar.formattedValue) : bar.formattedValue;
2015
+ if (layout.showValues) {
2016
+ lines.push(`${coloredLabel} ${coloredBar} ${coloredValue}`);
2017
+ } else {
2018
+ lines.push(`${coloredLabel} ${coloredBar}`);
2019
+ }
2020
+ }
2021
+ return lines.join("\n");
2022
+ }
2023
+ function renderVerticalBarChartAnsi(layout, options) {
2024
+ const { theme, input } = options;
2025
+ const lines = [];
2026
+ const chartHeight = layout.barAreaSize;
2027
+ const numBars = layout.bars.length;
2028
+ const barWidth = Math.max(1, Math.floor((layout.width - 2) / numBars) - 1);
2029
+ const yAxisWidth = layout.maxValueWidth + 2;
2030
+ const format = input.yAxis?.format ?? "number";
2031
+ const decimals = input.yAxis?.decimals;
2032
+ for (let row = chartHeight - 1; row >= 0; row--) {
2033
+ const rowThreshold = (row + 1) / chartHeight;
2034
+ let yLabel = " ".repeat(yAxisWidth);
2035
+ for (const tick of layout.yScale.ticks) {
2036
+ const tickNorm = (tick - layout.yScale.min) / (layout.yScale.max - layout.yScale.min);
2037
+ if (Math.abs(tickNorm - rowThreshold) < 0.5 / chartHeight) {
2038
+ yLabel = padToWidth2(formatTickValue(tick, format, decimals), yAxisWidth - 1) + " ";
2039
+ break;
2040
+ }
2041
+ }
2042
+ const axisChar = row === 0 ? AXIS_CHARS.origin : AXIS_CHARS.vertical;
2043
+ const segments = [];
2044
+ for (const bar of layout.bars) {
2045
+ const barNorm = bar.length / layout.barAreaSize;
2046
+ if (barNorm >= rowThreshold) {
2047
+ const barSegment = bar.barChar.repeat(barWidth);
2048
+ segments.push(theme ? theme.semantic.primary(barSegment) : barSegment);
2049
+ } else {
2050
+ segments.push(" ".repeat(barWidth));
2051
+ }
2052
+ }
2053
+ lines.push(`${yLabel}${axisChar}${segments.join(" ")}`);
2054
+ }
2055
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.width - yAxisWidth - 1);
2056
+ lines.push(" ".repeat(yAxisWidth) + AXIS_CHARS.origin + xAxisLine);
2057
+ const xLabels = [];
2058
+ for (const bar of layout.bars) {
2059
+ xLabels.push(padToWidth2(bar.label, barWidth));
2060
+ }
2061
+ lines.push(" ".repeat(yAxisWidth + 1) + xLabels.join(" "));
2062
+ return lines.join("\n");
2063
+ }
2064
+ function renderStackedBarChartAnsi(layout, options) {
2065
+ const { theme, input } = options;
2066
+ const lines = [];
2067
+ const isVertical = layout.type === "bar-stacked-vertical";
2068
+ if (isVertical) {
2069
+ const chartHeight = layout.barAreaSize;
2070
+ const numStacks = layout.stacks.length;
2071
+ const barWidth = Math.max(
2072
+ 1,
2073
+ Math.floor((layout.width - 2) / numStacks) - 1
2074
+ );
2075
+ const yAxisWidth = layout.maxValueWidth + 2;
2076
+ const format = input.yAxis?.format ?? "number";
2077
+ const decimals = input.yAxis?.decimals;
2078
+ const yAxisConfig = {
2079
+ scale: layout.yScale,
2080
+ chartHeight,
2081
+ labelWidth: yAxisWidth,
2082
+ format,
2083
+ decimals
2084
+ };
2085
+ for (let row = chartHeight - 1; row >= 0; row--) {
2086
+ const rowBottom = row / chartHeight;
2087
+ const rowTop = (row + 1) / chartHeight;
2088
+ const { label: yLabel, axisChar } = computeYAxisRow(row, yAxisConfig);
2089
+ const segments = [];
2090
+ for (const stack of layout.stacks) {
2091
+ let activeSegment = null;
2092
+ let cumHeight = 0;
2093
+ for (const segment of stack.segments) {
2094
+ const segmentHeight = segment.length / layout.barAreaSize;
2095
+ if (rowBottom < cumHeight + segmentHeight && rowTop > cumHeight) {
2096
+ activeSegment = segment;
2097
+ break;
2098
+ }
2099
+ cumHeight += segmentHeight;
2100
+ }
2101
+ if (activeSegment) {
2102
+ const barSegment = activeSegment.barChar.repeat(barWidth);
2103
+ segments.push(
2104
+ theme ? theme.semantic.primary(barSegment) : barSegment
2105
+ );
2106
+ } else {
2107
+ segments.push(" ".repeat(barWidth));
2108
+ }
2109
+ }
2110
+ lines.push(`${yLabel}${axisChar}${segments.join(" ")}`);
2111
+ }
2112
+ const xAxis = renderXAxis({
2113
+ categories: layout.stacks.map((s) => s.label),
2114
+ barWidth,
2115
+ yAxisWidth,
2116
+ chartWidth: layout.width,
2117
+ minValue: layout.yScale.min,
2118
+ format,
2119
+ decimals
2120
+ });
2121
+ lines.push(xAxis.axisLine);
2122
+ lines.push(xAxis.labelLine);
2123
+ } else {
2124
+ for (const stack of layout.stacks) {
2125
+ const label = padToWidth2(stack.label, layout.maxLabelWidth);
2126
+ const coloredLabel = theme ? theme.semantic.header(label) : label;
2127
+ let barStr = "";
2128
+ for (const segment of stack.segments) {
2129
+ const segmentStr = segment.barChar.repeat(segment.length);
2130
+ barStr += theme ? theme.semantic.primary(segmentStr) : segmentStr;
2131
+ }
2132
+ lines.push(`${coloredLabel} ${barStr}`);
2133
+ }
2134
+ }
2135
+ const legendItems = layout.seriesNames.map((name, i) => {
2136
+ const style = layout.seriesStyles[i];
2137
+ if (!style) {
2138
+ throw new Error(`Missing series style at index ${String(i)}`);
2139
+ }
2140
+ return {
2141
+ name,
2142
+ symbol: style.char,
2143
+ useBackticks: style.useBackticks
2144
+ };
2145
+ });
2146
+ const legendLayout = computeLegendLayout({
2147
+ items: legendItems,
2148
+ position: input.legend?.position ?? "bottom",
2149
+ maxWidth: layout.width
2150
+ });
2151
+ if (legendLayout.rows.length > 0) {
2152
+ lines.push("");
2153
+ for (const row of legendLayout.rows) {
2154
+ lines.push(renderLegendRowAnsi(row, theme));
2155
+ }
2156
+ }
2157
+ return lines.join("\n");
2158
+ }
2159
+ function renderLineChartAnsi(layout, options) {
2160
+ const { theme, input } = options;
2161
+ const lines = [];
2162
+ const chartWidth = layout.rows[0]?.chars.length ?? layout.categories.length;
2163
+ for (const row of layout.rows) {
2164
+ const yLabel = row.yLabel ? padToWidth2(row.yLabel, layout.yAxisWidth - 1) + " " : " ".repeat(layout.yAxisWidth);
2165
+ let content = "";
2166
+ for (let i = 0; i < row.chars.length; i++) {
2167
+ const char = row.chars[i];
2168
+ if (char === void 0) {
2169
+ continue;
2170
+ }
2171
+ const seriesIdx = row.seriesIndices[i];
2172
+ if (char !== " " && theme) {
2173
+ if (seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1) {
2174
+ content += theme.semantic.secondary(char);
2175
+ } else {
2176
+ content += theme.semantic.primary(char);
2177
+ }
2178
+ } else {
2179
+ content += char;
2180
+ }
2181
+ }
2182
+ lines.push(`${yLabel}${AXIS_CHARS.vertical}${content}`);
2183
+ }
2184
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(chartWidth);
2185
+ lines.push(" ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine);
2186
+ const labelLine = Array(chartWidth).fill(" ");
2187
+ const isBraille = layout.lineStyle === "braille";
2188
+ const spacing = isBraille ? 3 : 2;
2189
+ for (let i = 0; i < layout.categories.length; i++) {
2190
+ const label = layout.categories[i];
2191
+ if (label === void 0) {
2192
+ continue;
2193
+ }
2194
+ const pos = isBraille ? i * spacing + Math.floor(spacing / 2) : i * spacing;
2195
+ if (pos < chartWidth) {
2196
+ labelLine[pos] = label.charAt(0) || " ";
2197
+ }
2198
+ }
2199
+ lines.push(" ".repeat(layout.yAxisWidth + 1) + labelLine.join(""));
2200
+ if (layout.seriesNames.length > 1) {
2201
+ const legendItems = layout.seriesNames.map((name, i) => {
2202
+ return {
2203
+ name,
2204
+ symbol: "\u28FF",
2205
+ // Use braille full block for line chart legend
2206
+ useBackticks: i % 2 === 1
2207
+ };
2208
+ });
2209
+ const legendLayout = computeLegendLayout({
2210
+ items: legendItems,
2211
+ position: input.legend?.position ?? "bottom",
2212
+ maxWidth: layout.width
2213
+ });
2214
+ if (legendLayout.rows.length > 0) {
2215
+ lines.push("");
2216
+ for (const row of legendLayout.rows) {
2217
+ lines.push(renderLegendRowAnsi(row, theme));
2218
+ }
2219
+ }
2220
+ }
2221
+ return lines.join("\n");
2222
+ }
2223
+ function renderAreaChartAnsi(layout, options) {
2224
+ const { theme, input } = options;
2225
+ const lines = [];
2226
+ for (const row of layout.rows) {
2227
+ const yLabel = row.yLabel ? padToWidth2(row.yLabel, layout.yAxisWidth - 1) + " " : " ".repeat(layout.yAxisWidth);
2228
+ let content = "";
2229
+ for (const char of row.chars) {
2230
+ if (char !== " " && theme) {
2231
+ content += theme.semantic.primary(char);
2232
+ } else {
2233
+ content += char;
2234
+ }
2235
+ }
2236
+ lines.push(`${yLabel}${AXIS_CHARS.vertical}${content}`);
2237
+ }
2238
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.categories.length);
2239
+ lines.push(" ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine);
2240
+ const xLabels = layout.categories.map((c) => c.charAt(0) || " ").join("");
2241
+ lines.push(" ".repeat(layout.yAxisWidth + 1) + xLabels);
2242
+ const legendItems = layout.seriesNames.map((name, i) => {
2243
+ const style = layout.seriesStyles[i];
2244
+ if (!style) {
2245
+ throw new Error(`Missing series style at index ${String(i)}`);
2246
+ }
2247
+ return {
2248
+ name,
2249
+ symbol: style.char,
2250
+ useBackticks: style.useBackticks
2251
+ };
2252
+ });
2253
+ const legendLayout = computeLegendLayout({
2254
+ items: legendItems,
2255
+ position: input.legend?.position ?? "bottom",
2256
+ maxWidth: layout.width
2257
+ });
2258
+ if (legendLayout.rows.length > 0) {
2259
+ lines.push("");
2260
+ for (const row of legendLayout.rows) {
2261
+ lines.push(renderLegendRowAnsi(row, theme));
2262
+ }
2263
+ }
2264
+ return lines.join("\n");
2265
+ }
2266
+ function renderScatterChartAnsi(layout, options) {
2267
+ const { theme, input } = options;
2268
+ const lines = [];
2269
+ const format = input.yAxis?.format ?? "number";
2270
+ const decimals = input.yAxis?.decimals;
2271
+ for (let rowIndex = 0; rowIndex < layout.chartHeight; rowIndex++) {
2272
+ let yLabel = " ".repeat(layout.yAxisWidth);
2273
+ const rowTop = 1 - rowIndex / layout.chartHeight;
2274
+ const rowBottom = 1 - (rowIndex + 1) / layout.chartHeight;
2275
+ for (const tick of layout.yScale.ticks) {
2276
+ const tickNorm = (tick - layout.yScale.min) / (layout.yScale.max - layout.yScale.min);
2277
+ if (tickNorm > rowBottom && tickNorm <= rowTop) {
2278
+ yLabel = padToWidth2(
2279
+ formatTickValue(tick, format, decimals),
2280
+ layout.yAxisWidth - 1
2281
+ ) + " ";
2282
+ break;
2283
+ }
2284
+ }
2285
+ let content = "";
2286
+ if (layout.scatterStyle === "braille" && layout.brailleChars) {
2287
+ const rowChars = layout.brailleChars[rowIndex] ?? [];
2288
+ const rowIndices = layout.brailleSeriesIndices?.[rowIndex] ?? [];
2289
+ for (let i = 0; i < rowChars.length; i++) {
2290
+ const char = rowChars[i];
2291
+ if (char === void 0) {
2292
+ continue;
2293
+ }
2294
+ const seriesIdx = rowIndices[i];
2295
+ if (char !== " " && theme) {
2296
+ if (seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1) {
2297
+ content += theme.semantic.secondary(char);
2298
+ } else {
2299
+ content += theme.semantic.primary(char);
2300
+ }
2301
+ } else {
2302
+ content += char;
2303
+ }
2304
+ }
2305
+ } else if (layout.grid) {
2306
+ const rowChars = layout.grid[rowIndex] ?? [];
2307
+ const rowIndices = layout.seriesIndices?.[rowIndex] ?? [];
2308
+ for (let i = 0; i < rowChars.length; i++) {
2309
+ const char = rowChars[i];
2310
+ if (char === void 0) {
2311
+ continue;
2312
+ }
2313
+ const seriesIdx = rowIndices[i];
2314
+ if (char !== " " && theme) {
2315
+ if (seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1) {
2316
+ content += theme.semantic.secondary(char);
2317
+ } else {
2318
+ content += theme.semantic.primary(char);
2319
+ }
2320
+ } else {
2321
+ content += char;
2322
+ }
2323
+ }
2324
+ }
2325
+ lines.push(`${yLabel}${AXIS_CHARS.vertical}${content}`);
2326
+ }
2327
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.chartWidth);
2328
+ lines.push(" ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine);
2329
+ const xFormat = input.xAxis?.format ?? "number";
2330
+ const xDecimals = input.xAxis?.decimals;
2331
+ const labelPositions = [];
2332
+ for (const tick of layout.xScale.ticks) {
2333
+ const norm = (tick - layout.xScale.min) / (layout.xScale.max - layout.xScale.min);
2334
+ const pos = Math.round(norm * (layout.chartWidth - 1));
2335
+ const label = formatTickValue(tick, xFormat, xDecimals);
2336
+ labelPositions.push({ pos, label });
2337
+ }
2338
+ const labelLine = Array(layout.chartWidth).fill(" ");
2339
+ for (const { pos, label } of labelPositions) {
2340
+ const halfLen = Math.floor(label.length / 2);
2341
+ const startPos = Math.max(
2342
+ 0,
2343
+ Math.min(layout.chartWidth - label.length, pos - halfLen)
2344
+ );
2345
+ for (let i = 0; i < label.length && startPos + i < layout.chartWidth; i++) {
2346
+ const char = label[i];
2347
+ if (char !== void 0) {
2348
+ labelLine[startPos + i] = char;
2349
+ }
2350
+ }
2351
+ }
2352
+ lines.push(" ".repeat(layout.yAxisWidth + 1) + labelLine.join(""));
2353
+ if (layout.seriesNames.length > 1) {
2354
+ const symbolArray = ["\u25CF", "\u25A0", "\u25B2", "\u25C6", "+"];
2355
+ const legendItems = layout.seriesNames.map((name, i) => {
2356
+ const symbol = layout.scatterStyle === "braille" ? "\u28FF" : symbolArray[i % 5];
2357
+ if (!symbol) {
2358
+ throw new Error(`Missing symbol at index ${String(i % 5)}`);
2359
+ }
2360
+ return {
2361
+ name,
2362
+ symbol,
2363
+ useBackticks: i % 2 === 1
2364
+ };
2365
+ });
2366
+ const legendLayout = computeLegendLayout({
2367
+ items: legendItems,
2368
+ position: input.legend?.position ?? "bottom",
2369
+ maxWidth: layout.width
2370
+ });
2371
+ if (legendLayout.rows.length > 0) {
2372
+ lines.push("");
2373
+ for (const row of legendLayout.rows) {
2374
+ lines.push(renderLegendRowAnsi(row, theme));
2375
+ }
2376
+ }
2377
+ }
2378
+ return lines.join("\n");
2379
+ }
2380
+ function renderPieChartAnsi(layout, options) {
2381
+ const { theme, input } = options;
2382
+ const lines = [];
2383
+ if (layout.slices.length === 0) {
2384
+ lines.push("No data");
2385
+ return lines.join("\n");
2386
+ }
2387
+ for (let rowIndex = 0; rowIndex < layout.brailleChars.length; rowIndex++) {
2388
+ const rowChars = layout.brailleChars[rowIndex] ?? [];
2389
+ const rowIndices = layout.brailleSeriesIndices[rowIndex] ?? [];
2390
+ let content = "";
2391
+ for (let i = 0; i < rowChars.length; i++) {
2392
+ const char = rowChars[i];
2393
+ if (char === void 0) {
2394
+ continue;
2395
+ }
2396
+ const seriesIdx = rowIndices[i];
2397
+ if (char !== " " && theme) {
2398
+ if (seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1) {
2399
+ content += theme.semantic.secondary(char);
2400
+ } else {
2401
+ content += theme.semantic.primary(char);
2402
+ }
2403
+ } else {
2404
+ content += char;
2405
+ }
2406
+ }
2407
+ if (layout.type === "donut" && layout.centerLabel && rowIndex === Math.floor(layout.brailleChars.length / 2)) {
2408
+ const labelLen = layout.centerLabel.length;
2409
+ const startPos = Math.floor((layout.width - labelLen) / 2);
2410
+ if (startPos >= 0) {
2411
+ const contentArray = [...content];
2412
+ for (let i = 0; i < labelLen && startPos + i < contentArray.length; i++) {
2413
+ const char = layout.centerLabel[i];
2414
+ if (char !== void 0) {
2415
+ contentArray[startPos + i] = char;
2416
+ }
2417
+ }
2418
+ content = contentArray.join("");
2419
+ }
2420
+ }
2421
+ lines.push(content);
2422
+ }
2423
+ const legendItems = layout.slices.map((slice) => ({
2424
+ name: `${slice.label} ${slice.percentage.toFixed(0)}%`,
2425
+ symbol: slice.barChar,
2426
+ useBackticks: slice.useBackticks
2427
+ }));
2428
+ const legendLayout = computeLegendLayout({
2429
+ items: legendItems,
2430
+ position: input.legend?.position ?? "bottom",
2431
+ maxWidth: layout.width
2432
+ });
2433
+ if (legendLayout.rows.length > 0) {
2434
+ lines.push("");
2435
+ for (const row of legendLayout.rows) {
2436
+ lines.push(renderLegendRowAnsi(row, theme));
2437
+ }
2438
+ }
2439
+ return lines.join("\n");
2440
+ }
2441
+ function renderHeatmapAnsi(layout, options) {
2442
+ const { theme } = options;
2443
+ const lines = [];
2444
+ const colLabelRow = " ".repeat(layout.rowLabelWidth) + layout.colLabels.map((label) => padToWidth2(label, layout.colLabelWidth)).join(" ");
2445
+ lines.push(colLabelRow);
2446
+ for (let rowIdx = 0; rowIdx < layout.rowLabels.length; rowIdx++) {
2447
+ const rowLabel = layout.rowLabels[rowIdx];
2448
+ if (rowLabel === void 0) {
2449
+ continue;
2450
+ }
2451
+ const rowCells = layout.cells[rowIdx] ?? [];
2452
+ let row = padToWidth2(rowLabel, layout.rowLabelWidth);
2453
+ for (const cell of rowCells) {
2454
+ let cellStr;
2455
+ if (layout.heatmapStyle === "numeric") {
2456
+ cellStr = padToWidth2(cell.displayChar, layout.colLabelWidth);
2457
+ } else {
2458
+ cellStr = padToWidth2(cell.displayChar, layout.colLabelWidth);
2459
+ }
2460
+ if (theme) {
2461
+ if (cell.normalizedValue > 0.5) {
2462
+ cellStr = theme.semantic.primary(cellStr);
2463
+ } else if (cell.normalizedValue > 0.25) {
2464
+ cellStr = theme.semantic.secondary(cellStr);
2465
+ }
2466
+ }
2467
+ row += cellStr + " ";
2468
+ }
2469
+ lines.push(row.trimEnd());
2470
+ }
2471
+ if (layout.heatmapStyle !== "numeric") {
2472
+ lines.push("");
2473
+ const scaleLabel = layout.heatmapStyle === "blocks" ? `Scale: \u2591 ${String(layout.valueRange.min)} \u2592 \u2593 \u2588 ${String(layout.valueRange.max)}` : `Scale: . ${String(layout.valueRange.min)} : * # ${String(layout.valueRange.max)}`;
2474
+ lines.push(theme ? theme.semantic.secondary(scaleLabel) : scaleLabel);
2475
+ }
2476
+ return lines.join("\n");
2477
+ }
2478
+
2479
+ // src/renderers/markdown.ts
2480
+ import { padToWidth as padToWidth3, anchorLine, DEFAULT_ANCHOR } from "@tuicomponents/core";
2481
+ function wrapInlineCode(text, useBackticks) {
2482
+ if (useBackticks) {
2483
+ return ` \`${text}\``;
2484
+ }
2485
+ return text;
2486
+ }
2487
+ function renderBarChartMarkdown(layout, _options) {
2488
+ const lines = [];
2489
+ for (const bar of layout.bars) {
2490
+ const label = padToWidth3(bar.label, layout.maxLabelWidth);
2491
+ const barStr = bar.barChar.repeat(bar.length);
2492
+ const styledBar = wrapInlineCode(barStr, bar.useBackticks);
2493
+ let content;
2494
+ if (layout.showValues) {
2495
+ content = `${label} ${styledBar} ${bar.formattedValue}`;
2496
+ } else {
2497
+ content = `${label} ${styledBar}`;
2498
+ }
2499
+ if (bar.useBackticks) {
2500
+ content += " ";
2501
+ }
2502
+ lines.push(anchorLine(content, DEFAULT_ANCHOR));
2503
+ }
2504
+ return lines.join("\n");
2505
+ }
2506
+ function renderVerticalBarChartMarkdown(layout, options) {
2507
+ const { input } = options;
2508
+ const lines = [];
2509
+ const chartHeight = layout.barAreaSize;
2510
+ const numBars = layout.bars.length;
2511
+ const barWidth = Math.max(1, Math.floor((layout.width - 2) / numBars) - 1);
2512
+ const yAxisWidth = layout.maxValueWidth + 2;
2513
+ const format = input.yAxis?.format ?? "number";
2514
+ const decimals = input.yAxis?.decimals;
2515
+ for (let row = chartHeight - 1; row >= 0; row--) {
2516
+ const rowThreshold = (row + 1) / chartHeight;
2517
+ let yLabel = " ".repeat(yAxisWidth);
2518
+ for (const tick of layout.yScale.ticks) {
2519
+ const tickNorm = (tick - layout.yScale.min) / (layout.yScale.max - layout.yScale.min);
2520
+ if (Math.abs(tickNorm - rowThreshold) < 0.5 / chartHeight) {
2521
+ yLabel = padToWidth3(formatTickValue(tick, format, decimals), yAxisWidth - 1) + " ";
2522
+ break;
2523
+ }
2524
+ }
2525
+ const axisChar = row === 0 ? AXIS_CHARS.origin : AXIS_CHARS.vertical;
2526
+ const segments = [];
2527
+ for (const bar of layout.bars) {
2528
+ const barNorm = bar.length / layout.barAreaSize;
2529
+ if (barNorm >= rowThreshold) {
2530
+ const barSegment = bar.barChar.repeat(barWidth);
2531
+ segments.push(wrapInlineCode(barSegment, bar.useBackticks));
2532
+ } else {
2533
+ segments.push(" ".repeat(barWidth));
2534
+ }
2535
+ }
2536
+ lines.push(
2537
+ anchorLine(`${yLabel}${axisChar}${segments.join(" ")}`, DEFAULT_ANCHOR)
2538
+ );
2539
+ }
2540
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.width - yAxisWidth - 1);
2541
+ lines.push(
2542
+ anchorLine(
2543
+ " ".repeat(yAxisWidth) + AXIS_CHARS.origin + xAxisLine,
2544
+ DEFAULT_ANCHOR
2545
+ )
2546
+ );
2547
+ const xLabels = [];
2548
+ for (const bar of layout.bars) {
2549
+ xLabels.push(padToWidth3(bar.label, barWidth));
2550
+ }
2551
+ lines.push(
2552
+ anchorLine(" ".repeat(yAxisWidth + 1) + xLabels.join(" "), DEFAULT_ANCHOR)
2553
+ );
2554
+ return lines.join("\n");
2555
+ }
2556
+ function renderStackedBarChartMarkdown(layout, options) {
2557
+ const { input } = options;
2558
+ const lines = [];
2559
+ const isVertical = layout.type === "bar-stacked-vertical";
2560
+ if (isVertical) {
2561
+ const chartHeight = layout.barAreaSize;
2562
+ const numStacks = layout.stacks.length;
2563
+ const barWidth = Math.max(
2564
+ 1,
2565
+ Math.floor((layout.width - 2) / numStacks) - 1
2566
+ );
2567
+ const yAxisWidth = layout.maxValueWidth + 2;
2568
+ const format = input.yAxis?.format ?? "number";
2569
+ const decimals = input.yAxis?.decimals;
2570
+ const yAxisConfig = {
2571
+ scale: layout.yScale,
2572
+ chartHeight,
2573
+ labelWidth: yAxisWidth,
2574
+ format,
2575
+ decimals
2576
+ };
2577
+ for (let row = chartHeight - 1; row >= 0; row--) {
2578
+ const rowBottom = row / chartHeight;
2579
+ const rowTop = (row + 1) / chartHeight;
2580
+ const { label: yLabel, axisChar } = computeYAxisRow(row, yAxisConfig);
2581
+ const segments = [];
2582
+ for (const stack of layout.stacks) {
2583
+ let activeSegment = null;
2584
+ let cumHeight = 0;
2585
+ for (const segment of stack.segments) {
2586
+ const segmentHeight = segment.length / layout.barAreaSize;
2587
+ if (rowBottom < cumHeight + segmentHeight && rowTop > cumHeight) {
2588
+ activeSegment = segment;
2589
+ break;
2590
+ }
2591
+ cumHeight += segmentHeight;
2592
+ }
2593
+ if (activeSegment) {
2594
+ const barSegment = activeSegment.barChar.repeat(barWidth);
2595
+ if (activeSegment.useBackticks) {
2596
+ segments.push(`\`${barSegment}\``);
2597
+ } else {
2598
+ segments.push(barSegment);
2599
+ }
2600
+ } else {
2601
+ segments.push(" ".repeat(barWidth));
2602
+ }
2603
+ }
2604
+ lines.push(
2605
+ anchorLine(`${yLabel}${axisChar}${segments.join(" ")}`, DEFAULT_ANCHOR)
2606
+ );
2607
+ }
2608
+ const xAxis = renderXAxis({
2609
+ categories: layout.stacks.map((s) => s.label),
2610
+ barWidth,
2611
+ yAxisWidth,
2612
+ chartWidth: layout.width,
2613
+ minValue: layout.yScale.min,
2614
+ format,
2615
+ decimals
2616
+ });
2617
+ lines.push(anchorLine(xAxis.axisLine, DEFAULT_ANCHOR));
2618
+ lines.push(anchorLine(xAxis.labelLine, DEFAULT_ANCHOR));
2619
+ } else {
2620
+ for (const stack of layout.stacks) {
2621
+ const label = padToWidth3(stack.label, layout.maxLabelWidth);
2622
+ let barStr = "";
2623
+ for (const segment of stack.segments) {
2624
+ const segmentStr = segment.barChar.repeat(segment.length);
2625
+ if (segment.useBackticks) {
2626
+ barStr += `\`${segmentStr}\``;
2627
+ } else {
2628
+ barStr += segmentStr;
2629
+ }
2630
+ }
2631
+ lines.push(anchorLine(`${label} ${barStr}`, DEFAULT_ANCHOR));
2632
+ }
2633
+ }
2634
+ const legendItems = layout.seriesNames.map((name, i) => {
2635
+ const style = layout.seriesStyles[i];
2636
+ if (!style) {
2637
+ throw new Error(`Missing style for series ${String(i)}`);
2638
+ }
2639
+ return {
2640
+ name,
2641
+ symbol: style.char,
2642
+ useBackticks: style.useBackticks
2643
+ };
2644
+ });
2645
+ const legendLayout = computeLegendLayout({
2646
+ items: legendItems,
2647
+ position: input.legend?.position ?? "bottom",
2648
+ maxWidth: layout.width
2649
+ });
2650
+ if (legendLayout.rows.length > 0) {
2651
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2652
+ for (const row of legendLayout.rows) {
2653
+ lines.push(anchorLine(renderLegendRow(row, true), DEFAULT_ANCHOR));
2654
+ }
2655
+ }
2656
+ return lines.join("\n");
2657
+ }
2658
+ function renderLineChartMarkdown(layout, options) {
2659
+ const { input } = options;
2660
+ const lines = [];
2661
+ const chartWidth = layout.rows[0]?.chars.length ?? layout.categories.length;
2662
+ for (const row of layout.rows) {
2663
+ const yLabel = row.yLabel ? padToWidth3(row.yLabel, layout.yAxisWidth - 1) + " " : " ".repeat(layout.yAxisWidth);
2664
+ let content = "";
2665
+ for (let i = 0; i < row.chars.length; i++) {
2666
+ const char = row.chars[i];
2667
+ const useBackticks = row.useBackticks[i];
2668
+ if (char === void 0 || useBackticks === void 0) {
2669
+ throw new Error(`Missing char or useBackticks at index ${String(i)}`);
2670
+ }
2671
+ content += wrapInlineCode(char, useBackticks && char !== " ");
2672
+ }
2673
+ lines.push(
2674
+ anchorLine(`${yLabel}${AXIS_CHARS.vertical}${content}`, DEFAULT_ANCHOR)
2675
+ );
2676
+ }
2677
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(chartWidth);
2678
+ lines.push(
2679
+ anchorLine(
2680
+ " ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine,
2681
+ DEFAULT_ANCHOR
2682
+ )
2683
+ );
2684
+ const labelLine = Array(chartWidth).fill(" ");
2685
+ const isBraille = layout.lineStyle === "braille";
2686
+ const spacing = isBraille ? 3 : 2;
2687
+ for (let i = 0; i < layout.categories.length; i++) {
2688
+ const label = layout.categories[i];
2689
+ if (label === void 0) {
2690
+ throw new Error(`Missing category at index ${String(i)}`);
2691
+ }
2692
+ const pos = isBraille ? i * spacing + Math.floor(spacing / 2) : i * spacing;
2693
+ if (pos < chartWidth) {
2694
+ labelLine[pos] = label.charAt(0) || " ";
2695
+ }
2696
+ }
2697
+ lines.push(
2698
+ anchorLine(
2699
+ " ".repeat(layout.yAxisWidth + 1) + labelLine.join(""),
2700
+ DEFAULT_ANCHOR
2701
+ )
2702
+ );
2703
+ if (layout.seriesNames.length > 1) {
2704
+ const legendItems = layout.seriesNames.map((name, i) => {
2705
+ return {
2706
+ name,
2707
+ symbol: "\u28FF",
2708
+ // Use braille full block for line chart legend
2709
+ useBackticks: i % 2 === 1
2710
+ };
2711
+ });
2712
+ const legendLayout = computeLegendLayout({
2713
+ items: legendItems,
2714
+ position: input.legend?.position ?? "bottom",
2715
+ maxWidth: layout.width
2716
+ });
2717
+ if (legendLayout.rows.length > 0) {
2718
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2719
+ for (const row of legendLayout.rows) {
2720
+ lines.push(anchorLine(renderLegendRow(row, true), DEFAULT_ANCHOR));
2721
+ }
2722
+ }
2723
+ }
2724
+ return lines.join("\n");
2725
+ }
2726
+ function renderAreaChartMarkdown(layout, options) {
2727
+ const { input } = options;
2728
+ const lines = [];
2729
+ for (const row of layout.rows) {
2730
+ const yLabel = row.yLabel ? padToWidth3(row.yLabel, layout.yAxisWidth - 1) + " " : " ".repeat(layout.yAxisWidth);
2731
+ let content = "";
2732
+ for (let i = 0; i < row.chars.length; i++) {
2733
+ const char = row.chars[i];
2734
+ const useBackticks = row.useBackticks[i];
2735
+ if (char === void 0 || useBackticks === void 0) {
2736
+ throw new Error(`Missing char or useBackticks at index ${String(i)}`);
2737
+ }
2738
+ content += wrapInlineCode(char, useBackticks && char !== " ");
2739
+ }
2740
+ lines.push(
2741
+ anchorLine(`${yLabel}${AXIS_CHARS.vertical}${content}`, DEFAULT_ANCHOR)
2742
+ );
2743
+ }
2744
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.categories.length);
2745
+ lines.push(
2746
+ anchorLine(
2747
+ " ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine,
2748
+ DEFAULT_ANCHOR
2749
+ )
2750
+ );
2751
+ const xLabels = layout.categories.map((c) => c.charAt(0) || " ").join("");
2752
+ lines.push(
2753
+ anchorLine(" ".repeat(layout.yAxisWidth + 1) + xLabels, DEFAULT_ANCHOR)
2754
+ );
2755
+ const legendItems = layout.seriesNames.map((name, i) => {
2756
+ const style = layout.seriesStyles[i];
2757
+ if (!style) {
2758
+ throw new Error(`Missing style for series ${String(i)}`);
2759
+ }
2760
+ return {
2761
+ name,
2762
+ symbol: style.char,
2763
+ useBackticks: style.useBackticks
2764
+ };
2765
+ });
2766
+ const legendLayout = computeLegendLayout({
2767
+ items: legendItems,
2768
+ position: input.legend?.position ?? "bottom",
2769
+ maxWidth: layout.width
2770
+ });
2771
+ if (legendLayout.rows.length > 0) {
2772
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2773
+ for (const row of legendLayout.rows) {
2774
+ lines.push(anchorLine(renderLegendRow(row, true), DEFAULT_ANCHOR));
2775
+ }
2776
+ }
2777
+ return lines.join("\n");
2778
+ }
2779
+ function renderScatterChartMarkdown(layout, options) {
2780
+ const { input } = options;
2781
+ const lines = [];
2782
+ const format = input.yAxis?.format ?? "number";
2783
+ const decimals = input.yAxis?.decimals;
2784
+ for (let rowIndex = 0; rowIndex < layout.chartHeight; rowIndex++) {
2785
+ let yLabel = " ".repeat(layout.yAxisWidth);
2786
+ const rowTop = 1 - rowIndex / layout.chartHeight;
2787
+ const rowBottom = 1 - (rowIndex + 1) / layout.chartHeight;
2788
+ for (const tick of layout.yScale.ticks) {
2789
+ const tickNorm = (tick - layout.yScale.min) / (layout.yScale.max - layout.yScale.min);
2790
+ if (tickNorm > rowBottom && tickNorm <= rowTop) {
2791
+ yLabel = padToWidth3(
2792
+ formatTickValue(tick, format, decimals),
2793
+ layout.yAxisWidth - 1
2794
+ ) + " ";
2795
+ break;
2796
+ }
2797
+ }
2798
+ let content = "";
2799
+ if (layout.scatterStyle === "braille" && layout.brailleChars) {
2800
+ const rowChars = layout.brailleChars[rowIndex] ?? [];
2801
+ const rowIndices = layout.brailleSeriesIndices?.[rowIndex] ?? [];
2802
+ for (let i = 0; i < rowChars.length; i++) {
2803
+ const char = rowChars[i];
2804
+ if (char === void 0) {
2805
+ throw new Error(`Missing char at index ${String(i)}`);
2806
+ }
2807
+ const seriesIdx = rowIndices[i];
2808
+ const useBackticks = seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1;
2809
+ content += wrapInlineCode(char, useBackticks && char !== " ");
2810
+ }
2811
+ } else if (layout.grid) {
2812
+ const rowChars = layout.grid[rowIndex] ?? [];
2813
+ const rowIndices = layout.seriesIndices?.[rowIndex] ?? [];
2814
+ for (let i = 0; i < rowChars.length; i++) {
2815
+ const char = rowChars[i];
2816
+ if (char === void 0) {
2817
+ throw new Error(`Missing char at index ${String(i)}`);
2818
+ }
2819
+ const seriesIdx = rowIndices[i];
2820
+ const useBackticks = seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1;
2821
+ content += wrapInlineCode(char, useBackticks && char !== " ");
2822
+ }
2823
+ }
2824
+ lines.push(
2825
+ anchorLine(`${yLabel}${AXIS_CHARS.vertical}${content}`, DEFAULT_ANCHOR)
2826
+ );
2827
+ }
2828
+ const xAxisLine = AXIS_CHARS.horizontal.repeat(layout.chartWidth);
2829
+ lines.push(
2830
+ anchorLine(
2831
+ " ".repeat(layout.yAxisWidth) + AXIS_CHARS.origin + xAxisLine,
2832
+ DEFAULT_ANCHOR
2833
+ )
2834
+ );
2835
+ const xFormat = input.xAxis?.format ?? "number";
2836
+ const xDecimals = input.xAxis?.decimals;
2837
+ const labelPositions = [];
2838
+ for (const tick of layout.xScale.ticks) {
2839
+ const norm = (tick - layout.xScale.min) / (layout.xScale.max - layout.xScale.min);
2840
+ const pos = Math.round(norm * (layout.chartWidth - 1));
2841
+ const label = formatTickValue(tick, xFormat, xDecimals);
2842
+ labelPositions.push({ pos, label });
2843
+ }
2844
+ const labelLine = Array(layout.chartWidth).fill(" ");
2845
+ for (const { pos, label } of labelPositions) {
2846
+ const halfLen = Math.floor(label.length / 2);
2847
+ const startPos = Math.max(
2848
+ 0,
2849
+ Math.min(layout.chartWidth - label.length, pos - halfLen)
2850
+ );
2851
+ for (let i = 0; i < label.length && startPos + i < layout.chartWidth; i++) {
2852
+ const char = label[i];
2853
+ if (char === void 0) {
2854
+ throw new Error(`Missing char at index ${String(i)}`);
2855
+ }
2856
+ labelLine[startPos + i] = char;
2857
+ }
2858
+ }
2859
+ lines.push(
2860
+ anchorLine(
2861
+ " ".repeat(layout.yAxisWidth + 1) + labelLine.join(""),
2862
+ DEFAULT_ANCHOR
2863
+ )
2864
+ );
2865
+ if (layout.seriesNames.length > 1) {
2866
+ const legendItems = layout.seriesNames.map((name, i) => {
2867
+ const symbols = ["\u25CF", "\u25A0", "\u25B2", "\u25C6", "+"];
2868
+ const symbol = layout.scatterStyle === "braille" ? "\u28FF" : symbols[i % 5];
2869
+ if (!symbol) {
2870
+ throw new Error(`Missing symbol for series ${String(i)}`);
2871
+ }
2872
+ return {
2873
+ name,
2874
+ symbol,
2875
+ useBackticks: i % 2 === 1
2876
+ };
2877
+ });
2878
+ const legendLayout = computeLegendLayout({
2879
+ items: legendItems,
2880
+ position: input.legend?.position ?? "bottom",
2881
+ maxWidth: layout.width
2882
+ });
2883
+ if (legendLayout.rows.length > 0) {
2884
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2885
+ for (const row of legendLayout.rows) {
2886
+ lines.push(anchorLine(renderLegendRow(row, true), DEFAULT_ANCHOR));
2887
+ }
2888
+ }
2889
+ }
2890
+ return lines.join("\n");
2891
+ }
2892
+ function renderPieChartMarkdown(layout, options) {
2893
+ const { input } = options;
2894
+ const lines = [];
2895
+ if (layout.slices.length === 0) {
2896
+ lines.push(anchorLine("No data", DEFAULT_ANCHOR));
2897
+ return lines.join("\n");
2898
+ }
2899
+ for (let rowIndex = 0; rowIndex < layout.brailleChars.length; rowIndex++) {
2900
+ const rowChars = layout.brailleChars[rowIndex] ?? [];
2901
+ const rowIndices = layout.brailleSeriesIndices[rowIndex] ?? [];
2902
+ let content = "";
2903
+ for (let i = 0; i < rowChars.length; i++) {
2904
+ const char = rowChars[i];
2905
+ if (char === void 0) {
2906
+ throw new Error(`Missing char at index ${String(i)}`);
2907
+ }
2908
+ const seriesIdx = rowIndices[i];
2909
+ const useBackticks = seriesIdx !== null && seriesIdx !== void 0 && seriesIdx % 2 === 1;
2910
+ content += wrapInlineCode(char, useBackticks && char !== " ");
2911
+ }
2912
+ if (layout.type === "donut" && layout.centerLabel && rowIndex === Math.floor(layout.brailleChars.length / 2)) {
2913
+ const labelLen = layout.centerLabel.length;
2914
+ const startPos = Math.floor((layout.width - labelLen) / 2);
2915
+ if (startPos >= 0) {
2916
+ const contentArray = [...content];
2917
+ for (let i = 0; i < labelLen && startPos + i < contentArray.length; i++) {
2918
+ const char = layout.centerLabel[i];
2919
+ if (char === void 0) {
2920
+ throw new Error(`Missing centerLabel char at index ${String(i)}`);
2921
+ }
2922
+ contentArray[startPos + i] = char;
2923
+ }
2924
+ content = contentArray.join("");
2925
+ }
2926
+ }
2927
+ lines.push(anchorLine(content, DEFAULT_ANCHOR));
2928
+ }
2929
+ const legendItems = layout.slices.map((slice) => ({
2930
+ name: `${slice.label} ${slice.percentage.toFixed(0)}%`,
2931
+ symbol: slice.barChar,
2932
+ useBackticks: slice.useBackticks
2933
+ }));
2934
+ const legendLayout = computeLegendLayout({
2935
+ items: legendItems,
2936
+ position: input.legend?.position ?? "bottom",
2937
+ maxWidth: layout.width
2938
+ });
2939
+ if (legendLayout.rows.length > 0) {
2940
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2941
+ for (const row of legendLayout.rows) {
2942
+ lines.push(anchorLine(renderLegendRow(row, true), DEFAULT_ANCHOR));
2943
+ }
2944
+ }
2945
+ return lines.join("\n");
2946
+ }
2947
+ function renderHeatmapMarkdown(layout, options) {
2948
+ const { input: _input } = options;
2949
+ const lines = [];
2950
+ if (layout.cells.length === 0 || (layout.cells[0]?.length ?? 0) === 0) {
2951
+ lines.push(anchorLine("No data", DEFAULT_ANCHOR));
2952
+ return lines.join("\n");
2953
+ }
2954
+ const colHeaderPadding = " ".repeat(layout.rowLabelWidth);
2955
+ const colHeaders = layout.colLabels.map((label) => padToWidth3(label, layout.colLabelWidth)).join("");
2956
+ lines.push(anchorLine(`${colHeaderPadding}${colHeaders}`, DEFAULT_ANCHOR));
2957
+ for (let rowIdx = 0; rowIdx < layout.rowLabels.length; rowIdx++) {
2958
+ const rowLabelText = layout.rowLabels[rowIdx];
2959
+ if (!rowLabelText) continue;
2960
+ const rowLabel = padToWidth3(rowLabelText, layout.rowLabelWidth);
2961
+ const rowCells = layout.cells[rowIdx];
2962
+ if (!rowCells) continue;
2963
+ let rowContent = "";
2964
+ for (const cell of rowCells) {
2965
+ if (layout.heatmapStyle === "numeric") {
2966
+ rowContent += padToWidth3(cell.displayChar, layout.colLabelWidth);
2967
+ } else {
2968
+ const useBackticks = cell.normalizedValue > 0.5;
2969
+ const paddedChar = padToWidth3(cell.displayChar, layout.colLabelWidth);
2970
+ const styled = wrapInlineCode(paddedChar, useBackticks);
2971
+ rowContent += styled;
2972
+ }
2973
+ }
2974
+ lines.push(anchorLine(`${rowLabel}${rowContent}`, DEFAULT_ANCHOR));
2975
+ }
2976
+ if (layout.heatmapStyle !== "numeric") {
2977
+ lines.push(anchorLine("", DEFAULT_ANCHOR));
2978
+ const scaleChars = layout.heatmapStyle === "blocks" ? ["\u2591", "\u2592", "\u2593", "\u2588"] : [".", ":", "*", "#"];
2979
+ const scaleLabels = ["Low", "", "", "High"];
2980
+ let scaleLine = "Scale: ";
2981
+ for (let i = 0; i < scaleChars.length; i++) {
2982
+ const char = scaleChars[i] ?? "";
2983
+ const label = scaleLabels[i] ?? "";
2984
+ scaleLine += `${char} ${label}`;
2985
+ if (i < scaleChars.length - 1) scaleLine += " ";
2986
+ }
2987
+ lines.push(anchorLine(scaleLine, DEFAULT_ANCHOR));
2988
+ }
2989
+ return lines.join("\n");
2990
+ }
2991
+
2992
+ // src/chart.ts
2993
+ var ChartComponent = class extends BaseTuiComponent {
2994
+ metadata = {
2995
+ name: "chart",
2996
+ description: "Renders various chart types including bar, line, and area charts",
2997
+ version: "0.1.0",
2998
+ supportedModes: ["ansi", "markdown"],
2999
+ examples: [
3000
+ {
3001
+ name: "horizontal-bar",
3002
+ description: "Simple horizontal bar chart",
3003
+ input: {
3004
+ type: "bar",
3005
+ series: [
3006
+ {
3007
+ name: "Sales",
3008
+ data: [
3009
+ { x: "Q1", y: 120 },
3010
+ { x: "Q2", y: 150 },
3011
+ { x: "Q3", y: 180 },
3012
+ { x: "Q4", y: 200 }
3013
+ ]
3014
+ }
3015
+ ],
3016
+ showValues: true
3017
+ }
3018
+ },
3019
+ {
3020
+ name: "vertical-bar",
3021
+ description: "Vertical bar chart (column chart)",
3022
+ input: {
3023
+ type: "bar-vertical",
3024
+ series: [
3025
+ {
3026
+ name: "Revenue",
3027
+ data: [
3028
+ { x: "Jan", y: 65 },
3029
+ { x: "Feb", y: 80 },
3030
+ { x: "Mar", y: 95 },
3031
+ { x: "Apr", y: 70 }
3032
+ ]
3033
+ }
3034
+ ],
3035
+ height: 8,
3036
+ width: 30
3037
+ }
3038
+ },
3039
+ {
3040
+ name: "stacked-bar",
3041
+ description: "Stacked horizontal bar chart",
3042
+ input: {
3043
+ type: "bar-stacked",
3044
+ series: [
3045
+ {
3046
+ name: "Product A",
3047
+ data: [
3048
+ { x: "Q1", y: 50 },
3049
+ { x: "Q2", y: 60 },
3050
+ { x: "Q3", y: 70 }
3051
+ ]
3052
+ },
3053
+ {
3054
+ name: "Product B",
3055
+ data: [
3056
+ { x: "Q1", y: 30 },
3057
+ { x: "Q2", y: 40 },
3058
+ { x: "Q3", y: 35 }
3059
+ ]
3060
+ }
3061
+ ],
3062
+ width: 40
3063
+ }
3064
+ },
3065
+ {
3066
+ name: "stacked-vertical",
3067
+ description: "Stacked vertical bar chart",
3068
+ input: {
3069
+ type: "bar-stacked-vertical",
3070
+ series: [
3071
+ {
3072
+ name: "Revenue",
3073
+ data: [
3074
+ { x: "Q1", y: 100 },
3075
+ { x: "Q2", y: 120 },
3076
+ { x: "Q3", y: 90 },
3077
+ { x: "Q4", y: 150 }
3078
+ ]
3079
+ },
3080
+ {
3081
+ name: "Costs",
3082
+ data: [
3083
+ { x: "Q1", y: 60 },
3084
+ { x: "Q2", y: 70 },
3085
+ { x: "Q3", y: 55 },
3086
+ { x: "Q4", y: 80 }
3087
+ ]
3088
+ },
3089
+ {
3090
+ name: "Profit",
3091
+ data: [
3092
+ { x: "Q1", y: 40 },
3093
+ { x: "Q2", y: 50 },
3094
+ { x: "Q3", y: 35 },
3095
+ { x: "Q4", y: 70 }
3096
+ ]
3097
+ }
3098
+ ],
3099
+ height: 10,
3100
+ width: 35
3101
+ }
3102
+ },
3103
+ {
3104
+ name: "line",
3105
+ description: "Line chart with height blocks",
3106
+ input: {
3107
+ type: "line",
3108
+ series: [
3109
+ {
3110
+ name: "Temperature",
3111
+ data: [
3112
+ { x: "J", y: 30 },
3113
+ { x: "F", y: 35 },
3114
+ { x: "M", y: 50 },
3115
+ { x: "A", y: 65 },
3116
+ { x: "M", y: 75 },
3117
+ { x: "J", y: 85 },
3118
+ { x: "J", y: 90 },
3119
+ { x: "A", y: 88 },
3120
+ { x: "S", y: 78 },
3121
+ { x: "O", y: 62 },
3122
+ { x: "N", y: 45 },
3123
+ { x: "D", y: 32 }
3124
+ ]
3125
+ }
3126
+ ],
3127
+ height: 8,
3128
+ width: 20
3129
+ }
3130
+ },
3131
+ {
3132
+ name: "multi-line",
3133
+ description: "Multiple series line chart",
3134
+ input: {
3135
+ type: "line",
3136
+ series: [
3137
+ {
3138
+ name: "2023",
3139
+ data: [
3140
+ { x: "Q1", y: 100 },
3141
+ { x: "Q2", y: 120 },
3142
+ { x: "Q3", y: 110 },
3143
+ { x: "Q4", y: 140 }
3144
+ ]
3145
+ },
3146
+ {
3147
+ name: "2024",
3148
+ data: [
3149
+ { x: "Q1", y: 130 },
3150
+ { x: "Q2", y: 145 },
3151
+ { x: "Q3", y: 135 },
3152
+ { x: "Q4", y: 160 }
3153
+ ]
3154
+ }
3155
+ ],
3156
+ height: 6,
3157
+ width: 20
3158
+ }
3159
+ },
3160
+ {
3161
+ name: "area",
3162
+ description: "Area chart",
3163
+ input: {
3164
+ type: "area",
3165
+ series: [
3166
+ {
3167
+ name: "Users",
3168
+ data: [
3169
+ { x: "W1", y: 100 },
3170
+ { x: "W2", y: 150 },
3171
+ { x: "W3", y: 180 },
3172
+ { x: "W4", y: 160 },
3173
+ { x: "W5", y: 200 }
3174
+ ]
3175
+ }
3176
+ ],
3177
+ height: 6,
3178
+ width: 15
3179
+ }
3180
+ },
3181
+ {
3182
+ name: "stacked-area",
3183
+ description: "Stacked area chart",
3184
+ input: {
3185
+ type: "area-stacked",
3186
+ series: [
3187
+ {
3188
+ name: "Mobile",
3189
+ data: [
3190
+ { x: "J", y: 50 },
3191
+ { x: "F", y: 60 },
3192
+ { x: "M", y: 70 },
3193
+ { x: "A", y: 65 }
3194
+ ]
3195
+ },
3196
+ {
3197
+ name: "Desktop",
3198
+ data: [
3199
+ { x: "J", y: 100 },
3200
+ { x: "F", y: 90 },
3201
+ { x: "M", y: 85 },
3202
+ { x: "A", y: 95 }
3203
+ ]
3204
+ }
3205
+ ],
3206
+ height: 8,
3207
+ width: 15
3208
+ }
3209
+ },
3210
+ {
3211
+ name: "scatter",
3212
+ description: "Scatter plot with numeric axes",
3213
+ input: {
3214
+ type: "scatter",
3215
+ series: [
3216
+ {
3217
+ name: "Data",
3218
+ data: [
3219
+ { x: 10, y: 20 },
3220
+ { x: 30, y: 50 },
3221
+ { x: 50, y: 30 },
3222
+ { x: 70, y: 80 },
3223
+ { x: 90, y: 60 }
3224
+ ]
3225
+ }
3226
+ ],
3227
+ height: 8,
3228
+ width: 30
3229
+ }
3230
+ },
3231
+ {
3232
+ name: "pie",
3233
+ description: "Pie chart with percentage breakdown",
3234
+ input: {
3235
+ type: "pie",
3236
+ series: [
3237
+ {
3238
+ name: "Revenue",
3239
+ data: [
3240
+ { label: "Sales", x: "Sales", y: 45 },
3241
+ { label: "Support", x: "Support", y: 30 },
3242
+ { label: "Other", x: "Other", y: 25 }
3243
+ ]
3244
+ }
3245
+ ],
3246
+ height: 10,
3247
+ width: 30
3248
+ }
3249
+ },
3250
+ {
3251
+ name: "donut",
3252
+ description: "Donut chart with center label",
3253
+ input: {
3254
+ type: "donut",
3255
+ series: [
3256
+ {
3257
+ name: "Market Share",
3258
+ data: [
3259
+ { label: "Chrome", x: "Chrome", y: 65 },
3260
+ { label: "Firefox", x: "Firefox", y: 20 },
3261
+ { label: "Safari", x: "Safari", y: 15 }
3262
+ ]
3263
+ }
3264
+ ],
3265
+ height: 10,
3266
+ width: 30,
3267
+ centerLabel: "100%",
3268
+ innerRadius: 0.5
3269
+ }
3270
+ },
3271
+ {
3272
+ name: "heatmap",
3273
+ description: "Heatmap showing intensity values",
3274
+ input: {
3275
+ type: "heatmap",
3276
+ series: [
3277
+ {
3278
+ name: "Activity",
3279
+ data: [
3280
+ { x: "Mon", y: 10, label: "9am" },
3281
+ { x: "Tue", y: 50, label: "9am" },
3282
+ { x: "Wed", y: 80, label: "9am" },
3283
+ { x: "Mon", y: 30, label: "10am" },
3284
+ { x: "Tue", y: 70, label: "10am" },
3285
+ { x: "Wed", y: 90, label: "10am" }
3286
+ ]
3287
+ }
3288
+ ],
3289
+ height: 6,
3290
+ width: 25,
3291
+ heatmapStyle: "blocks"
3292
+ }
3293
+ }
3294
+ ]
3295
+ };
3296
+ schema = chartInputSchema;
3297
+ getJsonSchema() {
3298
+ return zodToJsonSchema(this.schema, {
3299
+ name: this.metadata.name,
3300
+ $refStrategy: "none"
3301
+ });
3302
+ }
3303
+ render(input, context) {
3304
+ const parsed = this.schema.parse(input);
3305
+ if (parsed.series.length === 0 || parsed.series.every((s) => s.data.length === 0)) {
3306
+ return { output: "", actualWidth: 0, lineCount: 0 };
3307
+ }
3308
+ let output;
3309
+ switch (parsed.type) {
3310
+ case "bar": {
3311
+ const layout = computeBarLayout(parsed);
3312
+ output = context.renderMode === "markdown" ? renderBarChartMarkdown(layout, { input: parsed }) : renderBarChartAnsi(layout, {
3313
+ theme: context.theme,
3314
+ input: parsed
3315
+ });
3316
+ break;
3317
+ }
3318
+ case "bar-vertical": {
3319
+ const layout = computeBarLayout(parsed);
3320
+ output = context.renderMode === "markdown" ? renderVerticalBarChartMarkdown(layout, { input: parsed }) : renderVerticalBarChartAnsi(layout, {
3321
+ theme: context.theme,
3322
+ input: parsed
3323
+ });
3324
+ break;
3325
+ }
3326
+ case "bar-stacked":
3327
+ case "bar-stacked-vertical": {
3328
+ const layout = computeStackedBarLayout(parsed);
3329
+ output = context.renderMode === "markdown" ? renderStackedBarChartMarkdown(layout, { input: parsed }) : renderStackedBarChartAnsi(layout, {
3330
+ theme: context.theme,
3331
+ input: parsed
3332
+ });
3333
+ break;
3334
+ }
3335
+ case "line": {
3336
+ const layout = computeLineLayout(parsed);
3337
+ output = context.renderMode === "markdown" ? renderLineChartMarkdown(layout, { input: parsed }) : renderLineChartAnsi(layout, {
3338
+ theme: context.theme,
3339
+ input: parsed
3340
+ });
3341
+ break;
3342
+ }
3343
+ case "area":
3344
+ case "area-stacked": {
3345
+ const layout = computeAreaLayout(parsed);
3346
+ output = context.renderMode === "markdown" ? renderAreaChartMarkdown(layout, { input: parsed }) : renderAreaChartAnsi(layout, {
3347
+ theme: context.theme,
3348
+ input: parsed
3349
+ });
3350
+ break;
3351
+ }
3352
+ case "scatter": {
3353
+ const layout = computeScatterLayout(parsed);
3354
+ output = context.renderMode === "markdown" ? renderScatterChartMarkdown(layout, { input: parsed }) : renderScatterChartAnsi(layout, {
3355
+ theme: context.theme,
3356
+ input: parsed
3357
+ });
3358
+ break;
3359
+ }
3360
+ case "pie":
3361
+ case "donut": {
3362
+ const layout = computePieLayout(parsed);
3363
+ output = context.renderMode === "markdown" ? renderPieChartMarkdown(layout, { input: parsed }) : renderPieChartAnsi(layout, {
3364
+ theme: context.theme,
3365
+ input: parsed
3366
+ });
3367
+ break;
3368
+ }
3369
+ case "heatmap": {
3370
+ const layout = computeHeatmapLayout(parsed);
3371
+ output = context.renderMode === "markdown" ? renderHeatmapMarkdown(layout, { input: parsed }) : renderHeatmapAnsi(layout, {
3372
+ theme: context.theme,
3373
+ input: parsed
3374
+ });
3375
+ break;
3376
+ }
3377
+ default: {
3378
+ const _exhaustiveCheck = parsed.type;
3379
+ throw new Error(`Unknown chart type: ${String(_exhaustiveCheck)}`);
3380
+ }
3381
+ }
3382
+ if (parsed.title) {
3383
+ const titleLine = context.theme ? context.theme.semantic.header(parsed.title) : parsed.title;
3384
+ output = titleLine + "\n" + output;
3385
+ }
3386
+ const measured = measureLines(output);
3387
+ return {
3388
+ output,
3389
+ actualWidth: measured.maxWidth,
3390
+ lineCount: measured.lineCount
3391
+ };
3392
+ }
3393
+ };
3394
+ function createChart() {
3395
+ return new ChartComponent();
3396
+ }
3397
+ registry.register(createChart);
3398
+
3399
+ // src/core/axis.ts
3400
+ import { getStringWidth as getStringWidth8 } from "@tuicomponents/core";
3401
+ function computeAxisLayout(options) {
3402
+ const {
3403
+ dataMin,
3404
+ dataMax,
3405
+ size,
3406
+ orientation,
3407
+ position,
3408
+ tickCount = 5,
3409
+ showTicks = true,
3410
+ format = "number",
3411
+ decimals,
3412
+ label,
3413
+ forceMin,
3414
+ forceMax,
3415
+ includeZero = true
3416
+ } = options;
3417
+ const scale = computeNiceTicks({
3418
+ dataMin,
3419
+ dataMax,
3420
+ tickCount,
3421
+ includeZero,
3422
+ forceMin,
3423
+ forceMax
3424
+ });
3425
+ const ticks = scale.ticks.map((value) => {
3426
+ const label2 = formatTickValue(value, format, decimals);
3427
+ const normalizedPosition = (value - scale.min) / (scale.max - scale.min);
3428
+ const position2 = orientation === "vertical" ? size * (1 - normalizedPosition) : size * normalizedPosition;
3429
+ return { value, label: label2, position: position2 };
3430
+ });
3431
+ let maxLabelWidth = 0;
3432
+ for (const tick of ticks) {
3433
+ const width = getStringWidth8(tick.label);
3434
+ if (width > maxLabelWidth) {
3435
+ maxLabelWidth = width;
3436
+ }
3437
+ }
3438
+ let totalWidth;
3439
+ let totalHeight;
3440
+ if (orientation === "vertical") {
3441
+ totalWidth = maxLabelWidth + (showTicks ? 2 : 1);
3442
+ totalHeight = size;
3443
+ } else {
3444
+ totalWidth = size;
3445
+ totalHeight = 1 + (label ? 1 : 0);
3446
+ }
3447
+ return {
3448
+ orientation,
3449
+ position,
3450
+ scale,
3451
+ ticks,
3452
+ maxLabelWidth,
3453
+ title: label,
3454
+ totalWidth,
3455
+ totalHeight
3456
+ };
3457
+ }
3458
+ function computeDualAxisLayout(options) {
3459
+ const {
3460
+ xMin,
3461
+ xMax,
3462
+ yMin,
3463
+ yMax,
3464
+ chartWidth,
3465
+ chartHeight,
3466
+ xAxis: xAxisOpts = {},
3467
+ yAxis: yAxisOpts = {}
3468
+ } = options;
3469
+ const yAxisInitial = computeAxisLayout({
3470
+ dataMin: yMin,
3471
+ dataMax: yMax,
3472
+ size: chartHeight,
3473
+ orientation: "vertical",
3474
+ position: "left",
3475
+ ...yAxisOpts
3476
+ });
3477
+ const chartAreaX = yAxisInitial.totalWidth;
3478
+ const chartAreaY = 0;
3479
+ const chartAreaWidth = Math.max(1, chartWidth - yAxisInitial.totalWidth);
3480
+ const chartAreaHeight = Math.max(1, chartHeight - 2);
3481
+ const yAxis = computeAxisLayout({
3482
+ dataMin: yMin,
3483
+ dataMax: yMax,
3484
+ size: chartAreaHeight,
3485
+ orientation: "vertical",
3486
+ position: "left",
3487
+ ...yAxisOpts
3488
+ });
3489
+ const xAxis = computeAxisLayout({
3490
+ dataMin: xMin,
3491
+ dataMax: xMax,
3492
+ size: chartAreaWidth,
3493
+ orientation: "horizontal",
3494
+ position: "bottom",
3495
+ ...xAxisOpts
3496
+ });
3497
+ return {
3498
+ xAxis,
3499
+ yAxis,
3500
+ chartAreaWidth,
3501
+ chartAreaHeight,
3502
+ chartAreaX,
3503
+ chartAreaY
3504
+ };
3505
+ }
3506
+
3507
+ // src/core/grid.ts
3508
+ var DEFAULT_GRID_CHAR = "\xB7";
3509
+ function computeGridLayout(options) {
3510
+ const {
3511
+ width,
3512
+ height,
3513
+ xAxis,
3514
+ yAxis,
3515
+ showHorizontal = true,
3516
+ showVertical = false,
3517
+ char = DEFAULT_GRID_CHAR
3518
+ } = options;
3519
+ const horizontalLines = [];
3520
+ const verticalLines = [];
3521
+ if (showHorizontal && yAxis) {
3522
+ for (const tick of yAxis.ticks) {
3523
+ if (tick.position === 0 || tick.position === height) continue;
3524
+ horizontalLines.push({
3525
+ orientation: "horizontal",
3526
+ position: Math.round(tick.position),
3527
+ start: 0,
3528
+ end: width
3529
+ });
3530
+ }
3531
+ }
3532
+ if (showVertical && xAxis) {
3533
+ for (const tick of xAxis.ticks) {
3534
+ if (tick.position === 0 || tick.position === width) continue;
3535
+ verticalLines.push({
3536
+ orientation: "vertical",
3537
+ position: Math.round(tick.position),
3538
+ start: 0,
3539
+ end: height
3540
+ });
3541
+ }
3542
+ }
3543
+ return {
3544
+ horizontalLines,
3545
+ verticalLines,
3546
+ char
3547
+ };
3548
+ }
3549
+ function createGridBuffer(width, height, grid) {
3550
+ const buffer = [];
3551
+ for (let y = 0; y < height; y++) {
3552
+ const row = [];
3553
+ for (let x = 0; x < width; x++) {
3554
+ row.push(" ");
3555
+ }
3556
+ buffer.push(row);
3557
+ }
3558
+ for (const line of grid.horizontalLines) {
3559
+ const y = line.position;
3560
+ if (y >= 0 && y < height) {
3561
+ const row = buffer[y];
3562
+ if (row) {
3563
+ for (let x = line.start; x < line.end && x < width; x++) {
3564
+ row[x] = grid.char;
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ for (const line of grid.verticalLines) {
3570
+ const x = line.position;
3571
+ if (x >= 0 && x < width) {
3572
+ for (let y = line.start; y < line.end && y < height; y++) {
3573
+ const row = buffer[y];
3574
+ if (row) {
3575
+ row[x] = grid.char;
3576
+ }
3577
+ }
3578
+ }
3579
+ }
3580
+ return buffer;
3581
+ }
3582
+ export {
3583
+ AXIS_CHARS,
3584
+ BAR_CHARS,
3585
+ BRAILLE_BASE,
3586
+ BRAILLE_DOTS,
3587
+ BrailleCanvas,
3588
+ ChartComponent,
3589
+ DEFAULT_GRID_CHAR,
3590
+ HEATMAP_ASCII,
3591
+ HEATMAP_BLOCKS,
3592
+ HEIGHT_BLOCKS,
3593
+ LINE_CHARS,
3594
+ SCATTER_MARKERS,
3595
+ SCATTER_MARKER_SEQUENCE,
3596
+ SERIES_STYLES,
3597
+ axisConfigSchema,
3598
+ barStyleSchema,
3599
+ buildChartRow,
3600
+ chartInputSchema,
3601
+ chartTypeSchema,
3602
+ computeAreaLayout,
3603
+ computeAxisLayout,
3604
+ computeBarLayout,
3605
+ computeDualAxisLayout,
3606
+ computeGridLayout,
3607
+ computeHeatmapLayout,
3608
+ computeLegendLayout,
3609
+ computeLineLayout,
3610
+ computeNiceTicks,
3611
+ computePieLayout,
3612
+ computeScatterLayout,
3613
+ computeStackedBarLayout,
3614
+ computeYAxisRow,
3615
+ createChart,
3616
+ createGridBuffer,
3617
+ dataPointSchema,
3618
+ dataSeriesSchema,
3619
+ formatLegendItem,
3620
+ formatTickValue,
3621
+ getBarChar,
3622
+ getLegendItemWidth,
3623
+ gridConfigSchema,
3624
+ groupBarsByCategory,
3625
+ heatmapStyleSchema,
3626
+ legendConfigSchema,
3627
+ legendPositionSchema,
3628
+ lineStyleSchema,
3629
+ renderAreaChartAnsi,
3630
+ renderAreaChartMarkdown,
3631
+ renderBarChartAnsi,
3632
+ renderBarChartMarkdown,
3633
+ renderHeatmapAnsi,
3634
+ renderHeatmapMarkdown,
3635
+ renderLegendRow,
3636
+ renderLineChartAnsi,
3637
+ renderLineChartMarkdown,
3638
+ renderPieChartAnsi,
3639
+ renderPieChartMarkdown,
3640
+ renderScatterChartAnsi,
3641
+ renderScatterChartMarkdown,
3642
+ renderStackedBarChartAnsi,
3643
+ renderStackedBarChartMarkdown,
3644
+ renderVerticalBarChartAnsi,
3645
+ renderVerticalBarChartMarkdown,
3646
+ renderXAxis,
3647
+ scaleValue,
3648
+ scatterMarkerSchema,
3649
+ scatterStyleSchema,
3650
+ toBrailleChar,
3651
+ unscaleValue,
3652
+ valueFormatSchema,
3653
+ valueToBlock,
3654
+ valueToHeatmapChar
3655
+ };
3656
+ //# sourceMappingURL=index.js.map