Dash VTK⑥(VTK dynamic streamlines)

VTK dynamic streamlines

Dash VTKを使って、バイク走行中の風力イメージを表示してみます。

下記URLからbike.vtpファイルtunnel.vtuファイルをダウンロードし、ソースコードと同じフォルダに格納します。

データセット - https://github.com/plotly/dash-vtk/tree/master/demos/usage-vtk-cfd/data

下記のソースコードでは、PyVista/VTKを使用してデータセットを読み込み、Dash VTKを使用して 3D Web ビューで表示しています。

風力イメージを調整するためのコントローラも設定しています。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import os
import random

import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc

from dash.dependencies import Input, Output, State

import dash_vtk
from dash_vtk.utils import to_mesh_state, preset_as_options

import vtk

random.seed(42)

# -----------------------------------------------------------------------------
# VTK Pipeline
# -----------------------------------------------------------------------------
class Viz:
def __init__(self, data_directory):
self.color_range = [0, 1]
bike_filename = "bike.vtp"
tunnel_filename = "tunnel.vtu"

# Seeds settings
self.resolution = 10
self.point1 = [-0.4, 0, 0.05]
self.point2 = [-0.4, 0, 1.5]

# VTK Pipeline setup
bikeReader = vtk.vtkXMLPolyDataReader()
bikeReader.SetFileName(bike_filename)
bikeReader.Update()
self.bike_mesh = to_mesh_state(bikeReader.GetOutput())

tunnelReader = vtk.vtkXMLUnstructuredGridReader()
tunnelReader.SetFileName(tunnel_filename)
tunnelReader.Update()

self.lineSeed = vtk.vtkLineSource()
self.lineSeed.SetPoint1(*self.point1)
self.lineSeed.SetPoint2(*self.point2)
self.lineSeed.SetResolution(self.resolution)

streamTracer = vtk.vtkStreamTracer()
streamTracer.SetInputConnection(tunnelReader.GetOutputPort())
streamTracer.SetSourceConnection(self.lineSeed.GetOutputPort())
streamTracer.SetIntegrationDirectionToForward()
streamTracer.SetIntegratorTypeToRungeKutta45()
streamTracer.SetMaximumPropagation(3)
streamTracer.SetIntegrationStepUnit(2)
streamTracer.SetInitialIntegrationStep(0.2)
streamTracer.SetMinimumIntegrationStep(0.01)
streamTracer.SetMaximumIntegrationStep(0.5)
streamTracer.SetMaximumError(0.000001)
streamTracer.SetMaximumNumberOfSteps(2000)
streamTracer.SetTerminalSpeed(0.00000000001)

self.tubeFilter = vtk.vtkTubeFilter()
self.tubeFilter.SetInputConnection(streamTracer.GetOutputPort())
self.tubeFilter.SetRadius(0.01)
self.tubeFilter.SetNumberOfSides(6)
self.tubeFilter.CappingOn()
self.tubeFilter.Update()

def updateSeedPoints(self, p1_y, p2_y, resolution):
self.point1[1] = p1_y
self.point2[1] = p2_y
self.resolution = resolution

self.lineSeed.SetPoint1(*self.point1)
self.lineSeed.SetPoint2(*self.point2)
self.lineSeed.SetResolution(resolution)

def getTubesMesh(self, color_by_field_name):
self.tubeFilter.Update()
ds = self.tubeFilter.GetOutput()
mesh_state = to_mesh_state(ds, color_by_field_name)
self.color_range = mesh_state["field"]["dataRange"]
return mesh_state

def getBikeMesh(self):
return self.bike_mesh

def getColorRange(self):
return self.color_range

def getSeedState(self):
return {
"point1": self.point1,
"point2": self.point2,
"resolution": self.resolution,
}

# -----------------------------------------------------------------------------
# GUI setup
# -----------------------------------------------------------------------------
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server
viz = Viz(os.path.join(os.path.dirname(__file__), "data"))

