@@ -3,9 +3,12 @@ import * as d3 from "d3";
33
44const MARGIN = { top : 16 , right : 48 , bottom : 8 , left : 160 } ;
55const MIN_WIDTH = 220 ;
6- const MIN_HEIGHT = 240 ;
7- const MAX_BANDWIDTH = 20 ;
8- const BAND_PADDING = 0.2 ;
6+ const BAR_HEIGHT = 18 ;
7+ const BAR_GAP = 4 ;
8+ const ROW_HEIGHT = BAR_HEIGHT + BAR_GAP ;
9+ const PADDING_INNER = BAR_GAP / ROW_HEIGHT ;
10+ const TICK_FONT_SIZE = 15 ;
11+ const LABEL_FONT_SIZE = 14 ;
912
1013/**
1114 * React wrapper around the original D3 barplot from client/js/main.js (barplot_new).
@@ -19,7 +22,7 @@ export default function Barplot({ data, onTickClick, disabled }) {
1922 const svgRef = useRef ( null ) ;
2023 const onTickClickRef = useRef ( onTickClick ) ;
2124 const disabledRef = useRef ( disabled ) ;
22- const [ size , setSize ] = useState ( { width : 0 , height : 0 } ) ;
25+ const [ width , setWidth ] = useState ( 0 ) ;
2326
2427 useEffect ( ( ) => {
2528 onTickClickRef . current = onTickClick ;
@@ -33,10 +36,7 @@ export default function Barplot({ data, onTickClick, disabled }) {
3336 if ( ! el ) return ;
3437 const update = ( ) => {
3538 const rect = el . getBoundingClientRect ( ) ;
36- setSize ( {
37- width : Math . max ( MIN_WIDTH , Math . floor ( rect . width ) ) ,
38- height : Math . max ( MIN_HEIGHT , Math . floor ( rect . height ) ) ,
39- } ) ;
39+ setWidth ( Math . max ( MIN_WIDTH , Math . floor ( rect . width ) ) ) ;
4040 } ;
4141 update ( ) ;
4242 const ro = new ResizeObserver ( update ) ;
@@ -51,49 +51,40 @@ export default function Barplot({ data, onTickClick, disabled }) {
5151 d3 . select ( svgEl ) . selectAll ( "*" ) . remove ( ) ;
5252
5353 if ( ! data || data . length === 0 ) return ;
54- if ( size . width === 0 || size . height === 0 ) return ;
54+ if ( width === 0 ) return ;
5555
5656 const plotData = data . slice ( 0 , 50 ) ;
57+ const n = plotData . length ;
5758
58- const { width : outerW , height : outerH } = size ;
59- const width = outerW - MARGIN . left - MARGIN . right ;
60- const height = outerH - MARGIN . top - MARGIN . bottom ;
59+ const outerW = width ;
60+ const contentHeight = n * ROW_HEIGHT - BAR_GAP ;
61+ const outerH = MARGIN . top + contentHeight + MARGIN . bottom ;
62+ const innerWidth = outerW - MARGIN . left - MARGIN . right ;
6163
6264 d3 . select ( svgEl )
6365 . attr ( "viewBox" , `0 0 ${ outerW } ${ outerH } ` )
64- . attr ( "preserveAspectRatio" , "none" )
6566 . attr ( "width" , outerW )
6667 . attr ( "height" , outerH ) ;
6768
68- // Fill the full height. If bandwidth would exceed MAX_BANDWIDTH, increase
69- // padding instead of shrinking the range — bars stay capped, gaps absorb
70- // the extra space.
71- const n = plotData . length ;
72- const naturalBandwidth = ( height / ( n + BAND_PADDING ) ) * ( 1 - BAND_PADDING ) ;
73- const bandPadding =
74- naturalBandwidth > MAX_BANDWIDTH
75- ? ( height - MAX_BANDWIDTH * n ) / ( height + MAX_BANDWIDTH )
76- : BAND_PADDING ;
77-
7869 const svg = d3
7970 . select ( svgEl )
8071 . append ( "g" )
8172 . attr ( "transform" , `translate(${ MARGIN . left } ,${ MARGIN . top } )` )
8273 . attr ( "fill" , "white" ) ;
8374
8475 const xMax = d3 . max ( plotData , ( d ) => d . prob ) || 1 ;
85- const xScale = d3 . scaleLinear ( ) . domain ( [ 0 , xMax ] ) . range ( [ 0 , width ] ) ;
76+ const xScale = d3 . scaleLinear ( ) . domain ( [ 0 , xMax ] ) . range ( [ 0 , innerWidth ] ) ;
8677 const yScale = d3
8778 . scaleBand ( )
8879 . domain ( plotData . map ( ( d ) => d . text ) )
89- . range ( [ 0 , height ] )
90- . padding ( bandPadding ) ;
80+ . range ( [ 0 , contentHeight ] )
81+ . paddingInner ( PADDING_INNER )
82+ . paddingOuter ( 0 ) ;
9183
9284 const yAxis = svg . append ( "g" ) . call ( d3 . axisLeft ( yScale ) ) ;
93- const tickFontSize = Math . min ( 15 , Math . max ( 5 , yScale . bandwidth ( ) * 0.9 ) ) ;
9485 yAxis
9586 . selectAll ( "text" )
96- . style ( "font-size" , `${ tickFontSize } px` )
87+ . style ( "font-size" , `${ TICK_FONT_SIZE } px` )
9788 . style ( "font-weight" , "500" ) ;
9889
9990 yAxis
@@ -132,7 +123,6 @@ export default function Barplot({ data, onTickClick, disabled }) {
132123 . attr ( "height" , yScale . bandwidth ( ) )
133124 . attr ( "width" , ( d ) => xScale ( d . prob ) ) ;
134125
135- const labelFontSize = Math . min ( 14 , Math . max ( 5 , yScale . bandwidth ( ) * 0.9 ) ) ;
136126 svg
137127 . selectAll ( ".label" )
138128 . data ( plotData )
@@ -143,14 +133,14 @@ export default function Barplot({ data, onTickClick, disabled }) {
143133 . attr ( "y" , ( d ) => yScale ( d . text ) + yScale . bandwidth ( ) / 2 )
144134 . attr ( "dy" , "0.35em" )
145135 . attr ( "dx" , "8px" )
146- . style ( "font-size" , `${ labelFontSize } px` )
136+ . style ( "font-size" , `${ LABEL_FONT_SIZE } px` )
147137 . text ( ( d ) => ( Number ( d . prob ) === 0 ? "<0.01" : d . prob ) ) ;
148- } , [ data , size ] ) ;
138+ } , [ data , width ] ) ;
149139
150140 const hasData = data && data . length > 0 ;
151141
152142 return (
153- < div ref = { containerRef } className = "w-full h-full relative" >
143+ < div ref = { containerRef } className = "w-full h-full relative overflow-y-auto " >
154144 { hasData ? (
155145 < svg ref = { svgRef } className = "barplot-svg block" />
156146 ) : (
0 commit comments