Skip to content

Commit c7f45ab

Browse files
committed
FEAT(plot): Operation BADGES
1 parent e7e1cdd commit c7f45ab

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ v6.3.0 (XX Apr 2020, @ankostis): arg-->kw-modifier, ops act like functions
111111

112112
+ FEAT: now `operations` are also :term:`plottable`.
113113

114+
+ FEAT: Operation BADGES to distinguish endured, rescheduled, parallel, marshalled,
115+
returns_dict.
116+
114117
+ FIX: Cancel/Evict styles were misclassified.
115118

116119
+ feat(plot): change label in sol_sideffects; add exceptions as tooltips on

graphtik/plot.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ class Theme:
414414

415415
##########
416416
## DATA node
417+
##
417418

418419
#: Reduce margins, since sideffects take a lost of space
419420
#: (default margin: x=0.11, y=0.055O)
@@ -427,12 +428,6 @@ class Theme:
427428
3: {"shape": "hexagon"}, # Inp/Out
428429
}
429430
kw_data_mapped = {"label": make_template("<{{ nx_item | eee }}>")}
430-
431-
kw_data_pruned = {
432-
"fontcolor": Ref("pruned_color"),
433-
"color": Ref("pruned_color"),
434-
"tooltip": "(pruned)",
435-
}
436431
kw_data_sideffect = {
437432
"color": "blue",
438433
"fontcolor": "blue",
@@ -451,6 +446,14 @@ class Theme:
451446
"style": ["dashed"],
452447
"tooltip": "(to evict)",
453448
}
449+
##
450+
## data STATE
451+
##
452+
kw_data_pruned = {
453+
"fontcolor": Ref("pruned_color"),
454+
"color": Ref("pruned_color"),
455+
"tooltip": "(pruned)",
456+
}
454457
kw_data_in_solution = {"style": ["filled"], "fillcolor": Ref("fill_color")}
455458
kw_data_evicted = {"penwidth": "3", "tooltip": "(evicted)"}
456459
kw_data_overwritten = {"style": ["filled"], "fillcolor": Ref("overwrite_color")}
@@ -462,6 +465,7 @@ class Theme:
462465

463466
##########
464467
## OPERATION node
468+
##
465469