# -----------------------------------------------------------------------------
# 3D Viz
# -----------------------------------------------------------------------------
vtk_view = dash_vtk.View(
id="vtk-view",
children=[
dash_vtk.GeometryRepresentation(
id="bike-rep",
children=[
dash_vtk.Mesh(
id="bike",
state=viz.getBikeMesh(),
)
],
),
dash_vtk.GeometryRepresentation(
id="tubes-rep",
colorMapPreset="erdc_rainbow_bright",
colorDataRange=viz.getColorRange(),
children=[
dash_vtk.Mesh(
id="tubes-mesh",
state=viz.getTubesMesh("p"),
)
],
),
dash_vtk.GeometryRepresentation(
id="seed-rep",
property={
"color": [0.8, 0, 0],
"representation": 0,
"pointSize": 8,
},
children=[
dash_vtk.Algorithm(
id="seed-line",
vtkClass="vtkLineSource",
state=viz.getSeedState(),
)
],
),
],
)

# -----------------------------------------------------------------------------
# Control UI
# -----------------------------------------------------------------------------
high = 0.6
low = -0.55

controls = [
dbc.Card(
[
dbc.CardHeader("Seeds"),
dbc.CardBody(
[
html.P("Line seed position (from bottom):"),
dcc.Slider(
id="point-1",
min=low,
max=high,
step=0.05,
value=0,
marks={low: str(low), high: str(high)},
),
html.Br(),
html.P("Line seed position (from top):"),
dcc.Slider(
id="point-2",
min=low,
max=high,
step=0.05,
value=0,
marks={low: str(low), high: str(high)},
),
html.Br(),
html.P("Line resolution:"),
dcc.Slider(
id="seed-resolution",
min=5,
max=50,
step=1,
value=10,
marks={5: "5", 50: "50"},
),
]
),
]
),
html.Br(),
dbc.Card(
[
dbc.CardHeader("Color By"),
dbc.CardBody(
[
html.P("Field name"),
dcc.Dropdown(
id="color-by",
options=[
{"label": "p", "value": "p"},
{"label": "Rotation", "value": "Rotation"},
{"label": "U", "value": "U"},
{"label": "Vorticity", "value": "Vorticity"},
{"label": "k", "value": "k"},
],
value="p",
),
html.Br(),
html.P("Color Preset"),
dcc.Dropdown(
id="preset",
options=preset_as_options,
value="erdc_rainbow_bright",
),
]
),
]
),
]

# -----------------------------------------------------------------------------
# App UI
# -----------------------------------------------------------------------------
app.layout = dbc.Container(
fluid=True,
style={"marginTop": "15px", "height": "calc(100vh - 30px)"},
children=[
dbc.Row(
[
dbc.Col(width=4, children=controls),
dbc.Col(
width=8,
children=[
html.Div(vtk_view, style={"height": "100%", "width": "100%"})
],
),
],
style={"height": "100%"},
),
],
)

# -----------------------------------------------------------------------------
# Handle controls
# -----------------------------------------------------------------------------
@app.callback(
[
Output("seed-line", "state"),
Output("tubes-mesh", "state"),
Output("tubes-rep", "colorDataRange"),
Output("tubes-rep", "colorMapPreset"),
Output("vtk-view", "triggerRender"),
],
[
Input("point-1", "drag_value"),
Input("point-2", "drag_value"),
Input("point-1", "value"),
Input("point-2", "value"),
Input("seed-resolution", "value"),
Input("color-by", "value"),
Input("preset", "value"),
],
)
def update_seeds(y1_drag, y2_drag, y1, y2, resolution, colorByField, presetName):
triggered = dash.callback_context.triggered

if triggered and "drag_value" in triggered[0]["prop_id"]:
viz.updateSeedPoints(y1_drag, y2_drag, resolution)
return [
viz.getSeedState(),
dash.no_update,
dash.no_update,
dash.no_update,
random.random(), # trigger a render
]

