Spaces:
Sleeping
Sleeping
File size: 7,342 Bytes
ee2a899 c16b3dc ee2a899 9970a69 ee2a899 9970a69 ee2a899 9970a69 | 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 | ---
title: B&W Flowchart Sketcher
emoji: ๐
colorFrom: gray
colorTo: gray
sdk: streamlit
sdk_version: 1.35.0
app_file: app.py
pinned: false
license: mit
---
# B&W Flowchart Sketcher (Web & Desktop)
A premium, interactive utility to sketch clean, high-contrast, black-and-white flowcharts. This repository bundles two interfaces:
1. **Interactive Tkinter Desktop App** (`flowchart_app.py`): Supports canvas drag-and-drop node adjustments, visual drafting gridlines, and project serialization.
2. **Streamlit Web Application** (`app.py`): Designed for cloud deployment (Hugging Face Spaces) with interactive slider coordinates for node tuning.
---
## ๐ Table of Contents
- [Architecture & Schema Design](#-architecture--schema-design)
- [Mathematical Engine (Shapes & Ray Intersection)](#-mathematical-engine-shapes--ray-intersection)
- [Hierarchical Auto-Layout Solver](#-hierarchical-auto-layout-solver)
- [Consistent Text-Wrapping Engine](#-consistent-text-wrapping-engine)
- [Pillow Cropping & Vector-Like Rendering](#-pillow-cropping--vector-like-rendering)
- [Getting Started](#-getting-started)
- [Deployment on Hugging Face](#-deployment-on-hugging-face)
---
## ๐๏ธ Architecture & Schema Design
Both the desktop and web clients rely on a shared data schema managed by a backend model:
```
+------------------+ +---------------------+
| Desktop GUI | | Web GUI |
| (flowchart_app) | | (app.py) |
+--------+---------+ +----------+----------+
| |
+-------------+--------------+
|
v
+-----------+-----------+
| FlowchartModel |
| - nodes {} |
| - edges [] |
+-----------+-----------+
|
+-------------+-------------+
| |
v v
+--------+--------+ +--------+--------+
| Pillow Export | | JSON Project |
| (Vector-like) | | Save/Load |
+-----------------+ +-----------------+
```
- **`Node`**: Houses coordinates `(x, y)`, dimension bounding boxes `(w, h)` matched to the shape, designation (short ID), shape name, and description.
- **`Edge`**: Houses connectivity mappings `(u -> v)` and styling properties (`style` = straight/curved/dotted).
- **`FlowchartModel`**: Provides path parsing (e.g. `A -> B -> C`), topological sorting layout, and JSON imports/exports.
---
## ๐ Mathematical Engine (Shapes & Ray Intersection)
To produce professional-grade diagrams, connector lines and arrowheads must terminate **precisely** at the boundary of a node shape, rather than drawing to its coordinate center (which causes overlaps).
The mathematical engine calculates boundary intersections for every shape dynamically:
### 1. Ellipses (Circles & Ovals)
An oval centered at $(x_c, y_c)$ with semi-axes $a$ (horizontal radius) and $b$ (vertical radius) satisfies:
$$\frac{(x - x_c)^2}{a^2} + \frac{(y - y_c)^2}{b^2} = 1$$
To find where a ray in direction $(dx, dy)$ intersects the boundary, we solve for $t$:
$$t = \frac{1}{\sqrt{\frac{dx^2}{a^2} + \frac{dy^2}{b^2}}}$$
The boundary intersection point is $(x_c + t \cdot dx, y_c + t \cdot dy)$.
### 2. Polygons (Squares, Diamonds, Inverted Triangles)
For shapes represented by vertices, we cast a ray from the center $(x_c, y_c)$ to $(x_c + 10000 \cdot dx, y_c + 10000 \cdot dy)$ to ensure it crosses the boundary. We perform 2D segment-segment intersection against all edges of the polygon.
Given ray segment $AB$ and polygon edge $CD$:
$$P_{int} = A + t(B - A) = C + u(D - C)$$
Solving this linear system yields parameters $t$ and $u$. An intersection is valid if $0 \le t \le 1$ and $0 \le u \le 1$.
### 3. Curved Connector Splines
Curved connectors are drawn using **quadratic Bezier curves** parameterized by $t \in [0, 1]$:
$$B(t) = (1-t)^2 P_{start} + 2(1-t)t P_{ctrl} + t^2 P_{end}$$
- The control point $P_{ctrl}$ is computed by taking the midpoint of the node centers and offsetting it perpendicularly by 45 pixels.
- The boundary points $P_{start}$ and $P_{end}$ are computed using the ray-casting intersection formulas directed at $P_{ctrl}$.
- The arrowhead at $P_{end}$ is drawn aligned to the tangent vector $P_{end} - P_{ctrl}$, which represents the instantaneous direction of the curve at $t=1$.
---
## ๐งฎ Hierarchical Auto-Layout Solver
The layout solver uses a **Layered Hierarchical Graph Layout** algorithm:
1. **In-Degree Calculation**: Build an adjacency list and count incoming edges for every node.
2. **Topological Leveling (BFS)**: Node levels are assigned. Nodes with `in-degree = 0` start at Level 0. Outgoing neighbors are pushed to Level $L + 1$. Loops or cycles are broken by selecting a starter node when in-degrees are cyclic.
3. **Coordinate Allocation**:
- Levels are mapped to vertical coordinates ($Y$-coordinate) spaced 140px apart.
- Nodes within the same level are sorted alphabetically (to ensure layout stability) and distributed symmetrically along the horizontal center ($X$-coordinate) with 150px spacing.
---
## โ๏ธ Consistent Text-Wrapping Engine
Because Tkinter Canvas and Pillow use different text-rendering engines, we implemented a custom, unified word-wrapper:
- Font metrics are queried dynamically using `font.measure` in Tkinter and `draw.textlength` in Pillow.
- The wrapper walks through the description, building text lines word-by-word. It splits to a new line as soon as the measured width exceeds the shape's specific maximum text boundaries.
- **Maximum margins** are customized for each shape to prevent text from overflowing corners (e.g. diamonds restrict width to $58\%$ of node width, whereas squares allow up to $82\%$).
---
## ๐จ Pillow Cropping & Vector-Like Rendering
When exporting a PNG:
1. The app calculates the bounding box of the entire flowchart:
$$x_{min} = \min(x_i - w_i/2),\quad x_{max} = \max(x_i + w_i/2)$$
$$y_{min} = \min(y_i - h_i/2),\quad y_{max} = \max(y_i + h_i/2)$$
2. It expands this box by a 50px margin on all sides.
3. The Pillow engine creates an image matching these exact cropped dimensions and applies a coordinate offset of $-(x_{min} - \text{margin}), -(y_{min} - \text{margin})$ to all drawing routines.
4. Shapes are rendered with crisp black borders, clean fills, and drop shadows offset by $+4\text{px}$ in light-gray, generating a beautiful modern aesthetic.
---
## ๐ Getting Started
### Prerequisites
Ensure Python is installed and fetch dependencies:
```bash
pip install Pillow streamlit
```
### Running the Desktop Application
```bash
python flowchart_app.py
```
### Running the Web Application Locally
```bash
streamlit run app.py
```
---
## ๐ Deployment on Hugging Face
This project is pre-configured for Hugging Face Spaces using the Streamlit SDK.
1. Create a new Space on [Hugging Face](https://huggingface.co/spaces) and choose the **Streamlit** SDK.
2. Link your local directory to the Git remote of the Space:
```bash
git remote add origin https://huggingface.co/spaces/your-username/your-space-name
```
3. Push to Hugging Face:
```bash
git push -u origin main --force
```
|