%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%------------------------------------------------------------
%%
%% Places a Digraph in a tree-like manner. The vertices in the digraph
%% is updated with x and y positions. The operation is not atomic. The
%% digraph may be cyclic but edges must then have been labeled primary
%% or secondary and the set of primary links must make up a non-cyclic
%% graph (a tree).
%%
%%
%% IMPLEMENTATION DETAIL
%% ---------------------
%%
%% The placement algorithm is straightforward, place the
%% nodes in the vertical plane (y-plane) and then place
%% nodes in the horisontal plane (x-plane).
%%
%% First all nodes are placed in the y (vertical) plane
%% by a standard traversing of the tree. We then place
%% the tree in the x (horisontal) plane. Each node is
%% placed in the middle of its children as far to the
%% left as possible, preferably at the left margin. Two
%% things can make a node not be placed at the left
%% margin and that is the case when a previous node has
%% been placed at the same vertical level as the node we
%% are trying to place (thus forcing a placement further
%% to the right), and the second case is when the middle
%% of the subtree of the node is not at the left margin
%% (which it only is when the subtree is empty). The
%% algorithm obviously depends on keeping track of the
%% rightmost positions at all depths, and this
%% information is also usefull when calculating the width
%% of the tree.
%%
%%
%%
%%------------------------------------------------------------
-module(appmon_place).
-export([place/2]).
-include("appmon_dg.hrl").
-import(lists, [foreach/2, foldl/3]).
place(DG, Root) ->
case appmon_dg:get(data, DG, Root) of
false -> [0];
_Other ->
placey(DG, Root, 1),
placex(DG, Root, [])
end.
%%------------------------------------------------------------
%%
%%
%% Placing a graph in y plane
%% --------------------------
%%
%% Place nodes in the graph in the y plane rather stupidly
%%
placey(DG, V, Y) ->
appmon_dg:set(y, DG, V, Y),
Y1 = Y+1,
foreach(fun(C) -> placey(DG, C, Y1) end, appmon_dg:get(out, DG, V)).
%%------------------------------------------------------------
%%
%%
%% Place a tree in the x plane
%% ---------------------------
%%
%% Place nodes in the tree in the x plane. The goal of the x
%% placement is to place all nodes as far to the left as possible
%% while maintaining a nice tree shape.
%%
%% To place a node we must first place its children, the
%% intention is to place the current node in the middle and above
%% its children. The calc_mid function will place the node in the
%% middle of its children. If the node should be placed further
%% to the right than the middle of its children, then its
%% children are moved DeltaX positions to be balanced under the
%% node. Thus at the end the node and its children form a nice
%% looking tree.
%%
%% The function also maintains the 'rightmost x on each level'
%% list LastX by putting its own position on top of the list
%%
%%
placex(DG, V, LastX) ->
Ch = appmon_dg:get(out, DG, V),
ChLX = foldl(fun(C, Accu) -> placex(DG, C, Accu) end,
tll(LastX),
Ch),
Width = appmon_dg:get(w, DG, V),
MyX = calc_mid(DG, Width, Ch),
DeltaX = calc_delta(MyX, hdd(LastX)+spacex()),
appmon_dg:set(x, DG, V, MyX),
move(DG, V, [MyX+Width | ChLX], DeltaX).
%%------------------------------------------------------------
%%
%%
%% Move a subtree DeltaX positions to the right
%% --------------------------------------------
%%
%% Used when moving children to balance under an already placed
%% parent. Note that the correct LastX depends on the ordering of
%% the children which must be the same as when the children were
%% first placed. It must be ensured that hdd(NewLastX) is the
%% same as hdd(NewLastX)+DeltaX. If the order of children is
%% preserved then so is hdd(LastX). Another solution would be to
%% set hdd(LastX) from the parent
%%
%% Note the two base clauses, one for the no-children case and
%% one optimisation clause (unneccessary perhaps) for DeltaX==0
%%
move(_DG, _L, LastX, 0) -> LastX;
move(DG, V, LastX, DeltaX) -> move2(DG, V, LastX, DeltaX).
move2(DG, V, LastX, DeltaX) ->
NewX = appmon_dg:get(x, DG, V)+DeltaX,
appmon_dg:set(x, DG, V, NewX),
ChLX = foldl(fun(C, LX) -> move2(DG, C, LX, DeltaX) end,
tll(LastX),
appmon_dg:get(out, DG, V)),
[erlang:max(NewX+appmon_dg:get(w, DG, V), hdd(LastX)) | ChLX].
%%------------------------------------------------------------
%%
%%
%% Calculate the middle position of the children
%% ---------------------------------------------
%%
%% Calculates the mid x position for a list of children. This
%% position is later compared to the position dictated by LastX
%% in calc_delta.
calc_mid(_DG, _Width, []) -> 0;
calc_mid(DG, Width, ChList) ->
LeftMostX = appmon_dg:get(x, DG, hd(ChList)),
Z2 = lists:last(ChList),
RightMostX = appmon_dg:get(x, DG, Z2)+appmon_dg:get(w, DG, Z2),
trunc((LeftMostX+RightMostX)/2)-trunc(Width/2).
calc_delta(Mid, Right) ->
if Right>Mid -> Right-Mid;
true -> 0
end.
%% Special head and tail
%% Handles empty list in a non-standard way
tll([]) -> [];
tll([_|T]) -> T.
hdd([]) -> 0;
hdd([H|_]) -> H.
spacex() -> 20. % Should be macro??