diff --git a/examples/QueryWidget.ipynb b/examples/QueryWidget.ipynb
new file mode 100644
index 0000000..44725f9
--- /dev/null
+++ b/examples/QueryWidget.ipynb
@@ -0,0 +1,169 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Query Widget\n",
+ "\n",
+ "A simple widget for construting and visualizing a SPARQL query and its results.\n",
+ "\n",
+ "Reusable self-contained widgets are preferrable to monolithic widgets. Therefore, we\n",
+ "break down the widget components available in `ipyradiant` that are aggregated into a\n",
+ "unified `QueryWidget`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, we load an example graph file from the library data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ipyradiant import FileManager, PathLoader\n",
+ "\n",
+ "lw = FileManager(loader=PathLoader(path=\"data\"))\n",
+ "lw.loader.file_picker.value = lw.loader.file_picker.options[\"starwars.ttl\"]\n",
+ "lw"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## QueryPreview\n",
+ "\n",
+ "The `QueryPreview` widget allows users to enter a `query` in the left panel, and see\n",
+ "live syntax highlighting in the right panel.\n",
+ "\n",
+ "In a future update, it may be possible to provide syntax highlighting and tips as part\n",
+ "of a single query entry widget."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ipyradiant.query.visualize import QueryPreview\n",
+ "\n",
+ "qp = QueryPreview()\n",
+ "# Specify an example query for demonstration pu\n",
+ "qp.query = \"\"\"\\\n",
+ "PREFIX voc: \n",
+ "CONSTRUCT {\n",
+ " ?s ?p ?o .\n",
+ " voc:Character a rdfs:Class .\n",
+ "} WHERE {\n",
+ " {\n",
+ " SELECT DISTINCT ?s\n",
+ " WHERE {\n",
+ " ?s a voc:Character .\n",
+ " }\n",
+ " LIMIT 3\n",
+ " }\n",
+ " ?s ?p ?o .\n",
+ "}\n",
+ "\"\"\"\n",
+ "qp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## QueryResultsGrid\n",
+ "\n",
+ "The `QueryResultsGrid` provides a simple way to view the results of a query as a grid.\n",
+ "\n",
+ "A future update may include the ability to apply operations on the grid (i.e.\n",
+ "filtering)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ipyradiant.query.visualize import QueryResultsGrid\n",
+ "\n",
+ "qrg = QueryResultsGrid(namespaces=dict(lw.graph.namespaces()))\n",
+ "# set the query results for the demonstration\n",
+ "qrg.query_result = lw.graph.query(qp.query)\n",
+ "qrg"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## QueryWidget\n",
+ "\n",
+ "The `QueryWidget` aggregates the `QueryPreview` and `QueryResultsGrid` together with a\n",
+ "\"Run Query\" button to support the workflow of query specification, execution, and\n",
+ "analysis of results.\n",
+ "\n",
+ "> Tip: The results of the query can be collected via `QueryWidget.query_result`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ipyradiant.query.app import QueryWidget\n",
+ "\n",
+ "qw = QueryWidget(graph=lw.graph)\n",
+ "# specify an example query for demonstration\n",
+ "qw.query = \"\"\"\\\n",
+ "CONSTRUCT {\n",
+ " ?s ?p ?o .\n",
+ " voc:Character a rdfs:Class .\n",
+ "} WHERE {\n",
+ " {\n",
+ " SELECT DISTINCT ?s\n",
+ " WHERE {\n",
+ " ?s a voc:Character .\n",
+ " }\n",
+ " LIMIT 3\n",
+ " }\n",
+ " ?s ?p ?o .\n",
+ "}\n",
+ "\"\"\"\n",
+ "# automate the execution of the query for demonstration\n",
+ "qw.run_button.click()\n",
+ "qw"
+ ]
+ }
+ ],
+ "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": 4
+}
diff --git a/examples/RemoteQuery_Example.ipynb b/examples/RemoteQuery_Example.ipynb
index 0f38cf0..b6ab7a5 100644
--- a/examples/RemoteQuery_Example.ipynb
+++ b/examples/RemoteQuery_Example.ipynb
@@ -218,10 +218,9 @@
"outputs": [],
"source": [
"widget = WidgetExample()\n",
- "widget.query.query_constructor.query_type = \"SELECT DISTINCT\"\n",
- "widget.query.query_constructor.query_line = \"*\"\n",
- "widget.query.query_constructor.query_body = \"\"\"\n",
- "{\n",
+ "widget.query.query = \"\"\"\\\n",
+ "SELECT DISTINCT *\n",
+ "WHERE {\n",
" SERVICE \n",
" {\n",
" SELECT ?s ?p ?o\n",
@@ -229,19 +228,7 @@
" LIMIT 10\n",
" }\n",
"}\n",
- "\"\"\"\n",
- "widget.query.query_constructor.formatted_query.value = \"\"\"\n",
- "SELECT DISTINCT *\n",
- "WHERE { \n",
- " SERVICE \n",
- " {\n",
- " SELECT ?s ?p ?o\n",
- " WHERE {?s ?p ?o}\n",
- " LIMIT 10\n",
- " }\n",
- " }\n",
- "\"\"\"\n",
- "widget.query"
+ "\"\"\""
]
},
{
@@ -389,7 +376,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.7.10"
}
},
"nbformat": 4,
diff --git a/examples/Tab_App_Example.ipynb b/examples/Tab_App_Example.ipynb
index ff9aea7..048c368 100644
--- a/examples/Tab_App_Example.ipynb
+++ b/examples/Tab_App_Example.ipynb
@@ -50,7 +50,6 @@
" graph = T.Instance(Graph, allow_none=True)\n",
" file_manager = T.Instance(FileManager)\n",
" query = T.Instance(QueryWidget)\n",
- " vis = T.Instance(CytoscapeVisualizer)\n",
" log = W.Output()\n",
"\n",
" def __init__(self, graph: Graph = None, *args, **kwargs):\n",
@@ -64,12 +63,10 @@
" super().__init__(*args, **kwargs)\n",
" T.link((self.file_manager, \"graph\"), (self, \"graph\"))\n",
" T.link((self, \"graph\"), (self.query, \"graph\"))\n",
- " T.link((self, \"graph\"), (self.vis, \"graph\"))\n",
"\n",
- " self.children = [self.file_manager, self.query, self.vis]\n",
+ " self.children = [self.file_manager, self.query]\n",
" self.set_title(0, \"Load\")\n",
" self.set_title(1, \"Query\")\n",
- " self.set_title(2, \"Visualize\")\n",
"\n",
" @T.default(\"graph\")\n",
" def make_default_graph(self):\n",
@@ -81,11 +78,10 @@
"\n",
" @T.default(\"query\")\n",
" def make_default_query_widget(self):\n",
- " return QueryWidget()\n",
- "\n",
- " @T.default(\"vis\")\n",
- " def make_vis_widget(self):\n",
- " return CytoscapeVisualizer()"
+ " qw = QueryWidget()\n",
+ " # set default query\n",
+ " qw.query = \"\"\"SELECT DISTINCT ?s ?p ?o\\nWHERE {\\n ?s ?p ?o .\\n}\\nLIMIT 10\"\"\"\n",
+ " return qw"
]
},
{
@@ -102,7 +98,7 @@
"outputs": [],
"source": [
"tabs = RadiantTabs()\n",
- "W.VBox([tabs, tabs.query.log])"
+ "tabs"
]
}
],
@@ -122,7 +118,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.8"
+ "version": "3.7.10"
}
},
"nbformat": 4,
diff --git a/examples/Test_Tab_App.ipynb b/examples/Test_Tab_App.ipynb
index f8ac65d..56c758c 100644
--- a/examples/Test_Tab_App.ipynb
+++ b/examples/Test_Tab_App.ipynb
@@ -39,7 +39,6 @@
"outputs": [],
"source": [
"pl = tabs.file_manager.loader\n",
- "vis = tabs.vis\n",
"q = tabs.query"
]
},
@@ -98,6 +97,21 @@
" print(f\"[{key}]\", f\"+{int(delta)}\", msg)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "TEST_QUERY = \"\"\"\\\n",
+ "SELECT DISTINCT ?s ?p ?o\n",
+ "WHERE {\n",
+ " ?s ?p ?o .\n",
+ "}\n",
+ "LIMIT 5\n",
+ "\"\"\""
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -111,8 +125,6 @@
" timestamp(p, \"starting...\")\n",
" tabs.graph = Graph()\n",
" timestamp(p, \"cleaned...\")\n",
- " assert not vis.cyto_widget.graph.edges\n",
- " assert not vis.cyto_widget.graph.nodes\n",
" tabs.selected_index = 0\n",
" timestamp(p, f\"loading...\")\n",
" pl.file_picker.value = pl.file_picker.options[p]\n",
@@ -120,10 +132,9 @@
" assert len(pl.graph)\n",
" tabs.selected_index = 1\n",
" timestamp(p, \"querying...\")\n",
+ " q.query = TEST_QUERY\n",
" q.run_button.click()\n",
- " tabs.selected_index = 2\n",
- " assert vis.cyto_widget.graph.edges\n",
- " assert vis.cyto_widget.graph.nodes\n",
+ " assert len(q.query_result) > 0, \"Failed to execute query.\"\n",
" timestamp(p, \"OK!\")\n",
" except Exception as err:\n",
" timestamp(p, \"ERROR\")\n",
@@ -185,7 +196,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.8"
+ "version": "3.7.10"
}
},
"nbformat": 4,
diff --git a/src/ipyradiant/loader/manager.py b/src/ipyradiant/loader/manager.py
index 70041c4..226d848 100644
--- a/src/ipyradiant/loader/manager.py
+++ b/src/ipyradiant/loader/manager.py
@@ -9,66 +9,43 @@
from .base import BaseLoader
from .upload import UpLoader
-from .util import get_n_predicates, get_n_subjects
-class FileManager(W.VBox):
- """Wraps a file selector and stats"""
+class FileManager(W.HBox):
+ """Wraps a file selector with graph info."""
- n_triples = T.Int()
- n_subjects = T.Int()
- n_predicates = T.Int()
-
- loader = T.Instance(BaseLoader)
- stats = T.Instance(W.HTML)
-
- graph = T.Instance(Graph)
+ loader = T.Instance(BaseLoader, default_value=UpLoader())
+ graph = T.Instance(Graph, kw={})
graph_id = T.Instance(BNode)
+ msg = T.Instance(W.HTML)
+
+ def build_html(self):
+ """Basic HTML string with graph length."""
+ if len(self.graph) == 0:
+ return "No graph loaded."
+ else:
+ return f"Loaded graph with {len(self.loader.graph)} triples."
+
+ @T.validate("children")
+ def validate_children(self, proposal):
+ """
+ Validate method for default children.
+ This is necessary because @trt.default does not work on children.
+ """
+ children = proposal.value
+ if not children:
+ children = (self.loader, self.msg)
+ return children
+
+ @T.default("msg")
+ def make_default_msg(self):
+ return W.HTML(self.build_html())
- log = W.Output()
+ @T.observe("graph_id")
+ def update_msg(self, change):
+ self.msg.value = self.build_html()
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ @T.observe("loader")
+ def update_loader(self, change):
T.link((self.loader, "graph"), (self, "graph"))
T.link((self.loader, "graph_id"), (self, "graph_id"))
- self.children = [self.loader, self.stats]
-
- @T.default("loader")
- def make_default_loader(self):
- return UpLoader()
-
- @T.default("n_triples")
- def make_default_n_triples(self):
- return len(self.graph), 0
-
- @T.default("n_subjects")
- def make_default_n_subjects(self):
- return get_n_subjects(self.graph)
-
- @T.default("n_predicates")
- def make_default_n_predicates(self):
- return get_n_subjects(self.graph)
-
- def build_html_str(self):
- return f"""
- Stats:
-
- - n_triples: {self.n_triples}
-
- - n_subjects: {self.n_subjects}
- - n_predicates: {self.n_predicates}
-
-
- """
-
- @T.default("stats")
- def make_default_stats(self):
- html = W.HTML(self.build_html_str())
- return html
-
- @T.observe("graph_id")
- def update_stats(self, change):
- self.n_triples = len(self.graph)
- self.n_subjects = get_n_subjects(self.graph)
- self.n_predicates = get_n_predicates(self.graph)
- self.stats.value = self.build_html_str()
diff --git a/src/ipyradiant/query/__init__.py b/src/ipyradiant/query/__init__.py
index 91f4659..7eb7a5f 100644
--- a/src/ipyradiant/query/__init__.py
+++ b/src/ipyradiant/query/__init__.py
@@ -3,7 +3,8 @@
# Copyright (c) 2021 ipyradiant contributors.
# Distributed under the terms of the Modified BSD License.
-__all__ = ["QueryWidget", "service_patch_rdflib"]
+__all__ = ["LegacyQueryWidget", "QueryWidget", "service_patch_rdflib"]
-from .query_widget import QueryWidget
+from .app import QueryWidget
+from .query_widget import QueryWidget as LegacyQueryWidget
from .utils import service_patch_rdflib
diff --git a/src/ipyradiant/query/api.py b/src/ipyradiant/query/api.py
index 839fbba..54efbf2 100644
--- a/src/ipyradiant/query/api.py
+++ b/src/ipyradiant/query/api.py
@@ -1,5 +1,6 @@
# Copyright (c) 2021 ipyradiant contributors.
# Distributed under the terms of the Modified BSD License.
+
import logging
import re
diff --git a/src/ipyradiant/query/app.py b/src/ipyradiant/query/app.py
new file mode 100644
index 0000000..6b804f5
--- /dev/null
+++ b/src/ipyradiant/query/app.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2021 ipyradiant contributors.
+# Distributed under the terms of the Modified BSD License.
+
+import ipywidgets as W
+import traitlets as T
+from rdflib import Graph
+
+from ipyradiant.query.visualize import QueryPreview, QueryResultsGrid
+
+
+class QueryWidget(W.VBox):
+ """Widget used to visualize and run SPARQL queries. Results are displayed as a DataFrame grid."""
+
+ query = T.Instance(str, ("",))
+ query_preview = T.Instance(QueryPreview)
+ query_result = T.Any()
+ query_results_grid = T.Instance(QueryResultsGrid)
+ graph = T.Instance(Graph, kw={})
+ run_button = T.Instance(W.Button)
+
+ @T.validate("children")
+ def validate_children(self, proposal):
+ """
+ Validate method for default children.
+ This is necessary because @trt.default does not work on children.
+ """
+ children = proposal.value
+ if not children:
+ children = (self.query_preview, self.run_button, self.query_results_grid)
+ return children
+
+ def run_query(self, button):
+ self.query_result = self.graph.query(self.query)
+
+ @T.default("query_results_grid")
+ def make_default_query_results_grid(self):
+ widget = QueryResultsGrid(namespaces=dict(self.graph.namespaces()))
+ T.link((widget, "query_result"), (self, "query_result"))
+ return widget
+
+ @T.default("query_preview")
+ def make_default_query_preview(self):
+ widget = QueryPreview()
+ T.link((widget, "query"), (self, "query"))
+ return widget
+
+ @T.default("run_button")
+ def make_default_run_button(self):
+ button = W.Button(
+ description="Run Query",
+ icon="search",
+ tooltip="Click to execute query with current configuration.",
+ )
+ button.on_click(self.run_query)
+ return button
diff --git a/src/ipyradiant/query/namespace_manager.py b/src/ipyradiant/query/namespace_manager.py
index 2d73570..f887b46 100644
--- a/src/ipyradiant/query/namespace_manager.py
+++ b/src/ipyradiant/query/namespace_manager.py
@@ -17,9 +17,20 @@
def collapse_namespace(namespaces, cell):
- """TODO"""
+ """
+ TODO prevent from collapsing a partial namespace
+ e.g.
+ PREFIX ex:
+ URI = https://example.org/thing/stuff
+
+ current behavior: ex:thing/stuff
+ expected behavior: no collapsing
+ """
uf_link = """{}"""
+ if isinstance(namespaces, dict):
+ namespaces = tuple(namespaces.items())
+
or_statement = "|".join([uri for _, uri in namespaces])
pattern = f"({or_statement}).*"
quick_check = re.match(pattern, str(cell))
diff --git a/src/ipyradiant/query/query_constructor.py b/src/ipyradiant/query/query_constructor.py
index 949bd89..d4e28f9 100644
--- a/src/ipyradiant/query/query_constructor.py
+++ b/src/ipyradiant/query/query_constructor.py
@@ -5,12 +5,9 @@
import ipywidgets as W
import traitlets as T
-from pygments import highlight
-from pygments.formatters import HtmlFormatter
-from pygments.lexers.rdf import SparqlLexer
-from pygments.styles import STYLE_MAP
from .query_form import QueryInput
+from .visualize import QueryColorizer
# TODO improve
query_template = """{}
@@ -19,50 +16,6 @@
"""
-class QueryColorizer(W.VBox):
- """Takes sparql query and runs it through pygments lexer and html formatter"""
-
- query = T.Unicode()
- formatter_style = T.Enum(values=list(STYLE_MAP.keys()), default_value="colorful")
- style_picker = T.Instance(
- W.Dropdown,
- kw=dict(
- description="Style",
- options=list(STYLE_MAP.keys()),
- layout=W.Layout(min_height="30px"),
- ),
- )
- html_output = T.Instance(W.HTML, kw={})
-
- _style_defs = T.Unicode(default_value="")
- formatter: HtmlFormatter = None
- _sqrl_lexer: SparqlLexer = None
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- T.link((self, "formatter_style"), (self.style_picker, "value"))
- self.children = [self.style_picker, self.html_output]
-
- @T.observe("formatter_style")
- def _update_style(self, change=None) -> HtmlFormatter:
- """update the css style from the formatter"""
- self.formatter = HtmlFormatter(style=self.formatter_style)
- self._sqrl_lexer = SparqlLexer()
- self._style_defs = f""
-
- @T.observe(
- "query",
- "_style_defs",
- )
- def update_formatted_query(self, change):
- """Update the html output widget with the highlighted query"""
- if not self.formatter or not self._sqrl_lexer:
- self._update_style()
- self.html_output.value = self._style_defs + highlight(
- self.query, self._sqrl_lexer, self.formatter
- )
-
-
class QueryConstructor(W.HBox):
"""TODO
- way better templating and more efficient formatting
diff --git a/src/ipyradiant/query/visualize.py b/src/ipyradiant/query/visualize.py
new file mode 100644
index 0000000..05c3d1d
--- /dev/null
+++ b/src/ipyradiant/query/visualize.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2021 ipyradiant contributors.
+# Distributed under the terms of the Modified BSD License.
+
+import IPython
+import ipywidgets as W
+import traitlets as T
+from pandas import DataFrame
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+from pygments.lexers.rdf import SparqlLexer
+from pygments.styles import STYLE_MAP
+from rdflib import URIRef
+from rdflib.plugins.sparql.processor import SPARQLResult
+
+from ipyradiant.query.namespace_manager import collapse_namespace
+
+
+class QueryColorizer(W.VBox):
+ """Takes sparql query and runs it through pygments lexer and html formatter"""
+
+ query = T.Unicode()
+ formatter_style = T.Enum(values=list(STYLE_MAP.keys()), default_value="colorful")
+ style_picker = T.Instance(W.Dropdown)
+ html_output = T.Instance(W.HTML, kw={})
+
+ _style_defs = T.Unicode(default_value="")
+ formatter: HtmlFormatter = None
+ _sqrl_lexer: SparqlLexer = None
+
+ @T.default("style_picker")
+ def make_default_style_picker(self) -> W.Dropdown:
+ widget = W.Dropdown(
+ description="Style",
+ options=list(STYLE_MAP.keys()),
+ layout=W.Layout(min_height="30px"),
+ )
+ T.link((self, "formatter_style"), (widget, "value"))
+ return widget
+
+ @T.validate("children")
+ def validate_children(self, proposal):
+ """
+ Validate method for default children.
+ This is necessary because @trt.default does not work on children.
+ """
+ children = proposal.value
+ if not children:
+ children = (self.style_picker, self.html_output)
+ return children
+
+ @T.observe("formatter_style")
+ def _update_style(self, change=None) -> HtmlFormatter:
+ """update the css style from the formatter"""
+ self.formatter = HtmlFormatter(style=self.formatter_style)
+ self._sqrl_lexer = SparqlLexer()
+ self._style_defs = f""
+
+ @T.observe(
+ "query",
+ "_style_defs",
+ )
+ def update_formatted_query(self, change):
+ """Update the html output widget with the highlighted query"""
+ if not self.formatter or not self._sqrl_lexer:
+ self._update_style()
+ self.html_output.value = self._style_defs + highlight(
+ self.query, self._sqrl_lexer, self.formatter
+ )
+
+
+class QueryPreview(W.HBox):
+ """A widget for writing and previewing (with syntax highlighting) a SPARQL query."""
+
+ query = T.Instance(str, ("",))
+ query_input = T.Instance(W.Textarea)
+ query_view = T.Instance(QueryColorizer)
+ styler = T.Bool(default_value=False)
+
+ @T.validate("children")
+ def validate_children(self, proposal):
+ """
+ Validate method for default children.
+ This is necessary because @trt.default does not work on children.
+ """
+ children = proposal.value
+ if not children:
+ if self.styler:
+ children = (self.query_input, self.query_view)
+ else:
+ # if self.styler is False, don't include in the children
+ children = (self.query_input, self.query_view.children[1])
+ return children
+
+ @T.default("query_input")
+ def make_default_query_input(self) -> W.Textarea:
+ widget = W.Textarea()
+ widget.layout = {
+ "display": "flex",
+ "flex_flow": "row",
+ "align_items": "stretch",
+ "width": "auto",
+ "min_width": "25%",
+ "max_width": "50%",
+ }
+ T.link((widget, "value"), (self, "query"))
+ return widget
+
+ @T.default("query_view")
+ def make_default_query_view(self) -> QueryColorizer:
+ widget = QueryColorizer()
+ T.link((widget, "query"), (self, "query"))
+ return widget
+
+
+class QueryResultsGrid(W.Box):
+ """A widget for viewing the result of SPARQL queries as a DataFrame grid."""
+
+ grid = T.Instance(W.Output)
+ log = W.Output(layout={"border": "1px solid black"})
+ current_dataframe = T.Instance(DataFrame)
+ namespaces = T.Instance(dict, kw={})
+ query_result = T.Any()
+
+ @T.validate("children")
+ def validate_children(self, proposal):
+ """
+ Validate method for default children.
+ This is necessary because @trt.default does not work on children.
+ """
+ children = proposal.value
+ if not children:
+ children = (self.grid,)
+ return children
+
+ @T.validate("query_result")
+ def validate_query_result(self, proposal):
+ query_result = proposal.value
+ if query_result:
+ if isinstance(query_result, DataFrame):
+ pass
+ elif isinstance(query_result, (list, tuple)):
+ item_len = len(query_result[0])
+ assert (
+ item_len == 3
+ ), f"Unexpected number of items in query_result, {item_len}!=3"
+ query_result = DataFrame(query_result)
+ elif isinstance(query_result, SPARQLResult):
+ query_result = DataFrame(query_result)
+ else:
+ query_result = DataFrame()
+
+ self.observe(self.run_query, "query_result")
+ return query_result
+
+ @log.capture(clear_output=True)
+ def run_query(self, change):
+ # TODO move to validate method?
+ self.current_dataframe = DataFrame(self.query_result)
+ # TODO set columns
+ collapsed_data = DataFrame(self.query_result)
+ for ii, row in collapsed_data.iterrows():
+ for jj, cell in enumerate(row):
+ if isinstance(cell, URIRef):
+ collapsed_data.iat[ii, jj] = collapse_namespace(
+ self.namespaces, cell
+ )
+ self.grid.clear_output()
+ with self.grid:
+ IPython.display.display(
+ IPython.display.HTML(collapsed_data.to_html(escape=False))
+ )
+
+ @T.default("grid")
+ def make_default_grid(self):
+ return W.Output(layout=dict(max_height="60vh"))
diff --git a/src/ipyradiant/visualization/improved_cytoscape.py b/src/ipyradiant/visualization/improved_cytoscape.py
index d5df452..b93424c 100644
--- a/src/ipyradiant/visualization/improved_cytoscape.py
+++ b/src/ipyradiant/visualization/improved_cytoscape.py
@@ -1,5 +1,6 @@
# Copyright (c) 2021 ipyradiant contributors.
# Distributed under the terms of the Modified BSD License.
+
import ipycytoscape as cyto
import ipywidgets as W
import networkx as nx