spanner-graph-notebook 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- assets/authentication.png +0 -0
- assets/full_viz.png +0 -0
- assets/hero.png +0 -0
- assets/hero_with_properties.png +0 -0
- assets/jupyter-spin-up.png +0 -0
- assets/load_ext.png +0 -0
- assets/mock_data_result.png +0 -0
- assets/new_notebook.png +0 -0
- assets/notebook_package_load.png +0 -0
- assets/query_graph.png +0 -0
- assets/sample_jupyter.png +0 -0
- spanner_graph_notebook-1.0.0.dist-info/LICENSE +202 -0
- spanner_graph_notebook-1.0.0.dist-info/METADATA +169 -0
- spanner_graph_notebook-1.0.0.dist-info/RECORD +40 -0
- spanner_graph_notebook-1.0.0.dist-info/WHEEL +5 -0
- spanner_graph_notebook-1.0.0.dist-info/top_level.txt +2 -0
- spanner_graphs/__init__.py +19 -0
- spanner_graphs/conversion.py +299 -0
- spanner_graphs/database.py +199 -0
- spanner_graphs/graph_entities.py +405 -0
- spanner_graphs/graph_mock_data.csv +51 -0
- spanner_graphs/graph_mock_schema.json +681 -0
- spanner_graphs/magics.py +211 -0
- spanner_graphs/schema_manager.py +60 -0
- templates/assets/images/graph-bg.svg +4 -0
- templates/spanner-graph/models/edge.js +77 -0
- templates/spanner-graph/models/graph-object.js +64 -0
- templates/spanner-graph/models/node.js +77 -0
- templates/spanner-graph/models/schema.js +327 -0
- templates/spanner-graph/spanner-config.js +304 -0
- templates/spanner-graph/spanner-store.js +382 -0
- templates/spanner-graph/visualization/spanner-forcegraph.js +1380 -0
- templates/spanner-graph/visualization/spanner-sidebar.js +904 -0
- templates/template-spannergraph.html +210 -0
- tests/__init__.py +13 -0
- tests/conversion_test.py +163 -0
- tests/database_test.py +62 -0
- tests/graph_entities_test.py +124 -0
- tests/schema_manager_test.py +115 -0
- tests/test_notebook.json +23 -0
spanner_graphs/magics.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Magic class for our visualization"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import base64
|
|
19
|
+
from enum import Enum, auto
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from IPython.core.magic import Magics, magics_class, cell_magic
|
|
24
|
+
from IPython.display import display, clear_output, IFrame
|
|
25
|
+
from networkx import DiGraph
|
|
26
|
+
import ipywidgets as widgets
|
|
27
|
+
from ipywidgets import interact
|
|
28
|
+
from jinja2 import Template
|
|
29
|
+
|
|
30
|
+
from spanner_graphs.conversion import (columns_to_native_numpy,
|
|
31
|
+
prepare_data_for_graphing, SizeMode)
|
|
32
|
+
|
|
33
|
+
from spanner_graphs.database import SpannerDatabase, MockSpannerDatabase
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _load_file(path: list[str]) -> str:
|
|
37
|
+
file_path = os.path.sep.join(path)
|
|
38
|
+
if not os.path.exists(file_path):
|
|
39
|
+
raise FileNotFoundError(f"Template file not found: {file_path}")
|
|
40
|
+
|
|
41
|
+
with open(file_path, 'r') as file:
|
|
42
|
+
content = file.read()
|
|
43
|
+
|
|
44
|
+
return content
|
|
45
|
+
|
|
46
|
+
def _load_image(path: list[str]) -> str:
|
|
47
|
+
file_path = os.path.sep.join(path)
|
|
48
|
+
if not os.path.exists(file_path):
|
|
49
|
+
print("image does not exist")
|
|
50
|
+
return ''
|
|
51
|
+
|
|
52
|
+
if file_path.lower().endswith('.svg'):
|
|
53
|
+
with open(file_path, 'r') as file:
|
|
54
|
+
svg = file.read()
|
|
55
|
+
return base64.b64encode(svg.encode('utf-8')).decode('utf-8')
|
|
56
|
+
else:
|
|
57
|
+
with open(file_path, 'rb') as file:
|
|
58
|
+
return base64.b64decode(file.read()).decode('utf-8')
|
|
59
|
+
|
|
60
|
+
def _generate_html(graph: DiGraph, rows, schema):
|
|
61
|
+
if not isinstance(rows, list):
|
|
62
|
+
rows = []
|
|
63
|
+
|
|
64
|
+
# Get the directory of the current file (magics.py)
|
|
65
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
66
|
+
|
|
67
|
+
# Go up directories until we find the 'templates' folder
|
|
68
|
+
search_dir = current_dir
|
|
69
|
+
while 'templates' not in os.listdir(search_dir):
|
|
70
|
+
parent = os.path.dirname(search_dir)
|
|
71
|
+
if parent == search_dir: # We've reached the root directory after I updated
|
|
72
|
+
raise FileNotFoundError("Could not find 'templates' directory")
|
|
73
|
+
search_dir = parent
|
|
74
|
+
|
|
75
|
+
# Retrieve the javascript content
|
|
76
|
+
template_content = _load_file([search_dir, 'templates', 'template-spannergraph.html'])
|
|
77
|
+
schema_content = _load_file([search_dir, 'templates', 'spanner-graph', 'models', 'schema.js'])
|
|
78
|
+
graph_object_content = _load_file([search_dir, 'templates', 'spanner-graph', 'models', 'graph-object.js'])
|
|
79
|
+
node_content = _load_file([search_dir, 'templates', 'spanner-graph', 'models', 'node.js'])
|
|
80
|
+
edge_content = _load_file([search_dir, 'templates', 'spanner-graph', 'models', 'edge.js'])
|
|
81
|
+
config_content = _load_file([search_dir, 'templates', 'spanner-graph', 'spanner-config.js'])
|
|
82
|
+
store_content = _load_file([search_dir, 'templates', 'spanner-graph', 'spanner-store.js'])
|
|
83
|
+
graph_content = _load_file([search_dir, 'templates', 'spanner-graph', 'visualization', 'spanner-forcegraph.js'])
|
|
84
|
+
sidebar_content = _load_file([search_dir, 'templates', 'spanner-graph', 'visualization', 'spanner-sidebar.js'])
|
|
85
|
+
|
|
86
|
+
# Retrieve image content
|
|
87
|
+
graph_background_image = _load_image([search_dir, "templates", "assets", "images", "graph-bg.svg"])
|
|
88
|
+
|
|
89
|
+
# Create a Jinja2 template
|
|
90
|
+
template = Template(template_content)
|
|
91
|
+
|
|
92
|
+
nodes = []
|
|
93
|
+
for (node_id, node) in graph.nodes(data=True):
|
|
94
|
+
nodes.append(node)
|
|
95
|
+
|
|
96
|
+
edges = []
|
|
97
|
+
for (from_id, to_id, edge) in graph.edges(data=True):
|
|
98
|
+
edges.append(edge)
|
|
99
|
+
|
|
100
|
+
# Render the template with the graph data and JavaScript content
|
|
101
|
+
html_content = template.render(
|
|
102
|
+
graph_background_image=graph_background_image,
|
|
103
|
+
template_content=template_content,
|
|
104
|
+
schema_content=schema_content,
|
|
105
|
+
graph_object_content=graph_object_content,
|
|
106
|
+
node_content=node_content,
|
|
107
|
+
edge_content=edge_content,
|
|
108
|
+
config_content=config_content,
|
|
109
|
+
graph_content=graph_content,
|
|
110
|
+
store_content=store_content,
|
|
111
|
+
sidebar_content=sidebar_content,
|
|
112
|
+
nodes=nodes,
|
|
113
|
+
edges=edges,
|
|
114
|
+
rows=rows,
|
|
115
|
+
schema=schema
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return html_content
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _parse_element_display(element_rep: str) -> dict[str, str]:
|
|
122
|
+
"""Helper function to parse element display fields into a dict."""
|
|
123
|
+
if not element_rep:
|
|
124
|
+
return {}
|
|
125
|
+
res = {
|
|
126
|
+
e.strip().split(":")[0].lower(): e.strip().split(":")[1]
|
|
127
|
+
for e in element_rep.strip().split(",")
|
|
128
|
+
}
|
|
129
|
+
return res
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@magics_class
|
|
133
|
+
class NetworkVisualizationMagics(Magics):
|
|
134
|
+
"""Network visualizer with Networkx"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, shell):
|
|
137
|
+
super().__init__(shell)
|
|
138
|
+
self.database = None
|
|
139
|
+
self.limit = 5
|
|
140
|
+
self.args = None
|
|
141
|
+
self.cell = None
|
|
142
|
+
|
|
143
|
+
def visualize(self, limit):
|
|
144
|
+
"""Helper function to create and display the visualization"""
|
|
145
|
+
query_result, fields, rows, schema_json = self.database.execute_query(self.cell, limit)
|
|
146
|
+
d, ignored_columns = columns_to_native_numpy(query_result, fields)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
graph: DiGraph = prepare_data_for_graphing(
|
|
150
|
+
incoming=d,
|
|
151
|
+
schema_json=schema_json)
|
|
152
|
+
|
|
153
|
+
if len(ignored_columns) > 0:
|
|
154
|
+
print(f"Some returned fields are not graph "
|
|
155
|
+
f"element JSON type, so they are not "
|
|
156
|
+
f"visualized below: {', '.join(ignored_columns)}")
|
|
157
|
+
|
|
158
|
+
# Generate the HTML content
|
|
159
|
+
html_content = _generate_html(graph, rows, schema_json)
|
|
160
|
+
|
|
161
|
+
# Encode the HTML content
|
|
162
|
+
encoded_content = base64.b64encode(html_content.encode()).decode()
|
|
163
|
+
data_uri = f"data:text/html;base64,{encoded_content}"
|
|
164
|
+
|
|
165
|
+
display(IFrame(src=data_uri, width="100%", height="700px"))
|
|
166
|
+
|
|
167
|
+
@cell_magic
|
|
168
|
+
def spanner_graph(self, line: str, cell: str):
|
|
169
|
+
"""spanner_graph function"""
|
|
170
|
+
parser = argparse.ArgumentParser(
|
|
171
|
+
description="Visualize network from Spanner database")
|
|
172
|
+
parser.add_argument("--project", help="GCP project ID")
|
|
173
|
+
parser.add_argument("--instance",
|
|
174
|
+
help="Spanner instance ID")
|
|
175
|
+
parser.add_argument("--database",
|
|
176
|
+
help="Spanner database ID")
|
|
177
|
+
parser.add_argument("--mock",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help="Use mock database")
|
|
180
|
+
|
|
181
|
+
args = parser.parse_args(line.split())
|
|
182
|
+
if not args.mock:
|
|
183
|
+
if not (args.project and args.instance and args.database):
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"Please provide `--project`, `--instance`, "
|
|
186
|
+
"and `--database` values for your query.")
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
self.args = parser.parse_args(line.split())
|
|
190
|
+
self.cell = cell
|
|
191
|
+
|
|
192
|
+
if self.args.mock:
|
|
193
|
+
self.database = MockSpannerDatabase()
|
|
194
|
+
else:
|
|
195
|
+
self.database = SpannerDatabase(self.args.project,
|
|
196
|
+
self.args.instance,
|
|
197
|
+
self.args.database)
|
|
198
|
+
|
|
199
|
+
clear_output(wait=True)
|
|
200
|
+
self.visualize(self.limit)
|
|
201
|
+
except ValueError as e:
|
|
202
|
+
print(f"Error: {e}")
|
|
203
|
+
print("Usage: %%spanner_graph_viz --project PROJECT_ID "
|
|
204
|
+
"--instance INSTANCE_ID --database DATABASE_ID "
|
|
205
|
+
"[--mock] ")
|
|
206
|
+
print(" SELECT ... you query here ...")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def load_ipython_extension(ipython):
|
|
210
|
+
"""Registration function"""
|
|
211
|
+
ipython.register_magics(NetworkVisualizationMagics)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Dict, List, Set
|
|
18
|
+
from .graph_entities import Node
|
|
19
|
+
|
|
20
|
+
class SchemaManager:
|
|
21
|
+
def __init__(self, schema_json: dict = None):
|
|
22
|
+
self.schema_dict = schema_json or {}
|
|
23
|
+
self.node_label_to_property_names = self._build_node_mappings()
|
|
24
|
+
self.unique_node_labels = self._find_unique_node_labels()
|
|
25
|
+
|
|
26
|
+
def _find_unique_node_labels(self) -> Set[str]:
|
|
27
|
+
label_count = {}
|
|
28
|
+
for node_table in self.schema_dict.get('nodeTables', []):
|
|
29
|
+
labelNames = node_table.get('labelNames', [])
|
|
30
|
+
if len(labelNames) == 1:
|
|
31
|
+
label = labelNames[0]
|
|
32
|
+
label_count[label] = label_count.get(label, 0) + 1
|
|
33
|
+
return { label for label, count in label_count.items() if count == 1}
|
|
34
|
+
|
|
35
|
+
def _build_node_mappings(self) -> Dict[str, List[str]]:
|
|
36
|
+
node_label_to_property_names = {}
|
|
37
|
+
for node_table in self.schema_dict.get('nodeTables', []):
|
|
38
|
+
labelNames = node_table.get('labelNames', [])
|
|
39
|
+
if len(labelNames) != 1:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
keyColumns = node_table.get('keyColumns', [])
|
|
43
|
+
propertyDefinitions = node_table.get('propertyDefinitions', [])
|
|
44
|
+
label = labelNames[0]
|
|
45
|
+
key_property_names = []
|
|
46
|
+
for keyColumn in keyColumns:
|
|
47
|
+
for prop in propertyDefinitions:
|
|
48
|
+
if prop.get('valueExpressionSql', '') == keyColumn:
|
|
49
|
+
key_property_names.append(prop.get('propertyDeclarationName'))
|
|
50
|
+
break
|
|
51
|
+
node_label_to_property_names[label] = key_property_names
|
|
52
|
+
|
|
53
|
+
return node_label_to_property_names
|
|
54
|
+
|
|
55
|
+
def get_key_property_names(self, obj: Node) -> List[str]:
|
|
56
|
+
if not isinstance(obj, Node):
|
|
57
|
+
raise TypeError("node expected")
|
|
58
|
+
if len(obj.labels) == 1 and obj.labels[0] in self.unique_node_labels:
|
|
59
|
+
return self.node_label_to_property_names.get(obj.labels[0], [])
|
|
60
|
+
return []
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
class Edge extends GraphObject {
|
|
17
|
+
/**
|
|
18
|
+
* The identifier of the node this edge is directed to.
|
|
19
|
+
* @type {Node}
|
|
20
|
+
*/
|
|
21
|
+
target;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The identifier of the node this edge originates from.
|
|
25
|
+
* @type {Node}
|
|
26
|
+
*/
|
|
27
|
+
source;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} EdgeData - The label shown in the sidebar or graph.
|
|
31
|
+
* @property {string} label
|
|
32
|
+
* @property {string|Object} properties - An optional property:value map.
|
|
33
|
+
* @property {Object} key_property_names
|
|
34
|
+
* @property {string} color
|
|
35
|
+
* @property {number} from
|
|
36
|
+
* @property {number} to
|
|
37
|
+
* @property {number} id
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An edge is the line that connects two Nodes.
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} params
|
|
44
|
+
* @param {string} params.to - The identifier of the node this edge is directed to.
|
|
45
|
+
* @param {string} params.from - The identifier of the node this edge originates from.
|
|
46
|
+
* @param {string} params.label - The label for the edge.
|
|
47
|
+
* @param {string|Object} params.title - The optional property:value map for the edge.
|
|
48
|
+
* @extends GraphObject
|
|
49
|
+
*/
|
|
50
|
+
constructor({ to, from, label, properties, title }) {
|
|
51
|
+
super({ label, title, properties });
|
|
52
|
+
|
|
53
|
+
if (!this.isNumber(to) || !this.isNumber(from)) {
|
|
54
|
+
this.instantiated = false;
|
|
55
|
+
console.log('Failed to instantiate edge', { reason: '"to" and "from" are not numbers', to, from, label, title });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* preserve ID from getting
|
|
61
|
+
* overwritten by ForceGraph
|
|
62
|
+
*/
|
|
63
|
+
this.to = to;
|
|
64
|
+
this.from = from;
|
|
65
|
+
|
|
66
|
+
this.source = from;
|
|
67
|
+
this.target = to;
|
|
68
|
+
|
|
69
|
+
this.instantiated = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
isNumber(value) {
|
|
73
|
+
return Number.isFinite(Number(value));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
window[namespace].Edge = Edge;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
class GraphObject {
|
|
17
|
+
/**
|
|
18
|
+
* The label of the Graph Object.
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
label;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A map of properties and their values describing the Graph Ebject.
|
|
25
|
+
* @type {{[key: string]: string}}
|
|
26
|
+
*/
|
|
27
|
+
properties = {};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A boolean indicating if the Graph Object object has been instantiated.
|
|
31
|
+
* @type {boolean}
|
|
32
|
+
*/
|
|
33
|
+
instantiated = false;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The key property names for the graph element determines what keys in the properties
|
|
37
|
+
* are to be displayed.
|
|
38
|
+
* @type {string[]}
|
|
39
|
+
*/
|
|
40
|
+
key_property_names = [];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The reason for the instantiation error.
|
|
44
|
+
* @type {string}
|
|
45
|
+
*/
|
|
46
|
+
instantiationErrorReason;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An object that renders on the graph.
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} params
|
|
53
|
+
* @param {string} params.label - The label for the object.
|
|
54
|
+
* @param {Object} params.properties - The optional property:value map for the object.
|
|
55
|
+
*/
|
|
56
|
+
constructor({ label, properties, key_property_names }) {
|
|
57
|
+
this.label = label;
|
|
58
|
+
this.properties = properties;
|
|
59
|
+
this.key_property_names = key_property_names;
|
|
60
|
+
this.instantiated = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
window[namespace].GraphObject = GraphObject;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Represents a graph node.
|
|
18
|
+
* @class
|
|
19
|
+
*/
|
|
20
|
+
class Node extends GraphObject {
|
|
21
|
+
/**
|
|
22
|
+
* Arbitrary value
|
|
23
|
+
* @type {number}
|
|
24
|
+
*/
|
|
25
|
+
value;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The numeric value associated with the node.
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
id;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The numeric value associated with the neighborhood.
|
|
35
|
+
* This may be used by the visualization implementation for clustering.
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
38
|
+
neighborhood = 0;
|
|
39
|
+
|
|
40
|
+
color = '#ec0001'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {Object} NodeData - The label shown in the sidebar or graph.
|
|
44
|
+
* @property {string} label
|
|
45
|
+
* @property {Object} properties - An optional property:value map.
|
|
46
|
+
* @property {Object} key_property_names
|
|
47
|
+
* @property {string} color
|
|
48
|
+
* @property {number} id
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A node on the graph
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} params
|
|
55
|
+
* @param {string} params.label - The label for the edge.
|
|
56
|
+
* @param {string|Object} params.title - The optional property:value map for the edge.
|
|
57
|
+
* @param {string} params.color - The color of the edge
|
|
58
|
+
* @extends GraphObject
|
|
59
|
+
*/
|
|
60
|
+
constructor({ label, title, properties, value, id, neighborhood, color, key_property_names }) {
|
|
61
|
+
super({ label, title, properties, key_property_names });
|
|
62
|
+
|
|
63
|
+
if (typeof id != 'number') {
|
|
64
|
+
this.instantiationErrorReason = "Node does not have an ID";
|
|
65
|
+
console.error(this.instantiationErrorReason, { label, title, value, id });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.id = id;
|
|
70
|
+
this.value = value;
|
|
71
|
+
this.instantiated = true;
|
|
72
|
+
this.neighborhood = typeof neighborhood === 'number' ? neighborhood : 0;
|
|
73
|
+
this.color = color;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
window[namespace].Node = Node;
|