1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Algorithms to generate plots.
21 from collections import OrderedDict
22 from copy import deepcopy
25 import plotly.offline as ploff
26 import plotly.graph_objs as plgo
28 from plotly.exceptions import PlotlyError
30 from pal_utils import mean, stdev
33 COLORS = [u"SkyBlue", u"Olive", u"Purple", u"Coral", u"Indigo", u"Pink",
34 u"Chocolate", u"Brown", u"Magenta", u"Cyan", u"Orange", u"Black",
35 u"Violet", u"Blue", u"Yellow", u"BurlyWood", u"CadetBlue", u"Crimson",
36 u"DarkBlue", u"DarkCyan", u"DarkGreen", u"Green", u"GoldenRod",
37 u"LightGreen", u"LightSeaGreen", u"LightSkyBlue", u"Maroon",
38 u"MediumSeaGreen", u"SeaGreen", u"LightSlateGrey"]
40 REGEX_NIC = re.compile(r'\d*ge\dp\d\D*\d*-')
43 def generate_plots(spec, data):
44 """Generate all plots specified in the specification file.
46 :param spec: Specification read from the specification file.
47 :param data: Data to process.
48 :type spec: Specification
53 u"plot_nf_reconf_box_name": plot_nf_reconf_box_name,
54 u"plot_perf_box_name": plot_perf_box_name,
55 u"plot_lat_err_bars_name": plot_lat_err_bars_name,
56 u"plot_tsa_name": plot_tsa_name,
57 u"plot_http_server_perf_box": plot_http_server_perf_box,
58 u"plot_nf_heatmap": plot_nf_heatmap
61 logging.info(u"Generating the plots ...")
62 for index, plot in enumerate(spec.plots):
64 logging.info(f" Plot nr {index + 1}: {plot.get(u'title', u'')}")
65 plot[u"limits"] = spec.configuration[u"limits"]
66 generator[plot[u"algorithm"]](plot, data)
67 logging.info(u" Done.")
68 except NameError as err:
70 f"Probably algorithm {plot[u'algorithm']} is not defined: "
73 logging.info(u"Done.")
76 def plot_nf_reconf_box_name(plot, input_data):
77 """Generate the plot(s) with algorithm: plot_nf_reconf_box_name
78 specified in the specification file.
80 :param plot: Plot to generate.
81 :param input_data: Data to process.
82 :type plot: pandas.Series
83 :type input_data: InputData
88 f" Creating the data set for the {plot.get(u'type', u'')} "
89 f"{plot.get(u'title', u'')}."
91 data = input_data.filter_tests_by_name(
92 plot, params=[u"result", u"parent", u"tags", u"type"]
95 logging.error(u"No data.")
98 # Prepare the data for the plot
99 y_vals = OrderedDict()
104 if y_vals.get(test[u"parent"], None) is None:
105 y_vals[test[u"parent"]] = list()
106 loss[test[u"parent"]] = list()
108 y_vals[test[u"parent"]].append(test[u"result"][u"time"])
109 loss[test[u"parent"]].append(test[u"result"][u"loss"])
110 except (KeyError, TypeError):
111 y_vals[test[u"parent"]].append(None)
113 # Add None to the lists with missing data
115 nr_of_samples = list()
116 for val in y_vals.values():
117 if len(val) > max_len:
119 nr_of_samples.append(len(val))
120 for val in y_vals.values():
121 if len(val) < max_len:
122 val.extend([None for _ in range(max_len - len(val))])
126 df_y = pd.DataFrame(y_vals)
128 for i, col in enumerate(df_y.columns):
129 tst_name = re.sub(REGEX_NIC, u"",
130 col.lower().replace(u'-ndrpdr', u'').
131 replace(u'2n1l-', u''))
133 traces.append(plgo.Box(
134 x=[str(i + 1) + u'.'] * len(df_y[col]),
135 y=[y if y else None for y in df_y[col]],
138 f"({nr_of_samples[i]:02d} "
139 f"run{u's' if nr_of_samples[i] > 1 else u''}, "
140 f"packets lost average: {mean(loss[col]):.1f}) "
141 f"{u'-'.join(tst_name.split(u'-')[3:-2])}"
147 layout = deepcopy(plot[u"layout"])
148 layout[u"title"] = f"<b>Time Lost:</b> {layout[u'title']}"
149 layout[u"yaxis"][u"title"] = u"<b>Implied Time Lost [s]</b>"
150 layout[u"legend"][u"font"][u"size"] = 14
151 layout[u"yaxis"].pop(u"range")
152 plpl = plgo.Figure(data=traces, layout=layout)
155 file_type = plot.get(u"output-file-type", u".html")
156 logging.info(f" Writing file {plot[u'output-file']}{file_type}.")
161 filename=f"{plot[u'output-file']}{file_type}"
163 except PlotlyError as err:
165 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
170 def plot_perf_box_name(plot, input_data):
171 """Generate the plot(s) with algorithm: plot_perf_box_name
172 specified in the specification file.
174 :param plot: Plot to generate.
175 :param input_data: Data to process.
176 :type plot: pandas.Series
177 :type input_data: InputData
182 f" Creating data set for the {plot.get(u'type', u'')} "
183 f"{plot.get(u'title', u'')}."
185 data = input_data.filter_tests_by_name(
186 plot, params=[u"throughput", u"parent", u"tags", u"type"])
188 logging.error(u"No data.")
191 # Prepare the data for the plot
192 y_vals = OrderedDict()
196 if y_vals.get(test[u"parent"], None) is None:
197 y_vals[test[u"parent"]] = list()
199 if (test[u"type"] in (u"NDRPDR", ) and
200 u"-pdr" in plot.get(u"title", u"").lower()):
201 y_vals[test[u"parent"]].\
202 append(test[u"throughput"][u"PDR"][u"LOWER"])
203 elif (test[u"type"] in (u"NDRPDR", ) and
204 u"-ndr" in plot.get(u"title", u"").lower()):
205 y_vals[test[u"parent"]]. \
206 append(test[u"throughput"][u"NDR"][u"LOWER"])
207 elif test[u"type"] in (u"SOAK", ):
208 y_vals[test[u"parent"]].\
209 append(test[u"throughput"][u"LOWER"])
212 except (KeyError, TypeError):
213 y_vals[test[u"parent"]].append(None)
215 # Add None to the lists with missing data
217 nr_of_samples = list()
218 for val in y_vals.values():
219 if len(val) > max_len:
221 nr_of_samples.append(len(val))
222 for val in y_vals.values():
223 if len(val) < max_len:
224 val.extend([None for _ in range(max_len - len(val))])
228 df_y = pd.DataFrame(y_vals)
231 for i, col in enumerate(df_y.columns):
232 tst_name = re.sub(REGEX_NIC, u"",
233 col.lower().replace(u'-ndrpdr', u'').
234 replace(u'2n1l-', u''))
237 x=[str(i + 1) + u'.'] * len(df_y[col]),
238 y=[y / 1000000 if y else None for y in df_y[col]],
241 f"({nr_of_samples[i]:02d} "
242 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
249 val_max = max(df_y[col])
251 y_max.append(int(val_max / 1000000) + 2)
252 except (ValueError, TypeError) as err:
253 logging.error(repr(err))
258 layout = deepcopy(plot[u"layout"])
259 if layout.get(u"title", None):
260 layout[u"title"] = f"<b>Throughput:</b> {layout[u'title']}"
262 layout[u"yaxis"][u"range"] = [0, max(y_max)]
263 plpl = plgo.Figure(data=traces, layout=layout)
266 logging.info(f" Writing file {plot[u'output-file']}.html.")
271 filename=f"{plot[u'output-file']}.html"
273 except PlotlyError as err:
275 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
280 def plot_lat_err_bars_name(plot, input_data):
281 """Generate the plot(s) with algorithm: plot_lat_err_bars_name
282 specified in the specification file.
284 :param plot: Plot to generate.
285 :param input_data: Data to process.
286 :type plot: pandas.Series
287 :type input_data: InputData
291 plot_title = plot.get(u"title", u"")
293 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
295 data = input_data.filter_tests_by_name(
296 plot, params=[u"latency", u"parent", u"tags", u"type"])
298 logging.error(u"No data.")
301 # Prepare the data for the plot
302 y_tmp_vals = OrderedDict()
307 logging.debug(f"test[u'latency']: {test[u'latency']}\n")
308 except ValueError as err:
309 logging.warning(repr(err))
310 if y_tmp_vals.get(test[u"parent"], None) is None:
311 y_tmp_vals[test[u"parent"]] = [
312 list(), # direction1, min
313 list(), # direction1, avg
314 list(), # direction1, max
315 list(), # direction2, min
316 list(), # direction2, avg
317 list() # direction2, max
320 if test[u"type"] not in (u"NDRPDR", ):
321 logging.warning(f"Invalid test type: {test[u'type']}")
323 if u"-pdr" in plot_title.lower():
325 elif u"-ndr" in plot_title.lower():
329 f"Invalid test type: {test[u'type']}"
332 y_tmp_vals[test[u"parent"]][0].append(
333 test[u"latency"][ttype][u"direction1"][u"min"])
334 y_tmp_vals[test[u"parent"]][1].append(
335 test[u"latency"][ttype][u"direction1"][u"avg"])
336 y_tmp_vals[test[u"parent"]][2].append(
337 test[u"latency"][ttype][u"direction1"][u"max"])
338 y_tmp_vals[test[u"parent"]][3].append(
339 test[u"latency"][ttype][u"direction2"][u"min"])
340 y_tmp_vals[test[u"parent"]][4].append(
341 test[u"latency"][ttype][u"direction2"][u"avg"])
342 y_tmp_vals[test[u"parent"]][5].append(
343 test[u"latency"][ttype][u"direction2"][u"max"])
344 except (KeyError, TypeError) as err:
345 logging.warning(repr(err))
351 nr_of_samples = list()
352 for key, val in y_tmp_vals.items():
353 name = re.sub(REGEX_NIC, u"", key.replace(u'-ndrpdr', u'').
354 replace(u'2n1l-', u''))
355 x_vals.append(name) # dir 1
356 y_vals.append(mean(val[1]) if val[1] else None)
357 y_mins.append(mean(val[0]) if val[0] else None)
358 y_maxs.append(mean(val[2]) if val[2] else None)
359 nr_of_samples.append(len(val[1]) if val[1] else 0)
360 x_vals.append(name) # dir 2
361 y_vals.append(mean(val[4]) if val[4] else None)
362 y_mins.append(mean(val[3]) if val[3] else None)
363 y_maxs.append(mean(val[5]) if val[5] else None)
364 nr_of_samples.append(len(val[3]) if val[3] else 0)
369 for idx, _ in enumerate(x_vals):
370 if not bool(int(idx % 2)):
371 direction = u"West-East"
373 direction = u"East-West"
375 f"No. of Runs: {nr_of_samples[idx]}<br>"
376 f"Test: {x_vals[idx]}<br>"
377 f"Direction: {direction}<br>"
379 if isinstance(y_maxs[idx], float):
380 hovertext += f"Max: {y_maxs[idx]:.2f}uSec<br>"
381 if isinstance(y_vals[idx], float):
382 hovertext += f"Mean: {y_vals[idx]:.2f}uSec<br>"
383 if isinstance(y_mins[idx], float):
384 hovertext += f"Min: {y_mins[idx]:.2f}uSec"
386 if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
387 array = [y_maxs[idx] - y_vals[idx], ]
390 if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
391 arrayminus = [y_vals[idx] - y_mins[idx], ]
393 arrayminus = [None, ]
394 traces.append(plgo.Scatter(
398 legendgroup=x_vals[idx],
399 showlegend=bool(int(idx % 2)),
405 arrayminus=arrayminus,
406 color=COLORS[int(idx / 2)]
410 color=COLORS[int(idx / 2)],
415 annotations.append(dict(
422 text=u"E-W" if bool(int(idx % 2)) else u"W-E",
432 file_type = plot.get(u"output-file-type", u".html")
433 logging.info(f" Writing file {plot[u'output-file']}{file_type}.")
434 layout = deepcopy(plot[u"layout"])
435 if layout.get(u"title", None):
436 layout[u"title"] = f"<b>Latency:</b> {layout[u'title']}"
437 layout[u"annotations"] = annotations
438 plpl = plgo.Figure(data=traces, layout=layout)
443 show_link=False, auto_open=False,
444 filename=f"{plot[u'output-file']}{file_type}"
446 except PlotlyError as err:
448 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
453 def plot_tsa_name(plot, input_data):
454 """Generate the plot(s) with algorithm:
456 specified in the specification file.
458 :param plot: Plot to generate.
459 :param input_data: Data to process.
460 :type plot: pandas.Series
461 :type input_data: InputData
465 plot_title = plot.get(u"title", u"")
467 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
469 data = input_data.filter_tests_by_name(
470 plot, params=[u"throughput", u"parent", u"tags", u"type"])
472 logging.error(u"No data.")
475 y_vals = OrderedDict()
479 if y_vals.get(test[u"parent"], None) is None:
480 y_vals[test[u"parent"]] = {
486 if test[u"type"] not in (u"NDRPDR",):
489 if u"-pdr" in plot_title.lower():
491 elif u"-ndr" in plot_title.lower():
496 if u"1C" in test[u"tags"]:
497 y_vals[test[u"parent"]][u"1"]. \
498 append(test[u"throughput"][ttype][u"LOWER"])
499 elif u"2C" in test[u"tags"]:
500 y_vals[test[u"parent"]][u"2"]. \
501 append(test[u"throughput"][ttype][u"LOWER"])
502 elif u"4C" in test[u"tags"]:
503 y_vals[test[u"parent"]][u"4"]. \
504 append(test[u"throughput"][ttype][u"LOWER"])
505 except (KeyError, TypeError):
509 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
513 for test_name, test_vals in y_vals.items():
514 for key, test_val in test_vals.items():
516 avg_val = sum(test_val) / len(test_val)
517 y_vals[test_name][key] = [avg_val, len(test_val)]
518 ideal = avg_val / (int(key) * 1000000.0)
519 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
520 y_1c_max[test_name] = ideal
526 pci_limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
527 for test_name, test_vals in y_vals.items():
529 if test_vals[u"1"][1]:
533 test_name.replace(u'-ndrpdr', u'').replace(u'2n1l-', u'')
535 vals[name] = OrderedDict()
536 y_val_1 = test_vals[u"1"][0] / 1000000.0
537 y_val_2 = test_vals[u"2"][0] / 1000000.0 if test_vals[u"2"][0] \
539 y_val_4 = test_vals[u"4"][0] / 1000000.0 if test_vals[u"4"][0] \
542 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
543 vals[name][u"rel"] = [1.0, None, None]
544 vals[name][u"ideal"] = [
546 y_1c_max[test_name] * 2,
547 y_1c_max[test_name] * 4
549 vals[name][u"diff"] = [
550 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1, None, None
552 vals[name][u"count"] = [
559 val_max = max(vals[name][u"val"])
560 except ValueError as err:
561 logging.error(repr(err))
564 y_max.append(val_max)
567 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
568 vals[name][u"diff"][1] = \
569 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
571 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
572 vals[name][u"diff"][2] = \
573 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
574 except IndexError as err:
575 logging.warning(f"No data for {test_name}")
576 logging.warning(repr(err))
579 if u"x520" in test_name:
580 limit = plot[u"limits"][u"nic"][u"x520"]
581 elif u"x710" in test_name:
582 limit = plot[u"limits"][u"nic"][u"x710"]
583 elif u"xxv710" in test_name:
584 limit = plot[u"limits"][u"nic"][u"xxv710"]
585 elif u"xl710" in test_name:
586 limit = plot[u"limits"][u"nic"][u"xl710"]
587 elif u"x553" in test_name:
588 limit = plot[u"limits"][u"nic"][u"x553"]
591 if limit > nic_limit:
594 mul = 2 if u"ge2p" in test_name else 1
595 if u"10ge" in test_name:
596 limit = plot[u"limits"][u"link"][u"10ge"] * mul
597 elif u"25ge" in test_name:
598 limit = plot[u"limits"][u"link"][u"25ge"] * mul
599 elif u"40ge" in test_name:
600 limit = plot[u"limits"][u"link"][u"40ge"] * mul
601 elif u"100ge" in test_name:
602 limit = plot[u"limits"][u"link"][u"100ge"] * mul
605 if limit > lnk_limit:
614 threshold = 1.1 * max(y_max) # 10%
615 except ValueError as err:
618 nic_limit /= 1000000.0
619 traces.append(plgo.Scatter(
621 y=[nic_limit, ] * len(x_vals),
622 name=f"NIC: {nic_limit:.2f}Mpps",
631 annotations.append(dict(
638 text=f"NIC: {nic_limit:.2f}Mpps",
646 y_max.append(nic_limit)
648 lnk_limit /= 1000000.0
649 if lnk_limit < threshold:
650 traces.append(plgo.Scatter(
652 y=[lnk_limit, ] * len(x_vals),
653 name=f"Link: {lnk_limit:.2f}Mpps",
662 annotations.append(dict(
669 text=f"Link: {lnk_limit:.2f}Mpps",
677 y_max.append(lnk_limit)
679 pci_limit /= 1000000.0
680 if (pci_limit < threshold and
681 (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
682 traces.append(plgo.Scatter(
684 y=[pci_limit, ] * len(x_vals),
685 name=f"PCIe: {pci_limit:.2f}Mpps",
694 annotations.append(dict(
701 text=f"PCIe: {pci_limit:.2f}Mpps",
709 y_max.append(pci_limit)
711 # Perfect and measured:
713 for name, val in vals.items():
716 for idx in range(len(val[u"val"])):
718 if isinstance(val[u"val"][idx], float):
720 f"No. of Runs: {val[u'count'][idx]}<br>"
721 f"Mean: {val[u'val'][idx]:.2f}Mpps<br>"
723 if isinstance(val[u"diff"][idx], float):
724 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
725 if isinstance(val[u"rel"][idx], float):
726 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
727 hovertext.append(htext)
734 mode=u"lines+markers",
743 hoverinfo=u"text+name"
750 name=f"{name} perfect",
758 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
763 except (IndexError, ValueError, KeyError) as err:
764 logging.warning(f"No data for {name}\n{repr(err)}")
768 file_type = plot.get(u"output-file-type", u".html")
769 logging.info(f" Writing file {plot[u'output-file']}{file_type}.")
770 layout = deepcopy(plot[u"layout"])
771 if layout.get(u"title", None):
772 layout[u"title"] = f"<b>Speedup Multi-core:</b> {layout[u'title']}"
773 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
774 layout[u"annotations"].extend(annotations)
775 plpl = plgo.Figure(data=traces, layout=layout)
782 filename=f"{plot[u'output-file']}{file_type}"
784 except PlotlyError as err:
786 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
791 def plot_http_server_perf_box(plot, input_data):
792 """Generate the plot(s) with algorithm: plot_http_server_perf_box
793 specified in the specification file.
795 :param plot: Plot to generate.
796 :param input_data: Data to process.
797 :type plot: pandas.Series
798 :type input_data: InputData
803 f" Creating the data set for the {plot.get(u'type', u'')} "
804 f"{plot.get(u'title', u'')}."
806 data = input_data.filter_data(plot)
808 logging.error(u"No data.")
811 # Prepare the data for the plot
816 if y_vals.get(test[u"name"], None) is None:
817 y_vals[test[u"name"]] = list()
819 y_vals[test[u"name"]].append(test[u"result"])
820 except (KeyError, TypeError):
821 y_vals[test[u"name"]].append(None)
823 # Add None to the lists with missing data
825 nr_of_samples = list()
826 for val in y_vals.values():
827 if len(val) > max_len:
829 nr_of_samples.append(len(val))
830 for val in y_vals.values():
831 if len(val) < max_len:
832 val.extend([None for _ in range(max_len - len(val))])
836 df_y = pd.DataFrame(y_vals)
838 for i, col in enumerate(df_y.columns):
841 f"({nr_of_samples[i]:02d} " \
842 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
843 f"{col.lower().replace(u'-ndrpdr', u'')}"
845 name_lst = name.split(u'-')
848 for segment in name_lst:
849 if (len(name) + len(segment) + 1) > 50 and split_name:
852 name += segment + u'-'
855 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
861 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
865 f" Writing file {plot[u'output-file']}"
866 f"{plot[u'output-file-type']}."
872 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
874 except PlotlyError as err:
876 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
881 def plot_nf_heatmap(plot, input_data):
882 """Generate the plot(s) with algorithm: plot_nf_heatmap
883 specified in the specification file.
885 :param plot: Plot to generate.
886 :param input_data: Data to process.
887 :type plot: pandas.Series
888 :type input_data: InputData
891 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
892 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
894 r'(\d+vm\d+t|\d+dcr\d+t).*$')
899 f" Creating the data set for the {plot.get(u'type', u'')} "
900 f"{plot.get(u'title', u'')}."
902 data = input_data.filter_data(plot, continue_on_error=True)
903 if data is None or data.empty:
904 logging.error(u"No data.")
910 for tag in test[u"tags"]:
911 groups = re.search(regex_cn, tag)
913 chain = str(groups.group(1))
914 node = str(groups.group(2))
918 groups = re.search(regex_test_name, test[u"name"])
919 if groups and len(groups.groups()) == 3:
921 f"{str(groups.group(1))}-"
922 f"{str(groups.group(2))}-"
923 f"{str(groups.group(3))}"
927 if vals.get(chain, None) is None:
929 if vals[chain].get(node, None) is None:
930 vals[chain][node] = dict(
938 if plot[u"include-tests"] == u"MRR":
939 result = test[u"result"][u"receive-rate"]
940 elif plot[u"include-tests"] == u"PDR":
941 result = test[u"throughput"][u"PDR"][u"LOWER"]
942 elif plot[u"include-tests"] == u"NDR":
943 result = test[u"throughput"][u"NDR"][u"LOWER"]
950 vals[chain][node][u"vals"].append(result)
953 logging.error(u"No data.")
959 txt_chains.append(key_c)
960 for key_n in vals[key_c].keys():
961 txt_nodes.append(key_n)
962 if vals[key_c][key_n][u"vals"]:
963 vals[key_c][key_n][u"nr"] = len(vals[key_c][key_n][u"vals"])
964 vals[key_c][key_n][u"mean"] = \
965 round(mean(vals[key_c][key_n][u"vals"]) / 1000000, 1)
966 vals[key_c][key_n][u"stdev"] = \
967 round(stdev(vals[key_c][key_n][u"vals"]) / 1000000, 1)
968 txt_nodes = list(set(txt_nodes))
970 def sort_by_int(value):
971 """Makes possible to sort a list of strings which represent integers.
973 :param value: Integer as a string.
975 :returns: Integer representation of input parameter 'value'.
980 txt_chains = sorted(txt_chains, key=sort_by_int)
981 txt_nodes = sorted(txt_nodes, key=sort_by_int)
983 chains = [i + 1 for i in range(len(txt_chains))]
984 nodes = [i + 1 for i in range(len(txt_nodes))]
986 data = [list() for _ in range(len(chains))]
990 val = vals[txt_chains[chain - 1]][txt_nodes[node - 1]][u"mean"]
991 except (KeyError, IndexError):
993 data[chain - 1].append(val)
996 my_green = [[0.0, u"rgb(235, 249, 242)"],
997 [1.0, u"rgb(45, 134, 89)"]]
999 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1000 [1.0, u"rgb(57, 115, 172)"]]
1002 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1003 [1.0, u"rgb(102, 102, 102)"]]
1006 annotations = list()
1008 text = (u"Test: {name}<br>"
1013 for chain, _ in enumerate(txt_chains):
1015 for node, _ in enumerate(txt_nodes):
1016 if data[chain][node] is not None:
1025 text=str(data[chain][node]),
1033 hover_line.append(text.format(
1034 name=vals[txt_chains[chain]][txt_nodes[node]][u"name"],
1035 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1036 val=data[chain][node],
1037 stdev=vals[txt_chains[chain]][txt_nodes[node]][u"stdev"]))
1038 hovertext.append(hover_line)
1046 title=plot.get(u"z-axis", u""),
1060 colorscale=my_green,
1066 for idx, item in enumerate(txt_nodes):
1084 for idx, item in enumerate(txt_chains):
1111 text=plot.get(u"x-axis", u""),
1128 text=plot.get(u"y-axis", u""),
1137 updatemenus = list([
1148 u"colorscale": [my_green, ],
1149 u"reversescale": False
1158 u"colorscale": [my_blue, ],
1159 u"reversescale": False
1168 u"colorscale": [my_grey, ],
1169 u"reversescale": False
1180 layout = deepcopy(plot[u"layout"])
1181 except KeyError as err:
1182 logging.error(f"Finished with error: No layout defined\n{repr(err)}")
1185 layout[u"annotations"] = annotations
1186 layout[u'updatemenus'] = updatemenus
1190 plpl = plgo.Figure(data=traces, layout=layout)
1194 f" Writing file {plot[u'output-file']}"
1195 f"{plot[u'output-file-type']}."
1201 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1203 except PlotlyError as err:
1205 f" Finished with error: {repr(err)}".replace(u"\n", u" ")