AeroViz 0.1.0__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.

Potentially problematic release.


This version of AeroViz might be problematic. Click here for more details.

Files changed (102) hide show
  1. AeroViz/__init__.py +15 -0
  2. AeroViz/dataProcess/Chemistry/__init__.py +63 -0
  3. AeroViz/dataProcess/Chemistry/_calculate.py +27 -0
  4. AeroViz/dataProcess/Chemistry/_isoropia.py +99 -0
  5. AeroViz/dataProcess/Chemistry/_mass_volume.py +175 -0
  6. AeroViz/dataProcess/Chemistry/_ocec.py +184 -0
  7. AeroViz/dataProcess/Chemistry/_partition.py +29 -0
  8. AeroViz/dataProcess/Chemistry/_teom.py +16 -0
  9. AeroViz/dataProcess/Optical/_IMPROVE.py +61 -0
  10. AeroViz/dataProcess/Optical/__init__.py +62 -0
  11. AeroViz/dataProcess/Optical/_absorption.py +54 -0
  12. AeroViz/dataProcess/Optical/_extinction.py +36 -0
  13. AeroViz/dataProcess/Optical/_mie.py +16 -0
  14. AeroViz/dataProcess/Optical/_mie_sd.py +143 -0
  15. AeroViz/dataProcess/Optical/_scattering.py +30 -0
  16. AeroViz/dataProcess/SizeDistr/__init__.py +61 -0
  17. AeroViz/dataProcess/SizeDistr/__merge.py +250 -0
  18. AeroViz/dataProcess/SizeDistr/_merge.py +245 -0
  19. AeroViz/dataProcess/SizeDistr/_merge_v1.py +254 -0
  20. AeroViz/dataProcess/SizeDistr/_merge_v2.py +243 -0
  21. AeroViz/dataProcess/SizeDistr/_merge_v3.py +518 -0
  22. AeroViz/dataProcess/SizeDistr/_merge_v4.py +424 -0
  23. AeroViz/dataProcess/SizeDistr/_size_distr.py +93 -0
  24. AeroViz/dataProcess/VOC/__init__.py +19 -0
  25. AeroViz/dataProcess/VOC/_potential_par.py +76 -0
  26. AeroViz/dataProcess/__init__.py +11 -0
  27. AeroViz/dataProcess/core/__init__.py +92 -0
  28. AeroViz/plot/__init__.py +7 -0
  29. AeroViz/plot/distribution/__init__.py +1 -0
  30. AeroViz/plot/distribution/distribution.py +582 -0
  31. AeroViz/plot/improve/__init__.py +1 -0
  32. AeroViz/plot/improve/improve.py +240 -0
  33. AeroViz/plot/meteorology/__init__.py +1 -0
  34. AeroViz/plot/meteorology/meteorology.py +317 -0
  35. AeroViz/plot/optical/__init__.py +2 -0
  36. AeroViz/plot/optical/aethalometer.py +77 -0
  37. AeroViz/plot/optical/optical.py +388 -0
  38. AeroViz/plot/templates/__init__.py +8 -0
  39. AeroViz/plot/templates/contour.py +47 -0
  40. AeroViz/plot/templates/corr_matrix.py +108 -0
  41. AeroViz/plot/templates/diurnal_pattern.py +42 -0
  42. AeroViz/plot/templates/event_evolution.py +65 -0
  43. AeroViz/plot/templates/koschmieder.py +156 -0
  44. AeroViz/plot/templates/metal_heatmap.py +57 -0
  45. AeroViz/plot/templates/regression.py +256 -0
  46. AeroViz/plot/templates/scatter.py +130 -0
  47. AeroViz/plot/templates/templates.py +398 -0
  48. AeroViz/plot/timeseries/__init__.py +1 -0
  49. AeroViz/plot/timeseries/timeseries.py +317 -0
  50. AeroViz/plot/utils/__init__.py +3 -0
  51. AeroViz/plot/utils/_color.py +71 -0
  52. AeroViz/plot/utils/_decorator.py +74 -0
  53. AeroViz/plot/utils/_unit.py +55 -0
  54. AeroViz/process/__init__.py +31 -0
  55. AeroViz/process/core/DataProc.py +19 -0
  56. AeroViz/process/core/SizeDist.py +90 -0
  57. AeroViz/process/core/__init__.py +4 -0
  58. AeroViz/process/method/PyMieScatt_update.py +567 -0
  59. AeroViz/process/method/__init__.py +2 -0
  60. AeroViz/process/method/mie_theory.py +258 -0
  61. AeroViz/process/method/prop.py +62 -0
  62. AeroViz/process/script/AbstractDistCalc.py +143 -0
  63. AeroViz/process/script/Chemical.py +176 -0
  64. AeroViz/process/script/IMPACT.py +49 -0
  65. AeroViz/process/script/IMPROVE.py +161 -0
  66. AeroViz/process/script/Others.py +65 -0
  67. AeroViz/process/script/PSD.py +103 -0
  68. AeroViz/process/script/PSD_dry.py +94 -0
  69. AeroViz/process/script/__init__.py +5 -0
  70. AeroViz/process/script/retrieve_RI.py +70 -0
  71. AeroViz/rawDataReader/__init__.py +68 -0
  72. AeroViz/rawDataReader/core/__init__.py +397 -0
  73. AeroViz/rawDataReader/script/AE33.py +31 -0
  74. AeroViz/rawDataReader/script/AE43.py +34 -0
  75. AeroViz/rawDataReader/script/APS_3321.py +47 -0
  76. AeroViz/rawDataReader/script/Aurora.py +38 -0
  77. AeroViz/rawDataReader/script/BC1054.py +46 -0
  78. AeroViz/rawDataReader/script/EPA_vertical.py +18 -0
  79. AeroViz/rawDataReader/script/GRIMM.py +35 -0
  80. AeroViz/rawDataReader/script/IGAC_TH.py +104 -0
  81. AeroViz/rawDataReader/script/IGAC_ZM.py +90 -0
  82. AeroViz/rawDataReader/script/MA350.py +45 -0
  83. AeroViz/rawDataReader/script/NEPH.py +57 -0
  84. AeroViz/rawDataReader/script/OCEC_LCRES.py +34 -0
  85. AeroViz/rawDataReader/script/OCEC_RES.py +28 -0
  86. AeroViz/rawDataReader/script/SMPS_TH.py +41 -0
  87. AeroViz/rawDataReader/script/SMPS_aim11.py +51 -0
  88. AeroViz/rawDataReader/script/SMPS_genr.py +51 -0
  89. AeroViz/rawDataReader/script/TEOM.py +46 -0
  90. AeroViz/rawDataReader/script/Table.py +28 -0
  91. AeroViz/rawDataReader/script/VOC_TH.py +30 -0
  92. AeroViz/rawDataReader/script/VOC_ZM.py +37 -0
  93. AeroViz/rawDataReader/script/__init__.py +22 -0
  94. AeroViz/tools/__init__.py +3 -0
  95. AeroViz/tools/database.py +94 -0
  96. AeroViz/tools/dataclassifier.py +117 -0
  97. AeroViz/tools/datareader.py +66 -0
  98. AeroViz-0.1.0.dist-info/LICENSE +21 -0
  99. AeroViz-0.1.0.dist-info/METADATA +117 -0
  100. AeroViz-0.1.0.dist-info/RECORD +102 -0
  101. AeroViz-0.1.0.dist-info/WHEEL +5 -0
  102. AeroViz-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,317 @@