viz.updateSeedPoints(y1, y2, resolution)
return [
viz.getSeedState(),
viz.getTubesMesh(colorByField),
viz.getColorRange(),
presetName,
random.random(), # trigger a render
]
# -----------------------------------------------------------------------------
if __name__ == "__main__":
app.run_server(debug=True)

[ブラウザで表示]

バイク走行中の風力イメージを表示できました。

各コントローラを使って風力イメージの表示を調整したり、ドラッグすることにより角度を変えて表示することが可能です。

Dash VTK⑤(Real medical image)

Real medical image

Dash VTKを使って、人体の頭部(医療データ)を表示してみます。

頭部データとして下記のファイルをダウンロードし、ソースコードと同じフォルダに格納します。

頭部データ - https://github.com/plotly/dash-vtk/blob/master/demos/data/head.vti

頭部を表示するソースコードは下記の通りです。11行目で、ダウンロードしたファイルを指定しています。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import os
import dash
import dash_html_components as html

import dash_vtk
from dash_vtk.utils import to_volume_state

import vtk

# Data file path
head_vti = "head.vti"

# Load dataset from dist
reader = vtk.vtkXMLImageDataReader()
reader.SetFileName(head_vti)
reader.Update()

volume_state = to_volume_state(reader.GetOutput())

vtk_view = dash_vtk.View(
dash_vtk.VolumeRepresentation(
children=[
dash_vtk.VolumeController(),
dash_vtk.Volume(state=volume_state),
]
)
)

app = dash.Dash(__name__)
server = app.server

app.layout = html.Div(
style={"height": "calc(100vh - 16px)", "width": "100%"},
children=[html.Div(vtk_view, style={"height": "100%", "width": "100%"})],
)

if __name__ == "__main__":
app.run_server(debug=True)

[ブラウザで表示]

人体の頭部を表示することができました。

各コントローラを使って頭部の透過度を変えたり、ドラッグすることにより角度を変えて表示することが可能です。

Dash VTK④(地形追従メッシュ)

地形追従メッシュ

地形追従メッシュ(Terrain Following Mesh)を使うと、地形を3Dのメッシュで表示することができます。

地形追従メッシュは、水文学モデリングなどの環境科学でよく使われます。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# pip install dash_bootstrap_components

import dash
import dash_vtk
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State

import random
import json
import numpy as np
import pyvista as pv
from pyvista import examples
from vtk.util.numpy_support import vtk_to_numpy

from dash_vtk.utils import presets

random.seed(42)

def toDropOption(name):
return {"label": name, "value": name}

# Get point cloud data from PyVista
uniformGrid = examples.download_crater_topo()
subset = uniformGrid.extract_subset((500, 900, 400, 800, 0, 0), (5, 5, 1))

def updateWarp(factor=1):
terrain = subset.warp_by_scalar(factor=factor)
polydata = terrain.extract_geometry()
points = polydata.points.ravel()
polys = vtk_to_numpy(polydata.GetPolys().GetData())
elevation = polydata["scalar1of1"]
min_elevation = np.amin(elevation)
max_elevation = np.amax(elevation)
return [points, polys, elevation, [min_elevation, max_elevation]]

points, polys, elevation, color_range = updateWarp(1)

# Setup VTK rendering of PointCloud
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server

vtk_view = dash_vtk.View(
id="vtk-view",
pickingModes=["hover"],
children=[
dash_vtk.GeometryRepresentation(
id="vtk-representation",
children=[
dash_vtk.PolyData(
id="vtk-polydata",
points=points,
polys=polys,
children=[
dash_vtk.PointData(
[
dash_vtk.DataArray(
id="vtk-array",
registration="setScalars",
name="elevation",
values=elevation,
)
]
)
],
)
],
colorMapPreset="erdc_blue2green_muted",
colorDataRange=color_range,
property={"edgeVisibility": True},
showCubeAxes=True,
cubeAxesStyle={"axisLabels": ["", "", "Altitude"]},
),
dash_vtk.GeometryRepresentation(
id="pick-rep",
actor={"visibility": False},
children=[
dash_vtk.Algorithm(
id="pick-sphere",
vtkClass="vtkSphereSource",
state={"radius": 100},
)
],
),
],
)

