From 032205f37f454a5034119007dac7c91c397b3f4a Mon Sep 17 00:00:00 2001 From: chrishavlin Date: Thu, 30 May 2024 11:46:23 -0500 Subject: [PATCH] add dict kwargs for interactive styling --- .../ex_006_interactive_graph_styles.ipynb | 108 ++++++++++++++++++ inheritance_explorer/inheritance_explorer.py | 78 +++++++++++-- .../tests/test_inheritance_explorer.py | 17 +++ 3 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 docs/examples/ex_006_interactive_graph_styles.ipynb diff --git a/docs/examples/ex_006_interactive_graph_styles.ipynb b/docs/examples/ex_006_interactive_graph_styles.ipynb new file mode 100644 index 0000000..17289ba --- /dev/null +++ b/docs/examples/ex_006_interactive_graph_styles.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "02cf8f5a-7b49-42aa-9689-74c015b9264a", + "metadata": {}, + "outputs": [], + "source": [ + "from yt.frontends import *\n", + "from yt.data_objects.static_output import Dataset\n", + "from inheritance_explorer import ClassGraphTree\n", + "\n", + "cgt = ClassGraphTree(Dataset, funcname='_parse_parameter_file')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a70029ac-5a0a-4c13-bcbe-03536f8e086b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "_tmp.html\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "node_style = {'size': 20.0, 'color':'magenta'}\n", + "edge_style = {'weight': 5}\n", + "sim_style = {'color': (.5, 1., 1.), 'weight':5}\n", + "graph = cgt.build_interactive_graph(width=\"500px\",\n", + " height=\"500px\",\n", + " bgcolor=(0.98,.98,.98),\n", + " font_color='black', \n", + " node_style=node_style, \n", + " edge_style = edge_style,\n", + " similarity_edge_style=sim_style, \n", + " override_node_color='black',\n", + " cdn_resources='in_line'\n", + " ) \n", + "graph.show('_tmp.html')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7fba449e-7f99-40b7-9cb8-fd0df2f17f79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc78a8f2-1acb-4d42-9a19-64fbea5842a4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/inheritance_explorer/inheritance_explorer.py b/inheritance_explorer/inheritance_explorer.py index 739d943..b7a6f69 100644 --- a/inheritance_explorer/inheritance_explorer.py +++ b/inheritance_explorer/inheritance_explorer.py @@ -344,7 +344,13 @@ def plot_similarity( return sim_labels, ax def build_interactive_graph( - self, include_similarity: bool = True, **kwargs + self, + include_similarity: bool = True, + node_style: dict = None, + edge_style: dict = None, + similarity_edge_style: dict = None, + override_node_color: Union[str, tuple] = None, + **kwargs, ) -> Network: """ @@ -356,6 +362,19 @@ def build_interactive_graph( any arg accepted by pydot.Dot include_similarity: bool include edges for similar code (default True) + node_style: dict + a dictionary of parameters to pass to pyvis.network.Network.add_node + note that these settings will be applied to **all** nodes. + edge_style: dict + a dictionary of parameters to pass to pyvis.network.Network.add_edge + note that these settings will be applied to **all** edges. + similarity_edge_style: dict + a dictionary of parameters to pass to pyvis.network.Network.add_edge + for the similarity links. Only used if include_similarity is True. + override_node_color: str or tuple + the color for nodes that over-ride the function being tracked. Only + used if the base ClassGraphTree was initialized with a ``funcname`` + to track. **kwargs: any additional keyword arguments are passed to graphviz.Digraph(**kwargs) @@ -365,15 +384,37 @@ def build_interactive_graph( the pyvis.Network representation of the class hierarchy. """ + if node_style is None: + node_style = {} + if edge_style is None: + edge_style = {} + if similarity_edge_style is None: + similarity_edge_style = {} + + sim_node_physics = similarity_edge_style.pop("physics", False) + edge_physics = edge_style.pop("physics", True) + + node_color = _validate_color(node_style.get("color", None), (0.7, 0.7, 0.7)) + edge_color = _validate_color(edge_style.pop("color", None), (0.7, 0.7, 0.7)) + sim_edge_color = _validate_color( + similarity_edge_style.pop("color", None), (0, 0.5, 1.0) + ) + override_color = _validate_color(override_node_color, (0.5, 0.5, 1.0)) + + bgcolor = _validate_color(kwargs.pop("bgcolor", None), (1.0, 1.0, 1.0)) + font_color = _validate_color(kwargs.pop("font_color", None), (0.0, 0.0, 0.0)) + grph = nx.Graph(directed=True) iset = 0 for node in self._node_list: if node.color == "#000000": - # no override. show improve this... - hexcolor = rgb2hex((0.7, 0.7, 0.7)) + # this node is over-ridden, use over-ride color + clr_val = override_color else: - hexcolor = rgb2hex((0.5, 0.5, 1.0)) + clr_val = node_color + + node_style["color"] = clr_val if node.parent: parent_info = f"({node.parent.__name__})" @@ -382,33 +423,37 @@ def build_interactive_graph( grph.add_node( node.child_id, title=f"{node.child_name}{parent_info}", - color=hexcolor, + **node_style, ) if include_similarity: if int(node.child_id) in self.similarity_sets: - hexcolor = rgb2hex((0, 0.5, 1.0)) iset += 1 for similar_node_id in self.similarity_sets[int(node.child_id)]: grph.add_edge( node.child_id, str(similar_node_id), - color=hexcolor, - physics=False, + color=sim_edge_color, + physics=sim_node_physics, + **similarity_edge_style, ) arrowsop = {"from": {"enabled": True}} + if node.parent: grph.add_edge( node.child_id, node.parent_id, - color=rgb2hex((0.7, 0.7, 0.7)), - physics=True, + color=edge_color, + physics=edge_physics, arrows=arrowsop, + **edge_style, ) # return the interactive pyvis Network graph - network_wrapper = Network(notebook=True, **kwargs) + network_wrapper = Network( + notebook=True, bgcolor=bgcolor, font_color=font_color, **kwargs + ) network_wrapper.from_nx(grph) return network_wrapper @@ -473,6 +518,17 @@ def display_code_comparison(self): display_code_compare(self) +def _validate_color(clr, default_rgb_tuple: tuple) -> str: + if clr is None: + return rgb2hex(default_rgb_tuple) + elif isinstance(clr, tuple): + return rgb2hex(clr) + elif isinstance(clr, str): + return clr + msg = f"clr has unexpected type: {type(clr)}" + raise TypeError(msg) + + def _show_graph(dot_graph: pydot.Dot, format: str = "svg", env: str = "notebook"): # return a GraphViz dot graph in a jupyter-friendly format. create_func = getattr(dot_graph, f"create_{format}") diff --git a/inheritance_explorer/tests/test_inheritance_explorer.py b/inheritance_explorer/tests/test_inheritance_explorer.py index c572e4c..eccf7ea 100644 --- a/inheritance_explorer/tests/test_inheritance_explorer.py +++ b/inheritance_explorer/tests/test_inheritance_explorer.py @@ -109,6 +109,23 @@ def test_interactive(cgt): _ = cgt.show_graph(env=None) +def test_interactive_styles(cgt): + node_style = {"size": 20.0, "color": "magenta"} + edge_style = {"weight": 5} + sim_style = {"color": (0.5, 1.0, 1.0), "weight": 5} + _ = cgt.build_interactive_graph( + width="500px", + height="500px", + bgcolor=(0.98, 0.98, 0.98), + font_color="black", + node_style=node_style, + edge_style=edge_style, + similarity_edge_style=sim_style, + override_node_color="black", + cdn_resources="in_line", + ) + + @pytest.mark.parametrize("max_recursion_level", (0, 1)) def test_recursion_level(max_recursion_level): cgt = ClassGraphTree(