flograph
A visual node-based Python environment: dataflow on an infinite Blueprint-style canvas, where every node is real, editable Python — and the same graph also gives you interactive dashboards and printable reports.
Build the pipeline on the model canvas, put its live results on a dashboard page for someone who will never open the model, and write the write-up on a report page that pulls the same charts and numbers into markdown and prints to PDF. One file, three surfaces, no export step between them.
Install
flograph is a standard pip-installable package (hatchling build backend):
pip install flograph # or, from a checkout: pip install -e .
That puts a flograph command on your PATH and makes python -m flograph
work. Optional extras pull in what individual nodes need:
| Extra | Brings | For |
|---|---|---|
matplotlib |
matplotlib | Show Plot, Chart per Value |
plotly |
plotly | Show Plotly, Chart per Value (Plotly) |
excel |
openpyxl | Read/Write Excel |
parquet |
pyarrow | Read/Write Parquet |
geo |
geopandas, folium | maps in a web-view node |
ai |
requests | the local-LLM node assistant |
dev |
pytest, pytest-qt | running the test suite |
pip install "flograph[matplotlib,plotly,excel]"
You don't have to decide up front — Tools > Manage Packages installs into the running environment, and a node can use a package the moment it's there.
The project was renamed from flopy to flograph:
flopywas already taken on PyPI (USGS MODFLOW).
One-file script (no install)
If you can't install packages on a given machine — locked-down work laptop,
no index access — but PySide6, pandas, jedi and psutil are already there,
build a single self-contained .py:
python scripts/build_onefile.py # -> dist/flograph_onefile_<version>.py
python flograph_onefile_<version>.py # run it anywhere, no install
It embeds flograph's own source as a base64 zip and unpacks to a temp dir at startup. It does not bundle the third-party dependencies themselves.
Run it
flograph # after install
python -m flograph # equivalent
python main.py project.flograph # open a project from a checkout
python -m flograph.engine.headless project.flograph # run it with no GUI
File > Create Desktop Shortcut… writes a desktop shortcut that starts
flograph the same way it's running right now — a .lnk on Windows, a
.desktop entry on Linux, a .command script on macOS. It names this
environment's interpreter by absolute path (so nothing depends on what
python means to the desktop) and the entry point you're actually on: the
one-file bundle's own .py if that's how you started, otherwise
-m flograph. If a project is open it can be baked into the shortcut, so
double-clicking opens flograph with that flow loaded.
File > Open Example ships eleven worked projects — filter-and-visualise, an aggregate dashboard, a custom-script chart, join/group-by comparison, an interactive slicer dashboard, a scripted pipeline in a frame, a retail ops command centre, two geo/folium maps, an SVG retrofit workbench that diffs a redrawn SVG against the page already wired to it, and a Goto/From workflow that runs a three-branch report off one source prep with no wires crossing the page. They're the fastest way in.
The idea
Nodes are Python scripts. Every node — the shipped library included — is
one small module: a NODE dict declaring typed ports, an optional PARAMS
list that auto-generates its properties form, and a run(ctx, **inputs)
function. Double-click any node to read or fork its code in the built-in
editor, with syntax highlighting, jedi completion, find/replace, and error
markers on the failing line. There is no privileged built-in tier: the
Group By node is a file you can open and change.
Dataflow semantics. Data flows through typed ports; execution is a
topological walk of the dirty subgraph on a background thread, so re-runs
only recompute what actually changed. Outputs are cached per node. Status
LEDs read at a glance: grey idle, yellow queued, pulsing blue running, green
done, red error — and a node that reports ctx.progress(0..1) from its loop
fills that LED as a ring instead of pulsing, with the percentage beside the
node's name in the status bar. Cancellation is cooperative
(ctx.check_cancelled()).
Inspect everything. Click any node or wire to see the data on it — a paged table view for DataFrames (millions of rows are fine), matplotlib figures with a toolbar, pretty-printed objects. Per-node stdout and tracebacks land in the Log dock, with the traceback mapped back to the line in your node script.
Projects are plain JSON (.flograph) — diffable, reviewable, and small.
Node output caches are written to a side-car <project>.flograph.cache/
directory keyed by a fingerprint of each node's source, params and
everything upstream, so reopening a project restores the results you had
without a re-run. A stale or corrupt entry just leaves that node dirty; it
can never block a load.
Canvas
| Action | Binding |
|---|---|
| Add node | Tab (search palette), right-click, or drag from the library |
| Connect | drag from a port; drop on empty canvas to pick a compatible node |
| Reroute dot | double-click a wire (double-click the dot again to name it) |
| Comment frame | Ctrl+G around the selection (frames move their contents) |
| Run all / selected / cancel | F5 / F6 / Esc |
| Run to this node | right-click a node |
| Pan / zoom | middle-drag or Space+drag / wheel |
| Frame view | F |
| Nudge selection | arrow keys |
| Raise / lower | Ctrl+] / Ctrl+[ — add Shift for front / back |
| Duplicate / delete / rename | Ctrl+D / Del / F2 |
| Settings | Ctrl+, |
| Undo anything | Ctrl+Z — every graph mutation is on the undo stack |
Nodes can be recoloured, aligned and distributed, locked into frames, and stacked in a deliberate front-to-back order. Goto / From nodes give you a wire without the wire: name a value at the Goto, pick it up at any number of Froms, and keep a busy canvas readable. A minimap (toggleable in Settings) and a status-bar resource monitor — system memory, the open project's cache footprint, the selected node's own — keep an eye on the scale of things.
Large graphs get a GPU-accelerated viewport option and zoom-based level of detail, so cards stop rendering their contents when they're too small to read.
Cards, dashboards and controls
Any node can declare NODE["card"] and become a live card on the canvas —
not a preview pane elsewhere, the node is the chart. Card kinds shipped
today: figure, webview, table_viewer, kpi, grid, slicer,
button, note, control, report, plus the structural reroute, goto
and from.
Click + on the page bar to add a dashboard page. Drag nodes onto it and each becomes a tile — the same widget as the canvas card, resizable and arrangeable, showing STALE when its node is dirty. Tiles maximise to fullscreen; pages can be renamed, recoloured, reordered by dragging, and duplicated.
Input controls are the other half of that: a whole node category that you set rather than compute. Slider, Number, Text, Date, Toggle and Choice each carry a caption you write, are typed properly so wires still validate, and re-run everything downstream when you move them. A Slicer does the same for picking values out of a column. The result is a dashboard you can hand to someone who will never open the model canvas: they turn the knobs, the charts answer.
A control's options and bounds can come from its own optional input ports — wire a column into a Choice node and its dropdown is that column's values.
Report pages
The other page kind is a report: markdown that you write, with your results dropped in by name.
# Q3 review
Revenue came to ![[Total Revenue]] across ![[Region Count]] regions.
![[Revenue by Region]]
![[Sales Table|filtered]]
![[Label]] embeds a node's output — a figure, a table, a scalar, a
markdown string — resolved by node label, with ![[Label|port]] picking a
specific output port. Embeds render inline mid-sentence for scalars and as
blocks for charts and tables, update when the flow re-runs, and warn visibly
when a name doesn't resolve. The page prints to PDF at 300dpi; the
preview and the PDF are literally the same document, so they can't disagree.
There is also a Report card (Viz > Report) — the same markdown, but as
a node inside the flow, embedding its own wired inputs. It edits in place
on the canvas, has a right-click Insert menu listing everything embeddable,
and tiles onto a dashboard. That gives you rich prose on a dashboard, which
a chart tile can't do.
Node library
The library dock shows every node type in its category. Right-click any
node to Add to Favorites (or Ctrl+Shift+F on a selected row) and it
is pinned in a ★ Favorites section at the top; the same star puts
favorites first in the Tab search popup. The star button next to the
search box narrows the whole tree to favorites only. Favorites persist
per-machine in settings.
Input — Slider, Number, Text, Date, Toggle, Choice.
IO — Read/Write CSV, Excel, Parquet, JSON (incl. JSONL) and SQLite
(query in, table out); drag a file onto the canvas to get the right reader
already configured. Table is a real spreadsheet you edit on the canvas,
with formulas (=SUM(A1:A9), plus AVERAGE, ROUND, POWER, CONCAT,
LEFT/MID/RIGHT, AND/OR/NOT and the rest of the usual set), fill,
copy/paste, and an optional linked input that keeps its contents when you
disconnect.
Transform — Select Columns, Filter Rows, Sort, Join, Group By, Expression, Concatenate, Missing Values, Duplicate Row Filter, Rename Columns, Pivot, Unpivot, Row Sampling, Convert Types, String Manipulation, Statistics, Data Profile.
Viz — Show Table, Show Plot (matplotlib, live on-canvas), Show Plotly (a real interactive plotly.js chart embedded on the canvas — hover, zoom and pan in place), Show Web View (render anything that produces HTML: folium maps, altair, bokeh, your own template), Card (a Power BI-style KPI number), Table Spec (the incoming table's structure), Chart per Value and Chart per Value (Plotly) — one chart per distinct value of a column, as a stack, in either backend — Slicer, Report.
Any web-view node has Open in Browser on its right-click menu — the same document, in a real browser, refreshed in place when the flow re-runs.
Util — Constant, Reroute, Note, Action Button, Goto, From.
Scripting — Python Script, plus Node Template and Control Template to fork when you're writing your own.
Writing a node
"""My Node
The first paragraph of the docstring shows in the properties panel.
"""
NODE = {
"label": "My Node",
"category": "Transform",
"inputs": [("table", "dataframe")],
"outputs": [("result", "dataframe")],
}
PARAMS = [
{"name": "factor", "type": "float", "default": 1.0},
]
def run(ctx, table):
ctx.log(f"scaling by {ctx.params['factor']}")
ctx.check_cancelled() # cooperative cancellation
ctx.progress(0.5) # 0..1 through a long loop; throttled
return {"result": table * ctx.params["factor"]}
Port types: any, dataframe, series, number, string, bool, object, figure.
Param types include string, text, int, float, bool, choice, columns, date, password. A columns param renders with a ▾ picker listing the columns of
the DataFrames cached on the node's inputs (run upstream once to populate
it); add "multi": False so picking replaces instead of toggling a comma
list.
Rules that matter:
- Treat inputs as read-only — outputs are cached by reference, so a write
that escapes your node rewrites what every other branch reads. The engine
guards what it can guard for free: a pandas input arrives as a
copy-on-write shallow copy, and a list, dict, set or bytearray is rebuilt
one level deep, so appending to a list or assigning a column stays local to
your node. A numpy array arrives read-only and raises if you write to it —
arr = arr.copy()first. Reaching through an input (rows[0]["x"] = 1), and anything else you pass between nodes, remain yours to copy. - Heavy imports go inside
run(). Node scripts are executed to be read, so a top-level import runs at library-load time. - matplotlib: the OO API only (
matplotlib.figure.Figure()), neverpyplot— it isn't thread-safe from the worker. - A list output renders as a stack. Return a list of figures and every surface that draws one figure draws them stacked. That's the whole "one chart per value" mechanism — the loop lives in your script, not in a faceting UI.
Add "card": "figure" (or webview, table_viewer, kpi, grid, …) to
give the node a live card. For an input control, "card": "control" plus
"control": "slider" — one host renders every control shape from that and
your PARAMS, so a new control node is usually just a script.
Drop new .py files under src/flograph/nodes/<category>/ (or your user
nodes directory) and they appear in the library on next launch. If a node's
import is missing, it loads as a broken placeholder that keeps its code and
params — install the package, re-apply the code, and it repairs itself.
AI assistant (optional)
Tools > AI Assistant Settings points flograph at any local OpenAI-compatible chat server — Ollama, LM Studio, llama.cpp. You can then describe a change in English ("filter out rows where price is negative") and get a rewritten node script. It is never applied automatically: the reply lands in the editor for you to read, and Apply stays a separate, explicit action. Nothing leaves your machine unless you point it somewhere that isn't local.
Packages
Tools > Manage Packages installs, upgrades and uninstalls pip packages in
flograph's own environment. Nodes execute in-process, so anything installed
there is importable from a node's run() immediately — no restart for new
installs; upgrades of already-imported modules take effect next launch. The
dialog uses pip when the interpreter has it and falls back to uv pip
(uv-made venvs ship without pip). flograph's own core dependencies are
protected from uninstall.
Settings
Ctrl+, opens a searchable two-column settings grid with a navigation tree:
General (window behaviour, resets), Canvas (display, snapping, colour
muting strength, GPU viewport, previews, page-bar position), Table Node,
and About. Selecting a group narrows the grid; the search box filters
across the page.
Development
uv pip install -p .venv/bin/python -e ".[dev]"
QT_QPA_PLATFORM=offscreen .venv/bin/python -m pytest tests/ -q
Architecture (src layout):
flograph/core— Qt-free model: graph, typed ports, script contract, registry, JSON serialization, spreadsheet engine, report parsing, layering. Fully unit-testable; a poison test keeps Qt and pandas out of its import graph.flograph/engine— background execution: plan builder, single-thread pool worker, output cache and its on-disk persistence, cancellation, per-node stdout capture, tracebacks mapped to node script lines.flograph/nodes— the standard library; each node is a script file loaded as text through the same contract as user code.flograph/ui— canvas (QGraphicsView from scratch), dashboard and report pages, code editor, inspector, properties, console.
Two invariants hold everywhere:
core/is Qt-free, enforced by a test that imports it in a subprocess and asserts PySide6 and pandas never appear.- QUndoCommands are the sole writers to the graph. UI items react to
graph events; nothing mutates the graph from a click handler. That is why
Ctrl+Zworks on literally everything.
See AGENTS.md for the full contributor briefing.
Changelog
See CHANGELOG.md for what's new in each version.
License
MIT — free for commercial and private use, modification and redistribution; just keep the copyright and license notice.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flograph-0.1.8.tar.gz.
File metadata
- Download URL: flograph-0.1.8.tar.gz
- Upload date:
- Size: 713.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Bazzite","version":"44","id":"Kinoite","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1cf5939e6b9bf0768286598c1596c54331b455e0732a7696eff472c308db47b
|
|
| MD5 |
9a101ffa1269410cf97cc71f8ca616ff
|
|
| BLAKE2b-256 |
28cb5d02ee947d94c8c60d39b42938a2004ea8c9ca360f68c49f28b276b9fb98
|
File details
Details for the file flograph-0.1.8-py3-none-any.whl.
File metadata
- Download URL: flograph-0.1.8-py3-none-any.whl
- Upload date:
- Size: 534.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Bazzite","version":"44","id":"Kinoite","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b15b6c8cdebe6efe6604e1b789588463d6361ce919f2a1e4e2ba53152bd177f
|
|
| MD5 |
b0d1355204c38274bcbafea893f9163e
|
|
| BLAKE2b-256 |
759e05249701450812aff13d0b827317c0202e32769fcb3e2031ad03fcf26144
|