cannect 1.0.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.
Files changed (58) hide show
  1. cannect/__init__.py +30 -0
  2. cannect/api/__init__.py +0 -0
  3. cannect/config.py +114 -0
  4. cannect/core/__init__.py +0 -0
  5. cannect/core/ascet/__init__.py +3 -0
  6. cannect/core/ascet/amd.py +698 -0
  7. cannect/core/ascet/formula.py +33 -0
  8. cannect/core/ascet/oid.py +29 -0
  9. cannect/core/ascet/ws.py +154 -0
  10. cannect/core/can/__init__.py +2 -0
  11. cannect/core/can/ascet/__init__.py +3 -0
  12. cannect/core/can/ascet/_db2code.py +344 -0
  13. cannect/core/can/ascet/_db2elem.py +399 -0
  14. cannect/core/can/ascet/comdef.py +256 -0
  15. cannect/core/can/ascet/comrx.py +139 -0
  16. cannect/core/can/ascet/diag.py +691 -0
  17. cannect/core/can/db/__init__.py +4 -0
  18. cannect/core/can/db/reader.py +148 -0
  19. cannect/core/can/db/schema.py +269 -0
  20. cannect/core/can/db/specification/__init__.py +0 -0
  21. cannect/core/can/db/specification/message.py +230 -0
  22. cannect/core/can/db/specification/styles.py +81 -0
  23. cannect/core/can/db/specification/wrapper.py +161 -0
  24. cannect/core/can/db/vcs.py +104 -0
  25. cannect/core/can/rule.py +229 -0
  26. cannect/core/can/testcase/__init__.py +0 -0
  27. cannect/core/can/testcase/unitcase/__init__.py +0 -0
  28. cannect/core/can/testcase/unitcase/asw2can.py +48 -0
  29. cannect/core/can/testcase/unitcase/decode.py +63 -0
  30. cannect/core/can/testcase/unitcase/diagnosis.py +479 -0
  31. cannect/core/can/testcase/unitcase/encode.py +60 -0
  32. cannect/core/ir/__init__.py +2 -0
  33. cannect/core/ir/changehistory.py +310 -0
  34. cannect/core/ir/delivereables.py +71 -0
  35. cannect/core/ir/diff.py +97 -0
  36. cannect/core/ir/ir.py +581 -0
  37. cannect/core/ir/sdd.py +148 -0
  38. cannect/core/mdf.py +66 -0
  39. cannect/core/subversion.py +136 -0
  40. cannect/core/testcase/__init__.py +3 -0
  41. cannect/core/testcase/plotter.py +181 -0
  42. cannect/core/testcase/style.py +981 -0
  43. cannect/core/testcase/testcase.py +160 -0
  44. cannect/core/testcase/unitcase.py +227 -0
  45. cannect/errors.py +20 -0
  46. cannect/schema/__init__.py +5 -0
  47. cannect/schema/candb.py +226 -0
  48. cannect/schema/datadictionary.py +60 -0
  49. cannect/utils/__init__.py +3 -0
  50. cannect/utils/excel.py +29 -0
  51. cannect/utils/logger.py +81 -0
  52. cannect/utils/ppt.py +236 -0
  53. cannect/utils/tools.py +207 -0
  54. cannect-1.0.0.dist-info/METADATA +214 -0
  55. cannect-1.0.0.dist-info/RECORD +58 -0
  56. cannect-1.0.0.dist-info/WHEEL +5 -0
  57. cannect-1.0.0.dist-info/licenses/LICENSE +21 -0
  58. cannect-1.0.0.dist-info/top_level.txt +1 -0
