westland/Flowcharting
B&W Flowchart Sketcher (Web & Desktop)
A premium, interactive utility to sketch clean, high-contrast, black-and-white flowcharts. This repository bundles two interfaces:
- Interactive Tkinter Desktop App (
flowchart_app.py): Supports canvas drag-and-drop node adjustments, visual drafting gridlines, and project serialization. - 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
- Mathematical Engine (Shapes & Ray Intersection)
- Hierarchical Auto-Layout Solver
- Consistent Text-Wrapping Engine
- Pillow Cropping & Vector-Like Rendering
- Getting Started
- 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 $(xc, yc)$ with semi-axes $a$ (horizontal radius) and $b$ (vertical radius) satisfies: $$\frac{(x - xc)^2}{a^2} + \frac{(y - yc)^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 $(xc + t \cdot dx, yc + t \cdot dy)$.
2. Polygons (Squares, Diamonds, Inverted Triangles)
For shapes represented by vertices, we cast a ray from the center $(xc, yc)$ to $(xc + 10000 \cdot dx, yc + 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:
- In-Degree Calculation: Build an adjacency list and count incoming edges for every node.
- Topological Leveling (BFS): Node levels are assigned. Nodes with
in-degree = 0start 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. - 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.measurein Tkinter anddraw.textlengthin 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:
- The app calculates the bounding box of the entire flowchart: $$x{min} = \min(xi - wi/2),\quad x{max} = \max(xi + wi/2)$$ $$y{min} = \min(yi - hi/2),\quad y{max} = \max(yi + hi/2)$$
- It expands this box by a 50px margin on all sides.
- 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.
- 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:
pip install Pillow streamlitRunning the Desktop Application
python flowchart_app.pyRunning the Web Application Locally
streamlit run app.py๐ Deployment on Hugging Face
This project is pre-configured for Hugging Face Spaces using the Streamlit SDK.
- Create a new Space on Hugging Face and choose the Streamlit SDK.
- Link your local directory to the Git remote of the Space:
git remote add origin https://huggingface.co/spaces/your-username/your-space-name- Push to Hugging Face:
git push -u origin main --force