Hi Jonathon,
On the drag-resize concern:
I understand the concern. Looking at fromProtoBuf in image.R, the width and height in the protobuf are the
full dimensions (already including whatever scale the client applied), and they are restored unconditionally:
Code: Select all
private$.width <- image$width
private$.height <- image$height
if (image$path == '' || 'theme' %in% oChanges || 'palette' %in% oChanges)
private$.filePath <- NULL
else
private$.filePath <- image$path
So yes, if you were to add a width/height comparison to decide whether to clear filePath, a user drag-resize would be caught by that comparison too, so you cannot simply remove a size check without losing drag-resize invalidation.
However, when the user drags to resize, jamovi updates the widthScale / heightScale options and those appear in oChanges. So the fix would be to invalidate filePath when widthScale or heightScale appear in oChanges, the same way theme and palette are already handled on that line, rather than comparing the raw width/height values. That way:
- User drag-resize --> widthScale/heightScale in oChanges --> filePath cleared --> image re-renders
- Programmatic setSize() from .run()/.postInit() --> no oChanges entry --> filePath preserved --> image does not re-render
The current problem is that on each engine request a fresh R6 object starts at the YAML dimensions, while the stored protobuf has the dimensions from the last setSize() call. Any width/height comparison will see a mismatch and clear filePath, even though nothing meaningful changed. The mismatch only exists because the fresh object has not had setSize() applied yet.
On the R package / anticipating image size:
We are using the
meta R package for meta-analysis. The specific plot is a forest plot, but this is not unique to meta. It is a general characteristic of forest plots and similar composite graphics that embed tabular text alongside graphical elements.
Forest plots are fundamentally different from most R plots when it comes to sizing. A typical plot, like a ggplot scatter plot, uses
relative coordinates; the axes, points, and labels all scale proportionally to fill whatever device size you give it. You can render it at 500x400 or 800x600 and it looks fine either way, just bigger or smaller.
Forest plots do not work like that. They render study labels, effect estimates, confidence intervals, and summary statistics as
columns of text next to a graphical panel. These text columns use
fixed, absolute units (inches/cm) because font metrics are fixed; a character rendered at 12pt takes a specific number of inches regardless of the device size. The columns do not stretch or shrink to fill the device. If you give the device too little width, the text gets clipped. If you give it too much, you get empty whitespace. The same applies to height; each study row takes a fixed amount of vertical space, so a 5-study forest plot and a 50-study forest plot need very different heights.
This means the total dimensions of the plot are entirely determined by the content: the number of studies, the length of the longest label, the number of columns, font size, spacing, etc. You cannot set a fixed width/height in the YAML and have it work for all inputs.
While meta::forest() does have some internal logic to estimate the
height, it is a complex function that requires many parameters and in practice does not always produce the correct result. You end up with cropping or excessive whitespace. This is actually an
open issue in the meta package where other users have the same problem of needing to guess dimensions for export.
The correct and reliable approach is to
build the plot first, then
measure the actual grid object to extract its true dimensions. This is what we do: we render the forest plot into a grid grob, then use grid::convertWidth() and grid::convertHeight() to read back the exact width and height in inches. Other forest plot packages like forestploter use the same technique.
Even if we tried to write a custom function to anticipate the dimensions mathematically, it would be extremely fragile. The final size depends on the exact string width of every label and number in the table. Trying to calculate all of that analytically before rendering is not just complex, it's error-prone. Measuring the final rendered grid object is the only robust way to guarantee the plot won't be cropped and won't have excessive padding.
So we
cannot anticipate the image size from the source or options alone. We need to compute the model, render the plot, measure it, and then call setSize() with the measured dimensions. That is why setSize() must be called from .run(), and why the current invalidation creates the problem described above.
Thanks,
Nour