Skip to content

Commit

Permalink
jupyrdfGH-118: Add initial example widget.
Browse files Browse the repository at this point in the history
  • Loading branch information
zwelz3 committed Jun 30, 2021
1 parent bcfb88c commit f48503f
Showing 1 changed file with 229 additions and 0 deletions.
229 changes: 229 additions & 0 deletions examples/GraphSearchWidget.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f6f51118",
"metadata": {},
"source": [
"## Basic widget for searching a graph to find nodes/edges\n",
"\n",
"Opinionated search. Looking for specific keys from an RDF graph present in the converted networkx graph."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "338ea955",
"metadata": {},
"outputs": [],
"source": [
"import ipywidgets as W\n",
"import traitlets as T\n",
"from IPython.display import JSON, display\n",
"from pathlib import Path\n",
"from networkx import Graph as NXGraph\n",
"from pandas import DataFrame\n",
"from rdflib.graph import Graph as RDFGraph\n",
"from rdflib.term import URIRef"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54b02ad2",
"metadata": {},
"outputs": [],
"source": [
"from ipyradiant import FileManager, PathLoader\n",
"\n",
"\n",
"lw = FileManager(loader=PathLoader(path=\"data\"))\n",
"# here we hard set what we want the file to be, but ideally a user can choose a file to work with.\n",
"lw.loader.file_picker.value = lw.loader.file_picker.options[\"starwars.ttl\"]\n",
"rdf_graph = lw.graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a946dabd",
"metadata": {},
"outputs": [],
"source": [
"# run the converter so that we can work with the networkx graph\n",
"from ipyradiant.rdf2nx import RDF2NX\n",
"\n",
"nx_graph = RDF2NX.convert(rdf_graph)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee9363c9",
"metadata": {},
"outputs": [],
"source": [
"def get_types(graph: NXGraph):\n",
" \"\"\"Return the options for node types present in a networkx graph.\"\"\"\n",
" types = set()\n",
" for _, data in nx_graph.nodes(data=True):\n",
" # TODO how to make this more generic, e.g. for custom type URIs\n",
" rdf_type = data.get(\"rdf:type\")\n",
" if not rdf_type:\n",
" continue\n",
" elif type(rdf_type) == URIRef:\n",
" types.add(rdf_type)\n",
" elif type(rdf_type) in {tuple, list}:\n",
" types.update(rdf_type)\n",
"\n",
" s_types = sorted(types)\n",
" \n",
" # TODO need intelligent URI parsing later on\n",
" return [(f\"<< {Path(type_).name} >>\", (type_, \"node\")) for type_ in s_types]\n",
"\n",
"\n",
"def get_predicates(graph: NXGraph):\n",
" \"\"\"Return the options for edge types present in a networkx graph.\"\"\"\n",
" predicates = set()\n",
" predicate_options = []\n",
" for source, target, edge_data in nx_graph.edges(data=True):\n",
" # TODO how to make this more generic\n",
" predicate = edge_data.get(\"predicate\")\n",
" if not predicate:\n",
" continue\n",
" \n",
" # TODO should this be configurable?\n",
" label = edge_data.get(\"_label\") or Path(predicate).name\n",
" if predicate not in predicates:\n",
" predicates.add(predicate)\n",
" predicate_options.append((f\"o-- {label} --o\", (predicate, \"edge\")))\n",
" \n",
" return sorted(predicate_options, key=lambda x: x[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7839b7bf",
"metadata": {},
"outputs": [],
"source": [
"text_widget = W.Text(placeholder=\"Start typing to search....\")\n",
"select_options = W.Select(rows=4)\n",
"run_button = W.Button(description=\"Run\")\n",
"\n",
"# store on class\n",
"available_graph_types = get_types(nx_graph)\n",
"available_graph_predicates = get_predicates(nx_graph)\n",
"\n",
"\n",
"def return_matches(b):\n",
" \"\"\"Returns nodes/edges that match the user's selection.\"\"\"\n",
" # TODO get nx_graph from widget class\n",
" \n",
" # TODO obv this needs to be linked for real\n",
" try:\n",
" choice, type_ = select_options.value\n",
" except TypeError:\n",
" # there is no selection, so don't do anything\n",
" return\n",
" \n",
" if type_ == \"node\":\n",
" # TODO configure the return types\n",
" # Challenge, node can have multiple types\n",
" matches = set()\n",
" for node, data in nx_graph.nodes(data=True):\n",
" types = data.get(\"rdf:type\")\n",
" if (\n",
" (type(types) == URIRef and types == choice) or \\\n",
" (type(types) in {list, tuple} and choice in types)\n",
" ):\n",
" matches.add(node)\n",
" print(matches)\n",
" \n",
" elif type_ == \"edge\":\n",
" matches = [\n",
" (source, target) \n",
" for source, target, data in nx_graph.edges(data=True) \n",
" if data.get(\"predicate\") == choice\n",
" ]\n",
" print(matches)\n",
"\n",
"def clear_select_options():\n",
" select_options.options = []\n",
" select_options.value = None\n",
"\n",
"\n",
"def update_options(change):\n",
" \"\"\"Update the Select options as the user changes the search string.\"\"\"\n",
" if len(change.new) < 2:\n",
" clear_select_options()\n",
" return\n",
" \n",
" if change.old != change.new:\n",
" # TODO obv this needs to be linked for real\n",
" select_options.options = [\n",
" option for option in [*available_graph_types, *available_graph_predicates]\n",
" if change.new.lower() in option[0].lower()\n",
" ]\n",
"\n",
"text_widget.observe(update_options, \"value\")\n",
"run_button.on_click(return_matches)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50648f07",
"metadata": {},
"outputs": [],
"source": [
"# TODO gridspec layout\n",
"# Note: combobox does not accept tuple for display option vs value\n",
"W.HBox([\n",
" W.VBox([\n",
" text_widget,\n",
" select_options\n",
" ]),\n",
" run_button\n",
"])"
]
},
{
"cell_type": "markdown",
"id": "e01a4d52",
"metadata": {},
"source": [
"#### Questions/TODO\n",
"* How would we support chaining multiple selections e.g. Character - homeworld - Planet?\n",
"* Provide recommendations before information is types into the search bar (types, and then predicates)\n",
"* Add to actual graph view (cytoscape)\n",
" * warn user if too many nodes/edges would be added\n",
" * clear view button\n",
"* create actual widget class\n",
"* widget that displays information about the types/edges present in the networkx graph"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

0 comments on commit f48503f

Please sign in to comment.