Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@
# What is QLever UI?
**QLever UI** is an easy-to-use interactive **user interface** for the **SPARQL search engine QLever** that helps to discover the scopes and information in very large knowledge bases by providing **context-sensitive suggestions** and **auto-completion** while adding helpful information and additional views to the various outputs of the queries.

QLever UI supports different types of results (e.g. geographical data, named instances, images, and more) and is highly customizable to the needs of its users and the structure of the underlying dataset.
QLever UI supports different types of results (e.g. geographical data, named instances, images, and more) and is highly customizable to the needs of its users and the structure of the underlying dataset. The interface includes **interactive query analysis** for exploring query execution and performance.

## Key Features
- **Multi-backend support** - Configure and switch between different QLever instances
- **SPARQL formatting** - Automatic query formatting and syntax validation
- **Context-sensitive SPARQL autocompletion** - Smart suggestions based on query context
- **Customizable result views** - Support for geographic data, images, and structured results
- **Interactive query execution analysis** - Navigate through query trees to understand performance
- **Real-time query monitoring** - Live query progress updates during execution

### What is QLever?
QLever (pronounced "clever") is an efficient SPARQL engine that can handle very large datasets. For example, QLever can index the complete Wikidata (~ 18 billion triples) in less than 24 hours on a standard Linux machine using around 40 GB of RAM, with subsequent query times below 1 second even for relatively complex queries with large result sets. On top of the standard SPARQL functionality, QLever also supports SPARQL+Text search.

Expand All @@ -45,6 +54,7 @@ Distributed under the Apache 2.0 License. See `LICENSE` for more information.
* [Setting up the database manually](docs/install_qleverui.md#setting-up-the-database-manually)
* [Running QLever UI without docker](docs/install_qleverui.md#running-qlever-ui-without-docker)
* [Configure QLever UI](docs/configure_qleverui.md)
* [Query Analysis](docs/query-analysis.md)
* [Extending QLever UI](#construct-and-theoretical-approach)
* [Extending the language parser](docs/extending_parser.md)
* [Extending the suggestions](docs/extending_suggestions.md)
Expand Down
73 changes: 63 additions & 10 deletions backend/static/css/style.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*

Basic HTML Elements

*/
Expand All @@ -13,7 +13,7 @@ kbd {
}

/*

Custom Elements

*/
Expand Down Expand Up @@ -89,7 +89,7 @@ th { color: #82B36F; white-space: nowrap; }

.d,
#help,
#helpButton {
#helpButton {
display: none;
}

Expand All @@ -99,7 +99,7 @@ th { color: #82B36F; white-space: nowrap; }
#helpButton {
display: inline;
}

#dynamicSuggestions {
margin-top: 0px;
}
Expand All @@ -121,9 +121,9 @@ th { color: #82B36F; white-space: nowrap; }
}

/**

CodeMirror Styles

**/

.CodeMirror {max-width: 100%;}
Expand All @@ -133,7 +133,7 @@ th { color: #82B36F; white-space: nowrap; }
background: -moz-linear-gradient(top, #34759E 0%, #4787AD 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, #34759E 0%,#4787AD 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, #34759E 0%,#4787AD 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
border-radius: 5px;
border-radius: 5px;
color: white;
padding: 0px 5px 2px 5px;
}
Expand Down Expand Up @@ -199,9 +199,9 @@ th { color: #82B36F; white-space: nowrap; }
.cm-s-railscasts .CodeMirror-activeline-background { background: #303040; }

/**

CodeMirror Hints

**/

.CodeMirror-hint {
Expand All @@ -212,7 +212,7 @@ th { color: #82B36F; white-space: nowrap; }
color: black;
cursor: pointer;
position: relative;
line-height: 1.5;
line-height: 1.5;
}

.CodeMirror-hints {
Expand Down Expand Up @@ -275,9 +275,62 @@ li.CodeMirror-hint-active {
font-size: 80%;
}

#result-query {
max-height: 200px;
overflow-y: auto;
margin-bottom: 10px;
}

/* required LIB STYLES */
/* .Treant se automatski dodaje na svaki chart conatiner */
.Treant { position: relative; overflow: hidden; padding: 0 !important; }
/* Visualization modal styles */
#visualisation .modal-dialog {
width: 80%;
}

#visualisation .modal-body {
height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}

#visualisation .modal-header .pull-right {
margin-right: 30px;
font-size: 12px;
color: #666;
}

/* Tree viewport and panzoom styles */
#tree-viewport {
width: 100%;
flex: 1;
border: 1px solid #ddd;
overflow: auto;
}

#result-tree {
position: relative;
cursor: grab;
width: 100%;
height: 100%;
top: 0;
left: 0;
}

#result-tree .Treant {
overflow: visible;
width: auto !important;
height: auto !important;
min-width: 100%;
min-height: 100%;
}

