ZahirJS commited on
Commit
3cbeab5
·
verified ·
1 Parent(s): 05beaf5

Update wbs_diagram_generator.py

Browse files
Files changed (1) hide show
  1. wbs_diagram_generator.py +259 -28
wbs_diagram_generator.py CHANGED
@@ -1,3 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import graphviz
2
  import json
3
  from tempfile import NamedTemporaryFile
@@ -128,30 +371,31 @@ def generate_wbs_diagram(json_input: str, output_format: str) -> str:
128
  name='WBSDiagram',
129
  format='png',
130
  graph_attr={
131
- 'rankdir': 'TB', # Top-to-Bottom hierarchy
132
- 'splines': 'ortho', # Straight lines
133
- 'bgcolor': 'white', # White background
134
- 'pad': '0.5', # Padding
135
- 'ranksep': '0.6', # Adjust vertical separation between ranks
136
- 'nodesep': '0.5' # Adjust horizontal separation between nodes
137
  }
138
  )
139
 
140
- base_color = '#19191a'
141
 
142
- # Main node
143
  dot.node(
144
  'project_root',
145
  data['project_title'],
146
  shape='box',
147
  style='filled,rounded',
148
  fillcolor=base_color,
149
- fontcolor='white',
150
  fontsize='18'
151
  )
152
 
153
- # Helper for color and font based on depth for WBS
154
- def get_gradient_color(depth, base_hex_color, lightening_factor=0.12):
 
 
155
  base_r = int(base_hex_color[1:3], 16)
156
  base_g = int(base_hex_color[3:5], 16)
157
  base_b = int(base_hex_color[5:7], 16)
@@ -162,16 +406,8 @@ def generate_wbs_diagram(json_input: str, output_format: str) -> str:
162
 
163
  return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
164
 
165
- def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.12):
166
- base_r = int(base_hex_color[1:3], 16)
167
- base_g = int(base_hex_color[3:5], 16)
168
- base_b = int(base_hex_color[5:7], 16)
169
- current_r = base_r + (255 - base_r) * depth * lightening_factor
170
- current_g = base_g + (255 - base_g) * depth * lightening_factor
171
- current_b = base_b + (255 - base_b) * depth * lightening_factor
172
-
173
- luminance = (0.2126 * current_r + 0.7152 * current_g + 0.0722 * current_b) / 255
174
- return 'white' if luminance < 0.5 else 'black'
175
 
176
  def _add_wbs_nodes_recursive(parent_id, current_level_tasks, current_depth):
177
  for task_data in current_level_tasks:
@@ -196,15 +432,12 @@ def generate_wbs_diagram(json_input: str, output_format: str) -> str:
196
  )
197
  dot.edge(parent_id, task_id, color='#4a4a4a', arrowhead='none')
198
 
199
- # Recursively call for next level of tasks (subtasks, sub_subtasks, etc.)
200
- # This handles arbitrary nested keys like 'subtasks', 'sub_subtasks', 'final_level_tasks'
201
  next_level_keys = ['tasks', 'subtasks', 'sub_subtasks', 'sub_sub_subtasks', 'final_level_tasks']
202
  for key_idx, key in enumerate(next_level_keys):
203
  if key in task_data and isinstance(task_data[key], list):
204
  _add_wbs_nodes_recursive(task_id, task_data[key], current_depth + 1)
205
- break # Only process the first found sub-level key
206
 
207
- # Process phases (level 1 from project_root)
208
  phase_depth = 1
209
  for phase in data['phases']:
210
  phase_id = phase.get('id')
@@ -228,7 +461,6 @@ def generate_wbs_diagram(json_input: str, output_format: str) -> str:
228
  )
229
  dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none')
230
 
231
- # Start recursion for tasks under this phase
232
  if 'tasks' in phase and isinstance(phase['tasks'], list):
233
  _add_wbs_nodes_recursive(phase_id, phase['tasks'], phase_depth + 1)
234
 
@@ -239,5 +471,4 @@ def generate_wbs_diagram(json_input: str, output_format: str) -> str:
239
  except json.JSONDecodeError:
240
  return "Error: Invalid JSON format"
241
  except Exception as e:
242
- return f"Error: {str(e)}"
243
-
 
