"
14 | # includes modules with methods run()
15 | # and excludes if ('enabled' in info() and info()['enabled'] == False)
16 | # ... and further include/exclude via info()['tags']
17 | AppPicker().load_active_app()
18 |
19 | # AppPicker(include=[], exclude=['demo']).load_active_app()
20 |
--------------------------------------------------------------------------------
/src/python/neptune_helper/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/graphistry/graph-app-kit/2e05765413393613240121173dce635f0ad0ce18/src/python/neptune_helper/__init__.py
--------------------------------------------------------------------------------
/src/python/neptune_helper/df_helper.py:
--------------------------------------------------------------------------------
1 | def vertex_to_dict(vertex):
2 | d = {}
3 | for k in vertex.keys():
4 | if isinstance(vertex[k], list):
5 | d[str(k)] = vertex[k][0]
6 | else:
7 | d[str(k)] = vertex[k]
8 | d['id'] = d.pop('T.id')
9 | d['label'] = d.pop('T.label')
10 | return d
11 |
12 |
13 | def edge_to_dict(edge, start_id, end_id):
14 | d = {}
15 | for k in edge.keys():
16 | if isinstance(edge[k], list):
17 | d[str(k)] = edge[k][0]
18 | else:
19 | d[str(k)] = edge[k]
20 | d['id'] = d.pop('T.id')
21 | d['label'] = d.pop('T.label')
22 | d['source'] = start_id
23 | d['target'] = end_id
24 | return d
25 |
26 |
27 | def flatten_df(df):
28 |
29 | def obj_as_primitive(v):
30 | if (v is None) or type(v) == str:
31 | return v
32 | if type(v) == list:
33 | return ','.join([str(x) for x in v])
34 | return str(v)
35 |
36 | df2 = df.copy(deep=False)
37 | for c in df.columns:
38 | if df2[c].dtype.name == 'object':
39 | df2[c] = df2[c].apply(obj_as_primitive)
40 |
41 | return df2
42 |
--------------------------------------------------------------------------------
/src/python/neptune_helper/gremlin_helper.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 | from gremlin_python import statics
4 | from gremlin_python.structure.graph import Graph
5 | from gremlin_python.process.graph_traversal import __
6 | from gremlin_python.process.anonymous_traversal import traversal
7 | from gremlin_python.process.strategies import *
8 | from gremlin_python.process.traversal import *
9 | from gremlin_python.structure.graph import Path, Vertex, Edge
10 | from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
11 | from util import getChild
12 |
13 | logger = getChild(__name__)
14 |
15 | def connect_to_neptune():
16 | """Creates a connection to Neptune and returns the traversal source"""
17 | if ('NEPTUNE_READER_HOST' in os.environ and 'NEPTUNE_READER_PORT' in os.environ
18 | and 'NEPTUNE_READER_PROTOCOL' in os.environ):
19 | server = os.environ["NEPTUNE_READER_HOST"]
20 | port = os.environ["NEPTUNE_READER_PORT"]
21 | protocol = os.environ["NEPTUNE_READER_PROTOCOL"]
22 | endpoint = f'{protocol}://{server}:{port}/gremlin'
23 | logger.info(endpoint)
24 | connection = DriverRemoteConnection(endpoint, 'g')
25 | gts = traversal().withRemote(connection)
26 | return (gts, connection)
27 | else:
28 | logging.error("Internal Configuraiton Error Occurred. ")
29 | return None
30 |
--------------------------------------------------------------------------------
/src/python/requirements-app.txt:
--------------------------------------------------------------------------------
1 | ###
2 | ### Use this file for your custom view requirements here vs in the requirements-system.txt
3 | ###
4 |
--------------------------------------------------------------------------------
/src/python/requirements-system.txt:
--------------------------------------------------------------------------------
1 | graphistry==0.33.8
2 | streamlit==1.25.0
3 |
4 | ################
5 | #
6 | # Per-DB
7 | #
8 | ################
9 |
10 | ### Neptune
11 | # bumping version to get around tornado dependency issue w/ streamlit
12 | # gremlinpython==3.4.10
13 | gremlinpython==3.7.0
14 |
15 | # tcook: not used?
16 | # sshtunnel==0.1.5
17 |
18 | ### TigerGraph
19 | pyTigerGraph # Unpinned as TG Cloud is rapidly moving
20 | plotly # for demos
21 |
22 | ### Splunk
23 | splunk-sdk==1.7.4
24 |
25 |
--------------------------------------------------------------------------------
/src/python/test/README.md:
--------------------------------------------------------------------------------
1 | # Placeholder
2 |
--------------------------------------------------------------------------------
/src/python/test/test_stub.py:
--------------------------------------------------------------------------------
1 | def test_stub():
2 | assert True is True
3 |
--------------------------------------------------------------------------------
/src/python/tox.ini:
--------------------------------------------------------------------------------
1 | # tox (https://tox.readthedocs.io/) is a tool for running tests
2 | # in multiple virtualenvs. This configuration file will run the
3 | # test suite on all supported python versions. To use it, "pip install tox"
4 | # and then run "tox" from this directory.
5 |
6 | [tox]
7 | envlist = py37
8 |
9 | [testenv]
10 | deps =
11 | flake8
12 | pytest
13 | commands =
14 | python -m pytest test
15 |
16 | [flake8]
17 | ignore = E121,E122,E126,E127,E128,E129,E131,E201,E202,E401,E501,F401,F403,F541,W503
18 | max-complexity = 10
19 | max-line-length = 127
20 |
--------------------------------------------------------------------------------
/src/python/util/__init__.py:
--------------------------------------------------------------------------------
1 | from .log import getChild
2 |
--------------------------------------------------------------------------------
/src/python/util/log.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | # logging.basicConfig(format="%(levelname)s %(asctime)s %(name)s:%(message)s\n")
5 | logging.basicConfig(format="%(levelname)s %(asctime)s %(name)s:%(message)s\n")
6 |
7 | def getChild(*args, **kwargs):
8 |
9 | logger = logging.getLogger('gak')
10 |
11 | log_level_str = os.environ.get('LOG_LEVEL', 'ERROR').upper()
12 | log_level = getattr(logging, log_level_str)
13 | logger.debug(f"util.log log_level == {log_level_str} ({log_level})")
14 |
15 | out=logger.getChild(*args, **kwargs)
16 | out.setLevel(log_level)
17 |
18 | out.debug(f"calling logging.setLevel() to log_level == {log_level}")
19 |
20 | return out
21 |
--------------------------------------------------------------------------------
/src/python/views/demo_01_fancy/__init__.py:
--------------------------------------------------------------------------------
1 | import graphistry, os, pandas as pd, streamlit as st
2 | from components import GraphistrySt, URLParam
3 | from graphistry import PyGraphistry
4 | from css import all_css
5 | from time import sleep
6 | import logging
7 |
8 | ############################################
9 | #
10 | # DASHBOARD SETTINGS
11 | #
12 | ############################################
13 | # Controls how entrypoint.py picks it up
14 |
15 |
16 | app_id = 'app_01'
17 | logger = logging.getLogger(app_id)
18 | urlParams = URLParam(app_id)
19 |
20 |
21 | def info():
22 | return {
23 | 'id': app_id,
24 | 'name': 'INTRO: fancy graph',
25 | 'enabled': True,
26 | 'tags': ['demo', 'demo_intro']
27 | }
28 |
29 |
30 | def run():
31 | run_all()
32 |
33 |
34 | ############################################
35 | #
36 | # CUSTOM CSS
37 | #
38 | ############################################
39 | # Have fun!
40 |
41 | def custom_css():
42 |
43 | all_css() # our favorites
44 |
45 |
46 | ############################################
47 | #
48 | # SIDEBAR RENDER AERA
49 | #
50 | ############################################
51 | # Given URL params, render left sidebar form and return combined filter settings
52 |
53 | # #https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
54 | def sidebar_area():
55 | st.sidebar.title('Pick graph')
56 |
57 | n_init = urlParams.get_field('N', 100)
58 | n = st.sidebar.number_input('Number of nodes', min_value=10, max_value=100000, value=n_init, step=20)
59 | urlParams.set_field('N', n)
60 |
61 | base_url = os.environ.get('BASE_URL', 'http://localhost:8501')
62 |
63 | edges_df = pd.concat([
64 | pd.DataFrame({
65 | 's': [x for x in range(n)],
66 | 'd': [(x + 1) % n for x in range(n)],
67 | 'link': [
68 | '' + str(x % n) + " nodes"
69 | for x in range(n)
70 | ]
71 | }),
72 | pd.DataFrame({
73 | 's': [x for x in range(n)],
74 | 'd': [(x + 6) % n for x in range(n)],
75 | 'link': [
76 | '' + str(x % n) + " nodes"
77 | for x in range(n)
78 | ]
79 | })
80 | ], sort=False, ignore_index=True)
81 |
82 | st.sidebar.title("Filter")
83 | option_to_label = {
84 | 'all': 'All',
85 | 'odd': 'Odds',
86 | 'even': 'Evens'
87 | }
88 |
89 | filter_by_node_type_init = urlParams.get_field('filter_by_type', default='all')
90 | filter_by_node_type = \
91 | st.sidebar.selectbox(
92 | 'Filter nodes by:',
93 | ('all', 'odd', 'even'),
94 | index=('all', 'odd', 'even').index(filter_by_node_type_init),
95 | format_func=(lambda option: option_to_label[option]))
96 | urlParams.set_field('filter_by_type', filter_by_node_type)
97 |
98 | filter_by_node_range_init = (
99 | urlParams.get_field('filter_by_node_range_min', default=0),
100 | urlParams.get_field('filter_by_node_range_max', default=n))
101 | logger.info('filter_by_node_range_init: %s :: %s', filter_by_node_range_init, type(filter_by_node_range_init))
102 | filter_by_node_range = st.sidebar.slider(
103 | 'Filter for nodes in range:',
104 | min_value=0, max_value=n, value=filter_by_node_range_init, step=1)
105 | urlParams.set_field('filter_by_node_range_min', filter_by_node_range[0])
106 | urlParams.set_field('filter_by_node_range_max', filter_by_node_range[1])
107 |
108 | return {
109 | 'n': n,
110 | 'edges_df': edges_df,
111 | 'node_type': filter_by_node_type,
112 | 'node_range': filter_by_node_range
113 | }
114 |
115 |
116 | ############################################
117 | #
118 | # FILTER PIPELINE
119 | #
120 | ############################################
121 | # Given filter settings, generate/cache/return dataframes & viz
122 |
123 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
124 | @st.cache_data
125 | def run_filters(node_type, node_range, edges_df, n):
126 |
127 | filtered_edges_df = edges_df
128 | if node_type == 'all':
129 | pass
130 | elif node_type == 'odd':
131 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['s'] % 2 == 1 ]
132 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['d'] % 2 == 1 ]
133 | elif node_type == 'even':
134 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['s'] % 2 == 0 ]
135 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['d'] % 2 == 0 ]
136 | else:
137 | raise Exception('Unknown filter1 option result: %s' % node_type)
138 |
139 | if node_range[0] > 0:
140 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['s'] >= node_range[0] ]
141 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['d'] >= node_range[0] ]
142 | if node_range[1] <= n:
143 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['s'] <= node_range[1] ]
144 | filtered_edges_df = filtered_edges_df[ filtered_edges_df['d'] <= node_range[1] ]
145 |
146 | # include viz generation as part of cache
147 | url = plot_url(filtered_edges_df, n)
148 |
149 | return {
150 | 'edges_df': filtered_edges_df,
151 | 'url': url
152 | }
153 |
154 |
155 | ############################################
156 | #
157 | # VIZ
158 | #
159 | ############################################
160 |
161 |
162 | def plot_url(edges_df, n):
163 |
164 | nodes_df = pd.DataFrame({
165 | 'n': pd.concat([edges_df['s'], edges_df['d']]).unique()
166 | })
167 |
168 | nodes_df['nc'] = nodes_df['n'].apply(lambda v: 0x01000000 * round(255 * v / n))
169 |
170 | logger.info('Starting graphistry plot')
171 | if not GraphistrySt().test_login():
172 | return ''
173 |
174 | url = graphistry\
175 | .bind(source="s", destination="d")\
176 | .edges(edges_df)\
177 | .nodes(nodes_df)\
178 | .bind(node='n', point_color='nc')\
179 | .settings(url_params={
180 | 'pointSize': 0.3,
181 | 'splashAfter': 'false',
182 | 'bg': '%23' + 'f0f2f6'
183 | })\
184 | .plot(render=False)
185 |
186 | logger.info('Generated viz, got back urL: %s', url)
187 |
188 | return url
189 |
190 |
191 | ############################################
192 | #
193 | # MAIN RENDER AERA
194 | #
195 | ############################################
196 | # Given configured filters and computed results (cached), render
197 |
198 | def main_area(edges_df, url):
199 |
200 | logger.debug('rendering main area, with url: %s', url)
201 | gst = GraphistrySt()
202 | if PyGraphistry._is_authenticated:
203 | gst.render_url(url)
204 | else:
205 | st.title("Welcome to graph-app-kit!")
206 | st.write("""
207 | This particular demo requires configuring your graph-app-kit with service credentials for
208 | accessing your Graphistry server
209 |
210 | If this is the first time you are seeing graph-app-kit, it is Graphistry's open-source extension
211 | of the https://streamlit.io/ low-code Python dashboarding tool. It adds:
212 | * Optional Docker, Docker Compose, and AWS CloudFormation self-hosted quick launchers
213 | * Multiple dashboard support
214 | * Optional GPU & AI dependencies (Nvidia driver, RAPIDS, PyTorch) aligned with Graphistry releases
215 | * Graph computing dependencies (Gremlin, TigerGraph, ...)
216 | * A Graphistry plotting component
217 |
218 | Starting with Graphistry 2.39, graph-app-kit comes prebundled:
219 | * Public and staff-only Private dashboards
220 | * Control access via User -> Admin port -> DJANGO-WAFFLE -> Flags"
221 | * ... then edit to desired visibility for flag_show_public_dashboard, flag_show_private_dashboard
222 | * ... and optionally prevent running of the services via your docker-compose.override.yml
223 | """)
224 |
225 |
226 | ############################################
227 | #
228 | # Putting it all together
229 | #
230 | ############################################
231 |
232 | def run_all():
233 |
234 | custom_css()
235 |
236 | try:
237 |
238 | # Render sidebar and get current settings
239 | sidebar_filters = sidebar_area()
240 |
241 | # logger.debug('sidebar_filters: %s', sidebar_filters)
242 |
243 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
244 | # Selective mark these as URL params as well
245 | filter_pipeline_result = run_filters(**sidebar_filters)
246 |
247 | # Render main viz area based on computed filter pipeline results and sidebar settings
248 | main_area(**filter_pipeline_result)
249 |
250 | except Exception as exn:
251 | st.write('Error loading dashboard')
252 | st.write(exn)
253 |
--------------------------------------------------------------------------------
/src/python/views/demo_02_disabled/__init__.py:
--------------------------------------------------------------------------------
1 | import streamlit as st
2 |
3 |
4 | def info():
5 | return {
6 | 'id': 'app_02',
7 | 'name': 'INTRO: disabled',
8 | 'enabled': False,
9 | 'tags': ['demo', 'demo_intro']
10 | }
11 |
12 |
13 | def run():
14 | st.title('app2')
15 | st.markdown('hello! (disabled: not visible in menu)')
16 |
--------------------------------------------------------------------------------
/src/python/views/demo_03_minimal/__init__.py:
--------------------------------------------------------------------------------
1 | import streamlit as st
2 |
3 | # For copy/paste, 04_simple is probably better
4 |
5 |
6 | def info():
7 | return {
8 | 'id': 'app_03',
9 | 'name': 'INTRO: minimal',
10 | 'tags': ['demo', 'demo_intro']
11 | }
12 |
13 |
14 | def run():
15 | st.title('app3')
16 | st.markdown('hello! (minimal)')
17 |
--------------------------------------------------------------------------------
/src/python/views/demo_04_simple/__init__.py:
--------------------------------------------------------------------------------
1 | import graphistry, pandas as pd, streamlit as st
2 | from components import GraphistrySt, URLParam
3 | from css import all_css
4 | import logging, os
5 |
6 | ############################################
7 | #
8 | # DASHBOARD SETTINGS
9 | #
10 | ############################################
11 | # Controls how entrypoint.py picks it up
12 |
13 |
14 | app_id = 'app_04'
15 | logger = logging.getLogger(app_id)
16 | urlParams = URLParam(app_id)
17 |
18 |
19 | def info():
20 | return {
21 | 'id': app_id,
22 | 'name': 'INTRO: simple pipeline',
23 | 'tags': ['demo', 'demo_intro']
24 | }
25 |
26 |
27 | def run():
28 | run_all()
29 |
30 |
31 | ############################################
32 | #
33 | # PIPELINE PIECES
34 | #
35 | ############################################
36 |
37 |
38 | # Have fun!
39 | def custom_css():
40 | all_css()
41 | st.markdown(
42 | """""", unsafe_allow_html=True)
45 |
46 |
47 | # Given URL params, render left sidebar form and return combined filter settings
48 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
49 | def sidebar_area():
50 |
51 | # regular param (not in url)
52 | e = st.sidebar.number_input('Number of edges', min_value=10, max_value=100000, value=100, step=20)
53 |
54 | # deep-linkable param (in url)
55 | n_init = urlParams.get_field('N', 100)
56 | n = st.sidebar.number_input('Number of nodes', min_value=10, max_value=100000, value=n_init, step=20)
57 | urlParams.set_field('N', n)
58 |
59 | return {'num_nodes': n, 'num_edges': e}
60 |
61 |
62 | # Given filter settings, generate/cache/return dataframes & viz
63 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
64 | @st.cache_data
65 | def run_filters(num_nodes, num_edges):
66 | nodes_df = pd.DataFrame({ 'n': [x for x in range(0, num_nodes)] })
67 | edges_df = pd.DataFrame({
68 | 's': [x % num_nodes for x in range(0, num_edges)],
69 | 'd': [(x + 1) % num_nodes for x in range(0, num_edges)],
70 | })
71 | graph_url = \
72 | graphistry.nodes(nodes_df).edges(edges_df) \
73 | .bind(source='s', destination='d', node='n')\
74 | .plot(render=False)
75 | return { 'nodes_df': nodes_df, 'edges_df': edges_df, 'graph_url': graph_url }
76 |
77 |
78 | def main_area(num_nodes, num_edges, nodes_df, edges_df, graph_url):
79 | logger.debug('rendering main area, with url: %s', graph_url)
80 | GraphistrySt().render_url(graph_url)
81 |
82 |
83 | ############################################
84 | #
85 | # PIPELINE FLOW
86 | #
87 | ############################################
88 |
89 |
90 | def run_all():
91 |
92 | custom_css()
93 |
94 | try:
95 |
96 | # Render sidebar and get current settings
97 | sidebar_filters = sidebar_area()
98 |
99 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
100 | # Selective mark these as URL params as well
101 | filter_pipeline_result = run_filters(**sidebar_filters)
102 |
103 | # Render main viz area based on computed filter pipeline results and sidebar settings
104 | main_area(**sidebar_filters, **filter_pipeline_result)
105 |
106 | except Exception as exn:
107 | st.write('Error loading dashboard')
108 | st.write(exn)
109 |
--------------------------------------------------------------------------------
/src/python/views/demo_avr/app.css:
--------------------------------------------------------------------------------
1 | .block-container {
2 | padding-top: 0rem;
3 | padding-bottom: 0rem;
4 | padding-left: 1rem;
5 | padding-right: 1rem;
6 | }
7 |
8 | .main {
9 | align-items: left;
10 | }
11 |
12 | h2 {
13 | padding-top: 0rem;
14 | padding-bottom: 0.5rem;
15 | }
16 |
17 | [data-testid="stSidebar"] {
18 | width: 300px !important;
19 | }
20 |
21 | .e1fqkh3o4 {
22 | padding-top: 3.2rem;
23 | padding-bottom: 0rem;
24 | padding-left: 0rem;
25 | padding-right: 0rem;
26 | }
27 |
28 | /* Hide the Built with streamlit header/footer */
29 | header {
30 | display: none !important;
31 | }
32 |
33 | footer {
34 | display: none !important;
35 | }
36 |
37 | hr {
38 | margin-block-start: 0.1rem;
39 | margin-block-end: 0.1rem;
40 | }
--------------------------------------------------------------------------------
/src/python/views/demo_bio_01_funcoup/__init__.py:
--------------------------------------------------------------------------------
1 | import graphistry, os, pandas as pd, streamlit as st
2 | from components import GraphistrySt, URLParam
3 | from graphistry import PyGraphistry
4 | from css import all_css
5 | from time import sleep
6 | import logging
7 |
8 | ############################################
9 | #
10 | # DASHBOARD SETTINGS
11 | #
12 | ############################################
13 | # Controls how entrypoint.py picks it up
14 |
15 |
16 | app_id = 'app_bio_01'
17 | logger = logging.getLogger(app_id)
18 | urlParams = URLParam(app_id)
19 |
20 |
21 | def info():
22 | return {
23 | 'id': app_id,
24 | 'name': 'Bio: FUNCOUP',
25 | 'enabled': True,
26 | 'tags': ['bio', 'large', 'funcoup','demo'],
27 | }
28 |
29 |
30 | def run():
31 | run_all()
32 |
33 |
34 | ############################################
35 | #
36 | # CUSTOM CSS
37 | #
38 | ############################################
39 | # Have fun!
40 |
41 | def custom_css():
42 |
43 | all_css() # our favorites
44 |
45 |
46 | ############################################
47 | #
48 | # SIDEBAR RENDER AERA
49 | #
50 | ############################################
51 | # Given URL params, render left sidebar form and return combined filter settings
52 |
53 | # #https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
54 | def sidebar_area():
55 | st.sidebar.title("Select a Species")
56 | species_to_label = {
57 | 'A.thaliana': 'A.thaliana',
58 | 'B.subtilis': 'B.subtilis',
59 | 'B.taurus': 'B.taurus',
60 | 'C.elegans': 'C.elegans',
61 | 'C.familiaris': 'C.familiaris',
62 | 'C.intestinalis': 'C.intestinalis',
63 | 'D.discoideum': 'D.discoideum',
64 | 'D.melanogaster': 'D.melanogaster',
65 | 'D.rerio': 'D.rerio',
66 | 'E.coli': 'E.coli',
67 | 'G.gallus': 'G.gallus',
68 | 'H.sapiens': 'H.sapiens',
69 | 'M.jannaschii': 'M.jannaschii',
70 | 'M.musculus': 'M.musculus',
71 | 'O.sativa': 'O.sativa',
72 | 'P.falciparum': 'P.falciparum',
73 | 'R.norvegicus': 'R.norvegicus',
74 | 'S.cerevisiae': 'S.cerevisiae',
75 | 'S.pombe': 'S.pombe',
76 | 'S.scrofa': 'S.scrofa',
77 | 'S.solfataricus': 'S.solfataricus',
78 | }
79 |
80 | base_url = os.environ['BASE_URL']
81 |
82 | filter_by_org_type_init = urlParams.get_field('filter_by_org', default='B.subtilis')
83 | filter_by_org_type = \
84 | st.sidebar.selectbox(
85 | 'Choose organism:',
86 | ('A.thaliana', 'B.subtilis', 'B.taurus','C.elegans','C.familiaris','C.intestinalis','D.discoideum','D.melanogaster','D.rerio','E.coli','G.gallus','H.sapiens','M.jannaschii','M.musculus','O.sativa','P.falciparum','R.norvegicus','S.cerevisiae','S.pombe','S.scrofa','S.solfataricus'),
87 | index=('A.thaliana', 'B.subtilis', 'B.taurus','C.elegans','C.familiaris','C.intestinalis','D.discoideum','D.melanogaster','D.rerio','E.coli','G.gallus','H.sapiens','M.jannaschii','M.musculus','O.sativa','P.falciparum','R.norvegicus','S.cerevisiae','S.pombe','S.scrofa','S.solfataricus').index(filter_by_org_type_init),
88 | format_func=(lambda option: species_to_label[option]))
89 | urlParams.set_field('filter_by_org', filter_by_org_type)
90 |
91 | st.sidebar.title("Select a Network Type")
92 | umap_to_label = {
93 | True: 'UMAP',
94 | False: 'FunCoup',
95 | }
96 |
97 | filter_by_umap_type_init = urlParams.get_field('filter_by_umap', default='FunCoup')
98 | filter_by_umap_type = \
99 | st.sidebar.selectbox(
100 | 'Display functional coupling network (select link evidence below) or UMAP against all 40 evidence types:',
101 | (True,False),
102 | index=(True,False).index(filter_by_umap_type_init),
103 | format_func=(lambda option: umap_to_label[option]))
104 | urlParams.set_field('filter_by_umap', filter_by_umap_type)
105 |
106 | if filter_by_umap_type is 'UMAP':
107 | filter_by_net_type = 'full'
108 | else:
109 | filter_by_net_type = 'compact'
110 |
111 | st.sidebar.title("Select an Evidence Type")
112 | edge_to_label = {'PFC':'PFC', 'FBS_max':'FBS_max'}
113 |
114 | filter_by_node_type_init = urlParams.get_field('filter_by_node', default='PFC')
115 | filter_by_node_type = \
116 | st.sidebar.selectbox(
117 | 'for FunCoup Network display',
118 | ('PFC', 'FBS_max'),
119 | index=('PFC', 'FBS_max').index(filter_by_node_type_init),
120 | format_func=(lambda option: edge_to_label[option]))
121 | urlParams.set_field('filter_by_node', filter_by_node_type)
122 |
123 |
124 | edges_df = pd.read_csv('https://funcoup.org/downloads/download.action?type=network&instanceID=24480085&fileName=FC5.0_'+filter_by_org_type+'_'+filter_by_net_type+'.gz', sep='\t')
125 |
126 | return {
127 | 'edges_df': edges_df,
128 | 'node_type': filter_by_node_type,
129 | 'umap_type': filter_by_umap_type,
130 | }
131 |
132 |
133 | ############################################
134 | #
135 | # FILTER PIPELINE
136 | #
137 | ############################################
138 | # Given filter settings, generate/cache/return dataframes & viz
139 |
140 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
141 | @st.cache_data
142 | def run_filters(edges_df, node_type, umap_type=False):
143 |
144 | filtered_edges_df = edges_df
145 | # filtered_edges_df = filtered_edges_df.replace({'ENSG00000':''},regex=True)
146 | filtered_edges_df.columns=filtered_edges_df.columns.str.split(':').str[1]
147 |
148 | # include viz generation as part of cache
149 | url = plot_url(filtered_edges_df,node_type,umap_type)
150 |
151 | return {
152 | 'edges_df': filtered_edges_df,
153 | 'url': url,
154 | }
155 |
156 |
157 | ############################################
158 | #
159 | # VIZ
160 | #
161 | ############################################
162 |
163 |
164 | def plot_url(edges_df,node_type, umap_type=False):
165 |
166 | edges_df.replace({'ENSG00000':''},regex=True,inplace=True) ## remove ENSG00000 from gene names for better compression
167 |
168 | nodes_df = pd.DataFrame({
169 | 'n': pd.concat([edges_df['Gene1'], edges_df['Gene2']]).unique()
170 | })
171 | n = len(nodes_df)
172 |
173 | nodes_df['ind'] = nodes_df.index
174 | nodes_df['nc'] = nodes_df['ind'].apply(lambda v: 0x01000000 * round(255 * v / n,2))
175 |
176 | logger.info('Starting graphistry plot')
177 | if not GraphistrySt().test_login():
178 | return ''
179 |
180 | if umap_type == False:
181 | url = graphistry\
182 | .edges(edges_df)\
183 | .bind(source="Gene1", destination="Gene2", edge_weight=node_type)\
184 | .nodes(nodes_df)\
185 | .bind(node='n', point_color='nc')\
186 | .settings(url_params={
187 | 'pointSize': 0.3,
188 | 'splashAfter': 'false',
189 | 'bg': '%23' + 'f0f2f6'
190 | })\
191 | .plot(render=False)#, as_files=True, suffix='.html', output=None, open=False)
192 | elif umap_type == True:
193 |
194 | AA = graphistry\
195 | .nodes(edges_df)\
196 | .bind(source="Gene1", destination="Gene2")\
197 | .settings(url_params={
198 | 'pointSize': 0.3,
199 | 'splashAfter': 'false',
200 | 'bg': '%23' + 'f0f2f6'
201 | })\
202 | .umap(feature_engine='dirty_cat',engine='umap_learn',memoize=True)
203 | emb2=AA._node_embedding
204 | url=graphistry.nodes(emb2.reset_index(),'index').edges(AA._edges,'_src_implicit','_dst_implicit').bind(point_x="x",point_y="y").settings(url_params={"play":0}).addStyle(bg={'color': '#eee'}).plot(render=False)
205 |
206 | logger.info('Generated viz, got back urL: %s', url)
207 |
208 | return url
209 |
210 |
211 | ############################################
212 | #
213 | # MAIN RENDER AERA
214 | #
215 | ############################################
216 | # Given configured filters and computed results (cached), render
217 |
218 | def main_area(edges_df, url):
219 |
220 | logger.debug('rendering main area, with url: %s', url)
221 | gst = GraphistrySt()
222 | if PyGraphistry._is_authenticated:
223 | gst.render_url(url)
224 | else:
225 | st.title("Welcome to graph-app-kit!")
226 | st.write("""
227 | This particular demo requires configuring your graph-app-kit with service credentials for
228 | accessing your Graphistry server
229 |
230 | If this is the first time you are seeing graph-app-kit, it is Graphistry's open-source extension
231 | of the https://streamlit.io/ low-code Python dashboarding tool. It adds:
232 | * Optional Docker, Docker Compose, and AWS CloudFormation self-hosted quick launchers
233 | * Multiple dashboard support
234 | * Optional GPU & AI dependencies (Nvidia driver, RAPIDS, PyTorch) aligned with Graphistry releases
235 | * Graph computing dependencies (Gremlin, TigerGraph, ...)
236 | * A Graphistry plotting component
237 |
238 | Starting with Graphistry 2.39, graph-app-kit comes prebundled:
239 | * Public and staff-only Private dashboards
240 | * Control access via User -> Admin port -> DJANGO-WAFFLE -> Flags"
241 | * ... then edit to desired visibility for flag_show_public_dashboard, flag_show_private_dashboard
242 | * ... and optionally prevent running of the services via your docker-compose.override.yml
243 | """)
244 |
245 |
246 | ############################################
247 | #
248 | # Putting it all together
249 | #
250 | ############################################
251 |
252 | def run_all():
253 |
254 | custom_css()
255 |
256 | try:
257 |
258 | # Render sidebar and get current settings
259 | sidebar_filters = sidebar_area()
260 |
261 | # logger.debug('sidebar_filters: %s', sidebar_filters)
262 |
263 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
264 | # Selective mark these as URL params as well
265 | filter_pipeline_result = run_filters(**sidebar_filters)
266 |
267 | # Render main viz area based on computed filter pipeline results and sidebar settings
268 | main_area(**filter_pipeline_result)
269 |
270 | except Exception as exn:
271 | st.write('Error loading dashboard')
272 | st.write(exn)
273 |
--------------------------------------------------------------------------------
/src/python/views/demo_login/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import sys
4 | from datetime import datetime, time, timedelta
5 | from typing import Dict, List, Union
6 |
7 | import dateutil.parser as dp
8 | import pandas as pd
9 | import streamlit.components.v1 as components
10 | from components import URLParam
11 | from components.Splunk import SplunkConnection
12 | from css import all_css
13 | from graphistry import Plottable
14 | from requests.exceptions import HTTPError
15 | from views.demo_login.marlowe import (
16 | AUTH_SAFE_FIELDS,
17 | AuthDataResource,
18 | AuthMarlowe,
19 | AuthMissingData,
20 | )
21 |
22 | import streamlit as st
23 |
24 | ############################################
25 | #
26 | # DASHBOARD SETTINGS
27 | #
28 | ############################################
29 | # Controls how entrypoint.py picks it up
30 |
31 |
32 | app_id = "demo_login"
33 | logger = logging.getLogger(app_id)
34 | urlParams = URLParam(app_id)
35 |
36 | # Splunk configuration
37 | INDEX = "auth_txt_50k"
38 | DEFAULT_PIVOT_URL_INVESTIGATION_ID = "123"
39 |
40 |
41 | def info():
42 | return {
43 | "id": app_id,
44 | "name": "Cyber: Login Analyzer",
45 | "tags": ["cyber", "cybersecurity", "security"],
46 | "enabled": True,
47 | }
48 |
49 |
50 | def run():
51 | run_all()
52 |
53 |
54 | ############################################
55 | #
56 | # PIPELINE PIECES
57 | #
58 | ############################################
59 |
60 |
61 | # Have fun!
62 | def custom_css():
63 | all_css()
64 | st.markdown(
65 | """""",
107 | unsafe_allow_html=True,
108 | )
109 |
110 |
111 | # Given URL params, render left sidebar form and return combined filter settings
112 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
113 | def sidebar_area():
114 | with st.sidebar:
115 | # Write a description in the sidebar
116 | st.sidebar.markdown(
117 | 'Nodes: Logins, colored by attack category
',
118 | unsafe_allow_html=True,
119 | )
120 | st.sidebar.markdown(
121 | 'Edges: Link logins by similarity
',
122 | unsafe_allow_html=True,
123 | )
124 |
125 | st.sidebar.divider()
126 |
127 | now = datetime.now()
128 | today = now.date()
129 | current_hour = now.time()
130 | month_ago = today - timedelta(days=60)
131 |
132 | start_date = st.sidebar.date_input(label="Start Date", value=month_ago)
133 | start_time = st.sidebar.time_input(label="Start Time", value=time(0, 00))
134 | end_date = st.sidebar.date_input(label="End Date", value=now)
135 | end_time = st.sidebar.time_input(label="End Time", value=current_hour)
136 |
137 | logger.debug(f"start_date={start_date} start_time={start_time} | end_date={end_date} end_time={end_time}\n")
138 |
139 | start_datetime = dp.parse(f"{start_date} {start_time}")
140 | end_datetime = dp.parse(f"{end_date} {end_time}")
141 |
142 | # st.sidebar.divider()
143 |
144 | # urlParams.get_field("dbscan", 0)
145 | # dbscan: int = st.sidebar.number_input(label="Cluster ID", value=0, step=1)
146 | # urlParams.set_field("dbscan", dbscan)
147 |
148 | return {
149 | "start_datetime": start_datetime,
150 | "end_datetime": end_datetime,
151 | # "dbscan": dbscan,
152 | }
153 |
154 |
155 | # Cache the Splunk client as a resource so it is re-used
156 | @st.cache_resource
157 | def cache_splunk_client(username: str, password: str, host: str) -> SplunkConnection:
158 | splunk_client = SplunkConnection(username, password, host)
159 | assert splunk_client.connect()
160 | return splunk_client
161 |
162 |
163 | # Given filter settings, generate/cache/return dataframes & viz
164 | def run_filters(start_datetime, end_datetime): # , dbscan):
165 | with st.spinner("Generating graph..."):
166 | splunk_client = cache_splunk_client(
167 | os.environ["SPLUNK_USERNAME"],
168 | os.environ["SPLUNK_PASSWORD"],
169 | os.environ["SPLUNK_HOST"],
170 | )
171 |
172 | query_dict: Dict[str, Union[str, float, List[str]]] = {
173 | "datetime": [
174 | (">=", start_datetime.isoformat()),
175 | ("<=", end_datetime.isoformat()),
176 | ],
177 | }
178 | # if dbscan > 0:
179 | # query_dict["dbscan"] = dbscan
180 |
181 | splunk_query = SplunkConnection.build_query(
182 | index=INDEX,
183 | query_dict=query_dict,
184 | fields=list(AUTH_SAFE_FIELDS.keys()),
185 | sort=[],
186 | debug=True,
187 | )
188 | logger.debug(f"Splunk query: {splunk_query}\n")
189 | results = splunk_client.one_shot_splunk(splunk_query)
190 |
191 | # Clean the Splunk results and send them to Graphistry to GPU render and return a url
192 | try:
193 | data_resource = AuthDataResource(edf=results, feature_columns=list(AUTH_SAFE_FIELDS.keys()))
194 |
195 | #
196 | # Bring in standard graphistry environment variables: Set env/*.env files, in .env --> docker-compose.yml --> os.getenv(key) --> AVRMarlowe.register()
197 | #
198 |
199 | logger.info("Configuring environment variables...\n")
200 | investigation_id: str = os.getenv("PIVOT_URL_INVESTIGATION_ID", DEFAULT_PIVOT_URL_INVESTIGATION_ID)
201 | logger.debug(f"investigation_id={investigation_id}\n")
202 |
203 | data_resource.add_pivot_url_column(
204 | investigation_id=investigation_id,
205 | )
206 |
207 | # Generate the graph
208 | marlowe: AuthMarlowe = AuthMarlowe(data_resource=data_resource)
209 | g: Plottable = marlowe.umap() # next line describe_clusters uses dbscan clusters from umap
210 | cluster_df: pd.DataFrame = marlowe.describe_clusters()
211 | try:
212 | graph_url: str = g.plot(render=False)
213 | except HTTPError as e:
214 | logging.exception(e)
215 |
216 | return {
217 | "graph_url": graph_url,
218 | "cluster_df": cluster_df,
219 | }
220 | except AuthMissingData:
221 | st.error("Your query returned no records.", icon="🚨")
222 |
223 |
224 | def main_area(
225 | start_datetime,
226 | end_datetime,
227 | # dbscan,
228 | graph_url=None,
229 | cluster_df=None,
230 | ):
231 | logger.debug("Rendering main area, with url: %s\n", graph_url)
232 | components.iframe(src=graph_url, height=650, scrolling=True)
233 | st.dataframe(cluster_df, use_container_width=True, height=176)
234 |
235 |
236 | ############################################
237 | #
238 | # PIPELINE FLOW
239 | #
240 | ############################################
241 |
242 |
243 | def run_all():
244 | custom_css()
245 |
246 | try:
247 | # Render sidebar and get current settings
248 | sidebar_filters = sidebar_area()
249 |
250 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
251 | # Selective mark these as URL params as well
252 | filter_pipeline_result = run_filters(**sidebar_filters)
253 |
254 | # Render main viz area based on computed filter pipeline results and sidebar settings
255 | main_area(
256 | **sidebar_filters,
257 | # Fill in empties or main_area will choke
258 | **filter_pipeline_result or {"graph_url": None, "cluster_df": None},
259 | )
260 |
261 | except Exception as exn:
262 | st.write("Error loading dashboard")
263 | st.write(exn)
264 |
--------------------------------------------------------------------------------
/src/python/views/demo_neptune_01_minimal_gremlin/__init__.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import graphistry
3 | import os
4 | import pandas as pd
5 | import streamlit as st
6 | from components import GraphistrySt, URLParam
7 | from css import all_css
8 | from neptune_helper import gremlin_helper, df_helper
9 |
10 | from gremlin_python import statics
11 | from gremlin_python.process.graph_traversal import __
12 | from gremlin_python.process.traversal import WithOptions, T
13 | import logging
14 |
15 | ############################################
16 | #
17 | # DASHBOARD SETTINGS
18 | #
19 | ############################################
20 | # Controls how entrypoint.py picks it up
21 |
22 |
23 | app_id = 'app_neptune_01'
24 | logger = logging.getLogger(app_id)
25 | urlParams = URLParam(app_id)
26 | node_id_col = 'id'
27 | src_id_col = 'source'
28 | dst_id_col = 'target'
29 | node_label_col = 'label'
30 | edge_label_col = 'label'
31 |
32 |
33 | def info():
34 | return {
35 | 'id': app_id,
36 | 'name': 'GREMLIN: Simple Sample',
37 | 'tags': ['demo', 'neptune_demo']
38 | }
39 |
40 |
41 | def run():
42 | run_all()
43 |
44 |
45 | ############################################
46 | #
47 | # PIPELINE PIECES
48 | #
49 | ############################################
50 | # Have fun!
51 | def custom_css():
52 | all_css()
53 | st.markdown(
54 | """""", unsafe_allow_html=True)
57 |
58 |
59 | # Given URL params, render left sidebar form and return combined filter settings
60 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
61 | def sidebar_area():
62 |
63 | num_edges_init = urlParams.get_field('num_edges', 100)
64 | num_edges = st.sidebar.slider(
65 | 'Number of edges', min_value=1, max_value=10000, value=num_edges_init, step=20)
66 | urlParams.set_field('num_edges', num_edges)
67 |
68 | return {'num_edges': num_edges}
69 |
70 |
71 | def plot_url(nodes_df, edges_df):
72 | nodes_df = df_helper.flatten_df(nodes_df)
73 | edges_df = df_helper.flatten_df(edges_df)
74 |
75 | logger.info('Starting graphistry plot')
76 | g = graphistry\
77 | .edges(edges_df)\
78 | .bind(source=src_id_col, destination=dst_id_col)\
79 | .nodes(nodes_df)\
80 | .bind(node=node_id_col)
81 |
82 | if not (node_label_col is None):
83 | g = g.bind(point_title=node_label_col)
84 |
85 | if not (edge_label_col is None):
86 | g = g.bind(edge_title=edge_label_col)
87 |
88 | url = g\
89 | .settings(url_params={
90 | 'bg': '%23' + 'f0f2f6'
91 | })\
92 | .plot(render=False)
93 |
94 | logger.info('Generated viz, got back urL: %s', url)
95 |
96 | return url
97 |
98 |
99 | def path_to_df(p):
100 | nodes = {}
101 | edges = {}
102 |
103 | for triple in p:
104 |
105 | src_id = triple[0][T.id]
106 | nodes[src_id] = df_helper.vertex_to_dict(triple[0])
107 |
108 | dst_id = triple[2][T.id]
109 | nodes[dst_id] = df_helper.vertex_to_dict(triple[2])
110 |
111 | edges[triple[1][T.id]] = df_helper.edge_to_dict(
112 | triple[1], src_id, dst_id)
113 |
114 | return pd.DataFrame(nodes.values()), pd.DataFrame(edges.values())
115 |
116 |
117 | # Given filter settings, generate/cache/return dataframes & viz
118 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
119 | @st.cache_data
120 | def run_filters(num_edges):
121 | g, conn = gremlin_helper.connect_to_neptune()
122 |
123 | logger.info('Querying neptune')
124 | res = g.V().inE().limit(num_edges).outV().path().by(
125 | __.valueMap().with_(WithOptions.tokens)).toList()
126 |
127 | nodes_df, edges_df = path_to_df(res)
128 | url = plot_url(nodes_df, edges_df)
129 |
130 | logger.info("Finished compute phase")
131 |
132 | try:
133 | conn.close()
134 |
135 | except RuntimeError as e:
136 | if str(e) == "There is no current event loop in thread 'ScriptRunner.scriptThread'.":
137 | loop = asyncio.new_event_loop()
138 | asyncio.set_event_loop(loop)
139 | conn.close()
140 | else:
141 | raise e
142 |
143 | except Exception as e:
144 | logger.error('oops in gremlin', exc_info=True)
145 | raise e
146 |
147 | return {'nodes_df': nodes_df, 'edges_df': edges_df, 'url': url, 'res': res}
148 |
149 |
150 | def main_area(url):
151 |
152 | logger.debug('rendering main area, with url: %s', url)
153 | GraphistrySt().render_url(url)
154 |
155 |
156 | ############################################
157 | #
158 | # PIPELINE FLOW
159 | #
160 | ############################################
161 |
162 |
163 | def run_all():
164 | try:
165 | custom_css()
166 |
167 | # Render sidebar and get current settings
168 | sidebar_filters = sidebar_area()
169 |
170 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
171 | # Selective mark these as URL params as well
172 | filter_pipeline_result = run_filters(**sidebar_filters)
173 |
174 | # Render main viz area based on computed filter pipeline results and sidebar settings
175 | main_area(filter_pipeline_result['url'])
176 |
177 | except Exception as exn:
178 | st.write('Error loading dashboard')
179 | st.write(exn)
180 |
--------------------------------------------------------------------------------
/src/python/views/demo_neptune_02_gremlin/__init__.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import graphistry
3 | import os
4 | import pandas as pd
5 | import streamlit as st
6 | from components import GraphistrySt, URLParam
7 | from neptune_helper import gremlin_helper, df_helper
8 | from css import all_css
9 | import time
10 | import altair as alt
11 |
12 | from gremlin_python import statics
13 | from gremlin_python.process.graph_traversal import __
14 | from gremlin_python.process.traversal import WithOptions, T
15 | import logging
16 |
17 | ############################################
18 | #
19 | # DASHBOARD SETTINGS
20 | #
21 | ############################################
22 | # Controls how entrypoint.py picks it up
23 |
24 |
25 | app_id = 'app_neptune_02'
26 | logger = logging.getLogger(app_id)
27 | urlParams = URLParam(app_id)
28 | node_id_col = 'id'
29 | src_id_col = 'source'
30 | dst_id_col = 'target'
31 | node_label_col = 'label'
32 | edge_label_col = 'label'
33 |
34 | # Setup a structure to hold metrics
35 | metrics = {'neptune_time': 0, 'graphistry_time': 0,
36 | 'node_cnt': 0, 'edge_cnt': 0, 'prop_cnt': 0}
37 |
38 |
39 | # Define the name of the view
40 | def info():
41 | return {
42 | 'id': app_id,
43 | 'name': 'GREMLIN: Faceted Filter',
44 | 'tags': ['demo', 'neptune_demo']
45 | }
46 |
47 |
48 | def run():
49 | run_all()
50 |
51 |
52 | ############################################
53 | #
54 | # PIPELINE PIECES
55 | #
56 | ############################################
57 |
58 |
59 | # Have fun!
60 | def custom_css():
61 | all_css()
62 | st.markdown(
63 | """""", unsafe_allow_html=True)
66 |
67 |
68 | # Given URL params, render left sidebar form and return combined filter settings
69 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
70 | def sidebar_area():
71 |
72 | num_edges_init = urlParams.get_field('num_matches', 10000)
73 | state = st.sidebar.selectbox(
74 | 'Find users from this state?',
75 | [
76 | 'All States',
77 | 'Alabama',
78 | 'Alaska',
79 | 'Arizona',
80 | 'Arkansas',
81 | 'California',
82 | 'Colorado',
83 | 'Connecticut',
84 | 'Delaware',
85 | 'Florida',
86 | 'Georgia',
87 | 'Hawaii',
88 | 'Idaho',
89 | 'Illinois',
90 | 'Indiana',
91 | 'Iowa',
92 | 'Kansas',
93 | 'Kentucky',
94 | 'Louisiana',
95 | 'Maine',
96 | 'Maryland',
97 | 'Massachusetts',
98 | 'Michigan',
99 | 'Minnesota',
100 | 'Mississippi',
101 | 'Missouri',
102 | 'Montana',
103 | 'Nebraska',
104 | 'Nevada',
105 | 'New Hampshire',
106 | 'New Jersey',
107 | 'New Mexico',
108 | 'New York',
109 | 'North Carolina',
110 | 'North Dakota',
111 | 'Ohio',
112 | 'Oklahoma',
113 | 'Oregon',
114 | 'Pennsylvania',
115 | 'Rhode Island',
116 | 'South Carolina',
117 | 'South Dakota',
118 | 'Tennessee',
119 | 'Texas',
120 | 'Utah',
121 | 'Vermont',
122 | 'Virginia',
123 | 'Washington',
124 | 'West Virginia',
125 | 'Wisconsin',
126 | 'Wyoming'
127 | ])
128 |
129 | city = st.sidebar.text_input(
130 | 'Find users from this city?',
131 | "")
132 |
133 | num_edges = st.sidebar.slider(
134 | 'Number of edges', min_value=1, max_value=10000, value=num_edges_init, step=20)
135 | urlParams.set_field('num_edges', num_edges)
136 | urlParams.set_field('state', state)
137 |
138 | return {'num_edges': num_edges, 'state': state, 'city': city}
139 |
140 |
141 | def plot_url(nodes_df, edges_df):
142 | global metrics
143 | nodes_df = df_helper.flatten_df(nodes_df)
144 | edges_df = df_helper.flatten_df(edges_df)
145 |
146 | logger.info('Starting graphistry plot')
147 | tic = time.perf_counter()
148 | g = graphistry\
149 | .edges(edges_df)\
150 | .bind(source=src_id_col, destination=dst_id_col)\
151 | .nodes(nodes_df)\
152 | .bind(node=node_id_col)
153 |
154 | if not (node_label_col is None):
155 | g = g.bind(point_title=node_label_col)
156 |
157 | if not (edge_label_col is None):
158 | g = g.bind(edge_title=edge_label_col)
159 |
160 | url = g\
161 | .settings(url_params={
162 | 'bg': '%23' + 'f0f2f6'
163 | })\
164 | .plot(render=False)
165 | toc = time.perf_counter()
166 | metrics['graphistry_time'] = toc - tic
167 | logger.info(f'Graphisty Time: {metrics["graphistry_time"]}')
168 | logger.info('Generated viz, got back urL: %s', url)
169 |
170 | return url
171 |
172 |
173 | def path_to_df(p):
174 | nodes = {}
175 | edges = {}
176 |
177 | for triple in p:
178 |
179 | src_id = triple[0][T.id]
180 | nodes[src_id] = df_helper.vertex_to_dict(triple[0])
181 |
182 | dst_id = triple[2][T.id]
183 | nodes[dst_id] = df_helper.vertex_to_dict(triple[2])
184 |
185 | edges[triple[1][T.id]] = df_helper.edge_to_dict(
186 | triple[1], src_id, dst_id)
187 |
188 | return pd.DataFrame(nodes.values()), pd.DataFrame(edges.values())
189 |
190 | # Given filter settings, generate/cache/return dataframes & viz
191 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
192 | @st.cache_data
193 | def run_filters(num_edges, state, city):
194 | global metrics
195 | g, conn = gremlin_helper.connect_to_neptune()
196 |
197 | logger.info('Querying neptune')
198 | tic = time.perf_counter()
199 | t = g.V().inE()
200 | # Conditionally add the state filtering in here
201 | if not state == "All States":
202 | t = t.has('visited', 'state', state)
203 | # Conditionally add the city filtering in here
204 | if not city == "":
205 | t = t.has('visited', 'city', city)
206 | res = t.limit(num_edges).outV().path().by(
207 | __.valueMap().with_(WithOptions.tokens)).toList()
208 | toc = time.perf_counter()
209 | logger.info(f'Query Execution: {toc-tic:0.02f} seconds')
210 | logger.debug('Query Result Count: %s', len(res))
211 | metrics['neptune_time'] = toc - tic
212 |
213 | nodes_df, edges_df = path_to_df(res)
214 |
215 | # Calculate the metrics
216 | metrics['node_cnt'] = nodes_df.size
217 | metrics['edge_cnt'] = edges_df.size
218 | metrics['prop_cnt'] = (nodes_df.size * nodes_df.columns.size) + \
219 | (edges_df.size * edges_df.columns.size)
220 |
221 | if nodes_df.size > 0:
222 | url = plot_url(nodes_df, edges_df)
223 | else:
224 | url = ""
225 |
226 | logger.info("Finished compute phase")
227 |
228 | try:
229 | conn.close()
230 |
231 | except RuntimeError as e:
232 | if str(e) == "There is no current event loop in thread 'ScriptRunner.scriptThread'.":
233 | loop = asyncio.new_event_loop()
234 | asyncio.set_event_loop(loop)
235 | conn.close()
236 | else:
237 | raise e
238 |
239 | except Exception as e:
240 | logger.error('oops in gremlin', exc_info=True)
241 | raise e
242 |
243 | return {'nodes_df': nodes_df, 'edges_df': edges_df, 'url': url, 'res': res}
244 |
245 |
246 | def main_area(url, nodes, edges, state):
247 |
248 | logger.debug('rendering main area, with url: %s', url)
249 | GraphistrySt().render_url(url)
250 |
251 | # Get the count by state of visits shown
252 | bar_chart_data = edges[edges['label'] == 'visited']
253 | group_label = 'state'
254 | if not state == 'All States': # If a state is chosen group by city
255 | group_label = 'city'
256 | bar_chart_data['count'] = bar_chart_data.groupby(
257 | group_label)[group_label].transform('count')
258 | bar_chart_data = bar_chart_data[[group_label, 'count']].drop_duplicates().reset_index()[
259 | [group_label, 'count']]
260 | # Sort the values by group_label
261 | bar_chart_data.sort_values(by=[group_label], inplace=True)
262 | chart = alt.Chart(bar_chart_data).mark_bar().encode(
263 | x=group_label,
264 | y='count')
265 | st.altair_chart(chart, use_container_width=True)
266 | # Show a datatable with the values transposed
267 | st.dataframe(bar_chart_data.set_index(group_label).T)
268 |
269 | st.markdown(f'''
270 | Neptune Load Time (s): {float(metrics['neptune_time']):0.2f} |
271 | Graphistry Load Time (s): {float(metrics['graphistry_time']):0.2f} |
272 | Node Count: {metrics['node_cnt']} |
273 | Edge Count: {metrics['edge_cnt']} |
274 | Property Count: {metrics['prop_cnt']}
275 | ''', unsafe_allow_html=True)
276 |
277 |
278 | ############################################
279 | #
280 | # PIPELINE FLOW
281 | #
282 | ############################################
283 |
284 |
285 | def run_all():
286 |
287 | custom_css()
288 |
289 | try:
290 |
291 | # Render sidebar and get current settings
292 | sidebar_filters = sidebar_area()
293 |
294 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
295 | # Selective mark these as URL params as well
296 | filter_pipeline_result = run_filters(**sidebar_filters)
297 |
298 | # Render main viz area based on computed filter pipeline results and sidebar settings if data is returned
299 | if filter_pipeline_result['nodes_df'].size > 0:
300 | main_area(filter_pipeline_result['url'],
301 | filter_pipeline_result['nodes_df'],
302 | filter_pipeline_result['edges_df'],
303 | sidebar_filters['state'])
304 | else: # render a message
305 | st.write("No data matching the specfiied criteria is found")
306 |
307 | except Exception as exn:
308 | st.write('Error loading dashboard')
309 | st.write(exn)
310 |
--------------------------------------------------------------------------------
/src/python/views/demo_neptune_03_c360/__init__.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import graphistry
3 | import os
4 | import pandas as pd
5 | import streamlit as st
6 | from components import GraphistrySt, URLParam
7 | from neptune_helper import gremlin_helper, df_helper
8 | from css import all_css
9 | import time
10 | import altair as alt
11 |
12 | from gremlin_python import statics
13 | from gremlin_python.process.graph_traversal import __
14 | from gremlin_python.process.traversal import WithOptions, T, TextP
15 | import logging
16 |
17 | ############################################
18 | #
19 | # DASHBOARD SETTINGS
20 | #
21 | ############################################
22 | # Controls how entrypoint.py picks it up
23 |
24 |
25 | app_id = 'app_neptune_03'
26 | logger = logging.getLogger(app_id)
27 | urlParams = URLParam(app_id)
28 | node_id_col = 'id'
29 | src_id_col = 'source'
30 | dst_id_col = 'target'
31 | node_label_col = 'label'
32 | edge_label_col = 'label'
33 |
34 | # Setup a structure to hold metrics
35 | metrics = {'neptune_time': 0, 'graphistry_time': 0,
36 | 'node_cnt': 0, 'edge_cnt': 0, 'prop_cnt': 0}
37 |
38 |
39 | # Define the name of the view
40 | def info():
41 | return {
42 | 'id': app_id,
43 | 'name': 'GREMLIN: Customer 360',
44 | 'tags': ['demo', 'neptune_demo']
45 | }
46 |
47 |
48 | def run():
49 | run_all()
50 |
51 |
52 | ############################################
53 | #
54 | # PIPELINE PIECES
55 | #
56 | ############################################
57 |
58 |
59 | # Have fun!
60 | def custom_css():
61 | all_css()
62 | st.markdown(
63 | """""", unsafe_allow_html=True)
66 |
67 |
68 | # Given URL params, render left sidebar form and return combined filter settings
69 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
70 | def sidebar_area():
71 |
72 | num_edges_init = urlParams.get_field('num_edges', 10000)
73 |
74 | transient_id = st.sidebar.text_input(
75 | 'Show me items starting with this transient id?',
76 | "")
77 |
78 | num_matches = st.sidebar.slider(
79 | 'Number of matches', min_value=1, max_value=100, value=50, step=5)
80 |
81 | num_edges = st.sidebar.slider(
82 | 'Number of edges', min_value=1, max_value=10000, value=num_edges_init, step=20)
83 | urlParams.set_field('num_edges', num_edges)
84 |
85 | return {'num_edges': num_edges, 'num_matches': num_matches, 'transient_id': transient_id}
86 |
87 |
88 | def plot_url(nodes_df, edges_df):
89 | global metrics
90 | nodes_df = df_helper.flatten_df(nodes_df)
91 | edges_df = df_helper.flatten_df(edges_df)
92 |
93 | logger.info('Starting graphistry plot')
94 | tic = time.perf_counter()
95 | g = graphistry\
96 | .edges(edges_df)\
97 | .bind(source=src_id_col, destination=dst_id_col)\
98 | .nodes(nodes_df)\
99 | .bind(node=node_id_col)
100 |
101 | if not (node_label_col is None):
102 | g = g.bind(point_title=node_label_col)
103 |
104 | if not (edge_label_col is None):
105 | g = g.bind(edge_title=edge_label_col)
106 |
107 | url = g\
108 | .settings(url_params={
109 | 'bg': '%23' + 'f0f2f6'
110 | })\
111 | .plot(render=False)
112 | toc = time.perf_counter()
113 | metrics['graphistry_time'] = toc - tic
114 | logger.info(f'Graphisty Time: {metrics["graphistry_time"]}')
115 | logger.info('Generated viz, got back urL: %s', url)
116 |
117 | return url
118 |
119 |
120 | def path_to_df(p):
121 | nodes = {}
122 | edges = {}
123 |
124 | for triple in p:
125 |
126 | src_id = triple[0][T.id]
127 | nodes[src_id] = df_helper.vertex_to_dict(triple[0])
128 |
129 | dst_id = triple[2][T.id]
130 | nodes[dst_id] = df_helper.vertex_to_dict(triple[2])
131 |
132 | edges[triple[1][T.id]] = df_helper.edge_to_dict(
133 | triple[1], src_id, dst_id)
134 |
135 | return pd.DataFrame(nodes.values()), pd.DataFrame(edges.values())
136 |
137 |
138 | # Given filter settings, generate/cache/return dataframes & viz
139 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
140 | @st.cache_data
141 | def run_filters(num_edges, num_matches, transient_id):
142 | global metrics
143 | g, conn = gremlin_helper.connect_to_neptune()
144 |
145 | logger.info('Querying neptune')
146 | tic = time.perf_counter()
147 | t = g.V().hasLabel('transientId')
148 | if not transient_id == "":
149 | # If using Neptune full text search this will perform much faster than the built in Gremlin text search
150 | t = t.has('uid', TextP.containing(transient_id))
151 | res = t.limit(num_matches).bothE().otherV().limit(num_edges).path().by(
152 | __.valueMap().with_(WithOptions.tokens)).toList()
153 |
154 | toc = time.perf_counter()
155 | logger.info(f'Query Execution: {toc-tic:0.02f} seconds')
156 | logger.debug('Query Result Count: %s', len(res))
157 | metrics['neptune_time'] = toc - tic
158 |
159 | nodes_df, edges_df = path_to_df(res)
160 |
161 | # Calculate the metrics
162 | metrics['node_cnt'] = nodes_df.size
163 | metrics['edge_cnt'] = edges_df.size
164 | metrics['prop_cnt'] = (nodes_df.size * nodes_df.columns.size) + \
165 | (edges_df.size * edges_df.columns.size)
166 |
167 | if nodes_df.size > 0:
168 | url = plot_url(nodes_df, edges_df)
169 | else:
170 | url = ""
171 |
172 | logger.info("Finished compute phase")
173 |
174 | try:
175 | conn.close()
176 |
177 | except RuntimeError as e:
178 | if str(e) == "There is no current event loop in thread 'ScriptRunner.scriptThread'.":
179 | loop = asyncio.new_event_loop()
180 | asyncio.set_event_loop(loop)
181 | conn.close()
182 | else:
183 | raise e
184 |
185 | except Exception as e:
186 | logger.error('oops in gremlin', exc_info=True)
187 | raise e
188 |
189 | return {'nodes_df': nodes_df, 'edges_df': edges_df, 'url': url, 'res': res}
190 |
191 |
192 | def main_area(url, nodes, edges):
193 |
194 | logger.debug('rendering main area, with url: %s', url)
195 | GraphistrySt().render_url(url)
196 |
197 |
198 | ############################################
199 | #
200 | # PIPELINE FLOW
201 | #
202 | ############################################
203 |
204 |
205 | def run_all():
206 |
207 | custom_css()
208 |
209 | try:
210 |
211 | # Render sidebar and get current settings
212 | sidebar_filters = sidebar_area()
213 |
214 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
215 | # Selective mark these as URL params as well
216 | filter_pipeline_result = run_filters(**sidebar_filters)
217 |
218 | # Render main viz area based on computed filter pipeline results and sidebar settings if data is returned
219 | if filter_pipeline_result['nodes_df'].size > 0:
220 | main_area(filter_pipeline_result['url'],
221 | filter_pipeline_result['nodes_df'],
222 | filter_pipeline_result['edges_df'])
223 | else: # render a message
224 | st.write("No data matching the specfiied criteria is found")
225 |
226 | except Exception as exn:
227 | st.write('Error loading dashboard')
228 | st.write(exn)
229 |
--------------------------------------------------------------------------------
/src/python/views/demo_rapids_01_simple/__init__.py:
--------------------------------------------------------------------------------
1 | import graphistry, pandas as pd, streamlit as st
2 | from components import GraphistrySt, URLParam
3 | from css import all_css
4 | import logging
5 |
6 | ############################################
7 | #
8 | # DASHBOARD SETTINGS
9 | #
10 | ############################################
11 | # Controls how entrypoint.py picks it up
12 |
13 |
14 | app_id = 'app_rapids_01'
15 | logger = logging.getLogger(app_id)
16 | urlParams = URLParam(app_id)
17 |
18 |
19 | def info():
20 | return {
21 | 'id': app_id,
22 | 'name': 'RAPIDS: RAPIDS (cudf)',
23 | 'tags': ['demo', 'demo_rapids']
24 | }
25 |
26 |
27 | def run():
28 | run_all()
29 |
30 |
31 | ############################################
32 | #
33 | # PIPELINE PIECES
34 | #
35 | ############################################
36 |
37 |
38 | # Have fun!
39 | def custom_css():
40 | all_css()
41 | st.markdown(
42 | """""", unsafe_allow_html=True)
45 |
46 |
47 | # Given URL params, render left sidebar form and return combined filter settings
48 | # https://docs.streamlit.io/en/stable/api.html#display-interactive-widgets
49 | def sidebar_area():
50 |
51 | # regular param (not in url)
52 | e = st.sidebar.number_input('Number of edges', min_value=10, max_value=100000, value=100, step=20)
53 |
54 | # deep-linkable param (in url)
55 | n_init = urlParams.get_field('N', 100)
56 | n = st.sidebar.number_input('Number of nodes', min_value=10, max_value=100000, value=n_init, step=20)
57 | urlParams.set_field('N', n)
58 |
59 | return {'num_nodes': n, 'num_edges': e}
60 |
61 |
62 | # Given filter settings, generate/cache/return dataframes & viz
63 | #@st.cache(suppress_st_warning=True, allow_output_mutation=True)
64 | @st.cache_data
65 | def run_filters(num_nodes, num_edges):
66 |
67 | try:
68 | import cudf
69 | except Exception as e:
70 | st.exception(RuntimeError('Failed importing cudf'))
71 | raise e
72 |
73 | nodes_df = cudf.DataFrame({ 'n': [x for x in range(0, num_nodes)] })
74 | edges_df = cudf.DataFrame({
75 | 's': [x % num_nodes for x in range(0, num_edges)],
76 | 'd': [(x + 1) % num_nodes for x in range(0, num_edges)],
77 | })
78 | graph_url = graphistry.nodes(nodes_df).edges(edges_df) \
79 | .bind(source='s', destination='d', node='n')\
80 | .plot(render=False)
81 | return { 'nodes_df': nodes_df.to_pandas(), 'edges_df': edges_df.to_pandas(), 'graph_url': graph_url }
82 |
83 |
84 | def main_area(num_nodes, num_edges, nodes_df, edges_df, graph_url):
85 | logger.debug('rendering main area, with url: %s', graph_url)
86 | GraphistrySt().render_url(graph_url)
87 | st.header('Edges (RAPIDS GPU cudf DataFrame)')
88 | st.write(edges_df)
89 |
90 |
91 | ############################################
92 | #
93 | # PIPELINE FLOW
94 | #
95 | ############################################
96 |
97 |
98 | def run_all():
99 |
100 | custom_css()
101 |
102 | try:
103 |
104 | # Render sidebar and get current settings
105 | sidebar_filters = sidebar_area()
106 |
107 | # Compute filter pipeline (with auto-caching based on filter setting inputs)
108 | # Selective mark these as URL params as well
109 | filter_pipeline_result = run_filters(**sidebar_filters)
110 |
111 | # Render main viz area based on computed filter pipeline results and sidebar settings
112 | main_area(**sidebar_filters, **filter_pipeline_result)
113 |
114 | except Exception as exn:
115 | st.write('Error loading dashboard')
116 | st.write(exn)
117 |
--------------------------------------------------------------------------------
/src/streamlit/config.toml:
--------------------------------------------------------------------------------
1 |
2 | [browser]
3 | gatherUsageStats = false
4 |
5 | [theme]
6 | #base="dark"
7 | #primaryColor="#00ff98"
8 | #secondaryBackgroundColor="#182333"
9 | #textColor="#dae5fd"
10 |
11 |
12 | ### must be last: entrypoint appends baseUrlPath = \"$BASE_PATH\"\n\
13 | [server]
14 | enableXsrfProtection = false
15 | enableCORS = false
16 | baseUrlPath = "$BASE_PATH"
--------------------------------------------------------------------------------
/src/streamlit/credentials.toml:
--------------------------------------------------------------------------------
1 | [general]
2 | email = ""
--------------------------------------------------------------------------------