diff --git a/.github/workflows/gds-integration-tests.yml b/.github/workflows/gds-integration-tests.yml index 21034a7d..53a94b86 100644 --- a/.github/workflows/gds-integration-tests.yml +++ b/.github/workflows/gds-integration-tests.yml @@ -33,11 +33,11 @@ jobs: with: python-version: "3.11" enable-cache: true - - run: uv sync --group dev --extra pandas --extra neo4j --extra gds --extra snowflake + - run: uv sync --group dev --extra pandas --extra neo4j --extra gds - name: Run tests env: AURA_API_CLIENT_ID: 4V1HYCYEeoU4dSxThKnBeLvE2U4hSphx AURA_API_CLIENT_SECRET: ${{ secrets.AURA_API_CLIENT_SECRET }} - AURA_API_TENANT_ID: eee7ec28-6b1a-5286-8e3a-3362cc1c4c78 + AURA_API_PROJECT_ID: 3f8df5e7-4800-4d4f-ad1d-2d044dfd587c run: uv run pytest tests/ --include-neo4j-and-gds diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 8d7cc821..99e324f1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] defaults: run: diff --git a/changelog.md b/changelog.md index eb2810e6..7db9a34d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,17 +1,21 @@ -# Changes in 1.4.0 +# Changes in 1.5.0 ## Breaking changes ## New features +* Add `GraphWidget` methods to change render options in place without re-rendering: `set_layout`, `set_zoom`, `set_pan`, `set_renderer`, and `set_show_layout_button` +* Add `GraphWidget` methods to change styling in place without re-rendering such as `color_relationships` + ## Bug fixes -- Fixed a bug in displaying the `Download`, `Selection` and `Layout` buttons, which was introduced in 1.2.0. +* Warn when relationships reference node ids that are not in the graph. It is configurable via the `on_dangling` parameter (`"warn"` (default), `"error"`, or `"none"`) on `render`, `render_widget`, and `GraphWidget.add_data` ## Improvements -- Support `neo4j.EagerResult` in the `from_neo4j` integration which is the default return type by `neo4j.Driver.execute_query()`. -- Detect light/dark theme changes and adapt rendering unless theme was explicitly set. Before the theme would only be checked on the first render. - +* Support Python 3.14 +* Support Aura Graph Analytics +* Support `gds.v2` endpoints +* Use typed options field in `GraphWidget` ## Other changes diff --git a/docs/antora/modules/ROOT/pages/rendering.adoc b/docs/antora/modules/ROOT/pages/rendering.adoc index e3ae6181..a76ae2cc 100644 --- a/docs/antora/modules/ROOT/pages/rendering.adoc +++ b/docs/antora/modules/ROOT/pages/rendering.adoc @@ -38,6 +38,12 @@ It defaults to 10.000, because rendering a large number of nodes can be slow and However, you can increase this value if you are confident that your environment can handle the scale. In this case you might also want to pass `Renderer.WEB_GL` as the `renderer` to improve performance. +Relationships whose `source` or `target` is not part of the graph's nodes ("dangling" relationships) cannot be +drawn, and a graph containing them may appear empty. +By default `render` emits a warning that lists the offending relationships so the problem is not silent. +You can change this with the `on_dangling` parameter: pass `"error"` to raise a `ValueError` instead, or `"none"` +to skip the check entirely. + By default a tooltip showing IDs and properties will be shown when mouse hovering over a node or relationship. But you can disable this by passing `show_hover_tooltip=False`. @@ -60,4 +66,4 @@ html = VG.render(...) with open("my_graph.html", "w") as f: f.write(html.data) ----- \ No newline at end of file +---- diff --git a/docs/source/api-reference/widget.rst b/docs/source/api-reference/widget.rst new file mode 100644 index 00000000..8ec6510f --- /dev/null +++ b/docs/source/api-reference/widget.rst @@ -0,0 +1,2 @@ +.. autoclass:: neo4j_viz.GraphWidget + :members: diff --git a/examples/gds-example.ipynb b/examples/gds-example.ipynb index 29b1387c..cd7ab333 100644 --- a/examples/gds-example.ipynb +++ b/examples/gds-example.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "133e0a86", "metadata": {}, "source": [ "# Visualizing Neo4j Graph Data Science (GDS) Graphs" @@ -10,54 +11,112 @@ { "cell_type": "code", "execution_count": null, + "id": "eab42b4e", "metadata": {}, "outputs": [], "source": [ "%pip install graphdatascience\n", - "%pip install matplotlib" + "%pip install matplotlib\n", + "%pip install python-dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1dde3aae", + "metadata": {}, + "outputs": [], + "source": [ + "import dotenv\n", + "\n", + "dotenv.load_dotenv()" ] }, { "cell_type": "markdown", + "id": "d9daded3", "metadata": {}, "source": [ - "## Setup GDS graph" + "## Setup GDS graph\n", + "\n", + "To use GDS, you can either use GDS as a plugin or Aura Graph Analytics.\n", + "In the following, you can choose:\n", + "\n", + " * Provide Aura API credentials and and use Aura Graph Analytics.\n", + " * Use Neo4j + GDS Plugin.\n", + "\n", + "For more information, see the [GDS documentation](https://neo4j.com/docs/graph-data-science/current/installation/)." ] }, { "cell_type": "code", "execution_count": null, + "id": "9ca9a1de", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", + "from graphdatascience.session import (\n", + " GdsSessions,\n", + " DbmsConnectionInfo,\n", + " AuraAPICredentials,\n", + " SessionMemory,\n", + ")\n", "from graphdatascience import GraphDataScience\n", "\n", "# Get Neo4j DB URI, credentials and name from environment if applicable\n", - "NEO4J_URI = os.environ.get(\"NEO4J_URI\", \"bolt://localhost:7687\")\n", - "NEO4J_AUTH = (\"neo4j\", None)\n", - "NEO4J_DB = os.environ.get(\"NEO4J_DB\", \"neo4j\")\n", - "if os.environ.get(\"NEO4J_USER\") and os.environ.get(\"NEO4J_PASSWORD\"):\n", - " NEO4J_AUTH = (\n", - " os.environ.get(\"NEO4J_USER\"),\n", - " os.environ.get(\"NEO4J_PASSWORD\"),\n", + "db_connection = DbmsConnectionInfo(\n", + " aura_instance_id=os.environ.get(\"AURA_INSTANCEID\"),\n", + " username=os.environ.get(\"NEO4J_USERNAME\", \"neo4j\"),\n", + " password=os.environ.get(\"NEO4J_PASSWORD\"),\n", + " uri=os.environ[\"NEO4J_URI\"],\n", + ")\n", + "\n", + "session_name = \"neo4j-viz-gds-example\"\n", + "if os.environ.get(\"AURA_API_CLIENT_ID\"):\n", + " # Use Aura Graph Analytics\n", + " sessions = GdsSessions(\n", + " api_credentials=AuraAPICredentials(\n", + " client_id=os.environ[\"AURA_API_CLIENT_ID\"],\n", + " client_secret=os.environ[\"AURA_API_CLIENT_SECRET\"],\n", + " project_id=os.environ.get(\"AURA_API_PROJECT_ID\"),\n", + " )\n", + " )\n", + " gds = sessions.get_or_create(\n", + " session_name=session_name,\n", + " memory=SessionMemory.m_2GB,\n", + " db_connection=db_connection,\n", " )\n", - "gds = GraphDataScience(NEO4J_URI, auth=NEO4J_AUTH, database=NEO4J_DB)" + "else:\n", + " # Use GDS Plugin\n", + " sessions = None\n", + " gds = GraphDataScience(\n", + " endpoint=db_connection.get_uri(),\n", + " auth=(db_connection.username, db_connection.password),\n", + " )" ] }, { "cell_type": "code", "execution_count": null, + "id": "735411fa", "metadata": {}, "outputs": [], "source": [ "G = gds.graph.load_cora(graph_name=\"cora\")" ] }, + { + "cell_type": "markdown", + "id": "8cbc843a", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": null, + "id": "35a65700", "metadata": {}, "outputs": [], "source": [ @@ -71,6 +130,7 @@ }, { "cell_type": "markdown", + "id": "cde09804", "metadata": {}, "source": [ "## Visualization" @@ -79,1648 +139,32 @@ { "cell_type": "code", "execution_count": null, + "id": "cab5ffab", "metadata": {}, "outputs": [], "source": [ "from neo4j_viz.gds import from_gds\n", "\n", - "VG = from_gds(gds, G, max_node_count=500)\n", - "str(VG)" + "VG = from_gds(\n", + " gds,\n", + " G,\n", + " max_node_count=100,\n", + ")" ] }, { "cell_type": "code", - "execution_count": 6, - "metadata": { - "tags": [ - "preserve-output" - ] - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "
\n", - " \n", - " \n", - "Expected window.__NEO4J_VIZ_DATA__ to be set.
This page should be generated by neo4j_viz's render() method.