Skip to content

Commit

Permalink
added metromap layout
Browse files Browse the repository at this point in the history
  • Loading branch information
schochastics committed Jan 16, 2024
1 parent ddb6586 commit 39ff292
Show file tree
Hide file tree
Showing 13 changed files with 563 additions and 123 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ Suggests:
LinkingTo:
Rcpp,
RcppArmadillo
RoxygenNote: 7.2.3
RoxygenNote: 7.3.0
Roxygen: list(markdown = TRUE)
VignetteBuilder: knitr
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export(annotate_circle)
export(draw_circle)
export(layout_as_backbone)
export(layout_as_dynamic)
export(layout_as_metromap)
export(layout_as_multilevel)
export(layout_igraph_backbone)
export(layout_igraph_centrality)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* `layout_with_constrained_stress()` and `layout_with_constrained_stress3D()`
work for disconnected graphs
* internal code refactoring
* added `layout_as_metromap()`

# graphlayouts 1.0.2

Expand Down
24 changes: 24 additions & 0 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@ constrained_stress_major3D <- function(y, dim, W, D, iter, tol) {
.Call(`_graphlayouts_constrained_stress_major3D`, y, dim, W, D, iter, tol)
}

criterion_angular_resolution <- function(adj, xy) {
.Call(`_graphlayouts_criterion_angular_resolution`, adj, xy)
}

criterion_edge_length <- function(el, xy, lg) {
.Call(`_graphlayouts_criterion_edge_length`, el, xy, lg)
}

criterion_balanced_edge_length <- function(adj_deg2, xy) {
.Call(`_graphlayouts_criterion_balanced_edge_length`, adj_deg2, xy)
}

criterion_line_straightness <- function() {
.Call(`_graphlayouts_criterion_line_straightness`)
}

criterion_octilinearity <- function(el, xy) {
.Call(`_graphlayouts_criterion_octilinearity`, el, xy)
}

layout_as_metro_iter <- function(adj, el, adj_deg2, xy, bbox, l, gr, w, bsize) {
.Call(`_graphlayouts_layout_as_metro_iter`, adj, el, adj_deg2, xy, bbox, l, gr, w, bsize)
}

reweighting <- function(el, N_ranks) {
.Call(`_graphlayouts_reweighting`, el, N_ranks)
}
Expand Down
8 changes: 8 additions & 0 deletions R/data-examples.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@

#' @format igraph object
"multilvl_ex"

#' Subway network of Berlin
#'
#' A dataset containing the subway network of Berlin
#' @format igraph object
#' @references
#' Kujala, Rainer, et al. "A collection of public transport network data sets for 25 cities." Scientific data 5 (2018): 180089.
"metro_berlin"
77 changes: 77 additions & 0 deletions R/metro_multicriteria.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#' @title Metro Map Layout
#' @description Metro map layout based on multicriteria optimization
#' @param object original graph
#' @param xy initial layout of the original graph
#' @param l desired multiple of grid point spacing. (l*gr determines desired edge length)
#' @param gr grid spacing. (l*gr determines desired edge length)
#' @param w weight vector for criteria (see details)
#' @param bsize number of grid points a station can move away rom its original position
#' @details The function optimizes the following five criteria using a hill climbing algorithm:
#' - *Angular Resolution Criterion*: The angles of incident edges at each station should be maximized, because if there is only a small angle between any two adjacent edges, then it can become difficult to distinguish between them
#' - *Edge Length Criterion*: The edge lengths across the whole map should be approximately equal to ensure regular spacing between stations. It is based on the preferred multiple, l, of the grid spacing, g. The purpose of the criterion is to penalize edges that are longer than or shorter than lg.
#' - *Balanced Edge Length Criterion*: The length of edges incident to a particular station should be similar
#' - *Line Straightness Criterion*: (not yet implemented) Edges that form part of a line should, where possible, be co-linear either side of each station that the line passes through
#' - *Octiinearity Criterion*: Each edge should be drawn horizontally, vertically, or diagonally at 45 degree, so we penalize edges that are not at a desired angle
#' @return new coordinates for stations
#' @references
#' Stott, Jonathan, et al. "Automatic metro map layout using multicriteria optimization." IEEE Transactions on Visualization and Computer Graphics 17.1 (2010): 101-114.
#' @author David Schoch
#' @examples
#' # the algorithm has problems with parallel edges
#' library(igraph)
#' g <- simplify(metro_berlin)
#' xy <- cbind(V(g)$lon, V(g)$lat) * 100
#'
#' # the algorithm is not very stable. try playing with the parameters
#' xy_new <- layout_as_metromap(g, xy, l = 2, gr = 0.5, w = c(100, 100, 1, 1, 100), bsize = 35)
#' @export
layout_as_metromap <- function(object, xy, l = 2, gr = 0.0025, w = rep(1, 5), bsize = 5) {
adj <- as_adj_list1(object)
adj <- lapply(adj, function(x) x - 1)
adj_deg2 <- adj[unlist(lapply(adj, length)) == 2]
el <- igraph::get.edgelist(object, FALSE) - 1

xy <- snap_to_grid(xy, gr)

bbox <- station_bbox(xy, bsize, gr)

xy_new <- layout_as_metro_iter(adj, el, adj_deg2, xy, bbox, l, gr, w, bsize)
xy_new
}

