plotext-plus 1.0.8__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/__init__.py +20 -15
- plotext_plus/__main__.py +1 -0
- plotext_plus/_api.py +632 -371
- plotext_plus/_build.py +765 -149
- plotext_plus/_core.py +609 -164
- plotext_plus/_date.py +50 -32
- plotext_plus/_default.py +35 -28
- plotext_plus/_dict.py +807 -103
- plotext_plus/_doc.py +867 -296
- plotext_plus/_doc_utils.py +279 -245
- plotext_plus/_figure.py +1295 -303
- plotext_plus/_global.py +238 -140
- plotext_plus/_matrix.py +217 -63
- plotext_plus/_monitor.py +1036 -489
- plotext_plus/_output.py +29 -23
- plotext_plus/_shtab.py +2 -0
- plotext_plus/_themes.py +363 -247
- plotext_plus/_utility.py +581 -313
- plotext_plus/api.py +418 -332
- plotext_plus/charts.py +47 -24
- plotext_plus/core.py +570 -177
- plotext_plus/mcp_cli.py +15 -13
- plotext_plus/mcp_server.py +842 -166
- plotext_plus/plotext_cli.py +414 -275
- plotext_plus/plotting.py +86 -70
- plotext_plus/themes.py +10 -13
- plotext_plus/utilities.py +33 -33
- plotext_plus/utils.py +240 -140
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/METADATA +7 -2
- plotext_plus-1.0.10.dist-info/RECORD +33 -0
- plotext_plus-1.0.8.dist-info/RECORD +0 -33
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/WHEEL +0 -0
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/entry_points.txt +0 -0
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/licenses/LICENSE +0 -0
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
|
|
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,205 +35,310 @@ class Chart:
|
|
|
32
35
|
self._data = []
|
|
33
36
|
self._legend = None
|
|
34
37
|
self._config = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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_plus import _utility as _ut
|
|
48
|
-
|
|
51
|
+
|
|
52
|
+
banner_width = (
|
|
53
|
+
_ut.terminal_width()
|
|
54
|
+
) # This now returns adjusted width for banners
|
|
49
55
|
if banner_width:
|
|
50
|
-
self._config[
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
|
|
99
|
+
def pie(
|
|
100
|
+
self,
|
|
101
|
+
labels,
|
|
102
|
+
values,
|
|
103
|
+
colors=None,
|
|
104
|
+
radius=None,
|
|
105
|
+
show_values=True,
|
|
106
|
+
show_percentages=True,
|
|
107
|
+
show_values_on_slices=False,
|
|
108
|
+
donut=False,
|
|
109
|
+
remaining_color=None,
|
|
110
|
+
):
|
|
88
111
|
"""Add pie chart data"""
|
|
89
|
-
self._data.append(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
self._data.append(
|
|
113
|
+
{
|
|
114
|
+
"type": "pie",
|
|
115
|
+
"labels": labels,
|
|
116
|
+
"values": values,
|
|
117
|
+
"colors": colors,
|
|
118
|
+
"radius": radius,
|
|
119
|
+
"show_values": show_values,
|
|
120
|
+
"show_percentages": show_percentages,
|
|
121
|
+
"show_values_on_slices": show_values_on_slices,
|
|
122
|
+
"donut": donut,
|
|
123
|
+
"remaining_color": remaining_color,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
return self
|
|
127
|
+
|
|
103
128
|
def title(self, title):
|
|
104
129
|
"""Set chart title"""
|
|
105
|
-
self._config[
|
|
130
|
+
self._config["title"] = title
|
|
106
131
|
return self
|
|
107
|
-
|
|
132
|
+
|
|
108
133
|
def xlabel(self, label):
|
|
109
134
|
"""Set x-axis label"""
|
|
110
|
-
self._config[
|
|
135
|
+
self._config["x_label"] = label
|
|
111
136
|
return self
|
|
112
|
-
|
|
137
|
+
|
|
113
138
|
def ylabel(self, label):
|
|
114
139
|
"""Set y-axis label"""
|
|
115
|
-
self._config[
|
|
140
|
+
self._config["y_label"] = label
|
|
116
141
|
return self
|
|
117
|
-
|
|
142
|
+
|
|
118
143
|
def size(self, width=None, height=None):
|
|
119
144
|
"""Set chart size"""
|
|
120
|
-
self._config[
|
|
121
|
-
self._config[
|
|
145
|
+
self._config["width"] = width
|
|
146
|
+
self._config["height"] = height
|
|
122
147
|
return self
|
|
123
|
-
|
|
148
|
+
|
|
124
149
|
def theme(self, theme_name):
|
|
125
150
|
"""Set chart theme"""
|
|
126
|
-
self._config[
|
|
151
|
+
self._config["theme"] = theme_name
|
|
127
152
|
return self
|
|
128
|
-
|
|
153
|
+
|
|
129
154
|
def banner_title(self, title):
|
|
130
155
|
"""Set banner title (if banner mode is enabled)"""
|
|
131
156
|
self.banner_title = title
|
|
132
157
|
if self.use_banners:
|
|
133
158
|
set_output_mode(True, title)
|
|
134
159
|
return self
|
|
135
|
-
|
|
160
|
+
|
|
136
161
|
def legend(self, legend_instance=None):
|
|
137
162
|
"""
|
|
138
163
|
Set or get legend for this chart
|
|
139
|
-
|
|
164
|
+
|
|
140
165
|
Args:
|
|
141
166
|
legend_instance (Legend): Legend instance to apply to chart
|
|
142
|
-
|
|
167
|
+
|
|
143
168
|
Returns:
|
|
144
169
|
Chart: Self for chaining, or current legend if no args provided
|
|
145
170
|
"""
|
|
146
171
|
if legend_instance is None:
|
|
147
172
|
return self._legend
|
|
148
|
-
|
|
173
|
+
|
|
149
174
|
self._legend = legend_instance
|
|
150
175
|
legend_instance.apply_to_chart(self)
|
|
151
176
|
return self
|
|
152
|
-
|
|
177
|
+
|
|
153
178
|
def show(self):
|
|
154
179
|
"""Render and display the chart"""
|
|
155
180
|
# Clear any existing plot data
|
|
156
181
|
_core.clear_figure()
|
|
157
|
-
|
|
182
|
+
|
|
183
|
+
# Apply theme if specified
|
|
184
|
+
if self._config["theme"] and self._config["theme"] != "default":
|
|
185
|
+
_core.theme(self._config["theme"])
|
|
186
|
+
|
|
158
187
|
# Configure plot settings
|
|
159
|
-
if self._config[
|
|
160
|
-
_core.title(self._config[
|
|
161
|
-
if self._config[
|
|
162
|
-
_core.xlabel(self._config[
|
|
163
|
-
if self._config[
|
|
164
|
-
_core.ylabel(self._config[
|
|
165
|
-
if self._config[
|
|
166
|
-
_core.plot_size(self._config[
|
|
167
|
-
|
|
188
|
+
if self._config["title"]:
|
|
189
|
+
_core.title(self._config["title"])
|
|
190
|
+
if self._config["x_label"]:
|
|
191
|
+
_core.xlabel(self._config["x_label"])
|
|
192
|
+
if self._config["y_label"]:
|
|
193
|
+
_core.ylabel(self._config["y_label"])
|
|
194
|
+
if self._config["width"] or self._config["height"]:
|
|
195
|
+
_core.plot_size(self._config["width"], self._config["height"])
|
|
196
|
+
|
|
168
197
|
# Add data to plot
|
|
169
198
|
for data_item in self._data:
|
|
170
|
-
if data_item[
|
|
199
|
+
if data_item["type"] == "scatter":
|
|
171
200
|
_core.scatter(
|
|
172
|
-
data_item[
|
|
173
|
-
data_item[
|
|
174
|
-
marker=data_item[
|
|
175
|
-
color=data_item[
|
|
176
|
-
label=data_item[
|
|
201
|
+
data_item["x"],
|
|
202
|
+
data_item["y"],
|
|
203
|
+
marker=data_item["marker"],
|
|
204
|
+
color=data_item["color"],
|
|
205
|
+
label=data_item["label"],
|
|
177
206
|
)
|
|
178
|
-
elif data_item[
|
|
207
|
+
elif data_item["type"] == "line":
|
|
179
208
|
_core.plot(
|
|
180
|
-
data_item[
|
|
181
|
-
data_item[
|
|
182
|
-
marker=data_item[
|
|
183
|
-
color=data_item[
|
|
184
|
-
label=data_item[
|
|
209
|
+
data_item["x"],
|
|
210
|
+
data_item["y"],
|
|
211
|
+
marker=data_item["marker"],
|
|
212
|
+
color=data_item["color"],
|
|
213
|
+
label=data_item["label"],
|
|
185
214
|
)
|
|
186
|
-
elif data_item[
|
|
187
|
-
if data_item[
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
215
|
+
elif data_item["type"] == "bar":
|
|
216
|
+
orientation = "horizontal" if data_item["horizontal"] else "vertical"
|
|
217
|
+
_core.bar(
|
|
218
|
+
data_item["labels"],
|
|
219
|
+
data_item["values"],
|
|
220
|
+
color=data_item["color"],
|
|
221
|
+
orientation=orientation,
|
|
222
|
+
)
|
|
223
|
+
elif data_item["type"] == "pie":
|
|
192
224
|
_core.pie(
|
|
193
|
-
data_item[
|
|
194
|
-
data_item[
|
|
195
|
-
colors=data_item[
|
|
196
|
-
radius=data_item[
|
|
197
|
-
show_values=data_item[
|
|
198
|
-
show_percentages=data_item[
|
|
199
|
-
show_values_on_slices=data_item[
|
|
200
|
-
donut=data_item.get(
|
|
201
|
-
remaining_color=data_item.get(
|
|
225
|
+
data_item["labels"],
|
|
226
|
+
data_item["values"],
|
|
227
|
+
colors=data_item["colors"],
|
|
228
|
+
radius=data_item["radius"],
|
|
229
|
+
show_values=data_item["show_values"],
|
|
230
|
+
show_percentages=data_item["show_percentages"],
|
|
231
|
+
show_values_on_slices=data_item["show_values_on_slices"],
|
|
232
|
+
donut=data_item.get("donut", False),
|
|
233
|
+
remaining_color=data_item.get("remaining_color", None),
|
|
234
|
+
)
|
|
235
|
+
elif data_item["type"] == "histogram":
|
|
236
|
+
_core.hist(
|
|
237
|
+
data_item["data"], bins=data_item["bins"], color=data_item["color"]
|
|
202
238
|
)
|
|
203
|
-
|
|
204
|
-
_core.hist(data_item['data'], bins=data_item['bins'], color=data_item['color'])
|
|
205
|
-
|
|
239
|
+
|
|
206
240
|
# Display the chart
|
|
207
241
|
_core.show()
|
|
208
242
|
return self
|
|
209
|
-
|
|
210
|
-
def save(self, path, format=
|
|
243
|
+
|
|
244
|
+
def save(self, path, format="txt"):
|
|
211
245
|
"""Save chart to file"""
|
|
212
|
-
if format ==
|
|
246
|
+
if format == "html":
|
|
213
247
|
_core.save_fig(path, keep_colors=True)
|
|
214
248
|
else:
|
|
215
249
|
_core.save_fig(path)
|
|
216
250
|
return self
|
|
217
251
|
|
|
252
|
+
def __str__(self):
|
|
253
|
+
"""Return the chart as a string without displaying it"""
|
|
254
|
+
# Clear any existing plot data
|
|
255
|
+
_core.clear_figure()
|
|
256
|
+
|
|
257
|
+
# Apply theme if specified
|
|
258
|
+
if self._config["theme"] and self._config["theme"] != "default":
|
|
259
|
+
_core.theme(self._config["theme"])
|
|
260
|
+
|
|
261
|
+
# Configure plot settings
|
|
262
|
+
if self._config["title"]:
|
|
263
|
+
_core.title(self._config["title"])
|
|
264
|
+
if self._config["x_label"]:
|
|
265
|
+
_core.xlabel(self._config["x_label"])
|
|
266
|
+
if self._config["y_label"]:
|
|
267
|
+
_core.ylabel(self._config["y_label"])
|
|
268
|
+
if self._config["width"] or self._config["height"]:
|
|
269
|
+
_core.plot_size(self._config["width"], self._config["height"])
|
|
270
|
+
|
|
271
|
+
# Add data to plot
|
|
272
|
+
for data_item in self._data:
|
|
273
|
+
if data_item["type"] == "scatter":
|
|
274
|
+
_core.scatter(
|
|
275
|
+
data_item["x"],
|
|
276
|
+
data_item["y"],
|
|
277
|
+
marker=data_item["marker"],
|
|
278
|
+
color=data_item["color"],
|
|
279
|
+
label=data_item["label"],
|
|
280
|
+
)
|
|
281
|
+
elif data_item["type"] == "line":
|
|
282
|
+
_core.plot(
|
|
283
|
+
data_item["x"],
|
|
284
|
+
data_item["y"],
|
|
285
|
+
marker=data_item["marker"],
|
|
286
|
+
color=data_item["color"],
|
|
287
|
+
label=data_item["label"],
|
|
288
|
+
)
|
|
289
|
+
elif data_item["type"] == "bar":
|
|
290
|
+
orientation = "horizontal" if data_item["horizontal"] else "vertical"
|
|
291
|
+
_core.bar(
|
|
292
|
+
data_item["labels"],
|
|
293
|
+
data_item["values"],
|
|
294
|
+
color=data_item["color"],
|
|
295
|
+
orientation=orientation,
|
|
296
|
+
)
|
|
297
|
+
elif data_item["type"] == "pie":
|
|
298
|
+
_core.pie(
|
|
299
|
+
data_item["labels"],
|
|
300
|
+
data_item["values"],
|
|
301
|
+
colors=data_item["colors"],
|
|
302
|
+
radius=data_item["radius"],
|
|
303
|
+
show_values=data_item["show_values"],
|
|
304
|
+
show_percentages=data_item["show_percentages"],
|
|
305
|
+
show_values_on_slices=data_item["show_values_on_slices"],
|
|
306
|
+
donut=data_item.get("donut", False),
|
|
307
|
+
remaining_color=data_item.get("remaining_color", None),
|
|
308
|
+
)
|
|
309
|
+
elif data_item["type"] == "histogram":
|
|
310
|
+
_core.hist(
|
|
311
|
+
data_item["data"], bins=data_item["bins"], color=data_item["color"]
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Build and return the chart as a string instead of displaying
|
|
315
|
+
return _core.build()
|
|
316
|
+
|
|
218
317
|
|
|
219
318
|
class ScatterChart(Chart):
|
|
220
319
|
"""
|
|
221
320
|
Specialized class for creating scatter plots with a focused API
|
|
222
321
|
"""
|
|
223
|
-
|
|
224
|
-
def __init__(
|
|
322
|
+
|
|
323
|
+
def __init__(
|
|
324
|
+
self,
|
|
325
|
+
x,
|
|
326
|
+
y,
|
|
327
|
+
marker=None,
|
|
328
|
+
color=None,
|
|
329
|
+
label=None,
|
|
330
|
+
use_banners=False,
|
|
331
|
+
banner_title=None,
|
|
332
|
+
):
|
|
225
333
|
"""Initialize a scatter chart with data"""
|
|
226
334
|
super().__init__(use_banners, banner_title)
|
|
227
335
|
self.scatter(x, y, marker, color, label)
|
|
228
|
-
|
|
229
|
-
def add_trend_line(self, x, y, color=
|
|
336
|
+
|
|
337
|
+
def add_trend_line(self, x, y, color="red", label="Trend"):
|
|
230
338
|
"""Add a trend line to the scatter plot"""
|
|
231
339
|
self.line(x, y, color=color, label=label)
|
|
232
340
|
return self
|
|
233
|
-
|
|
341
|
+
|
|
234
342
|
def add_regression(self):
|
|
235
343
|
"""Add linear regression line (future enhancement)"""
|
|
236
344
|
# Placeholder for regression functionality
|
|
@@ -241,17 +349,26 @@ class LineChart(Chart):
|
|
|
241
349
|
"""
|
|
242
350
|
Specialized class for creating line charts with enhanced features
|
|
243
351
|
"""
|
|
244
|
-
|
|
245
|
-
def __init__(
|
|
352
|
+
|
|
353
|
+
def __init__(
|
|
354
|
+
self,
|
|
355
|
+
x,
|
|
356
|
+
y,
|
|
357
|
+
marker=None,
|
|
358
|
+
color=None,
|
|
359
|
+
label=None,
|
|
360
|
+
use_banners=False,
|
|
361
|
+
banner_title=None,
|
|
362
|
+
):
|
|
246
363
|
"""Initialize a line chart with data"""
|
|
247
364
|
super().__init__(use_banners, banner_title)
|
|
248
365
|
self.line(x, y, marker, color, label)
|
|
249
|
-
|
|
366
|
+
|
|
250
367
|
def add_fill(self, fillx=False, filly=False):
|
|
251
368
|
"""Add fill under the line (future enhancement)"""
|
|
252
369
|
# Placeholder for fill functionality
|
|
253
370
|
return self
|
|
254
|
-
|
|
371
|
+
|
|
255
372
|
def smooth(self, window_size=3):
|
|
256
373
|
"""Apply smoothing to the line (future enhancement)"""
|
|
257
374
|
# Placeholder for smoothing functionality
|
|
@@ -262,29 +379,39 @@ class BarChart(Chart):
|
|
|
262
379
|
"""
|
|
263
380
|
Specialized class for creating bar charts with extensive customization
|
|
264
381
|
"""
|
|
265
|
-
|
|
266
|
-
def __init__(
|
|
382
|
+
|
|
383
|
+
def __init__(
|
|
384
|
+
self,
|
|
385
|
+
labels,
|
|
386
|
+
values,
|
|
387
|
+
color=None,
|
|
388
|
+
horizontal=False,
|
|
389
|
+
use_banners=False,
|
|
390
|
+
banner_title=None,
|
|
391
|
+
):
|
|
267
392
|
"""Initialize a bar chart with data"""
|
|
268
393
|
super().__init__(use_banners, banner_title)
|
|
269
394
|
self.bar(labels, values, color, horizontal)
|
|
270
395
|
self.labels = labels
|
|
271
396
|
self.values = values
|
|
272
|
-
|
|
397
|
+
|
|
273
398
|
def stack(self, values, color=None, label=None):
|
|
274
399
|
"""Add stacked bars (future enhancement)"""
|
|
275
400
|
# Placeholder for stacked bar functionality
|
|
276
401
|
return self
|
|
277
|
-
|
|
402
|
+
|
|
278
403
|
def group(self, values, color=None, label=None):
|
|
279
404
|
"""Add grouped bars (future enhancement)"""
|
|
280
405
|
# Placeholder for grouped bar functionality
|
|
281
406
|
return self
|
|
282
|
-
|
|
407
|
+
|
|
283
408
|
def sort_by_value(self, ascending=True):
|
|
284
409
|
"""Sort bars by value"""
|
|
285
410
|
# Simple implementation for sorting
|
|
286
|
-
sorted_pairs = sorted(
|
|
287
|
-
|
|
411
|
+
sorted_pairs = sorted(
|
|
412
|
+
zip(self.values, self.labels, strict=False), reverse=not ascending
|
|
413
|
+
)
|
|
414
|
+
self.values, self.labels = zip(*sorted_pairs, strict=False)
|
|
288
415
|
return self
|
|
289
416
|
|
|
290
417
|
|
|
@@ -292,30 +419,27 @@ class HistogramChart(Chart):
|
|
|
292
419
|
"""
|
|
293
420
|
Specialized class for creating histograms with statistical features
|
|
294
421
|
"""
|
|
295
|
-
|
|
422
|
+
|
|
296
423
|
def __init__(self, data, bins=20, color=None, use_banners=False, banner_title=None):
|
|
297
424
|
"""Initialize a histogram with data"""
|
|
298
425
|
super().__init__(use_banners, banner_title)
|
|
299
426
|
self.data = data
|
|
300
427
|
self.bins = bins
|
|
301
428
|
self._create_histogram(data, bins, color)
|
|
302
|
-
|
|
429
|
+
|
|
303
430
|
def _create_histogram(self, data, bins, color):
|
|
304
431
|
"""Create histogram from raw data"""
|
|
305
432
|
# Add histogram data to the chart
|
|
306
|
-
self._data.append(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})
|
|
312
|
-
return self
|
|
313
|
-
|
|
433
|
+
self._data.append(
|
|
434
|
+
{"type": "histogram", "data": data, "bins": bins, "color": color}
|
|
435
|
+
)
|
|
436
|
+
return self
|
|
437
|
+
|
|
314
438
|
def add_normal_curve(self):
|
|
315
439
|
"""Overlay a normal distribution curve (future enhancement)"""
|
|
316
440
|
# Placeholder for normal curve overlay
|
|
317
441
|
return self
|
|
318
|
-
|
|
442
|
+
|
|
319
443
|
def add_statistics(self):
|
|
320
444
|
"""Add mean, median, std dev lines (future enhancement)"""
|
|
321
445
|
# Placeholder for statistical lines
|
|
@@ -326,11 +450,11 @@ class CandlestickChart(Chart):
|
|
|
326
450
|
"""
|
|
327
451
|
Specialized class for financial candlestick charts
|
|
328
452
|
"""
|
|
329
|
-
|
|
453
|
+
|
|
330
454
|
def __init__(self, dates, data, colors=None, use_banners=False, banner_title=None):
|
|
331
455
|
"""
|
|
332
456
|
Initialize a candlestick chart
|
|
333
|
-
|
|
457
|
+
|
|
334
458
|
Args:
|
|
335
459
|
dates: List of dates
|
|
336
460
|
data: List of [open, high, low, close] values
|
|
@@ -339,56 +463,49 @@ class CandlestickChart(Chart):
|
|
|
339
463
|
super().__init__(use_banners, banner_title)
|
|
340
464
|
self.dates = dates
|
|
341
465
|
self.data = data
|
|
342
|
-
self._data.append(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
def add_volume(self, volumes, color='blue'):
|
|
466
|
+
self._data.append(
|
|
467
|
+
{"type": "candlestick", "dates": dates, "data": data, "colors": colors}
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def add_volume(self, volumes, color="blue"):
|
|
350
471
|
"""Add volume bars below candlesticks (future enhancement)"""
|
|
351
472
|
# Placeholder for volume functionality
|
|
352
473
|
return self
|
|
353
|
-
|
|
354
|
-
def add_moving_average(self, period=20, color=
|
|
474
|
+
|
|
475
|
+
def add_moving_average(self, period=20, color="orange"):
|
|
355
476
|
"""Add moving average line (future enhancement)"""
|
|
356
477
|
# Placeholder for moving average
|
|
357
478
|
return self
|
|
358
|
-
|
|
479
|
+
|
|
359
480
|
def show(self):
|
|
360
481
|
"""Render and display the candlestick chart"""
|
|
361
482
|
_core.clear_figure()
|
|
362
|
-
|
|
363
|
-
if self._config[
|
|
364
|
-
_core.title(self._config[
|
|
365
|
-
if self._config[
|
|
366
|
-
_core.xlabel(self._config[
|
|
367
|
-
if self._config[
|
|
368
|
-
_core.ylabel(self._config[
|
|
369
|
-
if self._config[
|
|
370
|
-
_core.plot_size(self._config[
|
|
371
|
-
|
|
483
|
+
|
|
484
|
+
if self._config["title"]:
|
|
485
|
+
_core.title(self._config["title"])
|
|
486
|
+
if self._config["x_label"]:
|
|
487
|
+
_core.xlabel(self._config["x_label"])
|
|
488
|
+
if self._config["y_label"]:
|
|
489
|
+
_core.ylabel(self._config["y_label"])
|
|
490
|
+
if self._config["width"] or self._config["height"]:
|
|
491
|
+
_core.plot_size(self._config["width"], self._config["height"])
|
|
492
|
+
|
|
372
493
|
for data_item in self._data:
|
|
373
|
-
if data_item[
|
|
494
|
+
if data_item["type"] == "candlestick":
|
|
374
495
|
# Convert list format to dictionary format expected by plotext
|
|
375
|
-
dates = data_item[
|
|
376
|
-
ohlc_data = data_item[
|
|
377
|
-
|
|
496
|
+
dates = data_item["dates"]
|
|
497
|
+
ohlc_data = data_item["data"]
|
|
498
|
+
|
|
378
499
|
# Format data as expected by plotext candlestick function
|
|
379
500
|
formatted_data = {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
501
|
+
"Open": [item[0] for item in ohlc_data],
|
|
502
|
+
"High": [item[1] for item in ohlc_data],
|
|
503
|
+
"Low": [item[2] for item in ohlc_data],
|
|
504
|
+
"Close": [item[3] for item in ohlc_data],
|
|
384
505
|
}
|
|
385
|
-
|
|
386
|
-
_core.candlestick(
|
|
387
|
-
|
|
388
|
-
formatted_data,
|
|
389
|
-
colors=data_item['colors']
|
|
390
|
-
)
|
|
391
|
-
|
|
506
|
+
|
|
507
|
+
_core.candlestick(dates, formatted_data, colors=data_item["colors"])
|
|
508
|
+
|
|
392
509
|
_core.show()
|
|
393
510
|
return self
|
|
394
511
|
|
|
@@ -397,11 +514,11 @@ class HeatmapChart(Chart):
|
|
|
397
514
|
"""
|
|
398
515
|
Specialized class for creating heatmaps and matrix visualizations
|
|
399
516
|
"""
|
|
400
|
-
|
|
517
|
+
|
|
401
518
|
def __init__(self, data, colorscale=None, use_banners=False, banner_title=None):
|
|
402
519
|
"""
|
|
403
520
|
Initialize a heatmap chart
|
|
404
|
-
|
|
521
|
+
|
|
405
522
|
Args:
|
|
406
523
|
data: 2D matrix or pandas DataFrame
|
|
407
524
|
colorscale: Color scale for the heatmap
|
|
@@ -409,209 +526,238 @@ class HeatmapChart(Chart):
|
|
|
409
526
|
super().__init__(use_banners, banner_title)
|
|
410
527
|
self.data = data
|
|
411
528
|
self.colorscale = colorscale
|
|
412
|
-
self._data.append({
|
|
413
|
-
|
|
414
|
-
'data': data,
|
|
415
|
-
'colorscale': colorscale
|
|
416
|
-
})
|
|
417
|
-
|
|
529
|
+
self._data.append({"type": "heatmap", "data": data, "colorscale": colorscale})
|
|
530
|
+
|
|
418
531
|
def annotate(self, show_values=True):
|
|
419
532
|
"""Add value annotations to cells (future enhancement)"""
|
|
420
533
|
# Placeholder for annotations
|
|
421
534
|
return self
|
|
422
|
-
|
|
535
|
+
|
|
423
536
|
def show(self):
|
|
424
537
|
"""Render and display the heatmap"""
|
|
425
538
|
_core.clear_figure()
|
|
426
|
-
|
|
539
|
+
|
|
427
540
|
# Set appropriate plot size for heatmaps FIRST - ensure full width usage
|
|
428
|
-
if self._config[
|
|
429
|
-
_core.plotsize(self._config[
|
|
541
|
+
if self._config["width"] or self._config["height"]:
|
|
542
|
+
_core.plotsize(self._config["width"], self._config["height"])
|
|
430
543
|
else:
|
|
431
544
|
# Default to full terminal width for better heatmap display
|
|
432
545
|
import plotext_plus._utility as _ut
|
|
546
|
+
|
|
433
547
|
terminal_width = _ut.terminal_width()
|
|
434
548
|
if terminal_width:
|
|
435
549
|
# Set reasonable dimensions for heatmap display
|
|
436
550
|
heatmap_height = max(20, len(self.data) * 6 + 10)
|
|
437
551
|
_core.plotsize(terminal_width - 4, heatmap_height)
|
|
438
|
-
|
|
552
|
+
|
|
439
553
|
# Configure plot settings (same as base Chart class)
|
|
440
|
-
if self._config[
|
|
441
|
-
_core.title(self._config[
|
|
442
|
-
if self._config[
|
|
443
|
-
_core.xlabel(self._config[
|
|
444
|
-
if self._config[
|
|
445
|
-
_core.ylabel(self._config[
|
|
446
|
-
|
|
554
|
+
if self._config["title"]:
|
|
555
|
+
_core.title(self._config["title"])
|
|
556
|
+
if self._config["x_label"]:
|
|
557
|
+
_core.xlabel(self._config["x_label"])
|
|
558
|
+
if self._config["y_label"]:
|
|
559
|
+
_core.ylabel(self._config["y_label"])
|
|
560
|
+
|
|
447
561
|
for data_item in self._data:
|
|
448
|
-
if data_item[
|
|
449
|
-
data = data_item[
|
|
450
|
-
|
|
562
|
+
if data_item["type"] == "heatmap":
|
|
563
|
+
data = data_item["data"]
|
|
564
|
+
|
|
451
565
|
# Check if data is a pandas DataFrame
|
|
452
|
-
if hasattr(data,
|
|
566
|
+
if hasattr(data, "columns"):
|
|
453
567
|
# It's already a DataFrame
|
|
454
|
-
_core.heatmap(data, color=data_item[
|
|
568
|
+
_core.heatmap(data, color=data_item["colorscale"])
|
|
455
569
|
else:
|
|
456
570
|
# It's a list/matrix, create filled heatmap blocks
|
|
457
|
-
self._draw_filled_heatmap(data, data_item[
|
|
458
|
-
|
|
571
|
+
self._draw_filled_heatmap(data, data_item["colorscale"])
|
|
572
|
+
|
|
459
573
|
_core.show()
|
|
460
574
|
return self
|
|
461
|
-
|
|
575
|
+
|
|
462
576
|
def _draw_list_heatmap(self, matrix, colorscale):
|
|
463
577
|
"""Draw a heatmap using continuous colored blocks"""
|
|
464
578
|
if not matrix or not matrix[0]:
|
|
465
579
|
return
|
|
466
|
-
|
|
580
|
+
|
|
467
581
|
rows = len(matrix)
|
|
468
582
|
cols = len(matrix[0])
|
|
469
|
-
|
|
583
|
+
|
|
470
584
|
# Flatten and normalize the data for color mapping
|
|
471
585
|
flat_data = [val for row in matrix for val in row]
|
|
472
586
|
min_val = min(flat_data)
|
|
473
587
|
max_val = max(flat_data)
|
|
474
588
|
value_range = max_val - min_val if max_val != min_val else 1
|
|
475
|
-
|
|
589
|
+
|
|
476
590
|
# Define color palette
|
|
477
591
|
color_palettes = {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
592
|
+
"plasma": ["black", "purple", "magenta", "red", "orange", "yellow"],
|
|
593
|
+
"viridis": ["black", "blue", "green", "bright green", "yellow"],
|
|
594
|
+
"cool": ["cyan", "blue", "magenta", "white"],
|
|
595
|
+
"hot": ["black", "red", "orange", "yellow", "white"],
|
|
596
|
+
"default": ["blue", "cyan", "green", "yellow", "red", "magenta"],
|
|
483
597
|
}
|
|
484
|
-
colors = color_palettes.get(colorscale, color_palettes[
|
|
485
|
-
|
|
598
|
+
colors = color_palettes.get(colorscale, color_palettes["default"])
|
|
599
|
+
|
|
486
600
|
# Create heatmap using continuous filled rectangles for each cell
|
|
487
601
|
for row_idx in range(rows):
|
|
488
602
|
row_data = matrix[row_idx]
|
|
489
603
|
y_level = rows - row_idx - 1 # Flip so row 0 is at top
|
|
490
|
-
|
|
604
|
+
|
|
491
605
|
for col_idx, value in enumerate(row_data):
|
|
492
606
|
# Normalize value to get color
|
|
493
607
|
normalized = (value - min_val) / value_range
|
|
494
608
|
color_idx = int(normalized * (len(colors) - 1))
|
|
495
609
|
color = colors[min(color_idx, len(colors) - 1)]
|
|
496
|
-
|
|
610
|
+
|
|
497
611
|
# Create a continuous filled rectangle for this cell
|
|
498
612
|
# Use multiple closely spaced points to fill the area
|
|
499
613
|
cell_points_x = []
|
|
500
614
|
cell_points_y = []
|
|
501
|
-
|
|
615
|
+
|
|
502
616
|
# Fill the cell with dense points to create solid appearance
|
|
503
|
-
for x_offset in [
|
|
504
|
-
|
|
617
|
+
for x_offset in [
|
|
618
|
+
-0.45,
|
|
619
|
+
-0.35,
|
|
620
|
+
-0.25,
|
|
621
|
+
-0.15,
|
|
622
|
+
-0.05,
|
|
623
|
+
0.05,
|
|
624
|
+
0.15,
|
|
625
|
+
0.25,
|
|
626
|
+
0.35,
|
|
627
|
+
0.45,
|
|
628
|
+
]:
|
|
629
|
+
for y_offset in [
|
|
630
|
+
-0.45,
|
|
631
|
+
-0.35,
|
|
632
|
+
-0.25,
|
|
633
|
+
-0.15,
|
|
634
|
+
-0.05,
|
|
635
|
+
0.05,
|
|
636
|
+
0.15,
|
|
637
|
+
0.25,
|
|
638
|
+
0.35,
|
|
639
|
+
0.45,
|
|
640
|
+
]:
|
|
505
641
|
cell_points_x.append(col_idx + x_offset)
|
|
506
642
|
cell_points_y.append(y_level + y_offset)
|
|
507
|
-
|
|
643
|
+
|
|
508
644
|
# Draw all points for this cell at once with the same color
|
|
509
645
|
if cell_points_x and cell_points_y:
|
|
510
|
-
_core.scatter(cell_points_x, cell_points_y, color=color, marker=
|
|
511
|
-
|
|
646
|
+
_core.scatter(cell_points_x, cell_points_y, color=color, marker="█")
|
|
647
|
+
|
|
512
648
|
# Set axis limits and labels to show the grid properly
|
|
513
649
|
_core.xlim(-0.5, cols - 0.5)
|
|
514
650
|
_core.ylim(-0.5, rows - 0.5)
|
|
515
|
-
_core.xlabel(
|
|
516
|
-
_core.ylabel(
|
|
517
|
-
|
|
651
|
+
_core.xlabel("Column")
|
|
652
|
+
_core.ylabel("Row")
|
|
653
|
+
|
|
518
654
|
def _draw_filled_heatmap(self, matrix, colorscale):
|
|
519
655
|
"""Draw a heatmap using filled rectangular blocks for each cell"""
|
|
520
656
|
if not matrix or not matrix[0]:
|
|
521
657
|
return
|
|
522
|
-
|
|
658
|
+
|
|
523
659
|
rows = len(matrix)
|
|
524
660
|
cols = len(matrix[0])
|
|
525
|
-
|
|
661
|
+
|
|
526
662
|
# Flatten and normalize the data for color mapping
|
|
527
663
|
flat_data = [val for row in matrix for val in row]
|
|
528
664
|
min_val = min(flat_data)
|
|
529
665
|
max_val = max(flat_data)
|
|
530
666
|
value_range = max_val - min_val if max_val != min_val else 1
|
|
531
|
-
|
|
667
|
+
|
|
532
668
|
# Define color palette
|
|
533
669
|
color_palettes = {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
670
|
+
"plasma": ["black", "purple", "magenta", "red", "orange", "yellow"],
|
|
671
|
+
"viridis": ["black", "blue", "green", "bright green", "yellow"],
|
|
672
|
+
"cool": ["cyan", "blue", "magenta", "white"],
|
|
673
|
+
"hot": ["black", "red", "orange", "yellow", "white"],
|
|
674
|
+
"default": ["blue", "cyan", "green", "yellow", "red", "magenta"],
|
|
539
675
|
}
|
|
540
|
-
colors = color_palettes.get(colorscale, color_palettes[
|
|
541
|
-
|
|
676
|
+
colors = color_palettes.get(colorscale, color_palettes["default"])
|
|
677
|
+
|
|
542
678
|
# Create filled rectangles for each cell using bar charts
|
|
543
679
|
for row_idx in range(rows):
|
|
544
680
|
row_data = matrix[row_idx]
|
|
545
681
|
y_center = rows - row_idx - 1 # Flip so row 0 is at top
|
|
546
|
-
|
|
682
|
+
|
|
547
683
|
for col_idx, value in enumerate(row_data):
|
|
548
684
|
# Normalize value to get color
|
|
549
685
|
normalized = (value - min_val) / value_range
|
|
550
686
|
color_idx = int(normalized * (len(colors) - 1))
|
|
551
687
|
color = colors[min(color_idx, len(colors) - 1)]
|
|
552
|
-
|
|
688
|
+
|
|
553
689
|
# Create a filled rectangle using horizontal bar at this cell position
|
|
554
690
|
# Bar from col_idx-0.4 to col_idx+0.4, at y_center with height 0.8
|
|
555
691
|
x_positions = []
|
|
556
692
|
y_positions = []
|
|
557
|
-
|
|
693
|
+
|
|
558
694
|
# Fill the rectangle with a dense grid of points
|
|
559
695
|
x_steps = 20 # More density for smoother appearance
|
|
560
696
|
y_steps = 8
|
|
561
|
-
|
|
697
|
+
|
|
562
698
|
for i in range(x_steps + 1):
|
|
563
699
|
for j in range(y_steps + 1):
|
|
564
700
|
x_offset = (i / x_steps - 0.5) * 0.9 # -0.45 to +0.45
|
|
565
701
|
y_offset = (j / y_steps - 0.5) * 0.9 # -0.45 to +0.45
|
|
566
702
|
x_positions.append(col_idx + x_offset)
|
|
567
703
|
y_positions.append(y_center + y_offset)
|
|
568
|
-
|
|
704
|
+
|
|
569
705
|
# Draw all points for this cell with the same color
|
|
570
706
|
if x_positions and y_positions:
|
|
571
|
-
_core.scatter(x_positions, y_positions, color=color, marker=
|
|
572
|
-
|
|
707
|
+
_core.scatter(x_positions, y_positions, color=color, marker="█")
|
|
708
|
+
|
|
573
709
|
# Set axis limits and labels to show the grid properly
|
|
574
710
|
_core.xlim(-0.5, cols - 0.5)
|
|
575
711
|
_core.ylim(-0.5, rows - 0.5)
|
|
576
|
-
_core.xlabel(
|
|
577
|
-
_core.ylabel(
|
|
712
|
+
_core.xlabel("Column")
|
|
713
|
+
_core.ylabel("Row")
|
|
578
714
|
|
|
579
715
|
|
|
580
716
|
class MatrixChart(Chart):
|
|
581
717
|
"""
|
|
582
718
|
Specialized class for matrix plotting with advanced features
|
|
583
719
|
"""
|
|
584
|
-
|
|
585
|
-
def __init__(
|
|
720
|
+
|
|
721
|
+
def __init__(
|
|
722
|
+
self,
|
|
723
|
+
matrix,
|
|
724
|
+
marker=None,
|
|
725
|
+
style=None,
|
|
726
|
+
fast=False,
|
|
727
|
+
use_banners=False,
|
|
728
|
+
banner_title=None,
|
|
729
|
+
):
|
|
586
730
|
"""Initialize a matrix plot"""
|
|
587
731
|
super().__init__(use_banners, banner_title)
|
|
588
732
|
self.matrix = matrix
|
|
589
|
-
self._data.append(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
733
|
+
self._data.append(
|
|
734
|
+
{
|
|
735
|
+
"type": "matrix",
|
|
736
|
+
"matrix": matrix,
|
|
737
|
+
"marker": marker,
|
|
738
|
+
"style": style,
|
|
739
|
+
"fast": fast,
|
|
740
|
+
}
|
|
741
|
+
)
|
|
742
|
+
|
|
597
743
|
def show(self):
|
|
598
744
|
"""Render and display the matrix plot"""
|
|
599
745
|
_core.clear_figure()
|
|
600
|
-
|
|
601
|
-
if self._config[
|
|
602
|
-
_core.title(self._config[
|
|
603
|
-
if self._config[
|
|
604
|
-
_core.plot_size(self._config[
|
|
605
|
-
|
|
746
|
+
|
|
747
|
+
if self._config["title"]:
|
|
748
|
+
_core.title(self._config["title"])
|
|
749
|
+
if self._config["width"] or self._config["height"]:
|
|
750
|
+
_core.plot_size(self._config["width"], self._config["height"])
|
|
751
|
+
|
|
606
752
|
for data_item in self._data:
|
|
607
|
-
if data_item[
|
|
753
|
+
if data_item["type"] == "matrix":
|
|
608
754
|
_core.matrix_plot(
|
|
609
|
-
data_item[
|
|
610
|
-
marker=data_item[
|
|
611
|
-
style=data_item[
|
|
612
|
-
fast=data_item[
|
|
755
|
+
data_item["matrix"],
|
|
756
|
+
marker=data_item["marker"],
|
|
757
|
+
style=data_item["style"],
|
|
758
|
+
fast=data_item["fast"],
|
|
613
759
|
)
|
|
614
|
-
|
|
760
|
+
|
|
615
761
|
_core.show()
|
|
616
762
|
return self
|
|
617
763
|
|
|
@@ -620,43 +766,47 @@ class StemChart(Chart):
|
|
|
620
766
|
"""
|
|
621
767
|
Specialized class for stem plots (lollipop charts)
|
|
622
768
|
"""
|
|
623
|
-
|
|
624
|
-
def __init__(
|
|
769
|
+
|
|
770
|
+
def __init__(
|
|
771
|
+
self,
|
|
772
|
+
x,
|
|
773
|
+
y,
|
|
774
|
+
color=None,
|
|
775
|
+
orientation="vertical",
|
|
776
|
+
use_banners=False,
|
|
777
|
+
banner_title=None,
|
|
778
|
+
):
|
|
625
779
|
"""Initialize a stem chart"""
|
|
626
780
|
super().__init__(use_banners, banner_title)
|
|
627
781
|
self.x = x
|
|
628
782
|
self.y = y
|
|
629
783
|
self.orientation = orientation
|
|
630
784
|
# Use vertical lines to create stem effect
|
|
631
|
-
self._data.append(
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
'color': color,
|
|
636
|
-
'orientation': orientation
|
|
637
|
-
})
|
|
638
|
-
|
|
785
|
+
self._data.append(
|
|
786
|
+
{"type": "stem", "x": x, "y": y, "color": color, "orientation": orientation}
|
|
787
|
+
)
|
|
788
|
+
|
|
639
789
|
def show(self):
|
|
640
790
|
"""Render and display the stem chart"""
|
|
641
791
|
_core.clear_figure()
|
|
642
|
-
|
|
643
|
-
if self._config[
|
|
644
|
-
_core.title(self._config[
|
|
645
|
-
if self._config[
|
|
646
|
-
_core.xlabel(self._config[
|
|
647
|
-
if self._config[
|
|
648
|
-
_core.ylabel(self._config[
|
|
649
|
-
if self._config[
|
|
650
|
-
_core.plot_size(self._config[
|
|
651
|
-
|
|
792
|
+
|
|
793
|
+
if self._config["title"]:
|
|
794
|
+
_core.title(self._config["title"])
|
|
795
|
+
if self._config["x_label"]:
|
|
796
|
+
_core.xlabel(self._config["x_label"])
|
|
797
|
+
if self._config["y_label"]:
|
|
798
|
+
_core.ylabel(self._config["y_label"])
|
|
799
|
+
if self._config["width"] or self._config["height"]:
|
|
800
|
+
_core.plot_size(self._config["width"], self._config["height"])
|
|
801
|
+
|
|
652
802
|
for data_item in self._data:
|
|
653
|
-
if data_item[
|
|
803
|
+
if data_item["type"] == "stem":
|
|
654
804
|
# Create stem plot using scatter points only for now
|
|
655
805
|
# Full stem functionality would require extending core API
|
|
656
|
-
_core.scatter(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
806
|
+
_core.scatter(
|
|
807
|
+
data_item["x"], data_item["y"], color=data_item["color"], marker="●"
|
|
808
|
+
) # Use solid dot for stem heads
|
|
809
|
+
|
|
660
810
|
_core.show()
|
|
661
811
|
return self
|
|
662
812
|
|
|
@@ -665,75 +815,77 @@ class Legend:
|
|
|
665
815
|
"""
|
|
666
816
|
Legend class for adding legends to any chart type
|
|
667
817
|
"""
|
|
668
|
-
|
|
818
|
+
|
|
669
819
|
def __init__(self):
|
|
670
820
|
self.items = []
|
|
671
|
-
self.position =
|
|
672
|
-
self.style =
|
|
821
|
+
self.position = "upper right"
|
|
822
|
+
self.style = "box"
|
|
673
823
|
self.show_border = True
|
|
674
|
-
|
|
824
|
+
|
|
675
825
|
def add(self, label, color=None, marker=None, line_style=None):
|
|
676
826
|
"""
|
|
677
827
|
Add an item to the legend
|
|
678
|
-
|
|
828
|
+
|
|
679
829
|
Args:
|
|
680
830
|
label (str): Text label for the legend item
|
|
681
831
|
color (str): Color for the legend item
|
|
682
832
|
marker (str): Marker style for the legend item
|
|
683
833
|
line_style (str): Line style for the legend item
|
|
684
834
|
"""
|
|
685
|
-
self.items.append(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
835
|
+
self.items.append(
|
|
836
|
+
{
|
|
837
|
+
"label": label,
|
|
838
|
+
"color": color or "default",
|
|
839
|
+
"marker": marker or "■",
|
|
840
|
+
"line_style": line_style or "solid",
|
|
841
|
+
}
|
|
842
|
+
)
|
|
843
|
+
return self
|
|
844
|
+
|
|
693
845
|
def set_position(self, pos):
|
|
694
846
|
"""Set legend position ('upper right', 'upper left', 'lower right', 'lower left')"""
|
|
695
847
|
self.position = pos
|
|
696
848
|
return self
|
|
697
|
-
|
|
849
|
+
|
|
698
850
|
def set_style(self, style_name):
|
|
699
851
|
"""Set legend style ('box', 'plain')"""
|
|
700
852
|
self.style = style_name
|
|
701
853
|
return self
|
|
702
|
-
|
|
854
|
+
|
|
703
855
|
def set_border(self, show=True):
|
|
704
856
|
"""Show or hide legend border"""
|
|
705
857
|
self.show_border = show
|
|
706
858
|
return self
|
|
707
|
-
|
|
859
|
+
|
|
708
860
|
def apply_to_chart(self, chart_instance):
|
|
709
861
|
"""Apply this legend to a chart instance"""
|
|
710
862
|
# Set this legend as the chart's legend (replace any existing legend)
|
|
711
863
|
chart_instance._legend = self
|
|
712
864
|
return self
|
|
713
|
-
|
|
865
|
+
|
|
714
866
|
def render_legend_text(self):
|
|
715
867
|
"""Generate legend text representation"""
|
|
716
868
|
if not self.items:
|
|
717
869
|
return []
|
|
718
|
-
|
|
870
|
+
|
|
719
871
|
legend_lines = []
|
|
720
|
-
if self.show_border and self.style ==
|
|
872
|
+
if self.show_border and self.style == "box":
|
|
721
873
|
legend_lines.append("┌─ Legend ─┐")
|
|
722
|
-
|
|
874
|
+
|
|
723
875
|
for item in self.items:
|
|
724
|
-
marker = item[
|
|
725
|
-
label = item[
|
|
876
|
+
marker = item["marker"]
|
|
877
|
+
label = item["label"]
|
|
726
878
|
# Use color-coded markers if available
|
|
727
|
-
if self.style ==
|
|
879
|
+
if self.style == "box":
|
|
728
880
|
legend_lines.append(f"│ {marker} {label}")
|
|
729
881
|
else:
|
|
730
882
|
legend_lines.append(f"{marker} {label}")
|
|
731
|
-
|
|
732
|
-
if self.show_border and self.style ==
|
|
883
|
+
|
|
884
|
+
if self.show_border and self.style == "box":
|
|
733
885
|
legend_lines.append("└──────────┘")
|
|
734
|
-
|
|
886
|
+
|
|
735
887
|
return legend_lines
|
|
736
|
-
|
|
888
|
+
|
|
737
889
|
def show(self):
|
|
738
890
|
"""Display the legend independently"""
|
|
739
891
|
legend_text = self.render_legend_text()
|
|
@@ -747,16 +899,34 @@ class PlotextAPI:
|
|
|
747
899
|
Modern functional API that provides cleaner function-based interface
|
|
748
900
|
while maintaining the flexibility of the original plotext.
|
|
749
901
|
"""
|
|
750
|
-
|
|
902
|
+
|
|
751
903
|
@staticmethod
|
|
752
904
|
def create_chart(use_banners=False, banner_title=None):
|
|
753
905
|
"""Create a new Chart instance"""
|
|
754
906
|
return Chart(use_banners, banner_title)
|
|
755
|
-
|
|
907
|
+
|
|
756
908
|
@staticmethod
|
|
757
|
-
def quick_scatter(
|
|
909
|
+
def quick_scatter(
|
|
910
|
+
x,
|
|
911
|
+
y,
|
|
912
|
+
title=None,
|
|
913
|
+
xlabel=None,
|
|
914
|
+
ylabel=None,
|
|
915
|
+
theme_name=None,
|
|
916
|
+
use_banners=False,
|
|
917
|
+
banner_title=None,
|
|
918
|
+
):
|
|
758
919
|
"""Quickly create and display a scatter plot"""
|
|
759
920
|
chart = Chart(use_banners, banner_title)
|
|
921
|
+
if theme_name:
|
|
922
|
+
chart.theme(theme_name)
|
|
923
|
+
|
|
924
|
+
# Set chart size for better presentation
|
|
925
|
+
import plotext_plus._core as _core
|
|
926
|
+
|
|
927
|
+
term_width, term_height = _core.terminal_size()
|
|
928
|
+
chart.size(min(term_width, 120), max(20, term_height - 4))
|
|
929
|
+
|
|
760
930
|
chart.scatter(x, y)
|
|
761
931
|
if title:
|
|
762
932
|
chart.title(title)
|
|
@@ -766,11 +936,29 @@ class PlotextAPI:
|
|
|
766
936
|
chart.ylabel(ylabel)
|
|
767
937
|
chart.show()
|
|
768
938
|
return chart
|
|
769
|
-
|
|
939
|
+
|
|
770
940
|
@staticmethod
|
|
771
|
-
def quick_line(
|
|
941
|
+
def quick_line(
|
|
942
|
+
x,
|
|
943
|
+
y,
|
|
944
|
+
title=None,
|
|
945
|
+
xlabel=None,
|
|
946
|
+
ylabel=None,
|
|
947
|
+
theme_name=None,
|
|
948
|
+
use_banners=False,
|
|
949
|
+
banner_title=None,
|
|
950
|
+
):
|
|
772
951
|
"""Quickly create and display a line plot"""
|
|
773
952
|
chart = Chart(use_banners, banner_title)
|
|
953
|
+
if theme_name:
|
|
954
|
+
chart.theme(theme_name)
|
|
955
|
+
|
|
956
|
+
# Set chart size for better presentation
|
|
957
|
+
import plotext_plus._core as _core
|
|
958
|
+
|
|
959
|
+
term_width, term_height = _core.terminal_size()
|
|
960
|
+
chart.size(min(term_width, 120), max(20, term_height - 4))
|
|
961
|
+
|
|
774
962
|
chart.line(x, y)
|
|
775
963
|
if title:
|
|
776
964
|
chart.title(title)
|
|
@@ -780,56 +968,129 @@ class PlotextAPI:
|
|
|
780
968
|
chart.ylabel(ylabel)
|
|
781
969
|
chart.show()
|
|
782
970
|
return chart
|
|
783
|
-
|
|
971
|
+
|
|
784
972
|
@staticmethod
|
|
785
|
-
def quick_bar(
|
|
973
|
+
def quick_bar(
|
|
974
|
+
labels,
|
|
975
|
+
values,
|
|
976
|
+
title=None,
|
|
977
|
+
horizontal=False,
|
|
978
|
+
use_banners=False,
|
|
979
|
+
banner_title=None,
|
|
980
|
+
theme_name=None,
|
|
981
|
+
):
|
|
786
982
|
"""Quickly create and display a bar chart"""
|
|
787
983
|
chart = Chart(use_banners, banner_title)
|
|
984
|
+
if theme_name:
|
|
985
|
+
chart.theme(theme_name)
|
|
986
|
+
|
|
987
|
+
# Set chart size to use full terminal width for better presentation
|
|
988
|
+
import plotext_plus._core as _core
|
|
989
|
+
|
|
990
|
+
term_width, term_height = _core.terminal_size()
|
|
991
|
+
# Use a larger width to ensure the chart fills the available space
|
|
992
|
+
chart_width = min(
|
|
993
|
+
max(term_width, 100), 140
|
|
994
|
+
) # At least 100, up to 140 characters wide
|
|
995
|
+
chart.size(chart_width, 20)
|
|
996
|
+
|
|
788
997
|
chart.bar(labels, values, horizontal=horizontal)
|
|
789
998
|
if title:
|
|
790
999
|
chart.title(title)
|
|
791
1000
|
chart.show()
|
|
792
1001
|
return chart
|
|
793
|
-
|
|
794
|
-
@staticmethod
|
|
795
|
-
def quick_pie(
|
|
796
|
-
|
|
1002
|
+
|
|
1003
|
+
@staticmethod
|
|
1004
|
+
def quick_pie(
|
|
1005
|
+
labels,
|
|
1006
|
+
values,
|
|
1007
|
+
colors=None,
|
|
1008
|
+
title=None,
|
|
1009
|
+
use_banners=False,
|
|
1010
|
+
banner_title=None,
|
|
1011
|
+
show_values=True,
|
|
1012
|
+
show_percentages=True,
|
|
1013
|
+
show_values_on_slices=False,
|
|
1014
|
+
donut=False,
|
|
1015
|
+
remaining_color=None,
|
|
1016
|
+
theme_name=None,
|
|
1017
|
+
):
|
|
797
1018
|
"""Quickly create and display a pie chart"""
|
|
798
1019
|
chart = Chart(use_banners, banner_title)
|
|
799
|
-
|
|
1020
|
+
if theme_name:
|
|
1021
|
+
chart.theme(theme_name)
|
|
1022
|
+
|
|
1023
|
+
# Set chart size for better presentation
|
|
1024
|
+
import plotext_plus._core as _core
|
|
1025
|
+
|
|
1026
|
+
term_width, term_height = _core.terminal_size()
|
|
1027
|
+
chart.size(min(term_width, 120), max(20, term_height - 4))
|
|
1028
|
+
|
|
1029
|
+
chart.pie(
|
|
1030
|
+
labels,
|
|
1031
|
+
values,
|
|
1032
|
+
colors=colors,
|
|
1033
|
+
show_values=show_values,
|
|
1034
|
+
show_percentages=show_percentages,
|
|
1035
|
+
show_values_on_slices=show_values_on_slices,
|
|
1036
|
+
donut=donut,
|
|
1037
|
+
remaining_color=remaining_color,
|
|
1038
|
+
)
|
|
800
1039
|
if title:
|
|
801
1040
|
chart.title(title)
|
|
802
1041
|
chart.show()
|
|
803
1042
|
return chart
|
|
804
|
-
|
|
1043
|
+
|
|
805
1044
|
@staticmethod
|
|
806
|
-
def quick_donut(
|
|
807
|
-
|
|
1045
|
+
def quick_donut(
|
|
1046
|
+
labels,
|
|
1047
|
+
values,
|
|
1048
|
+
colors=None,
|
|
1049
|
+
title=None,
|
|
1050
|
+
use_banners=False,
|
|
1051
|
+
banner_title=None,
|
|
1052
|
+
show_values=True,
|
|
1053
|
+
show_percentages=True,
|
|
1054
|
+
show_values_on_slices=False,
|
|
1055
|
+
remaining_color=None,
|
|
1056
|
+
theme_name=None,
|
|
1057
|
+
):
|
|
808
1058
|
"""Quickly create and display a doughnut chart"""
|
|
809
|
-
return
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1059
|
+
return PlotextAPI.quick_pie(
|
|
1060
|
+
labels,
|
|
1061
|
+
values,
|
|
1062
|
+
colors=colors,
|
|
1063
|
+
title=title,
|
|
1064
|
+
use_banners=use_banners,
|
|
1065
|
+
banner_title=banner_title,
|
|
1066
|
+
show_values=show_values,
|
|
1067
|
+
show_percentages=show_percentages,
|
|
1068
|
+
show_values_on_slices=show_values_on_slices,
|
|
1069
|
+
donut=True,
|
|
1070
|
+
remaining_color=remaining_color,
|
|
1071
|
+
theme_name=theme_name,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
813
1074
|
@staticmethod
|
|
814
1075
|
def enable_banners(enabled=True, default_title="Plotext Chart"):
|
|
815
1076
|
"""Globally enable or disable banner mode"""
|
|
816
1077
|
set_output_mode(enabled, default_title)
|
|
817
|
-
|
|
1078
|
+
|
|
818
1079
|
@staticmethod
|
|
819
1080
|
def log_info(message):
|
|
820
1081
|
"""Output info message using chuk-term"""
|
|
821
1082
|
info(message)
|
|
822
|
-
|
|
1083
|
+
|
|
823
1084
|
@staticmethod
|
|
824
1085
|
def log_success(message):
|
|
825
1086
|
"""Output success message using chuk-term"""
|
|
826
1087
|
success(message)
|
|
827
|
-
|
|
1088
|
+
|
|
828
1089
|
@staticmethod
|
|
829
1090
|
def log_warning(message):
|
|
830
1091
|
"""Output warning message using chuk-term"""
|
|
831
1092
|
warning(message)
|
|
832
|
-
|
|
1093
|
+
|
|
833
1094
|
@staticmethod
|
|
834
1095
|
def log_error(message):
|
|
835
1096
|
"""Output error message using chuk-term"""
|
|
@@ -854,26 +1115,26 @@ log_error = api.log_error
|
|
|
854
1115
|
|
|
855
1116
|
# Export specialized chart classes
|
|
856
1117
|
__all__ = [
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
]
|
|
1118
|
+
"Chart",
|
|
1119
|
+
"ScatterChart",
|
|
1120
|
+
"LineChart",
|
|
1121
|
+
"BarChart",
|
|
1122
|
+
"HistogramChart",
|
|
1123
|
+
"CandlestickChart",
|
|
1124
|
+
"HeatmapChart",
|
|
1125
|
+
"MatrixChart",
|
|
1126
|
+
"StemChart",
|
|
1127
|
+
"Legend",
|
|
1128
|
+
"PlotextAPI",
|
|
1129
|
+
"create_chart",
|
|
1130
|
+
"quick_scatter",
|
|
1131
|
+
"quick_line",
|
|
1132
|
+
"quick_bar",
|
|
1133
|
+
"quick_pie",
|
|
1134
|
+
"quick_donut",
|
|
1135
|
+
"enable_banners",
|
|
1136
|
+
"log_info",
|
|
1137
|
+
"log_success",
|
|
1138
|
+
"log_warning",
|
|
1139
|
+
"log_error",
|
|
1140
|
+
]
|