-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add demo page #170
feat: add demo page #170
Conversation
Deploying augurs with Cloudflare Pages
|
WalkthroughThe changes introduce new functionality for detecting changepoints and clustering in time series data visualization. A new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MainThread
participant ChangepointWorker
participant ClusteringWorker
User->>MainThread: Initiate changepoint detection
MainThread->>ChangepointWorker: Send data for processing
ChangepointWorker-->>MainThread: Return detected changepoints
MainThread->>User: Display changepoint plot
User->>MainThread: Initiate clustering
MainThread->>ClusteringWorker: Send data and options
ClusteringWorker-->>MainThread: Return clustering results
MainThread->>User: Display clustering plot
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Outside diff range and nitpick comments (8)
demo/changepoint.worker.js (1)
1-3
: Consider using npm package instead of local dist fileImporting from a
dist
directory suggests using a local copy of the library. Consider installing@bsull/augurs
via npm and importing it directly for better dependency management and versioning.demo/clustering.js (3)
76-78
: Enhance color mapping for multiple clustersThe current color mapping handles only a few cluster labels and defaults to yellow for others. This may not provide distinct colors for all clusters, especially when dealing with more clusters.
Consider using a color palette or generating colors dynamically to represent each cluster uniquely:
const colors = ["blue", "red", "green", "orange", "purple", "cyan", "magenta"]; const clusterColor = cluster === -1 ? "black" : colors[cluster % colors.length] || "yellow"; u.addSeries({ label: `${i} (cluster ${cluster})`, - stroke: cluster === -1 ? "black" : cluster === 0 ? "blue" : cluster === 1 ? "red" : "yellow", + stroke: clusterColor, width: 1, }, seriesIdx);
71-82
: Ensure series indices are managed correctly inrunClustering
When updating the plot with new clustering results, the code deletes and adds series for each data point. This could lead to performance issues or index mismatches if not managed carefully.
Instead of deleting and adding series each time, consider updating the properties of existing series to reflect the new clustering results:
clusterLabels.forEach((cluster, i) => { const seriesIdx = i + 1; - u.delSeries(seriesIdx); - u.addSeries({ + u.series[seriesIdx] = { label: `${i} (cluster ${cluster})`, - stroke: /* color mapping */, + stroke: clusterColor, width: 1, - }, seriesIdx); + }; }); -u.redraw(); +u.setSeries(null, false);
66-99
: Handle potential errors in event listenersThe event listeners for input changes assume that the input values are valid numbers. Providing invalid input (e.g., non-numeric values) could cause runtime errors or unexpected behavior.
Add input validation to ensure that the values are valid numbers before running clustering:
document.getElementById("clustering-dtw-window").addEventListener("change", function() { const value = parseFloat(this.value); + if (isNaN(value)) { + console.warn("Invalid DTW window value:", this.value); + return; + } dtwOpts.window = value; runClustering(dtwOpts, dbscanOpts); });Apply similar checks for the other event listeners.
demo/changepoint.js (4)
6-895
: Consider loading large data from an external sourceIncluding large datasets like
df.ds
anddf.y
directly in the code can impact maintainability and initial load time. Consider loading the data from an external file or generating it dynamically to improve performance and manageability.For example, you could fetch the data from a JSON file:
const df = { - ds: [...], // existing array - y: [...], // existing array + ds: [], // to be populated from external source + y: [], }; + fetch('data.json') + .then(response => response.json()) + .then(data => { + df.ds = data.ds; + df.y = data.y; + // Proceed with worker initialization or other logic here + });Ensure that you adjust the logic to handle asynchronous data loading.
932-971
: Consider handling potential errors from the Web WorkerCurrently, there is no error handling for messages from the Web Worker that might indicate an error. Consider adding an error handler to gracefully handle any issues that might occur during processing.
For example, you can add an
onerror
handler:worker.onmessage = (e) => { // existing code }; + worker.onerror = (e) => { + console.error('Worker error:', e); + // Display an error message to the user or take other appropriate actions + };
938-938
: Optimize data transfer to the Web Worker usingTransferable
objectsSending the large
df
object viapostMessage
can be inefficient due to serialization overhead. Consider usingTransferable
objects such asFloat64Array
fordf.ds
anddf.y
to improve data transfer efficiency.Apply changes to convert
df.ds
anddf.y
to typed arrays and transfer them:const df = { - ds: [...], // existing array - y: [...], // existing array + ds: new Float64Array([...]), // convert to typed array + y: new Float64Array([...]), }; // In the worker communication - worker.postMessage(df); + worker.postMessage(df, [df.ds.buffer, df.y.buffer]); // transfer the underlying buffersEnsure that you adjust the worker code to handle typed arrays appropriately.
964-966
: Prevent potential memory leaks by cleaning up event listenersWhen adding event listeners like
window.addEventListener("resize", ...)
, it's important to remove them when they are no longer needed to prevent memory leaks.If the
uPlot
instance or the page may be destroyed, consider removing the event listener:const resizeHandler = () => { u.setSize(getSize()); }; window.addEventListener("resize", resizeHandler); + // When appropriate, e.g., before destroying the plot or on component unmount + window.removeEventListener("resize", resizeHandler);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (131)
demo/dist/@bsull/augurs-prophet-wasmstan/README.md
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/augurs-prophet-wasmstan-optimizer.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/augurs-prophet-wasmstan-types.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-environment.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-exit.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-stderr.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-stdin.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-stdout.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-terminal-input.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-terminal-output.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-terminal-stderr.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-terminal-stdin.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-cli-terminal-stdout.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-clocks-monotonic-clock.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-clocks-wall-clock.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-filesystem-preopens.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-filesystem-types.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-io-error.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-io-streams.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/interfaces/wasi-random-random.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/package.json
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/prophet-wasmstan.core.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs-prophet-wasmstan/prophet-wasmstan.core2.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs-prophet-wasmstan/prophet-wasmstan.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs-prophet-wasmstan/prophet-wasmstan.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/changepoint.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/changepoint.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/changepoint_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/changepoint_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/clustering.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/clustering.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/clustering_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/clustering_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/core.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/core.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/core_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/core_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/dtw.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/dtw.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/dtw_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/dtw_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/ets.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/ets.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/ets_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/ets_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/mstl.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/mstl.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/mstl_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/mstl_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/outlier.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/outlier.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/outlier_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/outlier_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/package.json
is excluded by!**/dist/**
demo/dist/@bsull/augurs/prophet.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/prophet.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/prophet_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/prophet_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/seasons.d.ts
is excluded by!**/dist/**
demo/dist/@bsull/augurs/seasons.js
is excluded by!**/dist/**
demo/dist/@bsull/augurs/seasons_bg.wasm
is excluded by!**/dist/**
,!**/*.wasm
demo/dist/@bsull/augurs/seasons_bg.wasm.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/LICENSE
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/README.md
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/cli.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/clocks.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/filesystem.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/http.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/index.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/io.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/random.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/browser/sockets.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/calls.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-http.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-io.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-socket-tcp.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-socket-udp.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-sockets.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/lib/io/worker-thread.js
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/package.json
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/cli.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/clocks.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/filesystem.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/http.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/index.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-environment.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-exit.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-run.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-stderr.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-stdin.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-stdout.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-terminal-input.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-terminal-output.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-terminal-stderr.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-terminal-stdin.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-cli-terminal-stdout.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-clocks-monotonic-clock.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-clocks-wall-clock.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-filesystem-preopens.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-filesystem-types.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-http-incoming-handler.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-http-outgoing-handler.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-http-types.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-io-error.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-io-poll.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-io-streams.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-random-insecure-seed.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-random-insecure.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-random-random.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-instance-network.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-ip-name-lookup.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-network.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-tcp-create-socket.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-tcp.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-udp-create-socket.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/interfaces/wasi-sockets-udp.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/io.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/random.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/sockets.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/wasi-cli-command.d.ts
is excluded by!**/dist/**
demo/dist/@bytecodealliance/preview2-shim/types/wasi-http-proxy.d.ts
is excluded by!**/dist/**
demo/dist/uPlot/uPlot.esm.js
is excluded by!**/dist/**
demo/dist/uPlot/uPlot.min.css
is excluded by!**/dist/**
demo/dist/worker-with-import-map/EventHandler.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/Worker.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/WorkerWithImportMapViaBedfordsShim.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/WorkerWithImportMapViaBedfordsShim.worker.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/WorkerWithImportMapViaInlineFrame.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/getImportMap.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/index.js
is excluded by!**/dist/**
demo/dist/worker-with-import-map/node.js
is excluded by!**/dist/**
📒 Files selected for processing (4)
demo/changepoint.js
(1 hunks)demo/changepoint.worker.js
(1 hunks)demo/clustering.js
(1 hunks)demo/clustering.worker.js
(1 hunks)
🔇 Additional comments (4)
demo/clustering.worker.js (1)
1-4
: Verify the existence of imported modules
Let's ensure the required modules are present in the correct location.
✅ Verification successful
Let me verify the exports more thoroughly by checking for default exports and named exports using different patterns.
Import paths are correctly referenced and modules exist with expected exports
The verification shows that:
- Both imported modules exist at the correct paths:
demo/dist/@bsull/augurs/dtw.js
demo/dist/@bsull/augurs/clustering.js
- The modules export the required symbols:
dtw.js
exportsinitSync
as default and has theDtw
classclustering.js
exportsinitSync
as default and has theDbscanClusterer
class
The imports are valid and match the module structure.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if the required module files exist in the dist directory
fd -e js -p "dist/@bsull/augurs/(dtw|clustering).js$"
# Verify the exported symbols
for file in $(fd -e js -p "dist/@bsull/augurs/(dtw|clustering).js$"); do
echo "Checking exports in $file"
ast-grep --pattern 'export { $_ }' "$file"
done
Length of output: 821
Script:
#!/bin/bash
# Check for default exports
echo "Checking default exports:"
ast-grep --pattern 'export default $_'
# Check for named exports with different patterns
echo -e "\nChecking class exports:"
ast-grep --pattern 'export class $_ {'
echo -e "\nChecking const/let/var exports:"
ast-grep --pattern 'export (const|let|var) $_ ='
# Check the actual import statement in the worker
echo -e "\nChecking the worker's import statement:"
cat demo/clustering.worker.js
Length of output: 272998
demo/changepoint.js (3)
1-5
: Imports are correctly defined
The required modules and plugins are properly imported.
897-900
: Web Worker is correctly initialized
The Web Worker is properly instantiated with the correct module type.
901-930
: highlightChangepointsPlugin
function is correctly implemented
The plugin function sets up a drawClear
hook to highlight changepoints on the plot. The logic for alternating colors and drawing highlighted regions is sound.
ChangepointDetector, | ||
} from "./dist/@bsull/augurs/changepoint.js"; | ||
|
||
await initChangepoint(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for initialization
The initialization lacks error handling and might not work in environments that don't support top-level await. Consider wrapping it in an async IIFE with proper error handling:
-await initChangepoint();
+const initialize = async () => {
+ try {
+ await initChangepoint();
+ self.postMessage("ready");
+ } catch (error) {
+ self.postMessage({ error: "Failed to initialize changepoint detector" });
+ }
+};
+
+initialize();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
await initChangepoint(); | |
const initialize = async () => { | |
try { | |
await initChangepoint(); | |
self.postMessage("ready"); | |
} catch (error) { | |
self.postMessage({ error: "Failed to initialize changepoint detector" }); | |
} | |
}; | |
initialize(); |
const cps = cpd.detectChangepoints(y); | ||
self.postMessage(cps); | ||
}; | ||
self.postMessage("ready"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove premature ready notification
The ready message is sent before initialization completes, which could lead to race conditions. This line should be removed as the ready notification has been moved to the initialization function in the previous suggestion.
Additionally, consider standardizing message formats for consistency:
-self.postMessage("ready");
+// Remove this line as ready notification is handled in initialization
+
+// Consider using consistent message format:
+// self.postMessage({ type: 'ready' }); // for ready notification
+// self.postMessage({ type: 'result', data: cps }); // for results
+// self.postMessage({ type: 'error', message: error.message }); // for errors
Committable suggestion skipped: line range outside the PR's diff.
self.onmessage = (e) => { | ||
const { y } = e.data; | ||
const cpd = new ChangepointDetector("normal-gamma"); | ||
const cps = cpd.detectChangepoints(y); | ||
self.postMessage(cps); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and input validation
The message handling lacks error handling and input validation. This could lead to silent failures or crashes. Consider adding proper validation and error handling:
self.onmessage = (e) => {
+ try {
+ if (!e.data || !Array.isArray(e.data.y)) {
+ throw new Error('Invalid input: expected array of numbers');
+ }
+ if (!e.data.y.every(n => typeof n === 'number')) {
+ throw new Error('Invalid input: array must contain only numbers');
+ }
const { y } = e.data;
const cpd = new ChangepointDetector("normal-gamma");
const cps = cpd.detectChangepoints(y);
self.postMessage(cps);
+ } catch (error) {
+ self.postMessage({ error: error.message });
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
self.onmessage = (e) => { | |
const { y } = e.data; | |
const cpd = new ChangepointDetector("normal-gamma"); | |
const cps = cpd.detectChangepoints(y); | |
self.postMessage(cps); | |
}; | |
self.onmessage = (e) => { | |
try { | |
if (!e.data || !Array.isArray(e.data.y)) { | |
throw new Error('Invalid input: expected array of numbers'); | |
} | |
if (!e.data.y.every(n => typeof n === 'number')) { | |
throw new Error('Invalid input: array must contain only numbers'); | |
} | |
const { y } = e.data; | |
const cpd = new ChangepointDetector("normal-gamma"); | |
const cps = cpd.detectChangepoints(y); | |
self.postMessage(cps); | |
} catch (error) { | |
self.postMessage({ error: error.message }); | |
} | |
}; |
import initChangepoint, { | ||
ChangepointDetector, | ||
} from "./dist/@bsull/augurs/changepoint.js"; | ||
|
||
await initChangepoint(); | ||
|
||
self.onmessage = (e) => { | ||
const { y } = e.data; | ||
const cpd = new ChangepointDetector("normal-gamma"); | ||
const cps = cpd.detectChangepoints(y); | ||
self.postMessage(cps); | ||
}; | ||
self.postMessage("ready"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the analysis of the codebase, I can now provide the final response:
Worker lifecycle needs proper cleanup
The worker is instantiated but never terminated when no longer needed. Add cleanup code in the main thread:
- In
demo/changepoint.js
, add a cleanup function to terminate the worker:function cleanup() { worker.terminate(); }
- Call this cleanup when the visualization is destroyed or when switching views
🔗 Analysis chain
Verify worker lifecycle management
Ensure that the main thread properly manages this worker's lifecycle, including termination when no longer needed.
Let me gather more context about the worker usage pattern.
Let me check if there are any cleanup patterns in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for worker cleanup in the main thread
rg -A 5 "new Worker.*changepoint.worker"
rg "worker.*terminate"
# Check for any memory management patterns
ast-grep --pattern 'const cpd = new ChangepointDetector($$$)'
Length of output: 715
Script:
#!/bin/bash
# Check the complete worker usage in the main thread
rg -A 10 "const worker = new Worker" demo/changepoint.js
# Check if there are any event listeners for cleanup
rg "worker\.(on|add)" demo/changepoint.js
# Check the complete file content to understand the lifecycle
cat demo/changepoint.js
Length of output: 68915
Script:
#!/bin/bash
# Check for any cleanup or disposal patterns
rg -A 5 "window\.(un|remove).*" demo/
rg -A 5 "dispose|cleanup|destroy" demo/
# Check if the worker is used in other files
fd -e js -x rg "worker\.(terminate|close)" {}
Length of output: 22652
const labels = clusterer.fit(distanceMatrix); | ||
self.postMessage(labels); | ||
}; | ||
self.postMessage("ready"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move ready message after initialization
The ready message should be sent after successful initialization to prevent race conditions.
Move the ready message inside the initialization block:
-await Promise.all([initDtw(), initClustering()]);
-self.postMessage("ready");
+try {
+ await Promise.all([initDtw(), initClustering()]);
+ self.postMessage("ready");
+} catch (error) {
+ self.postMessage({ error: `Failed to initialize modules: ${error.message}` });
+ close();
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
self.postMessage("ready"); | |
try { | |
await Promise.all([initDtw(), initClustering()]); | |
self.postMessage("ready"); | |
} catch (error) { | |
self.postMessage({ error: `Failed to initialize modules: ${error.message}` }); | |
close(); | |
} |
self.onmessage = (e) => { | ||
const { dtwOpts, dbscanOpts, data } = e.data; | ||
const dtw = Dtw.euclidean(dtwOpts); | ||
const distanceMatrix = dtw.distanceMatrix(data); | ||
const clusterer = new DbscanClusterer(dbscanOpts); | ||
const labels = clusterer.fit(distanceMatrix); | ||
self.postMessage(labels); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add input validation and error handling
The message handler lacks input validation and error handling, which could lead to runtime errors or unclear failure modes.
Consider implementing these safety measures:
self.onmessage = (e) => {
+ try {
const { dtwOpts, dbscanOpts, data } = e.data;
+ if (!Array.isArray(data) || data.length === 0) {
+ throw new Error('Invalid or empty data array');
+ }
+ if (!dtwOpts || !dbscanOpts) {
+ throw new Error('Missing required options');
+ }
const dtw = Dtw.euclidean(dtwOpts);
const distanceMatrix = dtw.distanceMatrix(data);
+ // Send progress update
+ self.postMessage({ progress: 'Distance matrix computed' });
const clusterer = new DbscanClusterer(dbscanOpts);
const labels = clusterer.fit(distanceMatrix);
- self.postMessage(labels);
+ self.postMessage({ result: labels });
+ } catch (error) {
+ self.postMessage({ error: error.message });
+ }
};
Additionally, consider implementing memory-efficient processing for large datasets:
- Process the distance matrix in chunks
- Provide progress updates during computation
Committable suggestion skipped: line range outside the PR's diff.
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | ||
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | ||
this.dataPromise.then((data) => { | ||
this.data = data.data; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for data fetching in constructor
When fetching the clustering data in the constructor, there is no error handling for the fetch
call. If the network request fails, this.data
will remain undefined, potentially causing errors later.
Modify the constructor to handle fetch errors gracefully:
this.dataPromise = fetch("./outlier.data.json")
.then((res) => res.json())
+ .then((data) => {
+ this.data = data.data;
+ })
+ .catch((error) => {
+ console.error("Failed to load data:", error);
+ // Handle the error appropriately, e.g., set a fallback or notify the user
+ });
Committable suggestion skipped: line range outside the PR's diff.
demo/clustering.js
Outdated
cluster = async (dtwOpts, dbscanOpts) => { | ||
return new Promise((resolve, reject) => { | ||
const start = performance.now(); | ||
this.worker.postMessage({ | ||
dtwOpts, | ||
dbscanOpts, | ||
data: this.data.slice(1).map(arr => new Float64Array(arr)), | ||
}); | ||
this.worker.onmessage = (e) => { | ||
const elapsed = (performance.now() - start); | ||
resolve({ clusterLabels: e.data, elapsed }); | ||
}; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid overwriting the worker's onmessage
handler in cluster
method
Currently, the cluster
method assigns a new onmessage
handler to this.worker.onmessage
each time it's called. If cluster
is invoked multiple times before previous invocations have completed, this could lead to race conditions and incorrect message handling, as the handler is overwritten.
To handle multiple concurrent calls without interference, consider using addEventListener
to attach message handlers and ensure they are properly cleaned up after use. Here's how you can modify the cluster
method:
cluster = async (dtwOpts, dbscanOpts) => {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
const start = performance.now();
this.worker.postMessage({
dtwOpts,
dbscanOpts,
data: this.data.slice(1).map(arr => new Float64Array(arr)),
});
- this.worker.onmessage = (e) => {
+ const handleMessage = (e) => {
const elapsed = (performance.now() - start);
resolve({ clusterLabels: e.data, elapsed });
+ this.worker.removeEventListener('message', handleMessage);
};
- };
- });
+ this.worker.addEventListener('message', handleMessage);
+ })
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
cluster = async (dtwOpts, dbscanOpts) => { | |
return new Promise((resolve, reject) => { | |
const start = performance.now(); | |
this.worker.postMessage({ | |
dtwOpts, | |
dbscanOpts, | |
data: this.data.slice(1).map(arr => new Float64Array(arr)), | |
}); | |
this.worker.onmessage = (e) => { | |
const elapsed = (performance.now() - start); | |
resolve({ clusterLabels: e.data, elapsed }); | |
}; | |
}); | |
cluster = async (dtwOpts, dbscanOpts) => { | |
return new Promise((resolve) => { | |
const start = performance.now(); | |
this.worker.postMessage({ | |
dtwOpts, | |
dbscanOpts, | |
data: this.data.slice(1).map(arr => new Float64Array(arr)), | |
}); | |
const handleMessage = (e) => { | |
const elapsed = (performance.now() - start); | |
resolve({ clusterLabels: e.data, elapsed }); | |
this.worker.removeEventListener('message', handleMessage); | |
}; | |
this.worker.addEventListener('message', handleMessage); | |
}) | |
} |
class ClusteringWorker { | ||
constructor() { | ||
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | ||
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | ||
this.dataPromise.then((data) => { | ||
this.data = data.data; | ||
}); | ||
} | ||
|
||
static create = () => { | ||
return new Promise((resolve, reject) => { | ||
const worker = new ClusteringWorker(); | ||
worker.worker.onmessage = (e) => { | ||
if (e.data === "ready") { | ||
worker.dataPromise.then(() => resolve(worker)); | ||
} else { | ||
reject(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Potential issue with onmessage
handler in ClusteringWorker.create
method
In the create
method, the onmessage
handler is set directly on worker.worker.onmessage
. This could conflict with other onmessage
handlers and may not handle errors effectively.
Consider modifying the create
method to use addEventListener
for the worker messages and to ensure proper cleanup:
static create = () => {
return new Promise((resolve, reject) => {
const worker = new ClusteringWorker();
- worker.worker.onmessage = (e) => {
+ const handleMessage = (e) => {
if (e.data === "ready") {
worker.dataPromise.then(() => resolve(worker));
+ worker.worker.removeEventListener('message', handleMessage);
} else {
reject();
+ worker.worker.removeEventListener('message', handleMessage);
}
}
+ worker.worker.addEventListener('message', handleMessage);
})
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class ClusteringWorker { | |
constructor() { | |
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | |
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | |
this.dataPromise.then((data) => { | |
this.data = data.data; | |
}); | |
} | |
static create = () => { | |
return new Promise((resolve, reject) => { | |
const worker = new ClusteringWorker(); | |
worker.worker.onmessage = (e) => { | |
if (e.data === "ready") { | |
worker.dataPromise.then(() => resolve(worker)); | |
} else { | |
reject(); | |
} | |
} | |
class ClusteringWorker { | |
constructor() { | |
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | |
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | |
this.dataPromise.then((data) => { | |
this.data = data.data; | |
}); | |
} | |
static create = () => { | |
return new Promise((resolve, reject) => { | |
const worker = new ClusteringWorker(); | |
const handleMessage = (e) => { | |
if (e.data === "ready") { | |
worker.dataPromise.then(() => resolve(worker)); | |
worker.worker.removeEventListener('message', handleMessage); | |
} else { | |
reject(); | |
worker.worker.removeEventListener('message', handleMessage); | |
} | |
} | |
worker.worker.addEventListener('message', handleMessage); |
for (const cp of cps.indices.slice(1)) { | ||
const right = ds[cp]; | ||
const rightPos = u.valToPos(right, "x", true); | ||
highlightAt(left, rightPos); | ||
left = rightPos; | ||
color = color === "lightgrey" ? "white" : "lightgrey"; | ||
u.ctx.fillStyle = color; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure indices are within bounds when accessing ds
When looping over cps.indices.slice(1)
, accessing ds[cp]
assumes that cp
is within the bounds of ds
. To prevent potential runtime errors, ensure that cp
is within the bounds of ds
.
Apply this diff to add a bounds check:
for (const cp of cps.indices.slice(1)) {
+ if (cp >= ds.length) continue;
const right = ds[cp];
const rightPos = u.valToPos(right, "x", true);
highlightAt(left, rightPos);
left = rightPos;
color = color === "lightgrey" ? "white" : "lightgrey";
u.ctx.fillStyle = color;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
for (const cp of cps.indices.slice(1)) { | |
const right = ds[cp]; | |
const rightPos = u.valToPos(right, "x", true); | |
highlightAt(left, rightPos); | |
left = rightPos; | |
color = color === "lightgrey" ? "white" : "lightgrey"; | |
u.ctx.fillStyle = color; | |
} | |
for (const cp of cps.indices.slice(1)) { | |
if (cp >= ds.length) continue; | |
const right = ds[cp]; | |
const rightPos = u.valToPos(right, "x", true); | |
highlightAt(left, rightPos); | |
left = rightPos; | |
color = color === "lightgrey" ? "white" : "lightgrey"; | |
u.ctx.fillStyle = color; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (2)
demo/clustering.js (2)
1-4
: Consider improving import robustness and documentationThe imports from local directories could be made more maintainable:
- Consider using package imports for uPlot instead of relying on local dist files
- Add JSDoc comments to document the imported helper functions and plugins
70-83
: Improve runClustering implementationThe runClustering function could be improved:
- Extract cluster colors to a configuration object
- Add error handling for clustering operations
- Consider using a color scale for more than 3 clusters
+const CLUSTER_COLORS = { + NOISE: "black", + DEFAULT_COLORS: ["blue", "red", "yellow", "green", "purple"] +}; + async function runClustering(dtwOpts, dbscanOpts) { + try { const { clusterLabels, elapsed } = await worker.cluster(dtwOpts, dbscanOpts); clusterLabels.forEach((cluster, i) => { const seriesIdx = i + 1; u.delSeries(seriesIdx); u.addSeries({ label: `${i} (cluster ${cluster})`, - stroke: cluster === -1 ? "black" : cluster === 0 ? "blue" : cluster === 1 ? "red" : "yellow", + stroke: cluster === -1 ? CLUSTER_COLORS.NOISE : CLUSTER_COLORS.DEFAULT_COLORS[cluster % CLUSTER_COLORS.DEFAULT_COLORS.length], width: 1, }, seriesIdx); }); u.redraw() document.getElementById("clustering-title").innerText = `Clustering with DBSCAN - done in ${elapsed}ms`; + } catch (error) { + console.error("Clustering failed:", error); + document.getElementById("clustering-title").innerText = "Clustering failed"; + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
demo/clustering.js
(1 hunks)
🔇 Additional comments (1)
demo/clustering.js (1)
50-63
: Previous review comment is still valid: Fix onmessage handler in cluster method
The issue with overwriting the worker's onmessage handler in the cluster method, as mentioned in the previous review, is still present. This could lead to race conditions with multiple concurrent calls.
class ClusteringWorker { | ||
constructor() { | ||
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | ||
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | ||
this.dataPromise.then((data) => { | ||
this.data = data.data; | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and resource cleanup
The ClusteringWorker constructor needs improvements:
- Add error handling for worker creation
- Add a cleanup method to terminate the worker
class ClusteringWorker {
constructor() {
- this.worker = new Worker("./clustering.worker.js", { type: "module" });
+ try {
+ this.worker = new Worker("./clustering.worker.js", { type: "module" });
+ } catch (error) {
+ console.error("Failed to create worker:", error);
+ throw error;
+ }
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json());
this.dataPromise.then((data) => {
this.data = data.data;
});
}
+
+ destroy() {
+ if (this.worker) {
+ this.worker.terminate();
+ }
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class ClusteringWorker { | |
constructor() { | |
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | |
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | |
this.dataPromise.then((data) => { | |
this.data = data.data; | |
}); | |
} | |
class ClusteringWorker { | |
constructor() { | |
try { | |
this.worker = new Worker("./clustering.worker.js", { type: "module" }); | |
} catch (error) { | |
console.error("Failed to create worker:", error); | |
throw error; | |
} | |
this.dataPromise = fetch("./outlier.data.json").then((res) => res.json()); | |
this.dataPromise.then((data) => { | |
this.data = data.data; | |
}); | |
} | |
destroy() { | |
if (this.worker) { | |
this.worker.terminate(); | |
} | |
} |
function setUpPlot(data) { | ||
const opts = { | ||
...getSize(), | ||
series: [ | ||
{}, | ||
...data.slice(1).map((_, i) => { | ||
return { | ||
label: `${i + 1}`, | ||
stroke: "black", | ||
width: 1, | ||
}; | ||
}), | ||
], | ||
plugins: [legendAsTooltipPlugin()], | ||
}; | ||
const u = uPlot(opts, data, document.getElementById("clustering-plot")); | ||
window.addEventListener("resize", () => { | ||
u.setSize(getSize()); | ||
}); | ||
return u; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and cleanup
The plot setup function could be improved:
- Add error handling for DOM element lookup
- Clean up the resize event listener when the plot is destroyed
- Add type checking for input data
function setUpPlot(data) {
+ const plotElement = document.getElementById("clustering-plot");
+ if (!plotElement) {
+ throw new Error("Clustering plot element not found");
+ }
+ if (!Array.isArray(data)) {
+ throw new TypeError("Data must be an array");
+ }
const opts = {
...getSize(),
series: [
{},
...data.slice(1).map((_, i) => ({
label: `${i + 1}`,
stroke: "black",
width: 1,
})),
],
plugins: [legendAsTooltipPlugin()],
};
- const u = uPlot(opts, data, document.getElementById("clustering-plot"));
+ const u = uPlot(opts, data, plotElement);
+ const resizeHandler = () => u.setSize(getSize());
- window.addEventListener("resize", () => {
- u.setSize(getSize());
- });
+ window.addEventListener("resize", resizeHandler);
+ const originalDestroy = u.destroy;
+ u.destroy = () => {
+ window.removeEventListener("resize", resizeHandler);
+ originalDestroy.call(u);
+ };
return u;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function setUpPlot(data) { | |
const opts = { | |
...getSize(), | |
series: [ | |
{}, | |
...data.slice(1).map((_, i) => { | |
return { | |
label: `${i + 1}`, | |
stroke: "black", | |
width: 1, | |
}; | |
}), | |
], | |
plugins: [legendAsTooltipPlugin()], | |
}; | |
const u = uPlot(opts, data, document.getElementById("clustering-plot")); | |
window.addEventListener("resize", () => { | |
u.setSize(getSize()); | |
}); | |
return u; | |
} | |
function setUpPlot(data) { | |
const plotElement = document.getElementById("clustering-plot"); | |
if (!plotElement) { | |
throw new Error("Clustering plot element not found"); | |
} | |
if (!Array.isArray(data)) { | |
throw new TypeError("Data must be an array"); | |
} | |
const opts = { | |
...getSize(), | |
series: [ | |
{}, | |
...data.slice(1).map((_, i) => ({ | |
label: `${i + 1}`, | |
stroke: "black", | |
width: 1, | |
})), | |
], | |
plugins: [legendAsTooltipPlugin()], | |
}; | |
const u = uPlot(opts, data, plotElement); | |
const resizeHandler = () => u.setSize(getSize()); | |
window.addEventListener("resize", resizeHandler); | |
const originalDestroy = u.destroy; | |
u.destroy = () => { | |
window.removeEventListener("resize", resizeHandler); | |
originalDestroy.call(u); | |
}; | |
return u; | |
} |
document.getElementById("clustering-dtw-window").addEventListener("change", function() { | ||
dtwOpts.window = parseFloat(this.value); | ||
runClustering(dtwOpts, dbscanOpts); | ||
}); | ||
document.getElementById("clustering-dbscan-epsilon").addEventListener("change", function() { | ||
dbscanOpts.epsilon = parseFloat(this.value); | ||
runClustering(dtwOpts, dbscanOpts); | ||
}); | ||
document.getElementById("clustering-dbscan-min-cluster-size").addEventListener("change", function() { | ||
dbscanOpts.minClusterSize = parseInt(this.value); | ||
runClustering(dtwOpts, dbscanOpts); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add debouncing to UI updates
The event listeners could trigger frequent recalculations. Consider debouncing these updates to improve performance.
+function debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+}
+
+const debouncedRunClustering = debounce(runClustering, 300);
+
document.getElementById("clustering-dtw-window").addEventListener("change", function() {
dtwOpts.window = parseFloat(this.value);
- runClustering(dtwOpts, dbscanOpts);
+ debouncedRunClustering(dtwOpts, dbscanOpts);
});
Committable suggestion skipped: line range outside the PR's diff.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation