diff --git a/docs/.gitignore b/docs/.gitignore index 378eac25..d6846fe5 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,5 @@ build + +# Antora docs +antora/package-lock.json +antora/node_modules/ \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf1..f2d75902 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. -SPHINXOPTS ?= +SPHINXOPTS ?= -b dirhtml SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build @@ -14,7 +14,6 @@ help: .PHONY: help Makefile -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +# Use this format to make use of the "-b dirhtml" switch %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)" diff --git a/docs/antora/antora.yml b/docs/antora/antora.yml new file mode 100644 index 00000000..319e2218 --- /dev/null +++ b/docs/antora/antora.yml @@ -0,0 +1,12 @@ +name: python-graph-visualization +title: Neo4j Python Graph Visualization +version: '1' +start_page: ROOT:index.adoc +nav: +- modules/ROOT/content-nav.adoc +asciidoc: + attributes: + # Version needs to be updated manually for now + docs-version: '1' + api-docs-uri: '{neo4j-docs-base-uri}/python-graph-visualization/{docs-version}/reference/api-reference' + tutorials-docs-uri: '{neo4j-docs-base-uri}/python-graph-visualization/{docs-version}/reference/tutorials' \ No newline at end of file diff --git a/docs/antora/modules/ROOT/content-nav.adoc b/docs/antora/modules/ROOT/content-nav.adoc new file mode 100644 index 00000000..1d5e0b39 --- /dev/null +++ b/docs/antora/modules/ROOT/content-nav.adoc @@ -0,0 +1,8 @@ +* xref:index.adoc[] +* xref:installation.adoc[] +* xref:getting-started.adoc[] +* xref:integration.adoc[] +* xref:rendering.adoc[] +* xref:customizing.adoc[] +* link:{tutorials-docs-uri}[Tutorials] +* link:{api-docs-uri}[API reference] \ No newline at end of file diff --git a/docs/antora/modules/ROOT/images/graph_00ff5513.png b/docs/antora/modules/ROOT/images/graph_00ff5513.png new file mode 100644 index 00000000..7b5697c9 Binary files /dev/null and b/docs/antora/modules/ROOT/images/graph_00ff5513.png differ diff --git a/docs/antora/modules/ROOT/images/graph_2120034f.png b/docs/antora/modules/ROOT/images/graph_2120034f.png new file mode 100644 index 00000000..10353d73 Binary files /dev/null and b/docs/antora/modules/ROOT/images/graph_2120034f.png differ diff --git a/docs/antora/modules/ROOT/pages/customizing.adoc b/docs/antora/modules/ROOT/pages/customizing.adoc new file mode 100644 index 00000000..a0692c0a --- /dev/null +++ b/docs/antora/modules/ROOT/pages/customizing.adoc @@ -0,0 +1,222 @@ += Customizing the visualization + +Once created, a link:{api-docs-uri}/visualization-graph[`VisualizationGraph` object] can be modified in various ways +to adjust what the visualization looks like the next time you render it. +In this section we will discuss how to color, size, and pin nodes, as well as how to directly modify nodes and +relationships of existing `VisualizationGraph` objects. + +If you have not yet created a `VisualizationGraph` object, refer to one of the following sections: + +* xref:getting-started.adoc[Getting started] for creating a visualization graph from scratch using `neo4j-viz` + primitives like link:{api-docs-uri}/node/[Node] and link:{api-docs-uri}/relationship/[Relationship] and + link:{api-docs-uri}/visualization-graph[`VisualizationGraph`] directly. +* xref:integration.adoc[] for importing data from a Pandas DataFrame or Neo4j GDS projection. + +== Setting node captions + +Node captions are the text labels displayed on nodes in the visualization. + +=== The `set_node_captions` method + +By calling the `neo4j_viz.VisualizationGraph.set_node_captions()` method, you can set node captions based on a node field (like `id`, `size`, etc.) or a node property (members of the `Node.properties` map). + +The method accepts an `override` parameter (default `True`) that controls whether to replace existing captions. +If `override=False`, only nodes without captions will be updated. + +Here is an example of setting node captions from a property: + +[source, python] +---- +# VG is a VisualizationGraph object with nodes that have a "name" property +VG.set_node_captions(property="name") +---- + +You can also set captions from a node field, and choose not to override existing captions: + +[source, python] +---- +# VG is a VisualizationGraph object +VG.set_node_captions(field="id", override=False) +---- + +For more complex scenarios where you need fallback logic or want to combine multiple properties, you can iterate over nodes directly: + +[source, python] +---- +# VG is a VisualizationGraph object +for node in VG.nodes: + caption = node.properties.get("name") or node.properties.get("title") or node.id + node.caption = str(caption) +---- + +== Coloring nodes + +Nodes can be colored directly by providing them with a color field, upon creation. +This can for example be done by passing a color as a string to the `color` parameter of the +link:{api-docs-uri}/node[Node] object. + +Alternatively, you can color nodes based on a field or property of the nodes after a `VisualizationGraph` object has been +created. + +=== The `color_nodes` method + +By calling the link:{api-docs-uri}/visualization-graph/#neo4j_viz.VisualizationGraph.color_nodes[`neo4j_viz.VisualizationGraph.color_nodes()`] method, you can color nodes based on a node field or property (members of the `Node.properties` map). + +It's possible to color the nodes based on a discrete or continuous color space (see link:{api-docs-uri}/colors[`ColorSpace`]). +In the discrete case, a new color from the `colors` provided is assigned to each unique value of the node field/property. +In the continuous case, the `colors` should be a list of colors representing a range that are used to +create a gradient of colors based on the values of the node field/property. + +By default the Neo4j color palette, that works for both light and dark mode, will be used. +If you want to use a different color palette, you can pass a dictionary or iterable of colors as the `colors` +parameter. +A color value can for example be either strings like "blue", or hexadecimal color codes like "#FF0000", or even a tuple of RGB values like (255, 0, 255). + +If some nodes already have a `color` set, you can choose whether or not to override it with the `override` +parameter. + +==== By discrete color space + +To not use the default colors, we can provide a list of custom colors based on the discrete node field "caption" to the `color_nodes` method: + +[source, python] +---- +from neo4j_viz.colors import ColorSpace + +# VG is a VisualizationGraph object +VG.color_nodes( + field="caption", + ["red", "#7fffd4", (255, 255, 255, 0.5), "hsl(270, 60%, 70%)"], + color_space=ColorSpace.DISCRETE +) +---- + +The full set of allowed values for colors are listed link:https://docs.pydantic.dev/2.0/usage/types/extra_types/color_types/[here]. + +Instead of defining your own colors, you could also for example use the color palettes from the link:https://jiffyclub.github.io/palettable/[`palettable` library] as in this snippet: + +[source, python] +---- +from palettable.wesanderson import Moonrise1_5 + +# VG is a VisualizationGraph object +VG.color_nodes(field="caption", Moonrise1_5.colors) # PropertyType.DISCRETE is default +---- + +In theses cases, all nodes with the same caption will get the same color. + +If there are fewer colors than unique values for the node `field` or `property` provided, the colors will be reused in a cycle. +To avoid that, you could use a larger palette or extend one with additional colors. +Refer to the link:/tutorials/gds-example[Visualizing Neo4j Graph Data Science (GDS) Graphs tutorial] for an example on how +to do the latter. + +==== By continuous color space + +To not use the default colors, we can provide a list of custom colors representing a range to the `color_nodes` method: + +[source, python] +---- +from neo4j_viz.colors import PropertyType + +# VG is a VisualizationGraph object +VG.color_nodes( + property="centrality_score", + [(255, 0, 0), (191, 64, 0), (128, 128, 0), (64, 191, 0), (0, 255, 0)] # From red to green + color_space=ColorSpace.CONTINUOUS +) +---- + +In this case, the nodes will be colored based on the value of the "centrality_score" property, with the lowest values being colored red and the highest values being colored green. +Since we only provided five colors in the range, the granularity of the gradient will be limited to five steps. + +`palettable` and `matplotlib` are great libraries to use to create custom color gradients. + +== Sizing nodes + +Nodes can be given a size directly by providing them with a size field, upon creation. +This can for example be done by passing a size as an integer to the `size` parameter of the link:{api-docs-uri}/node[Node] object. + +Alternatively, you can size nodes after a `VisualizationGraph` object has been created. + +=== The `resize_nodes` method + +By calling the link:{api-docs-uri}/visualization-graph/#neo4j_viz.VisualizationGraph.resize_nodes[`neo4j_viz.VisualizationGraph.resize_nodes()`] method, you can resize nodes by: + +* passing new nodes sizes as a dictionary `sizes`, mapping node IDs to sizes in pixels, or +* providing a tuple of two numbers `node_radius_min_max`: minimum and maximum radii (sizes) in pixels to which the + nodes will be scaled. + +Or you could provide both `sizes` and `node_radius_min_max`, in which case the dictionary will be used to first set +the sizes of the nodes, and then the minimum and maximum values of the tuple will be subsequently used to scale the +sizes to the provided range. + +If you provide only the `node_radius_min_max` parameter, the sizes of the nodes will be scaled such that the smallest +node will have the size of the first value, and the largest node will have the size of the second value. +The other nodes will be scaled linearly between these two values according to their relative size. +This can be useful if node sizes vary a lot, or are all very small or very big. + +In the following example, we resize the node with ID 42 to have a size of 88 pixels, and then scales all nodes to have +sizes between 5 and 20 pixels: + +[source, python] +---- +# VG is a VisualizationGraph object +VG.resize_nodes(sizes={42: 88}, node_radius_min_max=(5, 20)) +---- + +Note that means that also the node with ID 42 will be scaled to be between 5 and 20 pixels in size. + +== Pinning nodes + +Nodes can be pinned to their current position in the visualization, so that they will not be moved by the force-directed +layout algorithm. +This can be useful if you want to keep a node in a specific position, for example to highlight it. + +Nodes can be pinned directly upon creation. +This can for example be done by passing `pinned=True` to the link:{api-docs-uri}/node[Node] object. + +Alternatively, you can toggle node pinning after a `VisualizationGraph` object has been created. + +=== The `toggle_nodes_pinned` method + +By calling the link:{api-docs-uri}/visualization-graph/#neo4j_viz.VisualizationGraph.toggle_nodes_pinned[`neo4j_viz.VisualizationGraph.toggle_nodes_pinned()`] method, you can toggle whether nodes should be +pinned or not. +This method takes dictionary that maps node IDs to boolean values, where `True` means that the node is pinned, and +`False` means that the node is not pinned. + +In the following example, we pin the node with ID 1337 and unpin the node with ID 42: + +[source, python] +---- +# VG is a VisualizationGraph object +VG.toggle_nodes_pinned(1337: True, 42: False)}) +---- + +== Direct modification of nodes and relationships + +Nodes and relationships can also be modified directly by accessing the `nodes` and `relationships` fields of an +existing `VisualizationGraph` object. +These fields list of all the link:{api-docs-uri}/node[Nodes] and link:{api-docs-uri}/relationship[Relationships] in the graph, respectively. + +Each node and relationship has attributes that can be accessed and modified directly, as in the following example: + +[source, python] +---- +# VG is a VisualizationGraph object + +# Modify the first node and fifth relationship +VG.nodes[0].size = 10 +VG.nodes[0].properties["height"] = 170 +VG.relationships[4].caption = "BUYS" + +# Set the coordinates for all nodes from an existing property +for node in VG.nodes: + node.x = node.properties.get("x") + node.y = node.properties.get("y") + +# Change the caption size for all relationships +for relationship in VG.relationships: + relationship.caption_size = 15 +---- + +Any changes made to the nodes and relationships will be reflected in the next rendering of the graph. \ No newline at end of file diff --git a/docs/antora/modules/ROOT/pages/getting-started.adoc b/docs/antora/modules/ROOT/pages/getting-started.adoc new file mode 100644 index 00000000..6d570337 --- /dev/null +++ b/docs/antora/modules/ROOT/pages/getting-started.adoc @@ -0,0 +1,91 @@ += Getting started + +In this section, we will cover the very basics of creating a visualization graph using the `neo4j-viz` library. +We will use a small toy graph representing the purchase history of a few people and products. + +To follow along with the example below, please make sure you have xref:installation.adoc[installed the library] first. + +We start by instantiating the link:{api-docs-uri}/node/[Nodes] and link:{api-docs-uri}/relationship[Relationships] we want in our graph. +The only mandatory fields for a node are the “id”, and “source” and “target” for a relationship. +But the other fields can optionally be used to customize the appearance of the nodes and relationships in the visualization. + +Lastly we create a link:{api-docs-uri}/visualization-graph/[VisualizationGraph] object with the nodes and relationships we created, and call its `render` method to display the graph. + +[source, python, role=nocollapse] +---- +from neo4j_viz import Node, Relationship, VisualizationGraph + +nodes = [ + Node(id=0, size=10, caption="Person"), + Node(id=1, size=10, caption="Product"), + Node(id=2, size=20, caption="Product"), + Node(id=3, size=10, caption="Person"), + Node(id=4, size=10, caption="Product"), +] + +relationships = [ + Relationship( + source=0, + target=1, + caption="BUYS", + ), + Relationship( + source=0, + target=2, + caption="BUYS", + ), + Relationship( + source=3, + target=2, + caption="BUYS", + ), +] + +VG = VisualizationGraph(nodes=nodes, relationships=relationships) + +VG.render(initial_zoom=2) +---- + +[TIP] +==== +See the link:{tutorials-docs-uri}/getting-started/[Jupyter notebook] to interact with the graph. +==== + +image::graph_2120034f.png["Graph"] + +As we can see in the graph above, the radius of one of the nodes is larger than the others. +This is because we set the “size” field of the node to 20, while the others are set to 10. + +At this time all nodes have the same color. +If we want to distinguish between the different types of nodes, we can color them differently with the `color_nodes` method. +We can pass the field we want to use to color the nodes as an argument. +In this case, we will use the "caption" field. +Nodes with the same "caption" will have the same color. +We will use the default color scheme, which is the Neo4j color scheme. + +[source, python] +---- +VG.color_nodes(field="caption") + +VG.render(initial_zoom=2) +---- + +[TIP] +==== +See the link:{tutorials-docs-uri}/getting-started/[Jupyter notebook] to interact with the graph. +==== + +image::graph_00ff5513.png["Graph with colors"] + +We are now easily able to distinguish between the different types of nodes in the graph. + +== Next steps + +Now that we have covered the basics of creating and rendering a visualization graph, we can move on to more advanced topics. + +Here are some suggestions for what to do next: + +* Browse the link:{tutorials-docs-uri}[notebook examples] for more examples and use cases +* Learn about the xref:integration.adoc[integrations] with other libraries such as Neo4j GDS and Pandas +* Check out the xref:rendering.adoc[rendering options] and convenience methods of `VisualizationGraph` +* Read about our link:{api-docs-uri}/[API reference] diff --git a/docs/antora/modules/ROOT/pages/index.adoc b/docs/antora/modules/ROOT/pages/index.adoc new file mode 100644 index 00000000..9f2ef116 --- /dev/null +++ b/docs/antora/modules/ROOT/pages/index.adoc @@ -0,0 +1,10 @@ += Introduction + +This is the documentation for the `neo4j-viz` Python library by Neo4j. +The library allows you to visualize graph data interactively in Python using a simple API. + +The library wraps the link:https://neo4j.com/docs/nvl/current/[Neo4j Visualization JavaScript library (NVL)], and provides additional features for working with graph data in Python. +Notably, there are convenience methods for importing data from source such as link:https://pandas.pydata.org/[Pandas DataFrames], link:https://neo4j.com/docs/graph-data-science/current/[Neo4j Graph Data Science], link:https://neo4j.com/docs/python-manual/current/[Neo4j Database], and link:https://docs.snowflake.com/[Snowflake tables]. + +The source code is available on link:https://github.com/neo4j/python-graph-visualization[GitHub]. +If you have a suggestion on how we can improve the library or want to report a problem, you can create a link:https://github.com/neo4j/python-graph-visualization/issues/new[new issue]. \ No newline at end of file diff --git a/docs/antora/modules/ROOT/pages/installation.adoc b/docs/antora/modules/ROOT/pages/installation.adoc new file mode 100644 index 00000000..47ef95f6 --- /dev/null +++ b/docs/antora/modules/ROOT/pages/installation.adoc @@ -0,0 +1,57 @@ += Installation + +To install the latest version of the library, run: + +[source, console] +---- +pip install neo4j-viz +---- + +== Optional dependencies + +In addition, there are a few optional dependencies that you can install to enable additional features of the library. + +=== Pandas DataFrames `from_dfs` importer + +To install the additional dependencies required for the link:{api-docs-uri}/from_pandas[`from_dfs`] importer you can run: + +[source, console] +---- +pip install neo4j-viz[pandas] +---- + +=== Neo4j `from_neo4j` importer + +To install the additional dependencies required for the link:{api-docs-uri}/from_neo4j[`from_neo4j`] importer you can run: + +[source, console] +---- +pip install neo4j-viz[neo4j] +---- + +=== Neo4j Graph Data Science `from_gds` importer + +To install the additional dependencies required for the link:{api-docs-uri}/from_gds[`from_gds`] importer you can run: + +[source, console] +---- +pip install neo4j-viz[gds] +---- + +=== Neo4j Graph Data Science `from_snowflake` importer + +To install the additional dependencies required for the link:{api-docs-uri}/from_snowflake[`from_snowflake`] importer you can run: + +[source, console] +---- +pip install neo4j-viz[snowflake] +---- + +=== Notebook tutorials + +To install all the additional dependencies required for the link:{tutorials-docs-uri}[notebook examples] you can run: + +[source, console] +---- +pip install neo4j-viz[notebook] +---- \ No newline at end of file diff --git a/docs/antora/modules/ROOT/pages/integration.adoc b/docs/antora/modules/ROOT/pages/integration.adoc new file mode 100644 index 00000000..67c7422b --- /dev/null +++ b/docs/antora/modules/ROOT/pages/integration.adoc @@ -0,0 +1,286 @@ += Integration with other libraries + +In addition to creating graphs from scratch, with `neo4j-viz` as is shown in the +xref:getting-started.adoc[], you can also import data directly from external sources. +In this section we will cover how to import data from link:https://pandas.pydata.org/[Pandas DataFrames], +link:https://neo4j.com/docs/graph-data-science/current/[Neo4j Graph Data Science], +link:https://neo4j.com/docs/python-manual/current/[Neo4j Database], +link:https://neo4j.com/docs/cypher-manual/current/clauses/create/[GQL `CREATE` queries], and link:https://docs.snowflake.com/[Snowflake tables]. + +== Pandas DataFrames + +The `neo4j-viz` library provides a convenience method for importing data from Pandas DataFrames. +These DataFrames can be created from many sources, such as CSV files. +It requires and additional dependency to be installed, which you can do by running: + +[source, console] +---- +pip install neo4j-viz[pandas] +---- + +Once you have installed the additional dependency, you can use the link:{api-docs-uri}/from_pandas[`from_pandas`] method +to import pandas DataFrames. + +The `from_dfs` method takes two mandatory positional parameters: + +* A Pandas `DataFrame`, or iterable (eg. list) of DataFrames representing the nodes of the graph. + The rows of the DataFrame(s) should represent the individual nodes, and the columns should represent the node + IDs and attributes. + The node ID will be set on the link:{api-docs-uri}/node[Node], Other columns will be a key in each node's properties dictionary, that maps to the node's corresponding value in the column. + If the graph has no node properties, the nodes can be derived from the relationships DataFrame alone. +* A Pandas `DataFrame`, or iterable (eg. list) of DataFrames representing the relationships of the graph. + The rows of the DataFrame(s) should represent the individual relationships, and the columns should represent the + relationship IDs and attributes. + The relationship id, source and target node IDs will be set on the link:{api-docs-uri}/relationship[Relationship]. + Other columns will be a key in each relationship's _properties_ dictionary, that maps to the relationship's corresponding value in the column. + +=== Example + +In this small example, we import a tiny toy graph representing a social network from two Pandas DataFrames. +As we can see the column names of the DataFrames map directly to the fields of link:{api-docs-uri}/node[Nodes] +and link:{api-docs-uri}/relationship[Relationships]. + +[source, python] +---- +from pandas import DataFrame +from neo4j_viz.pandas import from_dfs + +nodes = DataFrame({ + "id": [1, 2, 3], + "caption": ["Alice", "Bob", "Charlie"], + "size": [20, 10, 10], +}) + +relationships = DataFrame({ + "source": [1, 2], + "target": [2, 3], + "caption": ["LIKES", "KNOWS"], +}) + +VG = from_dfs(nodes, relationships) +---- + +== Neo4j Graph Data Science (GDS) library + +The `neo4j-viz` library provides a convenience method for importing data from the Neo4j Graph Data Science (GDS) +library. +It requires and additional dependency to be installed, which you can do by running: + +[source, console] +---- +pip install neo4j-viz[gds] +---- + +Once you have installed the additional dependency, you can use the link:{api-docs-uri}/from_gds[`from_gds`] method +to import projections from the GDS library. + +The `from_gds` method takes two mandatory positional parameters: + +* An initialized `GraphDataScience` object for the connection to the GDS instance, and +* A `Graph` representing the projection that one wants to import. + +The optional `max_node_count` parameter can be used to limit the number of nodes that are imported from the projection. +By default, it is set to 10.000, meaning that if the projection has more than 10.000 nodes, `from_gds` will sample from it using random walk with restarts, to get a smaller graph that can be visualized. +If you want to have more control of the sampling, such as choosing a specific start node for the sample, you can call a link:https://neo4j.com/docs/graph-data-science/current/management-ops/graph-creation/sampling/[sampling] method yourself and passing the resulting projection to `from_gds`. + +The `node_properties` parameter is also optional, and should be a list of additional node properties of the projection that you want to include in the visualization. +The default is `None`, which means that all properties of the nodes in the projection will be included. +Apart from being visible through on-hover tooltips, these properties could be used to color the nodes, or give captions to them in the visualization, or simply included in the nodes' `Node.properties` maps without directly impacting the visualization. +If you want to include node properties stored at the Neo4j database, you can include them in the visualization by using the _db_node_properties_ parameter. + +=== Example + +In this small example, we import a graph projection from the GDS library, that has the node properties "pagerank" and +"componentId". +We use the "pagerank" property to determine the size of the nodes, and the "componentId" property to color the nodes. + +[source, python] +---- +from graphdatascience import GraphDataScience +from neo4j_viz.gds import from_gds +gds = GraphDataScience(...) +G = gds.graph.project(...) +# Compute the PageRank and Weakly Connected Components +gds.pageRank.mutate(G, mutateProperty="pagerank") +gds.wcc.mutate(G, mutateProperty="componentId") +# Import the projection into a `VisualizationGraph` +# Make sure to include `pagerank` and `componentId` +VG = from_gds( + gds, + G, + size_property="pagerank", + additional_node_properties=["componentId"], +) +# Color the nodes by the `componentId` property, so that the nodes are +# colored by the connected component they belong to +VG.color_nodes(property="componentId") +---- + +See the link:/tutorials/gds-example[Visualizing Neo4j Graph Data Science (GDS) Graphs tutorial] for a more extensive example. + +== Neo4j Database + +The `neo4j-viz` library provides a convenience method for importing data from Neo4j. +It requires and additional dependency to be installed, which you can do by running: + +[source, console] +---- +pip install neo4j-viz[neo4j] +---- + +Once you have installed the additional dependency, you can use the link:{api-docs-uri}/from_neo4j[`from_neo4j`] method +to import query results from Neo4j. + +The `from_neo4j` method takes one mandatory positional parameter: A `data` argument representing either a query result in the shape of a `neo4j.graph.Graph` or `neo4j.Result`, or a `neo4j.Driver` in which case a simple default query will be executed internally to retrieve the graph data. + +The optional `max_rows` parameter can be used to limit the number of relationships shown in the visualization. +By default, it is set to 10.000, meaning that if the database has more than 10.000 rows, a warning will be raised. +Note, this only applies if the `data` parameter is a `neo4j.Driver`. + +=== Example + +In this small example, we import a graph from a Neo4j query result. + +[source, python] +---- +from neo4j import GraphDatabase, RoutingControl, Result +from neo4j_viz.gds import from_gds + +# Modify this to match your Neo4j instance's URI and credentials +URI = "neo4j://localhost:7687" +auth = ("neo4j", "password") + +with GraphDatabase.driver(URI, auth=auth) as driver: + driver.verify_connectivity() + result = driver.execute_query( + "MATCH (n)-[r]->(m) RETURN n,r,m", + database_="neo4j", + routing_=RoutingControl.READ, + result_transformer_=Result.graph, + ) + +VG = from_neo4j(result) +---- + +See the link:/tutorials/neo4j-example[Visualizing Neo4j Graphs tutorial] for a more extensive example. + +== GQL `CREATE` query + +The `neo4j-viz` library provides convenience for creating visualization graphs from GQL `CREATE` queries via the link:{api-docs-uri}/from_gql_create[`from_gql_create`] method. + +The `from_gql_create` method takes one mandatory positional parameter: + +* A valid `query` representing a GQL `CREATE` query as a string. + +=== Example + +In this small example, we create a visualization graph from a GQL `CREATE` query. + +[source, python] +---- +from neo4j_viz.gql_create import from_gql_create + +query = """ + CREATE + (a:User {name: 'Alice', age: 23}), + (b:User {name: 'Bridget', age: 34}), + (c:User {name: 'Charles', age: 45}), + (d:User {name: 'Dana', age: 56}), + (e:User {name: 'Eve', age: 67}), + (f:User {name: 'Fawad', age: 78}), + (a)-[:LINK {weight: 0.5}]->(b), + (a)-[:LINK {weight: 4}]->(c), + (e)-[:LINK {weight: 1.1}]->(d), + (e)-[:LINK {weight: -2}]->(f); + """ + +VG = from_gql_create(query) +---- + +== Snowflake Tables + +The `neo4j-viz` library provides a convenience method for importing data from Snowflake tables. +It requires and additional dependency to be installed, which you can do by running: + +[source, console] +---- +pip install neo4j-viz[snowflake] +---- + +Once you have installed the additional dependency, you can use the link:{api-docs-uri}/from_snowflake[`from_snowflake`] method to import Snowflake tables into a `VisualizationGraph`. + +The `from_snowflake` method takes two mandatory positional parameters: + +* A `snowflake.snowpark.Session` object for the connection to Snowflake, and + +* A link:https://neo4j.com/docs/snowflake-graph-analytics/current/jobs/#jobs-project[project configuration] as a dictionary, that specifies how you want your tables to be projected as a graph. +This configuration is the same as the project configuration of the link:https://neo4j.com/docs/snowflake-graph-analytics/current/[Neo4j Snowflake Graph Analytics application]. + +You can further customize the visualization after the _VisualizationGraph_ has been created, by using the methods described in the link:{api-docs-uri}/customizing/[Customizing the visualization] section. + +=== Default behavior + +The node and relationship captions will be set to the names of the corresponding tables. +The nodes will be colored so that nodes from the same table have the same color, and different tables have different colors. + +=== Example + +In this small example, we import a toy graph representing a social network from two tables in Snowflake. + +[source, python] +---- +from snowflake.snowpark import Session +from neo4j_viz.snowflake import from_dfs + +# Configure according to your own setup +connection_parameters = { + "account": os.environ.get("SNOWFLAKE_ACCOUNT"), + "user": os.environ.get("SNOWFLAKE_USER"), + "password": os.environ.get("SNOWFLAKE_PASSWORD"), + "role": os.environ.get("SNOWFLAKE_ROLE"), + "warehouse": os.environ.get("SNOWFLAKE_WAREHOUSE"), +} + +session.sql( + "CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.PERSONS (NODEID VARCHAR);" +).collect() + +session.sql(""" +INSERT INTO EXAMPLE_DB.DATA_SCHEMA.PERSONS VALUES + ('Alice'), + ('Bob'), + ('Carol'), + ('Dave'), + ('Eve'); + """).collect() + +session.sql( + "CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.KNOWS (SOURCENODEID VARCHAR, TARGETNODEID VARCHAR);" +).collect() + +session.sql(""" +INSERT INTO EXAMPLE_DB.DATA_SCHEMA.KNOWS VALUES + ('Alice', 'Dave'), + ('Alice', 'Carol'), + ('Bob', 'Carol'), + ('Dave', 'Eve'), + """).collect() + +VG = from_snowflake( + session, + { + "nodeTables": [ + "EXAMPLE_DB.DATA_SCHEMA.PERSONS", + ], + "relationshipTables": { + "EXAMPLE_DB.DATA_SCHEMA.KNOWS": { + "sourceTable": "EXAMPLE_DB.DATA_SCHEMA.PERSONS", + "targetTable": "EXAMPLE_DB.DATA_SCHEMA.PERSONS", + "orientation": "UNDIRECTED", + } + }, + }, +) +---- + +For a full example of the `from_snowflake` importer in action, please see the link:http://localhost:9000/tutorials/snowflake-example/[Visualizing Snowflake Tables tutorial]. \ No newline at end of file diff --git a/docs/antora/modules/ROOT/pages/rendering.adoc b/docs/antora/modules/ROOT/pages/rendering.adoc new file mode 100644 index 00000000..2ce089ed --- /dev/null +++ b/docs/antora/modules/ROOT/pages/rendering.adoc @@ -0,0 +1,63 @@ += Rendering a graph + +In this section, we will discuss how to render a link:{api-docs-uri}/visualization-graph[`VisualizationGraph` object] +to display the graph visualization. + +If you have not yet created a `VisualizationGraph` object, refer to one of the following sections: + +* xref:getting-started.adoc[Getting started] for creating a visualization graph from scratch using `neo4j-viz` + primitives like link:{api-docs-uri}/node[Node] and link:{api-docs-uri}/relationship[Relationship] and + link:{api-docs-uri}/visualization-graph>[`VisualizationGraph`] directly. +* xref:integration.adoc[] for importing data from a Pandas DataFrame or Neo4j GDS projection. + +== The `render` method + +Once you have a `VisualizationGraph` object, you can render it using the `render` method. +This will return a HTML object that will be displayed in an environment that supports HTML rendering, such as +Jupyter notebooks or streamlit application. + +All parameter of the `render` method are optional, and the full list of parameters of them is listed in the API +reference: link:{api-docs-uri}/visualization-graph/#neo4j_viz.VisualizationGraph.render[neo4j_viz.VisualizationGraph.render()]. + +The most important parameters to be aware of are the `width` and `height` parameters, which control the size of +HTML object that will be rendered. +You can provide these either as a percentage of the available space (eg. `"80%"`), or as an absolute pixel value +(eg. `"800px"`). + +Further you can change the layout of the graph using the `layout` parameter, which can be set to one of the following values: + +* `Layout.FORCE_DIRECTED` - Nodes are arranged using the Force-Directed algorithm, which simulates physical forces. +To customize the layout, use _ForceDirectedOptions_ via _layout_options_. +* `Layout.HIERARCHICAL` - Arranges nodes by the directionality of their relationships, creating a tree-like structure. +To customize the layout use _HierarchicalLayoutOptions_ via _layout_options_. +* `Layout.COORDINATE` - Arranges nodes based on coordinates defined in _x_ and _y_ properties on each node. + +Another thing of note is the `max_allowed_nodes` parameter, which controls the maximum number of nodes that is allowed +for the graph to contain in order to be rendered. +It defaults to 10.000, because rendering a large number of nodes can be slow and unresponsive. +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. + +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`. + +=== Examples + +Refer to the xref:getting-started.adoc[] and the link:/tutorials/index[tutorials] for +examples of `render` method usage. + +== Exporting to HTML + +The object returned by the `render` method is a `IPython.display.HTML` object. +In addition to being displayed in a Jupyter notebook or streamlit application, it can also be saved as an HTML file. +This could be useful if you want to share the visualization with others or embed it in a web page. + +To save the HTML data to a file, you can use the `data` attribute of the HTML object: + +[source, python] +---- +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/antora/package.json b/docs/antora/package.json new file mode 100644 index 00000000..4a64a880 --- /dev/null +++ b/docs/antora/package.json @@ -0,0 +1,54 @@ +{ + "name": "python-graph-visualization", + "version": "1.0.0", + "description": "Neo4j Python Graph Visualization", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "prestart": "npm update", + "start": "nodemon --exec \"npm run build\"", + "serve": "node server.js", + "clean": "rm -rf build", + "build": "npm run build:preview", + "postbuild": "node server.js", + "build:preview": "antora preview.yml --stacktrace --log-format=pretty", + "build:publish": "npm run clean && antora publish.yml --stacktrace --log-format=pretty", + "verify:preview": "antora --stacktrace --fetch preview.yml --log-format=json --log-level=info --log-file ./build/log/log.json", + "verify:publish": "antora --stacktrace --fetch publish.yml --log-format=json --log-level=info --log-file ./build/log/log.json" + }, + "keywords": [ + "antora", + "neo4j" + ], + "author": "Neo4j", + "license": "ISC", + "dependencies": { + "@neo4j-antora/aliases-redirects": "^0.2.5", + "@neo4j-antora/antora-add-notes": "^0.3.2", + "@neo4j-antora/mark-terms": "^1.1.0", + "@neo4j-antora/roles-labels": "^0.1.1", + "@neo4j-antora/selector-labels": "^0.1.1", + "@neo4j-antora/table-footnotes": "^1.0.0", + "@neo4j-antora/xref-hash-validator": "^0.1.3", + "@neo4j-documentation/macros": "^1.0.4", + "@neo4j-documentation/remote-include": "^1.0.0", + "antora": "3.1.14" + }, + "devDependencies": { + "express": "^5.1.0", + "nodemon": "^3.1.0" + }, + "nodemonConfig": { + "watch": [ + "**/modules/**", + "**/antora.yml", + "**/preview.yml", + "**/publish.yml" + ], + "ext": "yml,yaml,adoc,svg,png,jpg", + "ignore": [ + "build", + "node_modules" + ] + } +} diff --git a/docs/antora/preview.yml b/docs/antora/preview.yml new file mode 100644 index 00000000..3845f156 --- /dev/null +++ b/docs/antora/preview.yml @@ -0,0 +1,64 @@ +site: + title: Neo4j Docs + url: https://neo4j.com/docs + start_page: python-graph-visualization:ROOT:index.adoc + +content: + sources: + - url: ../../ + start_path: docs/antora + branches: HEAD + exclude: + - '!**/_includes/*' + - '!**/readme.adoc' + - '!**/README.adoc' + +ui: + bundle: + url: https://static-content.neo4j.com/build/ui-bundle-latest.zip + snapshot: true + output_dir: /assets + +urls: + html_extension_style: indexify + +antora: + extensions: + - "@neo4j-antora/roles-labels" + - "@neo4j-antora/table-footnotes" + - "@neo4j-antora/xref-hash-validator" + +asciidoc: + extensions: + - "@neo4j-documentation/remote-include" + - "@neo4j-documentation/macros" + - "@neo4j-antora/mark-terms" + attributes: + # page-attributes are used by the ui-bundle and by extensions + page-theme: docs + page-type: Docs + page-search-type: Docs + page-search-site: Reference Docs + page-canonical-root: /docs + page-terms-to-mark: Cypher + page-pagination: true + page-no-canonical: true + page-origin-private: true # change to false to display 'Raise an issue' links + page-hide-toc: false + page-mixpanel: 4bfb2414ab973c741b6f067bf06d5575 + # legacy attributes - do not change these + includePDF: false + nonhtmloutput: "" + experimental: '' + # update the copyright value with the first commit in a new year + copyright: 2025 + # icon attributes + check-mark: icon:check[] + cross-mark: icon:times[] + # neo4j.com attributes. Always use when linking to neo4j.com URLs + neo4j-base-uri: https://neo4j.com + neo4j-docs-base-uri: '{neo4j-base-uri}/docs' + common-license-page-uri: '{neo4j-docs-base-uri}/license' + # Uses a local server to preview Sphinx docs + api-docs-uri: http://localhost:9000/api-reference + tutorials-docs-uri: http://localhost:9000/tutorials diff --git a/docs/antora/publish.yml b/docs/antora/publish.yml new file mode 100644 index 00000000..e67574ec --- /dev/null +++ b/docs/antora/publish.yml @@ -0,0 +1,63 @@ +# Use this playbook file when you need to build and publish multiple versions of your content together +site: + title: Neo4j Docs + url: https://neo4j.com/docs + start_page: python-graph-visualization:ROOT:index.adoc + +content: + sources: + - url: ../../ + start_path: docs/antora + branches: ['HEAD'] + exclude: + - '!**/_includes/*' + - '!**/readme.adoc' + - '!**/README.adoc' + +ui: + bundle: + url: https://static-content.neo4j.com/build/ui-bundle-latest.zip + snapshot: true + output_dir: /assets + +urls: + html_extension_style: indexify + +antora: + extensions: + - "@neo4j-antora/aliases-redirects" + - "@neo4j-antora/roles-labels" + - "@neo4j-antora/table-footnotes" + - "@neo4j-antora/xref-hash-validator" + +asciidoc: + extensions: + - "@neo4j-documentation/remote-include" + - "@neo4j-documentation/macros" + - "@neo4j-antora/mark-terms" + attributes: + # page-attributes are used by the ui-bundle and by extensions + page-theme: docs + page-type: Docs + page-search-type: Docs + page-search-site: Reference Docs + page-canonical-root: /docs + page-terms-to-mark: Cypher + page-pagination: true + page-no-canonical: true + page-origin-private: true # change to false to display 'Raise an issue' links + page-hide-toc: false + page-mixpanel: 4bfb2414ab973c741b6f067bf06d5575 + # legacy attributes - do not change these + includePDF: false + nonhtmloutput: "" + experimental: '' + # update the copyright value with the first commit in a new year + copyright: 2025 + # icon attributes + check-mark: icon:check[] + cross-mark: icon:times[] + # neo4j.com attributes. Always use when linking to neo4j.com URLs + neo4j-base-uri: '' + neo4j-docs-base-uri: /docs + common-license-page-uri: '{neo4j-docs-base-uri}/license' \ No newline at end of file diff --git a/docs/antora/server.js b/docs/antora/server.js new file mode 100644 index 00000000..294616fe --- /dev/null +++ b/docs/antora/server.js @@ -0,0 +1,10 @@ +const express = require('express') + +const app = express() +app.use(express.static('./build/site')) + +app.use('/static/assets', express.static('./build/site/assets')) + +app.get('/', (req, res) => res.redirect('/docs/')) + +app.listen(8000, () => console.log('📘 http://localhost:8000')) \ No newline at end of file diff --git a/docs/extra/.ipynb_checkpoints/getting-started-checkpoint.ipynb b/docs/extra/.ipynb_checkpoints/getting-started-checkpoint.ipynb deleted file mode 100644 index 2d08544a..00000000 --- a/docs/extra/.ipynb_checkpoints/getting-started-checkpoint.ipynb +++ /dev/null @@ -1,378 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0d3ffc27", - "metadata": {}, - "source": [ - "# Getting started" - ] - }, - { - "cell_type": "markdown", - "id": "6b83277d", - "metadata": {}, - "source": [ - "In this section, we will cover the very basics of creating a visualization graph using the `neo4j-viz` library.\n", - "We will use a small toy graph representing the purchase history of a few people and products.\n", - "\n", - "To follow along with the example below, please make sure you have [installed the library](installation.rst) first.\n", - "\n", - "We start by instantiating the [Nodes](./api-reference/node.rst) and [Relationships](./api-reference/relationship.rst) we want in our graph.\n", - "The only mandatory fields for a node are the \"id\", and \"source\" and \"target\" for a relationship.\n", - "But the other fields can optionally be used to customize the appearance of the nodes and relationships in the visualization.\n", - "\n", - "Lastly we create a [VisualizationGraph](./api-reference/visualization-graph.rst) object with the nodes and relationships we created, and call its `render` method to display the graph.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "801d0bed", - "metadata": { - "tags": [ - "preserve-output" - ] - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - "