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.
- cannect/__init__.py +30 -0
- cannect/api/__init__.py +0 -0
- cannect/config.py +114 -0
- cannect/core/__init__.py +0 -0
- cannect/core/ascet/__init__.py +3 -0
- cannect/core/ascet/amd.py +698 -0
- cannect/core/ascet/formula.py +33 -0
- cannect/core/ascet/oid.py +29 -0
- cannect/core/ascet/ws.py +154 -0
- cannect/core/can/__init__.py +2 -0
- cannect/core/can/ascet/__init__.py +3 -0
- cannect/core/can/ascet/_db2code.py +344 -0
- cannect/core/can/ascet/_db2elem.py +399 -0
- cannect/core/can/ascet/comdef.py +256 -0
- cannect/core/can/ascet/comrx.py +139 -0
- cannect/core/can/ascet/diag.py +691 -0
- cannect/core/can/db/__init__.py +4 -0
- cannect/core/can/db/reader.py +148 -0
- cannect/core/can/db/schema.py +269 -0
- cannect/core/can/db/specification/__init__.py +0 -0
- cannect/core/can/db/specification/message.py +230 -0
- cannect/core/can/db/specification/styles.py +81 -0
- cannect/core/can/db/specification/wrapper.py +161 -0
- cannect/core/can/db/vcs.py +104 -0
- cannect/core/can/rule.py +229 -0
- cannect/core/can/testcase/__init__.py +0 -0
- cannect/core/can/testcase/unitcase/__init__.py +0 -0
- cannect/core/can/testcase/unitcase/asw2can.py +48 -0
- cannect/core/can/testcase/unitcase/decode.py +63 -0
- cannect/core/can/testcase/unitcase/diagnosis.py +479 -0
- cannect/core/can/testcase/unitcase/encode.py +60 -0
- cannect/core/ir/__init__.py +2 -0
- cannect/core/ir/changehistory.py +310 -0
- cannect/core/ir/delivereables.py +71 -0
- cannect/core/ir/diff.py +97 -0
- cannect/core/ir/ir.py +581 -0
- cannect/core/ir/sdd.py +148 -0
- cannect/core/mdf.py +66 -0
- cannect/core/subversion.py +136 -0
- cannect/core/testcase/__init__.py +3 -0
- cannect/core/testcase/plotter.py +181 -0
- cannect/core/testcase/style.py +981 -0
- cannect/core/testcase/testcase.py +160 -0
- cannect/core/testcase/unitcase.py +227 -0
- cannect/errors.py +20 -0
- cannect/schema/__init__.py +5 -0
- cannect/schema/candb.py +226 -0
- cannect/schema/datadictionary.py +60 -0
- cannect/utils/__init__.py +3 -0
- cannect/utils/excel.py +29 -0
- cannect/utils/logger.py +81 -0
- cannect/utils/ppt.py +236 -0
- cannect/utils/tools.py +207 -0
- cannect-1.0.0.dist-info/METADATA +214 -0
- cannect-1.0.0.dist-info/RECORD +58 -0
- cannect-1.0.0.dist-info/WHEEL +5 -0
- cannect-1.0.0.dist-info/licenses/LICENSE +21 -0
- 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,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
|
+
|