MatplotLibAPI 3.1.2__tar.gz → 3.2.1__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.
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Bubble.py +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Composite.py +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Network.py +1 -1
- matplotlibapi-3.2.1/MatplotLibAPI/StyleTemplate.py +169 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Table.py +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Timeserie.py +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Treemap.py +1 -1
- matplotlibapi-3.2.1/MatplotLibAPI/__init__.py +181 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1/MatplotLibAPI.egg-info}/PKG-INFO +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI.egg-info/SOURCES.txt +1 -0
- {matplotlibapi-3.1.2/MatplotLibAPI.egg-info → matplotlibapi-3.2.1}/PKG-INFO +1 -1
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/pyproject.toml +1 -1
- matplotlibapi-3.1.2/MatplotLibAPI/__init__.py +0 -310
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/LICENSE +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI/Pivot.py +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI.egg-info/dependency_links.txt +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI.egg-info/requires.txt +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/MatplotLibAPI.egg-info/top_level.txt +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/README.md +0 -0
- {matplotlibapi-3.1.2 → matplotlibapi-3.2.1}/setup.cfg +0 -0
|
@@ -6,7 +6,7 @@ import matplotlib.pyplot as plt
|
|
|
6
6
|
from matplotlib.axes import Axes
|
|
7
7
|
import seaborn as sns
|
|
8
8
|
|
|
9
|
-
from . import DynamicFuncFormatter, StyleTemplate, generate_ticks, string_formatter, bmk_formatter, percent_formatter, format_func,validate_dataframe
|
|
9
|
+
from .StyleTemplate import DynamicFuncFormatter, StyleTemplate, generate_ticks, string_formatter, bmk_formatter, percent_formatter, format_func,validate_dataframe
|
|
10
10
|
|
|
11
11
|
BUBBLE_STYLE_TEMPLATE = StyleTemplate(
|
|
12
12
|
format_funcs={"label": string_formatter,
|
|
@@ -8,7 +8,7 @@ from matplotlib.figure import Figure
|
|
|
8
8
|
from .Network import plot_network, plot_network_components, DEFAULT
|
|
9
9
|
from .Bubble import plot_bubble, BUBBLE_STYLE_TEMPLATE
|
|
10
10
|
from .Table import plot_table
|
|
11
|
-
from . import StyleTemplate, format_func, validate_dataframe
|
|
11
|
+
from .StyleTemplate import StyleTemplate, format_func, validate_dataframe
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def plot_composite_bubble(
|
|
@@ -13,7 +13,7 @@ from networkx import Graph
|
|
|
13
13
|
from networkx.classes.graph import Graph
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
from . import StyleTemplate, string_formatter, format_func,validate_dataframe
|
|
16
|
+
from .StyleTemplate import StyleTemplate, string_formatter, format_func,validate_dataframe
|
|
17
17
|
|
|
18
18
|
NETWORK_STYLE_TEMPLATE = StyleTemplate(
|
|
19
19
|
)
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
|
@@ -3,7 +3,7 @@ import pandas as pd
|
|
|
3
3
|
import matplotlib.pyplot as plt
|
|
4
4
|
from matplotlib.axes import Axes
|
|
5
5
|
|
|
6
|
-
from . import StyleTemplate, string_formatter,validate_dataframe
|
|
6
|
+
from .StyleTemplate import StyleTemplate, string_formatter,validate_dataframe
|
|
7
7
|
|
|
8
8
|
TABLE_STYLE_TEMPLATE = StyleTemplate(
|
|
9
9
|
background_color='black',
|
|
@@ -6,7 +6,7 @@ import matplotlib.pyplot as plt
|
|
|
6
6
|
from matplotlib.axes import Axes
|
|
7
7
|
import seaborn as sns
|
|
8
8
|
|
|
9
|
-
from . import DynamicFuncFormatter, StyleTemplate, string_formatter, bmk_formatter, format_func,validate_dataframe
|
|
9
|
+
from .StyleTemplate import DynamicFuncFormatter, StyleTemplate, string_formatter, bmk_formatter, format_func,validate_dataframe
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
TIMESERIE_STYLE_TEMPLATE = StyleTemplate(
|
|
@@ -5,7 +5,7 @@ import pandas as pd
|
|
|
5
5
|
from pandas import CategoricalDtype,BooleanDtype
|
|
6
6
|
import plotly.graph_objects as go
|
|
7
7
|
|
|
8
|
-
from . import StyleTemplate, string_formatter, percent_formatter,validate_dataframe
|
|
8
|
+
from .StyleTemplate import StyleTemplate, string_formatter, percent_formatter,validate_dataframe
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
|
|
2
|
+
from .StyleTemplate import StyleTemplate
|
|
3
|
+
from .Bubble import plot_bubble, BUBBLE_STYLE_TEMPLATE
|
|
4
|
+
from .Composite import plot_composite_bubble
|
|
5
|
+
from .Timeserie import plot_timeserie, TIMESERIE_STYLE_TEMPLATE
|
|
6
|
+
from .Table import plot_table, TABLE_STYLE_TEMPLATE
|
|
7
|
+
from .Network import plot_network, plot_network_components, NETWORK_STYLE_TEMPLATE
|
|
8
|
+
from .Treemap import plot_treemap, TREEMAP_STYLE_TEMPLATE
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from pandas.api.extensions import register_dataframe_accessor
|
|
12
|
+
|
|
13
|
+
from matplotlib.axes import Axes
|
|
14
|
+
from matplotlib.figure import Figure
|
|
15
|
+
import plotly.graph_objects as go
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@register_dataframe_accessor("mpl")
|
|
19
|
+
class DataFrameAccessor:
|
|
20
|
+
|
|
21
|
+
def __init__(self, pd_df: pd.DataFrame):
|
|
22
|
+
self._obj = pd_df
|
|
23
|
+
|
|
24
|
+
def plot_bubble(self,
|
|
25
|
+
label: str,
|
|
26
|
+
x: str,
|
|
27
|
+
y: str,
|
|
28
|
+
z: str,
|
|
29
|
+
title: Optional[str] = None,
|
|
30
|
+
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
31
|
+
max_values: int = 50,
|
|
32
|
+
center_to_mean: bool = False,
|
|
33
|
+
sort_by: Optional[str] = None,
|
|
34
|
+
ascending: bool = False,
|
|
35
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
36
|
+
|
|
37
|
+
return plot_bubble(pd_df=self._obj,
|
|
38
|
+
label=label,
|
|
39
|
+
x=x,
|
|
40
|
+
y=y,
|
|
41
|
+
z=z,
|
|
42
|
+
title=title,
|
|
43
|
+
style=style,
|
|
44
|
+
max_values=max_values,
|
|
45
|
+
center_to_mean=center_to_mean,
|
|
46
|
+
sort_by=sort_by,
|
|
47
|
+
ascending=ascending,
|
|
48
|
+
ax=ax)
|
|
49
|
+
|
|
50
|
+
def plot_composite_bubble(self,
|
|
51
|
+
label: str,
|
|
52
|
+
x: str,
|
|
53
|
+
y: str,
|
|
54
|
+
z: str,
|
|
55
|
+
title: Optional[str] = None,
|
|
56
|
+
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
57
|
+
max_values: int = 100,
|
|
58
|
+
center_to_mean: bool = False,
|
|
59
|
+
sort_by: Optional[str] = None,
|
|
60
|
+
ascending: bool = False,
|
|
61
|
+
ax: Optional[Axes] = None) -> Figure:
|
|
62
|
+
|
|
63
|
+
return plot_composite_bubble(pd_df=self._obj,
|
|
64
|
+
label=label,
|
|
65
|
+
x=x,
|
|
66
|
+
y=y,
|
|
67
|
+
z=z,
|
|
68
|
+
title=title,
|
|
69
|
+
style=style,
|
|
70
|
+
max_values=max_values,
|
|
71
|
+
center_to_mean=center_to_mean,
|
|
72
|
+
sort_by=sort_by,
|
|
73
|
+
ascending=ascending,
|
|
74
|
+
ax=ax)
|
|
75
|
+
|
|
76
|
+
def plot_table(self,
|
|
77
|
+
cols: List[str],
|
|
78
|
+
title: Optional[str] = None,
|
|
79
|
+
style: StyleTemplate = TABLE_STYLE_TEMPLATE,
|
|
80
|
+
max_values: int = 20,
|
|
81
|
+
sort_by: Optional[str] = None,
|
|
82
|
+
ascending: bool = False,
|
|
83
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
84
|
+
|
|
85
|
+
return plot_table(pd_df=self._obj,
|
|
86
|
+
cols=cols,
|
|
87
|
+
title=title,
|
|
88
|
+
style=style,
|
|
89
|
+
max_values=max_values,
|
|
90
|
+
sort_by=sort_by,
|
|
91
|
+
ascending=ascending,
|
|
92
|
+
ax=ax)
|
|
93
|
+
|
|
94
|
+
def plot_timeserie(self,
|
|
95
|
+
label: str,
|
|
96
|
+
x: str,
|
|
97
|
+
y: str,
|
|
98
|
+
title: Optional[str] = None,
|
|
99
|
+
style: StyleTemplate = TIMESERIE_STYLE_TEMPLATE,
|
|
100
|
+
max_values: int = 100,
|
|
101
|
+
sort_by: Optional[str] = None,
|
|
102
|
+
ascending: bool = False,
|
|
103
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
104
|
+
|
|
105
|
+
return plot_timeserie(pd_df=self._obj,
|
|
106
|
+
label=label,
|
|
107
|
+
x=x,
|
|
108
|
+
y=y,
|
|
109
|
+
title=title,
|
|
110
|
+
style=style,
|
|
111
|
+
max_values=max_values,
|
|
112
|
+
sort_by=sort_by,
|
|
113
|
+
ascending=ascending,
|
|
114
|
+
ax=ax)
|
|
115
|
+
|
|
116
|
+
def plot_network(self,
|
|
117
|
+
source: str = "source",
|
|
118
|
+
target: str = "target",
|
|
119
|
+
weight: str = "weight",
|
|
120
|
+
title: Optional[str] = None,
|
|
121
|
+
style: StyleTemplate = NETWORK_STYLE_TEMPLATE,
|
|
122
|
+
sort_by: Optional[str] = None,
|
|
123
|
+
ascending: bool = False,
|
|
124
|
+
node_list: Optional[List] = None,
|
|
125
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
126
|
+
|
|
127
|
+
return plot_network(df=self._obj,
|
|
128
|
+
source=source,
|
|
129
|
+
target=target,
|
|
130
|
+
weight=weight,
|
|
131
|
+
title=title,
|
|
132
|
+
style=style,
|
|
133
|
+
sort_by=sort_by,
|
|
134
|
+
ascending=ascending,
|
|
135
|
+
node_list=node_list,
|
|
136
|
+
ax=ax)
|
|
137
|
+
|
|
138
|
+
def plot_network_components(self,
|
|
139
|
+
source: str = "source",
|
|
140
|
+
target: str = "target",
|
|
141
|
+
weight: str = "weight",
|
|
142
|
+
title: Optional[str] = None,
|
|
143
|
+
style: StyleTemplate = NETWORK_STYLE_TEMPLATE,
|
|
144
|
+
sort_by: Optional[str] = None,
|
|
145
|
+
ascending: bool = False,
|
|
146
|
+
node_list: Optional[List] = None,
|
|
147
|
+
ax: Optional[Axes] = None) -> Axes:
|
|
148
|
+
|
|
149
|
+
return plot_network_components(df=self._obj,
|
|
150
|
+
source=source,
|
|
151
|
+
target=target,
|
|
152
|
+
weight=weight,
|
|
153
|
+
title=title,
|
|
154
|
+
style=style,
|
|
155
|
+
sort_by=sort_by,
|
|
156
|
+
ascending=ascending,
|
|
157
|
+
node_list=node_list,
|
|
158
|
+
ax=ax)
|
|
159
|
+
|
|
160
|
+
def plot_treemap(self,
|
|
161
|
+
path: str,
|
|
162
|
+
values: str,
|
|
163
|
+
style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
|
|
164
|
+
title: Optional[str] = None,
|
|
165
|
+
color: Optional[str] = None,
|
|
166
|
+
max_values: int = 100,
|
|
167
|
+
sort_by: Optional[str] = None,
|
|
168
|
+
ascending: bool = False) -> go.Figure:
|
|
169
|
+
return plot_treemap(pd_df=self._obj,
|
|
170
|
+
path=path,
|
|
171
|
+
values=values,
|
|
172
|
+
title=title,
|
|
173
|
+
style=style,
|
|
174
|
+
color=color,
|
|
175
|
+
max_values=max_values,
|
|
176
|
+
sort_by=sort_by,
|
|
177
|
+
ascending=ascending)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
__all__ = ["validate_dataframe", "plot_bubble", "plot_timeserie", "plot_table", "plot_network", "plot_network_components",
|
|
181
|
+
"plot_pivotbar", "plot_treemap", "plot_composite_bubble", "StyleTemplate", "DataFrameAccessor"]
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
|
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
[project]
|
|
5
5
|
name = "MatplotLibAPI"
|
|
6
|
-
version="v3.1
|
|
6
|
+
version="v3.2.1"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python=">=3.7"
|
|
9
9
|
dependencies = ["pandas","matplotlib","networkx","plotly","seaborn","scikit-learn","kaleido","nbformat"]
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from .Treemap import plot_treemap, TREEMAP_STYLE_TEMPLATE
|
|
3
|
-
from .Network import Graph
|
|
4
|
-
from .Table import plot_table, TABLE_STYLE_TEMPLATE
|
|
5
|
-
from .Timeserie import plot_timeserie, TIMESERIE_STYLE_TEMPLATE
|
|
6
|
-
from .Composite import plot_composite_bubble
|
|
7
|
-
from .Bubble import plot_bubble, BUBBLE_STYLE_TEMPLATE
|
|
8
|
-
from typing import List, Optional, Dict, Callable, Union
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
import pandas as pd
|
|
11
|
-
from pandas.api.extensions import register_dataframe_accessor
|
|
12
|
-
import numpy as np
|
|
13
|
-
|
|
14
|
-
from matplotlib.axes import Axes
|
|
15
|
-
from matplotlib.figure import Figure
|
|
16
|
-
from matplotlib.dates import num2date
|
|
17
|
-
from matplotlib.ticker import FuncFormatter
|
|
18
|
-
import plotly.graph_objects as go
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# region Utils
|
|
22
|
-
|
|
23
|
-
def validate_dataframe(pd_df: pd.DataFrame,
|
|
24
|
-
cols: List[str],
|
|
25
|
-
sort_by: Optional[str] = None):
|
|
26
|
-
_columns = cols.copy()
|
|
27
|
-
if sort_by and sort_by not in _columns:
|
|
28
|
-
_columns.append(sort_by)
|
|
29
|
-
for col in _columns:
|
|
30
|
-
if col not in pd_df.columns:
|
|
31
|
-
raise AttributeError(f"{col} is not a DataFrame's column")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def format_func(
|
|
35
|
-
format_funcs: Optional[Dict[str, Optional[Callable[[Union[int, float, str]], str]]]],
|
|
36
|
-
label: Optional[str] = None,
|
|
37
|
-
x: Optional[str] = None,
|
|
38
|
-
y: Optional[str] = None,
|
|
39
|
-
z: Optional[str] = None):
|
|
40
|
-
|
|
41
|
-
if label and "label" in format_funcs:
|
|
42
|
-
format_funcs[label] = format_funcs["label"]
|
|
43
|
-
if x and "x" in format_funcs:
|
|
44
|
-
format_funcs[x] = format_funcs["x"]
|
|
45
|
-
if y and "y" in format_funcs:
|
|
46
|
-
format_funcs[y] = format_funcs["y"]
|
|
47
|
-
if z and "z" in format_funcs:
|
|
48
|
-
format_funcs[z] = format_funcs["z"]
|
|
49
|
-
return format_funcs
|
|
50
|
-
|
|
51
|
-
# endregion
|
|
52
|
-
|
|
53
|
-
# region Style
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
MAX_RESULTS = 50
|
|
57
|
-
X_COL = "index"
|
|
58
|
-
Y_COL = "overlap"
|
|
59
|
-
Z_COL = "users"
|
|
60
|
-
FIG_SIZE = (19.2, 10.8)
|
|
61
|
-
BACKGROUND_COLOR = 'black'
|
|
62
|
-
TEXT_COLOR = 'white'
|
|
63
|
-
PALETTE = "Greys_r"
|
|
64
|
-
FONT_SIZE = 14
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@dataclass
|
|
68
|
-
class StyleTemplate:
|
|
69
|
-
background_color: str = BACKGROUND_COLOR
|
|
70
|
-
fig_border: str = BACKGROUND_COLOR
|
|
71
|
-
font_name: str = 'Arial'
|
|
72
|
-
font_size: int = FONT_SIZE
|
|
73
|
-
font_color: str = TEXT_COLOR
|
|
74
|
-
palette: str = PALETTE
|
|
75
|
-
legend: bool = True
|
|
76
|
-
xscale: Optional[str] = None
|
|
77
|
-
x_ticks: int = 10
|
|
78
|
-
yscale: Optional[str] = None
|
|
79
|
-
y_ticks: int = 5
|
|
80
|
-
format_funcs: Optional[Dict[str, Optional[Callable[[
|
|
81
|
-
Union[int, float, str]], str]]]] = None
|
|
82
|
-
col_widths: Optional[List[float]] = None
|
|
83
|
-
|
|
84
|
-
@property
|
|
85
|
-
def font_mapping(self):
|
|
86
|
-
return {0: self.font_size-3,
|
|
87
|
-
1: self.font_size-1,
|
|
88
|
-
2: self.font_size,
|
|
89
|
-
3: self.font_size+1,
|
|
90
|
-
4: self.font_size+3}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class DynamicFuncFormatter(FuncFormatter):
|
|
94
|
-
def __init__(self, func_name):
|
|
95
|
-
super().__init__(func_name)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def percent_formatter(val, pos: Optional[int] = None):
|
|
99
|
-
if val*100 <= 0.1: # For 0.1%
|
|
100
|
-
return f"{val*100:.2f}%"
|
|
101
|
-
elif val*100 <= 1: # For 1%
|
|
102
|
-
return f"{val*100:.1f}%"
|
|
103
|
-
else:
|
|
104
|
-
return f"{val*100:.0f}%"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def bmk_formatter(val, pos: Optional[int] = None):
|
|
108
|
-
if val >= 1_000_000_000: # Billions
|
|
109
|
-
return f"{val / 1_000_000_000:.2f}B"
|
|
110
|
-
elif val >= 1_000_000: # Millions
|
|
111
|
-
return f"{val / 1_000_000:.1f}M"
|
|
112
|
-
elif val >= 1_000: # Thousands
|
|
113
|
-
return f"{val / 1_000:.1f}K"
|
|
114
|
-
else:
|
|
115
|
-
return f"{int(val)}"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def integer_formatter(value, pos: Optional[int] = None):
|
|
119
|
-
return f"{int(value)}"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def string_formatter(value, pos: Optional[int] = None):
|
|
123
|
-
return str(value).replace("-", " ").replace("_", " ").title()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def yy_mm__formatter(x, pos: Optional[int] = None):
|
|
127
|
-
return num2date(x).strftime('%Y-%m')
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def yy_mm_dd__formatter(x, pos: Optional[int] = None):
|
|
131
|
-
return num2date(x).strftime('%Y-%m-%D')
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def percent_formatter(x, pos: Optional[int] = None):
|
|
135
|
-
return f"{x * 100:.0f}%"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def generate_ticks(min_val, max_val, num_ticks="10"):
|
|
139
|
-
# Identify the type of the input
|
|
140
|
-
try:
|
|
141
|
-
min_val = float(min_val)
|
|
142
|
-
max_val = float(max_val)
|
|
143
|
-
is_date = False
|
|
144
|
-
except ValueError:
|
|
145
|
-
is_date = True
|
|
146
|
-
|
|
147
|
-
# Convert string inputs to appropriate numerical or date types
|
|
148
|
-
num_ticks = int(num_ticks)
|
|
149
|
-
|
|
150
|
-
if is_date:
|
|
151
|
-
min_val = pd.Timestamp(min_val).to_datetime64()
|
|
152
|
-
max_val = pd.Timestamp(max_val).to_datetime64()
|
|
153
|
-
data_range = (max_val - min_val).astype('timedelta64[D]').astype(int)
|
|
154
|
-
else:
|
|
155
|
-
data_range = max_val - min_val
|
|
156
|
-
|
|
157
|
-
# Calculate a nice step size
|
|
158
|
-
step_size = data_range / (num_ticks - 1)
|
|
159
|
-
|
|
160
|
-
# If date, convert back to datetime
|
|
161
|
-
if is_date:
|
|
162
|
-
ticks = pd.date_range(
|
|
163
|
-
start=min_val, periods=num_ticks, freq=f"{step_size}D")
|
|
164
|
-
else:
|
|
165
|
-
# Round the step size to a "nice" number
|
|
166
|
-
exponent = np.floor(np.log10(step_size))
|
|
167
|
-
fraction = step_size / 10**exponent
|
|
168
|
-
nice_fraction = round(fraction)
|
|
169
|
-
|
|
170
|
-
# Create nice step size
|
|
171
|
-
nice_step = nice_fraction * 10**exponent
|
|
172
|
-
|
|
173
|
-
# Generate the tick marks based on the nice step size
|
|
174
|
-
ticks = np.arange(min_val, max_val + nice_step, nice_step)
|
|
175
|
-
|
|
176
|
-
return ticks
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
# endregion
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
@register_dataframe_accessor("mpl")
|
|
183
|
-
class DataFrameAccessor:
|
|
184
|
-
|
|
185
|
-
def __init__(self, pd_df: pd.DataFrame):
|
|
186
|
-
self._obj = pd_df
|
|
187
|
-
|
|
188
|
-
def plot_bubble(self,
|
|
189
|
-
label: str,
|
|
190
|
-
x: str,
|
|
191
|
-
y: str,
|
|
192
|
-
z: str,
|
|
193
|
-
title: Optional[str] = None,
|
|
194
|
-
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
195
|
-
max_values: int = 50,
|
|
196
|
-
center_to_mean: bool = False,
|
|
197
|
-
sort_by: Optional[str] = None,
|
|
198
|
-
ascending: bool = False) -> Axes:
|
|
199
|
-
|
|
200
|
-
return plot_bubble(pd_df=self._obj,
|
|
201
|
-
label=label,
|
|
202
|
-
x=x,
|
|
203
|
-
y=y,
|
|
204
|
-
z=z,
|
|
205
|
-
title=title,
|
|
206
|
-
style=style,
|
|
207
|
-
max_values=max_values,
|
|
208
|
-
center_to_mean=center_to_mean,
|
|
209
|
-
sort_by=sort_by,
|
|
210
|
-
ascending=ascending)
|
|
211
|
-
|
|
212
|
-
def plot_composite_bubble(self,
|
|
213
|
-
label: str,
|
|
214
|
-
x: str,
|
|
215
|
-
y: str,
|
|
216
|
-
z: str,
|
|
217
|
-
title: Optional[str] = None,
|
|
218
|
-
style: StyleTemplate = BUBBLE_STYLE_TEMPLATE,
|
|
219
|
-
max_values: int = 100,
|
|
220
|
-
center_to_mean: bool = False,
|
|
221
|
-
sort_by: Optional[str] = None,
|
|
222
|
-
ascending: bool = False) -> Figure:
|
|
223
|
-
|
|
224
|
-
return plot_composite_bubble(pd_df=self._obj,
|
|
225
|
-
label=label,
|
|
226
|
-
x=x,
|
|
227
|
-
y=y,
|
|
228
|
-
z=z,
|
|
229
|
-
title=title,
|
|
230
|
-
style=style,
|
|
231
|
-
max_values=max_values,
|
|
232
|
-
center_to_mean=center_to_mean,
|
|
233
|
-
sort_by=sort_by,
|
|
234
|
-
ascending=ascending)
|
|
235
|
-
|
|
236
|
-
def plot_table(self,
|
|
237
|
-
cols: List[str],
|
|
238
|
-
title: Optional[str] = None,
|
|
239
|
-
style: StyleTemplate = TABLE_STYLE_TEMPLATE,
|
|
240
|
-
max_values: int = 20,
|
|
241
|
-
sort_by: Optional[str] = None,
|
|
242
|
-
ascending: bool = False) -> Axes:
|
|
243
|
-
|
|
244
|
-
return plot_table(pd_df=self._obj,
|
|
245
|
-
cols=cols,
|
|
246
|
-
title=title,
|
|
247
|
-
style=style,
|
|
248
|
-
max_values=max_values,
|
|
249
|
-
sort_by=sort_by,
|
|
250
|
-
ascending=ascending)
|
|
251
|
-
|
|
252
|
-
def plot_timeserie(self,
|
|
253
|
-
label: str,
|
|
254
|
-
x: str,
|
|
255
|
-
y: str,
|
|
256
|
-
title: Optional[str] = None,
|
|
257
|
-
style: StyleTemplate = TIMESERIE_STYLE_TEMPLATE,
|
|
258
|
-
max_values: int = 100,
|
|
259
|
-
sort_by: Optional[str] = None,
|
|
260
|
-
ascending: bool = False) -> Axes:
|
|
261
|
-
|
|
262
|
-
return plot_timeserie(pd_df=self._obj,
|
|
263
|
-
label=label,
|
|
264
|
-
x=x,
|
|
265
|
-
y=y,
|
|
266
|
-
title=title,
|
|
267
|
-
style=style,
|
|
268
|
-
max_values=max_values,
|
|
269
|
-
sort_by=sort_by,
|
|
270
|
-
ascending=ascending)
|
|
271
|
-
|
|
272
|
-
def plot_network(self,
|
|
273
|
-
source: str = "source",
|
|
274
|
-
target: str = "target",
|
|
275
|
-
weight: str = "weight",
|
|
276
|
-
title: Optional[str] = None,
|
|
277
|
-
style: StyleTemplate = TIMESERIE_STYLE_TEMPLATE,
|
|
278
|
-
max_values: int = 20,
|
|
279
|
-
sort_by: Optional[str] = None,
|
|
280
|
-
ascending: bool = False) -> Axes:
|
|
281
|
-
|
|
282
|
-
graph = Graph.from_pandas_edgelist(df=self._obj,
|
|
283
|
-
source=source,
|
|
284
|
-
target=target,
|
|
285
|
-
weight=weight)
|
|
286
|
-
|
|
287
|
-
return graph.plotX(title, style)
|
|
288
|
-
|
|
289
|
-
def plot_treemap(self,
|
|
290
|
-
path: str,
|
|
291
|
-
values: str,
|
|
292
|
-
style: StyleTemplate = TREEMAP_STYLE_TEMPLATE,
|
|
293
|
-
title: Optional[str] = None,
|
|
294
|
-
color: Optional[str] = None,
|
|
295
|
-
max_values: int = 100,
|
|
296
|
-
sort_by: Optional[str] = None,
|
|
297
|
-
ascending: bool = False) -> go.Figure:
|
|
298
|
-
return plot_treemap(pd_df=self._obj,
|
|
299
|
-
path=path,
|
|
300
|
-
values=values,
|
|
301
|
-
title=title,
|
|
302
|
-
style=style,
|
|
303
|
-
color=color,
|
|
304
|
-
max_values=max_values,
|
|
305
|
-
sort_by=sort_by,
|
|
306
|
-
ascending=ascending)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
__all__ = ["validate_dataframe", "plot_bubble", "plot_timeserie", "plot_table", "plot_network", "plot_network_components",
|
|
310
|
-
"plot_pivotbar", "plot_treemap", "plot_composite_bubble", "StyleTemplate", "DataFrameAccessor"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|