library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.1.5
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.0 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.2.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(plotly)
##
## Attaching package: 'plotly'
##
## The following object is masked from 'package:ggplot2':
##
## last_plot
##
## The following object is masked from 'package:stats':
##
## filter
##
## The following object is masked from 'package:graphics':
##
## layout
library(readr)
library(janitor)
##
## Attaching package: 'janitor'
##
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
library(htmlwidgets)
# -----------------------------
# DATA
# -----------------------------
ev <- read_csv("bmw_ev_transition.csv", show_col_types = FALSE) %>%
clean_names() %>%
mutate(series = str_to_title(series))
actual <- ev %>% filter(series == "Actual")
last_actual_year <- max(actual$year, na.rm = TRUE)
last_actual_value <- actual %>% filter(year == last_actual_year) %>% pull(ev_share_pct)
forecast_2035 <- 80
required_path <- tibble(
year = seq(last_actual_year, 2035, by = 1),
ev_share_pct = seq(last_actual_value, forecast_2035,
length.out = length(seq(last_actual_year, 2035, by = 1)))
)
# -----------------------------
# POLICY MILESTONES
# -----------------------------
policy <- tibble(
year = c(2025, 2030, 2035),
y = c(28, 52, 78),
label = c(
"EU CO₂ Fleet Targets (Fit for 55) – tightening",
"EU 2030 CO₂ Reduction Target (~55%)",
"EU 2035 ICE Ban (Zero-emission mandate)"
),
short = c("2025", "2030", "2035")
)
# -----------------------------
# PLOTLY INTERACTIVE CHART
# -----------------------------
p <- plot_ly()
# Actual BMW EV share
p <- p %>%
add_lines(
data = actual,
x = ~year, y = ~ev_share_pct,
name = "BMW actual EV share",
line = list(color = "#0072B2", width = 4),
marker = list(color = "#0072B2", size = 7),
hovertemplate = paste(
"<b>BMW actual EV share</b><br>",
"Year: %{x}<br>",
"EV share: %{y:.1f}%<extra></extra>"
)
) %>%
add_markers(
data = actual,
x = ~year, y = ~ev_share_pct,
name = "BMW actual EV share",
marker = list(color = "#0072B2", size = 8),
showlegend = FALSE,
hovertemplate = paste(
"<b>BMW actual EV share</b><br>",
"Year: %{x}<br>",
"EV share: %{y:.1f}%<extra></extra>"
)
)
# Required path
p <- p %>%
add_lines(
data = required_path,
x = ~year, y = ~ev_share_pct,
name = "Required path to 2035",
line = list(color = "#5C5C5C", width = 3, dash = "dash"),
hovertemplate = paste(
"<b>Required path to 2035</b><br>",
"Year: %{x}<br>",
"Implied EV share: %{y:.1f}%<extra></extra>"
)
)
# Policy milestone markers
p <- p %>%
add_markers(
data = policy,
x = ~year, y = ~y,
name = "Policy milestones",
marker = list(
color = "#D55E00",
size = 12,
symbol = "diamond"
),
text = ~label,
customdata = ~short,
hovertemplate = paste(
"<b>%{customdata}</b><br>",
"%{text}<extra></extra>"
)
)
# Endpoint label
p <- p %>%
add_text(
x = 2035.25, y = forecast_2035,
text = "~80%",
textposition = "middle right",
showlegend = FALSE,
textfont = list(size = 18, color = "#5C5C5C", family = "Arial Black")
)
# Layout
p <- p %>%
layout(
title = list(
text = paste0(
"<b>BMW Is Electrifying — But Policy Requires ~80% EV by 2035</b>",
"<br><sup>Click or hover on the orange markers to inspect major policy pressure points.</sup>"
),
x = 0.01,
xanchor = "left"
),
xaxis = list(
title = "",
range = c(2009.5, 2036),
tickvals = c(2010, 2015, 2020, 2025, 2030, 2035),
showgrid = FALSE,
zeroline = FALSE
),
yaxis = list(
title = list(text = "<b>EV share of BMW global deliveries</b>"),
range = c(0, 90),
tickvals = c(0, 20, 40, 60, 80),
ticksuffix = "%",
gridcolor = "#E6E6E6",
zeroline = FALSE
),
legend = list(
orientation = "h",
x = 0.02,
y = 1.08
),
shapes = list(
list(
type = "rect",
x0 = 2030, x1 = 2035.5,
y0 = 0, y1 = 90,
fillcolor = "rgba(217,217,217,0.20)",
line = list(width = 0),
layer = "below"
)
),
annotations = list(
list(
x = 2032.7, y = 86,
text = "<b>Policy target zone</b>",
showarrow = FALSE,
font = list(size = 16, color = "#5C5C5C")
)
),
plot_bgcolor = "white",
paper_bgcolor = "white",
margin = list(l = 90, r = 90, t = 100, b = 70)
)
# Show in Viewer
p
## A marker object has been specified, but markers is not in the mode
## Adding markers to the mode...
# Save interactive html file
saveWidget(p, "plot_1_bmw_ev_transition_interactive.html", selfcontained = TRUE)
## A marker object has been specified, but markers is not in the mode
## Adding markers to the mode...