ZahirJS commited on
Commit
00a1c64
·
verified ·
1 Parent(s): 62d0de1

Update wbs_diagram_generator.py

Browse files
Files changed (1) hide show
  1. wbs_diagram_generator.py +64 -55
wbs_diagram_generator.py CHANGED
@@ -2,9 +2,9 @@ import graphviz
2
  import json
3
  from tempfile import NamedTemporaryFile
4
  import os
5
- from graph_generator_utils import add_nodes_and_edges
6
 
7
- def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
8
  """
9
  Generates a Work Breakdown Structure (WBS) Diagram from JSON input.
10
 
@@ -23,17 +23,23 @@ def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
23
  "id": "phase_prep",
24
  "label": "1. Preparation",
25
  "tasks": [
26
- {"id": "task_vision", "label": "1.1. Identify Vision"},
27
- {"id": "task_design", "label": "1.2. Design & Staffing"}
28
- ]
29
- },
30
- {
31
- "id": "phase_plan",
32
- "label": "2. Planning",
33
- "tasks": [
34
- {"id": "task_cost", "label": "2.1. Cost Analysis"},
35
- {"id": "task_benefit", "label": "2.2. Benefit Analysis"},
36
- {"id": "task_risk", "label": "2.3. Risk Assessment"}
 
 
 
 
 
 
37
  ]
38
  }
39
  ]
@@ -69,13 +75,12 @@ def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
69
  data['project_title'],
70
  shape='box',
71
  style='filled,rounded',
72
- fillcolor=base_color, # Use the selected base color
73
  fontcolor='white',
74
  fontsize='18'
75
  )
76
 
77
- # Logic for color gradient within WBS specific nodes (Phases and Tasks)
78
- # This ensures the gradient works correctly with the hardcoded base_color
79
  def get_gradient_color(depth, base_hex_color, lightening_factor=0.12):
80
  base_r = int(base_hex_color[1:3], 16)
81
  base_g = int(base_hex_color[3:5], 16)
@@ -88,8 +93,6 @@ def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
88
  return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
89
 
90
  def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.12):
91
- # Calculate brightness/lightness of the node color at this depth
92
- # and return black/white for text accordingly
93
  base_r = int(base_hex_color[1:3], 16)
94
  base_g = int(base_hex_color[3:5], 16)
95
  base_b = int(base_hex_color[5:7], 16)
@@ -97,23 +100,53 @@ def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
97
  current_g = base_g + (255 - base_g) * depth * lightening_factor
98
  current_b = base_b + (255 - base_b) * depth * lightening_factor
99
 
100
- # Simple luminance check (ITU-R BT.709 coefficients)
101
  luminance = (0.2126 * current_r + 0.7152 * current_g + 0.0722 * current_b) / 255
102
  return 'white' if luminance < 0.5 else 'black'
103
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- current_depth = 1 # Depth for phases
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  for phase in data['phases']:
107
  phase_id = phase.get('id')
108
  phase_label = phase.get('label')
109
- tasks = phase.get('tasks', [])
110
-
111
  if not all([phase_id, phase_label]):
112
- raise ValueError(f"Invalid phase: {phase}")
 
 
 
 
113
 
114
- phase_fill_color = get_gradient_color(current_depth, base_color)
115
- phase_font_color = get_font_color_for_background(current_depth, base_color)
116
-
117
  dot.node(
118
  phase_id,
119
  phase_label,
@@ -121,38 +154,14 @@ def generate_wbs_diagram(json_input: str) -> str: # Removed base_color parameter
121
  style='filled,rounded',
122
  fillcolor=phase_fill_color,
123
  fontcolor=phase_font_color,
124
- fontsize='14'
125
  )
126
- dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none') # Connect to root
127
 
128
- task_depth = current_depth + 1 # Depth for tasks
129
- task_font_size = max(9, 14 - (task_depth * 2))
 
130
 
131
- task_nodes_in_phase = []
132
- for task in tasks:
133
- task_id = task.get('id')
134
- task_label = task.get('label')
135
- if not all([task_id, task_label]):
136
- raise ValueError(f"Invalid task: {task}")
137
-
138
- task_fill_color = get_gradient_color(task_depth, base_color)
139
- task_font_color = get_font_color_for_background(task_depth, base_color)
140
-
141
- dot.node(
142
- task_id,
143
- task_label,
144
- shape='box',
145
- style='filled,rounded',
146
- fillcolor=task_fill_color,
147
- fontcolor=task_font_color,
148
- fontsize=str(task_font_size)
149
- )
150
- dot.edge(phase_id, task_id, color='#4a4a4a', arrowhead='none') # Connect task to phase
151
- task_nodes_in_phase.append(task_id)
152
-
153
- # Use subgraph to enforce vertical alignment for tasks within a phase
154
- if task_nodes_in_phase: # Only create subgraph if there are tasks
155
- dot.subgraph(name=f'cluster_{phase_id}')
156
  # Save to temporary file
157
  with NamedTemporaryFile(delete=False, suffix='.png') as tmp:
158
  dot.render(tmp.name, format='png', cleanup=True)
 
2
  import json
3
  from tempfile import NamedTemporaryFile
4
  import os
5
+ from graph_generator_utils import add_nodes_and_edges # Keeping this import for consistency, though WBS will use its own recursive logic.
6
 
7
+ def generate_wbs_diagram(json_input: str) -> str:
8
  """
