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} 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} 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} 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); }); }); });