# loading libraries
suppressPackageStartupMessages({
library(here)
library(readr)
library(dplyr)
library(echarts4r)
library(htmltools)
library(htmlwidgets)
})
# 1. Bringing in the data:
palomar_survey <- read_csv("data/tt_2026_08_11/palomar_survey.csv")
# 2. Clean survey data & compute geometric 4-region BPT boundaries
df_bpt <- palomar_survey |>
filter(
!is.na(log_nii_ha),
!is.na(log_oiii_hb),
!is.na(helio_velocity_km_s),
!is.na(activity_type),
activity_type != "Absorption"
) |>
mutate(
dist_mpc = round(abs(helio_velocity_km_s) / 70, 1),
# 2.1 Theoretical curve y-values evaluated at each point's x coordinate
kauffmann_y = if_else(log_nii_ha < 0.05, 0.61 / (log_nii_ha - 0.05) + 1.30, NA_real_),
kewley_y = if_else(log_nii_ha < 0.47, 0.61 / (log_nii_ha - 0.47) + 1.19, NA_real_),
seyfert_y = 1.01 * log_nii_ha + 0.48,
# 2.2 Coordinate-based BPT classification
bpt_region = case_when(
# 2.2.1. Pure Star-Forming (far left, below Kauffmann line)
log_nii_ha < 0.05 & log_oiii_hb < kauffmann_y ~ "Star-Forming Zone",
# 2.2.2. Seyfert (above Kewley limit/line AND above Seyfert/LINER line)
(log_nii_ha >= 0.47 | log_oiii_hb >= kewley_y) & log_oiii_hb >= seyfert_y ~ "Seyfert Zone",
# 2.2.3. LINER (far right x >= 0.47 AND below Seyfert/LINER line)
log_nii_ha >= 0.47 & log_oiii_hb < seyfert_y ~ "LINER Zone",
# 2.2.4. Transition Zone (composite zone between Kauffmann & LINER boundaries)
TRUE ~ "Transition Zone"
),
bpt_region = factor(
bpt_region,
levels = c("Star-Forming Zone", "Transition Zone", "Seyfert Zone", "LINER Zone")
),
tooltip_label = sprintf(
"<div style='font-family: sans-serif; width: 210px; white-space: normal; word-wrap: break-word;'>
<div style='font-weight: bold; font-size: 22px; color: #111; margin-bottom: 4px;'>%s</div>
<div style='font-size: 18px; color: #666; margin-bottom: 6px;'>Catalog Type: %s</div>
<div style='border-top: 1px solid #eee; padding-top: 4px; font-size: 16px; line-height: 1.4;'>
<div><span style='color: #888;'>Zone:</span> <b>%s</b></div>
<div><span style='color: #888;'>Distance:</span> <b>%.1f Mpc</b></div>
</div>
</div>",
galaxy_name, activity_type, bpt_region, dist_mpc
)
)
min_dist <- min(df_bpt$dist_mpc, na.rm = TRUE)
max_dist <- max(df_bpt$dist_mpc, na.rm = TRUE)
# 3. Generating Boundary Line Data Frames
# 3.1 Kauffmann Line (Pure Starburst boundary)
df_kauffmann <- data.frame(x = seq(-0.2, 0.02, length.out = 100)) |>
mutate(y = 0.61 / (x - 0.05) + 1.30) |>
filter(y >= -0.2)
# 3.2 Kewley Line (Theoretical maximum starburst limit)
df_kewley <- data.frame(x = seq(-0.2, 0.43, length.out = 100)) |>
mutate(y = 0.61 / (x - 0.47) + 1.19) |>
filter(y >= -0.2)
# 3.3 Seyfert / LINER Divider
df_seyfert <- data.frame(x = seq(0, 0.75, length.out = 100)) |>
mutate(y = 1.01 * x + 0.48)
# 4. Standard Grey Info Badges (Zones + Axes)
df_info_badges <- data.frame(
x = c(
0.35, # Seyfert Zone
0.68, # LINER Zone
-0.10, # Starburst Zone
0.25, # Transition Zone
-0.18, # Y-Axis Info
0.73, # X-Axis Info
0.10 # Seyfert / LINER Divider
),
y = c(
1.32, # Seyfert Zone
0.52, # LINER Zone
0.30, # Starburst Zone
0.35, # Transition Zone
1.44, # Y-Axis Info
-0.12, # X-Axis Info
0.70 # Seyfert / LINER Divider
),
info_card = c(
# SEYFERT EXPLANATION
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 20px; color: #9013FE; margin-bottom: 6px;'>Seyfert Galaxies</div>
<p style='font-size: 16px; color: #333; line-height: 1.45; margin: 0 0 8px 0;'>
<b>What powers these galaxies?</b> An actively feeding supermassive black hole emitting ultraviolet light and X-rays.
</p>
<p style='font-size: 16px; color: #555; line-height: 1.45; margin: 0;'>
<b>Why do they plot here?</b> High-energy radiation strips electrons from oxygen atoms ([O III]), pushing them above the Seyfert/LINER divider.
</p>
</div>",
# LINER EXPLANATION
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 20px; color: #4A90E2; margin-bottom: 6px;'>LINER Galaxies</div>
<p style='font-size: 16px; color: #333; line-height: 1.45; margin: 0 0 8px 0;'>
<b>What powers these galaxies?</b> Low-accretion black holes, shockwaves, or evolved star populations.
</p>
<p style='font-size: 16px; color: #555; line-height: 1.45; margin: 0;'>
<b>Why do they plot here?</b> High nitrogen emission shifted right (x ≥ 0.47), but lacking top-tier oxygen ionization energy.
</p>
</div>",
# STARBURST EXPLANATION
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 20px; color: #7ED321; margin-bottom: 6px;'>Starburst Nurseries</div>
<p style='font-size: 16px; color: #333; line-height: 1.45; margin: 0 0 8px 0;'>
<b>What powers these galaxies?</b> Massive young star formation (x < 0.05).
</p>
</div>",
# TRANSITION EXPLANATION
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 20px; color: #F5A623; margin-bottom: 6px;'>Transition / Composite Zone</div>
<p style='font-size: 16px; color: #333; line-height: 1.45; margin: 0 0 8px 0;'>
<b>What powers these galaxies?</b> A hybrid mix of starlight ionization and weak central black hole activity.
</p>
<p style='font-size: 16px; color: #555; line-height: 1.45; margin: 0;'>
<b>Why do they plot here?</b> They form the main cluster between pure H II starbursts and pure AGN excitation regimes.
</p>
</div>",
# Y-AXIS
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 18px; color: #111; margin-bottom: 4px;'>Vertical Axis: log([O III] / Hβ)</div>
<p style='font-size: 16px; color: #444; line-height: 1.4; margin: 0;'>
Measures total <b>ionization energy</b>.
</p>
</div>",
# X-AXIS
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 18px; color: #111; margin-bottom: 4px;'>Horizontal Axis: log([N II] / Hα)</div>
<p style='font-size: 16px; color: #444; line-height: 1.4; margin: 0;'>
Measures the <b>gas excitation mechanism</b>.
</p>
</div>",
# SEYFERT / LINER DIVIDER
"<div style='font-family: sans-serif; width: 360px; white-space: normal; word-wrap: break-word; padding: 2px;'>
<div style='font-weight: bold; font-size: 20px; color: #111; margin-bottom: 6px;'>Seyfert / LINER Divider</div>
<p style='font-size: 16px; color: #333; line-height: 1.45; margin: 0;'>
Separates high-energy Seyfert AGN (above) from lower-energy LINERs (below).
</p>
</div>"
)
)
# 5. Render Final Explorer
dist_scale <- function(x) { 5 + (x / max_dist) * 16 }
# Generating the chart
palomar_interactive_viz <-
df_bpt |>
group_by(bpt_region, .drop = FALSE) |>
e_charts(
log_nii_ha,
height = 620
) |>
e_scatter(
log_oiii_hb,
size = dist_mpc,
bind = tooltip_label,
scale = dist_scale,
itemStyle = list(
borderColor = "#443",
borderWidth = 1
)
) |>
e_color(c("#7ED321", "#F5A623", "#9013FE", "#4A90E2"), background = "#f8f9fa") |>
e_visual_map(
min = min_dist,
max = max_dist,
dimension = 2,
seriesIndex = c(0, 1, 2, 3),
top = 30,
orient = "horizontal",
left = "center",
text = c("Far (Mpc)", "Near (Mpc)"),
textStyle = list(color = "#444444", fontSize = 16, fontWeight = "bold"),
itemWidth = 14,
itemHeight = 160,
target = list(inRange = list(opacity = 0.85), outOfRange = list(opacity = 0.05)),
controller = list(inRange = list(color = c("#888888", "#888888")))
) |>
# Kauffmann Line
e_data(df_kauffmann, x) |>
e_line(
y, name = "Kauffmann Line", symbol = "none", legend = FALSE,
lineStyle = list(type = "dashed", width = 1.5, color = "#888888")
) |>
# Kewley Line
e_data(df_kewley, x) |>
e_line(
y, name = "Kewley Line", symbol = "none", legend = FALSE,
lineStyle = list(type = "dashed", width = 2, color = "#333333")
) |>
# Seyfert / LINER Divider
e_data(df_seyfert, x) |>
e_line(
y, name = "Seyfert / LINER Divider", symbol = "none", legend = FALSE,
lineStyle = list(type = "dotted", width = 2, color = "#333333")
) |>
# Badges
e_data(df_info_badges, x) |>
e_scatter(
y,
bind = info_card,
name = "Info Badges",
legend = FALSE,
symbol = "path://M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z",
symbol_size = 25,
itemStyle = list(color = "#9CA3AF"),
emphasis = list(
itemStyle = list(color = "#1F2937"),
scale = 1.25
)
) |>
e_x_axis(
name = "log([N II] / Hα)",
nameLocation = "end",
name_gap = 28,
nameTextStyle = list(color = "#666666", fontSize = 16, fontWeight = "bold"),
axisLabel = list(color = "#666666", margin = 20, fontSize = 14),
axisLine = list(lineStyle = list(width = 2, color = "#666666")),
min = -0.2,
max = 0.75
) |>
e_y_axis(
name = "log([O III] / Hβ)",
nameLocation = "end",
nameGap = 20,
nameTextStyle = list(color = "#666666", fontSize = 16, fontWeight = "bold"),
axisLabel = list(color = "#666666", margin = 20, fontSize = 14),
axisLine = list(lineStyle = list(width = 2, color = "#666666")),
min = -0.2,
max = 1.5
) |>
e_legend(
left = "center",
top = 100,
textStyle = list(fontSize = 14)
) |>
e_grid(
top = 210,
bottom = 25
) |>
e_tooltip(
formatter = htmlwidgets::JS("function(params) { return params.name; }"),
backgroundColor = "#ffffff",
borderColor = "#cccccc",
borderWidth = 1,
extraCssText = "box-shadow: 0 4px 16px rgba(0,0,0,0.15); border-radius: 8px; padding: 12px;"
)
# presentation base container (the canvas)
palomar_survey_viz <-
htmltools::tagList(
tags$div(
style = "
background-color: white;
padding: 50px;
margin-left: 20px;
margin-right: 50px;
margin-bottom: 20px;
",
# responsive flexbox for the headline
tags$div(
style = "
display: flex;
flex-wrap: wrap;
gap: 30px;
align-items: flex-start;
width: 100%;
",
# headline and subtitle wrapper
tags$div(
style = "
flex: 1;
min-width: 400px;
display: flex;
flex-direction: column;
padding-bottom: 20px;
",
# title
tags$div(
style = "text-align:left; margin-bottom: 15px; font-family: Plus Jakarta Sans; font-weight: 950; line-height: 1.1;",
HTML(
"<span style='color:#666666; font-size:26px;'>What Powers Nearby Galaxies? Most Are </span> <span style='color:#F5A623; font-size:26px;'> Hybrids</span> <span style='color:#666666; font-size:26px;'>, Not </span> <span style='color:#9013FE; font-size:26px;'>Violent </span> "
)
),
# subtitle
tags$div(
style = "text-align:left; font-family: Inter, sans-serif; font-size:18px; font-weight: 400; # color:#666666; line-height:1.4; margin-bottom:0px;",
HTML(
"Galaxies generate energy through newborn stars, central black holes, or a mix of both. In our local universe, pure <span style='color:#7ED321; font-weight:bold;'>starbursts</span> are rare. Most galaxies sit in a hybrid <span style='color:#F5A623; font-weight:bold;'>Transition zone</span> or idle on starving black hole diets in the <span style='color:#4A90E2; font-weight:bold;'>LINER zone</span>, while only a small fraction actively rage as high-energy <span style='color:#9013FE; font-weight:bold;'>Seyferts</span>."
)
)
)
),
# responsive flexbox that holds the chart
tags$div(
style = "
display: flex;
flex-wrap: wrap;
gap: 0px;
align-items: flex-start;
width: 100%;
",
# the chart
palomar_interactive_viz
), # closing responsive flexbox that holds the chart
# start of page footer with data and author details
tags$div(
style = "display: flex; justify-content: center; align-items: center; flex-wrap: wrap; margin-top: 50px; margin-bottom: 10px;",
# data source wrapper to ensure it scales nicely
tags$span(
style = "display: inline-flex; align-items: center;",
tags$span(
"Data Source :",
style = "color: #888; text-decoration: none; font-family: Plus Jakarta Sans; font-size: 14px; font-weight: 450; margin-right: 5px;"
),
# tidy tuesday repo link
tags$a(
"Palomar Spectroscopic Survey of Nearby Galaxies via Tidy Tuesday (2026-32)",
href = "[https://github.com/rfordatascience/tidytuesday/blob/main/data/2026/2026-08-04/readme.md](https://github.com/rfordatascience/tidytuesday/blob/main/data/2026/2026-08-11/readme.md)",
target = "_blank",
style = "color: #F5A623; text-decoration: none; font-family: Plus Jakarta Sans; font-size: 14px; font-weight: bold;"
)
),
# css margin spacer
tags$span(style = "margin-left: 40px;"),
# author social media handles wrapper
tags$span(
style = "display: inline-flex; align-items: center; gap: 20px;",
# linkedin wrapper
tags$span(
style = "display: inline-flex; align-items: center; white-space: nowrap;",
# linkedin logo
tags$img(
HTML('<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18" fill="#F5A623" class="bi bi-linkedin" viewBox="0 0 16 16">
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
</svg>')
),
# linkedin link
tags$a(
"Ntobeko Sosibo",
href = "[https://www.linkedin.com/in/ntobeko-sosibo/](https://www.linkedin.com/in/ntobeko-sosibo/)",
target = "_blank",
style = "margin-left: 10px; color: #888; text-decoration: none; font-family: 'Plus Jakarta Sans'; font-size: 14px; font-weight: 450;"
)
),
# github wrapper
tags$span(
style = "display: inline-flex; align-items: center; white-space: nowrap;",
# github logo
tags$img(
HTML(
'<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18" fill="#F5A623" class="bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63 -.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
</svg>'
)
),
# github link
tags$a(
"afrikaniz3d-za",
href = "[https://github.com/afrikaniz3d-za](https://github.com/afrikaniz3d-za)",
target = "_blank",
style = "margin-left: 10px; color: #888; text-decoration: none; font-family: Plus Jakarta Sans; font-size: 14px; font-weight: 450;"
)
),
# bluesky wrapper
tags$span(
style = "display: inline-flex; align-items: center; white-space: nowrap;",
# bluesky logo
tags$img(
HTML(
'<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18" fill="#F5A623" class="bi bi-bluesky" viewBox="0 0 16 16">
<path d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.698-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948"/>
</svg>'
)
),
# bluesky link
tags$a(
"afrikaniz3d",
href = "[https://bsky.app/profile/afrikaniz3d.bsky.social](https://bsky.app/profile/afrikaniz3d.bsky.social)",
target = "_blank",
style = "margin-left: 10px; color: #888; text-decoration: none; font-family: Plus Jakarta Sans; font-size: 14px; font-weight: 450;"
)
), # closing bluesky wrapper
# mastodon wrapper
tags$span(
style = "display: inline-flex; align-items: center; white-space: nowrap;",
# mastodon logo
tags$img(
HTML(
'<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18" fill="#F5A623" class="bi bi-mastodon" viewBox="0 0 16 16">
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z"/>
</svg>'
)
),
# mastodon link
tags$a(
"afrikaniz3d",
href = "[https://mastodon.social/@afrikaniz3d](https://mastodon.social/@afrikaniz3d)",
target = "_blank",
style = "margin-left: 10px; color: #888; text-decoration: none; font-family: Plus Jakarta Sans; font-size: 14px; font-weight: 450;"
)
) # closing mastodon wrapper
) # closing social media handles wrapper
) # closing page footer
)
)
palomar_survey_viz