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/__init__.py +31 -0
- plotext_plus/__main__.py +2 -0
- plotext_plus/_api.py +828 -0
- plotext_plus/_build.py +270 -0
- plotext_plus/_core.py +581 -0
- plotext_plus/_date.py +60 -0
- plotext_plus/_default.py +83 -0
- plotext_plus/_dict.py +210 -0
- plotext_plus/_doc.py +707 -0
- plotext_plus/_doc_utils.py +291 -0
- plotext_plus/_figure.py +488 -0
- plotext_plus/_global.py +370 -0
- plotext_plus/_matrix.py +171 -0
- plotext_plus/_monitor.py +848 -0
- plotext_plus/_output.py +136 -0
- plotext_plus/_shtab.py +9 -0
- plotext_plus/_themes.py +343 -0
- plotext_plus/_utility.py +853 -0
- plotext_plus/api.py +828 -0
- plotext_plus/charts.py +42 -0
- plotext_plus/core.py +581 -0
- plotext_plus/mcp_cli.py +79 -0
- plotext_plus/mcp_server.py +505 -0
- plotext_plus/plotext_cli.py +375 -0
- plotext_plus/plotting.py +92 -0
- plotext_plus/themes.py +29 -0
- plotext_plus/utilities.py +52 -0
- plotext_plus/utils.py +370 -0
- plotext_plus-1.0.1.dist-info/METADATA +303 -0
- plotext_plus-1.0.1.dist-info/RECORD +33 -0
- plotext_plus-1.0.1.dist-info/WHEEL +4 -0
- plotext_plus-1.0.1.dist-info/entry_points.txt +3 -0
- plotext_plus-1.0.1.dist-info/licenses/LICENSE +23 -0
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
|
+
]
|