Skip to content

fix: update lasso.js for D3 v6+ compatibility#1276

Open
tonypzy wants to merge 3 commits into
fossasia:devfrom
tonypzy:fix/997-lasso-d3v6
Open

fix: update lasso.js for D3 v6+ compatibility#1276
tonypzy wants to merge 3 commits into
fossasia:devfrom
tonypzy:fix/997-lasso-d3v6

Conversation

@tonypzy
Copy link
Copy Markdown
Contributor

@tonypzy tonypzy commented May 6, 2026

Description

This PR follows up on #997 to resolve remaining CI failures and ensure compatibility across different D3 versions (v4 through v6+).

Key Changes:

  • D3 Version Compatibility: Added a fallback mechanism for event coordinates. It now uses d3.pointer where available (D3 v6+) and falls back to d3.mouse for legacy environments (v4/v5).
  • Null Pointer Protection: Added safety checks in handleDrag for lassoPolygon, lassoPath, and closePath. This prevents TypeError crashes if the lasso is reset during an active interaction.
  • ESLint & Style Fixes: Refactored distance logic using Math.hypot() for better clarity.
    • Fixed max-len violations by wrapping long statements and comments to meet the 80-character limit.
    • Cleaned up syntax for consistency with ES6 standards.

How Has This Been Tested?

Verified via code inspection.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code refactor or cleanup (changes to existing code for improved readability or performance)

Checklist:

  • I adapted the version number under py/visdom/VERSION according to Semantic Versioning
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

Summary by Sourcery

Ensure the lasso selection helper works reliably across D3 versions and is more robust during drag interactions.

Bug Fixes:

  • Fix lasso drag event handling to support both legacy d3.mouse and modern d3.pointer APIs for D3 v4–v6+ compatibility.
  • Prevent runtime errors during lasso interactions by guarding against null lasso state when dragging or ending a drag.
  • Avoid errors when ending a drag by safely handling removal of the close path and lasso path elements.

Enhancements:

  • Simplify geometric distance calculation using Math.hypot for clearer, more modern JavaScript code.
  • Tidy lasso implementation style by using arrow functions and reducing unnecessary wrapping/boilerplate in path generation.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 6, 2026

Reviewer's Guide

Updates js/lasso.js to be compatible with D3 v6+ while maintaining support for v4/v5, adds null-safety around lasso interaction state, and applies minor ES6/style cleanups including a clearer distance calculation.

Sequence diagram for lasso drag lifecycle with D3 v6 pointer fallback and null safety

sequenceDiagram
  actor User
  participant Root as RootSVG
  participant Lasso as lasso
  participant Drag as d3drag
  participant D3 as d3_selection
  participant LassoPath as lassoPath
  participant ClosePath as closePath
  participant Dispatch as dispatch

  User->>Root: mousedown and drag on area
  Root->>Drag: drag behavior triggered
  Drag->>Lasso: handleDragStart(event)
  alt d3.mouse available
    Lasso->>D3: mouse(this)
    D3-->>Lasso: startPoint
  else d3.mouse not available
    Lasso->>D3: pointer(event, this)
    D3-->>Lasso: startPoint
  end
  Lasso->>Lasso: lassoPolygon = [startPoint]
  Lasso->>Root: append path as lassoPath
  Lasso->>Root: append line as closePath
  Lasso->>Dispatch: start(lassoPolygon)

  loop while user drags
    Drag->>Lasso: handleDrag(event)
    Lasso->>Lasso: check lassoPolygon && lassoPath && closePath
    alt lasso state missing
      Lasso-->>Drag: return (no-op)
    else lasso state valid
      alt d3.mouse available
        Lasso->>D3: mouse(this)
        D3-->>Lasso: point
      else d3.mouse not available
        Lasso->>D3: pointer(event, this)
        D3-->>Lasso: point
      end
      Lasso->>Lasso: push point into lassoPolygon
      Lasso->>LassoPath: update d attribute with polygonToPath(lassoPolygon)
      Lasso->>Lasso: compute distance(startPoint, lastPoint)
      alt distance < closeDistance
        Lasso->>ClosePath: update x1,y1,opacity=1
      else
        Lasso->>ClosePath: opacity=0
      end
    end
  end

  User->>Root: mouseup
  Drag->>Lasso: handleDragEnd()
  alt closePath exists
    Lasso->>ClosePath: remove()
    Lasso->>Lasso: closePath = null
  else no closePath
    Lasso-->>Drag: continue
  end
  Lasso->>Lasso: check lassoPolygon
  alt lassoPolygon exists and distance(startPoint, lastPoint) < closeDistance
    Lasso->>LassoPath: close path with Z
    Lasso->>Dispatch: end(lassoPolygon)
  else cancel lasso
    alt lassoPath exists
      Lasso->>LassoPath: remove()
      Lasso->>Lasso: lassoPath = null
    end
  end
  Lasso->>Lasso: lassoPolygon = null

  User->>Lasso: calls reset()
  Lasso->>LassoPath: remove if exists
  Lasso->>ClosePath: remove if exists
  Lasso->>Lasso: lassoPolygon = null, lassoPath = null, closePath = null
