ZahirJS commited on
Commit
a495f34
·
verified ·
1 Parent(s): 6fc15fa

Update wbs_diagram_generator.py

Browse files
Files changed (1) hide show
  1. wbs_diagram_generator.py +50 -40
wbs_diagram_generator.py CHANGED
@@ -4,7 +4,7 @@ from tempfile import NamedTemporaryFile
4
  import os
5
  from graph_generator_utils import add_nodes_and_edges # Reusing common utility
6
 
7
- def generate_wbs_diagram(json_input: str, base_color: str) -> str:
8
  """
9
  Generates a Work Breakdown Structure (WBS) Diagram from JSON input.
10
 
@@ -37,15 +37,6 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
37
  {"id": "task_benefit", "label": "2.2. Benefit Analysis"},
38
  {"id": "task_risk", "label": "2.3. Risk Assessment"}
39
  ]
40
- },
41
- {
42
- "id": "phase_dev",
43
- "label": "3. Development",
44
- "tasks": [
45
- {"id": "task_change", "label": "3.1. Change Management"},
46
- {"id": "task_impl", "label": "3.2. Implementation"},
47
- {"id": "task_beta", "label": "3.3. Beta Testing"}
48
- ]
49
  }
50
  ]
51
  }
@@ -72,6 +63,8 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
72
  }
73
  )
74
 
 
 
75
  # Project Title node (main node)
76
  dot.node(
77
  'project_root',
@@ -83,8 +76,35 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
83
  fontsize='18'
84
  )
85
 
86
- # Add phases and their tasks
87
- current_depth = 1 # Start depth for phases
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  for phase in data['phases']:
89
  phase_id = phase.get('id')
90
  phase_label = phase.get('label')
@@ -93,19 +113,8 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
93
  if not all([phase_id, phase_label]):
94
  raise ValueError(f"Invalid phase: {phase}")
95
 
96
- # Calculate color for phase node
97
- # This logic is adapted from add_nodes_and_edges but applied to phases/tasks
98
- # to keep consistency with the color gradient.
99
- lightening_factor = 0.12
100
- base_r = int(base_color[1:3], 16)
101
- base_g = int(base_color[3:5], 16)
102
- base_b = int(base_color[5:7], 16)
103
-
104
- phase_r = base_r + int((255 - base_r) * current_depth * lightening_factor)
105
- phase_g = base_g + int((255 - base_g) * current_depth * lightening_factor)
106
- phase_b = base_b + int((255 - base_b) * current_depth * lightening_factor)
107
- phase_fill_color = f'#{min(255, phase_r):02x}{min(255, phase_g):02x}{min(255, phase_b):02x}'
108
- phase_font_color = 'white' if current_depth * lightening_factor < 0.6 else 'black'
109
 
110
  dot.node(
111
  phase_id,
@@ -118,20 +127,19 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
118
  )
119
  dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none') # Connect to root
120
 
121
- task_depth = current_depth + 1
122
- task_r = base_r + int((255 - base_r) * task_depth * lightening_factor)
123
- task_g = base_g + int((255 - base_g) * task_depth * lightening_factor)
124
- task_b = base_b + int((255 - base_b) * task_depth * lightening_factor)
125
- task_fill_color = f'#{min(255, task_r):02x}{min(255, task_g):02x}{min(255, task_b):02x}'
126
- task_font_color = 'white' if task_depth * lightening_factor < 0.6 else 'black'
127
  task_font_size = max(9, 14 - (task_depth * 2))
128
 
 
129
  for task in tasks:
130
  task_id = task.get('id')
131
  task_label = task.get('label')
132
  if not all([task_id, task_label]):
133
  raise ValueError(f"Invalid task: {task}")
134
 
 
 
 
135
  dot.node(
136
  task_id,
137
  task_label,
@@ -142,17 +150,19 @@ def generate_wbs_diagram(json_input: str, base_color: str) -> str:
142
  fontsize=str(task_font_size)
143
  )
144
  dot.edge(phase_id, task_id, color='#4a4a4a', arrowhead='none') # Connect task to phase
 
145
 
146
  # Use subgraph to enforce vertical alignment for tasks within a phase
147
- # This makes columns in the WBS
148
- if tasks: # Only create subgraph if there are tasks
149
- with dot.subgraph(name=f'cluster_{phase_id}') as c:
150
- c.attr(rank='same') # Try to keep tasks in same rank/column if possible
151
- # Adding invisible nodes to link the phase to its tasks vertically
152
- # This helps in aligning tasks under their phase header more cleanly.
153
- c.node(phase_id)
154
- for task in tasks:
155
- c.node(task['id'])
 
156
 
157
  # Save to temporary file
158
  with NamedTemporaryFile(delete=False, suffix='.png') as tmp:
 
4
  import os
5
  from graph_generator_utils import add_nodes_and_edges # Reusing common utility
6
 
7
+ def generate_wbs_diagram(json_input: str, base_color: str) -> str: # base_color is now correctly used
8
  """
