MatplotLibAPI 3.2.7__tar.gz → 3.2.9__tar.gz

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.
Files changed (21) hide show
  1. matplotlibapi-3.2.9/MatplotLibAPI/Composite.py +195 -0
  2. matplotlibapi-3.2.9/MatplotLibAPI/StyleTemplate.py +371 -0
  3. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Treemap.py +38 -14
  4. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/__init__.py +48 -4
  5. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9/MatplotLibAPI.egg-info}/PKG-INFO +1 -1
  6. {matplotlibapi-3.2.7/MatplotLibAPI.egg-info → matplotlibapi-3.2.9}/PKG-INFO +1 -1
  7. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/pyproject.toml +1 -1
  8. matplotlibapi-3.2.7/MatplotLibAPI/Composite.py +0 -126
  9. matplotlibapi-3.2.7/MatplotLibAPI/StyleTemplate.py +0 -169
  10. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/LICENSE +0 -0
  11. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Bubble.py +0 -0
  12. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Network.py +0 -0
  13. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Pivot.py +0 -0
  14. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Table.py +0 -0
  15. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI/Timeserie.py +0 -0
  16. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI.egg-info/SOURCES.txt +0 -0
  17. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI.egg-info/dependency_links.txt +0 -0
  18. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI.egg-info/requires.txt +0 -0
  19. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/MatplotLibAPI.egg-info/top_level.txt +0 -0
  20. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/README.md +0 -0
  21. {matplotlibapi-3.2.7 → matplotlibapi-3.2.9}/setup.cfg +0 -0
