Skip to content

Commit 579e3b0

Browse files
refactor: splits main logic into multiple steps and introduces caching
1 parent 20a82f4 commit 579e3b0

File tree

2 files changed

+93
-50
lines changed

2 files changed

+93
-50
lines changed

DESCRIPTION

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Encoding: UTF-8
1010
Roxygen: list(markdown = TRUE)
1111
RoxygenNote: 7.3.2
1212
Imports:
13+
digest,
1314
dplyr,
1415
igraph,
1516
jsonlite,

R/app.R

+92-50
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,24 @@
2121
#' }
2222
#' @export
2323
easylayout <- function(graph) {
24-
precompute_iterations <- 1000
25-
initial_size_multiplier <- 75
26-
2724
# Nodes must have some sort of identifier.
2825
# Falls back to 1, 2, 3... if "name" is not available.
2926
if (is.null(igraph::V(graph)$name)) {
3027
igraph::V(graph)$name <- as.character(1:igraph::vcount(graph))
3128
}
3229

33-
graph_components <- igraph::components(graph)
34-
largest_component_id <- graph_components$csize |> which.max()
30+
if (is.matrix(layout)) {
31+
print("Loading user-specified initial layout...")
32+
return(start_app(graph, layout))
33+
}
3534

36-
# Nodes outside the largest component
37-
# will receive special treatment in the web app
38-
flag_for_grouping <- ifelse(
39-
test = graph_components$membership == largest_component_id,
40-
yes = NA,
41-
no = as.character(graph_components$membership)
42-
)
35+
hash <- igraph::as_edgelist(graph) |> digest::digest()
36+
cached_layout <- get_layout(hash)
37+
38+
if (!is.null(cached_layout)) {
39+
print("Using cached layout from previous run...")
40+
return(start_app(graph, cached_layout$layout))
41+
}
4342

4443
# Magic precomputing
4544
vertices <- igraph::as_data_frame(graph, "vertices")
@@ -51,55 +50,44 @@ easylayout <- function(graph) {
5150
factor_columns <- vertices |>
5251
dplyr::select(-name) |>
5352
dplyr::select_if(is.factor) |>
54-
dplyr::mutate(dplyr::across(dplyr::everything(), ~ as.numeric))
53+
dplyr::mutate(dplyr::across(dplyr::everything(), ~as.numeric))
5554

5655
bound_columns <- cbind(numeric_columns, factor_columns)
57-
print(head(bound_columns))
5856

5957
# Precomputing only works if there are numeric columns
60-
both_dimensions_not_empty <- all(bound_columns |> dim() > 0)
58+
columns_not_empty <- all(bound_columns |> dim() > 0)
6159

62-
if (both_dimensions_not_empty) {
63-
print("The following columns will be used to precompute initial positions:")
60+
if (columns_not_empty) {
61+
print("easylayout will use the following columns to precompute initial positions:")
6462
print(colnames(bound_columns))
6563

66-
distance_matrix <- numeric_columns |>
67-
apply(2, rescale, from = 0, to = 1) |>
68-
dist() |>
69-
as.matrix()
70-
71-
similarity_matrix <- 1 / (distance_matrix^2)
72-
73-
similarity_matrix[similarity_matrix == Inf] <- max(similarity_matrix[similarity_matrix < Inf])
74-
75-
row.names(similarity_matrix) <- igraph::V(graph)$name
76-
colnames(similarity_matrix) <- igraph::V(graph)$name
77-
78-
similarity_graph <- igraph::graph_from_adjacency_matrix(
79-
adjmatrix = similarity_matrix,
80-
mode = "undirected",
81-
weighted = TRUE,
82-
diag = FALSE
83-
)
84-
85-
similarity_layout <- igraph::layout_with_fr(
86-
graph = similarity_graph,
87-
niter = precompute_iterations
88-
) * initial_size_multiplier
64+
precomputed_layout <- precompute_layout(graph = graph, cols = bound_columns)
65+
return(start_app(graph, precomputed_layout))
66+
}
8967

90-
# Centers layout around origin = [0, 0]
91-
similarity_layout[, 1] <- similarity_layout[, 1] - mean(similarity_layout[, 1])
92-
similarity_layout[, 2] <- similarity_layout[, 2] - mean(similarity_layout[, 2])
68+
# If any of the previous attempts at retrieving an initial layout failed,
69+
# then we just run the app without any initial layout
70+
return(start_app(graph))
71+
}
9372

94-
igraph::V(graph)$x <- similarity_layout[, 1]
95-
igraph::V(graph)$y <- similarity_layout[, 2]
73+
start_app <- function(graph, layout) {
74+
if (!missing(layout)) {
75+
# Browser stuff generally considers [0, 0] to be the top-left corner
76+
# of the screen, therefore we need to invert the Y axis
77+
igraph::V(graph)$y <- layout[, 2] * -1
78+
igraph::V(graph)$x <- layout[, 1]
9679
}
9780

98-
# TODO: Handle user given layout
99-
# if (is.matrix(layout)) {
100-
# igraph::V(graph)$x <- layout[, 1]
101-
# igraph::V(graph)$y <- layout[, 2]
102-
# }
81+
graph_components <- igraph::components(graph)
82+
largest_component_id <- graph_components$csize |> which.max()
83+
84+
# Only nodes outside the largest component
85+
# will receive special treatment in the web app
86+
flag_for_grouping <- ifelse(
87+
test = graph_components$membership == largest_component_id,
88+
yes = NA,
89+
no = as.character(graph_components$membership)
90+
)
10391

10492
igraph::V(graph)$component <- flag_for_grouping
10593

@@ -136,9 +124,63 @@ easylayout <- function(graph) {
136124

137125
layout[, 2] <- -1 * layout[, 2]
138126

127+
hash <- igraph::as_edgelist(graph) |> digest::digest()
128+
129+
set_layout(hash, list(layout = layout, opts = "lalala"))
130+
139131
layout
140132
}
141133

134+
precompute_layout <- function(graph, cols) {
135+
LAYOUT_SIZE_FACTOR <- 75
136+
137+
distance_matrix <- cols |>
138+
apply(2, rescale, from = 0, to = 1) |>
139+
dist() |>
140+
as.matrix()
141+
142+
similarity_matrix <- 1 / (distance_matrix^2)
143+
144+
similarity_matrix[similarity_matrix == Inf] <- max(similarity_matrix[similarity_matrix < Inf])
145+
146+
row.names(similarity_matrix) <- igraph::V(graph)$name
147+
colnames(similarity_matrix) <- igraph::V(graph)$name
148+
149+
similarity_graph <- igraph::graph_from_adjacency_matrix(
150+
adjmatrix = similarity_matrix,
151+
mode = "undirected",
152+
weighted = TRUE,
153+
diag = FALSE
154+
)
155+
156+
similarity_layout <- igraph::layout_with_fr(
157+
graph = similarity_graph,
158+
niter = 1000
159+
) * LAYOUT_SIZE_FACTOR
160+
161+
# Centers layout around origin = [0, 0]
162+
similarity_layout[, 1] <- similarity_layout[, 1] - mean(similarity_layout[, 1])
163+
similarity_layout[, 2] <- similarity_layout[, 2] - mean(similarity_layout[, 2])
164+
165+
similarity_layout
166+
}
167+
142168
rescale <- function(x, from, to) approxfun(range(x), c(from, to))(x)
143169

144170
has_at_least_two_values <- function(x) length(unique(x)) > 1
171+
172+
# https://hydroecology.net/implementing-session-cache-r-packages/
173+
# https://github.com/tidyverse/ggplot2/blob/main/R/plot-last.R
174+
.layout_store <- function() {
175+
.layout_map <- NULL
176+
177+
list(
178+
get = function(hash) .layout_map[[hash]],
179+
set = function(hash, value) .layout_map[[hash]] <<- value
180+
)
181+
}
182+
.store <- .layout_store()
183+
184+
set_layout <- function(hash, value) .store$set(hash, value)
185+
186+
get_layout <- function(hash) .store$get(hash)

0 commit comments

Comments
 (0)