Skip to content

implementing geom_label_aligned #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open

implementing geom_label_aligned #203

wants to merge 61 commits into from

Conversation

suhaani-agarwal
Copy link
Contributor

@suhaani-agarwal suhaani-agarwal commented Jun 6, 2025

resolving #174

This PR introduces a new geom, geom_aligned_boxes, as proposed in the issue. (name of the geom can be changed if needed)

Status: Initial implementation — still a work in progress and requires a lot of modifications and bug fixing. I am pushing the code made till now to seek guidance.
Current issue: While testing with examples, I noticed that the generated HTML renders:

<g class="geom1_alignedboxes_plot"><g class="PANEL1"></g></g>

The PANEL1 elements are empty, no boxes are being rendered. I suspect I may be missing a key step in correctly registering or drawing the new geom. Any guidance on what might be causing this?

@suhaani-agarwal
Copy link
Contributor Author

I have implemented the new geom - geom_aligned_boxes, which takes x, y, and label as required aesthetics, along with optional ones like fill, stroke, and color etc. On the JavaScript side, it dynamically measures the text size and draws a rectangle around it positioned by quadprog on renderer side, to form a label box. an example of the structure generated in the rendered HTML is as follows:

<g class="geom1_alignedboxes_pricesPlot">
  <g class="PANEL1">
    <g class="geom" style="opacity: 1; stroke: black; stroke-width: 2; fill: rgb(248, 118, 109);">
      <rect x="135.0775" y="226.1861" width="50.9219" height="28.5156" stroke-width="1" rx="0" ry="0"></rect>
      <text x="160.5385" y="244.4439" font-size="12px" style="text-anchor: middle;" stroke-width="1">Apple</text>
    </g>
  </g>
</g>

Although most of the initial implementation is complete, the positioning of these boxes using quadprog.js is not yet accurate. The elements are not restricted to the axis boundaries. I plan to refine the constraints in the quadratic programming setup to ensure correct and bounded placement. Once that is done, I will make the renderer tests for this geom.

Please let me know if the current structure and approach are appropriate or if any modifications are required.

@suhaani-agarwal
Copy link
Contributor Author

suhaani-agarwal commented Jun 12, 2025

I have improved the function for optimised positioning of the boxes that collided. For this, I referenced how the directlabels R package handles label positioning and collision avoidance , particularly this qp.labels and this source

an example of this new geom is below
The source code for this example is : here
Here I purposefully made 3 labels collide and they have been positioned in a way that they dont overlap and are visible. (by the position optimization functions)

image

@suhaani-agarwal
Copy link
Contributor Author

suhaani-agarwal commented Jun 12, 2025

I am still working on some edge cases for the positioning functions that uses quadratic programming. (like examples with horizontal alignment are not yet being positioned in the most optimal way)

@suhaani-agarwal suhaani-agarwal marked this pull request as ready for review June 13, 2025 18:22
@suhaani-agarwal suhaani-agarwal linked an issue Jun 13, 2025 that may be closed by this pull request
@suhaani-agarwal
Copy link
Contributor Author

suhaani-agarwal commented Jun 14, 2025

An example of how geom_aligned_boxes look is as follows :

Screen.Recording.2025-06-15.at.12.08.29.AM.mov
  • I have to work on how the text looks like. right now it looks a little extra bold. I will correct that.
  • Shall I add this example as a test (testing that the geom aligned boxes do not collide even after interactions and iterations) ?

@suhaani-agarwal suhaani-agarwal requested a review from tdhock June 17, 2025 12:28
@suhaani-agarwal
Copy link
Contributor Author

Hi @tdhock , can you please review this PR when you get time?

@suhaani-agarwal
Copy link
Contributor Author

Is that what is expected and should happen? Then geom_label_aligned will only be useful when we have labels on exactly the same x or y positions needing collision avoidance in only one axes.

Exact-position grouping should be preferred/default.

If there is a threshold, then it should be settable by user, and setting it to zero should result in exact position grouping.

I would prefer not having a threshold, since it seems difficult to control and to test.

Got it! I have modified the grouping code to have the exact-position grouping mechanism now.

@tdhock
Copy link
Collaborator

tdhock commented Jul 16, 2025

I added an expectation about font-size that fails using the current code on the World Bank test case.
geom_text_aligned(size=5) has no effect (font-size: 12px is used no matter what the user specifies as size param). can you please fix?

Also there is a problem with the smooth transitions. When the geom appears, it flies from the upper left of the plot, but it should just appear instantly (no smooth transition).
See video:
https://github.com/user-attachments/assets/069eaf7e-be62-4924-a5d9-12708edc97e8

@suhaani-agarwal
Copy link
Contributor Author

I added an expectation about font-size that fails using the current code on the World Bank test case.
geom_label_aligned(size=5) has no effect (font-size: 12px is used no matter what the user specifies as size param). can you please fix?

This issue has been fixed now, the user will now be able to specify the size parameter for fontsize of the text.
for example for geom_label_aligned(size = 5) in the same plot, we now get:

image

Comment on lines +341 to +343
initial_font_sizes_num <- sapply(initial_text_nodes, function(node) {
as.numeric(gsub("px", "", xmlGetAttr(node, "font-size")))
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please use getStyleValue instead? (if it works? I think it should)

  ts_size <- getStyleValue(info$html, '//g[@class="geom2_labelaligned_lifeExpectancyPlot"]//text', "font-size")

if (alignment == "vertical") {
return d.optimizedPos - d.boxHeight / 2;
} else {
return d.scaledY - d.boxHeight * get_vjust(d);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vjust is not supported anywhere else in the code -- please add test cases for values other than 0.5

var fontsize = d.size;
var textSize = measureText(d.label, fontsize, d.angle, textStyle);
d.boxWidth = textSize.width;
d.boxHeight = textSize.height;
Copy link
Collaborator

@tdhock tdhock Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this boxHeight definition seems reasonable, but when the boxes are rendered, there seems to be some extra space under the text, do you have any idea why?
image
above text size=20
below text size=40
image

#' @inheritParams geom_point
#' @param label_r Radius of rounded corners. Defaults to 0.15 lines.
#' @param alignment One of "vertical" (QP on Y axis) or "horizontal" (QP on X axis)
#' @param min_distance Minimum distance between boxes in native units.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please clarify units for min_distance.
it is written native units, which I interpret to mean the units displayed on the axis.
However I tried size=40 with min_distance=5 and the distance between boxes is clearly smaller than 5 axis units.
image

Comment on lines +182 to +183
expect_equal(as.numeric(attrs[["rx"]]), 5)
expect_equal(as.numeric(attrs[["ry"]]), 5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is rx 5 if label_r=9 ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please change so that rx/ry are same as label_r, or justify this choice.

@tdhock
Copy link
Collaborator

tdhock commented Jul 18, 2025

This is really excellent work! Please fix the few things I mentioned before I merge. We are almost done.

Toby Dylan Hocking added 2 commits July 18, 2025 10:29
Comment on lines +94 to +99
test_that("label size is correct", {
ts_size <- getStyleValue(info$html, '//g[@class="geom2_labelaligned_lifeExpectancyPlot"]//text', "font-size")
expect_equal(ts_size, rep(5, nrow(label_data_line)))
scatter_size <- getStyleValue(info$html, '//g[@class="geom4_labelaligned_worldbankAnim"]//text', "font-size")
expect_equal(scatter_size, rep(10, nrow(label_data_line)))
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this block to test font-size attribute.

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.

direct labels aligned boxes qp solver
2 participants