MatplotLibAPI 3.2.13__py3-none-any.whl → 3.2.14__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.
@@ -1,117 +1,114 @@
1
+ """Common style utilities and formatters for plotting."""
1
2
 
2
-
3
- from typing import List, Optional, Dict, Callable, Union
4
3
  from dataclasses import dataclass
5
- import pandas as pd
6
- import numpy as np
4
+ from typing import Callable, Dict, List, Optional, Union, cast
7
5
 
6
+ import numpy as np
7
+ import pandas as pd
8
8
  from matplotlib.dates import num2date
9
9
  from matplotlib.ticker import FuncFormatter
10
10
 
11
- # region Utils
11
+ # Type alias for formatter functions compatible with matplotlib tick formatters
12
+ FormatterFunc = Callable[[Union[int, float, str], Optional[int]], str]
12
13
 
13
14
 
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
15
+ # region Validation
16
+
17
+
18
+ def validate_dataframe(
19
+ pd_df: pd.DataFrame, cols: List[str], sort_by: Optional[str] = None
20
+ ) -> None:
21
+ """Ensure required columns and optional sort column exist in a DataFrame.
22
+
23
+ Parameters
24
+ ----------
25
+ pd_df : pd.DataFrame
26
+ The DataFrame to validate.
27
+ cols : list[str]
28
+ Required column names.
29
+ sort_by : str, optional
30
+ Optional column used for sorting.
31
+
32
+ Raises
33
+ ------
34
+ AttributeError
35
+ If any column is missing.
36
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")
37
+ required_cols = set(cols)
38
+ if sort_by:
39
+ required_cols.add(sort_by)
40
+ missing = required_cols - set(pd_df.columns)
41
+ if missing:
42
+ raise AttributeError(f"Missing columns in DataFrame: {missing}")
43
+
44
+
45
+ # endregion
46
+
47
+ # region Format Dispatcher
43
48
 
44
49
 
45
50
  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
- }
51
+ format_funcs: Optional[Dict[str, Optional[FormatterFunc]]],
52
+ label: Optional[str] = None,
53
+ x: Optional[str] = None,
54
+ y: Optional[str] = None,
55
+ z: Optional[str] = None,
56
+ ) -> Optional[Dict[str, Optional[FormatterFunc]]]:
57
+ """Map shared formatters to specific keys if provided.
58
+
59
+ Parameters
60
+ ----------
61
+ format_funcs : dict[str, FormatterFunc], optional
62
+ Dictionary of formatting functions.
63
+ label : str, optional
64
+ Label column name.
65
+ x : str, optional
66
+ X-axis column name.
67
+ y : str, optional
68
+ Y-axis column name.
69
+ z : str, optional
70
+ Z-axis column name.
71
+
72
+ Returns
73
+ -------
74
+ dict[str, FormatterFunc], optional
75
+ Updated format function dictionary.
85
76
  """
77
+ if not format_funcs:
78
+ return None
86
79
 
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
80
+ new_format_funcs = format_funcs.copy()
96
81
 
97
- # endregion
82
+ for generic, specific in {"label": label, "x": x, "y": y, "z": z}.items():
83
+ if specific and generic in new_format_funcs:
84
+ new_format_funcs[specific] = new_format_funcs[generic]
85
+ return new_format_funcs
98
86
 
99
- # region Style
100
87
 
88
+ # endregion
101
89
 
90
+ # region Style Constants
102
91
 
103
92
  FIG_SIZE = (19.2, 10.8)
104
- BACKGROUND_COLOR = 'black'
105
- TEXT_COLOR = 'white'
93
+ BACKGROUND_COLOR = "black"
94
+ TEXT_COLOR = "white"
106
95
  PALETTE = "Greys_r"
107
96
  FONT_SIZE = 14
97
+ MAX_RESULTS = 50
98
+
99
+
100
+ # endregion
101
+
102
+ # region Style Template
108
103
 
109
104
 
110
105
  @dataclass
111
106
  class StyleTemplate:
107
+ """Configuration container for plot styling options."""
108
+
112
109
  background_color: str = BACKGROUND_COLOR
113
110
  fig_border: str = BACKGROUND_COLOR
114
- font_name: str = 'Arial'
111
+ font_name: str = "Arial"
115
112
  font_size: int = FONT_SIZE
116
113
  font_color: str = TEXT_COLOR
117
114
  palette: str = PALETTE
@@ -120,249 +117,193 @@ class StyleTemplate:
120
117
  x_ticks: int = 5
121
118
  yscale: Optional[str] = None
122
119
  y_ticks: int = 5
123
- format_funcs: Optional[Dict[str, Optional[Callable[[
124
- Union[int, float, str]], str]]]] = None
120
+ format_funcs: Optional[Dict[str, Optional[FormatterFunc]]] = None
125
121
  col_widths: Optional[List[float]] = None
126
- """
127
- A class to define style templates for data visualization with customizable attributes.
128
-
129
- Attributes:
130
- background_color (str):
131
- The background color for the visualizations. Default is BACKGROUND_COLOR.
132
- fig_border (str):
133
- The border color for the figures. Default is BACKGROUND_COLOR.
134
- font_name (str):
135
- The name of the font to use. Default is 'Arial'.
136
- font_size (int):
137
- The base size of the font. Default is FONT_SIZE.
138
- font_color (str):
139
- The color of the font. Default is TEXT_COLOR.
140
- palette (str):
141
- The color palette to use. Default is PALETTE.
142
- legend (bool):
143
- A flag to determine if the legend should be displayed. Default is True.
144
- xscale (Optional[str]):
145
- The scale type for the x-axis. Default is None.
146
- x_ticks (int):
147
- The number of ticks on the x-axis. Default is 10.
148
- yscale (Optional[str]):
149
- The scale type for the y-axis. Default is None.
150
- y_ticks (int):
151
- The number of ticks on the y-axis. Default is 5.
152
- format_funcs (Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]]):
153
- A dictionary mapping data keys to formatting functions. Default is None.
154
- col_widths (Optional[List[float]]):
155
- A list of column widths. Default is None.
156
-
157
- Properties:
158
- font_mapping (dict):
159
- A dictionary mapping font size levels to specific font sizes.
160
-
161
- Example:
162
- >>> template = StyleTemplate()
163
- >>> template.font_mapping
164
- {0: FONT_SIZE-3, 1: FONT_SIZE-1, 2: FONT_SIZE, 3: FONT_SIZE+1, 4: FONT_SIZE+3}
165
- """
166
- @property
167
- def font_mapping(self):
168
- return {0: self.font_size-3,
169
- 1: self.font_size-1,
170
- 2: self.font_size,
171
- 3: self.font_size+1,
172
- 4: self.font_size+3}
173
-
174
-
175
- class DynamicFuncFormatter(FuncFormatter):
176
- """
177
- A class to create a dynamic function formatter for matplotlib plots.
178
122
 
