Comments (11)
Yes, I had a similar thought that I described back in #11, but it was very half - baked. I think the key features would need to be:
- The path / line itself remains unchanged
- The text follows the curve's 'centre of mass',
- The degree of smoothing should be user - controllable
One of the problems is that the anchor points themselves may sit on angles that are not representative of the curve as a whole. This would mean that the angles are wrong at the start and end of the path. It might be more appropriate to perform some kind of regression on the line around the anchor points and in the midpoint to get an accurate angle and x,y placement. This would be complex but not intractable.
from geomtextpath.
Having had a think about this, I wonder if we just smooth the path that the text is drawn on with a kernel smoother. Remember we have to handle noisy paths as well as noisy x-ordered lines. So we could have something like this:
library(geomtextpath)
#> Loading required package: ggplot2
set.seed(3)
x <- runif(5)
y <- runif(5)
x <- spline(seq_along(x), x, xout = seq(1, length(x), len = 1000))$y
y <- spline(seq_along(y), y, xout = seq(1, length(y), len = 1000))$y
theta <- geomtextpath:::.angle_from_xy(x, y, norm = TRUE)
offset <- rnorm(1000) * 0.01
df <- data.frame(x = x + offset * cos(c(1, theta)),
y = y + offset * sin(c(1, theta)),
label = "Path label")
p <- ggplot(df, aes(x, y, label = label)) +
geom_textpath(size = 8)
p
A simple kernel smoother would be something like this (I know this is very inefficient, but it's just for demonstration):
path_smoother <- function(data, width = 0.02) {
x <- data$x
y <- data$y
dist <- geomtextpath:::.arclength_from_xy(x, y)
sd <- max(dist) * width
data$x <- sapply(dist, function(i) {
dn <- dnorm(dist, mean = i, sd = sd)
sum(x * dn/sum(dn))
})
data$y <- sapply(dist, function(i) {
dn <- dnorm(dist, mean = i, sd = sd)
sum(y * dn/sum(dn))
})
data
}
It does a pretty good job of picking out the signal from the noise:
p + geom_textpath(data = path_smoother(df),
color = "red", size = 8, vjust = 1.5)
And of course it's adjustable with the width parameter.
If you find a more efficient way to do it, that would be great, but this makes a solid (if slow) fallback implementation.
Created on 2021-12-01 by the reprex package (v2.0.0)
from geomtextpath.
Hi Allan,
I'm sorry I've been absent for the past few days, I haven't really had the time to look at / work on new code.
I agree with you on most parts, except for the control over smoothing in the sense of curved smoothing. The reason I don't agree is because I'm of the opinion that if a user wants smoothing, they can just use stat_smooth()
or related stats to smoothen out a curve of noisy data. I think we can define some stat_*()
functions that do this, but I don't really see the need to have this implemented at the grob level.
Instead, I'm imagining the dumb textpath as something that finds a straight line through a noisy piece of curve and places the text on that straight line. For example, if you imagine the curve is a circle, I'd want to find the tangent line to that circle at some user-defined point. That way, it only needs a single angle calculation per label instead of per letter, which simplifies the problem (and why I've termed it 'dumb', as opposed to the smarter curve calculations we currently have in textpathGrob()
). The reason that this would need draw-time / makeContent calculations, is because the text-width would determine the parts of the curve that are considered.
A benefit of the straight-line approach is that we can accommodate plotmath/expressions as you mentioned in the second suggestion in #25
from geomtextpath.
Hi Teun
There is no need to apologise at all. This project is just something we can do for enjoyment when we have the time, and when real-life commitments don't get in the way.
I see your point about the straight-line text. This should be fairly easy to do at grob level - I can have a look at this next, since getting plotmath working even in a basic form would be nice. With one eye to the future, it turns out that it isn't too difficult to convert plotmath to svg, so there may be a more sophisticated way to get plotmath to follow curves. Incidentally, it's interesting to see that there is an svg entity called textPath
, which works by associating a text string to a path, and looks as though its implemented in a similar way to our textpaths.
In any case, I think it's a good idea to have a flat-but-angled label as a "bail-out" for difficult curves (and difficult labels). This could maybe even be an option for automatically fixing the "offset exceeds curvature" cases.
Does this really need its own geom, or could it be an option in geom_textpath
, since the implementation is likely to be inside the textpath Grob?
from geomtextpath.
The latest commit includes, as a stepping stone to this idea, a keep_straight
parameter. This simply passes the string through measure_exp
so that all of the glyphs are treated as a single unit. We will still need to ensure the angle matches the overall trend of the plot, but it should be possible to do that within makeContent
,
Current behaviour is:
library(ggplot2)
library(geomtextpath)
p <- ggplot(economics, aes(date, unemploy)) +
geom_path(colour = "grey")
p + geom_textpath(
aes(label = "Decline", group = 1),
hjust = 0.6, size = 5, include_line = FALSE, vjust = 0
)
#> Warning: The text offset exceeds the curvature in one or more paths. This will result in
#> displaced letters. Consider reducing the vjust or text size, or use the hjust
#> parameter to move the string to a different point on the path.
p + geom_textpath(
aes(label = "Decline", group = 1),
hjust = 0.6, size = 5, include_line = FALSE, vjust = 0,
keep_straight = TRUE
)
Created on 2021-12-05 by the reprex package (v2.0.0)
from geomtextpath.
I have implemented angle smoothing based on a simple lm
, so this should work in approximately the same way as subsetting with geom_smooth
. It is called automatically if either expressions are used, or if keep_straight = TRUE
. It's still a little temperamental, since it only uses one string-length worth of path data for the regression. Still, it seems to work tolerably well for most use cases, and at least the infrastructure is now in place so we can improve the algorithm. The calculations are inside a new function called .project_flat
that replaces .project_text
when there are expressions or keep_straight = TRUE
To give an idea of how it positions text, I have created a little animation:
library(gganimate)
library(geomtextpath)
anim <- ggplot(within(rbind(economics, economics),
hjust <- rep(c(1, 0), each = 574)),
aes(date, unemploy, hjust = hjust)) +
geom_path(colour = "grey") +
geom_textpath(
aes(label = "Decline", group = 1),
size = 5, include_line = FALSE, vjust = 0, keep_straight = TRUE
) +
transition_states(hjust,
transition_length = 30,
state_length = 1)
myanim <- animate(anim, fps = 50, duration = 30)
save_animation(myanim, "unemploy.gif")
unemply.gif
from geomtextpath.
I wasn't happy with the above behaviour. The problem is that the offset path is often inverted (exceeds the curvature). For that reason, I think we should use localized path smoothing. I am happier with the results, since they are much more stable, but they still aren't perfect. I think the approach is correct, though we could perhaps change the specific algorithm:
from geomtextpath.
Hi Alan, it looks like you've made some nice progress on this! I agree that lm()
might not be the best solution, but from the theoretical consideration that the error is expected to be in one direction (in the dependent variable, i.e. y
) and not in the other. I came across this method for polygon offsetting, which is also based on the bisector, but it appears it can handle excess curvature gracefully. I'm not saying this is a neat fit with how we've implemented offsetting now, but it can be food for thought.
from geomtextpath.
Thanks Teun - I'll have a look. I was using lm
to regress y on t and x on t separately (where t is just seq_along(x)
), but I gave up on this approach anyway, since the offset / curvature problem was getting in the way. I was thinking that something like a concave hull might be closer to what we need, but that won't work for self-intersecting paths.
The current method gives a workable (and very user-friendly) solution to your example at the moment, and of course it works a lot better on less jagged lines. If we come up with a better algorithm, at least we know it should slot into the code fairly easily.
By workable, I guess I mean we just need to tweak the hjust to find a reasonable point:
library(ggplot2)
library(geomtextpath)
p <- ggplot(economics, aes(date, unemploy)) +
geom_path(colour = "grey")
p + geom_textpath(
aes(label = "Decline", group = 1),
hjust = 0.53, size = 5, include_line = FALSE, vjust = 0
)
p + geom_textpath(
aes(label = "Decline", group = 1),
hjust = 0.53, size = 5, include_line = FALSE, vjust = 0,
keep_straight = TRUE
)
Created on 2021-12-07 by the reprex package (v2.0.0)
from geomtextpath.
Is there merit in developing the dumb textpath placement further? If you watch the second animation (current behaviour), it looks as though label placement will be good in most instances, and never looks bad, except maybe at the left edge of the path due to the narrowing. It would be nice if the label rose above the peaks as they went round, but it's not clear whether the behaviour in a narrow valley could be made much better. It seems to me that in either case a decent end result could be achieved by the user tweaking the vjust. Another option is to expose the smoothing bandwidth to the user, by replacing keep_flat
with a numeric smooth_text
. It would have a default NA that produced curved text, and any numeric value would produce straight text, with its angle determined by the smoothed path obtained by using that number as a bandwidth (currently the bandwidth is fixed)
from geomtextpath.
I haven't gotten around to playing with this yet, but we can close the issue for now as indeed it seems to work to satisfaction. I wouldn't say exposing the bandwidth parameter is necessary because if the user is into that level of customisation, they can simply use stat_smooth()
to get whatever they are after.
from geomtextpath.
Related Issues (20)
- geom_textsegment() with arrow() draws line to the top left of the plot HOT 4
- Denisty fill HOT 6
- label border? HOT 2
- geom_textsmooth() doesn't choose method if not explicitly set HOT 2
- Request something like "dodge" along path HOT 3
- Chinese character support HOT 3
- geom_labelsf() not recognizing aesthetics HOT 5
- While working with ragg, minuses aren't drawn HOT 2
- geom_textpath() always draw a empty box when label is ""
- Ignoring unknown parameters: text_smoothing HOT 10
- Feature request - multiple labels per line HOT 7
- geom_textsmooth computation fails if method argument is not specified HOT 3
- ggplot2 is separating size and linewidth HOT 2
- Negative values in geom_textcontour not appearing HOT 2
- geom_textlinerange HOT 1
- Question on angle HOT 1
- text_only in geom_labelsegment
- Feature request - avoid text overlapping
- two labels on same curve HOT 5
- straight argument unknown in geom_textsf() HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from geomtextpath.