plotext-plus 1.0.5__py3-none-any.whl → 1.0.8__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 +1 -1
- plotext_plus/_api.py +51 -0
- plotext_plus/_core.py +21 -0
- plotext_plus/_dict.py +2 -2
- plotext_plus/_figure.py +3 -0
- plotext_plus/_monitor.py +403 -0
- plotext_plus/_themes.py +3 -2
- plotext_plus/charts.py +2 -2
- plotext_plus/mcp_server.py +306 -0
- plotext_plus/plotting.py +1 -1
- {plotext_plus-1.0.5.dist-info → plotext_plus-1.0.8.dist-info}/METADATA +4 -5
- {plotext_plus-1.0.5.dist-info → plotext_plus-1.0.8.dist-info}/RECORD +15 -15
- {plotext_plus-1.0.5.dist-info → plotext_plus-1.0.8.dist-info}/WHEEL +0 -0
- {plotext_plus-1.0.5.dist-info → plotext_plus-1.0.8.dist-info}/entry_points.txt +0 -0
- {plotext_plus-1.0.5.dist-info → plotext_plus-1.0.8.dist-info}/licenses/LICENSE +0 -0
plotext_plus/__init__.py
CHANGED
|
@@ -26,6 +26,6 @@ from ._api import (
|
|
|
26
26
|
Chart, Legend, PlotextAPI, api,
|
|
27
27
|
ScatterChart, LineChart, BarChart, HistogramChart,
|
|
28
28
|
CandlestickChart, HeatmapChart, MatrixChart, StemChart,
|
|
29
|
-
create_chart, quick_scatter, quick_line, quick_bar,
|
|
29
|
+
create_chart, quick_scatter, quick_line, quick_bar, quick_pie, quick_donut,
|
|
30
30
|
enable_banners, log_info, log_success, log_warning, log_error
|
|
31
31
|
)
|
plotext_plus/_api.py
CHANGED
|
@@ -84,6 +84,22 @@ class Chart:
|
|
|
84
84
|
})
|
|
85
85
|
return self
|
|
86
86
|
|
|
87
|
+
def pie(self, labels, values, colors=None, radius=None, show_values=True, show_percentages=True, show_values_on_slices=False, donut=False, remaining_color=None):
|
|
88
|
+
"""Add pie chart data"""
|
|
89
|
+
self._data.append({
|
|
90
|
+
'type': 'pie',
|
|
91
|
+
'labels': labels,
|
|
92
|
+
'values': values,
|
|
93
|
+
'colors': colors,
|
|
94
|
+
'radius': radius,
|
|
95
|
+
'show_values': show_values,
|
|
96
|
+
'show_percentages': show_percentages,
|
|
97
|
+
'show_values_on_slices': show_values_on_slices,
|
|
98
|
+
'donut': donut,
|
|
99
|
+
'remaining_color': remaining_color
|
|
100
|
+
})
|
|
101
|
+
return self
|
|
102
|
+
|
|
87
103
|
def title(self, title):
|
|
88
104
|
"""Set chart title"""
|
|
89
105
|
self._config['title'] = title
|
|
@@ -172,6 +188,18 @@ class Chart:
|
|
|
172
188
|
_core.horizontal_bar(data_item['labels'], data_item['values'], color=data_item['color'])
|
|
173
189
|
else:
|
|
174
190
|
_core.bar(data_item['labels'], data_item['values'], color=data_item['color'])
|
|
191
|
+
elif data_item['type'] == 'pie':
|
|
192
|
+
_core.pie(
|
|
193
|
+
data_item['labels'],
|
|
194
|
+
data_item['values'],
|
|
195
|
+
colors=data_item['colors'],
|
|
196
|
+
radius=data_item['radius'],
|
|
197
|
+
show_values=data_item['show_values'],
|
|
198
|
+
show_percentages=data_item['show_percentages'],
|
|
199
|
+
show_values_on_slices=data_item['show_values_on_slices'],
|
|
200
|
+
donut=data_item.get('donut', False),
|
|
201
|
+
remaining_color=data_item.get('remaining_color', None)
|
|
202
|
+
)
|
|
175
203
|
elif data_item['type'] == 'histogram':
|
|
176
204
|
_core.hist(data_item['data'], bins=data_item['bins'], color=data_item['color'])
|
|
177
205
|
|
|
@@ -763,6 +791,25 @@ class PlotextAPI:
|
|
|
763
791
|
chart.show()
|
|
764
792
|
return chart
|
|
765
793
|
|
|
794
|
+
@staticmethod
|
|
795
|
+
def quick_pie(labels, values, colors=None, title=None, use_banners=False, banner_title=None,
|
|
796
|
+
show_values=True, show_percentages=True, show_values_on_slices=False, donut=False, remaining_color=None):
|
|
797
|
+
"""Quickly create and display a pie chart"""
|
|
798
|
+
chart = Chart(use_banners, banner_title)
|
|
799
|
+
chart.pie(labels, values, colors=colors, show_values=show_values, show_percentages=show_percentages, show_values_on_slices=show_values_on_slices, donut=donut, remaining_color=remaining_color)
|
|
800
|
+
if title:
|
|
801
|
+
chart.title(title)
|
|
802
|
+
chart.show()
|
|
803
|
+
return chart
|
|
804
|
+
|
|
805
|
+
@staticmethod
|
|
806
|
+
def quick_donut(labels, values, colors=None, title=None, use_banners=False, banner_title=None,
|
|
807
|
+
show_values=True, show_percentages=True, show_values_on_slices=False, remaining_color=None):
|
|
808
|
+
"""Quickly create and display a doughnut chart"""
|
|
809
|
+
return api.quick_pie(labels, values, colors=colors, title=title, use_banners=use_banners,
|
|
810
|
+
banner_title=banner_title, show_values=show_values,
|
|
811
|
+
show_percentages=show_percentages, show_values_on_slices=show_values_on_slices, donut=True, remaining_color=remaining_color)
|
|
812
|
+
|
|
766
813
|
@staticmethod
|
|
767
814
|
def enable_banners(enabled=True, default_title="Plotext Chart"):
|
|
768
815
|
"""Globally enable or disable banner mode"""
|
|
@@ -797,6 +844,8 @@ create_chart = api.create_chart
|
|
|
797
844
|
quick_scatter = api.quick_scatter
|
|
798
845
|
quick_line = api.quick_line
|
|
799
846
|
quick_bar = api.quick_bar
|
|
847
|
+
quick_pie = api.quick_pie
|
|
848
|
+
quick_donut = api.quick_donut
|
|
800
849
|
enable_banners = api.enable_banners
|
|
801
850
|
log_info = api.log_info
|
|
802
851
|
log_success = api.log_success
|
|
@@ -820,6 +869,8 @@ __all__ = [
|
|
|
820
869
|
'quick_scatter',
|
|
821
870
|
'quick_line',
|
|
822
871
|
'quick_bar',
|
|
872
|
+
'quick_pie',
|
|
873
|
+
'quick_donut',
|
|
823
874
|
'enable_banners',
|
|
824
875
|
'log_info',
|
|
825
876
|
'log_success',
|
plotext_plus/_core.py
CHANGED
|
@@ -278,6 +278,27 @@ def matrix_plot(matrix, marker = None, style = None, fast = False):
|
|
|
278
278
|
_figure._active.matrix_plot(matrix, marker = marker, style = style, fast = fast)
|
|
279
279
|
_figure.show() if _figure._interactive else None
|
|
280
280
|
|
|
281
|
+
def pie(labels, values, colors = None, radius = None, show_values = True, show_percentages = True, title = None, show_values_on_slices = False, donut = False, remaining_color = None):
|
|
282
|
+
"""
|
|
283
|
+
Create a pie chart representation using terminal characters.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
labels (list): Labels for each slice
|
|
287
|
+
values (list): Values for each slice
|
|
288
|
+
colors (list, optional): Colors for each slice
|
|
289
|
+
radius (int, optional): Radius of the pie chart
|
|
290
|
+
show_values (bool): Whether to show actual values in legend
|
|
291
|
+
show_percentages (bool): Whether to show percentages in legend
|
|
292
|
+
title (str, optional): Title for the chart
|
|
293
|
+
show_values_on_slices (bool): Whether to show values/percentages on pie slices themselves
|
|
294
|
+
donut (bool): If True, creates a doughnut chart with hollow center (inner radius = 1/3 outer radius)
|
|
295
|
+
remaining_color (str): If specified, colors the remaining slice with this color instead of leaving it as spaces
|
|
296
|
+
"""
|
|
297
|
+
_figure._active.pie(labels, values, colors = colors, radius = radius,
|
|
298
|
+
show_values = show_values, show_percentages = show_percentages,
|
|
299
|
+
title = title, show_values_on_slices = show_values_on_slices, donut = donut, remaining_color = remaining_color)
|
|
300
|
+
_figure.show() if _figure._interactive else None
|
|
301
|
+
|
|
281
302
|
def heatmap(dataframe, color = None, style=None):
|
|
282
303
|
_figure._active.heatmap(dataframe, color = color, style = style)
|
|
283
304
|
_figure.show() if _figure._interactive else None
|
plotext_plus/_dict.py
CHANGED
|
@@ -191,12 +191,12 @@ themes['dracula'] = [(40, 42, 54), (248, 248, 242), (139, 233, 253), 'bold', seq
|
|
|
191
191
|
# Solarized Dark - popular dark theme with muted colors
|
|
192
192
|
sequence = [(42, 161, 152), (133, 153, 0), (181, 137, 0), (203, 75, 22), (211, 54, 130)] # base0, green, yellow, orange, magenta
|
|
193
193
|
sequence += [el for el in color_sequence if el not in sequence]
|
|
194
|
-
themes['solarized_dark'] = [(0, 43, 54), (
|
|
194
|
+
themes['solarized_dark'] = [(0, 43, 54), (5, 5, 5), (147, 161, 161), no_color, sequence]
|
|
195
195
|
|
|
196
196
|
# Solarized Light - light version of solarized
|
|
197
197
|
sequence = [(42, 161, 152), (133, 153, 0), (181, 137, 0), (203, 75, 22), (211, 54, 130)] # same colors as dark
|
|
198
198
|
sequence += [el for el in color_sequence if el not in sequence]
|
|
199
|
-
themes['solarized_light'] = [(253, 246, 227), (101, 123, 131), (
|
|
199
|
+
themes['solarized_light'] = [(253, 246, 227), (101, 123, 131), (64, 64, 64), no_color, sequence]
|
|
200
200
|
|
|
201
201
|
# === CHUK-TERM COMPATIBLE THEMES ===
|
|
202
202
|
# Import and integrate chuk-term compatible themes
|
plotext_plus/_figure.py
CHANGED
|
@@ -277,6 +277,9 @@ class _figure_class():
|
|
|
277
277
|
def matrix_plot(self, matrix, marker = None, style = None, fast = False):
|
|
278
278
|
self.monitor.draw_matrix(matrix, marker = marker, style = style, fast = fast) if self._no_plots else [[self._get_subplot(row, col).matrix_plot(matrix, marker = marker, style = style, fast = fast) for col in self._Cols] for row in self._Rows]
|
|
279
279
|
|
|
280
|
+
def pie(self, labels, values, colors = None, radius = None, show_values = True, show_percentages = True, title = None, show_values_on_slices = False, donut = False, remaining_color = None):
|
|
281
|
+
self.monitor.draw_pie(labels, values, colors = colors, radius = radius, show_values = show_values, show_percentages = show_percentages, title = title, show_values_on_slices = show_values_on_slices, donut = donut, remaining_color = remaining_color) if self._no_plots else [[self._get_subplot(row, col).pie(labels, values, colors = colors, radius = radius, show_values = show_values, show_percentages = show_percentages, title = title, show_values_on_slices = show_values_on_slices, donut = donut, remaining_color = remaining_color) for col in self._Cols] for row in self._Rows]
|
|
282
|
+
|
|
280
283
|
def heatmap(self, dataframe, color = None, style = None):
|
|
281
284
|
self.monitor.draw_heatmap(dataframe, color = color, style = style) if self._no_plots else [[self._get_subplot(row, col).heatmap(dataframe, color = color, style = style) for col in self._Cols] for row in self._Rows]
|
|
282
285
|
|
plotext_plus/_monitor.py
CHANGED
|
@@ -775,6 +775,409 @@ class monitor_class(build_class):
|
|
|
775
775
|
self.matrix.canvas = '\n'.join([''.join(row) for row in matrix])
|
|
776
776
|
self.fast_plot = True
|
|
777
777
|
|
|
778
|
+
def draw_pie(self, labels, values, colors = None, radius = None, show_values = True, show_percentages = True, title = None, show_values_on_slices = False, donut = False, remaining_color = None):
|
|
779
|
+
"""
|
|
780
|
+
Draw a pie chart using filled colored segments and a legend.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
donut (bool): If True, creates a doughnut chart with hollow center (inner radius = 1/3 outer radius)
|
|
784
|
+
remaining_color (str): If specified, colors the remaining slice with this color instead of leaving it as spaces
|
|
785
|
+
"""
|
|
786
|
+
import math
|
|
787
|
+
|
|
788
|
+
# Input validation
|
|
789
|
+
if len(labels) != len(values):
|
|
790
|
+
raise ValueError("Labels and values must have the same length")
|
|
791
|
+
|
|
792
|
+
# Calculate total and percentages
|
|
793
|
+
total = sum(values)
|
|
794
|
+
if total == 0:
|
|
795
|
+
raise ValueError("Total of values cannot be zero")
|
|
796
|
+
|
|
797
|
+
percentages = [(value / total) * 100 for value in values]
|
|
798
|
+
|
|
799
|
+
# Default colors if not provided
|
|
800
|
+
if colors is None:
|
|
801
|
+
color_cycle = ['red', 'blue', 'green', 'orange', 'magenta', 'cyan', 'white']
|
|
802
|
+
colors = [color_cycle[i % len(color_cycle)] for i in range(len(labels))]
|
|
803
|
+
|
|
804
|
+
# Default radius - calculate based on available plot space
|
|
805
|
+
if radius is None:
|
|
806
|
+
# Get the actual plot area dimensions
|
|
807
|
+
plot_width, plot_height = self.size
|
|
808
|
+
|
|
809
|
+
# Set radius to half of the smaller dimension minus 4 for border margin
|
|
810
|
+
radius = (min(plot_width, plot_height) - 4) / 2.0
|
|
811
|
+
radius = max(radius, 3) # Ensure minimum radius of 3
|
|
812
|
+
|
|
813
|
+
# Center the pie chart
|
|
814
|
+
center_x = 0
|
|
815
|
+
center_y = 0
|
|
816
|
+
|
|
817
|
+
# Terminal characters have an aspect ratio of approximately 1.5:1 (height:width)
|
|
818
|
+
# To make circles appear circular, we need to adjust the x-axis scaling
|
|
819
|
+
aspect_ratio = 1.5
|
|
820
|
+
|
|
821
|
+
# Remove axes - pie charts don't have them
|
|
822
|
+
self.set_xfrequency(0)
|
|
823
|
+
self.set_yfrequency(0)
|
|
824
|
+
self.set_axes_color('default')
|
|
825
|
+
self.set_canvas_color('default')
|
|
826
|
+
|
|
827
|
+
# Collect all points for each segment, then draw each segment in one call
|
|
828
|
+
# Use efficient scanning - just slightly beyond the actual pie radius
|
|
829
|
+
# For doughnuts, use denser scanning to ensure solid ring
|
|
830
|
+
if donut:
|
|
831
|
+
scan_radius_x = int(radius * aspect_ratio * 1.5 + 3)
|
|
832
|
+
scan_radius_y = int(radius * 1.5 + 3)
|
|
833
|
+
else:
|
|
834
|
+
scan_radius_x = int(radius * aspect_ratio * 1.2 + 2)
|
|
835
|
+
scan_radius_y = int(radius * 1.2 + 2)
|
|
836
|
+
|
|
837
|
+
# Pre-calculate cumulative angles for segment boundaries
|
|
838
|
+
segment_boundaries = []
|
|
839
|
+
current_cumulative = 0
|
|
840
|
+
for value in values:
|
|
841
|
+
slice_angle = (value / total) * 2 * math.pi
|
|
842
|
+
segment_boundaries.append((current_cumulative, current_cumulative + slice_angle))
|
|
843
|
+
current_cumulative += slice_angle
|
|
844
|
+
|
|
845
|
+
# Collect all points for each segment using sets to avoid duplicates
|
|
846
|
+
segment_points = [set() for _ in range(len(labels))] # One set per segment
|
|
847
|
+
|
|
848
|
+
# Use FLOOD FILL approach - systematically fill every position in concentric circles
|
|
849
|
+
# This ensures no gaps by filling from center outward
|
|
850
|
+
for y_offset in range(-scan_radius_y, scan_radius_y + 1):
|
|
851
|
+
for x_offset in range(-scan_radius_x, scan_radius_x + 1):
|
|
852
|
+
# Calculate distance from center with aspect ratio correction
|
|
853
|
+
# Since terminal chars are ~1.5x taller than wide, compress x coordinate
|
|
854
|
+
adjusted_x = x_offset / aspect_ratio
|
|
855
|
+
distance = math.sqrt(adjusted_x * adjusted_x + y_offset * y_offset)
|
|
856
|
+
|
|
857
|
+
# For doughnut inner boundary, use elliptical check to create circular appearance
|
|
858
|
+
# The inner boundary should be elliptical in terminal coordinates to appear circular
|
|
859
|
+
inner_radius = radius / 3.0 if donut else 0
|
|
860
|
+
|
|
861
|
+
# Check if point is outside the inner ellipse (for circular appearance)
|
|
862
|
+
if donut:
|
|
863
|
+
# Create elliptical inner boundary: x^2/a^2 + y^2/b^2 > r^2
|
|
864
|
+
# where a = inner_radius * aspect_ratio, b = inner_radius
|
|
865
|
+
ellipse_x_term = (x_offset * x_offset) / (inner_radius * aspect_ratio * inner_radius * aspect_ratio)
|
|
866
|
+
ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
|
|
867
|
+
ellipse_value = ellipse_x_term + ellipse_y_term
|
|
868
|
+
outside_inner = ellipse_value > 1.0
|
|
869
|
+
|
|
870
|
+
else:
|
|
871
|
+
outside_inner = True
|
|
872
|
+
|
|
873
|
+
# Use exact radius to stay within plot boundaries
|
|
874
|
+
threshold = radius
|
|
875
|
+
|
|
876
|
+
if distance <= threshold and outside_inner:
|
|
877
|
+
# Calculate angle for this position using adjusted coordinates
|
|
878
|
+
angle = math.atan2(y_offset, adjusted_x)
|
|
879
|
+
if angle < 0:
|
|
880
|
+
angle += 2 * math.pi
|
|
881
|
+
|
|
882
|
+
# Find which segment this position belongs to using robust angle detection
|
|
883
|
+
segment_idx = 0
|
|
884
|
+
found_segment = False
|
|
885
|
+
epsilon = 0.02 # Even larger epsilon for maximum boundary coverage
|
|
886
|
+
|
|
887
|
+
for i, (start_angle, end_angle) in enumerate(segment_boundaries):
|
|
888
|
+
# Handle wraparound case for segments that cross 0 degrees
|
|
889
|
+
if end_angle > 2 * math.pi:
|
|
890
|
+
wrap_end = end_angle - 2 * math.pi
|
|
891
|
+
if angle >= start_angle - epsilon or angle <= wrap_end + epsilon:
|
|
892
|
+
segment_idx = i
|
|
893
|
+
found_segment = True
|
|
894
|
+
break
|
|
895
|
+
else:
|
|
896
|
+
# Use very generous boundary detection
|
|
897
|
+
# For the last segment, use <= to include the boundary
|
|
898
|
+
if i == len(segment_boundaries) - 1:
|
|
899
|
+
if start_angle - epsilon <= angle <= end_angle + epsilon:
|
|
900
|
+
segment_idx = i
|
|
901
|
+
found_segment = True
|
|
902
|
+
break
|
|
903
|
+
else:
|
|
904
|
+
if start_angle - epsilon <= angle < end_angle + epsilon:
|
|
905
|
+
segment_idx = i
|
|
906
|
+
found_segment = True
|
|
907
|
+
break
|
|
908
|
+
|
|
909
|
+
# If no segment found (due to floating point precision), assign based on closest angle
|
|
910
|
+
if not found_segment:
|
|
911
|
+
# Find the segment with the smallest angle distance
|
|
912
|
+
min_distance = float('inf')
|
|
913
|
+
for i, (start_angle, end_angle) in enumerate(segment_boundaries):
|
|
914
|
+
mid_angle = (start_angle + end_angle) / 2
|
|
915
|
+
# Handle wraparound for mid angle calculation
|
|
916
|
+
if end_angle > 2 * math.pi:
|
|
917
|
+
mid_angle = start_angle + ((end_angle - start_angle) / 2)
|
|
918
|
+
if mid_angle > 2 * math.pi:
|
|
919
|
+
mid_angle -= 2 * math.pi
|
|
920
|
+
|
|
921
|
+
# Calculate angular distance (accounting for circular nature)
|
|
922
|
+
angle_diff = abs(angle - mid_angle)
|
|
923
|
+
if angle_diff > math.pi:
|
|
924
|
+
angle_diff = 2 * math.pi - angle_diff
|
|
925
|
+
|
|
926
|
+
if angle_diff < min_distance:
|
|
927
|
+
min_distance = angle_diff
|
|
928
|
+
segment_idx = i
|
|
929
|
+
|
|
930
|
+
# Add this exact character position to the appropriate segment
|
|
931
|
+
char_x = center_x + x_offset
|
|
932
|
+
char_y = center_y + y_offset
|
|
933
|
+
segment_points[segment_idx].add((char_x, char_y))
|
|
934
|
+
|
|
935
|
+
# SECOND PASS: Fill any potential gaps by adding adjacent positions
|
|
936
|
+
# This ensures complete coverage by adding neighboring positions to existing points
|
|
937
|
+
additional_points = [set() for _ in range(len(labels))]
|
|
938
|
+
for segment_idx, points in enumerate(segment_points):
|
|
939
|
+
for (x, y) in points:
|
|
940
|
+
# Add neighboring positions to ensure no gaps
|
|
941
|
+
for dx in [-1, 0, 1]:
|
|
942
|
+
for dy in [-1, 0, 1]:
|
|
943
|
+
neighbor_x = x + dx
|
|
944
|
+
neighbor_y = y + dy
|
|
945
|
+
|
|
946
|
+
# Check if this neighbor is within the circular area using same logic as main pass
|
|
947
|
+
x_offset = neighbor_x - center_x
|
|
948
|
+
y_offset = neighbor_y - center_y
|
|
949
|
+
adjusted_x = x_offset / aspect_ratio
|
|
950
|
+
neighbor_distance = math.sqrt(adjusted_x * adjusted_x + y_offset * y_offset)
|
|
951
|
+
|
|
952
|
+
# Use same boundary checks as main algorithm
|
|
953
|
+
inner_radius = radius / 3.0 if donut else 0
|
|
954
|
+
|
|
955
|
+
# Check if point is outside the inner ellipse (for circular appearance)
|
|
956
|
+
if donut:
|
|
957
|
+
# Use same elliptical inner boundary as main pass
|
|
958
|
+
ellipse_x_term = (x_offset * x_offset) / (inner_radius * aspect_ratio * inner_radius * aspect_ratio)
|
|
959
|
+
ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
|
|
960
|
+
outside_inner = ellipse_x_term + ellipse_y_term > 1.0
|
|
961
|
+
else:
|
|
962
|
+
outside_inner = True
|
|
963
|
+
|
|
964
|
+
# Use exact radius to stay within plot boundaries (same as main pass)
|
|
965
|
+
threshold = radius
|
|
966
|
+
|
|
967
|
+
if neighbor_distance <= threshold and outside_inner:
|
|
968
|
+
additional_points[segment_idx].add((neighbor_x, neighbor_y))
|
|
969
|
+
|
|
970
|
+
# Merge additional points with main points
|
|
971
|
+
for segment_idx in range(len(labels)):
|
|
972
|
+
segment_points[segment_idx].update(additional_points[segment_idx])
|
|
973
|
+
|
|
974
|
+
# Draw each segment using a different approach - draw filled shapes row by row
|
|
975
|
+
# This ensures complete filling without gaps
|
|
976
|
+
for segment_idx, (points, color) in enumerate(zip(segment_points, colors)):
|
|
977
|
+
if points: # Only draw if segment has points
|
|
978
|
+
# Handle remaining_color for single-value pie charts
|
|
979
|
+
if color == "default":
|
|
980
|
+
if remaining_color is not None:
|
|
981
|
+
# Use the specified remaining_color instead of default
|
|
982
|
+
color = remaining_color
|
|
983
|
+
else:
|
|
984
|
+
# Skip drawing - leave as spaces (current behavior)
|
|
985
|
+
continue
|
|
986
|
+
|
|
987
|
+
points_list = list(points)
|
|
988
|
+
|
|
989
|
+
# Group points by y-coordinate to draw horizontal filled lines
|
|
990
|
+
y_groups = {}
|
|
991
|
+
for x, y in points_list:
|
|
992
|
+
if y not in y_groups:
|
|
993
|
+
y_groups[y] = []
|
|
994
|
+
y_groups[y].append(x)
|
|
995
|
+
|
|
996
|
+
# For doughnut charts, use smart filling that avoids the hollow center
|
|
997
|
+
# For regular pie charts, use full horizontal line filling
|
|
998
|
+
if donut:
|
|
999
|
+
# Smart filling for doughnut charts: fill gaps within ring segments but avoid center
|
|
1000
|
+
for y_coord, x_coords in y_groups.items():
|
|
1001
|
+
if x_coords:
|
|
1002
|
+
x_coords.sort() # Sort x coordinates
|
|
1003
|
+
|
|
1004
|
+
# Find continuous segments, avoiding the center gap
|
|
1005
|
+
fill_x_coords = []
|
|
1006
|
+
x_step = 0.5
|
|
1007
|
+
|
|
1008
|
+
# Determine if this y_coord passes through the hollow center
|
|
1009
|
+
y_offset = y_coord - center_y
|
|
1010
|
+
center_x_range = []
|
|
1011
|
+
|
|
1012
|
+
# Calculate the x-range that should be hollow at this y-coordinate
|
|
1013
|
+
if abs(y_offset) < inner_radius:
|
|
1014
|
+
# This y-line passes through the hollow center
|
|
1015
|
+
# Calculate x-bounds of the elliptical hollow area
|
|
1016
|
+
ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
|
|
1017
|
+
if ellipse_y_term < 1.0:
|
|
1018
|
+
ellipse_x_term_needed = 1.0 - ellipse_y_term
|
|
1019
|
+
max_x_offset = math.sqrt(ellipse_x_term_needed) * inner_radius * aspect_ratio
|
|
1020
|
+
center_x_min = center_x - max_x_offset
|
|
1021
|
+
center_x_max = center_x + max_x_offset
|
|
1022
|
+
center_x_range = [center_x_min, center_x_max]
|
|
1023
|
+
|
|
1024
|
+
# Fill between consecutive x-coordinates, but avoid the center region
|
|
1025
|
+
i = 0
|
|
1026
|
+
while i < len(x_coords):
|
|
1027
|
+
segment_start = x_coords[i]
|
|
1028
|
+
|
|
1029
|
+
# Find the end of this continuous segment
|
|
1030
|
+
j = i
|
|
1031
|
+
while j < len(x_coords) - 1:
|
|
1032
|
+
gap = x_coords[j + 1] - x_coords[j]
|
|
1033
|
+
# If there's a large gap, this segment ends
|
|
1034
|
+
if gap > 2.0: # Allow small gaps but break on large ones
|
|
1035
|
+
break
|
|
1036
|
+
j += 1
|
|
1037
|
+
|
|
1038
|
+
segment_end = x_coords[j]
|
|
1039
|
+
|
|
1040
|
+
# Fill this segment, but avoid the center region
|
|
1041
|
+
if center_x_range:
|
|
1042
|
+
# Split segment around the hollow center
|
|
1043
|
+
center_min, center_max = center_x_range
|
|
1044
|
+
|
|
1045
|
+
# Fill left part (before center)
|
|
1046
|
+
if segment_start < center_min:
|
|
1047
|
+
left_end = min(segment_end, center_min)
|
|
1048
|
+
current_x = segment_start
|
|
1049
|
+
while current_x <= left_end:
|
|
1050
|
+
fill_x_coords.append(current_x)
|
|
1051
|
+
current_x += x_step
|
|
1052
|
+
|
|
1053
|
+
# Fill right part (after center)
|
|
1054
|
+
if segment_end > center_max:
|
|
1055
|
+
right_start = max(segment_start, center_max)
|
|
1056
|
+
current_x = right_start
|
|
1057
|
+
while current_x <= segment_end:
|
|
1058
|
+
fill_x_coords.append(current_x)
|
|
1059
|
+
current_x += x_step
|
|
1060
|
+
else:
|
|
1061
|
+
# No center interference, fill entire segment
|
|
1062
|
+
current_x = segment_start
|
|
1063
|
+
while current_x <= segment_end:
|
|
1064
|
+
fill_x_coords.append(current_x)
|
|
1065
|
+
current_x += x_step
|
|
1066
|
+
|
|
1067
|
+
i = j + 1
|
|
1068
|
+
|
|
1069
|
+
# Draw the filled segments
|
|
1070
|
+
if fill_x_coords:
|
|
1071
|
+
fill_y_coords = [y_coord] * len(fill_x_coords)
|
|
1072
|
+
self.draw(fill_x_coords, fill_y_coords, marker='sd', color=color)
|
|
1073
|
+
else:
|
|
1074
|
+
# For regular pie charts, use full horizontal line filling
|
|
1075
|
+
for y_coord, x_coords in y_groups.items():
|
|
1076
|
+
if x_coords:
|
|
1077
|
+
x_coords.sort() # Sort x coordinates
|
|
1078
|
+
x_min, x_max = min(x_coords), max(x_coords)
|
|
1079
|
+
|
|
1080
|
+
# Create a continuous range of x coordinates to fill the gap
|
|
1081
|
+
if x_max > x_min:
|
|
1082
|
+
# Draw filled horizontal line from x_min to x_max
|
|
1083
|
+
fill_x_coords = []
|
|
1084
|
+
x_step = 0.5 # Smaller step for better coverage
|
|
1085
|
+
current_x = x_min
|
|
1086
|
+
while current_x <= x_max:
|
|
1087
|
+
fill_x_coords.append(current_x)
|
|
1088
|
+
current_x += x_step
|
|
1089
|
+
fill_y_coords = [y_coord] * len(fill_x_coords)
|
|
1090
|
+
self.draw(fill_x_coords, fill_y_coords, marker='sd', color=color)
|
|
1091
|
+
else:
|
|
1092
|
+
# Single point
|
|
1093
|
+
self.draw([x_min], [y_coord], marker='sd', color=color)
|
|
1094
|
+
|
|
1095
|
+
# Reset cumulative_angle for label drawing
|
|
1096
|
+
cumulative_angle = 0
|
|
1097
|
+
for i, (label, value, percentage, color) in enumerate(zip(labels, values, percentages, colors)):
|
|
1098
|
+
slice_angle = (value / total) * 2 * math.pi
|
|
1099
|
+
|
|
1100
|
+
# Add value labels on the pie slice (only if show_values_on_slices is True)
|
|
1101
|
+
if show_values_on_slices and (show_values or show_percentages):
|
|
1102
|
+
# Calculate middle angle of the slice for label placement
|
|
1103
|
+
middle_angle = cumulative_angle + slice_angle / 2
|
|
1104
|
+
# Position label at 70% of radius for better visibility
|
|
1105
|
+
label_radius = radius * 0.7
|
|
1106
|
+
label_x = center_x + (label_radius * math.cos(middle_angle)) * aspect_ratio
|
|
1107
|
+
label_y = center_y + label_radius * math.sin(middle_angle)
|
|
1108
|
+
|
|
1109
|
+
# Build label text for the slice
|
|
1110
|
+
slice_label = ""
|
|
1111
|
+
if show_values and show_percentages:
|
|
1112
|
+
slice_label = f"{value}\n({percentage:.1f}%)"
|
|
1113
|
+
elif show_values:
|
|
1114
|
+
slice_label = str(value)
|
|
1115
|
+
elif show_percentages:
|
|
1116
|
+
slice_label = f"{percentage:.1f}%"
|
|
1117
|
+
|
|
1118
|
+
# Draw the label on the slice
|
|
1119
|
+
self.draw_text(slice_label, label_x, label_y, color='white', alignment='center')
|
|
1120
|
+
|
|
1121
|
+
cumulative_angle += slice_angle
|
|
1122
|
+
|
|
1123
|
+
# Extend the plot area to accommodate legend (calculate before filtering)
|
|
1124
|
+
max_text_length = max(len(f"{label}: {value} ({percentage:.1f}%)")
|
|
1125
|
+
for label, value, percentage in zip(labels, values, percentages)
|
|
1126
|
+
if label.lower() != "remaining") if any(label.lower() != "remaining" for label in labels) else 20
|
|
1127
|
+
|
|
1128
|
+
# Set plot limits to include legend area (adjust x for aspect ratio)
|
|
1129
|
+
x_radius = radius * aspect_ratio
|
|
1130
|
+
self.set_xlim(-x_radius - 1, x_radius + max_text_length + 2)
|
|
1131
|
+
self.set_ylim(-radius - 1, radius + 1)
|
|
1132
|
+
|
|
1133
|
+
# Create legend positioned in the bottom right corner of the chart
|
|
1134
|
+
legend_start_x = x_radius + 1.5
|
|
1135
|
+
legend_start_y = -radius + len(labels) * 1.0 - 0.5
|
|
1136
|
+
|
|
1137
|
+
# Filter out "Remaining" labels and default colors for single-value pie charts
|
|
1138
|
+
legend_items = []
|
|
1139
|
+
for i, (label, value, percentage, color) in enumerate(zip(labels, values, percentages, colors)):
|
|
1140
|
+
# Handle remaining_color logic for legend
|
|
1141
|
+
if color == "default":
|
|
1142
|
+
if remaining_color is not None:
|
|
1143
|
+
# Show "Remaining" in legend when remaining_color is specified
|
|
1144
|
+
legend_items.append((label, value, percentage, remaining_color))
|
|
1145
|
+
# Skip if no remaining_color (leave as spaces)
|
|
1146
|
+
else:
|
|
1147
|
+
# Always show non-default colors
|
|
1148
|
+
legend_items.append((label, value, percentage, color))
|
|
1149
|
+
|
|
1150
|
+
# Adjust legend positioning for filtered items
|
|
1151
|
+
legend_start_y = -radius + len(legend_items) * 1.0 - 0.5
|
|
1152
|
+
|
|
1153
|
+
for i, (label, value, percentage, color) in enumerate(legend_items):
|
|
1154
|
+
legend_x = legend_start_x
|
|
1155
|
+
legend_y = legend_start_y - i * 1.2 # Space between legend items
|
|
1156
|
+
|
|
1157
|
+
# Draw colored square for legend matching pie chart blocks
|
|
1158
|
+
self.draw([legend_x], [legend_y], marker='sd', color=color)
|
|
1159
|
+
|
|
1160
|
+
# Build legend text with colored block prefix
|
|
1161
|
+
block_char = "█" # Solid block character
|
|
1162
|
+
legend_text = f"{block_char} {label}"
|
|
1163
|
+
if show_values and show_percentages:
|
|
1164
|
+
legend_text += f": {value} ({percentage:.1f}%)"
|
|
1165
|
+
elif show_values:
|
|
1166
|
+
legend_text += f": {value}"
|
|
1167
|
+
elif show_percentages:
|
|
1168
|
+
legend_text += f": {percentage:.1f}%"
|
|
1169
|
+
|
|
1170
|
+
# Use draw_text for the legend with the same color as the segment
|
|
1171
|
+
self.draw_text(legend_text, legend_x, legend_y, color=color)
|
|
1172
|
+
|
|
1173
|
+
# Set title if provided
|
|
1174
|
+
if title:
|
|
1175
|
+
self.set_title(title)
|
|
1176
|
+
|
|
1177
|
+
# Remove axis labels since pie charts don't need them
|
|
1178
|
+
self.set_xlabel('')
|
|
1179
|
+
self.set_ylabel('')
|
|
1180
|
+
|
|
778
1181
|
def draw_heatmap(self, dataframe, color = None, style=None):
|
|
779
1182
|
color = self.default.cmatrix_color if color is None else self.check_color(color)
|
|
780
1183
|
style = self.default.cmatrix_style if style is None else self.check_style(style)
|
plotext_plus/_themes.py
CHANGED
|
@@ -53,6 +53,7 @@ rgb_colors = {
|
|
|
53
53
|
'term_black': (0, 0, 0),
|
|
54
54
|
'term_white': (255, 255, 255),
|
|
55
55
|
'term_gray': (128, 128, 128),
|
|
56
|
+
'dark_gray': (64, 64, 64), # Dark gray for better readability
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
def create_chuk_term_themes():
|
|
@@ -140,7 +141,7 @@ def create_chuk_term_themes():
|
|
|
140
141
|
sequence = [rgb_colors['sol_cyan'], rgb_colors['sol_green'], rgb_colors['sol_yellow'],
|
|
141
142
|
rgb_colors['sol_blue'], rgb_colors['sol_magenta'], rgb_colors['sol_red']]
|
|
142
143
|
sequence += [el for el in color_sequence if el not in sequence]
|
|
143
|
-
themes['solarized_dark'] = [rgb_colors['sol_base03'],
|
|
144
|
+
themes['solarized_dark'] = [rgb_colors['sol_base03'], (5, 5, 5),
|
|
144
145
|
rgb_colors['sol_base1'], no_color, sequence]
|
|
145
146
|
|
|
146
147
|
# Solarized Light
|
|
@@ -148,7 +149,7 @@ def create_chuk_term_themes():
|
|
|
148
149
|
rgb_colors['sol_blue'], rgb_colors['sol_magenta'], rgb_colors['sol_red']]
|
|
149
150
|
sequence += [el for el in color_sequence if el not in sequence]
|
|
150
151
|
themes['solarized_light'] = [rgb_colors['sol_base3'], rgb_colors['sol_base01'],
|
|
151
|
-
rgb_colors['
|
|
152
|
+
rgb_colors['dark_gray'], no_color, sequence]
|
|
152
153
|
|
|
153
154
|
# Matrix theme (enhanced version)
|
|
154
155
|
sequence = [(0, 255, 65), (0, 200, 50), (0, 150, 35), (0, 100, 20)]
|
plotext_plus/charts.py
CHANGED
|
@@ -20,7 +20,7 @@ from ._api import (
|
|
|
20
20
|
CandlestickChart, HeatmapChart, MatrixChart, StemChart,
|
|
21
21
|
|
|
22
22
|
# Convenience functions
|
|
23
|
-
create_chart, quick_scatter, quick_line, quick_bar,
|
|
23
|
+
create_chart, quick_scatter, quick_line, quick_bar, quick_pie, quick_donut,
|
|
24
24
|
|
|
25
25
|
# Banner and logging utilities
|
|
26
26
|
enable_banners, log_info, log_success, log_warning, log_error,
|
|
@@ -35,7 +35,7 @@ __all__ = [
|
|
|
35
35
|
'CandlestickChart', 'HeatmapChart', 'MatrixChart', 'StemChart',
|
|
36
36
|
|
|
37
37
|
# Convenience functions
|
|
38
|
-
'create_chart', 'quick_scatter', 'quick_line', 'quick_bar',
|
|
38
|
+
'create_chart', 'quick_scatter', 'quick_line', 'quick_bar', 'quick_pie', 'quick_donut',
|
|
39
39
|
|
|
40
40
|
# Utilities
|
|
41
41
|
'enable_banners', 'log_info', 'log_success', 'log_warning', 'log_error',
|
plotext_plus/mcp_server.py
CHANGED
|
@@ -150,6 +150,53 @@ async def matrix_plot(data: List[List[Union[int, float]]], title: Optional[str]
|
|
|
150
150
|
return output + show_output
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
@tool
|
|
154
|
+
async def image_plot(image_path: str, title: Optional[str] = None,
|
|
155
|
+
marker: Optional[str] = None, style: Optional[str] = None,
|
|
156
|
+
fast: bool = False, grayscale: bool = False) -> str:
|
|
157
|
+
"""Display an image in the terminal using ASCII art.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
image_path: Path to the image file to display
|
|
161
|
+
title: Plot title (optional)
|
|
162
|
+
marker: Custom marker for image rendering (optional)
|
|
163
|
+
style: Style for image rendering (optional)
|
|
164
|
+
fast: Enable fast rendering mode for better performance (optional)
|
|
165
|
+
grayscale: Render image in grayscale (optional)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The rendered image plot as text
|
|
169
|
+
"""
|
|
170
|
+
plotting.clear_figure()
|
|
171
|
+
if title:
|
|
172
|
+
plotting.title(title)
|
|
173
|
+
|
|
174
|
+
_, output = _capture_plot_output(plotting.image_plot, image_path,
|
|
175
|
+
marker=marker, style=style,
|
|
176
|
+
fast=fast, grayscale=grayscale)
|
|
177
|
+
_, show_output = _capture_plot_output(plotting.show)
|
|
178
|
+
|
|
179
|
+
return output + show_output
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@tool
|
|
183
|
+
async def play_gif(gif_path: str) -> str:
|
|
184
|
+
"""Play a GIF animation in the terminal.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
gif_path: Path to the GIF file to play
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Confirmation message (GIF plays automatically)
|
|
191
|
+
"""
|
|
192
|
+
plotting.clear_figure()
|
|
193
|
+
|
|
194
|
+
# play_gif handles its own output and doesn't need show()
|
|
195
|
+
plotting.play_gif(gif_path)
|
|
196
|
+
|
|
197
|
+
return f"Playing GIF: {gif_path}"
|
|
198
|
+
|
|
199
|
+
|
|
153
200
|
# Chart Class Tools
|
|
154
201
|
@tool
|
|
155
202
|
async def quick_scatter(x: List[Union[int, float]], y: List[Union[int, float]],
|
|
@@ -205,6 +252,65 @@ async def quick_bar(labels: List[str], values: List[Union[int, float]],
|
|
|
205
252
|
return output
|
|
206
253
|
|
|
207
254
|
|
|
255
|
+
@tool
|
|
256
|
+
async def quick_pie(labels: List[str], values: List[Union[int, float]],
|
|
257
|
+
colors: Optional[List[str]] = None, title: Optional[str] = None,
|
|
258
|
+
show_values: bool = True, show_percentages: bool = True,
|
|
259
|
+
show_values_on_slices: bool = False, donut: bool = False,
|
|
260
|
+
remaining_color: Optional[str] = None) -> str:
|
|
261
|
+
"""Create a quick pie chart using the chart classes API.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
labels: List of pie segment labels
|
|
265
|
+
values: List of pie segment values
|
|
266
|
+
colors: List of colors for segments (optional)
|
|
267
|
+
title: Chart title (optional)
|
|
268
|
+
show_values: Show values in legend (optional, default True)
|
|
269
|
+
show_percentages: Show percentages in legend (optional, default True)
|
|
270
|
+
show_values_on_slices: Show values directly on pie slices (optional, default False)
|
|
271
|
+
donut: Create doughnut chart with hollow center (optional, default False)
|
|
272
|
+
remaining_color: Color for remaining slice in single-value charts (optional)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
The rendered pie chart as text
|
|
276
|
+
"""
|
|
277
|
+
_, output = _capture_plot_output(charts.quick_pie, labels, values, colors=colors,
|
|
278
|
+
title=title, show_values=show_values,
|
|
279
|
+
show_percentages=show_percentages,
|
|
280
|
+
show_values_on_slices=show_values_on_slices,
|
|
281
|
+
donut=donut, remaining_color=remaining_color)
|
|
282
|
+
return output
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@tool
|
|
286
|
+
async def quick_donut(labels: List[str], values: List[Union[int, float]],
|
|
287
|
+
colors: Optional[List[str]] = None, title: Optional[str] = None,
|
|
288
|
+
show_values: bool = True, show_percentages: bool = True,
|
|
289
|
+
show_values_on_slices: bool = False,
|
|
290
|
+
remaining_color: Optional[str] = None) -> str:
|
|
291
|
+
"""Create a quick doughnut chart (pie chart with hollow center) using the chart classes API.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
labels: List of pie segment labels
|
|
295
|
+
values: List of pie segment values
|
|
296
|
+
colors: List of colors for segments (optional)
|
|
297
|
+
title: Chart title (optional)
|
|
298
|
+
show_values: Show values in legend (optional, default True)
|
|
299
|
+
show_percentages: Show percentages in legend (optional, default True)
|
|
300
|
+
show_values_on_slices: Show values directly on pie slices (optional, default False)
|
|
301
|
+
remaining_color: Color for remaining slice in single-value charts (optional)
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The rendered doughnut chart as text
|
|
305
|
+
"""
|
|
306
|
+
_, output = _capture_plot_output(charts.quick_donut, labels, values, colors=colors,
|
|
307
|
+
title=title, show_values=show_values,
|
|
308
|
+
show_percentages=show_percentages,
|
|
309
|
+
show_values_on_slices=show_values_on_slices,
|
|
310
|
+
remaining_color=remaining_color)
|
|
311
|
+
return output
|
|
312
|
+
|
|
313
|
+
|
|
208
314
|
# Theme Tools
|
|
209
315
|
@tool
|
|
210
316
|
async def get_available_themes() -> Dict[str, Any]:
|
|
@@ -371,6 +477,66 @@ async def get_plot_config() -> Dict[str, Any]:
|
|
|
371
477
|
}
|
|
372
478
|
|
|
373
479
|
|
|
480
|
+
# Resource for tool documentation/info
|
|
481
|
+
@resource("info://plotext")
|
|
482
|
+
async def get_tool_info() -> Dict[str, Any]:
|
|
483
|
+
"""Get comprehensive information about all available plotting tools."""
|
|
484
|
+
return {
|
|
485
|
+
"server_info": {
|
|
486
|
+
"name": "Plotext Plus MCP Server",
|
|
487
|
+
"description": "Model Context Protocol server for plotext_plus terminal plotting library",
|
|
488
|
+
"version": "1.0.0",
|
|
489
|
+
"capabilities": ["plotting", "theming", "multimedia", "charts"]
|
|
490
|
+
},
|
|
491
|
+
"plotting_tools": {
|
|
492
|
+
"scatter_plot": "Create scatter plots with x/y data points",
|
|
493
|
+
"line_plot": "Create line plots for time series and continuous data",
|
|
494
|
+
"bar_chart": "Create bar charts for categorical data",
|
|
495
|
+
"matrix_plot": "Create heatmaps from 2D matrix data",
|
|
496
|
+
"image_plot": "Display images in terminal using ASCII art",
|
|
497
|
+
"play_gif": "Play animated GIFs in the terminal"
|
|
498
|
+
},
|
|
499
|
+
"quick_chart_tools": {
|
|
500
|
+
"quick_scatter": "Quickly create scatter charts with theming",
|
|
501
|
+
"quick_line": "Quickly create line charts with theming",
|
|
502
|
+
"quick_bar": "Quickly create bar charts with theming",
|
|
503
|
+
"quick_pie": "Quickly create pie charts with custom colors, donut mode, and remaining_color options",
|
|
504
|
+
"quick_donut": "Quickly create doughnut charts (hollow center pie charts)"
|
|
505
|
+
},
|
|
506
|
+
"theme_tools": {
|
|
507
|
+
"get_available_themes": "List all available color themes",
|
|
508
|
+
"apply_plot_theme": "Apply a theme to plots"
|
|
509
|
+
},
|
|
510
|
+
"utility_tools": {
|
|
511
|
+
"get_terminal_width": "Get current terminal width",
|
|
512
|
+
"colorize_text": "Apply colors to text output",
|
|
513
|
+
"log_info": "Output informational messages",
|
|
514
|
+
"log_success": "Output success messages",
|
|
515
|
+
"log_warning": "Output warning messages",
|
|
516
|
+
"log_error": "Output error messages"
|
|
517
|
+
},
|
|
518
|
+
"configuration_tools": {
|
|
519
|
+
"set_plot_size": "Set plot dimensions",
|
|
520
|
+
"enable_banner_mode": "Enable/disable banner mode",
|
|
521
|
+
"clear_plot": "Clear current plot"
|
|
522
|
+
},
|
|
523
|
+
"supported_formats": {
|
|
524
|
+
"image_formats": ["PNG", "JPG", "JPEG", "BMP", "GIF (static)"],
|
|
525
|
+
"gif_formats": ["GIF (animated)"],
|
|
526
|
+
"chart_types": ["scatter", "line", "bar", "pie", "doughnut", "matrix/heatmap", "image"],
|
|
527
|
+
"themes": "20+ built-in themes including solarized, dracula, cyberpunk"
|
|
528
|
+
},
|
|
529
|
+
"usage_tips": {
|
|
530
|
+
"pie_charts": "Best for 3-7 categories, use full terminal dimensions",
|
|
531
|
+
"doughnut_charts": "Modern alternative to pie charts with hollow center, great for progress indicators",
|
|
532
|
+
"single_value_charts": "Perfect for progress/completion rates: ['Complete', 'Remaining'] with 'default' color",
|
|
533
|
+
"images": "Use fast=True for better performance with large images",
|
|
534
|
+
"themes": "Apply themes before creating plots for consistent styling",
|
|
535
|
+
"banners": "Enable banner mode for professional-looking outputs"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
374
540
|
# MCP Prompts for common plotting scenarios
|
|
375
541
|
@prompt("basic_scatter")
|
|
376
542
|
async def basic_scatter_prompt() -> str:
|
|
@@ -494,6 +660,146 @@ async def complete_workflow_prompt() -> str:
|
|
|
494
660
|
6. Generate a summary report"""
|
|
495
661
|
|
|
496
662
|
|
|
663
|
+
@prompt("image_display")
|
|
664
|
+
async def image_display_prompt() -> str:
|
|
665
|
+
"""Image plotting example"""
|
|
666
|
+
return """Display an image in the terminal:
|
|
667
|
+
1. First download the test cat image using utilities.download() with the test_image_url
|
|
668
|
+
2. Display it using image_plot with title 'Test Image Display'
|
|
669
|
+
3. Try both normal and grayscale versions
|
|
670
|
+
4. Clean up by deleting the file afterward"""
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
@prompt("gif_animation")
|
|
674
|
+
async def gif_animation_prompt() -> str:
|
|
675
|
+
"""GIF animation example"""
|
|
676
|
+
return """Play a GIF animation in terminal:
|
|
677
|
+
1. Download the test Homer Simpson GIF using utilities.download() with test_gif_url
|
|
678
|
+
2. Play the GIF animation using play_gif
|
|
679
|
+
3. Clean up the file afterward
|
|
680
|
+
Note: The GIF will play automatically in the terminal"""
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@prompt("image_styling")
|
|
684
|
+
async def image_styling_prompt() -> str:
|
|
685
|
+
"""Custom image styling example"""
|
|
686
|
+
return """Experiment with image rendering styles:
|
|
687
|
+
1. Display the same image with different markers (try 'CuteCat' as marker)
|
|
688
|
+
2. Use 'inverted' style for visual effects
|
|
689
|
+
3. Compare fast vs normal rendering modes
|
|
690
|
+
4. Show both color and grayscale versions"""
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
@prompt("multimedia_showcase")
|
|
694
|
+
async def multimedia_showcase_prompt() -> str:
|
|
695
|
+
"""Complete multimedia demonstration"""
|
|
696
|
+
return """Showcase multimedia capabilities:
|
|
697
|
+
1. Download and display a static image with custom styling
|
|
698
|
+
2. Download and play an animated GIF
|
|
699
|
+
3. Set appropriate plot sizes for optimal viewing
|
|
700
|
+
4. Add descriptive titles to each display
|
|
701
|
+
5. Clean up all downloaded files"""
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@prompt("basic_pie_chart")
|
|
705
|
+
async def basic_pie_chart_prompt() -> str:
|
|
706
|
+
"""Basic pie chart example"""
|
|
707
|
+
return """Create a simple pie chart showing market share data:
|
|
708
|
+
- Categories: ['iOS', 'Android', 'Windows', 'Other']
|
|
709
|
+
- Values: [35, 45, 15, 5]
|
|
710
|
+
- Use colors: ['blue', 'green', 'orange', 'gray']
|
|
711
|
+
- Add title 'Mobile OS Market Share'
|
|
712
|
+
- Use quick_pie tool for fast creation"""
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
@prompt("pie_chart_styling")
|
|
716
|
+
async def pie_chart_styling_prompt() -> str:
|
|
717
|
+
"""Advanced pie chart styling example"""
|
|
718
|
+
return """Create a styled pie chart with advanced features:
|
|
719
|
+
1. Use quick_pie with show_values_on_slices=True
|
|
720
|
+
2. Data: Budget categories ['Housing', 'Food', 'Transport', 'Entertainment']
|
|
721
|
+
3. Values: [1200, 400, 300, 200] (monthly budget)
|
|
722
|
+
4. Custom colors for each category
|
|
723
|
+
5. Add meaningful title and ensure full terminal usage"""
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@prompt("pie_chart_comparison")
|
|
727
|
+
async def pie_chart_comparison_prompt() -> str:
|
|
728
|
+
"""Pie chart comparison example"""
|
|
729
|
+
return """Create multiple pie charts for comparison:
|
|
730
|
+
1. Q1 Sales: ['Product A', 'Product B', 'Product C'] = [30, 45, 25]
|
|
731
|
+
2. Q2 Sales: ['Product A', 'Product B', 'Product C'] = [25, 50, 25]
|
|
732
|
+
3. Show both charts with different colors
|
|
733
|
+
4. Use appropriate titles ('Q1 Sales Distribution', 'Q2 Sales Distribution')
|
|
734
|
+
5. Discuss the trends visible in the comparison"""
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
@prompt("pie_chart_best_practices")
|
|
738
|
+
async def pie_chart_best_practices_prompt() -> str:
|
|
739
|
+
"""Pie chart best practices demonstration"""
|
|
740
|
+
return """Demonstrate pie chart best practices:
|
|
741
|
+
1. Start with many categories: ['A', 'B', 'C', 'D', 'E', 'F', 'G'] = [5, 8, 12, 15, 25, 20, 15]
|
|
742
|
+
2. Show why this is problematic (too many small segments)
|
|
743
|
+
3. Combine small categories: ['A+B+C', 'D', 'E', 'F', 'G'] = [25, 15, 25, 20, 15]
|
|
744
|
+
4. Create the improved version with title 'Improved: Combined Small Categories'
|
|
745
|
+
5. Explain the improvement in readability"""
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
@prompt("single_value_pie_chart")
|
|
749
|
+
async def single_value_pie_chart_prompt() -> str:
|
|
750
|
+
"""Single-value pie chart for progress indicators"""
|
|
751
|
+
return """Create single-value pie charts perfect for progress indicators:
|
|
752
|
+
1. Basic progress chart: ['Complete', 'Remaining'] = [75, 25], colors=['green', 'default']
|
|
753
|
+
2. Title: 'Project Progress: 75%'
|
|
754
|
+
3. Show only percentages (show_values=False, show_percentages=True)
|
|
755
|
+
4. Note: Remaining area appears as spaces, legend only shows 'Complete' entry
|
|
756
|
+
5. Perfect for dashboards, completion meters, utilization rates"""
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@prompt("single_value_pie_with_remaining_color")
|
|
760
|
+
async def single_value_pie_with_remaining_color_prompt() -> str:
|
|
761
|
+
"""Single-value pie chart with colored remaining area"""
|
|
762
|
+
return """Create single-value pie chart with remaining_color parameter:
|
|
763
|
+
1. Data: ['Complete', 'Remaining'] = [60, 40], colors=['blue', 'default']
|
|
764
|
+
2. Add remaining_color='gray' to color the remaining slice
|
|
765
|
+
3. Title: 'Task Completion: 60%'
|
|
766
|
+
4. Compare with version without remaining_color
|
|
767
|
+
5. Note: When remaining_color is specified, 'Remaining' appears in legend"""
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@prompt("doughnut_chart_basic")
|
|
771
|
+
async def doughnut_chart_basic_prompt() -> str:
|
|
772
|
+
"""Basic doughnut chart with hollow center"""
|
|
773
|
+
return """Create a doughnut chart with hollow center:
|
|
774
|
+
1. Data: ['Sales', 'Marketing', 'Support', 'Development'] = [40, 25, 15, 20]
|
|
775
|
+
2. Colors: ['blue', 'orange', 'green', 'red']
|
|
776
|
+
3. Add donut=True parameter to create hollow center
|
|
777
|
+
4. Title: 'Department Budget - Doughnut Chart'
|
|
778
|
+
5. Note: Inner radius automatically set to 1/3 of outer radius, center remains empty"""
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
@prompt("doughnut_progress_indicator")
|
|
782
|
+
async def doughnut_progress_indicator_prompt() -> str:
|
|
783
|
+
"""Doughnut chart as progress indicator"""
|
|
784
|
+
return """Create a doughnut chart progress indicator:
|
|
785
|
+
1. Single-value data: ['Completed', 'Remaining'] = [85, 15]
|
|
786
|
+
2. Colors: ['cyan', 'default']
|
|
787
|
+
3. Use both donut=True and show only percentages
|
|
788
|
+
4. Title: 'Project Progress - 85% Complete'
|
|
789
|
+
5. Perfect for modern dashboards - combines hollow center with progress visualization"""
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
@prompt("quick_donut_convenience")
|
|
793
|
+
async def quick_donut_convenience_prompt() -> str:
|
|
794
|
+
"""Using quick_donut convenience function"""
|
|
795
|
+
return """Demonstrate the quick_donut convenience function:
|
|
796
|
+
1. Use quick_donut instead of quick_pie with donut=True
|
|
797
|
+
2. Data: ['Task A', 'Task B', 'Task C'] = [30, 45, 25]
|
|
798
|
+
3. Colors: ['purple', 'yellow', 'green']
|
|
799
|
+
4. Title: 'Task Distribution'
|
|
800
|
+
5. Show how quick_donut automatically creates hollow center charts"""
|
|
801
|
+
|
|
802
|
+
|
|
497
803
|
# Main server entry point
|
|
498
804
|
def start_server():
|
|
499
805
|
"""Start the MCP server."""
|
plotext_plus/plotting.py
CHANGED
|
@@ -12,7 +12,7 @@ All the core plotting capabilities are exposed through clean, public interfaces.
|
|
|
12
12
|
# Import all main plotting functions from the internal core module
|
|
13
13
|
from ._core import (
|
|
14
14
|
# Basic plotting functions
|
|
15
|
-
scatter, plot, bar,
|
|
15
|
+
scatter, plot, bar, pie,
|
|
16
16
|
matrix_plot, candlestick,
|
|
17
17
|
|
|
18
18
|
# Plot customization
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotext_plus
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
4
4
|
Summary: Modern terminal plotting library with enhanced visual features, themes, and AI integration
|
|
5
5
|
Project-URL: Homepage, https://github.com/ccmitchellusa/plotext_plus
|
|
6
6
|
Project-URL: Repository, https://github.com/ccmitchellusa/plotext_plus.git
|
|
@@ -42,18 +42,17 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
# 📊 Plotext+ - Modern Terminal Plotting
|
|
43
43
|
|
|
44
44
|
[](https://badge.fury.io/py/plotext_plus)
|
|
45
|
-
[](https://github.com/ccmitchellusa/plotext_plus/stargazers)
|
|
46
45
|
[](https://pepy.tech/project/plotext_plus)
|
|
47
46
|
[](https://github.com/ccmitchellusa/plotext_plus/issues)
|
|
48
47
|
[](https://github.com/ccmitchellusa/plotext_plus/pulls)
|
|
49
48
|
|
|
50
|
-

|
|
51
50
|
|
|
52
51
|
**Plotext+ plots directly in your terminal** with stunning visuals, modern APIs, and professional styling. Plotext+ is a redesigned version of the original [plotext](https://github.com/piccolomo/plotext) library by Savino Piccolomo. New features include an updated API with object oriented features, an MCP server to make the project easily usable with AI & LLM scenarios, new themes and integration with chuk-term to make sure it works in the awesome [mcp-cli](https://github.com/chrishayuk/mcp-cli) by Chris Hay.
|
|
53
52
|
|
|
54
53
|
## ✨ Key Features
|
|
55
54
|
|
|
56
|
-
🎯 **Multiple Plot Types**: [scatter](docs/basic.md#scatter-plot), [line](docs/basic.md#line-plot), [bar](docs/bar.md), [histogram](docs/bar.md#histogram-plot), [candlestick](docs/datetime.md#candlestick-plot), [heatmap](docs/special.md), [confusion matrix](docs/special.md#confusion-matrix), and more
|
|
55
|
+
🎯 **Multiple Plot Types**: [scatter](docs/basic.md#scatter-plot), [line](docs/basic.md#line-plot), [bar](docs/bar.md), [histogram](docs/bar.md#histogram-plot), [candlestick](docs/datetime.md#candlestick-plot), [heatmap](docs/special.md), [confusion matrix](docs/special.md#confusion-matrix), [pie](docs/basic.md#pie-plot), [doughnut](docs/basic.md#doughnut-charts) and more
|
|
57
56
|
|
|
58
57
|
🎨 **Rich Visuals**: [Banner mode](docs/chart_classes.md), [themes](docs/themes.md), [colored text](docs/utilities.md#colored-text), automatic terminal width detection
|
|
59
58
|
|
|
@@ -65,7 +64,7 @@ Description-Content-Type: text/markdown
|
|
|
65
64
|
|
|
66
65
|
⚡ **Zero Dependencies**: No required dependencies (optional packages for multimedia and AI integration)
|
|
67
66
|
|
|
68
|
-

|
|
69
68
|
|
|
70
69
|
## 🚀 Quick Start
|
|
71
70
|
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
plotext_plus/__init__.py,sha256=
|
|
1
|
+
plotext_plus/__init__.py,sha256=ZqQT3ybOtVRB4rY406K_RzBZ3CJ1Z3z2FZU0J_Wwc04,855
|
|
2
2
|
plotext_plus/__main__.py,sha256=UkpSWVzOAfWBm3O1jyN1QO6KILQWhaSw4DcIdohJlbI,37
|
|
3
|
-
plotext_plus/_api.py,sha256=
|
|
3
|
+
plotext_plus/_api.py,sha256=EwP0LePRoqvid0IyqhYZzCKTTknAbspa4R2WeVGhxk8,31026
|
|
4
4
|
plotext_plus/_build.py,sha256=sW7Fm2hYEkRDFapbk_B7sp7u9addBOzFH9MSULn54U8,20936
|
|
5
|
-
plotext_plus/_core.py,sha256=
|
|
5
|
+
plotext_plus/_core.py,sha256=sP3WIzXFO4ltdNX-QXm00Duc4GXe71b8gI02kb1DrkM,22025
|
|
6
6
|
plotext_plus/_date.py,sha256=kTjx-_df5M0yvUTJlg1LqGyDRLxOL2or0op6Si0CUU0,2879
|
|
7
7
|
plotext_plus/_default.py,sha256=bbPO60SFziIpPOI8jY6xtP3urri0H2vhYzwasfmybz4,3316
|
|
8
|
-
plotext_plus/_dict.py,sha256=
|
|
8
|
+
plotext_plus/_dict.py,sha256=I9Wb2YZauMaHjv-1p4oyQ4kRncvhjJGXzwHefeVirMI,21832
|
|
9
9
|
plotext_plus/_doc.py,sha256=qJ--cy1sssH_1EDDkrcOtLeAHahmdqSaYyhOrj4gk54,37183
|
|
10
10
|
plotext_plus/_doc_utils.py,sha256=vVwtDnAJ7vVUafL9zulkILXupGNK3v3Z9aeddX1F828,10397
|
|
11
|
-
plotext_plus/_figure.py,sha256=
|
|
11
|
+
plotext_plus/_figure.py,sha256=xLkCqi5nu8NHY9Lv9Zo2jcg-5BP07ggkJ35rs8peSiY,31924
|
|
12
12
|
plotext_plus/_global.py,sha256=NFEupN17WY2Xk4EgDY-On_1J3MOvwNBYubnt6Rg8QW4,15387
|
|
13
13
|
plotext_plus/_matrix.py,sha256=e4sHwghNZeclBORUgWc5BZxdfqoH0yDeh49XymPNHlY,8298
|
|
14
|
-
plotext_plus/_monitor.py,sha256=
|
|
14
|
+
plotext_plus/_monitor.py,sha256=TH-WC2qQj-GRMxtrcvHdgXi67VPsY4_xSGr9kJWJMtI,62946
|
|
15
15
|
plotext_plus/_output.py,sha256=9hBPOum2R6FA1EgIyIMgkMsL7DjtSpX3En8zXGbdx-c,4315
|
|
16
16
|
plotext_plus/_shtab.py,sha256=l1bDdxUbbg5urjNjyC5agbdiYu-OLbruRJZ_AuyirbQ,281
|
|
17
|
-
plotext_plus/_themes.py,sha256=
|
|
17
|
+
plotext_plus/_themes.py,sha256=2KQFpAxBvksV_iE8QCMs3lbjq5Tv2TsK_OQLiRg5Gmg,14177
|
|
18
18
|
plotext_plus/_utility.py,sha256=kp5FvE5kooOw0fcVfK_k4o0Fr9ClyFIXMYh4BiXOMlc,32918
|
|
19
19
|
plotext_plus/api.py,sha256=56xYn9uzkMe88iELbanRQ-12bC8Rvv2Jk0BX45Fk8zY,28424
|
|
20
|
-
plotext_plus/charts.py,sha256=
|
|
20
|
+
plotext_plus/charts.py,sha256=VfL0keEo02TPwjUZ6eNFx2q2qOTpiVoq_igL5rGE-gY,1315
|
|
21
21
|
plotext_plus/core.py,sha256=k6g2dYuHFcNevLJM0elkYupz7fC8Qqk_vLEyrXu2V3I,20668
|
|
22
22
|
plotext_plus/mcp_cli.py,sha256=otZaP1tolvAL2vx84TVNiTlFdJyDx1cMIMcbhqsSna8,2651
|
|
23
|
-
plotext_plus/mcp_server.py,sha256=
|
|
23
|
+
plotext_plus/mcp_server.py,sha256=96NOcmhi68QRnCa5Mcxrt27nM7aX9nnZYeY8xQwKeE0,28041
|
|
24
24
|
plotext_plus/plotext_cli.py,sha256=o3JmSNc7Ify4C6Wkva8x8QRh-pLeiD3VArc3E78xlVU,17677
|
|
25
|
-
plotext_plus/plotting.py,sha256=
|
|
25
|
+
plotext_plus/plotting.py,sha256=7YUISs2Aolqo-lMEX8amGfMNOSFV9zcSqlrEMwVK2RM,2063
|
|
26
26
|
plotext_plus/themes.py,sha256=xX6XqL2U-7MQmv-k1HU-UTSZRbTfv9op5_4ZRXm435g,725
|
|
27
27
|
plotext_plus/utilities.py,sha256=scdPDGf6po2rvEL5DX7y1pY41uxlCXTVVIo0w5qA8JE,1170
|
|
28
28
|
plotext_plus/utils.py,sha256=NFEupN17WY2Xk4EgDY-On_1J3MOvwNBYubnt6Rg8QW4,15387
|
|
29
|
-
plotext_plus-1.0.
|
|
30
|
-
plotext_plus-1.0.
|
|
31
|
-
plotext_plus-1.0.
|
|
32
|
-
plotext_plus-1.0.
|
|
33
|
-
plotext_plus-1.0.
|
|
29
|
+
plotext_plus-1.0.8.dist-info/METADATA,sha256=ef4BaFUFBVldc-p72EQyNiZGkwwoj39lUqijj00RVZc,11355
|
|
30
|
+
plotext_plus-1.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
plotext_plus-1.0.8.dist-info/entry_points.txt,sha256=XocAh2z8hTGtuQL1zEcGTulrgfZ2g5UMyHSV-JOSNN8,103
|
|
32
|
+
plotext_plus-1.0.8.dist-info/licenses/LICENSE,sha256=MkgUiRFwIvXwUVDEPy11uIULq7pGDHpIulD4KulsjnM,1150
|
|
33
|
+
plotext_plus-1.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|