fix: Snap TimeSeries seek events to nearest data point to eliminate floating-point precision errors
Reason for Change
Problem: When clicking on a TimeSeries visualization, seek events emit inaccurate time values that don't match the clicked data point due to floating-point precision errors in pixel-to-time conversion calculations.
For example:
- Clicking on data point
1770.677344emits seek event1770.6719467913167 - Clicking on data point
1733.719emits seek event1733.72516
These incorrect values don't exist in the original dataset, causing:
- Synchronization issues with audio/video players
- Annotation accuracy problems in time-based labeling tasks
- Confusion between displayed values and actual seek positions
Root Cause: The pixel-to-time conversion uses continuous interpolation with multiple floating-point operations that accumulate rounding errors:
const timeClicked = brushTimeStartNative + (clickX / plottingAreaWidth) * brushDurationNative;
Solution:
Implemented data point snapping using binary search (O(log n)) to find and return the nearest actual data point from the dataset instead of using interpolated values. Created a shared snapToNearestDataPoint() helper function to eliminate code duplication across three locations.
Videos
- Bug demonstration: https://www.loom.com/share/5f1f429a21f0438ca5f11e7146570bfe
- Fix demonstration: https://www.loom.com/share/b1b2b9ea3230461eb6e58848c40edfe2
Rollout Strategy
No special rollout needed - This is a bug fix with backward compatibility:
- No feature flags required
- No environment variables needed
- No API changes
- Existing TimeSeries functionality is preserved and improved
- Change applies automatically to all TimeSeries visualizations
Testing
Manual Testing Performed:
-
Basic Click Test
- Clicked on various data points in TimeSeries visualization
- Verified seek event values now match exact data point values (see fix demo video)
- Tested with both numeric and date-formatted time columns
-
Edge Cases
- Clicks at the very start/end of timeseries
- Clicks between two closely-spaced data points
- Sparse data (few points)
- Dense data (many points)
-
Synchronization Test
- TimeSeries synced with Audio using
syncattribute - Verified audio seeks to exact time when clicking TimeSeries
- No drift or offset between components
- TimeSeries synced with Audio using
-
MultiChannel Test
- Tested with MultiChannel TimeSeries visualization
- Verified
plotClickHandlerworks correctly with snapping
Code Quality:
- No linting errors introduced
- Follows existing code patterns and conventions
- Well-documented with JSDoc comments
Risks
Risk Level: LOW
Performance:
- Binary search is O(log n) - efficient even for large datasets
- No performance degradation compared to original implementation
- Actually improved by eliminating ~135 lines of duplicated code
Backward Compatibility:
- No breaking changes
- Snapping to nearest data point is expected and desired behavior
- Fallback to original value if data is unavailable
- All existing TimeSeries functionality preserved
Security:
- No security implications
- No new dependencies added
- No changes to data handling or storage
Reviewer Notes
Key Points to Review:
-
Helper Function (
web/libs/editor/src/tags/object/TimeSeries/helpers.js)- New
snapToNearestDataPoint()function uses binary search - Handles edge cases (null data, empty arrays)
- Well-documented with JSDoc
- New
-
Three Refactored Locations (
web/libs/editor/src/tags/object/TimeSeries.jsx)-
emitSeekSync()- Line ~1164 -
plotClickHandler()- Line ~1189 -
handleMainAreaClick()- Line ~1650 - All now use the shared helper instead of duplicated logic
-
-
Code Quality Improvements:
- Eliminated ~135 lines of duplicated binary search logic
- Single source of truth for snapping behavior
- Consistent behavior across all interaction points
Testing the Fix:
- Watch the fix demonstration video to see the exact values now match
- Load any TimeSeries task and click on data points
- Observe that seek events emit exact data point values from the dataset
General Notes
Implementation Details:
The snapToNearestDataPoint() helper function:
- Takes a calculated time value and the array of actual data points
- Uses binary search to efficiently find the closest data point
- Checks adjacent points to ensure absolute closest match
- Returns exact data point value from the dataset
Files Changed:
-
web/libs/editor/src/tags/object/TimeSeries/helpers.js- Added shared helper function -
web/libs/editor/src/tags/object/TimeSeries.jsx- Refactored three locations to use helper
Benefits:
- Seek events now emit exact data point values
- Eliminates floating-point precision errors
- Improves synchronization accuracy with video/audio
- Better annotation precision
- Reduced code duplication
- Easier to maintain and test
No Migration Required - This is a transparent bug fix that improves accuracy without requiring any user action or configuration changes.
Related Issues
This fix addresses floating-point precision errors that may have affected:
- TimeSeries synchronization with Audio/Video players
- Annotation accuracy in time-based labeling tasks
- TimeSeries region creation precision
Deploy request for heartex-docs pending review.
Visit the deploys page to approve it
| Name | Link |
|---|---|
| Latest commit | 2bdc2b9a59c477e2e94ba33df4edc5294f2aae9d |
Deploy request for label-studio-docs-new-theme pending review.
Visit the deploys page to approve it
| Name | Link |
|---|---|
| Latest commit | 2bdc2b9a59c477e2e94ba33df4edc5294f2aae9d |
Deploy Preview for label-studio-storybook ready!
| Name | Link |
|---|---|
| Latest commit | 2bdc2b9a59c477e2e94ba33df4edc5294f2aae9d |
| Latest deploy log | https://app.netlify.com/projects/label-studio-storybook/deploys/68f0bde3eeefd600071cc07e |
| Deploy Preview | https://deploy-preview-8602--label-studio-storybook.netlify.app |
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify project configuration.
Deploy Preview for label-studio-playground ready!
| Name | Link |
|---|---|
| Latest commit | 2bdc2b9a59c477e2e94ba33df4edc5294f2aae9d |
| Latest deploy log | https://app.netlify.com/projects/label-studio-playground/deploys/68f0bde3485ecd0008918dbd |
| Deploy Preview | https://deploy-preview-8602--label-studio-playground.netlify.app |
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify project configuration.
/git merge
Workflow run Successfully merged: create mode 100644 web/libs/core/src/providers/api-provider.tsx
/git merge
Workflow run Successfully merged: create mode 100644 web/libs/core/src/providers/api-provider.tsx
This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.
This PR was closed because it has been stalled for 10 days with no activity.