%let gpath='.'; /*%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; USA 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; /*--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=pi/100;*/ 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=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 to 1 by 0.05; 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; ods html; proc print data=links;run; ods html close; /*--Diagram with radial node names and links colored by country of origin--*/ title 'Players from One Country Playing in Other Country'; footnote j=l 'Color represents the country of origin'; ods graphics / reset width=6in height=6in imagename='Circle_Links_From'; proc sgplot data=links aspect=1 nowall noborder subpixel noautolegend; series x=x y=y / group=link lineattrs=(thickness=2 pattern=solid) grouplc=colorFrom transparency=0.2 nomissinggroup smoothconnect thickresp=linkcount thickmax=36 thickmaxresp=3; series x=x y=y / group=country lineattrs=(thickness=20 pattern=solid color=white) nomissinggroup; series x=x y=y / group=country lineattrs=(thickness=20 pattern=solid) grouplc=colorFrom transparency=0.2 name='a' nomissinggroup; text x=xlbl y=ylbl text=label1 / rotate=rotate1 position=right textattrs=(size=10) pad=(right=50px left=50px); text x=xlbl y=ylbl text=label2 / rotate=rotate1 position=left textattrs=(size=10); xaxis display=none; yaxis display=none; run; title; footnote; /*--Diagram with circular node names and 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_Links_To'; proc sgplot data=links aspect=1 nowall noborder subpixel noautolegend; series x=x y=y / group=link lineattrs=(pattern=solid) grouplc=colorTo transparency=0.2 nomissinggroup smoothconnect thickresp=linkcount thickmax=36 thickmaxresp=3; series x=x y=y / group=country lineattrs=(thickness=20 pattern=solid color=white) nomissinggroup; series x=x y=y / group=country lineattrs=(thickness=20 pattern=solid) grouplc=colorTo transparency=0.2 name='a' nomissinggroup; text x=xlbl y=ylbl text=country / rotate=rotate2 position=center textattrs=(size=9 color=White) backlight; xaxis display=none; yaxis display=none; run; title; footnote;