BookmarkSubscribeRSS Feed
Rick_SAS
SAS Super FREQ

An artist named Odili Donald Odita painted a beautiful picture ("Phantom’s Shadow, 2018") that contains geometric polygons in different colors and orientations. A reproduction of Odita's image is shown below:

 

Odita0.png

 

In a blog post, I show how to use symmetry and PROC SGPANEL to create tilings that are inspired by Odita's work. I analyze some of the mathematical structures in Odita's painting and creates the following image in SAS; see the blog post for its mathematical significance:

 

Odita10.png

 

You can create images like these from basic mathematical principles. To simplify the process, the following data set contains the 16 pinwheel-shaped images that appear in the tiling above. The SAS program creates the mathematical version of Odita's artwork. 

 

data Pinwheels;
input Group ID @;
do i = 1 to 5;
   input x y @@;
   output;
end;
drop i;
datalines;
0 0 0 0   0.667  0.333   1  1   0  1  0 0 
0 1 0 0  -0.333  0.667  -1  1  -1  0  0 0 
0 2 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
0 3 0 0   0.333 -0.667   1 -1   1  0  0 0 
1 0 0 0  -0.333  0.667  -1  1  -1  0  0 0 
1 1 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
1 2 0 0   0.333 -0.667   1 -1   1  0  0 0 
1 3 0 0   0.667  0.333   1  1   0  1  0 0 
2 0 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
2 1 0 0   0.333 -0.667   1 -1   1  0  0 0 
2 2 0 0   0.667  0.333   1  1   0  1  0 0 
2 3 0 0  -0.333  0.667  -1  1  -1  0  0 0 
3 0 0 0   0.333 -0.667   1 -1   1  0  0 0 
3 1 0 0   0.667  0.333   1  1   0  1  0 0 
3 2 0 0  -0.333  0.667  -1  1  -1  0  0 0 
3 3 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
4 0 0 0  -0.667  0.333  -1  1   0  1  0 0 
4 1 0 0   0.333  0.667   1  1   1  0  0 0 
4 2 0 0   0.667 -0.333   1 -1   0 -1  0 0 
4 3 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
5 0 0 0   0.333  0.667   1  1   1  0  0 0 
5 1 0 0   0.667 -0.333   1 -1   0 -1  0 0 
5 2 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
5 3 0 0  -0.667  0.333  -1  1   0  1  0 0 
6 0 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
6 1 0 0  -0.667  0.333  -1  1   0  1  0 0 
6 2 0 0   0.333  0.667   1  1   1  0  0 0 
6 3 0 0   0.667 -0.333   1 -1   0 -1  0 0 
7 0 0 0   0.667 -0.333   1 -1   0 -1  0 0 
7 1 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
7 2 0 0  -0.667  0.333  -1  1   0  1  0 0 
7 3 0 0   0.333  0.667   1  1   1  0  0 0 
8 0 0 0   0.667  0.333   1  1   0  1  0 0 
8 3 0 0  -0.333  0.667  -1  1  -1  0  0 0 
8 2 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
8 1 0 0   0.333 -0.667   1 -1   1  0  0 0 
9 0 0 0  -0.333  0.667  -1  1  -1  0  0 0 
9 3 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
9 2 0 0   0.333 -0.667   1 -1   1  0  0 0 
9 1 0 0   0.667  0.333   1  1   0  1  0 0 
10 0 0 0  -0.667 -0.333  -1 -1  0 -1  0 0 
10 3 0 0   0.333 -0.667   1 -1   1  0  0 0 
10 2 0 0   0.667  0.333   1  1   0  1  0 0 
10 1 0 0  -0.333  0.667  -1  1  -1  0  0 0 
11 0 0 0   0.333 -0.667   1 -1   1  0  0 0 
11 3 0 0   0.667  0.333   1  1   0  1  0 0 
11 2 0 0  -0.333  0.667  -1  1  -1  0  0 0 
11 1 0 0  -0.667 -0.333  -1 -1   0 -1  0 0 
12 0 0 0  -0.667  0.333  -1  1   0  1  0 0 
12 3 0 0   0.333  0.667   1  1   1  0  0 0 
12 2 0 0   0.667 -0.333   1 -1   0 -1  0 0 
12 1 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
13 0 0 0   0.333  0.667   1  1   1  0  0 0 
13 3 0 0   0.667 -0.333   1 -1   0 -1  0 0 
13 2 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
13 1 0 0  -0.667  0.333  -1  1   0  1  0 0 
14 0 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
14 3 0 0  -0.667  0.333  -1  1   0  1  0 0 
14 2 0 0   0.333  0.667   1  1   1  0  0 0 
14 1 0 0   0.667 -0.333   1 -1   0 -1  0 0 
15 0 0 0   0.667 -0.333   1 -1   0 -1  0 0 
15 3 0 0  -0.333 -0.667  -1 -1  -1  0  0 0 
15 2 0 0  -0.667  0.333  -1  1   0  1  0 0 
15 1 0 0   0.333  0.667   1  1   1  0  0 0 
;

