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

 

hackathon24-white-horiz.png

2025 SAS Hackathon: There is still time!

Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

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