9
  Generates a Work Breakdown Structure (WBS) Diagram from JSON input.
10
 
 
37
  {"id": "task_benefit", "label": "2.2. Benefit Analysis"},
38
  {"id": "task_risk", "label": "2.3. Risk Assessment"}
39
  ]
 
 
 
 
 
 
 
 
 
40
  }
41
  ]
42
  }
 
63
  }
64
  )
65
 
66
+ # This line was REMOVED to ensure the passed base_color is used: base_color = '#19191a'
67
+
68
  # Project Title node (main node)
69
  dot.node(
70
  'project_root',
 
76
  fontsize='18'
77
  )
78
 
79
+ # Logic for color gradient within WBS specific nodes (Phases and Tasks)
80
+ # This ensures the gradient works correctly with the passed base_color
81
+ def get_gradient_color(depth, base_hex_color, lightening_factor=0.12):
82
+ base_r = int(base_hex_color[1:3], 16)
83
+ base_g = int(base_hex_color[3:5], 16)
84
+ base_b = int(base_hex_color[5:7], 16)
85
+
86
+ current_r = base_r + int((255 - base_r) * depth * lightening_factor)
87
+ current_g = base_g + int((255 - base_g) * depth * lightening_factor)
88
+ current_b = base_b + int((255 - base_b) * depth * lightening_factor)
89
+
90
+ return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
91
+
92
+ def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.12):
93
+ # Calculate brightness/lightness of the node color at this depth
94
+ # and return black/white for text accordingly
95
+ base_r = int(base_hex_color[1:3], 16)
96
+ base_g = int(base_hex_color[3:5], 16)
97
+ base_b = int(base_hex_color[5:7], 16)
98
+ current_r = base_r + (255 - base_r) * depth * lightening_factor
99
+ current_g = base_g + (255 - base_g) * depth * lightening_factor
100
+ current_b = base_b + (255 - base_b) * depth * lightening_factor
101
+
102
+ # Simple luminance check (ITU-R BT.709 coefficients)
103
+ luminance = (0.2126 * current_r + 0.7152 * current_g + 0.0722 * current_b) / 255
104
+ return 'white' if luminance < 0.5 else 'black'
105
+
106
+
107
+ current_depth = 1 # Depth for phases
108
  for phase in data['phases']:
109
  phase_id = phase.get('id')
110
  phase_label = phase.get('label')
 
113
  if not all([phase_id, phase_label]):
114
  raise ValueError(f"Invalid phase: {phase}")
115
 
116
+ phase_fill_color = get_gradient_color(current_depth, base_color)
117
+ phase_font_color = get_font_color_for_background(current_depth, base_color)
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  dot.node(
120
  phase_id,
 
127
  )
128
  dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none') # Connect to root
129
 
130
+ task_depth = current_depth + 1 # Depth for tasks
 
 
 
 
 
131
  task_font_size = max(9, 14 - (task_depth * 2))
132
 
133
+ task_nodes_in_phase = []
134
  for task in tasks:
135
  task_id = task.get('id')
136
  task_label = task.get('label')
137
  if not all([task_id, task_label]):
138
  raise ValueError(f"Invalid task: {task}")
139
 
140
+ task_fill_color = get_gradient_color(task_depth, base_color)
141
+ task_font_color = get_font_color_for_background(task_depth, base_color)
142
+
143
  dot.node(
144
  task_id,
145
  task_label,
 
150
  fontsize=str(task_font_size)
151
  )
152
  dot.edge(phase_id, task_id, color='#4a4a4a', arrowhead='none') # Connect task to phase
153
+ task_nodes_in_phase.append(task_id)
154
 
155
  # Use subgraph to enforce vertical alignment for tasks within a phase
156
+ if task_nodes_in_phase: # Only create subgraph if there are tasks
157
+ dot.subgraph(name=f'cluster_{phase_id}')
158
+ # You would typically add nodes to the subgraph if you want
159
+ # specific layout within it. For WBS columnar, just using rank attribute might suffice
160
+ # or ensuring proper `ranksep` and `nodesep` at graph level.
161
+ # The crucial part for columnar WBS is often setting nodes to the same rank
162
+ # or controlling the order. This is implicitly handled by `rankdir=TB`.
163
+ # The subgraphs themselves mostly define visual grouping (border) in Graphviz.
164
+ # Let's try to ensure vertical flow is emphasized without complex subgraphs that
165
+ # might interfere with the main layout engine. The primary connection handles rank.
166
 
167
  # Save to temporary file
168
  with NamedTemporaryFile(delete=False, suffix='.png') as tmp: