diff --git a/requirements.txt b/requirements.txt index ef1b1207..17b1f7ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,33 +1,7 @@ -cycler==0.11.0 -Flask==2.2.5 -Flask_Cors==4.0.0 -fonttools==4.38.0 -importlib-metadata==6.7.0 -Jinja2==3.1.2 -joblib==1.3.2 -kiwisolver==1.4.5 -kmapper==2.0.1 -llvmlite==0.39.1 -MarkupSafe==2.1.3 -matplotlib==3.5.2 -networkx==2.6.3 -numba==0.56.4 -numpy==1.21.6 -packaging==23.2 -pandas==1.3.5 -Pillow==9.2.0 -pymilvus==2.1.0 -pynndescent==0.5.11 -pyparsing==3.1.1 -python-dateutil==2.8.2 -scikit-learn==1.0.2 -scipy==1.7.3 -six==1.16.0 -tensorflow==2.7.0 -threadpoolctl==3.1.0 -torch==1.13.1 -tqdm==4.66.1 -typing_extensions==4.7.1 -umap==0.1.1 -umap-learn==0.5.3 -zipp==3.15.0 \ No newline at end of file +scikit-learn +tqdm +numpy +Pillow +matplotlib +flask +flask-cors diff --git a/tool/server/server.py b/tool/server/server.py index 076243e4..47a3dbd4 100644 --- a/tool/server/server.py +++ b/tool/server/server.py @@ -1,7 +1,5 @@ import os import sys -# from llm_agent import call_llm_agent -from run_visualization import visualize_run from flask import request, Flask, jsonify, make_response, send_file,send_from_directory from flask_cors import CORS, cross_origin diff --git a/tool/server/server_utils.py b/tool/server/server_utils.py index 59cd329c..0cc46a2f 100644 --- a/tool/server/server_utils.py +++ b/tool/server/server_utils.py @@ -12,18 +12,13 @@ from PIL import Image import matplotlib.pyplot as plt -import torch -import torchvision -import torchvision.transforms as transforms -from torch.utils.data import Dataset, DataLoader -from transformers import RobertaTokenizer sys.path.append('..') sys.path.append('../visualize') -from visualize.data_provider import DataProvider -from visualize.training_event import TrainingEventDetector -from influence_function.IF import EmpiricalIF, PairWiseEmpiricalIF -from influence_function.CustomEncoderModel import CustomEncoderModel +# from visualize.data_provider import DataProvider +# from visualize.training_event import TrainingEventDetector +# from influence_function.IF import EmpiricalIF, PairWiseEmpiricalIF +# from influence_function.CustomEncoderModel import CustomEncoderModel # Func: infer available epochs files, return a list of available epochs def infer_epoch_structure(content_path): @@ -511,26 +506,6 @@ def prediction_attribution(content_path, epoch, training_event, num_samples=10): }) return influence_samples - -class CodeSearchNetDataset(Dataset): - def __init__(self, file_path, tokenizer, sample_limit=None): - self.samples = [] - count = 0 - with open(file_path, 'r', encoding='utf-8') as f: - for line in tqdm(f, desc="读取数据集"): - line_data = json.loads(line) - docstring_tensor = tokenizer(line_data['docstring'], padding='max_length', truncation=True, max_length=256, return_tensors='pt')['input_ids'].squeeze(0) - code_tensor = tokenizer(line_data['code'], padding='max_length', truncation=True, max_length=256, return_tensors='pt')['input_ids'].squeeze(0) - self.samples.append((docstring_tensor, code_tensor)) - count += 1 - if sample_limit and count > sample_limit: - break - - def __len__(self): - return len(self.samples) - - def __getitem__(self, idx): - return self.samples[idx] def movement_attribution(content_path, epoch, training_event, num_samples=10): diff --git a/web/package-lock.json b/web/package-lock.json index 4ae82c3f..c752fdb6 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -22,6 +22,7 @@ "mathjax3-react": "^1.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-resizable-panels": "^2.1.7", "styled-components": "^6.1.14", "three": "^0.169.0", "zustand": "^5.0.1" @@ -136,6 +137,7 @@ "version": "7.26.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -363,6 +365,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -601,7 +604,6 @@ "resolved": "https://registry.npmmirror.com/@duckdb/duckdb-wasm/-/duckdb-wasm-1.30.0.tgz", "integrity": "sha512-9aWrm+4ayl4sTlvGtl/b+LxrUyXaac3yyVqkoJ3F7Vkd62PoS8PcQIRJ/KjXBW36LP1CnPY5jjvFyIcTFLtcXA==", "license": "MIT", - "peer": true, "dependencies": { "apache-arrow": "^17.0.0" } @@ -631,6 +633,380 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "dev": true, @@ -897,7 +1273,6 @@ "resolved": "https://registry.npmmirror.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", "license": "MIT", - "peer": true, "engines": { "node": "20 || >=22" } @@ -907,7 +1282,6 @@ "resolved": "https://registry.npmmirror.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "license": "MIT", - "peer": true, "dependencies": { "@isaacs/balanced-match": "^4.0.1" }, @@ -920,7 +1294,6 @@ "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", - "peer": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -952,7 +1325,6 @@ "resolved": "https://registry.npmmirror.com/@observablehq/plot/-/plot-0.6.17.tgz", "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", "license": "ISC", - "peer": true, "dependencies": { "d3": "^7.9.0", "interval-tree-1d": "^1.0.0", @@ -1217,6 +1589,7 @@ "node_modules/@react-three/fiber": { "version": "8.17.10", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/debounce": "^1.2.1", @@ -1673,7 +2046,6 @@ "resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.17.tgz", "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1682,8 +2054,7 @@ "version": "2.8.1", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/@turf/boolean-clockwise": { "version": "6.5.0", @@ -1820,15 +2191,13 @@ "version": "5.2.3", "resolved": "https://registry.npmmirror.com/@types/command-line-args/-/command-line-args-5.2.3.tgz", "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/command-line-usage": { "version": "5.0.4", "resolved": "https://registry.npmmirror.com/@types/command-line-usage/-/command-line-usage-5.0.4.tgz", "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/debounce": { "version": "1.2.4", @@ -1863,6 +2232,7 @@ "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1878,6 +2248,7 @@ "node_modules/@types/react": { "version": "18.3.12", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1922,6 +2293,7 @@ "node_modules/@types/three": { "version": "0.169.0", "license": "MIT", + "peer": true, "dependencies": { "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", @@ -2243,6 +2615,7 @@ "version": "8.11.0", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.11.0", "@typescript-eslint/types": "8.11.0", @@ -2793,8 +3166,7 @@ "version": "2.2.6", "resolved": "https://registry.npmmirror.com/@uwdata/flechette/-/flechette-2.2.6.tgz", "integrity": "sha512-DoeWHYWHvda7kaxa1BeKVp0+S9ZiqGoRxLC+z9uuYyfMLDQ+3vW4s4WVxnQYFJm8nshS5ezAsbKBN9ojCBtdEQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@uwdata/mosaic-core": { "version": "0.21.1", @@ -2813,7 +3185,6 @@ "resolved": "https://registry.npmmirror.com/@uwdata/mosaic-inputs/-/mosaic-inputs-0.21.1.tgz", "integrity": "sha512-9h/PFk71QL5+Nhqsai9pqVdnHUQb8OAemZrPossbfvb3q62o7RpU+jMwxcAId4uB/qRTAFxr0LgRfoPxrjfZYg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@uwdata/mosaic-core": "^0.21.1", "@uwdata/mosaic-sql": "^0.21.1" @@ -2824,7 +3195,6 @@ "resolved": "https://registry.npmmirror.com/@uwdata/mosaic-plot/-/mosaic-plot-0.21.1.tgz", "integrity": "sha512-ZPBD0Km44VIexZ7l88n1yWh8QpVJakJnmkyq6zr8aujw2i2+MkrcI8Abc2aPJ74o2YiSb9hMKxno83nB/Mfy7A==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@observablehq/plot": "^0.6.17", "@uwdata/mosaic-core": "^0.21.1", @@ -3168,7 +3538,6 @@ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3181,7 +3550,6 @@ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3258,7 +3626,6 @@ "resolved": "https://registry.npmmirror.com/apache-arrow/-/apache-arrow-17.0.0.tgz", "integrity": "sha512-X0p7auzdnGuhYMVKYINdQssS4EcKec9TCXyez/qtJt32DrIMGbzqiaMiQ0X6fQlQpw8Fl0Qygcv4dfRAr5Gu9Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.11", "@types/command-line-args": "^5.2.3", @@ -3279,7 +3646,6 @@ "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.24.tgz", "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3289,7 +3655,6 @@ "resolved": "https://registry.npmmirror.com/array-back/-/array-back-3.1.0.tgz", "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -3352,8 +3717,7 @@ "version": "2.0.5", "resolved": "https://registry.npmmirror.com/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.3", @@ -3531,7 +3895,6 @@ "resolved": "https://registry.npmmirror.com/chalk-template/-/chalk-template-0.4.0.tgz", "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.1.2" }, @@ -3625,7 +3988,6 @@ "resolved": "https://registry.npmmirror.com/command-line-args/-/command-line-args-5.2.1.tgz", "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "license": "MIT", - "peer": true, "dependencies": { "array-back": "^3.1.0", "find-replace": "^3.0.0", @@ -3641,7 +4003,6 @@ "resolved": "https://registry.npmmirror.com/command-line-usage/-/command-line-usage-7.0.3.tgz", "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", "license": "MIT", - "peer": true, "dependencies": { "array-back": "^6.2.2", "chalk-template": "^0.4.0", @@ -3657,7 +4018,6 @@ "resolved": "https://registry.npmmirror.com/array-back/-/array-back-6.2.2.tgz", "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.17" } @@ -3667,7 +4027,6 @@ "resolved": "https://registry.npmmirror.com/typical/-/typical-7.3.0.tgz", "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.17" } @@ -3845,7 +4204,6 @@ "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "license": "ISC", - "peer": true, "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -3892,7 +4250,6 @@ "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3902,7 +4259,6 @@ "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -3919,7 +4275,6 @@ "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "license": "ISC", - "peer": true, "dependencies": { "d3-path": "1 - 3" }, @@ -3932,7 +4287,6 @@ "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3942,7 +4296,6 @@ "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "license": "ISC", - "peer": true, "dependencies": { "d3-array": "^3.2.0" }, @@ -3955,7 +4308,6 @@ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", - "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -3968,7 +4320,6 @@ "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "license": "ISC", - "peer": true, "dependencies": { "delaunator": "5" }, @@ -3981,7 +4332,6 @@ "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3991,7 +4341,6 @@ "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -4026,7 +4375,6 @@ "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=12" } @@ -4036,7 +4384,6 @@ "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "license": "ISC", - "peer": true, "dependencies": { "d3-dsv": "1 - 3" }, @@ -4049,7 +4396,6 @@ "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -4064,7 +4410,6 @@ "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4095,7 +4440,6 @@ "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", - "peer": true, "dependencies": { "d3-color": "1 - 3" }, @@ -4108,7 +4452,6 @@ "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4118,7 +4461,6 @@ "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4128,7 +4470,6 @@ "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4138,7 +4479,6 @@ "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4148,7 +4488,6 @@ "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", - "peer": true, "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -4165,7 +4504,6 @@ "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", - "peer": true, "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -4179,7 +4517,6 @@ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", - "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -4202,7 +4539,6 @@ "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", - "peer": true, "dependencies": { "d3-path": "^3.1.0" }, @@ -4215,7 +4551,6 @@ "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", - "peer": true, "dependencies": { "d3-array": "2 - 3" }, @@ -4228,7 +4563,6 @@ "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", - "peer": true, "dependencies": { "d3-time": "1 - 3" }, @@ -4241,7 +4575,6 @@ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", - "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -4254,7 +4587,6 @@ "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4264,7 +4596,6 @@ "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "license": "ISC", - "peer": true, "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -4284,7 +4615,6 @@ "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -4301,7 +4631,6 @@ "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 10" } @@ -4311,7 +4640,6 @@ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", - "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -4324,7 +4652,6 @@ "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "license": "ISC", - "peer": true, "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -4350,7 +4677,6 @@ "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "license": "ISC", - "peer": true, "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -4363,7 +4689,6 @@ "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -4374,7 +4699,8 @@ "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "peer": true }, "node_modules/debounce": { "version": "1.2.1", @@ -4419,7 +4745,6 @@ "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", "license": "ISC", - "peer": true, "dependencies": { "robust-predicates": "^3.0.2" } @@ -4483,8 +4808,7 @@ "version": "0.2.0", "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", @@ -4519,8 +4843,7 @@ "version": "9.2.2", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -4599,6 +4922,7 @@ "version": "9.13.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", @@ -4738,6 +5062,7 @@ "version": "8.13.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4971,7 +5296,6 @@ "resolved": "https://registry.npmmirror.com/find-replace/-/find-replace-3.0.0.tgz", "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "license": "MIT", - "peer": true, "dependencies": { "array-back": "^3.0.1" }, @@ -5069,8 +5393,7 @@ "version": "24.12.23", "resolved": "https://registry.npmmirror.com/flatbuffers/-/flatbuffers-24.12.23.tgz", "integrity": "sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -5097,7 +5420,6 @@ "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -5283,7 +5605,6 @@ "resolved": "https://registry.npmmirror.com/glob/-/glob-11.0.3.tgz", "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "license": "ISC", - "peer": true, "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", @@ -5318,7 +5639,6 @@ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.1.1.tgz", "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -5504,7 +5824,6 @@ "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5514,7 +5833,6 @@ "resolved": "https://registry.npmmirror.com/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", "license": "MIT", - "peer": true, "dependencies": { "binary-search-bounds": "^2.0.0" } @@ -5550,7 +5868,6 @@ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5610,8 +5927,7 @@ "version": "0.2.1", "resolved": "https://registry.npmmirror.com/isoformat/-/isoformat-0.2.1.tgz", "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/its-fine": { "version": "1.2.5", @@ -5635,7 +5951,6 @@ "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-4.1.1.tgz", "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5657,7 +5972,6 @@ "version": "0.0.3", "resolved": "https://registry.npmmirror.com/json-bignum/-/json-bignum-0.0.3.tgz", "integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==", - "peer": true, "engines": { "node": ">=0.8" } @@ -5757,8 +6071,7 @@ "version": "4.3.0", "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5784,7 +6097,6 @@ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.2.tgz", "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "license": "ISC", - "peer": true, "engines": { "node": "20 || >=22" } @@ -5947,7 +6259,6 @@ "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "license": "ISC", - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -5997,7 +6308,6 @@ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6131,8 +6441,7 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0", - "peer": true + "license": "BlueOak-1.0.0" }, "node_modules/parse-svg-path": { "version": "0.1.2", @@ -6204,7 +6513,6 @@ "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.0.tgz", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" @@ -6983,6 +7291,7 @@ "node_modules/react": { "version": "18.3.1", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -7003,6 +7312,7 @@ "node_modules/react-dom": { "version": "18.3.1", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -7044,6 +7354,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable-panels": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", + "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://mirrors.cloud.tencent.com/npm/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7084,8 +7404,7 @@ "version": "3.0.2", "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense", - "peer": true + "license": "Unlicense" }, "node_modules/roughjs": { "version": "4.5.2", @@ -7126,7 +7445,6 @@ "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -7360,7 +7678,6 @@ "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -7510,7 +7827,6 @@ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", - "peer": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -7529,7 +7845,6 @@ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7544,7 +7859,6 @@ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7553,15 +7867,13 @@ "version": "8.0.0", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7574,7 +7886,6 @@ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -7591,7 +7902,6 @@ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7604,7 +7914,6 @@ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7666,7 +7975,6 @@ "resolved": "https://registry.npmmirror.com/table-layout/-/table-layout-4.1.1.tgz", "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", "license": "MIT", - "peer": true, "dependencies": { "array-back": "^6.2.2", "wordwrapjs": "^5.1.0" @@ -7680,7 +7988,6 @@ "resolved": "https://registry.npmmirror.com/array-back/-/array-back-6.2.2.tgz", "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.17" } @@ -7698,7 +8005,8 @@ }, "node_modules/three": { "version": "0.169.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/three-mesh-bvh": { "version": "0.7.8", @@ -7823,7 +8131,6 @@ "resolved": "https://registry.npmmirror.com/ts-json-schema-generator/-/ts-json-schema-generator-2.4.0.tgz", "integrity": "sha512-HbmNsgs58CfdJq0gpteRTxPXG26zumezOs+SB9tgky6MpqiFgQwieCn2MW70+sxpHouZ/w9LW0V6L4ZQO4y1Ug==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15", "commander": "^13.1.0", @@ -7846,7 +8153,6 @@ "resolved": "https://registry.npmmirror.com/commander/-/commander-13.1.0.tgz", "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -7855,15 +8161,13 @@ "version": "2.8.1", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/ts-json-schema-generator/node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7934,6 +8238,7 @@ "version": "5.6.3", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7969,7 +8274,6 @@ "resolved": "https://registry.npmmirror.com/typical/-/typical-4.0.0.tgz", "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8054,6 +8358,7 @@ "version": "5.4.10", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -8244,7 +8549,6 @@ "resolved": "https://registry.npmmirror.com/wordwrapjs/-/wordwrapjs-5.1.1.tgz", "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.17" } @@ -8254,7 +8558,6 @@ "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -8273,7 +8576,6 @@ "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8291,7 +8593,6 @@ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8301,7 +8602,6 @@ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8316,15 +8616,13 @@ "version": "8.0.0", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8339,7 +8637,6 @@ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, diff --git a/web/package.json b/web/package.json index 52b1b82f..35f15f16 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ "mathjax3-react": "^1.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-resizable-panels": "^2.1.7", "styled-components": "^6.1.14", "three": "^0.169.0", "zustand": "^5.0.1" diff --git a/web/src/component/chart.tsx b/web/src/component/chart.tsx index 0aaabf23..ca999c6f 100644 --- a/web/src/component/chart.tsx +++ b/web/src/component/chart.tsx @@ -1,10 +1,15 @@ // ChartComponent.tsx -import { memo, useEffect, useMemo, useRef, useState } from 'react'; -import { EmbeddingView, type EmbeddingViewProps, type DataPoint, type ViewportState } from 'embedding-atlas/react'; +import { memo, useEffect, useMemo, useRef, useState } from "react"; +import { + EmbeddingView, + type EmbeddingViewProps, + type DataPoint, + type ViewportState, +} from "embedding-atlas/react"; import { useDefaultStore } from "../state/state.unified"; -import { transferArray2Color } from './utils'; +import { transferArray2Color } from "./utils"; -type EmbeddingData = NonNullable; +type EmbeddingData = NonNullable; type PreparedEmbedding = { simpleData: EmbeddingData; @@ -14,28 +19,56 @@ type PreparedEmbedding = { export const ChartComponent = memo(() => { const atlasRef = useRef(null); - const [dimensions, setDimensions] = useState<{ width: number; height: number }>({ width: 0, height: 0 }); - - const { epoch, allEpochData, globalBounds } = useDefaultStore(["epoch", "allEpochData","globalBounds"]); - const { inherentLabelData, colorDict, labelDict, textData } = useDefaultStore(["inherentLabelData", "colorDict", "labelDict", "textData"]); - const { shownData, index, isFocusMode, focusIndices } = useDefaultStore(["shownData", "index", "isFocusMode", "focusIndices"]); + const [dimensions, setDimensions] = useState<{ + width: number; + height: number; + }>({ width: 0, height: 0 }); + + const { epoch, allEpochData, globalBounds } = useDefaultStore([ + "epoch", + "allEpochData", + "globalBounds", + ]); + const { inherentLabelData, colorDict, labelDict, textData } = + useDefaultStore([ + "inherentLabelData", + "colorDict", + "labelDict", + "textData", + ]); + const { shownData, index, isFocusMode, focusIndices } = useDefaultStore([ + "shownData", + "index", + "isFocusMode", + "focusIndices", + ]); const { setHoveredIndex } = useDefaultStore(["setHoveredIndex"]); const { mode } = useDefaultStore(["mode"]); const { pointSize } = useDefaultStore(["pointSize"]); - const { revealOriginalNeighbors, revealProjectionNeighbors } = useDefaultStore(["revealOriginalNeighbors", "revealProjectionNeighbors"]); - const { showLabel, showIndex } = useDefaultStore(["showLabel", "showIndex"]); + const { revealOriginalNeighbors, revealProjectionNeighbors } = + useDefaultStore([ + "revealOriginalNeighbors", + "revealProjectionNeighbors", + ]); + const { showLabel, showIndex } = useDefaultStore([ + "showLabel", + "showIndex", + ]); const { selectedIndices } = useDefaultStore(["selectedIndices"]); const { availableEpochs } = useDefaultStore(["availableEpochs"]); const { showTrail } = useDefaultStore(["showTrail"]); const { setSelectedIndices } = useDefaultStore(["setSelectedIndices"]); - const epochData = allEpochData[epoch]; + // const epochData = allEpochData[epoch]; + const epochData = allEpochData?.[epoch]; // plot view helpers let [tooltip, setTooltip] = useState(null); // selection can be added later when needed - let [viewportState, setViewportState] = useState(null); + let [viewportState, setViewportState] = useState( + null, + ); // observe container size change useEffect(() => { @@ -49,11 +82,11 @@ export const ChartComponent = memo(() => { return; } const { width, height } = entry.contentRect; - setDimensions((prev) => ( + setDimensions((prev) => prev.width === width && prev.height === height ? prev - : { width, height } - )); + : { width, height }, + ); }); observer.observe(node); @@ -90,16 +123,16 @@ export const ChartComponent = memo(() => { y: centerY, scale, }); - }, [globalBounds]); - // filter dataIndices const filteredIndices = useMemo(() => { if (!epochData) { return [] as number[]; } - const totalIndices = epochData.projection.map((_, idx) => idx); + + const totalIndices = (epochData?.projection ?? []).map((_, idx) => idx); + let current = totalIndices; if (shownData.length > 0) { @@ -135,10 +168,11 @@ export const ChartComponent = memo(() => { const categoryColorList: string[] = []; const labelToCategoryIndex = new Map(); - let dataPoints : DataPoint[] = [] + let dataPoints: DataPoint[] = []; filteredIndices.forEach((originalIndex, position) => { - const [px, py] = epochData.projection[originalIndex] ?? [0, 0]; + const [px, py] = epochData?.projection?.[originalIndex] ?? [0, 0]; + x[position] = px; y[position] = py; @@ -159,8 +193,8 @@ export const ChartComponent = memo(() => { category: label, text: `Index: ${originalIndex}\nLabel: ${label}`, identifier: originalIndex, - fields: {} // add more fields if needed - }) + fields: {}, // add more fields if needed + }); }); const simpleData = { @@ -172,10 +206,16 @@ export const ChartComponent = memo(() => { return { simpleData, dataPoints, - categoryColors: categoryColorList.length > 0 ? categoryColorList : null, + categoryColors: + categoryColorList.length > 0 ? categoryColorList : null, }; }, [colorDict, epochData, filteredIndices, inherentLabelData]); + const selectedPoints = useMemo(() => { + if (!prepared) return []; + return selectedIndices.map((idx) => prepared.dataPoints[idx]); + }, [prepared, selectedIndices]); + const posMap = useMemo(() => { const m = new Map(); if (prepared) { @@ -187,15 +227,61 @@ export const ChartComponent = memo(() => { }, [prepared]); const [trailRefresh, setTrailRefresh] = useState(0); - useEffect(() => { setTrailRefresh((v) => v + 1); }, [selectedIndices]); + useEffect(() => { + setTrailRefresh((v) => v + 1); + }, [selectedIndices]); const neighborOverlayProps = useMemo(() => { - if (!prepared || !epochData) return { center: null, original: [], projection: [], dataX: new Float32Array(0), dataY: new Float32Array(0), pointSize, revealOriginalNeighbors, revealProjectionNeighbors } as any; + const neighborsLoaded = epochData?.neighborsLoaded; + if (!neighborsLoaded) { + } + + if (!prepared || !epochData || !epochData.projection) + return { + center: null, + original: [], + projection: [], + dataX: new Float32Array(0), + dataY: new Float32Array(0), + pointSize, + revealOriginalNeighbors, + revealProjectionNeighbors, + } as any; const idsByPos = prepared.dataPoints.map((p) => p.identifier as number); - if (!tooltip) return { center: null, original: [], projection: [], dataX: prepared.simpleData.x as Float32Array, dataY: prepared.simpleData.y as Float32Array, pointSize, revealOriginalNeighbors, revealProjectionNeighbors, idsByPos, showLabel, showIndex, labelDict, textData, inherentLabelData, viewportState, showTrail, availableEpochs, allEpochData, currentEpoch: epoch, setSelectedIndices, selectedIndices } as any; + + if (!tooltip) + return { + center: null, + original: [], + projection: [], + dataX: prepared.simpleData.x as Float32Array, + dataY: prepared.simpleData.y as Float32Array, + pointSize, + revealOriginalNeighbors, + revealProjectionNeighbors, + idsByPos, + showLabel, + showIndex, + labelDict, + textData, + inherentLabelData, + viewportState, + showTrail, + availableEpochs, + allEpochData, + currentEpoch: epoch, + setSelectedIndices, + selectedIndices, + } as any; const hoverId = tooltip.identifier as number; - const orig = (epochData.originalNeighbors?.[hoverId] ?? []).filter((nid) => posMap.has(nid)); - const proj = (epochData.projectionNeighbors?.[hoverId] ?? []).filter((nid) => posMap.has(nid)); + const orig = (epochData?.originalNeighbors?.[hoverId] ?? []).filter( + (nid) => posMap.has(nid), + ); + + const proj = (epochData?.projectionNeighbors?.[hoverId] ?? []).filter( + (nid) => posMap.has(nid), + ); + return { center: tooltip, original: orig, @@ -219,7 +305,27 @@ export const ChartComponent = memo(() => { setSelectedIndices, selectedIndices, }; - }, [prepared, epochData, tooltip, posMap, pointSize, revealOriginalNeighbors, revealProjectionNeighbors, showLabel, showIndex, labelDict, textData, inherentLabelData, viewportState, showTrail, availableEpochs, allEpochData, epoch, trailRefresh, selectedIndices]); + }, [ + prepared, + epochData, + tooltip, + posMap, + pointSize, + revealOriginalNeighbors, + revealProjectionNeighbors, + showLabel, + showIndex, + labelDict, + textData, + inherentLabelData, + viewportState, + showTrail, + availableEpochs, + allEpochData, + epoch, + trailRefresh, + selectedIndices, + ]); class NeighborOverlay { private el: HTMLDivElement | null = null; @@ -232,29 +338,40 @@ export const ChartComponent = memo(() => { this.el = target; this.props = props; this.proxy = props.proxy; - this.handleClickBound = this.handleClick.bind(this); this.mount(); } mount() { if (!this.el) return; - this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - this.svg.setAttribute('width', String(this.proxy.width)); - this.svg.setAttribute('height', String(this.proxy.height)); + this.svg = document.createElementNS( + "http://www.w3.org/2000/svg", + "svg", + ); + this.svg.setAttribute("width", String(this.proxy.width)); + this.svg.setAttribute("height", String(this.proxy.height)); this.el.appendChild(this.svg); - this.svg.addEventListener('click', this.handleClickBound); - this.defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); - const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker'); - marker.setAttribute('id', 'trail-arrow'); - marker.setAttribute('viewBox', '0 0 10 10'); - marker.setAttribute('markerUnits', 'userSpaceOnUse'); - marker.setAttribute('markerWidth', '10'); - marker.setAttribute('markerHeight', '10'); - marker.setAttribute('refX', '8'); - marker.setAttribute('refY', '5'); - marker.setAttribute('orient', 'auto'); - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - path.setAttribute('d', 'M0,0 L10,5 L0,10 Z'); - path.setAttribute('fill', '#7F8C8D'); + this.svg.addEventListener("click", this.handleClickBound); + this.defs = document.createElementNS( + "http://www.w3.org/2000/svg", + "defs", + ); + const marker = document.createElementNS( + "http://www.w3.org/2000/svg", + "marker", + ); + marker.setAttribute("id", "trail-arrow"); + marker.setAttribute("viewBox", "0 0 10 10"); + marker.setAttribute("markerUnits", "userSpaceOnUse"); + marker.setAttribute("markerWidth", "10"); + marker.setAttribute("markerHeight", "10"); + marker.setAttribute("refX", "8"); + marker.setAttribute("refY", "5"); + marker.setAttribute("orient", "auto"); + const path = document.createElementNS( + "http://www.w3.org/2000/svg", + "path", + ); + path.setAttribute("d", "M0,0 L10,5 L0,10 Z"); + path.setAttribute("fill", "#7F8C8D"); marker.appendChild(path); this.defs.appendChild(marker); this.svg.appendChild(this.defs); @@ -264,140 +381,169 @@ export const ChartComponent = memo(() => { if (this.svg) { const children = Array.from(this.svg.childNodes); for (const child of children) { - if ((child as Element).nodeName.toLowerCase() !== 'defs') { + if ((child as Element).nodeName.toLowerCase() !== "defs") { this.svg.removeChild(child); } } } } - handleClick(e: MouseEvent) { - if (!this.svg) return; - const rect = this.svg.getBoundingClientRect(); - const sx = e.clientX - rect.left; - const sy = e.clientY - rect.top; - let minD2 = Infinity; - let minIdx = -1; - for (let i = 0; i < this.props.dataX.length; i++) { - const x = this.props.dataX[i]; - const y = this.props.dataY[i]; - const loc = this.proxy.location(x, y); - const dx = loc.x - sx; - const dy = loc.y - sy; - const d2 = dx * dx + dy * dy; - if (d2 < minD2) { - minD2 = d2; - minIdx = i; - } - } - const threshold = 100; - if (minIdx >= 0 && minD2 <= threshold) { - const id = this.props.idsByPos[minIdx]; - if (this.props.setSelectedIndices) { - console.log(`Click on id ${id}`); - this.props.setSelectedIndices([id]); - } - } - } render() { if (!this.svg) return; this.clear(); - const { center, original, projection, dataX, dataY, pointSize, revealOriginalNeighbors, revealProjectionNeighbors } = this.props; - const centerLoc = center ? this.proxy.location(center.x, center.y) : null; - const neighborGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + const { + center, + original, + projection, + dataX, + dataY, + pointSize, + revealOriginalNeighbors, + revealProjectionNeighbors, + } = this.props; + const centerLoc = center + ? this.proxy.location(center.x, center.y) + : null; + const neighborGroup = document.createElementNS( + "http://www.w3.org/2000/svg", + "g", + ); + const trailGroup = document.createElementNS( + "http://www.w3.org/2000/svg", + "g", + ); const drawNeighbor = (nid: number, color: string) => { const pos = this.props.posMap.get(nid); if (pos == null) return; const x = dataX[pos]; const y = dataY[pos]; const loc = this.proxy.location(x, y); - const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', String(centerLoc.x)); - line.setAttribute('y1', String(centerLoc.y)); - line.setAttribute('x2', String(loc.x)); - line.setAttribute('y2', String(loc.y)); - line.setAttribute('stroke', color); - line.setAttribute('stroke-width', '1.5'); - line.setAttribute('stroke-linecap', 'round'); + const line = document.createElementNS( + "http://www.w3.org/2000/svg", + "line", + ); + line.setAttribute("x1", String(centerLoc.x)); + line.setAttribute("y1", String(centerLoc.y)); + line.setAttribute("x2", String(loc.x)); + line.setAttribute("y2", String(loc.y)); + line.setAttribute("stroke", color); + line.setAttribute("stroke-width", "1.5"); + line.setAttribute("stroke-linecap", "round"); neighborGroup.appendChild(line); - const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - circle.setAttribute('cx', String(loc.x)); - circle.setAttribute('cy', String(loc.y)); - circle.setAttribute('r', String(pointSize + 1.5)); - circle.setAttribute('fill', 'none'); - circle.setAttribute('stroke', color); - circle.setAttribute('stroke-width', '2'); + const circle = document.createElementNS( + "http://www.w3.org/2000/svg", + "circle", + ); + circle.setAttribute("cx", String(loc.x)); + circle.setAttribute("cy", String(loc.y)); + circle.setAttribute("r", String(pointSize + 1.5)); + circle.setAttribute("fill", "none"); + circle.setAttribute("stroke", color); + circle.setAttribute("stroke-width", "2"); neighborGroup.appendChild(circle); }; - const COLOR_ORIG = '#E74C3C'; - const COLOR_PROJ = '#2E86DE'; + const drawTrail = (nid: number) => { + const epochs = this.props.availableEpochs || []; + const currentIdx = epochs.indexOf(this.props.currentEpoch); + if (typeof nid === "number") { + const points: { x: number; y: number }[] = []; + for (let i = 0; i <= currentIdx; i++) { + const ep = epochs[i]; + const epData = this.props.allEpochData?.[ep]; + const coord = epData?.projection?.[nid]; + if (!coord) continue; + const locp = this.proxy.location(coord[0], coord[1]); + points.push({ x: locp.x, y: locp.y }); + } + for (let i = 0; i < points.length; i++) { + const c = document.createElementNS( + "http://www.w3.org/2000/svg", + "circle", + ); + c.setAttribute("cx", String(points[i].x)); + c.setAttribute("cy", String(points[i].y)); + c.setAttribute("r", String(Math.max(3, pointSize + 1))); + c.setAttribute("fill", "#7F8C8D"); + c.setAttribute("fill-opacity", "0.85"); + c.setAttribute("stroke", "#7F8C8D"); + c.setAttribute("stroke-width", "0.5"); + trailGroup.appendChild(c); + } + for (let i = 1; i < points.length; i++) { + const l = document.createElementNS( + "http://www.w3.org/2000/svg", + "line", + ); + l.setAttribute("x1", String(points[i - 1].x)); + l.setAttribute("y1", String(points[i - 1].y)); + l.setAttribute("x2", String(points[i].x)); + l.setAttribute("y2", String(points[i].y)); + l.setAttribute("stroke", "#7F8C8D"); + l.setAttribute("stroke-width", "2"); + l.setAttribute("stroke-dasharray", "6 3"); + l.setAttribute("stroke-linecap", "round"); + l.setAttribute("stroke-opacity", "0.9"); + l.setAttribute("marker-end", "url(#trail-arrow)"); + trailGroup.appendChild(l); + } + } + }; + + if (this.props.showTrail) { + const trailIds = new Set( + this.props.selectedIndices || [], + ); + console.log("Drawing trails for selected IDs: ", trailIds); + if (typeof this.props.center?.identifier === "number") { + trailIds.add(this.props.center.identifier); + } + for (const id of trailIds) { + drawTrail(id); + } + this.svg.appendChild(trailGroup); + } + + const COLOR_ORIG = "#E74C3C"; + const COLOR_PROJ = "#2E86DE"; if (centerLoc) { if (revealOriginalNeighbors) { - original.forEach((nid: number) => drawNeighbor(nid, COLOR_ORIG)); + original.forEach((nid: number) => + drawNeighbor(nid, COLOR_ORIG), + ); } if (revealProjectionNeighbors) { - projection.forEach((nid: number) => drawNeighbor(nid, COLOR_PROJ)); + projection.forEach((nid: number) => + drawNeighbor(nid, COLOR_PROJ), + ); } this.svg.appendChild(neighborGroup); - const centerCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - centerCircle.setAttribute('cx', String(centerLoc.x)); - centerCircle.setAttribute('cy', String(centerLoc.y)); - centerCircle.setAttribute('r', String(pointSize + 2)); - centerCircle.setAttribute('fill', 'none'); - centerCircle.setAttribute('stroke', '#666'); - centerCircle.setAttribute('stroke-width', '2'); + const centerCircle = document.createElementNS( + "http://www.w3.org/2000/svg", + "circle", + ); + centerCircle.setAttribute("cx", String(centerLoc.x)); + centerCircle.setAttribute("cy", String(centerLoc.y)); + centerCircle.setAttribute("r", String(pointSize + 2)); + centerCircle.setAttribute("fill", "none"); + centerCircle.setAttribute("stroke", "#666"); + centerCircle.setAttribute("stroke-width", "2"); this.svg.appendChild(centerCircle); - if (this.props.showTrail) { - const trailGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - const epochs = this.props.availableEpochs || []; - const currentIdx = epochs.indexOf(this.props.currentEpoch); - const centerId = this.props.center?.identifier as number; - if (typeof centerId === 'number') { - const points: { x: number; y: number }[] = []; - for (let i = 0; i <= currentIdx; i++) { - const ep = epochs[i]; - const epData = this.props.allEpochData?.[ep]; - const coord = epData?.projection?.[centerId]; - if (!coord) continue; - const locp = this.proxy.location(coord[0], coord[1]); - points.push({ x: locp.x, y: locp.y }); - } - for (let i = 0; i < points.length; i++) { - const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - c.setAttribute('cx', String(points[i].x)); - c.setAttribute('cy', String(points[i].y)); - c.setAttribute('r', String(Math.max(3, pointSize + 1))); - c.setAttribute('fill', '#7F8C8D'); - c.setAttribute('fill-opacity', '0.85'); - c.setAttribute('stroke', '#7F8C8D'); - c.setAttribute('stroke-width', '0.5'); - trailGroup.appendChild(c); - } - for (let i = 1; i < points.length; i++) { - const l = document.createElementNS('http://www.w3.org/2000/svg', 'line'); - l.setAttribute('x1', String(points[i - 1].x)); - l.setAttribute('y1', String(points[i - 1].y)); - l.setAttribute('x2', String(points[i].x)); - l.setAttribute('y2', String(points[i].y)); - l.setAttribute('stroke', '#7F8C8D'); - l.setAttribute('stroke-width', '2'); - l.setAttribute('stroke-dasharray', '6 3'); - l.setAttribute('stroke-linecap', 'round'); - l.setAttribute('stroke-opacity', '0.9'); - l.setAttribute('marker-end', 'url(#trail-arrow)'); - trailGroup.appendChild(l); - } - } - this.svg.appendChild(trailGroup); - } } else { this.svg.appendChild(neighborGroup); } if (this.props.showLabel || this.props.showIndex) { - const textGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - + const textGroup = document.createElementNS( + "http://www.w3.org/2000/svg", + "g", + ); + // Store occupied bounding boxes - const occupiedBoxes: { x: number, y: number, width: number, height: number }[] = []; + const occupiedBoxes: { + x: number; + y: number; + width: number; + height: number; + }[] = []; const padding = 2; // Padding between labels const charWidth = 6; // Approximate width per character for font-size 10 monospace const charHeight = 10; // Approximate height for font-size 10 @@ -407,8 +553,13 @@ export const ChartComponent = memo(() => { const x = this.props.dataX[i]; const y = this.props.dataY[i]; const loc = this.proxy.location(x, y); - const labelTextData = this.props.textData && this.props.textData[id] ? this.props.textData[id] : (this.props.labelDict?.get(this.props.inherentLabelData[id]) ?? ''); - let content = ''; + const labelTextData = + this.props.textData && this.props.textData[id] + ? this.props.textData[id] + : (this.props.labelDict?.get( + this.props.inherentLabelData[id], + ) ?? ""); + let content = ""; if (this.props.showLabel && this.props.showIndex) { content = `${id}.${labelTextData}`; } else if (this.props.showLabel) { @@ -425,19 +576,24 @@ export const ChartComponent = memo(() => { // Actually, to make collision detection easier, let's treat (labelX, labelY) as the top-left corner for calculation purposes, // but we need to adjust for SVG text rendering which uses baseline. // Standard SVG text y is the baseline. So the box top is y - charHeight. - + const boxX = labelX; - const boxY = labelY - charHeight; + const boxY = labelY - charHeight; const boxWidth = content.length * charWidth; const boxHeight = charHeight; // Check for collision let collision = false; // Check against canvas boundaries - if (boxX < 0 || boxY < 0 || boxX + boxWidth > this.props.proxy.width || boxY + boxHeight > this.props.proxy.height) { - // Optional: we might want to allow labels to be slightly out or just clip them. - // But usually we want to avoid drawing them if they are cut off? - // For now let's just check against other labels. + if ( + boxX < 0 || + boxY < 0 || + boxX + boxWidth > this.props.proxy.width || + boxY + boxHeight > this.props.proxy.height + ) { + // Optional: we might want to allow labels to be slightly out or just clip them. + // But usually we want to avoid drawing them if they are cut off? + // For now let's just check against other labels. } for (const box of occupiedBoxes) { @@ -453,28 +609,40 @@ export const ChartComponent = memo(() => { } if (!collision) { - const textEl = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - textEl.setAttribute('x', String(labelX)); - textEl.setAttribute('y', String(labelY)); - textEl.setAttribute('fill', '#000'); - textEl.setAttribute('font-size', '10'); - textEl.setAttribute('font-family', 'Console, monospace'); + const textEl = document.createElementNS( + "http://www.w3.org/2000/svg", + "text", + ); + textEl.setAttribute("x", String(labelX)); + textEl.setAttribute("y", String(labelY)); + textEl.setAttribute("fill", "#000"); + textEl.setAttribute("font-size", "10"); + textEl.setAttribute( + "font-family", + "Console, monospace", + ); textEl.textContent = content; textGroup.appendChild(textEl); - occupiedBoxes.push({ x: boxX, y: boxY, width: boxWidth, height: boxHeight }); + occupiedBoxes.push({ + x: boxX, + y: boxY, + width: boxWidth, + height: boxHeight, + }); } } this.svg.appendChild(textGroup); } - - } update(nextProps: Partial) { this.props = { ...this.props, ...nextProps }; if (this.svg) { - this.svg.setAttribute('width', String(this.props.proxy.width)); - this.svg.setAttribute('height', String(this.props.proxy.height)); + this.svg.setAttribute("width", String(this.props.proxy.width)); + this.svg.setAttribute( + "height", + String(this.props.proxy.height), + ); } this.render(); } @@ -483,7 +651,7 @@ export const ChartComponent = memo(() => { this.el.removeChild(this.svg); } if (this.svg) { - this.svg.removeEventListener('click', this.handleClickBound); + this.svg.removeEventListener("click", this.handleClickBound); } this.defs = null; this.svg = null; @@ -492,7 +660,11 @@ export const ChartComponent = memo(() => { } // find selected datapoint - async function querySelection(x: number, y: number, unitDistance: number): Promise { + async function querySelection( + x: number, + y: number, + unitDistance: number, + ): Promise { if (!prepared) { return null; } @@ -500,13 +672,19 @@ export const ChartComponent = memo(() => { let minDistance2: number | null = null; let minIndex: number | null = null; for (let i = 0; i < simpleData.x.length; i++) { - let d2 = (simpleData.x[i] - x) * (simpleData.x[i] - x) + (simpleData.y[i] - y) * (simpleData.y[i] - y); - if (minDistance2 == null || d2 < minDistance2) { - minDistance2 = d2; - minIndex = i; - } + let d2 = + (simpleData.x[i] - x) * (simpleData.x[i] - x) + + (simpleData.y[i] - y) * (simpleData.y[i] - y); + if (minDistance2 == null || d2 < minDistance2) { + minDistance2 = d2; + minIndex = i; + } } - if (minIndex == null || minDistance2 == null || Math.sqrt(minDistance2) > unitDistance * 10) { + if ( + minIndex == null || + minDistance2 == null || + Math.sqrt(minDistance2) > unitDistance * 10 + ) { return null; } return prepared.dataPoints[minIndex]; @@ -518,18 +696,30 @@ export const ChartComponent = memo(() => { categoryColors={prepared.categoryColors} width={dimensions.width || undefined} height={dimensions.height || undefined} - config={{ mode: mode, colorScheme: 'light', pointSize: pointSize }} + config={{ mode: mode, colorScheme: "light", pointSize: pointSize }} tooltip={tooltip} onTooltip={(v) => { - setHoveredIndex(v ? v.identifier as number : undefined); + setHoveredIndex(v ? (v.identifier as number) : undefined); setTooltip(v); }} viewportState={viewportState} onViewportState={(v) => setViewportState(v)} - querySelection={ querySelection } + querySelection={querySelection} + onSelection={(v) => { + console.log("Current Selected Points: ", v); + if (v === null || v.length === 0) { + setSelectedIndices([]); + return; + } + const newSelectedIndices = v.map( + (pt) => pt.identifier as number, + ); + setSelectedIndices(newSelectedIndices); + }} + selection={selectedPoints} customOverlay={{ class: NeighborOverlay as any, - props: { ...neighborOverlayProps, posMap } + props: { ...neighborOverlayProps, posMap }, }} /> ) : null; @@ -538,14 +728,14 @@ export const ChartComponent = memo(() => {
-
- {content ??
} +
+ {content ??
}
); diff --git a/web/src/component/main-block.tsx b/web/src/component/main-block.tsx index 13dcfbc9..23482696 100644 --- a/web/src/component/main-block.tsx +++ b/web/src/component/main-block.tsx @@ -1,11 +1,11 @@ -import { useEffect, useMemo, useRef, useState } from 'react' -import { useDefaultStore } from '../state/state.unified'; -import ChartComponent from './chart'; -import { notifyEpochSwitch } from '../communication/extension'; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useDefaultStore } from "../state/state.unified"; +import ChartComponent from "./chart"; +import { notifyEpochSwitch } from "../communication/extension"; // https://stackoverflow.com/questions/54095994/react-useeffect-comparing-objects // FIXME use a library for all object/array nested comparison -function deepCompareEquals(a: Array, b: Array){ +function deepCompareEquals(a: Array, b: Array) { if (a.length !== b.length) { return false; } @@ -28,10 +28,20 @@ function useDeepCompareMemoize(value: Array) { ref.current = value; } - return ref.current + return ref.current; } -function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, epochs: number[], progress: number, onSwitchEpoch: (epoch: number) => void }) { +function Timeline({ + epoch, + epochs, + progress, + onSwitchEpoch, +}: { + epoch: number; + epochs: number[]; + progress: number; + onSwitchEpoch: (epoch: number) => void; +}) { epochs = useDeepCompareMemoize(epochs); const [isPlaying, setIsPlaying] = useState(false); const intervalRef = useRef(null); @@ -43,7 +53,7 @@ function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, e // Set the initial epoch from the passed epochs array useEffect(() => { if (epochs.length > 0) { - onSwitchEpoch( epochs[0]); + onSwitchEpoch(epochs[0]); currentEpochIndexRef.current = 0; } }, [epochs]); @@ -55,15 +65,18 @@ function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, e useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { const currentIndex = epochs.indexOf(epoch); - if (event.key === 'ArrowRight' && currentIndex < epochs.length - 1) { + if ( + event.key === "ArrowRight" && + currentIndex < epochs.length - 1 + ) { onSwitchEpoch(epochs[currentIndex + 1]); - } else if (event.key === 'ArrowLeft' && currentIndex > 0) { + } else if (event.key === "ArrowLeft" && currentIndex > 0) { onSwitchEpoch(epochs[currentIndex - 1]); } }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); }, [epochs, epoch]); const togglePlayPause = () => { @@ -74,7 +87,8 @@ function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, e } } else { intervalRef.current = setInterval(() => { - const nextIndex = (currentEpochIndexRef.current + 1) % epochs.length; + const nextIndex = + (currentEpochIndexRef.current + 1) % epochs.length; if (nextIndex === 0) { if (intervalRef.current) { clearInterval(intervalRef.current); @@ -104,17 +118,17 @@ function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, e // Convert epochs into a list of nodes with x and y positions const svgDimensions = useMemo(() => { if (nodes.length > 0) { - const minX = Math.min(...nodes.map(node => node.x)) - 20; - const maxX = Math.max(...nodes.map(node => node.x)) + 20; + const minX = Math.min(...nodes.map((node) => node.x)) - 20; + const maxX = Math.max(...nodes.map((node) => node.x)) + 20; return { width: maxX - minX + nodeOffset, height: NODE_LINE_HEIGHT, - } + }; } else { return { width: 0, height: NODE_LINE_HEIGHT, - } + }; } }, [nodes]); @@ -128,124 +142,173 @@ function Timeline({ epoch, epochs, progress, onSwitchEpoch }: { epoch: number, e // Render nodes and links (simple lines between nodes) return ( -
+
- - {/* Links (lines between nodes) */} - {nodes.map((node, index) => { - if (index < nodes.length - 1) { - const nextNode = nodes[index + 1]; + width={svgDimensions.width} + height={svgDimensions.height} + // viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`} + className="timeline-svg" + > + + {/* Links (lines between nodes) */} + {nodes.map((node, index) => { + if (index < nodes.length - 1) { + const nextNode = nodes[index + 1]; + // progress is 0-100, so we map it to the number of nodes + const loadedNodeCount = + (progress / 100) * nodes.length; + const nextNodeIndex = index + 1; + const isLinkLoaded = + loadedNodeCount >= nextNodeIndex + 1; + return ( + + ); + } + return null; + })} + + {/* Nodes */} + {nodes.map((node, index) => { // progress is 0-100, so we map it to the number of nodes const loadedNodeCount = (progress / 100) * nodes.length; - const nextNodeIndex = index + 1; - const isLinkLoaded = loadedNodeCount >= (nextNodeIndex + 1); + const isLoaded = loadedNodeCount >= index + 1; + return ( - - ); - } - return null; - })} - - {/* Nodes */} - {nodes.map((node, index) => { - // progress is 0-100, so we map it to the number of nodes - const loadedNodeCount = (progress / 100) * nodes.length; - const isLoaded = loadedNodeCount >= (index + 1); - - return ( - - onSwitchEpoch(node.value)} - /> - - {node.value} - - - ); - })} - + onSwitchEpoch(node.value)} + /> + + {node.value} + + + ); + })} + {/* Play/Pause Button */}
); -}; +} export function MainBlock() { - const { epoch, setEpoch } = useDefaultStore(['epoch', 'setEpoch']); - const { availableEpochs } = useDefaultStore(['availableEpochs']); - const { progress } = useDefaultStore(['progress']); + const { epoch, setEpoch } = useDefaultStore(["epoch", "setEpoch"]); + const { availableEpochs } = useDefaultStore(["availableEpochs"]); + const { progress } = useDefaultStore(["progress"]); + + const { visualizationID, contentPath } = useDefaultStore([ + "visualizationID", + "contentPath", + ]); // only consider single container for now return (
- + - ) +
+ ); } diff --git a/web/src/state/state.unified.ts b/web/src/state/state.unified.ts index c3f90f87..02784f32 100644 --- a/web/src/state/state.unified.ts +++ b/web/src/state/state.unified.ts @@ -12,27 +12,37 @@ export type EpochData = { projection: number[][]; prediction: number[]; predProbability: number[][]; - originalNeighbors: number[][]; - projectionNeighbors: number[][]; background: string; + + originalNeighbors?: number[][]; + projectionNeighbors?: number[][]; + + neighborsLoaded?: boolean; + neighborsLoading?: boolean; // Added Optionality }; // Unified state interface combining all views export type BaseMutableGlobalStore = { // Basic configuration contentPath: string; - dataType: 'Image' | 'Text'; + dataType: "Image" | "Text"; taskType: string; - + visualizationID: string; + // Epoch and time-related data epoch: number; availableEpochs: number[]; - globalBounds: { minX:number; maxX:number; minY:number; maxY:number } | null; - + globalBounds: { + minX: number; + maxX: number; + minY: number; + maxY: number; + } | null; + // Color and label mappings colorDict: Map; labelDict: Map; - + // Text and token data textData: string[]; tokenList: string[]; @@ -40,13 +50,13 @@ export type BaseMutableGlobalStore = { originalTextData: Record; inherentLabelData: number[]; alignment: number[][]; - + // Epoch data allEpochData: Record; progress: number; - + // Display settings - mode: 'points' | 'density'; + mode: "points" | "density"; pointSize: number; showIndex: boolean; showLabel: boolean; @@ -54,26 +64,26 @@ export type BaseMutableGlobalStore = { revealOriginalNeighbors: boolean; revealProjectionNeighbors: boolean; showBackground: boolean; - + // Filter and display data index: Record; shownData: string[]; highlightData: string[]; - + // Interaction state hoveredIndex: number | undefined; selectedIndices: number[]; selectedListener: SelectedListener; - + // Focus mode isFocusMode: boolean; focusIndices: number[]; - + // Training events and influence trainingEvents: TrainingEvent[]; trainingEvent: TrainingEvent | null; // Current training event for influence view influenceSamples: InfluenceSample[]; - + // Raw data for detail views rawData: string; shownDoc: string; @@ -82,19 +92,21 @@ export type BaseMutableGlobalStore = { export let initMutableGlobalStore: BaseMutableGlobalStore = { // Basic configuration - contentPath: '', - dataType: 'Image', - taskType: '', - + contentPath: "", + dataType: "Image", + taskType: "", + + visualizationID: "", + // Epoch and time-related data epoch: 1, availableEpochs: [], globalBounds: null, - + // Color and label mappings colorDict: new Map(), labelDict: new Map(), - + // Text and token data textData: [], tokenList: [], @@ -102,13 +114,13 @@ export let initMutableGlobalStore: BaseMutableGlobalStore = { originalTextData: {}, inherentLabelData: [], alignment: [], - + // Epoch data for different views allEpochData: {}, progress: 0, - + // Display settings - mode: 'points', + mode: "points", pointSize: 2, showIndex: true, showLabel: true, @@ -116,30 +128,30 @@ export let initMutableGlobalStore: BaseMutableGlobalStore = { revealOriginalNeighbors: true, revealProjectionNeighbors: true, showBackground: false, - + // Filter and display data index: {}, shownData: ["train", "test"], highlightData: [], - + // Interaction state hoveredIndex: undefined, selectedIndices: [], selectedListener: new SelectedListener(), - + // Focus mode isFocusMode: false, focusIndices: [], - + // Training events and influence trainingEvents: [], trainingEvent: null, influenceSamples: [], - + // Raw data for detail views rawData: "", - shownDoc: '', - shownCode: '' + shownDoc: "", + shownCode: "", }; type SetFunction = (setState: (state: T) => T | Partial) => void; @@ -150,13 +162,17 @@ type SettersOnAttr = { type WithSettersOnAttr = T & SettersOnAttr; -function createMutableTypes(initialState: T, set: SetFunction): WithSettersOnAttr { +function createMutableTypes( + initialState: T, + set: SetFunction, +): WithSettersOnAttr { // eslint-disable-next-line @typescript-eslint/no-explicit-any const setters: any = {}; for (const key in initialState) { const setterName = `set${key.charAt(0).toUpperCase()}${key.slice(1)}`; - setters[setterName] = (value: typeof key) => set(() => ({ [key]: value })); + setters[setterName] = (value: typeof key) => + set(() => ({ [key]: value })); } return { ...initialState, @@ -180,38 +196,43 @@ const configuredMutableGlobalStore = initMutableGlobalStore; type MutableGlobalStore = WithSettersOnAttr; type DefaultValueSetter = (key: K, value: T[K]) => void; -function createDefaultValueSetter(set: SetFunction): DefaultValueSetter { +function createDefaultValueSetter( + set: SetFunction, +): DefaultValueSetter { return (key, value) => set(() => ({ [key]: value })); } type WithDefaultValueSetter = { - setValue: DefaultValueSetter + setValue: DefaultValueSetter; }; type WithClear = { - clear: () => void; + clear: () => void; }; type GlobalStore = MutableGlobalStore & WithDefaultValueSetter & WithClear; -const useGlobalStore = create()(subscribeWithSelector((set) => ({ - setValue: createDefaultValueSetter(set), - ...createMutableTypes(configuredMutableGlobalStore, set), - clear: () => { - const newInitialState = { - ...initMutableGlobalStore, - colorDict: new Map(), - labelDict: new Map(), - selectedListener: new SelectedListener() - }; - set(newInitialState); - }, -}))); +// Added Export to this function +export const useGlobalStore = create()( + subscribeWithSelector((set) => ({ + setValue: createDefaultValueSetter(set), + ...createMutableTypes(configuredMutableGlobalStore, set), + clear: () => { + const newInitialState = { + ...initMutableGlobalStore, + colorDict: new Map(), + labelDict: new Map(), + selectedListener: new SelectedListener(), + }; + set(newInitialState); + }, + })), +); // Utility functions for shallow comparison export function useShallow( store: UseBoundStore>, - keys: K[] + keys: K[], ): Pick { return useStoreWithEqualityFn( store, @@ -222,17 +243,17 @@ export function useShallow( } return result; }, - shallow + shallow, ); -}; +} export function useShallowAll(store: UseBoundStore>): T { return useStoreWithEqualityFn(store, (state) => state, shallow); -}; +} export function useOnSetOperation( store: UseBoundStore>, - keys: K[] + keys: K[], ): Pick { return useStoreWithEqualityFn( store, @@ -243,7 +264,7 @@ export function useOnSetOperation( } return result; }, - shallow + shallow, ); } @@ -253,4 +274,4 @@ export const useDefaultStore = (keys: K[]) => { export const useDefaultStoreAll = () => { return useShallowAll(useGlobalStore); -} \ No newline at end of file +}; diff --git a/web/src/utils/lazyLoader.ts b/web/src/utils/lazyLoader.ts new file mode 100644 index 00000000..1435c239 --- /dev/null +++ b/web/src/utils/lazyLoader.ts @@ -0,0 +1,71 @@ +import * as BackendAPI from "../communication/backend"; +import { useGlobalStore } from "../state/state.unified"; + +const LOG_PREFIX = "[LazyLoader]"; + +function logWithTimestamp(message: string): void { + console.log(`${LOG_PREFIX}[${new Date().toISOString()}] ${message}`); +} + +export const fetchHeavyDataForEpoch = async ( + epochNum: number, + contentPath: string, + visualizationID: string, +) => { + try { + const store = useGlobalStore.getState(); + const allEpochData = store.allEpochData; + + // Guard: epoch not initialized + if (!allEpochData[epochNum]) { + logWithTimestamp(`Epoch ${epochNum} data not initialized yet`); + return; + } + + const epochData = allEpochData[epochNum]; + + // Guard: already loaded + if ( + (epochData?.originalNeighbors?.length ?? 0) > 0 || + (epochData?.projectionNeighbors?.length ?? 0) > 0 + ) { + logWithTimestamp(`Epoch ${epochNum} neighbors already loaded`); + return; + } + + logWithTimestamp(` Lazy loading neighbors for epoch ${epochNum}...`); + + // Fetch in Parallel + const [originalNeighbors, projectionNeighbors] = await Promise.all([ + BackendAPI.getOriginalNeighbors(contentPath, epochNum), + BackendAPI.getProjectionNeighbors( + contentPath, + visualizationID, + epochNum, + ), + ]); + + // Update state + const updatedAllData = { + ...allEpochData, + [epochNum]: { + ...allEpochData[epochNum], + originalNeighbors: originalNeighbors.neighbors || [], + projectionNeighbors: projectionNeighbors.neighbors || [], + neighborsLoaded: true, + neighborsLoading: false, + }, + }; + + useGlobalStore.setState({ + allEpochData: updatedAllData, + }); + + logWithTimestamp(` Epoch ${epochNum} neighbors loaded successfully`); + } catch (error) { + console.error( + ` Failed to load heavy data for Epoch ${epochNum}:`, + error, + ); + } +}; diff --git a/web/src/views/plotView.tsx b/web/src/views/plotView.tsx index 77a043d8..b0ef8ca6 100644 --- a/web/src/views/plotView.tsx +++ b/web/src/views/plotView.tsx @@ -1,19 +1,21 @@ -import React, { useEffect, useState } from 'react'; -import { message, Tabs } from 'antd'; -import { MainBlock } from '../component/main-block'; -import { FunctionPanel } from '../component/function-panel'; -import { TrainingEventPanel } from '../component/training-event-panel'; -import InfluenceAnalysisPanel from '../component/influence-panel'; -import { TokenPanel } from '../component/token-panel'; -import { useDefaultStore } from '../state/state.unified'; -import * as BackendAPI from '../communication/backend'; +import React, { useEffect, useState } from "react"; +import { message, Tabs } from "antd"; +import { MainBlock } from "../component/main-block"; +import { FunctionPanel } from "../component/function-panel"; +import { TrainingEventPanel } from "../component/training-event-panel"; +import InfluenceAnalysisPanel from "../component/influence-panel"; +import { TokenPanel } from "../component/token-panel"; +import { useDefaultStore } from "../state/state.unified"; +import * as BackendAPI from "../communication/backend"; import { createRoot } from "react-dom/client"; import { StrictMode } from "react"; +import { fetchHeavyDataForEpoch } from "../utils/lazyLoader"; + import "../index.css"; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; -const LOG_PREFIX = '[TTVisualizer]'; +const LOG_PREFIX = "[TTVisualizer]"; function logWithTimestamp(message: string): void { console.log(`${LOG_PREFIX}[${new Date().toISOString()}] ${message}`); @@ -22,20 +24,36 @@ function logWithTimestamp(message: string): void { createRoot(document.getElementById("root")!).render( - + , ); // MessageHandler component for handling extension communication and backend requests function MessageHandler() { // State from unified store - const { - setContentPath, setAvailableEpochs, setDataType, setTaskType, - setTextData, setTokenList, setInherentLabelData, - setColorDict, setLabelDict, setProgress, setValue + const { + setContentPath, + setAvailableEpochs, + setDataType, + setTaskType, + setTextData, + setTokenList, + setInherentLabelData, + setColorDict, + setLabelDict, + setProgress, + setValue, } = useDefaultStore([ - 'setContentPath', 'setAvailableEpochs', 'setDataType', 'setTaskType', - 'setTextData', 'setTokenList', 'setInherentLabelData', - 'setColorDict', 'setLabelDict', 'setProgress', 'setValue' + "setContentPath", + "setAvailableEpochs", + "setDataType", + "setTaskType", + "setTextData", + "setTokenList", + "setInherentLabelData", + "setColorDict", + "setLabelDict", + "setProgress", + "setValue", ]); // Start visualizing process @@ -45,53 +63,80 @@ function MessageHandler() { visualizationID: string, dataType: string, taskType: string, - visConfig: any + visConfig: any, ) => { try { let startTime = Date.now(); - await BackendAPI.triggerStartVisualizing(contentPath, visualizationMethod, visualizationID, dataType, taskType, visConfig); - logWithTimestamp(`Visualization process started in backend. timeCost=${Date.now() - startTime}ms`); + await BackendAPI.triggerStartVisualizing( + contentPath, + visualizationMethod, + visualizationID, + dataType, + taskType, + visConfig, + ); + logWithTimestamp( + `Visualization process started in backend. timeCost=${Date.now() - startTime}ms`, + ); } catch (error) { - console.error('Error starting visualization process:', error); - message.error('Failed to start visualization process'); + console.error("Error starting visualization process:", error); + message.error("Failed to start visualization process"); } - } + }; // Load visualization data from backend with configuration - const handleLoadVisualization = async (config: any, visualizationID: string) => { + const handleLoadVisualization = async ( + config: any, + visualizationID: string, + ) => { try { - const { contentPath, visualizationMethod, dataType, taskType } = config; - - logWithTimestamp(`Web plot view start loading visualization. config=${JSON.stringify({ contentPath, visualizationMethod, visualizationID, dataType, taskType })}`); - + const { contentPath, visualizationMethod, dataType, taskType } = + config; + + logWithTimestamp( + `Web plot view start loading visualization. config=${JSON.stringify({ contentPath, visualizationMethod, visualizationID, dataType, taskType })}`, + ); + + //Set the visualizationID in state! + setValue("visualizationID", visualizationID); + // Set basic configuration setContentPath(contentPath); setDataType(dataType); setTaskType(taskType); - + // Get training process info - const processInfo = await BackendAPI.fetchTrainingProcessInfo(contentPath); + const processInfo = + await BackendAPI.fetchTrainingProcessInfo(contentPath); const epochs = processInfo.available_epochs || []; setAvailableEpochs(epochs); if (!epochs.length) { - logWithTimestamp('No epochs available from backend.'); + logWithTimestamp("No epochs available from backend."); } const colorMap = new Map(); const labelMap = new Map(); - for(let i = 0; i < processInfo.color_list.length; i++) { - colorMap.set(i, [processInfo.color_list[i][0], processInfo.color_list[i][1], processInfo.color_list[i][2]]); + for (let i = 0; i < processInfo.color_list.length; i++) { + colorMap.set(i, [ + processInfo.color_list[i][0], + processInfo.color_list[i][1], + processInfo.color_list[i][2], + ]); labelMap.set(i, processInfo.label_text_list[i]); } - + setColorDict(colorMap); setLabelDict(labelMap); - const labelsResponse = await BackendAPI.getAttributeResource(contentPath, epochs[0], 'label'); + const labelsResponse = await BackendAPI.getAttributeResource( + contentPath, + epochs[0], + "label", + ); setInherentLabelData(labelsResponse.label || []); - + // Load text data if text type - if (dataType === 'Text') { + if (dataType === "Text") { const textResponse = await BackendAPI.getText(contentPath); setTextData(textResponse.text_data || []); setTokenList(textResponse.token_list || []); @@ -103,49 +148,92 @@ function MessageHandler() { let lastEpochReceiveTimestamp: Date | undefined; const totalEpochCount = epochs.length; - let globalMinX = Infinity, globalMaxX = -Infinity; - let globalMinY = Infinity, globalMaxY = -Infinity; + let globalMinX = Infinity, + globalMaxX = -Infinity; + let globalMinY = Infinity, + globalMaxY = -Infinity; for (const epochNum of epochs) { const epochRequestStart = new Date(); if (!firstEpochRequestTimestamp) { firstEpochRequestTimestamp = epochRequestStart; - logWithTimestamp(`First epoch request sent. epoch=${epochNum} at ${epochRequestStart.toISOString()}`); + logWithTimestamp( + `First epoch request sent. epoch=${epochNum} at ${epochRequestStart.toISOString()}`, + ); } else { - logWithTimestamp(`Epoch request sent. epoch=${epochNum} at ${epochRequestStart.toISOString()}`); + logWithTimestamp( + `Epoch request sent. epoch=${epochNum} at ${epochRequestStart.toISOString()}`, + ); } - allEpochDataTemp = { ...allEpochDataTemp, [epochNum]: {} }; + allEpochDataTemp[epochNum] = { + projection: [], + prediction: [], + predProbability: [], + background: "", - // Load main plot data - const projection = await BackendAPI.fetchEpochProjection(contentPath, visualizationID, epochNum); - allEpochDataTemp[epochNum]['projection'] = projection.projection || []; + originalNeighbors: undefined, + projectionNeighbors: undefined, - // Load neighbors data - const originalNeighbors = await BackendAPI.getOriginalNeighbors(contentPath, epochNum); - const projectionNeighbors = await BackendAPI.getProjectionNeighbors(contentPath, visualizationID, epochNum); - allEpochDataTemp[epochNum]['originalNeighbors'] = originalNeighbors.neighbors || []; - allEpochDataTemp[epochNum]['projectionNeighbors'] = projectionNeighbors.neighbors || []; + neighborsLoaded: false, + neighborsLoading: false, + }; - if (taskType === 'Classification') { - const predictionResponse = await BackendAPI.getAttributeResource(contentPath, epochNum, 'prediction'); - allEpochDataTemp[epochNum]['predProbability'] = predictionResponse.prediction || []; + // Load main plot data + const projection = await BackendAPI.fetchEpochProjection( + contentPath, + visualizationID, + epochNum, + ); + allEpochDataTemp[epochNum]["projection"] = + projection.projection || []; + + allEpochDataTemp[epochNum]["originalNeighbors"] = []; + allEpochDataTemp[epochNum]["projectionNeighbors"] = []; + + if (taskType === "Classification") { + const predictionResponse = + await BackendAPI.getAttributeResource( + contentPath, + epochNum, + "prediction", + ); + allEpochDataTemp[epochNum]["predProbability"] = + predictionResponse.prediction || []; let predictions: number[] = []; - for (const prob of allEpochDataTemp[epochNum]['predProbability']) { + for (const prob of allEpochDataTemp[epochNum][ + "predProbability" + ]) { const predClass = prob.indexOf(Math.max(...prob)); predictions.push(predClass); } - allEpochDataTemp[epochNum]['prediction'] = predictions; - - const background = await BackendAPI.getBackground(contentPath, visualizationID, epochNum); - allEpochDataTemp[epochNum]['background'] = background || ''; + allEpochDataTemp[epochNum]["prediction"] = predictions; + + const background = await BackendAPI.getBackground( + contentPath, + visualizationID, + epochNum, + ); + allEpochDataTemp[epochNum]["background"] = background || ""; } - let minX = allEpochDataTemp[epochNum]['projection'].reduce((min: number, p: number[]) => p[0] < min ? p[0] : min, Infinity); - let maxX = allEpochDataTemp[epochNum]['projection'].reduce((max: number, p: number[]) => p[0] > max ? p[0] : max, -Infinity); - let minY = allEpochDataTemp[epochNum]['projection'].reduce((min: number, p: number[]) => p[1] < min ? p[1] : min, Infinity); - let maxY = allEpochDataTemp[epochNum]['projection'].reduce((max: number, p: number[]) => p[1] > max ? p[1] : max, -Infinity); + let minX = allEpochDataTemp[epochNum]["projection"].reduce( + (min: number, p: number[]) => (p[0] < min ? p[0] : min), + Infinity, + ); + let maxX = allEpochDataTemp[epochNum]["projection"].reduce( + (max: number, p: number[]) => (p[0] > max ? p[0] : max), + -Infinity, + ); + let minY = allEpochDataTemp[epochNum]["projection"].reduce( + (min: number, p: number[]) => (p[1] < min ? p[1] : min), + Infinity, + ); + let maxY = allEpochDataTemp[epochNum]["projection"].reduce( + (max: number, p: number[]) => (p[1] > max ? p[1] : max), + -Infinity, + ); globalMinX = Math.min(globalMinX, minX); globalMaxX = Math.max(globalMaxX, maxX); @@ -153,87 +241,160 @@ function MessageHandler() { globalMaxY = Math.max(globalMaxY, maxY); // Update store with new epoch data - setValue('globalBounds', { + setValue("globalBounds", { minX: globalMinX, maxX: globalMaxX, minY: globalMinY, - maxY: globalMaxY + maxY: globalMaxY, }); - setValue('allEpochData', { ...allEpochDataTemp }); - + setValue("allEpochData", { ...allEpochDataTemp }); + // Calculate progress based on the number of processed epochs // We use index + 1 because epochs array is 0-indexed in the loop, but we want to show progress for the current epoch const currentEpochIndex = epochs.indexOf(epochNum); setProgress(((currentEpochIndex + 1) / epochs.length) * 100); lastEpochReceiveTimestamp = new Date(); - const latencyMs = lastEpochReceiveTimestamp.getTime() - epochRequestStart.getTime(); - logWithTimestamp(`Epoch data received. epoch=${epochNum} at ${lastEpochReceiveTimestamp.toISOString()} duration=${latencyMs} ms`); + const latencyMs = + lastEpochReceiveTimestamp.getTime() - + epochRequestStart.getTime(); + logWithTimestamp( + `Epoch data received. epoch=${epochNum} at ${lastEpochReceiveTimestamp.toISOString()} duration=${latencyMs} ms`, + ); } if (firstEpochRequestTimestamp) { - logWithTimestamp(`First epoch request timestamp recorded at ${firstEpochRequestTimestamp.toISOString()}.`); + logWithTimestamp( + `First epoch request timestamp recorded at ${firstEpochRequestTimestamp.toISOString()}.`, + ); } if (lastEpochReceiveTimestamp) { - logWithTimestamp(`Last epoch data received at ${lastEpochReceiveTimestamp.toISOString()} after processing ${totalEpochCount} epoch(s).`); + logWithTimestamp( + `Last epoch data received at ${lastEpochReceiveTimestamp.toISOString()} after processing ${totalEpochCount} epoch(s).`, + ); } - + setProgress(100); - message.success('Visualization loaded successfully!'); - + message.success("Visualization loaded successfully!"); } catch (error) { - console.error('Error loading visualization:', error); - message.error('Failed to load visualization'); + console.error("Error loading visualization:", error); + message.error("Failed to load visualization"); } }; const handleMessage = async (event: MessageEvent) => { const { command, data } = event.data; - console.log('Received message from extension:', event); + console.log("Received message from extension:", event); switch (command) { - case 'startVisualizing': - await handleStartVisualizing(data.contentPath, data.visualizationMethod, data.visualizationID, data.dataType, data.taskType, data.visConfig); + case "startVisualizing": + await handleStartVisualizing( + data.contentPath, + data.visualizationMethod, + data.visualizationID, + data.dataType, + data.taskType, + data.visConfig, + ); break; - case 'loadVisualization': - await handleLoadVisualization(data.config, data.visualizationID); + case "loadVisualization": + await handleLoadVisualization( + data.config, + data.visualizationID, + ); break; default: - console.log('Unknown message command:', command); + console.log("Unknown message command:", command); } }; useEffect(() => { - window.addEventListener('message', handleMessage); + window.addEventListener("message", handleMessage); - return () => window.removeEventListener('message', handleMessage); + return () => window.removeEventListener("message", handleMessage); }, []); + // Register the Lazy Loader Util function + (window as any).fetchHeavyData = async ( + epochNum: number, + path: string, + visID: string, + ) => { + await fetchHeavyDataForEpoch(epochNum, path, visID); + }; return <>; } export function AppCombinedView() { return ( -
- +
+ - + -
+
- - -
+ + +
- -
+ +
@@ -244,15 +405,30 @@ export function AppCombinedView() { } function FunctionViewPanels() { - const [activeKey, setActiveKey] = useState<'FunctionPanel' | 'TrainingEventPanel'>('FunctionPanel'); + const [activeKey, setActiveKey] = useState< + "FunctionPanel" | "TrainingEventPanel" + >("FunctionPanel"); const items = [ - { key: 'FunctionPanel', label: Functions }, - { key: 'TrainingEventPanel', label: Training Events }, + { + key: "FunctionPanel", + label: Functions, + }, + { + key: "TrainingEventPanel", + label: Training Events, + }, ]; return ( -
+
-
- {activeKey === 'FunctionPanel' && } - {activeKey === 'TrainingEventPanel' && } +
+ {activeKey === "FunctionPanel" && } + {activeKey === "TrainingEventPanel" && }
); } function BottomDock() { - const [activeKey, setActiveKey] = useState<'Influence' | 'Tokens'>('Influence'); + const [activeKey, setActiveKey] = useState<"Influence" | "Tokens">( + "Influence", + ); const items = [ - { key: 'Influence', label: Influence, children: }, - { key: 'Tokens', label: Tokens, children: }, + { + key: "Influence", + label: Influence, + children: , + }, + { + key: "Tokens", + label: Tokens, + children: , + }, ]; return ( @@ -284,7 +470,7 @@ function BottomDock() { size="small" tabBarGutter={0} tabBarStyle={{ marginLeft: 0 }} - style={{ height: '100%' }} + style={{ height: "100%" }} items={items} activeKey={activeKey} onChange={(key) => setActiveKey(key as typeof activeKey)} @@ -292,4 +478,4 @@ function BottomDock() { ); } -window.vscode?.postMessage({ state: 'load' }, '*'); \ No newline at end of file +window.vscode?.postMessage({ state: "load" }, "*"); diff --git a/web/vite.config.ts b/web/vite.config.ts index 770aeeb3..c7f61c7e 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,6 +1,6 @@ -import { defineConfig, UserConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { resolve } from 'path'; +import { defineConfig, UserConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { resolve } from "path"; // const VIEW_TYPE_PANEL_VIEW = 'extension-panel-view'; // const VIEW_TYPE_PLOT_VIEW = 'extension-plot-view'; @@ -32,11 +32,11 @@ import { resolve } from 'path'; export default defineConfig({ plugins: [react()], build: { - outDir: 'dist', + outDir: "dist", rollupOptions: { input: { - plotView: resolve(__dirname, './configs/plotView/index.html'), - } + plotView: resolve(__dirname, "./configs/plotView/index.html"), + }, }, - } -}) + }, +});