-
Notifications
You must be signed in to change notification settings - Fork 109
Description
System details:
Positron and OS details:
Positron Version: 2025.04.0 (Universal) build 250
Code - OSS Version: 1.98.0
Commit: 627b3d723ee0b3a5bd86731a75902d419cafe22d
Date: 2025-04-03T16:31:38.728Z
Electron: 34.2.0
Chromium: 132.0.6834.196
Node.js: 20.18.2
V8: 13.2.152.36-electron.0
OS: Darwin x64 24.4.0
Interpreter details:
R 4.5.0
Describe the issue:
Positron integration with tinyplot is much better now, thanks to #6748 (on your side) and grantmcdermott/tinyplot#377 (on ours).
However, one remaining incompatibility is adding layers to a faceted tinyplot
. I think I know what the problem is, although am not entirely sure how to resolve and so I would be grateful for any suggestions. But first let me take a quick detour and explain how plot layering works in tinyplot...
Note: I am using the dev version of tinyplot here, which is available from R-universe: install.packages("tinyplot", repos = "https://grantmcdermott.r-universe.dev")
Simple layering (no facets)
Consider the following code:
library(tinyplot)
plt(mpg ~ wt | factor(am), mtcars)
plt_add(type = 'h')
The end result will be a "lollipop" plot, where the second call plt_add(type = 'h')
layers on vertical histogram lines on top of the existing scatter plot. Underneath the hood, tinyplot has captured the arguments of the immediately preceding call and spliced in any new arguments provided by plt_add()
, before calling tinyplot(<updated arguments>, add = TRUE)
. The approach is very simple, but it works reliably across different IDEs and devices, including Positron. So far, so good.
Faceted layering
Things are slightly more complicated when it comes to layering on top of a faceted tinyplot, though. The reason is that we forcibly reset the plot layout after a faceted plot is called.1 To accommodate plt_add
with the fact that we have reset the plot layout, we therefore grab the par
settings as they were from just before the reset step (which we have saved in an internal environment list) and re-instantiate them as part of the next tinyplot(<updated arguments>, add = TRUE)
call.
While the internal layering steps for this faceted version are more complicated because of the par
reset/restore steps, the underlying approach is the same. And it, too, generally works as expected across every IDE and graphics device that we have tested... with the exception of Positron. For example, here you can see that the points and histogram lines are misaligned.
plt(mpg ~ wt | factor(am), data = mtcars, facet = 'by')
plt_add(type = 'h')
What's gone wrong?
I'm not 100% certain, but I believe the problem stems from the fact that the Positron graphics pane relies on a recordPlot
+ replayPlot
rendering process, as explained to me here.2 Because the par
settings are inherited from the "dummy"png()
device that is created to initiate the plot, rather than the (resized) Positron/Ark graphics device itself, the restored parameters that are passed to any added layers are wrong. Put differently, plt_add()
is going to inherit par
values from the original 5x5 PNG rather than the actual graphics pane.
Does my hypothesis here seem correct? If so, do you have suggestions for grabbing the par
setting from the actual rendered Positron/Ark graphics device, rather than the dummy png()
device?
Alternatively, if my guess here is wrong and someone knows the real reason why the misalignment is happening, I'd be grateful for suggestions.
Thanks in advance.
Expected or desired behavior:
The added components should be aligned. Simple reprex example (call from Positron, as it happens).
library(tinyplot)
plt(mpg ~ wt | factor(am), data = mtcars, facet = 'by')
plt_add(type = 'h')
Created on 2025-04-21 with reprex v2.1.1
Were there any error messages in the UI, Output panel, or Developer Tools console?
No.
Footnotes
-
Otherwise users will be confronted by multipanel plot windows for any subsequent plots, unless they manually reset
par(mfrow)
themselves. ↩ -
Essentially, Positron opens up a dummy
png()
device that is used to capture all the plotting steps viarecordPlot
, which is then passed to the actual graphics pane viareplayPlot()
afterwards for rendering. ↩