179
- Inherits from:
180
- FuncFormatter: A base class from matplotlib for formatting axis ticks.
123
+ @property
124
+ def font_mapping(self) -> Dict[int, int]:
125
+ """Map font levels to adjusted font sizes.
126
+
127
+ Returns
128
+ -------
129
+ dict[int, int]
130
+ Level to font size mapping.
131
+ """
132
+ return {
133
+ 0: self.font_size - 3,
134
+ 1: self.font_size - 1,
135
+ 2: self.font_size,
136
+ 3: self.font_size + 1,
137
+ 4: self.font_size + 3,
138
+ }
181
139
 
182
- Parameters:
183
- func_name (Callable): The function to be used for formatting.
184
140
 
185
- Example:
186
- >>> formatter = DynamicFuncFormatter(percent_formatter)
187
- """
188
- def __init__(self, func_name):
189
- super().__init__(func_name)
141
+ # endregion
190
142
 
143
+ # region Custom Formatters
191
144
 
192
- def percent_formatter(val, pos: Optional[int] = None):
193
- """
194
- Format a value as a percentage.
195
145
 
196
- Parameters:
197
- val (float): The value to format.
198
- pos (Optional[int]): The position (not used).
199
-
200
- Returns:
201
- str: The formatted percentage string.
146
+ class DynamicFuncFormatter(FuncFormatter):
147
+ """A wrapper for dynamic formatting functions."""
202
148
 