%let teal   = CX288c95;
%let orange = CXeba411;
%let blue   = CX0f5098;
%let salmon = CXd5856e;
%let gray   = CX929386;

ods graphics / width=640px height=640px;
%macro colOpts; colaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;
%macro rowOpts;  rowaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;

title "Dihedral Shadow";
proc sgpanel data=Pinwheels noautolegend;
   styleattrs wallcolor=&gray datacolors=(&teal &orange &blue &salmon);
   panelby Group / columns=4 onepanel noheader;
   polygon x=x y=y ID=ID / group=ID fill;
   %colOpts; %rowOpts;
run;

 

Here is a challenge for SAS programmers: create similar images! Who is up for the challenge? Use these 16 polygons in whatever order you want to create an Odita-inspired image, or create your own polygons. 

Post your SAS code and a small image that shows your creativity and programming skills!

 

13 REPLIES 13
Ksharp
Super User

Of course. Calling out @tc  @GraphGuy 

Ksharp
Super User

Rick,

OK. I post my code here .

 

%let alpha = 0.333;          /* Odita's work uses a vertex as (1-alpha, alpha) */
data Poly1;
ID = 1;
input x y @@;
if x=. then do;              /* just for fun, support other locations of vertex */
   x = 1 - α y = α
end;
datalines;
0 0   . .   1 1   0 1   0 0
;


%macro rotation(dsn=);
%do i=0 %to 7;
%do j=0 %to 7;
%let id=%sysevalf(%sysfunc(rand(uniform))*99999,int);
%let type=%sysfunc(rand(table,0.25,0.25,0.25));

%if &type=1 %then %let angle=0;
 %else %if &type=2 %then %let angle=90;
  %else %if &type=3 %then %let angle=180;
   %else %if  &type=4 %then %let angle=270;

data temp(keep=id group x_rotated y_rotated rename=(x_rotated=x y_rotated=y));
 set &dsn. ;
 id=&id.;
 group=&type.;
angle_radians=&angle.*(constant("pi")/180);
x_rotated=cos(angle_radians)*x - sin(angle_radians)*y;
y_rotated=sin(angle_radians)*x + cos(angle_radians)*y;

%if &type=2 %then %do;
x_rotated=x_rotated+1;
%end;
%else %if &type=3 %then %do;
x_rotated=x_rotated+1;
y_rotated=y_rotated+1;
%end;
%else %if &type=4 %then %do;
y_rotated=y_rotated+1;
%end;

x_rotated=x_rotated+&i.;
y_rotated=y_rotated+&j.;
run;
proc append base=want data=temp force;run;
%end;
%end;
%mend;


proc delete data=want;run;
%rotation(dsn=Poly1)




%let blue   = CX0f5098;
%let orange = CXeba411;
%let teal   = CX288c95;
%let salmon = CXd5856e;
%let gray   = CX929386;
 
ods graphics / width=480px height=480px noborder;
title "Base Polygon";
proc sgplot data=want aspect=1 noborder noautolegend;
   styleattrs wallcolor=&gray datacolors=(&teal &orange &blue &salmon);
   polygon x=x y=y ID=ID / group=group fill;
   xaxis display=none offsetmax=0 offsetmin=0 ;
   yaxis display=none offsetmax=0 offsetmin=0 ;
run;

Ksharp.png

Ksharp
Super User

Rick,

Here is another one . the same with  Odili's .

 

 

 

%let alpha = 0.3;          /* Odita's work uses a vertex as (1-alpha, alpha) */
data Poly1;
ID = 1;
input x y @@;
if x=. then do;              /* just for fun, support other locations of vertex */
   x = 1 - α y = α
end;
datalines;
0 0   . .   1 1   0 1   0 0
;
data Poly2;
ID = 1;
input x y @@;
if x=. then do;              /* just for fun, support other locations of vertex */
   x = α y = 1 -  α
end;
datalines;
0 0   1 0   1 1   . .   0 0
;


