Skip to content

Add custom size legend to scatterPlot when point size encodes a numeric column#293

Merged
j-andrews7 merged 11 commits into
scatterPlot_sizelegendfrom
copilot/fix-size-legend-issue
Jun 15, 2026
Merged

Add custom size legend to scatterPlot when point size encodes a numeric column#293
j-andrews7 merged 11 commits into
scatterPlot_sizelegendfrom
copilot/fix-size-legend-issue

Conversation

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

plotly cannot render a native legend when marker size is mapped to a numeric variable (plotly.R#705), so the scatterPlot module silently dropped size-encoding information. This ports the manual circle legend already used by the DotPlot module to scatterPlot.

UI (dittoViz_ScatterPlot_module_ui.R)

  • New "Size By" selector in the Points section, restricted to numeric columns. dittoViz::scatterPlot()'s size accepts a number or a column name.
  • New "Size Legend X Position" input in the Legend section (default 1.02) that nudges the whole custom size legend along the x-axis, wired to the start_x argument of .custom_legend().
  • New "Size Legend Y Position" input in the Legend section (default 0.95) that nudges the whole custom size legend along the y-axis, wired to the start_y argument of .custom_legend().

Server (dittoViz_ScatterPlot_module_server.R)

  • size argument resolves to the selected column when size.by is set, else the numeric Point Size:
    size = if (!is.null(null.na.inputs$size.by)) null.na.inputs$size.by else isolate_fn(input$size),
  • Renders the legend via the existing .custom_legend() helper, which derives circle breaks from the plotted marker sizes (no-op when size.by is unset).
  • Passes the "Size Legend X Position" and "Size Legend Y Position" inputs through to .custom_legend()'s start_x/start_y so users can reposition the legend horizontally (when it overflows a narrow plot or drifts on a wide one) and vertically (to offset it from an overlapping color/shape legend). The earlier categorical auto-offset of start_y was removed in favor of the explicit input.
  • Size column added to hover data; reset handling added for the new inputs.

DotPlot module (plotthis_DotPlot_module_ui.R, plotthis_DotPlot_module_server.R)

  • Mirrors the scatterPlot change: adds a "Size Legend Y Position" input (default 0.95) wired to .custom_legend()'s start_y, alongside the existing "Size Legend X Position" input, with matching reset handling.

.custom_legend() helper (plot_mods.R)

  • New start_y parameter (default 0.95) controls where the legend column begins, allowing the caller to vertically offset it from an overlapping color/shape legend; invalid values fall back to the default.
  • New start_x parameter (default 1.02) anchors the legend column (circles, labels, and title) horizontally so the whole set can be nudged along the x-axis; invalid values fall back to the default.
  • The legend title is now centered over the column (x_pos + 0.02 instead of x_pos + 0.1) so it no longer drifts off the plot area.
  • Numeric labels are now offset from each circle by a fixed pixel distance — the glyph's rendered radius plus a constant gap — using the annotation's xshift (measured in pixels) rather than a relative paper-space offset. This keeps the marker-to-label spacing constant regardless of plot width, instead of drifting away from the markers on wide plots and crowding them on narrow ones.
  • Legend annotations are now appended directly to the built plotly layout instead of via deferred add_annotations(). This fixes label annotations being duplicated at the same position (and prevents re-duplication by later plotly_build() calls).
  • When point size maps to a column, ggplotly joins the color and size guide titles (e.g. color<br />size); the helper now strips the size variable from that combined title so the color legend shows only the color variable.

Notes

  • size.min/size.max are intentionally not implemented: dittoViz::scatterPlot() exposes no size-range parameter (unlike plotthis DotPlot), so honoring them would require post-hoc rescaling of marker sizes — better addressed upstream and flagged as non-critical in the issue.
  • Tests added in tests/testthat/test-scatterPlot.R, plus .custom_legend regression tests in tests/testthat/test-plot_mods.R covering de-duplication, title stripping, the start_y offset (including invalid-value fallback), the start_x horizontal shift (including invalid-value fallback), and the fixed pixel-based label xshift (left-anchored, positive, and growing with glyph size); man/ regenerated via roxygen2.

Copilot AI changed the title [WIP] Fix size legend not displaying for altered point sizes Add custom size legend to scatterPlot when point size encodes a numeric column Jun 12, 2026
Copilot AI requested a review from j-andrews7 June 12, 2026 22:04
@j-andrews7

Copy link
Copy Markdown
Owner

@copilot Remove the margin adjustment, as it doesn't help anything. In addition, when used with the color legend, they heavily overlap and need to be positioned:

image

While I don't expect perfection, try to better offset them vertically.

In addition, the legend annotations seem to be doubled, i.e. the text annotations are duplicated in the exact same position, which is annoying when trying to reposition as the top one has to be deleted first.

Lastly, the color legend title includes both the color and size variables (rather than just the color variable).

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Remove the margin adjustment, as it doesn't help anything. In addition, when used with the color legend, they heavily overlap and need to be positioned:

<img width="155" he...

Addressed in d8... (latest commits):

  • Margin: removed the margin.r = 140 default from the scatterPlot UI.
  • Vertical overlap: .custom_legend() now takes a start_y argument, and scatterPlot starts the size legend lower (0.45 vs 0.95) whenever a categorical color/shape legend is present, so the two no longer sit on top of each other.
  • Doubled annotations: the labels were duplicated because the deferred add_annotations(..., font = list(size, color)) call emitted each label twice. The legend annotations are now assembled and appended directly to the built plotly layout, so each entry appears exactly once.
  • Color legend title: when point size maps to a column, ggplotly joins the color and size guide titles (e.g. grp<br />val); .custom_legend() now strips the size variable from that combined title, leaving just the color variable.

Verified the de-duplication, title stripping, and offset behaviors with plotly and added regression tests.

@j-andrews7

Copy link
Copy Markdown
Owner

@copilot the custom size legend title tends to be horizontally offset to a degree where it's sometimes not visible on the plot area:

image

In addition, the text for each marker can sometimes be rather far from the actual markers depending on plot re-sizing (e.g. when the plot is very wide).

Lastly, when the plot is made more narrow, the size legend can overlap the plot area, requiring manual movement of all markers and text for the legend:

image

Either dynamically keep the legend from the edge of a plot area in a sensible way, OR, perhaps more simply, provide a "start_x" parameter to .custom_legend wired up to an input that allows the whole set to be nudged along the x-axis.

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot the custom size legend title tends to be horizontally offset to a degree where it's sometimes not visible on the plot area:

<img width="121" height="302" alt="image" src="h...

Added a start_x parameter to .custom_legend, wired up to a new "Size Legend X Position" input in the scatterPlot Legend section, so the whole legend column (circles, labels, and title) can be nudged along the x-axis to keep it on the plot area on narrow/wide plots. I also recentered the legend title over the column (it was offset far to the right at x_pos + 0.1, now x_pos + 0.02) so it no longer drifts off the plot. (95414a8 → latest commit)

@j-andrews7

Copy link
Copy Markdown
Owner

@copilot Make start_y similarly adjustable, extend to DotPlot module, and remove the categorical start_y adjustment logic for simplicity.

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Make start_y similarly adjustable, extend to DotPlot module, and remove the categorical start_y adjustment logic for simplicity.

Done in 0f3b70d. Added a "Size Legend Y Position" input (default 0.95) to both the scatterPlot and DotPlot modules, wired to .custom_legend()'s start_y (which now validates and falls back when given an invalid value), and removed the categorical auto-offset logic in scatterPlot.

@j-andrews7

Copy link
Copy Markdown
Owner

@copilot try to scale the size legend marker text label spacing from the actual marker by a specific value rather than a relative one (or calculate the relative distance based on total plot width). Currently, it requires manual adjustment based on plot width (when wide, needs to be brought closer, when narrow, the opposite).

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot try to scale the size legend marker text label spacing from the actual marker by a specific value rather than a relative one (or calculate the relative distance based on total...

Done in e1fb2d0. The numeric labels are now anchored at each circle's x in paper space but offset purely in pixels via the annotation xshift (the glyph's rendered radius plus a constant 6px gap). Because xshift is measured in pixels, the marker-to-label spacing stays fixed regardless of plot width, instead of growing on wide plots and crowding on narrow ones as the previous relative paper-space offset did.

@j-andrews7 j-andrews7 marked this pull request as ready for review June 15, 2026 18:12
@j-andrews7

Copy link
Copy Markdown
Owner

Closes #10.

@j-andrews7 j-andrews7 merged commit c4a0345 into scatterPlot_sizelegend Jun 15, 2026
4 checks passed
@j-andrews7 j-andrews7 deleted the copilot/fix-size-legend-issue branch June 15, 2026 18:13
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.

size legend doesn't display when point size is altered

2 participants