Skip to content

Commit 67fa01f

Browse files
authored
Merge pull request #216 from plotly/bugfix/update-maxzoom-cyleaflet
Bugfix/update maxzoom cyleaflet
2 parents 77817ed + b1cbb51 commit 67fa01f

19 files changed

+216
-59
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ What does this implement/fix? Explain your changes.
2525
- [ ] All changes were documented in CHANGELOG.md.
2626
- [ ] All tests on CircleCI have passed.
2727
- [ ] All Percy visual changes have been approved.
28-
- [ ] Two people have :dancer:'d the pull request. You can be one of these people if you are a Dash Cytoscape core contributor.
28+
- [ ] At least one person has :dancer:'d the pull request.
2929

3030

3131
## Reference Issues

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
9+
### Fixed
10+
* [#205](https://github.com/plotly/dash-cytoscape/pull/205) Fixed updating maxZoom via callback in CyLeaflet AIO component.
11+
* [#207](https://github.com/plotly/dash-cytoscape/pull/207) Allow access to updated lat/lon when Cytoscape nodes in CyLeaflet AIO component are modified via UI.
12+
* [#208](https://github.com/plotly/dash-cytoscape/pull/208) Allow updating Cytoscape elements in CyLeaflet AIO component via callback.
13+
714
## [1.0.0] - 2024-01-26
815

916
### Removed

dash_cytoscape/CyLeaflet.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
ClientsideFunction,
55
Output,
66
Input,
7+
State,
78
html,
89
dcc,
910
MATCH,
11+
no_update,
1012
)
1113

1214
import dash_cytoscape as cyto
15+
import math
16+
1317

1418
try:
1519
import dash_leaflet as dl
@@ -19,17 +23,12 @@
1923
# Max zoom of default Leaflet tile layer
2024
LEAFLET_DEFAULT_MAX_ZOOM = 18
2125

22-
# Empirically-determined max zoom values for Cytoscape
23-
# which correspond to max zoom values of Leaflet
24-
LEAF_TO_CYTO_MAX_ZOOM_MAPPING = {
25-
16: 0.418,
26-
17: 0.837,
27-
18: 1.674,
28-
19: 3.349,
29-
20: 6.698,
30-
21: 13.396,
31-
22: 26.793,
32-
}
26+
27+
# Approximates max zoom values for Cytoscape from max zoom
28+
# values of Leaflet. Empirically-determined
29+
def get_cytoscape_max_zoom(leaflet_max_zoom):
30+
leaflet_max_zoom = leaflet_max_zoom or 0
31+
return 0.418 * (2 ** (leaflet_max_zoom - 16))
3332

3433

3534
class CyLeaflet(html.Div):
@@ -62,7 +61,7 @@ def __init__(
6261

6362
self.ids = {
6463
s: {"id": id, "component": "cyleaflet", "sub": s}
65-
for s in ["cy", "leaf", "elements"]
64+
for s in ["cy", "leaf", "elements", "cy-extent"]
6665
}
6766

6867
self.CYTOSCAPE_ID = self.ids["cy"]
@@ -102,6 +101,7 @@ def __init__(
102101
},
103102
),
104103
dcc.Store(id=self.ids["elements"], data=elements),
104+
dcc.Store(id=self.ids["cy-extent"]),
105105
],
106106
style={
107107
"width": width,
@@ -195,14 +195,7 @@ def get_leaflet_max_zoom(self, leaflet_children):
195195
# Given a maxZoom value for Leaflet, map it to the corresponding maxZoom value for Cytoscape
196196
# If the value is out of range, return the closest value
197197
def get_cytoscape_max_zoom(self, leaflet_max_zoom):
198-
leaflet_max_zoom = leaflet_max_zoom or 0
199-
leaflet_max_zoom = min(
200-
leaflet_max_zoom, max(LEAF_TO_CYTO_MAX_ZOOM_MAPPING.keys())
201-
)
202-
leaflet_max_zoom = max(
203-
leaflet_max_zoom, min(LEAF_TO_CYTO_MAX_ZOOM_MAPPING.keys())
204-
)
205-
return LEAF_TO_CYTO_MAX_ZOOM_MAPPING[leaflet_max_zoom]
198+
return get_cytoscape_max_zoom(leaflet_max_zoom)
206199

207200

208201
if dl is not None:
@@ -212,10 +205,29 @@ def get_cytoscape_max_zoom(self, leaflet_max_zoom):
212205
{"id": MATCH, "component": "cyleaflet", "sub": "leaf"}, "invalidateSize"
213206
),
214207
Output({"id": MATCH, "component": "cyleaflet", "sub": "leaf"}, "viewport"),
208+
Output({"id": MATCH, "component": "cyleaflet", "sub": "cy-extent"}, "data"),
215209
Input({"id": MATCH, "component": "cyleaflet", "sub": "cy"}, "extent"),
210+
Input({"id": MATCH, "component": "cyleaflet", "sub": "cy"}, "maxZoom"),
211+
State({"id": MATCH, "component": "cyleaflet", "sub": "cy-extent"}, "data"),
216212
)
217213
clientside_callback(
218214
ClientsideFunction(namespace="cyleaflet", function_name="transformElements"),
219-
Output({"id": MATCH, "component": "cyleaflet", "sub": "cy"}, "elements"),
215+
Output(
216+
{"id": MATCH, "component": "cyleaflet", "sub": "cy"},
217+
"elements",
218+
allow_duplicate=True,
219+
),
220220
Input({"id": MATCH, "component": "cyleaflet", "sub": "elements"}, "data"),
221+
prevent_initial_call="initial_duplicate",
222+
)
223+
clientside_callback(
224+
ClientsideFunction(namespace="cyleaflet", function_name="updateLonLat"),
225+
Output({"id": MATCH, "component": "cyleaflet", "sub": "elements"}, "data"),
226+
Input({"id": MATCH, "component": "cyleaflet", "sub": "cy"}, "elements"),
227+
prevent_initial_call=True,
228+
)
229+
clientside_callback(
230+
ClientsideFunction(namespace="cyleaflet", function_name="updateCytoMaxZoom"),
231+
Output({"id": MATCH, "component": "cyleaflet", "sub": "cy"}, "maxZoom"),
232+
Input({"id": MATCH, "component": "cyleaflet", "sub": "leaf"}, "children"),
221233
)

