syedkhalid076 commited on
Commit
498da51
Β·
verified Β·
1 Parent(s): c1d32fe

Added Visualization

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +167 -59
src/streamlit_app.py CHANGED
@@ -2,6 +2,7 @@ import streamlit as st
2
  import re
3
  from io import BytesIO
4
  import base64
 
5
 
6
  # Page configuration
7
  st.set_page_config(
@@ -113,49 +114,122 @@ class DBMLParser:
113
  })
114
 
115
 
116
- class MermaidGenerator:
117
- """Generate Mermaid diagram from parsed DBML"""
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  def __init__(self, tables, relationships):
120
  self.tables = tables
121
  self.relationships = relationships
122
 
123
  def generate(self):
124
- """Generate Mermaid ER diagram"""
125
- lines = ['erDiagram']
 
 
 
126
 
127
- # Add tables with columns
128
  for table_name, columns in self.tables.items():
129
- for col in columns:
130
- col_def = f"{col['type']}"
131
-
132
- # Add constraints
133
- constraints = []
134
- if col['is_pk']:
135
- constraints.append('PK')
136
- if col['is_unique']:
137
- constraints.append('UK')
138
- if col['is_not_null']:
139
- constraints.append('NOT NULL')
140
-
141
- constraint_str = f" \"{','.join(constraints)}\"" if constraints else ""
142
-
143
- lines.append(f" {table_name} {{")
144
- lines.append(f" {col_def} {col['name']}{constraint_str}")
145
- lines.append(f" }}")
146
 
147
- # Add relationships
148
  for rel in self.relationships:
149
  if rel['type'] == 'one-to-many':
150
- rel_symbol = '||--o{'
 
 
 
 
 
 
 
 
151
  elif rel['type'] == 'many-to-one':
152
- rel_symbol = '}o--||'
 
 
 
 
 
 
 
 
153
  else:
154
- rel_symbol = '||--||'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- lines.append(f" {rel['from_table']} {rel_symbol} {rel['to_table']} : \"{rel['from_column']}-{rel['to_column']}\"")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- return '\n'.join(lines)
159
 
160
 
161
  class PostgreSQLGenerator:
@@ -252,7 +326,7 @@ def main():
252
  st.write("""
253
  This tool allows you to:
254
  - πŸ“Š Visualize DBML schemas
255
- - πŸ’Ύ Download diagram as image
256
  - πŸ”§ Generate PostgreSQL DDL
257
  - πŸ“‹ Copy SQL to clipboard
258
  """)
@@ -260,6 +334,10 @@ def main():
260
  st.header("πŸ’‘ Example DBML")
261
  if st.button("Load Example"):
262
  st.session_state.example_loaded = True
 
 
 
 
263
 
264
  # Example DBML
265
  example_dbml = """Table users {
@@ -306,44 +384,74 @@ Ref: comments.user_id > users.id"""
306
  if st.button("🎨 Generate Visualization & SQL", type="primary", use_container_width=True):
307
  if dbml_input.strip():
308
  try:
309
- # Parse DBML
310
- parser = DBMLParser(dbml_input)
311
- tables, relationships = parser.parse()
312
-
313
- if not tables:
314
- st.error("❌ No tables found in DBML code. Please check your syntax.")
315
- else:
316
- # Generate Mermaid diagram
317
- mermaid_gen = MermaidGenerator(tables, relationships)
318
- mermaid_code = mermaid_gen.generate()
319
-
320
- # Generate PostgreSQL
321
- sql_gen = PostgreSQLGenerator(tables, relationships)
322
- sql_code = sql_gen.generate()
323
-
324
- # Store in session state
325
- st.session_state.mermaid_code = mermaid_code
326
- st.session_state.sql_code = sql_code
327
- st.session_state.tables = tables
328
- st.session_state.relationships = relationships
329
 
330
- st.success(f"βœ… Successfully parsed {len(tables)} tables and {len(relationships)} relationships!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  except Exception as e:
333
  st.error(f"❌ Error parsing DBML: {str(e)}")
 
 
334
  else:
335
  st.warning("⚠️ Please enter some DBML code first.")
336
 
337
  with col2:
338
- if 'mermaid_code' in st.session_state:
339
  st.subheader("πŸ“Š Database Diagram")
340
 
341
- # Display Mermaid diagram
342
- st.markdown(f"""
343
- ```mermaid
344
- {st.session_state.mermaid_code}
345
- ```
346
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
  # Info about tables
349
  st.info(f"πŸ“‹ **{len(st.session_state.tables)}** tables, **{len(st.session_state.relationships)}** relationships")
@@ -370,8 +478,8 @@ Ref: comments.user_id > users.id"""
370
  use_container_width=True
371
  )
372
 
373
- if st.button("πŸ“‹ Copy to Clipboard", use_container_width=True):
374
- st.toast("SQL copied to clipboard! (Use Ctrl+C to copy the code block)", icon="βœ…")
375
 
376
 
377
  if __name__ == "__main__":
 
2
  import re
3
  from io import BytesIO
4
  import base64
5
+ import graphviz
6
 
7
  # Page configuration
8
  st.set_page_config(
 
114
  })
