Я работаю с языком программирования R.
У меня есть набор данных городов и туристических маршрутов (все маршруты: Латинская Америка -> Северная Америка -> Европа -> Азия). Я создал графовую сеть этих данных:
library(igraph)
north_american_cities <- c("New York", "Los Angeles", "Chicago", "Houston", "Phoenix")
european_cities <- c("London", "Berlin", "Madrid", "Rome", "Paris")
asian_cities <- c("Tokyo", "Delhi", "Shanghai", "Beijing", "Mumbai")
latin_american_cities <- c("Lima", "Bogota", "Buenos Aires", "Sao Paulo", "Mexico City")
set.seed(123)
n <- 30
la_cities_sample <- sample(latin_american_cities, n, replace = TRUE)
na_cities_sample <- sample(north_american_cities, n, replace = TRUE)
eu_cities_sample <- sample(european_cities, n, replace = TRUE)
as_cities_sample <- sample(asian_cities, n, replace = TRUE)
df <- data.frame(LatinAmerica = la_cities_sample,
NorthAmerica = na_cities_sample,
Europe = eu_cities_sample,
Asia = as_cities_sample,
stringsAsFactors = FALSE)
df <- df[!duplicated(df), ]
edges_df <- data.frame(from = c(df$LatinAmerica, df$NorthAmerica, df$Europe),
to = c(df$NorthAmerica, df$Europe, df$Asia))
edge_list <- as.matrix(edges_df)
g <- graph_from_edgelist(edge_list, directed = TRUE)
plot(g)
Отсюда я написал функцию, которая берет любой город и находит все возможные маршруты путешествий, проходящие через этот город от начала до конца:
find_paths_through_city <- function(graph, target_city, path_length = 4) {
all_paths <- all_simple_paths(graph, V(graph))
valid_paths <- list()
for (path in all_paths) {
path_cities <- V(graph)[path]$name
if (target_city %in% path_cities && length(path_cities) == path_length) {
valid_paths <- append(valid_paths, list(path_cities))
}
}
if (length(valid_paths) > 0) {
paths_df <- do.call(rbind, lapply(valid_paths, function(x) as.data.frame(t(x), stringsAsFactors = FALSE)))
colnames(paths_df) <- paste0("City", 1:path_length)
} else {
paths_df <- data.frame(matrix(ncol = path_length, nrow = 0))
colnames(paths_df) <- paste0("City", 1:path_length)
}
return(paths_df)
}
Здесь я тестировал эту функцию для конкретного города:
city <- "New York"
paths_through_city <- find_paths_through_city(g, target_city = city, path_length = 4)
unique_cities <- unique(as.vector(as.matrix(paths_through_city)))
subgraph <- induced_subgraph(g, vids = unique_cities)
plot(subgraph, vertex.size=10, vertex.label.cex=0.8, edge.arrow.size=0.5, main=paste("Subgraph of Paths Passing Through", city))
Мой вопрос: Отсюда я хочу создать интерактивный график, который позволит пользователю щелкнуть заданный узел на графике с помощью Visnetwork, а затем выделить все возможные маршруты передвижения, проходящие через этот узел.
Мои друзья и я попытались узнать, как это сделать сегодня — мы попытались написать функцию javascript для этого и проделали половину работы:
library(visNetwork)
nodes <- data.frame(id = V(g)$name, label = V(g)$name, stringsAsFactors = FALSE)
edges <- data.frame(from = edges_df$from, to = edges_df$to, stringsAsFactors = FALSE)
highlight_js <- '
function(params) {
if (params.nodes.length == 0) return;
var selectedNode = params.nodes[0];
var pathLength = 4;
var graph = this.body.data;
var allNodes = graph.nodes.get();
var allEdges = graph.edges.get();
var validPaths = [];
function findPaths(currentPath, currentNode, depth) {
if (depth == pathLength) {
validPaths.push(currentPath.slice());
return;
}
var connectedEdges = allEdges.filter(function(edge) {
return edge.from == currentNode;
});
connectedEdges.forEach(function(edge) {
findPaths(currentPath.concat(edge.to), edge.to, depth + 1);
});
}
findPaths([selectedNode], selectedNode, 1);
var nodesToUpdate = {};
var edgesToUpdate = {};
validPaths.forEach(function(path) {
path.forEach(function(nodeId, index) {
nodesToUpdate[nodeId] = {
id: nodeId,
color: "red",
label: allNodes.find(node => node.id == nodeId).label
};
if (index < path.length - 1) {
var fromNode = nodeId;
var toNode = path[index + 1];
var edge = allEdges.find(edge => edge.from == fromNode && edge.to == toNode);
if (edge) {
edgesToUpdate[edge.id] = {
id: edge.id,
color: "red"
};
}
}
});
});
graph.nodes.update(Object.values(nodesToUpdate));
graph.edges.update(Object.values(edgesToUpdate));
}
'
visNetwork(nodes, edges) %>%
visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) %>%
visPhysics(stabilization = list(iterations = 2000), solver = "barnesHut", minVelocity = 0.75) %>%
visEvents(selectNode = highlight_js)
Как можно видеть здесь, хотя выбран азиатский город (Токио), ни один город Латинской Америки не выделен.
В исходном наборе данных это выглядит так:
> df[df$Asia == "Tokyo",]
LatinAmerica NorthAmerica Europe Asia
13 Buenos Aires Houston Madrid Tokyo
15 Sao Paulo Los Angeles Paris Tokyo
21 Bogota New York Rome Tokyo
23 Buenos Aires Houston Berlin Tokyo
Может кто-нибудь показать нам, как это исправить?
Спасибо!
Предполагая, что вы действительно хотите получить все пути определенной длины, проходящие через город, вы можете сделать следующее:
Н.Б. Для более красивого оформления кода я напечатал следующие коды Javascript как JavaScript. Для использования в R вы, конечно, должны определить их как строки (экранированные JS
) и передать их соответствующим функциям,
window.routes
).store_all_paths
// store_all_paths <- JS("
function(el, data) {
const network = this.network;
function visit_node(network, city, path, path_list, max_length) {
if (!path.includes(city)) {
path.push(city);
if (path.length === max_length) {
// need to stringify because unique contraint does not work on array objects
path_list.add(JSON.stringify(path))
} else {
const neighbors = network.getConnectedNodes(city, 'to');
neighbors.forEach((nb) => visit_node(network, nb, [...path], path_list,
max_length));
}
}
}
const max_length = 4;
const path_list = new Set();
data.nodes.id.forEach((city) => visit_node(network, city, [], path_list, max_length));
window.routes = [...path_list].map((rt) => JSON.parse(rt));
}
//")
handle_click
// handle_click <- JS("
function(params) {
params.event.preventDefault();
if (params.nodes.length > 0) {
// 1. Find all routes including the selected nodes
const routes = params.nodes.map(node =>
window.routes.filter(route => route.includes(node))).flat();
// 2. From these routes extract all involved cities and all involved edges
const cities_on_route = [...new Set(routes.flat(2))];
const all_edges = this.body.data.edges;
const edges_on_route = [...new Set(routes.map(function(route) {
const edges = [];
for(let i = 0; i < route.length - 1; i++) {
const edge = all_edges.get({
filter: (item) => item.from === route[i] & item.to === route[i + 1]
})
edges.push(...edge)
}
return edges.map((e) => e.id);
}).flat())];
// 3. Color Edges and Nodes
// N.B. we could do that immediately instead of first saving all ids
// but for illustrative purposes i made this performance wise not os smart split
this.body.data.nodes.updateOnly(
cities_on_route.map((id) => (
{
id: id,
color: {
background: 'red',
border: 'red',
highlight: {
background: 'red',
border: 'red'
}},
label: this.body.data.nodes.get(id).label
}
))
);
this.body.data.edges.updateOnly(
edges_on_route.map((id) => (
{
id: id,
color: {
color: 'red',
highlight: 'red'
}
}
))
);
console.info({routes: routes, edges: edges_on_route})
}
}
//")
handle_deselect
// handle_deselect <- JS("
function(params) {
this.body.data.nodes.updateOnly(this.body.data.nodes.get().map((node) =>
({
id: node.id,
color: {
background: '#D2E5FF',
border: '#2B7CE9',
highlight: {
background: '#D2E5FF',
border: '#2B7CE9'
}
}
})
));
this.body.data.edges.updateOnly(this.body.data.edges.get().map((edge) =>
({
id: edge.id,
color: {
color: '#848484',
highlight: '#848484',
inherit: false
}
})
)
);
}
//")
visNetwork(nodes, edges) %>%
visOptions(nodesIdSelection = TRUE) %>%
visPhysics(stabilization = list(iterations = 2000), solver = "barnesHut",
minVelocity = 0.75) %>%
visEvents(selectNode = handle_click, deselectNode = handle_deselect) %>%
onRender(store_all_paths)
@thotal: огромное спасибо за чудесный ответ! Я с нетерпением жду возможности последовать вашему совету и попытаться превратить эти Java-функции в R в виде строк!
Как вы думаете, ваш подход можно применить к этому вопросу здесь? stackoverflow.com/questions/78675817/r-multiple-dropdown-menнас
Ну, это совершенно другой вопрос (множественный выбор, и он не имеет ничего общего с этим вопросом, кроме работы с vis
.
Если ответ соответствует вашим потребностям, я был бы рад, если бы вы его приняли.
Я думаю, что библиотека visnetwork — ваш лучший вариант — воссоздал ее с помощью следующего кода:
library(visNetwork)
# Define nodes and edges
nodes <- data.frame(id = 1:6, label = paste("Node", 1:6))
edges <- data.frame(from = c(1, 1, 2, 3, 4, 4), to = c(2, 3, 4, 5, 5, 6))
# Create the network graph
visNetwork(nodes, edges) %>%
visNodes(
shape = "dot",
size = 10
) %>%
visEdges(
arrows = "to"
) %>%
visInteraction(
navigationButtons = TRUE
)
Я не понимаю, как вы придумываете свои пути. Почему путь
['Bogota', 'New York', 'Paris', 'Mumbai']
не включен в вашpaths_through_city
?