1
+ from datetime import datetime
2
+ from typing import Literal
3
+
4
+ import matplotlib.pyplot as plt
5
+ from matplotlib.cm import ScalarMappable
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from mpl_toolkits.axes_grid1.inset_locator import inset_axes
8
+ from pandas import DataFrame, date_range, Timestamp
9
+
10
+ from AeroViz.plot.utils import *
11
+
12
+ __all__ = ['timeseries', 'timeseries_template']
13
+
14
+ default_bar_kws = dict(
15
+ width=0.0417,
16
+ edgecolor=None,
17
+ linewidth=0,
18
+ cmap='jet',
19
+ )
20
+
21
+ default_scatter_kws = dict(
22
+ marker='o',
23
+ s=5,
24
+ edgecolor=None,
25
+ linewidths=0.3,
26
+ alpha=0.9,
27
+ cmap='jet',
28
+ )
29
+
30
+ default_insert_kws = dict(
31
+ width="1.5%",
32
+ height="100%",
33
+ loc='lower left',
34
+ bbox_to_anchor=(1.01, 0, 1.2, 1),
35
+ borderpad=0
36
+ )
37
+
38
+ default_plot_kws = dict()
39
+
40
+ default_cbar_kws = dict()
41
+
42
+
43
+ def _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws):
44
+ if _c is None or _c not in df.columns:
45
+ scatter_kws.pop('cmap')
46
+ ax.scatter(df.index, df[_y], **scatter_kws)
47
+ else:
48
+ ax.scatter(df.index, df[_y], c=df[_c], **scatter_kws)
49
+ cax = inset_axes(ax, **inset_kws)
50
+
51
+ # Filter the children to find ScalarMappable objects
52
+ mappable_objects = [child for child in ax.get_children() if isinstance(child, ScalarMappable)]
53
+
54
+ # Use the first mappable object for the colorbar
55
+ if mappable_objects:
56
+ plt.colorbar(mappable=mappable_objects[0], cax=cax, **cbar_kws)
57
+ else:
58
+ print("No mappable objects found.")
59
+
60
+ # plt.colorbar(mappable=ax.get_children()[0], cax=cax, **cbar_kws)
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 combine_legends(axes_list: list[Axes]) -> tuple[list, list]:
75
+ return (
76
+ [legend for axes in axes_list for legend in axes.get_legend_handles_labels()[0]],
77
+ [label for axes in axes_list for label in axes.get_legend_handles_labels()[1]]
78
+ )
79
+
80
+
81
+ @set_figure(fs=8, autolayout=False)
82
+ def timeseries(df: DataFrame,
83
+ y: list[str] | str,
84
+ y2: list[str] | str = None,
85
+ c: list[str] | str = None,
86
+ # color: list[str] | str = None,
87
+ rolling: str | int | None = None,
88
+ times: tuple[datetime | Timestamp | str, datetime | Timestamp | str] = None,
89
+ freq: str = '1MS',
90
+ style: list[Literal['scatter', 'bar', 'line']] | str | None = None,
91
+ ax: Axes | None = None,
92
+ set_xaxis_visible: bool | None = None,
93
+ legend_loc: Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'] = 'best',
94
+ legend_ncol: int = 1,
95
+ **kwargs
96
+ ) -> tuple[Figure, Axes]:
97
+ """
98
+ Plot the timeseries data with the option of scatterplot, barplot, and lineplot.
99
+
100
+ Parameters
101
+ -----------
102
+ df : DataFrame
103
+ The data to plot.
104
+ y : list[str] | str
105
+ The primary y-axis data columns.
106
+ y2 : list[str] | str, optional
107
+ The secondary y-axis data columns. Defaults to None.
108
+ c : str, optional
109
+ The column for color mapping or the color. Defaults to None.
110
+ rolling : str | int | None, optional
111
+ Rolling window size for smoothing. Defaults to None.
112
+ times : tuple[datetime, datetime] | tuple[Timestamp, Timestamp], optional
113
+ Time range for the data. Defaults to None.
114
+ freq : str, optional
115
+ Frequency for x-axis ticks. Defaults to '2MS'.
116
+ style : Literal['scatter', 'bar', 'line'] | None, optional
117
+ Style of the plot. Defaults to 'scatter'.
118
+ ax : Axes | None, optional
119
+ Matplotlib Axes object to plot on. Defaults to None.
120
+ set_xaxis_visible : bool | None, optional
121
+ Whether to set x-axis visibility. Defaults to None.
122
+ legend_loc : Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'], optional
123
+ Location of the legend. Defaults to 'best'.
124
+ legend_ncol : int, optional
125
+ Number of columns in the legend. Defaults to 1.
126
+ **kwargs : Additional keyword arguments for customization.
127
+ fig_kws : dict, optional
128
+ Additional keyword arguments for the figure. Defaults to {}.
129
+ scatter_kws : dict, optional
130
+ Additional keyword arguments for the scatter plot. Defaults to {}.
131
+ bar_kws : dict, optional
132
+ Additional keyword arguments for the bar plot. Defaults to {}.
133
+ ax_plot_kws : dict, optional
134
+ Additional keyword arguments for the primary y-axis plot. Defaults to {}.
135
+ ax2_plot_kws : dict, optional
136
+ Additional keyword arguments for the secondary y-axis plot. Defaults to {}.
137
+ cbar_kws : dict, optional
138
+ Additional keyword arguments for the colorbar. Defaults to {}.
139
+ inset_kws : dict, optional
140
+ Additional keyword arguments for the inset axes. Defaults to {}.
141
+
142
+ Returns
143
+ -------
144
+ ax : AxesSubplot
145
+ Matplotlib AxesSubplot.
146
+
147
+ Example
148
+ -------
149
+ >>> timeseries(df, y='WS', c='WD', scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]), ylim=[0, None])
150
+ """
151
+ # Set the time
152
+ st_tm, fn_tm = (df.index[0], df.index[-1]) if times is None else map(Timestamp, times)
153
+
154
+ # Apply rolling window if specified
155
+ df = df.loc[st_tm:fn_tm] if rolling is None else (
156
+ df.loc[st_tm:fn_tm].rolling(window=rolling, min_periods=1).mean(numeric_only=True))
157
+
158
+ # Initialize figure and axis if not provided
159
+ fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
160
+ ax.get_figure(), ax)
161
+
162
+ # Ensure y, y2, c, and style are lists
163
+ y = [y] if isinstance(y, str) else y
164
+ y2 = [y2] if isinstance(y2, str) else y2 if y2 is not None else []
165
+ c = [c] if isinstance(c, str) else c if c is not None else [None] * (len(y) + len(y2))
166
+ style = [style] if isinstance(style, str) else style if style is not None else ['plot'] * (len(y) + len(y2))
167
+
168
+ if len(c) != len(y) + len(y2):
169
+ raise ValueError("The length of c must match the combined length of y and y2")
170
+
171
+ if len(style) != len(y) + len(y2):
172
+ raise ValueError("The length of style must match the combined length of y and y2")
173
+
174
+ # Create a secondary y-axis if y2 is not empty
175
+ ax2 = ax.twinx() if y2 else None
176
+
177
+ # # Set color cycle
178
+ ax.set_prop_cycle(Color.color_cycle)
179
+ if y2:
180
+ ax2.set_prop_cycle(Color.color_cycle[len(y):])
181
+
182
+ if y2 and ('scatter' or 'bar') in style:
183
+ fig.subplots_adjust(right=0.8)
184
+
185
+ for i, _c in enumerate(c):
186
+ if _c is not None and _c in df.columns:
187
+ style[i] = 'scatter'
188
+
189
+ for i, (_y, _c, _style) in enumerate(zip(y, c, style)):
190
+ scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws', {})}
191
+ bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws', {})}
192
+ plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws', {})}
193
+
194
+ if _style in ['scatter', 'bar']:
195
+ cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws', {})}
196
+ inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws', {})}
197
+
198
+ if _style == 'scatter':
199
+ _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
200
+
201
+ elif _style == 'bar':
202
+ _bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws)
203
+
204
+ else:
205
+ _plot(ax, df, _y, _c, plot_kws)
206
+
207
+ if y2:
208
+ for i, (_y, _c, _style) in enumerate(zip(y2, c[len(y):], style[len(y):])):
209
+ scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws2', {})}
210
+ bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws2', {})}
211
+ plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws2', {})}
212
+
213
+ if _style in ['scatter', 'bar']:
214
+ cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws2', {})}
215
+ inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws2', {})}
216
+
217
+ if _style == 'scatter':
218
+ _scatter(ax2, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
219
+
220
+ elif _style == 'bar':
221
+ _bar(ax2, df, _y, _c, bar_kws, cbar_kws, inset_kws)
222
+
223
+ else: # line plot
224
+ _plot(ax2, df, _y, _c, plot_kws)
225
+
226
+ # Combine legends from ax and ax2
227
+ ax.legend(*combine_legends([ax, ax2]), loc=legend_loc, ncol=legend_ncol)
228
+
229
+ else:
230
+ ax.legend(loc=legend_loc, ncol=legend_ncol)
231
+
232
+ if set_xaxis_visible is not None:
233
+ ax.axes.xaxis.set_visible(set_xaxis_visible)
234
+
235
+ ax.set(xlabel=kwargs.get('xlabel', ''),
236
+ ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
237
+ xticks=kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
238
+ yticks=kwargs.get('yticks', ax.get_yticks()),
239
+ xticklabels=kwargs.get('xticklabels', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
240
+ yticklabels=kwargs.get('yticklabels', ax.get_yticklabels()),
241
+ xlim=kwargs.get('xlim', (st_tm, fn_tm)),
242
+ ylim=kwargs.get('ylim', (None, None)),
243
+ title=kwargs.get('title', '')
244
+ )
245
+
246
+ if y2:
247
+ ax2.set(ylabel=kwargs.get('ylabel2', Unit(y2) if isinstance(y2, str) else Unit(y2[0])),
248
+ yticks=kwargs.get('yticks2', ax2.get_yticks()),
249
+ yticklabels=kwargs.get('yticklabels2', ax2.get_yticklabels()),
250
+ ylim=kwargs.get('ylim2', (None, None)))
251
+
252
+ plt.show()
253
+
254
+ return fig, ax
255
+
256
+
257
+ @set_figure(fs=8, autolayout=False)
258
+ def timeseries_template(df: DataFrame) -> tuple[Figure, Axes]:
259
+ fig, ax = plt.subplots(5, 1, figsize=(len(df.index) * 0.01, 4))
260
+ (ax1, ax2, ax3, ax4, ax5) = ax
261
+
262
+ timeseries(df,
263
+ y=['Extinction', 'Scattering', 'Absorption'],
264
+ rolling=30,
265
+ ax=ax1,
266
+ ylabel='Coefficient',
267
+ ylim=[0., None],
268
+ set_xaxis_visible=False,
269
+ legend_ncol=3,
270
+ )
271
+
272
+ # Temp, RH
273
+ timeseries(df,
274
+ y='AT',
275
+ y2='RH',
276
+ rolling=30,
277
+ ax=ax2,
278
+ ax_plot_kws=dict(color='r'),
279
+ ax2_plot_kws=dict(color='b'),
280
+ ylim=[10, 40],
281
+ ylim2=[20, 100],
282
+ set_xaxis_visible=False,
283
+ legend_ncol=2,
284
+ )
285
+
286
+ timeseries(df, y='WS', c='WD', style='scatter', ax=ax3, scatter_kws=dict(cmap='hsv'),
287
+ cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
288
+ ylim=[0, None], set_xaxis_visible=False)
289
+
290
+ timeseries(df, y='VC', c='PBLH', style='bar', ax=ax4, bar_kws=dict(cmap='Blues'), set_xaxis_visible=False,
291
+ ylim=[0, 5000])
292
+
293
+ timeseries(df, y='PM25', c='PM1/PM25', style='scatter', ax=ax5, ylim=[0, None])
294
+
295
+ plt.show()
296
+
297
+ return fig, ax
298
+
299
+
300
+ if __name__ == '__main__':
301
+ from AeroViz import *
302
+
303
+ df = DataBase('/Users/chanchihyu/NTU/2020能見度計畫/data/All_data.csv')
304
+
305
+ plot.timeseries.timeseries(df,
306
+ y=['Extinction', 'Scattering', 'Absorption'],
307
+ y2=['PBLH'],
308
+ c=['PM25', None, None, None],
309
+ style=['scatter', 'line', 'line', 'line'],
310
+ times=('2020-10-01', '2020-11-30'), ylim=[0, None], ylim2=[0, None], rolling=50,
311
+ inset_kws=dict(bbox_to_anchor=(1.12, 0, 1.2, 1)),
312
+ legend_ncol=4)
313
+
314
+ # timeseries(df, y='WS', c='WD', style='scatter', times=('2020-10-01', '2020-11-30'), scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
315
+ # ylim=[0, None])
316
+
317
+ # timeseries_template(df.loc['2020-09-01':'2020-12-31'])
@@ -0,0 +1,3 @@
1
+ from ._color import Color
2
+ from ._decorator import set_figure
3
+ from ._unit import Unit
@@ -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,74 @@
1
+ from functools import wraps
2
+
3
+ import matplotlib.pyplot as plt
4
+
5
+ __all__ = ['set_figure']
6
+
7
+
8
+ # For more details please see https://matplotlib.org/stable/users/explain/customizing.html
9
+
10
+
11
+ def set_figure(func=None,
12
+ *,
13
+ figsize: tuple | None = None,
14
+ fs: int | None = None,
15
+ fw: str = None,
16
+ autolayout: bool = True
17
+ ):
18
+ def decorator(_func):
19
+ @wraps(_func)
20
+ def wrapper(*args, **kwargs):
21
+ print(f'\t\t Plot: \033[96m{_func.__name__}\033[0m')
22
+
23
+ plt.rcParams['mathtext.fontset'] = 'custom'
24
+ plt.rcParams['mathtext.rm'] = 'Times New Roman'
25
+ plt.rcParams['mathtext.it'] = 'Times New Roman: italic'
26
+ plt.rcParams['mathtext.bf'] = 'Times New Roman: bold'
27
+ plt.rcParams['mathtext.default'] = 'regular'
28
+
29
+ # The font properties used by `text.Text`.
30
+ # The text, annotate, label, title, ticks, are used to create text
31
+ plt.rcParams['font.family'] = 'Times New Roman'
32
+ plt.rcParams['font.weight'] = fw or 'normal'
33
+ plt.rcParams['font.size'] = fs or 8
34
+
35
+ plt.rcParams['axes.titlesize'] = 'large'
36
+ plt.rcParams['axes.titleweight'] = 'bold'
37
+ plt.rcParams['axes.labelweight'] = 'bold'
38
+
39
+ # color
40
+ plt.rcParams['axes.prop_cycle'] = plt.cycler(color=['b', 'g', 'r', 'c', 'm', 'y', 'k'])
41
+
42
+ plt.rcParams['xtick.labelsize'] = 'medium'
43
+ plt.rcParams['ytick.labelsize'] = 'medium'
44
+
45
+ # matplotlib.font_manager.FontProperties ---> matplotlib.rcParams
46
+ plt.rcParams['legend.loc'] = 'best'
47
+ plt.rcParams['legend.frameon'] = False
48
+ plt.rcParams['legend.fontsize'] = 'small'
49
+ plt.rcParams['legend.title_fontsize'] = 'medium'
50
+ plt.rcParams['legend.handlelength'] = 1.5
51
+ plt.rcParams['legend.labelspacing'] = 0.7
52
+
53
+ plt.rcParams['figure.figsize'] = figsize or (5, 4)
54
+ plt.rcParams['figure.dpi'] = 200
55
+ plt.rcParams['figure.autolayout'] = autolayout
56
+
57
+ if ~autolayout:
58
+ plt.rcParams['figure.subplot.left'] = 0.1
59
+ plt.rcParams['figure.subplot.right'] = 0.875
60
+ plt.rcParams['figure.subplot.top'] = 0.875
61
+ plt.rcParams['figure.subplot.bottom'] = 0.125
62
+
63
+ # plt.rcParams['figure.constrained_layout.use'] = True
64
+
65
+ plt.rcParams['savefig.transparent'] = True
66
+
67
+ return _func(*args, **kwargs)
68
+
69
+ return wrapper
70
+
71
+ if func is None:
72
+ return decorator
73
+
74
+ return decorator(func)
@@ -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.")
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ from pandas import read_csv, concat
4
+
5
+ from AeroViz.process.script import (ImpactProc, ImproveProc, ChemicalProc, ParticleSizeDistProc,
6
+ ExtinctionDistProc, OthersProc)
7
+
8
+ __all__ = ['DataProcess']
9
+
10
+
11
+ class DataProcess:
12
+ def __new__(cls, file_path, reset: bool = False, save_file: Path | str = 'All_data.csv'):
13
+ file_path = Path(file_path)
14
+
15
+ print(f'\t\t \033[96m --- Processing Data --- \033[0m')
16
+
17
+ if file_path.exists() and not reset:
18
+ return read_csv(file_path, parse_dates=['Time'], index_col='Time',
19
+ na_values=('-', 'E', 'F'), low_memory=False)
20
+
21
+ processor = [ImpactProc, ChemicalProc, ImproveProc, ParticleSizeDistProc, ExtinctionDistProc, OthersProc]
22
+ reset = [False, False, False, False, False, False]
23
+ save_filename = ['IMPACT.csv', 'chemical.csv', 'revised_IMPROVE.csv', 'PSD.csv', 'PESD.csv', 'Others.csv']
24
+
25
+ _df = concat([processor().process_data(reset, save_filename) for processor, reset, save_filename in
26
+ zip(processor, reset, save_filename)], axis=1)
27
+
28
+ # 7. save result
29
+ _df.to_csv(file_path)
30
+
31
+ return _df
@@ -0,0 +1,19 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+ from pandas import DataFrame
5
+
6
+ __all__ = ['DataProc']
7
+
8
+
9
+ class DataProc(ABC):
10
+ def __init__(self):
11
+ pass
12
+
13
+ @abstractmethod
14
+ def process_data(self,
15
+ reset: bool = False,
16
+ save_filename: str | Path = None
17
+ ) -> DataFrame:
18
+ """ Implementation of processing data """
19
+ pass
@@ -0,0 +1,90 @@
1
+ from typing import Literal
2
+
3
+ import numpy as np
4
+ from pandas import DataFrame
5
+
6
+ __all__ = ['SizeDist']
7
+
8
+
9
+ class SizeDist:
10
+ """
11
+ Attributes
12
+ ----------
13
+
14
+ _data: DataFrame
15
+ The processed PSD data stored as a pandas DataFrame.
16
+
17
+ _dp: ndarray
18
+ The array of particle diameters from the PSD data.
19
+
20
+ _dlogdp: ndarray
21
+ The array of logarithmic particle diameter bin widths.
22
+
23
+ _index: DatetimeIndex
24
+ The index of the DataFrame representing time.
25
+
26
+ _state: str
27
+ The state of particle size distribution data.
28
+
29
+ Methods
30
+ -------
31
+ number()
32
+ Calculate number distribution properties.
33
+
34
+ surface(filename='PSSD_dSdlogdp.csv')
35
+ Calculate surface distribution properties.
36
+
37
+ volume(filename='PVSD_dVdlogdp.csv')
38
+ Calculate volume distribution properties.
39
+
40
+ """
41
+
42
+ def __init__(self,
43
+ data: DataFrame,
44
+ state: Literal['dN', 'ddp', 'dlogdp'] = 'dlogdp',
45
+ weighting: Literal['n', 's', 'v', 'ext_in', 'ext_ex'] = 'n'
46
+ ):
47
+ self._data = data
48
+ self._dp = np.array(self._data.columns, dtype=float)
49
+ self._dlogdp = np.full_like(self._dp, 0.014)
50
+ self._index = self._data.index.copy()
51
+ self._state = state
52
+ self._weighting = weighting
53
+
54
+ @property
55
+ def data(self) -> DataFrame:
56
+ return self._data
57
+
58
+ @property
59
+ def dp(self) -> np.ndarray:
60
+ return self._dp
61
+
62
+ @dp.setter
63
+ def dp(self, new_dp: np.ndarray):
64
+ self._dp = new_dp
65
+
66
+ @property
67
+ def dlogdp(self) -> np.ndarray:
68
+ return self._dlogdp
69
+
70
+ @dlogdp.setter
71
+ def dlogdp(self, new_dlogdp: np.ndarray):
72
+ self._dlogdp = new_dlogdp
73
+
74
+ @property
75
+ def index(self):
76
+ return self._index
77
+
78
+ @property
79
+ def state(self):
80
+ return self._state
81
+
82
+ @state.setter
83
+ def state(self, value):
84
+ if value not in ['dN', 'dlogdp', 'ddp']:
85
+ raise ValueError("state must be 'dlogdp' or 'ddp'")
86
+ self._state = value
87
+
88
+ @property
89
+ def weighting(self):
90
+ return self._weighting
@@ -0,0 +1,4 @@
1
+ from .DataProc import DataProc
2
+ from .SizeDist import SizeDist
3
+
4
+ __all__ = ['DataProc', 'SizeDist']