# feat\(charts\): mirror crosshair \(\#19\) · flambo/atlantis

[View on GitCafe](https://git.cafe/flambo/atlantis/commit/47b81aae7f5e63dc6170b638fe1167b948f33a47)

Repository: [flambo/atlantis](https://git.cafe/flambo/atlantis)

Visibility: public

Requested revision: 47b81aae7f5e63dc6170b638fe1167b948f33a47

Requested commit: 47b81aae7f5e63dc6170b638fe1167b948f33a47

Commit: 47b81aae7f5e63dc6170b638fe1167b948f33a47

Tree: d449a011cc07c926c4df9a7b2a2315e5878b639e

Author: oliver

Committer: GitHub

## Message

````
feat(charts): mirror crosshair (#19)

<!-- greptile_comment -->

<h3>Greptile Summary</h3>


Implements synchronized crosshair functionality across multiple Chart.js visualizations using a centralized store pattern.

**Key Changes:**
- Created `CrosshairStore` singleton (`crosshair.ts`) to coordinate hover state across charts
- Extended crosshair plugin with `sync` callbacks (`onHover`, `getExternalLabel`) for multi-chart coordination
- Integrated crosshair synchronization in all chart components (`ChartContainer`, `IPChart`, `ProtocolChart`, `SpectrumStatsChart`)
- Each chart registers/unregisters with the store on mount/destroy
- Local hover takes precedence over external hover for better UX
- Tooltips only display on the actively hovered chart, while crosshairs mirror across all registered charts

**Architecture:**
The implementation uses a pub-sub pattern where the hovered chart notifies the store, which triggers redraws on all other charts. Charts query the store during their draw cycle to determine if they should display an external crosshair.

<h3>Confidence Score: 5/5</h3>


- This PR is safe to merge with minimal risk
- Implementation is clean, well-architected, and follows established patterns. Proper lifecycle management with register/unregister prevents memory leaks. The singleton store pattern is appropriate for coordinating global UI state. No logic errors or security issues identified.
- No files require special attention

<h3>Important Files Changed</h3>




| Filename | Overview |
|----------|----------|
| netflow-webapp/src/lib/stores/crosshair.ts | New singleton store for synchronized crosshair state management across charts |
| netflow-webapp/src/lib/components/charts/crosshair-plugin.ts | Added sync callbacks and external label support for cross-chart synchronization |
| netflow-webapp/src/lib/components/charts/ChartContainer.svelte | Integrated crosshair store with registration/unregistration and sync callbacks |
| netflow-webapp/src/lib/components/charts/IPChart.svelte | Added crosshair plugin with sync support and proper chart lifecycle management |
| netflow-webapp/src/lib/components/charts/ProtocolChart.svelte | Added crosshair plugin with sync support and proper chart lifecycle management |
| netflow-webapp/src/lib/components/charts/SpectrumStatsChart.svelte | Added crosshair plugin with sync support for scatter chart type |

</details>



<h3>Sequence Diagram</h3>

```mermaid
sequenceDiagram
    participant User
    participant ChartA as Chart A (e.g., IPChart)
    participant Plugin as Crosshair Plugin
    participant Store as Crosshair Store
    participant ChartB as Chart B (e.g., ProtocolChart)
    
    Note over ChartA,ChartB: Initialization Phase
    ChartA->>Store: register('ip', chartInstance)
    ChartB->>Store: register('protocol', chartInstance)
    
    Note over ChartA,ChartB: User Hovers Over Chart A
    User->>ChartA: mousemove event
    ChartA->>Plugin: afterEvent (mousemove)
    Plugin->>Plugin: calculateHoveredDate(mouseX)
    Plugin->>Store: setHover(label, 'ip')
    Store->>Store: _hoveredLabel = label<br/>_sourceChartId = 'ip'
    Store->>ChartB: chart.draw()
    
    Note over ChartB: Chart B Redraw
    ChartB->>Plugin: afterDraw
    Plugin->>Store: getExternalLabel('protocol')
    Store-->>Plugin: return label (sourceId != 'protocol')
    Plugin->>Plugin: Find label index<br/>Convert to pixel position
    Plugin->>ChartB: Draw crosshair at synchronized position
    
    Note over ChartA: Chart A Continues
    Plugin->>ChartA: afterDraw
    Plugin->>Store: getExternalLabel('ip')
    Store-->>Plugin: return null (sourceId == 'ip')
    Plugin->>ChartA: Draw crosshair at local mouse position<br/>Show tooltip after delay
    
    Note over ChartA,ChartB: User Leaves Chart A
    User->>ChartA: mouseout event
    ChartA->>Plugin: afterEvent (mouseout)
    Plugin->>Store: setHover(null, 'ip')
    Store->>Store: _hoveredLabel = null<br/>_sourceChartId = null
    Store->>ChartB: chart.draw()
    ChartB->>Plugin: afterDraw
    Plugin->>Store: getExternalLabel('protocol')
    Store-->>Plugin: return null
    Plugin->>ChartB: No crosshair drawn
    
    Note over ChartA,ChartB: Cleanup Phase
    ChartA->>Store: unregister('ip')
    ChartB->>Store: unregister('protocol')
```

<!-- greptile_other_comments_section -->

<!-- /greptile_comment -->
````

## Parents

- [1ae89b4d8a462ecd84c65c918db1146236415306](https://git.cafe/flambo/atlantis/commit/1ae89b4d8a462ecd84c65c918db1146236415306?format=markdown)

- [d2bd6a1b45f24e70d686e3e88459a9203579a377](https://git.cafe/flambo/atlantis/commit/d2bd6a1b45f24e70d686e3e88459a9203579a377?format=markdown)

[Source at this commit](https://git.cafe/flambo/atlantis/tree/47b81aae7f5e63dc6170b638fe1167b948f33a47?format=markdown)
