%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 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%
-module(reltool_fgraph_win).
-export([
new/2,
add_node/2,
add_node/3,
del_node/2,
change_node/3,
add_link/2,
del_link/2,
set_dbl_click/2,
stop/2
]).
-include_lib("wx/include/wx.hrl").
-include("reltool_fgraph.hrl").
-record(state,
{
parent_pid,
frame,
window,
width,
height,
q_slider,
l_slider,
k_slider,
mouse_act,
is_frozen,
ticker
}).
-record(graph,
{
pen,
brush,
font,
select = none,
offset = {0,0},
offset_state = false,
ke = 0,
vs = [],
es = []
}).
-define(BRD,10).
-define(ARC_R, 10).
-define(reset, 80).
-define(lock, 81).
-define(unlock, 82).
-define(move, 83).
-define(select, 84).
-define(delete, 85).
-define(freeze, 86).
-define(q_slider, 90).
-define(l_slider, 91).
-define(k_slider, 92).
-define(default_q, 20).
-define(default_l, 20).
-define(default_k, 20).
-define(color_bg, {45,50,95}).
-define(color_fg, {235,245,230}).
-define(color_default, {10,220,20}).
-define(color_default_bg, {20,230,30}).
-define(color_alternate, {220,10,20}).
-define(color_alternate_bg, {230,20,30}).
add_node(Pid, Key) -> add_node(Pid, Key, default).
add_node(Pid, Key, Color) -> Pid ! {add_node, Key, Color}.
del_node(Pid, Key) -> Pid ! {del_node, Key}.
change_node(Pid, Key, Color) -> Pid ! {change_node, Key, Color}.
add_link(Pid, {FromKey, ToKey}) -> Pid ! {add_link, {FromKey, ToKey}}.
del_link(Pid, {FromKey, ToKey}) -> Pid ! {del_link, {FromKey, ToKey}}.
stop(Pid, Reason) ->
Ref = erlang:monitor(process, Pid),
Pid ! {stop, Reason},
receive
{'DOWN', Ref, _, _, _} ->
ok
end.
set_dbl_click(Pid, Fun) -> Pid ! {set_dbl_click, Fun}.
new(Parent, Options) ->
Env = wx:get_env(),
Me = self(),
Pid = spawn_link(fun() -> init([Parent, Me, Env, Options]) end),
receive {Pid, {?MODULE, Panel}} -> {Pid,Panel} end.
init([ParentWin, Pid, Env, Options]) ->
wx:set_env(Env),
BReset = wxButton:new(ParentWin, ?reset, [{label,"Reset"}]),
BFreeze = wxButton:new(ParentWin, ?freeze, [{label,"Freeze"}]),
BLock = wxButton:new(ParentWin, ?lock, [{label,"Lock"}]),
BUnlock = wxButton:new(ParentWin, ?unlock, [{label,"Unlock"}]),
BDelete = wxButton:new(ParentWin, ?delete, [{label,"Delete"}]),
SQ = wxSlider:new(ParentWin, ?q_slider, ?default_q, 1, 500,
[{style, ?wxVERTICAL}]),
SL = wxSlider:new(ParentWin, ?l_slider, ?default_l, 1, 500,
[{style, ?wxVERTICAL}]),
SK = wxSlider:new(ParentWin, ?k_slider, ?default_k, 1, 500,
[{style, ?wxVERTICAL}]),
Win = wxWindow:new(ParentWin, ?wxID_ANY, Options),
ButtonSizer = wxBoxSizer:new(?wxVERTICAL),
wxSizer:add(ButtonSizer, BReset),
wxSizer:add(ButtonSizer, BFreeze),
wxSizer:add(ButtonSizer, BLock),
wxSizer:add(ButtonSizer, BUnlock),
wxSizer:add(ButtonSizer, BDelete),
SliderSizer = wxBoxSizer:new(?wxHORIZONTAL),
wxSizer:add(SliderSizer, SQ, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxSizer:add(SliderSizer, SL, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxSizer:add(SliderSizer, SK, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxSizer:add(ButtonSizer, SliderSizer, [{flag, ?wxEXPAND}, {proportion, 1}]),
WindowSizer = wxBoxSizer:new(?wxHORIZONTAL),
wxSizer:add(WindowSizer, ButtonSizer, [{flag, ?wxEXPAND}, {proportion, 0}]),
wxSizer:add(WindowSizer, Win, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxButton:setToolTip(BReset, "Remove selection and unlock all nodes."),
wxButton:setToolTip(BFreeze, "Start/stop redraw of screen."),
wxButton:setToolTip(BLock, "Lock all selected nodes."),
wxButton:setToolTip(BUnlock, "Unlock all selected nodes."),
wxButton:setToolTip(BDelete, "Delete all selected nodes."),
wxButton:setToolTip(SQ, "Control repulsive force. This can also be"
" controlled with the mouse wheel on the canvas."),
wxButton:setToolTip(SL, "Control link length."),
wxButton:setToolTip(SK, "Control attractive force. Use with care."),
wxButton:setToolTip(Win,
"Drag mouse while left mouse button is pressed "
"to perform various operations. "
"Combine with control key to select. Combine "
"with shift key to lock single node."),
wxButton:connect(BReset, command_button_clicked),
wxButton:connect(BFreeze, command_button_clicked),
wxButton:connect(BLock, command_button_clicked),
wxButton:connect(BUnlock, command_button_clicked),
wxButton:connect(BDelete, command_button_clicked),
wxWindow:connect(SQ, command_slider_updated),
wxWindow:connect(SL, command_slider_updated),
wxWindow:connect(SK, command_slider_updated),
wxWindow:connect(Win, enter_window),
wxWindow:connect(Win, move),
wxWindow:connect(Win, motion),
wxWindow:connect(Win, mousewheel),
wxWindow:connect(Win, key_up),
wxWindow:connect(Win, left_down),
wxWindow:connect(Win, left_up),
wxWindow:connect(Win, right_down),
wxWindow:connect(Win, paint, [{skip, true}]),
Pen = wxPen:new({0,0,0}, [{width, 3}]),
Font = wxFont:new(12, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[]),
Brush = wxBrush:new({0,0,0}),
Pid ! {self(), {?MODULE, WindowSizer}},
wxWindow:setFocus(Win), %% Get keyboard focus
Vs = reltool_fgraph:new(),
Es = reltool_fgraph:new(),
Me = self(),
Ticker = spawn_link(fun() -> ticker_init(Me) end),
loop( #state{ parent_pid = Pid,
q_slider = SQ,
l_slider = SL,
k_slider = SK,
mouse_act = ?move,
frame = ParentWin,
window = Win,
is_frozen = false,
ticker = Ticker},
#graph{ vs = Vs,
es = Es,
pen = Pen,
font = Font,
brush = Brush}).
graph_add_node_unsure(Key, State, G = #graph{ vs = Vs }) ->
case reltool_fgraph:is_defined(Key, Vs) of
true -> G;
false -> graph_add_node(Key, State, G)
end.
graph_add_node(Key, Color, G = #graph{ vs = Vs}) ->
Q = 20.0, % repulsive force
M = 0.5, % mass
P = {float(450 + random:uniform(100)),
float(450 + random:uniform(100))},
G#graph{ vs = reltool_fgraph:add(Key,
#fg_v{ p = P, m = M, q = Q, color = Color},
Vs)}.
graph_change_node(Key, Color, G) ->
case reltool_fgraph:get(Key, G#graph.vs) of
undefined ->
G;
V ->
G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ color = Color },
G#graph.vs)}
end.
graph_del_node(Key, G = #graph{ vs = Vs0, es = Es0}) ->
Vs = reltool_fgraph:del(Key, Vs0),
Es = delete_edges(Es0, [Key]),
G#graph{ vs = Vs, es = Es }.
graph_add_link(Key0, Key1, G = #graph{ es = Es}) ->
K = 60.0, % attractive force
L = 5.0, % spring length
G#graph{ es = reltool_fgraph:add({Key0, Key1}, #fg_e{ k = K, l = L}, Es) }.
graph_del_link(Key0, Key1, G = #graph{ es = Es}) ->
G#graph{ es = reltool_fgraph:del({Key0, Key1}, Es) }.
ticker_init(Pid) ->
ticker_loop(Pid, 50).
ticker_loop(Pid, Time) ->
receive after Time ->
Pid ! {self(), redraw},
T0 = now(),
receive {Pid, ok} -> ok end,
T1 = now(),
D = timer:now_diff(T1, T0)/1000,
case round(40 - D) of
Ms when Ms < 0 ->
%io:format("ticker: wait is 0 ms [fg ~7s ms] [fps ~7s]~n",
% [s(D), s(1000/D)]),
ticker_loop(Pid, 0);
Ms ->
%io:format("ticker: wait is ~3s ms [fg ~7s ms] [fps ~7s]~n",
% [s(Ms), s(D), s(1000/40)]),
ticker_loop(Pid, Ms)
end
end.
delete_edges(Es, []) ->
Es;
delete_edges(Es, [Key|Keys]) ->
Edges = reltool_fgraph:foldl(fun
({{K1, K2}, _}, Out) when K1 =:= Key -> [{K1,K2}|Out];
({{K1, K2}, _}, Out) when K2 =:= Key -> [{K1,K2}|Out];
(_, Out) -> Out
end, [], Es),
Es1 = lists:foldl(fun
(K, Esi) -> reltool_fgraph:del(K, Esi)
end, Es, Edges),
delete_edges(Es1, Keys).
set_charge(Q, Vs) -> % Repulsive force
F = fun({Key, Value}) -> {Key, Value#fg_v{ q = Q}} end,
reltool_fgraph:map(F, Vs).
set_length(L, Es) -> % Spring length
F = fun({Ps, E}) -> {Ps, E#fg_e{ l = L}} end,
reltool_fgraph:map(F, Es).
set_spring(K, Es) -> % Attractive force
F = fun({Ps, E}) -> {Ps, E#fg_e{ k = K}} end,
reltool_fgraph:map(F, Es).
loop(S, G) ->
receive
#wx{id = ?reset, event = #wxCommand{type=command_button_clicked}} ->
%% Remove selection and unlock all nodes
Q = ?default_q,
L = ?default_l,
K = ?default_k,
wxSlider:setValue(S#state.q_slider, Q),
wxSlider:setValue(S#state.l_slider, L),
wxSlider:setValue(S#state.k_slider, K),
Es = set_length(L, G#graph.es),
Es2 = set_spring(K, Es),
Vs2 =
reltool_fgraph:map(fun({Key, V}) ->
{Key, V#fg_v{selected = false,
type = dynamic,
q = Q}}
end,
G#graph.vs),
{Xs, Ys} =
reltool_fgraph:foldl(fun({_Key,
#fg_v{p = {X, Y}}}, {Xs, Ys}) ->
{[X| Xs], [Y | Ys]}
end,
{[], []},
Vs2),
%% io:format("Before: ~p\n", [G#graph.offset]),
Offset =
case length(Xs) of
0 ->
{0, 0};
N ->
MeanX = (lists:sum(Xs) / N),
MeanY = (lists:sum(Ys) / N),
{SizeX, SizeY} = wxWindow:getSize(S#state.window),
%% io:format("Min: ~p\n",
%% [{lists:min(Xs), lists:min(Ys)}]),
%% io:format("Mean: ~p\n",
%% [{MeanX, MeanY}]),
%% io:format("Max: ~p\n",
%% [{lists:max(Xs), lists:max(Ys)}]),
%% io:format("Size: ~p\n", [{SizeX, SizeY}]),
%% {XM - (XS / 2), YM - (YS / 2)}
%% {0 - lists:min(Xs) + 20, 0 - lists:min(Ys) + 20}
{0 - MeanX + (SizeX / 2), 0 - MeanY + (SizeY / 2)}
end,
%% io:format("After: ~p\n", [Offset]),
loop(S, G#graph{vs = Vs2,
es = Es2,
offset = Offset,
offset_state = false});
#wx{id = ?freeze, event = #wxCommand{type=command_button_clicked}} ->
%% Start/stop redraw of screen
IsFrozen =
case S#state.is_frozen of
true ->
S#state.ticker ! {self(), ok},
false;
false ->
true
end,
loop(S#state{is_frozen = IsFrozen}, G);
#wx{id = ?lock, event = #wxCommand{type=command_button_clicked}} ->
%% Lock all selected nodes
Vs = reltool_fgraph:map(fun
({Key, V = #fg_v{selected = true}}) ->
{Key, V#fg_v{ type = static }};
(KV) -> KV
end, G#graph.vs),
loop(S, G#graph{ vs = Vs });
#wx{id = ?unlock, event = #wxCommand{type=command_button_clicked}} ->
%% Unlock all selected nodes
Vs = reltool_fgraph:map(fun
({Key, V = #fg_v{selected = true}}) ->
{Key, V#fg_v{ type = dynamic }};
(KV) -> KV
end, G#graph.vs),
loop(S, G#graph{ vs = Vs });
#wx{id = ?delete, event = #wxCommand{type=command_button_clicked}} ->
%% Delete all selected nodes
{Vs1, Keys} =
reltool_fgraph:foldl(fun
({Key,
#fg_v{ selected = true}},
{Vs, Ks}) ->
{reltool_fgraph:del(Key,Vs),
[Key|Ks]};
(_, {Vs, Ks}) ->
{Vs, Ks}
end, {G#graph.vs,[]}, G#graph.vs),
Es = delete_edges(G#graph.es, Keys),
loop(S, G#graph{ vs = Vs1, es = Es});
#wx{id = ?select, event = #wxCommand{type=command_button_clicked}} ->
loop(S#state{ mouse_act = ?select }, G);
#wx{id = ?move, event = #wxCommand{type=command_button_clicked}} ->
loop(S#state{ mouse_act = ?move }, G);
#wx{id = ?q_slider, event = #wxCommand{type=command_slider_updated,
commandInt = Q}} ->
loop(S, G#graph{ vs = set_charge(Q, G#graph.vs)});
#wx{id = ?l_slider, event = #wxCommand{type=command_slider_updated,
commandInt = L}} ->
loop(S, G#graph{ es = set_length(L, G#graph.es)});
#wx{id = ?k_slider, event = #wxCommand{type=command_slider_updated,
commandInt = K}} ->
loop(S, G#graph{ es = set_spring(K, G#graph.es)});
#wx{event=#wxKey{type=key_up, keyCode = 127}} -> % delete
{Vs1, Keys} =
reltool_fgraph:foldl(fun({Key,
#fg_v{ selected = true}},
{Vs, Ks}) ->
{reltool_fgraph:del(Key,Vs),
[Key|Ks]};
(_, {Vs, Ks}) ->
{Vs, Ks}
end,
{G#graph.vs,[]}, G#graph.vs),
Es = delete_edges(G#graph.es, Keys),
loop(S, G#graph{ vs = Vs1, es = Es});
#wx{event=#wxKey{type=key_up}} ->
loop(S, G);
#wx{event=#wxKey{type=key_down}} ->
loop(S, G);
%% mouse
#wx{event=#wxMouse{type=left_down,
shiftDown=Shift,
controlDown=Ctrl,
x=X,
y=Y}} ->
if
Shift ->
loop(S, mouse_left_down_move(G, {X,Y}));
Ctrl ->
loop(S, mouse_left_down_select(G, {X,Y}));
S#state.mouse_act =:= ?move ->
loop(S, mouse_left_down_move(G, {X,Y}));
S#state.mouse_act =:= ?select ->
loop(S, mouse_left_down_select(G, {X,Y}))
end;
#wx{event=#wxMouse{type=motion,
shiftDown=Shift,
controlDown=Ctrl,
x=X,
y=Y}} ->
if
Shift ->
loop(S, mouse_motion_move(G, {X,Y}));
Ctrl ->
loop(S, mouse_motion_select(G, {X,Y}));
S#state.mouse_act =:= ?move ->
loop(S, mouse_motion_move(G, {X,Y}));
S#state.mouse_act =:= ?select ->
loop(S, mouse_motion_select(G, {X,Y}))
end;
#wx{event=#wxMouse{type=left_up,
shiftDown=Shift,
controlDown=Ctrl, x=X, y=Y}} ->
if
Shift ->
loop(S, mouse_left_up_move(G, {X,Y}, Shift));
Ctrl ->
loop(S, mouse_left_up_select(G, {X,Y}));
S#state.mouse_act =:= ?move ->
loop(S, mouse_left_up_move(G, {X,Y}, Shift));
S#state.mouse_act =:= ?select ->
loop(S, mouse_left_up_select(G, {X,Y}))
end;
#wx{event=#wxMouse{type=right_down,x=_X,y=_Y}} ->
loop(S, G);
%% mouse wheel
#wx{event=#wxMouse{type=mousewheel, wheelRotation=Rotation}} ->
Q = wxSlider:getValue(S#state.q_slider),
if
Rotation > 0, Q > 5 ->
wxSlider:setValue(S#state.q_slider, Q - 4),
loop(S, G#graph{ vs = set_charge(Q - 4, G#graph.vs) });
Rotation < 0 ->
wxSlider:setValue(S#state.q_slider, Q + 4),
loop(S, G#graph{ vs = set_charge(Q + 4, G#graph.vs) });
true ->
loop(S, G)
end;
%% #wx{event=#wxClose{}} ->
%% catch wxWindow:'Destroy'(S#state.frame);
%% #wx{id=?wxID_EXIT, event=#wxCommand{type=command_menu_selected}} ->
%% wxWindow:close(S#state.frame,[]);
#wx{obj=_Win,event=#wxPaint{}} ->
redraw(S, G),
loop(S, G);
#wx{obj=Win,event=#wxMouse{type=enter_window}} ->
wxWindow:setFocus(Win),
loop(S, G);
%% Graph manipulation
{add_node, Key, State} ->
loop(S, graph_add_node_unsure(Key, State, G));
{del_node, Key} ->
loop(S, graph_del_node(Key, G));
{change_node, Key, Color} ->
loop(S, graph_change_node(Key, Color, G));
{add_link, {K0,K1}} ->
loop(S, graph_add_link(K0, K1, G));
{del_link, {K0,K1}} ->
loop(S, graph_del_link(K0, K1, G));
{Req, redraw} ->
{SizeX, SizeY} = wxWindow:getSize(S#state.window),
Vs = reltool_fgraph:step(G#graph.vs,
G#graph.es,
{SizeX/2.0 - 20.0, SizeY/2.0}),
case S#state.is_frozen of
false ->
Req ! {self(), ok};
true ->
ignore
end,
redraw(S, G),
loop(S, G#graph{ vs = Vs} );
{stop, Reason} ->
unlink(S#state.parent_pid),
exit(Reason);
Other ->
error_logger:format("~p~p got unexpected message:\n\t~p\n",
[?MODULE, self(), Other]),
loop(S, G)
end.
mouse_left_down_select(G, {X0,Y0}) ->
G#graph{ select = {{X0,Y0}, {X0,Y0}} }.
mouse_left_down_move(#graph{vs = Vs} = G, {X, Y}) ->
% point on node?
case coord_to_key(G, {X, Y}) of
false ->
G#graph{ offset_state = {X,Y}};
{true, Key} ->
V = #fg_v{ type = Type} = reltool_fgraph:get(Key, Vs),
G#graph{ vs = reltool_fgraph:set(Key,
V#fg_v{ type = moving}, Vs),
select = {node, Key, Type, X, Y} }
end.
coord_to_key(#graph{vs = Vs, offset = {Xo, Yo}}, {X, Y}) ->
Xr = X - Xo,
Yr = Y - Yo,
reltool_fgraph:foldl(fun({Key, #fg_v{ p = {Px, Py}}}, _)
when abs(Px - Xr) < 10,
abs(Py - Yr) < 10 ->
{true, Key};
(_, Out) ->
Out
end, false, Vs).
mouse_left_up_select(G, {_X,_Y}) ->
case G#graph.select of
{{X0,Y0}, {X1, Y1}} ->
{Xo, Yo} = G#graph.offset,
Xmin = lists:min([X0,X1]) - Xo,
Xmax = lists:max([X1,X0]) - Xo,
Ymin = lists:min([Y0,Y1]) - Yo,
Ymax = lists:max([Y1,Y0]) - Yo,
Vs = reltool_fgraph:map(fun
({Key, Value = #fg_v{ p = {Px, Py}}})
when Px > Xmin, Px < Xmax, Py > Ymin, Py < Ymax ->
{Key, Value#fg_v{ selected = true }};
({Key, Value}) -> {Key, Value#fg_v{ selected = false }}
end, G#graph.vs),
G#graph{ select = none, vs = Vs};
_ ->
G#graph{ select = none}
end.
mouse_left_up_move(G = #graph{ select = Select, vs = Vs} = G, {X,Y}, Shift) ->
case Select of
{node, Key, _, X, Y} ->
io:format("click: ~p\n", [Key]),
G#graph{ select = none, offset_state = false };
{node, Key, Type, _, _} ->
V = reltool_fgraph:get(Key, Vs),
Type2 =
case Shift of
true -> static;
false -> Type
end,
G#graph{ select = none,
vs = reltool_fgraph:set(Key, V#fg_v{ type = Type2}, Vs),
offset_state = false };
_ ->
G#graph{ select = none, offset_state = false }
end.
mouse_motion_select(G, {X,Y}) ->
case G#graph.select of
{P0, _P1} -> G#graph{ select = {P0, {X,Y}}};
_ -> G
end.
mouse_motion_move(G = #graph{ select = {node, Key, _, _, _}, vs = Vs}, {X,Y}) ->
{Xo, Yo} = G#graph.offset,
V = reltool_fgraph:get(Key, Vs),
V2 = V#fg_v{ p = {float(X - Xo), float(Y - Yo)}},
G#graph{ vs = reltool_fgraph:set(Key, V2, Vs) };
mouse_motion_move(G, {X,Y}) ->
case G#graph.offset_state of
{X1,Y1} ->
{X0, Y0} = G#graph.offset,
G#graph{ offset_state = {X,Y},
offset = {X0 - (X1 - X), Y0 - (Y1 - Y)} };
_ ->
G
end.
redraw(#state{window=Win}, G) ->
DC0 = wxClientDC:new(Win),
DC = wxBufferedDC:new(DC0),
Size = wxWindow:getSize(Win),
redraw(DC, Size, G),
wxBufferedDC:destroy(DC),
wxClientDC:destroy(DC0),
ok.
redraw(DC, _Size, G) ->
wx:batch(fun() ->
Pen = G#graph.pen,
Font = G#graph.font,
Brush = G#graph.brush,
wxDC:setTextForeground(DC,?color_fg),
wxBrush:setColour(Brush, ?color_bg),
wxDC:setBrush(DC, Brush),
wxDC:setBackground(DC, Brush),
wxPen:setWidth(Pen, 1),
wxDC:clear(DC),
% draw vertices and edges
wxPen:setColour(Pen, ?color_fg),
wxDC:setPen(DC,Pen),
%draw_es(DC, G#graph.es_pts, G#graph.offset),
draw_es(DC, G#graph.vs, G#graph.es, G#graph.offset, Pen, Brush),
draw_vs(DC, G#graph.vs, G#graph.offset, Pen, Brush),
% draw selection box
wxPen:setColour(Pen, ?color_fg),
wxDC:setPen(DC,Pen),
draw_select_box(DC, G#graph.select),
% draw information text
wxFont:setWeight(Font,?wxNORMAL),
draw_text(DC,
reltool_fgraph:'size'(G#graph.vs),
reltool_fgraph:'size'(G#graph.es), G#graph.ke),
ok
end).
draw_select_box(DC, {{X0,Y0}, {X1,Y1}}) ->
draw_line(DC, {X0,Y0}, {X1,Y0}, {0,0}),
draw_line(DC, {X1,Y1}, {X1,Y0}, {0,0}),
draw_line(DC, {X1,Y1}, {X0,Y1}, {0,0}),
draw_line(DC, {X0,Y0}, {X0,Y1}, {0,0}),
ok;
draw_select_box(_DC, _) ->
ok.
draw_es(DC, Vs, Es, Po, Pen, Brush) ->
reltool_fgraph:foreach(fun
({{K1, K2}, _}) ->
#fg_v{ p = P1} = reltool_fgraph:'get'(K1, Vs),
#fg_v{ p = P2} = reltool_fgraph:'get'(K2, Vs),
draw_arrow(DC, P1, P2, Po, Pen, Brush)
end, Es).
draw_arrow(DC, {X0,Y0}, {X1, Y1}, {X, Y}, Pen, Brush) ->
Xdiff = (X0 - X1) / 4,
Ydiff = (Y0 - Y1) / 4,
X2 = X1 + Xdiff + X,
Y2 = Y1 + Ydiff + Y,
wxDC:setPen(DC, Pen),
wxDC:setBrush(DC, Brush),
draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}),
%% Draw arrow head
Radians = calc_angle({X0, Y0}, {X1, Y1}),
Len = 10,
%% Angle = 30,
%% Degrees = radians_to_degrees(Radians),
%% Radians2 = degrees_to_radians(Degrees + Angle + 180),
%% Radians3 = degrees_to_radians(Degrees - Angle + 180),
Radians2 = Radians + 3.665191429188092,
Radians3 = Radians + 2.617993877991494,
{X3, Y3} = calc_point({X2, Y2}, Len, Radians2),
{X4, Y4} = calc_point({X2, Y2}, Len, Radians3),
Points = [{round(X2), round(Y2)},
{round(X3), round(Y3)},
{round(X4), round(Y4)}],
wxDC:drawPolygon(DC, Points, []).
draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}) ->
wxDC:drawLine(DC,
{round(X0 + X), round(Y0 + Y)},
{round(X1 + X), round(Y1 + Y)}).
draw_vs(DC, Vs, {Xo, Yo}, Pen, Brush) ->
reltool_fgraph:foreach(fun({Key,
#fg_v{p ={X, Y},
color = Color,
selected = Sel}}) ->
String = s(Key),
case Sel of
true ->
wxPen:setColour(Pen, ?color_fg),
wxBrush:setColour(Brush, ?color_bg),
wxDC:setPen(DC,Pen),
wxDC:setBrush(DC, Brush),
SelProps = {round(X-12 + Xo),
round(Y-12 + Yo),
24,
24},
wxDC:drawRoundedRectangle(DC,
SelProps,
float(?ARC_R)),
ok;
false ->
ok
end,
case Color of
default ->
wxPen:setColour(Pen, ?color_default),
wxBrush:setColour(Brush,
?color_default_bg);
alternate ->
wxPen:setColour(Pen,
?color_alternate),
wxBrush:setColour(Brush,
?color_alternate_bg);
{FgColor, BgColor} ->
wxPen:setColour(Pen, FgColor),
wxBrush:setColour(Brush, BgColor);
Color ->
wxPen:setColour(Pen, Color),
wxBrush:setColour(Brush, Color)
end,
wxDC:setPen(DC,Pen),
wxDC:setBrush(DC, Brush),
NodeProps = {round(X-8 + Xo),
round(Y-8 + Yo),17,17},
wxDC:drawRoundedRectangle(DC,
NodeProps,
float(?ARC_R)),
wxDC:drawText(DC,
String,
{round(X + Xo),
round(Y + Yo)}),
ok;
(_) ->
ok
end,
Vs).
draw_text(DC, Nvs, Nes, _KE) ->
VsString = "#nodes: " ++ integer_to_list(Nvs),
EsString = "#links: " ++ integer_to_list(Nes),
%% KEString = " ke: " ++ s(KE),
wxDC:drawText(DC, VsString, {10,10}),
wxDC:drawText(DC, EsString, {10,25}),
%% wxDC:drawText(DC, KEString, {10,40}),
ok.
s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
s(Term) when is_float(Term) -> s("~.2f", [Term]);
s(Term) when is_integer(Term) -> integer_to_list(Term);
s(Term) when is_atom(Term) -> atom_to_list(Term);
s(Term) -> s("~p", [Term]).
%% Calclulate angle in radians for a line between two points
calc_angle({X1, Y1}, {X2, Y2}) ->
math:atan2((Y2 - Y1), (X2 - X1)).
%% Calc new point at a given distance and angle from another point
calc_point({X, Y}, Length, Radians) ->
X2 = round(X + Length * math:cos(Radians)),
Y2 = round(Y + Length * math:sin(Radians)),
{X2, Y2}.
%% %% Convert from an angle in radians to degrees
%% radians_to_degrees(Radians) ->
%% Radians * 180 / math:pi().
%%
%% %% Convert from an angle in degrees to radians
%% degrees_to_radians(Degrees) ->
%% Degrees * math:pi() / 180.