arif670 commited on
Commit
c4e99ed
·
verified ·
1 Parent(s): 1cc9417

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -40
app.py CHANGED
@@ -1,19 +1,44 @@
1
- import os
2
- import logging
3
  import gradio as gr
4
  import pandas as pd
5
- import base64
 
6
  import pdfkit
 
7
  import shutil
8
- from graphviz import Digraph
9
-
10
- # Debug prints for environment variables
11
- print("PATH =", os.environ.get("PATH"))
12
- print("GRAPHVIZ_DOT =", os.environ.get("GRAPHVIZ_DOT"))
13
 
14
- # Set up logging for debugging
15
  logging.basicConfig(level=logging.INFO)
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def get_wkhtmltopdf_path():
18
  path = shutil.which("wkhtmltopdf")
19
  if path:
@@ -23,6 +48,9 @@ def get_wkhtmltopdf_path():
23
  return candidate
24
  return None
25
 
 
 
 
26
  def process_uploaded_file(file):
27
  logging.info(f"Processing uploaded file: {file}")
28
  if isinstance(file, str):
@@ -33,7 +61,6 @@ def process_uploaded_file(file):
33
  logging.error(f"Provided file path '{file}' is a directory.")
34
  return None
35
  return file
36
-
37
  if isinstance(file, dict):
38
  if "data" in file and file["data"]:
39
  temp_path = "/tmp/uploaded.csv"
@@ -57,19 +84,19 @@ def process_uploaded_file(file):
57
  return None
58
  return None
59
 
 
 
 
60
  def generate_chart(file, title):
61
  logging.info(f"generate_chart called with file: {file} and title: {title}")
62
  if not file:
63
  return None, "Please upload a CSV file."
64
-
65
  file_path = process_uploaded_file(file)
66
  if not file_path:
67
  return None, "Error processing uploaded file. Please upload a valid CSV file."
68
-
69
  if os.path.isdir(file_path):
70
  logging.error(f"Processed file path '{file_path}' is a directory.")
71
  return None, "Uploaded file is a directory. Please upload a valid CSV file."
72
-
73
  try:
74
  df = pd.read_csv(file_path)
75
  except Exception as e:
@@ -80,35 +107,69 @@ def generate_chart(file, title):
80
  if not expected_columns.issubset(set(df.columns)):
81
  return None, "CSV must contain Name, Role, and Reporting To columns."
82
 
83
- # Create a Graphviz Digraph for the org chart.
84
- dot = Digraph(comment=title, format='png')
85
- dot.attr(rankdir='TB')
86
- dot.attr('node', shape='box', style='rounded,filled', fillcolor='lightblue',
87
- fontsize='10', fontname='Helvetica', margin='0.1')
88
-
89
- # Add nodes.
90
  for idx, row in df.iterrows():
91
- node_id = row['Name'] # Assumes names are unique.
92
- label = f"{row['Name']}\n({row['Role']})"
93
- dot.node(node_id, label=label)
 
94
 
95
- # Add edges.
96
  for idx, row in df.iterrows():
97
- if pd.notna(row['Reporting To']) and row['Reporting To'] != "":
98
- manager = row['Reporting To']
99
- subordinate = row['Name']
100
- if manager not in df['Name'].values:
101
- dot.node(manager, label=manager)
102
- dot.edge(manager, subordinate)
103
-
104
- # Render diagram to PNG.
105
- output_path = "/tmp/chart"
106
- dot.render(output_path, cleanup=True)
107
- chart_path = output_path + ".png"
108
-
109
- # Build HTML content with embedded chart image.
110
- with open(chart_path, "rb") as img_file:
111
- encoded = base64.b64encode(img_file.read()).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  img_html = f"<img src='data:image/png;base64,{encoded}' style='width:100%;'/>"
113
  html_content = f"""
114
  <html>
@@ -123,7 +184,6 @@ def generate_chart(file, title):
123
  </html>
124
  """
125
 
126
- # Generate PDF.
127
  wkhtml_path = get_wkhtmltopdf_path()
