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.

266 lines
10 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('removeButton').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);
}
/**
* Removes the point with the given coordinates from the MTree.
* After removing the point, updates the status element with a success or failure message.
*/
async function removePoint() {
const pointString = document.getElementById('point').value;
const point = JSON.parse(pointString);
const response = await fetch('/remove', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ point })
});
setStatus(response.ok);
document.getElementById('status').innerText = response.ok ? 'Point removed' : `Error: ${await response.text()}`;
}
/**
* 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('removeButton').addEventListener('click', removePoint);
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);
});
});
});

Powered by TurnKey Linux.