Skip to content

Conversation

@vincentarelbundock
Copy link
Collaborator

@vincentarelbundock vincentarelbundock commented Jan 12, 2026

Fix #540

Problem

PR #438 fixed a long-standing issue where resizing the plot window
would cause legends to become misaligned. The fix worked by wrapping
coordinate-dependent calculations inside recordGraphics(), which R
replays when the device is resized.

PR #536 refactored the legend code but inadvertently broke this fix.
The refactoring moved critical calculations outside of
recordGraphics():

  # These now happen OUTSIDE recordGraphics - only run once at initial  
  size:                                                                 
  legend_outer_margins(legend_env, apply = draw)     # margin adjustment
  legend_env$inset = measure_legend_inset(legend_env) # inset           
  calculation                                                           
  plot.new()  # plot refresh                                            
                                                                        
  # INSIDE recordGraphics - only this replays on resize:                
  recordGraphics({                                                      
    do.call("legend", legend_env$args)  # draws with STALE inset values 
  }, ...)                                                               
                      

When the window is resized, only the final legend() call replays, but
it uses margin/inset values calculated for the original window size.

Fix

Move the margin application, inset calculation, and plot refresh back
inside recordGraphics() via a new tinylegend() helper function:

  # Initial setup only (first plot.new, measurement):                   
  legend_outer_margins(legend_env, apply = FALSE)                       
                                                                        
  # Store base values for replay:                                       
  legend_env$omar_base = legend_env$omar                                
  legend_env$ooma_base = legend_env$ooma                                
  legend_env$inset_base = legend_env$args[["inset"]]                    
                                                                        
  # Everything else inside recordGraphics:                              
  recordGraphics(                                                       
    tinylegend(legend_env),  # recalculates margins, inset, then draws  
    list = list(legend_env = legend_env),                               
    env = getNamespace("tinyplot")                                      
  )                                                                                                                              

The tinylegend() function resets to base values on each replay, then
recalculates soma, applies margins, calculates inset, and
draws—ensuring correct positioning after resize.

@vincentarelbundock
Copy link
Collaborator Author

Disclaimer: This solution was 1-shotted by Claude Code after I fed it .diff files for the two PRs mentioned above.

I have reviewed the code. The changes seem pretty straightforward and, AFAICT, "correct".

I have also tested it locally and resizing the Positron window no longer cuts-off the legend.

@grantmcdermott
Copy link
Owner

Code LGTM too. Local testing confirms the fix.

Thanks for the quick turnaround @vincentarelbundock (and Claude)!

@grantmcdermott grantmcdermott merged commit 62463c0 into grantmcdermott:main Jan 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: Legend margins are broken

2 participants