File size: 6,705 Bytes
1cc9417
 
c4e99ed
 
8314931
 
1cc9417
0fa5e33
 
f01b8f5
e014ca7
f5af3c4
 
6ed1496
f01b8f5
 
6ed1496
cec9f06
e014ca7
f01b8f5
 
 
cec9f06
f01b8f5
 
 
 
 
 
cec9f06
 
6ed1496
f01b8f5
e014ca7
 
f5af3c4
6ed1496
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8314931
07c578c
0fa5e33
f5af3c4
0fa5e33
f5af3c4
 
 
8817595
6ed1496
 
 
 
07c578c
 
f5af3c4
 
8314931
c4e99ed
8314931
 
 
e33b0b5
8314931
cec9f06
6ed1496
f5af3c4
8314931
 
6ed1496
8314931
 
 
 
 
cec9f06
8314931
1d4021b
8314931
07c578c
 
 
e014ca7
cec9f06
f5af3c4
07c578c
 
8314931
0fa5e33
1e2fa73
0fa5e33
07c578c
 
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import gradio as gr
import pandas as pd
import os
import logging
from weasyprint import HTML
from pdf2image import convert_from_path

logging.basicConfig(level=logging.INFO)

# Name of the default CSV file. Ensure this file exists in your repository.
DEFAULT_CSV = "default.csv"

def load_csv(file):
    """
    Load a CSV file from the given file path.
    If the file is missing, empty, or is a directory, load the default CSV.
    """
    cwd = os.getcwd()
    logging.info(f"load_csv received: {file}")
    
    # If file is empty, None, or a directory, use the default CSV.
    if not file or file.strip() == "" or os.path.isdir(file) or os.path.abspath(file) == cwd:
        logging.info("No valid file provided; using default CSV.")
        if os.path.isfile(DEFAULT_CSV):
            return pd.read_csv(DEFAULT_CSV)
        else:
            raise FileNotFoundError("Default CSV not found.")
    
    # If the file exists and is not a directory, load it.
    if os.path.isfile(file):
        logging.info(f"Loading uploaded CSV: {file}")
        return pd.read_csv(file)
    
    logging.warning(f"Provided file path '{file}' is not valid. Using default CSV.")
    return pd.read_csv(DEFAULT_CSV)

def build_tree(df):
    employees = {}
    children = {}
    all_emps = set()
    all_managers = set()
    
    for _, row in df.iterrows():
        name = row["Name"].strip()
        role = row["Role"].strip()
        label = f"{name}<br>({role})"
        employees[name] = label
        all_emps.add(name)
        children.setdefault(name, [])
    
    for _, row in df.iterrows():
        subordinate = row["Name"].strip()
        manager = str(row["Reporting To"]).strip()
        if manager and manager.lower() != "nan":
            children.setdefault(manager, []).append(subordinate)
            all_managers.add(manager)
    
    roots = [emp for emp in all_emps if emp not in all_managers]
    if not roots:
        roots = [df.iloc[0]["Name"].strip()]
    return employees, children, roots

def generate_node_html(node, employees, children, visited=None):
    if visited is None:
        visited = set()
    if node in visited:
        return f"<li><div class='node'>{employees.get(node, node)} (cycle)</div></li>"
    visited.add(node)
    label = employees.get(node, node)
    html = f"<li><div class='node'>{label}</div>"
    if node in children and children[node]:
        html += "<ul>"
        for child in children[node]:
            html += generate_node_html(child, employees, children, visited)
        html += "</ul>"
    html += "</li>"
    visited.remove(node)
    return html

def generate_org_chart_html(df, title):
    employees, children, roots = build_tree(df)
    tree_html = ""
    for root in roots:
        tree_html += generate_node_html(root, employees, children)
    html_content = f"""
    <html>
      <head>
        <meta charset="utf-8">
        <title>{title}</title>
        <style>
            body {{
                font-family: Arial, sans-serif;
            }}
            .org-chart {{
                text-align: center;
                margin: 20px;
            }}
            .org-chart ul {{
                padding-top: 20px; 
                position: relative;
                display: inline-block;
            }}
            .org-chart li {{
                list-style-type: none;
                position: relative;
                padding: 20px 5px 0 5px;
                text-align: center;
            }}
            .org-chart li::before, .org-chart li::after {{
                content: '';
                position: absolute;
                top: 0;
                border-top: 2px solid #ccc;
                width: 50%;
                height: 20px;
            }}
            .org-chart li::before {{
                right: 50%;
                border-right: 2px solid #ccc;
            }}
            .org-chart li::after {{
                left: 50%;
                border-left: 2px solid #ccc;
            }}
            .org-chart li:only-child::after, .org-chart li:only-child::before {{
                display: none;
            }}
            .org-chart li:only-child {{
                padding-top: 0;
            }}
            .org-chart .node {{
                display: inline-block;
                padding: 5px 10px;
                border: 1px solid #ccc;
                border-radius: 5px;
                background: #e5e5e5;
                white-space: nowrap;
            }}
        </style>
      </head>
      <body>
        <h1 style="text-align:center;">{title}</h1>
        <div class="org-chart">
            <ul>
                {tree_html}
            </ul>
        </div>
      </body>
    </html>
    """
    return html_content

def generate_chart(file, title):
    try:
        df = load_csv(file)
    except Exception as e:
        logging.error(f"Error loading CSV: {e}")
        return None, f"Error loading CSV: {e}"
    
    # Clean header names.
    df.columns = df.columns.str.strip()
    logging.info("CSV columns: " + ", ".join(df.columns))
    logging.info(f"CSV read successfully with {df.shape[0]} rows.")
    
    expected_columns = {"Name", "Role", "Reporting To"}
    if not expected_columns.issubset(set(df.columns)):
        return None, "CSV must contain Name, Role, and Reporting To columns."
    
    html_content = generate_org_chart_html(df, title)
    
    pdf_path = "/tmp/chart.pdf"
    try:
        HTML(string=html_content).write_pdf(pdf_path)
        logging.info("PDF generated successfully.")
    except Exception as e:
        logging.error(f"Error generating PDF: {e}")
        return None, f"Error generating PDF: {e}"
    
    try:
        images = convert_from_path(pdf_path, dpi=150)
        if images:
            image_path = "/tmp/chart.png"
            images[0].save(image_path, 'PNG')
        else:
            image_path = ""
    except Exception as e:
        logging.error(f"Error converting PDF to image: {e}")
        image_path = ""
    
    return image_path, pdf_path

with gr.Blocks() as demo:
    gr.Markdown("## Organization Chart Generator")
    gr.Markdown("Upload a CSV file (optional). If no file is uploaded or an invalid file is provided, the default CSV (default.csv) will be used.")
    
    file_input = gr.File(label="Upload CSV File (optional)", type="filepath")
    title_input = gr.Textbox(label="Enter PDF Title", placeholder="Company Org Chart")
    submit_button = gr.Button("Generate Chart")
    image_output = gr.Image(label="Generated Chart (PNG)")
    pdf_output = gr.File(label="Download PDF")
    
    submit_button.click(generate_chart, inputs=[file_input, title_input], outputs=[image_output, pdf_output])

demo.launch()