|
4 | 4 | % Preparing data for testing |
5 | 5 | -export([prepare_unsigned_data/0, prepare_signed_data/0, |
6 | 6 | prepare_deeply_nested_complex_message/0]). |
| 7 | +-export([cache_path_to_graph/3, get_graph_data/1]). |
7 | 8 | -include("include/hb.hrl"). |
8 | 9 |
|
9 | 10 | %% @doc Render the given Key into svg |
@@ -200,6 +201,100 @@ collect_output(Port, Acc) -> |
200 | 201 | {error, timeout} |
201 | 202 | end. |
202 | 203 |
|
| 204 | +%% @doc Get graph data for the Three.js visualization |
| 205 | +get_graph_data(Opts) -> |
| 206 | + % Get the store from options |
| 207 | + Store = hb_opts:get(store, no_viable_store, Opts), |
| 208 | + |
| 209 | + % Try to generate graph using hb_cache_render |
| 210 | + Graph = try |
| 211 | + % Use hb_cache_render to build the graph |
| 212 | + {ok, Keys} = hb_store:list(Store, "/"), |
| 213 | + cache_path_to_graph(Store, #{}, Keys) |
| 214 | + catch |
| 215 | + Error:Reason:Stack -> |
| 216 | + ?event({hyperbuddy_graph_error, Error, Reason, Stack}), |
| 217 | + #{nodes => #{}, arcs => #{}, visited => #{}} |
| 218 | + end, |
| 219 | + |
| 220 | + % Extract nodes and links for the visualization |
| 221 | + NodesMap = maps:get(nodes, Graph, #{}), |
| 222 | + ArcsMap = maps:get(arcs, Graph, #{}), |
| 223 | + |
| 224 | + % Limit to top 500 nodes if there are too many |
| 225 | + NodesList = |
| 226 | + case maps:size(NodesMap) > 50000 of |
| 227 | + true -> |
| 228 | + % Take a subset of nodes |
| 229 | + {ReducedNodes, _} = lists:split( |
| 230 | + 500, |
| 231 | + maps:to_list(NodesMap) |
| 232 | + ), |
| 233 | + ReducedNodes; |
| 234 | + false -> |
| 235 | + maps:to_list(NodesMap) |
| 236 | + end, |
| 237 | + |
| 238 | + % Get node IDs for filtering links |
| 239 | + NodeIds = [ID || {ID, _} <- NodesList], |
| 240 | + |
| 241 | + % Convert to JSON format for web visualization |
| 242 | + Nodes = [ |
| 243 | + #{ |
| 244 | + <<"id">> => ID, |
| 245 | + <<"label">> => get_label(hb_util:bin(ID)), |
| 246 | + <<"type">> => get_node_type(Color) |
| 247 | + } |
| 248 | + || {ID, {_, Color}} <- NodesList |
| 249 | + ], |
| 250 | + |
| 251 | + % Filter links to only include those between nodes we're showing |
| 252 | + FilteredLinks = [ |
| 253 | + {From, To, Label} |
| 254 | + || {From, To, Label} <- maps:keys(ArcsMap), |
| 255 | + lists:member(From, NodeIds) andalso lists:member(To, NodeIds) |
| 256 | + ], |
| 257 | + |
| 258 | + Links = [ |
| 259 | + #{ |
| 260 | + <<"source">> => From, |
| 261 | + <<"target">> => To, |
| 262 | + <<"label">> => Label |
| 263 | + } |
| 264 | + || {From, To, Label} <- FilteredLinks |
| 265 | + ], |
| 266 | + |
| 267 | + % Return the JSON data |
| 268 | + JsonData = hb_json:encode(#{ |
| 269 | + <<"nodes">> => Nodes, |
| 270 | + <<"links">> => Links |
| 271 | + }), |
| 272 | + |
| 273 | + {ok, #{ |
| 274 | + <<"body">> => JsonData, |
| 275 | + <<"content-type">> => <<"application/json">> |
| 276 | + }}. |
| 277 | + |
| 278 | +%% @doc Convert node color from hb_cache_render to node type for visualization |
| 279 | +get_node_type(Color) -> |
| 280 | + case Color of |
| 281 | + "lightblue" -> <<"simple">>; |
| 282 | + "lightcoral" -> <<"composite">>; |
| 283 | + _ -> <<"unknown">> |
| 284 | + end. |
| 285 | + |
| 286 | +%% @doc Extract a readable label from a path |
| 287 | +get_label(Path) -> |
| 288 | + case binary:split(Path, <<"/">>, [global]) of |
| 289 | + [] -> Path; |
| 290 | + Parts -> |
| 291 | + FilteredParts = [P || P <- Parts, P /= <<>>], |
| 292 | + case FilteredParts of |
| 293 | + [] -> Path; |
| 294 | + _ -> lists:last(FilteredParts) |
| 295 | + end |
| 296 | + end. |
| 297 | + |
203 | 298 | % Test data preparation functions |
204 | 299 | prepare_unsigned_data() -> |
205 | 300 | Opts = #{ |
|
0 commit comments