proc format;
invalue color_a
0  =1
90 =2
180=3
270=4;
invalue color_b
0  =3
90 =2
180=1
270=4;
invalue color_c
0  =4
90 =1
180=2
270=3;
invalue color_d
0  =2
90 =1
180=4
270=3;
invalue color_e
0  =3
90 =4
180=1
270=2;
invalue color_f
0  =1
90 =4
180=3
270=2;
invalue color_g
0  =2
90 =3
180=4
270=1;
invalue color_h
0  =4
90 =3
180=2
270=1;
run;


%macro rotation(dsn= ,color=,x=,y=);
%let angles=0 90 180 270;
%let group=%sysfunc(rand(integer,1,999999));
%do i=1 %to 4;
%let angle=%scan(&angles.,&i.,%str( ));
data temp(keep=id group x_rotated y_rotated rename=(x_rotated=x y_rotated=y));
 set &dsn. ;
%if &color.=1 %then %do;id=inputn(&angle.,"color_a.");%end;
%if &color.=2 %then %do;id=inputn(&angle.,"color_b.");%end;
%if &color.=3 %then %do;id=inputn(&angle.,"color_c.");%end;
%if &color.=4 %then %do;id=inputn(&angle.,"color_d.");%end;
%if &color.=5 %then %do;id=inputn(&angle.,"color_e.");%end;
%if &color.=6 %then %do;id=inputn(&angle.,"color_f.");%end;
%if &color.=7 %then %do;id=inputn(&angle.,"color_g.");%end;
%if &color.=8 %then %do;id=inputn(&angle.,"color_h.");%end;
angle_radians=&angle.*(constant("pi")/180);
x_rotated=cos(angle_radians)*x - sin(angle_radians)*y;
y_rotated=sin(angle_radians)*x + cos(angle_radians)*y;
x_rotated=x_rotated+&x.;
y_rotated=y_rotated+&y.;
group=&group.;
run;
proc append base=want data=temp force;run;
%end;
%mend;



proc delete data=want;run;
%rotation(dsn=Poly1,color=1,x=6,y=6)
%rotation(dsn=Poly2,color=2,x=4,y=6)
%rotation(dsn=Poly2,color=3,x=2,y=6)
%rotation(dsn=Poly1,color=4,x=0,y=6)

%rotation(dsn=Poly2,color=4,x=6,y=4)
%rotation(dsn=Poly1,color=5,x=4,y=4)
%rotation(dsn=Poly1,color=6,x=2,y=4)
%rotation(dsn=Poly2,color=7,x=0,y=4)

%rotation(dsn=Poly2,color=7,x=6,y=2)
%rotation(dsn=Poly1,color=8,x=4,y=2)
%rotation(dsn=Poly1,color=1,x=2,y=2)
%rotation(dsn=Poly2,color=2,x=0,y=2)

%rotation(dsn=Poly1,color=2,x=6,y=0)
%rotation(dsn=Poly2,color=3,x=4,y=0)
%rotation(dsn=Poly2,color=4,x=2,y=0)
%rotation(dsn=Poly1,color=5,x=0,y=0)

%let blue   = CX0f5098;
%let orange = CXeba411;
%let teal   = CX288c95;
%let salmon = CXd5856e;
%let gray   = CX929386;
 
ods graphics / width=480px height=480px noborder;
title "Base Polygon";
proc sgplot data=want aspect=1 noborder noautolegend;
   styleattrs wallcolor=&gray datacolors=(&teal &orange &blue &salmon);
   polygon x=x y=y ID=id / group=id fill;
   xaxis display=none offsetmax=0 offsetmin=0 ;
   yaxis display=none offsetmax=0 offsetmin=0 ;
run;

Ksharp.png

IanWakeling
Barite | Level 11

Hi Rick,

Many thanks for the inspiring blog post. Below I am attempting to combine your 2nd and 3rd suggestions from the end of the post.  So I have modified your code to look for a random 'artwork' with no adjacent polygons of the same colour, that also uses all 16 of the pinwheels.

 

/* Rick's Code */
data Shape;
input ID x y @@;
datalines;
0  0 0  0  0.667  0.333  0  1  1  0  0  1  0  0  0  
1  0 0  1 -0.333  0.667  1 -1  1  1 -1  0  1  0  0 
2  0 0  2 -0.667 -0.333  2 -1 -1  2  0 -1  2  0  0 
3  0 0  3  0.333 -0.667  3  1 -1  3  1  0  3  0  0 
;