1
+ # 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, output_format: str) -> str:
8
+ # """
9
+ # Generates a Work Breakdown Structure (WBS) Diagram from JSON input.
10
+
11
+ # Args:
12
+ # json_input (str): A JSON string describing the WBS structure.
13
+ # It must follow the Expected JSON Format Example below.
14
+
15
+ # Expected JSON Format Example:
16
+ # {
17
+ # "project_title": "AI Model Development Project",
18
+ # "phases": [
19
+ # {
20
+ # "id": "phase_prep",
21
+ # "label": "Preparation",
22
+ # "tasks": [
23
+ # {
24
+ # "id": "task_1_1_vision",
25
+ # "label": "Identify Vision",
26
+ # "subtasks": [
27
+ # {
28
+ # "id": "subtask_1_1_1_design_staff",
29
+ # "label": "Design & Staffing",
30
+ # "sub_subtasks": [
31
+ # {
32
+ # "id": "ss_task_1_1_1_1_env_setup",
33
+ # "label": "Environment Setup",
34
+ # "sub_sub_subtasks": [
35
+ # {
36
+ # "id": "sss_task_1_1_1_1_1_lib_install",
37
+ # "label": "Install Libraries",
38
+ # "final_level_tasks": [
39
+ # {"id": "ft_1_1_1_1_1_1_data_access", "label": "Grant Data Access"}
40
+ # ]
41
+ # }
42
+ # ]
43
+ # }
44
+ # ]
45
+ # }
46
+ # ]
47
+ # }
48
+ # ]
49
+ # },
50
+ # {
51
+ # "id": "phase_plan",
52
+ # "label": "Planning",
53
+ # "tasks": [
54
+ # {
55
+ # "id": "task_2_1_cost_analysis",
56
+ # "label": "Cost Analysis",
57
+ # "subtasks": [
58
+ # {
59
+ # "id": "subtask_2_1_1_benefit_analysis",
60
+ # "label": "Benefit Analysis",
61
+ # "sub_subtasks": [
62
+ # {
63
+ # "id": "ss_task_2_1_1_1_risk_assess",
64
+ # "label": "AI Risk Assessment",
65
+ # "sub_sub_subtasks": [
66
+ # {
67
+ # "id": "sss_task_2_1_1_1_1_model_selection",
68
+ # "label": "Model Selection",
69
+ # "final_level_tasks": [
70
+ # {"id": "ft_2_1_1_1_1_1_data_strategy", "label": "Data Strategy"}
71
+ # ]
72
+ # }
73
+ # ]
74
+ # }
75
+ # ]
76
+ # }
77
+ # ]
78
+ # }
79
+ # ]
80
+ # },
81
+ # {
82
+ # "id": "phase_dev",
83
+ # "label": "Development",
84
+ # "tasks": [
85
+ # {
86
+ # "id": "task_3_1_change_mgmt",
87
+ # "label": "Data Preprocessing",
88
+ # "subtasks": [
89
+ # {
90
+ # "id": "subtask_3_1_1_implementation",
91
+ # "label": "Feature Engineering",
92
+ # "sub_subtasks": [
93
+ # {
94
+ # "id": "ss_task_3_1_1_1_beta_testing",
95
+ # "label": "Model Training",
96
+ # "sub_sub_subtasks": [
97
+ # {
98
+ # "id": "sss_task_3_1_1_1_1_other_task",
99
+ # "label": "Model Evaluation",
100
+ # "final_level_tasks": [
101
+ # {"id": "ft_3_1_1_1_1_1_hyperparam_tune", "label": "Hyperparameter Tuning"}
102
+ # ]
103
+ # }
104
+ # ]
105
+ # }
106
+ # ]
107
+ # }
108
+ # ]
109
+ # }
110
+ # ]
111
+ # }
112
+ # ]
113
+ # }
114
+
115
+ # Returns:
116
+ # str: The filepath to the generated PNG image file.
117
+ # """
118
+ # try:
119
+ # if not json_input.strip():
120
+ # return "Error: Empty input"
121
+
122
+ # data = json.loads(json_input)
123
+
124
+ # if 'project_title' not in data or 'phases' not in data:
125
+ # raise ValueError("Missing required fields: project_title or phases")
126
+
127
+ # dot = graphviz.Digraph(
128
+ # name='WBSDiagram',
129
+ # format='png',
130
+ # graph_attr={
131
+ # 'rankdir': 'TB', # Top-to-Bottom hierarchy
132
+ # 'splines': 'ortho', # Straight lines
133
+ # 'bgcolor': 'white', # White background
134
+ # 'pad': '0.5', # Padding
135
+ # 'ranksep': '0.6', # Adjust vertical separation between ranks
136
+ # 'nodesep': '0.5' # Adjust horizontal separation between nodes
137
+ # }
138
+ # )
139
+
140
+ # base_color = '#19191a'
141
+
142
+ # # Main node
143
+ # dot.node(
144
+ # 'project_root',
145
+ # data['project_title'],
146
+ # shape='box',
147
+ # style='filled,rounded',
148
+ # fillcolor=base_color,
149
+ # fontcolor='white',
150
+ # fontsize='18'
151
+ # )
152
+
153
+ # # Helper for color and font based on depth for WBS
154
+ # def get_gradient_color(depth, base_hex_color, lightening_factor=0.12):
155
+ # base_r = int(base_hex_color[1:3], 16)
156
+ # base_g = int(base_hex_color[3:5], 16)
157
+ # base_b = int(base_hex_color[5:7], 16)
158
+
159
+ # current_r = base_r + int((255 - base_r) * depth * lightening_factor)
160
+ # current_g = base_g + int((255 - base_g) * depth * lightening_factor)
161
+ # current_b = base_b + int((255 - base_b) * depth * lightening_factor)
162
+
163
+ # return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
164
+
165
+ # def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.12):
166
+ # base_r = int(base_hex_color[1:3], 16)
167
+ # base_g = int(base_hex_color[3:5], 16)
168
+ # base_b = int(base_hex_color[5:7], 16)
169
+ # current_r = base_r + (255 - base_r) * depth * lightening_factor
170
+ # current_g = base_g + (255 - base_g) * depth * lightening_factor
171
+ # current_b = base_b + (255 - base_b) * depth * lightening_factor
172
+
173
+ # luminance = (0.2126 * current_r + 0.7152 * current_g + 0.0722 * current_b) / 255
174
+ # return 'white' if luminance < 0.5 else 'black'
175
+
176
+ # def _add_wbs_nodes_recursive(parent_id, current_level_tasks, current_depth):
177
+ # for task_data in current_level_tasks:
178
+ # task_id = task_data.get('id')
179
+ # task_label = task_data.get('label')
180
+
181
+ # if not all([task_id, task_label]):
182
+ # raise ValueError(f"Invalid task data at depth {current_depth}: {task_data}")
183
+
184
+ # node_fill_color = get_gradient_color(current_depth, base_color)
185
+ # node_font_color = get_font_color_for_background(current_depth, base_color)
186
+ # font_size = max(9, 14 - (current_depth * 2))
187
+
188
+ # dot.node(
189
+ # task_id,
190
+ # task_label,
191
+ # shape='box',
192
+ # style='filled,rounded',
193
+ # fillcolor=node_fill_color,
194
+ # fontcolor=node_font_color,
195
+ # fontsize=str(font_size)
196
+ # )
197
+ # dot.edge(parent_id, task_id, color='#4a4a4a', arrowhead='none')
198
+
199
+ # # Recursively call for next level of tasks (subtasks, sub_subtasks, etc.)
200
+ # # This handles arbitrary nested keys like 'subtasks', 'sub_subtasks', 'final_level_tasks'
201
+ # next_level_keys = ['tasks', 'subtasks', 'sub_subtasks', 'sub_sub_subtasks', 'final_level_tasks']
202
+ # for key_idx, key in enumerate(next_level_keys):
203
+ # if key in task_data and isinstance(task_data[key], list):
204
+ # _add_wbs_nodes_recursive(task_id, task_data[key], current_depth + 1)
205
+ # break # Only process the first found sub-level key
206
+
207
+ # # Process phases (level 1 from project_root)
208
+ # phase_depth = 1
209
+ # for phase in data['phases']:
210
+ # phase_id = phase.get('id')
211
+ # phase_label = phase.get('label')
212
+
213
+ # if not all([phase_id, phase_label]):
214
+ # raise ValueError(f"Invalid phase data: {phase}")
215
+
216
+ # phase_fill_color = get_gradient_color(phase_depth, base_color)
217
+ # phase_font_color = get_font_color_for_background(phase_depth, base_color)
218
+ # font_size_phase = max(9, 14 - (phase_depth * 2))
219
+
220
+ # dot.node(
221
+ # phase_id,
222
+ # phase_label,
223
+ # shape='box',
224
+ # style='filled,rounded',
225
+ # fillcolor=phase_fill_color,
226
+ # fontcolor=phase_font_color,
227
+ # fontsize=str(font_size_phase)
228
+ # )
229
+ # dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none')
230
+
231
+ # # Start recursion for tasks under this phase
232
+ # if 'tasks' in phase and isinstance(phase['tasks'], list):
233
+ # _add_wbs_nodes_recursive(phase_id, phase['tasks'], phase_depth + 1)
234
+
235
+ # with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
236
+ # dot.render(tmp.name, format=output_format, cleanup=True)
237
+ # return f"{tmp.name}.{output_format}"
238
+
239
+ # except json.JSONDecodeError:
240
+ # return "Error: Invalid JSON format"
241
+ # except Exception as e:
242
+ # return f"Error: {str(e)}"
243
+
244
  import graphviz
