# Line Drawing Improvements Plan ## Context The candle annotator app has a working line drawing feature, but it needs enhancements: - Currently only draws blue lines - No way to select or edit lines after creation - Limited visual feedback during drawing - Delete tool works by clicking near line ## Current State - Line drawing is working: click to start, move mouse (dashed preview), click to finish - Lines are stored in database with geometry (startTime, startPrice, endTime, endPrice) - Lines maintain relative position to candles during zoom/pan (coordinate conversion working) - SVG overlay is properly layered (z-index) and sized (CSS 100%) ## Approved Design Decisions 1. **Color**: Preset color buttons below "Draw Line" button (Option C) 2. **Selection**: Lines selectable only when "Draw Line" tool is active (Option B) 3. **Editing**: Drag endpoints only - no full line dragging (simplest approach) 4. **Visual Feedback**: Add small circle at mouse cursor during drawing ## Implementation Plan ### Phase 1: Color Support #### 1.1 Database Schema **File**: `src/lib/db/schema.ts` ```typescript // Add to annotations table: color: text('color').default('#3b82f6'), // hex color code ``` - Need to run migration or recreate database - Default color: `#3b82f6` (blue) #### 1.2 Toolbox UI **File**: `src/components/Toolbox.tsx` - Add state: `const [selectedColor, setSelectedColor] = useState('#3b82f6')` - Add preset color buttons below "Draw Line" button - Colors: Red (#ef4444), Green (#22c55e), Blue (#3b82f6), Yellow (#eab308), White (#ffffff) - Active color button should be highlighted (variant='default', others 'outline') - Pass `selectedColor` up to parent via new prop: `onColorChange` #### 1.3 Page Component **File**: `src/app/page.tsx` - Add state: `const [selectedColor, setSelectedColor] = useState('#3b82f6')` - Pass to Toolbox: `onColorChange={setSelectedColor}` - Pass to CandleChart: `selectedColor={selectedColor}` #### 1.4 Chart Component **File**: `src/components/CandleChart.tsx` - Accept new prop: `selectedColor: string` - Pass to SvgOverlay: `selectedColor={selectedColor}` #### 1.5 SVG Overlay - Create with Color **File**: `src/components/SvgOverlay.tsx` - Accept new prop: `selectedColor: string` - In `handleClick` when saving line (second click), add color to POST body: ```typescript body: JSON.stringify({ timestamp: drawingLine.start.time, label_type: 'line', color: selectedColor, // NEW geometry: { ... } }) ``` #### 1.6 SVG Overlay - Render with Color **File**: `src/components/SvgOverlay.tsx` - In `renderLines()`, read color from annotation: ```typescript stroke={annotation.color || '#3b82f6'} ``` #### 1.7 API - Handle Color **File**: `src/app/api/annotations/route.ts` - In POST handler, extract `color` from body - Save to database: `color: body.color || '#3b82f6'` --- ### Phase 2: Visual Feedback #### 2.1 Cursor Circle During Drawing **File**: `src/components/SvgOverlay.tsx` - Add new render function: ```typescript const renderCursorCircle = () => { if (!drawingLine || !mousePosition) return null; return ( ); }; ``` - Call in SVG return: `{renderCursorCircle()}` --- ### Phase 3: Line Selection #### 3.1 Selection State **File**: `src/components/SvgOverlay.tsx` - Add state: `const [selectedLineId, setSelectedLineId] = useState(null)` #### 3.2 Click Logic Update **File**: `src/components/SvgOverlay.tsx` - In `handleClick`, when `activeTool === 'line'`: - If NOT drawing line (drawingLine === null): - First check if clicking near existing line - If yes → select it (setSelectedLineId) - If no → start new line - If drawing line: - Finish and save line (existing behavior) #### 3.3 Visual Styling for Selected Line **File**: `src/components/SvgOverlay.tsx` - In `renderLines()`, check if line is selected: ```typescript const isSelected = annotation.id === selectedLineId; strokeWidth={isSelected ? 3 : 2} opacity={isSelected ? 1 : 0.85} ``` #### 3.4 Deselect on Tool Change or Escape **File**: `src/components/SvgOverlay.tsx` - Reset `selectedLineId` when `activeTool` changes - Add to existing Escape key handler: also clear `selectedLineId` --- ### Phase 4: Endpoint Dragging #### 4.1 Drag State **File**: `src/components/SvgOverlay.tsx` - Add state: ```typescript interface DragState { lineId: number; endpoint: 'start' | 'end'; originalPoint: Point; } const [dragState, setDragState] = useState(null); ``` #### 4.2 Render Endpoint Handles **File**: `src/components/SvgOverlay.tsx` - Add new render function: ```typescript const renderHandles = () => { if (!selectedLineId) return null; const line = annotations.find(a => a.id === selectedLineId); if (!line || !line.geometry) return null; const start = dataToPixel(line.geometry.startTime, line.geometry.startPrice); const end = dataToPixel(line.geometry.endTime, line.geometry.endPrice); if (!start || !end) return null; return ( <> handleHandleMouseDown(e, line.id, 'start')} /> handleHandleMouseDown(e, line.id, 'end')} /> ); }; ``` #### 4.3 Handle Drag Interaction **File**: `src/components/SvgOverlay.tsx` - Implement handlers: ```typescript const handleHandleMouseDown = (e: React.MouseEvent, lineId: number, endpoint: 'start' | 'end') => { e.stopPropagation(); // Prevent line click const line = annotations.find(a => a.id === lineId); if (!line?.geometry) return; const point = endpoint === 'start' ? { time: line.geometry.startTime, price: line.geometry.startPrice } : { time: line.geometry.endTime, price: line.geometry.endPrice }; setDragState({ lineId, endpoint, originalPoint: point }); }; ``` #### 4.4 Mouse Move During Drag **File**: `src/components/SvgOverlay.tsx` - Update `handleMouseMove` to handle drag preview: ```typescript if (dragState && mousePosition) { const dataPoint = pixelToData(mousePosition.x, mousePosition.y); if (dataPoint) { // Update local preview of line position // (optimistically update annotations array temporarily) } } ``` #### 4.5 Mouse Up - Save to Database **File**: `src/components/SvgOverlay.tsx` - Add handler: ```typescript const handleMouseUp = async () => { if (!dragState || !mousePosition) return; const dataPoint = pixelToData(mousePosition.x, mousePosition.y); if (!dataPoint) return; const line = annotations.find(a => a.id === dragState.lineId); if (!line?.geometry) return; const updatedGeometry = { ...line.geometry, ...(dragState.endpoint === 'start' ? { startTime: dataPoint.time, startPrice: dataPoint.price } : { endTime: dataPoint.time, endPrice: dataPoint.price }) }; await fetch(`/api/annotations/${dragState.lineId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ geometry: updatedGeometry }) }); await fetchAnnotations(); setDragState(null); onAnnotationChange?.(); }; ``` - Add to SVG: `onMouseUp={handleMouseUp}` #### 4.6 API - PATCH Endpoint **File**: `src/app/api/annotations/[id]/route.ts` - Add PATCH handler: ```typescript export async function PATCH( request: NextRequest, { params }: { params: { id: string } } ) { try { const body = await request.json(); const { geometry } = body; const result = await db .update(annotations) .set({ geometry: JSON.stringify(geometry) }) .where(eq(annotations.id, parseInt(params.id))) .returning(); const updated = result[0]; return NextResponse.json({ ...updated, geometry: updated.geometry ? JSON.parse(updated.geometry) : null }); } catch (error: any) { return NextResponse.json( { error: error.message || 'Failed to update annotation' }, { status: 500 } ); } } ``` --- ## Testing Checklist After implementation, verify: - [ ] Can select different colors before drawing line - [ ] Lines are drawn in selected color - [ ] Existing lines render with their stored color - [ ] Small circle appears at cursor during line drawing - [ ] Can click existing line (in line tool mode) to select it - [ ] Selected line has visual highlight (thicker stroke) - [ ] Selected line shows endpoint handles (white circles) - [ ] Can drag endpoint handles to reposition line - [ ] Line updates in database after dragging - [ ] Lines maintain position during zoom/pan - [ ] Delete tool still works (click near line) - [ ] Can deselect by clicking empty space or pressing Escape - [ ] Switching tools deselects line --- ## Files Modified Summary 1. `src/lib/db/schema.ts` - Add color field 2. `src/components/Toolbox.tsx` - Color picker UI 3. `src/app/page.tsx` - Pass color state 4. `src/components/CandleChart.tsx` - Pass color to overlay 5. `src/components/SvgOverlay.tsx` - Selection, dragging, visual feedback 6. `src/app/api/annotations/route.ts` - Handle color in POST 7. `src/app/api/annotations/[id]/route.ts` - NEW: PATCH handler --- ## Notes - Lines use data coordinates (time/price) so they survive zoom/pan automatically - SVG overlay has z-index and proper sizing - this is working - Current delete functionality works by clicking near line - keep this behavior - Color state is managed at page level, flows down to components - Database migration needed for color field (or recreate dev database)