128
  if wkhtml_path is None:
129
  logging.error("wkhtmltopdf not found, creating dummy PDF file.")
@@ -145,6 +205,9 @@ def generate_chart(file, title):
145
 
146
  return chart_path, pdf_path
147
 
 
 
 
148
  def download_template():
149
  template_path = "/tmp/template.csv"
150
  template_data = "Name,Role,Reporting To\nAlice,CEO,\nBob,Manager,Alice\nCharlie,Engineer,Bob"
@@ -152,6 +215,9 @@ def download_template():
152
  f.write(template_data)
153
  return template_path
154
 
 
 
 
155
  with gr.Blocks() as demo:
156
  gr.Markdown("## Organization Chart Generator")
157
  gr.Markdown(
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
+ import networkx as nx
4
+ import matplotlib.pyplot as plt
5
  import pdfkit
6
+ import base64
7
  import shutil
8
+ import os
9
+ import logging
 
 
 
10
 
 
11
  logging.basicConfig(level=logging.INFO)
12
 
13
+ # -------------------------------
14
+ # Helper: Custom tree layout
15
+ # -------------------------------
16
+ def hierarchy_pos(G, root, width=1.0, vert_gap=0.2, vert_loc=0, xcenter=0.5):
17
+ """
18
+ If the graph is a tree, this will return positions to plot a hierarchical layout.
19
+
20
+ G: the tree (a DiGraph)
21
+ root: the root node of the tree
22
+ width: horizontal space allocated for the tree
23
+ vert_gap: gap between levels of the tree
24
+ vert_loc: vertical location of root
25
+ xcenter: horizontal location of root
26
+ """
27
+ def _hierarchy_pos(G, root, left, right, vert_loc, pos, parent=None):
28
+ pos[root] = ((left + right) / 2, vert_loc)
29
+ children = list(G.successors(root))
30
+ if children:
31
+ dx = (right - left) / len(children)
32
+ nextx = left
33
+ for child in children:
34
+ pos = _hierarchy_pos(G, child, nextx, nextx + dx, vert_loc - vert_gap, pos, root)
35
+ nextx += dx
36
+ return pos
37
+ return _hierarchy_pos(G, root, 0, width, vert_loc, {})
38
+
39
+ # -------------------------------
40
+ # Helpers for system dependencies
41
+ # -------------------------------
42
  def get_wkhtmltopdf_path():
43
  path = shutil.which("wkhtmltopdf")
44
  if path:
 
48
  return candidate
49
  return None
50
 
51
+ # -------------------------------
52
+ # File processing helper
53
+ # -------------------------------
54
  def process_uploaded_file(file):
55
  logging.info(f"Processing uploaded file: {file}")
56
  if isinstance(file, str):
 
61
  logging.error(f"Provided file path '{file}' is a directory.")
62
  return None
63
  return file
 
64
  if isinstance(file, dict):
65
  if "data" in file and file["data"]:
66
  temp_path = "/tmp/uploaded.csv"
 
84
  return None
85
  return None
86
 
87
+ # -------------------------------
88
+ # Main function: Generate Chart
89
+ # -------------------------------
90
  def generate_chart(file, title):
91
  logging.info(f"generate_chart called with file: {file} and title: {title}")
92
  if not file:
93
  return None, "Please upload a CSV file."
 
94
  file_path = process_uploaded_file(file)
95
  if not file_path:
96
  return None, "Error processing uploaded file. Please upload a valid CSV file."
 
97
  if os.path.isdir(file_path):
98
  logging.error(f"Processed file path '{file_path}' is a directory.")
99
  return None, "Uploaded file is a directory. Please upload a valid CSV file."
 
100
  try:
101
  df = pd.read_csv(file_path)
102
  except Exception as e:
 
107
  if not expected_columns.issubset(set(df.columns)):
108
  return None, "CSV must contain Name, Role, and Reporting To columns."
109
 
110
+ # Build tree using names as nodes; store label as attribute
111
+ T = nx.DiGraph()
 
 
 
 
 
112
  for idx, row in df.iterrows():
113
+ name = row["Name"]
114
+ role = row["Role"]
115
+ label = f"{name}\n({role})"
116
+ T.add_node(name, label=label)
117
 
118
+ # Add edges (manager -> subordinate)
119
  for idx, row in df.iterrows():
120
+ if pd.notna(row["Reporting To"]) and row["Reporting To"] != "":
121
+ manager = row["Reporting To"]
122
+ subordinate = row["Name"]
123
+ if manager not in T.nodes:
124
+ # If manager not present, add with label = manager
125
+ T.add_node(manager, label=manager)
126
+ T.add_edge(manager, subordinate)
127
+
128
+ # Determine the root (node with in-degree 0)
129
+ roots = [n for n, d in T.in_degree() if d == 0]
130
+ if not roots:
131
+ return None, "Could not determine root of the organization tree."
132
+ root = roots[0]
133
+
134
+ pos = hierarchy_pos(T, root, width=1.0, vert_gap=0.2, vert_loc=1.0, xcenter=0.5)
135
+
136
+ # Draw the tree using Matplotlib
137
+ plt.figure(figsize=(10, 8))
138
+ ax = plt.gca()
139
+
140
+ # Draw nodes
141
+ for node, (x, y) in pos.items():
142
+ label = T.nodes[node].get("label", node)
143
+ lines = label.split("\n")
144
+ max_line_length = max(len(line) for line in lines)
145
+ # Use dynamic sizing: adjust factors as needed.
146
+ node_width = max_line_length * 3 # factor for width (in points)
147
+ node_height = len(lines) * 8 # factor for height (in points)
148
+ # For plotting, we use a conversion factor to scale node size relative to the plot.
149
+ # Here, we assume pos is normalized [0,1] and we scale sizes accordingly.
150
+ # Adjust these scaling factors as needed.
151
+ scale = 2 # adjust scale factor if needed
152
+ rect = plt.Rectangle((x - node_width/200.0*scale, y - node_height/200.0*scale),
153
+ node_width/100.0*scale, node_height/100.0*scale,
154
+ facecolor='lightblue', edgecolor='black', lw=1.5, zorder=2)
155
+ ax.add_patch(rect)
156
+ plt.text(x, y, label, horizontalalignment='center', verticalalignment='center', fontsize=8, zorder=3)
157
+
158
+ # Draw edges as straight lines connecting centers
159
+ for u, v in T.edges():
160
+ if u in pos and v in pos:
161
+ x1, y1 = pos[u]
162
+ x2, y2 = pos[v]
163
+ plt.plot([x1, x2], [y1, y2], color='black', zorder=1)
164
+
165
+ plt.axis('off')
166
+ chart_path = "/tmp/chart.png"
167
+ plt.savefig(chart_path, format="png", dpi=300, bbox_inches="tight")
168
+ plt.close()
169
+
170
+ # Embed chart in HTML for PDF conversion
171
+ with open(chart_path, "rb") as f:
172
+ encoded = base64.b64encode(f.read()).decode()
173
  img_html = f"<img src='data:image/png;base64,{encoded}' style='width:100%;'/>"
174
  html_content = f"""
175
  <html>
 
184
  </html>
185
  """
186
 
 
187
  wkhtml_path = get_wkhtmltopdf_path()
188
  if wkhtml_path is None:
189
  logging.error("wkhtmltopdf not found, creating dummy PDF file.")
 
205
 
206
  return chart_path, pdf_path
207
 
208
+ # -------------------------------
209
+ # Template download function
210
+ # -------------------------------
211
  def download_template():
212
  template_path = "/tmp/template.csv"
213
  template_data = "Name,Role,Reporting To\nAlice,CEO,\nBob,Manager,Alice\nCharlie,Engineer,Bob"
 
215
  f.write(template_data)
216
  return template_path
217
 
218
+ # -------------------------------
219
+ # Gradio Interface
220
+ # -------------------------------
221
  with gr.Blocks() as demo:
222
  gr.Markdown("## Organization Chart Generator")
223
  gr.Markdown(