chore: archive line-rectangle-annotations change and sync specs
- Archived change to openspec/changes/archive/2026-02-17-line-rectangle-annotations/ - Updated annotation-tools spec: added rectangle tool mode, TrendLine plugin rendering, line hit testing, line selection handles; updated line drawing and delete requirements; removed SVG overlay rendering - Created new rectangle-annotation spec with full requirements for rectangle drawing, rendering, hit testing, selection, deletion, and database storage Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d1557a3846
commit
0e8dcc6707
8 changed files with 522 additions and 8 deletions
|
|
@ -1,7 +1,7 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Active tool mode
|
||||
The system SHALL maintain an "active tool" state that determines what happens when the user clicks on the chart. Available tool modes are: "select" (default, no action on click), "break_up" (label Break Up), "break_down" (label Break Down), "line" (draw trend line), and "delete" (remove annotation). Only one tool SHALL be active at a time.
|
||||
The system SHALL maintain an "active tool" state that determines what happens when the user clicks on the chart. Available tool modes are: "select" (default, no action on click), "break_up" (label Break Up), "break_down" (label Break Down), "line" (draw trend line), "rectangle" (draw rectangle annotation), and "delete" (remove annotation). Only one tool SHALL be active at a time.
|
||||
|
||||
#### Scenario: Tool activation
|
||||
- **WHEN** user clicks a tool button in the sidebar
|
||||
|
|
@ -30,30 +30,71 @@ When the "break_down" tool is active and the user clicks on the chart, the syste
|
|||
- **THEN** system saves a "break_down" annotation for that candle's timestamp and displays a red arrow marker below the bar
|
||||
|
||||
### Requirement: Two-click line drawing
|
||||
When the "line" tool is active, the system SHALL implement a two-click drawing interaction. The first click sets the start point (time, price). The second click sets the end point (time, price). After the second click, the system SHALL save an annotation with `label_type: "line"` and `geometry` containing JSON: `{"startTime": <unix>, "startPrice": <float>, "endTime": <unix>, "endPrice": <float>}`. The line SHALL render immediately on the SVG overlay.
|
||||
When the "line" tool is active, the system SHALL implement a two-click drawing interaction using `chart.subscribeClick()` for click detection and `chart.subscribeCrosshairMove()` for preview updates. The first click sets the start point (time, price). The second click sets the end point (time, price). After the second click, the system SHALL save an annotation with `label_type: "line"` and `geometry` containing JSON: `{"startTime": <unix>, "startPrice": <float>, "endTime": <unix>, "endPrice": <float>}`. The line SHALL render immediately as a TrendLine primitive attached to the candlestick series.
|
||||
|
||||
#### Scenario: Draw a trend line
|
||||
- **WHEN** "line" tool is active and user clicks two points on the chart
|
||||
- **THEN** system saves a line annotation with start/end coordinates and renders the line on the overlay
|
||||
- **THEN** system saves a line annotation with start/end coordinates and renders the line as a TrendLine primitive
|
||||
|
||||
#### Scenario: Visual feedback during line drawing
|
||||
- **WHEN** "line" tool is active and user has clicked the first point but not the second
|
||||
- **THEN** system displays a preview line from the first point to the current cursor position
|
||||
- **THEN** system displays a preview TrendLine primitive (dashed or semi-transparent) from the first point to the current crosshair position, updating via `subscribeCrosshairMove()`
|
||||
|
||||
#### Scenario: Cancel line drawing
|
||||
- **WHEN** user presses Escape during a two-click line drawing (after first click)
|
||||
- **THEN** system cancels the line drawing and clears the preview without saving
|
||||
- **THEN** system cancels the line drawing, detaches the preview primitive, and clears the drawing state without saving
|
||||
|
||||
### Requirement: Line rendering via TrendLine plugin
|
||||
The system SHALL render saved line annotations using the `TrendLine` class (implementing `ISeriesPrimitive<Time>`) instead of SVG `<line>` elements. Each line annotation SHALL have one `TrendLine` primitive instance attached to the candlestick series via `series.attachPrimitive()`. The `SvgOverlay` component SHALL be removed.
|
||||
|
||||
#### Scenario: Saved lines render as canvas primitives
|
||||
- **WHEN** line annotations exist for the active chart
|
||||
- **THEN** each line renders via a TrendLine primitive on the chart canvas (not SVG overlay)
|
||||
|
||||
#### Scenario: Lines participate in autoscaling
|
||||
- **WHEN** a line annotation's price range extends beyond visible candle data
|
||||
- **THEN** the chart autoscale includes the line's price range via `autoscaleInfo()`
|
||||
|
||||
#### Scenario: Lines update on zoom/pan
|
||||
- **WHEN** user zooms or pans the chart
|
||||
- **THEN** line primitives automatically reposition via the ISeriesPrimitive lifecycle
|
||||
|
||||
### Requirement: Line hit testing
|
||||
The `TrendLine` class SHALL implement `hitTest(x, y)` to detect clicks near the line. Hit testing SHALL calculate the perpendicular distance from the click point to the line segment and return a hit if within 10 CSS pixels (scaled by device pixel ratio).
|
||||
|
||||
#### Scenario: Click near line detected
|
||||
- **WHEN** user clicks within 10 CSS pixels of a line segment
|
||||
- **THEN** `hitTest()` returns a `PrimitiveHoveredItem` with the annotation ID as `externalId`
|
||||
|
||||
#### Scenario: Click far from line not detected
|
||||
- **WHEN** user clicks more than 10 CSS pixels from any line segment
|
||||
- **THEN** `hitTest()` returns null
|
||||
|
||||
### Requirement: Line selection handles via plugin
|
||||
When a line is selected, the `TrendLine` renderer SHALL draw circular endpoint handles (radius 6px) at both endpoints of the line. The handles SHALL be rendered as part of the canvas draw call, not as separate SVG elements.
|
||||
|
||||
#### Scenario: Handles appear on selection
|
||||
- **WHEN** a line is selected (via click with line tool active)
|
||||
- **THEN** circular handles render at both endpoints of the line on the canvas
|
||||
|
||||
#### Scenario: Handles disappear on deselection
|
||||
- **WHEN** the selected line is deselected (Escape key or clicking elsewhere)
|
||||
- **THEN** the endpoint handles no longer render
|
||||
|
||||
### Requirement: Delete annotation
|
||||
When the "delete" tool is active and the user clicks on or near an existing annotation (marker or line), the system SHALL remove that annotation from the database and update the chart display immediately.
|
||||
When the "delete" tool is active and the user clicks on or near an existing annotation (marker, line, or rectangle), the system SHALL remove that annotation from the database and update the chart display immediately. Line and rectangle hit detection SHALL use the primitive's `hitTest()` method instead of SVG proximity calculation.
|
||||
|
||||
#### Scenario: Delete a marker annotation
|
||||
- **WHEN** "delete" tool is active and user clicks on a candle that has a marker annotation
|
||||
- **THEN** system removes the annotation from the database and the marker disappears from the chart
|
||||
|
||||
#### Scenario: Delete a line annotation
|
||||
- **WHEN** "delete" tool is active and user clicks near an existing line on the overlay
|
||||
- **THEN** system removes the line annotation from the database and the line disappears from the overlay
|
||||
- **WHEN** "delete" tool is active and user clicks near an existing line
|
||||
- **THEN** system detects the hit via `TrendLine.hitTest()`, sends DELETE /api/annotations/{id}, detaches the primitive from the series, and updates the annotation list
|
||||
|
||||
#### Scenario: Delete a rectangle annotation
|
||||
- **WHEN** "delete" tool is active and user clicks within a rectangle
|
||||
- **THEN** system detects the hit via `RectangleDrawingPrimitive.hitTest()`, sends DELETE /api/annotations/{id}, detaches the primitive, and updates the annotation list
|
||||
|
||||
### Requirement: Coordinate mapping
|
||||
The system SHALL convert mouse click pixel coordinates to chart data coordinates (time and price) using the lightweight-charts API: `chart.timeScale().coordinateToTime(x)` for time and `series.coordinateToPrice(y)` for price. For point annotations, the time SHALL be snapped to the nearest candle timestamp.
|
||||
|
|
|
|||
105
openspec/specs/rectangle-annotation/spec.md
Normal file
105
openspec/specs/rectangle-annotation/spec.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Rectangle tool mode
|
||||
The Toolbox SHALL include a "rectangle" tool button. When activated, the chart enters rectangle drawing mode. Only one tool SHALL be active at a time — activating the rectangle tool deactivates any other active tool.
|
||||
|
||||
#### Scenario: Activate rectangle tool
|
||||
- **WHEN** user clicks the rectangle tool button in the Toolbox
|
||||
- **THEN** the rectangle tool becomes active, the button appears visually selected, and the chart cursor changes to crosshair
|
||||
|
||||
#### Scenario: Deactivate rectangle tool
|
||||
- **WHEN** user clicks the already-active rectangle tool button
|
||||
- **THEN** the tool deactivates, the mode returns to "select", and the cursor returns to default
|
||||
|
||||
#### Scenario: Rectangle tool deactivates other tools
|
||||
- **WHEN** the "line" tool is active and user clicks the rectangle tool button
|
||||
- **THEN** the line tool deactivates and the rectangle tool becomes active
|
||||
|
||||
### Requirement: Two-click rectangle drawing
|
||||
When the "rectangle" tool is active, the system SHALL implement a two-click interaction to define a rectangle. The first click sets one corner point (time, price). The second click sets the opposite corner point (time, price). The rectangle is defined by these two arbitrary data-coordinate corners — it is NOT constrained to candle boundaries.
|
||||
|
||||
#### Scenario: Draw a rectangle
|
||||
- **WHEN** "rectangle" tool is active and user clicks two points on the chart
|
||||
- **THEN** the system saves a rectangle annotation with the two corner coordinates and renders a filled semi-transparent rectangle on the chart
|
||||
|
||||
#### Scenario: First click registers corner
|
||||
- **WHEN** "rectangle" tool is active and user clicks on the chart
|
||||
- **THEN** the system records the click position as {time, price} for the first corner
|
||||
|
||||
#### Scenario: Second click completes rectangle
|
||||
- **WHEN** user has set the first corner and clicks a second position
|
||||
- **THEN** the system saves a rectangle annotation via POST /api/annotations with `label_type: "rectangle"` and `geometry: {"startTime", "startPrice", "endTime", "endPrice"}`
|
||||
|
||||
### Requirement: Rectangle preview during drawing
|
||||
After the first click, the system SHALL display a preview rectangle that stretches from the first corner to the current cursor position. The preview SHALL have a dashed border and reduced opacity to distinguish it from saved rectangles.
|
||||
|
||||
#### Scenario: Preview follows cursor
|
||||
- **WHEN** user has clicked the first corner and moves the mouse
|
||||
- **THEN** a semi-transparent preview rectangle renders from the first corner to the cursor position, updating in real-time via crosshair move events
|
||||
|
||||
#### Scenario: Preview disappears on cancel
|
||||
- **WHEN** user presses Escape during rectangle drawing (after first click)
|
||||
- **THEN** the preview rectangle disappears and the drawing is cancelled without saving
|
||||
|
||||
### Requirement: Rectangle rendering via ISeriesPrimitive
|
||||
The system SHALL render saved rectangle annotations using a `RectangleDrawingPrimitive` class that implements `ISeriesPrimitive<Time>`. Each rectangle annotation SHALL have one primitive instance attached to the candlestick series via `series.attachPrimitive()`.
|
||||
|
||||
#### Scenario: Rectangle renders for saved annotation
|
||||
- **WHEN** a rectangle annotation exists for the active chart
|
||||
- **THEN** a semi-transparent filled rectangle renders on the chart canvas at the stored corner coordinates
|
||||
|
||||
#### Scenario: Rectangle uses annotation color
|
||||
- **WHEN** a rectangle annotation has a color value
|
||||
- **THEN** the rectangle renders with that color as fill (at reduced opacity) and border
|
||||
|
||||
#### Scenario: Rectangle updates on zoom/pan
|
||||
- **WHEN** user zooms or pans the chart
|
||||
- **THEN** rectangle primitives automatically reposition via the ISeriesPrimitive lifecycle (coordinate conversion in paneView.update())
|
||||
|
||||
#### Scenario: Rectangle z-order
|
||||
- **WHEN** rectangle annotations render on the chart
|
||||
- **THEN** they SHALL render at "bottom" z-order (behind candlesticks), consistent with span rectangles
|
||||
|
||||
### Requirement: Rectangle hit testing
|
||||
The `RectangleDrawingPrimitive` SHALL implement `hitTest(x, y)` to detect clicks within the rectangle bounds. Hit testing SHALL convert pixel coordinates to data coordinates and check if the point falls within the rectangle's time/price range.
|
||||
|
||||
#### Scenario: Click inside rectangle detected
|
||||
- **WHEN** user clicks at a position inside a rectangle's bounds
|
||||
- **THEN** `hitTest()` returns a `PrimitiveHoveredItem` with the annotation ID as `externalId`
|
||||
|
||||
#### Scenario: Click outside rectangle not detected
|
||||
- **WHEN** user clicks at a position outside all rectangle bounds
|
||||
- **THEN** `hitTest()` returns null
|
||||
|
||||
### Requirement: Rectangle selection
|
||||
The system SHALL allow users to select a rectangle annotation by clicking within its bounds when the "rectangle" or "select" tool is active.
|
||||
|
||||
#### Scenario: Click to select rectangle
|
||||
- **WHEN** user clicks within a rectangle annotation
|
||||
- **THEN** the rectangle appears selected (thicker border or increased opacity)
|
||||
|
||||
#### Scenario: Click to deselect rectangle
|
||||
- **WHEN** user clicks outside all rectangle annotations while one is selected
|
||||
- **THEN** the selection is cleared
|
||||
|
||||
### Requirement: Rectangle deletion
|
||||
The system SHALL allow users to delete rectangle annotations via the delete tool.
|
||||
|
||||
#### Scenario: Delete rectangle with delete tool
|
||||
- **WHEN** the "delete" tool is active and user clicks within a rectangle
|
||||
- **THEN** the system sends DELETE /api/annotations/{id}, removes the primitive from the chart, and updates the annotation list
|
||||
|
||||
#### Scenario: Delete selected rectangle with keyboard
|
||||
- **WHEN** a rectangle is selected and user presses Delete or Backspace
|
||||
- **THEN** the system deletes the rectangle annotation and removes it from the chart
|
||||
|
||||
### Requirement: Rectangle database storage
|
||||
Rectangle annotations SHALL be stored in the existing `annotations` table with `label_type: "rectangle"` and `geometry` containing JSON: `{"startTime": <unix>, "startPrice": <float>, "endTime": <float>, "endPrice": <float>}`. The `startTime/startPrice` represents one corner, `endTime/endPrice` the opposite corner.
|
||||
|
||||
#### Scenario: Rectangle annotation persisted
|
||||
- **WHEN** user completes a two-click rectangle drawing
|
||||
- **THEN** the system sends POST /api/annotations with label_type "rectangle", the active chart_id, selected color, and geometry JSON with the two corner coordinates
|
||||
|
||||
#### Scenario: Rectangle annotation loaded on chart switch
|
||||
- **WHEN** user switches to a chart that has rectangle annotations
|
||||
- **THEN** the system fetches annotations, creates RectangleDrawingPrimitive instances for each rectangle annotation, and attaches them to the series
|
||||
Loading…
Add table
Add a link
Reference in a new issue