Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
45 changes: 45 additions & 0 deletions backend/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,51 @@ li.CodeMirror-hint-active {
/* 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-y: auto;
}

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

/* Tree viewport and panzoom styles */
#tree-viewport {
width: 100%;
height: 600px;
border: 1px solid #ddd;
overflow: hidden !important;
position: relative;
}

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

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

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

// Set up panzoom cleanup when visualization modal is closed
$('#visualisation').on('hidden.bs.modal', function () {
if (typeof window.destroyPanzoom === 'function') {
window.destroyPanzoom();
}
});

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

Expand Down Expand Up @@ -954,6 +961,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,9 +974,22 @@ 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);

// Initialize panzoom for the tree after it's created
if (typeof window.initializePanzoom === 'function') {
// Use setTimeout to ensure the tree is fully rendered
setTimeout(() => {
window.initializePanzoom();
}, 100);
}

$("div.node").each(function () {
const details_childs = $(this).children(".node-details");
Expand Down
8 changes: 8 additions & 0 deletions backend/templates/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
window.determineOperationType = determineOperationType;
</script>

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

<!-- Our own JavaScript code for this demo -->
<script src="{% static "js/helper.js" %}"></script>
<script src="{% static "js/raphael.js" %}"></script>
Expand Down
13 changes: 9 additions & 4 deletions backend/templates/partials/modals/visualisation.html
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
<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>
<div class="pull-right">
<small>💡 Drag to pan, scroll to zoom, double-click to reset</small>
</div>
<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 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>
Expand Down
Binary file modified db/qleverui.sqlite3
Binary file not shown.
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
42 changes: 42 additions & 0 deletions docs/query-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Query Analysis

QLever UI provides interactive analysis of SPARQL query execution to help understand query performance and structure.

## Query Execution Tree

After executing a query, click the "Analysis" button to view:
- **Query execution tree** showing the hierarchical structure of query operations
- **Performance timing** for each operation node
- **Cache status** indicators (cached, pinned, ancestor cached)
- **Result sizes** and cost estimates for each step

## Navigation

For large or complex query trees, the visualization provides interactive navigation:
- **Navigate large trees** by dragging to move around the visualization
- **Zoom in/out** using mouse wheel for detailed inspection
- **Reset view** by double-clicking to return to default position
- **Hover details** on nodes to see additional performance metrics

## Performance Indicators

The query tree uses visual cues to help identify performance characteristics:
- **Color-coded nodes** indicate execution time (normal, high, very high)
- **Cache status** helps identify optimization opportunities
- **Total computation time** and query planning metrics displayed at the top
- **Node details** show sizes, cost estimates, and status information

## Real-time Updates

During query execution, the tree updates in real-time via WebSocket connection, showing progress through different execution phases. This helps understand which parts of complex queries are taking the most time.

## Query History

When logging is enabled, you can navigate between different query executions using the pagination controls at the bottom of the analysis modal.

## Tips for Analysis

- **Large trees**: Use the navigation controls to explore different sections of complex query trees
- **Performance bottlenecks**: Look for red (very high time) or orange (high time) nodes
- **Cache optimization**: Nodes marked as "cached" indicate good performance optimization
- **Query planning**: Check the meta information for time spent on query planning vs execution
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"author": "",
"description": "",
"dependencies": {
"@panzoom/panzoom": "^4.5.1",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-wasm": "^6.2.2",
Expand Down
52 changes: 52 additions & 0 deletions panzoom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Panzoom from '@panzoom/panzoom';

let panzoomInstance = null;

function initializePanzoom() {
const treeViewport = document.getElementById('tree-viewport');
const resultTree = document.getElementById('result-tree');

if (treeViewport && resultTree && !panzoomInstance) {
console.log('Initializing panzoom on tree-viewport:', treeViewport);

panzoomInstance = Panzoom(resultTree, {
maxScale: 5,
minScale: 0.1,
cursor: 'grab',
step: 0.1,
// Don't constrain panning - let users see all content
contain: false
});

// Enable zooming with mouse wheel on the viewport
treeViewport.addEventListener('wheel', panzoomInstance.zoomWithWheel);

// Reset zoom when double-clicking on the viewport
treeViewport.addEventListener('dblclick', () => {
panzoomInstance.reset();
});

console.log('Panzoom initialized successfully');
}
}

function destroyPanzoom() {
if (panzoomInstance) {
const treeViewport = document.getElementById('tree-viewport');
if (treeViewport) {
treeViewport.removeEventListener('wheel', panzoomInstance.zoomWithWheel);
treeViewport.removeEventListener('dblclick', panzoomInstance.reset);
}
panzoomInstance.destroy();
panzoomInstance = null;
console.log('Panzoom destroyed');
}
}

function resetPanzoom() {
if (panzoomInstance) {
panzoomInstance.reset();
}
}

export { initializePanzoom, destroyPanzoom, resetPanzoom };
40 changes: 26 additions & 14 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import resolve from '@rollup/plugin-node-resolve';
import { wasm } from '@rollup/plugin-wasm';

export default {
input: './formatter/index.js', // Entry point for your app
output: {
dir: './backend/static/wasm/formatter/',
format: 'esm',
export default [
{
input: './formatter/index.js', // Entry point for your app
output: {
dir: './backend/static/wasm/formatter/',
format: 'esm',
},
plugins: [
resolve(),
wasm({
maxFileSize: 0, // Set to 0 to always load as separate files
publicPath: "/static/wasm/formatter/"
}
),
]
},
plugins: [
resolve(),
wasm({
maxFileSize: 0, // Set to 0 to always load as separate files
publicPath: "/static/wasm/formatter/"
}
),
]
};
{
input: './panzoom/index.js', // Entry point for panzoom
output: {
file: './backend/static/js/panzoom.js',
format: 'esm',
},
plugins: [
resolve(),
]
}
];