33import React , { useMemo , useCallback , useEffect , useState } from 'react'
44import dynamic from 'next/dynamic'
55import { useAppSelector , useAppDispatch } from '@/lib/hooks'
6- import type { BubblePlotGroup } from './bubblePlotSlice'
6+ import type { BubblePlotGroup } from '@/lib/features/bubblePlot/bubblePlotSlice'
7+ import { BUBBLE_SIZE_COLOR_VALUE } from '@/lib/features/bubblePlot/BubblePlotMoreControls'
78import type { PlotData , Layout , Config } from 'plotly.js'
89import { usePlotlyLayout , usePlotlyConfig , usePlotlyColors } from '@/lib/utils/plotlyTheme'
910import { setSamplingCondition } from '@/lib/features/viewing/viewingSlice'
@@ -84,7 +85,7 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
8485 marker . colorscale = colorScale
8586 marker . showscale = true
8687 marker . colorbar = {
87- title : colorColumn ,
88+ title : colorColumn === BUBBLE_SIZE_COLOR_VALUE ? 'Bubble Size' : colorColumn ,
8889 thickness : 15 ,
8990 len : 0.5
9091 }
@@ -104,7 +105,7 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
104105 `<b>Y ${ yColumn } :</b> ~%{y}<br>` +
105106 ( breakdownColumn ? `<b>${ breakdownColumn } :</b> ${ item . name } <br>` : '' ) +
106107 `<b>Count:</b> %{customdata.count}<br>` +
107- ( hasColorValues ? `<b>${ colorColumn } :</b> %{customdata.colorValue:.2f}<br>` : '' ) +
108+ ( hasColorValues && colorColumn !== BUBBLE_SIZE_COLOR_VALUE ? `<b>${ colorColumn } :</b> %{customdata.colorValue:.2f}<br>` : '' ) +
108109 '<extra></extra>' ,
109110 }
110111 return trace
@@ -117,10 +118,10 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
117118 }
118119 } , [ data , xColumn , yColumn , breakdownColumn , colorColumn , colorScale , opacity , maxMarkerSize , minMarkerSize , markerSizeContrastRatio , colors . foreground ] )
119120
121+ // Get display name for color column
122+ const colorDisplayName = colorColumn === BUBBLE_SIZE_COLOR_VALUE ? 'Bubble Size' : colorColumn
123+
120124 const baseLayout = usePlotlyLayout ( {
121- title : colorColumn ? `Color: ${ colorColumn } ` : undefined ,
122- xTitle : `X: ${ xColumn } ` ,
123- yTitle : `Y: ${ yColumn } ` ,
124125 showLegend : breakdownColumn !== null
125126 } )
126127
@@ -172,6 +173,18 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
172173 }
173174 } , [ dispatch ] )
174175
176+ // Compute bubble stats for info line
177+ const bubbleStats = useMemo ( ( ) => {
178+ if ( ! data || data . length === 0 ) return { total : 0 , minSize : 0 , maxSize : 0 }
179+
180+ const allCounts = data . flatMap ( group => group . customdata . map ( c => c . count ) )
181+ const total = data . reduce ( ( sum , group ) => sum + group . customdata . length , 0 )
182+ const minSize = allCounts . length > 0 ? Math . min ( ...allCounts ) : 0
183+ const maxSize = allCounts . length > 0 ? Math . max ( ...allCounts ) : 0
184+
185+ return { total, minSize, maxSize }
186+ } , [ data ] )
187+
175188 // Don't render on server side
176189 if ( ! isClient ) {
177190 return (
@@ -194,21 +207,34 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
194207 }
195208
196209 return (
197- < div className = "w-full h-full min-h-[400px]" >
198- < Plot
199- data = { plotData }
200- layout = { layout }
201- config = { config }
202- style = { { width : '100%' , height : '100%' } }
203- useResizeHandler = { true }
204- onClick = { handleClick }
205- onSelected = { handleSelected }
206- // eslint-disable-next-line @typescript-eslint/no-explicit-any
207- onError = { ( error : any ) => {
208- console . error ( 'Plotly error:' , error )
209- setPlotlyError ( error . message || 'Unknown plotting error' )
210- } }
211- />
210+ < div className = "w-full h-full min-h-[400px] flex flex-col" >
211+ < div className = "flex-1 min-h-0" >
212+ < Plot
213+ data = { plotData }
214+ layout = { layout }
215+ config = { config }
216+ style = { { width : '100%' , height : '100%' } }
217+ useResizeHandler = { true }
218+ onClick = { handleClick }
219+ onSelected = { handleSelected }
220+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221+ onError = { ( error : any ) => {
222+ console . error ( 'Plotly error:' , error )
223+ setPlotlyError ( error . message || 'Unknown plotting error' )
224+ } }
225+ />
226+ </ div >
227+ < div className = "flex-shrink-0 px-4 py-1 text-sm text-muted-foreground border-t flex" >
228+ < div >
229+ x: { xColumn } | y: { yColumn }
230+ { colorColumn && ` | color: ${ colorDisplayName } ` }
231+ { breakdownColumn && ` | breakdown: ${ breakdownColumn } ` }
232+ </ div >
233+ < div className = "flex-1" />
234+ < div >
235+ { bubbleStats . total } bubbles | size: { bubbleStats . minSize . toLocaleString ( ) } to { bubbleStats . maxSize . toLocaleString ( ) }
236+ </ div >
237+ </ div >
212238 </ div >
213239 )
214240} )
0 commit comments