CSV nodes + edges pair
Two files: nodes.csv carries per-node properties, edges.csv carries the connections. This is the right format when your spreadsheet has columns like age, category, tags on each node.
Minimum viable example
Section titled “Minimum viable example”id,label,x,y,age:number,joined:date,active:boolean,tags:string[]n1,Alice,10,20,34,2021-03-15,true,engineer|foundern2,Bob,-5,8,28,2023-11-02,false,designern3,Carol,100,-30,45,2019-07-20,true,source,target,weightn1,n2,0.8n2,n3,1.2Download nodes.csv · Download edges.csv
TSV works identically — save with tab separators and rename to .tsv.
How to drop
Section titled “How to drop”Labelled slots (recommended). The drop zone has two slots — Nodes and Edges. Drop one file into each; the graph loads the moment both slots are filled. Filenames don’t matter.
Multi-select. Drag both files onto the drop zone at once. Knotviz pairs them by filename — one must contain nodes, the other edges.
Nodes file — columns
Section titled “Nodes file — columns”| Column | Required | Notes |
|---|---|---|
id | yes | Unique string. |
label | no | Display text. Also exposed as a filterable property (see the label column below). |
x, y | no | Numeric positions. Preserved only if all nodes have them (positions). |
| Any other | no | Per-node property. Typed via :type suffix or inferred from sample values. |
Typed column headers
Section titled “Typed column headers”Append :type to a column name to declare its type explicitly. Five recognised types:
age:numberjoined:dateactive:booleanhomepage:stringtags:string[]Inference fills in untyped columns. For string[] specifically, Knotviz auto-detects the type when every non-empty cell in the column contains a pipe — so a column literally named tags with values a|b, c|d, e|f becomes string[] without a suffix. If some cells are prose that happens to contain a pipe ("a | b" as a sentence), use :string to force the literal interpretation.
Full inference rules and edge cases: Shared conventions → Type inference.
The label column
Section titled “The label column”label is dual-role: it drives the node’s display label and is exposed as a filterable / colourable property. So if your data has a real label column (e.g. taxonomy names, category labels), you keep it for filters and colour-encoding rather than having the display layer silently absorb it.
id, x, and y stay structural-only — they never appear in the Filter or Analyze panels.
Edges file — columns
Section titled “Edges file — columns”Same shape as the CSV edge list format. Unknown source / target ids (ones not in the nodes file) skip the edge with a console warning.
Data-quality warnings
Section titled “Data-quality warnings”If a typed column has cells that don’t match the declared type, Knotviz drops the offending cell and counts it. Before the graph loads, a modal summarises per-column failures so you can fix the source file:
age— 300 nodes failed (e.g."thirty-four")joined— 12 nodes failed (e.g."March 15 2021")
Cancel if the numbers look wrong, fix the source, re-drop. Load anyway if you’re comfortable treating the failed cells as missing — they’ll back-fill with the type default.
Coming from another tool
Section titled “Coming from another tool”Pandas
Section titled “Pandas”Two DataFrames — nodes_df with an id column, edges_df with source and target.
# Keep only the columns you want as properties; drop anything else first.nodes_df.to_csv("nodes.csv", index=False)edges_df[["source", "target", "weight"]].to_csv("edges.csv", index=False)Add :type suffixes to column headers if inference won’t pick the type you want:
nodes_df.rename(columns={"age": "age:number", "joined": "joined:date", "tags": "tags:string[]"}) \ .to_csv("nodes.csv", index=False)PostgreSQL
Section titled “PostgreSQL”Two \copy statements — one per file. Put the type suffix in the query alias.
psql -d mydb -c "\copy ( SELECT id, name AS label, age AS \"age:number\", joined AS \"joined:date\" FROM people) TO 'nodes.csv' CSV HEADER"
psql -d mydb -c "\copy ( SELECT source_id AS source, target_id AS target, weight FROM edges) TO 'edges.csv' CSV HEADER"NetworkX
Section titled “NetworkX”Walk nodes and edges separately.
import csv, networkx as nx
# Decide up front which node attributes become property columns.prop_keys = ["age", "community", "joined"]
with open("nodes.csv", "w", newline="") as f: w = csv.writer(f) w.writerow(["id", "label", *prop_keys]) for node_id, data in G.nodes(data=True): w.writerow([node_id, data.get("label", node_id), *(data.get(k) for k in prop_keys)])
with open("edges.csv", "w", newline="") as f: w = csv.writer(f) w.writerow(["source", "target", "weight"]) for u, v, data in G.edges(data=True): w.writerow([u, v, data.get("weight", 1)])Gotchas
Section titled “Gotchas”- Edge endpoints must match ids in the nodes file. Unknown ids are skipped with a console warning.
- Pipe cells in non-array columns. A column with pipes only in some cells infers as
string(pipes treated literally). A column with pipes in every non-empty cell infers asstring[]. When in doubt, declare with:stringor:string[]. - Leading-zero strings stay strings.
0012as an id or property value is kept as"0012"(zip codes, phone numbers). Force numeric with:numberif you want12. - Declared-but-empty columns survive. A column whose every cell is empty is still registered — it defaults to
numberand back-fills with0. Example:id,label,noteswith everynotescell blank will still produce anotesnumber filter in the UI. You’ll see a pre-load modal reporting the replacement count. - A column called
labelisn’t lost. It shows up as both the display label and a filterable property.