/* Query history styles */
#lastQueries {
margin: -25px 0px;
}
.Treant > .node,
.Treant > .pseudo { position: absolute; display: block; visibility: hidden; }
.Treant.Treant-loaded .node,
Expand Down
78 changes: 73 additions & 5 deletions backend/static/js/qleverUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ $(document).ready(function () {
entities.data('has-mouseenter-handler', 'true');
}

// Set up panzoom initialization and cleanup for visualization modal
$('#visualisation').on('shown.bs.modal', function () {
// Initialize panzoom when modal is displayed
if (typeof window.initializePanzoom === 'function') {
setTimeout(() => {
window.initializePanzoom();
// Center the tree after initialization
setTimeout(() => {
centerTreeInViewport();
}, 100);
}, 50);
}
});

$('#visualisation').on('hidden.bs.modal', function () {
if (typeof window.destroyPanzoom === 'function') {
window.destroyPanzoom();
}
});

// Initialization done.
log('Editor initialized', 'other');

Expand Down Expand Up @@ -316,7 +336,7 @@ $(document).ready(function () {
editor.getValue(), {"name_service": "if_checked"});
const queryRewrittenAndNormalizedAndWithEscapedQuotes =
normalizeQuery(queryRewritten).replace(/\\/g, "\\\\").replace(/"/g, "\\\"");

if (editor.state.completionActive) { editor.state.completionActive.close(); }

// POST request to Django, for the query hash.
Expand Down Expand Up @@ -389,7 +409,7 @@ $(document).ready(function () {
accessToken.on("input", function () {
updateBackendCommandVisibility();
});

});

function addNameHover(element, domElement, list, namepredicate, prefixes) {
Expand Down Expand Up @@ -619,7 +639,7 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {

try {
let result = await fetchQleverBackend(params, headers);

log('Evaluating and displaying results...', 'other');

if (result.status === "ERROR") {
Expand Down Expand Up @@ -840,13 +860,13 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
}
}
}

async function handleStatsDisplay() {
try {
log('Loading backend statistics...', 'other');
$('#statsButton span').html('Loading information...');
$('#statsButton').attr('disabled', 'disabled');

try {
const response = await fetch(`${BASEURL}?cmd=stats`);
if (!response.ok) {
Expand Down Expand Up @@ -896,6 +916,47 @@ function showQueryPlanningTree(entry = undefined) {

let currentTree = null;

// Centers and fits the tree in the viewport using panzoom
function centerTreeInViewport(attempt = 1, maxAttempts = 10) {
console.log('centerTreeInViewport called, attempt:', attempt);
const treeViewport = document.getElementById('tree-viewport');
const resultTree = document.getElementById('result-tree');

if (!treeViewport || !resultTree) {
console.log('Missing elements: treeViewport=', treeViewport, 'resultTree=', resultTree);
return;
}

// Look for any tree content - try multiple selectors
let treantContainer = resultTree.querySelector('.Treant') ||
resultTree.querySelector('[class*="Treant"]') ||
(resultTree.children.length > 0 ? resultTree : null);

if (!treantContainer) {
console.log('No tree container found, attempt', attempt);
if (attempt < maxAttempts) {
setTimeout(() => centerTreeInViewport(attempt + 1, maxAttempts), 100);
} else {
console.log('Max attempts reached, giving up on centering');
}
return;
}

console.log('Found tree container, applying simple reset...');

// Simple approach - just reset panzoom to make tree visible
if (window.panzoomInstance) {
try {
window.panzoomInstance.reset();
console.log('Tree reset to default view');
} catch (error) {
console.log('Error during reset:', error.message);
}
} else {
console.log('No panzoom instance available');
}
}

// Uses the information inside of request_log
// to populate the DOM with the current runtime information.
function renderRuntimeInformationToDom(entry = undefined) {
Expand Down Expand Up @@ -954,6 +1015,8 @@ function renderRuntimeInformationToDom(entry = undefined) {
container: "#result-tree",
rootOrientation: "NORTH",
connectors: { type: "step" },
// Allow unlimited canvas size for large trees
scrollbar: "resize"
},
nodeStructure: runtime_info["query_execution_tree"]
}
Expand All @@ -965,6 +1028,11 @@ function renderRuntimeInformationToDom(entry = undefined) {
if (currentTree !== null) {
currentTree.destroy();
}
// Destroy any existing panzoom instance
if (typeof window.destroyPanzoom === 'function') {
window.destroyPanzoom();
}

currentTree = new Treant(treant_tree);
$("#visualisation").scrollTop(scrollTop);
$("#result-tree").scrollLeft(scrollLeft);
Expand Down
9 changes: 9 additions & 0 deletions backend/templates/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@
window.determineOperationType = determineOperationType;
</script>

<!-- Panzoom for tree visualization -->
<script type="module">
import { initializePanzoom, destroyPanzoom, resetPanzoom, centerTree } from "{% static '/js/panzoom.js' %}";
window.initializePanzoom = initializePanzoom;
window.destroyPanzoom = destroyPanzoom;
window.resetPanzoom = resetPanzoom;
window.centerTree = centerTree;
</script>

<!-- Our own JavaScript code for this demo -->
<script src="{% static "js/helper.js" %}"></script>
<script src="{% static "js/raphael.js" %}"></script>
Expand Down
26 changes: 17 additions & 9 deletions backend/templates/partials/modals/visualisation.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
<div class="modal" tabindex="-1" role="dialog" id="visualisation">
<div class="modal-dialog" role="document" style="width: 80%;">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title pull-left">Query Analysis</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>

</div>
<div class="modal-body">
<div id="result-query"></div>
<div id="meta-info"></div>
<div id="result-tree">
<br><strong style="color: red;">Please execute your query first.</strong><br><br>
<div class="row">
<div id="meta-info" class="col-md-6">

</div>
<div class="col-md-6 text-right text-muted" id="tree-info">
If there is no tree visible. Zoom out to see the tree.
</div>
</div>

<div id="tree-viewport">
<div id="result-tree">
<br><strong style="color: red;">Please execute your query first.</strong><br><br>
</div>
</div>
</div>
<div class="modal-footer">
<div id="lastQueries" class="pull-left" style="margin: -25px 0px;"></div>
<div id="lastQueries" class="pull-left"></div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>


10 changes: 8 additions & 2 deletions docs/install_qleverui.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,18 @@ You should now be able to connect to QLever UI via <http://localhost:7000>. Cont
# Installing QLever UI without docker
When not using docker there are some additional steps to do. QLever UI is built upon a [Python 3](https://www.python.org/downloads/) / [Django 5](https://www.djangoproject.com/) backend so you will need to have Python 3 installed in order to run QLever UI. It's strongly recommended to use [virtual environments](https://docs.python.org/3/library/venv.html) to manage the project's dependencies when not using the docker build. In order to manage the dependencies, we use pip.

1. Setup formatter
1. Build frontend components

QLever UI includes interactive components that require building:

```shell
npm install
npm ci
npm run build
```

This builds:
- SPARQL formatter (WebAssembly module)
- Query visualization components for interactive tree analysis

2. If "[pip](https://pypi.org/project/pip/)" is installed on your system / in your virtual environment you can simply use
```shell
Expand Down
Loading