app.layout = dbc.Container(
fluid=True,
style={"height": "100vh"},
children=[
dbc.Row(
[
dbc.Col(
children=dcc.Slider(
id="scale-factor",
min=0.1,
max=5,
step=0.1,
value=1,
marks={0.1: "0.1", 5: "5"},
)
),
dbc.Col(
children=dcc.Dropdown(
id="dropdown-preset",
options=list(map(toDropOption, presets)),
value="erdc_rainbow_bright",
),
),
dbc.Col(
children=dcc.Checklist(
id="toggle-cube-axes",
options=[
{"label": " Show axis grid", "value": "grid"},
],
value=[],
labelStyle={"display": "inline-block"},
),
),
],
style={"height": "12%", "alignItems": "center"},
),
html.Div(
html.Div(vtk_view, style={"height": "100%", "width": "100%"}),
style={"height": "88%"},
),
html.Pre(
id="tooltip",
style={
"position": "absolute",
"bottom": "25px",
"left": "25px",
"zIndex": 1,
"color": "white",
},
),
],
)


@app.callback(
[
Output("vtk-representation", "showCubeAxes"),
Output("vtk-representation", "colorMapPreset"),
Output("vtk-representation", "colorDataRange"),
Output("vtk-polydata", "points"),
Output("vtk-polydata", "polys"),
Output("vtk-array", "values"),
Output("vtk-view", "triggerResetCamera"),
],
[
Input("dropdown-preset", "value"),
Input("scale-factor", "value"),
Input("toggle-cube-axes", "value"),
],
)
def updatePresetName(name, scale_factor, cubeAxes):
points, polys, elevation, color_range = updateWarp(scale_factor)
return [
"grid" in cubeAxes,
name,
color_range,
points,
polys,
elevation,
random.random(),
]

@app.callback(
[
Output("tooltip", "children"),
Output("pick-sphere", "state"),
Output("pick-rep", "actor"),
],
[
Input("vtk-view", "clickInfo"),
Input("vtk-view", "hoverInfo"),
],
)
def onInfo(clickData, hoverData):
info = hoverData if hoverData else clickData
if info:
if (
"representationId" in info
and info["representationId"] == "vtk-representation"
):
return (
[json.dumps(info, indent=2)],
{"center": info["worldPosition"]},
{"visibility": True},
)
return dash.no_update, dash.no_update, dash.no_update
return [""], {}, {"visibility": False}

if __name__ == "__main__":
app.run_server(debug=True)

[ブラウザで表示]

地形を3Dのメッシュで表示することができました。

各コントローラを使って表示を変えたり、地形をドラッグすることにより角度を変えて表示することが可能です。

Dash VTK③(PyVista)

PyVista

PyVistaはVisualization Toolkit (VTK) 用のヘルパー・モジュールです。

VTKとのインターフェースを提供し,データセットの迅速なプロトタイピング分析,および視覚的統合を容易にします。

このモジュールは,プレゼンテーションや研究論文の科学的プロットにも使用できます。

PyVistaのインストール

PyVistaを使うためには、下記のコマンドでインストールを行います。

[コマンド]

1
pip install pyvista

サンプル実行

PyVistaを使ったサンプルを実行してみます。

PyVistaexpmanplesから lidarデータセット をダウンロードしています。(14行目)

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import dash
import dash_vtk
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State

import numpy as np
import pyvista as pv
from pyvista import examples

np.random.seed(42)