Loading

Class diagram for lasso module with updated handlers and utilities

classDiagram
  class PolygonUtils {
    +polygonToPath(polygon)
    +distance(pt1, pt2)
  }

  class Lasso {
    +hoverSelect : Boolean
    +closeDistance : Number
    +lassoPolygon : Array
    +lassoPath : Object
    +closePath : Object
    +g : Object
    +area : Object
    +bbox : Object
    +dispatch : Object
    +lasso(root)
    +handleDragStart(event)
    +handleDrag(event)
    +handleDragEnd()
    +reset()
  }

  class D3Selection {
    +mouse(target)
    +pointer(event, target)
  }

  class D3Drag {
    +drag()
  }

  PolygonUtils <.. Lasso : uses
  D3Selection <.. Lasso : uses mouse_or_pointer
  D3Drag <.. Lasso : used_for_drag_behavior
Loading

File-Level Changes

Change Details Files
Add D3 v6+ pointer support while keeping backward compatibility with d3.mouse for lasso interactions.
  • Update drag handlers to accept the D3 drag event argument
  • Derive pointer coordinates using d3.pointer(event, this) when available, falling back to d3.mouse(this) for older D3 versions
js/lasso.js
Harden lasso drag lifecycle against null state when reset is called mid-interaction.
  • Add early-return guards in handleDrag when lassoPolygon, lassoPath, or closePath are null
  • Wrap closePath removal in null checks in handleDragEnd
  • Ensure lassoPolygon is nulled at the end of drag and only used when defined
js/lasso.js
Refine utility functions and style for clarity and ES6 consistency.
  • Refactor polygonToPath to use arrow functions and a single-line return
  • Replace manual distance computation using Math.sqrt/Math.pow with Math.hypot
  • Apply minor formatting changes to satisfy max-len and general style guidelines
js/lasso.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The D3 coordinate fallback logic (d3.mouse ? d3.mouse(...) : d3.pointer(...)) is duplicated and a bit brittle; consider extracting a small helper like getPointer(event, node) and preferring d3.pointer when available (with a typeof d3.pointer === 'function' check and d3.mouse as the legacy fallback) to make the version-compat behavior clearer and easier to maintain.
  • In handleDragStart, the new code assumes lassoPolygon and g are always valid; if reset() can be called before a new drag starts, consider mirroring the null-check pattern you added in handleDrag/handleDragEnd so a partially reset lasso group cannot cause errors when a drag is initiated.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The D3 coordinate fallback logic (`d3.mouse ? d3.mouse(...) : d3.pointer(...)`) is duplicated and a bit brittle; consider extracting a small helper like `getPointer(event, node)` and preferring `d3.pointer` when available (with a `typeof d3.pointer === 'function'` check and `d3.mouse` as the legacy fallback) to make the version-compat behavior clearer and easier to maintain.
- In `handleDragStart`, the new code assumes `lassoPolygon` and `g` are always valid; if `reset()` can be called before a new drag starts, consider mirroring the null-check pattern you added in `handleDrag`/`handleDragEnd` so a partially reset lasso group cannot cause errors when a drag is initiated.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@tonypzy
Copy link
Copy Markdown
Contributor Author

tonypzy commented May 8, 2026

@Manik-Khajuria-5 @Jayantparashar10 @vedansh-5 Please review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates the lasso interaction helper to be compatible across D3 v4–v6+ and more robust during active drag interactions to address remaining CI failures from #997.

Changes:

  • Adds coordinate extraction fallback between legacy d3.mouse and modern d3.pointer.
  • Guards drag handling against mid-interaction resets to prevent TypeError crashes.
  • Refactors geometry and formatting (Math.hypot, shorter path generation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread js/lasso.js
Comment on lines 93 to 118
function handleDragEnd() {
// remove the close path
closePath.remove();
closePath = null;
if (closePath) {
closePath.remove();
closePath = null;
}

// successfully closed
if (
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
closeDistance
lassoPolygon &&
distance(lassoPolygon[0],
lassoPolygon[lassoPolygon.length - 1]) < closeDistance
) {
lassoPath.attr('d', polygonToPath(lassoPolygon) + 'Z');
dispatch.call('end', lasso, lassoPolygon);

// otherwise cancel
} else {
lassoPath.remove();
lassoPath = null;
lassoPolygon = null;
if (lassoPath) {
lassoPath.remove();
lassoPath = null;
}
}

lassoPolygon = null;
}
Comment thread js/lasso.js
Comment on lines +50 to +51
function handleDragStart(event) {
lassoPolygon = [d3.mouse ? d3.mouse(this) : d3.pointer(event, this)];
Comment thread js/lasso.js

function handleDrag() {
var point = d3.mouse(this);
function handleDrag(event) {
Comment thread js/lasso.js
// If reset() was called mid-drag, bail out safely.
if (!lassoPolygon || !lassoPath || !closePath) return;

var point = d3.mouse ? d3.mouse(this) : d3.pointer(event, this);
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.

3 participants