21
21
# ' }
22
22
# ' @export
23
23
easylayout <- function (graph ) {
24
- precompute_iterations <- 1000
25
- initial_size_multiplier <- 75
26
-
27
24
# Nodes must have some sort of identifier.
28
25
# Falls back to 1, 2, 3... if "name" is not available.
29
26
if (is.null(igraph :: V(graph )$ name )) {
30
27
igraph :: V(graph )$ name <- as.character(1 : igraph :: vcount(graph ))
31
28
}
32
29
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
+ }
35
34
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
+ }
43
42
44
43
# Magic precomputing
45
44
vertices <- igraph :: as_data_frame(graph , " vertices" )
@@ -51,55 +50,44 @@ easylayout <- function(graph) {
51
50
factor_columns <- vertices | >
52
51
dplyr :: select(- name ) | >
53
52
dplyr :: select_if(is.factor ) | >
54
- dplyr :: mutate(dplyr :: across(dplyr :: everything(), ~ as.numeric ))
53
+ dplyr :: mutate(dplyr :: across(dplyr :: everything(), ~ as.numeric ))
55
54
56
55
bound_columns <- cbind(numeric_columns , factor_columns )
57
- print(head(bound_columns ))
58
56
59
57
# 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 )
61
59
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:" )
64
62
print(colnames(bound_columns ))
65
63
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
+ }
89
67
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
+ }
93
72
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 ]
96
79
}
97
80
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
+ )
103
91
104
92
igraph :: V(graph )$ component <- flag_for_grouping
105
93
@@ -136,9 +124,63 @@ easylayout <- function(graph) {
136
124
137
125
layout [, 2 ] <- - 1 * layout [, 2 ]
138
126
127
+ hash <- igraph :: as_edgelist(graph ) | > digest :: digest()
128
+
129
+ set_layout(hash , list (layout = layout , opts = " lalala" ))
130
+
139
131
layout
140
132
}
141
133
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
+
142
168
rescale <- function (x , from , to ) approxfun(range(x ), c(from , to ))(x )
143
169
144
170
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