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 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), (131, 148, 150), (147, 161, 161), no_color, sequence]
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), (88, 110, 117), no_color, sequence]
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'], rgb_colors['sol_base0'],
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['sol_base00'], no_color, sequence]
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',
@@ -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.5
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
  [![PyPi](https://badge.fury.io/py/plotext_plus.svg)](https://badge.fury.io/py/plotext_plus)
45
- [![GitHub stars](https://img.shields.io/github/stars/piccolomo/plotext_plus.svg)](https://github.com/ccmitchellusa/plotext_plus/stargazers)
46
45
  [![Downloads](https://pepy.tech/badge/plotext_plus/month)](https://pepy.tech/project/plotext_plus)
47
46
  [![GitHubIssues](https://img.shields.io/badge/issue_tracking-github-blue.svg)](https://github.com/ccmitchellusa/plotext_plus/issues)
48
47
  [![GitTutorial](https://img.shields.io/badge/PR-Welcome-%23FF8300.svg?)](https://github.com/ccmitchellusa/plotext_plus/pulls)
49
48
 
50
- ![logo](https://raw.githubusercontent.com/ccmitchellusa/plotext_plus/master/data/logo.png)
49
+ ![logo](https://raw.githubusercontent.com/ccmitchellusa/plotext_plus/refs/heads/main/data/logo.png)
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
- ![subplots](https://raw.githubusercontent.com/ccmitchellusa/plotext_plus/master/data/subplots.png)
67
+ ![subplots](https://raw.githubusercontent.com/ccmitchellusa/plotext_plus/refs/heads/main/data/subplots.png)
69
68
 
70
69
  ## 🚀 Quick Start
71
70
 
@@ -1,33 +1,33 @@
1
- plotext_plus/__init__.py,sha256=lFka1k6nnii0o746a8J6xzpy9rfHK6x1zQ24R1DHFf8,831
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=E3AR-hNkQalVdZ2yv9lWv6PJxdDWgi81vPyrNYzA-lA,28429
3
+ plotext_plus/_api.py,sha256=EwP0LePRoqvid0IyqhYZzCKTTknAbspa4R2WeVGhxk8,31026
4
4
  plotext_plus/_build.py,sha256=sW7Fm2hYEkRDFapbk_B7sp7u9addBOzFH9MSULn54U8,20936
5
- plotext_plus/_core.py,sha256=dST_wXAHsJMrILPkUzON7eJz41bwVlsz_oi2X8oWTLU,20673
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=53vWfUX5uAGbrgIX0ZYP3cgEKIwToRlWe5aFmAC6bWI,21840
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=zXLV-GAUo-VpVPrcKbo9RobEwY9iIAXxuiv94HM5zHU,31144
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=ccBuN1DcY85LVJ7ScUrstlKhqYxeEQ6yxi3DGEZ8f_Q,40749
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=pP6m98dRz41nZbaTSaa3jHENwoWX1oUZijB7OZ_AzSA,14125
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=jQcIe_9MQBFN845l-11-VIhnaYjTca6hxkQhnc_j4dY,1263
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=6DW98E38TTR8OfTp-nBumcQf9Djz1w5980ueN2_O12M,14528
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=upSC-HTcamuWV89G-GkLWH2ClzhezZKVd1FLgskJg08,2059
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.5.dist-info/METADATA,sha256=UaGRm9nS4Im4vk_j1jHPwnpD0dlIjgKen_BCZVOdmx8,11404
30
- plotext_plus-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- plotext_plus-1.0.5.dist-info/entry_points.txt,sha256=XocAh2z8hTGtuQL1zEcGTulrgfZ2g5UMyHSV-JOSNN8,103
32
- plotext_plus-1.0.5.dist-info/licenses/LICENSE,sha256=MkgUiRFwIvXwUVDEPy11uIULq7pGDHpIulD4KulsjnM,1150
33
- plotext_plus-1.0.5.dist-info/RECORD,,
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,,