AeroViz 0.1.21__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.
- AeroViz/__init__.py +13 -0
- AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/data/DEFAULT_DATA.csv +1417 -0
- AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/data/hysplit_example_data.txt +101 -0
- AeroViz/dataProcess/Chemistry/__init__.py +149 -0
- AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
- AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
- AeroViz/dataProcess/Optical/__init__.py +281 -0
- AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/_derived.py +518 -0
- AeroViz/dataProcess/Optical/_extinction.py +123 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
- AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
- AeroViz/dataProcess/Optical/coefficient.py +72 -0
- AeroViz/dataProcess/Optical/fRH.pkl +0 -0
- AeroViz/dataProcess/Optical/mie_theory.py +260 -0
- AeroViz/dataProcess/README.md +271 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
- AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
- AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
- AeroViz/dataProcess/SizeDistr/prop.py +62 -0
- AeroViz/dataProcess/VOC/__init__.py +14 -0
- AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/VOC/_potential_par.py +108 -0
- AeroViz/dataProcess/VOC/support_voc.json +446 -0
- AeroViz/dataProcess/__init__.py +66 -0
- AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/core/__init__.py +272 -0
- AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/mcp_server.py +352 -0
- AeroViz/plot/__init__.py +13 -0
- AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/distribution.py +576 -0
- AeroViz/plot/meteorology/CBPF.py +295 -0
- AeroViz/plot/meteorology/__init__.py +3 -0
- AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/hysplit.py +93 -0
- AeroViz/plot/meteorology/wind_rose.py +77 -0
- AeroViz/plot/optical/__init__.py +1 -0
- AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/pie.py +210 -0
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/regression.py +200 -0
- AeroViz/plot/scatter.py +174 -0
- AeroViz/plot/templates/__init__.py +6 -0
- AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +267 -0
- AeroViz/plot/templates/diurnal_pattern.py +61 -0
- AeroViz/plot/templates/koschmieder.py +95 -0
- AeroViz/plot/templates/metal_heatmap.py +164 -0
- AeroViz/plot/timeseries/__init__.py +2 -0
- AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +446 -0
- AeroViz/plot/utils/__init__.py +4 -0
- AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_unit.py +55 -0
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +89 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/rawDataReader/FLOW.md +138 -0
- AeroViz/rawDataReader/__init__.py +220 -0
- AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__init__.py +0 -0
- AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/supported_instruments.py +135 -0
- AeroViz/rawDataReader/core/__init__.py +658 -0
- AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/logger.py +171 -0
- AeroViz/rawDataReader/core/pre_process.py +308 -0
- AeroViz/rawDataReader/core/qc.py +961 -0
- AeroViz/rawDataReader/core/report.py +579 -0
- AeroViz/rawDataReader/script/AE33.py +173 -0
- AeroViz/rawDataReader/script/AE43.py +151 -0
- AeroViz/rawDataReader/script/APS.py +339 -0
- AeroViz/rawDataReader/script/Aurora.py +191 -0
- AeroViz/rawDataReader/script/BAM1020.py +90 -0
- AeroViz/rawDataReader/script/BC1054.py +161 -0
- AeroViz/rawDataReader/script/EPA.py +79 -0
- AeroViz/rawDataReader/script/GRIMM.py +68 -0
- AeroViz/rawDataReader/script/IGAC.py +140 -0
- AeroViz/rawDataReader/script/MA350.py +179 -0
- AeroViz/rawDataReader/script/Minion.py +218 -0
- AeroViz/rawDataReader/script/NEPH.py +199 -0
- AeroViz/rawDataReader/script/OCEC.py +173 -0
- AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
- AeroViz/rawDataReader/script/SMPS.py +389 -0
- AeroViz/rawDataReader/script/TEOM.py +181 -0
- AeroViz/rawDataReader/script/VOC.py +106 -0
- AeroViz/rawDataReader/script/Xact.py +244 -0
- AeroViz/rawDataReader/script/__init__.py +28 -0
- AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__init__.py +2 -0
- AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
- AeroViz/tools/database.py +95 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/dataprinter.py +58 -0
- aeroviz-0.1.21.dist-info/METADATA +294 -0
- aeroviz-0.1.21.dist-info/RECORD +180 -0
- aeroviz-0.1.21.dist-info/WHEEL +5 -0
- aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
- aeroviz-0.1.21.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from matplotlib.cm import ScalarMappable
|
|
7
|
+
from matplotlib.pyplot import Figure, Axes
|
|
8
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
9
|
+
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
10
|
+
from pandas import DataFrame, date_range, Timedelta
|
|
11
|
+
|
|
12
|
+
from AeroViz.plot.utils import *
|
|
13
|
+
|
|
14
|
+
__all__ = ['timeseries', 'timeseries_stacked']
|
|
15
|
+
|
|
16
|
+
default_bar_kws = dict(
|
|
17
|
+
width=0.0417,
|
|
18
|
+
edgecolor=None,
|
|
19
|
+
linewidth=0,
|
|
20
|
+
cmap='jet',
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
default_scatter_kws = dict(
|
|
24
|
+
marker='o',
|
|
25
|
+
s=5,
|
|
26
|
+
edgecolor=None,
|
|
27
|
+
linewidths=0.3,
|
|
28
|
+
alpha=0.9,
|
|
29
|
+
cmap='jet',
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
default_insert_kws = dict(
|
|
33
|
+
width="1.5%",
|
|
34
|
+
height="100%",
|
|
35
|
+
loc='lower left',
|
|
36
|
+
bbox_to_anchor=(1.01, 0, 1.2, 1),
|
|
37
|
+
borderpad=0
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
default_plot_kws = dict()
|
|
41
|
+
|
|
42
|
+
default_cbar_kws = dict()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws):
|
|
46
|
+
if _c is None or _c not in df.columns:
|
|
47
|
+
scatter_kws.pop('cmap')
|
|
48
|
+
ax.scatter(df.index, df[_y], **scatter_kws)
|
|
49
|
+
else:
|
|
50
|
+
ax.scatter(df.index, df[_y], c=df[_c], **scatter_kws)
|
|
51
|
+
cax = inset_axes(ax, **inset_kws)
|
|
52
|
+
|
|
53
|
+
# Filter the children to find ScalarMappable objects
|
|
54
|
+
mappable_objects = [child for child in ax.get_children() if isinstance(child, ScalarMappable)]
|
|
55
|
+
|
|
56
|
+
# Use the first mappable object for the colorbar
|
|
57
|
+
if mappable_objects:
|
|
58
|
+
plt.colorbar(mappable=mappable_objects[0], cax=cax, **cbar_kws)
|
|
59
|
+
else:
|
|
60
|
+
print("No mappable objects found.")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws):
|
|
64
|
+
scalar_map, colors = Color.color_maker(df[_c].values, cmap=bar_kws.pop('cmap'))
|
|
65
|
+
ax.bar(df.index, df[_y], color=scalar_map.to_rgba(colors), **bar_kws)
|
|
66
|
+
cax = inset_axes(ax, **inset_kws)
|
|
67
|
+
plt.colorbar(mappable=scalar_map, cax=cax, **cbar_kws)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _plot(ax, df, _y, _color, plot_kws):
|
|
71
|
+
ax.plot(df.index, df[_y], color=_color, **plot_kws)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _wind_arrow(ax, df, y, c, scatter_kws, cbar_kws, inset_kws):
|
|
75
|
+
"""
|
|
76
|
+
Plot wind arrows on a scatter plot.
|
|
77
|
+
|
|
78
|
+
:param ax: matplotlib axes
|
|
79
|
+
:param df: pandas DataFrame
|
|
80
|
+
:param y: column name for wind speed
|
|
81
|
+
:param c: column name for wind direction
|
|
82
|
+
:param scatter_kws: keyword arguments for scatter plot
|
|
83
|
+
:param cbar_kws: keyword arguments for colorbar
|
|
84
|
+
:param inset_kws: keyword arguments for inset axes
|
|
85
|
+
"""
|
|
86
|
+
# First, create a scatter plot
|
|
87
|
+
sc = ax.scatter(df.index, df[y], c=df[c], **scatter_kws)
|
|
88
|
+
|
|
89
|
+
# Add colorbar
|
|
90
|
+
divider = make_axes_locatable(ax)
|
|
91
|
+
cax = divider.append_axes("right", size="2%", pad=0.05)
|
|
92
|
+
plt.colorbar(sc, cax=cax, **cbar_kws)
|
|
93
|
+
|
|
94
|
+
# Add wind arrows
|
|
95
|
+
for idx, row in df.iterrows():
|
|
96
|
+
wind_speed = row[y]
|
|
97
|
+
wind_dir = np.radians(row[c])
|
|
98
|
+
dx = np.sin(wind_dir) * wind_speed / 20 # Scale factor can be adjusted
|
|
99
|
+
dy = np.cos(wind_dir) * wind_speed / 20
|
|
100
|
+
ax.annotate('', xy=(idx + 10 * dx * Timedelta(hours=5), wind_speed + 4 * dy),
|
|
101
|
+
xytext=(idx - 10 * dx * Timedelta(hours=5), wind_speed - 4 * dy),
|
|
102
|
+
arrowprops=dict(arrowstyle='->', color='k', linewidth=0.5))
|
|
103
|
+
|
|
104
|
+
# Set the x-axis limit to show all data points
|
|
105
|
+
# ax.set_xlim(df.index.min() - datetime.timedelta(days=1), df.index.max())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def process_timeseries_data(df, rolling=None, interpolate_limit=None, full_time_index=None):
|
|
109
|
+
# 1. 先建立完整的時間索引
|
|
110
|
+
if full_time_index is None:
|
|
111
|
+
full_time_index = pd.date_range(start=df.index.min(), end=df.index.max(), freq='h') # 或其他適合的頻率
|
|
112
|
+
|
|
113
|
+
# 2. 重新索引,這會產生缺失值而不是丟棄時間點
|
|
114
|
+
df = df.reindex(full_time_index)
|
|
115
|
+
|
|
116
|
+
# apply interpolation if specified
|
|
117
|
+
df = df.interpolate(method='time', limit=interpolate_limit) if interpolate_limit is not None else df
|
|
118
|
+
|
|
119
|
+
# apply rolling window if specified
|
|
120
|
+
df = df.rolling(window=rolling, min_periods=1).mean(numeric_only=True) if rolling is not None else df
|
|
121
|
+
|
|
122
|
+
return df
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@set_figure(autolayout=False)
|
|
126
|
+
def timeseries(df: DataFrame,
|
|
127
|
+
y: list[str] | str,
|
|
128
|
+
y2: list[str] | str = None,
|
|
129
|
+
yi: list[str] | str = None,
|
|
130
|
+
color: list[str] | str | None = None,
|
|
131
|
+
label: list[str] | str | None = None,
|
|
132
|
+
rolling: int | str | None = 3,
|
|
133
|
+
interpolate_limit: int | None = 6,
|
|
134
|
+
major_freq: str = '1MS',
|
|
135
|
+
minor_freq: str = '10d',
|
|
136
|
+
style: list[Literal['scatter', 'bar', 'line', 'arrow']] | str | None = None,
|
|
137
|
+
ax: Axes | None = None,
|
|
138
|
+
set_xaxis_visible: bool | None = None,
|
|
139
|
+
legend_loc: Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'] = 'best',
|
|
140
|
+
legend_ncol: int = 1,
|
|
141
|
+
**kwargs
|
|
142
|
+
) -> tuple[Figure, Axes]:
|
|
143
|
+
"""
|
|
144
|
+
Plot the timeseries data with the option of scatterplot, barplot, and lineplot.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
-----------
|
|
148
|
+
df : DataFrame
|
|
149
|
+
The data to plot.
|
|
150
|
+
y : list[str] | str
|
|
151
|
+
The primary y-axis data columns.
|
|
152
|
+
y2 : list[str] | str, optional
|
|
153
|
+
The secondary y-axis data columns. Defaults to None.
|
|
154
|
+
yi : list[str] | str, optional
|
|
155
|
+
The components for percentage calculation. Defaults to None.
|
|
156
|
+
color : str, optional
|
|
157
|
+
The column for color mapping or the color. Defaults to None.
|
|
158
|
+
label : str, optional
|
|
159
|
+
The label for the legend. Defaults to None.
|
|
160
|
+
rolling : str | int | None, optional
|
|
161
|
+
Rolling window size for smoothing. Defaults to None.
|
|
162
|
+
interpolate_limit : int, optional
|
|
163
|
+
Interpolation limit for missing values. Defaults to None.
|
|
164
|
+
major_freq : str, optional
|
|
165
|
+
Frequency for x-axis ticks. Defaults to '1MS'.
|
|
166
|
+
minor_freq : str, optional
|
|
167
|
+
Frequency for x-axis minor ticks. Defaults to '10d'.
|
|
168
|
+
style : Literal['scatter', 'bar', 'line'] | None, optional
|
|
169
|
+
Style of the plot. Defaults to 'scatter'.
|
|
170
|
+
ax : Axes | None, optional
|
|
171
|
+
Matplotlib Axes object to plot on. Defaults to None.
|
|
172
|
+
set_xaxis_visible : bool | None, optional
|
|
173
|
+
Whether to set x-axis visibility. Defaults to None.
|
|
174
|
+
legend_loc : Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'], optional
|
|
175
|
+
Location of the legend. Defaults to 'best'.
|
|
176
|
+
legend_ncol : int, optional
|
|
177
|
+
Number of columns in the legend. Defaults to 1.
|
|
178
|
+
**kwargs : Additional keyword arguments for customization.
|
|
179
|
+
fig_kws : dict, optional
|
|
180
|
+
Additional keyword arguments for the figure. Defaults to {}.
|
|
181
|
+
scatter_kws : dict, optional
|
|
182
|
+
Additional keyword arguments for the scatter plot. Defaults to {}.
|
|
183
|
+
bar_kws : dict, optional
|
|
184
|
+
Additional keyword arguments for the bar plot. Defaults to {}.
|
|
185
|
+
ax_plot_kws : dict, optional
|
|
186
|
+
Additional keyword arguments for the primary y-axis plot. Defaults to {}.
|
|
187
|
+
ax2_plot_kws : dict, optional
|
|
188
|
+
Additional keyword arguments for the secondary y-axis plot. Defaults to {}.
|
|
189
|
+
cbar_kws : dict, optional
|
|
190
|
+
Additional keyword arguments for the colorbar. Defaults to {}.
|
|
191
|
+
inset_kws : dict, optional
|
|
192
|
+
Additional keyword arguments for the inset axes. Defaults to {}.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
ax : AxesSubplot
|
|
197
|
+
Matplotlib AxesSubplot.
|
|
198
|
+
|
|
199
|
+
Example
|
|
200
|
+
-------
|
|
201
|
+
>>> timeseries(df, y='WS', color='WD', scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]), ylim=[0, None])
|
|
202
|
+
"""
|
|
203
|
+
# Set the time
|
|
204
|
+
try:
|
|
205
|
+
st_tm, fn_tm = df.index[0], df.index[-1]
|
|
206
|
+
except IndexError:
|
|
207
|
+
raise IndexError("The DataFrame is empty. Please provide a valid DataFrame.")
|
|
208
|
+
|
|
209
|
+
# calculate the percentage of each component
|
|
210
|
+
if yi is not None:
|
|
211
|
+
df_pct = df[yi].div(df[yi].sum(axis=1), axis=0) * 100
|
|
212
|
+
mean = [f"{_label} : {df[comp].mean():.2f}" for _label, comp in zip(label, yi)]
|
|
213
|
+
pct = [f"{_label} : {df_pct[comp].mean():.2f}%" for _label, comp in zip(label, yi)]
|
|
214
|
+
df_pct = process_timeseries_data(df_pct, rolling, interpolate_limit)
|
|
215
|
+
|
|
216
|
+
# process data
|
|
217
|
+
df = process_timeseries_data(df, rolling, interpolate_limit)
|
|
218
|
+
|
|
219
|
+
# Initialize figure and axis if not provided
|
|
220
|
+
fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
221
|
+
ax.get_figure(), ax)
|
|
222
|
+
|
|
223
|
+
# Ensure y, y2, c, and style are lists
|
|
224
|
+
y = [y] if isinstance(y, str) else y
|
|
225
|
+
y2 = [y2] if isinstance(y2, str) else y2 if y2 is not None else []
|
|
226
|
+
color = [color] if isinstance(color, str) else color if color is not None else [None] * (len(y) + len(y2))
|
|
227
|
+
label = [label] if isinstance(label, str) else label if label is not None else [None] * (len(y) + len(y2))
|
|
228
|
+
style = [style] if isinstance(style, str) else style if style is not None else ['plot'] * (len(y) + len(y2))
|
|
229
|
+
|
|
230
|
+
for name, lst in [("c", color), ("style", style), ("label", label)]:
|
|
231
|
+
if len(lst) != len(y) + len(y2):
|
|
232
|
+
raise ValueError(f"The length of {name} must match the combined length of y and y2")
|
|
233
|
+
|
|
234
|
+
# Create a secondary y-axis if y2 is not empty
|
|
235
|
+
ax2 = ax.twinx() if y2 else None
|
|
236
|
+
|
|
237
|
+
# # Set color cycle
|
|
238
|
+
ax.set_prop_cycle(Color.color_cycle)
|
|
239
|
+
if y2:
|
|
240
|
+
ax2.set_prop_cycle(Color.color_cycle[len(y):])
|
|
241
|
+
|
|
242
|
+
if y2 and ('scatter' or 'bar') in style:
|
|
243
|
+
fig.subplots_adjust(right=0.8)
|
|
244
|
+
|
|
245
|
+
# for i, _c in enumerate(color):
|
|
246
|
+
# if _c is not None and _c in df.columns:
|
|
247
|
+
# style[i] = 'scatter'
|
|
248
|
+
|
|
249
|
+
for i, (_y, _c, _label, _style) in enumerate(zip(y, color, label, style)):
|
|
250
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws', {})}
|
|
251
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws', {})}
|
|
252
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws', {})}
|
|
253
|
+
|
|
254
|
+
if _style in ['scatter', 'bar', 'arrow']:
|
|
255
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws', {})}
|
|
256
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws', {})}
|
|
257
|
+
|
|
258
|
+
if _style == 'scatter':
|
|
259
|
+
_scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
260
|
+
|
|
261
|
+
elif _style == 'bar':
|
|
262
|
+
_bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
263
|
+
|
|
264
|
+
elif _style == 'arrow':
|
|
265
|
+
_wind_arrow(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
266
|
+
|
|
267
|
+
else:
|
|
268
|
+
_plot(ax, df, _y, _c, plot_kws)
|
|
269
|
+
|
|
270
|
+
if y2:
|
|
271
|
+
for i, (_y, _c, _style) in enumerate(zip(y2, color[len(y):], style[len(y):])):
|
|
272
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws2', {})}
|
|
273
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws2', {})}
|
|
274
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws2', {})}
|
|
275
|
+
|
|
276
|
+
if _style in ['scatter', 'bar']:
|
|
277
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws2', {})}
|
|
278
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws2', {})}
|
|
279
|
+
|
|
280
|
+
if _style == 'scatter':
|
|
281
|
+
_scatter(ax2, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
282
|
+
|
|
283
|
+
elif _style == 'bar':
|
|
284
|
+
_bar(ax2, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
285
|
+
|
|
286
|
+
elif _style == 'arrow':
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
else: # line plot
|
|
290
|
+
_plot(ax2, df, _y, _c, plot_kws)
|
|
291
|
+
|
|
292
|
+
# Combine legends from ax and ax2
|
|
293
|
+
ax.legend(*combine_legends([ax, ax2]), loc=legend_loc, ncol=legend_ncol)
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
ax.legend(loc=legend_loc, ncol=legend_ncol)
|
|
297
|
+
|
|
298
|
+
if set_xaxis_visible is not None:
|
|
299
|
+
ax.axes.xaxis.set_visible(set_xaxis_visible)
|
|
300
|
+
|
|
301
|
+
ax.set(xlabel=kwargs.get('xlabel', ''),
|
|
302
|
+
ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
|
|
303
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
304
|
+
ylim=kwargs.get('ylim', (None, None)),
|
|
305
|
+
title=kwargs.get('title', '')
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
|
|
309
|
+
minor_xticks = kwargs.get('minor_xticks', date_range(start=st_tm, end=fn_tm, freq=minor_freq))
|
|
310
|
+
|
|
311
|
+
ax.set_xticks(ticks=xticks, labels=xticks.strftime("%F"))
|
|
312
|
+
ax.set_xticks(minor_xticks, minor=True)
|
|
313
|
+
|
|
314
|
+
if y2:
|
|
315
|
+
ax2.set(ylim=kwargs.get('ylim2', (None, None)),
|
|
316
|
+
ylabel=kwargs.get('ylabel2', Unit(y2) if isinstance(y2, str) else Unit(y2[0]))
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
plt.show()
|
|
320
|
+
|
|
321
|
+
return fig, ax
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@set_figure(figsize=(6, 3), fs=6, autolayout=False)
|
|
325
|
+
def timeseries_stacked(df,
|
|
326
|
+
y: list[str] | str,
|
|
327
|
+
yi: list[str] | str,
|
|
328
|
+
label: list[str] | str,
|
|
329
|
+
plot_type: Literal["absolute", "percentage", "both"] | str = 'both',
|
|
330
|
+
rolling: int | str | None = 4,
|
|
331
|
+
interpolate_limit: int | None = 4,
|
|
332
|
+
major_freq: str = '10d',
|
|
333
|
+
minor_freq: str = '1d',
|
|
334
|
+
support_df: DataFrame | None = None,
|
|
335
|
+
ax: Axes | None = None,
|
|
336
|
+
**kwargs
|
|
337
|
+
) -> tuple[Figure, Axes]:
|
|
338
|
+
try:
|
|
339
|
+
st_tm, fn_tm = df.index[0], df.index[-1]
|
|
340
|
+
except IndexError:
|
|
341
|
+
raise IndexError("The DataFrame is empty. Please provide a valid DataFrame.")
|
|
342
|
+
|
|
343
|
+
if plot_type not in ['absolute', 'percentage', 'both']:
|
|
344
|
+
raise ValueError("plot_type must be one of 'absolute', 'percentage', or 'both'")
|
|
345
|
+
|
|
346
|
+
# calculate the percentage of each component
|
|
347
|
+
df = df.dropna()
|
|
348
|
+
df_pct = df[yi].div(df[yi].sum(axis=1), axis=0) * 100
|
|
349
|
+
|
|
350
|
+
mean = [f"{_label} : {df[comp].mean():.2f}" for _label, comp in zip(label, yi)]
|
|
351
|
+
pct = [f"{_label} : {df_pct[comp].mean():.2f}%" for _label, comp in zip(label, yi)]
|
|
352
|
+
|
|
353
|
+
full_time_index = pd.date_range(start=st_tm, end=fn_tm, freq='h')
|
|
354
|
+
|
|
355
|
+
# process data
|
|
356
|
+
df = process_timeseries_data(df, rolling, interpolate_limit, full_time_index)
|
|
357
|
+
df_pct = process_timeseries_data(df_pct, rolling, interpolate_limit, full_time_index)
|
|
358
|
+
|
|
359
|
+
# Set figure size based on plot_type
|
|
360
|
+
figsize = (7, 6) if plot_type == 'both' else (7, 3)
|
|
361
|
+
if plot_type == 'both':
|
|
362
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, **{**{'figsize': figsize, 'dpi': 600}, **kwargs.get('fig_kws', {})})
|
|
363
|
+
else:
|
|
364
|
+
fig, ax1 = plt.subplots(1, 1, **{**{'figsize': figsize, 'dpi': 600}, **kwargs.get('fig_kws', {})})
|
|
365
|
+
|
|
366
|
+
plt.subplots_adjust(right=0.95)
|
|
367
|
+
width = 0.0417
|
|
368
|
+
color = Color.colors1
|
|
369
|
+
|
|
370
|
+
for name, lst in [("color", color), ("label", label)]:
|
|
371
|
+
if len(lst) != len(yi):
|
|
372
|
+
raise ValueError(f"The length of {name} must match the combined length of y and y2")
|
|
373
|
+
|
|
374
|
+
def plot_stacked_bars(ax, data, labels, is_percentage=False):
|
|
375
|
+
bottom = None
|
|
376
|
+
for i, (_column, _color, _label) in enumerate(zip(yi, color, labels)):
|
|
377
|
+
if i == 0:
|
|
378
|
+
bottom = data[_column] * 0
|
|
379
|
+
ax.bar(data.index, data[_column], color=_color, width=width, bottom=bottom, label=_label)
|
|
380
|
+
bottom += data[_column]
|
|
381
|
+
|
|
382
|
+
# Set axis properties
|
|
383
|
+
if kwargs.get('legend', True):
|
|
384
|
+
ax.legend(loc='upper left', ncol=2, prop={'weight': 'bold'}, bbox_to_anchor=(0.75, 0, 0.2, 1))
|
|
385
|
+
|
|
386
|
+
ylim = (0, 100) if is_percentage else kwargs.get('ylim', (None, None))
|
|
387
|
+
ylabel = 'Percentage (%)' if is_percentage else (
|
|
388
|
+
kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])))
|
|
389
|
+
|
|
390
|
+
ax.set(xlabel=kwargs.get('xlabel', ''),
|
|
391
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
392
|
+
ylim=ylim,
|
|
393
|
+
title=kwargs.get('title', ''))
|
|
394
|
+
|
|
395
|
+
ax.set_ylabel(ylabel, fontsize=12)
|
|
396
|
+
|
|
397
|
+
# Set ticks
|
|
398
|
+
xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
|
|
399
|
+
yticks = kwargs.get('yticks', np.linspace(*ax.get_ylim(), num=6))
|
|
400
|
+
minor_xticks = kwargs.get('minor_xticks', date_range(start=st_tm, end=fn_tm, freq=minor_freq))
|
|
401
|
+
|
|
402
|
+
ax.set_xticks(ticks=xticks, labels=xticks.strftime("%F"))
|
|
403
|
+
ax.set_yticks(ticks=yticks, labels=[f'{tick:.0f}' for tick in yticks])
|
|
404
|
+
ax.set_xticks(minor_xticks, minor=True)
|
|
405
|
+
|
|
406
|
+
# Plot based on plot_type
|
|
407
|
+
if plot_type in ['absolute', 'both']:
|
|
408
|
+
plot_stacked_bars(ax1, df, mean, is_percentage=False)
|
|
409
|
+
if plot_type == 'absolute':
|
|
410
|
+
ax1.axes.xaxis.set_visible(True)
|
|
411
|
+
|
|
412
|
+
if support_df is not None: # 確保support_df存在
|
|
413
|
+
# 創建次要Y軸
|
|
414
|
+
ax_right = ax1.twinx()
|
|
415
|
+
|
|
416
|
+
support_df = process_timeseries_data(support_df, rolling, interpolate_limit, full_time_index)
|
|
417
|
+
|
|
418
|
+
# 繪製線圖在次要Y軸上
|
|
419
|
+
ax_right.plot(support_df.index, support_df['PM2.5'],
|
|
420
|
+
color='black', linewidth=1.5,
|
|
421
|
+
label=f'Measured $PM_{{2.5}}$')
|
|
422
|
+
|
|
423
|
+
# ax_right.plot(support_df.index, support_df['PM10'],
|
|
424
|
+
# color='gray', linewidth=1.5,
|
|
425
|
+
# label=f'Measured $PM_{{10}}$')
|
|
426
|
+
|
|
427
|
+
# 設置次要Y軸的標籤和格式
|
|
428
|
+
# ax_right.set_ylabel(Unit('PM2.5'), fontsize=12)
|
|
429
|
+
ax_right.set_ylim(0, 120)
|
|
430
|
+
ax_right.axes.yaxis.set_visible(False)
|
|
431
|
+
|
|
432
|
+
# ax_right.tick_params(axis='y', colors='black')
|
|
433
|
+
# ax_right.legend(loc='upper right', prop={'size': 12})
|
|
434
|
+
|
|
435
|
+
if plot_type in ['percentage', 'both']:
|
|
436
|
+
ax_pct = ax2 if plot_type == 'both' else ax1
|
|
437
|
+
plot_stacked_bars(ax_pct, df_pct, pct, is_percentage=True)
|
|
438
|
+
|
|
439
|
+
if plot_type == 'both':
|
|
440
|
+
pass
|
|
441
|
+
# ax1.axes.xaxis.set_visible(False)
|
|
442
|
+
|
|
443
|
+
plt.savefig('/Users/chanchihyu/Desktop/times_stacked.png', transparent=True)
|
|
444
|
+
|
|
445
|
+
plt.show()
|
|
446
|
+
return fig, ax1
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import matplotlib.colors as plc
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
import seaborn as sns
|
|
5
|
+
from cycler import cycler
|
|
6
|
+
from matplotlib import colormaps
|
|
7
|
+
|
|
8
|
+
__all__ = ['Color']
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Color:
|
|
12
|
+
color_cycle = cycler(color=['b', 'g', 'r', 'c', 'm', 'y', 'k'])
|
|
13
|
+
|
|
14
|
+
linecolor = [{'line': '#1a56db', 'edge': '#0F50A6', 'face': '#5983D9'},
|
|
15
|
+
{'line': '#046c4e', 'edge': '#1B591F', 'face': '#538C4A'},
|
|
16
|
+
{'line': '#c81e1e', 'edge': '#f05252', 'face': '#f98080'}]
|
|
17
|
+
|
|
18
|
+
# colors = ['#FF3333', '#33FF33', '#FFFF33', '#5555FF', '#B94FFF', '#AAAAAA', '#748690'] # the last one is "unknown"
|
|
19
|
+
|
|
20
|
+
colors1 = ['#A65E58', '#A5BF6B', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB']
|
|
21
|
+
colors2 = ['#A65E58', '#A5BF6B', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB', '#96c8e6']
|
|
22
|
+
colors3 = ['#A65E58', '#A5BF6B', '#a6710d', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB', '#96c8e6'] # POC SOC
|
|
23
|
+
|
|
24
|
+
colors_mutiWater = ['#A65E58', '#c18e8a', '#A5BF6B', '#c5d6a0', '#F2BF5E', '#3F83BF', '#c089ca', '#d3acda',
|
|
25
|
+
'#D1CFCB']
|
|
26
|
+
colors_mutiWater2 = ['#A65E58', '#96c8e6', '#A5BF6B', '#96c8e6', '#F2BF5E', '#3F83BF', '#c089ca', '#96c8e6',
|
|
27
|
+
'#D1CFCB'] # water
|
|
28
|
+
|
|
29
|
+
color_choose = {'Clean': ['#1d4a9f', '#84a7e9'],
|
|
30
|
+
'Transition': ['#4a9f1d', '#a7e984'],
|
|
31
|
+
'Event': ['#9f1d4a', '#e984a7']}
|
|
32
|
+
|
|
33
|
+
paired = [plt.get_cmap('Paired')(i) for i in range(4)]
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def getColor(num: int = 6, cmap: str = 'jet_r'):
|
|
37
|
+
category_colors = plt.colormaps[cmap](np.linspace(0.1, 0.9, num))
|
|
38
|
+
return [plc.to_hex(category_colors[i]) for i in range(num)]
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def palplot(*args, **kwargs):
|
|
42
|
+
sns.palplot(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def adjust_opacity(colors: str | list[str], alpha: float):
|
|
46
|
+
if isinstance(colors, str):
|
|
47
|
+
colors = [colors]
|
|
48
|
+
|
|
49
|
+
adjusted_colors = []
|
|
50
|
+
for color in colors:
|
|
51
|
+
# 將顏色轉換為RGB表示
|
|
52
|
+
r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
|
|
53
|
+
# 調整透明度
|
|
54
|
+
r_new = int(alpha * r + (1 - alpha) * 255)
|
|
55
|
+
g_new = int(alpha * g + (1 - alpha) * 255)
|
|
56
|
+
b_new = int(alpha * b + (1 - alpha) * 255)
|
|
57
|
+
# 轉換為新的色碼
|
|
58
|
+
new_color = '#{:02X}{:02X}{:02X}'.format(r_new, g_new, b_new)
|
|
59
|
+
adjusted_colors.append(new_color)
|
|
60
|
+
return adjusted_colors
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def color_maker(obj, cmap='Blues'):
|
|
64
|
+
colors = np.nan_to_num(obj, nan=0)
|
|
65
|
+
scalar_map = plt.cm.ScalarMappable(cmap=colormaps[cmap]) # create a scalar map for the colorbar
|
|
66
|
+
scalar_map.set_array(colors)
|
|
67
|
+
return scalar_map, colors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
Color.palplot(Color.colors2)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
__all__ = ['Unit']
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Unit:
|
|
8
|
+
file_path = Path(__file__).parent / 'units.json'
|
|
9
|
+
data = None
|
|
10
|
+
|
|
11
|
+
def __new__(cls, unit: str):
|
|
12
|
+
cls.data = cls.load_jsonfile()
|
|
13
|
+
try:
|
|
14
|
+
value = cls.data[unit]
|
|
15
|
+
return r'${}$'.format(value.replace(' ', r'\ '))
|
|
16
|
+
except KeyError:
|
|
17
|
+
print(f"Attribute '{unit}' not found. Using default value.")
|
|
18
|
+
return r'${}$'.format(unit.replace(' ', r'\ ')) if unit is not None else 'None'
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def load_jsonfile(cls):
|
|
22
|
+
""" 讀取 JSON 檔中數據并將其變成屬性 """
|
|
23
|
+
try:
|
|
24
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
print(f"JSON file '{cls.file_path}' not found.")
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
print(f"Invalid JSON format in '{cls.file_path}'.")
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def update_jsonfile(cls, key, value):
|
|
34
|
+
""" 更新JSON檔 """
|
|
35
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
36
|
+
old_data = json.load(f)
|
|
37
|
+
|
|
38
|
+
old_data[key] = value
|
|
39
|
+
|
|
40
|
+
with open(cls.file_path, 'w', encoding='utf-8') as f:
|
|
41
|
+
json.dump(old_data, f, indent=4)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def del_jsonfile(cls, key):
|
|
45
|
+
""" 更新JSON檔 """
|
|
46
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
47
|
+
old_data = json.load(f)
|
|
48
|
+
|
|
49
|
+
if key in old_data:
|
|
50
|
+
del old_data[key]
|
|
51
|
+
|
|
52
|
+
with open(cls.file_path, 'w', encoding='utf-8') as f:
|
|
53
|
+
json.dump(old_data, f, indent=4)
|
|
54
|
+
else:
|
|
55
|
+
print(f"Key '{key}' not found.")
|