So, for my project, I want to show how we smooth racetrack bounds as part of the autonomous racing team. For some context, the gps coordinates of the edges of a racetrack are usually programmatically calculated from satellite imagery, but this can have a lot of small errors and be really rough in general, so the track won’t be as smooth as we want it. We need to smooth the race track bounds so that when we generate racelines, the racelines themselves won’t be shaky, as shaky racelines means our racecar will lose traction and crash. So we apply smoothing algorithms to correct this. These smoothing algorithms are compared by their ability to smooth out the bounds but also keep the smoothed bounds from veering off where they actually are. This plot focuses mainly on the smoothing aspect.
p0 <- ggplot(data = bounds, aes(x = Index)) +
geom_line(aes(y = Change.in.Heading.Inner, color = "Inner")) +
geom_line(aes(y = Change.in.Heading.Outer, color = "Outer")) +
facet_wrap(~ Smoothing.Type) +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
color = "Path") +
theme_minimal()
p0
This is my first, basic plot. Honestly, I think it looks pretty solid. We can clearly see the difference in smoothness between each of the smoothing techniques.
p1 <- ggplot(data = bounds, aes(x = Index)) +
geom_area(aes(y = Change.in.Heading.Inner, fill = "Inner"), alpha = 0.5) +
geom_area(aes(y = Change.in.Heading.Outer, fill = "Outer"), alpha = 0.5) +
facet_wrap(~ Smoothing.Type) +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
fill = "Path") +
theme_minimal()
p1
This looks fine, but kind of messes with the viewer’s ability to recognize that we are using SMOOTHING techniques, as smoothing shapes isn’t as intuitive as smoothing lines.
bounds_long <- bounds %>%
gather(key = "Measure", value = "Value", -Index, -Smoothing.Type) %>%
mutate(Bound.Type = ifelse(grepl("Inner", Measure), "Inner", "Outer"),
Measure = gsub("Change.in.Heading.", "", Measure),
Color = paste(Bound.Type, Smoothing.Type, sep = " - "))
p2 <- ggplot(data = bounds_long, aes(x = Index, y = Value,
group = Color, color = Color)) +
geom_line() +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
color = "Path Type") +
theme_minimal() +
scale_color_manual(values = c("Inner - Sma" = "blue", "Outer - Sma" = "red",
"Inner - Sg" = "green", "Outer - Sg" = "orange",
"Inner - Gaussian" = "purple",
"Outer - Gaussian" = "brown"))
p2
So now all the bounds are on the same graph, but it’s really difficult
to actually compare the lines. There’s way too much going on. I’m going
to try matching the colors of the pairs of inner and outer bounds so we
can cut through the mess.
bounds_long <- bounds %>%
pivot_longer(cols = -c(Index, Smoothing.Type),
names_to = "Bound.Type",
values_to = "Value") %>%
mutate(
Bound.Type = case_when(
grepl("Inner", Bound.Type) ~ "Inner",
TRUE ~ "Outer"
),
ID = interaction(Smoothing.Type, Bound.Type, sep = " - ")
)
colors <- c("Sma - Inner" = "#1b9e77", "Sma - Outer" = "#66c2a5",
"Sg - Inner" = "#d95f02", "Sg - Outer" = "#fc8d62",
"Gaussian - Inner" = "#7570b3", "Gaussian - Outer" = "#9e9ac8")
p3 <- ggplot(data = bounds_long, aes(x = Index, y = Value, group = ID, color = ID)) +
geom_line() +
scale_color_manual(values = colors) +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
color = "Path Type & Smoothing") +
theme_minimal()
p3
This really isn’t much better.
bounds_long <- bounds %>%
gather(key = "Measure", value = "Value", -Index, -Smoothing.Type) %>%
mutate(Bound.Type = ifelse(grepl("Inner", Measure), "Inner", "Outer"),
Measure = gsub("Change.in.Heading.", "", Measure),
Color = paste(Bound.Type, Smoothing.Type, sep = " - "))
p4 <- ggplot(data = bounds_long, aes(x = Index, y = Value, color = Color)) +
geom_line(size=0.8) +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
color = "Path Type") +
theme_minimal() +
scale_color_manual(values = c("Inner - Sma" = "blue", "Outer - Sma" = "lightblue",
"Inner - Sg" = "green", "Outer - Sg" = "lightgreen",
"Inner - Gaussian" = "purple",
"Outer - Gaussian" = "darkorchid1"))
p4 <- ggplotly(p3)
buttons <- list(
list(method = "restyle",
args = list("visible", list(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE)),
label = "All"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE)),
label = "Sma"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE)),
label = "Sg"),
list(method = "restyle",
args = list("visible", list(TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE)),
label = "Gaussian")
)
p4 <- layout(p4,
updatemenus = list(
list(
x = 1.0,
y = 1.0,
buttons = buttons)
))
p4
Here I tried using a dropdown filter to cutdown on the amount of infomration, but it doesn’t work quite as well as imagined, because we still can’t directly compare filtering techniques against each other.
p5 <- ggplot(data = bounds, aes(x = Index)) +
geom_line(aes(y = Change.in.Heading.Inner, color = "Inner")) +
geom_line(aes(y = Change.in.Heading.Outer, color = "Outer")) +
labs(title = "Change in Heading for Inner and Outer Paths",
y = "Change in Heading (radians)",
color = "Path") +
theme_minimal()
animation <- p5 +
transition_states(Smoothing.Type, transition_length = 2, state_length = 1) +
enter_fade() + exit_fade() +
ease_aes('linear') +
labs(subtitle = 'Smoothing Type: {closest_state}')
# NOTE: I rendered the animation outside of Rmd knit because knitting was slow
#anim <- animate(animation, nframes = 50, duration = 5, width = 800, height = 600, renderer=gifski_renderer())
#output_file <- "smooth.gif"
#anim_save(filename = output_file, animation = anim)
I tried to do some animation here, and it looks really good aesthetically. But, my concern is that the viewer kind of has to wait to directly compare the smoothing algorithms, and it’s hard to compare them visually when the animation is constantly shifting. I want to explore a way where I can show all smoothing types simultaneously.
Okay, let’s switch things up. I think this idea of plotting everything on the same plot isn’t working, so let’s plot these graphs individually side by side.
bounds_long <- bounds %>%
pivot_longer(cols = -c(Index, Smoothing.Type),
names_to = "Bound.Type",
values_to = "Value") %>%
mutate(Bound.Type = case_when(
grepl("Inner", .data$`Bound.Type`) ~ "Inner",
TRUE ~ "Outer"
))
plots <- list()
for (bound_type in c("Inner", "Outer")) {
bounds_filtered <- bounds_long %>%
filter(Bound.Type == bound_type)
p6 <- ggplot(bounds_filtered, aes(x = Index, y = Value,
color = Smoothing.Type)) +
geom_line() +
facet_wrap(~ Smoothing.Type, ncol = 1, scales = "free_y") +
labs(title = paste("Change in Heading for", bound_type, "Bounds"),
y = "Change in Heading (radians)",
x = "Index",
color = "Smoothing Technique") +
theme_minimal() +
theme(legend.position = "bottom")
plots[[bound_type]] <- p6
}
plots$Inner + plots$Outer
This is good, kind of similar to the original idea. We’ve come full circle. However, I still think this plot struggles to be able to compare how well the smoothing did more directly.
bounds_long <- bounds %>%
pivot_longer(cols = starts_with("Change.in.Heading"),
names_to = "Bound.Type",
names_prefix = "Change.in.Heading.",
values_to = "Change.in.Heading") %>%
mutate(Bound.Type = factor(Bound.Type, levels = c("Inner", "Outer")))
create_plot <- function(data, bound_type, smoothing_type) {
data %>%
filter(`Bound.Type` == bound_type, Smoothing.Type %in% c("Original", smoothing_type)) %>%
ggplot(aes(x = Index, y = `Change.in.Heading`, color = Smoothing.Type)) +
geom_line() +
labs(title = paste(smoothing_type, bound_type, "with Original"),
x = "Index", y = "Change in Heading") +
scale_color_manual(values = c("Original" = "gray", smoothing_type = "blue")) +
theme_minimal() +
theme(legend.position = "bottom")
}
plots <- list()
for (smoothing_type in c("Sma", "Sg", "Gaussian")) {
for (bound_type in c("Inner", "Outer")) {
plots[[paste(smoothing_type, bound_type)]] <- create_plot(bounds_long, bound_type, smoothing_type)
}
}
p7 <- plots[["Sma Inner"]] / plots[["Sg Inner"]] / plots[["Gaussian Inner"]] |
plots[["Sma Outer"]] / plots[["Sg Outer"]] / plots[["Gaussian Outer"]]
p7
I think this is a big improvement, with the original bounds behind our smoothed bounds, it becomes a lot easier to compare how well the smoothing did. It also helps because I’m available to make sure these smoothing isn’t TOO strong and messing up the bounds. This plot was really difficult to make for some reason, so I had to get rid of a lot of the previous plot’s aesthetics, though. Let’s add the color and aggregated titles back. It’s WAY too small as well.
bounds_long <- bounds %>%
mutate(Smoothing.Type = factor(Smoothing.Type, levels = c("Original", "Sma", "Sg", "Gaussian")))
bounds_long <- bounds_long %>%
pivot_longer(cols = starts_with("Change.in.Heading"),
names_to = "Bound.Type",
names_prefix = "Change.in.Heading.",
values_to = "Change.in.Heading") %>%
mutate(Bound.Type = factor(Bound.Type, levels = c("Inner", "Outer")))
create_plot <- function(data, bound_type, smoothing_type, hide_x_axis_text, hide_y_axis_text) {
full_names <- c(Sma = "Simple Moving Average",
Sg = "Savitzky-Golay",
Gaussian = "Gaussian")
plot_title <- full_names[[smoothing_type]]
colors <- c("Original" = "gray",
"Sma" = "#377eb8",
"Sg" = "#ff7f00",
"Gaussian" = "#4daf4a") # Adjust colors as needed
p8 <- data %>%
filter(`Bound.Type` == bound_type, Smoothing.Type %in% c("Original", smoothing_type)) %>%
ggplot(aes(x = Index, y = `Change.in.Heading`, color = Smoothing.Type)) +
geom_line() +
labs(title = plot_title,
x = ifelse(hide_x_axis_text, "", "Reference Index"),
y = ifelse(hide_y_axis_text, "", "Change in Heading (radians)")) +
scale_color_manual(values = colors) +
theme_minimal() +
theme(legend.position = "none",
axis.text.x = element_text(size = 4),
axis.text.y = element_text(size = 4),
axis.ticks = element_blank(),
plot.margin = margin(t = 5, b = 5, unit = "pt"),
plot.title = element_text(hjust = 0.5, size = 8, color = colors[smoothing_type]))
if (hide_x_axis_text) {
p8 <- p8 + theme(axis.title.x = element_blank())
}
if (hide_y_axis_text) {
p8 <- p8 + theme(axis.title.y = element_blank())
}
return(p8)
}
plots <- list()
for (smoothing_type in c("Sma", "Sg", "Gaussian")) {
for (bound_type in c("Inner", "Outer")) {
plot_key <- paste(smoothing_type, bound_type)
plots[[plot_key]] <- create_plot(bounds_long, bound_type, smoothing_type,
hide_x_axis_text = smoothing_type != "Gaussian",
hide_y_axis_text = smoothing_type != "Sg")
}
}
plot_layout <- (plots[["Sma Inner"]] / plots[["Sg Inner"]] / plots[["Gaussian Inner"]] |
plots[["Sma Outer"]] / plots[["Sg Outer"]] / plots[["Gaussian Outer"]])
plot_layout <- plot_layout +
theme(legend.position = "none") +
plot_annotation(title = "Inner Bound Change in Heading Outer Bound Change in Heading",
theme = theme(plot.title = element_text(hjust = 0.5,
size = 14,
face = "bold")))
print(plot_layout)
Added back the aesthetics from an earlier version, reformatted titles to be more clear and non-redundant, and made the plots bigger. I think this looks A LOT better, and also has the same comparison ability as the graph above. Added labels directly on the line instead of in the legend. This is more readable because they viewer doesn’t have to switch back and forth between the legend and the plots. It’s also better than the animation because we can compare all the smoothing techniques simultaneously.
Aesthetic changes between plots:
Shape (Plot 1 vs Plot 2)
Color (Plot 2 vs Plot 3)
Interactive Filtering (Plot 3 vs Plot 4)
Animation (plot 4 vs Plot 5)
Faceting/using patchwork to combine graphs (Plot 6 vs Plot 7)
Titles/themes/legend/axis label (Plot 7 vs 8)
Convos with ChatGPT
Prompt: what are the best three colors to use to maximize visibility, account for colorblindness, and look good
Answer: Choosing colors that maximize visibility, are colorblind-friendly, and also aesthetically pleasing is crucial for creating accessible and effective visualizations. A set of colors often recommended for these purposes includes blue, orange, and green, as they are generally distinguishable by most forms of color vision deficiency. Here are specific shades of these colors that work well together: Blue: A solid, bright blue is usually visible to all and offers good contrast. Hex: #377eb8 Orange: A vibrant orange can stand out and is often distinguishable from blue and green by those with color vision deficiencies. Hex: #ff7f00 Green: A bluish-green or teal can be a good choice, distinct from the other two colors yet still visible to most forms of colorblindness. Hex: #4daf4a.
Prompt: My smoothed lines dont have color how can I give them colors based off of smoothing type
Answer: To assign different colors to the smoothed lines based on the smoothing type, and keep the “Original” line in gray, you’re already on the right track with scale_color_manual(). However, if the smoothed lines are not displaying in the colors you’ve specified, it may be due to how the colors are defined or applied in scale_color_manual().Let’s ensure each smoothing type gets its unique color, including a distinct color for the “Original” data. You can extend the values argument in scale_color_manual() to explicitly map each smoothing type to a specific color.colors <- c(“Original” = “gray”, “Sma” = “blue”, “Sg” = “green”, “Gaussian” = “red”) # Adjust colors as needed