proc iml;
/* actions of the D4 dihedral group */
start D4Action(v, act);
   /* the subroup of rotations by 90 degrees */
   if      act=0 then M = { 1  0,  0  1};
   else if act=1 then M = { 0 -1,  1  0};
   else if act=2 then M = {-1  0,  0 -1};
   else if act=3 then M = { 0  1, -1  0};
   /* the subgroup of reflections across horz, vert, or diagonals */
   else if act=4 then M = {-1  0,  0  1};
   else if act=6 then M = { 0 -1, -1  0};
   else if act=7 then M = { 1  0,  0 -1};
   else if act=5 then M = { 0  1,  1  0};
   return( v*M` );  /* = (M*z`)` */
finish;
/* read in the pinwheel shape */
use Shape; read all var {x y} into P1; 
read all var "ID"; close;

/* Modifying Rick's code below to output all 16 pinwheels.
   Define ID2 where the odd colour numbers are switched */
ID2 = ID;
do i = 1 to 20;
  if mod(ID2[i], 2)=1 then ID2[i] = 4 - ID2[i];
end;

/* write out the transformation of the pinwheel under the D4 actions */
OpNames = {"I" "R1" "R2" "R3" "S0" "S1" "S2" "S3",
           "ZI" "ZR1" "ZR2" "ZR3" "ZS0" "ZS1" "ZS2" "ZS3"};
Name = OpNames[1];
Q = {. . .};
create Panel from Name Q[c={'Name' 'ID' 'x' 'y'}];
do j = 1 to 2;
  do i = 0 to 7;
     R = D4Action(P1, i);
     if j=1 then Q = ID || R; else Q=ID2 || R;
     Name = j(nrow(Q), 1, OpNames[j,i+1]);
     append from Name Q;
  end;
end;
close;
QUIT;

/* get colour by quadrant for each of the 16 pinwheels
   Q4|Q1
   -----
   Q3|Q2
*/ 
data quadrant_col;
  set Panel(where=( (abs(x) + abs(y)) = 2 ));
  if x=1
    then if y>0 then quad=1; else quad=2;
    else if y>0 then quad=4; else quad=3;
  drop x y;
run;
proc sort; by Name quad; run;
proc transpose data=quadrant_col out=quadrant_col(drop=_:) prefix=Q;
  var ID;
  by Name;
  id quad;
run;

proc iml;
  start grid_eval(x) global(c_LR, c_TB);
    /* return the number of adjacent same coloured polygons in a grid of pinwheels */
    nr = nrow(x);
	nc = ncol(x);
	count = 0;
	if nc>1 then do i = 1 to nr; do j = 1 to nc-1;
	    count = count + c_LR[ x[i,j], x[i,j+1] ]; 
	  end; end;
	if nr>1 then do i = 1 to nr-1; do j = 1 to nc;
	    count = count + c_TB[ x[i,j], x[i+1,j] ]; 
	  end; end;
	return(count);
  finish;

  use quadrant_col;
  read all var _num_ into q;
  use Panel;
  read all var {ID x y} into pdata;

  /* create two matrices that count the number of same coloured polygons next to each other
     when 2 pinwheels are either side by side (row indexes left pinwheel, col indexes right
     pinwheel) or when stacked on top of each other (row indexes top pinwheel, col indexes
     bottom pinwheel) */
  c_LR = j(16, 16, .);
  c_TB = j(16, 16, .);  
  do i = 1 to 16; do j = 1 to 16;
    c_LR[i, j] = sum( q[i,{1 2}] = q[j,{4 3}]);
    c_TB[i, j] = sum( q[i,{3 2}] = q[j,{4 1}]);
  end; end;

  /* start with a random assignment of 16 pinwheels to a 4x4 grid */
  g = shape(ranperm(16), 4, 4);
  cc = grid_eval( g );

  /* consider swaps of one pinwheel for another that might improve the artwork. Use
     matrix b to keep a list for each cycle of swaps that give the best improvement */
  b = j(100, 2);

  do cycle = 1 to 200 until(cc=0);
    bestcc = 1E20;  /* best cc this cycle */
    do i = 1 to 15; do j = i+1 to 16;
      h = g;
	  h[i] = g[j];
	  h[j] = g[i];
	  newcc = grid_eval( h );
      if newcc<=bestcc then do;
	    if newcc<bestcc then do;
	      bestcc=newcc;
		  nb = 0;
	    end;
        nb = nb + 1;
	    b[nb, 1] = i;
	    b[nb, 2] = j;
	  end;
    end; end;
    /* choose a swap at random from those in b */
    swap = 1 + floor(rand('uniform')#nb);
    besti = b[swap,1];
    bestj = b[swap,2];
    t = g[besti];
    g[besti] = g[bestj];
    g[bestj] = t;
	cc = bestcc;
  end;

  reset noname;
  if cc=0 then print 'Found grid with no adjacent same colour polygons after' cycle [format=3.0] 'swaps';
          else print 'Optimal arrangement was not found!';
  print g [format=2.0];
  Q = {. . . .};
  create OptPanel from Q[c={'Cell' 'ID' 'x' 'y'}];
  do i = 1 to 16;
	/* work out which rows of panel data correspond to ith pinwheel in g */
	ridx = ( 20#(g[i]-1) + 1) : (20#g[i]);
	Q = j(20,1,i) || pdata[ridx, ];
	append from Q;
  end;
quit;

/* Show the artwork */
%let teal   = CX288c95;
%let orange = CXeba411;
%let blue   = CX0f5098;
%let salmon = CXd5856e;
%let gray   = CX929386;

ods graphics / width=500px height=500px;
/* for convenience, define macros for the COLAXIS and ROWAXIS options */
%macro colOpts; colaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;
%macro rowOpts;  rowaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;

title 'Random Shadow';
proc sgpanel data=OptPanel noautolegend;
   styleattrs wallcolor=&gray datacolors=(&teal &orange &blue &salmon);
   panelby Cell / columns=4 onepanel noheader noborder;
   polygon x=x y=y ID=ID / group=ID fill;
   %colOpts; %rowOpts;
run;

RandomShadow.png

IanWakeling
Barite | Level 11

It is possible to make a slightly more interesting pattern by modifying my code above as follows.  Replace the grid_eval module with this:

  start grid_eval(x) global(c_LR, c_TB, p_pen);
    /* return the number of adjacent same coloured polygons in a grid of pinwheels */
    nr = nrow(x);
	nc = ncol(x);
	count = 0;
	if nc>1 then do i = 1 to nr; do j = 1 to nc-1;
	    count = count + c_LR[ x[i,j], x[i,j+1] ] + p_pen[ x[i,j], x[i,j+1] ]; 
	  end; end;
	if nr>1 then do i = 1 to nr-1; do j = 1 to nc;
	    count = count + c_TB[ x[i,j], x[i+1,j] ] + p_pen[ x[i,j], x[i+1,j] ]; 
	  end; end;
	return(count);
  finish;

where p_pen is defined in the main program as follows:

  p_pen = j(2, 2) @ {1 0, 0 1} @ j(4, 4);

This modifies the criterion to penalize the number of times polygons meet at a point along the edge where two pinwheels meet.  The algorithm does not always converge to the optimum, but when it does it finds a pattern where there is a 3x3 sub-matrix of hourglass shapes visible.  For me this pattern seems to dominate, so much so that it is difficult to focus on the original 16 pinwheels.

RS3.png

Rick_SAS
SAS Super FREQ
Name it "Hourglass Shadow"! 🙂
IanWakeling
Barite | Level 11

I was not really thinking about taking artistic credit for the two images above 🙂,  I shall rename the first image "Mangled Shadow" and take your suggestion of "Hourglass Shadow" for the second.

IanWakeling
Barite | Level 11

I have worked-up my earlier sketch above into the final "Hourglass Shadow" 😜.  The algorithm I am using can just about cope with an 8x8 grid using your 16 pinwheels exactly four times each.

 

HourglassShadow.png

PaigeMiller
Diamond | Level 26
ods _all_ close;
ods listing;
data polygon;
    file print;
    put '/\' / '\/';
run;

It's a style of art that is called "minimalism".

 

Do I win?

--
Paige Miller
hw
Pyrite | Level 9 hw
Pyrite | Level 9
data Pinwheels;
input Group ID @;
Group = rand("integer", 0, 15);
ID = rand("integer", 0, 3);
do i = 1 to 5;
   input x y @@;
   output;
end;
drop i;
datalines;
.......

I decided to introduce randomness by changing the variables GROUP and ID using the RAND function.

random.png

IanWakeling
Barite | Level 11

Interesting to see some of the dart shapes colored in.  Presumably where one polygon has overprinted another?

jl3
Pyrite | Level 9 jl3
Pyrite | Level 9
/* Uisng a random number to generate the pinwheels dataset */
/* Depends when the program is run, the shape can be quite different */

data pinwheels;
     rand1 = rand('uniform');
     
     group = 0; id = 0; x = 0; y = 0; output;
                        x = rand1; y = 1 - x; output;
                        x = 1; y = 1; output;
                        x = 0; y = 1; output;
                        x = 0; y = 0; output;
     group = 0; id = 1; x = 0; y = 0; output;
                        x = rand1 - 1; y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 0; output;
     group = 0; id = 2; x = 0; y = 0; output;
                        x = -1 * rand1; y = -1* (1 - rand1); output;
                        x = -1; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group = 0; id = 3; x = 0; y = 0; output;
                        x = 1 - rand1; y = -1 * rand1; output;
                        x = 1; y = -1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 1; id = 0; x = 0; y = 0; output;
                        x = -1*(1 - rand1); y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 1; id = 1; x = 0; y = 0; output;
                        x = -1* rand1; y = -1 *(1-rand1); output;
                        x = -1; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group = 1; id = 2; x = 0; y = 0; output;
                        x = 1 - rand1; y = -1 *rand1; output;
                        x = 1; y = -1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 1; id = 3; x = 0; y = 0; output;
                        x = rand1; y = 1 - rand1; output;
                        x = 1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 2; id = 0; x = 0; y = 0; output;
                        x = -1*rand1; y = -1 *(1- rand1); output;
                        x = -1; y = -1; output;
                        x = 0  ; y = -1; output;
                        x = 0; y = 0; output;
     group = 2; id = 1; x = 0; y = 0; output;
                        x =  1 -rand1; y = -1 *rand1; output;
                        x = 1; y = -1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 2; id = 2; x = 0; y = 0; output;
                        x = rand1; y = 1 - rand1; output;
                        x = 1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 2; id = 3; x = 0; y = 0; output;
                        x = -1 *(1- rand1); y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 3; id = 0; x = 0; y = 0; output;
                        x = 1 -rand1; y = -1 * rand1; output;
                        x = 1; y = -1; output;
                        x = 1  ; y = 0; output;
                        x = 0; y = 0; output;
     group = 3; id = 1; x = 0; y = 0; output;
                        x =  rand1; y = 1 - rand1; output;
                        x = 1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 3; id = 2; x = 0; y = 0; output;
                        x = -1 * (1 - rand1); y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 3; id = 3; x = 0; y = 0; output;
                        x = -1 * rand1; y = -1 *(1-rand1); output;
                        x = -1; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group = 4; id = 0; x = 0; y = 0; output;
                        x = 1 *rand1; y =  1 - rand1; output;
                        x = -1; y = 1; output;
                        x = 0  ; y = 1; output;
                        x = 0; y = 0; output;
     group = 4; id = 1; x = 0; y = 0; output;
                        x = 1 - rand1; y = rand1; output;
                        x = 1; y = 1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 4; id = 2; x = 0; y = 0; output;
                        x = rand1; y = -1 * (1-rand1); output;
                        x = 1; y = -1; output;
                        x = 0  ; y = -1; output;
                        x = 0; y = 0; output;
     group = 4; id = 3; x = 0; y = 0; output;
                        x = -1 * (1-rand1); y = -1 * rand1; output;
                        x = -1; y = -1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 0; output;
     group = 5; id = 0; x = 0; y = 0; output;
                        x = 1 -rand1; y =  rand1; output;
                        x = 1; y = 1; output;
                        x = 1  ; y = 0; output;
                        x = 0; y = 0; output;
     group = 5; id = 1; x = 0; y = 0; output;
                        x =  rand1; y = -1 * (1-rand1); output;
                        x = 1; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group = 5; id = 2; x = 0; y = 0; output;
                        x = -1*(1-rand1); y = -1* rand1; output;
                        x = -1; y = -1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 5; id = 3; x = 0; y = 0; output;
                        x = -1*rand1; y = 1 - rand1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 1; output;
                        x = 0; y = 0; output;
     group = 6; id = 0; x = 0; y = 0; output;
                        x = -1*(1 -rand1); y = -1* rand1; output;
                        x = -1; y = -1; output;
                        x = -1  ; y = 0; output;
                        x = 0; y = 0; output;
     group = 6; id = 1; x = 0; y = 0; output;
                        x = -1 * rand1; y = 1-rand1; output;
                        x = -1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 6; id = 2; x = 0; y = 0; output;
                        x = 1-rand1; y = rand1; output;
                        x = 1; y = 1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 6; id = 3; x = 0; y = 0; output;
                        x =  rand1; y = -1 *(1 - rand1); output;
                        x = 1; y = -1; output;
                        x = 0; y = -1; output;
                        x = 0; y = 0; output;
     group = 7; id = 0; x = 0; y = 0; output;
                        x = rand1; y = -1* (1-rand1); output;
                        x = 1; y = -1; output;
                        x = 0  ; y = -1; output;
                        x = 0; y = 0; output;
     group = 7; id = 1; x = 0; y = 0; output;
                        x = -1 * (1-rand1); y = -1*rand1; output;
                        x = -1; y = -1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 7; id = 2; x = 0; y = 0; output;
                        x = -1*rand1; y = 1-rand1; output;
                        x = -1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 7; id = 3; x = 0; y = 0; output;
                        x = 1- rand1; y = rand1; output;
                        x = 1; y = 1; output;
                        x = 1; y = 0; output;
                        x = 0; y = 0; output;
     group = 8; id = 0; x = 0; y = 0; output;
                        x = rand1; y = 1-rand1 ; output;
                        x = 1; y = 1; output;
                        x = 0  ; y = 1; output;
                        x = 0; y = 0; output;
     group = 8; id = 1; x = 0; y = 0; output;
                        x = 1-rand1; y = -1* rand1; output;
                        x = 1; y = -1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 8; id = 2; x = 0; y = 0; output;
                        x = -1*rand1; y = -1*(1-rand1); output;
                        x = -1; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group = 8; id = 3; x = 0; y = 0; output;
                        x = -1*(1- rand1); y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 0; output;
     group = 9; id = 0; x = 0; y = 0; output;
                        x = -1 * (1-rand1); y = rand1 ; output;
                        x = -1; y = 1; output;
                        x = -1  ; y = 0; output;
                        x = 0; y = 0; output;
     group = 9; id = 1; x = 0; y = 0; output;
                        x = rand1; y =  1- rand1; output;
                        x = 1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group = 9; id = 2; x = 0; y = 0; output;
                        x =  1-rand1; y = -1*rand1; output;
                        x = 1; y = -1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group = 9; id = 3; x = 0; y = 0; output;
                        x = -1 * rand1; y = 1-rand1; output;
                        x = -1; y = -1; output;
                        x = 0; y = -1; output;
                        x = 0; y = 0; output;
     group =10; id = 0; x = 0; y = 0; output;
                        x = -1 * rand1; y = -1*(1-rand1); output;
                        x = -1; y = -1; output;
                        x = 0  ; y = 1; output;
                        x = 0; y = 0; output;
     group =10; id = 1; x = 0; y = 0; output;
                        x = -1*(1-rand1); y =  rand1; output;
                        x = -1; y = 1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group =10; id = 2; x = 0; y = 0; output;
                        x =  rand1; y = 1 - rand1; output;
                        x = 1; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group =10; id = 3; x = 0; y = 0; output;
                        x =  1 - rand1; y = -1*rand1; output;
                        x = 1; y = -1; output;
                        x = 1; y = 0; output;
                        x = 0; y = 0; output;
     group =11; id = 0; x = 0; y = 0; output;
                        x = 1 - rand1; y = -1* rand1; output;
                        x = 1; y = -1; output;
                        x = 1  ; y = 0; output;
                        x = 0; y = 0; output;
     group =11; id = 1; x = 0; y = 0; output;
                        x = -1 *rand1; y = -1*(1- rand1); output;
                        x = -1; y = -1; output;
                        x = 0  ; y = -1; output;
                        x = 0; y = 0; output;
     group =11; id = 2; x = 0; y = 0; output;
                        x =  -1 * (1-rand1); y = rand1; output;
                        x = -1; y = 1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group =11; id = 3; x = 0; y = 0; output;
                        x =  rand1; y = 1- rand1; output;
                        x = 1; y = 1; output;
                        x = 0; y = 1; output;
                        x = 0; y = 0; output;
     group =12; id = 0; x = 0; y = 0; output;
                        x = -1 *rand1; y = 1 - rand1; output;
                        x = -1; y = 1; output;
                        x = 0  ; y = 1; output;
                        x = 0; y = 0; output;
     group =12; id = 1; x = 0; y = 0; output;
                        x = -1 *(1-rand1); y = -1* rand1; output;
                        x = -1; y = -1; output;
                        x = -1  ; y = 0; output;
                        x = 0; y = 0; output;
     group =12; id = 2; x = 0; y = 0; output;
                        x =  rand1; y = -1 * (1-rand1); output;
                        x = 1 ; y = -1; output;
                        x = 0 ; y = -1; output;
                        x = 0; y = 0; output;
     group =12; id = 3; x = 0; y = 0; output;
                        x = 1 - rand1; y = rand1; output;
                        x = 1; y = 1; output;
                        x = 1; y = 0; output;
                        x = 0; y = 0; output;
     group =13; id = 0; x = 0; y = 0; output;
                        x = 1 - rand1; y = rand1; output;
                        x =  1; y = 1; output;
                        x = 1  ; y = 0; output;
                        x = 0; y = 0; output;
     group =13; id = 1; x = 0; y = 0; output;
                        x = -1*rand1; y = 1 - rand1; output;
                        x = -1; y = 1; output;
                        x =  0  ; y = 1; output;
                        x = 0; y = 0; output;
     group =13; id = 2; x = 0; y = 0; output;
                        x = -1 *(1- rand1); y = -1 * rand1; output;
                        x = -1 ; y = -1; output;
                        x = -1 ; y = 0; output;
                        x = 0; y = 0; output;
     group =13; id = 3; x = 0; y = 0; output;
                        x = rand1; y = -1 * (1-rand1); output;
                        x = 1; y = -1; output;
                        x = 0; y = -1; output;
                        x = 0; y = 0; output;
     group =14; id = 0; x = 0; y = 0; output;
                        x = -1 *(1 - rand1); y =-1 * rand1; output;
                        x =  -1; y = -1; output;
                        x = -1  ; y = 0; output;
                        x = 0; y = 0; output;
     group =14; id = 1; x = 0; y = 0; output;
                        x = rand1; y = -1*(1 - rand1); output;
                        x = 1; y = -1; output;
                        x =  0  ; y = 1; output;
                        x = 0; y = 0; output;
     group =14; id = 2; x = 0; y = 0; output;
                        x = 1- rand1; y = rand1; output;
                        x = 1 ; y = 1; output;
                        x = 1 ; y = 0; output;
                        x = 0; y = 0; output;
     group =14; id = 3; x = 0; y = 0; output;
                        x = -1 * rand1; y = 1-rand1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 1; output;
                        x = 0; y = 0; output;
     group =15; id = 0; x = 0; y = 0; output;
                        x = rand1; y =-1 * (1-rand1); output;
                        x =  1; y = -1; output;
                        x = 0  ; y = -1; output;
                        x = 0; y = 0; output;
     group =15; id = 1; x = 0; y = 0; output;
                        x = 1-rand1; y = rand1; output;
                        x = 1; y = 1; output;
                        x =  1  ; y = 0; output;
                        x = 0; y = 0; output;
     group =15; id = 2; x = 0; y = 0; output;
                        x = -1 * rand1; y = 1-rand1; output;
                        x = -1 ; y = 1; output;
                        x = 0 ; y = 1; output;
                        x = 0; y = 0; output;
     group =15; id = 3; x = 0; y = 0; output;
                        x = -1 * (1-rand1); y = -1*rand1; output;
                        x = -1; y = -1; output;
                        x = -1; y = 0; output;
                        x = 0; y = 0; output;
 run;
 
 

%let teal   = CX288c95;
%let orange = CXeba411;
%let blue   = CX0f5098;
%let salmon = CXd5856e;
%let gray   = CX929386;

ods graphics / width=640px height=640px;
%macro colOpts; colaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;
%macro rowOpts;  rowaxis offsetmin=0 offsetmax=0 display=(nolabel noticks novalues); %mend;

title "Dihedral Shadow";
proc sgpanel data=Pinwheels noautolegend;
   styleattrs wallcolor=&gray datacolors=(&teal &orange &blue &salmon);
   panelby Group / columns=4 onepanel noheader;
   polygon x=x y=y ID=ID / group=ID fill;
   %colOpts; %rowOpts;
run;

jl3_0-1628857424640.png

 

sas-innovate-2024.png

Don't miss out on SAS Innovate - Register now for the FREE Livestream!

Can't make it to Vegas? No problem! Watch our general sessions LIVE or on-demand starting April 17th. Hear from SAS execs, best-selling author Adam Grant, Hot Ones host Sean Evans, top tech journalist Kara Swisher, AI expert Cassie Kozyrkov, and the mind-blowing dance crew iLuminate! Plus, get access to over 20 breakout sessions.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 13 replies
  • 2337 views
  • 35 likes
  • 7 in conversation