%let gpath='.';
%let dpi=200;
ods html close;
ods listing gpath=&gpath image_dpi=&dpi;
/*--Input data set. This could be external--*/
/*--Linkcount is the number of players "From" country playing in "To" country--*/
/*--If "From" = "To", LinkCount is the number of players playing locally--*/
data soccer;
input From $1-10 To $11-20 LinkCount;
datalines;
Austria Spain 2
USA Germany 3
England Germany 2
England Italy 3
England Norway 1
England Spain 2
England Belgium 2
England Brazil 1
France Belgium 1
Italy Argentina 1
Argentina Argentina 9
Austria Austria 6
Belgium Belgium 8
Brazil Brazil 8
Bulgaria Bulgaria 7
Denmark Denmark 8
England England 6
France France 9
Germany Germany 12
Greece Greece 6
Italy Italy 6
Norway Norway 6
Spain Spain 8
;
run;
/*ods html;*/
/*proc print data=soccer(firstobs=8 obs=12);*/
/*run;*/
/*ods html close;*/
/*proc print;run;*/
/*--Compute total players--*/
data _null_;
retain total 0;
set soccer end=last;
total+linkCount;
if last then call symput("Total", total);
run;
/*--Create Node and Link Hash Tables--*/
data _null_;
length count start end center rotate rotate1 rotate2 xlbl ylbl 8;
length country label1 label2 $15;
length LinkId $10;
retain id 0;
set soccer end=last;
/* Declare the hash object for Nodes and Links */
if _n_ = 1 then do;
declare hash nodes( ordered:'a');
rc = nodes.defineKey('Country');
rc = nodes.defineData('Country', 'Count', 'Start', 'End', 'Center', 'Rotate', 'Rotate1', 'Rotate2', 'label1', 'label2', 'xlbl', 'ylbl');
rc = nodes.defineDone();
declare hash links( ordered:'a');
rc = links.defineKey('LinkId');
rc = links.defineData('LinkId', 'From', 'To', 'LinkCount');
rc = links.defineDone();
end;
/*--Add nodes to Node-hash--*/
country=from;
rc=nodes.find();
if rc ne 0 then do; /*--Node is not in hash--*/
count=linkcount; nodes.add();
end;
else do; /*--Node is already in hash--*/
count+linkcount; nodes.replace();
end;
/*--Add links to Link-hash when "From" not same as "To"--*/
if from ne to then do;
id+1;
LinkId="Link_" || strip( put(id, 3.0));
rc=links.add();
end;
/*--Save Node and Link hash tables--*/
if last then do;
rc = nodes.output(dataset: "work.CircleNodes");
rc = links.output(dataset: "work.CircleLinks");
end;
run;
/*ods html;*/
/*proc print data=work.CircleNodes;run;*/
/*proc print data=work.CircleLinks;run;*/
/*ods html close;*/
/*--Create circular node data set for diagram--*/
data circle;
pi=constant("PI");
pad='A0'x || 'A0'x || 'A0'x || 'A0'x;
length count start end center rotate rotate1 rotate2 xlbl ylbl LinkCount 8;
length country label1 label2 $15;
length LinkId From To $10;
first=0;
del=0;
r=10;
/* Declare the hash object for Nodes from data set*/
declare hash nodes( dataset:'work.circleNodes', ordered:'a');
rc = nodes.defineKey('Country');
rc = nodes.defineData('Country', 'Count', 'Start', 'End', 'Center', 'Rotate', 'Rotate1', 'Rotate2', 'label1', 'label2', 'xlbl', 'ylbl');
rc = nodes.defineDone();
/* Declare the hash object for Links from data set*/
declare hash links( dataset:'work.circleLinks', ordered:'a');
rc = links.defineKey('LinkId');
rc = links.defineData('LinkId', 'From', 'To', 'LinkCount');
rc = links.defineDone();
/*--Declare hash iterators--*/
declare hiter nodeIter linkIter;
nodeIter = _new_ hiter('Nodes');
linkIter = _new_ hiter('Links');
/*--For every node in list, determine start and end angle--*/
rc = nodeIter.first();
do while (rc = 0);
start=first;
end=start+count*2*pi/&total;
center=(start+end)/2;
rotate=center*180/pi;
first=end;
/*--Set label and rotation based on center angle--*/
if center > pi/2 and center < 3*pi/2 then do;
rotate1=rotate+180;
label2= strip(country) || pad; label1='';
end;
else do;
rotate1=rotate;
label1= pad || strip(country); label2='';
end;
rotate2=rotate+90;
if center < pi then rotate2+180;
/*--Compute X & Y values based on radius and angle--*/
xlbl=r*cos(center); ylbl=r*sin(center);
nodes.replace();
x=r*cos(start); y=r*sin(start); output;
call missing (xlbl, ylbl);
do i=start+0.05 to end+del by 0.05;
x=r*cos(i); y=r*sin(i); output;
end;
x=r*cos(end); y=r*sin(end); output;
rc = nodeIter.next();
end;
rc = nodes.output(dataset: "work.CircleNodes");
run;
/*--Macro for creating Bezier links--*/
%macro makeLink (name=, from=, to=, linkcount=);
link=&name;
country=&from; rc1=nodes.find(); x1=xlbl; y1=ylbl; center1=center;
country=&to; rc2=nodes.find(); x3=xlbl; y3=ylbl; center2=center;
x2=0; y2=0;
/*--adjust the center point #3 based on angle between points--*/
if abs(center1-center2) < pi/2 then do;
fac=0.2+0.8*( abs(center1-center2) / (pi/2) );
xc=0; yc=0;
xm=(x1+x3)/2; ym=(y1+y3)/2;
x2=xm+fac*(xc-xm); y2=ym+fac*(yc-ym);
end;
call missing (country, start, end, center, rotate, rotate2, label1, label2, xlbl, ylbl);
colorFrom=&from;
colorTo=&to;
/*--Compute bezier--*/
do t=0.0 to 0.85 by 0.05, 0.97;
x = (1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * x2 + t * t * x3;
y = (1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * y2 + t * t * y3;
output;
end;
%mend makeLink;
/*--Add Links data to Nodes data--*/
/*options mprint mlogic;*/
data links;
set circle end=last;
keep country link label1 label2 x y xlbl ylbl rotate rotate1 rotate2 colorFrom colorTo count linkcount;
length count start end center rotate rotate1 rotate2 xlbl ylbl LinkCount 8;
length country label1 label2 $15;
length LinkId From To $10;
length colorGrp $15;
/* Declare the hash object for Nodes and Links */
if _n_ = 1 then do;
declare hash nodes( dataset:'work.circleNodes', ordered:'a');
rc = nodes.defineKey('Country');
rc = nodes.defineData('Country', 'Count', 'Start', 'End', 'Center', 'Rotate', 'Rotate1', 'Rotate2', 'label1', 'label2', 'xlbl', 'ylbl');
rc = nodes.defineDone();
declare hash links( dataset:'work.circleLinks', ordered:'a');
rc = links.defineKey('LinkId');
rc = links.defineData('LinkId', 'From', 'To', 'LinkCount');
rc = links.defineDone();
/*--Declare hash iterators--*/
declare hiter nodeIter linkIter;
nodeIter = _new_ hiter('Nodes');
linkIter = _new_ hiter('Links');
end;
colorFrom=country; colorTo=country;
output;
/*--Iterate through Links Hash and add links--*/
if last then do;
rc = linkIter.first();
do while (rc = 0);
put name from to count;
%makeLink (name=LinkId, from=From, to=To, linkcount=linkcount);
rc = linkIter.next();
end;
end;
run;
/*--Diagram with links colored by country of destination--*/
title 'Players from One Country Playing in Other Country';
footnote j=l 'Color represents the country of destination';
ods graphics / reset width=6in height=6in imagename='Circle_Graph_Arrow';
proc sgplot data=links aspect=1 nowall noborder subpixel noautolegend;
series x=x y=y / group=link lineattrs=(pattern=solid thickness=5)
grouplc=colorTo transparency=0.05 nomissinggroup
arrowheadpos=end arrowheadshape=barbed;
series x=x y=y / group=country lineattrs=(thickness=25
pattern=solid color=white) nomissinggroup;
series x=x y=y / group=country nomissinggroup
lineattrs=(thickness=25 pattern=solid)
grouplc=colorTo transparency=0.2 name='a';
text x=xlbl y=ylbl text=country / rotate=rotate2
position=center textattrs=(size=11 color=White) backlight;
xaxis display=none offsetmin=0.05 offsetmax=0.05;
yaxis display=none offsetmin=0.05 offsetmax=0.05;
run;
title;
footnote;
/*--Diagram with links colored by country of destination--*/
title 'Players from One Country Playing in Other Country';
footnote j=l 'Color represents the country of destination';
ods graphics / reset width=6in height=6in imagename='Circle_Graph_Arrow_Resp';
proc sgplot data=links aspect=1 nowall noborder subpixel noautolegend;
series x=x y=y / group=link lineattrs=(pattern=solid thickness=5)
grouplc=colorTo transparency=0.05 nomissinggroup
arrowheadpos=end arrowheadshape=barbed thickresp=linkcount
thickmax=16 thickmaxresp=3;
series x=x y=y / group=country lineattrs=(thickness=25
pattern=solid color=white) nomissinggroup;
series x=x y=y / group=country nomissinggroup
lineattrs=(thickness=25 pattern=solid)
grouplc=colorTo transparency=0.2 name='a';
text x=xlbl y=ylbl text=country / rotate=rotate2
position=center textattrs=(size=11 color=White) backlight;
xaxis display=none offsetmin=0.05 offsetmax=0.05;
yaxis display=none offsetmin=0.05 offsetmax=0.05;
run;
title;
footnote;