AllanCameron / geomtextpath

Create curved text paths in ggplot2
https://allancameron.github.io/geomtextpath
Other
624 stars 24 forks source link

vjust and hjust as aesthetics in geom_textsf #93

Open glorious1 opened 1 year ago

glorious1 commented 1 year ago

I'm trying to use vjust and hjust as aesthetics to adjust the label positions individually on my river paths. No matter how I do it, the first value in the data column is used for all the labels, and the rest are ignored. For example

 geom_textsf( data=Rc, aes( label=name, hjust=Hjust, vjust=Vjust ) . . .
OR
geom_textsf( data=Rc, aes( label=name ), hjust=Rc$Hjust, vjust=Rc$Vjust . . .

In both cases, only the first value is used for all vjust/hjust. Is that a little bug in geomtextpath, or an issue in ggplot2?

The label= aesthetic works fine, and the size= aesthetic also works to individually control text size (although it works strangely inside aes()). Also, vjust and hjust do work (at least the second way) with geom_sf_text from sf package. Just not with geom_textsf.

geom_textsf does not list vjust/hjust as aesthetics sent to geom_textpath, but geom_textpath says it does understand the aesthetics vjust/hjust. So I'm a bit confused.

AllanCameron commented 1 year ago

Thanks for raising this. If I understand you correctly, the issue is that hjust and vjust aesthetics are not vectorized in geom_textsf. The following reprex should demonstrate that:

library(sf)
#> Linking to GEOS 3.9.3, GDAL 3.5.2, PROJ 8.2.1; sf_use_s2() is TRUE
library(geomtextpath)
#> Loading required package: ggplot2

df <- list(st_linestring(cbind(seq(-20, 20, 0.1), 80)),
           st_linestring(cbind(seq(-20, 20, 0.1), 85))) |>
  st_sfc(crs = "WGS84") |>
  st_sf(river = c("South river", "North river")) |>
  `st_geometry<-`("geometry") |>
  st_transform(3177)

df
#> Simple feature collection with 2 features and 1 field
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 331213.7 ymin: 8854109 xmax: 1057997 ymax: 9522189
#> Projected CRS: LGD2006 / Libya TM
#>         river                       geometry
#> 1 South river LINESTRING (331213.7 907494...
#> 2 North river LINESTRING (665243.6 952218...

ggplot(df) +
  geom_textsf(aes(label = river),  size = 6,
              hjust = c(0.1, 0.9),  # Note different hjust
              vjust = c(-0.1, 1.1)  # Note different vjust
              ) 

I think this may have been a conscious decision because some rows in an sf object may not be linestrings. For example, your sf object might have several polygons, some points, a couple of geometry collections and three linestrings. To vectorize hjust and vjust, you would need to have an entry for each row, which doesn't make sense with non-linestrings, and it would be confusing for users to have to specify dozens of hjust and vjust values just to correctly position the three text labels on the linestrings. A user would expect to only need three vjust and hjust values in this scenario, but I did not seriously explore a way to achieve this, since aesthetics all need to be the same length as the input data frame and it's not immediately apparent how to get round this. The "use the first value" approach was probably a stop-gap that I never got round to fixing.

I'm sure there's a way, but I'll need to think about it, and will leave this issue open meantime.

Created on 2023-07-12 with reprex v2.0.2

glorious1 commented 1 year ago

Thank you for considering and verifying this. Regarding the possible reason for setting it up this way: If a user has such mixed geometry, it would be easy to subset to just the LINESTRINGs with something like sfObj <- subset( sfObj, st_geometry_type( sfObj )=="LINESTRING" ). That's a small price to pay so that anyone can use hjust and vjust as aesthetics (which, if I understand ggplot2's use of the word, means that they can be supplied as vectors so there is a custom value for each feature).

It's not even necessary to subset I guess. Users who don't want to use them as aesthetics can simply give a value outside of aes(), like hjust=0.7.

AllanCameron commented 1 year ago

Yes, the mechanism already includes filtering out just the linestrings for special treatment. It's the specifics of the interface and implementation that will require a bit of thought.