@@ -3254,74 +3254,73 @@ def get_tool_id(tool, tool_type):
32543254 directional_tools = ('wheel_zoom' , 'pan' , 'zoom_in' , 'zoom_out' , 'box_zoom' )
32553255 if tool in directional_tools :
32563256 return (tool_type , 'both' )
3257- elif tool .startswith (('x' , 'y' )):
3258- if tool [1 :] in directional_tools :
3259- dimension = 'width' if tool .startswith ('x' ) else 'height'
3260- return (tool_type , dimension )
3257+ elif tool .startswith (('x' , 'y' )) and tool [1 :] in directional_tools :
3258+ dimension = 'width' if tool .startswith ('x' ) else 'height'
3259+ return (tool_type , dimension )
32613260 elif tool == 'auto_box_zoom' :
32623261 return (tool_type , 'auto' )
32633262 return tool
32643263 elif hasattr (tool , 'dimensions' ) and tool .dimensions :
32653264 return (tool_type , tool .dimensions )
32663265 elif hasattr (tool , 'description' ) and tool .description :
32673266 return (tool_type , tool .description )
3267+ return tool_type
3268+
3269+ def is_subcoordy_zoom (tool , tool_type ):
3270+ """Check if tool is a subcoordinate_y zoom tool."""
3271+ return (self .subcoordinate_y and isinstance (tool , _zoom_types ) and
3272+ 'hv_created' in tool .tags and len (tool .tags ) == 2 )
3273+
3274+ def process_tool (tool , skip_subcoordy_overlay_check = False ):
3275+ """Process a single tool and return whether to add it."""
3276+ if isinstance (tool , str ):
3277+ tool_type = TOOL_TYPES .get (tool )
32683278 else :
3269- return tool_type
3279+ tool_type = type (tool )
3280+
3281+ # Skip subcoordinate_y zoom tools already handled by subplots (overlay tools only)
3282+ if (skip_subcoordy_overlay_check and self .subcoordinate_y and
3283+ tool_type in _zoom_types and isinstance (tool , str ) and
3284+ not tool .startswith ('x' ) and tool .replace ('_tool' , '' ) in zooms_subcoordy ):
3285+ return False
32703286
3287+ tool_id = get_tool_id (tool , tool_type )
3288+
3289+ # Handle HoverTool deduplication by tooltips
3290+ if isinstance (tool , tools .HoverTool ):
3291+ if isinstance (tool .tooltips , bokeh .models .dom .Div ):
3292+ tooltips = tool .tooltips
3293+ else :
3294+ tooltips = tuple (tool .tooltips ) if tool .tooltips else ()
3295+ if tooltips in hover_tools :
3296+ return False
3297+ hover_tools [tooltips ] = tool
3298+ # Handle subcoordinate_y zoom tools
3299+ elif is_subcoordy_zoom (tool , tool_type ):
3300+ if tool .tags [1 ] in zooms_subcoordy :
3301+ return False
3302+ zooms_subcoordy [tool .tags [1 ]] = tool
3303+ self .handles ['zooms_subcoordy' ] = zooms_subcoordy
3304+ # Skip duplicate tools
3305+ elif tool_id in tool_ids :
3306+ return False
3307+
3308+ tool_ids .add (tool_id )
3309+ return True
3310+
3311+ # Collect tools from subplots
32713312 for key , subplot in self .subplots .items ():
32723313 el = element .get (key )
32733314 if el is not None :
32743315 el_tools = subplot ._init_tools (el , self .callbacks )
32753316 for tool in el_tools :
3276- if isinstance (tool , str ):
3277- tool_type = TOOL_TYPES .get (tool )
3278- else :
3279- tool_type = type (tool )
3280-
3281- tool_id = get_tool_id (tool , tool_type )
3282- if isinstance (tool , tools .HoverTool ):
3283- if isinstance (tool .tooltips , bokeh .models .dom .Div ):
3284- tooltips = tool .tooltips
3285- else :
3286- tooltips = tuple (tool .tooltips ) if tool .tooltips else ()
3287- if tooltips in hover_tools :
3288- continue
3289- else :
3290- hover_tools [tooltips ] = tool
3291- elif (
3292- self .subcoordinate_y and isinstance (tool , _zoom_types )
3293- and 'hv_created' in tool .tags and len (tool .tags ) == 2
3294- ):
3295- if tool .tags [1 ] in zooms_subcoordy :
3296- continue
3297- else :
3298- zooms_subcoordy [tool .tags [1 ]] = tool
3299- self .handles ['zooms_subcoordy' ] = zooms_subcoordy
3300- elif tool_id in tool_ids :
3301- continue
3302-
3303- tool_ids .add (tool_id )
3304- init_tools .append (tool )
3317+ if process_tool (tool ):
3318+ init_tools .append (tool )
33053319
33063320 # Add tools specified directly on the overlay
33073321 overlay_tools = self .default_tools + self .tools
33083322 for tool in overlay_tools :
3309- if isinstance (tool , str ):
3310- tool_type = TOOL_TYPES .get (tool )
3311- else :
3312- tool_type = type (tool )
3313-
3314- tool_id = get_tool_id (tool , tool_type )
3315-
3316- # Skip subcoordinate_y zoom tools that were already handled
3317- if (self .subcoordinate_y and tool_type in _zoom_types and
3318- isinstance (tool , str ) and not tool .startswith ('x' )):
3319- tool_name = tool .replace ('_tool' , '' )
3320- if tool_name in zooms_subcoordy :
3321- continue
3322-
3323- if tool_id not in tool_ids :
3324- tool_ids .add (tool_id )
3323+ if process_tool (tool , skip_subcoordy_overlay_check = True ):
33253324 init_tools .append (tool )
33263325
33273326 self .handles ['hover_tools' ] = hover_tools
0 commit comments