203
- Example:
204
- >>> percent_formatter(0.005)
205
- '1%'
206
- """
207
- if val*100 <= 0.1: # For 0.1%
208
- return f"{val*100:.2f}%"
209
- elif val*100 <= 1: # For 1%
210
- return f"{val*100:.1f}%"
211
- else:
212
- return f"{val*100:.0f}%"
149
+ def __init__(self, func_name: FormatterFunc):
150
+ """Initialize the formatter.
213
151
 
152
+ Parameters
153
+ ----------
154
+ func_name : FormatterFunc
155
+ A formatting function.
156
+ """
157
+ super().__init__(func_name)
214
158
 
215
- def bmk_formatter(val, pos: Optional[int] = None):
216
- """
217
- Format a value as billions, millions, or thousands.
218
159
 
219
- Parameters:
220
- val (float): The value to format.
221
- pos (Optional[int]): The position (not used).
160
+ def percent_formatter(val: Union[int, float, str], pos: Optional[int] = None) -> str:
161
+ """Format a value as a percentage."""
162
+ if isinstance(val, str):
163
+ val = float(val)
164
+ val *= 100
165
+ if val <= 0.1:
166
+ return f"{val:.2f}%"
167
+ elif val <= 1:
168
+ return f"{val:.1f}%"
169
+ return f"{val:.0f}%"
222
170
 
223
- Returns:
224
- str: The formatted string with B, M, or K suffix.
225
171
 
226
- Example:
227
- >>> bmk_formatter(1500000)
228
- '1.5M'
229
- """
230
- if val >= 1_000_000_000: # Billions
172
+ def bmk_formatter(val: float, pos: Optional[int] = None) -> str:
173
+ """Format large numbers using B, M, or K suffixes."""
174
+ if val >= 1_000_000_000:
231
175
  return f"{val / 1_000_000_000:.2f}B"
232
- elif val >= 1_000_000: # Millions
176
+ elif val >= 1_000_000:
233
177
  return f"{val / 1_000_000:.1f}M"
234
- elif val >= 1_000: # Thousands
178
+ elif val >= 1_000:
235
179
  return f"{val / 1_000:.1f}K"
236
- else:
237
- return f"{int(val)}"
238
-
180
+ return str(int(val))
239
181
 
240
- def integer_formatter(value, pos: Optional[int] = None):
241
- """
242
- Format a value as an integer.
243
182
 
244
- Parameters:
245
- value (float): The value to format.
246
- pos (Optional[int]): The position (not used).
183
+ def integer_formatter(val: float, pos: Optional[int] = None) -> str:
184
+ """Format a value as an integer."""
185
+ return str(int(val))
247
186
 
248
- Returns:
249
- str: The formatted integer string.
250
187
 
251
- Example:
252
- >>> integer_formatter(42.9)
253
- '42'
254
- """
255
- return f"{int(value)}"
188
+ def string_formatter(val: Union[int, float, str], pos: Optional[int] = None) -> str:
189
+ """Format a string to be title-case with spaces."""
190
+ return str(val).replace("-", " ").replace("_", " ").title()
256
191
 
257
192
 
258
- def string_formatter(value, pos: Optional[int] = None):
259
- """
260
- Format a string by replacing '-' and '_' with spaces and capitalizing words.
193
+ def yy_mm_formatter(x: float, pos: Optional[int] = None) -> str:
194
+ """Format a float date value as YYYY-MM."""
195
+ return num2date(x).strftime("%Y-%m")
261
196
 