@@ -0,0 +1,195 @@
1
+ # Hint for Visual Code Python Interactive window
2
+ # %%
3
+ from typing import Optional, Tuple, List, Dict
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ from matplotlib.figure import Figure
7
+ import plotly.graph_objects as go
8
+ from plotly.subplots import make_subplots
9
+ from .Bubble import aplot_bubble, BUBBLE_STYLE_TEMPLATE
10
+ from .Table import aplot_table
11
+ from .Treemap import aplot_treemap, TREEMAP_STYLE_TEMPLATE
12
+ from .StyleTemplate import StyleTemplate, format_func, validate_dataframe
13
+
14
+
15
+ def plot_composite_bubble(
16
+ pd_df: pd.DataFrame,
17
+ label: str,
18
+ x: str,
19
+ y: str,
20
+ z: str,
21
+ title: Optional[str] = "Test",
22
+ style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
23
+ max_values: int = 50,
24
+ center_to_mean: bool = False,
25
+ filter_by: Optional[str] = None,
26
+ sort_by: Optional[str] = None,
27
+ ascending: bool = False,
28
+ table_rows: int = 10,
29
+ figsize: Tuple[float, float] = (19.2, 10.8)) -> Figure:
30
+ """
31
+ Plot a composite bubble chart with additional tables for top and bottom values.
32
+
33
+ Parameters:
34
+ pd_df (pd.DataFrame):
35
+ The pandas DataFrame containing the data to be plotted.
36
+ label (str):
37
+ The column name for the bubble labels.
38
+ x (str):
39
+ The column name for the x-axis values.
40
+ y (str):
41
+ The column name for the y-axis values.
42
+ z (str):
43
+ The column name for the bubble sizes.
44
+ title (Optional[str]):
45
+ The title of the plot. Default is "Test".
46
+ style (StyleTemplate):
47
+ A StyleTemplate object to define the visual style of the plot. Default is BUBBLE_STYLE_TEMPLATE.
48
+ max_values (int):
49
+ The maximum number of values to display in the bubble chart. Default is 50.
50
+ center_to_mean (bool):
51
+ A flag indicating whether to center the bubbles to the mean. Default is False.
52
+ filter_by (Optional[str]):
53
+ The column name to filter the data by. Default is None.
54
+ sort_by (Optional[str]):
55
+ The column name to sort the data by. Default is None.
56
+ ascending (bool):
57
+ A flag indicating whether to sort in ascending order. Default is False.
58
+ table_rows (int):
59
+ The number of rows to display in the tables. Default is 10.
60
+ figsize (Tuple[float, float]):
61
+ The size of the figure. Default is (19.2, 10.8).
62
+
63
+ Returns:
64
+ Figure:
65
+ The matplotlib figure object containing the composite bubble chart and tables.
66
+
67
+ Example:
68
+ >>> df = pd.DataFrame({
69
+ ... "Label": ["A", "B", "C"],
70
+ ... "X": [1, 2, 3],
71
+ ... "Y": [4, 5, 6],
72
+ ... "Z": [7, 8, 9]
73
+ ... })
74
+ >>> fig = plot_composite_bubble(df, label="Label", x="X", y="Y", z="Z")
75
+ >>> fig.show()
76
+ """
77
+ validate_dataframe(pd_df, cols=[label, x, y, z], sort_by=sort_by)
78
+
79
+ if not sort_by:
80
+ sort_by = z
81
+ if not filter_by:
82
+ filter_by = z
83
+ plot_df = pd_df.sort_values(by=filter_by,
84
+ ascending=ascending)[[label, x, y, z]].head(max_values)
85
+ style.format_funcs = format_func(
86
+ style.format_funcs, label=label, x=x, y=y, z=z)
87
+ fig = plt.figure(figsize=figsize)
88
+ fig.patch.set_facecolor(style.background_color)
89
+ grid = plt.GridSpec(2, 2, height_ratios=[2, 1], width_ratios=[1, 1])
90
+ ax = fig.add_subplot(grid[0, 0:])
91
+ ax = aplot_bubble(pd_df=plot_df,
92
+ label=label,
93
+ x=x,
94
+ y=y,
95
+ z=z,
96
+ title=title,
97
+ style=style,
98
+ max_values=max_values,
99
+ center_to_mean=center_to_mean,
100
+ sort_by=sort_by,
101
+ ascending=ascending,
102
+ ax=ax)
103
+
104
+ if "label" in style.format_funcs:
105
+ style.format_funcs[label] = style.format_funcs["label"]
106
+ if "x" in style.format_funcs:
107
+ style.format_funcs[x] = style.format_funcs["x"]
108
+ if "y" in style.format_funcs:
109
+ style.format_funcs[y] = style.format_funcs["y"]
110
+ if "z" in style.format_funcs:
111
+ style.format_funcs[z] = style.format_funcs["z"]
112
+
113
+ ax2 = fig.add_subplot(grid[1, 0])
114
+ ax2 = aplot_table(
115
+ pd_df=plot_df,
116
+ cols=[label, z, y, x],
117
+ title=f"Top {table_rows}",
118
+ ax=ax2,
119
+ sort_by=sort_by,
120
+ ascending=False,
121
+ max_values=table_rows,
122
+ style=style
123
+ )
124
+ ax3 = fig.add_subplot(grid[1, 1])
125
+ ax3 = aplot_table(
126
+ pd_df=plot_df,
127
+ cols=[label, z, y, x],
128
+ title=f"Last {table_rows}",
129
+ ax=ax3,
130
+ sort_by=sort_by,
131
+ ascending=True,
132
+ max_values=table_rows,
133
+ style=style
134
+ )
135
+ fig.tight_layout()
136
+ return fig
137
+
138
+
139
+ def plot_composite_treemap(pd_dfs: Dict[str, pd.DataFrame],
140
+ values: str,
141
+ style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
142
+ title: Optional[str] = None,
143
+ color: Optional[str] = None,
144
+ sort_by: Optional[str] = None,
145
+ ascending: bool = False,
146
+ max_values: int = 100) -> Optional[go.Figure]:
147
+ """
148
+ Plot a composite treemap from multiple DataFrames.
149
+
150
+ Parameters:
151
+ pd_dfs (Dict[str, pd.DataFrame]):
152
+ A dictionary where keys are dimension names and values are pandas DataFrames to be plotted.
153
+ values (str):
154
+ The column name in each DataFrame that contains the values to be visualized in the treemap.
155
+ style (StyleTemplate):
156
+ A StyleTemplate object to define the visual style of the treemap. Default is TREEMAP_STYLE_TEMPLATE.
157
+ title (Optional[str]):
158
+ An optional title for the composite treemap plot. Default is None.
159
+ color (Optional[str]):
160
+ An optional column name for coloring the treemap. Default is None.
161
+ sort_by (Optional[str]):
162
+ An optional column name for sorting the treemap values. Default is None.
163
+ ascending (bool):
164
+ A flag indicating whether to sort in ascending order. Default is False.
165
+ max_values (int):
166
+ The maximum number of values to display in each treemap. Default is 100.
167
+
168
+ Returns:
169
+ Optional[go.Figure]:
170
+ The composite treemap figure object or None if no DataFrames are provided.
171
+ """
172
+ num_dimensions = len(pd_dfs)
173
+ if num_dimensions > 0:
174
+ subplot_titles = [f"{title}::{dim.title()}" if title is not None else dim.title(
175
+ ) for dim in pd_dfs.keys()]
176
+ fig = make_subplots(
177
+ rows=num_dimensions,
178
+ cols=1,
179
+ subplot_titles=subplot_titles,
180
+ vertical_spacing=0.2
181
+ )
182
+
183
+ current_row = 1
184
+ for path, df in pd_dfs.items():
185
+ trm = aplot_treemap(pd_df=df,
186
+ path=path,
187
+ values=values,
188
+ style=style,
189
+ color=color,
190
+ sort_by=sort_by,
191
+ ascending=ascending,
192
+ max_values=max_values)
193
+ fig.add_trace(trm, row=current_row, col=1)
194
+ current_row += 1
195
+ return fig
@@ -0,0 +1,371 @@
1
+
2
+
3
+ from typing import List, Optional, Dict, Callable, Union
4
+ from dataclasses import dataclass
5
+ import pandas as pd
6
+ import numpy as np
7
+
8
+ from matplotlib.dates import num2date
9
+ from matplotlib.ticker import FuncFormatter
10
+
11
+ # region Utils
12
+
13
+
14
+ def validate_dataframe(pd_df: pd.DataFrame,
15
+ cols: List[str],
16
+ sort_by: Optional[str] = None):
17
+ """
18
+ Validate that specified columns exist in a pandas DataFrame and optionally check for a sorting column.
19
+
20
+ Parameters:
21
+ pd_df (pd.DataFrame): The pandas DataFrame to validate.
22
+ cols (List[str]): A list of column names that must exist in the DataFrame.
23
+ sort_by (Optional[str]): An optional column name that, if provided, must also exist in the DataFrame.
24
+
25
+ Raises:
26
+ AttributeError: If any of the specified columns or the sorting column (if provided) do not exist in the DataFrame.
27
+
28
+ Example:
29
+ >>> import pandas as pd
30
+ >>> data = {'A': [1, 2, 3], 'B': [4, 5, 6]}
31
+ >>> df = pd.DataFrame(data)
32
+ >>> validate_dataframe(df, ['A', 'B']) # No error
33
+ >>> validate_dataframe(df, ['A', 'C']) # Raises AttributeError
34
+ >>> validate_dataframe(df, ['A'], sort_by='B') # No error
35
+ >>> validate_dataframe(df, ['A'], sort_by='C') # Raises AttributeError
36
+ """
37
+ _columns = cols.copy()
38
+ if sort_by and sort_by not in _columns:
39
+ _columns.append(sort_by)
40
+ for col in _columns:
41
+ if col not in pd_df.columns:
42
+ raise AttributeError(f"{col} is not a DataFrame's column")
43
+
44
+
45
+ def format_func(
46
+ format_funcs: Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]],
47
+ label: Optional[str] = None,
48
+ x: Optional[str] = None,
49
+ y: Optional[str] = None,
50
+ z: Optional[str] = None):
51
+ """
52
+ Update the formatting functions for specified keys if they exist in the provided format functions dictionary.
53
+
54
+ Parameters:
55
+ format_funcs (Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]]):
56
+ A dictionary mapping keys to formatting functions. The keys can be 'label', 'x', 'y', and 'z'.
57
+ label (Optional[str]):
58
+ The key to update with the 'label' formatting function from the dictionary.
59
+ x (Optional[str]):
60
+ The key to update with the 'x' formatting function from the dictionary.
61
+ y (Optional[str]):
62
+ The key to update with the 'y' formatting function from the dictionary.
63
+ z (Optional[str]):
64
+ The key to update with the 'z' formatting function from the dictionary.
65
+
66
+ Returns:
67
+ Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]]:
68
+ The updated dictionary with the specified keys pointing to their corresponding formatting functions.
69
+
70
+ Example:
71
+ >>> format_funcs = {
72
+ ... "label": lambda x: f"Label: {x}",
73
+ ... "x": lambda x: f"X-axis: {x}",
74
+ ... "y": lambda y: f"Y-axis: {y}",
75
+ ... }
76
+ >>> updated_funcs = format_func(format_funcs, label="new_label", x="new_x")
77
+ >>> print(updated_funcs)
78
+ {
79
+ "label": lambda x: f"Label: {x}",
80
+ "x": lambda x: f"X-axis: {x}",
81
+ "y": lambda y: f"Y-axis: {y}",
82
+ "new_label": lambda x: f"Label: {x}",
83
+ "new_x": lambda x: f"X-axis: {x}",
84
+ }
85
+ """
86
+
87
+ if label and "label" in format_funcs:
88
+ format_funcs[label] = format_funcs["label"]
89
+ if x and "x" in format_funcs:
90
+ format_funcs[x] = format_funcs["x"]
91
+ if y and "y" in format_funcs:
92
+ format_funcs[y] = format_funcs["y"]
93
+ if z and "z" in format_funcs:
94
+ format_funcs[z] = format_funcs["z"]
95
+ return format_funcs
96
+
97
+ # endregion
98
+
99
+ # region Style
100
+
101
+
102
+ MAX_RESULTS = 50
103
+ X_COL = "index"
104
+ Y_COL = "overlap"
105
+ Z_COL = "users"
106
+ FIG_SIZE = (19.2, 10.8)
107
+ BACKGROUND_COLOR = 'black'
108
+ TEXT_COLOR = 'white'
109
+ PALETTE = "Greys_r"
110
+ FONT_SIZE = 14
111
+
112
+
113
+ @dataclass
114
+ class StyleTemplate:
115
+ background_color: str = BACKGROUND_COLOR
116
+ fig_border: str = BACKGROUND_COLOR
117
+ font_name: str = 'Arial'
118
+ font_size: int = FONT_SIZE
119
+ font_color: str = TEXT_COLOR
120
+ palette: str = PALETTE
121
+ legend: bool = True
122
+ xscale: Optional[str] = None
123
+ x_ticks: int = 5
124
+ yscale: Optional[str] = None
125
+ y_ticks: int = 5
126
+ format_funcs: Optional[Dict[str, Optional[Callable[[
127
+ Union[int, float, str]], str]]]] = None
128
+ col_widths: Optional[List[float]] = None
129
+ """
130
+ A class to define style templates for data visualization with customizable attributes.
131
+
132
+ Attributes:
133
+ background_color (str):
134
+ The background color for the visualizations. Default is BACKGROUND_COLOR.
135
+ fig_border (str):
136
+ The border color for the figures. Default is BACKGROUND_COLOR.
137
+ font_name (str):
138
+ The name of the font to use. Default is 'Arial'.
139
+ font_size (int):
140
+ The base size of the font. Default is FONT_SIZE.
141
+ font_color (str):
142
+ The color of the font. Default is TEXT_COLOR.
143
+ palette (str):
144
+ The color palette to use. Default is PALETTE.
145
+ legend (bool):
146
+ A flag to determine if the legend should be displayed. Default is True.
147
+ xscale (Optional[str]):
148
+ The scale type for the x-axis. Default is None.
149
+ x_ticks (int):
150
+ The number of ticks on the x-axis. Default is 10.
151
+ yscale (Optional[str]):
152
+ The scale type for the y-axis. Default is None.
153
+ y_ticks (int):
154
+ The number of ticks on the y-axis. Default is 5.
155
+ format_funcs (Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]]):
156
+ A dictionary mapping data keys to formatting functions. Default is None.
157
+ col_widths (Optional[List[float]]):
158
+ A list of column widths. Default is None.
159
+
160
+ Properties:
161
+ font_mapping (dict):
162
+ A dictionary mapping font size levels to specific font sizes.
163
+
164
+ Example:
165
+ >>> template = StyleTemplate()
166
+ >>> template.font_mapping
167
+ {0: FONT_SIZE-3, 1: FONT_SIZE-1, 2: FONT_SIZE, 3: FONT_SIZE+1, 4: FONT_SIZE+3}
168
+ """
169
+ @property
170
+ def font_mapping(self):
171
+ return {0: self.font_size-3,
172
+ 1: self.font_size-1,
173
+ 2: self.font_size,
174
+ 3: self.font_size+1,
175
+ 4: self.font_size+3}
176
+
177
+
178
+ class DynamicFuncFormatter(FuncFormatter):
179
+ """
180
+ A class to create a dynamic function formatter for matplotlib plots.
181
+
182
+ Inherits from:
183
+ FuncFormatter: A base class from matplotlib for formatting axis ticks.
184
+
185
+ Parameters:
186
+ func_name (Callable): The function to be used for formatting.
187
+
188
+ Example:
189
+ >>> formatter = DynamicFuncFormatter(percent_formatter)
190
+ """
191
+ def __init__(self, func_name):
192
+ super().__init__(func_name)
193
+
194
+
195
+ def percent_formatter(val, pos: Optional[int] = None):
196
+ """
197
+ Format a value as a percentage.
198
+
199
+ Parameters:
200
+ val (float): The value to format.
201
+ pos (Optional[int]): The position (not used).
202
+
203
+ Returns:
204
+ str: The formatted percentage string.
205
+
206
+ Example:
207
+ >>> percent_formatter(0.005)
208
+ '1%'
209
+ """
210
+ if val*100 <= 0.1: # For 0.1%
211
+ return f"{val*100:.2f}%"
212
+ elif val*100 <= 1: # For 1%
213
+ return f"{val*100:.1f}%"
214
+ else:
215
+ return f"{val*100:.0f}%"
216
+
217
+
218
+ def bmk_formatter(val, pos: Optional[int] = None):
219
+ """
220
+ Format a value as billions, millions, or thousands.
221
+
222
+ Parameters:
223
+ val (float): The value to format.
224
+ pos (Optional[int]): The position (not used).
225
+
226
+ Returns:
227
+ str: The formatted string with B, M, or K suffix.
228
+
229
+ Example:
230
+ >>> bmk_formatter(1500000)
231
+ '1.5M'
232
+ """
233
+ if val >= 1_000_000_000: # Billions
234
+ return f"{val / 1_000_000_000:.2f}B"
235
+ elif val >= 1_000_000: # Millions
236
+ return f"{val / 1_000_000:.1f}M"
237
+ elif val >= 1_000: # Thousands
238
+ return f"{val / 1_000:.1f}K"
239
+ else:
240
+ return f"{int(val)}"
241
+
242
+
243
+ def integer_formatter(value, pos: Optional[int] = None):
244
+ """
245
+ Format a value as an integer.
246
+
247
+ Parameters:
248
+ value (float): The value to format.
249
+ pos (Optional[int]): The position (not used).
250
+
251
+ Returns:
252
+ str: The formatted integer string.
253
+
254
+ Example:
255
+ >>> integer_formatter(42.9)
256
+ '42'
257
+ """
258
+ return f"{int(value)}"
259
+
260
+
261
+ def string_formatter(value, pos: Optional[int] = None):
262
+ """
263
+ Format a string by replacing '-' and '_' with spaces and capitalizing words.
264
+
265
+ Parameters:
266
+ value (str): The string to format.
267
+ pos (Optional[int]): The position (not used).
268
+
269
+ Returns:
270
+ str: The formatted string.
271
+
272
+ Example:
273
+ >>> string_formatter("example-string_formatter")
274
+ 'Example String Formatter'
275
+ """
276
+ return str(value).replace("-", " ").replace("_", " ").title()
277
+
278
+
279
+ def yy_mm__formatter(x, pos: Optional[int] = None):
280
+ """
281
+ Format a date as 'YYYY-MM'.
282
+
283
+ Parameters:
284
+ x (float): The value to format.
285
+ pos (Optional[int]): The position (not used).
286
+
287
+ Returns:
288
+ str: The formatted date string.
289
+
290
+ Example:
291
+ >>> yy_mm__formatter(737060)
292
+ '2020-01'
293
+ """
294
+ return num2date(x).strftime('%Y-%m')
295
+
296
+
297
+ def yy_mm_dd__formatter(x, pos: Optional[int] = None):
298
+ """
299
+ Format a date as 'YYYY-MM-DD'.
300
+
301
+ Parameters:
302
+ x (float): The value to format.
303
+ pos (Optional[int]): The position (not used).
304
+
305
+ Returns:
306
+ str: The formatted date string.
307
+
308
+ Example:
309
+ >>> yy_mm_dd__formatter(737060)
310
+ '2020-01-01'
311
+ """
312
+ return num2date(x).strftime('%Y-%m-%D')
313
+
314
+
315
+ def generate_ticks(min_val, max_val, num_ticks:int=5):
316
+ """
317
+ Generate tick marks for a given range.
318
+
319
+ Parameters:
320
+ min_val (Union[float, str]): The minimum value of the range.
321
+ max_val (Union[float, str]): The maximum value of the range.
322
+ num_ticks (int): The number of ticks to generate. Default is 10.
323
+
324
+ Returns:
325
+ np.ndarray: An array of tick marks.
326
+
327
+ Example:
328
+ >>> generate_ticks(0, 100, 5)
329
+ array([ 0., 25., 50., 75., 100.])
330
+ """
331
+ # Identify the type of the input
332
+ try:
333
+ min_val = float(min_val)
334
+ max_val = float(max_val)
335
+ is_date = False
336
+ except ValueError:
337
+ is_date = True
338
+
339
+ # Convert string inputs to appropriate numerical or date types
340
+ num_ticks = int(num_ticks)
341
+
342
+ if is_date:
343
+ min_val = pd.Timestamp(min_val).to_datetime64()
344
+ max_val = pd.Timestamp(max_val).to_datetime64()
345
+ data_range = (max_val - min_val).astype('timedelta64[D]').astype(int)
346
+ else:
347
+ data_range = max_val - min_val
348
+
349
+ # Calculate a nice step size
350
+ step_size = data_range / (num_ticks - 1)
351
+
352
+ # If date, convert back to datetime
353
+ if is_date:
354
+ ticks = pd.date_range(
355
+ start=min_val, periods=num_ticks, freq=f"{step_size}D")
356
+ else:
357
+ # Round the step size to a "nice" number
358
+ exponent = np.floor(np.log10(step_size))
359
+ fraction = step_size / 10**exponent
360
+ nice_fraction = round(fraction)
361
+
362
+ # Create nice step size
363
+ nice_step = nice_fraction * 10**exponent
364
+
365
+ # Generate the tick marks based on the nice step size
366
+ ticks = np.arange(min_val, max_val + nice_step, nice_step)
367
+
368
+ return ticks
369
+
370
+
371
+ # endregion
@@ -2,11 +2,10 @@
2
2
  # %%
