# Цвета сообществ ────────────────────────────────────────────────────────────
COMM_COLORS <- c("#e41a1c","#377eb8","#4daf4a","#984ea3",
"#ff7f00","#a65628","#f781bf","#999999")
# igraph → visNetwork ────────────────────────────────────────────────────────
igraph_to_vis <- function(g, min_weight = 1,
node_scale = c(10, 50), edge_scale = c(1, 8)) {
g_trim <- delete_edges(g, which(E(g)$weight <= min_weight))
btw <- V(g_trim)$betweenness
pal <- colorRampPalette(c("#4575b4","#fee090","#d73027"))(100)
col_idx <- pmax(1, ceiling(rescale(btw, to = c(1, 100))))
ap_names <- tryCatch(V(g)$name[articulation_points(g)], error = function(e) character(0))
nodes <- tibble(
id = V(g_trim)$name,
label = V(g_trim)$name_ru,
value = rescale(V(g_trim)$strength, to = node_scale),
color = pal[col_idx],
shape = ifelse(id %in% ap_names, "diamond", "dot"),
borderWidth = ifelse(id %in% ap_names, 3, 1),
title = paste0(
"<b>", V(g_trim)$name_ru, "</b><br>",
"Степень: ", V(g_trim)$degree, "<br>",
"Сила: ", round(V(g_trim)$strength, 1), "<br>",
"Посредничество: ", round(V(g_trim)$betweenness, 4), "<br>",
"PageRank: ", round(V(g_trim)$pagerank, 5),
ifelse(id %in% ap_names, "<br><b>◆ Точка сочленения</b>", "")
)
)
el <- as_edgelist(g_trim, names = TRUE)
ew <- E(g_trim)$weight
edges <- tibble(
from = el[,1], to = el[,2],
value = rescale(ew, to = edge_scale),
title = paste0("Реплик: ", ew)
)
list(nodes = nodes, edges = edges)
}
# Окраска по сообществам
add_comm_colors <- function(vis, g, comm) {
df <- tibble(id = V(g)$name, community = as.character(membership(comm)))
lvls <- sort(unique(df$community))
cmap <- setNames(COMM_COLORS[seq_along(lvls)], lvls)
vis$nodes <- vis$nodes |>
left_join(df, by = "id") |>
mutate(color = cmap[community], group = paste("Сообщество", community))
vis
}
# Круговой эго-граф
make_ego_vis <- function(g_full, ego_id, r1 = 200, r2 = 420,
node_scale = c(10, 45), edge_scale = c(1, 10),
height = "680px", title = "") {
ego_g <- make_ego_graph(g_full, order = 2, nodes = ego_id)[[1]]
nbrs1 <- neighbors(g_full, ego_id)$name
V(ego_g)$ring <- case_when(
V(ego_g)$name == ego_id ~ 0L,
V(ego_g)$name %in% nbrs1 ~ 1L,
TRUE ~ 2L
)
r1_ids <- V(ego_g)$name[V(ego_g)$ring == 1L]
r2_ids <- V(ego_g)$name[V(ego_g)$ring == 2L]
n1 <- length(r1_ids); n2 <- length(r2_ids)
a1 <- if (n1>0) seq(0, 2*pi, length.out=n1+1)[-(n1+1)] else numeric(0)
a2 <- if (n2>0) seq(0, 2*pi, length.out=n2+1)[-(n2+1)] else numeric(0)
coords <- bind_rows(
tibble(name = ego_id, x = 0, y = 0),
tibble(name = r1_ids, x = r1*cos(a1), y = r1*sin(a1)),
tibble(name = r2_ids, x = r2*cos(a2), y = r2*sin(a2))
)
RING_COL <- c("0"="#d73027","1"="#fc8d59","2"="#4575b4")
str_vals <- pmax(1, V(ego_g)$strength)
nodes <- tibble(
id = V(ego_g)$name,
label = V(ego_g)$name_ru,
ring = as.character(V(ego_g)$ring),
value = pmax(8, rescale(str_vals, to = node_scale)),
color = RING_COL[as.character(V(ego_g)$ring)],
title = paste0("<b>", V(ego_g)$name_ru, "</b><br>",
"Кольцо: ", V(ego_g)$ring, "<br>",
"Сила: ", round(str_vals, 1))
) |> left_join(coords, by = c("id"="name"))
el <- as_edgelist(ego_g, names=TRUE)
ew <- E(ego_g)$weight
edges <- tibble(
from = el[,1], to = el[,2],
value = rescale(pmax(1,ew), to = edge_scale),
title = paste0("Реплик: ", ew),
color = ifelse(el[,1]==ego_id | el[,2]==ego_id, "#d73027", "#cccccc")
)
visNetwork(nodes, edges, width="100%", height=height, main=title) |>
visNodes(scaling=list(min=10, max=45),
font=list(size=12, strokeWidth=3, strokeColor="white")) |>
visEdges(smooth=FALSE) |>
visPhysics(enabled=FALSE) |>
visOptions(highlightNearest=list(enabled=TRUE, degree=1, hover=TRUE)) |>
visInteraction(navigationButtons=TRUE, tooltipDelay=80,
dragNodes=TRUE, zoomView=TRUE) |>
visLegend(
addNodes = list(
list(label="Центральный персонаж", shape="dot", color="#d73027", size=18),
list(label="Прямые собеседники", shape="dot", color="#fc8d59", size=14),
list(label="2-й порядок", shape="dot", color="#4575b4", size=10)
),
useGroups = FALSE
)
}
# Таблица топ-N персонажей
top_table <- function(g, n = 15, caption = "") {
tibble(
Имя = V(g)$name_ru,
Степень = V(g)$degree,
`Сила` = round(V(g)$strength, 1),
Посредничество = round(V(g)$betweenness, 4),
Близость = round(V(g)$closeness, 4),
PageRank = round(V(g)$pagerank, 5)
) |>
arrange(desc(Сила)) |>
slice_head(n = n) |>
kable(caption = caption) |>
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)
}
# Таблица сообществ
comm_table <- function(g, comm, caption = "") {
tibble(
id = V(g)$name,
name_ru = V(g)$name_ru,
community = membership(comm),
strength = V(g)$strength
) |>
arrange(community, desc(strength)) |>
group_by(community) |>
summarise(Размер = n(),
`Топ-10` = paste(head(name_ru, 10), collapse = ", "),
.groups = "drop") |>
rename(Сообщество = community) |>
arrange(desc(Размер)) |>
kable(caption = caption) |>
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)
}
# Таблица точек сочленения
ap_table <- function(g, caption = "") {
idx <- articulation_points(g)
if (length(idx) == 0) { cat("Точек сочленения нет.\n"); return(invisible(NULL)) }
tibble(
Имя = V(g)$name_ru[idx],
Степень = degree(g)[idx],
Посредничество = round(betweenness(g, normalized=TRUE)[idx], 4)
) |>
arrange(desc(Посредничество)) |>
kable(caption = caption) |>
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
}