Skip to content

Commit 94aca00

Browse files
authored
Merge pull request #148 from neo4j/add-screenshot-button
Add screenshot and zoom buttons
2 parents c9341ff + 869586a commit 94aca00

File tree

13 files changed

+1548
-102
lines changed

13 files changed

+1548
-102
lines changed

docs/extra/.ipynb_checkpoints/getting-started-checkpoint.ipynb

Lines changed: 378 additions & 0 deletions
Large diffs are not rendered by default.

docs/extra/getting-started.ipynb

Lines changed: 184 additions & 16 deletions
Large diffs are not rendered by default.

examples/gds-example.ipynb

Lines changed: 576 additions & 54 deletions
Large diffs are not rendered by default.

examples/neo4j-example.ipynb

Lines changed: 192 additions & 18 deletions
Large diffs are not rendered by default.

examples/snowpark-example.ipynb

Lines changed: 96 additions & 9 deletions
Large diffs are not rendered by default.

python-wrapper/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
*.egg-info
22
__pycache__
33
build
4+
dist
45

56
# Should we ignore the following?
67
src/neo4j_viz/resources/nvl_entrypoint/*
78
!src/neo4j_viz/resources/nvl_entrypoint/base.js
9+
!src/neo4j_viz/resources/nvl_entrypoint/styles.css
810
!src/neo4j_viz/resources/nvl_entrypoint/__init__.py

python-wrapper/pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ include = [
8888
namespaces = false # only scan directories with __init__.py files (true by default)
8989

9090
[tool.setuptools.package-data]
91-
neo4j_viz = ["resources/nvl_entrypoint/base.js", "py.typed"]
91+
neo4j_viz = [
92+
"resources/nvl_entrypoint/base.js",
93+
"resources/nvl_entrypoint/styles.css",
94+
"resources/icons/*.svg",
95+
"py.typed"
96+
]
9297

9398
[tool.pytest.ini_options]
9499
addopts = ["--import-mode=importlib"]

python-wrapper/src/neo4j_viz/nvl.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,27 @@ def __init__(self) -> None:
1919
nvl_entry_point = resource_folder / "nvl_entrypoint"
2020

2121
js_path = nvl_entry_point / "base.js"
22-
2322
with js_path.open("r", encoding="utf-8") as file:
2423
self.library_code = file.read()
2524

25+
styles_path = nvl_entry_point / "styles.css"
26+
with styles_path.open("r", encoding="utf-8") as file:
27+
self.styles = file.read()
28+
29+
icons = resource_folder / "icons"
30+
31+
zoom_in_path = icons / "zoom-in.svg"
32+
with zoom_in_path.open("r", encoding="utf-8") as file:
33+
self.zoom_in_svg = file.read()
34+
35+
zoom_out_path = icons / "zoom-out.svg"
36+
with zoom_out_path.open("r", encoding="utf-8") as file:
37+
self.zoom_out_svg = file.read()
38+
39+
screenshot_path = icons / "screenshot.svg"
40+
with screenshot_path.open("r", encoding="utf-8") as file:
41+
self.screenshot_svg = file.read()
42+
2643
def unsupported_field_type_error(self, e: TypeError, entity: str) -> Exception:
2744
if "not JSON serializable" in str(e):
2845
return ValueError(f"A field of a {entity} object is not supported: {str(e)}")
@@ -51,13 +68,19 @@ def render(
5168

5269
if show_hover_tooltip:
5370
hover_element = f"document.getElementById('{container_id}-tooltip')"
54-
hover_div = f'<div id="{container_id}-tooltip" style="width: 20%; min-width: 100px; max-width: 600px; max-height: 80%; position: absolute; z-index: 2147483647; right: 0; bottom: 0; background: white; display: none; border: solid; border-color: #BBBEC3; border-width: 0.5px; padding: 0.8rem; border-radius: 8px; margin-bottom: 1rem; margin-right: 0.5rem; filter: drop-shadow(0 4px 8px rgba(26,27,29,0.12)); font-family: PublicSans; color: #4D5157; font-size: 14px"></div>'
71+
hover_div = f'<div id="{container_id}-tooltip" class="tooltip" style="display: none;"></div>'
5572
else:
5673
hover_element = "null"
5774
hover_div = ""
5875

76+
# Using a different varname for every instance, so that a notebook
77+
# can use several instances without unwanted interactions.
78+
# The first part of the UUID should be "unique enough" in this context.
79+
nvl_varname = "graph_" + container_id.split("-")[0]
80+
download_name = nvl_varname + ".png"
81+
5982
js_code = f"""
60-
var myNvl = new NVLBase.NVL(
83+
var {nvl_varname} = new NVLBase.NVL(
6184
document.getElementById('{container_id}'),
6285
{hover_element},
6386
{nodes_json},
@@ -66,12 +89,37 @@ def render(
6689
);
6790
"""
6891
full_code = self.library_code + js_code
92+
6993
html_output = f"""
94+
<style>
95+
{self.styles}
96+
</style>
7097
<div id="{container_id}" style="width: {width}; height: {height}; position: relative;">
98+
<div style="position: absolute; z-index: 2147483647; right: 0; top: 0; padding: 1rem">
99+
<button type="button" title="Save as PNG" onclick="{nvl_varname}.nvl.saveToFile({{ filename: '{download_name}' }})" class="icon">
100+
{self.screenshot_svg}
101+
</button>
102+
<button type="button" title="Zoom in" onclick="{nvl_varname}.nvl.setZoom({nvl_varname}.nvl.getScale() + 0.5)" class="icon">
103+
{self.zoom_in_svg}
104+
</button>
105+
<button type="button" title="Zoom out" onclick="{nvl_varname}.nvl.setZoom({nvl_varname}.nvl.getScale() - 0.5)" class="icon">
106+
{self.zoom_out_svg}
107+
</button>
108+
</div>
71109
{hover_div}
72110
</div>
111+
73112
<script>
113+
getTheme = () => {{
114+
const backgroundColorString = window.getComputedStyle(document.body, null).getPropertyValue('background-color')
115+
const colorsArray = backgroundColorString.match(/\d+/g);
116+
const brightness = Number(colorsArray[0]) * 0.2126 + Number(colorsArray[1]) * 0.7152 + Number(colorsArray[2]) * 0.0722
117+
return brightness < 128 ? "dark" : "light"
118+
}}
119+
document.documentElement.className = getTheme()
120+
74121
{full_code}
75122
</script>
76123
"""
124+
77125
return HTML(html_output) # type: ignore[no-untyped-call]
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)