Graph viewer
cvcdocdb.viewer is a read-only, interactive viewer for the data in any
backend (NetworkXGraph, Neo4jGraph, MemgraphGraph, JenaGraph):
node labels with counts, relation types, search by label, property filters and
free text with paging, node properties, and a graph canvas where you expand a
node’s neighbours. It never writes to the graph.
It runs in two ways: inside a Jupyter notebook, or as a web page served from your machine.
In a Jupyter notebook
Install the notebook extra, which adds anywidget:
pip install "cvcdocdb[notebook]"
and make show() the last expression of a cell:
from cvcdocdb.viewer import show
viewer = show(graph) # explore from the table and the search
viewer = show(graph, load_all=True) # small graphs: draw every node and relation
viewer = show(graph, language="ca") # interface in Catalan ("es": Spanish)
It is an anywidget widget, so it can run in JupyterLab, Jupyter Notebook 7, VS Code and Colab (so far it has been tested in JupyterLab), and it follows the notebook’s light or dark theme. No server is involved: the viewer asks the Python kernel for each piece of data, so the graph is live but needs a running kernel (a saved notebook reopened without one shows no data).
The viewer reads the graph when you ask it for something. After changing the
graph from Python, call refresh() to update every view of it:
graph.insertNode(Node(pk={"doc": "DOC-002"}, main_label="Document"))
viewer.refresh()
Closing the graph empties it, and the viewer with it, so close it only when you
are done. WeakNode and Nested WeakNode (Interactive) combines the viewer
with an ipywidgets control panel that edits the graph.
In the browser
The viewer is also a small web server that needs nothing beyond the standard library:
python -m cvcdocdb.viewer path/to/graph.pkl # a NetworkXGraph pickle
python -m cvcdocdb.viewer --backend neo4j --url bolt://localhost:7687 --user neo4j
python -m cvcdocdb.viewer --backend jena --url http://localhost:3030/ds
The remote password is read from CVCDOCDB_PASSWORD (or asked for). The
server binds to 127.0.0.1:8765 by default (--host, --port); use
--token (or CVCDOCDB_VIEWER_TOKEN) to require a token on the API, and
--no-browser not to open the page, and --language ca (or es) for
the interface texts in Catalan or Spanish (the data isn’t translated). From
Python:
from cvcdocdb.viewer import serve_viewer
serve_viewer(graph) # http://127.0.0.1:8765/, until Ctrl+C
Security
The viewer only reads, through the store’s public API, and it validates and caps every input. The server accepts
GETrequests only.The server binds to a loopback address by default and then only answers requests addressed to a loopback name. Binding another address exposes the data to the network: set a token.
In a notebook, the page runs in a sandboxed
iframewith no network access and no access to the notebook page.The page uses a bundled copy of cytoscape.js (MIT), so it makes no requests to other sites.
API
- cvcdocdb.viewer.show(store, height=650, load_all=False, language='en')[source]
The viewer for store as a Jupyter widget; make it a cell’s last expression to display it.
- Parameters:
store (Any) – The graph store to browse, or a configured
GraphViewerData(to change its limits).height (int) – Height of the viewer in pixels.
load_all (bool) – Show the whole graph on the canvas (up to 300 nodes), for small graphs such as a tutorial’s.
language (str) – Interface language:
"en"(default),"ca"or"es".
- Return type:
Call
refresh()on the returned widget after changing the graph.- Returns:
- Raises:
ImportError – If anywidget isn’t installed (
pip install 'cvcdocdb[notebook]').- Parameters:
- Return type:
The graph viewer as a Jupyter widget (needs pip install cvcdocdb[notebook]).
from cvcdocdb.viewer import show
show(graph) # last expression of a cell
No HTTP server is involved, so it can run wherever anywidget does (JupyterLab,
Notebook 7, VS Code, Colab; tested in JupyterLab). The viewer page runs in a sandboxed iframe
that has no network access; it asks the widget for data with
postMessage, the widget forwards the request to the kernel, and the
kernel answers it with cvcdocdb.viewer.api.handle_api() — the same
read-only API the HTTP server exposes. The graph is live: it needs a running
kernel, and a saved notebook reopened without one shows no data.
- class cvcdocdb.viewer.notebook.GraphViewerWidget(*args, **kwargs)[source]
Read-only graph viewer for store, displayed in a notebook cell.
- Parameters:
store (Union[GraphStore, GraphViewerData]) – The graph store to browse, or a configured
GraphViewerData(to change its limits).height (int) – Height of the viewer in pixels.
load_all (bool) – Show the whole graph on the canvas (up to 300 nodes) and keep it whole on
refresh()— for small graphs, such as a tutorial’s.language (str) – Interface language:
"en"(default),"ca"or"es".kwargs (Any)
The viewer reads the store when the user asks for something. After changing the graph from Python, call
refresh()to update it.- height
- load_all
- __init__(store, height=650, load_all=False, language='en', **kwargs)[source]
- Parameters:
store (GraphStore | GraphViewerData)
height (int)
load_all (bool)
language (str)
kwargs (Any)
- Return type:
None
- cvcdocdb.viewer.notebook.build_page(layout='classic', load_all=False, accent=None, extra_css=None, language='en')[source]
The viewer page with its styles and scripts inlined, set to ask its parent window for data instead of the HTTP API.
- Parameters:
layout (str) –
"classic"or"light"(see the module docs).load_all (bool) – Draw the whole graph on start (up to 300 nodes).
accent (str | None) – Accent colour,
#RGBor#RRGGBB.extra_css (str | None) – A stylesheet added after the viewer’s own (host look).
language (str) – Interface language, one of
LANGUAGES.
- Raises:
ValueError – On an unknown layout or language, an invalid accent or extra_css.
- Return type:
HTTP server of the graph viewer (standard library only).
Routes (GET only — anything else gets 405):
/— the viewer page;/static/<file>— its scripts and styles./api/summary—GraphViewerData.summary()./api/nodes?label=&q=&f=prop:op:value&offset=&limit=—GraphViewerData.search()(fmay be repeated; the value is everything after the second:)./api/nodes/<id>—GraphViewerData.node()./api/nodes/<id>/neighbours?direction=&type=&limit=—GraphViewerData.neighbours()./api/relations?ids=<id>&ids=<id>...—GraphViewerData.relations_among().
When a token is configured, every /api request must carry it as
?token= or Authorization: Bearer <token> (401 otherwise). The
page and its static files hold no data and are served without it; the page
forwards the token from its own URL to the API.
The server binds to 127.0.0.1 by default. While bound to a loopback
address it only answers requests whose Host is a loopback name, which
blocks DNS-rebinding attacks from web pages open in the same browser.
- class cvcdocdb.viewer.server.ViewerServer(data, host, port, token, layout='classic', load_all=False, accent=None, extra_css=None, language='en')[source]
The viewer’s HTTP server;
urlis the address to open.- Parameters:
- daemon_threads = True
- cvcdocdb.viewer.server.create_server(store, host='127.0.0.1', port=8765, token=None, layout='classic', load_all=False, accent=None, extra_css=None, language='en')[source]
Build (but don’t start) the viewer server for store.
- Parameters:
store (GraphStore | GraphViewerData) – The graph store to browse, or a configured
GraphViewerData(to change its limits).host (str) – Address to bind;
127.0.0.1by default. Binding anything else exposes the data to the network — set a token.port (int) – TCP port;
0picks a free one.token (str | None) – If set, required by every
/apirequest.layout (str) –
"classic"or"light"(graph first; seecvcdocdb.viewer.page).load_all (bool) – Draw the whole graph on start (up to 300 nodes).
accent (str | None) – Accent colour of the page,
#RGBor#RRGGBB.extra_css (str | None) – A stylesheet added after the viewer’s own (your look).
language (str) – Interface language:
"en","ca"or"es".
- Returns:
A
ViewerServer; callserve_forever()to run it.- Return type:
- cvcdocdb.viewer.server.serve_viewer(store, host='127.0.0.1', port=8765, token=None, open_browser=False, layout='classic', load_all=False, accent=None, extra_css=None, language='en')[source]
Serve the read-only viewer for store until interrupted (Ctrl+C).
- Parameters:
store (GraphStore | GraphViewerData) – The graph store to browse (see
create_server()).host (str) – Address to bind;
127.0.0.1by default.port (int) – TCP port (default 8765);
0picks a free one.token (str | None) – If set, required by every
/apirequest.open_browser (bool) – Open the page in the default browser.
layout (str) – Page options (see
create_server()).load_all (bool) – Page options (see
create_server()).accent (str | None) – Page options (see
create_server()).extra_css (str | None) – Page options (see
create_server()).language (str) – Page options (see
create_server()).
- Return type:
None
The store is not closed on exit: it belongs to the caller.
Read-only, backend-agnostic data layer of the graph viewer.
GraphViewerData answers the questions the viewer’s page asks — which
labels and relation types exist, which nodes match some filters, what a node
holds and what it is connected to — using only the public
GraphStore API:
dict filters through
GraphStore.query()(main_label,$in,$ne,$gt…,$exists,$regex), which every backend translates itself (bound parameters on Neo4j/Memgraph, Python matching on NetworkX/Jena);a few fixed read-only Cypher statements with
$parametersfor counts, node detail and neighbours (Neo4j/Memgraph run them natively; NetworkXGraph and JenaGraph run them withcvcdocdb.nx_cypher).
User input never becomes query text: labels, property names and relation types are checked against an identifier pattern and passed as filter keys or parameters, values are always bound, and “contains” uses an escaped pattern. Every result is capped (page size, scan size, neighbours), so a large remote graph can’t be pulled whole by one request.
Calls are serialised with a lock: the server is multi-threaded and a Neo4j session isn’t thread-safe.
- class cvcdocdb.viewer.data.GraphViewerData(store, max_page_size=200, max_scan=5000, max_neighbours=500, max_relations=2000)[source]
Read-only browsing operations over any
GraphStore.- Parameters:
store (GraphStore) – An open graph store (
NetworkXGraph,Neo4jGraph,MemgraphGraph,JenaGraph…). It is only read.max_page_size (int) – Most results
search()returns per page.max_scan (int) – Most nodes one
search()reads from the store; the total it reports is a lower bound past this.max_neighbours (int) – Most relations
neighbours()returns.max_relations (int) – Most relations
relations_among()returns.
- __init__(store, max_page_size=200, max_scan=5000, max_neighbours=500, max_relations=2000)[source]
- Parameters:
store (GraphStore)
max_page_size (int)
max_scan (int)
max_neighbours (int)
max_relations (int)
- Return type:
None
- search(label=None, filters=(), text=None, offset=0, limit=50, labels=None)[source]
Find nodes by main label, property filters and free text.
- Parameters:
label (str | None) – Main label to restrict to.
filters (Sequence[Tuple[str, str, Any]]) –
(property, operator, value)triples, all of which must hold. Operators:eq,ne(numbers/booleans in value also match typed values),contains(case-insensitive substring),gt/gte/lt/lte,exists(value ignored).text (str | None) – Case-insensitive text looked for in every string property.
offset (int) – Results to skip (paging).
limit (int) – Results to return; capped at
max_page_size.labels (Sequence[str] | None) – Main labels to restrict to (any of them; at most
MAX_LABELS), together with label if both are given.
- Returns:
A dict with
items(node summaries),total,total_is_exact,offsetandlimit. At mostmax_scannodes are read, so past thattotalis a lower bound andtotal_is_exactis False.- Raises:
InvalidRequest – On an invalid label, property, operator or range.
- Return type:
- node(node_id)[source]
All the labels and properties of one node.
- Raises:
InvalidRequest – If node_id isn’t an integer.
NodeNotFound – If there is no such node.
- Parameters:
node_id (int)
- Return type:
- neighbours(node_id, direction='both', relation_type=None, limit=50)[source]
Relations of one node and the nodes at their other end.
- Parameters:
- Returns:
A dict with
node(its summary),relations(each withtype,direction,source,target,propertiesandneighbour, the other node’s summary) andtruncated.- Raises:
InvalidRequest – On an invalid argument.
NodeNotFound – If there is no such node.
- Return type:
- relations_among(node_ids)[source]
The relations whose two ends are both in node_ids, in one query.
Lets a client draw the edges of a whole canvas without asking for the neighbours of every node.
- Parameters:
node_ids (Sequence[int]) – Node ids (at most
MAX_RELATION_IDS); unknown ids are ignored.- Returns:
A dict with
relations(each withtype,source,targetandproperties) andtruncated, true when there were more thanmax_relations.- Raises:
InvalidRequest – On a non-integer id or too many ids.
- Return type:
- exception cvcdocdb.viewer.data.InvalidRequest[source]
A viewer request had an invalid or out-of-range parameter.
- cvcdocdb.viewer.data.FILTER_OPERATORS = ('eq', 'ne', 'contains', 'gt', 'gte', 'lt', 'lte', 'exists')
Filter operators accepted by
GraphViewerData.search().