245
  import json
246
  from tempfile import NamedTemporaryFile
 
371
  name='WBSDiagram',
372
  format='png',
373
  graph_attr={
374
+ 'rankdir': 'TB',
375
+ 'splines': 'ortho',
376
+ 'bgcolor': 'white',
377
+ 'pad': '0.5',
378
+ 'ranksep': '0.6',
379
+ 'nodesep': '0.5'
380
  }
381
  )
382
 
383
+ base_color = '#BEBEBE'
384
 
 
385
  dot.node(
386
  'project_root',
387
  data['project_title'],
388
  shape='box',
389
  style='filled,rounded',
390
  fillcolor=base_color,
391
+ fontcolor='black',
392
  fontsize='18'
393
  )
394
 
395
+ def get_gradient_color(depth, base_hex_color, lightening_factor=0.06):
396
+ if not isinstance(base_hex_color, str) or not base_hex_color.startswith('#') or len(base_hex_color) != 7:
397
+ base_hex_color = '#BEBEBE'
398
+
399
  base_r = int(base_hex_color[1:3], 16)
400
  base_g = int(base_hex_color[3:5], 16)
401
  base_b = int(base_hex_color[5:7], 16)
 
406
 
407
  return f'#{min(255, current_r):02x}{min(255, current_g):02x}{min(255, current_b):02x}'
