SankeyExcelParser 1.0.0b0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|