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