115
 
116
 
117
+ class GraphvizGenerator:
118
+ """Generate Graphviz diagram from parsed DBML"""
119
+
120
+ # Color scheme
121
+ COLORS = {
122
+ 'table_bg': '#E8F4F8',
123
+ 'table_border': '#2E86AB',
124
+ 'pk_bg': '#FFE5B4',
125
+ 'pk_text': '#8B4513',
126
+ 'header_bg': '#2E86AB',
127
+ 'header_text': '#FFFFFF',
128
+ 'text': '#333333',
129
+ 'arrow': '#555555'
130
+ }
131
 
132
  def __init__(self, tables, relationships):
133
  self.tables = tables
134
  self.relationships = relationships
135
 
136
  def generate(self):
137
+ """Generate Graphviz diagram"""
138
+ dot = graphviz.Digraph(comment='Database Schema')
139
+ dot.attr(rankdir='LR', bgcolor='white', splines='ortho', nodesep='1', ranksep='1.5')
140
+ dot.attr('node', shape='plaintext')
141
+ dot.attr('edge', color=self.COLORS['arrow'], penwidth='2')
142
 
143
+ # Add tables
144
  for table_name, columns in self.tables.items():
145
+ html_label = self._create_table_html(table_name, columns)
146
+ dot.node(table_name, label=f'<{html_label}>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ # Add relationships with proper arrows
149
  for rel in self.relationships:
150
  if rel['type'] == 'one-to-many':
151
+ # One to many: arrow from parent to child
152
+ dot.edge(
153
+ rel['to_table'],
154
+ rel['from_table'],
155
+ label=f" {rel['to_column']} β†’ {rel['from_column']} ",
156
+ arrowhead='crow',
157
+ fontsize='10',
158
+ fontcolor='#666666'
159
+ )
160
  elif rel['type'] == 'many-to-one':
161
+ # Many to one: arrow from child to parent
162
+ dot.edge(
163
+ rel['from_table'],
164
+ rel['to_table'],
165
+ label=f" {rel['from_column']} β†’ {rel['to_column']} ",
166
+ arrowhead='normal',
167
+ fontsize='10',
168
+ fontcolor='#666666'
169
+ )
170
  else:
171
+ # One to one
172
+ dot.edge(
173
+ rel['from_table'],
174
+ rel['to_table'],
175
+ label=f" {rel['from_column']} ↔ {rel['to_column']} ",
176
+ arrowhead='none',
177
+ dir='both',
178
+ fontsize='10',
179
+ fontcolor='#666666'
180
+ )
181
+
182
+ return dot
183
+
184
+ def _create_table_html(self, table_name, columns):
185
+ """Create HTML table for Graphviz node"""
186
+ html = f'''<
187
+ <TABLE BORDER="2" CELLBORDER="1" CELLSPACING="0" CELLPADDING="8" BGCOLOR="{self.COLORS['table_bg']}" COLOR="{self.COLORS['table_border']}">
188
+ <TR>
189
+ <TD COLSPAN="2" BGCOLOR="{self.COLORS['header_bg']}" ALIGN="CENTER">
190
+ <FONT COLOR="{self.COLORS['header_text']}" POINT-SIZE="14"><B>{table_name}</B></FONT>
191
+ </TD>
192
+ </TR>
193
+ '''
194
+
195
+ for col in columns:
196
+ # Determine background color
197
+ bg_color = self.COLORS['pk_bg'] if col['is_pk'] else self.COLORS['table_bg']
198
+
199
+ # Build column name with constraints
200
+ col_name = col['name']
201
+ if col['is_pk']:
202
+ col_name = f"πŸ”‘ {col_name}"
203
+
204
+ # Build type with constraints
205
+ type_str = col['type']
206
+ constraints = []
207
+ if col['is_pk']:
208
+ constraints.append('PK')
209
+ if col['is_unique']:
210
+ constraints.append('UQ')
211
+ if col['is_not_null']:
212
+ constraints.append('NOT NULL')
213
 
214
+ if constraints:
215
+ type_str += f" [{', '.join(constraints)}]"
216
+
217
+ html += f'''
218
+ <TR>
219
+ <TD ALIGN="LEFT" BGCOLOR="{bg_color}">
220
+ <FONT COLOR="{self.COLORS['text']}" POINT-SIZE="11"><B>{col_name}</B></FONT>
221
+ </TD>
222
+ <TD ALIGN="LEFT" BGCOLOR="{bg_color}">
223
+ <FONT COLOR="{self.COLORS['text']}" POINT-SIZE="10">{type_str}</FONT>
224
+ </TD>
225
+ </TR>
226
+ '''
227
+
228
+ html += '''
229
+ </TABLE>
230
+ >'''
231
 
232
+ return html
233
 
234
 
235
  class PostgreSQLGenerator:
 
326
  st.write("""
327
  This tool allows you to:
328
  - πŸ“Š Visualize DBML schemas
329
+ - πŸ’Ύ Download diagram as PNG/SVG
330
  - πŸ”§ Generate PostgreSQL DDL
331
  - πŸ“‹ Copy SQL to clipboard
332
  """)
 
334
  st.header("πŸ’‘ Example DBML")
335
  if st.button("Load Example"):
336
  st.session_state.example_loaded = True
337
+
338
+ st.header("βš™οΈ Diagram Settings")
339
+ image_format = st.selectbox("Download Format", ["PNG", "SVG"], index=0)
340
+ st.session_state.image_format = image_format.lower()
341
 
342
  # Example DBML
343
  example_dbml = """Table users {
 
384
  if st.button("🎨 Generate Visualization & SQL", type="primary", use_container_width=True):
385
  if dbml_input.strip():
386
  try:
387
+ with st.spinner("Parsing DBML and generating visualization..."):
388
+ # Parse DBML
389
+ parser = DBMLParser(dbml_input)
390
+ tables, relationships = parser.parse()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
+ if not tables:
393
+ st.error("❌ No tables found in DBML code. Please check your syntax.")
394
+ else:
395
+ # Generate Graphviz diagram
396
+ graph_gen = GraphvizGenerator(tables, relationships)
397
+ dot = graph_gen.generate()
398
+
399
+ # Generate PostgreSQL
400
+ sql_gen = PostgreSQLGenerator(tables, relationships)
401
+ sql_code = sql_gen.generate()
402
+
403
+ # Store in session state
404
+ st.session_state.dot = dot
405
+ st.session_state.sql_code = sql_code
406
+ st.session_state.tables = tables
407
+ st.session_state.relationships = relationships
408
+
409
+ st.success(f"βœ… Successfully parsed {len(tables)} tables and {len(relationships)} relationships!")
410
 
411
  except Exception as e:
412
  st.error(f"❌ Error parsing DBML: {str(e)}")
413
+ import traceback
414
+ st.code(traceback.format_exc())
415
  else:
416
  st.warning("⚠️ Please enter some DBML code first.")
417
 
418
  with col2:
419
+ if 'dot' in st.session_state:
420
  st.subheader("πŸ“Š Database Diagram")
421
 
422
+ # Display the diagram
423
+ st.graphviz_chart(st.session_state.dot)
424
+
425
+ # Download buttons
426
+ col_btn1, col_btn2 = st.columns(2)
427
+
428
+ with col_btn1:
429
+ # PNG download
430
+ try:
431
+ png_data = st.session_state.dot.pipe(format='png')
432
+ st.download_button(
433
+ label="πŸ“₯ Download PNG",
434
+ data=png_data,
435
+ file_name="database_schema.png",
436
+ mime="image/png",
437
+ use_container_width=True
438
+ )
439
+ except:
440
+ st.warning("PNG export requires Graphviz installation")
441
+
442
+ with col_btn2:
443
+ # SVG download
444
+ try:
445
+ svg_data = st.session_state.dot.pipe(format='svg')
446
+ st.download_button(
447
+ label="πŸ“₯ Download SVG",
448
+ data=svg_data,
449
+ file_name="database_schema.svg",
450
+ mime="image/svg+xml",
451
+ use_container_width=True
452
+ )
453
+ except:
454
+ st.warning("SVG export requires Graphviz installation")
455
 
456
  # Info about tables
457
  st.info(f"πŸ“‹ **{len(st.session_state.tables)}** tables, **{len(st.session_state.relationships)}** relationships")
 
478
  use_container_width=True
479
  )
480
 
481
+ if st.button("πŸ“‹ Copy SQL", use_container_width=True):
482
+ st.toast("Copy the SQL code from the code block above!", icon="πŸ“‹")
483
 
484
 
485
  if __name__ == "__main__":