- Implement disagreement visual highlighting with distinct colors - Yellow highlight for 'missed_by_human' predictions - Orange for 'label_mismatch' disagreements - Warning icon on disagreement markers - Add click-to-convert prediction feedback - Click disagreement predictions to create span annotations - Auto-fill with predicted label and times - Set source as 'model_confirmed' or 'model_corrected' - Add dismiss action for false positive predictions - Alt+Click or Ctrl+Click to dismiss predictions - Saves negative annotation with label 'O' - Records original prediction in model_prediction field - Filter predictions when 'Show only disagreements' is enabled
92 lines
8 KiB
Markdown
92 lines
8 KiB
Markdown
## Context
|
|
|
|
The candle annotator uses lightweight-charts for rendering candlestick data and currently has two rendering layers for annotations:
|
|
|
|
1. **Canvas-based (ISeriesPrimitive)**: Used by `SpanRectanglePrimitive.ts` for span annotations. These are attached to the series via `series.attachPrimitive()` and render natively within the chart canvas. They support `hitTest()`, autoscaling, and z-ordering.
|
|
|
|
2. **SVG overlay**: Used by `SvgOverlay.tsx` for line annotations. An absolutely-positioned SVG element sits on top of the chart with `zIndex: 1111`, intercepting pointer events when the line or delete tool is active. It duplicates coordinate conversion logic and manages its own interaction state.
|
|
|
|
The existing `src/plugins/trend-line.ts` already implements a `TrendLine` class using `ISeriesPrimitive<Time>` but is not wired into the interactive drawing flow. The Toolbox component manages tool modes — line drawing is triggered by annotation types with `category: 'line'`.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
- Replace SVG overlay line rendering with the existing `TrendLine` plugin (canvas-based)
|
|
- Add interactive drawing for lines using chart's native click/crosshair events instead of SVG pointer events
|
|
- Add a rectangle annotation tool using a new `RectangleDrawingPrimitive` plugin
|
|
- Maintain all existing line behaviors: two-click drawing, preview, selection, endpoint dragging, deletion
|
|
- Use the same database schema and API endpoints — rectangles stored as `label_type: "rectangle"` with geometry JSON
|
|
- Keep rendering approach consistent with `SpanRectanglePrimitive` patterns
|
|
|
|
**Non-Goals:**
|
|
- Changing span annotation implementation (already uses ISeriesPrimitive)
|
|
- Adding axis labels or price/time markers to lines or rectangles
|
|
- Multi-select or group operations on annotations
|
|
- Undo/redo functionality
|
|
- Toolbar UI for the rectangle drawing tool from the upstream example (we use our own Toolbox)
|
|
|
|
## Decisions
|
|
|
|
### 1. Remove SVG overlay entirely
|
|
|
|
**Decision**: Delete `SvgOverlay.tsx` and move all line rendering to the `TrendLine` plugin.
|
|
|
|
**Rationale**: The SVG overlay creates a layering problem — it must intercept pointer events by sitting above the chart, which blocks chart interactions (zoom, pan, crosshair) when the line tool is active. The plugin system renders within the chart canvas and can coexist with native chart interactions. `SpanRectanglePrimitive` already demonstrates this approach works well.
|
|
|
|
**Alternative considered**: Keep SVG for interactive drawing (preview), use plugin only for saved lines. Rejected because it still requires maintaining two rendering systems and the coordinate conversion duplication.
|
|
|
|
### 2. Manage drawing interaction in CandleChart via chart events
|
|
|
|
**Decision**: Use `chart.subscribeClick()` and `chart.subscribeCrosshairMove()` in `CandleChart.tsx` to handle the two-click drawing flow for both lines and rectangles.
|
|
|
|
**Rationale**: The chart API provides native click and crosshair events that include time/price data coordinates directly, eliminating the need for manual pixel-to-data conversion. This matches how the upstream `RectangleDrawingTool` example works. `CandleChart.tsx` already owns the chart and series references.
|
|
|
|
**Alternative considered**: Create a separate `DrawingManager` component. Rejected for this scope — CandleChart already manages span primitives, so adding line/rectangle primitive management follows the existing pattern. Extraction can happen later if complexity warrants it.
|
|
|
|
### 3. Enhance TrendLine plugin with hit testing and selection
|
|
|
|
**Decision**: Add `hitTest()` and `setSelected()` methods to the existing `TrendLine` class, following the `SpanRectanglePrimitive` pattern.
|
|
|
|
**Rationale**: `hitTest()` enables the chart's built-in click handling to detect which primitive was clicked, eliminating the need for manual distance-to-line-segment calculation in the overlay. Selection state changes line appearance (thicker stroke, handles).
|
|
|
|
**Implementation**: Hit testing for lines uses perpendicular distance to the line segment (same algorithm currently in `SvgOverlay`, moved into the renderer's coordinate space).
|
|
|
|
### 4. Create RectangleDrawingPrimitive following SpanRectanglePrimitive patterns
|
|
|
|
**Decision**: Create a new `src/plugins/rectangle-drawing.ts` that implements `ISeriesPrimitive<Time>` directly (not extending a base class), storing two corner points `{time, price}` and rendering a filled rectangle.
|
|
|
|
**Rationale**: The upstream `RectangleDrawingTool` example is complex (multiple axis views, toolbar UI, PluginBase class) — most of that complexity is unnecessary since we have our own Toolbox and don't need axis highlighting. Our `SpanRectanglePrimitive` already demonstrates a simpler rectangle rendering approach. The new plugin adapts that pattern but uses two arbitrary corner points instead of candle-range-derived bounds.
|
|
|
|
**Differences from SpanRectanglePrimitive**: Span rectangles are defined by candle ranges (start_time/end_time → compute min_low/max_high from candle data). Rectangle annotations are freeform — two arbitrary {time, price} corners, no dependency on candle data.
|
|
|
|
### 5. Preview primitives for drawing feedback
|
|
|
|
**Decision**: Create temporary "preview" instances of `TrendLine` and `RectangleDrawingPrimitive` during the two-click drawing flow. Update endpoint on crosshair move. Remove and replace with permanent instance on second click.
|
|
|
|
**Rationale**: This is the same approach used in the upstream `RectangleDrawingTool` example (`PreviewRectangle` class). A preview primitive with dashed/semi-transparent styling gives visual feedback without requiring a separate rendering layer. Attach on first click, update on move, detach and replace on second click.
|
|
|
|
### 6. Endpoint dragging via primitive update
|
|
|
|
**Decision**: When a line is selected and the user drags a handle, call `trendLine.updatePoints(p1, p2)` and `requestUpdate()` to re-render at new coordinates. On mouse up, persist via PATCH API.
|
|
|
|
**Rationale**: The `TrendLine` class already has `updatePoints()`. Drag detection uses crosshair move events. This replaces the SVG-based drag handling that manipulated DOM elements directly.
|
|
|
|
**Handle rendering**: Draw small circles at endpoints when selected, as part of the `TrendLinePaneRenderer.draw()` method (no separate SVG elements needed).
|
|
|
|
### 7. Database and API — no changes
|
|
|
|
**Decision**: Rectangles use the existing `annotations` table with `label_type: "rectangle"` and `geometry: {"startTime", "startPrice", "endTime", "endPrice"}` — identical schema to lines.
|
|
|
|
**Rationale**: The geometry format is the same (two points). No migration needed. The API endpoints (`POST/PATCH/DELETE /api/annotations`) already handle arbitrary `label_type` and `geometry` values.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
**[Risk] Line hit testing accuracy in canvas coordinates** → The distance-to-line-segment algorithm needs to work in bitmap coordinate space. Mitigation: Port the existing algorithm from SvgOverlay, test with various zoom levels. Use a tolerance of ~10 CSS pixels (scaled by pixel ratio).
|
|
|
|
**[Risk] Breaking existing saved line annotations** → Existing lines in the database use the same geometry format, so they should render identically with the plugin. Mitigation: No data migration needed; verify rendering parity before removing SVG overlay.
|
|
|
|
**[Risk] Pointer event handling conflicts** → Chart's native click events fire for both annotation tools and regular chart interaction. Mitigation: Only handle annotation clicks when `activeTool` is set to a drawing/delete tool. When no tool is active, clicks pass through to normal chart behavior (zoom, crosshair, etc.).
|
|
|
|
**[Risk] Performance with many primitives** → Each line/rectangle is a separate `ISeriesPrimitive` instance. Mitigation: This is the same approach used for spans, and the number of annotations per chart is typically small (tens, not thousands).
|
|
|
|
**[Trade-off] No PluginBase class** → We implement `ISeriesPrimitive` directly rather than using the upstream `PluginBase` abstraction. This means slightly more boilerplate per plugin but avoids adding a dependency/abstraction for just two plugin types.
|