plotext-plus 1.0.1__py3-none-any.whl

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.
plotext_plus/api.py ADDED
@@ -0,0 +1,828 @@
1
+ # /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Modern Plotext API - A cleaner, more intuitive interface for creating terminal charts
6
+ This module provides both object-oriented and functional interfaces while maintaining
7
+ backward compatibility with the existing plotext API.
8
+ """
9
+
10
+ import plotext_plus._core as _core
11
+ from plotext_plus._output import set_output_mode, info, success, warning, error
12
+
13
+
14
+ class Chart:
15
+ """
16
+ Modern object-oriented interface for creating charts.
17
+
18
+ This class provides a chainable API for building charts with method chaining
19
+ and cleaner separation of concerns.
20
+ """
21
+
22
+ def __init__(self, use_banners=False, banner_title=None):
23
+ """
24
+ Initialize a new chart.
25
+
26
+ Args:
27
+ use_banners (bool): Whether to display charts in chuk-term banners
28
+ banner_title (str): Title for the banner (if enabled)
29
+ """
30
+ self.use_banners = use_banners
31
+ self.banner_title = banner_title
32
+ self._data = []
33
+ self._legend = None
34
+ self._config = {
35
+ 'title': None,
36
+ 'x_label': None,
37
+ 'y_label': None,
38
+ 'width': None,
39
+ 'height': None,
40
+ 'theme': 'default'
41
+ }
42
+
43
+ # Configure output mode
44
+ if use_banners:
45
+ set_output_mode(True, banner_title)
46
+ # When using banners, automatically set appropriate size to fit within banner
47
+ from plotext import _utility as _ut
48
+ banner_width = _ut.terminal_width() # This now returns adjusted width for banners
49
+ if banner_width:
50
+ self._config['width'] = banner_width
51
+
52
+ def scatter(self, x, y, marker=None, color=None, label=None):
53
+ """Add scatter plot data"""
54
+ self._data.append({
55
+ 'type': 'scatter',
56
+ 'x': x,
57
+ 'y': y,
58
+ 'marker': marker,
59
+ 'color': color,
60
+ 'label': label
61
+ })
62
+ return self
63
+
64
+ def line(self, x, y, marker=None, color=None, label=None):
65
+ """Add line plot data"""
66
+ self._data.append({
67
+ 'type': 'line',
68
+ 'x': x,
69
+ 'y': y,
70
+ 'marker': marker,
71
+ 'color': color,
72
+ 'label': label
73
+ })
74
+ return self
75
+
76
+ def bar(self, labels, values, color=None, horizontal=False):
77
+ """Add bar chart data"""
78
+ self._data.append({
79
+ 'type': 'bar',
80
+ 'labels': labels,
81
+ 'values': values,
82
+ 'color': color,
83
+ 'horizontal': horizontal
84
+ })
85
+ return self
86
+
87
+ def title(self, title):
88
+ """Set chart title"""
89
+ self._config['title'] = title
90
+ return self
91
+
92
+ def xlabel(self, label):
93
+ """Set x-axis label"""
94
+ self._config['x_label'] = label
95
+ return self
96
+
97
+ def ylabel(self, label):
98
+ """Set y-axis label"""
99
+ self._config['y_label'] = label
100
+ return self
101
+
102
+ def size(self, width=None, height=None):
103
+ """Set chart size"""
104
+ self._config['width'] = width
105
+ self._config['height'] = height
106
+ return self
107
+
108
+ def theme(self, theme_name):
109
+ """Set chart theme"""
110
+ self._config['theme'] = theme_name
111
+ return self
112
+
113
+ def banner_title(self, title):
114
+ """Set banner title (if banner mode is enabled)"""
115
+ self.banner_title = title
116
+ if self.use_banners:
117
+ set_output_mode(True, title)
118
+ return self
119
+
120
+ def legend(self, legend_instance=None):
121
+ """
122
+ Set or get legend for this chart
123
+
124
+ Args:
125
+ legend_instance (Legend): Legend instance to apply to chart
126
+
127
+ Returns:
128
+ Chart: Self for chaining, or current legend if no args provided
129
+ """
130
+ if legend_instance is None:
131
+ return self._legend
132
+
133
+ self._legend = legend_instance
134
+ legend_instance.apply_to_chart(self)
135
+ return self
136
+
137
+ def show(self):
138
+ """Render and display the chart"""
139
+ # Clear any existing plot data
140
+ _core.clear_figure()
141
+
142
+ # Configure plot settings
143
+ if self._config['title']:
144
+ _core.title(self._config['title'])
145
+ if self._config['x_label']:
146
+ _core.xlabel(self._config['x_label'])
147
+ if self._config['y_label']:
148
+ _core.ylabel(self._config['y_label'])
149
+ if self._config['width'] or self._config['height']:
150
+ _core.plot_size(self._config['width'], self._config['height'])
151
+
152
+ # Add data to plot
153
+ for data_item in self._data:
154
+ if data_item['type'] == 'scatter':
155
+ _core.scatter(
156
+ data_item['x'],
157
+ data_item['y'],
158
+ marker=data_item['marker'],
159
+ color=data_item['color'],
160
+ label=data_item['label']
161
+ )
162
+ elif data_item['type'] == 'line':
163
+ _core.plot(
164
+ data_item['x'],
165
+ data_item['y'],
166
+ marker=data_item['marker'],
167
+ color=data_item['color'],
168
+ label=data_item['label']
169
+ )
170
+ elif data_item['type'] == 'bar':
171
+ if data_item['horizontal']:
172
+ _core.horizontal_bar(data_item['labels'], data_item['values'], color=data_item['color'])
173
+ else:
174
+ _core.bar(data_item['labels'], data_item['values'], color=data_item['color'])
175
+ elif data_item['type'] == 'histogram':
176
+ _core.hist(data_item['data'], bins=data_item['bins'], color=data_item['color'])
177
+
178
+ # Display the chart
179
+ _core.show()
180
+ return self
181
+
182
+ def save(self, path, format='txt'):
183
+ """Save chart to file"""
184
+ if format == 'html':
185
+ _core.save_fig(path, keep_colors=True)
186
+ else:
187
+ _core.save_fig(path)
188
+ return self
189
+
190
+
191
+ class ScatterChart(Chart):
192
+ """
193
+ Specialized class for creating scatter plots with a focused API
194
+ """
195
+
196
+ def __init__(self, x, y, marker=None, color=None, label=None, use_banners=False, banner_title=None):
197
+ """Initialize a scatter chart with data"""
198
+ super().__init__(use_banners, banner_title)
199
+ self.scatter(x, y, marker, color, label)
200
+
201
+ def add_trend_line(self, x, y, color='red', label='Trend'):
202
+ """Add a trend line to the scatter plot"""
203
+ self.line(x, y, color=color, label=label)
204
+ return self
205
+
206
+ def add_regression(self):
207
+ """Add linear regression line (future enhancement)"""
208
+ # Placeholder for regression functionality
209
+ return self
210
+
211
+
212
+ class LineChart(Chart):
213
+ """
214
+ Specialized class for creating line charts with enhanced features
215
+ """
216
+
217
+ def __init__(self, x, y, marker=None, color=None, label=None, use_banners=False, banner_title=None):
218
+ """Initialize a line chart with data"""
219
+ super().__init__(use_banners, banner_title)
220
+ self.line(x, y, marker, color, label)
221
+
222
+ def add_fill(self, fillx=False, filly=False):
223
+ """Add fill under the line (future enhancement)"""
224
+ # Placeholder for fill functionality
225
+ return self
226
+
227
+ def smooth(self, window_size=3):
228
+ """Apply smoothing to the line (future enhancement)"""
229
+ # Placeholder for smoothing functionality
230
+ return self
231
+
232
+
233
+ class BarChart(Chart):
234
+ """
235
+ Specialized class for creating bar charts with extensive customization
236
+ """
237
+
238
+ def __init__(self, labels, values, color=None, horizontal=False, use_banners=False, banner_title=None):
239
+ """Initialize a bar chart with data"""
240
+ super().__init__(use_banners, banner_title)
241
+ self.bar(labels, values, color, horizontal)
242
+ self.labels = labels
243
+ self.values = values
244
+
245
+ def stack(self, values, color=None, label=None):
246
+ """Add stacked bars (future enhancement)"""
247
+ # Placeholder for stacked bar functionality
248
+ return self
249
+
250
+ def group(self, values, color=None, label=None):
251
+ """Add grouped bars (future enhancement)"""
252
+ # Placeholder for grouped bar functionality
253
+ return self
254
+
255
+ def sort_by_value(self, ascending=True):
256
+ """Sort bars by value"""
257
+ # Simple implementation for sorting
258
+ sorted_pairs = sorted(zip(self.values, self.labels), reverse=not ascending)
259
+ self.values, self.labels = zip(*sorted_pairs)
260
+ return self
261
+
262
+
263
+ class HistogramChart(Chart):
264
+ """
265
+ Specialized class for creating histograms with statistical features
266
+ """
267
+
268
+ def __init__(self, data, bins=20, color=None, use_banners=False, banner_title=None):
269
+ """Initialize a histogram with data"""
270
+ super().__init__(use_banners, banner_title)
271
+ self.data = data
272
+ self.bins = bins
273
+ self._create_histogram(data, bins, color)
274
+
275
+ def _create_histogram(self, data, bins, color):
276
+ """Create histogram from raw data"""
277
+ # Add histogram data to the chart
278
+ self._data.append({
279
+ 'type': 'histogram',
280
+ 'data': data,
281
+ 'bins': bins,
282
+ 'color': color
283
+ })
284
+ return self
285
+
286
+ def add_normal_curve(self):
287
+ """Overlay a normal distribution curve (future enhancement)"""
288
+ # Placeholder for normal curve overlay
289
+ return self
290
+
291
+ def add_statistics(self):
292
+ """Add mean, median, std dev lines (future enhancement)"""
293
+ # Placeholder for statistical lines
294
+ return self
295
+
296
+
297
+ class CandlestickChart(Chart):
298
+ """
299
+ Specialized class for financial candlestick charts
300
+ """
301
+
302
+ def __init__(self, dates, data, colors=None, use_banners=False, banner_title=None):
303
+ """
304
+ Initialize a candlestick chart
305
+
306
+ Args:
307
+ dates: List of dates
308
+ data: List of [open, high, low, close] values
309
+ colors: Optional color scheme for up/down candles
310
+ """
311
+ super().__init__(use_banners, banner_title)
312
+ self.dates = dates
313
+ self.data = data
314
+ self._data.append({
315
+ 'type': 'candlestick',
316
+ 'dates': dates,
317
+ 'data': data,
318
+ 'colors': colors
319
+ })
320
+
321
+ def add_volume(self, volumes, color='blue'):
322
+ """Add volume bars below candlesticks (future enhancement)"""
323
+ # Placeholder for volume functionality
324
+ return self
325
+
326
+ def add_moving_average(self, period=20, color='orange'):
327
+ """Add moving average line (future enhancement)"""
328
+ # Placeholder for moving average
329
+ return self
330
+
331
+ def show(self):
332
+ """Render and display the candlestick chart"""
333
+ _core.clear_figure()
334
+
335
+ if self._config['title']:
336
+ _core.title(self._config['title'])
337
+ if self._config['x_label']:
338
+ _core.xlabel(self._config['x_label'])
339
+ if self._config['y_label']:
340
+ _core.ylabel(self._config['y_label'])
341
+ if self._config['width'] or self._config['height']:
342
+ _core.plot_size(self._config['width'], self._config['height'])
343
+
344
+ for data_item in self._data:
345
+ if data_item['type'] == 'candlestick':
346
+ # Convert list format to dictionary format expected by plotext
347
+ dates = data_item['dates']
348
+ ohlc_data = data_item['data']
349
+
350
+ # Format data as expected by plotext candlestick function
351
+ formatted_data = {
352
+ 'Open': [item[0] for item in ohlc_data],
353
+ 'High': [item[1] for item in ohlc_data],
354
+ 'Low': [item[2] for item in ohlc_data],
355
+ 'Close': [item[3] for item in ohlc_data]
356
+ }
357
+
358
+ _core.candlestick(
359
+ dates,
360
+ formatted_data,
361
+ colors=data_item['colors']
362
+ )
363
+
364
+ _core.show()
365
+ return self
366
+
367
+
368
+ class HeatmapChart(Chart):
369
+ """
370
+ Specialized class for creating heatmaps and matrix visualizations
371
+ """
372
+
373
+ def __init__(self, data, colorscale=None, use_banners=False, banner_title=None):
374
+ """
375
+ Initialize a heatmap chart
376
+
377
+ Args:
378
+ data: 2D matrix or pandas DataFrame
379
+ colorscale: Color scale for the heatmap
380
+ """
381
+ super().__init__(use_banners, banner_title)
382
+ self.data = data
383
+ self.colorscale = colorscale
384
+ self._data.append({
385
+ 'type': 'heatmap',
386
+ 'data': data,
387
+ 'colorscale': colorscale
388
+ })
389
+
390
+ def annotate(self, show_values=True):
391
+ """Add value annotations to cells (future enhancement)"""
392
+ # Placeholder for annotations
393
+ return self
394
+
395
+ def show(self):
396
+ """Render and display the heatmap"""
397
+ _core.clear_figure()
398
+
399
+ # Set appropriate plot size for heatmaps FIRST - ensure full width usage
400
+ if self._config['width'] or self._config['height']:
401
+ _core.plotsize(self._config['width'], self._config['height'])
402
+ else:
403
+ # Default to full terminal width for better heatmap display
404
+ import plotext_plus._utility as _ut
405
+ terminal_width = _ut.terminal_width()
406
+ if terminal_width:
407
+ # Set reasonable dimensions for heatmap display
408
+ heatmap_height = max(20, len(self.data) * 6 + 10)
409
+ _core.plotsize(terminal_width - 4, heatmap_height)
410
+
411
+ # Configure plot settings (same as base Chart class)
412
+ if self._config['title']:
413
+ _core.title(self._config['title'])
414
+ if self._config['x_label']:
415
+ _core.xlabel(self._config['x_label'])
416
+ if self._config['y_label']:
417
+ _core.ylabel(self._config['y_label'])
418
+
419
+ for data_item in self._data:
420
+ if data_item['type'] == 'heatmap':
421
+ data = data_item['data']
422
+
423
+ # Check if data is a pandas DataFrame
424
+ if hasattr(data, 'columns'):
425
+ # It's already a DataFrame
426
+ _core.heatmap(data, color=data_item['colorscale'])
427
+ else:
428
+ # It's a list/matrix, create filled heatmap blocks
429
+ self._draw_filled_heatmap(data, data_item['colorscale'])
430
+
431
+ _core.show()
432
+ return self
433
+
434
+ def _draw_list_heatmap(self, matrix, colorscale):
435
+ """Draw a heatmap using continuous colored blocks"""
436
+ if not matrix or not matrix[0]:
437
+ return
438
+
439
+ rows = len(matrix)
440
+ cols = len(matrix[0])
441
+
442
+ # Flatten and normalize the data for color mapping
443
+ flat_data = [val for row in matrix for val in row]
444
+ min_val = min(flat_data)
445
+ max_val = max(flat_data)
446
+ value_range = max_val - min_val if max_val != min_val else 1
447
+
448
+ # Define color palette
449
+ color_palettes = {
450
+ 'plasma': ['black', 'purple', 'magenta', 'red', 'orange', 'yellow'],
451
+ 'viridis': ['black', 'blue', 'green', 'bright green', 'yellow'],
452
+ 'cool': ['cyan', 'blue', 'magenta', 'white'],
453
+ 'hot': ['black', 'red', 'orange', 'yellow', 'white'],
454
+ 'default': ['blue', 'cyan', 'green', 'yellow', 'red', 'magenta']
455
+ }
456
+ colors = color_palettes.get(colorscale, color_palettes['default'])
457
+
458
+ # Create heatmap using continuous filled rectangles for each cell
459
+ for row_idx in range(rows):
460
+ row_data = matrix[row_idx]
461
+ y_level = rows - row_idx - 1 # Flip so row 0 is at top
462
+
463
+ for col_idx, value in enumerate(row_data):
464
+ # Normalize value to get color
465
+ normalized = (value - min_val) / value_range
466
+ color_idx = int(normalized * (len(colors) - 1))
467
+ color = colors[min(color_idx, len(colors) - 1)]
468
+
469
+ # Create a continuous filled rectangle for this cell
470
+ # Use multiple closely spaced points to fill the area
471
+ cell_points_x = []
472
+ cell_points_y = []
473
+
474
+ # Fill the cell with dense points to create solid appearance
475
+ for x_offset in [-0.45, -0.35, -0.25, -0.15, -0.05, 0.05, 0.15, 0.25, 0.35, 0.45]:
476
+ for y_offset in [-0.45, -0.35, -0.25, -0.15, -0.05, 0.05, 0.15, 0.25, 0.35, 0.45]:
477
+ cell_points_x.append(col_idx + x_offset)
478
+ cell_points_y.append(y_level + y_offset)
479
+
480
+ # Draw all points for this cell at once with the same color
481
+ if cell_points_x and cell_points_y:
482
+ _core.scatter(cell_points_x, cell_points_y, color=color, marker='█')
483
+
484
+ # Set axis limits and labels to show the grid properly
485
+ _core.xlim(-0.5, cols - 0.5)
486
+ _core.ylim(-0.5, rows - 0.5)
487
+ _core.xlabel('Column')
488
+ _core.ylabel('Row')
489
+
490
+ def _draw_filled_heatmap(self, matrix, colorscale):
491
+ """Draw a heatmap using filled rectangular blocks for each cell"""
492
+ if not matrix or not matrix[0]:
493
+ return
494
+
495
+ rows = len(matrix)
496
+ cols = len(matrix[0])
497
+
498
+ # Flatten and normalize the data for color mapping
499
+ flat_data = [val for row in matrix for val in row]
500
+ min_val = min(flat_data)
501
+ max_val = max(flat_data)
502
+ value_range = max_val - min_val if max_val != min_val else 1
503
+
504
+ # Define color palette
505
+ color_palettes = {
506
+ 'plasma': ['black', 'purple', 'magenta', 'red', 'orange', 'yellow'],
507
+ 'viridis': ['black', 'blue', 'green', 'bright green', 'yellow'],
508
+ 'cool': ['cyan', 'blue', 'magenta', 'white'],
509
+ 'hot': ['black', 'red', 'orange', 'yellow', 'white'],
510
+ 'default': ['blue', 'cyan', 'green', 'yellow', 'red', 'magenta']
511
+ }
512
+ colors = color_palettes.get(colorscale, color_palettes['default'])
513
+
514
+ # Create filled rectangles for each cell using bar charts
515
+ for row_idx in range(rows):
516
+ row_data = matrix[row_idx]
517
+ y_center = rows - row_idx - 1 # Flip so row 0 is at top
518
+
519
+ for col_idx, value in enumerate(row_data):
520
+ # Normalize value to get color
521
+ normalized = (value - min_val) / value_range
522
+ color_idx = int(normalized * (len(colors) - 1))
523
+ color = colors[min(color_idx, len(colors) - 1)]
524
+
525
+ # Create a filled rectangle using horizontal bar at this cell position
526
+ # Bar from col_idx-0.4 to col_idx+0.4, at y_center with height 0.8
527
+ x_positions = []
528
+ y_positions = []
529
+
530
+ # Fill the rectangle with a dense grid of points
531
+ x_steps = 20 # More density for smoother appearance
532
+ y_steps = 8
533
+
534
+ for i in range(x_steps + 1):
535
+ for j in range(y_steps + 1):
536
+ x_offset = (i / x_steps - 0.5) * 0.9 # -0.45 to +0.45
537
+ y_offset = (j / y_steps - 0.5) * 0.9 # -0.45 to +0.45
538
+ x_positions.append(col_idx + x_offset)
539
+ y_positions.append(y_center + y_offset)
540
+
541
+ # Draw all points for this cell with the same color
542
+ if x_positions and y_positions:
543
+ _core.scatter(x_positions, y_positions, color=color, marker='█')
544
+
545
+ # Set axis limits and labels to show the grid properly
546
+ _core.xlim(-0.5, cols - 0.5)
547
+ _core.ylim(-0.5, rows - 0.5)
548
+ _core.xlabel('Column')
549
+ _core.ylabel('Row')
550
+
551
+
552
+ class MatrixChart(Chart):
553
+ """
554
+ Specialized class for matrix plotting with advanced features
555
+ """
556
+
557
+ def __init__(self, matrix, marker=None, style=None, fast=False, use_banners=False, banner_title=None):
558
+ """Initialize a matrix plot"""
559
+ super().__init__(use_banners, banner_title)
560
+ self.matrix = matrix
561
+ self._data.append({
562
+ 'type': 'matrix',
563
+ 'matrix': matrix,
564
+ 'marker': marker,
565
+ 'style': style,
566
+ 'fast': fast
567
+ })
568
+
569
+ def show(self):
570
+ """Render and display the matrix plot"""
571
+ _core.clear_figure()
572
+
573
+ if self._config['title']:
574
+ _core.title(self._config['title'])
575
+ if self._config['width'] or self._config['height']:
576
+ _core.plot_size(self._config['width'], self._config['height'])
577
+
578
+ for data_item in self._data:
579
+ if data_item['type'] == 'matrix':
580
+ _core.matrix_plot(
581
+ data_item['matrix'],
582
+ marker=data_item['marker'],
583
+ style=data_item['style'],
584
+ fast=data_item['fast']
585
+ )
586
+
587
+ _core.show()
588
+ return self
589
+
590
+
591
+ class StemChart(Chart):
592
+ """
593
+ Specialized class for stem plots (lollipop charts)
594
+ """
595
+
596
+ def __init__(self, x, y, color=None, orientation='vertical', use_banners=False, banner_title=None):
597
+ """Initialize a stem chart"""
598
+ super().__init__(use_banners, banner_title)
599
+ self.x = x
600
+ self.y = y
601
+ self.orientation = orientation
602
+ # Use vertical lines to create stem effect
603
+ self._data.append({
604
+ 'type': 'stem',
605
+ 'x': x,
606
+ 'y': y,
607
+ 'color': color,
608
+ 'orientation': orientation
609
+ })
610
+
611
+ def show(self):
612
+ """Render and display the stem chart"""
613
+ _core.clear_figure()
614
+
615
+ if self._config['title']:
616
+ _core.title(self._config['title'])
617
+ if self._config['x_label']:
618
+ _core.xlabel(self._config['x_label'])
619
+ if self._config['y_label']:
620
+ _core.ylabel(self._config['y_label'])
621
+ if self._config['width'] or self._config['height']:
622
+ _core.plot_size(self._config['width'], self._config['height'])
623
+
624
+ for data_item in self._data:
625
+ if data_item['type'] == 'stem':
626
+ # Create stem plot using scatter points only for now
627
+ # Full stem functionality would require extending core API
628
+ _core.scatter(data_item['x'], data_item['y'],
629
+ color=data_item['color'],
630
+ marker='●') # Use solid dot for stem heads
631
+
632
+ _core.show()
633
+ return self
634
+
635
+
636
+ class Legend:
637
+ """
638
+ Legend class for adding legends to any chart type
639
+ """
640
+
641
+ def __init__(self):
642
+ self.items = []
643
+ self.position = 'upper right'
644
+ self.style = 'box'
645
+ self.show_border = True
646
+
647
+ def add(self, label, color=None, marker=None, line_style=None):
648
+ """
649
+ Add an item to the legend
650
+
651
+ Args:
652
+ label (str): Text label for the legend item
653
+ color (str): Color for the legend item
654
+ marker (str): Marker style for the legend item
655
+ line_style (str): Line style for the legend item
656
+ """
657
+ self.items.append({
658
+ 'label': label,
659
+ 'color': color or 'default',
660
+ 'marker': marker or '■',
661
+ 'line_style': line_style or 'solid'
662
+ })
663
+ return self
664
+
665
+ def set_position(self, pos):
666
+ """Set legend position ('upper right', 'upper left', 'lower right', 'lower left')"""
667
+ self.position = pos
668
+ return self
669
+
670
+ def set_style(self, style_name):
671
+ """Set legend style ('box', 'plain')"""
672
+ self.style = style_name
673
+ return self
674
+
675
+ def set_border(self, show=True):
676
+ """Show or hide legend border"""
677
+ self.show_border = show
678
+ return self
679
+
680
+ def apply_to_chart(self, chart_instance):
681
+ """Apply this legend to a chart instance"""
682
+ # Set this legend as the chart's legend (replace any existing legend)
683
+ chart_instance._legend = self
684
+ return self
685
+
686
+ def render_legend_text(self):
687
+ """Generate legend text representation"""
688
+ if not self.items:
689
+ return []
690
+
691
+ legend_lines = []
692
+ if self.show_border and self.style == 'box':
693
+ legend_lines.append("┌─ Legend ─┐")
694
+
695
+ for item in self.items:
696
+ marker = item['marker']
697
+ label = item['label']
698
+ # Use color-coded markers if available
699
+ if self.style == 'box':
700
+ legend_lines.append(f"│ {marker} {label}")
701
+ else:
702
+ legend_lines.append(f"{marker} {label}")
703
+
704
+ if self.show_border and self.style == 'box':
705
+ legend_lines.append("└──────────┘")
706
+
707
+ return legend_lines
708
+
709
+ def show(self):
710
+ """Display the legend independently"""
711
+ legend_text = self.render_legend_text()
712
+ for line in legend_text:
713
+ print(line)
714
+ return self
715
+
716
+
717
+ class PlotextAPI:
718
+ """
719
+ Modern functional API that provides cleaner function-based interface
720
+ while maintaining the flexibility of the original plotext.
721
+ """
722
+
723
+ @staticmethod
724
+ def create_chart(use_banners=False, banner_title=None):
725
+ """Create a new Chart instance"""
726
+ return Chart(use_banners, banner_title)
727
+
728
+ @staticmethod
729
+ def quick_scatter(x, y, title=None, xlabel=None, ylabel=None, use_banners=False, banner_title=None):
730
+ """Quickly create and display a scatter plot"""
731
+ chart = Chart(use_banners, banner_title)
732
+ chart.scatter(x, y)
733
+ if title:
734
+ chart.title(title)
735
+ if xlabel:
736
+ chart.xlabel(xlabel)
737
+ if ylabel:
738
+ chart.ylabel(ylabel)
739
+ chart.show()
740
+ return chart
741
+
742
+ @staticmethod
743
+ def quick_line(x, y, title=None, xlabel=None, ylabel=None, use_banners=False, banner_title=None):
744
+ """Quickly create and display a line plot"""
745
+ chart = Chart(use_banners, banner_title)
746
+ chart.line(x, y)
747
+ if title:
748
+ chart.title(title)
749
+ if xlabel:
750
+ chart.xlabel(xlabel)
751
+ if ylabel:
752
+ chart.ylabel(ylabel)
753
+ chart.show()
754
+ return chart
755
+
756
+ @staticmethod
757
+ def quick_bar(labels, values, title=None, horizontal=False, use_banners=False, banner_title=None):
758
+ """Quickly create and display a bar chart"""
759
+ chart = Chart(use_banners, banner_title)
760
+ chart.bar(labels, values, horizontal=horizontal)
761
+ if title:
762
+ chart.title(title)
763
+ chart.show()
764
+ return chart
765
+
766
+ @staticmethod
767
+ def enable_banners(enabled=True, default_title="Plotext Chart"):
768
+ """Globally enable or disable banner mode"""
769
+ set_output_mode(enabled, default_title)
770
+
771
+ @staticmethod
772
+ def log_info(message):
773
+ """Output info message using chuk-term"""
774
+ info(message)
775
+
776
+ @staticmethod
777
+ def log_success(message):
778
+ """Output success message using chuk-term"""
779
+ success(message)
780
+
781
+ @staticmethod
782
+ def log_warning(message):
783
+ """Output warning message using chuk-term"""
784
+ warning(message)
785
+
786
+ @staticmethod
787
+ def log_error(message):
788
+ """Output error message using chuk-term"""
789
+ error(message)
790
+
791
+
792
+ # Create a default API instance
793
+ api = PlotextAPI()
794
+
795
+ # Convenience functions for quick access
796
+ create_chart = api.create_chart
797
+ quick_scatter = api.quick_scatter
798
+ quick_line = api.quick_line
799
+ quick_bar = api.quick_bar
800
+ enable_banners = api.enable_banners
801
+ log_info = api.log_info
802
+ log_success = api.log_success
803
+ log_warning = api.log_warning
804
+ log_error = api.log_error
805
+
806
+ # Export specialized chart classes
807
+ __all__ = [
808
+ 'Chart',
809
+ 'ScatterChart',
810
+ 'LineChart',
811
+ 'BarChart',
812
+ 'HistogramChart',
813
+ 'CandlestickChart',
814
+ 'HeatmapChart',
815
+ 'MatrixChart',
816
+ 'StemChart',
817
+ 'Legend',
818
+ 'PlotextAPI',
819
+ 'create_chart',
820
+ 'quick_scatter',
821
+ 'quick_line',
822
+ 'quick_bar',
823
+ 'enable_banners',
824
+ 'log_info',
825
+ 'log_success',
826
+ 'log_warning',
827
+ 'log_error'
828
+ ]