9
  Generates a Work Breakdown Structure (WBS) Diagram from JSON input.
10
 
 
23
  "id": "phase_prep",
24
  "label": "1. Preparation",
25
  "tasks": [
26
+ {"id": "task_vision", "label": "1.1. Identify Vision",
27
+ "subtasks": [
28
+ {"id": "subtask_1_1_1", "label": "1.1.1. Problem Definition",
29
+ "sub_subtasks": [
30
+ {"id": "ss_task_1_1_1_1", "label": "1.1.1.1. Req. Analysis",
31
+ "sub_sub_subtasks": [
32
+ {"id": "sss_task_1_1_1_1_1", "label": "1.1.1.1.1. User Stories",
33
+ "final_level_tasks": [
34
+ {"id": "ft_1_1_1_1_1_1", "label": "1.1.1.1.1.1. Interview Users"}
35
+ ]
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ }
41
+ ]
42
+ }
43
  ]
44
  }
45
  ]
 
75
  data['project_title'],
76
  shape='box',
77
  style='filled,rounded',
78
+ fillcolor=base_color,
79
  fontcolor='white',
80
  fontsize='18'
81
  )
82
 
83
+ # Helper for color and font based on depth for WBS
 
84
  def get_gradient_color(depth, base_hex_color, lightening_factor=0.12):
85
  base_r = int(base_hex_color[1:3], 16)
86
  base_g = int(base_hex_color[3:5], 16)
 
93
  return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
94
 
95
  def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.12):
 
 
96
  base_r = int(base_hex_color[1:3], 16)
97
  base_g = int(base_hex_color[3:5], 16)
98
  base_b = int(base_hex_color[5:7], 16)
 
100
  current_g = base_g + (255 - base_g) * depth * lightening_factor
101
  current_b = base_b + (255 - base_b) * depth * lightening_factor
102
 
 
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
+ def _add_wbs_nodes_recursive(parent_id, current_level_tasks, current_depth):
107
+ for task_data in current_level_tasks:
108
+ task_id = task_data.get('id')
109
+ task_label = task_data.get('label')
110
+
111
+ if not all([task_id, task_label]):
112
+ raise ValueError(f"Invalid task data at depth {current_depth}: {task_data}")
113
+
114
+ node_fill_color = get_gradient_color(current_depth, base_color)
115
+ node_font_color = get_font_color_for_background(current_depth, base_color)
116
+ font_size = max(9, 14 - (current_depth * 2))
117
 
118
+ dot.node(
119
+ task_id,
120
+ task_label,
121
+ shape='box',
122
+ style='filled,rounded',
123
+ fillcolor=node_fill_color,
124
+ fontcolor=node_font_color,
125
+ fontsize=str(font_size)
126
+ )
127
+ dot.edge(parent_id, task_id, color='#4a4a4a', arrowhead='none')
128
+
129
+ # Recursively call for next level of tasks (subtasks, sub_subtasks, etc.)
130
+ # This handles arbitrary nested keys like 'subtasks', 'sub_subtasks', 'final_level_tasks'
131
+ next_level_keys = ['tasks', 'subtasks', 'sub_subtasks', 'sub_sub_subtasks', 'final_level_tasks']
132
+ for key_idx, key in enumerate(next_level_keys):
133
+ if key in task_data and isinstance(task_data[key], list):
134
+ _add_wbs_nodes_recursive(task_id, task_data[key], current_depth + 1)
135
+ break # Only process the first found sub-level key
136
+
137
+ # Process phases (level 1 from project_root)
138
+ phase_depth = 1
139
  for phase in data['phases']:
140
  phase_id = phase.get('id')
141
  phase_label = phase.get('label')
142
+
 
143
  if not all([phase_id, phase_label]):
144
+ raise ValueError(f"Invalid phase data: {phase}")
145
+
146
+ phase_fill_color = get_gradient_color(phase_depth, base_color)
147
+ phase_font_color = get_font_color_for_background(phase_depth, base_color)
148
+ font_size_phase = max(9, 14 - (phase_depth * 2))
149
 
 
 
 
150
  dot.node(
151
  phase_id,
152
  phase_label,
 
154
  style='filled,rounded',
155
  fillcolor=phase_fill_color,
156
  fontcolor=phase_font_color,
157
+ fontsize=str(font_size_phase)
158
  )
159
+ dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none')
160
 
161
+ # Start recursion for tasks under this phase
162
+ if 'tasks' in phase and isinstance(phase['tasks'], list):
163
+ _add_wbs_nodes_recursive(phase_id, phase['tasks'], phase_depth + 1)
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  # Save to temporary file
166
  with NamedTemporaryFile(delete=False, suffix='.png') as tmp:
167
  dot.render(tmp.name, format='png', cleanup=True)