You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
9.7 KiB
246 lines
9.7 KiB
let dimensions = 3;
|
|
|
|
/**
|
|
* Checks if the MTree is initialized by sending a request to the server.
|
|
* Disables or enables the range query, k-nearest neighbors, and insert buttons
|
|
* based on the server's response.
|
|
* @return {Promise<void>} A promise that resolves when the check is complete.
|
|
*/
|
|
async function checkIfTreeExists() {
|
|
const response = await fetch('/isInitialized');
|
|
const treeExists = await response.json();
|
|
toggleButtons(treeExists);
|
|
}
|
|
|
|
/**
|
|
* Enables or disables the range query, k-nearest neighbors, and insert buttons.
|
|
* @param {boolean} enable - Whether to enable (true) or disable (false) the buttons.
|
|
*/
|
|
function toggleButtons(enable) {
|
|
document.getElementById('rangeQueryButton').disabled = !enable;
|
|
document.getElementById('knnQueryButton').disabled = !enable;
|
|
document.getElementById('insertButton').disabled = !enable;
|
|
document.getElementById('saveTreeButton').disabled = !enable;
|
|
}
|
|
|
|
/**
|
|
* Performs a range query on the MTree.
|
|
* Retrieves all points in the tree that are within the given radius of the given query point.
|
|
* @return {Promise<void>} A promise that resolves when the query is finished.
|
|
*/
|
|
async function performRangeQuery() {
|
|
const queryPointElement = document.getElementById('queryPoint');
|
|
const radiusElement = document.getElementById('radius');
|
|
const queryPoint = queryPointElement.value;
|
|
const radius = parseFloat(radiusElement.value);
|
|
|
|
document.getElementById('rangeQueryButton').disabled = true;
|
|
const response = await fetch(`/rangeQuery?queryPoint=${encodeURIComponent(queryPoint)}&radius=${encodeURIComponent(radius)}`);
|
|
setStatus(response.ok);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
result.values.sort((a, b) => a.distance - b.distance);
|
|
|
|
const table = createTable(result.values);
|
|
document.getElementById('result').innerHTML = '';
|
|
document.getElementById('result').appendChild(table);
|
|
|
|
const { mtreeRangeQuery, sequentialSearch } = result.timingResult;
|
|
document.getElementById('status').innerText = `Range query successful: MTree (${mtreeRangeQuery.toFixed(3)} ms), Sequential (${sequentialSearch.toFixed(3)} ms)`;
|
|
} else {
|
|
document.getElementById('status').innerText = `Error: ${await response.text()}`;
|
|
}
|
|
|
|
document.getElementById('rangeQueryButton').disabled = false;
|
|
}
|
|
|
|
/**
|
|
* Performs a K-Nearest Neighbors query on the MTree.
|
|
* @return {Promise<void>} A promise that resolves when the query is finished.
|
|
*/
|
|
async function performKNNQuery() {
|
|
const queryPointElement = document.getElementById('queryPoint');
|
|
const kElement = document.getElementById('knn');
|
|
const queryPoint = queryPointElement.value;
|
|
const k = parseInt(kElement.value, 10);
|
|
|
|
const response = await fetch(`/kNNQuery?queryPoint=${encodeURIComponent(queryPoint)}&k=${encodeURIComponent(k)}`);
|
|
setStatus(response.ok);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
|
|
const table = createTable(result.values);
|
|
document.getElementById('result').innerHTML = '';
|
|
document.getElementById('result').appendChild(table);
|
|
|
|
const { mtreeKNNQuery, sequentialSearch } = result.timingResult;
|
|
document.getElementById('status').innerText = `KNN query successful: MTree (${mtreeKNNQuery.toFixed(3)} ms), Sequential (${sequentialSearch.toFixed(3)} ms)`;
|
|
} else {
|
|
document.getElementById('status').innerText = `Error: ${await response.text()}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts a point into the MTree.
|
|
* After inserting the point, updates the placeholder value for the point input.
|
|
*/
|
|
async function insertPoint() {
|
|
document.getElementById('insertButton').disabled = true;
|
|
|
|
const pointString = document.getElementById('point').value;
|
|
const point = JSON.parse(pointString);
|
|
|
|
const response = await fetch('/insert', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ point })
|
|
});
|
|
|
|
setStatus(response.ok);
|
|
document.getElementById('status').innerText = response.ok ? 'Point inserted' : `Error: ${await response.text()}`;
|
|
document.getElementById('insertButton').disabled = false;
|
|
}
|
|
|
|
/**
|
|
* Creates an HTML table from the given result of a range or K-NN query.
|
|
* @param {Object[]} result - The result of the query.
|
|
* Each element of the array should be an object with a 'point' property (an array of numbers)
|
|
* and a 'distance' property (a number).
|
|
* @return {HTMLTableElement} The created table.
|
|
*/
|
|
function createTable(result) {
|
|
const table = document.createElement('table');
|
|
const headerRow = table.createTHead().insertRow();
|
|
|
|
const th1 = document.createElement('th');
|
|
th1.innerHTML = 'Point';
|
|
headerRow.appendChild(th1);
|
|
|
|
const th2 = document.createElement('th');
|
|
th2.innerHTML = 'Distance';
|
|
headerRow.appendChild(th2);
|
|
|
|
//result.forEach(row => {
|
|
result.slice(0, 100).forEach(row => {
|
|
const rowElement = table.insertRow();
|
|
rowElement.insertCell().innerText = `[${row.point.map(x => x.toFixed(5)).join(', ')}]`;
|
|
rowElement.insertCell().innerText = row.distance.toFixed(5);
|
|
});
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Recreates the MTree with the given number of dimensions and points.
|
|
* After recreating the tree, updates the placeholder values for the query point and point inputs.
|
|
*/
|
|
async function recreateTree() {
|
|
const button = document.getElementById('recreateTreeButton')
|
|
button.disabled = true;
|
|
const dimensions = Number(document.getElementById('dimensions').value);
|
|
const pointCount = Number(document.getElementById('pointCount').value);
|
|
const capacity = Number(document.getElementById('capacity').value);
|
|
const distanceFunction = document.getElementById('distanceFunction').value;
|
|
const response = await fetch('/recreate', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ dimensions, pointCount, capacity, distanceFunction })
|
|
});
|
|
setStatus(response.ok);
|
|
document.getElementById('status').innerText = `${response.ok ? 'Tree recreated' : 'Error: ' + await response.text()}`;
|
|
if (response.ok) {
|
|
document.getElementById('queryPoint').placeholder = `e.g. [${Array(dimensions).fill(0.5).join(', ')}]`;
|
|
document.getElementById('point').placeholder = `e.g. [${Array(dimensions).fill(0.5).join(', ')}]`;
|
|
toggleButtons(true);
|
|
}
|
|
button.disabled = false;
|
|
button.textContent = 'Recreate Tree';
|
|
}
|
|
|
|
/**
|
|
* Saves the current MTree to a text area.
|
|
*/
|
|
async function saveTree() {
|
|
const response = await fetch('/tree');
|
|
setStatus(response.ok);
|
|
document.getElementById('status').innerText = response.ok ? 'Tree saved to file' : 'Error: ' + await response.text();
|
|
if (response.ok) {
|
|
const treeData = await response.text();
|
|
const blob = new Blob([treeData], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'mtree.json';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the MTree from the selected file and posts it to the loadTree API endpoint.
|
|
*/
|
|
async function loadTree() {
|
|
const input = document.getElementById('treeFileInput');
|
|
const file = input.files[0];
|
|
if (!file) {
|
|
setStatus(false);
|
|
document.getElementById('status').innerText = 'Error: No file selected';
|
|
return;
|
|
}
|
|
const reader = new FileReader();
|
|
reader.onload = async (event) => {
|
|
const treeData = event.target.result;
|
|
const response = await fetch('/loadTree', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: treeData
|
|
});
|
|
|
|
setStatus(response.ok);
|
|
if (response.ok) {
|
|
document.getElementById('status').innerText = 'Tree loaded';
|
|
toggleButtons(true);
|
|
} else {
|
|
document.getElementById('status').innerText = `Error: ${await response.text()}`;
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
/**
|
|
* Sets the status element to display a success or failure message.
|
|
* @param {boolean} success - Whether the operation was successful.
|
|
*/
|
|
function setStatus(success) {
|
|
const statusElement = document.getElementById('status');
|
|
statusElement.classList.remove('error', 'success');
|
|
statusElement.classList.add(success ? 'success' : 'error');
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
checkIfTreeExists();
|
|
document.getElementById('rangeQueryButton').addEventListener('click', performRangeQuery);
|
|
document.getElementById('knnQueryButton').addEventListener('click', performKNNQuery);
|
|
document.getElementById('insertButton').addEventListener('click', insertPoint);
|
|
document.getElementById('recreateTreeButton').addEventListener('click', recreateTree);
|
|
|
|
document.getElementById('saveTreeButton').addEventListener('click', saveTree);
|
|
document.getElementById('loadTreeButton').addEventListener('click', loadTree);
|
|
|
|
document.getElementById('queryPoint').placeholder = `e.g. [${Array(dimensions).fill(0.5).join(', ')}]`;
|
|
document.getElementById('point').placeholder = `e.g. [${Array(dimensions).fill(0.5).join(', ')}]`;
|
|
|
|
const distanceFunctionSelect = document.getElementById('distanceFunction');
|
|
fetch('/distanceFunctions')
|
|
.then(response => response.json())
|
|
.then(distanceFunctions => {
|
|
distanceFunctions.forEach(distanceFunction => {
|
|
const option = document.createElement('option');
|
|
option.value = distanceFunction;
|
|
option.innerText = distanceFunction;
|
|
distanceFunctionSelect.appendChild(option);
|
|
});
|
|
});
|
|
});
|