-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplotly-repel-mtcars.Rmd
More file actions
177 lines (137 loc) · 5.3 KB
/
plotly-repel-mtcars.Rmd
File metadata and controls
177 lines (137 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
---
title: "Getting Started With plotly.repel (mtcars)"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Getting Started With plotly.repel (mtcars)}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
`plotly.repel` brings **ggrepel-style** label repulsion to **interactive plotly** charts.
If you are used to `ggrepel::geom_text_repel()` and `ggrepel::geom_label_repel()`, the mental model here is similar:
- start with a scatter plot
- decide what points you want to label
- let the algorithm place labels to avoid overlaps
- tweak a few knobs when you need “more push” or “stay closer”
This vignette uses the built-in `mtcars` dataset so you can copy/paste and see results immediately.
## Setup
```{r}
library(plotly)
library(plotly.repel)
df <- mtcars
df$car <- rownames(mtcars)
```
## 1) The Baseline: Plotly Labels Overlap Quickly
```{r}
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers()
```
Now add labels for every point (this is intentionally too much):
```{r}
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers() %>%
add_text_repel(x = ~wt, y = ~mpg, text = ~car)
```
Even for `mtcars`, labeling everything can be busy. In real datasets, the best practice is to label only what matters.
## 2) Canonical Use: Label a Subset (Outliers / Highlights)
Here we label only “interesting” cars: light or very fuel-efficient.
```{r}
df$highlight <- with(df, wt < 2.2 | mpg > 25)
cols <- ifelse(
df$highlight,
plotly::toRGB("firebrick3", alpha = 0.85),
plotly::toRGB("grey50", alpha = 0.55)
)
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers(
marker = list(size = 9, color = I(cols))
) %>%
add_text_repel(
x = ~wt, y = ~mpg, text = ~car,
visibility = df$highlight,
on = c("render", "zoom", "resize"),
seed = 42
)
```
This is the workflow you will use most often:
- show all points
- label a subset
- re-solve on interaction when you care about “always readable”
Note: in this vignette, `visibility` is provided as a logical vector (one value per label). That is the simplest and most explicit way to select which points get labeled.
## 3) The Main Knobs (How To Tweak Repulsion)
Most plots work with defaults. When they do not, these parameters are the first place to look.
```{r}
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers(marker = list(size = 9, opacity = 0.65)) %>%
add_text_repel(
x = ~wt, y = ~mpg, text = ~car,
visibility = df$highlight,
force = 1.6, # repulsion strength: higher pushes labels apart more
force_pull = 0.9, # pull-to-point strength: higher keeps labels closer to points
box_padding = 0.35, # extra space around labels (helps reduce near-misses)
point_padding = 0.20, # extra space around points (helps avoid covering markers)
direction = "both", # "both", "x", or "y" (use "y" for vertical stacking plots)
max_time_ms = 25, # time budget per solve (lower keeps interaction snappy)
max_iter = 200, # solver iteration cap (raise if you need more settling)
max_overlaps = 10, # how much overlap to tolerate before culling
on = c("render", "zoom", "resize"),
seed = 42
)
```
Narrative guidance:
- If labels still overlap, try increasing `force` a bit (for example `1.2 -> 1.8`).
- If labels drift too far away from their points, increase `force_pull`.
- If labels sit “too close” even without overlap, increase `box_padding` and/or `point_padding`.
- If your plot has a clear layout direction, restricting `direction` can make results feel more consistent.
- If interaction feels heavy, reduce `max_time_ms` first (and consider labeling fewer points).
## 4) “ggrepel-style labels”: add_label_repel()
`add_label_repel()` is the analogue of `ggrepel::geom_label_repel()`: labels with a background box.
```{r}
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers(marker = list(size = 9, opacity = 0.65)) %>%
add_label_repel(
x = ~wt, y = ~mpg, text = ~car,
visibility = df$highlight,
force = 1.4,
force_pull = 1.0,
box_padding = 0.35,
point_padding = 0.2,
on = c("render", "zoom", "resize"),
seed = 42,
label = list(
bgcolor = "rgba(255,255,255,0.90)",
bordercolor = "rgba(0,0,0,0.25)",
borderwidth = 1,
borderpad = 3
)
)
```
## 5) A Practical Pattern: “When Space Is Tight, Keep The Best Labels”
Sometimes many points are eligible to be labeled, but you still want a clean plot. Use `priority` to keep the most important labels.
Here we use horsepower as a simple priority signal and only allow the highest-priority labels to survive when things get crowded.
```{r}
df$eligible <- with(df, wt < 2.6 | mpg > 23)
plot_ly(df, x = ~wt, y = ~mpg) %>%
add_markers(marker = list(size = 9, opacity = 0.65)) %>%
add_text_repel(
x = ~wt, y = ~mpg, text = ~car,
visibility = df$eligible,
priority = ~hp,
max_overlaps = 6,
force = 1.5,
force_pull = 1.0,
on = c("render", "zoom", "resize"),
seed = 7
)
```
## Summary
For most plots, the “canonical” recipe is:
1. Use `add_text_repel()` for text labels (ggrepel-style).
2. Label a subset using `visibility` and keep the plot readable.
3. Tweak `force` and `force_pull` first when you need control.
4. Use `add_label_repel()` if you want boxed labels.