File size: 6,075 Bytes
991c9f4
 
 
 
 
 
59966b9
991c9f4
 
 
 
 
 
 
 
59966b9
 
 
991c9f4
59966b9
 
991c9f4
59966b9
 
991c9f4
 
 
59966b9
 
991c9f4
 
 
59966b9
 
991c9f4
 
 
 
 
59966b9
 
 
991c9f4
59966b9
 
 
991c9f4
 
59966b9
991c9f4
 
 
 
 
59966b9
991c9f4
 
 
59966b9
991c9f4
 
 
59966b9
 
991c9f4
 
 
 
 
59966b9
991c9f4
 
 
 
 
59966b9
991c9f4
 
 
59966b9
991c9f4
59966b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# import graphviz

# def add_nodes_and_edges(dot: graphviz.Digraph, parent_id: str, nodes_list: list, current_depth: int, base_color: str):
#     """
#     Recursively adds nodes and edges to a Graphviz Digraph object,
#     applying a color gradient and consistent styling.

#     Args:
#         dot (graphviz.Digraph): The Graphviz Digraph object to modify.
#         parent_id (str): The ID of the parent node for the current set of nodes.
#         nodes_list (list): A list of dictionaries, each representing a node
#                            with 'id', 'label', 'relationship', and optional 'subnodes'.
#         current_depth (int): The current depth in the graph hierarchy (0 for central node).
#         base_color (str): The hexadecimal base color for the deepest nodes.
#     """
#     # Calculate color for current depth, making it lighter
#     # This factor determines how quickly the color lightens per level.
#     lightening_factor = 0.12 
    
#     # Convert base_color hex to RGB for interpolation
#     # Ensure base_color is a valid hex string before converting
#     if not isinstance(base_color, str) or not base_color.startswith('#') or len(base_color) != 7:
#         base_color = '#19191a' # Fallback to default dark if invalid

#     base_r = int(base_color[1:3], 16)
#     base_g = int(base_color[3:5], 16)
#     base_b = int(base_color[5:7], 16)

#     # Calculate current node color by blending towards white
#     current_r = base_r + int((255 - base_r) * current_depth * lightening_factor)
#     current_g = base_g + int((255 - base_g) * current_depth * lightening_factor)
#     current_b = base_b + int((255 - base_b) * current_depth * lightening_factor)

#     # Clamp values to 255 to stay within valid RGB range
#     current_r = min(255, current_r)
#     current_g = min(255, current_g)
#     current_b = min(255, current_b)
    
#     node_fill_color = f'#{current_r:02x}{current_g:02x}{current_b:02x}'

#     # Font color: white for dark nodes, black for very light nodes for readability
#     font_color = 'white' if current_depth * lightening_factor < 0.6 else 'black'
    
#     # Edge colors and font sizes
#     edge_color = '#4a4a4a' # Dark gray for lines
#     # Font size adjusts based on depth, ensuring a minimum size
#     font_size = max(9, 14 - (current_depth * 2)) 
#     edge_font_size = max(7, 10 - (current_depth * 1))

#     for node in nodes_list:
#         node_id = node.get('id')
#         label = node.get('label')
#         relationship = node.get('relationship')
        
#         # Basic validation for node data
#         if not all([node_id, label, relationship]):
#             raise ValueError(f"Invalid node: {node}")
            
#         # Add node with specified style
#         dot.node(
#             node_id,
#             label,
#             shape='box', # All nodes are rectangular
#             style='filled,rounded', # Filled and rounded corners
#             fillcolor=node_fill_color, 
#             fontcolor=font_color,
#             fontsize=str(font_size) 
#         )
        
#         # Add edge from parent to current node
#         dot.edge(
#             parent_id,
#             node_id,
#             label=relationship,
#             color=edge_color,
#             fontcolor=edge_color, # Edge label color also dark gray
#             fontsize=str(edge_font_size) 
#         )
        
#         # Recursively call for subnodes
#         if 'subnodes' in node:
#             add_nodes_and_edges(dot, node_id, node['subnodes'], current_depth + 1, base_color)

import graphviz

def add_nodes_and_edges(dot: graphviz.Digraph, parent_id: str, nodes_list: list, current_depth: int, base_color: str):
    """
    Recursively adds nodes and edges to a Graphviz Digraph object,
    applying a color gradient and consistent styling.
    Args:
        dot (graphviz.Digraph): The Graphviz Digraph object to modify.
        parent_id (str): The ID of the parent node for the current set of nodes.
        nodes_list (list): A list of dictionaries, each representing a node
                           with 'id', 'label', 'relationship', and optional 'subnodes'.
        current_depth (int): The current depth in the graph hierarchy (0 for central node).
        base_color (str): The hexadecimal base color for the deepest nodes.
    """
    lightening_factor = 0.06
    
    if not isinstance(base_color, str) or not base_color.startswith('#') or len(base_color) != 7:
        base_color = '#BEBEBE'
    base_r = int(base_color[1:3], 16)
    base_g = int(base_color[3:5], 16)
    base_b = int(base_color[5:7], 16)
    
    current_r = base_r + int((255 - base_r) * current_depth * lightening_factor)
    current_g = base_g + int((255 - base_g) * current_depth * lightening_factor)
    current_b = base_b + int((255 - base_b) * current_depth * lightening_factor)
    
    current_r = min(255, current_r)
    current_g = min(255, current_g)
    current_b = min(255, current_b)
    
    node_fill_color = f'#{current_r:02x}{current_g:02x}{current_b:02x}'
    font_color = 'black'
    
    edge_color = '#4a4a4a'
    font_size = max(9, 14 - (current_depth * 2)) 
    edge_font_size = max(7, 10 - (current_depth * 1))
    
    for node in nodes_list:
        node_id = node.get('id')
        label = node.get('label')
        relationship = node.get('relationship')
        
        if not all([node_id, label, relationship]):
            raise ValueError(f"Invalid node: {node}")
            
        dot.node(
            node_id,
            label,
            shape='box',
            style='filled,rounded',
            fillcolor=node_fill_color, 
            fontcolor=font_color,
            fontsize=str(font_size) 
        )
        
        dot.edge(
            parent_id,
            node_id,
            label=relationship,
            color=edge_color,
            fontcolor=edge_color,
            fontsize=str(edge_font_size) 
        )
        
        if 'subnodes' in node:
            add_nodes_and_edges(dot, node_id, node['subnodes'], current_depth + 1, base_color)