Skip to content

Commit a9275cc

Browse files
committed
Bubble plot improvements
1 parent 7944b88 commit a9275cc

File tree

7 files changed

+77
-22
lines changed

7 files changed

+77
-22
lines changed

smoosense-gui/src/components/common/CategoricalColumnDropdown.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface CategoricalColumnDropdownProps {
1717
settingKey: keyof UIState
1818
label: string
1919
shouldInitialize?: boolean
20+
postChange?: (newValue: string | null) => void
2021
}
2122

2223
type UIState = {
@@ -35,10 +36,11 @@ const actionMap = {
3536
boxPlotBreakdownColumn: setBoxPlotBreakdownColumn,
3637
} as const
3738

38-
export default function CategoricalColumnDropdown({
39-
settingKey,
39+
export default function CategoricalColumnDropdown({
40+
settingKey,
4041
label,
41-
shouldInitialize = false
42+
shouldInitialize = false,
43+
postChange
4244
}: CategoricalColumnDropdownProps) {
4345
const dispatch = useAppDispatch()
4446
const { isCategoricalColumns } = useIsCategoricalBulk()
@@ -57,7 +59,9 @@ export default function CategoricalColumnDropdown({
5759
const action = actionMap[settingKey]
5860
if (action) {
5961
// Convert "-" back to null
60-
dispatch(action(value === "-" ? null : value))
62+
const newValue = value === "-" ? null : value
63+
dispatch(action(newValue))
64+
postChange?.(newValue)
6165
}
6266
}
6367

smoosense-gui/src/components/common/NumericalColumnDropdown.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface NumericalColumnDropdownProps {
2727
shouldInitialize?: boolean
2828
showStats?: boolean
2929
extraOptions?: ExtraOption[]
30+
postChange?: (newValue: string) => void
3031
}
3132

3233
type UIState = {
@@ -48,7 +49,8 @@ export default function NumericalColumnDropdown({
4849
label,
4950
shouldInitialize = true,
5051
showStats = false,
51-
extraOptions = []
52+
extraOptions = [],
53+
postChange
5254
}: NumericalColumnDropdownProps) {
5355
const dispatch = useAppDispatch()
5456
const { isCategoricalColumns } = useIsCategoricalBulk()
@@ -71,27 +73,25 @@ export default function NumericalColumnDropdown({
7173
const action = actionMap[settingKey]
7274
if (action) {
7375
// Convert "-" back to empty string
74-
dispatch(action(value === "-" ? "" : value))
76+
const newValue = value === "-" ? "" : value
77+
dispatch(action(newValue))
78+
postChange?.(newValue)
7579
}
7680
}
7781

78-
// Auto-initialize:
79-
// - If shouldInitialize is true, initialize to first available column
80-
// - If extraOptions is provided, initialize to first extraOption
82+
// Auto-initialize only if shouldInitialize is true
83+
// Only runs once when columns become available and currentValue is empty
8184
useEffect(() => {
82-
if (!currentValue) {
85+
if (!shouldInitialize) return
86+
if (!currentValue && availableColumns.length > 0) {
8387
const action = actionMap[settingKey]
8488
if (action) {
85-
if (shouldInitialize && availableColumns.length > 0) {
86-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
87-
dispatch((action as any)(availableColumns[0]))
88-
} else if (extraOptions.length > 0) {
89-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90-
dispatch((action as any)(extraOptions[0].value))
91-
}
89+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90+
dispatch((action as any)(availableColumns[0]))
9291
}
9392
}
94-
}, [shouldInitialize, availableColumns, currentValue, settingKey, dispatch, extraOptions])
93+
// eslint-disable-next-line react-hooks/exhaustive-deps
94+
}, [shouldInitialize, availableColumns.length > 0])
9595

9696
const getPlaceholderText = () => {
9797
if (!isCategoricalColumns || !renderTypes) {

smoosense-gui/src/components/settings/ColorScaleDropdown.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const COLOR_SCALES: Record<string, string> = {
1515
'Hot': 'linear-gradient(to right, #000000, #e60000, #ffd200, #ffffff)',
1616
'Reds': 'linear-gradient(to right, #fff5f0, #fee0d2, #fcbba1, #fc9272, #fb6a4a, #ef3b2c, #cb181d, #a50f15, #67000d)',
1717
'Picnic': 'linear-gradient(to right, #0000ff, #3399ff, #66ccff, #99ccff, #ccccff, #ffffff, #ffcccc, #ff9999, #ff6666, #ff3333, #ff0000)',
18+
'Rainbow': 'linear-gradient(to right, #96005a, #0000c8, #0019ff, #0098ff, #2cff96, #97ff00, #ffea00, #ff6f00, #ff0000)',
1819
}
1920

2021
function ColorScaleBar({ scale }: { scale: string }) {

smoosense-gui/src/lib/features/bubblePlot/BubblePlotMoreControls.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import IconPopover from '@/components/common/IconPopover'
66
import CategoricalColumnDropdown from '@/components/common/CategoricalColumnDropdown'
77
import NumericalColumnDropdown from '@/components/common/NumericalColumnDropdown'
88
import ColorScaleDropdown from '@/components/settings/ColorScaleDropdown'
9+
import UISettingToggle from '@/components/ui/UISettingToggle'
910
import { useAppSelector, useAppDispatch } from '@/lib/hooks'
10-
import { setBubblePlotMaxMarkerSize, setBubblePlotMinMarkerSize, setBubblePlotOpacity, setBubblePlotMarkerSizeContrastRatio } from '@/lib/features/ui/uiSlice'
11+
import { setBubblePlotMaxMarkerSize, setBubblePlotMinMarkerSize, setBubblePlotOpacity, setBubblePlotMarkerSizeContrastRatio, setBubblePlotBreakdownColumn, setBubblePlotColorColumn } from '@/lib/features/ui/uiSlice'
12+
import { useCallback } from 'react'
13+
import { toast } from 'sonner'
1114

1215
// Special value for coloring by bubble size (count)
1316
export const BUBBLE_SIZE_COLOR_VALUE = '__bubble_size__'
@@ -22,23 +25,46 @@ function BubblePlotMoreControlsContent() {
2225
const bubblePlotMinMarkerSize = useAppSelector((state) => state.ui.bubblePlotMinMarkerSize)
2326
const bubblePlotOpacity = useAppSelector((state) => state.ui.bubblePlotOpacity)
2427
const bubblePlotMarkerSizeContrastRatio = useAppSelector((state) => state.ui.bubblePlotMarkerSizeContrastRatio)
28+
const currentColorColumn = useAppSelector((state) => state.ui.bubblePlotColorColumn)
29+
const currentBreakdownColumn = useAppSelector((state) => state.ui.bubblePlotBreakdownColumn)
30+
31+
// When breakdown is set to non-null, clear color by (if it was set)
32+
const handleBreakdownChange = useCallback((newValue: string | null) => {
33+
if (newValue !== null && currentColorColumn !== '') {
34+
dispatch(setBubblePlotColorColumn(''))
35+
toast.info('"Color by" cleared because "breakdown" was set')
36+
}
37+
}, [dispatch, currentColorColumn])
38+
39+
// When color by is set to non-null, clear breakdown (if it was set)
40+
const handleColorByChange = useCallback((newValue: string) => {
41+
if (newValue !== '' && currentBreakdownColumn !== null) {
42+
dispatch(setBubblePlotBreakdownColumn(null))
43+
toast.info('"Breakdown" cleared because "color by" was set')
44+
}
45+
}, [dispatch, currentBreakdownColumn])
2546

2647
return (
2748
<div className="space-y-4 w-full max-w-sm">
2849
<CategoricalColumnDropdown
2950
settingKey="bubblePlotBreakdownColumn"
3051
label="Breakdown"
52+
postChange={handleBreakdownChange}
3153
/>
3254

3355
<NumericalColumnDropdown
3456
settingKey="bubblePlotColorColumn"
3557
label="Color by"
3658
shouldInitialize={false}
3759
extraOptions={BUBBLE_SIZE_EXTRA_OPTIONS}
60+
postChange={handleColorByChange}
3861
/>
3962

4063
<ColorScaleDropdown />
4164

65+
<UISettingToggle settingKey="bubblePlotLogScaleX" label="Use log-scale for X-axis" />
66+
<UISettingToggle settingKey="bubblePlotLogScaleY" label="Use log-scale for Y-axis"/>
67+
4268
<div>
4369
<label className="text-sm font-medium mb-2 block">
4470
Min Marker Size: {bubblePlotMinMarkerSize}

smoosense-gui/src/lib/features/bubblePlot/PlotlyBubblePlot.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
4040
const markerSizeContrastRatio = useAppSelector((state) => state.ui.bubblePlotMarkerSizeContrastRatio)
4141
const colorColumn = useAppSelector((state) => state.ui.bubblePlotColorColumn)
4242
const colorScale = useAppSelector((state) => state.ui.bubblePlotColorScale)
43+
const logScaleX = useAppSelector((state) => state.ui.bubblePlotLogScaleX)
44+
const logScaleY = useAppSelector((state) => state.ui.bubblePlotLogScaleY)
4345

4446
// Get theme colors for marker styling
4547
const colors = usePlotlyColors()
@@ -128,8 +130,16 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
128130
const layout = useMemo((): Partial<Layout> => ({
129131
...baseLayout,
130132
selectdirection: 'any',
131-
dragmode: 'lasso'
132-
}), [baseLayout])
133+
dragmode: 'lasso',
134+
xaxis: {
135+
...baseLayout.xaxis,
136+
type: logScaleX ? 'log' : 'linear'
137+
},
138+
yaxis: {
139+
...baseLayout.yaxis,
140+
type: logScaleY ? 'log' : 'linear'
141+
}
142+
}), [baseLayout, logScaleX, logScaleY])
133143

134144
const baseConfig = usePlotlyConfig()
135145

@@ -226,7 +236,7 @@ const PlotlyBubblePlot = React.memo(function PlotlyBubblePlot({ data }: PlotlyBu
226236
</div>
227237
<div className="flex-shrink-0 px-4 py-1 text-sm text-muted-foreground border-t flex">
228238
<div>
229-
x: {xColumn} | y: {yColumn}
239+
x: {xColumn}{logScaleX && ' (log)'} | y: {yColumn}{logScaleY && ' (log)'}
230240
{colorColumn && ` | color: ${colorDisplayName}`}
231241
{breakdownColumn && ` | breakdown: ${breakdownColumn}`}
232242
</div>

smoosense-gui/src/lib/features/ui/uiSlice.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ interface UiState {
4141
bubblePlotMarkerSizeContrastRatio: number
4242
bubblePlotColorColumn: string
4343
bubblePlotColorScale: string
44+
bubblePlotLogScaleX: boolean
45+
bubblePlotLogScaleY: boolean
4446
heatmapXColumn: string | null
4547
heatmapYColumn: string | null
4648
boxPlotBreakdownColumn: string | null
@@ -89,6 +91,8 @@ const initialState: UiState = {
8991
bubblePlotMarkerSizeContrastRatio: 4.2,
9092
bubblePlotColorColumn: '__bubble_size__',
9193
bubblePlotColorScale: 'Jet',
94+
bubblePlotLogScaleX: false,
95+
bubblePlotLogScaleY: false,
9296
heatmapXColumn: null,
9397
heatmapYColumn: null,
9498
boxPlotBreakdownColumn: null,
@@ -226,6 +230,12 @@ export const uiSlice = createSlice({
226230
setBubblePlotColorScale: (state, action: PayloadAction<string>) => {
227231
state.bubblePlotColorScale = action.payload
228232
},
233+
setBubblePlotLogScaleX: (state, action: PayloadAction<boolean>) => {
234+
state.bubblePlotLogScaleX = action.payload
235+
},
236+
setBubblePlotLogScaleY: (state, action: PayloadAction<boolean>) => {
237+
state.bubblePlotLogScaleY = action.payload
238+
},
229239
setHeatmapXColumn: (state, action: PayloadAction<string | null>) => {
230240
state.heatmapXColumn = action.payload
231241
},
@@ -322,6 +332,8 @@ export const {
322332
setBubblePlotMarkerSizeContrastRatio,
323333
setBubblePlotColorColumn,
324334
setBubblePlotColorScale,
335+
setBubblePlotLogScaleX,
336+
setBubblePlotLogScaleY,
325337
setHeatmapXColumn,
326338
setHeatmapYColumn,
327339
setBoxPlotBreakdownColumn,

smoosense-gui/src/lib/test-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export function createDefaultTestState(): RootState {
6565
bubblePlotMarkerSizeContrastRatio: 0.5,
6666
bubblePlotColorColumn: '',
6767
bubblePlotColorScale: 'Jet',
68+
bubblePlotLogScaleX: false,
69+
bubblePlotLogScaleY: false,
6870
heatmapXColumn: null,
6971
heatmapYColumn: null,
7072
boxPlotBreakdownColumn: null,

0 commit comments

Comments
 (0)