@@ -6,6 +6,7 @@ import './OverviewChart.css';
66export default function OverviewChart ( { data, onModelSelect } ) {
77 const chartRef = useRef ( null ) ;
88 const chartInstance = useRef ( null ) ;
9+ const labelsContainerRef = useRef ( null ) ;
910
1011 const modelColors = {
1112 'Hyundai Ioniq 5' : '#667eea' ,
@@ -24,9 +25,6 @@ export default function OverviewChart({ data, onModelSelect }) {
2425 useEffect ( ( ) => {
2526 if ( ! data || data . length === 0 ) return ;
2627
27- // Store label positions for click detection
28- const labelBounds = [ ] ;
29-
3028 // Group data by model and date
3129 const models = [ ...new Set ( data . flatMap ( d => d . listings . map ( getModelKey ) ) ) ] ;
3230 const dates = [ ...new Set ( data . map ( d => d . scraped_at . split ( 'T' ) [ 0 ] ) ) ] . sort ( ) ;
@@ -118,8 +116,6 @@ export default function OverviewChart({ data, onModelSelect }) {
118116 const meta = chart . getDatasetMeta ( i ) ;
119117 if ( ! meta . hidden && meta . data . length > 0 ) {
120118 const lastPoint = meta . data [ meta . data . length - 1 ] ;
121- ctx . font = 'bold 12px sans-serif' ;
122- const textWidth = ctx . measureText ( dataset . label ) . width ;
123119
124120 labels . push ( {
125121 text : dataset . label ,
@@ -129,7 +125,6 @@ export default function OverviewChart({ data, onModelSelect }) {
129125 dataPointY : lastPoint . y ,
130126 x : lastPoint . x + 20 ,
131127 y : lastPoint . y ,
132- width : textWidth ,
133128 height : 16
134129 } ) ;
135130 }
@@ -154,10 +149,7 @@ export default function OverviewChart({ data, onModelSelect }) {
154149 }
155150 }
156151
157- // Clear previous label bounds
158- labelBounds . length = 0 ;
159-
160- // Draw callout lines and labels
152+ // Draw callout lines only
161153 labels . forEach ( label => {
162154 // Draw callout line if label was moved
163155 if ( Math . abs ( label . y - label . originalY ) > 2 ) {
@@ -171,82 +163,57 @@ export default function OverviewChart({ data, onModelSelect }) {
171163 ctx . stroke ( ) ;
172164 ctx . globalAlpha = 1.0 ;
173165 }
166+ } ) ;
174167
175- // Draw label
176- ctx . fillStyle = label . color ;
177- ctx . font = 'bold 12px sans-serif' ;
178- ctx . textAlign = 'left' ;
179- ctx . textBaseline = 'middle' ;
180- ctx . fillText ( label . text , label . x , label . y ) ;
168+ // Create DOM labels
169+ if ( labelsContainerRef . current ) {
170+ labelsContainerRef . current . innerHTML = '' ;
171+
172+ labels . forEach ( label => {
173+ const linkEl = document . createElement ( 'a' ) ;
174+ linkEl . href = `?model=${ encodeURIComponent ( label . model ) } ` ;
175+ linkEl . className = 'chart-label' ;
176+ linkEl . style . position = 'absolute' ;
177+ linkEl . style . left = label . x + 'px' ;
178+ linkEl . style . top = ( label . y - label . height / 2 ) + 'px' ;
179+ linkEl . style . color = label . color ;
180+ linkEl . style . fontWeight = 'bold' ;
181+ linkEl . style . fontSize = '12px' ;
182+ linkEl . style . textDecoration = 'none' ;
183+ linkEl . style . whiteSpace = 'nowrap' ;
184+ linkEl . style . display = 'flex' ;
185+ linkEl . style . alignItems = 'center' ;
186+ linkEl . style . gap = '4px' ;
187+
188+ const textSpan = document . createElement ( 'span' ) ;
189+ textSpan . textContent = label . text ;
190+
191+ const chevron = document . createElement ( 'span' ) ;
192+ chevron . textContent = '›' ;
193+ chevron . style . fontSize = '14px' ;
194+ chevron . style . opacity = '0.7' ;
195+
196+ linkEl . appendChild ( textSpan ) ;
197+ linkEl . appendChild ( chevron ) ;
198+
199+ linkEl . addEventListener ( 'click' , ( e ) => {
200+ e . preventDefault ( ) ;
201+ if ( onModelSelect ) {
202+ onModelSelect ( label . model ) ;
203+ }
204+ } ) ;
181205
182- // Store bounds for click detection
183- labelBounds . push ( {
184- x : label . x ,
185- y : label . y - label . height / 2 ,
186- width : label . width ,
187- height : label . height ,
188- model : label . model
206+ labelsContainerRef . current . appendChild ( linkEl ) ;
189207 } ) ;
190- } ) ;
208+ }
191209 }
192210 } ]
193211 } ) ;
194212
195- // Add click handler for labels
196- const handleCanvasClick = ( event ) => {
197- const rect = chartRef . current . getBoundingClientRect ( ) ;
198- const x = event . clientX - rect . left ;
199- const y = event . clientY - rect . top ;
200-
201- // Check if click is within any label bounds
202- for ( const bound of labelBounds ) {
203- if (
204- x >= bound . x &&
205- x <= bound . x + bound . width &&
206- y >= bound . y &&
207- y <= bound . y + bound . height
208- ) {
209- if ( onModelSelect ) {
210- onModelSelect ( bound . model ) ;
211- }
212- break ;
213- }
214- }
215- } ;
216-
217- // Add mousemove handler to show pointer cursor over labels
218- const handleCanvasMouseMove = ( event ) => {
219- const rect = chartRef . current . getBoundingClientRect ( ) ;
220- const x = event . clientX - rect . left ;
221- const y = event . clientY - rect . top ;
222-
223- let overLabel = false ;
224- for ( const bound of labelBounds ) {
225- if (
226- x >= bound . x &&
227- x <= bound . x + bound . width &&
228- y >= bound . y &&
229- y <= bound . y + bound . height
230- ) {
231- overLabel = true ;
232- break ;
233- }
234- }
235-
236- chartRef . current . style . cursor = overLabel ? 'pointer' : 'default' ;
237- } ;
238-
239- chartRef . current . addEventListener ( 'click' , handleCanvasClick ) ;
240- chartRef . current . addEventListener ( 'mousemove' , handleCanvasMouseMove ) ;
241-
242213 return ( ) => {
243214 if ( chartInstance . current ) {
244215 chartInstance . current . destroy ( ) ;
245216 }
246- if ( chartRef . current ) {
247- chartRef . current . removeEventListener ( 'click' , handleCanvasClick ) ;
248- chartRef . current . removeEventListener ( 'mousemove' , handleCanvasMouseMove ) ;
249- }
250217 } ;
251218 } , [ data , onModelSelect ] ) ;
252219
@@ -258,6 +225,7 @@ export default function OverviewChart({ data, onModelSelect }) {
258225 < div className = "overview-chart" >
259226 < div className = "chart-container" >
260227 < canvas ref = { chartRef } > </ canvas >
228+ < div ref = { labelsContainerRef } className = "chart-labels" > </ div >
261229 </ div >
262230 </ div >
263231 ) ;
0 commit comments