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