ttl3d
Turn any Turtle / RDF graph into a single self-contained HTML page with a 3D view (shaded spheres) and a 2D view (a flat canvas), permanent labels, predicate labels on the edges, a clickable legend, label search, and a detail card for every node. The page embeds all of its JavaScript, so it opens from a file, works offline, and never sends your data anywhere.
Why
Ontology editors show class trees. SPARQL browsers show one neighbourhood at a time. Sometimes you want the whole graph on screen at once: to see which modules actually connect, which file asserted which edge, or which node everything hangs from. ttl3d was written for exactly that while building a multi-file ontology, then generalized to any RDF input.
Install
Python 3.10 or newer.
pip install git+https://github.com/soheilabadifard/TTL_to_3D
Or clone the repository and run pip install -e .. The dependencies are
rdflib, networkx, numpy and scipy, all available on conda-forge as
well.
Quick start
ttl3d examples/solar-system.ttl examples/solar-system-missions.ttl -o solar.html
Open solar.html in any browser. Pass several files and they merge into one
graph; the default colouring then shows which file declared each node and
which file asserted each edge.
Without installing, run the module from a clone: python -m ttl3d ....
Options
| Option | Values | Meaning |
|---|---|---|
-o, --out |
path | Output file. Default: <first file stem>-<view>.html in the current directory. |
--color-by |
file (default), type, namespace |
What node colours and the legend mean. In file mode edges also take the colour of the file asserting them. |
--layout |
auto (default), stress, force |
stress pins every node to a precomputed Kamada-Kawai position, one layout per view (3D and 2D); force runs the live simulation. auto picks stress up to 1000 nodes. |
--labels |
auto (default), always, hover |
Permanent label sprites. auto keeps node labels up to 800 nodes and edge labels up to 800 links; hover leaves tooltips only. |
--view |
3d (default), 2d |
Starting view. The page has a 3D | 2D switch either way, and each view is pinned to its own stress layout. |
--title |
text | Page title. Default: the first file's stem. |
--lang |
language tag, default en |
Preferred language for labels and definitions. Untagged literals rank next; other languages become synonyms on the card. Matched exactly: --lang en does not select @en-GB. |
--format |
rdflib parser name | Force a parser for every input (turtle, xml, nt, json-ld, ...). Default: guess from the extension, then try Turtle. |
--type-links |
flag | Draw rdf:type as an edge from each instance to its class instead of listing it on the card only. |
--attribute-preds |
prefix:local, full IRI, or <urn:...> |
Predicates to keep off the picture and show on the card, e.g. foaf:homepage,rdfs:seeAlso. Repeatable; wrap a URN or mailto in angle brackets. |
--version |
flag | Print ttl3d <version> and exit. |
Input formats are guessed from the extension (.ttl, .nt, .n3, .rdf,
.owl, .jsonld, ...) through rdflib.
What becomes a node, what becomes a link
- A node is every IRI that is the subject of some triple or the object of a
link, except IRIs of the RDF, RDFS, OWL and XSD vocabularies and ontology
headers (
owl:Ontology). Blank nodes are skipped. - A link is an IRI-to-IRI triple whose predicate is a relation.
rdf:type,owl:imports,owl:versionIRI,owl:priorVersion,rdfs:isDefinedBy,prov:wasDerivedFromanddcterms:sourcedescribe the node instead and go to its card. Parallel edges collapse into one link listing every predicate. - Label:
rdfs:label, elseskos:prefLabel, else the local name. Definition:skos:definition, elserdfs:comment, elsedcterms:description. Every other literal lands in the card's property table. - Each node remembers the file that first declares it; each link remembers the files asserting it. A file that only adds edges between other files' nodes still owns something visible.
- An edge asserted in both directions (
:Luna :orbits :Earthand:Earth :hasMoon :Luna) is one link labelledorbits ⇄ hasMoon, without an arrowhead. - Labels follow
--lang: the requested language first, then untagged literals, then anything else; every other label value becomes a synonym on the card, and ardfs:commentthat loses to askos:definitionstill appears in the property table.
In the page
- Legend rows filter inclusively: a selected group keeps its own nodes, their direct neighbours, and the endpoints of every edge it asserts.
- The
3D | 2Dswitch rebuilds the picture in the other view. Legend filters, the search text and the open card carry over, and in pinned mode each view has its own precomputed stress layout. A browser without WebGL opens in 2D and says so. - Search dims everything whose label does not match.
- Click a node for its card: types, file, namespace, definition, synonyms, properties, incoming and outgoing relations (clickable), and sources.
- The label toggles rebuild the scene, so a big graph can start without sprites and switch them on later.
- "Free-float physics" releases pinned nodes into the live force layout and pins them back where they were.
- No fog: white fog made distant nodes vanish on zoom-out.
- Past twelve groups only the eleven largest keep a colour; the rest share one grey "other" row that filters them together.
Scale
The stress layout is quadratic in the number of nodes and label sprites are scene objects, so the automatic modes degrade instead of freezing:
| Graph size | Layout | Labels |
|---|---|---|
| up to 800 nodes / 800 links | pinned stress layout | nodes and edges |
| up to 1000 nodes | pinned stress layout | tooltips for the layer over its threshold |
| larger | live force simulation | tooltips; switch sprites on from the panel |
The stress layout is computed twice, once per view, so a pinned page takes about twice the layout time of 0.1.0.
Force the behaviour you want with --layout and --labels.
Limitations
- Blank nodes are skipped, so OWL restrictions and RDF lists do not appear.
- No reasoning: only asserted triples are drawn.
- One page holds one graph; there is no incremental loading.
- The stress layout is quadratic:
--layout stresson graphs far beyond 1000 nodes can take minutes, and the CLI says so on stderr.
Development
pip install -e .[test]
python -m pytest -q
The tests cover the loader, the node and link rules, the layout, the generated page, the vendored
bundle's pins and licence notices, and the command line. The viewer's CSS and JavaScript live in ttl3d/viewer.css, ttl3d/viewer.js (shared by both views), ttl3d/viewer-3d.js and ttl3d/viewer-2d.js (one renderer per view)
and are inlined into every page; when node is installed the suite syntax-checks the
JavaScript. pip install -e .[browser] && playwright install chromium enables the headless
browser tests that load the demo pages, switch views and assert zero console errors
(TTL3D_BROWSER=firefox or webkit picks another engine). CI runs the suite on Linux, Windows
and macOS, the browser tests on Chromium, Firefox and WebKit, and checks that the demo pages
built on the three systems are identical apart from the pinned layout's last digits. The vendored JavaScript
in ttl3d/vendor/ bundles 3d-force-graph, force-graph, three-spritetext and one shared three.js;
VENDOR.md there has the rebuild recipe and LICENSES.md the upstream notices.
License
MIT. The vendored JavaScript is under permissive licences (MIT, ISC and others); ttl3d/vendor/LICENSES.md lists every bundled package with its licence and copyright line.
Metadata
Release files for ttl3d 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ttl3d-0.2.2.tar.gz | 499.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ttl3d-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 983.9 kB
Release files / ttl3d-0.2.2.tar.gz
| Download URL | ttl3d-0.2.2.tar.gz |
|---|---|
| Size | 499.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cfe1f0dd04b4c6b43a192283238dbcd4c553268d8b2b053ea05607bd1996e57
|
|
BLAKE2b-256 checksum How to use checksums |
89d086b3d12017d46d66498b6ed77e96cdb4a08e94969f133175e46ddc3b2c1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / ttl3d-0.2.2-py3-none-any.whl
| Download URL | ttl3d-0.2.2-py3-none-any.whl |
|---|---|
| Size | 484.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ffaf56a03642a8df647bb4b057f2531f75fafc9cfb4b250a24a356619d1a3f91
|
|
BLAKE2b-256 checksum How to use checksums |
078ddcb97391bee9131d6f44943023765fa49d815dc420707994347e8192ab52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log