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