Fix data-aspect ratio for responsive Bokeh/HoloViews plots#774
Open
SimonHeybrock wants to merge 5 commits intomainfrom
Open
Fix data-aspect ratio for responsive Bokeh/HoloViews plots#774SimonHeybrock wants to merge 5 commits intomainfrom
SimonHeybrock wants to merge 5 commits intomainfrom
Conversation
HoloViews' data_aspect and aspect="equal" do not produce correctly shaped data areas when plots use responsive=True inside Panel containers (upstream bug in Bokeh/HoloViews/Panel). Add a JS hook that reads x/y ranges from the Bokeh figure and adjusts the figure height so pixels_per_x_unit / pixels_per_y_unit matches the configured data_aspect. HoloViews' own data_aspect/match_aspect opts are deliberately not set for these cases, as match_aspect pads ranges to fit the (wrong) frame shape, creating a circular dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The frame-aspect JS hook now respects the user's StretchMode setting: Fill width adjusts height (default), Fill height adjusts width. This lets users pick the mode that fits their container shape, avoiding overflow when the data aspect would require growing beyond the container along the fixed axis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All non-free aspect types now use the JS hook for frame enforcement, since HoloViews aspect opts conflict with responsive mode in Panel. Two hook variants: fixed frame ratio (square, aspect=N) that ignores data ranges, and data-aspect (equal, data_aspect=N) that reads x/y ranges. Guard the fig.height/width assignment with try/catch to avoid _update_layout errors during initial render before the view tree is fully connected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HoloViews drops overlay-level opts (including hooks) when an Overlay contains 3+ DynamicMaps during internal resolution. Calling .collate() first produces a single DynamicMap whose outputs are plain Overlays, so opts applied afterwards land on the OverlayPlot and persist correctly. Also adds document-presence guards to the JS callbacks in frame_aspect.py to prevent TypeError on detached figures during tab switching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bad873b to
28984e8
Compare
YooSunYoung
reviewed
Mar 10, 2026
| for curve in result: | ||
| opts = hv.Store.lookup_options('bokeh', curve, 'plot').kwargs | ||
| assert opts.get('aspect') == 'square' | ||
| assert opts.get('responsive') is True |
Member
There was a problem hiding this comment.
Should we also check that the plot width/height are the same? Or is not possible/unecessary...?
Member
Author
There was a problem hiding this comment.
They are not - sizing is done in JS, so I don't think the actual size ever makes it to the Python-side of the plot?
nvaytet
reviewed
Mar 10, 2026
Member
There was a problem hiding this comment.
It's a shame we need to have all this extra javascript code 😞
What do the plots look like without this fix? Are they really that bad?
Member
Author
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
aspect="square",data_aspect,aspect="equal",aspect=N) do not produce correctly shaped data areas (frames) when plots useresponsive=Trueinside Panel containers (upstream bug spanning Bokeh/HoloViews/Panel)fig.heightorfig.widthviaCustomJScallbacks after each Bokeh layout passsquareandaspect=N— ignores data ranges) and data aspect (forequalanddata_aspect=N— reads x/y ranges to compute the correct frame shape)data_aspect/match_aspectopts are deliberately not set —match_aspectpads ranges to fit the (wrong) frame shape, creating a circular dependency with the hookStretchModesetting (Fill width/Fill height) controls which axis the figure fills and which the JS adjusts, avoiding overflow in either directionFixes #576.
Limitations
This does not eliminate all issues - depending on Zoom level we will get plots that slightly exceed width or height (and do not have correct aspect any more). If that is the case, the user must switch from stretch-width to stretch-height mode (or vice-versa) manually. Did not see a simple automatic solution for this, as naive approaches a broken due to some cycles.
Examples
A couple of exampes with
equalsetting. Note how the whitespace around the plots changes with zoom level:Extreme case (note that our sizing of non-plot elements is broken at this scale, unrelated to what is fixed here):
Test plan
squareproduces a square frameequalanddata_aspectplots have correct frame proportions matching data coordinate rangesFill widthvsFill heightstretch modes both workfreetype is unaffected_update_layoutconsole errors on page load🤖 Generated with Claude Code