dash_cytoscape/dash_cytoscape.dev.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash_cytoscape/dash_cytoscape.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash_cytoscape/dash_cytoscape_extra.dev.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash_cytoscape/dash_cytoscape_extra.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash_cytoscape/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
"webpack": "^5.90.0",
7878
"webpack-cli": "^5.1.4"
7979
},
80+
"files": [
81+
"/dash_cytoscape/*{.js,.map,.txt}"
82+
],
8083
"engines": {
8184
"node": ">=8.11.0",
8285
"npm": ">=6.1.0"

demos/usage-cy-leaflet-aio.py

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
)
99
import dash_cytoscape as cyto
1010
import dash_leaflet as dl
11+
import random
1112

1213
cyto.load_extra_layouts()
1314

@@ -80,42 +81,82 @@
8081
options=location_options,
8182
value=location_options[0]["value"],
8283
),
84+
"Width",
8385
dcc.Input(
8486
id="width-input",
8587
type="number",
8688
value=int(default_div_style["width"][:-2]),
8789
debounce=True,
8890
),
91+
"Height",
8992
dcc.Input(
9093
id="height-input",
9194
type="number",
9295
value=int(default_div_style["height"][:-2]),
9396
debounce=True,
9497
),
98+
"Max zoom",
99+
dcc.Input(id="max-zoom", type="number", value=18, debounce=True),
100+
"Number of Nodes",
101+
dcc.Input(id="n-nodes", type="number", value=4, debounce=True),
95102
],
96103
),
104+
html.Div(id="elements"),
97105
],
98106
)
99107

