Skip to content
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

Merged
merged 5 commits into from
Nov 16, 2024
Merged

feat: add demo page #170

merged 5 commits into from
Nov 16, 2024

Conversation

sd2k
Copy link
Collaborator

@sd2k sd2k commented Nov 16, 2024

Summary by CodeRabbit

  • New Features

    • Introduced functionality for detecting and visualizing changepoints in time series data.
    • Added clustering data visualization capabilities using the DBSCAN algorithm and Dynamic Time Warping (DTW).
  • Bug Fixes

    • Improved error handling for worker messages in the changepoint detection process.
  • Documentation

    • Enhanced documentation to include usage instructions for the new changepoint and clustering features.

Copy link

cloudflare-workers-and-pages bot commented Nov 16, 2024

Deploying augurs with  Cloudflare Pages  Cloudflare Pages

Latest commit: a97417c
Status: ✅  Deploy successful!
Preview URL: https://49ed9a00.augurs.pages.dev

View logs

Copy link
Contributor

coderabbitai bot commented Nov 16, 2024

Walkthrough

The changes introduce new functionality for detecting changepoints and clustering in time series data visualization. A new changepoint.js file implements a Bayesian Normal Gamma model for changepoint detection, utilizing a web worker for asynchronous processing. Additionally, a clustering.js file is created to visualize clustering data using the uPlot library, incorporating a ClusteringWorker class for handling clustering operations. Corresponding worker scripts are also added to manage computations for both changepoint detection and clustering, ensuring efficient data processing and visualization.

Changes

File Change Summary
demo/changepoint.js Added highlightChangepointsPlugin and run functions; updated default export to run.
demo/changepoint.worker.js Added initChangepoint method and ChangepointDetector class; set up message handling for changepoints.
demo/clustering.js Added main function, ClusteringWorker class, static create method, and cluster method.
demo/clustering.worker.js Implemented message handling for DTW and DBSCAN clustering; computed distance matrix and sent results back.

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
Loading

🐇 "In the data's dance, changepoints play,
With clustering friends, they join the fray.
A plot so bright, with colors that gleam,
Together they weave a statistical dream!" 🌈


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 file

Importing 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 clusters

The 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 in runClustering

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 listeners

The 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 source

Including large datasets like df.ds and df.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 Worker

Currently, 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 using Transferable objects

Sending the large df object via postMessage can be inefficient due to serialization overhead. Consider using Transferable objects such as Float64Array for df.ds and df.y to improve data transfer efficiency.

Apply changes to convert df.ds and df.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 buffers

Ensure that you adjust the worker code to handle typed arrays appropriately.


964-966: Prevent potential memory leaks by cleaning up event listeners

When 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

📥 Commits

Reviewing files that changed from the base of the PR and between 937c102 and 7d0d801.

⛔ 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:

  1. Both imported modules exist at the correct paths:
    • demo/dist/@bsull/augurs/dtw.js
    • demo/dist/@bsull/augurs/clustering.js
  2. The modules export the required symbols:
    • dtw.js exports initSync as default and has the Dtw class
    • clustering.js exports initSync as default and has the DbscanClusterer 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

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.

Comment on lines +7 to +12
self.onmessage = (e) => {
const { y } = e.data;
const cpd = new ChangepointDetector("normal-gamma");
const cps = cpd.detectChangepoints(y);
self.postMessage(cps);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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 });
}
};

Comment on lines +1 to +13
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");
Copy link
Contributor

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
self.postMessage("ready");
try {
await Promise.all([initDtw(), initClustering()]);
self.postMessage("ready");
} catch (error) {
self.postMessage({ error: `Failed to initialize modules: ${error.message}` });
close();
}

Comment on lines +8 to +15
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);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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:

  1. Process the distance matrix in chunks
  2. Provide progress updates during computation

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +30 to +34
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;
});
Copy link
Contributor

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.

Comment on lines 50 to 62
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 });
};
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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);
})
}

Comment on lines +28 to +46
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();
}
}
Copy link
Contributor

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.

Suggested change
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);

Comment on lines +917 to +924
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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;
}

@sd2k sd2k enabled auto-merge (squash) November 16, 2024 00:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 documentation

The 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 implementation

The runClustering function could be improved:

  1. Extract cluster colors to a configuration object
  2. Add error handling for clustering operations
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7d0d801 and a97417c.

📒 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.

Comment on lines +28 to +35
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;
});
}
Copy link
Contributor

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:

  1. Add error handling for worker creation
  2. 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.

Suggested change
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();
}
}

Comment on lines +6 to +26
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;
}
Copy link
Contributor

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:

  1. Add error handling for DOM element lookup
  2. Clean up the resize event listener when the plot is destroyed
  3. 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.

Suggested change
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;
}

Comment on lines +88 to +99
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);
});
Copy link
Contributor

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.

@sd2k sd2k merged commit 125af81 into main Nov 16, 2024
24 checks passed
@sd2k sd2k deleted the add-demo branch November 16, 2024 00:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant