point_data <- pokemon |>
transmute(
x = umap_1,
y = umap_2,
pokemon = pokemon_label,
generation = as.character(generation_label),
type_1 = str_to_title(type_1),
type_2 = if_else(type_2 == "none", "—", str_to_title(type_2)),
type_color,
height_m = round(height / 10, 1),
weight_kg = round(weight / 10, 1),
hp,
attack,
defense,
special_attack,
special_defense,
speed,
capture_rate,
artwork_url,
sprite_url
) |>
purrr::pmap(function(
x, y, pokemon, generation, type_1, type_2, type_color,
height_m, weight_kg, hp, attack, defense,
special_attack, special_defense, speed, capture_rate,
artwork_url, sprite_url
) {
list(
x = x,
y = y,
name = pokemon,
pokemon = pokemon,
generation = generation,
type_1 = type_1,
type_2 = type_2,
type_color = type_color,
height_m = height_m,
weight_kg = weight_kg,
hp = hp,
attack = attack,
defense = defense,
special_attack = special_attack,
special_defense = special_defense,
speed = speed,
capture_rate = capture_rate,
artwork_url = artwork_url,
marker = list(
symbol = sprintf("url(%s)", sprite_url),
width = 28,
height = 28
)
)
}) |>
unname()
halo_data <- pokemon |>
transmute(
x = umap_1,
y = umap_2,
color = scales::alpha(type_color, 0.18)
) |>
purrr::pmap(function(x, y, color) {
list(
x = x,
y = y,
color = color,
marker = list(symbol = "circle", radius = 18)
)
}) |>
unname()
point_data_by_type <- split(point_data, pokemon$type_1)
halo_data_by_type <- split(halo_data, pokemon$type_1)
legend_type_focus <- htmlwidgets::JS(
"function () {
var chart = this;
function setFocus(typeKey) {
chart.series.forEach(function (series) {
var custom = series.options.custom || {};
var sameType = custom.typeKey === typeKey;
var opacity = custom.isHalo ? (sameType ? 1 : 0) :
(sameType ? 1 : 0.12);
var group = series.markerGroup || series.group;
if (group) {
group.attr({ opacity: opacity });
if (sameType) group.toFront();
}
});
}
function resetFocus() {
chart.series.forEach(function (series) {
var custom = series.options.custom || {};
var group = series.markerGroup || series.group;
if (group) group.attr({ opacity: custom.isHalo ? 0 : 1 });
});
}
chart.series.forEach(function (series) {
if (!series.options.showInLegend) return;
var legendGroup = series.legendItem && series.legendItem.group
? series.legendItem.group
: series.legendGroup;
var element = legendGroup && legendGroup.element;
if (!element || element.__pokemonTypeFocusBound) return;
element.__pokemonTypeFocusBound = true;
element.addEventListener('mouseenter', function () {
setFocus(series.options.custom.typeKey);
});
element.addEventListener('mouseleave', resetFocus);
});
}"
)
raise_tooltip <- htmlwidgets::JS(
"function () {
var chart = this.series.chart;
window.setTimeout(function () {
if (chart.tooltip && chart.tooltip.label) {
chart.tooltip.label.toFront();
}
if (chart.tooltip && chart.tooltip.container) {
chart.tooltip.container.style.zIndex = 99999;
}
}, 0);
}"
)
pokemon_series <- sort(names(point_data_by_type)) |>
purrr::map(function(type_key) {
type_name <- stringr::str_to_title(type_key)
type_color <- unname(type_palette[[type_key]])
sprite_id <- paste0("type-", type_key)
list(
list(
data = halo_data_by_type[[type_key]],
name = paste(type_name, "halo"),
linkedTo = sprite_id,
turboThreshold = 0,
showInLegend = FALSE,
enableMouseTracking = FALSE,
opacity = 0,
states = list(
inactive = list(opacity = 0),
hover = list(opacity = 0)
),
custom = list(typeKey = type_key, isHalo = TRUE),
zIndex = 1
),
list(
data = point_data_by_type[[type_key]],
id = sprite_id,
name = type_name,
color = type_color,
marker = list(symbol = "circle", radius = 5),
turboThreshold = 0,
showInLegend = TRUE,
custom = list(typeKey = type_key, isHalo = FALSE),
zIndex = 2
)
)
}) |>
purrr::list_flatten()
tooltip <- paste0(
'<div style="width:270px;padding:12px;font-family:inherit;background-color:rgb(244,246,248)!important;color:#17324d;border-radius:12px;box-shadow:0 8px 24px rgba(23,50,77,.16);opacity:1!important">',
'<div style="display:flex;gap:12px;align-items:center">',
'<img src="{point.artwork_url}" style="width:92px;height:92px;object-fit:contain">',
'<div><div style="font-size:18px;font-weight:600">{point.pokemon}</div>',
'<div style="font-size:11px;opacity:.65">{point.generation}</div>',
'<div style="margin-top:5px">',
'<span style="background:{point.type_color};color:white;padding:2px 7px;border-radius:10px">{point.type_1}</span>',
'<span style="margin-left:5px">{point.type_2}</span></div>',
'<div style="margin-top:8px">{point.height_m} m · {point.weight_kg} kg</div>',
'</div></div>',
'<table style="width:100%;margin-top:10px;font-size:12px;background-color:rgb(244,246,248)!important;opacity:1!important">',
'<tr><td>HP</td><td><b>{point.hp}</b></td><td>Attack</td><td><b>{point.attack}</b></td></tr>',
'<tr><td>Defense</td><td><b>{point.defense}</b></td><td>Speed</td><td><b>{point.speed}</b></td></tr>',
'<tr><td>Sp. Atk</td><td><b>{point.special_attack}</b></td><td>Sp. Def</td><td><b>{point.special_defense}</b></td></tr>',
'<tr><td>Capture</td><td colspan="3"><b>{point.capture_rate}</b></td></tr>',
'</table></div>'
)
pokemon_chart <- highchart() |>
hc_chart(
type = "scatter",
zoomType = "xy",
panning = list(enabled = TRUE, type = "xy"),
panKey = "shift",
backgroundColor = "transparent",
animation = FALSE,
events = list(load = legend_type_focus)
) |>
hc_title(text = "A Wild UMAP Appears!") |>
hc_subtitle(
text = "Stats, morphology, breeding and species traits · type and generation excluded"
) |>
hc_xAxis(visible = FALSE) |>
hc_yAxis(visible = FALSE) |>
hc_add_series_list(pokemon_series) |>
hc_legend(
enabled = TRUE,
align = "center",
verticalAlign = "top",
layout = "horizontal",
symbolRadius = 5,
itemStyle = list(fontWeight = 400)
) |>
hc_tooltip(
useHTML = TRUE,
outside = TRUE,
backgroundColor = "rgb(244, 246, 248)",
style = list(color = "#17324d", opacity = 1),
borderWidth = 0,
borderRadius = 12,
shadow = TRUE,
padding = 0,
headerFormat = "",
pointFormat = tooltip
) |>
hc_plotOptions(
series = list(
animation = FALSE,
point = list(
events = list(mouseOver = raise_tooltip)
),
states = list(
inactive = list(opacity = 1),
hover = list(halo = list(size = 34, opacity = 0.25))
)
)
) |>
hc_credits(enabled = FALSE) |>
hc_size(height = 900)