# Get point cloud data from PyVista
dataset = examples.download_lidar()
subset = 0.2
selection = np.random.randint(
low=0, high=dataset.n_points - 1, size=int(dataset.n_points * subset)
)
points = dataset.points[selection]
xyz = points.ravel()
elevation = points[:, -1].ravel()
min_elevation = np.amin(elevation)
max_elevation = np.amax(elevation)
print(f"Number of points: {points.shape}")
print(f"Elevation range: [{min_elevation}, {max_elevation}]")

# Setup VTK rendering of PointCloud
app = dash.Dash(__name__)
server = app.server

vtk_view = dash_vtk.View(
[
dash_vtk.PointCloudRepresentation(
xyz=xyz,
scalars=elevation,
colorDataRange=[min_elevation, max_elevation],
property={"pointSize": 2},
)
]
)

app.layout = html.Div(
style={"height": "calc(100vh - 16px)"},
children=[html.Div(vtk_view, style={"height": "100%", "width": "100%"})],
)

if __name__ == "__main__":
app.run_server(debug=True)

起動に時間がかかるので気長にお待ちください。

[ブラウザで表示]

建物と木々のような風景がプロット(表示)され、ドラッグすることによりいろいろな角度から表示することができます。

Dash VTK②(Volume Rendering)

Volume Rendering

Volume Renderingを使うと、3次元表示をいろいろと変化させることができます。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import dash
import dash_html_components as html

import dash_vtk
from dash_vtk.utils import to_volume_state

try:
# VTK 9+
from vtkmodules.vtkImagingCore import vtkRTAnalyticSource
except ImportError:
# VTK =< 8
from vtk.vtkImagingCore import vtkRTAnalyticSource

# Use VTK to get some data
data_source = vtkRTAnalyticSource()
data_source.Update() # <= Execute source to produce an output
dataset = data_source.GetOutput()

# Use helper to get a volume structure that can be passed as-is to a Volume
volume_state = to_volume_state(dataset) # No need to select field

content = dash_vtk.View([
dash_vtk.VolumeRepresentation([
# GUI to control Volume Rendering
# + Setup good default at startup
dash_vtk.VolumeController(),
# Actual volume
dash_vtk.Volume(state=volume_state),
]),
])

# Dash setup
app = dash.Dash(__name__)
server = app.server

app.layout = html.Div(
style={"width": "100%", "height": "400px"},
children=[content],
)

if __name__ == "__main__":
app.run_server(debug=True)

[ブラウザで表示]

画面左上にボリュームコントローラが表示され、このコントローラを操作することにより3次元表示をいろいろと変化させることができるようになりました。

Dash VTK①(イントロダクション)

Dash VTKとは

Dash VTKは、VTKによるビジュアライゼーションをDashフレームワークの中で実現します。

VTKVisualization Toolkitを意味しており、主に医療分野でのデータビジュアライゼーションを実現するライブラリです。

特にシミュレーションやセンサーからのデータを、3次元に表現する際によく使われています。

インストール

Dash VTKをインストールするためには、次のコマンドを実行します。

[コマンド]

1
pip install dash_vtk

サンプル

Dash VTKを使った最も簡単なサンプルコードは下記の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import dash
import dash_html_components as html

import dash_vtk
from dash_vtk.utils import to_mesh_state

try:
# VTK 9+
from vtkmodules.vtkImagingCore import vtkRTAnalyticSource
except ImportError:
# VTK =< 8
from vtk.vtkImagingCore import vtkRTAnalyticSource

# Use VTK to get some data
data_source = vtkRTAnalyticSource()
data_source.Update() # <= Execute source to produce an output
dataset = data_source.GetOutput()

# Use helper to get a mesh structure that can be passed as-is to a Mesh
# RTData is the name of the field
mesh_state = to_mesh_state(dataset)

content = dash_vtk.View([
dash_vtk.GeometryRepresentation([
dash_vtk.Mesh(state=mesh_state)
]),
])

