parent
8481ab84a1
commit
11e4a67708
@ -1,83 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>MTree API Interface</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
padding: 8px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>MTree API Interface</h1>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="queryPoint">Query Point (JSON Array):</label>
|
|
||||||
<input type="text" id="queryPoint" placeholder='e.g. [0.5, 0.5]' />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="radius">Radius:</label>
|
|
||||||
<input type="text" id="radius" placeholder="e.g. 1.0" />
|
|
||||||
</div>
|
|
||||||
<button onclick="performRangeQuery()">Perform Range Query</button>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="knn">Number of Neighbors (k):</label>
|
|
||||||
<input type="text" id="knn" placeholder="e.g. 5" />
|
|
||||||
</div>
|
|
||||||
<button onclick="performKNNQuery()">Perform KNN Query</button>
|
|
||||||
|
|
||||||
<div id="result"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function performRangeQuery() {
|
|
||||||
const queryPoint = document.getElementById('queryPoint').value;
|
|
||||||
const radius = document.getElementById('radius').value;
|
|
||||||
const response = await fetch(`/rangeQuery?queryPoint=${encodeURIComponent(queryPoint)}&radius=${encodeURIComponent(radius)}`);
|
|
||||||
document.getElementById('result').innerText = await response.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performKNNQuery() {
|
|
||||||
const queryPoint = document.getElementById('queryPoint').value;
|
|
||||||
const k = document.getElementById('knn').value;
|
|
||||||
const response = await fetch(`/kNNQuery?queryPoint=${encodeURIComponent(queryPoint)}&k=${encodeURIComponent(k)}`);
|
|
||||||
document.getElementById('result').innerText = await response.text();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>MTree Interface</title>
|
||||||
|
|
||||||
|
<link rel="preload" href="style/index.css" as="style">
|
||||||
|
<link rel="stylesheet" href="style/index.css">
|
||||||
|
<script src="script/main.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>MTree Interface</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section id="column-section">
|
||||||
|
<div class="column" id="insertion-column">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="point">Point to Insert:</label>
|
||||||
|
<input type="text" id="point" />
|
||||||
|
</div>
|
||||||
|
<button id="insertButton" disabled>Insert Point</button>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="dimensions">Dimensions:</label>
|
||||||
|
<input type="number" id="dimensions" value="3" placeholder="e.g. 3" min="0" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pointCount">Number of Points:</label>
|
||||||
|
<input type="number" id="pointCount" value="1000" placeholder="e.g. 1000" min="0" />
|
||||||
|
</div>
|
||||||
|
<button id="recreateTreeButton">Initialize Tree</button>
|
||||||
|
</div>
|
||||||
|
<div class="column" id="queries-column">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="queryPoint">Query Point:</label>
|
||||||
|
<input type="text" id="queryPoint" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="radius">Radius:</label>
|
||||||
|
<input type="number" id="radius" placeholder="e.g. 1.0" min="0" step="0.01" />
|
||||||
|
</div>
|
||||||
|
<button id="rangeQueryButton" disabled>Perform Range Query</button>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="knn">Number of Neighbors (k):</label>
|
||||||
|
<input type="number" id="knn" placeholder="e.g. 5" min="0" />
|
||||||
|
</div>
|
||||||
|
<button id="knnQueryButton" disabled>Perform KNN Query</button>
|
||||||
|
</div>
|
||||||
|
<div class="column" id="save-load-column">
|
||||||
|
<button id="saveTreeButton" disabled>Save Tree</button>
|
||||||
|
<button id="loadTreeButton">Load Tree</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="status-section">
|
||||||
|
<div id="status">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="result-section">
|
||||||
|
<div id="result">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -0,0 +1,220 @@
|
|||||||
|
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.sort((a, b) => a.distance - b.distance);
|
||||||
|
|
||||||
|
const table = createTable(result);
|
||||||
|
document.getElementById('result').innerHTML = '';
|
||||||
|
document.getElementById('result').appendChild(table);
|
||||||
|
document.getElementById('status').innerText = 'Range query successful';
|
||||||
|
} 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);
|
||||||
|
document.getElementById('result').innerHTML = '';
|
||||||
|
document.getElementById('result').appendChild(table);
|
||||||
|
document.getElementById('status').innerText = 'KNN query successful';
|
||||||
|
} 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 = 'Point';
|
||||||
|
headerRow.appendChild(th2);
|
||||||
|
|
||||||
|
result.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 response = await fetch('/recreate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ dimensions, pointCount })
|
||||||
|
});
|
||||||
|
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 = 'tree.json';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the MTree from the text area.
|
||||||
|
*/
|
||||||
|
async function loadTree() {
|
||||||
|
const input = document.getElementById('treeFileInput');
|
||||||
|
const file = input.files[0];
|
||||||
|
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(', ')}]`;
|
||||||
|
});
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
:root {
|
||||||
|
--background-color: #e9ecef;
|
||||||
|
--accent-color: #007bff;
|
||||||
|
--highlight-color: #0056b3;
|
||||||
|
--text-color: #6c757d;
|
||||||
|
--button-color: #007bff;
|
||||||
|
--button-highlight-color: #0056b3;
|
||||||
|
--section-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], input[type="number"] {
|
||||||
|
padding: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--text-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: var(--button-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--button-highlight-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#insertion-column {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queries-column {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#save-load-column {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background-color: var(--section-color);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-section {
|
||||||
|
display: flex;
|
||||||
|
width: auto;
|
||||||
|
column-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result-section {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result td, #result th {
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result tr:nth-child(odd) {
|
||||||
|
background-color: #b3cdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result tr:nth-child(even) {
|
||||||
|
background-color: #e7f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result th {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-section {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status.success {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status.error {
|
||||||
|
background-color: #ff6c6c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
Loading…
Reference in new issue