← Back to graph_utils documentation
graph_utils - Source Code¶
File: Distributed_Design_Optimizer/postprocess/utils/graph_utils.py
# Copyright (C) The DistributedDesignOptimizer Contributors
# Licensed under the GNU General Public License v3.0. See LICENSE file for details.
#
# Additional Request:
# We kindly request that any modifications or changes to the source code be
# shared with us by submitting them as pull requests to our GitHub repository.
# This request is not legally binding but is made in the spirit of
# collaboration and open-source development.
"""
Graph layout and color utility functions for topology analysis.
"""
import networkx as nx
# Color palettes matching the original LoadGraph visuals
NODE_TYPE_COLORS = {
"discipline": "#5B8FF9",
"optimizer": "#61DDAA",
"data": "#F6BD16",
}
NODE_TYPE_SHAPES = {
"discipline": "dot",
"optimizer": "triangle",
"data": "square",
}
NODE_TYPE_SIZES = {
"discipline": 14,
"optimizer": 14,
"data": 11,
}
EDGE_TYPE_COLORS = {
"discipline_connection": "#9467bd",
"optimizer_connection": "#8c564b",
}
CLUSTER_PALETTE = [
"#5B8FF9", "#61DDAA", "#F6BD16", "#E8684A", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf",
]
def hierarchical_layout(graph: nx.Graph, subsystem_details: dict) -> dict[str, dict]:
"""Compute hierarchical positions from subsystem level data.
Args:
graph: The NetworkX graph whose nodes will be positioned.
subsystem_details: Mapping of node id to detail dicts containing a ``Level`` key.
Returns:
A dict mapping node id to ``{"x": float, "y": float}`` position dicts.
"""
levels: dict[int, list] = {}
for node in graph.nodes():
node_str = str(node)
detail = subsystem_details.get(node_str, subsystem_details.get(node, {}))
level = detail.get("Level", 0) if isinstance(detail, dict) else 0
levels.setdefault(level, []).append(node_str)
positions: dict[str, dict] = {}
for level_idx, level_key in enumerate(sorted(levels.keys())):
nodes_in_level = sorted(levels[level_key])
n = len(nodes_in_level)
start_x = -(n - 1) * 200
for j, node_id in enumerate(nodes_in_level):
positions[node_id] = {"x": start_x + j * 400, "y": level_idx * 300}
return positions
def green_yellow_red(normalized: float) -> str:
"""Map 0..1 to a green-yellow-red hex color.
Args:
normalized: A value in [0, 1] where 0 is green and 1 is red.
Returns:
A CSS hex color string interpolated along the green-yellow-red gradient.
"""
normalized = max(0.0, min(1.0, normalized))
if normalized <= 0.5:
r = int(255 * (normalized / 0.5))
g = 255
else:
r = 255
g = int(255 * (1.0 - (normalized - 0.5) / 0.5))
return f"#{r:02x}{g:02x}{0:02x}"
def safe_edge_iter(graph: nx.Graph, *, data: bool = False) -> list:
"""Iterate edges safely for both multi and non-multi graphs.
Args:
graph: A NetworkX graph (regular or multigraph).
Returns:
A list of edge tuples, each containing ``(u, v, key)`` or
``(u, v, key, data)`` depending on the *data* flag.
"""
if data:
try:
return list(graph.edges(keys=True, data=True))
except TypeError:
return [(u, v, 0, d) for u, v, d in graph.edges(data=True)]
else:
try:
return list(graph.edges(keys=True))
except TypeError:
return [(u, v, 0) for u, v in graph.edges()]
def build_edges(graph: nx.Graph, positions: dict[str, dict],
default_color: str | None = None) -> list[dict]:
"""Build an edge list with source/target coordinates for GraphWidget.
Args:
graph: The NetworkX graph to extract edges from.
positions: Mapping of node id to ``{"x": float, "y": float}`` dicts.
default_color: Optional fallback CSS color for edges without a type.
Returns:
A list of dicts, each containing source/target coordinates, color,
width, and tooltip for one edge.
"""
edges: list[dict] = []
for u, v, key, data in safe_edge_iter(graph, data=True):
u_str, v_str = str(u), str(v)
pos_u = positions.get(u_str, {"x": 0, "y": 0})
pos_v = positions.get(v_str, {"x": 0, "y": 0})
etype = data.get("type", "")
color = default_color or EDGE_TYPE_COLORS.get(etype, "#CCCCCC")
width = 2 if etype in EDGE_TYPE_COLORS else 1
tooltip_lines = [f"Edge: {u_str} → {v_str}"]
if etype:
tooltip_lines.append(f"Type: {etype}")
for attr_key in ("primalmappedresidual", "primalsharedresidual", "Dimensionality"):
if attr_key in data:
tooltip_lines.append(f"{attr_key}: {data[attr_key]}")
edges.append({
"source_x": pos_u["x"], "source_y": pos_u["y"],
"target_x": pos_v["x"], "target_y": pos_v["y"],
"color": color, "width": width,
"tooltip": "\n".join(tooltip_lines),
})
return edges