SankeyExcelParser 1.0.0b0__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.
- SankeyExcelParser/__init__.py +0 -0
- SankeyExcelParser/io_excel.py +1867 -0
- SankeyExcelParser/io_excel_constants.py +811 -0
- SankeyExcelParser/sankey.py +3138 -0
- SankeyExcelParser/sankey_utils/__init__.py +0 -0
- SankeyExcelParser/sankey_utils/data.py +1118 -0
- SankeyExcelParser/sankey_utils/excel_source.py +31 -0
- SankeyExcelParser/sankey_utils/flux.py +344 -0
- SankeyExcelParser/sankey_utils/functions.py +278 -0
- SankeyExcelParser/sankey_utils/node.py +340 -0
- SankeyExcelParser/sankey_utils/protos/__init__.py +0 -0
- SankeyExcelParser/sankey_utils/protos/flux.py +84 -0
- SankeyExcelParser/sankey_utils/protos/node.py +386 -0
- SankeyExcelParser/sankey_utils/protos/sankey_object.py +135 -0
- SankeyExcelParser/sankey_utils/protos/tag_group.py +95 -0
- SankeyExcelParser/sankey_utils/sankey_object.py +165 -0
- SankeyExcelParser/sankey_utils/table_object.py +37 -0
- SankeyExcelParser/sankey_utils/tag.py +95 -0
- SankeyExcelParser/sankey_utils/tag_group.py +206 -0
- SankeyExcelParser/su_trace.py +239 -0
- SankeyExcelParser/tests/integration/__init__.py +0 -0
- SankeyExcelParser/tests/integration/test_base.py +356 -0
- SankeyExcelParser/tests/integration/test_run_check_input.py +100 -0
- SankeyExcelParser/tests/integration/test_run_conversions.py +96 -0
- SankeyExcelParser/tests/integration/test_run_load_input.py +94 -0
- SankeyExcelParser/tests/unit/__init__.py +0 -0
- SankeyExcelParser-1.0.0b0.data/scripts/run_parse_and_write_excel.py +155 -0
- SankeyExcelParser-1.0.0b0.data/scripts/run_parse_excel.py +115 -0
- SankeyExcelParser-1.0.0b0.dist-info/METADATA +113 -0
- SankeyExcelParser-1.0.0b0.dist-info/RECORD +32 -0
- SankeyExcelParser-1.0.0b0.dist-info/WHEEL +5 -0
- SankeyExcelParser-1.0.0b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
"""
|
2
|
+
Author : Vincent LE DOZE
|
3
|
+
Date : 31/05/23
|
4
|
+
|
5
|
+
This file contains description for ExcelSource class
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
# External libs ----------------------------------------------------------------
|
10
|
+
import pandas as pd
|
11
|
+
|
12
|
+
|
13
|
+
# CLASS ----------------------------------------------------------------------------
|
14
|
+
class ExcelSource(object):
|
15
|
+
user_sheet_name: str
|
16
|
+
sheet_type: str
|
17
|
+
_table: pd.DataFrame
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
user_sheet_name: str,
|
22
|
+
sheet_type: str,
|
23
|
+
):
|
24
|
+
self.user_sheet_name = user_sheet_name
|
25
|
+
self.sheet_type = sheet_type
|
26
|
+
|
27
|
+
def save_origin_table(
|
28
|
+
self,
|
29
|
+
table: pd.DataFrame
|
30
|
+
):
|
31
|
+
self.table = table
|
@@ -0,0 +1,344 @@
|
|
1
|
+
"""
|
2
|
+
Author : Vincent LE DOZE
|
3
|
+
Date : 31/05/23
|
4
|
+
|
5
|
+
This file contains descriptions for Flux class
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
# Local modules -----------------------------------------------------
|
10
|
+
from SankeyExcelParser.sankey_utils.protos.flux import _ProtoFlux
|
11
|
+
from SankeyExcelParser.sankey_utils.data import Data
|
12
|
+
from SankeyExcelParser.sankey_utils.data import DataConstraint
|
13
|
+
from SankeyExcelParser.sankey_utils.data import DataMinMax
|
14
|
+
from SankeyExcelParser.sankey_utils.data import MCData
|
15
|
+
from SankeyExcelParser.sankey_utils.node import Node
|
16
|
+
|
17
|
+
|
18
|
+
# CLASS ----------------------------------------------------------------------------
|
19
|
+
class Flux(_ProtoFlux):
|
20
|
+
"""
|
21
|
+
Define a flux.
|
22
|
+
Inherits from `_ProtoFlux`
|
23
|
+
|
24
|
+
Parameters
|
25
|
+
----------
|
26
|
+
:param orig: Flux starting node
|
27
|
+
:type orig: Node
|
28
|
+
|
29
|
+
:param dest: Flux ending node
|
30
|
+
:type dest: Node
|
31
|
+
|
32
|
+
:param datas: All datas for the flux
|
33
|
+
:type datas: list [Data, ...]
|
34
|
+
|
35
|
+
:param results: All results for the flux
|
36
|
+
:type results: list [Data, ...]
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
orig: Node,
|
42
|
+
dest: Node,
|
43
|
+
**kwargs
|
44
|
+
):
|
45
|
+
# Init super constructor
|
46
|
+
_ProtoFlux.__init__(self, orig, dest)
|
47
|
+
self._orig.add_output_flux(self)
|
48
|
+
self._dest.add_input_flux(self)
|
49
|
+
# Datas
|
50
|
+
self._datatags_combinations = []
|
51
|
+
self._datas = []
|
52
|
+
self._results = []
|
53
|
+
self._monte_carlo = None
|
54
|
+
# Init contraints values for all datas
|
55
|
+
self._min_max = DataMinMax(self)
|
56
|
+
self._max = None
|
57
|
+
self._constraints = {}
|
58
|
+
# Update values
|
59
|
+
self.update(**kwargs)
|
60
|
+
|
61
|
+
def update(
|
62
|
+
self,
|
63
|
+
**kwargs
|
64
|
+
):
|
65
|
+
for key, value in kwargs.items():
|
66
|
+
setattr(self, key, value)
|
67
|
+
|
68
|
+
@property
|
69
|
+
def datas(self):
|
70
|
+
return self._datas
|
71
|
+
|
72
|
+
def add_data(self, _):
|
73
|
+
if type(_) is float:
|
74
|
+
data = Data(value=_)
|
75
|
+
self._datas.append(data)
|
76
|
+
data.flux = self
|
77
|
+
return
|
78
|
+
if type(_) is Data:
|
79
|
+
self._datas.append(_)
|
80
|
+
_.flux = self
|
81
|
+
return
|
82
|
+
|
83
|
+
def has_data(self):
|
84
|
+
return len(self._datas) > 0
|
85
|
+
|
86
|
+
def instanciate_all_datas(
|
87
|
+
self,
|
88
|
+
data_taggs={}
|
89
|
+
):
|
90
|
+
"""
|
91
|
+
Create data according to data tags presents in a table line.
|
92
|
+
Data can only have one tag for every given data tag group.
|
93
|
+
If multiple tag are given, then we must create one data for each one and link them
|
94
|
+
to the reference flux.
|
95
|
+
|
96
|
+
Parameters
|
97
|
+
----------
|
98
|
+
:param data_taggs: List of data tags
|
99
|
+
:type data_taggs: dict as {key=TagGroup.name: value=TagGroup, ...} . (default={})
|
100
|
+
|
101
|
+
Returns
|
102
|
+
-------
|
103
|
+
:return: List of created datas
|
104
|
+
:rtype: list as [Data, ...]
|
105
|
+
"""
|
106
|
+
# If no data tag -> Create only one data
|
107
|
+
if len(data_taggs) == 0:
|
108
|
+
# Add only one data
|
109
|
+
self.add_data(Data())
|
110
|
+
# Add empty datatags combinations
|
111
|
+
self._datatags_combinations.append([])
|
112
|
+
return
|
113
|
+
# Otherwise create data recursivly
|
114
|
+
self._recursive_instanciate_all_datas(
|
115
|
+
[list(_.tags.values()) for _ in data_taggs.values()])
|
116
|
+
|
117
|
+
def _recursive_instanciate_all_datas(
|
118
|
+
self,
|
119
|
+
data_tags_per_tagg,
|
120
|
+
data_tags_per_datas=[[]]
|
121
|
+
):
|
122
|
+
"""
|
123
|
+
Create data recursivly according to data tags presents in a table line.
|
124
|
+
Data can only have one tag for every given data tag group.
|
125
|
+
If multiple tag are given, then we must create one data for each one and link them
|
126
|
+
to the reference flux.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
:param data_tags_per_tagg: List of data tags regrouped per data tag groups.
|
131
|
+
:type data_tags_per_tagg: list as [list as [Tag, ...], ...]
|
132
|
+
|
133
|
+
:param data_tags_per_datas: Must not be touch. Used for recursivity
|
134
|
+
:type data_tags_per_datas: list (default=[[]])
|
135
|
+
|
136
|
+
Returns
|
137
|
+
-------
|
138
|
+
:return: List of created datas
|
139
|
+
:rtype: list as [Data, ...]
|
140
|
+
"""
|
141
|
+
# Check if we arrived at the end of recursivity on data tags
|
142
|
+
if len(data_tags_per_tagg) == 0:
|
143
|
+
# Create unique data for each data tag per data tag groups
|
144
|
+
for data_tags in data_tags_per_datas:
|
145
|
+
# Create Data
|
146
|
+
data = Data()
|
147
|
+
# Add tags
|
148
|
+
for data_tag in data_tags:
|
149
|
+
data.add_tag(data_tag)
|
150
|
+
# Add data to flux
|
151
|
+
self.add_data(data)
|
152
|
+
# Save data_tags_per_datas as keys
|
153
|
+
self._datatags_combinations.append(data_tags.copy())
|
154
|
+
# Otherwise we continue to recurse
|
155
|
+
else:
|
156
|
+
# Get data tags related to datas to create and to a unique data tag group
|
157
|
+
data_tags = data_tags_per_tagg.pop()
|
158
|
+
# Create the list of unique data tags for each data
|
159
|
+
all_data_tags_per_datas = []
|
160
|
+
for data_tag in data_tags:
|
161
|
+
# data_tags_per_datas = [[tag1_1, tag2_1], [tag1_1, tag2_2], [tag1_2, tag2_1], ...]
|
162
|
+
# with data tag group 1 = (tag1_1, tag1_2)
|
163
|
+
# data tag group 2 = (tag2_1, tag2_2)
|
164
|
+
# So, if we have data tag group 3 = (tag3_1, tag3_2, ...)
|
165
|
+
# we must copy each list from data_tags_per_datas and append tag3_1
|
166
|
+
# then make anothers copies to append tag3_2, etc.
|
167
|
+
new_data_tags_per_datas = []
|
168
|
+
for data_tags_per_data in data_tags_per_datas:
|
169
|
+
new_data_tags_per_data = data_tags_per_data.copy()
|
170
|
+
new_data_tags_per_data.append(data_tag)
|
171
|
+
new_data_tags_per_datas.append(new_data_tags_per_data)
|
172
|
+
all_data_tags_per_datas += new_data_tags_per_datas
|
173
|
+
# We recurse to deal with data tag groups
|
174
|
+
self._recursive_instanciate_all_datas(
|
175
|
+
data_tags_per_tagg,
|
176
|
+
data_tags_per_datas=all_data_tags_per_datas)
|
177
|
+
|
178
|
+
def get_corresponding_datas_from_tags(
|
179
|
+
self,
|
180
|
+
datatags_to_match,
|
181
|
+
fluxtags_to_match=[]
|
182
|
+
):
|
183
|
+
"""
|
184
|
+
Get a list of data that correspond to the input list of tags
|
185
|
+
|
186
|
+
Parameters
|
187
|
+
----------
|
188
|
+
:param tags: list of tags to check
|
189
|
+
:type tags: list[Tag, ...]
|
190
|
+
|
191
|
+
Returns
|
192
|
+
-------
|
193
|
+
:return: List of corresponding datas
|
194
|
+
:rtype: list[Data, ...]
|
195
|
+
"""
|
196
|
+
# Init list of matched datas
|
197
|
+
matched_datas = []
|
198
|
+
# Match // datatags
|
199
|
+
for datatags, data in zip(self._datatags_combinations, self._datas):
|
200
|
+
ok_match_datatags = False
|
201
|
+
# If all current datatags are contained in datatags to match list
|
202
|
+
if set(datatags_to_match).issuperset(set(datatags)):
|
203
|
+
ok_match_datatags = True
|
204
|
+
# If all datatags to match are contained in current datatags list
|
205
|
+
if set(datatags).issuperset(set(datatags_to_match)):
|
206
|
+
ok_match_datatags = True
|
207
|
+
# Check flux tags also
|
208
|
+
if ok_match_datatags:
|
209
|
+
# If flux tags are related to curren data
|
210
|
+
if set(data.tags).issuperset(set(fluxtags_to_match)):
|
211
|
+
matched_datas.append(data)
|
212
|
+
# Output
|
213
|
+
return matched_datas
|
214
|
+
|
215
|
+
@property
|
216
|
+
def results(self):
|
217
|
+
return self._results
|
218
|
+
|
219
|
+
def add_result(self, _):
|
220
|
+
if type(_) is float:
|
221
|
+
result = Data(value=_)
|
222
|
+
self._results.append(result)
|
223
|
+
result.flux = self
|
224
|
+
return
|
225
|
+
if type(_) is Data:
|
226
|
+
self._results.append(_)
|
227
|
+
_.flux = self
|
228
|
+
return
|
229
|
+
|
230
|
+
def has_result(self):
|
231
|
+
return len(self._results) > 0
|
232
|
+
|
233
|
+
def reset_results(self):
|
234
|
+
# remove altergo links with datas
|
235
|
+
for result in self._results:
|
236
|
+
result.alterego = None
|
237
|
+
# Empty list
|
238
|
+
self._results = []
|
239
|
+
|
240
|
+
def get_corresponding_results_from_tags(self, tags):
|
241
|
+
"""
|
242
|
+
Get a list of data that correspond to the input list of tags
|
243
|
+
|
244
|
+
Parameters
|
245
|
+
----------
|
246
|
+
:param tag: tag
|
247
|
+
:type tag: str | Tag
|
248
|
+
|
249
|
+
Returns
|
250
|
+
-------
|
251
|
+
:return: Corresponding data if it exist, else None
|
252
|
+
:rtype: Data | None
|
253
|
+
"""
|
254
|
+
results = set(self._results)
|
255
|
+
for tag in tags:
|
256
|
+
results &= set(tag.references)
|
257
|
+
return list(results)
|
258
|
+
|
259
|
+
@property
|
260
|
+
def monte_carlo(self):
|
261
|
+
return self._monte_carlo
|
262
|
+
|
263
|
+
def add_monte_carlo(
|
264
|
+
self,
|
265
|
+
starting_mean_value,
|
266
|
+
starting_sigma,
|
267
|
+
result_mean_value,
|
268
|
+
result_sigma,
|
269
|
+
result_min,
|
270
|
+
result_max
|
271
|
+
):
|
272
|
+
self._monte_carlo = MCData(flux=self)
|
273
|
+
self._monte_carlo.starting_data = Data(
|
274
|
+
value=starting_mean_value,
|
275
|
+
sigma=starting_sigma)
|
276
|
+
self._monte_carlo.result_data = Data(
|
277
|
+
value=result_mean_value,
|
278
|
+
sigma=result_sigma)
|
279
|
+
self._monte_carlo.min = result_min
|
280
|
+
self._monte_carlo.max = result_max
|
281
|
+
|
282
|
+
@property
|
283
|
+
def min_max(self):
|
284
|
+
return self._min_max
|
285
|
+
|
286
|
+
@property
|
287
|
+
def min(self):
|
288
|
+
return self._min_max.min_val
|
289
|
+
|
290
|
+
@min.setter
|
291
|
+
def min(self, _):
|
292
|
+
self._min_max.min = _
|
293
|
+
|
294
|
+
@property
|
295
|
+
def max(self):
|
296
|
+
return self._min_max.max_val
|
297
|
+
|
298
|
+
@max.setter
|
299
|
+
def max(self, _):
|
300
|
+
self._min_max.max = _
|
301
|
+
|
302
|
+
@property
|
303
|
+
def constraints(self):
|
304
|
+
return self._constraints
|
305
|
+
|
306
|
+
def add_constraint(self, id_constraint, **kwargs):
|
307
|
+
# Create a new piece of constraint
|
308
|
+
constraint = DataConstraint(id_constraint, self, **kwargs)
|
309
|
+
# Update constraint for given id
|
310
|
+
if id_constraint in self._constraints.keys():
|
311
|
+
self._constraints[id_constraint].append(constraint)
|
312
|
+
else:
|
313
|
+
self._constraints[id_constraint] = [constraint]
|
314
|
+
# Return constraint
|
315
|
+
return constraint
|
316
|
+
|
317
|
+
def get_as_dict(self):
|
318
|
+
# Init output
|
319
|
+
output = {}
|
320
|
+
# Get values
|
321
|
+
output['orig'] = self.orig.name
|
322
|
+
output['dest'] = self.dest.name
|
323
|
+
output['datas'] = []
|
324
|
+
for data in self.datas:
|
325
|
+
output['datas'].append(data.get_as_dict())
|
326
|
+
return output
|
327
|
+
|
328
|
+
def __repr__(self):
|
329
|
+
if self.has_data():
|
330
|
+
if len(self.datas) > 1:
|
331
|
+
return '{0} --- [{2}, ...] ---> {1}'.format(
|
332
|
+
self._orig.name,
|
333
|
+
self._dest.name,
|
334
|
+
self.datas[0])
|
335
|
+
else:
|
336
|
+
return '{0} --- {2} ---> {1}'.format(
|
337
|
+
self._orig.name,
|
338
|
+
self._dest.name,
|
339
|
+
self.datas[0])
|
340
|
+
else:
|
341
|
+
return '{0} --- {2} ---> {1}'.format(
|
342
|
+
self._orig.name,
|
343
|
+
self._dest.name,
|
344
|
+
'No data')
|
@@ -0,0 +1,278 @@
|
|
1
|
+
"""
|
2
|
+
Author : Vincent LE DOZE
|
3
|
+
Date : 31/05/23
|
4
|
+
|
5
|
+
This file contains functions used in sankey_utils modules
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
# External libs ----------------------------------------------------------------
|
10
|
+
import pandas as pd
|
11
|
+
import re
|
12
|
+
import webcolors
|
13
|
+
|
14
|
+
# External modules ------------------------------------------------------------
|
15
|
+
from unidecode import unidecode
|
16
|
+
|
17
|
+
|
18
|
+
# FUNCTIONS -------------------------------------------------------------------
|
19
|
+
def _stdStr(s):
|
20
|
+
"""
|
21
|
+
Returns standardize format for input string.
|
22
|
+
|
23
|
+
Parameters
|
24
|
+
----------
|
25
|
+
:param s: input.
|
26
|
+
:type s: str | set | list
|
27
|
+
|
28
|
+
Returns
|
29
|
+
-------
|
30
|
+
:return: formatted string(s).
|
31
|
+
:rtype: same type as input
|
32
|
+
"""
|
33
|
+
if type(s) is str:
|
34
|
+
return unidecode(s).strip().lower().replace('.0', '')
|
35
|
+
if type(s) is set:
|
36
|
+
new_s = set()
|
37
|
+
for _ in s:
|
38
|
+
new_s.add(_stdStr(_))
|
39
|
+
return s
|
40
|
+
if type(s) is list:
|
41
|
+
return [_stdStr(_) for _ in s]
|
42
|
+
|
43
|
+
|
44
|
+
def _getValueIfPresent(
|
45
|
+
line: pd.Series,
|
46
|
+
index: int,
|
47
|
+
default_value
|
48
|
+
):
|
49
|
+
"""
|
50
|
+
Extract the value from a table line for given column name.
|
51
|
+
If nothing is found, returns a default value.
|
52
|
+
|
53
|
+
Parameters
|
54
|
+
----------
|
55
|
+
:param line: Line where the info should be find.
|
56
|
+
:type line: pd.Series
|
57
|
+
|
58
|
+
:param index: Column name under which we should find a value
|
59
|
+
:type index: int
|
60
|
+
|
61
|
+
:param default_value: If no value is found under the given column, this is the default output.
|
62
|
+
|
63
|
+
Returns
|
64
|
+
-------
|
65
|
+
:return: Found value or default value if nothing is found.
|
66
|
+
"""
|
67
|
+
value = line[index] if index in line.index else default_value
|
68
|
+
return value
|
69
|
+
|
70
|
+
|
71
|
+
def _extractFluxFromMatrix(
|
72
|
+
table: pd.DataFrame,
|
73
|
+
origins: list,
|
74
|
+
destinations: list,
|
75
|
+
values: list
|
76
|
+
):
|
77
|
+
"""
|
78
|
+
Extract flux from a matrix as following:
|
79
|
+
|
80
|
+
+------+------+------+------+
|
81
|
+
| - | C4 | C5 | C6 |
|
82
|
+
+======+======+======+======+
|
83
|
+
| R3 | None | x | x |
|
84
|
+
+------+------+------+------+
|
85
|
+
| R4 | x | None | None |
|
86
|
+
+------+------+------+------+
|
87
|
+
|
88
|
+
Parameters
|
89
|
+
----------
|
90
|
+
:param table: Matrix table (rows = Origins, cols = Destinations)
|
91
|
+
:type table: pd.DataFrame
|
92
|
+
|
93
|
+
:param origins: List of all origins
|
94
|
+
:type origins: list, modified
|
95
|
+
|
96
|
+
:param destinations: List of all destinations
|
97
|
+
:type destinations: list, modified
|
98
|
+
|
99
|
+
:param values: List of all values for flux
|
100
|
+
:type values: list, modified
|
101
|
+
|
102
|
+
"""
|
103
|
+
for orig in table.index:
|
104
|
+
for dest in table.columns:
|
105
|
+
if orig == dest:
|
106
|
+
continue
|
107
|
+
v = table.loc[orig][dest]
|
108
|
+
ok = (v is not None) and (v != 0) and (v != "0")
|
109
|
+
if ok:
|
110
|
+
origins.append(orig)
|
111
|
+
destinations.append(dest)
|
112
|
+
if re.fullmatch('[xX]', str(v)) is not None:
|
113
|
+
values.append(None)
|
114
|
+
else:
|
115
|
+
values.append(float(v))
|
116
|
+
|
117
|
+
|
118
|
+
def _createMatrixFromFlux(
|
119
|
+
origins: list,
|
120
|
+
destinations: list,
|
121
|
+
transpose=False
|
122
|
+
):
|
123
|
+
"""
|
124
|
+
Create a matrix table (rows = Origins, cols = Destinations)
|
125
|
+
|
126
|
+
Parameters
|
127
|
+
----------
|
128
|
+
:param origins: List of all origin nodes
|
129
|
+
:type origins: list[Node]
|
130
|
+
|
131
|
+
:param destinations: List of all destination nodes
|
132
|
+
:type destinations: list[Node]
|
133
|
+
|
134
|
+
:param transpose: Return matrix as transpose version
|
135
|
+
:type transpose: bool
|
136
|
+
|
137
|
+
Returns
|
138
|
+
-------
|
139
|
+
:return: matrix table (rows = Origins, cols = Destinations).
|
140
|
+
If "x" we have a flux from origin to destination
|
141
|
+
If None we dont have a flux
|
142
|
+
:rtype: list[list]
|
143
|
+
"""
|
144
|
+
table = []
|
145
|
+
if transpose:
|
146
|
+
for destination in destinations:
|
147
|
+
# Init new row
|
148
|
+
row = []
|
149
|
+
# Get all nodes that are registred as a flux destination from origin
|
150
|
+
registered_origins = \
|
151
|
+
[flux.orig for flux in destination.input_flux]
|
152
|
+
# If we have such nodes
|
153
|
+
if len(registered_origins) > 0:
|
154
|
+
for origin in origins:
|
155
|
+
# 'x' if we have a flux origin -> destination
|
156
|
+
# None otherwise
|
157
|
+
if origin in registered_origins:
|
158
|
+
row.append('x')
|
159
|
+
else:
|
160
|
+
row.append(None)
|
161
|
+
else:
|
162
|
+
row += [None]*len(origins)
|
163
|
+
# Add row to table
|
164
|
+
table.append(row)
|
165
|
+
else:
|
166
|
+
for origin in origins:
|
167
|
+
# Init new row
|
168
|
+
row = []
|
169
|
+
# Get all nodes that are registred as a flux destination from origin
|
170
|
+
registered_destinations = \
|
171
|
+
[flux.dest for flux in origin.output_flux]
|
172
|
+
# If we have such nodes
|
173
|
+
if len(registered_destinations) > 0:
|
174
|
+
for destination in destinations:
|
175
|
+
# 'x' if we have a flux origin -> destination
|
176
|
+
# None otherwise
|
177
|
+
if destination in registered_destinations:
|
178
|
+
row.append('x')
|
179
|
+
else:
|
180
|
+
row.append(None)
|
181
|
+
else:
|
182
|
+
row += [None]*len(destinations)
|
183
|
+
# Add row to table
|
184
|
+
table.append(row)
|
185
|
+
# Create and return panda table
|
186
|
+
return table
|
187
|
+
|
188
|
+
|
189
|
+
def is_hex(s):
|
190
|
+
return re.fullmatch(r"^\# ?[0-9a-fA-F]+$", s or "") is not None
|
191
|
+
|
192
|
+
|
193
|
+
def _convertColorToHex(color, default_color=''):
|
194
|
+
"""
|
195
|
+
Convert a color str to hex value.
|
196
|
+
|
197
|
+
Parameters
|
198
|
+
----------
|
199
|
+
:param color: color to convert
|
200
|
+
:type color: str
|
201
|
+
"""
|
202
|
+
if type(color) is str:
|
203
|
+
# is the color as hexa ?
|
204
|
+
if (re.fullmatch(r"^\# ?[0-9a-fA-F]+$", color) is not None) or (color == ""):
|
205
|
+
return color
|
206
|
+
else:
|
207
|
+
return webcolors.name_to_hex(color)
|
208
|
+
return default_color
|
209
|
+
|
210
|
+
|
211
|
+
def _reorderTable(
|
212
|
+
table: pd.DataFrame,
|
213
|
+
cols: list,
|
214
|
+
contents: list
|
215
|
+
):
|
216
|
+
"""
|
217
|
+
Reorder table lines accordlying to values presents in list of cols
|
218
|
+
|
219
|
+
Example :
|
220
|
+
-------
|
221
|
+
Col 1 | Col 2 | Col 3
|
222
|
+
---------------------
|
223
|
+
a | 1 | 2
|
224
|
+
b | 2 | 2
|
225
|
+
c | 2 | 1
|
226
|
+
d | 1 | 2
|
227
|
+
|
228
|
+
Reordering as [Col2, Col3] gives
|
229
|
+
Col 1 | Col 2 | Col 3
|
230
|
+
---------------------
|
231
|
+
a | 1 | 2
|
232
|
+
d | 1 | 2
|
233
|
+
c | 2 | 1
|
234
|
+
b | 2 | 2
|
235
|
+
|
236
|
+
Parameters
|
237
|
+
----------
|
238
|
+
:param table: Table to reorder
|
239
|
+
:type table: panda.DataFrame
|
240
|
+
|
241
|
+
:param cols: List of ref columns for reordering
|
242
|
+
:type cols: list as [str, ...]
|
243
|
+
|
244
|
+
:param contents: List possible content per col. Must be ordered following this.
|
245
|
+
If content not in this list, then will be processed at the end.
|
246
|
+
:type contents: list as [list as [str, ...], ...]
|
247
|
+
|
248
|
+
Returns
|
249
|
+
-------
|
250
|
+
:return: Reordered table
|
251
|
+
:rtype: panda.DataFrame
|
252
|
+
|
253
|
+
"""
|
254
|
+
# No more ordering cols -> return table
|
255
|
+
if len(cols) == 0:
|
256
|
+
return table
|
257
|
+
# Create new table to get block of sorted subtables
|
258
|
+
new_table = pd.DataFrame(columns=table.columns)
|
259
|
+
sorting_col = cols.pop()
|
260
|
+
sorting_contents = contents.pop()
|
261
|
+
present_contents = table[sorting_col].unique()
|
262
|
+
# Update sorting content with unexpected content
|
263
|
+
prio_sorting_contents = [_ for _ in sorting_contents if _ in present_contents]
|
264
|
+
other_sorting_contents = [_ for _ in present_contents if _ not in sorting_contents]
|
265
|
+
sorting_contents = prio_sorting_contents + other_sorting_contents
|
266
|
+
# Sort
|
267
|
+
for value in sorting_contents:
|
268
|
+
# Get subtable
|
269
|
+
if value is None:
|
270
|
+
sub_table = table.loc[table[sorting_col].isnull()]
|
271
|
+
else:
|
272
|
+
sub_table = table.loc[table[sorting_col] == value]
|
273
|
+
# Recursive filtering of sub_table
|
274
|
+
if len(cols) > 0:
|
275
|
+
sub_table = _reorderTable(sub_table, cols.copy(), contents.copy())
|
276
|
+
# Appending results
|
277
|
+
new_table = new_table._append(sub_table, ignore_index=True)
|
278
|
+
return new_table
|