absfuyu 5.0.1__py3-none-any.whl → 5.2.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 absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +1 -1
- absfuyu/__main__.py +3 -3
- absfuyu/cli/__init__.py +2 -2
- absfuyu/cli/color.py +30 -14
- absfuyu/cli/config_group.py +9 -2
- absfuyu/cli/do_group.py +13 -6
- absfuyu/cli/game_group.py +9 -2
- absfuyu/cli/tool_group.py +15 -9
- absfuyu/config/__init__.py +2 -2
- absfuyu/core/__init__.py +2 -2
- absfuyu/core/baseclass.py +448 -79
- absfuyu/core/baseclass2.py +2 -2
- absfuyu/core/decorator.py +70 -4
- absfuyu/core/docstring.py +43 -25
- absfuyu/core/dummy_cli.py +2 -2
- absfuyu/core/dummy_func.py +15 -4
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/dictext.py +5 -2
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +34 -3
- absfuyu/dxt/listext.py +300 -113
- absfuyu/dxt/strext.py +75 -15
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +36 -0
- absfuyu/extra/da/dadf.py +1177 -0
- absfuyu/extra/da/dadf_base.py +186 -0
- absfuyu/extra/da/df_func.py +97 -0
- absfuyu/extra/da/mplt.py +219 -0
- absfuyu/extra/data_analysis.py +10 -1067
- absfuyu/fun/__init__.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -3
- absfuyu/game/wordle.py +2 -2
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +2 -2
- absfuyu/general/shape.py +2 -2
- absfuyu/logger.py +2 -2
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/sort.py +2 -2
- absfuyu/tools/__init__.py +28 -2
- absfuyu/tools/checksum.py +27 -7
- absfuyu/tools/converter.py +120 -34
- absfuyu/tools/generator.py +251 -110
- absfuyu/tools/inspector.py +463 -0
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +45 -7
- absfuyu/tools/passwordlib.py +88 -24
- absfuyu/tools/shutdownizer.py +2 -2
- absfuyu/tools/web.py +2 -2
- absfuyu/typings.py +136 -0
- absfuyu/util/__init__.py +18 -4
- absfuyu/util/api.py +36 -16
- absfuyu/util/json_method.py +43 -14
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/path.py +190 -82
- absfuyu/util/performance.py +122 -7
- absfuyu/util/shorten_number.py +40 -10
- absfuyu/util/text_table.py +306 -0
- absfuyu/util/zipped.py +8 -7
- absfuyu/version.py +2 -2
- {absfuyu-5.0.1.dist-info → absfuyu-5.2.0.dist-info}/METADATA +9 -2
- absfuyu-5.2.0.dist-info/RECORD +76 -0
- absfuyu-5.0.1.dist-info/RECORD +0 -68
- {absfuyu-5.0.1.dist-info → absfuyu-5.2.0.dist-info}/WHEEL +0 -0
- {absfuyu-5.0.1.dist-info → absfuyu-5.2.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.0.1.dist-info → absfuyu-5.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Data Analysis
|
|
3
|
+
----------------------
|
|
4
|
+
Data Analyst DataFrame - Base/Core
|
|
5
|
+
|
|
6
|
+
Version: 5.2.0
|
|
7
|
+
Date updated: 10/03/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["DataAnalystDataFrameBase", "SplittedDF", "CityData"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
import random
|
|
18
|
+
from collections import deque
|
|
19
|
+
from typing import ClassVar, Literal, NamedTuple
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Class
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
class DataAnalystDataFrameBase(pd.DataFrame):
|
|
27
|
+
"""
|
|
28
|
+
Data Analyst ``pd.DataFrame`` - Base
|
|
29
|
+
|
|
30
|
+
Set class variable ``_DADF_INCLUDE`` to ``False`` to exclude from ``DADF_METHODS``
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Custom attribute
|
|
34
|
+
_DADF_INCLUDE: ClassVar[bool] = True # Include in DADF_METHODS
|
|
35
|
+
DADF_METHODS: ClassVar[dict[str, list[str]]] = {}
|
|
36
|
+
|
|
37
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
38
|
+
"""
|
|
39
|
+
This create a dictionary with:
|
|
40
|
+
- key (str) : Subclass
|
|
41
|
+
- value (list[str]): List of available methods
|
|
42
|
+
"""
|
|
43
|
+
super().__init_subclass__(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
if cls._DADF_INCLUDE and not any(
|
|
46
|
+
[x.endswith(cls.__name__) for x in cls.DADF_METHODS.keys()]
|
|
47
|
+
):
|
|
48
|
+
# if not any([x.endswith(cls.__name__) for x in cls.DADF_METHODS.keys()]):
|
|
49
|
+
methods_list: list[str] = [
|
|
50
|
+
k for k, v in cls.__dict__.items() if callable(v)
|
|
51
|
+
]
|
|
52
|
+
if len(methods_list) > 0:
|
|
53
|
+
name = f"{cls.__module__}.{cls.__name__}"
|
|
54
|
+
cls.DADF_METHODS.update({name: sorted(methods_list)})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SplittedDF(NamedTuple):
|
|
58
|
+
"""
|
|
59
|
+
DataFrame splitted into contains
|
|
60
|
+
missing values only and vice versa
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
df : DataFrame
|
|
65
|
+
DataFrame without missing values
|
|
66
|
+
|
|
67
|
+
df_na : DataFrame
|
|
68
|
+
DataFrame with missing values only
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
df: pd.DataFrame
|
|
72
|
+
df_na: pd.DataFrame
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def concat_df(
|
|
76
|
+
df_list: list[pd.DataFrame], join: Literal["inner", "outer"] = "inner"
|
|
77
|
+
) -> pd.DataFrame:
|
|
78
|
+
"""
|
|
79
|
+
Concat the list of DataFrame (static method)
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
df_list : list[DataFrame]
|
|
84
|
+
A sequence of DataFrame
|
|
85
|
+
|
|
86
|
+
join : str
|
|
87
|
+
Join type
|
|
88
|
+
(Default: ``"inner"``)
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
DataFrame
|
|
93
|
+
Joined DataFrame
|
|
94
|
+
"""
|
|
95
|
+
df: pd.DataFrame = pd.concat(df_list, axis=0, join=join).reset_index()
|
|
96
|
+
df.drop(columns=["index"], inplace=True)
|
|
97
|
+
return df
|
|
98
|
+
|
|
99
|
+
def concat(self, join: Literal["inner", "outer"] = "inner") -> pd.DataFrame:
|
|
100
|
+
"""
|
|
101
|
+
Concat the splitted DataFrame
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
join : str
|
|
106
|
+
Join type
|
|
107
|
+
(Default: ``"inner"``)
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
DataFrame
|
|
112
|
+
Joined DataFrame
|
|
113
|
+
"""
|
|
114
|
+
return self.concat_df(self, join=join) # type: ignore
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def divide_dataframe(df: pd.DataFrame, by_column: str) -> list[pd.DataFrame]:
|
|
118
|
+
"""
|
|
119
|
+
Divide DataFrame into a list of DataFrame
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
df : DataFrame
|
|
124
|
+
DataFrame
|
|
125
|
+
|
|
126
|
+
by_column : str
|
|
127
|
+
By which column
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
list[DataFrame]
|
|
132
|
+
Splitted DataFrame
|
|
133
|
+
"""
|
|
134
|
+
divided = [x for _, x in df.groupby(by_column)]
|
|
135
|
+
return divided
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class CityData(NamedTuple):
|
|
139
|
+
"""
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
city : str
|
|
143
|
+
City name
|
|
144
|
+
|
|
145
|
+
region : str
|
|
146
|
+
Region of the city
|
|
147
|
+
|
|
148
|
+
area : str
|
|
149
|
+
Area of the region
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
city: str
|
|
153
|
+
region: str
|
|
154
|
+
area: str
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _sample_city_data(size: int = 100):
|
|
158
|
+
"""
|
|
159
|
+
Generate sample city data (testing purpose)
|
|
160
|
+
"""
|
|
161
|
+
sample_range = 10 ** len(str(size))
|
|
162
|
+
|
|
163
|
+
# Serial list
|
|
164
|
+
serials: list[str] = []
|
|
165
|
+
while len(serials) != size: # Unique serial
|
|
166
|
+
serial = random.randint(0, sample_range - 1)
|
|
167
|
+
serial = str(serial).rjust(len(str(size)), "0") # type: ignore
|
|
168
|
+
if serial not in serials: # type: ignore
|
|
169
|
+
serials.append(serial) # type: ignore
|
|
170
|
+
|
|
171
|
+
ss2 = deque(serials[: int(len(serials) / 2)]) # Cut half for region
|
|
172
|
+
ss2.rotate(random.randrange(1, 5))
|
|
173
|
+
[ss2.extend(ss2) for _ in range(2)] # type: ignore # Extend back
|
|
174
|
+
|
|
175
|
+
ss3 = deque(serials[: int(len(serials) / 4)]) # Cut forth for area
|
|
176
|
+
ss3.rotate(random.randrange(1, 5))
|
|
177
|
+
[ss3.extend(ss3) for _ in range(4)] # type: ignore # Extend back
|
|
178
|
+
|
|
179
|
+
serials = ["city_" + x for x in serials]
|
|
180
|
+
ss2 = ["region_" + x for x in ss2] # type: ignore
|
|
181
|
+
ss3 = ["area_" + x for x in ss3] # type: ignore
|
|
182
|
+
|
|
183
|
+
ss = list(zip(serials, ss2, ss3)) # Zip back
|
|
184
|
+
out = list(map(CityData._make, ss))
|
|
185
|
+
|
|
186
|
+
return out
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Data Analysis
|
|
3
|
+
----------------------
|
|
4
|
+
DF Function
|
|
5
|
+
|
|
6
|
+
Version: 5.2.0
|
|
7
|
+
Date updated: 10/03/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["equalize_df", "compare_2_list", "rename_with_dict"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
from itertools import chain
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Function
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
def equalize_df(data: dict[str, list], fillna=np.nan) -> dict[str, list]:
|
|
26
|
+
"""
|
|
27
|
+
Make all list in dict have equal length to make pd.DataFrame
|
|
28
|
+
|
|
29
|
+
:param data: `dict` data that ready for `pd.DataFrame`
|
|
30
|
+
:param fillna: Fill N/A value (Default: `np.nan`)
|
|
31
|
+
"""
|
|
32
|
+
max_len = max(map(len, data.values()))
|
|
33
|
+
for _, v in data.items():
|
|
34
|
+
if len(v) < max_len:
|
|
35
|
+
missings = max_len - len(v)
|
|
36
|
+
for _ in range(missings):
|
|
37
|
+
v.append(fillna)
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def compare_2_list(*arr) -> pd.DataFrame:
|
|
42
|
+
"""
|
|
43
|
+
Compare 2 lists then create DataFrame
|
|
44
|
+
to see which items are missing
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
arr : list
|
|
49
|
+
List
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
DataFrame
|
|
54
|
+
Compare result
|
|
55
|
+
"""
|
|
56
|
+
# Setup
|
|
57
|
+
col_name = "list"
|
|
58
|
+
arr = [sorted(x) for x in arr] # type: ignore # map(sorted, arr)
|
|
59
|
+
|
|
60
|
+
# Total array
|
|
61
|
+
tarr = sorted(list(set(chain.from_iterable(arr))))
|
|
62
|
+
# max_len = len(tarr)
|
|
63
|
+
|
|
64
|
+
# Temp dataset
|
|
65
|
+
temp_dict = {"base": tarr}
|
|
66
|
+
for idx, x in enumerate(arr):
|
|
67
|
+
name = f"{col_name}{idx}"
|
|
68
|
+
|
|
69
|
+
# convert list
|
|
70
|
+
temp = [item if item in x else np.nan for item in tarr]
|
|
71
|
+
|
|
72
|
+
temp_dict.setdefault(name, temp)
|
|
73
|
+
|
|
74
|
+
df = pd.DataFrame(temp_dict)
|
|
75
|
+
df["Compare"] = np.where(
|
|
76
|
+
df[f"{col_name}0"].apply(lambda x: str(x).lower())
|
|
77
|
+
== df[f"{col_name}1"].apply(lambda x: str(x).lower()),
|
|
78
|
+
df[f"{col_name}0"], # Value when True
|
|
79
|
+
np.nan, # Value when False
|
|
80
|
+
)
|
|
81
|
+
return df
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def rename_with_dict(df: pd.DataFrame, col: str, rename_dict: dict) -> pd.DataFrame:
|
|
85
|
+
"""
|
|
86
|
+
Version: 2.0.0
|
|
87
|
+
|
|
88
|
+
:param df: DataFrame
|
|
89
|
+
:param col: Column name
|
|
90
|
+
:param rename_dict: Rename dictionary
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
name = f"{col}_filtered"
|
|
94
|
+
df[name] = df[col]
|
|
95
|
+
rename_val = list(rename_dict.keys())
|
|
96
|
+
df[name] = df[name].apply(lambda x: "Other" if x in rename_val else x)
|
|
97
|
+
return df
|
absfuyu/extra/da/mplt.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Data Analysis
|
|
3
|
+
----------------------
|
|
4
|
+
Matplotlib Helper
|
|
5
|
+
|
|
6
|
+
Version: 5.2.0
|
|
7
|
+
Date updated: 10/03/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["MatplotlibFormatString", "_PLTFormatString"]
|
|
13
|
+
|
|
14
|
+
# Library
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
import random
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from itertools import product
|
|
19
|
+
from typing import ClassVar, Self
|
|
20
|
+
|
|
21
|
+
from absfuyu.core.baseclass import AutoREPRMixin, BaseClass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Class
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
class _PLTFormatString(AutoREPRMixin):
|
|
27
|
+
"""
|
|
28
|
+
Matplotlib format string
|
|
29
|
+
|
|
30
|
+
Available format:
|
|
31
|
+
- ``<marker><line><color>``
|
|
32
|
+
- ``<color><marker><line>``
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__slots__ = (
|
|
36
|
+
"marker",
|
|
37
|
+
"line_style",
|
|
38
|
+
"color",
|
|
39
|
+
"_marker_fullname",
|
|
40
|
+
"_line_style_fullname",
|
|
41
|
+
"_color_fullname",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
marker: str,
|
|
47
|
+
line_style: str,
|
|
48
|
+
color: str,
|
|
49
|
+
*,
|
|
50
|
+
marker_fullname: str | None = None,
|
|
51
|
+
line_style_fullname: str | None = None,
|
|
52
|
+
color_fullname: str | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Matplotlib format string
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
marker : str
|
|
60
|
+
Maker
|
|
61
|
+
|
|
62
|
+
line_style : str
|
|
63
|
+
Line style
|
|
64
|
+
|
|
65
|
+
color : str
|
|
66
|
+
Color
|
|
67
|
+
|
|
68
|
+
marker_fullname : str | None, optional
|
|
69
|
+
Maker fullname, by default ``None``
|
|
70
|
+
|
|
71
|
+
line_style_fullname : str | None, optional
|
|
72
|
+
Line style fullname, by default ``None``
|
|
73
|
+
|
|
74
|
+
color_fullname : str | None, optional
|
|
75
|
+
Color fullname, by default ``None``
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self.marker = marker
|
|
79
|
+
self.line_style = line_style
|
|
80
|
+
self.color = color
|
|
81
|
+
|
|
82
|
+
self._marker_fullname = marker_fullname
|
|
83
|
+
self._line_style_fullname = line_style_fullname
|
|
84
|
+
self._color_fullname = color_fullname
|
|
85
|
+
|
|
86
|
+
def __str__(self) -> str:
|
|
87
|
+
return self.fstr
|
|
88
|
+
|
|
89
|
+
def __format__(self, format_spec: str) -> str:
|
|
90
|
+
if format_spec.lower() == "full":
|
|
91
|
+
if (
|
|
92
|
+
self._marker_fullname is None
|
|
93
|
+
or self._line_style_fullname is None
|
|
94
|
+
or self._color_fullname is None
|
|
95
|
+
):
|
|
96
|
+
return self.__str__()
|
|
97
|
+
clsname = self.__class__.__name__
|
|
98
|
+
return (
|
|
99
|
+
f"{clsname}(marker={repr(self._marker_fullname)}"
|
|
100
|
+
f", line_style={repr(self._line_style_fullname)}"
|
|
101
|
+
f", color={repr(self._color_fullname)})"
|
|
102
|
+
)
|
|
103
|
+
return super().__format__(format_spec)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def fstr(self) -> str:
|
|
107
|
+
"""Format string"""
|
|
108
|
+
return f"{self.marker}{self.line_style}{self.color}"
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def alternate(self) -> str:
|
|
112
|
+
"""Alternative version of format string"""
|
|
113
|
+
return f"{self.color}{self.marker}{self.line_style}"
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def _make(cls, iterable: Sequence[str]) -> Self:
|
|
117
|
+
if len(iterable) not in (3, 6):
|
|
118
|
+
raise ValueError("iterable must have a length of 3 or 6")
|
|
119
|
+
try: # Len 6
|
|
120
|
+
return cls(
|
|
121
|
+
marker=iterable[0],
|
|
122
|
+
line_style=iterable[1],
|
|
123
|
+
color=iterable[2],
|
|
124
|
+
marker_fullname=iterable[3],
|
|
125
|
+
line_style_fullname=iterable[4],
|
|
126
|
+
color_fullname=iterable[5],
|
|
127
|
+
)
|
|
128
|
+
except IndexError: # Len 3
|
|
129
|
+
return cls(iterable[0], iterable[1], iterable[2])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class MatplotlibFormatString(BaseClass):
|
|
133
|
+
"""
|
|
134
|
+
Matplotlib format string
|
|
135
|
+
|
|
136
|
+
Available format:
|
|
137
|
+
- ``<marker><line><color>``
|
|
138
|
+
- ``<color><marker><line>``
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
MARKER_DATA: ClassVar[dict[str, str]] = {
|
|
142
|
+
".": "point marker",
|
|
143
|
+
",": "pixel marker",
|
|
144
|
+
"o": "circle marker",
|
|
145
|
+
"v": "triangle_down marker",
|
|
146
|
+
"^": "triangle_up marker",
|
|
147
|
+
"<": "triangle_left marker",
|
|
148
|
+
">": "triangle_right marker",
|
|
149
|
+
"1": "tri_down marker",
|
|
150
|
+
"2": "tri_up marker",
|
|
151
|
+
"3": "tri_left marker",
|
|
152
|
+
"4": "tri_right marker",
|
|
153
|
+
"8": "octagon marker",
|
|
154
|
+
"s": "square marker",
|
|
155
|
+
"p": "pentagon marker",
|
|
156
|
+
"P": "plus (filled) marker",
|
|
157
|
+
"*": "star marker",
|
|
158
|
+
"h": "hexagon1 marker",
|
|
159
|
+
"H": "hexagon2 marker",
|
|
160
|
+
"+": "plus marker",
|
|
161
|
+
"x": "x marker",
|
|
162
|
+
"X": "x (filled) marker",
|
|
163
|
+
"D": "diamond marker",
|
|
164
|
+
"d": "thin_diamond marker",
|
|
165
|
+
"|": "vline marker",
|
|
166
|
+
"_": "hline marker",
|
|
167
|
+
}
|
|
168
|
+
LINE_STYLE_DATA: ClassVar[dict[str, str]] = {
|
|
169
|
+
"-": "solid line style",
|
|
170
|
+
"--": "dashed line style",
|
|
171
|
+
"-.": "dash-dot line style",
|
|
172
|
+
":": "dotted line style",
|
|
173
|
+
}
|
|
174
|
+
COLOR_DATA: ClassVar[dict[str, str]] = {
|
|
175
|
+
"b": "blue",
|
|
176
|
+
"g": "green",
|
|
177
|
+
"r": "red",
|
|
178
|
+
"c": "cyan",
|
|
179
|
+
"m": "magenta",
|
|
180
|
+
"y": "yellow",
|
|
181
|
+
"k": "black",
|
|
182
|
+
"w": "white",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def all_format_string(cls) -> list[_PLTFormatString]:
|
|
187
|
+
# This return full list without full name
|
|
188
|
+
# return [
|
|
189
|
+
# _PLTFormatString._make(x)
|
|
190
|
+
# for x in product(cls.MARKER_DATA, cls.LINE_STYLE_DATA, cls.COLOR_DATA)
|
|
191
|
+
# ]
|
|
192
|
+
|
|
193
|
+
# This return full list with full name
|
|
194
|
+
def convert(
|
|
195
|
+
x: tuple[tuple[str, str], tuple[str, str], tuple[str, str]],
|
|
196
|
+
) -> tuple[str, str, str, str, str, str]:
|
|
197
|
+
return (x[0][0], x[1][0], x[2][0], x[0][1], x[1][1], x[2][1])
|
|
198
|
+
|
|
199
|
+
return [
|
|
200
|
+
_PLTFormatString._make(convert(x))
|
|
201
|
+
for x in product(
|
|
202
|
+
cls.MARKER_DATA.items(),
|
|
203
|
+
cls.LINE_STYLE_DATA.items(),
|
|
204
|
+
cls.COLOR_DATA.items(),
|
|
205
|
+
)
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def get_random(cls) -> _PLTFormatString:
|
|
210
|
+
"""
|
|
211
|
+
Get a random format string
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
str
|
|
216
|
+
Random format string
|
|
217
|
+
"""
|
|
218
|
+
random_fmtstr = random.choice(cls.all_format_string())
|
|
219
|
+
return random_fmtstr
|