# helper ----
snap_to_grid <- function(xy, gr) {
xmin <- min(xy[, 1])
xmax <- max(xy[, 1])
ymin <- min(xy[, 2])
ymax <- max(xy[, 2])

deltax <- seq(xmin - 4 * gr, xmax + 4 * gr, by = gr)
deltay <- seq(ymin - 4 * gr, ymax + 4 * gr, by = gr)

xdiff <- outer(xy[, 1], deltax, function(x, y) abs(x - y))
ydiff <- outer(xy[, 2], deltay, function(x, y) abs(x - y))

xy_new <- cbind(deltax[apply(xdiff, 1, which.min)], deltay[apply(ydiff, 1, which.min)])
dups <- duplicated(xy_new)
while (any(dups)) {
xy_new[which(dups), ] <- xy_new[which(dups), ] + c(sample(c(1, -1), 1) * gr, sample(c(1, -1), 1) * gr)
dups <- duplicated(xy_new)
}
xy_new
}

station_bbox <- function(xy, bsize, gr) {
cbind(xy - bsize * gr, xy + bsize * gr)
}

as_adj_list1 <- function(g) {
n <- igraph::vcount(g)
lapply(1:n, function(i) {
x <- g[[i]][[1]]
attr(x, "env") <- NULL
attr(x, "graph") <- NULL
class(x) <- NULL
x
})
}
Binary file added data/metro_berlin.rda
Binary file not shown.
Binary file modified data/multilvl_ex.rda
Binary file not shown.
52 changes: 52 additions & 0 deletions man/layout_as_metromap.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions man/metro_berlin.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions src/RcppExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,84 @@ BEGIN_RCPP
return rcpp_result_gen;
END_RCPP
}
// criterion_angular_resolution
double criterion_angular_resolution(List adj, NumericMatrix xy);
RcppExport SEXP _graphlayouts_criterion_angular_resolution(SEXP adjSEXP, SEXP xySEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< List >::type adj(adjSEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type xy(xySEXP);
rcpp_result_gen = Rcpp::wrap(criterion_angular_resolution(adj, xy));
return rcpp_result_gen;
END_RCPP
}
// criterion_edge_length
double criterion_edge_length(IntegerMatrix el, NumericMatrix xy, double lg);
RcppExport SEXP _graphlayouts_criterion_edge_length(SEXP elSEXP, SEXP xySEXP, SEXP lgSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< IntegerMatrix >::type el(elSEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type xy(xySEXP);
Rcpp::traits::input_parameter< double >::type lg(lgSEXP);
rcpp_result_gen = Rcpp::wrap(criterion_edge_length(el, xy, lg));
return rcpp_result_gen;
END_RCPP
}
// criterion_balanced_edge_length
double criterion_balanced_edge_length(List adj_deg2, NumericMatrix xy);
RcppExport SEXP _graphlayouts_criterion_balanced_edge_length(SEXP adj_deg2SEXP, SEXP xySEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< List >::type adj_deg2(adj_deg2SEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type xy(xySEXP);
rcpp_result_gen = Rcpp::wrap(criterion_balanced_edge_length(adj_deg2, xy));
return rcpp_result_gen;
END_RCPP
}
// criterion_line_straightness
double criterion_line_straightness();
RcppExport SEXP _graphlayouts_criterion_line_straightness() {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
rcpp_result_gen = Rcpp::wrap(criterion_line_straightness());
return rcpp_result_gen;
END_RCPP
}
// criterion_octilinearity
double criterion_octilinearity(IntegerMatrix el, NumericMatrix xy);
RcppExport SEXP _graphlayouts_criterion_octilinearity(SEXP elSEXP, SEXP xySEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< IntegerMatrix >::type el(elSEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type xy(xySEXP);
rcpp_result_gen = Rcpp::wrap(criterion_octilinearity(el, xy));
return rcpp_result_gen;
END_RCPP
}
// layout_as_metro_iter
NumericMatrix layout_as_metro_iter(List adj, IntegerMatrix el, List adj_deg2, NumericMatrix xy, NumericMatrix bbox, double l, double gr, NumericVector w, double bsize);
RcppExport SEXP _graphlayouts_layout_as_metro_iter(SEXP adjSEXP, SEXP elSEXP, SEXP adj_deg2SEXP, SEXP xySEXP, SEXP bboxSEXP, SEXP lSEXP, SEXP grSEXP, SEXP wSEXP, SEXP bsizeSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< List >::type adj(adjSEXP);
Rcpp::traits::input_parameter< IntegerMatrix >::type el(elSEXP);
Rcpp::traits::input_parameter< List >::type adj_deg2(adj_deg2SEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type xy(xySEXP);
Rcpp::traits::input_parameter< NumericMatrix >::type bbox(bboxSEXP);
Rcpp::traits::input_parameter< double >::type l(lSEXP);
Rcpp::traits::input_parameter< double >::type gr(grSEXP);
Rcpp::traits::input_parameter< NumericVector >::type w(wSEXP);
Rcpp::traits::input_parameter< double >::type bsize(bsizeSEXP);
rcpp_result_gen = Rcpp::wrap(layout_as_metro_iter(adj, el, adj_deg2, xy, bbox, l, gr, w, bsize));
return rcpp_result_gen;
END_RCPP
}
// reweighting
NumericVector reweighting(IntegerMatrix el, List N_ranks);
RcppExport SEXP _graphlayouts_reweighting(SEXP elSEXP, SEXP N_ranksSEXP) {
Expand Down Expand Up @@ -191,6 +269,12 @@ static const R_CallMethodDef CallEntries[] = {
{"_graphlayouts_constrained_stress_major", (DL_FUNC) &_graphlayouts_constrained_stress_major, 6},
{"_graphlayouts_constrained_stress3D", (DL_FUNC) &_graphlayouts_constrained_stress3D, 3},
{"_graphlayouts_constrained_stress_major3D", (DL_FUNC) &_graphlayouts_constrained_stress_major3D, 6},
{"_graphlayouts_criterion_angular_resolution", (DL_FUNC) &_graphlayouts_criterion_angular_resolution, 2},
{"_graphlayouts_criterion_edge_length", (DL_FUNC) &_graphlayouts_criterion_edge_length, 3},
{"_graphlayouts_criterion_balanced_edge_length", (DL_FUNC) &_graphlayouts_criterion_balanced_edge_length, 2},
{"_graphlayouts_criterion_line_straightness", (DL_FUNC) &_graphlayouts_criterion_line_straightness, 0},
{"_graphlayouts_criterion_octilinearity", (DL_FUNC) &_graphlayouts_criterion_octilinearity, 2},
{"_graphlayouts_layout_as_metro_iter", (DL_FUNC) &_graphlayouts_layout_as_metro_iter, 9},
{"_graphlayouts_reweighting", (DL_FUNC) &_graphlayouts_reweighting, 2},
{"_graphlayouts_sparseStress", (DL_FUNC) &_graphlayouts_sparseStress, 6},
{"_graphlayouts_stress", (DL_FUNC) &_graphlayouts_stress, 3},
Expand Down
Loading

0 comments on commit 39ff292

Please sign in to comment.