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
   ```