apmtools 0.2.44__tar.gz → 0.2.45__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.
- {apmtools-0.2.44 → apmtools-0.2.45}/PKG-INFO +1 -1
- {apmtools-0.2.44 → apmtools-0.2.45}/pyproject.toml +1 -1
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/classes.py +3 -1
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/data.py +1 -2
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/matplotlib_renderer.py +5 -5
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/ui.py +126 -8
- {apmtools-0.2.44 → apmtools-0.2.45}/.gitignore +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/LICENSE +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/README.md +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/__init__.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/data_processing.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/functions.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/legacy.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/__init__.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/appearance.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/columns.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/config.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/export.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/plotly_renderer.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/shiny/summary.py +0 -0
- {apmtools-0.2.44 → apmtools-0.2.45}/src/apmtools/units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: apmtools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.45
|
|
4
4
|
Summary: A collection of tools for processing air pollution monitoring data
|
|
5
5
|
Project-URL: Homepage, https://github.com/federlorenz/apmtools
|
|
6
6
|
Author-email: Federico Lorenzetti <lorenzetti.federico@gmail.com>
|
|
@@ -861,7 +861,9 @@ class Dataset(DictionaryPlus):
|
|
|
861
861
|
z[1].to_csv(output, index=False)
|
|
862
862
|
out[z[0]] = z[1]
|
|
863
863
|
if merged:
|
|
864
|
-
|
|
864
|
+
out_merged = pd.concat([out[k] for k in out.keys()]).reset_index()
|
|
865
|
+
out_merged.description = self.description
|
|
866
|
+
return out_merged
|
|
865
867
|
return out
|
|
866
868
|
|
|
867
869
|
def save_data(self, directory="./saved/", levels=[]):
|
|
@@ -23,8 +23,7 @@ def _composite_labels(data: pd.DataFrame, columns: list[str]) -> pd.Series:
|
|
|
23
23
|
return "None" if pd.isna(value) else str(value)
|
|
24
24
|
if len(columns) == 1:
|
|
25
25
|
return data[columns[0]].map(label)
|
|
26
|
-
return pd.Series(
|
|
27
|
-
(" | ".join(label(value) for value in row)
|
|
26
|
+
return pd.Series((" | ".join(label(value) for value in row)
|
|
28
27
|
for row in data[columns].itertuples(index=False, name=None)),
|
|
29
28
|
index=data.index,
|
|
30
29
|
dtype="str",
|
|
@@ -327,7 +327,7 @@ def _matplotlib_direct_figure(spec, *, title="", x_label="", y_label="",
|
|
|
327
327
|
scatter_size="", scatter_marker="", scatter_alpha="", hist_alpha="",
|
|
328
328
|
box_showfliers=True, box_orientation="vertical",
|
|
329
329
|
title_font_size=14, axis_font_size=12, tick_font_size=10,
|
|
330
|
-
legend_font_size=12, panel_font_size=14, bins=30, density=False,
|
|
330
|
+
legend_font_size=12, panel_font_size=14, plot_dpi=100, bins=30, density=False,
|
|
331
331
|
stacked=False, histtype="bar", cumulative=0,
|
|
332
332
|
panel_by=None, independent_panel_limits=True, panel_titles="", _axes=None,
|
|
333
333
|
x_major_ticks=6, y_major_ticks=6, x_minor_ticks=False, y_minor_ticks=False):
|
|
@@ -339,7 +339,7 @@ def _matplotlib_direct_figure(spec, *, title="", x_label="", y_label="",
|
|
|
339
339
|
from matplotlib.patches import Patch
|
|
340
340
|
|
|
341
341
|
if _axes is None:
|
|
342
|
-
fig, ax = plt.subplots(figsize=(10, 5), dpi=
|
|
342
|
+
fig, ax = plt.subplots(figsize=(10, 5), dpi=plot_dpi)
|
|
343
343
|
else:
|
|
344
344
|
ax = _axes
|
|
345
345
|
fig = ax.figure
|
|
@@ -364,7 +364,7 @@ def _matplotlib_direct_figure(spec, *, title="", x_label="", y_label="",
|
|
|
364
364
|
plt.close(fig)
|
|
365
365
|
ncols = (2 if len(partitions) == 4 else min(3, len(partitions)))
|
|
366
366
|
nrows = math.ceil(len(partitions) / ncols)
|
|
367
|
-
fig, axes = plt.subplots(nrows, ncols, figsize=(5*ncols, 3.8*nrows), dpi=
|
|
367
|
+
fig, axes = plt.subplots(nrows, ncols, figsize=(5*ncols, 3.8*nrows), dpi=plot_dpi,
|
|
368
368
|
squeeze=False, sharex=False, sharey=False)
|
|
369
369
|
for idx, (panel_title, panel_df, other_groups) in enumerate(partitions):
|
|
370
370
|
panel_spec = {**spec, "data": panel_df, "groups": other_groups,
|
|
@@ -382,7 +382,7 @@ def _matplotlib_direct_figure(spec, *, title="", x_label="", y_label="",
|
|
|
382
382
|
hist_alpha=hist_alpha, box_showfliers=box_showfliers, box_orientation=box_orientation,
|
|
383
383
|
title_font_size=panel_font_size, axis_font_size=axis_font_size,
|
|
384
384
|
tick_font_size=tick_font_size, legend_font_size=legend_font_size,
|
|
385
|
-
panel_font_size=panel_font_size,
|
|
385
|
+
panel_font_size=panel_font_size, plot_dpi=plot_dpi,
|
|
386
386
|
bins=bins, density=density,
|
|
387
387
|
stacked=stacked, histtype=histtype, cumulative=cumulative, _axes=panel_ax,
|
|
388
388
|
x_major_ticks=x_major_ticks, y_major_ticks=y_major_ticks,
|
|
@@ -671,7 +671,7 @@ def _matplotlib_figure(plotly_figure):
|
|
|
671
671
|
import matplotlib.colors as mcolors
|
|
672
672
|
from matplotlib.patches import Patch
|
|
673
673
|
|
|
674
|
-
fig, ax = plt.subplots(figsize=(10, 5), dpi=
|
|
674
|
+
fig, ax = plt.subplots(figsize=(10, 5), dpi=plot_dpi)
|
|
675
675
|
source = plotly_figure
|
|
676
676
|
category_labels = list(getattr(source.layout.xaxis, "categoryarray", None) or [])
|
|
677
677
|
categorical = (getattr(source.layout.xaxis, "type", None) == "category")
|
|
@@ -41,6 +41,7 @@ def create_summary_app(
|
|
|
41
41
|
from shinywidgets import output_widget, render_plotly
|
|
42
42
|
# Keep the Summary object intact: subset(), dtfilter() and group_stat()
|
|
43
43
|
# are methods on Summary and are intentionally used below.
|
|
44
|
+
summary_description = str(getattr(data, "description", "") or "")
|
|
44
45
|
summary = data.copy(deep=False)
|
|
45
46
|
if not isinstance(summary, pd.DataFrame):
|
|
46
47
|
raise TypeError("data must be a pandas DataFrame/Summary")
|
|
@@ -91,6 +92,57 @@ def create_summary_app(
|
|
|
91
92
|
subset_choice_cache[name] = _subset_choices(summary, column_map[name])
|
|
92
93
|
return subset_choice_cache[name]
|
|
93
94
|
|
|
95
|
+
# The Info tab is the only tab at startup. Plot modules are created lazily
|
|
96
|
+
# when the user presses New plot, so no plotting inputs need to initialize
|
|
97
|
+
# until a plot actually exists.
|
|
98
|
+
info_tab = ui.nav_panel(
|
|
99
|
+
"Info",
|
|
100
|
+
ui.div(
|
|
101
|
+
ui.h2("Summary information"),
|
|
102
|
+
ui.p("Description of the Summary object used to launch this application."),
|
|
103
|
+
ui.tags.pre(summary_description, class_="summary-info-description"),
|
|
104
|
+
ui.h3("Using the plotting application"),
|
|
105
|
+
ui.p("Select + New plot to create Plot 1. Each additional plot opens "
|
|
106
|
+
"in its own tab. When a plot is selected, New plot copies its "
|
|
107
|
+
"current settings, which you can then edit independently."),
|
|
108
|
+
ui.h4("Left sidebar — data and plot settings"),
|
|
109
|
+
ui.tags.ul(
|
|
110
|
+
ui.tags.li(ui.strong("Plot settings: "), "Choose the plot type and the X and Y variables."),
|
|
111
|
+
ui.tags.li(ui.strong("Plot filters: "), "Select categorical subsets, numerical "
|
|
112
|
+
"greater-than and less-than bounds, and start/end datetime limits."),
|
|
113
|
+
ui.tags.li(ui.strong("Group by: "), "Choose one or more grouping variables "
|
|
114
|
+
"to distinguish data series."),
|
|
115
|
+
ui.tags.li(ui.strong("Frequency / statistical aggregation: "),
|
|
116
|
+
"Select a time frequency and statistical function for aggregated plots."),
|
|
117
|
+
),
|
|
118
|
+
ui.h4("Right sidebar — appearance and layout"),
|
|
119
|
+
ui.tags.ul(
|
|
120
|
+
ui.tags.li(ui.strong("Plot appearance: "), "Set the palette, plot theme, "
|
|
121
|
+
"title and title font size."),
|
|
122
|
+
ui.tags.li(ui.strong("Legend: "), "Control legend position, inside/outside "
|
|
123
|
+
"placement, labels, columns and font size."),
|
|
124
|
+
ui.tags.li(ui.strong("Axes: "), "Set axis labels, minimum and maximum "
|
|
125
|
+
"values, grid, tick controls and fonts."),
|
|
126
|
+
ui.tags.li(ui.strong("Plot components: "), "Customize trace colours, "
|
|
127
|
+
"lines, markers, histograms and boxplot options."),
|
|
128
|
+
ui.tags.li(ui.strong("Panel appearance: "), "Configure multi-panel "
|
|
129
|
+
"plots and panel titles (Matplotlib backend)."),
|
|
130
|
+
),
|
|
131
|
+
ui.h4("Plot tabs, export and application theme"),
|
|
132
|
+
ui.p("Use + New plot to add a plot tab, or Close selected plot to "
|
|
133
|
+
"remove the current plot. The Info tab remains available."),
|
|
134
|
+
ui.p("Export plotted data as CSV saves the data displayed in the plot. "
|
|
135
|
+
"Export plot saves the chart. With the Matplotlib backend, "
|
|
136
|
+
"Export as a Python file and associated data creates a "
|
|
137
|
+
"reproducible script and its data."),
|
|
138
|
+
ui.p("Use the theme selector at the top right to change the Shiny "
|
|
139
|
+
"application's appearance. This is separate from the plot theme "
|
|
140
|
+
"in the Plot appearance tab."),
|
|
141
|
+
class_="summary-info-content",
|
|
142
|
+
),
|
|
143
|
+
value="info",
|
|
144
|
+
)
|
|
145
|
+
|
|
94
146
|
@module.ui
|
|
95
147
|
def plot_tab_ui(initial_filters=None):
|
|
96
148
|
initial_filters = initial_filters or {}
|
|
@@ -148,7 +200,18 @@ def create_summary_app(
|
|
|
148
200
|
ui.input_select("palette", "Color palette", choices=["Default", "Plotly", "D3", "G10", "T10", "Set1", "Set2", "Pastel", "Dark24"], selected=initial("palette", "Default")),
|
|
149
201
|
ui.input_select("template", "Theme", choices={"plotly":"Light", "plotly_white":"White", "plotly_dark":"Dark", "simple_white":"Minimal", "ggplot2":"GGPlot"}, selected=initial("template", "plotly")),
|
|
150
202
|
ui.input_text("title", "Plot title", value=initial("title", "")),
|
|
151
|
-
ui.input_numeric("title_font_size", "Plot title font size", value=initial("title_font_size", 14), min=6, max=72)
|
|
203
|
+
ui.input_numeric("title_font_size", "Plot title font size", value=initial("title_font_size", 14), min=6, max=72),
|
|
204
|
+
(ui.div(
|
|
205
|
+
ui.help_text("Adjust all plot font sizes together (1 point per click)."),
|
|
206
|
+
ui.div(ui.input_action_button("decrease_plot_fonts", "− Decrease fonts"),
|
|
207
|
+
ui.input_action_button("increase_plot_fonts", "+ Increase fonts"),
|
|
208
|
+
class_="d-flex flex-wrap gap-2 mb-3"),
|
|
209
|
+
ui.input_numeric("plot_dpi", "Plot DPI", value=initial("plot_dpi", 100), min=30, max=600, step=25),
|
|
210
|
+
ui.div(ui.input_action_button("decrease_plot_dpi", "− DPI"),
|
|
211
|
+
ui.input_action_button("increase_plot_dpi", "+ DPI"),
|
|
212
|
+
class_="d-flex flex-wrap gap-2"),
|
|
213
|
+
ui.help_text("DPI affects Matplotlib plot rendering. Export DPI is set separately when exporting."),
|
|
214
|
+
) if backend == "matplotlib" else ui.div())),
|
|
152
215
|
ui.nav_panel("Legend",
|
|
153
216
|
ui.input_select("legend_position", "Legend position", choices={k:k.title() for k in ("right", "left", "top", "bottom")}, selected=initial("legend_position", "right")),
|
|
154
217
|
ui.input_checkbox("legend_inside", "Place legend inside plot", value=initial("legend_inside", False)),
|
|
@@ -843,6 +906,8 @@ def create_summary_app(
|
|
|
843
906
|
("tick_font_size", 10), ("legend_font_size", 12),
|
|
844
907
|
("panel_font_size", 14)):
|
|
845
908
|
ui.update_numeric(field, value=value)
|
|
909
|
+
if backend == "matplotlib":
|
|
910
|
+
ui.update_numeric("plot_dpi", value=100)
|
|
846
911
|
ui.update_numeric("line_width", value=2)
|
|
847
912
|
ui.update_checkbox("box_showfliers", value=True)
|
|
848
913
|
ui.update_select("box_orientation", selected="vertical")
|
|
@@ -857,6 +922,49 @@ def create_summary_app(
|
|
|
857
922
|
ui.update_checkbox("only_complete", value=True)
|
|
858
923
|
ui.update_checkbox("flooring_timestamp", value=True)
|
|
859
924
|
|
|
925
|
+
if backend == "matplotlib":
|
|
926
|
+
_font_fields = ("title_font_size", "axis_font_size", "tick_font_size",
|
|
927
|
+
"legend_font_size", "panel_font_size")
|
|
928
|
+
|
|
929
|
+
def _change_fonts(delta):
|
|
930
|
+
# Use current values, not the defaults, to retain individual
|
|
931
|
+
# adjustments made in the other appearance tabs.
|
|
932
|
+
with reactive.isolate():
|
|
933
|
+
for field in _font_fields:
|
|
934
|
+
current = _optional_input(field, None)
|
|
935
|
+
if current is None:
|
|
936
|
+
current = initial_filters.get(field, {
|
|
937
|
+
"title_font_size": 14, "axis_font_size": 12,
|
|
938
|
+
"tick_font_size": 10, "legend_font_size": 12,
|
|
939
|
+
"panel_font_size": 14,
|
|
940
|
+
}[field])
|
|
941
|
+
ui.update_numeric(field, value=max(6, min(72, float(current) + delta)))
|
|
942
|
+
|
|
943
|
+
@reactive.effect
|
|
944
|
+
@reactive.event(input.increase_plot_fonts)
|
|
945
|
+
def _increase_plot_fonts():
|
|
946
|
+
_change_fonts(1)
|
|
947
|
+
|
|
948
|
+
@reactive.effect
|
|
949
|
+
@reactive.event(input.decrease_plot_fonts)
|
|
950
|
+
def _decrease_plot_fonts():
|
|
951
|
+
_change_fonts(-1)
|
|
952
|
+
|
|
953
|
+
def _change_dpi(delta):
|
|
954
|
+
with reactive.isolate():
|
|
955
|
+
current = _optional_input("plot_dpi", initial_filters.get("plot_dpi", 100))
|
|
956
|
+
ui.update_numeric("plot_dpi", value=max(30, min(600, int(current or 100) + delta)))
|
|
957
|
+
|
|
958
|
+
@reactive.effect
|
|
959
|
+
@reactive.event(input.increase_plot_dpi)
|
|
960
|
+
def _increase_plot_dpi():
|
|
961
|
+
_change_dpi(25)
|
|
962
|
+
|
|
963
|
+
@reactive.effect
|
|
964
|
+
@reactive.event(input.decrease_plot_dpi)
|
|
965
|
+
def _decrease_plot_dpi():
|
|
966
|
+
_change_dpi(-25)
|
|
967
|
+
|
|
860
968
|
if backend == "plotly":
|
|
861
969
|
@reactive.calc
|
|
862
970
|
def styled_plotly_figure():
|
|
@@ -917,7 +1025,7 @@ def create_summary_app(
|
|
|
917
1025
|
x_minor_ticks=_optional_input("x_minor_ticks", False), y_minor_ticks=_optional_input("y_minor_ticks", False),
|
|
918
1026
|
title_font_size=_optional_input("title_font_size", 14), axis_font_size=_optional_input("axis_font_size", 12),
|
|
919
1027
|
tick_font_size=_optional_input("tick_font_size", 10), legend_font_size=_optional_input("legend_font_size", 12),
|
|
920
|
-
panel_font_size=_optional_input("panel_font_size", 14), hist_alpha=_optional_input("hist_alpha", ""),
|
|
1028
|
+
panel_font_size=_optional_input("panel_font_size", 14), plot_dpi=int(_optional_input("plot_dpi", 100) or 100), hist_alpha=_optional_input("hist_alpha", ""),
|
|
921
1029
|
box_showfliers=_optional_input("box_showfliers", True), box_orientation=_optional_input("box_orientation", "vertical"),
|
|
922
1030
|
line_width=(_optional_input("line_width", 2) if input.plot_type() == "line" else 2), line_alpha=_optional_input("line_alpha", ""),
|
|
923
1031
|
line_style=_optional_input("line_style", ""), scatter_size=_optional_input("scatter_size", ""),
|
|
@@ -958,7 +1066,7 @@ def create_summary_app(
|
|
|
958
1066
|
def snapshot():
|
|
959
1067
|
keys = ("plot_type", "x", "y", "subset_columns", "numeric_subset_columns", "datetime_start", "datetime_end",
|
|
960
1068
|
"group_by", "frequency", "aggregate", "only_complete", "flooring_timestamp",
|
|
961
|
-
"palette", "template", "title", "title_font_size", "legend_position",
|
|
1069
|
+
"palette", "template", "title", "title_font_size", "plot_dpi", "legend_position",
|
|
962
1070
|
"legend_inside", "legend_labels", "legend_columns", "legend_font_size",
|
|
963
1071
|
"x_axis_label", "y_axis_label", "x_min", "x_max", "y_min", "y_max",
|
|
964
1072
|
"show_grid", "x_major_ticks", "x_minor_ticks", "y_major_ticks",
|
|
@@ -1069,7 +1177,17 @@ def create_summary_app(
|
|
|
1069
1177
|
ui.div(shinyswatch.theme_picker_ui(), class_="summary-app-theme"),
|
|
1070
1178
|
class_="summary-top-toolbar",
|
|
1071
1179
|
),
|
|
1072
|
-
ui.
|
|
1180
|
+
ui.tags.style("""
|
|
1181
|
+
.summary-info-content {max-width: 980px; margin: 1.2rem auto 3rem;
|
|
1182
|
+
padding: 0 1.25rem; line-height: 1.6;}
|
|
1183
|
+
.summary-info-content h3 {margin-top: 2rem;}
|
|
1184
|
+
.summary-info-content h4 {margin-top: 1.4rem;}
|
|
1185
|
+
.summary-info-content li {margin-bottom: .45rem;}
|
|
1186
|
+
.summary-info-description {white-space: pre-wrap; overflow-wrap: anywhere;
|
|
1187
|
+
font: inherit; padding: 1rem 1.25rem; border: 1px solid var(--bs-border-color);
|
|
1188
|
+
border-radius: .5rem; background: var(--bs-tertiary-bg); min-height: 3rem;}
|
|
1189
|
+
"""),
|
|
1190
|
+
ui.navset_tab(info_tab, id="plot_tabs"),
|
|
1073
1191
|
),
|
|
1074
1192
|
title="Summary plots",
|
|
1075
1193
|
)
|
|
@@ -1079,9 +1197,9 @@ def create_summary_app(
|
|
|
1079
1197
|
# Its picker dynamically switches the app's Bootstrap theme without
|
|
1080
1198
|
# touching plot-specific options or the plot cloning logic.
|
|
1081
1199
|
shinyswatch.theme_picker_server()
|
|
1082
|
-
next_tab = reactive.value(
|
|
1083
|
-
active_tabs = reactive.value((
|
|
1084
|
-
tab_filters = {
|
|
1200
|
+
next_tab = reactive.value(0)
|
|
1201
|
+
active_tabs = reactive.value(())
|
|
1202
|
+
tab_filters = {}
|
|
1085
1203
|
|
|
1086
1204
|
@reactive.effect
|
|
1087
1205
|
@reactive.event(input.add_tab)
|
|
@@ -1110,7 +1228,7 @@ def create_summary_app(
|
|
|
1110
1228
|
def close_plot():
|
|
1111
1229
|
selected = input.plot_tabs()
|
|
1112
1230
|
tabs = active_tabs.get()
|
|
1113
|
-
if selected not in tabs
|
|
1231
|
+
if selected not in tabs:
|
|
1114
1232
|
return
|
|
1115
1233
|
ui.remove_nav_panel("plot_tabs", target=selected)
|
|
1116
1234
|
active_tabs.set(tuple(t for t in tabs if t != selected))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|