{"id":49079487,"url":"https://github.com/pablormier/easydot","last_synced_at":"2026-05-01T00:02:33.784Z","repository":{"id":352020542,"uuid":"1213382232","full_name":"pablormier/easydot","owner":"pablormier","description":"Graphviz in the browser. Zero installs. One line of Python.","archived":false,"fork":false,"pushed_at":"2026-04-22T08:50:59.000Z","size":1986,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T14:33:11.330Z","etag":null,"topics":["dot","graphs","graphviz","network","plots","python","wasm","webassembly"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pablormier.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-17T10:16:43.000Z","updated_at":"2026-04-22T08:51:02.000Z","dependencies_parsed_at":"2026-04-21T13:00:48.417Z","dependency_job_id":null,"html_url":"https://github.com/pablormier/easydot","commit_stats":null,"previous_names":["pablormier/easydot"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/pablormier/easydot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pablormier%2Feasydot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pablormier%2Feasydot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pablormier%2Feasydot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pablormier%2Feasydot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pablormier","download_url":"https://codeload.github.com/pablormier/easydot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pablormier%2Feasydot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32480557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dot","graphs","graphviz","network","plots","python","wasm","webassembly"],"created_at":"2026-04-20T12:14:37.047Z","updated_at":"2026-05-01T00:02:33.773Z","avatar_url":"https://github.com/pablormier.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/easydot-logo.png\" alt=\"easydot\" width=\"300\"\u003e\n\n**High-quality Graphviz plots from Python, with browser, WASM, and native backends.**\n\n[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776ab?logo=python\u0026logoColor=white)](https://www.python.org)\n[![pip install easydot](https://img.shields.io/badge/pip%20install-easydot-blue?logo=pypi\u0026logoColor=white)](https://pypi.org/project/easydot/)\n[![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-green)](LICENSE)\n[![No Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)]()\n[![marimo](https://marimo.io/shield.svg)](https://marimo.app/l/939bsu)\n\n\u003c/div\u003e\n\n```bash\npip install easydot\n```\n\n```python\nimport easydot\n\neasydot.render(\"digraph { A -\u003e B -\u003e C }\")\n```\n\n## Example\n\n\u003cimg src=\"assets/example.png\" alt=\"easydot example\" width=\"800px\"\u003e\n\n---\n\n## 💡 Why easydot\n\nGraphviz is the best way to lay out DOT graphs, but the right runtime depends\non where your code is running. Native `dot` is great when it is installed;\nbrowser rendering is better in notebooks and sandboxed frontends; server-side\nWASM is useful when you want static SVGs without system binaries.\n\n`easydot` gives all three paths a small Python API.\n\n- **One entry point.** `easydot.render(...)` returns a rich notebook display object; `easydot.to_string(...)` returns raw HTML or SVG.\n- **Three backends.** `browser` uses JS/WASM in the frontend, `wasm` uses `wasi-graphviz` in Python, and `native` shells to installed Graphviz executables.\n- **Pip-installable default.** The browser backend has no Python dependencies and does not require `brew`, `conda`, `apt-get`, or Dockerfile changes.\n- **Tiny notebook outputs.** The WASM bundle is vendored and served once over loopback instead of inlined into every cell.\n- **Offline-capable.** Browser assets ship in the package; server-side backends do not need browser network access.\n\n## 🔤 Why DOT\n\nDOT is a small text format for graph diagrams. Many Python libraries and build tools can generate it.\n\n- **Common output format.** [NetworkX](https://networkx.org/), [pydot](https://pypi.org/project/pydot/), [pygraphviz](https://pygraphviz.github.io/), [scikit-learn](https://scikit-learn.org) decision trees, [PyTorch](https://pytorch.org) and [TensorFlow](https://www.tensorflow.org) model viz, [Dask](https://www.dask.org/) task graphs, [Airflow](https://airflow.apache.org/) DAGs, Terraform, Bazel, Ninja, `gprof2dot`, and other tools can emit DOT.\n- **LLM-friendly.** Models can usually generate DOT for architecture diagrams, state machines, and dependency graphs.\n- **Plain text.** Diffs cleanly, templates easily, pipes nicely.\n- **Graphviz features.** Five layout engines (`dot`, `neato`, `fdp`, `circo`, `twopi`), clusters, HTML-like labels, and styling.\n\n## 🚀 Usage\n\n### Quick start\n\n`render()` is the main interface. It returns a `Graph` object that displays\nin Jupyter, marimo, and other rich-output environments. Use `backend=\"auto\"`\n(the default) to select the first working backend, or pick one explicitly.\n\n```python\nimport easydot\n\n# Auto-select the best available backend (native → wasm → browser)\neasydot.render(\"digraph { A -\u003e B -\u003e C }\")\n\n# Explicit backends\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", backend=\"browser\")   # browser JS/WASM\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", backend=\"wasm\")      # server-side WASM\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", backend=\"native\")    # native Graphviz\n\n# Fit and scale work on all backends\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", fit=\"horizontal\")\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", fit=\"both\", scale=1.5)\n\n# Raw output\neasydot.svg(\"digraph { A -\u003e B -\u003e C }\")                       # SVG string (wasm/native)\neasydot.html(\"digraph { A -\u003e B -\u003e C }\", fit=\"horizontal\")    # display-ready HTML\neasydot.native(\"digraph { A -\u003e B -\u003e C }\", format=\"png\")      # PNG bytes\n```\n\n### Backend guide\n\n| Backend   | Runtime             | Fit/scale | Best for                                      |\n| --------- | ------------------- | --------- | --------------------------------------------- |\n| `browser` | frontend JS/WASM    | ✓         | notebooks, marimo, JupyterLite, Pyodide       |\n| `wasm`    | Python WASI runtime | ✓         | saved notebooks, GitHub, CI without Graphviz  |\n| `native`  | Graphviz executable | ✓         | local/conda/server environments with Graphviz |\n\nCheck what works in the current runtime:\n\n```python\ncaps = easydot.capabilities()\ncaps[\"browser\"].available   # True if local or CDN browser assets are reachable\ncaps[\"wasm\"].available      # True if wasi-graphviz can render a probe graph\ncaps[\"native\"].available    # True if native dot can render a probe graph\n```\n\n`backend=\"auto\"` uses these probes and chooses `native`, then `wasm`, then\n`browser` with local assets, then `browser` with CDN assets.\nProbe results are cached in-process; pass `refresh_capabilities=True` to\n`render(..., backend=\"auto\")` or `refresh=True` to `capabilities()` if the\nruntime changes after startup.\n\n### Server-side WASM\n\nFor static SVG output that works in saved notebooks and GitHub without a live browser runtime:\n\n```bash\npip install easydot[wasm]\n```\n\n```python\nimport easydot\n\n# Raw SVG string\nsvg = easydot.svg(\"digraph { A -\u003e B -\u003e C }\", backend=\"wasm\")\n\n# Rich display object for notebooks — fit and scale work the same as browser\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", backend=\"wasm\", fit=\"horizontal\")\n\n# Display-ready HTML with fit/scale\nhtml = easydot.html(\"digraph { A -\u003e B -\u003e C }\", backend=\"wasm\", fit=\"both\")\n```\n\n### Native Graphviz\n\nIf Graphviz executables are installed and available on `PATH`, `easydot` can\nrender through the native toolchain:\n\n```python\nimport easydot\n\nsvg = easydot.svg(\"digraph { A -\u003e B -\u003e C }\", backend=\"native\")\neasydot.render(\"digraph { A -\u003e B -\u003e C }\", backend=\"native\", fit=\"horizontal\")\n\n# Non-SVG formats: native() returns bytes for binary formats\npng_bytes = easydot.native(\"digraph { A -\u003e B -\u003e C }\", format=\"png\")\npdf_bytes = easydot.native(\"digraph { A -\u003e B -\u003e C }\", format=\"pdf\")\n```\n\nThe native backend shells to the selected Graphviz engine, such as `dot` or\n`neato`, and fails if the executable is missing or Graphviz returns an error.\n\n### pydot\n\n```bash\npip install easydot[pydot]\n```\n\n```python\nimport easydot, pydot\n\ngraph = pydot.Dot(\"example\", graph_type=\"digraph\")\ngraph.add_edge(pydot.Edge(\"A\", \"B\"))\n\neasydot.render(graph)\n```\n\n### NetworkX\n\n```python\nimport easydot, networkx as nx\nfrom networkx.drawing.nx_pydot import to_pydot\n\nG = nx.DiGraph([(\"A\", \"B\"), (\"B\", \"C\"), (\"A\", \"C\")])\neasydot.render(to_pydot(G))\n```\n\n### CLI\n\n```bash\n# HTML output (default) — fit and scale work on all backends\necho 'digraph { A -\u003e B }' | easydot                              # browser backend HTML\necho 'digraph { A -\u003e B }' | easydot --backend auto              # best available backend\necho 'digraph { A -\u003e B }' | easydot --backend wasm --fit horizontal   # WASM with fit\necho 'digraph { A -\u003e B }' | easydot --backend native --scale 1.5      # native with scale\n\n# Raw SVG (wasm or native only)\necho 'digraph { A -\u003e B }' | easydot --format svg --backend wasm\necho 'digraph { A -\u003e B }' | easydot --format svg --backend native\n\n# Binary formats (native only)\necho 'digraph { A -\u003e B }' | easydot --format png --backend native \u003e graph.png\necho 'digraph { A -\u003e B }' | easydot --format pdf --backend native \u003e graph.pdf\n\neasydot --urls                                                    # print asset server URLs\n```\n\n## 🔀 Source Modes\n\nBy default, `easydot` tries a pinned CDN URL first and falls back to the local server.\n\n| Mode    | Local | CDN | Best for                                               |\n| ------- | :---: | :-: | ------------------------------------------------------ |\n| `auto`  |  yes  | yes | Most setups (default; CDN first, then local fallback)  |\n| `local` |  yes  | no  | Offline environments with no internet access           |\n| `cdn`   |  no   | yes | Remote hosts where `127.0.0.1` isn't browser-reachable |\n\n```python\neasydot.render(\"digraph { A -\u003e B }\", source=\"cdn\")\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEnvironment variables\u003c/b\u003e\u003c/summary\u003e\n\nSet a notebook-wide default without editing every call:\n\n```python\nimport os\nos.environ[\"EASYDOT_SOURCE\"] = \"cdn\"   # auto | local | cdn\n```\n\nOnly applies when `source=\"auto\"`. Explicit `source=` arguments still win.\n\nFor hosted marimo environments that protect generated iframe file URLs, force a self-contained iframe:\n\n```python\nos.environ[\"EASYDOT_IFRAME_MODE\"] = \"srcdoc\"   # auto | managed | srcdoc | data\n```\n\nPyCharm notebooks are detected automatically and use a `data:` iframe because\ntheir output recycling can detach and reattach `srcdoc` iframes while scrolling.\nYou can force that wrapper explicitly with `EASYDOT_IFRAME_MODE=\"data\"`.\n\nThe same modes are available per render call:\n\n```python\neasydot.render(\"digraph { A -\u003e B }\", iframe_mode=\"data\")\n```\n\n\u003c/details\u003e\n\n## 📓 marimo\n\nWorks out of the box. `easydot` detects marimo and uses its iframe display helper automatically, since marimo doesn't execute inline scripts from plain `text/html` outputs. All source modes work.\n\nThe managed iframe mode uses the installed notebook iframe helper when\navailable; otherwise it falls back to `srcdoc`.\n\n```bash\nuv run marimo edit examples/demo.py                                   # edit the demo\nuv run marimo run examples/demo.py --headless --port 2718 --no-token  # read-only preview\n```\n\n## ⏳ Large Graphs\n\nBrowser rendering is asynchronous relative to notebook cell execution: a cell\ncan finish before the browser has loaded Graphviz WASM and produced the SVG.\nBy default, `easydot` renders on the output iframe's main thread and shows an\nin-progress indicator while the graph is rendering. You can opt into Web Worker\nrendering for large graphs.\n\n```python\neasydot.render(dot, worker=False)   # default: render on the output iframe's main thread\neasydot.render(dot, worker=\"auto\")  # try a worker, visibly fall back if unavailable\neasydot.render(dot, worker=True)    # require a worker; no main-thread fallback\n```\n\nIf worker rendering is unavailable and `worker=\"auto\"` is used, `easydot` shows\na warning before falling back to main-thread rendering. Large graphs may freeze\nthat output iframe until Graphviz finishes in fallback mode.\n\n## 🔌 Library Integration\n\nFor libraries that generate their own HTML, use the lower-level asset API:\n\n```python\nfrom easydot import asset_urls\n\njs_url = asset_urls()[\"js\"]\n```\n\n```js\nconst mod = await import(jsUrl);\nconst graphviz = await mod.Graphviz.load();\nconst svg = graphviz.layout(\"digraph { A -\u003e B }\", \"svg\", \"dot\");\n```\n\n\u003e Need server-side rendering to files? Use `easydot.to_string(..., backend=\"wasm\")`\n\u003e or `easydot.to_string(..., backend=\"native\")`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eRuntime model\u003c/b\u003e\u003c/summary\u003e\n\nThe asset server is intentionally narrow:\n\n- Binds only to `127.0.0.1`\n- OS-assigned ephemeral port\n- Serves only known packaged files (no directory browsing)\n- Long-lived cache headers\n- Shuts down automatically when the Python process exits\n\n\u003c/details\u003e\n\n## 📜 License\n\n| Component              | License                                                                                                                                      |\n| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |\n| `easydot` Python code  | [BSD-3-Clause](LICENSE)                                                                                                                      |\n| Vendored Graphviz WASM | Apache-2.0, from [`@hpcc-js/wasm-graphviz`](https://www.npmjs.com/package/@hpcc-js/wasm-graphviz). Pinned version in `src/easydot/_version.py` |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpablormier%2Feasydot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpablormier%2Feasydot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpablormier%2Feasydot/lists"}