From a40d97c0da5af6d65987ebee10402dec5e4042ae Mon Sep 17 00:00:00 2001 From: HansBug Date: Thu, 23 Feb 2023 15:37:09 +0800 Subject: [PATCH] dev(hansbug): optimize graphviz visualization --- treevalue/tree/tree/graph.py | 30 ++++++++++++++++++++++++++---- treevalue/utils/tree.py | 4 +--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/treevalue/tree/tree/graph.py b/treevalue/tree/tree/graph.py index 39aafcbdb8..eb4653eb41 100644 --- a/treevalue/tree/tree/graph.py +++ b/treevalue/tree/tree/graph.py @@ -1,7 +1,8 @@ +import html from functools import lru_cache from typing import Type, Callable, Union, Optional, Tuple -from graphviz import Digraph, nohtml +from graphviz import Digraph from hbutils.reflection import post_process, dynamic_call, freduce from .tree import TreeValue @@ -134,6 +135,27 @@ _GENERIC_N = 36 _GENERIC_S = _GENERIC_N // 3 +def _custom_repr(x): + if isinstance(x, (str,)): + return repr(x) + else: + return str(x) + + +_LEFT_ALIGN_LINESEP = r'\l' + + +def _custom_html_escape(x): + s = html.escape(x) \ + .replace(' ', ' ') \ + .replace('\r\n', _LEFT_ALIGN_LINESEP) \ + .replace('\n', _LEFT_ALIGN_LINESEP) \ + .replace('\r', _LEFT_ALIGN_LINESEP) + if len(x.splitlines()) > 1 and not s.endswith(_LEFT_ALIGN_LINESEP): + s += _LEFT_ALIGN_LINESEP + return s + + def graphics(*trees, title: Optional[str] = None, cfg: Optional[dict] = None, dup_value: Union[bool, Callable, type, Tuple[Type, ...]] = False, repr_gen: Optional[Callable] = None, @@ -182,9 +204,9 @@ def graphics(*trees, title: Optional[str] = None, cfg: Optional[dict] = None, return build_graph( *trees, node_id_gen=_node_tag, - graph_title=title or "", + graph_title=title, graph_cfg=cfg or {}, - repr_gen=repr_gen or (lambda x: nohtml(repr(x))), + repr_gen=repr_gen or (lambda x: _custom_html_escape(_custom_repr(x))), iter_gen=lambda n: iter(n.items()) if isinstance(n, TreeValue) else None, node_cfg_gen=_dict_call_merge(lambda n, p, np, pp, is_node, is_root, root: { 'fillcolor': _shape_color(root[2]), @@ -193,7 +215,7 @@ def graphics(*trees, title: Optional[str] = None, cfg: Optional[dict] = None, 'style': 'filled', 'shape': 'diamond' if is_root else ('ellipse' if is_node else 'box'), 'penwidth': 3 if is_root else 1.5, - 'fontname': "Times-Roman bold" if is_node else "Times-Roman", + 'fontname': "monospace" if is_node else "monospace", }, (node_cfg_gen or (lambda: {}))), edge_cfg_gen=_dict_call_merge(lambda n, p, np, pp, is_node, root: { 'arrowhead': 'vee' if is_node else 'dot', diff --git a/treevalue/utils/tree.py b/treevalue/utils/tree.py index aad1a63dcb..8672773825 100644 --- a/treevalue/utils/tree.py +++ b/treevalue/utils/tree.py @@ -6,8 +6,6 @@ from typing import Optional, Mapping, Any, Callable from graphviz import Digraph from hbutils.reflection import dynamic_call, post_process -from .random import random_hex_with_timestamp - def _title_flatten(title): title = re.sub(r'[^a-zA-Z0-9_]+', '_', str(title)) @@ -104,7 +102,7 @@ def build_graph(*roots, node_id_gen: Optional[Callable] = None, roots = [item for item in roots if item is not None] node_id_gen = dynamic_call(suffixed_node_id(node_id_gen or _default_node_id)) - graph_title = graph_title or ('untitled_' + random_hex_with_timestamp()) + graph_title = graph_title or '' graph_name = graph_name or _title_flatten(graph_title) graph_cfg = _no_none_value(graph_cfg or {}) -- GitLab