A web-based visual node editor for building and simulating dynamic systems with PathSim as the backend. Runs entirely in the browser via Pyodide by default β no server required. Optionally, a Flask backend enables server-side Python execution with any packages (including those with native dependencies that Pyodide can't run). The UI is hosted at view.pathsim.org, free to use for everyone.
- SvelteKit 5 with Svelte 5 runes
- SvelteFlow for the node editor
- Pyodide for in-browser Python/NumPy/SciPy
- Plotly.js for interactive plots
- CodeMirror 6 for code editing
pip install pathview
pathview serveThis starts the PathView server with a local Python backend and opens your browser. No Node.js required.
Options:
--port PORTβ server port (default: 5000)--host HOSTβ bind address (default: 127.0.0.1)--no-browserβ don't auto-open the browser--debugβ debug mode with auto-reload
Convert PathView model files to standalone PathSim scripts:
pathview convert model.pvm # outputs model.py
pathview convert model.pvm -o output.py # custom output path
pathview convert model.pvm --stdout # print to stdoutOr use the Python API directly:
from pathview import convert
python_code = convert("model.pvm")npm install
npm run devTo use the Flask backend during development:
pip install flask flask-cors
npm run server # Start Flask backend on port 5000
npm run dev # Start Vite dev server (separate terminal)
# Open http://localhost:5173/?backend=flasksrc/
βββ lib/
β βββ actions/ # Svelte actions (paramInput)
β βββ animation/ # Graph loading animations
β βββ components/ # UI components
β β βββ canvas/ # Flow editor utilities (connection, transforms)
β β βββ dialogs/ # Modal dialogs
β β β βββ shared/ # Shared dialog components (ColorPicker, etc.)
β β βββ edges/ # SvelteFlow edge components (ArrowEdge)
β β βββ icons/ # Icon component (Icon.svelte)
β β βββ nodes/ # Node components (BaseNode, EventNode, AnnotationNode, PlotPreview)
β β βββ panels/ # Side panels (Simulation, NodeLibrary, CodeEditor, Plot, Console, Events)
β βββ constants/ # Centralized constants (nodeTypes, layout, handles)
β βββ events/ # Event system
β β βββ generated/ # Auto-generated from PathSim
β βββ export/ # Export utilities
β β βββ svg/ # SVG graph export (renderer, types)
β βββ nodes/ # Node type system
β β βββ generated/ # Auto-generated from PathSim
β β βββ shapes/ # Node shape definitions
β βββ plotting/ # Plot system
β β βββ core/ # Constants, types, utilities
β β βββ processing/ # Data processing, render queue
β β βββ renderers/ # Plotly and SVG renderers
β βββ routing/ # Orthogonal wire routing (A* pathfinding)
β βββ pyodide/ # Python runtime (backend, bridge)
β β βββ backend/ # Modular backend system (registry, state, types)
β β βββ pyodide/ # Pyodide Web Worker implementation
β β βββ flask/ # Flask HTTP/SSE backend implementation
β βββ schema/ # File I/O (save/load, component export)
β βββ simulation/ # Simulation metadata
β β βββ generated/ # Auto-generated defaults
β βββ stores/ # Svelte stores (state management)
β β βββ graph/ # Graph state with subsystem navigation
β βββ toolbox/ # Runtime toolbox install/registry (catalog, installer, dependencies)
β βββ tours/ # In-app onboarding tours (builder, anchors, scripts)
β βββ types/ # TypeScript type definitions
β βββ utils/ # Utilities (colors, download, csvExport, codemirror)
βββ routes/ # SvelteKit pages
βββ app.css # Global styles with CSS variables
pathview/ # Python package (pip install pathview)
βββ app.py # Flask server (subprocess management, HTTP routes)
βββ worker.py # REPL worker subprocess (Python execution)
βββ cli.py # CLI entry point (pathview serve)
βββ config.py # Server configuration (host, port, packages)
βββ converter.py # PVM to Python converter (public API)
βββ data/ # Bundled data files
β βββ registry.json # Block/event registry for converter
βββ static/ # Bundled frontend (generated at build time)
scripts/
βββ config/ # Configuration files for extraction
β βββ schemas/ # JSON schemas for validation
β βββ pathsim/ # Core PathSim blocks, events, simulation config
β βββ pathsim-chem/ # Chemical toolbox blocks
β βββ pyodide.json # Pyodide version and preload packages
β βββ requirements-pyodide.txt # Runtime Python packages
β βββ requirements-build.txt # Build-time Python packages
βββ generated/ # Generated files (from extract.py)
β βββ registry.json # Block/event registry with import paths
βββ extract.py # Unified extraction script
βββ pathview_introspect.py # Shared block/event introspection helpers
βββ pvm2py.py # Standalone .pvm to Python converter
βββ build_package.py # Build pip wheel + bundled frontend
βββ capture-screenshots.js # Snapshot block icons for build (Playwright)
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Graph Store βββββ>β pathsimRunner βββββ>β Python Code β
β (nodes, edges) β β (code gen) β β (string) β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
v
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Plot/Console β<βββββ bridge.ts β<βββββ Backend β
β (results) β β (queue + rAF) β β (Pyodide/Flask) β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
Simulations run in streaming mode for real-time visualization. The worker runs autonomously and pushes results without waiting for the UI:
Worker (10 Hz) Main Thread UI (10 Hz)
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Python loop β ββββββββ> β Result Queue β ββββββββ> β Plotly β
β (autonomous) β stream- β (accumulate) β rAF β extendTraces β
β β data β β batched β β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
- Decoupled rates: Python generates data at 10 Hz, UI renders at 10 Hz max
- Queue-based: Results accumulate in queue, merged on each UI frame
- Non-blocking: Simulation never waits for plot rendering
- extendTraces: Scope plots append data incrementally instead of full re-render
PathView uses Simulink-style orthogonal wire routing with A* pathfinding:
- Automatic routing: Wires route around nodes with 90Β° bends only
- User waypoints: Press
\on selected edge to add manual waypoints - Draggable waypoints: Drag waypoint markers to reposition, double-click to delete
- Segment dragging: Drag segment midpoints to create new waypoints
- Incremental updates: Spatial indexing (O(1) node updates) for smooth dragging
- Hybrid routing: Routes through user waypoints: Source β A* β W1 β A* β Target
Key files: src/lib/routing/ (pathfinder, grid builder, route calculator)
| Layer | Purpose | Key Files |
|---|---|---|
| Main App | Orchestrates panels, shortcuts, file ops | routes/+page.svelte |
| Flow Canvas | SvelteFlow wrapper, node/edge sync | components/FlowCanvas.svelte |
| Flow Updater | View control, animation triggers | components/FlowUpdater.svelte |
| Context Menus | Right-click menus for nodes/canvas/plots | components/ContextMenu.svelte, contextMenuBuilders.ts |
| Graph Store | Node/edge state, subsystem navigation | stores/graph/ |
| View Actions | Fit view, zoom, pan controls | stores/viewActions.ts, stores/viewTriggers.ts |
| Clipboard | Copy/paste/duplicate operations | stores/clipboard.ts |
| Plot Settings | Per-trace and per-block plot options | stores/plotSettings.ts |
| Node Registry | Block type definitions, parameters | nodes/registry.ts |
| Code Generation | Graph β Python code | pyodide/pathsimRunner.ts |
| Backend | Modular Python execution interface | pyodide/backend/ |
| Backend Registry | Factory for swappable backends | pyodide/backend/registry.ts |
| PyodideBackend | Web Worker Pyodide implementation | pyodide/backend/pyodide/ |
| FlaskBackend | HTTP/SSE Flask server implementation | pyodide/backend/flask/ |
| Simulation Bridge | High-level simulation API | pyodide/bridge.ts |
| Schema | File/component save/load operations | schema/fileOps.ts, schema/componentOps.ts |
| Export Utils | SVG/CSV/Python file downloads | utils/download.ts, export/svg/, utils/csvExport.ts |
Use these imports instead of magic strings:
import { NODE_TYPES } from '$lib/constants/nodeTypes';
// NODE_TYPES.SUBSYSTEM, NODE_TYPES.INTERFACE
import { PORT_COLORS, DIALOG_COLOR_PALETTE } from '$lib/utils/colors';
// PORT_COLORS.default, etc.Blocks are extracted automatically from PathSim using the Block.info() classmethod. The extraction is config-driven for easy maintenance.
The block must be importable from pathsim.blocks (or toolbox module):
from pathsim.blocks import YourNewBlockEdit scripts/config/pathsim/blocks.json and add the block class name to the appropriate category:
{
"categories": {
"Algebraic": [
"Adder",
"Multiplier",
"YourNewBlock"
]
}
}Port configurations are automatically extracted from Block.info():
Noneβ Variable/unlimited ports (UI allows add/remove){}β No ports of this type{"name": index}β Fixed labeled ports (locked count)
npm run extractThis generates TypeScript files in src/lib/*/generated/ with:
- Block metadata (parameters, descriptions, docstrings)
- Port configurations from
Block.info() - Pyodide runtime config
Start the dev server and check that your block appears in the Block Library panel.
Some blocks process inputs as parallel paths where each input has a corresponding output (e.g., Integrator, Amplifier, Sin). For these blocks, the UI only shows input port controls and outputs auto-sync.
Configure in src/lib/nodes/uiConfig.ts:
export const syncPortBlocks = new Set([
// Dynamic blocks
'Integrator', 'Differentiator', 'Delay',
// Algebraic blocks (element-wise)
'Amplifier', 'Sin', 'Cos', 'Tan', 'Tanh',
'Abs', 'Sqrt', 'Exp', 'Log', 'Log10',
'Mod', 'Clip', 'Pow', 'Polynomial',
'Rescale', 'Alias',
// Logic blocks
'LogicNot',
// Discrete blocks
'SampleHold', 'FirstOrderHold',
'DiscreteIntegrator', 'DiscreteDerivative'
]);Some blocks derive port names from a parameter (e.g., Scope and Spectrum use labels to name input traces). When the parameter changes, port names update automatically.
Configure in src/lib/nodes/uiConfig.ts:
export const portLabelParams: Record<string, PortLabelConfig | PortLabelConfig[]> = {
Scope: { param: 'labels', direction: 'input' },
Spectrum: { param: 'labels', direction: 'input' },
// Multiple directions supported:
// SomeBlock: [
// { param: 'input_labels', direction: 'input' },
// { param: 'output_labels', direction: 'output' }
// ]
};PathView supports two complementary ways to extend the block library: runtime toolboxes that the user installs from the UI at any time, and build-time toolboxes that are baked into the deployed bundle.
Users install toolboxes from the Toolbox Manager dialog without rebuilding the app. Three install sources are supported:
- PyPI β install a published package via
micropip(Pyodide) orpip(Flask backend) - URL β load a wheel or sdist hosted anywhere
- Inline β paste / upload a
.pymodule to register ad-hoc blocks for one session
Once installed, PathView introspects the module's Block (and optional Event) subclasses, registers them as new node types, and persists the selection to localStorage so it replays on reload. The user can disable individual blocks, override their category, name, shape, or syncPorts flag, and uninstall the toolbox at any time. Saving a .pvm file embeds the list of toolbox dependencies; opening one elsewhere prompts to install missing pieces.
Implementation lives in src/lib/toolbox/ (catalog, installer, register, persistence). The curated catalog of one-click installable toolboxes is in catalog.ts:
export const TOOLBOX_CATALOG: CatalogEntry[] = [
{
id: 'pathsim-chem',
displayName: 'pathsim-chem',
source: { type: 'pypi', pkg: 'pathsim-chem' },
importPath: 'pathsim_chem',
defaultCategory: 'Chemical',
preloaded: true // seeded into store on first launch
}
];To add an entry to the catalog: append a CatalogEntry to TOOLBOX_CATALOG and ship β no extraction step, no rebuild required for the user. Toolboxes outside the catalog still work via the manager's PyPI/URL/file inputs.
For the toolbox-package contract (which Python conventions a toolbox must follow to be introspectable), see docs/toolbox-spec.md.
For toolboxes that should be available without an install step (e.g. the core pathsim toolbox itself), block metadata is extracted at build time:
- Add the package to
scripts/config/requirements-pyodide.txtso Pyodide can install it. - Create
scripts/config/<toolbox-id>/blocks.json(and optionallyevents.json) listing the categories and block class names. - Run
npm run extractto regenerate the TypeScript registry undersrc/lib/*/generated/.
The build-time path is appropriate when a toolbox is part of the core deployment; for everything else prefer the runtime path, which avoids a redeploy.
The Python runtime uses a modular backend architecture, allowing different execution environments (Pyodide, local Python, remote server) to be swapped without changing application code.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Backend Interface β
β init(), exec(), evaluate(), startStreaming(), stopStreaming()... β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌβββββββββββββββ
βΌ βΌ βΌ
βββββββββββββ βββββββββββββ βββββββββββββ
β Pyodide β β Flask β β Remote β
β Backend β β Backend β β Backend β
β (default) β β (HTTP) β β (future) β
βββββββββββββ βββββββββββββ βββββββββββββ
β β
βΌ βΌ
βββββββββββββ βββββββββββββ
β Web Workerβ β Flask βββ> Python subprocess
β (Pyodide) β β Server β (one per session)
βββββββββββββ βββββββββββββ
import { getBackend, switchBackend, setFlaskHost } from '$lib/pyodide/backend';
// Get current backend (defaults to Pyodide)
const backend = getBackend();
// Switch to Flask backend
setFlaskHost('http://localhost:5000');
switchBackend('flask');Backend selection can also be controlled via URL parameters:
http://localhost:5173/?backend=flask # Flask on default port
http://localhost:5173/?backend=flask&host=http://myserver:5000 # Custom host
Requests (Main β Worker):
type REPLRequest =
| { type: 'init' }
| { type: 'exec'; id: string; code: string } // Execute code (no return)
| { type: 'eval'; id: string; expr: string } // Evaluate expression (returns JSON)
| { type: 'stream-start'; id: string; expr: string } // Start streaming loop
| { type: 'stream-stop' } // Stop streaming loop
| { type: 'stream-exec'; code: string } // Execute code during streamingResponses (Worker β Main):
type REPLResponse =
| { type: 'ready' }
| { type: 'ok'; id: string } // exec succeeded
| { type: 'value'; id: string; value: string } // eval result (JSON)
| { type: 'error'; id: string; error: string; traceback?: string }
| { type: 'stdout'; value: string }
| { type: 'stderr'; value: string }
| { type: 'progress'; value: string }
| { type: 'stream-data'; id: string; value: string } // Streaming result
| { type: 'stream-done'; id: string } // Streaming completedimport { init, exec, evaluate } from '$lib/pyodide/backend';
// Initialize backend (Pyodide by default)
await init();
// Execute Python code
await exec(`
import numpy as np
x = np.linspace(0, 10, 100)
`);
// Evaluate and get result
const result = await evaluate<number[]>('x.tolist()');For simulation, use the higher-level API in bridge.ts:
import {
runStreamingSimulation,
continueStreamingSimulation,
stopSimulation,
execDuringStreaming
} from '$lib/pyodide/bridge';
// Run streaming simulation
const result = await runStreamingSimulation(pythonCode, duration, (partialResult) => {
console.log('Progress:', partialResult.scopeData);
});
// result.scopeData, result.spectrumData, result.nodeNames
// Continue simulation from where it stopped
const moreResult = await continueStreamingSimulation('5.0');
// Stop simulation gracefully
await stopSimulation();
// Execute code during active simulation (queued between steps)
execDuringStreaming('source.amplitude = 2.0');The Flask backend enables server-side Python execution for packages that Pyodide can't run (e.g., FESTIM or other packages with native C/Fortran dependencies). It mirrors the Web Worker architecture: one subprocess per session with the same REPL protocol.
Browser Tab Flask Server Worker Subprocess
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β FlaskBackend β HTTP/SSE β app.py β stdin β worker.py β
β exec() βββPOSTββββββββββ route β session βββJSONβββββ exec(code, ns) β
β eval() βββPOSTββββββββββ subprocess mgr βββJSONβββββ eval(expr, ns) β
β stream() βββPOST (SSE)ββββ pipe SSE relay βββJSONβββββ streaming loop β
β inject() βββPOSTββββββββββ β code queue βββJSONβββββ queue drain β
β stop() βββPOSTββββββββββ β stop flag βββJSONβββββ stop check β
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
Standalone (pip package):
pip install pathview
pathview serveDevelopment (separate servers):
pip install flask flask-cors
npm run server # Starts Flask API on port 5000
npm run dev # Starts Vite dev server (separate terminal)
# Open http://localhost:5173/?backend=flaskKey properties:
- Process isolation β each session gets its own Python subprocess
- Host environment β workers run with the same Python used to install pathview, so all packages in the user's environment are available in the code editor
- Namespace persistence β variables persist across exec/eval calls within a session
- Dynamic packages β packages from
PYTHON_PACKAGES(the same config used by Pyodide) are pip-installed on first init if not already present - Session TTL β stale sessions cleaned up after 1 hour of inactivity
- Streaming β simulations stream via SSE, with the same code injection support as Pyodide
For the full protocol reference (message types, HTTP routes, SSE format, streaming semantics, how to implement a new backend), see docs/backend-protocol-spec.md.
API routes:
| Route | Method | Action |
|---|---|---|
/api/health |
GET | Health check |
/api/init |
POST | Initialize worker with packages |
/api/exec |
POST | Execute Python code |
/api/eval |
POST | Evaluate expression, return JSON |
/api/stream |
POST | Start streaming simulation (SSE) |
/api/stream/exec |
POST | Inject code during streaming |
/api/stream/stop |
POST | Stop streaming |
/api/session |
DELETE | Kill session subprocess |
SvelteFlow manages its own UI state (selection, viewport, node positions). The graph store manages application data:
| State Type | Managed By | Examples |
|---|---|---|
| UI State | SvelteFlow | Selection, viewport, dragging |
| App Data | Graph Store | Node parameters, connections, subsystems |
Do not duplicate SvelteFlow state in custom stores. Use SvelteFlow's APIs (useSvelteFlow, event handlers) to interact with canvas state.
Stores use Svelte's writable with custom wrapper objects:
const internal = writable<T>(initialValue);
export const myStore = {
subscribe: internal.subscribe,
// Custom methods
doSomething() {
internal.update(state => ({ ...state, ... }));
}
};Important: Do NOT wrap .subscribe() in $effect() - this causes infinite loops.
<script>
// Correct
myStore.subscribe(value => { localState = value; });
// Wrong - causes infinite loop
$effect(() => {
myStore.subscribe(value => { localState = value; });
});
</script>Subsystems are nested graphs with path-based navigation:
graphStore.drillDown(subsystemId); // Drill into subsystem
graphStore.drillUp(); // Go up one level
graphStore.navigateTo(level); // Navigate to breadcrumb level
graphStore.currentPath // Current navigation pathThe Interface node inside a subsystem mirrors its parent Subsystem's ports (with inverted direction).
Press ? to see all shortcuts in the app. Key shortcuts:
| Category | Shortcut | Action |
|---|---|---|
| File | Ctrl+O |
Open |
Ctrl+S |
Save | |
Ctrl+E |
Export Python | |
| Edit | Ctrl+Z/Y |
Undo/Redo |
Ctrl+D |
Duplicate | |
Ctrl+F |
Find | |
Del |
Delete | |
| Transform | R |
Rotate 90Β° |
X / Y |
Flip H/V | |
Arrows |
Nudge selection | |
| Wires | \ |
Add waypoint to selected edge |
| Labels | L |
Toggle port labels |
| View | F |
Fit view |
H |
Go to root | |
T |
Toggle theme | |
| Panels | B |
Blocks |
N |
Events | |
S |
Simulation | |
V |
Results | |
C |
Console | |
| Run | Ctrl+Enter |
Simulate |
Shift+Enter |
Continue |
PathView uses JSON-based file formats for saving and sharing:
| Extension | Type | Description |
|---|---|---|
.pvm |
Model | Complete simulation model (graph, events, settings, code) |
.blk |
Block | Single block with parameters (for sharing/reuse) |
.sub |
Subsystem | Subsystem with internal graph (for sharing/reuse) |
The .pvm format is fully documented in docs/pvm-spec.md. Use this spec if you are building tools that read or write PathView models (e.g., code generators, importers). A reference Python code generator is available at scripts/pvm2py.py.
| Document | Audience |
|---|---|
| docs/pvm-spec.md | Building tools that read/write .pvm model files |
| docs/backend-protocol-spec.md | Implementing a new execution backend (remote server, cloud worker, etc.) |
| docs/toolbox-spec.md | Creating a third-party toolbox package for PathView |
- File > Save - Save complete model as
.pvm - File > Export Python - Generate standalone Python script
- Right-click node > Export - Save individual block/subsystem
- Right-click canvas > Export SVG - Export graph as vector image
- Right-click plot > Download PNG/SVG - Export plot as image
- Right-click plot > Export CSV - Export simulation data as CSV
- Scope/Spectrum node context menu - Export simulation data as CSV
Models can be loaded directly from a URL using query parameters:
https://view.pathsim.org/?model=<url>
https://view.pathsim.org/?modelgh=<github-shorthand>
| Parameter | Description | Example |
|---|---|---|
model |
Direct URL to a .pvm or .json file |
?model=https://example.com/mymodel.pvm |
modelgh |
GitHub shorthand (expands to raw.githubusercontent.com) | ?modelgh=user/repo/path/to/model.pvm |
The modelgh parameter expands to a raw GitHub URL:
modelgh=user/repo/examples/demo.pvm
β https://raw.githubusercontent.com/user/repo/main/examples/demo.pvm
# Load from any URL
https://view.pathsim.org/?model=https://mysite.com/models/feedback.pvm
# Load from GitHub repository
https://view.pathsim.org/?modelgh=pathsim/pathview/static/examples/feedback-system.json
| Script | Purpose |
|---|---|
npm run dev |
Start Vite development server |
npm run server |
Start Flask backend server (port 5000) |
npm run build |
Production build (GitHub Pages) |
npm run build:package |
Build pip package (frontend + wheel) |
npm run preview |
Preview production build |
npm run check |
TypeScript/Svelte type checking |
npm run check:watch |
Type checking in watch mode |
npm run screenshots |
Capture block-icon screenshots (Playwright) |
npm run lint |
Run ESLint |
npm run format |
Format code with Prettier |
npm run extract |
Regenerate all definitions from PathSim |
npm run extract:blocks |
Blocks only |
npm run extract:events |
Events only |
npm run extract:simulation |
Simulation params only |
npm run extract:deps |
Dependencies only |
npm run extract:validate |
Validate config files |
npm run pvm2py -- <file> |
Convert .pvm file to standalone Python script |
Nodes are styled based on their category, with CSS-driven shapes and colors.
| Category | Shape | Border Radius |
|---|---|---|
| Sources | Pill | 20px |
| Dynamic | Rectangle | 4px |
| Algebraic | Rectangle | 4px |
| Logic | Rectangle | 4px |
| Discrete | Asymmetric | 12px 4px 12px 4px |
| Recording | Pill | 20px |
| Subsystem | Rectangle | 4px |
Shapes are defined in src/lib/nodes/shapes/registry.ts and applied via CSS classes (.shape-pill, .shape-rect, etc.).
- Default node color: CSS variable
--accent(#0070C0 - PathSim blue) - Custom colors: Right-click node β Properties β Color picker (12 colors available)
- Port colors:
PORT_COLORS.default(#969696 gray), customizable per-port
Colors are CSS-driven - see src/app.css for variables and src/lib/utils/colors.ts for palettes.
Port labels show the name of each input/output port alongside the node. Toggle globally with L key, or per-node via right-click menu.
- Global toggle: Press
Lto show/hide port labels for all nodes - Per-node override: Right-click node β "Show Input Labels" / "Show Output Labels"
- Truncation: Labels are truncated to 5 characters for compact display
- SVG export: Port labels are included when exporting the graph as SVG
-
Register the shape in
src/lib/nodes/shapes/registry.ts:registerShape({ id: 'hexagon', name: 'Hexagon', cssClass: 'shape-hexagon', borderRadius: '0px' });
-
Add CSS in
src/app.cssor component styles:.shape-hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); }
-
Optionally map categories to the new shape:
setCategoryShape('MyCategory', 'hexagon');
-
Python is first-class - All node parameters are Python expressions stored as strings and passed verbatim to PathSim. PathSim handles all type checking and validation at runtime.
-
Subsystems are nested graphs - The Interface node inside a subsystem mirrors its parent's ports (inverted direction).
-
No server required by default - Everything runs client-side via Pyodide. The optional Flask backend enables server-side execution for packages with native dependencies.
-
Registry pattern - Nodes and events are registered centrally for extensibility.
-
Minimal state - Derive where possible, avoid duplicating truth. SvelteFlow manages its own UI state.
-
CSS for styling - Use CSS variables from
app.cssand component<style>blocks, not JavaScript theme APIs. -
Svelte 5 runes - Use
$state,$derived,$effectexclusively.
- Autonomous worker: Python runs in a Web Worker loop, pushing results without waiting for UI acknowledgment
- Queue-based updates: Results accumulate in a queue, merged in batches via
requestAnimationFrame - Decoupled rates: Simulation @ 10 Hz, UI updates @ 10 Hz max - expensive plots don't slow simulation
- extendTraces: During streaming, scope plots append new data instead of full re-render
- SVG mode: Uses
scatter(SVG) instead ofscattergl(WebGL) for stability during streaming - Visibility API: Pauses plot updates when browser tab is hidden
- Separate render queue: Plot previews in nodes use SVG paths (not Plotly)
- Min-max decimation: Large datasets downsampled while preserving peaks/valleys
- Deferred rendering: Shared queue prevents preview updates from blocking main plots
PathView has two deployment targets:
| Trigger | What happens | Deployed to |
|---|---|---|
Push to main |
Build with base path /dev |
view.pathsim.org/dev/ |
| Release published | Bump package.json, build, deploy |
view.pathsim.org/ |
| Manual dispatch | Choose dev or release |
Respective path |
| Trigger | What happens | Published to |
|---|---|---|
| Release published | Build frontend + wheel, publish | pypi.org/project/pathview |
| Manual dispatch | Choose testpypi or pypi |
Respective index |
- Both versions deploy to the
deploymentbranch using GitHub Actions - Dev builds update only the
/devfolder, preserving the release at root - Release builds update root, preserving
/dev - Version in
package.jsonis automatically bumped from the release tag (e.g.,v0.4.0β0.4.0)
- Create a GitHub release with a version tag (e.g.,
v0.4.0) - The workflow automatically:
- Updates
package.jsonto match the tag - Commits the version bump to
main - Builds and deploys to production
- Updates
MIT