cannect/core/ir/sdd.py ADDED
@@ -0,0 +1,148 @@
1
+ from datetime import datetime
2
+ from typing import Any, SupportsBytes, Union
3
+ import os, string
4
+
5
+
6
+ class SddRW:
7
+
8
+ @classmethod
9
+ def _to_rtf(cls, text: str, fallback: str = "?") -> str:
10
+ out = []
11
+ for ch in text:
12
+ if ch in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] or \
13
+ ch in ["[", "]", ".", ",", "-", "_", ">", "<", " "] or \
14
+ ch.lower() in string.ascii_lowercase:
15
+ out.append(ch)
16
+ elif ch == '\n':
17
+ out.append(r'\r\n')
18
+ else:
19
+ code = ord(ch)
20
+ code = str(code)
21
+ out.append(f"\\u{code}{fallback}")
22
+ return "".join(out)
23
+
24
+ @classmethod
25
+ def generate(
26
+ cls,
27
+ path:str,
28
+ name:str,
29
+ oid:str,
30
+ description:str,
31
+ log:str='최초 배포'
32
+ ):
33
+ """
34
+ @path: SDD 노트 생성 상위 경로
35
+ @name: 모델 이름
36
+ @oid : 모델 OID
37
+ @description : 모델 기능 Description
38
+ @log : 최초 로그
39
+ """
40
+ root = os.path.join(path, oid)
41
+ os.makedirs(root, exist_ok=True)
42
+ time = datetime.now().strftime('%Y%m%d %H%M%S')
43
+ with open(os.path.join(root, 'FunctionDefinition.rtf'), 'w', encoding="ansi") as f:
44
+ f.write(r"""{\rtf1\ansi\deff0\uc1\ansicpg949\deftab720{\fonttbl{\f0\fnil\fcharset1 Arial;}{\f1\fnil\fcharset2 Wingdings;}{\f2\fnil\fcharset2 Symbol;}}{\colortbl\red0\green0\blue0;\red255\green0\blue0;\red0\green128\blue0;\red0\green0\blue255;\red255\green255\blue0;\red255\green0\blue255;\red128\green0\blue128;\red128\green0\blue0;\red0\green255\blue0;\red0\green255\blue255;\red0\green128\blue128;\red0\green0\blue128;\red255\green255\blue255;\red192\green192\blue192;\red128\green128\blue128;\red0\green0\blue0;}\wpprheadfoot1\paperw11906\paperh16838\margl567\margr624\margt850\margb850\headery720\footery720\endnhere\sectdefaultcl{\*\generator WPTools_6.250;}{\*\userprops {\propname oid}\proptype30{\staticval 040g1j9410g01q871c90dpcrfer3k}
45
+ {\propname userid}\proptype30{\staticval user}
46
+ {\propname filename}\proptype30{\staticval FunctionDefinition.rtf}
47
+ {\propname createby}\proptype30{\staticval user}
48
+ {\propname createdate}\proptype30{\staticval time}
49
+ {\propname updateby}\proptype30{\staticval user}
50
+ {\propname updatedate}\proptype30{\staticval time}
51
+ }{\plain\f0\fs20 %__NAME__ [00.00.001]\par
52
+ \pard\plain\plain\f0\fs20\par
53
+ \plain\f0\fs20 __DESCRIPTION__\par
54
+ \pard\plain\plain\f0\fs20\par
55
+ \plain\f0\fs20\u9654 ?\u48320 ?\u44221 ?\u45236 ?\u50669 ?\par
56
+ \plain\f0\fs20 [00.00.001] __LOG__\par
57
+ }}""" \
58
+ .replace("user", os.environ.get("USERNAME", "UNKNOWN")) \
59
+ .replace("time", time) \
60
+ .replace("__NAME__", name) \
61
+ .replace("__DESCRIPTION__", cls._to_rtf(description)) \
62
+ .replace("__LOG__", cls._to_rtf(log))
63
+ )
64
+ return
65
+
66
+ def __init__(self, sdd:Union[Any, str, SupportsBytes]):
67
+ self.fullpath = ''
68
+ if sdd.endswith('.rtf'):
69
+ self.fullpath = sdd
70
+ else:
71
+ for path, _, files in os.walk(sdd):
72
+ for file in files:
73
+ if not file == "FunctionDefinition.rtf":
74
+ continue
75
+ self.fullpath = os.path.join(path, file)
76
+ if not self.fullpath:
77
+ raise FileExistsError(f'{sdd} NOT FOUND')
78
+
79
+ self.version_doc = ''
80
+ self._n_doc = -1
81
+ self.version_log = ''
82
+ self.font = r'\f1'
83
+
84
+ self.syntax = []
85
+ with open(self.fullpath, "r", encoding='ansi') as f:
86
+ for n, line in enumerate(f.readlines()):
87
+ if "Arial" in line:
88
+ clip = line[:line.find('Arial')]
89
+ self.font = clip[clip.rfind('{') + 1:][:3]
90
+ if not self.version_doc and "[" in line and "]" in line:
91
+ if r"\f1" in line:
92
+ line = line.replace(r"\f1", "")
93
+ self.version_doc = line[line.find('[') + 1:line.find(']')].replace(" ", "")
94
+ self._n_doc = n
95
+
96
+ if self.version_doc and not self.version_log and "[" in line and "]" in line:
97
+ if r"\f1" in line:
98
+ line = line.replace(r"\f1", "")
99
+ self.version_log = line[line.find('[') + 1:line.find(']')].replace(" ", "")
100
+
101
+ self.syntax.append(line)
102
+ return
103
+
104
+ def update(self, log:str):
105
+
106
+ if self._n_doc == -1:
107
+ return "FAILED TO UPDATE SDD"
108
+ split = self.version_doc.split(".")
109
+ split[-1] = str(int(split[-1]) + 1).zfill(3)
110
+ self.version_doc = ".".join(split)
111
+
112
+ doc = self.syntax[self._n_doc]
113
+ prefix = doc[:doc.find('[') + 1]
114
+ suffix = doc[doc.find(']'): ]
115
+ self.syntax[self._n_doc] = f'{prefix}{self.version_doc}{suffix}'
116
+
117
+ log = self._to_rtf(log)
118
+ syntax = []
119
+ for line in self.syntax:
120
+ if self.version_log in line:
121
+ syntax.append(rf'\wpparid0\plain{self.font}\fs20 [{self.version_doc}] {log} \par' + '\n')
122
+ syntax.append(line)
123
+
124
+ self.version_log = self.version_doc
125
+ with open(self.fullpath, "w", encoding="ansi") as f:
126
+ f.write("".join(syntax))
127
+ return ''
128
+
129
+
130
+ if __name__ == "__main__":
131
+
132
+ sdd = SddReader(
133
+ r'D:\SDD\Notes\Files\040g030000001oo7086g4s4088ajq\FunctionDefinition.rtf'
134
+ # r"D:\Archive\00_프로젝트\2017 통신개발-\2026\DS0114 CR10785688 CNGPIO HS\05_Resources\SDD\040g030000001mo710eg5tbsm1ejc"
135
+ # r"C:\Users\ADMINI~1\AppData\Local\Temp\~cannect\040g030000001oo7086g4s4088ajq"
136
+ )
137
+ print(sdd.version_doc)
138
+ status = sdd.update('testing')
139
+ print(sdd.version_doc)
140
+
141
+
142
+ # SddReader.generate(
143
+ # path=r'D:\SDD\Notes\Files',
144
+ # name='CanFDEMS06',
145
+ # oid='040g030000001oo7086g4s4088ajq',
146
+ # description='EMS_06_100ms 메시지 송신\n(* SDD NOTE MS WORD 수정 금지)',
147
+ # log='최초 배포'
148
+ # )
cannect/core/mdf.py ADDED
@@ -0,0 +1,66 @@
1
+ from mdfreader import Mdf
2
+ from pandas import DataFrame
3
+ import pandas as pd
4
+ import os
5
+
6
+
7
+ class MdfReader(DataFrame):
8
+
9
+ _mdf:Mdf = None
10
+ _src:str = ""
11
+ def __init__(self, file:str):
12
+ self._mdf = dat = Mdf(file)
13
+ self._src = os.path.basename(file)
14
+ frm = pd.concat(objs=self.channels(dat), axis=1)
15
+ frm = frm.sort_index()
16
+ frm = frm.ffill().bfill()
17
+ super().__init__(data=frm.values, index=frm.index, columns=frm.columns)
18
+ for c in self:
19
+ self[c] = self[c].astype(int if "int" in str(dat.get_channel_data(c).dtype) else float)
20
+ return
21
+
22
+ def __repr__(self):
23
+ return repr(self.file)
24
+
25
+ def __str__(self):
26
+ return str(self.file)
27
+
28
+ @property
29
+ def mid(self) -> DataFrame:
30
+ half, quarter = len(self) // 2, len(self) // 4
31
+ return self.iloc[half - quarter: half + quarter] # 중앙 정렬 N/2개의 데이터 샘플 대상
32
+
33
+ @property
34
+ def mdf(self) -> Mdf:
35
+ return self._mdf
36
+
37
+ @property
38
+ def file(self) -> str:
39
+ return self._src
40
+
41
+ @staticmethod
42
+ def channels(dat:Mdf) -> list:
43
+ chns = []
44
+ for chn in dat.masterChannelList.values():
45
+ if chn[-1].startswith("$"):
46
+ continue
47
+ data = {
48
+ "time" if var.startswith("time") else var:dat.get_channel_data(var)
49
+ for var in chn
50
+ }
51
+ data = DataFrame(data=data)
52
+ data.set_index(keys="time", inplace=True)
53
+ chns.append(data)
54
+ return chns
55
+
56
+
57
+
58
+
59
+ if __name__ == "__main__":
60
+ from pandas import set_option
61
+ set_option('display.expand_frame_repr', False)
62
+
63
+ rd = MdfReader(r"\\kefico\keti\ENT\Softroom\Temp\J.H.Lee\00 CR\CR10785931 J1979-2 CAN 진단 대응 ICE CANFD\08_Verification\Data\CanFDABSD_Det_Diag.dat")
64
+ print(rd)
65
+ print(rd.columns)
66
+
@@ -0,0 +1,136 @@
1
+ from cannect.errors import SVNError
2
+ from cannect.utils.tools import path_abbreviate
3
+ from pandas import DataFrame
4
+ from pathlib import Path
5
+ from typing import Callable, Union
6
+ import pandas as pd
7
+ import subprocess, sqlite3
8
+ import os
9
+
10
+
11
+ class Subversion:
12
+
13
+ logger :Callable = print
14
+ silence:bool = False
15
+
16
+ @classmethod
17
+ def read_wcdb(cls, path:Union[Path,str]) -> DataFrame:
18
+ conn = sqlite3.connect(path)
19
+ data = pd.read_sql_query('SELECT * FROM NODES', conn)
20
+ conn.close()
21
+ return data
22
+
23
+ @classmethod
24
+ def commit(cls, path:Union[str, Path], message:str):
25
+ result = subprocess.run(
26
+ ["svn", "commit", path, "-m", message],
27
+ capture_output=True,
28
+ text=True,
29
+ check=True
30
+ )
31
+
32
+ if not cls.silence:
33
+ cls.logger("Commit successful!")
34
+ cls.logger(result.stdout)
35
+ return
36
+
37
+ @classmethod
38
+ def log(cls, path:Union[str, Path]) -> DataFrame:
39
+ """
40
+ :param path: .svn 하위의 파일 또는 폴더
41
+ :return:
42
+ """
43
+ result = subprocess.run(['svn', 'log', path], capture_output=True, text=True)
44
+ if result.returncode != 0:
45
+ raise SVNError(f'FAILED TO GET LOG: {path}')
46
+ text = [e for e in result.stdout.split('\n') if e and (not e.endswith('-'))]
47
+ data = []
48
+ line = ''
49
+ for n, part in enumerate(text):
50
+ if n % 2:
51
+ line = f'{line} | {part}'.split(' | ')
52
+ data.append(line)
53
+ line = ''
54
+ else:
55
+ line += part
56
+ data = DataFrame(data=data)
57
+ data = data.drop(columns=[3]).rename(columns={0: 'revision', 1:'author', 2: 'datetime', 4: 'log'})
58
+ data = data[data['revision'].str.startswith('r')]
59
+ data = data[data["log"].str.startswith('[')]
60
+ data["datetime"] = data["datetime"].apply(lambda x: x[:x.find('+0900') - 1])
61
+ data["log"] = data["log"].apply(lambda x: x.split('] ')[-1])
62
+ return data
63
+
64
+ @classmethod
65
+ def status(cls, path:Union[str, Path]):
66
+ try:
67
+ result = subprocess.run(
68
+ ['svn', 'status', str(path)],
69
+ capture_output=True, text=True
70
+ )
71
+ return result.stdout.strip()
72
+ except subprocess.CalledProcessError as e:
73
+ raise SVNError(f'FAILED TO CHECK STATUS: {path} FOR {e}')
74
+
75
+ @classmethod
76
+ def save_revision_to(cls, file:Union[str, Path], revision:Union[int, str], dst:Union[str, Path]=''):
77
+ """
78
+ :param file:
79
+ :param revision:
80
+ :param dst: 저장할 경로, 미입력 시 SVN 파일과 동일 경로
81
+ :return:
82
+ """
83
+ # svn export -r [리비전] [파일경로/URL] [저장할경로]
84
+ file = Path(file)
85
+ if not dst:
86
+ dst = file.parent
87
+ dst = Path(dst) / file.name.replace(".zip", f"-{revision}.zip")
88
+ command = ['svn', 'export', '-r', str(revision), file, dst, '--force']
89
+
90
+ try:
91
+ subprocess.run(command, check=True, capture_output=True, text=True)
92
+ if not cls.silence:
93
+ cls.logger(f"Save {file.name} as {path_abbreviate(dst.parent)}")
94
+ except subprocess.CalledProcessError as e:
95
+ raise SVNError(f'FAILED TO SAVE REVISION OF {file.name}: {e}')
96
+
97
+ @classmethod
98
+ def update(cls, path:Union[str, Path]) -> str:
99
+ """
100
+ :param path: .svn 하위의 파일 또는 폴더
101
+ :return:
102
+ """
103
+ path = str(path)
104
+ if not "." in os.path.basename(path):
105
+ path += r"\\"
106
+ try:
107
+ result = subprocess.run(
108
+ ['svn', 'update', path],
109
+ capture_output=True,
110
+ text=True,
111
+ check=True
112
+ )
113
+ msg = result.stdout[:-1]
114
+ except subprocess.CalledProcessError as e:
115
+ msg = f"Failed to update SVN repository: '{path}' {e.stderr}"
116
+
117
+ if not cls.silence:
118
+ cls.logger(msg)
119
+ return msg
120
+
121
+
122
+
123
+ if __name__ == "__main__":
124
+ from pandas import set_option
125
+ set_option('display.expand_frame_repr', False)
126
+
127
+ # Subversion.commit(
128
+ # path=r"E:\SVN\dev.bsw\hkmc.ems.bsw.docs\branches\HEPG_Ver1p1\11_ProjectManagement\CAN_Database\dev\G-PROJECT_KEFICO-EMS_CANFD_r21676@01.json",
129
+ # message="[LEE JEHYEUK] CAN DB Commit"
130
+ # )
131
+
132
+ # save_revision(
133
+ # r"E:\SVN\model\ascet\trunk\HNB_GASOLINE\_29_CommunicationVehicle\CANInterface\EMS\Message\CanEMSM_CNG\CanEMSM_CNG.zip",
134
+ # 23000,
135
+ # r"E:\SVN\model\ascet\trunk\HNB_GASOLINE\_29_CommunicationVehicle\CANInterface\EMS\Message\CanEMSM_CNG",
136
+ # )
@@ -0,0 +1,3 @@
1
+ from .testcase import TestCase
2
+ from .unitcase import UnitTestCase
3
+ from .plotter import Plot
@@ -0,0 +1,181 @@
1
+ from cannect.config import env
2
+ from cannect.core.mdf import MdfReader
3
+ from pandas import DataFrame, Series
4
+ from plotly.graph_objs import Figure, Layout, Scatter
5
+ import os
6
+
7
+
8
+ class Plot(Figure):
9
+
10
+ def __init__(
11
+ self,
12
+ data:DataFrame,
13
+ separate:bool=False,
14
+ linewidth:int=2,
15
+ legendfontsize:int=14,
16
+ ):
17
+ super().__init__(
18
+ layout=Layout(
19
+ plot_bgcolor="white", # [str] colors
20
+ hovermode="x unified", # [str] one of ( "x" | "y" | "closest" | False | "x unified" | "y unified" )
21
+ dragmode="zoom", # [str] one of ( "zoom" | "pan" | "select" | "lasso" |
22
+ # "drawclosedpath" | "drawopenpath" | "drawline" |
23
+ # "drawrect" | "drawcircle" | "orbit" | "turntable" | False )
24
+ margin={
25
+ "b": 40, # [int] bottom margin
26
+ "l": 40, # [int] left margin
27
+ "r": 40, # [int] right margin
28
+ "t": 40 # [int] top margin
29
+ },
30
+ legend={
31
+ "font": {
32
+ "size": legendfontsize,
33
+ },
34
+ "bgcolor": "white", # [str]
35
+ "bordercolor": "#444", # [str]
36
+ "borderwidth": 0, # [float]
37
+ "groupclick": "togglegroup", # [str] one of ( "toggleitem" | "togglegroup" )
38
+ "itemclick": "toggle", # [str] one of ( "toggle" | "toggleothers" | False )
39
+ "itemdoubleclick": "toggleothers", # [str | bool] one of ( "toggle" | "toggleothers" | False )
40
+ "itemsizing": "trace", # [str] one of ( "trace" | "constant" )
41
+ "itemwidth": 30, # [int] greater than or equal to 30
42
+ "orientation": "h", # [str] one of ( "v" | "h" )
43
+ "tracegroupgap": 10, # [int] greater than or equal to 0
44
+ "traceorder": "normal", # [str] combination of "normal", "reversed", "grouped" joined with "+"
45
+ "valign": "middle", # [str] one of ( "top" | "middle" | "bottom" )
46
+ "xanchor": "right", # [str] one of ( "auto" | "left" | "center" | "right" )
47
+ "x": 1.0, # [float] 1.02 for "v", 0.96 for "h"
48
+ "yanchor": "top", # [str] one of ( "auto" | "top" | "middle" | "bottom" )
49
+ "y": 1.04, # [float] 1.0 for both "v" and "h",
50
+
51
+ },
52
+ xaxis={
53
+ "autorange": True, # [str | bool] one of ( True | False | "reversed" | "min reversed" |
54
+ # "max reversed" | "min" | "max" )
55
+ "color": "#444", # [str]
56
+ "showgrid": True, # [bool]
57
+ "gridcolor": "lightgrey", # [str]
58
+ "griddash": "solid", # [str] one of ( "solid" | "dot" | "dash" | "longdash" | "dashdot" )
59
+ "gridwidth": 0.5, # [float]
60
+ "showline": True, # [bool]
61
+ "linecolor": "grey", # [str]
62
+ "linewidth": 1, # [float]
63
+ "mirror": False, # [str | bool] one of ( True | "ticks" | False | "all" | "allticks" )
64
+ "rangeslider": {
65
+ "visible": False # [bool]
66
+ },
67
+ "rangeselector": {
68
+ "visible": False, # [bool]
69
+ },
70
+ "showticklabels": True, # [bool]
71
+ "tickformat": "%Y/%m/%d", # [str]
72
+ "zeroline": True, # [bool]
73
+ "zerolinecolor": "lightgrey", # [str]
74
+ "zerolinewidth": 1, # [float]
75
+ "hoverformat": ".3f"
76
+ },
77
+ yaxis={
78
+ "autorange": True, # [str | bool] one of ( True | False | "reversed" | "min reversed" |
79
+ # "max reversed" | "min" | "max" )
80
+ "color": "#444", # [str]
81
+ "showgrid": True, # [bool]
82
+ "gridcolor": "lightgrey", # [str]
83
+ "griddash": "solid", # [str] one of ( "solid" | "dot" | "dash" | "longdash" | "dashdot" )
84
+ "gridwidth": 0.5, # [float]
85
+ "showline": True, # [bool]
86
+ "linecolor": "grey", # [str]
87
+ "linewidth": 1, # [float]
88
+ "mirror": False, # [str | bool] one of ( True | "ticks" | False | "all" | "allticks" )
89
+ "showticklabels": True, # [bool]
90
+ "zeroline": True, # [bool]
91
+ "zerolinecolor": "lightgrey", # [str]
92
+ "zerolinewidth": 1 # [float]
93
+ }
94
+ )
95
+ )
96
+
97
+ if separate:
98
+ self.set_subplots(
99
+ rows=max(len(data.columns), 1),
100
+ cols=1,
101
+ shared_xaxes=True,
102
+ vertical_spacing=0.01
103
+ )
104
+
105
+ for n, col in enumerate(data, start=1):
106
+ series = data[col]
107
+ if separate:
108
+ self.add_trace(self.trace(series, linewidth), row=n, col=1)
109
+ else:
110
+ self.add_trace(self.trace(series, linewidth))
111
+
112
+ if separate:
113
+ for axis in self['layout']:
114
+ if axis.startswith('xaxis') or axis.startswith('yaxis'):
115
+ self['layout'][axis].update({
116
+ "showgrid": True, # [bool]
117
+ "gridcolor": "lightgrey", # [str]
118
+ "griddash": "solid", # [str] one of ( "solid" | "dot" | "dash" | "longdash" | "dashdot" )
119
+ "gridwidth": 0.5, # [float]
120
+ "linecolor": "grey", # [str]
121
+ "linewidth": 1, # [float]
122
+ "mirror": False,
123
+ "zeroline": True, # [bool]
124
+ "zerolinecolor": "lightgrey", # [str]
125
+ "zerolinewidth": 1, # [float]
126
+ })
127
+
128
+ # self.layout.legend.font.size = legendfontsize
129
+ # figure = Figure(layout=self.layout)
130
+ # if separate:
131
+ # self.subplot['rows'] = len(data.columns)
132
+ # figure.set_subplots(**self.subplot)
133
+ # else:
134
+ # self.layout.xaxis.title = "Time[s]"
135
+ #
136
+ # for n, col in enumerate(data, start=1):
137
+ # series = data[col]
138
+ # if separate:
139
+ # figure.add_trace(self.trace(series, linewidth), row=n, col=1)
140
+ # else:
141
+ # figure.add_trace(self.trace(series, linewidth))
142
+ #
143
+ # if separate:
144
+ # for axis in figure['layout']:
145
+ # if axis.startswith('xaxis') or axis.startswith('yaxis'):
146
+ # figure['layout'][axis].update(**self.multiaxis)
147
+ #
148
+ #
149
+ # self.figure = figure
150
+ return
151
+
152
+ @classmethod
153
+ def trace(cls, series:Series, linewidth:int) -> Scatter:
154
+ return Scatter(
155
+ name=series.name,
156
+ x=series.index,
157
+ y=series,
158
+ mode='lines',
159
+ line={
160
+ 'width':linewidth
161
+ },
162
+ showlegend=True,
163
+ hovertemplate=f'{series.name}: %{{y}}<extra></extra>'
164
+ )
165
+
166
+ def save(self, filename:str='') -> str:
167
+ if not filename:
168
+ filename = self.filename
169
+ file = str(env.DOWNLOADS / f"{filename}")
170
+ self.write_image(
171
+ file=file,
172
+ width=1920,
173
+ height=1080
174
+ )
175
+ return file
176
+
177
+ if __name__ == "__main__":
178
+ from pandas import set_option
179
+ set_option('display.expand_frame_repr', False)
180
+
181
+