408
 
409
+ def get_font_color_for_background(depth, base_hex_color, lightening_factor=0.06):
410
+ return 'black'
 
 
 
 
 
 
 
 
411
 
412
  def _add_wbs_nodes_recursive(parent_id, current_level_tasks, current_depth):
413
  for task_data in current_level_tasks:
 
432
  )
433
  dot.edge(parent_id, task_id, color='#4a4a4a', arrowhead='none')
434
 
 
 
435
  next_level_keys = ['tasks', 'subtasks', 'sub_subtasks', 'sub_sub_subtasks', 'final_level_tasks']
436
  for key_idx, key in enumerate(next_level_keys):
437
  if key in task_data and isinstance(task_data[key], list):
438
  _add_wbs_nodes_recursive(task_id, task_data[key], current_depth + 1)
439
+ break
440
 
 
441
  phase_depth = 1
442
  for phase in data['phases']:
443
  phase_id = phase.get('id')
 
461
  )
462
  dot.edge('project_root', phase_id, color='#4a4a4a', arrowhead='none')
463
 
 
464
  if 'tasks' in phase and isinstance(phase['tasks'], list):
465
  _add_wbs_nodes_recursive(phase_id, phase['tasks'], phase_depth + 1)
466
 
 
471
  except json.JSONDecodeError:
472
  return "Error: Invalid JSON format"
473
  except Exception as e:
474
+ return f"Error: {str(e)}"