# Dash setup
app = dash.Dash(__name__)
server = app.server

app.layout = html.Div(
style={"width": "100%", "height": "400px"},
children=[content],
)

if __name__ == "__main__":
app.run_server(debug=True)

[ブラウザで表示]

立方体が表示され、ドラッグすると角度を変えて表示することができます。

Dash Cytoscape⑳(イベントコールバック/mouseoverEdgeData)

イベントコールバック/mouseoverEdgeData

mouseoverEdgeDataを使うと、カーソルをのせたエッジのデータ辞書を取得することができます。

コールバック関数の入力(Input)にmouseoverEdgeDataを指定します。(41行目)

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import json

import dash
import dash_cytoscape as cyto
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

nodes = [
{'data': {'id': short, 'label': label} }
for short, label in (
('la', 'Los Angeles'), ('nyc', 'New York'), ('to', 'Toronto'),
('mtl', 'Montreal'), ('van', 'Vancouver'), ('chi', 'Chicago'),
('bos', 'Boston'), ('hou', 'Houston')
)
]

edges = [
{'data': {'source': source, 'target': target}}
for source, target in (
('van', 'la'), ('la', 'chi'), ('hou', 'chi'),
('to', 'mtl'), ('mtl', 'bos'), ('nyc', 'bos'),
('to', 'hou'), ('to', 'nyc'), ('la', 'nyc'),
('nyc', 'bos')
)
]

app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape-event',
layout={'name': 'circle'},
elements=edges+nodes,
style={'width': '100%', 'height': '450px'}
),
html.Pre(id='show_json')
])

@app.callback(Output('show_json', 'children'),
Input('cytoscape-event', 'mouseoverEdgeData'))
def displayData(data):
return json.dumps(data, indent=2)

if __name__ == '__main__':
app.run_server(debug=True)

取得したエッジの情報は、json.dumps関数を使って文字列に変換しています。(44行目)

[ブラウザで表示]

カーソルを移動してエッジに合わせると、そのエッジの情報がブラウザ下部に表示されることを確認できます。

Dash Cytoscape⑲(イベントコールバック/mouseoverNodeData)

イベントコールバック/mouseoverNodeData

mouseoverNodeDataを使うと、カーソルをのせたノードのデータ辞書を取得することができます。

コールバック関数の入力(Input)にmouseoverNodeDataを指定します。(41行目)

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import json

import dash
import dash_cytoscape as cyto
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

nodes = [
{'data': {'id': short, 'label': label} }
for short, label in (
('la', 'Los Angeles'), ('nyc', 'New York'), ('to', 'Toronto'),
('mtl', 'Montreal'), ('van', 'Vancouver'), ('chi', 'Chicago'),
('bos', 'Boston'), ('hou', 'Houston')
)
]

edges = [
{'data': {'source': source, 'target': target}}
for source, target in (
('van', 'la'), ('la', 'chi'), ('hou', 'chi'),
('to', 'mtl'), ('mtl', 'bos'), ('nyc', 'bos'),
('to', 'hou'), ('to', 'nyc'), ('la', 'nyc'),
('nyc', 'bos')
)
]

app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape-event',
layout={'name': 'circle'},
elements=edges+nodes,
style={'width': '100%', 'height': '450px'}
),
html.Pre(id='show_json')
])

@app.callback(Output('show_json', 'children'),
Input('cytoscape-event', 'mouseoverNodeData'))
def displayData(data):
if data:
return "You hovered over the city: " + data['label']

if __name__ == '__main__':
app.run_server(debug=True)

上記ソースでは、カーソルをのせたノードのラベルを表示しています。

[ブラウザで表示]

カーソルを移動してノードに合わせると、そのノードのラベルがブラウザ下部に表示されることを確認できます。

Dash Cytoscape⑱(イベントコールバック/tapEdgeData)

イベントコールバック/tapEdgeData

tapEdgeDataを使って、クリックしたエッジのデータ辞書を取得します。