3
3
  from typing import Optional
4
4
  import pandas as pd
5
- from pandas import CategoricalDtype,BooleanDtype
5
+ from pandas import CategoricalDtype, BooleanDtype
6
6
  import plotly.graph_objects as go
7
7
 
8
- from .StyleTemplate import StyleTemplate, string_formatter, percent_formatter,validate_dataframe
9
-
8
+ from .StyleTemplate import StyleTemplate, string_formatter, percent_formatter, validate_dataframe
10
9
 
11
10
 
12
11
  TREEMAP_STYLE_TEMPLATE = StyleTemplate(
@@ -19,16 +18,15 @@ TREEMAP_STYLE_TEMPLATE = StyleTemplate(
19
18
  )
20
19
 
21
20
 
22
- def fplot_treemap(pd_df: pd.DataFrame,
23
- path: str,
24
- values: str,
25
- style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
26
- title: Optional[str] = None,
27
- color: Optional[str] = None,
28
- sort_by: Optional[str] = None,
29
- ascending: bool = False,
30
- max_values: int = 100,
31
- fig: Optional[go.Figure] = None) -> go.Figure:
21
+ def aplot_treemap(pd_df: pd.DataFrame,
22
+ path: str,
23
+ values: str,
24
+ style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
25
+ title: Optional[str] = None,
26
+ color: Optional[str] = None,
27
+ sort_by: Optional[str] = None,
28
+ ascending: bool = False,
29
+ max_values: int = 100) -> go.Trace:
32
30
  cols = [path, values]
33
31
  if color:
34
32
  cols.append(color)
@@ -57,7 +55,33 @@ def fplot_treemap(pd_df: pd.DataFrame,
57
55
  data['marker'] = dict(colorscale="Viridis",
58
56
  colors=color_data.to_list())
59
57
 
60
- g = go.Treemap(data)
58
+ g = go.Treemap(data,
59
+ root_color=style.background_color
60
+ )
61
+
62
+ return g
63
+
64
+
65
+ def fplot_treemap(pd_df: pd.DataFrame,
66
+ path: str,
67
+ values: str,
68
+ style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
69
+ title: Optional[str] = None,
70
+ color: Optional[str] = None,
71
+ sort_by: Optional[str] = None,
72
+ ascending: bool = False,
73
+ max_values: int = 100,
74
+ fig: Optional[go.Figure] = None) -> go.Figure:
75
+
76
+ g = aplot_treemap(pd_df=pd_df,
77
+ path=path,
78
+ values=values,
79
+ title=title,
80
+ style=style,
81
+ color=color,
82
+ sort_by=sort_by,
83
+ ascending=ascending,
84
+ max_values=max_values)
61
85
 
62
86
  if not fig:
63
87
  fig = go.Figure(g)
@@ -1,12 +1,12 @@
1
1
 
2
2
  from .StyleTemplate import StyleTemplate
3
3
  from .Bubble import aplot_bubble, fplot_bubble, BUBBLE_STYLE_TEMPLATE
4
- from .Composite import plot_composite_bubble
4
+ from .Composite import plot_composite_bubble,plot_composite_treemap
5
5
  from .Timeserie import aplot_timeserie, fplot_timeserie, TIMESERIE_STYLE_TEMPLATE
6
6
  from .Table import aplot_table, fplot_table, TABLE_STYLE_TEMPLATE
7
7
  from .Network import aplot_network, aplot_network_components, fplot_network, NETWORK_STYLE_TEMPLATE
8
- from .Treemap import fplot_treemap, TREEMAP_STYLE_TEMPLATE
9
- from typing import List, Optional, Tuple
8
+ from .Treemap import fplot_treemap, aplot_treemap, TREEMAP_STYLE_TEMPLATE
9
+ from typing import List, Optional, Tuple,Dict
10
10
  import pandas as pd
11
11
  from pandas.api.extensions import register_dataframe_accessor
12
12
 
@@ -274,6 +274,50 @@ class DataFrameAccessor:
274
274
  max_values=max_values,
275
275
  fig=fig)
276
276
 
277
+ def aplot_treemap(self,
278
+ path: str,
279
+ values: str,
280
+ style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
281
+ title: Optional[str] = None,
282
+ color: Optional[str] = None,
283
+ sort_by: Optional[str] = None,
284
+ max_values: int = 100,
285
+ ascending: bool = False,
286
+ fig: Optional[go.Figure] = None) -> go.Figure:
287
+ return aplot_treemap(pd_df=self._obj,
288
+ path=path,
289
+ values=values,
290
+ title=title,
291
+ style=style,
292
+ color=color,
293
+ sort_by=sort_by,
294
+ ascending=ascending,
295
+ max_values=max_values)
296
+
297
+ def fplot_composite_treemap(self,
298
+ pathes: List[str],
299
+ values: str,
300
+ style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
301
+ title: Optional[str] = None,
302
+ color: Optional[str] = None,
303
+ sort_by: Optional[str] = None,
304
+ max_values: int = 100,
305
+ ascending: bool = False,
306
+ fig: Optional[go.Figure] = None) -> go.Figure:
307
+ pd_dfs:Dict[str,pd.DataFrame]={}
308
+ for path in pathes:
309
+ pd_dfs[path]=self._obj
310
+
311
+
312
+ return plot_composite_treemap(pd_dfs=pd_dfs,
313
+ values=values,
314
+ title=title,
315
+ style=style,
316
+ color=color,
317
+ sort_by=sort_by,
318
+ ascending=ascending,
319
+ max_values=max_values)
320
+
277
321
 
278
322
  __all__ = ["validate_dataframe", "aplot_bubble", "aplot_timeserie", "aplot_table", "aplot_network", "aplot_network_components", "fplot_network",
279
- "plot_pivotbar", "fplot_treemap", "plot_composite_bubble", "StyleTemplate", "DataFrameAccessor"]
323
+ "plot_pivotbar", "fplot_treemap", "aplot_treemap", "plot_composite_bubble","plot_composite_treemap", "StyleTemplate", "DataFrameAccessor"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MatplotLibAPI
3
- Version: 3.2.7
3
+ Version: 3.2.9
4
4
  Requires-Python: >=3.7
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MatplotLibAPI
3
- Version: 3.2.7
3
+ Version: 3.2.9
4
4
  Requires-Python: >=3.7
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
  [project]
5
5
  name = "MatplotLibAPI"
6
- version="v3.2.7"
6
+ version="v3.2.9"
7
7
  readme = "README.md"
8
8
  requires-python=">=3.7"
9
9
  dependencies = ["pandas","matplotlib","networkx","plotly","seaborn","scikit-learn","kaleido","nbformat"]
@@ -1,126 +0,0 @@
1
- # Hint for Visual Code Python Interactive window
2
- # %%
3
- from typing import Optional, Tuple, List, Union, Dict
4
- import pandas as pd
5
- import matplotlib.pyplot as plt
6
- from matplotlib.pyplot import GridSpec
7
- from matplotlib.figure import Figure
8
- from matplotlib.axes import Axes
9
- import plotly.graph_objects as go
10
- from .Bubble import aplot_bubble, BUBBLE_STYLE_TEMPLATE
11
- from .Table import aplot_table
12
- from .Treemap import fplot_treemap, TREEMAP_STYLE_TEMPLATE
13
- from .StyleTemplate import StyleTemplate, format_func, validate_dataframe
14
-
15
-
16
- def plot_composite_bubble(
17
- pd_df: pd.DataFrame,
18
- label: str,
19
- x: str,
20
- y: str,
21
- z: str,
22
- title: Optional[str] = "Test",
23
- style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
24
- max_values: int = 50,
25
- center_to_mean: bool = False,
26
- filter_by: Optional[str] = None,
27
- sort_by: Optional[str] = None,
28
- ascending: bool = False,
29
- table_rows: int = 10,
30
- figsize: Tuple[float, float] = (19.2, 10.8)) -> Figure:
31
-
32
- validate_dataframe(pd_df, cols=[label, x, y, z], sort_by=sort_by)
33
-
34
- if not sort_by:
35
- sort_by = z
36
- if not filter_by:
37
- filter_by = z
38
- plot_df = pd_df.sort_values(by=filter_by,
39
- ascending=ascending)[[label, x, y, z]].head(max_values)
40
- style.format_funcs = format_func(
41
- style.format_funcs, label=label, x=x, y=y, z=z)
42
- fig = plt.figure(figsize=figsize)
43
- fig.patch.set_facecolor(style.background_color)
44
- grid = plt.GridSpec(2, 2, height_ratios=[2, 1], width_ratios=[1, 1])
45
- ax = fig.add_subplot(grid[0, 0:])
46
- ax = aplot_bubble(pd_df=plot_df,
47
- label=label,
48
- x=x,
49
- y=y,
50
- z=z,
51
- title=title,
52
- style=style,
53
- max_values=max_values,
54
- center_to_mean=center_to_mean,
55
- sort_by=sort_by,
56
- ascending=ascending,
57
- ax=ax)
58
-
59
- if "label" in style.format_funcs:
60
- style.format_funcs[label] = style.format_funcs["label"]
61
- if "x" in style.format_funcs:
62
- style.format_funcs[x] = style.format_funcs["x"]
63
- if "y" in style.format_funcs:
64
- style.format_funcs[y] = style.format_funcs["y"]
65
- if "z" in style.format_funcs:
66
- style.format_funcs[z] = style.format_funcs["z"]
67
-
68
- ax2 = fig.add_subplot(grid[1, 0])
69
- ax2 = aplot_table(
70
- pd_df=plot_df,
71
- cols=[label, z, y, x],
72
- title=f"Top {table_rows}",
73
- ax=ax2,
74
- sort_by=sort_by,
75
- ascending=False,
76
- max_values=table_rows,
77
- style=style
78
- )
79
- ax3 = fig.add_subplot(grid[1, 1])
80
- ax3 = aplot_table(
81
- pd_df=plot_df,
82
- cols=[label, z, y, x],
83
- title=f"Last {table_rows}",
84
- ax=ax3,
85
- sort_by=sort_by,
86
- ascending=True,
87
- max_values=table_rows,
88
- style=style
89
- )
90
- fig.tight_layout()
91
- return fig
92
-
93
-
94
- def fplot_treemaps(pd_dfs: List[pd.DataFrame],
95
- path: str,
96
- values: str,
97
- style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
98
- title: Optional[str] = None,
99
- color: Optional[str] = None,
100
- sort_by: Optional[str] = None,
101
- ascending: bool = False,
102
- max_values: int = 100) -> go.Figure:
103
-
104
- trms = []
105
- num_dimensions = len(pd_dfs)
106
- if num_dimensions > 0:
107
- fig = make_subplots(
108
- rows=num_dimensions,
109
- cols=1,
110
- specs=[[{'type': 'domain'}] for _ in range(num_dimensions)],
111
- vertical_spacing=0.02
112
- )
113
- current_row = 0
114
- for pd_df in pd_dfs:
115
- trm = fplot_treemap(pd_df=pd_df,
116
- path=path,
117
- values=values,
118
- title=title,
119
- style=style,
120
- color=color,
121
- sort_by=sort_by,
122
- ascending=ascending,
123
- max_values=max_values,
124
- fig=fig)
125
- trms.append(trm)
126
- current_row += 1
@@ -1,169 +0,0 @@
1
-
2
-
3
- from typing import List, Optional, Dict, Callable, Union
4
- from dataclasses import dataclass
5
- import pandas as pd
6
- import numpy as np
7
-
8
- from matplotlib.dates import num2date
9
- from matplotlib.ticker import FuncFormatter
10
-
11
- # region Utils
12
-
13
- def validate_dataframe(pd_df: pd.DataFrame,
14
- cols: List[str],
15
- sort_by: Optional[str] = None):
16
- _columns = cols.copy()
17
- if sort_by and sort_by not in _columns:
18
- _columns.append(sort_by)
19
- for col in _columns:
20
- if col not in pd_df.columns:
21
- raise AttributeError(f"{col} is not a DataFrame's column")
22
-
23
-
24
- def format_func(
25
- format_funcs: Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]],
26
- label: Optional[str] = None,
27
- x: Optional[str] = None,
28
- y: Optional[str] = None,
29
- z: Optional[str] = None):
30
-
31
- if label and "label" in format_funcs:
32
- format_funcs[label] = format_funcs["label"]
33
- if x and "x" in format_funcs:
34
- format_funcs[x] = format_funcs["x"]
35
- if y and "y" in format_funcs:
36
- format_funcs[y] = format_funcs["y"]
37
- if z and "z" in format_funcs:
38
- format_funcs[z] = format_funcs["z"]
39
- return format_funcs
40
-
41
- # endregion
42
-
43
- # region Style
44
-
45
-
46
- MAX_RESULTS = 50
47
- X_COL = "index"
48
- Y_COL = "overlap"
49
- Z_COL = "users"
50
- FIG_SIZE = (19.2, 10.8)
51
- BACKGROUND_COLOR = 'black'
52
- TEXT_COLOR = 'white'
53
- PALETTE = "Greys_r"
54
- FONT_SIZE = 14
55
-
56
-
57
- @dataclass
58
- class StyleTemplate:
59
- background_color: str = BACKGROUND_COLOR
60
- fig_border: str = BACKGROUND_COLOR
61
- font_name: str = 'Arial'
62
- font_size: int = FONT_SIZE
63
- font_color: str = TEXT_COLOR
64
- palette: str = PALETTE
65
- legend: bool = True
66
- xscale: Optional[str] = None
67
- x_ticks: int = 10
68
- yscale: Optional[str] = None
69
- y_ticks: int = 5
70
- format_funcs: Optional[Dict[str, Optional[Callable[[
71
- Union[int, float, str]], str]]]] = None
72
- col_widths: Optional[List[float]] = None
73
-
74
- @property
75
- def font_mapping(self):
76
- return {0: self.font_size-3,
77
- 1: self.font_size-1,
78
- 2: self.font_size,
79
- 3: self.font_size+1,
80
- 4: self.font_size+3}
81
-
82
-
83
- class DynamicFuncFormatter(FuncFormatter):
84
- def __init__(self, func_name):
85
- super().__init__(func_name)
86
-
87
-
88
- def percent_formatter(val, pos: Optional[int] = None):
89
- if val*100 <= 0.1: # For 0.1%
90
- return f"{val*100:.2f}%"
91
- elif val*100 <= 1: # For 1%
92
- return f"{val*100:.1f}%"
93
- else:
94
- return f"{val*100:.0f}%"
95
-
96
-
97
- def bmk_formatter(val, pos: Optional[int] = None):
98
- if val >= 1_000_000_000: # Billions
99
- return f"{val / 1_000_000_000:.2f}B"
100
- elif val >= 1_000_000: # Millions
101
- return f"{val / 1_000_000:.1f}M"
102
- elif val >= 1_000: # Thousands
103
- return f"{val / 1_000:.1f}K"
104
- else:
105
- return f"{int(val)}"
106
-
107
-
108
- def integer_formatter(value, pos: Optional[int] = None):
109
- return f"{int(value)}"
110
-
111
-
112
- def string_formatter(value, pos: Optional[int] = None):
113
- return str(value).replace("-", " ").replace("_", " ").title()
114
-
115
-
116
- def yy_mm__formatter(x, pos: Optional[int] = None):
117
- return num2date(x).strftime('%Y-%m')
118
-
119
-
120
- def yy_mm_dd__formatter(x, pos: Optional[int] = None):
121
- return num2date(x).strftime('%Y-%m-%D')
122
-
123
-
124
- def percent_formatter(x, pos: Optional[int] = None):
125
- return f"{x * 100:.0f}%"
126
-
127
-
128
- def generate_ticks(min_val, max_val, num_ticks="10"):
129
- # Identify the type of the input
130
- try:
131
- min_val = float(min_val)
132
- max_val = float(max_val)
133
- is_date = False
134
- except ValueError:
135
- is_date = True
136
-
137
- # Convert string inputs to appropriate numerical or date types
138
- num_ticks = int(num_ticks)
139
-
140
- if is_date:
141
- min_val = pd.Timestamp(min_val).to_datetime64()
142
- max_val = pd.Timestamp(max_val).to_datetime64()
143
- data_range = (max_val - min_val).astype('timedelta64[D]').astype(int)
144
- else:
145
- data_range = max_val - min_val
146
-
147
- # Calculate a nice step size
148
- step_size = data_range / (num_ticks - 1)
149
-
150
- # If date, convert back to datetime
151
- if is_date:
152
- ticks = pd.date_range(
153
- start=min_val, periods=num_ticks, freq=f"{step_size}D")
154
- else:
155
- # Round the step size to a "nice" number
156
- exponent = np.floor(np.log10(step_size))
157
- fraction = step_size / 10**exponent
158
- nice_fraction = round(fraction)
159
-
160
- # Create nice step size
161
- nice_step = nice_fraction * 10**exponent
162
-
163
- # Generate the tick marks based on the nice step size
164
- ticks = np.arange(min_val, max_val + nice_step, nice_step)
165
-
166
- return ticks
167
-
168
-
169
- # endregion
File without changes
File without changes
File without changes