466470
#: Keys to ignore from operation styles & node-attrs,
467471
#: because they are handled internally by HTML-Label, and/or
@@ -472,23 +476,47 @@ class Theme:
472476
kw_op = {}
473477
#: props only for HTML-Table label
474478
kw_op_label = {}
475-
kw_op_pruned = {"color": Ref("pruned_color"), "fontcolor": Ref("pruned_color")}
476479
kw_op_executed = {"fillcolor": Ref("fill_color")}
477480
kw_op_endured = {
478481
"penwidth": Ref("resched_thickness"),
479482
"style": ["dashed"],
480-
"tooltip": "(rescheduled)",
483+
"tooltip": "(endured)",
484+
"badges": ["E"],
481485
}
482486
kw_op_rescheduled = {
483487
"penwidth": Ref("resched_thickness"),
484488
"style": ["dashed"],
485489
"tooltip": "(rescheduled)",
490+
"badges": ["R"],
486491
}
492+
kw_op_parallel = {"badges": ["P"]}
493+
kw_op_marshalled = {"badges": ["M"]}
494+
kw_op_returns_dict = {"badges": ["D"]}
495+
##
496+
## op STATE
497+
##
498+
kw_op_pruned = {"color": Ref("pruned_color"), "fontcolor": Ref("pruned_color")}
487499
kw_op_failed = {
488500
"fillcolor": Ref("failed_color"),
489501
"tooltip": make_template("{{ solution.executed[nx_item] if solution | ex }}"),
490502
}
491503
kw_op_canceled = {"fillcolor": Ref("canceled_color"), "tooltip": "(canceled)"}
504+
#: Operation styles may specify one or more "letters"
505+
#: in a `badges` list item, as long as the "letter" is contained in the dictionary
506+
#: below.
507+
op_badge_styles = {
508+
"badge_styles": {
509+
"E": {"tooltip": "endured(!)", "bgcolor": "#04277d", "color": "white"},
510+
"R": {"tooltip": "rescheduled(?)", "bgcolor": "#fc89ac", "color": "white"},
511+
"P": {"tooltip": "parallel(|)", "bgcolor": "#b1ce9a", "color": "white"},
512+
"M": {"tooltip": "marshalled($)", "bgcolor": "#4e3165", "color": "white"},
513+
"D": {
514+
"tooltip": "returns_dict({})",
515+
"bgcolor": "#cc5500",
516+
"color": "white",
517+
},
518+
}
519+
}
492520
#: Try to mimic a regular `Graphviz`_ node attributes
493521
#: (see examples in ``test.test_plot.test_op_template_full()`` for params).
494522
#: TODO: fix jinja2 template is un-picklable!
@@ -512,10 +540,24 @@ class Theme:
512540
{{- '<B>OP:</B> <I>%s</I>' % op_name |ee if op_name -}}
513541
{%- if fontcolor -%}</FONT>{%- endif -%}
514542
</TD>
543+
<TD BORDER="1" SIDES="b">
544+
{%- if badges -%}
545+
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="1" CELLPADDING="2">
546+
<TR>
547+
{%- for badge in badges -%}
548+
<TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="{{ badge_styles[badge].bgcolor }}" TITLE="{{ badge_styles[badge].tooltip | e }}" TARGET="_self"
549+
><FONT FACE="monospace" COLOR="{{ badge_styles[badge].color }}"><B>
550+
{{- badge -}}
551+
</B></FONT></TD>
552+
{%- endfor -%}
553+
</TR>
554+
</TABLE>
555+
{%- endif -%}
556+
</TD>
515557
</TR>
516558
{%- if fn_name -%}
517559
<TR>
518-
<TD ALIGN="left"
560+
<TD COLSPAN="2" ALIGN="left"
519561
{{- {
520562
'TOOLTIP': fn_tooltip | truncate | eee,
521563
'HREF': fn_url | hrefer | ee,
@@ -538,6 +580,7 @@ class Theme:
538580

539581
##########
540582
## EDGE
583+
##
541584

542585
kw_edge = {}
543586
kw_edge_optional = {"style": ["dashed"]}
@@ -560,6 +603,7 @@ class Theme:
560603

561604
##########
562605
## Other
606+
##
563607

564608
include_steps = False
565609
kw_step = {
@@ -1035,6 +1079,12 @@ def _make_node(self, plot_args: PlotArgs) -> pydot.Node:
10351079
label_styles.add("kw_op_rescheduled")
10361080
if nx_node.endured:
10371081
label_styles.add("kw_op_endured")
1082+
if nx_node.parallel:
1083+
label_styles.add("kw_op_parallel")
1084+
if nx_node.marshalled:
1085+
label_styles.add("kw_op_marshalled")
1086+
if nx_node.returns_dict:
1087+
label_styles.add("kw_op_returns_dict")
10381088

10391089
## Op-state
10401090
#
@@ -1060,6 +1110,8 @@ def _make_node(self, plot_args: PlotArgs) -> pydot.Node:
10601110
},
10611111
)
10621112

1113+
label_styles.add("op_badge_styles")
1114+
10631115
label_styles.add("user-overrides", _pub_props(node_attrs))
10641116

10651117
kw = label_styles.merge()

test/test_plot.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def _striplines(s):
7272

7373

7474
def test_op_label_template_full():
75+
theme_op_badges = plot.Theme.op_badge_styles
76+
avail_badges = theme_op_badges["badge_styles"]
7577
kw = dict(
7678
op_name="the op",
7779
fn_name="the fn",
@@ -85,6 +87,8 @@ def test_op_label_template_full():
8587
fn_url='http://fn_url.com/quoto"and',
8688
fn_tooltip="<fn\ntooltip>",
8789
fn_link_target="_top",
90+
badges=list(avail_badges), # cspell: disable-line
91+
**theme_op_badges,
8892
)
8993
got = plot._render_template(plot.Theme.op_template, **kw)
9094
print(got)
@@ -93,16 +97,28 @@ def test_op_label_template_full():
9397
<TR>
9498
<TD BORDER="1" SIDES="b" ALIGN="left" TOOLTIP="&lt;op &quot; &#9; tooltip&gt;" HREF="http://op_url.com_label_" TARGET="_self"
9599
><FONT COLOR="blue"><B>OP:</B> <I>the op</I></FONT></TD>
100+
<TD BORDER="1" SIDES="b"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="1" CELLPADDING="2">
101+
<TR><TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="#04277d" TITLE="endured(!)" TARGET="_self"
102+
><FONT FACE="monospace" COLOR="white"><B>E</B></FONT></TD><TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="#fc89ac" TITLE="rescheduled(?)" TARGET="_self"
103+
><FONT FACE="monospace" COLOR="white"><B>R</B></FONT></TD><TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="#b1ce9a" TITLE="parallel(|)" TARGET="_self"
104+
><FONT FACE="monospace" COLOR="white"><B>P</B></FONT></TD><TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="#4e3165" TITLE="marshalled($)" TARGET="_self"
105+
><FONT FACE="monospace" COLOR="white"><B>M</B></FONT></TD><TD STYLE="rounded" HEIGHT="22" VALIGN="BOTTOM" BGCOLOR="#cc5500" TITLE="returns_dict({})" TARGET="_self"
106+
><FONT FACE="monospace" COLOR="white"><B>D</B></FONT></TD></TR>
107+
</TABLE></TD>
96108
</TR><TR>
97-
<TD ALIGN="left" TOOLTIP="&lt;fn&#10;tooltip&gt;" HREF="http://fn_url.com/quoto_and" TARGET="_top"
109+
<TD COLSPAN="2" ALIGN="left" TOOLTIP="&lt;fn&#10;tooltip&gt;" HREF="http://fn_url.com/quoto_and" TARGET="_top"
98110
><FONT COLOR="blue"><B>FN:</B> the fn</FONT></TD>
99111
</TR>
100112
</TABLE>>
101113
"""
102114

103115
assert _striplines(got) == _striplines(exp)
116+
117+
## Check
104118
for k, v in [
105-
(k, v) for k, v in kw.items() if "tooltip" not in k and "url" not in k
119+
(k, v)
120+
for k, v in kw.items()
121+
if "tooltip" not in k and "url" not in k and "badge" not in k
106122
]:
107123
assert v in got, (k, v)
108124

@@ -115,6 +131,7 @@ def test_op_label_template_empty():
115131
<TR>
116132
<TD BORDER="1" SIDES="b" ALIGN="left" TARGET=""
117133
></TD>
134+
<TD BORDER="1" SIDES="b"></TD>
118135
</TR>
119136
</TABLE>>
120137
"""
@@ -129,8 +146,9 @@ def test_op_label_template_fn_empty():
129146
<TR>
130147
<TD BORDER="1" SIDES="b" ALIGN="left" TARGET=""
131148
><B>OP:</B> <I>op</I></TD>
149+
<TD BORDER="1" SIDES="b"></TD>
132150
</TR><TR>
133-
<TD ALIGN="left" TARGET=""
151+
<TD COLSPAN="2" ALIGN="left" TARGET=""
134152
><B>FN:</B> fn</TD>
135153
</TR>
136154
</TABLE>>
@@ -159,6 +177,7 @@ def test_op_label_template_nones():
159177
<TR>
160178
<TD BORDER="1" SIDES="b" ALIGN="left" TARGET=""
161179
></TD>
180+
<TD BORDER="1" SIDES="b"></TD>
162181
</TR>
163182
</TABLE>>
164183
"""
@@ -458,8 +477,9 @@ def test_node_dot_str0(dot_str_pipeline):
458477
<TR>
459478
<TD BORDER="1" SIDES="b" ALIGN="left" TOOLTIP="FunctionalOperation(name=&#x27;node&#x27;, needs=[&#x27;edge&#x27;, &#x27;digraph: strict&#x27;], provides=[&#x27;&lt;graph&gt;&#x27;], fn=&#x27;add&#x27;)" TARGET=""
460479
><B>OP:</B> <I>node</I></TD>
480+
<TD BORDER="1" SIDES="b"></TD>
461481
</TR><TR>
462-
<TD ALIGN="left" TOOLTIP="Same as a + b." TARGET=""
482+
<TD COLSPAN="2" ALIGN="left" TOOLTIP="Same as a + b." TARGET=""
463483
><B>FN:</B> &lt;built-in function add&gt;</TD>
464484
</TR>
465485
</TABLE>>, shape=plain, tooltip=<node>];
@@ -468,8 +488,9 @@ def test_node_dot_str0(dot_str_pipeline):
468488
<TR>
469489
<TD BORDER="1" SIDES="b" ALIGN="left" TOOLTIP="FunctionalOperation(name=&#x27;cu:sto:m&#x27;, needs=[&#x27;edge&#x27;, &#x27;digraph: strict&#x27;], provides=[&#x27;&lt;graph&gt;&#x27;], fn=&#x27;func&#x27;)" TARGET=""
470490
><B>OP:</B> <I>cu:sto:m</I></TD>
491+
<TD BORDER="1" SIDES="b"></TD>
471492
</TR><TR>
472-
<TD ALIGN="left" TOOLTIP="def func(a, b):&#10; pass" TARGET=""
493+
<TD COLSPAN="2" ALIGN="left" TOOLTIP="def func(a, b):&#10; pass" TARGET=""
473494
><B>FN:</B> test.test_plot.func</TD>
474495
</TR>
475496
</TABLE>>, shape=plain, tooltip=<cu:sto:m>];
@@ -510,8 +531,9 @@ def test_node_dot_str1(dot_str_pipeline, monkeypatch):
510531
<TR>
511532
<TD BORDER="1" SIDES="b" ALIGN="left" TOOLTIP="FunctionalOperation(name=&#x27;cu:sto:m&#x27;, needs=[&#x27;edge&#x27;, &#x27;digraph: strict&#x27;], provides=[&#x27;&lt;graph&gt;&#x27;], fn=&#x27;func&#x27;)" HREF="abc#{&#x27;dot_path&#x27;: &#x27;test.test_plot.func&#x27;, &#x27;posix_path&#x27;: &#x27;test/test_plot/func&#x27;}" TARGET="bad"
512533
><B>OP:</B> <I>cu:sto:m</I></TD>
534+
<TD BORDER="1" SIDES="b"></TD>
513535
</TR><TR>
514-
<TD ALIGN="left" TOOLTIP="def func(a, b):&#10; pass" HREF="abc#{&#x27;dot_path&#x27;: &#x27;test.test_plot.func&#x27;, &#x27;posix_path&#x27;: &#x27;test/test_plot/func&#x27;}" TARGET="bad"
536+
<TD COLSPAN="2" ALIGN="left" TOOLTIP="def func(a, b):&#10; pass" HREF="abc#{&#x27;dot_path&#x27;: &#x27;test.test_plot.func&#x27;, &#x27;posix_path&#x27;: &#x27;test/test_plot/func&#x27;}" TARGET="bad"
515537
><B>FN:</B> test.test_plot.func</TD>
516538
</TR>
517539
</TABLE>>, shape=plain, tooltip=<cu:sto:m>];

0 commit comments

Comments
 (0)