Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: arcusfelis/gexf
base: 589a20de2a
...
head fork: arcusfelis/gexf
compare: c3d526f7fd
  • 2 commits
  • 3 files changed
  • 0 commit comments
  • 1 contributor
Commits on Dec 13, 2012
@arcusfelis Add offset for ellipint. 63d486f
Commits on Dec 19, 2012
@arcusfelis Add a world layout. c3d526f
Showing with 424 additions and 120 deletions.
  1. +14 −6 src/ellipint.erl
  2. +88 −114 src/gexf_xref.erl
  3. +322 −0 src/gexf_xref_world.erl
View
20 src/ellipint.erl
@@ -12,8 +12,8 @@
eccentricity/2,
- point_generator/3,
- cached_point_generator/3]).
+ point_generator/4,
+ cached_point_generator/4]).
-ifdef(TEST).
-include_lib("proper/include/proper.hrl").
@@ -184,19 +184,27 @@ rho_m(K, ThetaM) ->
%% @doc Generates N random points for an ellipse inside the ellipse,
%% parametered by (A cos t, B sin t).
-point_generator(A, B, N) ->
+%% O is a rotation offset.
+point_generator(A, B, N, O) ->
TotalArcLen = circumference(A, B),
ArcLen = TotalArcLen / N,
fun(X) when 1 =< X, X =< N ->
- P = (X - 1) * ArcLen,
+ P = fix_overflow(TotalArcLen, (X - 1) * ArcLen + O),
K = eccentricity(A, B),
Angle = arc_length_to_angle(P, K, A, 100, 100, 0.0001),
{A*math:cos(Angle), B*math:sin(Angle)}
end.
-cached_point_generator(A, B, N) ->
- F = point_generator(A, B, N),
+%% @doc Decrease Cur, when it is too high or too low.
+%% It fixes only if abs(Cur / Max) < 2.
+fix_overflow(Max, Cur) when Cur < 0 -> Max + Cur;
+fix_overflow(Max, Cur) when Cur < Max -> Cur;
+fix_overflow(Max, Cur) -> Cur - Max.
+
+
+cached_point_generator(A, B, N, O) ->
+ F = point_generator(A, B, N, O),
T = list_to_tuple(lists:map(F, lists:seq(1, N))),
fun(X) -> element(X, T) end.
View
202 src/gexf_xref.erl
@@ -1,6 +1,5 @@
-module(gexf_xref).
-%-export(['e-v'/1]).
--export(['e-v-m'/2]).
+-export([generate/2]).
-compile({parse_transform, mead}).
-compile({parse_transform, cut}).
-compile({parse_transform, chacha}).
@@ -21,16 +20,6 @@
-define(MFA_NODE_TYPE_ATTR_ID, 3).
-%'e-v'(Xref) ->
-% {ok, Funs} = xref:q(Xref, "V"),
-% {ok, Calls} = xref:q(Xref, "E"),
-% Fun2Num = enumerate(Funs),
-% Call2Num = enumerate(Calls),
-% Nodes = [mfa_node(Id, MFA) || {MFA, Id} <- Fun2Num],
-% Edges = [call_edge(Fun2Num, Id, FromMFA, ToMFA)
-% || {{FromMFA, ToMFA}, Id} <- Call2Num],
-% gexf:document(gexf:graph(Nodes, Edges)).
-
module_colors(Mods) ->
% crypto:rand_bytes(length(Mods) * 3).
RGBs =
@@ -55,12 +44,13 @@ brighter_colors(Mask, Colors) ->
union(D1, D2) ->
orddict:merge(fun(_K, X, _Y) -> X end, D1, D2).
-'e-v-m'(Xref, Info) ->
+generate(Xref, Info) ->
%% Returned values were already sorted and they are unique.
{ok, XFuns} = xref:q(Xref, "X"),
{ok, LFuns} = xref:q(Xref, "L"),
{ok, Calls} = xref:q(Xref, "XC|||(X+L)|||AM"),
{ok, Mods} = xref:q(Xref, "AM"), %% Mods :: [atom()]
+ [erlang:error(no_data) || Mods =:= []],
%% Connected functions are called from outside or calls something outside.
ConFuns = lists:usort(lists:flatmap(fun tuple_to_list/1, Calls)),
@@ -79,7 +69,7 @@ union(D1, D2) ->
Fun2Num = union(XFun2Num, LFun2Num),
%% Group functions by module.
- FunByMods = clusterize(fun({MFA, _}) -> mfa_to_module(MFA) end, Fun2Num),
+ FunByMods = lists2:group_with(fun({MFA, _}) -> mfa_to_module(MFA) end, Fun2Num),
%% Calculate how many functions are in each module.
FunCountByMods = [{M, length(Fs)} || {M, Fs} <- FunByMods],
@@ -108,7 +98,8 @@ union(D1, D2) ->
ClusterCount = LargeModCount + length(TinyFunCountByModsList),
%% This function calculates the position of the cluster from the cluster id.
- ClusterPos = get_module_circle_position(ClusterCount),
+ ExpClusterPos = get_module_exp_circle_position(ClusterCount),
+ LocClusterPos = get_module_loc_circle_position(ClusterCount),
%% Module to its node id.
Mod2Id = orddict:fetch(_, Mod2Num),
@@ -125,49 +116,83 @@ union(D1, D2) ->
%% This function returns true, if the passed MFA is exported.
IsFunExported = ordsets:is_element(_, XConFuns),
- %% A function for splitting a list into a list of groups.
- GroupKeyMaker = fun({MFA, _}) -> {Fun2ClusterId(MFA), IsFunExported(MFA)} end,
+ %% SMALL MODULE NODES
+ %% Few small modules are combined info one circle, so Funs can have modules
+ %% from different modules.
+ %% Layout for small modules consists of a set of segments.
+ %% While for usual layout the module node is in center,
+ %% the place for small module node is on a circle.
+ %%
+ %% This code inserts a module node in the head of segment.
+ %%
+ %% This code sorts functions and modules to put modules before functions
+ %% in each module group.
+ SortKeyMaker = fun
+ ({{M,_,_} = _MFA, _}) ->
+ M;
+ ({M, _ClusterNum}) ->
+ M
+ end,
+ SortedNodes = lists2:collate_with(SortKeyMaker, TinyMod2ClusterNum ++ Fun2Num),
+
+ %% A function for splitting a list into a list of groups.
+ GroupKeyMaker = fun
+ ({{_,_,_} = MFA, _}) ->
+ {Fun2ClusterId(MFA), IsFunExported(MFA)};
+ ({_M, ClusterNum}) ->
+ {ClusterNum, true}
+ end,
+
%% This orddict contains function names grouped by their cluster id and function type.
- FunGroups = clusterize(GroupKeyMaker, Fun2Num),
+ %% group_with/2 saves order.
+ FunGroups = lists2:group_with(GroupKeyMaker, SortedNodes),
FunNodes = % [[Node]]
[begin
%% Calculate the center of the cluster.
- ClusterPosValue = ClusterPos(ClusterId),
%% This function calculates the coordinates of the point on the circle.
%% xs and ys are in [-1..1].
ParentCirclePosValue =
if
- IsExported ->
- ClusterPosValue;
-
+ IsExported -> ExpClusterPos(ClusterId);
%% This paint is the second center of the cluster
%% (it is a point of the small ellipse).
- %%
- true ->
- %% Compress and rotate the main ellipse:
- chain(gexf:scale_position(0.7),
- gexf:rotate_position(degrees_to_radians(10))
- -- ClusterPosValue)
+ true -> LocClusterPos(ClusterId)
end,
Scale = function_node_scale(IsExported),
NodeSizeValue = chain(gexf:size, function_node_size -- IsExported),
+
FunPos = get_function_circle_position(length(Funs)),
- WithFuns = fun({MFA, FunId}, FunNumInCluster) ->
+ WithFuns = fun
+ ({MFA = {_,_,_}, FunId}, FunNumInCluster) ->
% io:format(user, "~p\n~p\t", [MFA, FunId]),
- chain(gexf:add_position(chain(
- %% Decrease the size of the calls' circle and move
- %% (the center of this circle is the center of the cluster
- %% or its virtual center).
- gexf:relative_position(ParentCirclePosValue),
- gexf:scale_position(Scale),
- FunPos -- FunNumInCluster)),
- gexf:add_size(NodeSizeValue),
- gexf:add_color(FunColor(MFA)),
- mfa_node(Info, FunId, IsExported) -- MFA)
+ chain(gexf:add_position(chain(
+ %% Decrease the size of the calls' circle and move
+ %% (the center of this circle is the center of the cluster
+ %% or its virtual center).
+ gexf:relative_position(ParentCirclePosValue),
+ gexf:scale_position(Scale),
+ FunPos -- FunNumInCluster)),
+ gexf:add_size(NodeSizeValue),
+ gexf:add_color(FunColor(MFA)),
+ mfa_node(Info, FunId, IsExported) -- MFA);
+
+ %% Step 3. It is a special case for module node.
+ ({Mod, _ClusterId}, ModNumInCluster) ->
+ Id = Mod2Id(Mod),
+ chain(gexf:add_position(chain(
+ %% Decrease the size of the calls' circle and move
+ %% (the center of this circle is the center of the cluster
+ %% or its virtual center).
+ gexf:relative_position(ParentCirclePosValue),
+ gexf:scale_position(Scale),
+ FunPos -- ModNumInCluster)),
+ gexf:add_size(gexf:size(9)),
+ gexf:add_color(module_color(ModColors, Id))
+ -- module_node(Info, Id, Mod))
end,
- countermap(WithFuns, Funs)
+ lists2:cmap(WithFuns, Funs)
end
%% The `Funs' variable is a list of MFA.
|| {{ClusterId, IsExported}, Funs} <- FunGroups],
@@ -179,7 +204,7 @@ union(D1, D2) ->
[begin
Id = Mod2Id(Mod),
ClusterId = Mod2ClusterId(Mod),
- chain(gexf:add_position(ClusterPos(ClusterId)),
+ chain(gexf:add_position(ExpClusterPos(ClusterId)),
gexf:add_size(gexf:size(15)),
gexf:add_color(module_color(ModColors, Id))
-- module_node(Info, Id, Mod))
@@ -187,28 +212,6 @@ union(D1, D2) ->
?check(?assertEqual(length(LargeModNodes), length(LargeMods))),
- TinyClusterNum2Mod = lists:sort(swap_pairs(TinyMod2ClusterNum)),
- SmallModGroups = tuple_e1_key_e2_value_groups(TinyClusterNum2Mod),
- %% Render small modules.
- SmallModNodes =
- [begin
- ModPos = get_function_circle_position(length(ModGroup)),
- ClusterPosValue = ClusterPos(ClusterId),
- {Mod2NumGroup, _Next} = enumerate(ModGroup),
- [begin
- Id = Mod2Id(Mod),
- chain(gexf:add_position(chain(
- gexf:relative_position(gexf:scale_position(1.2, ClusterPosValue)),
- gexf:scale_position(0.08),
- ModPos -- Mod2NumInGroup)),
- gexf:add_size(gexf:size(8)),
- gexf:add_color(module_color(ModColors, Id))
- -- module_node(Info, Id, Mod))
- end || {Mod, Mod2NumInGroup} <- Mod2NumGroup]
-
- end || {ClusterId, ModGroup} <- SmallModGroups],
-
- ?check(?assertEqual(length(SmallModGroups), length(SmallModNodes))),
%% ME ||| AM: Analyzed modules calls.
%% AME to a call count
@@ -217,10 +220,10 @@ union(D1, D2) ->
%% TODO: Are `Calls' sorted? then we can remove `usort'.
AME2Count = calls_to_ame_count(lists:usort(Calls)),
- Nodes = LargeModNodes ++ lists:flatten(SmallModNodes) ++ lists:flatten(FunNodes),
+ Nodes = LargeModNodes ++ lists:flatten(FunNodes),
{Call2Num, Next@} = enumerate(Calls),
{MF2Num, Next@} = enumerate(XConFuns ++ LConFuns, Next@),
- {MMC2Num, _Next} = enumerate(AME2Count, Next@),
+ {MMC2Num, _Next} = enumerate(AME2Count, Next@),
Fun2FunEdges =
[call_edge(Fun2Num, Id, FromMFA, ToMFA)
|| {{FromMFA, ToMFA}, Id} <- Call2Num],
@@ -319,30 +322,6 @@ mfa_to_string({_M, F, A}) ->
module_to_string(Mod) -> atom_to_list(Mod).
-clusterize(KeyFn, Xs) ->
- clusterize_sorted(KeyFn, key_than_value_sort(KeyFn, Xs)).
-
-%% @doc List is a sorted record with `KeyFn' as a sorter.
-clusterize_sorted(KeyFn, [H|T]) ->
- do_clusterize(KeyFn, KeyFn(H), T, [H], []).
-
-key_than_value_sort(KeyFn, Xs) ->
- lists:sort(fun(X, Y) -> {KeyFn(X), X} < {KeyFn(Y), Y} end, Xs).
-
-%% A - intermidiate acc; R - acc for result
-%% Accs are reversed.
-do_clusterize(KeyFn, PrevKey, [H|T], A, R) ->
- case KeyFn(H) of
- PrevKey ->
- do_clusterize(KeyFn, PrevKey, T, [H|A], R);
- NewKey ->
- RH = {PrevKey, lists:reverse(A)},
- do_clusterize(KeyFn, NewKey, T, [H], [RH|R])
- end;
-do_clusterize(_KeyFn, PrevKey, [], A, R) ->
- RH = {PrevKey, lists:reverse(A)},
- lists:reverse([RH|R]).
-
%% ------------------------------------------------------------------
%% Circle
%% ------------------------------------------------------------------
@@ -379,8 +358,16 @@ get_function_circle_position(PointCount) ->
get_dense_circle_position(PointCount).
-get_module_circle_position(PointCount) ->
- Gen = ellipint:cached_point_generator(1.6, 1, PointCount),
+get_module_exp_circle_position(PointCount) ->
+ Gen = ellipint:cached_point_generator(1.7, 1, PointCount, 0),
+ fun(Num) ->
+ {X, Y} = Gen(Num),
+ gexf:position(X, Y, 0)
+ end.
+
+
+get_module_loc_circle_position(PointCount) ->
+ Gen = ellipint:cached_point_generator(1.4, 0.7, PointCount, 0.05),
fun(Num) ->
{X, Y} = Gen(Num),
gexf:position(X, Y, 0)
@@ -425,15 +412,15 @@ get_dense_circle_position(PointCount) ->
-spec function_node_size(IsExported) -> Size when
IsExported :: boolean(), Size :: number().
-function_node_size(true) -> 6;
-function_node_size(false) -> 3.
+function_node_size(true) -> 5;
+function_node_size(false) -> 4.
%% @doc The scale controls the size of the function circle.
-spec function_node_scale(IsExported) -> Scale when
IsExported :: boolean(), Scale :: number().
-function_node_scale(true) -> 0.08;
+function_node_scale(true) -> 0.07;
function_node_scale(false) -> 0.05.
@@ -533,26 +520,6 @@ swap_pairs(Pairs) ->
[{V, K} || {K, V} <- Pairs].
-countermap(F, Xs) ->
- countermap(F, Xs, 1).
-
-
-countermap(F, [X|Xs], C) ->
- [F(X, C)|countermap(F, Xs, C+1)];
-
-countermap(_F, [], _C) ->
- [].
-
-
--ifdef(TEST).
-
-clusterize_test_() ->
- [?_assertEqual(clusterize(fun(X) -> X rem 2 end, [1,2,4,5,3]), [{0, [2, 4]}, {1, [1, 3, 5]}])].
-
-key_than_value_sort_test_() ->
- [?_assertEqual(key_than_value_sort(fun(X) -> X rem 2 end, [1,2,4,5,3]), [2,4,1,3,5])].
-
--endif.
@@ -571,3 +538,10 @@ calls_to_ame_count(Calls) ->
call_to_ame({{M1,_,_}, {M2,_,_}}) ->
{M1,M2}.
+
+
+od_get_value(Key, Dict, Def) ->
+ case orddict:find(Key, Dict) of
+ {ok, Val} -> Val;
+ error -> Def
+ end.
View
322 src/gexf_xref_world.erl
@@ -0,0 +1,322 @@
+-module(gexf_xref_world).
+-export([generate/1]).
+-compile({parse_transform, mead}).
+-compile({parse_transform, cut}).
+-compile({parse_transform, chacha}).
+-compile({parse_transform, seqbind}).
+
+-include_lib("eunit/include/eunit.hrl").
+
+%-define(check(X), ok).
+-define(check(X), X).
+
+-define(NODE_TYPE_ATTR_ID, 0).
+-define(EDGE_TYPE_ATTR_ID, 0).
+
+-define(NODE_TITLE_ATTR_ID, 1).
+-define(EDGE_TITLE_ATTR_ID, 1).
+
+-define(NODE_LINE_NUM_ATTR_ID, 2).
+-define(MFA_NODE_TYPE_ATTR_ID, 3).
+
+
+application_colors(Apps) ->
+% crypto:rand_bytes(length(Apps) * 3).
+ RGBs =
+ %% Get a hash of length 3 of each module name.
+ [binary:part(crypto:md5(unicode:characters_to_binary(application_to_string(App))), 0, 3)
+ || App <- Apps],
+ %% Join all RGBs together.
+ iolist_to_binary(RGBs).
+
+
+application_color(Colors, AppId) ->
+ %% First AppId = 1, Skip = 0
+ %% Second AppId = 2, Skip = 3
+ Skip = (AppId - 1) * 24,
+ <<_:Skip, R, G, B, _/binary>> = Colors,
+ gexf:color(R, G, B).
+
+brighter_colors(Mask, Colors) ->
+ << <<(X bor Mask)>> || <<X>> <= Colors>>.
+
+mod_color(Mod) ->
+ Bin = binary:part(crypto:md5(unicode:characters_to_binary(application_to_string(Mod))), 0, 3),
+ <<R, G, B>> = brighter_colors(128, Bin),
+ gexf:color(R, G, B).
+
+
+union(D1, D2) ->
+ orddict:merge(fun(_K, X, _Y) -> X end, D1, D2).
+
+generate(Xref) ->
+ %% Apps = [kernel,snmp,stdlib]
+ {ok, Apps} = xref:q(Xref, "A"),
+ %% [{kernel, [array, base64,...], {snmp, [...], ...]
+ ModByApps = [{App, application_modules(Xref, App)} || App <- Apps],
+ Mod2AppPL = [{Mod, App} || {App, Mods} <- ModByApps, Mod <- Mods],
+ %% Convert proplist to dict.
+ Mod2App = dict:from_list(Mod2AppPL),
+
+ %% `strict' deletes `{M, M}' (same module name).
+ %% `|| AM' deletes `{M,$M_EXPR}'.
+ {ok, M2MCalls} = xref:q(Xref, "(Mod) strict AE || AM"),
+
+ %% `AM' - analyzed modules.
+ {ok, Mods} = xref:q(Xref, "AM"),
+
+ %% Aplication edges.
+ {ok, AA} = xref:q(Xref, "strict AE"),
+
+ [erlang:error(no_data) || Mods =:= []],
+ AppColors = brighter_colors(128, application_colors(Apps)),
+
+ %% Add an unique id for each node.
+ {App2Num, Next@} = enumerate(Apps),
+ {Mod2Num, Next@} = enumerate(Mods, Next@),
+
+ %% Calculate how many modules are in each application.
+ ModCountByApps = [{A, length(Ms)} || {A, Ms} <- ModByApps],
+
+ %% Module to its node id.
+ Mod2Id = module_id(Mod2Num, _),
+ App2Id = application_id(App2Num, _),
+
+ Mod2AppNumPL = [{Mod, App2Id(App)} || {Mod, App} <- Mod2AppPL],
+ Mod2AppNum = dict:from_list(Mod2AppNumPL),
+ Mod2AppId = dict:fetch(_, Mod2AppNum),
+
+ %% Function, that converts MFA to GEXF color.
+% ModColor = chain(application_color(AppColors), Mod2AppId),
+ ModColor = fun mod_color/1,
+
+ %% The count of clusters.
+ ClusterCount = length(Apps),
+ ClusterPos = get_application_circle_position(ClusterCount),
+
+ %% Module to its virtual cluster number.
+ %% For large modules, the cluster center and the module center are the same.
+ Mod2ClusterId = orddict:fetch(_, Mod2AppNum),
+ App2ClusterId = orddict:fetch(_, App2Num),
+
+ App2Pos = chain(ClusterPos, App2ClusterId),
+
+ ModNodes = % [[Node]]
+ [begin
+ %% Calculate the center of the cluster.
+ %% This function calculates the coordinates of the point on the circle.
+ %% xs and ys are in [-1..1].
+ ParentCirclePosValue = App2Pos(App),
+
+ ModPos = get_module_circle_position(length(Mods)),
+ WithFuns = fun
+ (Mod, NumInCluster) ->
+ ModId = Mod2Id(Mod), % :: integer()
+ chain(gexf:add_position(chain(
+ %% Decrease the size of the calls' circle and move
+ %% (the center of this circle is the center of the cluster
+ %% or its virtual center).
+ gexf:relative_position(ParentCirclePosValue),
+ gexf:scale_position(0.07),
+ ModPos -- NumInCluster)),
+ gexf:add_size(gexf:size(5)),
+ gexf:add_color(ModColor(Mod))
+ -- module_node(ModId, Mod))
+ end,
+ lists2:cmap(WithFuns, Mods)
+ end
+ || {App, Mods} <- ModByApps],
+
+
+ ?check(?assertEqual(length(ModNodes), length(ModByApps))),
+
+ %% Render centers of applications.
+ AppNodes =
+ [begin
+ Id = App2Id(App),
+ PosValue = App2Pos(App),
+ chain(gexf:add_position(PosValue),
+ gexf:add_size(gexf:size(15)),
+ gexf:add_color(application_color(AppColors, Id))
+ -- application_node(Id, App))
+ end || App <- Apps],
+
+ ?check(?assertEqual(length(AppNodes), length(Apps))),
+
+ Nodes = AppNodes ++ lists:flatten(ModNodes),
+
+ {AA2Num, Next@} = enumerate(AA),
+ {Call2Num, Next@} = enumerate(M2MCalls, Next@),
+ {AM2Num, Next@} = enumerate(Mod2AppPL, Next@),
+ Mod2ModEdges =
+ [call_edge(Mod2Id, Id, FromMod, ToMod)
+ || {{FromMod, ToMod}, Id} <- Call2Num],
+ App2ModEdges =
+ [application_module_edge(App2Id, Mod2Id, Mod, App, Id)
+ || {{Mod, App}, Id} <- AM2Num],
+ App2AppEdges =
+ [application_application_edge(App2Id, App1, App2, Id)
+ || {{App1, App2}, Id} <- AA2Num],
+ Edges = App2AppEdges ++ Mod2ModEdges ++ App2ModEdges,
+
+ NAttrs = [gexf:attribute_metadata(?NODE_TYPE_ATTR_ID, "node_type", "string")
+ ,gexf:attribute_metadata(?NODE_TITLE_ATTR_ID, "node_title", "string")
+ ,gexf:attribute_metadata(?NODE_LINE_NUM_ATTR_ID, "line_num", "integer")
+ ,gexf:attribute_metadata(?MFA_NODE_TYPE_ATTR_ID, "is_exported", "boolean")
+ ],
+ EAttrs = [gexf:attribute_metadata(?EDGE_TYPE_ATTR_ID, "edge_type", "string")
+ ,gexf:attribute_metadata(?EDGE_TITLE_ATTR_ID, "edge_title", "string")],
+ chain(gexf:document_viz,
+ gexf:add_attribute_metadata(node, NAttrs),
+ gexf:add_attribute_metadata(edge, EAttrs)
+ -- gexf:graph(Nodes, Edges)).
+
+
+
+module_node(Id, Module) ->
+ chain(gexf:add_attribute_value(?NODE_TYPE_ATTR_ID, module),
+ gexf:set_label(module_to_string(Module))
+ -- gexf:node(Id)).
+
+application_node(Id, App) ->
+ chain(gexf:add_attribute_value(?NODE_TYPE_ATTR_ID, app),
+ gexf:set_label(application_to_string(App))
+ -- gexf:node(Id)).
+
+
+application_module_edge(App2Id, Mod2Id, Mod, App, Id) ->
+ chain(gexf:add_attribute_value(?EDGE_TYPE_ATTR_ID, am)
+ -- gexf:edge(Id, Mod2Id(Mod), App2Id(App))).
+
+application_application_edge(App2Id, App1, App2, Id) ->
+ chain(gexf:set_weight(15),
+ gexf:add_attribute_value(?EDGE_TYPE_ATTR_ID, aa)
+ -- gexf:edge(Id, App2Id(App1), App2Id(App2))).
+
+
+module_id(Mod2Num, Module) ->
+ orddict:fetch(Module, Mod2Num).
+
+application_id(App2Num, App) ->
+ orddict:fetch(App, App2Num).
+
+module_to_application(Mod2App, Mod) ->
+ orddict:fetch(Mod, Mod2App).
+
+
+
+call_edge(Mod2Id, Id, FromMod, ToMod) ->
+ gexf:edge(Id, Mod2Id(FromMod), Mod2Id(ToMod)).
+
+enumerate(Objects) ->
+ enumerate(Objects, 1).
+
+enumerate(Objects, From) ->
+ {Obj2Num, NextNum} = lists:mapfoldl(fun enumerate_single/2, From, Objects),
+ {orddict:from_list(Obj2Num), NextNum}.
+
+enumerate_single(Obj, Num) -> {{Obj, Num}, Num+1}.
+
+mfa_to_string({_M, F, A}) ->
+ lists:flatten(io_lib:format("~p/~p", [F, A])).
+
+application_to_string(App) -> atom_to_list(App).
+
+module_to_string(Mod) -> atom_to_list(Mod).
+
+
+%% ------------------------------------------------------------------
+%% Circle
+%% ------------------------------------------------------------------
+
+degrees_to_radians(Angle) ->
+ Angle * math:pi()/180.
+
+%% A in deegrees.
+-spec angle_to_coordinates(Rad) -> {X, Y} when
+ X :: 0 .. 1,
+ Y :: 0 .. 1,
+ Rad :: float().
+angle_to_coordinates(A) ->
+ {math:cos(A), math:sin(A)}.
+
+
+%% In degrees.
+-define(GAP_SIZE, 45).
+
+arc_length(PointCount) ->
+ 360 / PointCount.
+
+%% 45 degrees gap.
+arc_length_with_gap(PointCount) ->
+ (360 - ?GAP_SIZE*2) / PointCount.
+
+
+get_module_circle_position(PointCount) when PointCount == 0 ->
+ 0;
+get_module_circle_position(PointCount) when PointCount < 20 ->
+ get_random_sparse_circle_position(PointCount);
+
+get_module_circle_position(PointCount) ->
+ get_dense_circle_position(PointCount).
+
+
+get_application_circle_position(PointCount) ->
+ Gen = ellipint:cached_point_generator(1.7, 1, PointCount, 0),
+ fun(Num) ->
+ {X, Y} = Gen(Num),
+ gexf:position(X, Y, 0)
+ end.
+
+
+get_random_sparse_circle_position(PointCount) ->
+ RandomOffset = crypto:rand_uniform(0, 45),
+ get_sparse_circle_position(PointCount, RandomOffset).
+
+get_sparse_circle_position(PointCount, Offset) ->
+ AL = arc_length(PointCount),
+% io:format(user, "~n~p\t~p~n", [PointCount, Offset]),
+ fun(Num) ->
+% io:format(user, "~p\t~p\t~p~n", [PointCount, Offset, Num]),
+ {X, Y} = angle_to_coordinates(degrees_to_radians(AL * Num + Offset)),
+ gexf:position(X, Y, 0)
+ end.
+
+get_dense_circle_position(PointCount) ->
+ AL = arc_length_with_gap(PointCount),
+ %% There are two places, where node labels are clashes:
+ %% bottom and top of the circle.
+ %%
+ %% Top node's id is 0. Bottom node's id is CenterNum.
+ %% Create gaps.
+ CenterNum = round(PointCount / 2),
+
+ %% Offset in deegrees. Round the circle counterclockwise by 90 degrees.
+ Offset1 = 90 + (?GAP_SIZE * 0.5) - AL,
+ Offset2 = 90 + (?GAP_SIZE * 1.5) - AL,
+% io:format("~n~p ~p ~p ~p ~p", [CenterNum, AL, PointCount, Offset1, Offset2]),
+ fun(Num) when Num =< CenterNum ->
+ {X, Y} = angle_to_coordinates(degrees_to_radians(AL * Num + Offset1)),
+ gexf:position(X, Y, 0);
+ (Num) ->
+ {X, Y} = angle_to_coordinates(degrees_to_radians(AL * Num + Offset2)),
+ gexf:position(X, Y, 0)
+ end.
+
+
+
+
+od_get_value(Key, Dict, Def) ->
+ case orddict:find(Key, Dict) of
+ {ok, Val} -> Val;
+ error -> Def
+ end.
+
+
+application_modules(Xref, App) ->
+ %% 1. stdlib : App = [stdlib]
+ %% 2. (Mod) stdlib : App = [array, base64, ...]
+ {ok, Mods} = xref:q(Xref, "(Mod) \"" ++ atom_to_list(App) ++ "\" : App"),
+ Mods.
+
+

No commit comments for this range

Something went wrong with that request. Please try again.