262
- Parameters:
263
- value (str): The string to format.
264
- pos (Optional[int]): The position (not used).
265
197
 
266
- Returns:
267
- str: The formatted string.
268
-
269
- Example:
270
- >>> string_formatter("example-string_formatter")
271
- 'Example String Formatter'
272
- """
273
- return str(value).replace("-", " ").replace("_", " ").title()
198
+ def yy_mm_dd_formatter(x: float, pos: Optional[int] = None) -> str:
199
+ """Format a float date value as YYYY-MM-DD."""
200
+ return num2date(x).strftime("%Y-%m-%d")
274
201
 
275
202
 
276
- def yy_mm__formatter(x, pos: Optional[int] = None):
277
- """
278
- Format a date as 'YYYY-MM'.
279
-
280
- Parameters:
281
- x (float): The value to format.
282
- pos (Optional[int]): The position (not used).
283
-
284
- Returns:
285
- str: The formatted date string.
286
-
287
- Example:
288
- >>> yy_mm__formatter(737060)
289
- '2020-01'
290
- """
291
- return num2date(x).strftime('%Y-%m')
292
-
293
-
294
- def yy_mm_dd__formatter(x, pos: Optional[int] = None):
295
- """
296
- Format a date as 'YYYY-MM-DD'.
297
-
298
- Parameters:
299
- x (float): The value to format.
300
- pos (Optional[int]): The position (not used).
301
-
302
- Returns:
303
- str: The formatted date string.
304
-
305
- Example:
306
- >>> yy_mm_dd__formatter(737060)
307
- '2020-01-01'
308
- """
309
- return num2date(x).strftime('%Y-%m-%D')
310
-
311
-
312
- def generate_ticks(min_val, max_val, num_ticks:int=5):
313
- """
314
- Generate tick marks for a given range.
315
-
316
- Parameters:
317
- min_val (Union[float, str]): The minimum value of the range.
318
- max_val (Union[float, str]): The maximum value of the range.
319
- num_ticks (int): The number of ticks to generate. Default is 10.
320
-
321
- Returns:
322
- np.ndarray: An array of tick marks.
203
+ # endregion
323
204
 
324
- Example:
325
- >>> generate_ticks(0, 100, 5)
326
- array([ 0., 25., 50., 75., 100.])
205
+ # region Tick Generator
206
+
207
+
208
+ def generate_ticks(
209
+ min_val: Union[float, str, pd.Timestamp],
210
+ max_val: Union[float, str, pd.Timestamp],
211
+ num_ticks: int = 5,
212
+ ) -> Union[np.ndarray, pd.DatetimeIndex]:
213
+ """Generate evenly spaced ticks between min and max.
214
+
215
+ Parameters
216
+ ----------
217
+ min_val : float | str | pd.Timestamp
218
+ Minimum value of range.
219
+ max_val : float | str | pd.Timestamp
220
+ Maximum value of range.
221
+ num_ticks : int
222
+ Number of tick marks.
223
+
224
+ Returns
225
+ -------
226
+ np.ndarray | pd.DatetimeIndex
227
+ Tick values.
327
228
  """
328
- # Identify the type of the input
329
- try:
330
- min_val = float(min_val)
331
- max_val = float(max_val)
332
- is_date = False
333
- except ValueError:
334
- is_date = True
335
-
336
- # Convert string inputs to appropriate numerical or date types
337
- num_ticks = int(num_ticks)
338
-
339
- if is_date:
340
- min_val = pd.Timestamp(min_val).to_datetime64()
341
- max_val = pd.Timestamp(max_val).to_datetime64()
342
- data_range = (max_val - min_val).astype('timedelta64[D]').astype(int)
229
+ min_val_f: float = 0.0
230
+ max_val_f: float = 0.0
231
+
232
+ if isinstance(min_val, (int, float, str)) and isinstance(
233
+ max_val, (int, float, str)
234
+ ):
235
+ try:
236
+ min_val_f = float(min_val)
237
+ max_val_f = float(max_val)
238
+ is_date = False
239
+ except (ValueError, TypeError):
240
+ is_date = True
343
241
  else:
344
- data_range = max_val - min_val
345
-
346
- # Calculate a nice step size
347
- step_size = data_range / (num_ticks - 1)
242
+ is_date = True
348
243
 
349
- # If date, convert back to datetime
350
244
  if is_date:
351
- ticks = pd.date_range(
352
- start=min_val, periods=num_ticks, freq=f"{step_size}D")
353
- else:
354
- # Round the step size to a "nice" number
355
- exponent = np.floor(np.log10(step_size))
356
- fraction = step_size / 10**exponent
357
- nice_fraction = round(fraction)
245
+ min_ts = pd.Timestamp(min_val)
246
+ max_ts = pd.Timestamp(max_val)
247
+ if pd.isna(min_ts) or pd.isna(max_ts): # type: ignore
248
+ return pd.to_datetime([])
249
+ if min_ts == max_ts:
250
+ return pd.to_datetime([min_ts])
251
+ return pd.date_range(start=min_ts, end=max_ts, periods=num_ticks)
358
252
 
359
- # Create nice step size
360
- nice_step = nice_fraction * 10**exponent
253
+ data_range = max_val_f - min_val_f
254
+ raw_step = data_range / (num_ticks - 1)
255
+ exponent = np.floor(np.log10(raw_step))
256
+ nice_step = round(raw_step / 10**exponent) * 10**exponent
257
+ return np.arange(min_val_f, max_val_f + nice_step, nice_step)
361
258
 
362
- # Generate the tick marks based on the nice step size
363
- ticks = np.arange(min_val, max_val + nice_step, nice_step)
364
-
365
- return ticks
366
259
 
260
+ # endregion
367
261
 
262
+ # region Style Presets
263
+
264
+ BUBBLE_STYLE_TEMPLATE = StyleTemplate(
265
+ format_funcs=cast(
266
+ Dict[str, Optional[FormatterFunc]],
267
+ {
268
+ "label": string_formatter,
269
+ "x": bmk_formatter,
270
+ "y": percent_formatter,
271
+ "z": bmk_formatter,
272
+ },
273
+ ),
274
+ yscale="log",
275
+ )
276
+
277
+ TIMESERIE_STYLE_TEMPLATE = StyleTemplate(
278
+ format_funcs=cast(
279
+ Dict[str, Optional[FormatterFunc]],
280
+ {"x": yy_mm_formatter, "y": bmk_formatter},
281
+ )
282
+ )
283
+
284
+ TABLE_STYLE_TEMPLATE = StyleTemplate()
285
+
286
+ TREEMAP_STYLE_TEMPLATE = StyleTemplate()
287
+
288
+ PIVOTBARS_STYLE_TEMPLATE = StyleTemplate(
289
+ background_color="black",
290
+ fig_border="darkgrey",
291
+ font_color="white",
292
+ palette="magma",
293
+ format_funcs=cast(
294
+ Dict[str, Optional[FormatterFunc]],
295
+ {"y": percent_formatter, "label": string_formatter},
296
+ ),
297
+ )
298
+ PIVOTLINES_STYLE_TEMPLATE = StyleTemplate(
299
+ background_color="white",
300
+ fig_border="lightgrey",
301
+ palette="viridis",
302
+ format_funcs=cast(
303
+ Dict[str, Optional[FormatterFunc]],
304
+ {"y": percent_formatter, "label": string_formatter},
305
+ ),
306
+ )
307
+
308
+ NETWORK_STYLE_TEMPLATE = StyleTemplate()
368
309
  # endregion