From 6937a8abe7fd6f2ed594187781f9abcbdb1c44ef Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Wed, 31 May 2023 12:16:29 +0200 Subject: [PATCH] feat: allow exporting/rendering monochrome graphs --- arguebuf/cli/graph.py | 8 ++++---- arguebuf/dump/_dump_d2.py | 13 ++++++++----- arguebuf/dump/_dump_graphviz.py | 19 ++++++++++--------- arguebuf/model/node.py | 13 ++++++++++--- tests/test_render_graph.py | 12 +++++++++--- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/arguebuf/cli/graph.py b/arguebuf/cli/graph.py index 773c57b..d6b5dca 100644 --- a/arguebuf/cli/graph.py +++ b/arguebuf/cli/graph.py @@ -75,6 +75,7 @@ def render( max_nodes: t.Optional[int] = None, prog: str = "dot", dpi: int = 300, + monochrome: bool = False, ) -> None: if not output_folder: output_folder = input_folder @@ -116,6 +117,7 @@ def render( scheme_label=_strip_node_labels if strip_node_labels else None, edge_style=edge_style, max_nodes=max_nodes, + monochrome=monochrome, ) ag.render.graphviz(gv, path_pair.target, prog, dpi) @@ -180,8 +182,7 @@ def statistics( total_scheme_nodes = sum(scheme_nodes) total_edges = sum(edges) - typer.echo( - f"""Total Graphs: {total_graphs} + typer.echo(f"""Total Graphs: {total_graphs} Total Atom Nodes: {total_atom_nodes} Total Scheme Nodes: {total_scheme_nodes} @@ -197,5 +198,4 @@ def statistics( Min. Atom Nodes: {min(atom_nodes)} Min. Scheme Nodes: {min(scheme_nodes)} -Min. Edges: {min(edges)}""" - ) +Min. Edges: {min(edges)}""") diff --git a/arguebuf/dump/_dump_d2.py b/arguebuf/dump/_dump_d2.py index 157a312..1e476f8 100644 --- a/arguebuf/dump/_dump_d2.py +++ b/arguebuf/dump/_dump_d2.py @@ -1,10 +1,9 @@ -import textwrap import typing as t from arguebuf.model import Graph from arguebuf.model.edge import Edge -from arguebuf.model.node import AtomNode, SchemeNode, AbstractNode -from arguebuf.schemas.d2 import D2Graph, D2Edge, D2Node, D2Style +from arguebuf.model.node import AbstractNode, AtomNode, SchemeNode +from arguebuf.schemas.d2 import D2Edge, D2Graph, D2Node, D2Style def dump_d2( @@ -12,6 +11,7 @@ def dump_d2( atom_label: t.Optional[t.Callable[[AtomNode], str]] = None, scheme_label: t.Optional[t.Callable[[SchemeNode], str]] = None, max_nodes: t.Optional[int] = None, + monochrome: bool = False, ) -> t.Optional[D2Graph]: if len(graph.nodes) > (max_nodes or 1000): return None @@ -27,6 +27,7 @@ def dump_d2( d2_graph, major_claim=graph.major_claim == node, label_func=atom_label, + monochrome=monochrome, ) for node in graph._scheme_nodes.values(): @@ -35,6 +36,7 @@ def dump_d2( d2_graph, label_func=scheme_label, major_claim=False, + monochrome=monochrome, ) for edge in graph._edges.values(): @@ -47,14 +49,15 @@ def _dump_node( node: AbstractNode, g: D2Graph, major_claim: bool, + monochrome: bool, label_func: t.Optional[t.Callable[[AbstractNode], str]] = None, ) -> None: label: str = label_func(node) if label_func else node.label label = label.replace("\n", "") if type(node) == AtomNode: - color = node.color(major_claim) + color = node.color(major_claim, monochrome) else: - color = node.color(major_claim=False) + color = node.color(False, monochrome) nodeStyle: D2Style = D2Style( font_color=color.fg, diff --git a/arguebuf/dump/_dump_graphviz.py b/arguebuf/dump/_dump_graphviz.py index 3106d86..b225d1f 100644 --- a/arguebuf/dump/_dump_graphviz.py +++ b/arguebuf/dump/_dump_graphviz.py @@ -50,6 +50,7 @@ def dump_graphviz( edge_attr: t.Optional[t.Mapping[str, str]] = None, edge_style: t.Optional[EdgeStyle] = None, max_nodes: t.Optional[int] = None, + monochrome: bool = False, ) -> t.Optional[GraphvizGraph]: """Transform a Graph instance into an instance of GraphViz directed graph. Make sure that a GraphViz Executable path is set on your machine for visualization. Refer to the GraphViz library for additional information.""" @@ -86,10 +87,11 @@ def gv_margin(x): **node_attr, } ) - gv_graph.edge_attr.update({"color": "#9E9E9E", **edge_attr}) + gv_graph.edge_attr.update( + {"color": "#000000" if monochrome else "#9E9E9E", **edge_attr} + ) gv_graph.graph_attr.update( { - # "bgcolor": "#000000", "rankdir": "BT", "margin": "0", "nodesep": str(nodesep or 0.25), @@ -107,14 +109,11 @@ def gv_margin(x): graph.major_claim == node, label_func=atom_label, wrap_col=wrap_col or 36, + monochrome=monochrome, ) for node in graph._scheme_nodes.values(): - _dump_scheme( - node, - gv_graph, - label_func=scheme_label, - ) + _dump_scheme(node, gv_graph, label_func=scheme_label, monochrome=monochrome) for edge in graph._edges.values(): _dump_edge(edge, gv_graph) @@ -127,10 +126,11 @@ def _dump_atom( g: GraphvizGraph, major_claim: bool, wrap_col: int, + monochrome: bool, label_func: t.Optional[t.Callable[[AtomNode], str]] = None, ) -> None: """Submethod used to export Graph object g into GV Graph format.""" - color = node.color(major_claim) + color = node.color(major_claim, monochrome) label = label_func(node) if label_func else node.label # TODO: Improve wrapping @@ -149,11 +149,12 @@ def _dump_atom( def _dump_scheme( node: SchemeNode, g: GraphvizGraph, + monochrome: bool, label_func: t.Optional[t.Callable[[SchemeNode], str]] = None, ) -> None: """Submethod used to export Graph object g into GV Graph format.""" - color = node.color(major_claim=False) + color = node.color(False, monochrome) label = label_func(node) if label_func else node.label add_gv_node( diff --git a/arguebuf/model/node.py b/arguebuf/model/node.py index 20b86ee..0cd1760 100644 --- a/arguebuf/model/node.py +++ b/arguebuf/model/node.py @@ -43,6 +43,9 @@ def __init__( self.border = border or self.bg +COLOR_MONOCHROME = Color(bg="#ffffff", fg="#000000", border="#000000") + + scheme2color: t.Dict[t.Type[Scheme], Color] = { Support: Color(bg="#4CAF50"), Attack: Color(bg="#F44336"), @@ -98,7 +101,7 @@ def label(self) -> str: """Generate a matching node label (e.g., for graphviz)""" @abstractmethod - def color(self, major_claim: bool) -> Color: + def color(self, major_claim: bool, monochrome: bool) -> Color: """Get the color used in OVA based on `category`.""" @@ -165,8 +168,10 @@ def from_argdown_json( metadata=Metadata(timestamp, timestamp), ) - def color(self, major_claim: bool) -> Color: + def color(self, major_claim: bool, monochrome: bool) -> Color: """Get the color for rendering the node.""" + if monochrome: + return COLOR_MONOCHROME return Color(bg="#0D47A1") if major_claim else Color(bg="#2196F3") @@ -218,8 +223,10 @@ def label(self) -> str: return label - def color(self, major_claim: bool) -> Color: + def color(self, major_claim: bool, monochrome: bool) -> Color: """Get the color used in OVA based on `category`.""" + if monochrome: + return COLOR_MONOCHROME return scheme2color[type(self.scheme)] if self.scheme else Color(bg="#009688") diff --git a/tests/test_render_graph.py b/tests/test_render_graph.py index 8e16177..aecc310 100644 --- a/tests/test_render_graph.py +++ b/tests/test_render_graph.py @@ -9,7 +9,10 @@ def test_render_graph(): an1 = ag.AtomNode(text="Lincoln Chafee just isn't presidential material") an2 = ag.AtomNode(text="Chafee is president material") an3 = ag.AtomNode( - text="if elections weren't just popularity contests Chafee would probably do just fine" + text=( + "if elections weren't just popularity contests Chafee would probably do" + " just fine" + ) ) an4 = ag.AtomNode(text="Chafee is just not figurehead material") an5 = ag.AtomNode(text="Chafee does have good positions") @@ -63,5 +66,8 @@ def test_render_graph(): graphviz_graph = ag.dump.graphviz(g) # Render d2 graph and graphviz graph - ag.render.d2(d2_graph, "test_d2.png") - ag.render.graphviz(graphviz_graph, "test_graphviz.png") + if d2_graph is not None: + ag.render.d2(d2_graph, "test_d2.png") + + if graphviz_graph is not None: + ag.render.graphviz(graphviz_graph, "test_graphviz.png")