Skip to content

Commit 7bb1872

Browse files
committed
ENH(plot): URL/tooltips are now overriddable with node_props ...
to be overridden by graphixext. + ENH: prefix solution-value with its type in data-tooltipts.. + doc: TODO about merging `node_props` in a cloned networkX.
1 parent cbc2457 commit 7bb1872

File tree

2 files changed

+73
-20
lines changed

2 files changed

+73
-20
lines changed

graphtik/base.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,13 @@ def plot(
246246
:param title:
247247
an optional string to display at the bottom of the graph
248248
:param node_props:
249-
an optional nested dict of Graphviz attributes for certain nodes
249+
optional nested dict of Graphviz attributes for certain nodes
250+
Mind escaping values for `Graphviz`_ with ``html.escape()``.
251+
TODO: move `node_props` in a cloned networkX.
250252
:param edge_props:
251-
an optional nested dict of Graphviz attributes for certain edges
253+
An optional nested dict of Graphviz attributes for certain edges.
254+
Mind escaping values for `Graphviz`_ with ``html.escape()``.
255+
TODO: move `edge_props` in a cloned networkX.
252256
:param clusters:
253257
an optional mapping of nodes --> cluster-names, to group them
254258
:param splines:
@@ -346,7 +350,7 @@ def plot(
346350
fontname=italic;
347351
label=<netop>;
348352
splines=ortho;
349-
<a> [fillcolor=wheat, shape=invhouse, style=filled, tooltip=1];
353+
<a> [fillcolor=wheat, shape=invhouse, style=filled, tooltip="(int) 1"];
350354
...
351355
352356
"""

graphtik/plot.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import os
1010
import re
1111
from typing import Any, Callable, List, Mapping, Tuple, Union
12-
from urllib.parse import urlencode
1312

1413
import networkx
1514
import pydot
@@ -195,6 +194,15 @@ def as_identifier(s):
195194
return s
196195

197196

197+
# # TODO: plot meths with PlotContext
198+
# PlotContext = namedtuple(
199+
# "PlotContext", "inputs, outputs, solution, node_props, edge_props, clusters"
200+
# )
201+
# plot_context = PlotContext(
202+
# inputs, outputs, solution, node_props, edge_props, clusters
203+
# )
204+
205+
198206
def build_pydot(
199207
graph: networkx.Graph,
200208
name=None,
@@ -285,9 +293,6 @@ def get_node_name(a):
285293
if nx_node in getattr(solution, "overwrites", ())
286294
else fill_color
287295
)
288-
## NOTE: SVG tooltips not working without URL:
289-
# https://gitlab.com/graphviz/graphviz/issues/1425
290-
kw["tooltip"] = html.escape(str(solution.get(nx_node)))
291296

292297
else: # Operation
293298
kw = {
@@ -308,19 +313,25 @@ def get_node_name(a):
308313
kw["style"] = "filled"
309314
kw["fillcolor"] = cancel_color
310315

311-
try:
312-
filename = urlencode(inspect.getfile(nx_node.fn))
313-
kw["URL"] = f"file://{filename}"
314-
except Exception as ex:
315-
log.debug("Ignoring error while inspecting file of %s: %s", nx_node, ex)
316-
try:
317-
fn_tooltip = inspect.getsource(nx_node.fn)
318-
except Exception as ex:
319-
log.debug(
320-
"Ignoring error while inspecting source of %s: %s", nx_node, ex
321-
)
322-
fn_tooltip = str(nx_node)
323-
kw["tooltip"] = html.escape(fn_tooltip)
316+
# TODO: move `node_props` in a cloned networkX.
317+
if (
318+
not node_props
319+
or nx_node not in node_props
320+
or "URL" not in node_props[nx_node]
321+
):
322+
url = _get_node_url(nx_node)
323+
if url:
324+
kw["URL"] = url
325+
326+
# TODO: move `node_props` in a cloned networkX.
327+
if (
328+
not node_props
329+
or nx_node not in node_props
330+
or "tooltip" not in node_props[nx_node]
331+
):
332+
tooltip = _get_node_tooltip(nx_node, solution)
333+
if tooltip:
334+
kw["tooltip"] = tooltip
324335

325336
node = pydot.Node(**kw,)
326337
_apply_user_props(node, node_props, key=node.get_name())
@@ -390,6 +401,44 @@ def get_node_name(a):
390401
return dot
391402

392403

404+
def _get_node_url(nx_node) -> Union[str, None]:
405+
"""
406+
Default applied on all nodes if `URL` not in `node_props`.
407+
"""
408+
if isinstance(nx_node, Operation):
409+
try:
410+
filename = inspect.getfile(nx_node.fn)
411+
## TODO: hook sphinx to link nodes to generated documentation
412+
## NOTE: Jupyter lab is blocking local-urls (e.g. on SVGs).
413+
return html.escape(f"file://{filename}")
414+
except Exception as ex:
415+
log.debug("Ignoring error while inspecting file of %s: %s", nx_node, ex)
416+
417+
418+
def _get_node_tooltip(nx_node, sol: dict) -> Union[str, None]:
419+
"""
420+
Default applied on all nodes if `tooltip` not in `node_props`.
421+
422+
.. Note::
423+
SVG tooltips may not work without URL on PDFs:
424+
https://gitlab.com/graphviz/graphviz/issues/1425
425+
"""
426+
txt = None
427+
if isinstance(nx_node, Operation):
428+
try:
429+
txt = inspect.getsource(nx_node.fn)
430+
except Exception as ex:
431+
log.debug("Ignoring error while inspecting source of %s: %s", nx_node, ex)
432+
txt = str(nx_node)
433+
else:
434+
if sol is not None:
435+
val = sol.get(nx_node)
436+
txt = "None" if val is None else f"({type(val).__name__}) {val}"
437+
438+
if txt:
439+
return html.escape(txt)
440+
441+
393442
def supported_plot_formats() -> List[str]:
394443
"""return automatically all `pydot` extensions"""
395444
return [".%s" % f for f in pydot.Dot().formats]

0 commit comments

Comments
 (0)