100108

109+
@callback(Output("elements", "children"), Input(cyleaflet_instance.ELEMENTS_ID, "data"))
110+
def show_elements(elements):
111+
return str(elements)
112+
113+
114+
def generate_elements(n_nodes, location):
115+
d = 0.00005
116+
lat, lon = city_lat_lon[location]
117+
if n_nodes < 2:
118+
n_nodes = 2
119+
elif n_nodes > 10000:
120+
n_nodes = 10000
121+
122+
elements = []
123+
for i in range(n_nodes):
124+
rand_lat, rand_lon = random.randint(-5, 5) * i, random.randint(-5, 5) * i
125+
elements.append(
126+
{
127+
"data": {
128+
"id": f"{i}",
129+
"label": f"Node {i}",
130+
"lat": lat + d * rand_lat,
131+
"lon": lon + d * rand_lon,
132+
}
133+
}
134+
)
135+
elements.append({"data": {"id": "0-1", "source": "0", "target": "1"}})
136+
return elements
137+
138+
139+
@callback(
140+
Output(cyleaflet_instance.CYTOSCAPE_ID, "elements", allow_duplicate=True),
141+
Input("n-nodes", "value"),
142+
Input("location-dropdown", "value"),
143+
prevent_initial_call=True,
144+
)
145+
def control_number_nodes(n_nodes, location):
146+
return generate_elements(n_nodes, location)
147+
148+
101149
@callback(
102150
Output("cy-leaflet-div", "children"),
103151
Output("cy-leaflet-div", "style"),
104152
Output(cyleaflet_instance.LEAFLET_ID, "children"),
105153
Input("location-dropdown", "value"),
106154
Input("width-input", "value"),
107155
Input("height-input", "value"),
156+
Input("max-zoom", "value"),
108157
)
109-
def update_location(location, width, height):
110-
d = 0.001
111-
d2 = 0.0001
112-
lat, lon = city_lat_lon[location]
113-
new_elements = [
114-
{"data": {"id": "a", "label": "Node A", "lat": lat - d, "lon": lon - d}},
115-
{"data": {"id": "b", "label": "Node B", "lat": lat + d, "lon": lon + d}},
116-
{"data": {"id": "c", "label": "Node C", "lat": lat + d - d2, "lon": lon + d}},
117-
{"data": {"id": "ab", "source": "a", "target": "b"}},
118-
]
158+
def update_location(location, width, height, max_zoom):
159+
new_elements = generate_elements(4, location)
119160
markers = [
120161
dl.Marker(
121162
position=[e["data"]["lat"], e["data"]["lon"]],
@@ -128,7 +169,7 @@ def update_location(location, width, height):
128169
for e in new_elements
129170
if "lat" in e["data"]
130171
]
131-
leaflet_children = [dl.TileLayer()] + markers
172+
leaflet_children = [dl.TileLayer(maxZoom=max_zoom)] + markers
132173
new_style = dict(default_div_style)
133174
new_style["width"] = str(width) + "px" if width else default_div_style["width"]
134175
new_style["height"] = str(height) + "px" if height else default_div_style["height"]
@@ -150,8 +191,8 @@ def update_location(location, width, height):
150191
@callback(
151192
Output("bounds-display", "children"),
152193
Output("extent-display", "children"),
153-
Input({"id": "my-cy-leaflet", "sub": "leaf", "component": "cyleaflet"}, "bounds"),
154-
Input({"id": "my-cy-leaflet", "sub": "cy", "component": "cyleaflet"}, "extent"),
194+
Input(cyleaflet_instance.LEAFLET_ID, "bounds"),
195+
Input(cyleaflet_instance.CYTOSCAPE_ID, "extent"),
155196
)
156197
def display_leaf_bounds(bounds, extent):
157198
bounds = (

deps/dash_cytoscape.dev.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)