Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dict kwargs for interactive styling #27

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Examples
examples/command_line_usage.rst
examples/ex_004_code_comparison.rst
examples/ex_005_interactive_graph.rst
examples/ex_006_interactive_graph_styles.rst

Example notebooks:

Expand All @@ -17,4 +18,5 @@ Example notebooks:
- Example 003: :doc:`examples/command_line_usage`
- Example 004: :doc:`examples/ex_004_code_comparison`
- Example 005: :doc:`examples/ex_005_interactive_graph`
- Example 006: :doc:`examples/ex_006_interactive_graph_styles`

1 change: 1 addition & 0 deletions docs/examples/ex_005_interactive_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Interactive Class Graph
The ``inheritance_explorer`` relies on ``pyvis`` to construct interactive
graphs. Given an existing ``ClassGraphTree``::

from yt.frontends import *
from yt.data_objects.static_output import Dataset
from inheritance_explorer import ClassGraphTree

Expand Down
38 changes: 38 additions & 0 deletions docs/examples/ex_006_interactive_graph_styles.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Interactive Class Graph Styles
==============================

The style of interactive graphs can be controlled by a number of style dictionaries.
Using the previous example, first instantiate a ``ClassGraphTree``::

from yt.frontends import *
from yt.data_objects.static_output import Dataset
from inheritance_explorer import ClassGraphTree

cgt = ClassGraphTree(Dataset, funcname='_parse_parameter_file')

You can then pass in dictionaries to control the node style as well as the two
edge types: the base inheritance connections (``edge_style``) as well as the
similarity connections (``similarity_edge_style``). You can further set the color
of the nodes that over-ride the tracked function with the ``override_node_color``
keyword argument. For example::

node_style = {'size': 20.0, 'color':'magenta'}
edge_style = {'weight': 5}
sim_style = {'color': (.5, .5, 1.), 'weight':5}
graph = cgt.build_interactive_graph(width="500px",
height="500px",
bgcolor=(0.98,.98,.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'
)
graph.show('_tmp.html')

The following screen capture shows the interactive graph in action:

.. image:: /resources/interactive_styling.gif
:width: 500

Binary file added docs/resources/interactive_styling.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 71 additions & 17 deletions inheritance_explorer/inheritance_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ def _build_graph(
any arg accepted by pydot.Dot
include_similarity: bool
include edges for similar code (default True)
**kwargs:
any additional keyword arguments are passed to graphviz.Digraph(**kwargs)
kwargs:
any additional keyword arguments are passed to graphviz.Digraph
"""

gtype = "digraph"
Expand Down Expand Up @@ -344,36 +344,75 @@ 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:

"""
build an interactive Network graph from the current node list

Parameters
----------
*args:
any arg accepted by pydot.Dot
include_similarity: bool
include edges for similar code (default True)
**kwargs:
any additional keyword arguments are passed to graphviz.Digraph(**kwargs)
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 pyvis.Network

Returns
-------
Network
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))
clr_val = node_color
else:
hexcolor = rgb2hex((0.5, 0.5, 1.0))
# this node is over-ridden, use over-ride color
clr_val = override_color

node_style["color"] = clr_val

if node.parent:
parent_info = f"({node.parent.__name__})"
Expand All @@ -382,33 +421,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

Expand Down Expand Up @@ -473,6 +516,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}")
Expand Down
17 changes: 17 additions & 0 deletions inheritance_explorer/tests/test_inheritance_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading