UMCU/EmbeddingDistance
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Embedding Distance Visualization</title>7 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">8 <style>9 :root {10 --primary-color: #4361ee;11 --secondary-color: #3f37c9;12 --accent-color: #4895ef;13 --light-color: #f8f9fa;14 --dark-color: #212529;15 --success-color: #4cc9f0;16 --warning-color: #f8961e;17 --danger-color: #f94144;18 }19 20 * {21 box-sizing: border-box;22 margin: 0;23 padding: 0;24 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;25 }26 27 body {28 background-color: var(--light-color);29 color: var(--dark-color);30 line-height: 1.6;31 padding: 20px;32 }33 34 .container {35 max-width: 1200px;36 margin: 0 auto;37 padding: 20px;38 }39 40 header {41 text-align: center;42 margin-bottom: 30px;43 background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));44 color: white;45 padding: 20px;46 border-radius: 10px;47 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);48 }49 50 h1 {51 font-size: 2.5rem;52 margin-bottom: 10px;53 }54 55 .subtitle {56 font-size: 1.1rem;57 opacity: 0.9;58 }59 60 .upload-section {61 background-color: white;62 border-radius: 10px;63 padding: 30px;64 margin-bottom: 30px;65 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);66 text-align: center;67 }68 69 .upload-area {70 border: 2px dashed #ccc;71 border-radius: 8px;72 padding: 30px;73 margin: 20px 0;74 cursor: pointer;75 transition: all 0.3s;76 }77 78 .upload-area:hover {79 border-color: var(--primary-color);80 background-color: rgba(67, 97, 238, 0.05);81 }82 83 .upload-area.active {84 border-color: var(--success-color);85 background-color: rgba(76, 201, 240, 0.1);86 }87 88 .upload-icon {89 font-size: 3rem;90 color: var(--primary-color);91 margin-bottom: 15px;92 }93 94 .btn {95 background-color: var(--primary-color);96 color: white;97 border: none;98 padding: 12px 24px;99 border-radius: 5px;100 cursor: pointer;101 font-size: 1rem;102 font-weight: 600;103 transition: all 0.3s;104 display: inline-block;105 margin-top: 15px;106 }107 108 .btn:hover {109 background-color: var(--secondary-color);110 transform: translateY(-2px);111 }112 113 .btn:active {114 transform: translateY(0);115 }116 117 .btn-secondary {118 background-color: var(--light-color);119 color: var(--dark-color);120 border: 1px solid #ccc;121 }122 123 .btn-secondary:hover {124 background-color: #e9ecef;125 }126 127 .visualization-section {128 background-color: white;129 border-radius: 10px;130 padding: 30px;131 margin-bottom: 30px;132 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);133 position: relative;134 min-height: 600px;135 }136 137 .chart-container {138 width: 100%;139 height: 100%;140 margin-top: 20px;141 position: relative;142 }143 144 .tooltip {145 position: absolute;146 background-color: rgba(0, 0, 0, 0.8);147 color: white;148 padding: 8px 12px;149 border-radius: 4px;150 font-size: 14px;151 pointer-events: none;152 opacity: 0;153 transition: opacity 0.3s;154 z-index: 100;155 }156 157 .data-table {158 width: 100%;159 border-collapse: collapse;160 margin-top: 20px;161 }162 163 .data-table th, .data-table td {164 padding: 12px 15px;165 text-align: left;166 border-bottom: 1px solid #ddd;167 }168 169 .data-table th {170 background-color: var(--primary-color);171 color: white;172 }173 174 .data-table tr:hover {175 background-color: #f5f5f5;176 }177 178 .controls {179 display: flex;180 justify-content: space-between;181 align-items: center;182 margin-bottom: 20px;183 }184 185 .slider-container {186 flex-grow: 1;187 margin: 0 20px;188 }189 190 .slider-container label {191 display: block;192 margin-bottom: 5px;193 font-weight: 600;194 }195 196 .radio-group {197 display: flex;198 gap: 15px;199 align-items: center;200 flex-wrap: wrap;201 }202 203 .radio-group label {204 display: flex;205 align-items: center;206 gap: 5px;207 cursor: pointer;208 white-space: nowrap;209 }210 211 .loading {212 position: absolute;213 top: 0;214 left: 0;215 width: 100%;216 height: 100%;217 background-color: rgba(255, 255, 255, 0.8);218 display: flex;219 justify-content: center;220 align-items: center;221 z-index: 10;222 flex-direction: column;223 border-radius: 10px;224 }225 226 .spinner {227 width: 50px;228 height: 50px;229 border: 5px solid rgba(67, 97, 238, 0.1);230 border-radius: 50%;231 border-top-color: var(--primary-color);232 animation: spin 1s ease-in-out infinite;233 margin-bottom: 15px;234 }235 236 @keyframes spin {237 to { transform: rotate(360deg); }238 }239 240 .error-message {241 color: var(--danger-color);242 background-color: rgba(249, 65, 68, 0.1);243 padding: 15px;244 border-radius: 5px;245 margin: 20px 0;246 border-left: 4px solid var(--danger-color);247 }248 249 .info-message {250 color: var(--dark-color);251 background-color: rgba(33, 37, 41, 0.05);252 padding: 15px;253 border-radius: 5px;254 margin: 20px 0;255 border-left: 4px solid var(--dark-color);256 }257 258 .distance-legend {259 background-color: white;260 border-radius: 5px;261 padding: 10px;262 position: absolute;263 right: 50px;264 top: 50px;265 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);266 font-size: 0.9rem;267 }268 269 .distance-legend h4 {270 margin-bottom: 8px;271 border-bottom: 1px solid #eee;272 padding-bottom: 5px;273 }274 275 .distance-legend p {276 margin: 5px 0;277 }278 279 footer {280 text-align: center;281 margin-top: 40px;282 color: #6c757d;283 font-size: 0.9rem;284 }285 286 @media (max-width: 768px) {287 .controls {288 flex-direction: column;289 gap: 15px;290 }291 292 .slider-container {293 width: 100%;294 margin: 0;295 }296 297 .distance-legend {298 position: relative;299 right: auto;300 top: auto;301 margin: 20px 0;302 }303 }304 </style>305</head>306<body>307 <div class="container">308 <header>309 <h1>Sentence Embedding Visualizer</h1>310 <p class="subtitle">Visualize sentences by cosine distance (x-axis) and Euclidean distance (y-axis)</p>311 </header>312 313 <div class="upload-section">314 <h2><i class="fas fa-cloud-upload-alt"></i> Upload Your Data</h2>315 <p>Upload a JSON file containing an array of objects with 'sentence' and 'embeddings' properties</p>316 317 <div id="upload-area" class="upload-area">318 <div class="upload-icon">319 <i class="fas fa-file-upload"></i>320 </div>321 <h3>Drag & Drop your file here</h3>322 <p>or</p>323 <button id="browse-btn" class="btn btn-secondary">324 <i class="fas fa-folder-open"></i> Browse Files325 </button>326 <input type="file" id="file-input", accept=".json" style="display: none;">327 </div>328 329 <div id="sample-data" class="info-message" style="display: none;">330 <h4>Sample Data Format:</h4>331 <pre>[332 {"sentence": "This is a sample sentence", "embeddings": [0.1, 0.4, 0.2, 0.1, 0.2]},333 {"sentence": "Another example sentence", "embeddings": [0.5, 0.2, 0.3, 0.2, 0.1]},334 ...335]</pre>336 <button id="load-sample" class="btn">Load Sample Data</button>337 </div>338 </div>339 340 <div id="error-container" class="error-message" style="display: none;"></div>341 342 <div class="visualization-section">343 <div id="loading" class="loading" style="display: none;">344 <div class="spinner"></div>345 <p>Processing your embeddings...</p>346 </div>347 348 <h2><i class="fas fa-chart-line"></i> Distance Visualization</h2>349 350 <div id="controls" class="controls" style="display: none;">351 <div class="radio-group">352 <h4>Reference Point: </h4>353 <label><input type="radio" name="reference" value="first" checked> First Item</label>354 <label><input type="radio" name="reference" value="average"> Average Embedding</label>355 <label><input type="radio" name="reference" value="random"> Random Item</label>356 <label><input type="radio" name="reference" value="unit"> Unit Vector</label>357 </div>358 <div class="slider-container">359 <label for="point-size">Point Size</label>360 <input type="range" id="point-size" min="2" max="20" value="8">361 </div>362 <div class="slider-container">363 <label for="font-size">Font Size</label>364 <input type="range" id="font-size", min="8", max="24" value="12">365 </div>366 </div>367 368 <div id="distance-legend", class="distance-legend", style="display: none;">369 <h4>Distance Metrics:</h4>370 <p><strong>X-axis:</strong> Cosine distance from reference (0-1)</p>371 <p><strong>Y-axis:</strong> Euclidean distance from reference</p>372 <p>(Hover points for details)</p>373 </div>374 375 <div id="chart-container" class="chart-container"></div>376 </div>377 378 <div id="data-table-container" style="display: none;">379 <h2><i class="fas fa-table"></i> Data Summary</h2>380 <table class="data-table">381 <thead>382 <tr>383 <th>Sentence</th>384 <th>Embedding Length</th>385 <th>Cosine Distance</th>386 <th>Euclidean Distance</th>387 </tr>388 </thead>389 <tbody id="data-table-body"></tbody>390 </table>391 </div>392 393 <footer>394 <p>Sentence Embedding Visualizer © 2023 | Uses cosine and Euclidean distances</p>395 </footer>396 </div>397 398 <div id="tooltip" class="tooltip"></div>399 400 <script>401 document.addEventListener('DOMContentLoaded', function() {402 // DOM elements403 const uploadArea = document.getElementById('upload-area');404 const fileInput = document.getElementById('file-input');405 const browseBtn = document.getElementById('browse-btn');406 const errorContainer = document.getElementById('error-container');407 const loadingElement = document.getElementById('loading');408 const chartContainer = document.getElementById('chart-container');409 const controlsElement = document.getElementById('controls');410 const dataTableContainer = document.getElementById('data-table-container');411 const dataTableBody = document.getElementById('data-table-body');412 const tooltip = document.getElementById('tooltip');413 const sampleDataBtn = document.getElementById('load-sample');414 const sampleDataElement = document.getElementById('sample-data');415 const distanceLegend = document.getElementById('distance-legend');416 417 let currentData = [];418 419 // Sample data toggle420 browseBtn.addEventListener('click', () => {421 fileInput.click();422 sampleDataElement.style.display = 'none';423 });424 425 fileInput.addEventListener('change', handleFileSelect);426 427 // Drag and drop functionality428 ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {429 uploadArea.addEventListener(eventName, preventDefaults, false);430 });431 432 function preventDefaults(e) {433 e.preventDefault();434 e.stopPropagation();435 }436 437 ['dragenter', 'dragover'].forEach(eventName => {438 uploadArea.addEventListener(eventName, highlight, false);439 });440 441 ['dragleave', 'drop'].forEach(eventName => {442 uploadArea.addEventListener(eventName, unhighlight, false);443 });444 445 function highlight() {446 uploadArea.classList.add('active');447 sampleDataElement.style.display = 'none';448 }449 450 function unhighlight() {451 uploadArea.classList.remove('active');452 }453 454 uploadArea.addEventListener('drop', handleDrop, false);455 456 function handleDrop(e) {457 const dt = e.dataTransfer;458 const files = dt.files;459 if (files.length) {460 fileInput.files = files;461 handleFileSelect();462 }463 }464 465 // File handling466 function handleFileSelect() {467 const file = fileInput.files[0];468 if (!file) return;469 470 const reader = new FileReader();471 reader.onload = function(e) {472 try {473 const content = e.target.result;474 const data = JSON.parse(content);475 currentData = data;476 processData(data);477 } catch (error) {478 showError("Error parsing JSON file: " + error.message);479 }480 };481 reader.onerror = function() {482 showError="Error reading the file";483 };484 reader.readAsText(file);485 }486 487 // Sample data488 sampleDataBtn.addEventListener('click', function() {489 currentData = [490 {"sentence": "The quick brown fox jumps over the lazy dog", "embeddings": [0.8, 0.1, 0.05, 0.05, 0.1, 0.9, 0.1]},491 {"sentence": "Natural language processing is fascinating", "embeddings": [0.7, 0.2, 0.1, 0.1, 0.2, 0.8, 0.3]},492 {"sentence": "Machine learning models can understand text", "embeddings": [0.6, 0.3, 0.2, 0.2, 0.3, 0.7, 0.5]},493 {"sentence": "Word embeddings capture semantic meaning", "embeddings": [0.5, 0.4, 0.3, 0.3, 0.4, 0.6, 0.7]},494 {"sentence": "Sentences can be converted to numerical vectors", "embeddings": [0.4, 0.5, 0.4, 0.4, 0.5, 0.5, 0.8]},495 {"sentence": "Transformer models revolutionized NLP", "embeddings": [0.3, 0.6, 0.5, 0.5, 0.6, 0.4, 0.6]},496 {"sentence": "Attention mechanisms improved model performance", "embeddings": [0.2, 0.7, 0.6, 0.6, 0.7, 0.3, 0.4]},497 {"sentence": "BERT became a foundational model for NLP tasks", "embeddings": [0.1, 0.8, 0.7, 0.7, 0.8, 0.2, 0.2]},498 {"sentence": "GPT models can generate human-like text", "embeddings": [0.2, 0.6, 0.8, 0.8, 0.7, 0.4, 0.3]},499 {"sentence": "Vector space models represent meaning numerically", "embeddings": [0.9, 0.05, 0.01, 0.01, 0.05, 0.95, 0.05]}500 ];501 processData(currentData);502 });503 504 // Show sample data format when hovering upload area505 uploadArea.addEventListener('mouseenter', () => {506 sampleDataElement.style.display = 'block';507 });508 509 uploadArea.addEventListener('mouseleave', () => {510 if (!uploadArea.classList.contains('active')) {511 sampleDataElement.style.display = 'none';512 }513 });514 515 // Error handling516 function showError(message) {517 errorContainer.textContent = message;518 errorContainer.style.display = 'block';519 setTimeout(() => {520 errorContainer.style.display = 'none';521 }, 5000);522 }523 524 // Distance calculation functions525 function cosineDistance(a, b) {526 let dotProduct = 0;527 let magnitudeA = 0;528 let magnitudeB = 0;529 530 for (let i = 0; i < a.length; i++) {531 dotProduct += a[i] * b[i];532 magnitudeA += a[i] * a[i];533 magnitudeB += b[i] * b[i];534 }535 536 magnitudeA = Math.sqrt(magnitudeA);537 magnitudeB = Math.sqrt(magnitudeB);538 539 if (magnitudeA === 0 || magnitudeB === 0) return 0;540 541 const similarity = dotProduct / (magnitudeA * magnitudeB);542 return 1 - similarity; // Convert similarity to distance543 }544 545 function euclideanDistance(a, b) {546 let distance = 0;547 for (let i = 0; i < a.length; i++) {548 distance += Math.pow(a[i] - b[i], 2);549 }550 return Math.sqrt(distance);551 }552 553 function averageEmbedding(embeddings) {554 if (embeddings.length === 0) return [];555 556 const avg = new Array(embeddings[0].length).fill(0);557 for (const emb of embeddings) {558 for (let i = 0; i < emb.length; i++) {559 avg[i] += emb[i];560 }561 }562 563 return avg.map(val => val / embeddings.length);564 }565 566 // Create a unit vector of the same dimension as the embeddings567 function unitVector(dimension) {568 const divisor = Math.sqrt(dimension);569 return new Array(dimension).fill(1 / divisor);570 }571 572 // Data processing573 function processData(data) {574 if (!Array.isArray(data)) {575 showError("Data should be an array of objects");576 return;577 }578 579 // Validate data structure580 const invalidItems = data.filter(item => 581 !item.sentence || !Array.isArray(item.embeddings) || item.embeddings.length === 0582 );583 584 if (invalidItems.length > 0) {585 showError(`Some items are invalid (missing sentence or embeddings). First invalid item index: ${invalidItems[0]}`);586 return;587 }588 589 // Check that all embeddings have the same dimension590 const embeddingLengths = [...new Set(data.map(item => item.embeddings.length))];591 if (embeddingLengths.length > 1) {592 showError="All embeddings must have the same dimension";593 return;594 }595 596 loadingElement.style.display = 'flex';597 598 // Use setTimeout to allow UI to update before heavy computation599 setTimeout(() => {600 try {601 // Determine reference point based on radio button selection602 const referencePoint = getReferencePoint(data);603 604 // Calculate distances for each embedding605 const points = [];606 const embeddings = data.map(item => item.embeddings);607 608 for (let i = 0; i < data.length; i++) {609 const cosDist = cosineDistance(embeddings[i], referencePoint);610 const eucDist = euclideanDistance(embeddings[i], referencePoint);611 points.push({ x: cosDist, y: eucDist });612 }613 614 // Create visualization615 createScatterPlot(points, data, referencePoint);616 617 // Populate data table618 populateDataTable(points, data);619 620 controlsElement.style.display = 'flex';621 dataTableContainer.style.display = 'block';622 distanceLegend.style.display = 'block';623 624 // Add event listeners for reference point changes625 document.querySelectorAll('input[name="reference"]').forEach(radio => {626 radio.addEventListener('change', function() {627 if (this.checked) {628 loadingElement.style.display = 'flex';629 630 setTimeout(() => {631 try {632 const newReferencePoint = getReferencePoint(currentData);633 const newPoints = [];634 const embeddings = currentData.map(item => item.embeddings);635 636 for (let i = 0; i < currentData.length; i++) {637 const cosDist = cosineDistance(embeddings[i], newReferencePoint);638 const eucDist = euclideanDistance(embeddings[i], newReferencePoint);639 newPoints.push({ x: cosDist, y: eucDist });640 }641 642 createScatterPlot(newPoints, currentData, newReferencePoint);643 populateDataTable(newPoints, currentData);644 } finally {645 loadingElement.style.display = 'none';646 }647 }, 100);648 }649 });650 });651 } catch (error) {652 showError("Error processing data: " + error.message);653 console.error(error);654 } finally {655 loadingElement.style.display = 'none';656 }657 }, 100);658 }659 660 function getReferencePoint(data) {661 const embeddings = data.map(item => item.embeddings);662 const embeddingDimension = embeddings.length > 0 ? embeddings[0].length : 0;663 const reference = document.querySelector('input[name="reference"]:checked').value;664 665 switch(reference) {666 case 'first':667 return embeddings[0];668 case 'average':669 return averageEmbedding(embeddings);670 case 'random':671 return embeddings[Math.floor(Math.random() * embeddings.length)];672 case 'unit':673 return unitVector(embeddingDimension);674 default:675 return embeddings[0];676 }677 }678 679 // Visualization680 function createScatterPlot(points, originalData, referencePoint) {681 // Clear previous chart682 chartContainer.innerHTML = '';683 684 // Get container dimensions685 const width = chartContainer.clientWidth;686 const height = 500;687 688 // Create SVG689 const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");690 svg.setAttribute("width", "100%");691 svg.setAttribute("height", height);692 svg.setAttribute("viewBox", `0 0 ${width} ${height}`);693 chartContainer.appendChild(svg);694 695 // Calculate scales to fit all points with padding696 const xs = points.map(p => p.x);697 const ys = points.map(p => p.y);698 699 const xMin = Math.min(...xs);700 const xMax = Math.max(...xs);701 const yMin = Math.min(...ys);702 const yMax = Math.max(...ys);703 704 const xRange = xMax - xMin || 1;705 const yRange = yMax - yMin || 1;706 707 const padding = 0.1;708 709 const scaleX = value => {710 return ((value - (xMin - xRange * padding)) / (xRange * (1 + 2 * padding))) * width;711 };712 713 const scaleY = value => {714 return height - ((value - (yMin - yRange * padding)) / (yRange * (1 + 2 * padding))) * height;715 };716 717 // Create circles and labels718 const pointSize = document.getElementById('point-size').value;719 const fontSize = document.getElementById('font-size').value;720 721 // Add axes722 const xAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");723 xAxis.setAttribute("x1", scaleX(xMin - xRange * padding));724 xAxis.setAttribute("y1", scaleY(0));725 xAxis.setAttribute("x2", scaleX(xMax + xRange * padding));726 xAxis.setAttribute("y2", scaleY(0));727 xAxis.setAttribute("stroke", "#ccc");728 xAxis.setAttribute("stroke-width", "1");729 svg.appendChild(xAxis);730 731 const yAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");732 yAxis.setAttribute("x1", scaleX(0));733 yAxis.setAttribute("y1", scaleY(yMin - yRange * padding));734 yAxis.setAttribute("x2", scaleX(0));735 yAxis.setAttribute("y2", scaleY(yMax + yRange * padding));736 yAxis.setAttribute("stroke", "#ccc");737 yAxis.setAttribute("stroke-width", "1");738 svg.appendChild(yAxis);739 740 // Add axis labels741 const xAxisLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");742 xAxisLabel.setAttribute("x", scaleX(xMax + xRange * padding - 0.1 * xRange));743 xAxisLabel.setAttribute("y", scaleY(0) - 10);744 xAxisLabel.setAttribute("text-anchor", "end");745 xAxisLabel.setAttribute("font-size", "12");746 xAxisLabel.textContent = "Cosine Distance";747 svg.appendChild(xAxisLabel);748 749 const yAxisLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");750 yAxisLabel.setAttribute("x", scaleX(0) + 10);751 yAxisLabel.setAttribute("y", scaleY(yMax + yRange * padding - 0.1 * yRange) + 10);752 yAxisLabel.setAttribute("font-size", "12");753 yAxisLabel.textContent = "Euclidean Distance";754 svg.appendChild(yAxisLabel);755 756 // Highlight the reference point if it's one of the data points757 const referenceIndex = originalData.findIndex(item => {758 if (item.embeddings.length !== referencePoint.length) return false;759 return item.embeddings.every((val, i) => val === referencePoint[i]);760 });761 762 const referenceLabel = document.querySelector('input[name="reference"]:checked').value;763 let refDescription = "Reference: " + 764 (referenceLabel === 'first' ? "First Item" : 765 referenceLabel === 'average' ? "Average Embedding" : 766 referenceLabel === 'random' ? "Random Item" : 767 "Unit Vector");768 769 if (referenceIndex !== -1) {770 const refPoint = document.createElementNS("http://www.w3.org/2000/svg", "circle");771 refPoint.setAttribute("cx", scaleX(0));772 refPoint.setAttribute("cy", scaleY(0));773 refPoint.setAttribute("r", pointSize + 4);774 refPoint.setAttribute("fill", "none");775 refPoint.setAttribute("stroke", "#f94144");776 refPoint.setAttribute("stroke-width", "3");777 svg.appendChild(refPoint);778 779 const refLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");780 refLabel.setAttribute("x", scaleX(0) + pointSize + 5);781 refLabel.setAttribute("y", scaleY(0) + fontSize / 3);782 refLabel.setAttribute("font-size", fontSize);783 refLabel.setAttribute("font-weight", "bold");784 refLabel.setAttribute("fill", "#f94144");785 refLabel.textContent = refDescription;786 svg.appendChild(refLabel);787 } else {788 // For unit vector or other synthetic references, show a marker at (0,0)789 const refPoint = document.createElementNS("http://www.w3.org/2000/svg", "circle");790 refPoint.setAttribute("cx", scaleX(0));791 refPoint.setAttribute("cy", scaleY(0));792 refPoint.setAttribute("r", pointSize + 4);793 refPoint.setAttribute("fill", "none");794 refPoint.setAttribute("stroke", "#f94144");795 refPoint.setAttribute("stroke-width", "3");796 svg.appendChild(refPoint);797 798 const refLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");799 refLabel.setAttribute("x", scaleX(0) + pointSize + 5);800 refLabel.setAttribute("y", scaleY(0) + fontSize / 3);801 refLabel.setAttribute("font-size", fontSize);802 refLabel.setAttribute("font-weight", "bold");803 refLabel.setAttribute("fill", "#f94144");804 refLabel.textContent = refDescription;805 svg.appendChild(refLabel);806 }807 808 // Plot all points809 points.forEach((point, i) => {810 const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");811 circle.setAttribute("cx", scaleX(point.x));812 circle.setAttribute("cy", scaleY(point.y));813 circle.setAttribute("r", pointSize);814 circle.setAttribute("fill", `hsl(${(i * 360 / points.length)}, 70%, 50%)`);815 circle.setAttribute("data-index", i);816 circle.setAttribute("class", "data-point");817 svg.appendChild(circle);818 819 const label = document.createElementNS("http://www.w3.org/2000/svg", "text");820 label.setAttribute("x", scaleX(point.x) + pointSize + 5);821 label.setAttribute("y", scaleY(point.y) + fontSize / 3);822 label.setAttribute("font-size", fontSize);823 label.textContent = originalData[i].sentence.substring(0, 20) + 824 (originalData[i].sentence.length > 20 ? "..." : "");825 svg.appendChild(label);826 });827 828 // Add interactivity829 const dataPoints = document.querySelectorAll('.data-point');830 dataPoints.forEach(point => {831 point.addEventListener('mouseover', function(e) {832 const index = this.getAttribute('data-index');833 const sentence = originalData[index].sentence;834 const embeddings = originalData[index].embeddings;835 const embeddingsStr = embeddings.map(v => v.toFixed(2)).join(', ');836 837 tooltip.innerHTML = `838 <strong>${sentence}</strong><br>839 Cosine Distance: ${points[index].x.toFixed(4)}<br>840 Euclidean Distance: ${points[index].y.toFixed(4)}<br>841 Embeddings: [${embeddingsStr}]<br>842 ${refDescription}843 `;844 tooltip.style.left = `${e.clientX + 10}px`;845 tooltip.style.top = `${e.clientY + 10}px`;846 tooltip.style.opacity = 1;847 });848 849 point.addEventListener('mouseout', function() {850 tooltip.style.opacity = 0;851 });852 853 point.addEventListener('mousemove', function(e) {854 tooltip.style.left = `${e.clientX + 10}px`;855 tooltip.style.top = `${e.clientY + 10}px`;856 });857 });858 859 // Update visualization when controls change860 document.getElementById('point-size').addEventListener('input', function() {861 dataPoints.forEach(point => {862 point.setAttribute('r', this.value);863 });864 865 // Update reference point circle if it exists866 const refCircle = svg.querySelector('circle[stroke="#f94144"]');867 if (refCircle) {868 refCircle.setAttribute('r', parseInt(this.value) + 4);869 }870 });871 872 document.getElementById('font-size').addEventListener('input', function() {873 const labels = svg.querySelectorAll('text:not([font-weight="bold"])');874 labels.forEach(label => {875 label.setAttribute('font-size', this.value);876 });877 878 // Update reference label if it exists879 const refLabel = svg.querySelector('text[font-weight="bold"]');880 if (refLabel) {881 refLabel.setAttribute('font-size', this.value);882 }883 });884 }885 886 // Data table population887 function populateDataTable(points, originalData) {888 dataTableBody.innerHTML = '';889 890 originalData.forEach((item, i) => {891 const row = document.createElement('tr');892 893 const sentenceCell = document.createElement('td');894 sentenceCell.textContent = item.sentence;895 row.appendChild(sentenceCell);896 897 const dimCell = document.createElement('td');898 dimCell.textContent = item.embeddings.length;899 row.appendChild(dimCell);900 901 const cosCell = document.createElement('td');902 cosCell.textContent = points[i].x.toFixed(4);903 row.appendChild(cosCell);904 905 const eucCell = document.createElement('td');906 eucCell.textContent = points[i].y.toFixed(4);907 row.appendChild(eucCell);908 909 dataTableBody.appendChild(row);910 });911 }912 913 // Initially show sample data format914 sampleDataElement.style.display = 'block';915 });916 </script>917</body>918</html>