コールバック関数の入力(Input)にtapEdgeDataを指定しています。(62行目)

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import json

import dash
import dash_cytoscape as cyto
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}

nodes = [
{
'data': {'id': short, 'label': label},
'position': {'x': 20*lat, 'y': -20*long}
}
for short, label, long, lat in (
('la', 'Los Angeles', 34.03, -118.25),
('nyc', 'New York', 40.71, -74),
('to', 'Toronto', 43.65, -79.38),
('mtl', 'Montreal', 45.50, -73.57),
('van', 'Vancouver', 49.28, -123.12),
('chi', 'Chicago', 41.88, -87.63),
('bos', 'Boston', 42.36, -71.06),
('hou', 'Houston', 29.76, -95.37)
)
]

edges = [
{'data': {'source': source, 'target': target}}
for source, target in (
('van', 'la'),
('la', 'chi'),
('hou', 'chi'),
('to', 'mtl'),
('mtl', 'bos'),
('nyc', 'bos'),
('to', 'hou'),
('to', 'nyc'),
('la', 'nyc'),
('nyc', 'bos')
)
]

app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape-event-callbacks-1',
layout={'name': 'circle'},
elements=edges+nodes,
style={'width': '100%', 'height': '450px'}
),
html.Pre(id='cytoscape-tapEdgeData-json', style=styles['pre'])
])

@app.callback(Output('cytoscape-tapEdgeData-json', 'children'),
Input('cytoscape-event-callbacks-1', 'tapEdgeData'))
def displayTapEdgeData(data):
return json.dumps(data, indent=2)

if __name__ == '__main__':
app.run_server(debug=True)

取得したエッジ情報は、json.dumps関数を使って文字列に変換しています。(64行目)

[ブラウザで表示]

エッジをクリックするとそのエッジの情報がブラウザ下部に表示されることを確認できます。

Dash Cytoscape⑰(イベントコールバック/tapNodeData)

イベントコールバック/tapNodeData

tapNodeDataを使って、クリックしたノードのデータ辞書を取得します。

コールバック関数の入力(Input)にtapNodeDataを指定しています。(63行目)

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import json

import dash
import dash_cytoscape as cyto
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}

nodes = [
{
'data': {'id': short, 'label': label},
'position': {'x': 20*lat, 'y': -20*long}
}
for short, label, long, lat in (
('la', 'Los Angeles', 34.03, -118.25),
('nyc', 'New York', 40.71, -74),
('to', 'Toronto', 43.65, -79.38),
('mtl', 'Montreal', 45.50, -73.57),
('van', 'Vancouver', 49.28, -123.12),
('chi', 'Chicago', 41.88, -87.63),
('bos', 'Boston', 42.36, -71.06),
('hou', 'Houston', 29.76, -95.37)
)
]

edges = [
{'data': {'source': source, 'target': target}}
for source, target in (
('van', 'la'),
('la', 'chi'),
('hou', 'chi'),
('to', 'mtl'),
('mtl', 'bos'),
('nyc', 'bos'),
('to', 'hou'),
('to', 'nyc'),
('la', 'nyc'),
('nyc', 'bos')
)
]

app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape-event-callbacks-1',
layout={'name': 'circle'},
elements=edges+nodes,
style={'width': '100%', 'height': '450px'}
),
html.Pre(id='cytoscape-tapNodeData-json', style=styles['pre'])
])


@app.callback(Output('cytoscape-tapNodeData-json', 'children'),
Input('cytoscape-event-callbacks-1', 'tapNodeData'))
def displayTapNodeData(data):
return json.dumps(data, indent=2)

if __name__ == '__main__':
app.run_server(debug=True)

取得したノード情報は、json.dumps関数を使って文字列に変換しています。(65行目)

[ブラウザで表示]

ノードをクリックするとそのノードの情報がブラウザ下部に表示されることを確認できます。