# Getting started with Garphield > Open a graph in the workbench, start from Python or R, or add the renderer to another product. Garphield is a workbench for exploring, analysing, and presenting networks. It runs locally in the browser and comes with bindings for Python & R. Garphield can also be embedded on any website. ## The Three Laws of Garphield [Section titled “The Three Laws of Garphield”](#the-three-laws-of-garphield) 1. **No distracting layout animations.** Garphield builds the best graph it can, as fast as possible. 2. **Agent-ready.** Anything you can do, agents can do too. Don’t want to use the UI? That’s fine. 3. **No bad views.** Garphield protects you from the hairball. We’re working towards a future with no bad views. If you see something you don’t like, report it and we’ll work hard to prevent it in the future. [Open Garphield](/) or follow the [Quick start](/docs/getting-started/quick-start/). ## Automation [Section titled “Automation”](#automation) Choose the surface that matches the work you need to do: | Surface | Capability boundary | | ------------------------ | -------------------------------------------------------------------------------------------- | | HTTP-only agents | Read documentation, LLM files, the command schema, and fetch graph files by URL. | | Python and R | Validate, convert, fingerprint, and save `.gph` project documents without mounting a view. | | Mounted browser renderer | `window.garphield`, WebMCP, embeds, and notebook widgets require a mounted browser renderer. | | Playwright | Automates the mounted renderer in a headless browser; it is not browser-free. | Point your agent at the concise Garphield documentation index: ```plaintext https://garphield.com/llms.txt ``` The [complete manual](https://garphield.com/llms-full.txt) contains every documentation page and a build-time snapshot of the automation command schema. Then ask for what you need, for example: * Load this CSV and show me the most connected nodes. * Build a graph from the entities on this page and run community detection. * Download a PNG of this network for my presentation. Projects can be validated, saved, fingerprinted, and turned into complete Garphield links without opening a browser. Analysis, layout, visual inspection, and PNG export run in the live workbench or notebook renderer. See the [automation API](/docs/reference/automation-api/) to choose the right surface. ## Integrations [Section titled “Integrations”](#integrations) Garphield provides bindings for all environments. * [![](/docs/integrations/jupyter.svg)Jupyter](/docs/python/notebooks/ "Jupyter") * [![](/docs/integrations/marimo.svg)marimo](/docs/python/notebooks/ "marimo") * [![](/docs/integrations/snowflake.svg)Snowpark](/docs/python/snowpark/ "Snowpark") * [![](/docs/integrations/pandas.svg)pandas](/docs/python/pandas-and-networkx/ "pandas") * [![](/docs/integrations/networkx.svg)NetworkX](/docs/python/pandas-and-networkx/ "NetworkX") * [![](/docs/integrations/r.svg)R](/docs/r/ "R") * [![](/docs/integrations/raphtory.svg)Raphtory](/docs/raphtory/ "Raphtory") ## Supported file types [Section titled “Supported file types”](#supported-file-types) Garphield imports common graph and table formats. Here is the smallest useful example of each: | Format | Extension | Tiny example | | ----------------- | ---------------- | ----------------------------------- | | Garphield project | `.gph` | A saved Garphield workspace | | GEXF | `.gexf` | `…` | | GraphML | `.graphml` | `…` | | GML | `.gml` | `graph [ node [ id 0 ] ]` | | Graphviz DOT | `.dot`, `.gv` | `graph { a -- b }` | | Node-link JSON | `.json` | `{"nodes": […], "edges": […]}` | | CSV or TSV | `.csv`, `.tsv` | `source,target` | | Edge list | `.edges`, `.txt` | `a b` | | Matrix Market | `.mtx` | `%%MatrixMarket matrix coordinate…` | See [Supported file formats](/docs/reference/file-formats/) for details. ## Contact [Section titled “Contact”](#contact) Questions, ideas, or something not working? Email . --- # Garphield graph concepts > Graph data, the source catalogue, and how Garphield composes analysis and presentation. Garphield keeps graph data separate from the sources used to analyse, filter, and present it. Sources can be combined without replacing the underlying data. ## Graph data [Section titled “Graph data”](#graph-data) Graph data contains the network: nodes, links, direction, keys, weights, and typed attributes. Garphield accepts graph files, tables, URLs, generated networks, and projects from Python or R. Loading new data replaces the open network. A network can be directed or undirected, weighted, a multigraph, or contain self-loops. See [Load data](/docs/guides/load-data/) and [Supported file formats](/docs/reference/file-formats/). ## The source catalogue [Section titled “The source catalogue”](#the-source-catalogue) The source catalogue is everything Garphield can use to describe or change a network view. It combines: * underlying node and link data; * network science algorithms such as Degree, Betweenness, and Louvain; * application context such as the current selection and saved sets; * transformations produced earlier in the pipeline. Each source has a target—node, link, or graph—and a result type such as a number, category, or set. That lets Garphield show which visual channels, filters, and transformations can use it. See [Sources and algorithms](/docs/reference/sources-and-algorithms/). ## Compose sources [Section titled “Compose sources”](#compose-sources) Bind a source to size, colour, labels, or another visual channel. Use the same source in a filter, or feed a transformation into another algorithm. For example, Louvain communities can colour nodes, define a filter, and become saved sets without rerunning a separate workflow for each use. The pipeline stays reversible. Change an input, remove a step, or return to an earlier entry in History. See [Explore and analyze](/docs/guides/explore/) and [Visual channels](/docs/reference/visual-channels/). ## Projects, URLs, and exports [Section titled “Projects, URLs, and exports”](#projects-urls-and-exports) A `.gph` project stores the network and workspace: positions, bindings, filters, history, sets, annotations, and story steps. A Garphield URL stores a shareable view for supported browser data. Data exports contain the network; PNG export contains the presentation. See [Save, share, and export](/docs/guides/save-share-export/), [Project model](/docs/reference/project-model/), or [The `.gph` document](/docs/embed/document/). --- # Quick start with Garphield > Open a network, run browser analytics, inspect the data, and export your work. This page walks through three ways to get started with Garphield: on the main site, from Python or from R. ## Start in the workbench [Section titled “Start in the workbench”](#start-in-the-workbench) [Garphield](/) is available to anyone. 1. **Choose a network** Start with a pre-bundled network, choose one from the library, or bring your own files. 2. **Let the network load** Garphield loads the network as soon as possible. New graphs use **Degree** for node size and **Louvain** for colour up to 10,000 nodes and 100,000 edges. Larger graphs start with neutral colours; choose a source in **Color** to analyse them. The right panel shows the top five degree nodes and the matching community groups. 3. **Find a node** Press `/`, type a node label, then press `Enter`. 4. **Run network analytics** Analytics run in your browser. Click **Degree** and change it to **Betweenness** to compare the result. Open **Explore** for the full list, then drag a source into a visual channel to update the network. 5. **Use History** Open **History** in the sidebar. Every change can be reversed. 6. **Inspect the node and link tables** Toggle the table from the top right. Switch between **Nodes** and **Links**, sort a column by clicking its header, or edit details inline. 7. **Export your work** Export the current view as a PNG or `.gph` project. Continue with the [workspace tour](/docs/getting-started/workspace/) or [open your own data](/docs/guides/load-data/). ## Start from Python [Section titled “Start from Python”](#start-from-python) ```sh pip install 'garphield[networkx]' ``` Then display a graph in a notebook: ```python import garphield as gph import networkx as nx graph = nx.karate_club_graph() view = gph.show( graph, node_color="club", node_size=graph.degree, node_label=str, ) ``` `show()` returns a `GraphView`. Later cells can select nodes, change visual bindings, or read the edited graph back to NetworkX. Use **Open in Garphield** for the full workbench; **Return to notebook** sends the result back. Read the [Python guide](/docs/python/) for pandas, NetworkX, Raphtory, project files, and the notebook handoff. ## Start from R [Section titled “Start from R”](#start-from-r) Install `garphieldr`, then open an `igraph` network: ```r install.packages("garphieldr") library(garphieldr) library(igraph) project <- garphield_project(make_ring(12)) garphield(project) ``` The same `.gph` project moves between R, Python, and the browser workbench. Read the [R guide](/docs/r/) for data frames, RStudio, Quarto, and Shiny. ## Embed Garphield [Section titled “Embed Garphield”](#embed-garphield) Embed Garphield networks in another page with an iframe or the JavaScript driver. The host page can keep its own navigation, filters, and detail views. ```sh pnpm add garphield ``` ```ts import { createGarphield } from "garphield"; const frame = document.querySelector("#network"); if (!frame) throw new Error("Missing graph iframe"); const graph = createGarphield(frame); await graph.ready(); await graph.setDocument(project); ``` Start with the [embed guide](/docs/embed/) for both embed options, events, and host configuration. ## Read next [Section titled “Read next”](#read-next) * [Workspace tour](/docs/getting-started/workspace/) - full walkthrough of the Garphield interface. * [Supported file formats](/docs/reference/file-formats/) - files Garphield can open and export. --- # Garphield workspace tour > Full walkthrough of the Garphield interface. The canvas sits between the visual controls and the workspace panes. [![Garphield workspace with a network on the canvas](/docs/workspace-tour/workspace-overview.webp)![](/docs/workspace-tour/workspace-overview-dark.webp)![](/docs/workspace-tour/workspace-overview-seafoam.webp)](/docs/workspace-tour/workspace-overview.webp) The Garphield workspace. Open the image for a full-size view. ## Top strip [Section titled “Top strip”](#top-strip) * **File:** Open data, load a sample, save a project, or export. * **Undo and redo:** Move through recent changes. * **Table:** Toggle the node and link tables. * **Layout:** Choose Force, Levels, or a detected coordinate layout. Geo uses a map when the fields look geographic; other coordinates use the normal canvas. * **Labels:** Toggle node labels. * **Search:** Find a node or press `/` to focus the field. ### Levels [Section titled “Levels”](#levels) Levels arranges a graph in rows. Directed graphs follow edge direction where possible; cycles require some upward edges. Automatic mode compares layered placements for crossings, edges through nodes, and spacing after fitting the canvas. Select one or more nodes, then click **Levels** to use them as roots. Every root starts on the top row. Rows follow shortest graph distance, ignoring edge direction; disconnected components get their own automatic roots. Clicking **Levels** with no selection returns to automatic layout. Root IDs are saved in shared views and projects. Loading another dataset clears them. Changing selection does not rearrange the graph until you click **Levels** again. Levels appears directly, without the Force layout animation. The command API also accepts explicit IDs: ```js window.garphield.run("layout.levels", { roots: '["a","b"]' }); const result = await window.garphield.run("layout.wait"); if (!result.ok) throw new Error(result.error.message); ``` Use `{ levelAttribute: "stage" }` to choose a property, `{ selectionAsRoots: true }` to use the current selection, or `{}` to return to automatic layout. Await `layout.wait` after each request before reading positions or exporting an image. When both options are supplied, explicit property values take precedence over root distances. Larger graphs use a bounded layout to keep interaction responsive. ### Coordinates [Section titled “Coordinates”](#coordinates) Coordinate layout is available only when the graph has a usable field pair. Garphield recognizes latitude/longitude names and formats, as well as original X/Y fields. Geographic evidence determines whether the layout uses a basemap; ambiguous X/Y pairs start on the plain canvas with a **Use geographic map** option. A unique pair is selected automatically. If several pairs are compatible, choose the fields and click **Apply**. Nodes without usable coordinates are omitted from that layout along with their incident edges; the controls report how many nodes were placed. Imported coordinates remain available after running Force or Levels and when saving a project. ## Visual controls [Section titled “Visual controls”](#visual-controls) * **Viz:** Add graph fields, algorithms, transforms, filters, and visual bindings. * **Style:** Set the base appearance of nodes, links, labels, and the canvas. Choose **Flat** for the original fill, **Lumen** for soft relief, **Sphere** for complete balls, **Prism** for facets, or **Sketchy** for a hand-drawn double contour. * **Audit:** Review graph statistics, layout measures, and accessibility checks. Click **Browser** to find a source to drag, or click the pluses for any visual channel. ## Canvas [Section titled “Canvas”](#canvas) Pan by dragging, zoom with a scroll wheel or touchpad. Any nodes that are dragged are persisted in the app. ## Selection toolbar [Section titled “Selection toolbar”](#selection-toolbar) VMLT * **Pointer (V):** Select and drag nodes. * **Marquee (M):** Drag a rectangular selection. * **Lasso (L):** Draw around nodes to select them. * **Path target (T):** Choose the other end of a path. * **Fit:** Fit the network to the canvas. * **Grow:** Add neighbouring nodes to the selection. * **Shrink:** Remove the selection boundary. * **Invert:** Select everything outside the selection. * **Annotate:** Add a note to the selected set. ## Minimap [Section titled “Minimap”](#minimap) The minimap shows the full network and the current camera area. Open **Groups** for a summary of communities or other computed groups. ## Table [Section titled “Table”](#table) Toggle the table using the button in the top right. There are tabs for nodes and links. Click the headers to sort. Node and link details can be changed inline. [![Garphield workspace with the node table open](/docs/workspace-tour/workspace-table.webp)![](/docs/workspace-tour/workspace-table-dark.webp)![](/docs/workspace-tour/workspace-table-seafoam.webp)](/docs/workspace-tour/workspace-table.webp) The node table below the canvas. ## Sets [Section titled “Sets”](#sets) Save a selection as a named set. Sets can be selected again, used as a filter, or assigned to a visual channel. ## Audit [Section titled “Audit”](#audit) Audit collects graph summaries, drawing measures, and accessibility findings. Use it to check the network before you present or export it. ## Story [Section titled “Story”](#story) Story saves an ordered sequence of views. Each step can retain its camera, selection, annotations, and presentation state. ## History [Section titled “History”](#history) History records graph edits, visual changes, filters, sets, and story changes. Select an earlier entry to return to that state. ## Themes [Section titled “Themes”](#themes) Garphield includes Light, Graphite, and Seafoam. Choose **File → Theme** to switch between them. Seafoam takes its cue from industrial control rooms: a light mint-green canvas, solid green panels, and deep green lettering. Consistent outlines separate the controls, while full-width underlines identify active sidebar tabs and flat green fills mark selected tools. Search uses a lighter green field with dark text, and sidebar charts sit directly on their cards. Yellow warnings and red danger states keep their existing meanings. The documentation uses the same palette; use its theme button to cycle through the three themes. [![The Garphield workspace in Light, Graphite, and Seafoam themes](/docs/workspace-tour/workspace-themes.webp)![](/docs/workspace-tour/workspace-themes-dark.webp)![](/docs/workspace-tour/workspace-themes-seafoam.webp)](/docs/workspace-tour/workspace-themes.webp) Light, Graphite, and Seafoam use the same workspace controls. --- # Interactive NetworkX graphs: Python to workbench and back > Follow a NetworkX graph through a full visual analysis loop. This example follows the Les Misérables character network from NetworkX to a notebook, into the full workbench for editing, and back to the same Python session. ## 1. [Start with NetworkX](/docs/blog/networkx-to-interactive-notebook/) [Section titled “1. Start with NetworkX”](#1-start-with-networkx) Construct the graph, bind NetworkX data to the view, react to selection, and read the project back from the notebook renderer. ## 2. [Refine the view](/docs/blog/from-hairball-to-readable-map/) [Section titled “2. Refine the view”](#2-refine-the-view) Move into the full workbench, compare Full and Backbone views, and relax the layout without moving the part arranged by hand. ## 3. [Return to Python](/docs/blog/one-graph-two-workspaces/) [Section titled “3. Return to Python”](#3-return-to-python) Hand editing back to the notebook and use project fingerprints to distinguish an unchanged graph from an edited visual workspace. [Download the starting `.gph` project](/docs/demo/les-miserables-notebook.gph) or begin with [Start with NetworkX](/docs/blog/networkx-to-interactive-notebook/). --- # Make a dense network graph readable > Use the full workbench to simplify a dense drawing and protect hand-arranged nodes. *Part 2 of [Python to workbench and back](/docs/blog/).* [Download the notebook project](/docs/demo/les-miserables-notebook.gph) and open it in Garphield, or choose **Open in Garphield** from the notebook view. ## Switch between Full and Backbone [Section titled “Switch between Full and Backbone”](#switch-between-full-and-backbone) The Les Misérables graph has 77 nodes and 254 relationships. **Backbone** keeps the nodes and draws a structural edge set; **Full** restores every relationship. ![Garphield showing the Les Misérables network in Backbone view](/docs/blog/garphield-python-series/web-overview.webp) Backbone reduces edge competition while the complete graph remains available. The switch changes the drawing, not the project. It does not alter exports or graph fingerprints. ```js const view = window.garphield.view(); console.log(view.detail, view.rendered, view.hiddenEdges); ``` ## Protect an arrangement [Section titled “Protect an arrangement”](#protect-an-arrangement) Select the nodes you have placed by hand, switch to Force, and relax everything around them: ```js window.garphield.run("layout.set", { mode: "force" }); window.garphield.run("layout.relax", { hold: "selection" }); ``` Use `hold: "others"` to tidy only the selection instead. ![Garphield after a force relaxation that keeps a selected cluster fixed](/docs/blog/garphield-python-series/web-relax-selection.webp) The selected arrangement stays fixed while the surrounding graph settles. Save the project or choose **Return to notebook**. The final part, [Return to Python](/docs/blog/one-graph-two-workspaces/), compares what came back. --- # Interactive NetworkX graph visualization in Jupyter > Open a NetworkX graph in a live notebook view and bring the visual work back to Python. *Part 1 of [Python to workbench and back](/docs/blog/).* NetworkX supplies the graph and initial calculations. Garphield supplies the interactive view. ## Build the graph [Section titled “Build the graph”](#build-the-graph) ```sh uv pip install 'garphield[networkx]' ``` ```python import garphield as gph import networkx as nx graph = nx.les_miserables_graph() communities = nx.community.greedy_modularity_communities(graph, weight="weight") view = gph.show( graph, node_size=graph.degree, node_color=communities, node_label=str, layout="force", height=640, ) ``` ![JupyterLab showing a live Garphield network view beside the notebook files](/docs/blog/garphield-python-series/notebook-networkx.webp) NetworkX degree controls size; the community partition controls colour. ## Change the view from Python [Section titled “Change the view from Python”](#change-the-view-from-python) The same `GraphView` accepts later changes: ```python view.bind("node_color", gph.algorithm("louvain", resolution=1.1)) view.fit() ``` Attribute names, mappings, degree views, sets, partitions, and callables can all become visual bindings. ![Les Misérables network in JupyterLab, coloured by community and sized by degree](/docs/blog/garphield-python-series/notebook-styled.webp) The view stays beside the code that constructed the graph. ## React to selection [Section titled “React to selection”](#react-to-selection) ```python def report_selection(nodes): print(f"Selected: {nodes}") unsubscribe = view.on_selection(report_selection) ``` Use the callback to join selected identities to another table, inspect a model, or record a decision in the notebook. ## Read the project back [Section titled “Read the project back”](#read-the-project-back) ```python edited = view.to_project() edited_graph = view.to_networkx() edited.save("les-miserables-notebook.gph") ``` The project carries graph data and the visual work. Continue with [Refine the view](/docs/blog/from-hairball-to-readable-map/) to open it in the full workbench. --- # Edit a NetworkX graph visually and return it to Python > Hand a visual project back to the notebook and check what changed. *Part 3 of [Python to workbench and back](/docs/blog/).* The notebook and browser share one editing handoff. Choose **Open in Garphield** to move the current project to the workbench; choose **Return to notebook** to move the edited project back. ## Open the workbench [Section titled “Open the workbench”](#open-the-workbench) ```python import garphield as gph import networkx as nx graph = nx.les_miserables_graph() view = gph.show( graph, node_size=graph.degree, node_color=nx.community.greedy_modularity_communities(graph), node_label=str, layout="force", ) ``` Choose **Open in Garphield**. The notebook becomes a passive view while the browser owns editing. ![Garphield notebook view with the control that opens the project in the full workbench](/docs/blog/garphield-python-series/notebook-styled.webp) The project moves to the workbench; the notebook becomes a passive view. ## Return the edited project [Section titled “Return the edited project”](#return-the-edited-project) Choose **Return to notebook** in the browser, then continue with the same view: ```python view.status # 'ready' returned = view.to_project() returned_graph = view.to_networkx() returned.save("les-miserables-refined.gph") ``` ## Check what changed [Section titled “Check what changed”](#check-what-changed) ```python before = gph.Project.from_networkx(graph) after = view.to_project() { "same_graph": before.semantic_fingerprint() == after.semantic_fingerprint(), "same_workspace": before.project_fingerprint() == after.project_fingerprint(), } # {'same_graph': True, 'same_workspace': False} ``` ![Notebook output showing that graph data stayed the same while the project changed](/docs/blog/garphield-python-series/round-trip-fingerprints.webp) The graph is unchanged; the project records the visual decisions made in Garphield. The semantic fingerprint covers graph data. The project fingerprint also covers the view, history, sets, and story. --- # Interactive igraph networks in R, Quarto and Shiny > Build a project from igraph or data frames, then use it in Quarto or Shiny. Start with an `igraph` object: ```r library(garphieldr) library(igraph) network <- make_ring(12) project <- garphield_project(network) garphield(project) ``` `garphield_project()` creates a `.gph` project. `garphield()` displays its graph and saved view in an `htmlwidget`. ![RStudio window with igraph code beside a Garphield network](/docs/blog/garphieldr/rstudio-graph.webp) An igraph object displayed in the RStudio Viewer. ## Save the project [Section titled “Save the project”](#save-the-project) ```r write_gph(project, "network.gph") restored <- read_gph("network.gph") garphield(restored) ``` The file carries the graph and its visual workspace. The browser workbench and Python package open the same document. ## Start from tables [Section titled “Start from tables”](#start-from-tables) ```r nodes <- data.frame( id = c("Ada", "Grace", "Edsger", "Barbara"), group = c("language", "language", "algorithms", "systems") ) edges <- data.frame( source = c("Ada", "Grace", "Edsger", "Barbara"), target = c("Grace", "Edsger", "Barbara", "Ada"), relation = c("influenced", "influenced", "worked with", "influenced") ) project <- garphield_project(nodes = nodes, edges = edges) garphield(project) ``` `source`, `target`, and `id` define the topology. Other columns remain available as node and edge attributes. ## Put it in Quarto [Section titled “Put it in Quarto”](#put-it-in-quarto) Use the same call inside a code chunk: ````markdown ```{r} library(garphieldr) library(igraph) project <- garphield_project(make_ring(12)) garphield(project) ``` ```` ![Garphield embed showing a labelled network with a minimap](/docs/blog/garphieldr/graph-explorer.webp) The project rendered inside an HTML document. ## Update it from Shiny [Section titled “Update it from Shiny”](#update-it-from-shiny) Render once, then use a proxy for later changes: ```r server <- function(input, output, session) { project <- garphield_project(make_ring(12)) output$network <- renderGarphield(project) output$clicked <- renderPrint(input$network_node_click) observeEvent(input$theme, { garphieldProxyRun( garphieldProxy("network", session), "setTheme", list(colorScheme = input$theme) ) }) } ``` ![Shiny preview with controls beside a Garphield graph](/docs/blog/garphieldr/shiny-proxy.webp) A proxy changes the mounted graph without rebuilding the page. Continue with the [R overview](/docs/r/), [RStudio and Quarto](/docs/r/rstudio-and-quarto/), or [Shiny](/docs/r/shiny/) guide. --- # Embed Garphield in your application > Add an interactive Garphield network with HTML alone or control it from JavaScript. A plain embed needs only HTML. Interactive host control uses the JavaScript driver. ## Plain iframe [Section titled “Plain iframe”](#plain-iframe) The easiest way to integrate Garphield into your app is to use a plain iframe. Set the `file` parameter to a public `.gph` project: ```html ``` The file server must allow cross-origin browser requests. A same-site project can use a relative URL. ## JavaScript control [Section titled “JavaScript control”](#javascript-control) Use the browser driver when the host page needs to load projects, change the view, or respond to network events. ```sh pnpm add garphield ``` ```html ``` ```ts import { createGarphield } from "garphield"; const frame = document.querySelector("#network"); if (!frame) throw new Error("Missing graph iframe"); const graph = createGarphield(frame); await graph.ready(); await graph.setTheme({ colorScheme: "dark" }); await graph.setDocument(project); const stop = graph.on("nodeClick", ({ nodeId }) => { detailPanel.show(nodeId); }); // Before removing the iframe: stop(); graph.destroy(); ``` Pass `project` as a parsed [`.gph` document](/docs/embed/document/), not a JSON string. ## Choose UI components [Section titled “Choose UI components”](#choose-ui-components) The default embed includes the interaction toolbar and minimap. Set `chrome` to keep one or both: ```text /embed?chrome=minimap /embed?chrome=toolbar /embed?chrome=minimap,toolbar ``` Unknown component names are ignored. ## Choose a fixed theme [Section titled “Choose a fixed theme”](#choose-a-fixed-theme) Set an embed-level theme in the URL when the renderer should keep that theme even if the loaded document saved a different one: ```text /embed?theme=light /embed?theme=dark /embed?theme=seafoam ``` Omit `theme` when the saved document or `setTheme()` driver call should control the appearance. ## Shape the live view [Section titled “Shape the live view”](#shape-the-live-view) ```ts await graph.setBindingEnabled("nodeLabel", false); await graph.setEdgeFilter({ field: "weight", min: 2 }); await graph.goToStep(0); ``` Listen for `stepChange` to keep host navigation in sync with a saved Story. ## Driver methods [Section titled “Driver methods”](#driver-methods) | Method | Purpose | | ---------------------------------------- | -------------------------------------------------- | | `ready()` | Wait for the renderer. | | `setDocument(project)` | Replace the network and view. | | `setTheme({ colorScheme })` | Set Light, Graphite, or Seafoam. | | `setEdgeFilter({ field, min })` | Hide links below a value. | | `setBindingEnabled(channel, enabled)` | Toggle a visual binding. | | `goToStep()`, `nextStep()`, `prevStep()` | Control a saved Story. | | `run(command, params)` | Run a granted workbench command. | | `on(event, handler)` | Listen for node clicks, Story changes, and errors. | | `destroy()` | Remove listeners and reject pending calls. | Calls and the initial ready wait use a 10-second timeout by default. ## Use another deployment [Section titled “Use another deployment”](#use-another-deployment) `embedUrl` is the renderer loaded into the iframe. `appUrl` is the full Garphield workbench opened by **Open full app**. Set both when Garphield runs on your own domain, behind an internal gateway, or in a private deployment. ```ts const graph = createGarphield(frame, { embedUrl: "https://graphs.example.com/embed", appUrl: "https://graphs.example.com/", timeoutMs: 20_000, }); ``` The deployment must allow the host to frame the embed route and grant the commands it uses. See [Security and self-hosting](/docs/embed/security/). --- # The .gph document > The graph and visual project accepted by setDocument(). `setDocument()` accepts the same `.gph` project used by the workbench and Python package. Pass the parsed object: ```ts const project = await fetch("/network.gph").then((response) => response.json()); await graph.setDocument(project); ``` ## Project shape [Section titled “Project shape”](#project-shape) ```ts interface Gph { info: { version: 1; name?: string; created?: string; }; datasets: Array<{ id: string; graph: NodeLinkData; }>; config: { version: number; layout?: "force" | "levels" | "geo"; bindings?: Binding[]; filterStack?: Filter[]; [key: string]: unknown; }; } ``` Older projects may contain `"quality"`; the loader accepts it as a compatibility alias for `"force"`, but new projects should emit `"force"`. The project may also contain positions, saved sets, history, and a story. The embed applies the same validated project-loading path as the workbench. ## Node-link data [Section titled “Node-link data”](#node-link-data) ```json { "directed": true, "multigraph": false, "graph": { "title": "Dependencies" }, "nodes": [ { "id": "parser", "label": "Parser", "team": "core" }, { "id": "renderer", "label": "Renderer", "team": "visuals" } ], "links": [ { "source": "parser", "target": "renderer", "weight": 2 } ] } ``` Node and edge attributes become fields in the visual source catalogue. ## Visual configuration [Section titled “Visual configuration”](#visual-configuration) Bindings connect a source to a visual channel: ```json { "version": 1, "layout": "force", "bindings": [ { "channel": "color", "source": { "kind": "field", "id": "field:team" }, "resultType": "cat" }, { "channel": "size", "source": { "kind": "algorithm", "id": "degree" }, "resultType": "num" } ] } ``` Build the document in the workbench, with `Project.from_pandas()` or `Project.from_networkx()`, or against the published [GPH schema](/schemas/gph.schema.json). See the [Binding grammar](/docs/reference/binding-grammar/) for every channel, result type, source kind, and the `field:` id rule. --- # Events and errors > Keep host UI in sync with node clicks, story changes, and renderer failures. ## Node clicks [Section titled “Node clicks”](#node-clicks) ```ts const stop = graph.on("nodeClick", ({ nodeId }) => { detailPanel.show(nodeId); }); ``` Node ids are strings at the browser-driver boundary. ## Story changes [Section titled “Story changes”](#story-changes) ```ts graph.on("stepChange", ({ index, title }) => { chapterNav.select(index, title); }); ``` The event fires when the current story step changes through the host or the embedded player. ## Renderer errors [Section titled “Renderer errors”](#renderer-errors) ```ts graph.on("error", ({ message }) => { showGraphError(message); }); ``` Method calls reject when a project or parameter is invalid, a command is not granted, or the renderer does not reply before the timeout. ```ts try { await graph.setDocument(candidate); } catch (error) { console.error("Garphield rejected the project", error); } ``` ## Cleanup [Section titled “Cleanup”](#cleanup) Keep the unsubscribe functions returned by `on()`. Call them, then call `destroy()` before permanently removing the iframe. --- # Security and self-hosting > Allow trusted hosts to frame a Garphield renderer and grant the commands they need. Configure framing, messaging, and command access separately. Use the same exact host origins across all three layers. ## Allow host origins [Section titled “Allow host origins”](#allow-host-origins) ```dotenv VITE_PUBLIC_EMBED_ORIGINS=https://product.example,https://staging.example ``` Use origins only—scheme, hostname, and optional port—with no path. The renderer always accepts its own origin for same-site integrations. ## Allow framing [Section titled “Allow framing”](#allow-framing) Serve the embed route with a matching `frame-ancestors` policy: ```text Content-Security-Policy: frame-ancestors 'self' https://product.example ``` Keep the full workbench unframeable and scope this policy to `/embed` and `/embed/*`. The host page may also need: ```text Content-Security-Policy: frame-src https://garphield.com ``` ## Grant commands [Section titled “Grant commands”](#grant-commands) The default embed surface covers project loading, themes, edge filters, binding visibility, and story navigation. Set an origin-specific command list when a host also needs registry commands: ```dotenv VITE_PUBLIC_EMBED_CAPABILITIES={"https://product.example":["setDocument","setTheme","setEdgeFilter","setBindingEnabled","storyboard.next","storyboard.prev","storyboard.goto","selection.select","camera.frame"]} ``` A configured list replaces the default for that origin. Keep the grant as small as the integration allows. ## Development [Section titled “Development”](#development) `http://localhost:3000` and `http://localhost:4321` are different origins. Use the development host’s exact origin in the renderer allowlist and command grant, and pass the renderer’s absolute URL to `createGarphield()`. The driver and renderer both use exact `postMessage` origins. Requests with an unknown method, invalid version, untrusted origin, or missing capability are refused. --- # Audit and present > Check the network, add annotations, and build a guided sequence. ## Audit the network [Section titled “Audit the network”](#audit-the-network) Open **Audit** for graph summaries and accessibility checks. Compare visual patterns with the structural statistics before presenting them as findings. [![Garphield Audit panel beside the network](/docs/workbench/audit.webp)![](/docs/workbench/audit-dark.webp)![](/docs/workbench/audit-seafoam.webp)](/docs/workbench/audit.webp) Graph summaries and checks in Audit. The accessibility audit flags colour-only encoding, missing text alternatives, motion, and reproducibility issues. Pair it with keyboard and screen-reader testing for a published result. ## Add annotations [Section titled “Add annotations”](#add-annotations) Select nodes and choose the annotation action from the selection toolbar. The annotation stays with its saved set in the project. ## Build a story [Section titled “Build a story”](#build-a-story) Open **Story** and capture the current view. A step can retain the camera, selection, bindings, filters, annotations, and panel state. Add titles, reorder steps, and play the sequence from the canvas. [![Garphield Story panel with a captured step](/docs/workbench/story.webp)![](/docs/workbench/story-dark.webp)![](/docs/workbench/story-seafoam.webp)](/docs/workbench/story.webp) A titled Story step and its saved view. Save a `.gph` project to keep the editable story. Export a PNG for a static figure. See [Save, share, and export](/docs/guides/save-share-export/). --- # Explore and analyze graphs in Garphield > Find nodes, save sets, map sources to the view, and run network analysis. ## Navigate and find nodes [Section titled “Navigate and find nodes”](#navigate-and-find-nodes) Drag the canvas to pan. Use a wheel, touchpad, or pinch gesture to zoom. Press `/` to search by node label or id. Use pointer, marquee, or lasso selection. To inspect a route, select exactly one source node, choose **Pick path target** in the bottom control bar, then click an actual target node. Garphield emphasizes the exact shortest path, following link direction on directed networks. Clicking empty canvas or a node with no directed route leaves target mode active so you can try again. Choose the **Microscope** button immediately to its right to turn on the fisheye lens. Move the pointer to magnify local structure without losing the surrounding network, then click to pin it. Clicking empty canvas moves the lens to that location with a short glide; clicking a node glides to and centers exactly on the node. A pinned lens shows a subtle inner grab ring: drag that ring past its short resistance to release the lens back to pointer-following. The center stays available for moving the network or selected nodes. Press `Esc` to release it without dragging; touch layouts also show an unpin button at the lens’s upper right. Hover the outer boundary to reveal resize handles, then drag that boundary with the primary button to resize it. Right-drag up or down inside the circle to change magnification. Wheel, touchpad, and pinch gestures continue to zoom the canvas everywhere, including inside the lens. For keyboard access, focus the **Microscope** button and use the arrow keys to adjust magnification. The glide is disabled when reduced motion is requested. ## Temporarily hide nodes [Section titled “Temporarily hide nodes”](#temporarily-hide-nodes) Select one or more nodes, then choose **Hide** in the selection toolbar. On a phone, tap a node and choose **Hide node** in its **Info** details. On desktop, you can also right-click a node and choose **Hide node**. Right-click a selected node to hide the whole selection. Choose **Keep only selected** to hide all other visible nodes; this creates a separate group and preserves existing hides and filters. The action is also available in the phone’s Info details. The **hidden** count on the canvas opens the hidden groups in **Sets** (or the phone’s **Info** sheet). Hides within three seconds join one group and one undo step. A pause, another edit, a restore, or a pinned history point starts a new group. Rename a group and add a note to remember why you hid it. A notification reports how many nodes changed and offers **Undo**. Search within a group to restore individual nodes, check several and choose **Restore selected**, restore a whole group with its eye button, or choose **Restore all**. Incident edges return too, subject to active filters. Restored groups keep their membership so you can hide them again, or drag them onto a visual channel. Deleting a group also restores its nodes. Hide and restore support **Undo/Redo** and history navigation. Groups and their visibility are preserved in saved/shared views and `.gph` sessions. Loading a new graph clears the previous graph’s sets. Hiding keeps source data and node positions intact. Use **Zoom to fit** after moving or hiding nodes to frame the remaining visible nodes at their current positions. Fit preserves rotation and can be undone without moving the nodes. Community detection, centrality, and graph statistics run on the visible graph: hidden nodes and hidden edges are excluded, while faded elements remain included. The app evaluates filter predicates before analyzing their output, so a degree filter remains stable when the visible graph’s degrees change. Automatic drawing simplifications such as backbone views do not change this analysis topology. ## Save sets [Section titled “Save sets”](#save-sets) Open **Sets** to name the current selection. A saved set can be selected again, used in a filter, assigned to a visual channel, or included in a Story step. ## Encode and filter [Section titled “Encode and filter”](#encode-and-filter) Open **Explore** in the **Viz** panel. Drag a source into a compatible visual channel. | Source result | Examples | Visual channels | | ------------- | ----------------------------------- | --------------------------------- | | Number | Degree, Betweenness, weight | Size, colour, link width, heatmap | | Category | Louvain, Leiden, text fields | Colour, shape, contour | | Set | Selection, saved set, path, bridges | Border and set emphasis | Use **Style** for base node, link, label, and canvas appearance. Node surfaces can be **Flat**, softly lit with **Lumen**, fully round with **Sphere**, faceted with **Prism**, or hand-drawn with **Sketchy**. Sphere keeps node images and shades them as spherical textures; the other modes continue to respect the node’s data-driven fill. Filters accept numeric ranges, categories, dates, and sets. **Fade** retains context; **Hide** removes non-matching elements from the view. Filtering preserves the graph’s data, topology, and node positions. Share URLs preserve the complete visible style: node surface and size, node gap, link width and opacity, curvature, labels, link direction and gradient, theme, and the fisheye’s enabled state, magnification, and radius. The lens focus itself is transient and is not saved. ## Explore a network over time [Section titled “Explore a network over time”](#explore-a-network-over-time) Try **File → Data library → Collaboration over time**, a synthetic network with six weeks of dates. In **Viz**, use the **Filter** card above **Node marks** to choose **joined** and reveal nodes as they join, or **collaborated** to reveal edges as collaborations happen. The funnel adds another filter. **Browse** lists node and edge fields first. Date fields have a clock badge and can also be used as categories in visual channels. * The histogram shows how dates are distributed over time. Drag its upper handle to reveal values **as of** that instant. Drag the lower handle inward to select a date window; return it to the beginning to remove the start bound. * Click the date readout below the histogram to edit exact UTC dates, then press **Enter** to apply each date. Both boundaries are inclusive. * Use previous/next to step through distinct timestamps, or **Play** to advance the end date. For large timelines, **1×** covers the selected date range in about 20 seconds, **5×** in about 4 seconds, and **20×** in about one second. Playback batches dates when needed; previous/next still visits every date. A selected start date stays fixed. Gaps without events do not slow playback. * The same button becomes **Pause** during playback. **Reset** returns to the earliest date, and **Loop** restarts from the start date at the end. Play at the end also replays from the start. Playback pauses when the tab is hidden, the filter is disabled or removed, the data changes, or a saved view is restored. Dates must be `YYYY-MM-DD` (UTC midnight) or an RFC3339 timestamp with an explicit timezone, such as `2026-01-05T12:30:00Z` or `2026-01-05T13:30:00+01:00`. Seconds and milliseconds are preserved in the displayed cursor when needed. Numeric epochs, locale dates, and timestamps without a timezone are not inferred. Date-only end values mean midnight; use the next date or an explicit time when you want to include events later in a day. Time filters hide excluded nodes and edges by default; the filter mode control can switch to fading them as context. Every nonempty value in a field must be a valid date for automatic detection. Empty values are reported and excluded by default. **Show undated nodes/edges** includes them as context. Undated nodes need a connection to dated nodes in the window; disconnected timeless components stay excluded. If an existing temporal field gains an invalid value, its filter becomes unavailable until the value is corrected; the graph is left unfiltered by that row. The match count describes this field’s date range, before other filters are combined. Node date filters also affect incident edges. Edge date filters exclude nodes without a connection in the window, so future nodes do not appear as orphans. Shared URLs, `.gph` files, and captured Story steps preserve both date bounds and the undated-value choice. Playback speed and looping are local controls and do not autoplay when opening a saved view. **Hide** date filters also narrow the topology used for community detection and statistics; **Fade** date filters leave it intact. ## Analyze [Section titled “Analyze”](#analyze) Garphield runs network algorithms in the browser. Common sources include: * Degree, Betweenness, Closeness, PageRank, Eigenvector, Harmonic, and Katz centrality; * Louvain, Leiden, label propagation, greedy modularity, and K-clique communities; * shortest paths, ego networks, reachability, K-core, bridges, and articulation points; and * link measures including Betweenness, Jaccard, and Adamic–Adar. Bind an algorithm result, filter with it, inspect its values in the table, or reuse it later in the pipeline. See [Sources and algorithms](/docs/reference/sources-and-algorithms/) for the full catalogue. ## Transform or generate a network [Section titled “Transform or generate a network”](#transform-or-generate-a-network) Transformations extract structures such as the largest component, a spanning tree, a K-core, or a disparity-filter backbone. Each transformation becomes a reversible pipeline step and can feed later algorithms. Choose **File → Generate** for reproducible random, small-world, scale-free, tree, grid, cycle, star, wheel, barbell, or bipartite networks. [![Garphield Explore catalogue and Sets panel beside a network](/docs/workbench/explore-analyze.webp)![](/docs/workbench/explore-analyze-dark.webp)![](/docs/workbench/explore-analyze-seafoam.webp)](/docs/workbench/explore-analyze.webp) Sources and saved sets can be reused across the pipeline. Use **History** to reverse or compare any selection, binding, filter, transformation, or generated network. ## Layout animation [Section titled “Layout animation”](#layout-animation) Asynchronous Force layouts reveal moving nodes by default. Connected nodes flock together, then decelerate into the completed layout over about 1.7 seconds. Links then grow into view over 0.6 seconds. Levels moves directly to its completed layout without this animation. Reduced-motion preferences also disable the animation. Cancelling a layout or loading another graph cancels its preview. For layouts above 5,000 nodes or 20,000 edges, a preparation indicator can appear before the animation begins. It clears while the animation is running. Graphs open in **Full** view. Backbone and Onion remain available as explicit choices, and saved views retain the chosen presentation. --- # Load graph data into Garphield > Open a network file, build a network from tables, or load a public URL. Use **File → Data library** for bundled networks or **File → Open** for your own files. Opening data replaces the network in the workspace. [![Garphield start screen with the sample library and file upload area](/docs/workbench/load-data.webp)![](/docs/workbench/load-data-dark.webp)![](/docs/workbench/load-data-seafoam.webp)](/docs/workbench/load-data.webp) Choose a bundled network or bring your own graph. ## Open a network file [Section titled “Open a network file”](#open-a-network-file) Garphield opens `.gph`, GEXF, GraphML, GML, Graphviz DOT, node-link JSON, compact JSON, graph6, sparse6, digraph6, Matrix Market, CSV, TSV, and text edge lists. A `.gph` project also restores the Garphield workspace. See [Supported file formats](/docs/reference/file-formats/) for format details. ## Open an edge list [Section titled “Open an edge list”](#open-an-edge-list) A table with `source` and `target` headers opens as an edge list. Header matching is case-insensitive. Other columns become link attributes. ```csv source,target,relationship,weight ada,grace,worked-with,3 grace,linus,influenced,1 ``` ## Open node and link tables [Section titled “Open node and link tables”](#open-node-and-link-tables) Select two CSV or TSV files together: * a link table with `source` and `target` columns; and * a node table with an `id` column, or another key matching the link endpoints. Garphield identifies the tables from their headers. Node columns become node attributes. A `label` or `name` column is used as the displayed label. ## Build a network from a flat table [Section titled “Build a network from a flat table”](#build-a-network-from-a-flat-table) If a CSV has no `source` and `target` pair, choose two columns in the construction dialog. Build a bipartite network between the columns or a folded network between values that share a context. ## Load a URL [Section titled “Load a URL”](#load-a-url) Pass a graph URL through the `file` parameter. Garphield accepts `http:`, `https:`, and `data:` URLs. Relative same-origin URLs work too. A `blob:` URL is browser-context-local: it works only in the browser context that created it, so it cannot be copied to another browser or reopened later. ```text https://garphield.com/?file=https%3A%2F%2Fexample.com%2Fnetwork.gexf ``` Cross-origin HTTP(S) servers must allow the browser request with CORS headers. Same-origin, `data:`, and same-context `blob:` sources do not need a cross-origin server request. Prefer HTTPS for hosted files: browsers normally block an `http:` file request from the production HTTPS site as mixed active content. When loading through the Automation API, await the observable `FileLoadResultV1` result rather than treating a resolved call or a UI toast as completion: ```js const result = await Promise.resolve( window.garphield.run("file.load", { url, format: "gexf" }), ); if (!result?.ok) throw new Error(result?.error?.message ?? "Load failed"); if (result.value.status === "construction-required") { throw new Error("This table needs an explicit graph construction recipe"); } ``` `status: "loaded"` means the graph has been replaced; use `layout.wait` if settled positions are required. Fetch, network, and CORS acquisition failures return `LOAD_FAILED` with `retryable: true`. Once bytes/string acquisition completes, decode, parse, schema, and format failures return `LOAD_FAILED` with `retryable: false`; the error message and URL details are preserved and the current graph remains in place. A superseded request returns `STALE_STATE` with `retryable: true` and does not show an obsolete toast. To diagnose a hosted URL in the same browser context as Garphield, probe it with browser fetch and inspect the response headers. A browser CORS preflight is an `OPTIONS` request caused by a non-safelisted method, a request header such as `Authorization`, or a non-safelisted content type. A normal `file.load` GET is usually a simple request, but it still needs `Access-Control-Allow-Origin`; the server must answer a preflight with an allowed origin and requested method/headers. ```js const probe = await fetch(url, { mode: "cors" }); if (!probe.ok) throw new Error(`HTTP ${probe.status}`); await probe.arrayBuffer(); ``` An optional header check is: ```bash curl -sS -D - -o /dev/null -H 'Origin: https://garphield.com' "$GRAPH_URL" ``` `Access-Control-Allow-Origin` must be `*` or `https://garphield.com`. A rejected preflight or missing ACAO makes the direct browser fetch reject or reveals the missing header. `file.load` returns `LOAD_FAILED` with the failure message and URL details and preserves the current graph; a fetch/CORS failure is retryable, while malformed content is not. A successful curl body fetch alone does not prove that a browser can read the response; the browser probe and the `file.load` error envelope are the useful signals. Garphield normally chooses a parser from the URL path: | URL suffix | Parser / `format` value | | ----------------------------------- | ------------------------------------------------------ | | `.dot`, `.gv` | `dot` | | `.gexf`, `.gexf.xml` | `gexf` | | `.graphml` | `graphml` | | `.gml` | `gml` | | `.json` or an unknown/absent suffix | JSON content sniffing; defaults to `nodeLink` | | `.compact.json`, `.gf.json` | `compactJson` | | `.csv`, `.tsv` | `csv` | | `.txt` | `csv`; use `format=edgeList` for whitespace edge lists | | `.edges`, `.edgelist`, `.el` | `edgeList` | | `.mtx` | `matrixMarket` | | `.g6` | `graph6` | | `.s6` | `sparse6` | | `.d6` | `digraph6` | | `.gph` | `gph` | For an extensionless or ambiguous URL, add an authoritative `format` query parameter. Accepted values are `dot`, `gexf`, `graphml`, `gml`, `nodeLink`, `compactJson`, `csv`, `edgeList`, `matrixMarket`, `graph6`, `sparse6`, `digraph6`, and `gph`. The hint overrides the path and the default parser. ### Load inline data (no hosting) [Section titled “Load inline data (no hosting)”](#load-inline-data-no-hosting) `file` accepts a `data:` URL, so a small graph can be embedded directly in a Garphield link. The browser resolves it client-side with no network request or CORS requirement. Use an accurate MIME type for the URI (`application/json`, `text/csv`, or `application/xml`, for example), but also pass `format`. Garphield’s MIME type does not select the parser: URL suffix detection and the explicit `format` hint do. An extensionless JSON URI happens to default to `nodeLink`; CSV, GraphML, GEXF, and other extensionless sources need their explicit format to dispatch reliably. Compact JSON is the shortest agent-friendly form when numeric array indices are acceptable. A node’s array position is its node ID; edges use those positions: ```json {"nodes":["Alice",{"label":"Bob","group":"staff"},"Carol"],"edges":[[0,1],[1,2,{"weight":2}]],"directed":true} ``` `directed` and `multigraph` default to `false`. Node objects and the optional third link-tuple object carry attributes. Use node-link JSON instead when stable external IDs, edge keys, or graph-level attributes matter. Base64 is the recommended inline form. Build the complete `data:` URI first, then percent-encode the entire URI because it is the value of the `file` query parameter. That second encoding turns base64’s `+`, `/`, and `=` into `%2B`, `%2F`, and `%3D`. ```text https://garphield.com/?file=&format=compactJson ``` Python: ```python import base64 import json import urllib.parse mini = json.dumps(graph, separators=(",", ":")) b64 = base64.b64encode(mini.encode()).decode() data_uri = "data:application/json;base64," + b64 url = ( "https://garphield.com/?file=" + urllib.parse.quote(data_uri, safe="") + "&format=compactJson" ) ``` JavaScript: ```js const mini = JSON.stringify(graph); const bytes = new TextEncoder().encode(mini); const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join(""); const dataUri = `data:application/json;base64,${btoa(binary)}`; const url = `https://garphield.com/?file=${encodeURIComponent(dataUri)}` + "&format=compactJson"; ``` The plaintext variant avoids base64 but is usually longer after JSON punctuation is escaped. Percent-encode the JSON inside the URI, then encode the complete URI again as the query value: ```python mini = json.dumps(graph, separators=(",", ":")) data_uri = "data:application/json," + urllib.parse.quote(mini, safe="") url = ( "https://garphield.com/?file=" + urllib.parse.quote(data_uri, safe="") + "&format=compactJson" ) ``` graph6-family values are already printable ASCII and should stay plaintext. They have no filename inside a `data:` URI, so always supply the authoritative hint—for example: ```text https://garphield.com/?file=data%3Atext%2Fplain%2CDQc&format=graph6 ``` graph6, sparse6, and digraph6 carry topology only. Garphield labels their nodes `0`, `1`, … and cannot recover names, attributes, positions, or styling that were never present in the source. There is no Garphield-specific size cap for `?file=data:...`, but browsers, address bars, chat clients, and other intermediaries impose different URL limits. Keep graph-only data URLs small. For a complete graph plus Garphield state, use the existing compressed `#p=` project-link encoding generated by the browser, Python, or R package. Current `z2.` carriers compact node-link endpoints to indices before zlib/DEFLATE and unpadded base64url encoding. Garphield still opens legacy `z1.` carriers. The carrier is capped at 262,144 encoded characters; above that limit, host or send a `.gph` project file. --- # Save, share, and export > Save a Garphield project, share a URL, or export network data and images. [![Garphield export dialog with project, network-data, and image formats](/docs/workbench/save-export.webp)![](/docs/workbench/save-export-dark.webp)![](/docs/workbench/save-export-seafoam.webp)](/docs/workbench/save-export.webp) Export a complete project, portable network data, or a PNG. ## Save a project [Section titled “Save a project”](#save-a-project) Choose **File → Save project** or press `⌘ S`. A `.gph` project stores the network, positions, bindings, filters, history, sets, annotations, and Story. ## Share a URL [Section titled “Share a URL”](#share-a-url) For bundled networks and public URL-backed data, copy the Garphield URL to share the layout, bindings, filters, selection, camera, annotations, and panel state. The selected node surface style is included, so Flat, Lumen, Sphere, Prism, or Sketchy reopens with the same appearance. Python and R can also encode a complete project into an offline `#p=` URL: ```python url = gph.share_url(project) ``` This link carries the graph and Garphield project state without uploading it. When saved positions are present, Garphield preserves them. Otherwise the workbench runs its normal selected layout after the link opens. Inline project links are limited to 262,144 characters; larger projects must currently travel as `.gph` files. Current `z2.` links replace repeated node IDs in link endpoints with compact indices before zlib compression; Garphield still reads existing `z1.` links. ## Export network data [Section titled “Export network data”](#export-network-data) Choose **File → Export** for GEXF, GraphML, GML, or NetworkX-compatible node-link JSON. Use GEXF to continue in Gephi. Use `.gph` to retain the full Garphield workspace. ## Export an image [Section titled “Export an image”](#export-an-image) Export to PNG to capture the current Garphield view. | Artifact | Carries | Suitable for | | ---------------------------------- | --------------------------------------------- | -------------------------------------------------------- | | `.gph` project | Network and Garphield workspace | Continuing in Garphield, Python, or R | | View-state Garphield URL | Supported browser view state | Sharing the same presentation for public or bundled data | | Offline `#p=` project URL | Complete project within the inline size limit | Sharing without a file host | | GEXF, GraphML, GML, node-link JSON | Portable network data and attributes | Importing into Gephi or another analysis tool | | PNG | Current Garphield view | Reports, papers, and slides | See [Project model](/docs/reference/project-model/) for the contents of each artifact. --- # Garphield for Python > Open pandas and NetworkX data in Garphield, work visually, and bring the result back to Python. The `garphield` package moves a graph into an interactive notebook view and returns the edited project or NetworkX graph to the same Python session. Garphield supports Python 3.10 through 3.14. ## Install [Section titled “Install”](#install) ```sh pip install 'garphield[networkx]' ``` Use `'garphield[tables]'` for pandas without NetworkX, or install both extras. ## From NetworkX [Section titled “From NetworkX”](#from-networkx) ```python import garphield as gph import networkx as nx graph = nx.karate_club_graph() view = gph.show( graph, node_color="club", node_size=graph.degree, node_label=str, ) ``` `show()` displays the graph and returns a synchronous `GraphView`. Move between the notebook view and ordinary Python without a separate event loop: ```python view.select([0, 1, 2]).fit() view.bind("node_color", gph.algorithm("louvain", resolution=1.1)) edited_graph = view.to_networkx() edited_project = view.to_project() ``` Bindings accept familiar Python values: | Python value | Example | Use | | ------------------- | ----------------------------------- | -------------------------------- | | Attribute name | `node_color="club"` | Bind an existing attribute. | | Mapping or view | `node_size=graph.degree` | Match values by node identity. | | Iterable | `node_size=centrality.values()` | Assign in graph iteration order. | | Set | `node_color=important` | Encode membership. | | Partition | `node_color=[group_a, group_b]` | Encode categories. | | Callable | `node_label=lambda node: str(node)` | Compute a value. | | Garphield algorithm | `gph.algorithm("louvain")` | Compute inside the view. | Pass positions and an initial layout directly: ```python view = gph.show(graph, pos=nx.spring_layout(graph), layout="force") ``` ## From pandas [Section titled “From pandas”](#from-pandas) An edge table is enough: ```python import pandas as pd import garphield as gph edges = pd.DataFrame( { "source": ["ada", "ada", "grace"], "target": ["grace", "alan", "alan"], "weight": [1.5, 0.5, 2.0], } ) view = gph.show(edges) tables = view.to_project().to_pandas() ``` Pass a node table when you have node attributes: ```python project = gph.Project.from_pandas( edges, nodes, source="from_id", target="to_id", node_id="person_id", ) ``` ## Round-trip guarantees [Section titled “Round-trip guarantees”](#round-trip-guarantees) The adapters preserve direction, multigraph state, edge keys, attributes, and typed identities. Values such as `True`, `1`, `1.0`, and `"1"` remain distinct. The adapter escapes reserved attribute names in the project manifest and restores them on the way back. ```python multi = nx.MultiDiGraph() multi.add_edge("ada", "grace", key="mentor", weight=1.5) restored = gph.Project.from_networkx(multi).to_networkx() assert restored.edges["ada", "grace", "mentor"]["weight"] == 1.5 ``` An encoding collision raises `IdentityCollisionError` instead of merging nodes. ## Open the full workbench [Section titled “Open the full workbench”](#open-the-full-workbench) Choose **Open in Garphield** in the notebook view for the full workbench. **Return to notebook** sends the edited project back to the same `GraphView`. ## Read next [Section titled “Read next”](#read-next) * [Notebooks](/docs/python/notebooks/) - control a live view, choose chrome, export HTML, and use the browser handoff. * [Raphtory](/docs/raphtory/) - visualize temporal Raphtory graphs. * [Snowpark](/docs/python/snowpark/) - materialize Snowpark tables into a project. * [Projects](/docs/python/projects/) - load, save, and fingerprint `.gph` files. * [API reference](/docs/python/reference/) - `GraphView`, codecs, transport, and errors. * [Tutorial: NetworkX round trip](/docs/blog/) - one graph from a notebook into the workbench and back. --- # Interactive graphs in Jupyter notebooks > Display a graph, control it from Python, and move the project into the full workbench and back. ## Open a view [Section titled “Open a view”](#open-a-view) ```python import garphield as gph view = gph.show(project, height=720) ``` The wheel includes the notebook renderer and selects the browser path for Jupyter, VS Code notebooks, JupyterHub, Colab, and marimo. Run interaction calls from a later cell, after the host has mounted the display. ## Drive the view [Section titled “Drive the view”](#drive-the-view) Methods use logical Python node identities: ```python view.select(["ada", "grace"]).fit() view.set_layout("force") view.bind("node_color", gph.algorithm("louvain", resolution=1.1)) # The same logical controls are available from Python. view.selection_mode("lasso").focus("ada") ``` `show()` returns immediately. Later `GraphView` calls wait for the renderer when needed, so ordinary notebook code does not need `await`. ## Chrome and HTML export [Section titled “Chrome and HTML export”](#chrome-and-html-export) Keep only the minimap or toolbar when the view sits inside a report: ```python view = gph.show(project, chrome=["minimap"]) ``` Use `chrome=["toolbar"]` for the toolbar alone, or include both names. The same option works with `ProjectWidget` and `Project.widget()`. Write a standalone interactive page from the same project: ```python gph.save_html(project, "graph.html", chrome=["minimap"]) ``` The live view can create the same reproducible artifacts as the full app: ```python analysis = view.export("analysis-bundle", "analysis.json") image = view.save_png("graph.png") ``` Artifact manifests report the semantic and project fingerprints, what was included, and any declared loss. Writes are atomic and refuse to replace an existing file unless `overwrite=True`. Large results remain an app/browser continuation because the temporary notebook transport has a 1 MiB ceiling. ## Read the project back [Section titled “Read the project back”](#read-the-project-back) ```python edited = view.to_project() edited.save("after.gph") graph = view.to_networkx() selected = view.get_selection() ``` Selection can also drive notebook code: ```python def report(nodes): print(f"{len(nodes)} selected") unsubscribe = view.on_selection(report) ``` ## Move into the full app and back [Section titled “Move into the full app and back”](#move-into-the-full-app-and-back) Choose **Open in Garphield** in the notebook view. Arrange nodes, change encodings, save sets, or build a story in the workbench; then choose **Return to notebook**. The same `GraphView` now exposes the returned project. While the browser owns editing, mutating calls raise `OwnershipError`. If its window is lost, `view.reclaim()` returns control to the notebook. ## Close the connection [Section titled “Close the connection”](#close-the-connection) ```python unsubscribe() view.close() ``` For raw async control, `Project.widget()` returns the lower-level `ProjectWidget` API. See the [API reference](/docs/python/reference/) for transport limits and errors. The [NetworkX round-trip tutorial](/docs/blog/) follows one graph from a notebook into the full workbench and back. --- # Projects > Load, validate, save, and compare Garphield .gph projects from Python. A `.gph` project holds the graph and the visual workspace around it: positions, encodings, filters, history, sets, and story. The R package reads and writes the same `.gph` format. See [`garphieldr`](/docs/r/projects/) when an analysis starts in R and continues in Garphield or Python. ## Load and save [Section titled “Load and save”](#load-and-save) ```python import garphield as gph project = gph.Project.load("project.gph") project.save("copy.gph") ``` Loading validates against the schema bundled with the package. Saving produces deterministic, readable JSON, so project files diff cleanly in version control. Use dictionaries when the project is already in memory: ```python project = gph.Project.from_dict(payload) payload = project.to_dict() ``` `Project` is immutable at its public boundary. Display a project to edit its view: ```python view = gph.show(project) edited = view.to_project() edited.save("project.gph") ``` Write an interactive HTML shell when the project needs to open outside the notebook: ```python project.save_html("graph.html", chrome=["minimap"]) ``` The HTML loads Garphield’s remote `/embed` renderer in an iframe. It needs network access and a host/browser context that permits framing; it is not an offline bundle. ## Command line [Section titled “Command line”](#command-line) Installing the Python package also installs the `garphield` command. CSV input requires the `tables` extra: ```sh pip install "garphield[tables]" garphield build edges.csv --source source --target target \ --node-size algorithm:degree --layout force -o network.gph garphield validate network.gph --json garphield sources network.gph garphield share network.gph ``` `build`, `validate`, `sources`, and `share` accept a CSV edge list or an existing `.gph`/`.json` project. CSV columns default to `source` and `target`; use `--directed` for directed edges. Binding flags accept a field name or `algorithm:`. Use `sources` to discover available IDs. Add `--dry-run` to `build` to validate and report the encoded size without writing the output file. Add `--json` to a subcommand for machine-readable success output on stdout and operation errors on stderr. Failed operations exit nonzero. `share` creates a URL locally; it does not upload the project. When the graph is too large for a URL, share the `.gph` file instead. The `render` subcommand is a placeholder and exits with an error. Use the browser workbench to export PNGs, or `Project.widget()`/`Project.save_html()` for an interactive view. ## Style a project without a browser [Section titled “Style a project without a browser”](#style-a-project-without-a-browser) `Project.from_networkx()` and `Project.from_pandas()` take visual bindings directly, so you can author a styled project in a code sandbox with no mounted view: ```python import garphield as gph project = gph.Project.from_networkx( graph, node_color="kind", # an existing attribute node_size=gph.algorithm("degree"), # a computed source node_label="name", layout="force", ) ``` The channel keywords are snake\_case (`node_color`, `node_size`, `node_label`, `edge_color`, `edge_width`) and map to the camelCase wire channels. From NetworkX a channel also accepts a mapping, a callable, or a partition (a list of node sets); from pandas, bind a column name, an `algorithm(...)` spec, `pos`, or `layout`, and add a column for anything per-row. Discover the bindable ids and their types before you bind, still headless: ```python for source in project.sources(): print(source.id, source.kind, source.target, source.result_type) ``` Bindings are validated when the project is built: an unknown channel, a missing field, an unknown algorithm, or a result type the channel does not accept raises `BindingError` in Python instead of leaving a dead binding that renders nothing. See the [Binding grammar](/docs/reference/binding-grammar/) for the full vocabulary. ## Create a complete project link offline [Section titled “Create a complete project link offline”](#create-a-complete-project-link-offline) ```python share_string = gph.share_string(project) url = gph.share_url(project) custom = gph.share_url(project, base="https://example.com/garphield") ``` These pure functions perform no network I/O. They compact repeated node IDs in link endpoints, serialize canonical JSON, and encode zlib plus unpadded base64url in `#p=z2.…`. Opening the URL preserves a complete saved embedding; if positions are absent, the live workbench runs Garphield’s normal selected layout. The browser still reads existing `z1.` carriers. `ProjectShareTooLargeError` is raised when the encoded carrier exceeds 262,144 characters. Share larger projects as `.gph` files. ## Compare graph and project identity [Section titled “Compare graph and project identity”](#compare-graph-and-project-identity) ```python project.semantic_fingerprint() project.project_fingerprint() ``` The semantic fingerprint covers graph data. The project fingerprint covers the whole document, including the view. Compare them to tell “same graph, different visual work” from “same project”. ```python before = gph.Project.load("before.gph") after = gph.Project.load("after.gph") same_graph = before.semantic_fingerprint() == after.semantic_fingerprint() same_project = before.project_fingerprint() == after.project_fingerprint() ``` Both use RFC 8785 canonical bytes; formatting in the saved JSON does not affect the result. See [Canonical bytes](/docs/python/reference/#canonical-bytes) for the exported functions. --- # Garphield Python API reference > GraphView methods, canonical bytes, identity codecs, transport settings, and errors. The public building blocks behind projects, adapters, and notebook views. ## Canonical bytes [Section titled “Canonical bytes”](#canonical-bytes) RFC 8785 (JCS) canonical bytes determine fingerprints, not the saved file. The package exports all three functions so you can reproduce them. ```python from garphield import ( canonicalize_jcs_v1, canonicalize_project_v1, canonicalize_semantic_graph_v1, ) ``` | Function | Covers | | ----------------------------------------- | -------------------------------------------------- | | `canonicalize_jcs_v1(value)` | Any JSON value. | | `canonicalize_project_v1(project)` | The whole document. Feeds `project_fingerprint()`. | | `canonicalize_semantic_graph_v1(project)` | The graph only. Feeds `semantic_fingerprint()`. | A value that cannot be canonicalized raises `CanonicalizationError`. ## Offline project links [Section titled “Offline project links”](#offline-project-links) | Function | Result | | ------------------------------------------------------ | ---------------------------------------- | | `share_string(project)` | Versioned compact `z2.` project carrier. | | `share_url(project, *, base="https://garphield.com/")` | Absolute URL with the carrier in `#p=`. | Both require a validated `Project`, canonicalize and compress it locally, and perform no network I/O. `base` must be an absolute HTTP(S) URL; any existing query or fragment is removed. The browser accepts legacy `z1.` project links; new Python, R, and browser encoders emit `z2.`. ## Identity codecs [Section titled “Identity codecs”](#identity-codecs) The adapters use these to move Python values through JSON while preserving their types. The package exports them for anyone writing another adapter. | Codec | Handles | | ---------------------- | ------------------------------------------------- | | `IdentityCodecV1` | Node identities. | | `EdgeIdentityCodecV1` | Edge keys, scoped to their endpoint pair. | | `AttributeNameCodecV1` | Logical attribute names, including reserved ones. | Reserved names are `id`, `source`, `target`, `key`, and anything starting with `_gph:`. The manifest escapes them and restores them on the way back. ## Transport limits [Section titled “Transport limits”](#transport-limits) The transport between a notebook kernel and the view has a 1 MiB payload limit. The runtime refuses larger payloads; it does not truncate them. ```python from garphield import TRANSPORT_RUNTIME_CONFIG_V1 TRANSPORT_RUNTIME_CONFIG_V1.to_dict() ``` `prepare_budgeted_json`, `receive_budgeted_json`, `PreparedBudgetedJson` and `AnyWidgetTransportEndpoint` are exported for custom hosting. ## `GraphView` [Section titled “GraphView”](#graphview) `show()` returns `GraphView`. Its high-level methods are synchronous and return the view where chaining is useful. | Method | Result | | --------------------------------------------------------- | -------------------------------------------------------------------------- | | `select(nodes)`, `clear_selection()` | Select logical Python node identities. | | `fit()` | Fit the graph camera. | | `frame(nodes=...)`, `focus(node)`, `selection_mode(mode)` | Frame/focus logical nodes and choose pointer, marquee, or lasso selection. | | `select_polygon(points)` | Select nodes inside a polygon. | | `set_layout(mode)`, `relax(hold=...)` | Change or locally relax layout. | | `bind(channel, source)` | Bind an attribute name or `algorithm()`. | | `get_selection()` | Read selected logical identities. | | `to_project()`, `to_networkx()` | Read back the current settled document. | | `save(path)` | Save the current document. | | `capture()` | Return an in-memory PNG `Artifact`. | | `save_png(path)`, `save_html(path)` | Create an app-owned artifact and write it atomically. | | `export(format, target=...)` | Create a typed artifact in one of the app’s export formats. | | `reclaim()` | Explicitly recover from a lost full-app peer. | | `close()` | Release the runtime and connection. | `status` is `connecting`, `ready`, `in_app`, `closed`, or `error`. Artifacts expose `manifest`, `file_name`, `mime`, `data`, and an optional `path`. The manifest includes semantic/project fingerprints, included state, declared losses, and replay availability. Export targets are never overwritten unless `overwrite=True`; results larger than the temporary notebook transport budget continue in the full Garphield app through the browser download surface. ## Errors [Section titled “Errors”](#errors) | Error | Raised when | | ----------------------------------- | -------------------------------------------------------------- | | `ProjectValidationError` | A document fails schema validation. | | `ProjectShareError` | A project link or base URL cannot be encoded safely. | | `ProjectShareTooLargeError` | An inline project link exceeds 262,144 characters. | | `DuplicatePropertyError` | A document has the same key twice. | | `CanonicalizationError` | A value cannot be canonicalized to JCS bytes. | | `IdentityCollisionError` | Two identities collide once encoded. | | `WidgetNotDisplayedError` | A widget call runs before the view is ready. | | `WidgetEnvironmentUnsupportedError` | The environment cannot host the widget. | | `WidgetTransportError` | A widget request times out or breaks protocol. | | `OwnershipError` | The passive notebook attempts a live mutation or project read. | | `TransportError` | A transfer exceeds the budget or arrives malformed. | | `ArtifactManifest` / `Artifact` | Typed export metadata and bytes returned by the app. | --- # Visualize Snowpark graph tables > Materialize Snowpark edge and node tables into a Garphield project from a Python notebook. The Snowpark adapter follows the same graph shape as the pandas adapter. It materializes the selected Snowpark DataFrames with `.to_pandas()` in the notebook kernel, then applies Garphield’s ordinary table-to-project conversion. ## Show Snowpark tables [Section titled “Show Snowpark tables”](#show-snowpark-tables) Pass an edge table and, optionally, a node table to `show_snowpark()`: ```python import garphield as gph edges = session.table("GRAPH_EDGES") nodes = session.table("GRAPH_NODES") view = gph.show_snowpark( edges, nodes, source="SOURCE", target="TARGET", node_id="ID", ) ``` The edge table needs source and target columns. The node table supplies node attributes when present. Rename the structural columns with `source`, `target`, and `node_id`; use `edge_key` and `multigraph=True` for parallel edges. `show_snowpark()` returns the same synchronous `GraphView` as `show()`. You can select, bind, fit, read the project back, or open the full workbench: ```python view.select(["ada", "grace"]).fit() project = view.to_project() ``` ## Materialize the tables before conversion [Section titled “Materialize the tables before conversion”](#materialize-the-tables-before-conversion) Snowpark computation happens before the graph enters Garphield. The adapter requires each selected value to expose a callable `to_pandas()` method and requires that method to return a pandas DataFrame. The full selected tables are materialized in the kernel; filtering or limiting them in Snowflake first can keep the handoff bounded. After materialization, identity, direction, multigraph state, edge keys, and attributes follow the [conversion rules](/docs/python/#round-trip-guarantees). Use [Notebooks](/docs/python/notebooks/) for chrome, HTML export, and browser handoff behavior. ## Return the project [Section titled “Return the project”](#return-the-project) The notebook view remains the owner until you choose **Open in Garphield** and send the project back. While the browser owns editing, notebook mutations raise `OwnershipError`; after **Return to notebook**, the same `GraphView` exposes the settled project. --- # Garphield for R > Build and display Garphield projects from igraph or data frames. `garphieldr` turns `igraph` objects and node/edge data frames into `.gph` projects and displays them as interactive widgets. ## Install [Section titled “Install”](#install) ```r install.packages("garphieldr") ``` ## Open a graph [Section titled “Open a graph”](#open-a-graph) ```r library(garphieldr) library(igraph) project <- garphield_project(make_ring(12)) garphield(project) ``` `plot(project)` opens the same widget. To start from tables, pass `edges` and `nodes` to `garphield_project()`. ## Choose UI components [Section titled “Choose UI components”](#choose-ui-components) The widget includes the minimap and interaction toolbar. Keep either one on its own when the graph sits inside a report: ```r garphield(project, chrome = "minimap") ``` Write an interactive HTML file from the same project: ```r save_garphield_html(project, "network.html", chrome = "minimap") ``` ## Read next [Section titled “Read next”](#read-next) * [igraph and data frames](/docs/r/igraph-and-tables/) * [RStudio, Quarto, and R Markdown](/docs/r/rstudio-and-quarto/) * [Shiny](/docs/r/shiny/) * [Projects and fingerprints](/docs/r/projects/) * [Tutorial: igraph to Quarto and Shiny](/docs/blog/r-to-garphield/) - one network from RStudio through a Quarto page to a live Shiny app. --- # igraph and data frames > Move graphs and tabular network data between R and Garphield. ## igraph [Section titled “igraph”](#igraph) ```r library(igraph) library(garphieldr) graph <- make_ring(8, directed = TRUE) V(graph)$group <- rep(c("A", "B"), 4) project <- garphield_project(graph) garphield(project) round_trip <- as_igraph(project) ``` Vertex and edge attributes move into the project with direction and graph type intact. `as_igraph()` reconstructs the graph from the saved project. ## Data frames [Section titled “Data frames”](#data-frames) An edge table is enough: ```r edges <- data.frame( source = c("a", "b"), target = c("b", "c"), weight = c(1.2, 2.4) ) project <- garphield_project(edges = edges) ``` Pass a node table when you have node attributes: ```r nodes <- data.frame(id = c("a", "b", "c"), label = c("A", "B", "C")) project <- garphield_project(edges = edges, nodes = nodes) tables <- as_data_frames(project) ``` Rename the structural columns with the constructor arguments. For parallel edges, pass `edge_key = "key"` and `multigraph = TRUE`. Character, integer, double, and logical node identities remain distinct across the round trip. --- # Projects and fingerprints > Read, write, and compare Garphield projects from R. `garphield_project()` creates the same `.gph` document used by the browser and Python package. ## Read and write [Section titled “Read and write”](#read-and-write) ```r project <- garphield_project( edges = data.frame(source = c("a", "b"), target = c("b", "c")) ) write_gph(project, "network.gph") loaded <- read_gph("network.gph") project_to_list(loaded) ``` ## Compare projects [Section titled “Compare projects”](#compare-projects) ```r project_fingerprint(project) project_fingerprint(project, semantic = TRUE) ``` The project fingerprint covers the complete document, including positions and view configuration. The semantic fingerprint covers the graph data. Use both to distinguish a changed graph from a changed visual workspace. Open the same `.gph` file in Garphield or load it with the Python package. ## Create a complete project link offline [Section titled “Create a complete project link offline”](#create-a-complete-project-link-offline) ```r share_string <- garphield_share_string(project) url <- garphield_share_url(project) custom <- garphield_share_url(project, "https://example.com/garphield") ``` These functions perform no network I/O. They compact repeated node IDs in link endpoints and carry the complete canonical project in `#p=z2.…`, using zlib and unpadded base64url. Opening a project with saved positions preserves them; otherwise the live workbench runs Garphield’s normal selected layout. The browser still reads existing `z1.` carriers. Inline links are limited to 262,144 characters, so larger projects must be shared as `.gph` files. --- # RStudio, Quarto, and R Markdown > Display a Garphield widget in an R session or an HTML document. `garphield()` returns an `htmlwidget`, so the same call works in the RStudio or Positron Viewer and in HTML output from Quarto or R Markdown. ```r library(garphieldr) edges <- data.frame(source = c("a", "b"), target = c("b", "c")) garphield(edges) ``` ## Use less chrome [Section titled “Use less chrome”](#use-less-chrome) Keep only the minimap or toolbar when the graph is part of a document: ```r garphield(edges, chrome = "minimap") ``` ## Write an HTML file [Section titled “Write an HTML file”](#write-an-html-file) ```r project <- garphield_project(edges = edges) save_garphield_html(project, "network.html", chrome = "minimap") ``` The file contains the project and loads the configured Garphield embed. You can serve it without the originating R process. ## Use another renderer [Section titled “Use another renderer”](#use-another-renderer) Pass an absolute URL ending in `/embed`: ```r garphield(edges, embed_url = "http://localhost:4173/embed") ``` Use [Shiny](/docs/r/shiny/) when node clicks or R-side updates should move through a live session. The [igraph to Quarto and Shiny tutorial](/docs/blog/r-to-garphield/) walks through both. --- # Interactive network graphs in R Shiny > Render Garphield in Shiny and update it from R. Install Shiny alongside `garphieldr`: ```r install.packages("shiny") ``` ## Render a graph [Section titled “Render a graph”](#render-a-graph) ```r library(shiny) library(garphieldr) ui <- fluidPage( garphieldOutput("network"), verbatimTextOutput("clicked") ) server <- function(input, output, session) { project <- garphield_project( edges = data.frame(source = c("a", "b"), target = c("b", "c")) ) output$network <- renderGarphield(project) output$clicked <- renderPrint(input$network_node_click) } shinyApp(ui, server) ``` A node click arrives as `input$network_node_click`. ## Update the mounted graph [Section titled “Update the mounted graph”](#update-the-mounted-graph) Create a proxy when server code needs to replace the project or run a Garphield command: ```r proxy <- garphieldProxy("network", session) garphieldProxySetProject(proxy, project) garphieldProxyRun(proxy, "setTheme", list(colorScheme = "dark")) ``` `garphieldProxyOpenApp(proxy)` hands the current project to the full Garphield workbench. The [igraph to Quarto and Shiny tutorial](/docs/blog/r-to-garphield/) builds a complete app step by step. --- # Visualize Raphtory temporal graphs > Visualize a Raphtory temporal graph in Garphield from a notebook or via JSON export. [Raphtory](https://docs.raphtory.com) is a temporal graph library. Select a window or snapshot in Raphtory, export it to NetworkX, and open it in Garphield. ## From a notebook [Section titled “From a notebook”](#from-a-notebook) The shortest path is `gph.show()` on the exported NetworkX graph: ```python import garphield as gph import raphtory graph = raphtory.Graph() graph.add_edge(1, "alice", "bob", properties={"weight": 1.0}) graph.add_edge(2, "bob", "charlie", properties={"weight": 2.0}) view = graph.window(1, 10) nx_graph = view.to_networkx( include_property_history=False, include_update_history=False, ) view = gph.show(nx_graph, node_size="weight") ``` Disable `include_property_history` and `include_update_history` so attributes arrive as scalar values that Garphield can bind directly. With history enabled, values arrive as nested time/value structures that are valid data but unusable as visual encodings. ## From JSON [Section titled “From JSON”](#from-json) When the graph is produced outside a notebook, export to node-link JSON and open the file in Garphield: ```python import json import networkx as nx nx_graph = view.to_networkx( include_property_history=False, include_update_history=False, ) with open("raphtory-window.json", "w", encoding="utf-8") as output: json.dump(nx.node_link_data(nx_graph, edges="edges"), output) ``` Open `raphtory-window.json` with **File > Open** or pass its URL through the [URL loading path](/docs/guides/load-data/#load-a-url). ## Temporal windows and snapshots [Section titled “Temporal windows and snapshots”](#temporal-windows-and-snapshots) Select the time range before exporting: ```python window = graph.window("2026-07-01", "2026-08-01") snapshot = graph.at("2026-07-15") ``` Restrict to a layer or subgraph when the question concerns one relationship type: ```python focused = graph.window("2026-07-01", "2026-08-01").layer("messages") ``` ## Compare windows as a storyboard [Section titled “Compare windows as a storyboard”](#compare-windows-as-a-storyboard) Export one file per window, open the first in Garphield, capture a **Story** scene with the window’s dates as its title, then open the next file and capture another scene. Play or share the storyboard after all snapshots are captured. See [Audit and present](/docs/guides/audit-and-present/) for the storyboard workflow. --- # Garphield automation API > Drive the full workbench through its command registry and serializable state. Use the Automation API to drive the live workbench through its command registry. You can also use the [embed driver](/docs/embed/) for a more integrated approach to adding Garphield to your app. This page covers direct access to the live workbench. [Core concepts](/docs/getting-started/concepts/) explains graph data and project state. ## No browser available? [Section titled “No browser available?”](#no-browser-available) Use the Python or R package to validate a complete project and create a URL without network I/O: ```python import garphield as gph project = gph.Project.load("network.gph") url = gph.share_url(project) ``` The R equivalent is `garphield_share_url(project)`. Both return a URL carrying the complete project in a compressed `#p=` carrier. It uses zlib/DEFLATE plus unpadded base64url and accepts at most 262,144 encoded characters. Opening the link runs Garphield’s normal layout when the project has no saved positions; link generation itself does not run a layout or renderer. For a small graph-only payload, use the documented [`data:` URL plus `format`](/docs/guides/load-data/#load-a-url) path. | Surface | Capability boundary | | ------------------------ | -------------------------------------------------------------------------------------------------------------------- | | HTTP-only agents | Read documentation, LLM files, the command schema, and fetch graph files by URL. | | Python and R | Validate, convert, fingerprint, save `.gph` projects, and perform project-link encoding without mounting a view. | | Mounted browser renderer | `window.garphield`, WebMCP, embeds, and notebook widgets provide analysis, layout, audit, rendering, and PNG export. | | Playwright | Automates the mounted renderer in a headless browser; it is not browser-free. | There is currently no hosted-project or server-render endpoint. Projects above the inline limit must be shared as `.gph` files. ## Choose an automation surface [Section titled “Choose an automation surface”](#choose-an-automation-surface) The live automation surfaces in this section all require a mounted browser renderer. Browser-free Python and R workflows operate on project documents; they do not provide `window.garphield` or WebMCP. `window.garphield` is the stable, canonical automation API for same-frame JavaScript: use it from the browser console, extensions, and Playwright tests that can run code in the workbench frame. The remaining sections document that API. Compatible browser agents can instead discover page-defined `garphield_*` tools through the browser-native WebMCP bridge. This is an experimental, progressive enhancement based on the WebMCP W3C Community Group Draft: Garphield feature-detects it, so browsers and agent harnesses without WebMCP continue to use `window.garphield` unchanged. It is not the third-party webmcp.dev widget and it adds no Garphield UI. | WebMCP tool | Equivalent operation | | ------------------------- | --------------------------------------------- | | `garphield_schema` | List registered commands. | | `garphield_run` | Run a command with `{ id, params? }`. | | `garphield_get_state` | Read the serializable view state. | | `garphield_apply` | Apply a state document or compressed payload. | | `garphield_view` | Inspect the current drawing and projection. | | `garphield_export` | Export graph text in a supported format. | | `garphield_audit` | Read accessibility findings. | | `garphield_quality` | Read layout-quality findings. | | `garphield_get_share_url` | Get a complete share URL. | Agents should call `garphield_schema` before `garphield_run` and use `garphield_get_state` or `garphield_view` to inspect the result. WebMCP exposes request/response operations only; for subscriptions and other same-frame capabilities, use `window.garphield`. ## Run the production workbench with Playwright [Section titled “Run the production workbench with Playwright”](#run-the-production-workbench-with-playwright) Playwright is a headless-browser surface, not a browser-free surface. This script opens the production Garphield workbench, waits for its mounted API, loads a supported sample, awaits the requested layout, frames final coordinates, and reads the resulting state: ```js import { chromium } from "playwright"; const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); await page.goto("https://garphield.com/", { waitUntil: "domcontentloaded", }); await page.waitForFunction( () => typeof window.garphield?.run === "function", undefined, { timeout: 30_000 }, ); await page.evaluate(async () => { await Promise.resolve( window.garphield.run("sample.load", { id: "petersen" }), ); }); await page.waitForFunction( () => window.garphield?.getState?.().dataset?.id === "petersen", undefined, { timeout: 30_000 }, ); await page.evaluate(async () => { const layout = await Promise.resolve( window.garphield.run("layout.set", { mode: "force" }), ); if (layout && !layout.ok) { throw new Error(layout.error?.message ?? "Layout mode failed"); } const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (settled && !settled.ok) { throw new Error(settled.error?.message ?? "Layout did not settle"); } }); const id = await page.evaluate(async () => { const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const id = nodeLink.nodes?.[0]?.id; if (typeof id !== "string") throw new Error("The graph has no node ID"); const selected = await Promise.resolve( window.garphield.run("selection.select", { id }), ); if (selected && !selected.ok) { throw new Error(selected.error?.message ?? "Selection failed"); } return id; }); await page.evaluate(async (id) => { const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0, }), ); if (!framed?.ok) { throw new Error(framed?.error?.message ?? "Frame failed"); } const audit = window.garphield.audit(); const quality = window.garphield.quality(); if (!audit.ok) { throw new Error("Unsafe view"); } if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view"); } }, id); const state = await page.evaluate(() => window.garphield.getState()); console.log({ dataset: state.dataset.id, layout: state.layout, node: id }); } finally { await browser.close(); } ``` `layout.set` reserves the next layout epoch before the renderer effect runs, so an immediately following `layout.wait` (without an `epoch` parameter) waits for that request rather than a previous settlement. The same correlation is used after a successful graph `file.load` and after `apply()` when it changes the graph or layout. An explicit `layout.wait({ epoch })` remains supported. ## Discover and run commands [Section titled “Discover and run commands”](#discover-and-run-commands) `schema()` returns the live command catalogue. Run the ids it returns: Every call may be synchronous or asynchronous, so callers should use `await Promise.resolve(run(...))`. Read the declared result `delivery` marker: `"envelope"` results use the typed `{ ok, value }` or `{ ok: false, error }` shape, while `"raw"` results are consumed directly. Void commands may return `undefined` (or `null` on JSON transports). Unknown ids and invalid parameters can throw synchronously; raw-command transport failures may reject the promise or surface as an outer transport error rather than an envelope. ```js await Promise.resolve(window.garphield.run("sample.load", { id: "petersen" })); await Promise.resolve(window.garphield.run("layout.set", { mode: "levels" })); await Promise.resolve(window.garphield.run("layout.wait")); await Promise.resolve(window.garphield.run("selection.select", { id: "3" })); await Promise.resolve(window.garphield.run("camera.frame", { nodeIds: ["3"] })); await Promise.resolve(window.garphield.run("selection.path", { source: "3", target: "8" })); await Promise.resolve(window.garphield.run("view.fisheye", { enabled: true, magnification: 4, radiusScale: 0.45, })); ``` `selection.path` respects directed links and returns the exact shortest-path node and edge ids. It returns a validation error when no directed route exists. `view.fisheye` gives agents parity with the lens controls: it enables or disables the lens and sets its magnification and viewport-relative radius. Pointer focus remains transient; a Playwright driver can move it with a primary click (with a short transition), drag the inner grab ring past its breakaway threshold to release a pinned lens, primary-drag its outer boundary to resize it, or right-drag inside it to change magnification. Escape also releases a pinned lens. Wheel and touchpad gestures continue to zoom the camera, even inside the lens. ## Command families [Section titled “Command families”](#command-families) The registry groups commands by the job they perform: | Family | Examples | | -------------------- | ------------------------------------------------------------------------------------------------------ | | Data | Load a sample, load a URL, generate a graph. | | Layout and view | Switch layout, relax around a selection, set graph detail, frame the camera, or configure the fisheye. | | Channels and filters | Browse sources, bind or unbind a channel, add a filter, add or clear a transformation. | | Selection | Select, find a directed path, grow, shrink, invert, or clear a selection. | | History | Pin a state, jump to a point, describe the tree, or diff two points. | | Narrative | Save a set, capture a scene, play a storyboard, or copy a storyboard link. | Use `schema()` as the source of truth for current ids and parameter descriptions. The command palette and embed capability grants use the same registry. The Python widget reaches the renderer through its typed bridge. `file.load` accepts `http:`, `https:`, and `data:` URLs, plus a `blob:` URL created in the same browser context. Only cross-origin HTTP(S) sources need CORS. Prefer HTTPS for hosted files because the production HTTPS site normally cannot fetch an HTTP URL. The optional authoritative `format` is `dot`, `gexf`, `graphml`, `gml`, `nodeLink`, `compactJson`, `csv`, `edgeList`, `matrixMarket`, `graph6`, `sparse6`, `digraph6`, or `gph`. Garphield otherwise uses the URL suffix and defaults an unknown or absent suffix to JSON content sniffing. MIME type does not select the parser. Compact JSON uses node-array positions as IDs; the graph6 family carries topology only and therefore receives numeric labels. Build a `data:` URI first and then percent-encode the entire URI as the `file` query value. See the [inline-loading recipes](/docs/guides/load-data/#load-inline-data-no-hosting). `file.load` is asynchronous and returns the typed `FileLoadResultV1` descriptor inside the normal `CommandResult` envelope. Await it before reading state and branch on both transport failure and flat-table construction: ```js const result = await Promise.resolve( window.garphield.run("file.load", { url, format: "gexf" }), ); if (!result?.ok) throw new Error(result?.error?.message ?? "Load failed"); if (result.value.status === "construction-required") { throw new Error("This table needs an explicit graph construction recipe"); } const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle"); ``` For contrast, `artifact.describe` is declared with `delivery: "raw"` and resolves directly to an `ArtifactFormatV1[]`; consume that array without an `ok` branch. `artifact.create` and `artifact.download` likewise return raw typed objects. Use each command’s generated result descriptor to decide which branching rule applies instead of inferring it from `kind` or the command id. `status: "loaded"` means the graph store has been replaced; it does not mean layout has settled. The replacement reserves the layout epoch, so call `layout.wait` immediately after the load to await that exact layout before selecting or framing coordinates. Fetch, network, and CORS acquisition failures return `LOAD_FAILED` with `retryable: true`. After bytes/string acquisition, decode, parse, schema, and format failures return `LOAD_FAILED` with `retryable: false`; the message and URL details are preserved. A request superseded by newer graph activity returns `STALE_STATE` with `retryable: true` and no obsolete toast. ## Read and apply state [Section titled “Read and apply state”](#read-and-apply-state) ```js const next = structuredClone(window.garphield.getState()); next.layout = "levels"; next.style = { ...(next.style ?? {}), nodeSurface: "sphere", }; const applied = window.garphield.apply(next); if (!applied) throw new Error("StateDoc was rejected"); const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle"); const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const id = nodeLink.nodes?.[0]?.id; if (typeof id !== "string") throw new Error("The graph has no node ID"); const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0 }), ); if (!framed?.ok) throw new Error(framed?.error?.message ?? "Frame failed"); ``` `getState()` returns the serializable view document with the current camera. `apply()` accepts a complete state object or compressed share payload and returns whether validation and dispatch succeeded. A `true` result acknowledges the request; it does not report completion of asynchronous generator loading. For a generator workflow, await `generator.create`, confirm through `getState()` that the requested dataset was adopted, then await `layout.wait`. A newer edit or dataset request can supersede a generator while its module loads. For changes dispatched synchronously, `apply()` reserves a layout epoch when it changes the graph or layout; await `layout.wait` immediately after dispatch, then select or frame final coordinates. Set `style.nodeSurface` to `"flat"`, `"lumen"`, `"sphere"`, `"prism"`, or `"sketchy"`; the value is also retained by generated view-state URLs. The optional top-level `nodeSurfaceStyle` field remains accepted as a compatibility alias for older state documents. Validate complete StateDocs against the published [StateDoc JSON Schema](https://garphield.com/schemas/state-doc.schema.json). `false` means invalid and inert: the existing state does not change, and a StateDoc’s file reference does not carry or replace file graph bytes. `apply()` also strips `positions`; full positions and session restoration belong to `.gph` project loading. `true` means validation and dispatch succeeded, not that an asynchronous sample layout has settled. Use `layout.wait` when settled geometry is required. A successful graph `file.load` uses the same immediate wait contract. The canonical layout modes are `force`, `levels`, and `geo`. `quality` remains only a legacy input normalized to `force` when decoding older documents; new commands and documents should use the canonical names. The document’s `style` block contains node surface, node size, node gap, link width, link opacity, curvature, and the fisheye’s enabled state, magnification, and radius scale. Theme, labels, link direction, and link gradient remain top-level for backward compatibility. The same complete style is carried by share URLs, history pins, `.gph` projects, `apply()`, and `observe()` updates; fisheye pointer focus is deliberately transient. Subscribe to state changes: ```js const stop = window.garphield.observe((state) => { console.log(state.selection, state.camera); }); // Later stop(); ``` ## Inspect the current drawing [Section titled “Inspect the current drawing”](#inspect-the-current-drawing) `view()` separates the saved project from the current Full or Backbone presentation: ```js const view = window.garphield.view(); console.log(view.detail); // "full" or "overview" console.log(view.profile); // graph complexity console.log(view.rendered); // nodes and edges on canvas console.log(view.hiddenEdges); // shown and omitted edge counts ``` Switch the presentation with the matching command: ```js window.garphield.run("view.detail", { mode: "overview" }); ``` ## Node IDs and history IDs [Section titled “Node IDs and history IDs”](#node-ids-and-history-ids) Graph node IDs come from the node-link export’s `nodes[].id` values. They are data identifiers used by selection and camera commands: ```js const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const graphNodeId = nodeLink.nodes[0].id; await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [graphNodeId] }), ); ``` History IDs are provenance-operation IDs returned by `history.tree()`. They are not graph node IDs and are the values expected by `history.jump` and `history.diff`: ```js const tree = await Promise.resolve(window.garphield.run("history.tree")); const historyId = tree.nodes[0].id; await Promise.resolve( window.garphield.run("history.jump", { nodeId: historyId }), ); ``` ## Export and inspect [Section titled “Export and inspect”](#export-and-inspect) ```js const graphml = window.garphield.export("graphml"); const audit = window.garphield.audit(); const quality = window.garphield.quality(); const share = window.garphield.getShareString(); const shareUrl = window.garphield.getShareUrl(); const story = window.garphield.getStoryboardShareString(); ``` Supported export values are `gexf`, `graphml`, `gml`, and `nodeLink`. Gate an automated result on both reports, surfacing every non-good quality verdict: ```js const audit = window.garphield.audit(); const quality = window.garphield.quality(); if (!audit.ok) throw new Error("Unsafe view"); if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view"); } ``` `audit.ok` is false only when a critical automated accessibility heuristic fails; manual checks remain findings. `quality().verdict` is `good`, `warn`, `poor`, `info`, or `na`. Reject `poor`, surface `warn` for judgment, and treat `na` as ungraded rather than passed. Quality is not a certification. ## API summary [Section titled “API summary”](#api-summary) | Method | Returns | | ---------------------------- | ----------------------------------- | | `schema()` | Serializable command descriptors | | `run(id, params?)` | A registered command result | | `getState()` | Current view state | | `apply(doc)` | Whether state validated and applied | | `view()` | Current Full or Backbone projection | | `export(format)` | Serialized graph text | | `audit()` | Accessibility findings | | `quality()` | Layout-quality report | | `getShareString()` | Compressed view payload | | `getShareUrl()` | Full share URL for the current view | | `getStoryboardShareString()` | Compressed story payload | | `observe(callback)` | Unsubscribe function | `window.garphield` exists while the workbench is mounted. Look it up again after navigation rather than keeping a handle to an unmounted page. --- # Binding grammar > The full config.bindings wire format: channels, result types, source kinds, and field ids for authoring a project by hand or from code. A binding connects a source to a visual channel. The workbench writes bindings for you; this page is the wire format for authoring them directly - in a `.gph` document, over the embed protocol, or from the Python and R packages. The [GPH schema](/schemas/gph.schema.json) constrains a binding’s shape, but not whether a channel accepts a source. Garphield resolves that against the catalog below, and the Python package raises `BindingError` at build time when a binding does not resolve. ## Shape [Section titled “Shape”](#shape) Bindings live in `config.bindings` as an array. Each entry is: ```json { "channel": "color", "source": { "kind": "field", "id": "field:team" }, "resultType": "cat" } ``` * `channel` - the mark or layer the source drives (see [Channels](#channels)). * `source` - `{ "kind", "id", "params?", "target?" }` (see [Source kinds](#source-kinds)). * `resultType` - what the source produces: `num`, `cat`, or `set`. It must be a type the channel accepts, and for an `algorithm` source it must match what that algorithm produces. ## Channels [Section titled “Channels”](#channels) Channels are camelCase on the wire. The result types a channel accepts and its target (node or edge) are fixed. The Python and R packages also accept the snake\_case alias in the last column as a keyword argument; both resolve to the same camelCase channel on the wire. | Channel (wire) | Target | Accepts | Python / R alias | | -------------- | ------ | ------------------- | ---------------- | | `size` | node | `num` | `node_size` | | `color` | node | `cat`, `num`, `set` | `node_color` | | `shape` | node | `cat`, `num`, `set` | - | | `image` | node | `cat` | - | | `nodeLabel` | node | `cat`, `num` | `node_label` | | `border` | node | `cat`, `set` | - | | `pieSlices` | node | `cat`, `num` | - | | `edgeColor` | edge | `cat`, `num` | `edge_color` | | `edgeWidth` | edge | `num` | `edge_width` | | `edgeStyle` | edge | `cat`, `set` | - | | `edgeLabel` | edge | `cat`, `num` | - | | `hull` | node | `cat`, `set` | - | | `contour` | node | `cat`, `set` | - | | `heatmap` | node | `num` | - | A source’s target must match the channel’s target: a node source cannot drive an edge channel, and the reverse. A source’s result type must be in the channel’s `Accepts` list - for example, `size` and `edgeWidth` take only `num`, so binding a category to either does not resolve. ## Result types [Section titled “Result types”](#result-types) * `num` - a numeric value per node or edge. Drives size, edge width, heatmap, or any channel that lists `num`. * `cat` - a categorical value. Drives color, shape, labels, contours, and hulls. * `set` - a membership set. Drives color, shape, borders, hulls, contours, and link style. To capture link membership, select nodes and use **Save set → Include links between selected nodes**, or run `set.add` with `includeEdges: true`. Only links whose two endpoints are selected are captured; the set keeps that snapshot through later selections, undo/redo, and project saves. ## Source kinds [Section titled “Source kinds”](#source-kinds) * `field` - a value already on the graph. The `id` is `field:` for a node attribute or `efield:` for an edge attribute (see [Field ids](#field-ids)). * `algorithm` - a value computed in the browser, such as `degree` or `louvain`. The `id` is the bare algorithm id; optional `params` tune it (for example `{"resolution": 1.2}` for `louvain`). An algorithm’s own target and produced type are fixed - see [Sources and algorithms](/docs/reference/sources-and-algorithms/) for the catalog. Edge algorithms carry `"target": "edge"`. * `transform` - a graph transformation. These live in `config.filterStack`, not in a visual channel, and produce `trf`. * `set` - a saved set. A set can anchor nodes or edges; its `target` is resolved from the channel it is dropped on. ## Field ids [Section titled “Field ids”](#field-ids) A field id names an attribute by its internal, on-the-wire name: * Node attribute `team` becomes `field:team`. * Edge attribute `weight` becomes `efield:weight`. Most attribute names pass through unchanged. A few names are reserved because the wire format uses them for structure: `id` on a node; `source`, `target`, and `key` on an edge; and any name beginning with `_gph:` or `@gf:`. When your data uses a reserved name, Garphield escapes it to `@gf:attr:v1:` in the node or edge row and records the original name in `config.interop.attributeNames`, so the field id becomes `field:@gf:attr:v1:`. You rarely write these by hand: discover the exact ids with `project.sources()` (Python) instead of guessing. ## Authoring from Python [Section titled “Authoring from Python”](#authoring-from-python) `Project.from_networkx()` and `Project.from_pandas()` take the snake\_case channel keywords and write the bindings for you, so you do not hand-author the JSON: ```python import garphield as gph project = gph.Project.from_networkx( graph, node_color="kind", # an existing attribute -> field:kind node_size=gph.algorithm("degree"), # a computed source layout="force", ) ``` `from_networkx` also accepts a mapping, a callable, or a partition (a list of node sets) for a channel; it materializes those into a node field and binds it. `from_pandas` binds by column name, an `algorithm(...)` spec, `layout`, or `pos`; for a per-row value, add a column to the frame and bind it by name. Discover the bindable ids and their types without opening a browser: ```python for source in project.sources(): print(source.id, source.kind, source.target, source.result_type) # field:kind field node cat # efield:weight field edge num # degree algorithm node num # ... ``` Every binding is validated when the `Project` is built. An unknown channel, a missing field, an unknown algorithm, or a result type the channel does not accept raises `BindingError` in Python, instead of leaving a dead binding that renders nothing in a browser you cannot see. See [Visual channels](/docs/reference/visual-channels/) for what each channel is for, [Sources and algorithms](/docs/reference/sources-and-algorithms/) for the algorithm catalog, and [The .gph document](/docs/embed/document/) for the surrounding project shape. --- # Supported file formats > Graph and project formats Garphield can open and export. | Format | Extension | Open | Export | Carries | | -------------------- | ----------------------------------------------- | ---- | ------ | ------------------------------------------------------------ | | Garphield project | `.gph` | ✓ | Save | Graph, visual workspace, sets, history, story | | GEXF | `.gexf` | ✓ | ✓ | Graph type and attributes | | GraphML | `.graphml` | ✓ | ✓ | Graph type and scalar attributes | | GML | `.gml` | ✓ | ✓ | Graph data in a plain-text format | | Graphviz DOT | `.dot`, `.gv` | ✓ | — | Graph topology and scalar attributes | | Node-link JSON | `.json` | ✓ | ✓ | NetworkX-shaped graph data | | Compact JSON | `.compact.json`, `.gf.json`, or sniffed `.json` | ✓ | — | Indexed graph with node/link attributes | | graph6 | `.g6` | ✓ | — | Simple undirected topology only | | sparse6 | `.s6` | ✓ | — | Sparse undirected topology, including loops/parallel links | | digraph6 | `.d6` | ✓ | — | Directed topology, including loops | | CSV, TSV, text table | `.csv`, `.tsv`, `.txt` | ✓ | — | Edge list, flat-table construction, or two-file graph bundle | | Plain edge list | `.edges`, `.edgelist`, `.el` | URL | — | Whitespace-delimited endpoints | | Matrix Market | `.mtx` | URL | — | Sparse matrix as an undirected graph | | PNG | `.png` | — | ✓ | Rendered canvas | ## Node-link JSON [Section titled “Node-link JSON”](#node-link-json) ```json { "directed": false, "multigraph": false, "graph": {}, "nodes": [{ "id": "a" }, { "id": "b" }], "edges": [{ "source": "a", "target": "b" }] } ``` Garphield accepts the current NetworkX `edges` key and the older `links` key. Garphield uses a node’s `label` attribute for display and search when present. ## Compact JSON [Section titled “Compact JSON”](#compact-json) ```json { "nodes": ["Alice", { "label": "Bob", "group": "staff" }, "Carol"], "edges": [[0, 1], [1, 2, { "weight": 2 }]], "directed": true } ``` Each node’s array position is its node ID. A string node is shorthand for a `label`; an object contains node attributes. Each edge is `[sourceIndex, targetIndex]` or `[sourceIndex, targetIndex, attributes]`. `directed` and `multigraph` are optional and default to `false`. Isolates remain explicit in `nodes`, and duplicate labels are safe because structure uses array positions. This format is intentionally easy for agents to write and compact in inline URLs. It is import-only. Use node-link JSON when the original node IDs, edge keys, or graph attributes must survive. ## graph6 family [Section titled “graph6 family”](#graph6-family) graph6, sparse6, and digraph6 are established printable-ASCII topology formats: * graph6 stores simple undirected graphs compactly, especially dense ones. * sparse6 stores sparse undirected graphs and can represent loops and parallel links. * digraph6 stores directed graphs, including loops, with at most one link for each ordered node pair. Garphield accepts one graph record per load, with or without the standard header. Incremental sparse6 records are not accepted because they depend on a preceding record. These formats carry topology only: imported nodes receive keys and labels `0` through `n-1`. The loader caps them at 100,000 nodes and 1,000,000 links to prevent tiny declarations from causing unsafe allocations. For a URL with no matching suffix—especially `data:text/plain,...`—pass `format=graph6`, `format=sparse6`, or `format=digraph6` explicitly. ## CSV edge list [Section titled “CSV edge list”](#csv-edge-list) ```csv source,target,weight a,b,2 b,c,1 ``` Garphield matches `source` and `target` headers case-insensitively. Other columns become edge attributes. A table without that header pair opens the graph construction dialog instead. ## Two-file CSV graph bundle [Section titled “Two-file CSV graph bundle”](#two-file-csv-graph-bundle) Open two CSV or TSV files together from **File → Open**. Garphield treats the file with `source` and `target` columns as the edge table and the other file as the node table. It prefers an `id` column for the node key; when that column is absent, it infers a key column from the overlap with the edge endpoints. The node table’s remaining columns become node attributes, with `label` or `name` supplying the displayed label when available. Garphield classifies the files by their headers, so names such as `links.csv` and `points.csv` work without a special naming convention. You can also open a single-file edge list or construct a graph from a flat table. ## Garphield projects [Section titled “Garphield projects”](#garphield-projects) Use `.gph` for the graph and its visual work. The Python package and embed driver use the same project document. Validate projects against the published [GPH schema](/schemas/gph.schema.json). --- # Keyboard shortcuts > Current keyboard shortcuts for common Garphield actions. This table mirrors the in-app list, which you can open at any time with `?`. ## General [Section titled “General”](#general) | Shortcut | Action | | -------- | ---------------------- | | `?` | Open this help | | `⌘K` | Command palette | | `/` | Search nodes | | `⌘O` | Open a file | | `⌘S` | Save project | | `⇧⌘S` | Export | | `⌘Z` | Undo | | `⇧⌘Z` | Redo | | `[` | Toggle the left panel | | `]` | Toggle the right panel | | `\` | Toggle the data table | ## Canvas [Section titled “Canvas”](#canvas) | Shortcut | Action | | ---------------------- | ----------------------------------------------------------- | | `V` | Pointer mode | | `Shift + drag` | Add nodes with a box on empty canvas | | `L` | Lasso select | | `T` | Pick a path target | | `Z` | Hold and drag to zoom into a region | | `Click canvas` | Pin or move the fisheye lens | | `Drag inner lens ring` | Release the pinned fisheye lens | | `Drag lens edge` | Resize the pinned fisheye lens | | `Right-drag in lens` | Change fisheye magnification | | `↑/↓ on fisheye` | Change magnification from the toolbar | | `Esc` | Unpin fisheye, leave the current mode, then clear selection | ## Storyboard playback [Section titled “Storyboard playback”](#storyboard-playback) | Shortcut | Action | | -------- | -------------- | | `→` | Next scene | | `←` | Previous scene | | `Esc` | Stop playback | Shift-drag starts a box only on empty canvas and adds its nodes to the current selection. Release the pointer to return to normal navigation, even if Shift was released first. Shift-click on a node still toggles that node. For a box that replaces the selection, choose Marquee in the toolbar and drag without Shift. --- # Project model > How graph data, visual state, and presentation snapshots fit together in a Garphield project. The browser workbench, Python package, R package, and embed driver use the `.gph` format. It contains the graph and the workspace state needed to continue working with it. ## What a project contains [Section titled “What a project contains”](#what-a-project-contains) | Part | Contains | | ------------------ | -------------------------------------------------------------------------------------- | | `info` | Version and optional project metadata. | | `datasets` | One or more graph datasets in node-link form. | | `config` | Layout, bindings, filters, transformations, and related workspace state. | | Presentation state | Positions, camera, visible detail, saved sets, history, annotations, and story scenes. | The published [GPH schema](/schemas/gph.schema.json) is the validation reference. Use [File formats](/docs/reference/file-formats/) for the formats that carry graph data without the complete workspace. ## Project identity [Section titled “Project identity”](#project-identity) The Python and R packages expose complete-project and semantic graph fingerprints. A project fingerprint changes when visual state changes; a semantic fingerprint changes when the graph data changes. Use the pair when a pipeline needs to distinguish “the drawing changed” from “the network changed.” ## Choose a format [Section titled “Choose a format”](#choose-a-format) * Use `.gph` to continue editing in Garphield, Python, R, or an embed. * Use GEXF, GraphML, GML, or node-link JSON to exchange graph data. * Use a share link for a supported browser view and its current presentation. * Use PNG for a rendered image. See [Save, share, and export](/docs/guides/save-share-export/) and [The .gph document](/docs/embed/document/) for examples of each format. --- # Sources and algorithms > A compact map of the values Garphield can compute, bind, filter, or use to transform a graph. Sources are values from the current graph that you can put in a channel or filter. They include fields, algorithm results, selections, saved sets, and transformation results. A transformation changes the working graph passed to later sources. ## Algorithm families [Section titled “Algorithm families”](#algorithm-families) | Family | Shipped examples | Typical result | | -------------- | ------------------------------------------------------------------------------- | -------------------------------- | | Centrality | Degree, betweenness, closeness, PageRank, eigenvector, harmonic, Katz | Numeric node source | | Community | Louvain, Leiden, label propagation, greedy modularity, K-clique | Categorical or set source | | Spectral | Spectral bisection, Kernighan–Lin bisection | Partition source | | Paths | Shortest path, ego network, reachable-from-source | Selection or set source | | Structure | K-core, clustering coefficient, bridges, articulation points, largest component | Numeric, category, or set source | | Edge analysis | Edge betweenness, Jaccard, Adamic–Adar, resource allocation, edge bridges | Numeric edge source | | Sparsification | Spanning tree, disparity-filter backbone | Transformed working graph | The exact command and parameter surface is available through the [Automation API](/docs/reference/automation-api/). Use the workbench to inspect each result in the graph and table. ## Communities and node types [Section titled “Communities and node types”](#communities-and-node-types) New graphs start with degree sizing and Louvain coloring on networks up to 10,000 nodes and 100,000 edges. Larger networks start with neutral colors; choose **Color → Louvain** or another community source to analyze them. Computation and subsequent resolution changes run automatically in a worker; there is no separate Run step. The **Communities** section on the right reads that same result, including its membership and colors. Algorithm and resolution controls stay on the left. Opening a summary does not run a different community algorithm. The largest eight groups appear first. Expand **Remaining groups** to search or page through the complete partition. Each row shows its node count and share of all nodes; clicking selects those nodes. Disconnected components remain distinct. Louvain and Leiden use eight deterministic restarts. Among results within 0.005 modularity of the best restart at the chosen resolution, Garphield prefers fewer groups. This reduces fragmentation without forcing arbitrary merges or changing your resolution. On networks up to 10,000 nodes and 100,000 edges, approximate group counts at the slider’s endpoints appear after foreground work settles. Each estimate uses one restart; the applied result still uses eight. Explicit color bindings from users or saved documents are preserved on larger graphs. For semantic categories such as Person or Organization, choose **Color → Node type…**, then select the field that defines the type. This treats even numeric codes as categories and preserves the original data. Missing values appear as **Unspecified** in the type summary. The field choice is saved with the binding and restored with the view. ## Degree summary [Section titled “Degree summary”](#degree-summary) The right panel starts with the highest-degree nodes, showing names and incident edge counts for the top five nodes. Click a node to select it. **Degree distribution** shows degree bands, with node counts and percentages. A band containing one node shows its exact degree. Bars represent the share of all nodes in each range. Degree counts incoming plus outgoing edges on directed graphs. Parallel edges count separately, and a self-loop counts twice. It is not a count of unique neighbors. Degree summaries appear independently of slower analysis. ## Generators [Section titled “Generators”](#generators) **File → Generate** creates seeded, reproducible test graphs such as random, small-world, scale-free, tree, grid, cycle, star, wheel, barbell, and bipartite networks. Use a generator when you need a controlled example rather than an imported dataset. ## Transformations [Section titled “Transformations”](#transformations) Transformations form a non-destructive stack. They can extract the largest component, a spanning tree, a K-core, or a disparity-filter backbone. Reorder, toggle, and clear transformations from the filter stack; the original dataset and project history remain available. A binding changes what appears on the canvas. A transformation changes the nodes and edges passed to later layout and analysis steps. See [Explore and analyze](/docs/guides/explore/). ## Result types [Section titled “Result types”](#result-types) * **Numeric** values can drive size, color, edge width, or a heatmap. * **Category** values can drive color, shape, labels, or contours. * **Sets** can drive borders, contours, selection emphasis, and annotations. * **Edge results** stay attached to edges and can drive edge color, width, or labels where the channel accepts them. --- # Visual channels > Which graph results can drive which marks and layers in the Garphield workbench. Open **Viz** to connect a source to a channel, or **Style** to set appearance that does not come from graph data. A channel accepts only compatible result types and targets. ## Node channels [Section titled “Node channels”](#node-channels) | Channel | Accepts | Use | | ------------ | ---------------------- | ---------------------------------------------------------------- | | Size | Numeric | Compare magnitude or importance. | | Color | Numeric, category, set | Show a scale, group, or selected set. | | Shape | Numeric, category, set | Separate categories or roles. | | Image | Category | Select an image from a categorical field. | | Node label | Numeric, category | Show an id, name, or attribute. | | Border color | Category, set | Color the node outline by category, or highlight set membership. | | Pie slices | Numeric, category | Show a small composition on a node. | ## Edge channels [Section titled “Edge channels”](#edge-channels) | Channel | Accepts | Use | | ---------- | ---------------------- | ---------------------------------- | | Edge color | Numeric, category, set | Show a relationship value or type. | | Edge width | Numeric | Show weight or another magnitude. | | Link label | Numeric, category | Label relationships. | | Edge style | Category | Distinguish relationship styles. | Directed edges can also use tapered or line treatments and source-to-target color gradients from **Style**. ## Layers [Section titled “Layers”](#layers) Layers add structure around the marks: * **Convex hull** and **Contour** show group or set boundaries. * **Heatmap** shows a numeric density or intensity field. * The minimap can switch between the full drawing and a group-level view. Bound **Convex hull** and **Heatmap** wells include an **Intensity** slider. Both start at 50%; 0% suppresses the layer and 100% makes it more prominent. The slider changes appearance without changing the bound source or its algorithm parameters. One gesture creates one undo step, and the value is saved in shared views and `.gph` projects. Heatmaps use a warm sequential palette adapted to the canvas theme. Hotspots show accumulated contributions from nearby nodes, rather than a direct color lookup for each node’s value. Hidden nodes and invalid numeric values do not contribute. Numeric bindings use scales; categorical bindings use bounded palettes. Saved edge sets can drive **Edge color** to highlight their exact membership. See [Explore and analyze](/docs/guides/explore/#encode-and-filter), the [workspace tour](/docs/getting-started/workspace/), and [Sources and algorithms](/docs/reference/sources-and-algorithms/).