Reputation: 15685
UPDATE: Here's a link to the complete dataset sas data
I have a data set that consists of 11 provinces, semi annual time periods, a indicator, actual value and target value. I'm trying to create a 3d bar chart with axises province X time-period X value and then for each province and time period show 2 bars one for actual and one for target.
My dataset looks like this:
PROVINCE PERIOD VAR VALUE
Bandundu 2013S1 OUT_BUD_ODE 250000
Bandundu 2013S1 TAR_BUD_ODE 545000
Bandundu 2013S2 OUT_BUD_ODE 283000
Bandundu 2013S2 TAR_BUD_ODE 545000
Bandundu 2014S1 OUT_BUD_ODE 800000
Bandundu 2014S1 TAR_BUD_ODE 645000
Bandundu 2014S2 OUT_BUD_ODE 700000
Bandundu 2014S2 TAR_BUD_ODE 645000
Bandundu 2015S1 OUT_BUD_ODE 369363
Bandundu 2015S1 TAR_BUD_ODE 945288
Bandundu 2015S2 OUT_BUD_ODE 1217449
Bandundu 2015S2 TAR_BUD_ODE 958417
Bandundu 2016S1 OUT_BUD_ODE 96618
Bandundu 2016S1 TAR_BUD_ODE 787740
Bandundu 2016S2 OUT_BUD_ODE 1358568
Bandundu 2016S2 TAR_BUD_ODE 787740
Bandundu 2017S1 OUT_BUD_ODE 1312900
Bandundu 2017S1 TAR_BUD_ODE 311729
Bandundu 2017S2 OUT_BUD_ODE .
Bandundu 2017S2 TAR_BUD_ODE 1299771
Bas-Congo 2013S1 OUT_BUD_ODE 150000
Bas-Congo 2013S1 TAR_BUD_ODE 450000
Bas-Congo 2013S2 OUT_BUD_ODE 145000
Bas-Congo 2013S2 TAR_BUD_ODE 450000
Bas-Congo 2014S1 OUT_BUD_ODE 1000000
Bas-Congo 2014S1 TAR_BUD_ODE 900965
Bas-Congo 2014S2 OUT_BUD_ODE 900000
Bas-Congo 2014S2 TAR_BUD_ODE 900965
Bas-Congo 2015S1 OUT_BUD_ODE 433892
Bas-Congo 2015S1 TAR_BUD_ODE 1115965
Bas-Congo 2015S2 OUT_BUD_ODE 943387
Bas-Congo 2015S2 TAR_BUD_ODE 1115965
Bas-Congo 2016S1 OUT_BUD_ODE 552002
.
.
.
the chart if we were just plotting for 1 province would look something like the following in 2D, we have side by side bars, value and time period. We need to add Province as the third axis and also make the bars 3d:
we want the axises to be province, period, value but for each point we have two bars one for OUT_BOD_ODE AND ONE FOR TAR_BAR_ODE The bars would probably look better if they were 3d too
Upvotes: 2
Views: 1247
Reputation: 27508
There is no simple built in SAS statements that will do side by side 3d bars in a block like manner. The GCHART BLOCK statement uses the subgroup option to build a stacked bar at a block intersection, but can not do a grouped bar.
data have;
infile datalines truncover;
length PROVINCE $13. PERIOD $6. VAR $13. VALUE 8.;
input PROVINCE PERIOD VAR VALUE;
datalines;
Bandundu 2013S1 OUT_BUD_ODE 250000
Bandundu 2013S1 TAR_BUD_ODE 545000
Bandundu 2013S2 OUT_BUD_ODE 283000
Bandundu 2013S2 TAR_BUD_ODE 545000
Bandundu 2014S1 OUT_BUD_ODE 800000
Bandundu 2014S1 TAR_BUD_ODE 645000
Bandundu 2014S2 OUT_BUD_ODE 700000
Bandundu 2014S2 TAR_BUD_ODE 645000
Bandundu 2015S1 OUT_BUD_ODE 369363
Bandundu 2015S1 TAR_BUD_ODE 945288
Bandundu 2015S2 OUT_BUD_ODE 1217449
Bandundu 2015S2 TAR_BUD_ODE 958417
Bandundu 2016S1 OUT_BUD_ODE 96618
Bandundu 2016S1 TAR_BUD_ODE 787740
Bandundu 2016S2 OUT_BUD_ODE 1358568
Bandundu 2016S2 TAR_BUD_ODE 787740
Bandundu 2017S1 OUT_BUD_ODE 1312900
Bandundu 2017S1 TAR_BUD_ODE 311729
Bandundu 2017S2 OUT_BUD_ODE .
Bandundu 2017S2 TAR_BUD_ODE 1299771
Bas-Congo 2013S1 OUT_BUD_ODE 150000
Bas-Congo 2013S1 TAR_BUD_ODE 450000
Bas-Congo 2013S2 OUT_BUD_ODE 145000
Bas-Congo 2013S2 TAR_BUD_ODE 450000
Bas-Congo 2014S1 OUT_BUD_ODE 1000000
Bas-Congo 2014S1 TAR_BUD_ODE 900965
Bas-Congo 2014S2 OUT_BUD_ODE 900000
Bas-Congo 2014S2 TAR_BUD_ODE 900965
Bas-Congo 2015S1 OUT_BUD_ODE 433892
Bas-Congo 2015S1 TAR_BUD_ODE 1115965
Bas-Congo 2015S2 OUT_BUD_ODE 943387
Bas-Congo 2015S2 TAR_BUD_ODE 1115965
Bas-Congo 2016S1 OUT_BUD_ODE 552002
;
run;
The not quite there BLOCK chart
proc gchart data=have;
block province / group=period subgroup=var;
run;
quit;
The code in "A 3D Scatter Plot Macro" by Sanjay Matange, Blog "Graphically Speaking March 10, 2015", shows how to 3D represent data in SGPLOT.
After some data work and small modifications to the macro, this chart can be rendered:
Other, more sophisticated renderings are possible, but likely found outside the current SAS product offerings:
proc sql;
create table uniqs as
select distinct 'province' as _var_, province as key from have
union
select distinct 'period' as _var_, period as key from have
union
select distinct 'var' as _var_, var as key from have
;
quit;
data fmt(keep=fmtname start label);
set uniqs;
by _var_;
if first._var_ then id=1; else id+1;
fmtname = _var_;
start = id;
label = key;
run;
proc format cntlin = fmt;
run;
proc sql;
create table have_mapped as
select
have.*
, f1.start as province_id
, f2.start as period_id
, f3.start as var_id
from have
left join fmt f1 on f1.label = province
left join fmt f2 on f2.label = period
left join fmt f3 on f3.label = var
where f1.fmtname = 'province'
and f2.fmtname = 'period'
and f3.fmtname = 'var'
;
quit;
Jitter the province identity value in order to separate the ODE type within each period.
data have_jittered;
set have_mapped;
if var_id = 2 then province_id = province_id + 0.2;
size = 2;
run;
If you want to submit the blog entry code as-is the following will do so:
filename source1 http "https://blogs.sas.com/content/graphicallyspeaking/files/2015/03/Matrix_Functions.txt";
filename source2 http "https://blogs.sas.com/content/graphicallyspeaking/files/2015/03/Ortho_3D_Macro_94.txt";
%include source1;
%include source2;
filename source1;
filename source2;
I modified Sanjay's macro to handle more inputs that allowed a more 'bar-like' rendering to be produced.
No changes to the matrix support:
options cmplib=sasuser.funcs;
proc fcmp outlib=sasuser.funcs.mat;
subroutine MatInv(Mat[*,*], InvMat[*,*]);
outargs InvMat;
call inv(Mat, InvMat);
endsub;
subroutine MatMult(A[*,*], B[*,*], C[*,*]);
outargs C;
call mult(A, B, C);
endsub;
subroutine MatIdent(A[*,*]);
outargs A;
call identity(A);
endsub;
run;
quit;
Ortho 3D macro updated for turing walls on or off, setting marker sizes and symbols and relative scale of the 3D box. There are some future-proof arguments for adding tic marks (tic drawing is not in this version)
%macro Ortho3D_Macro
( Data=, X=, Y=, Z=, Group=, Size=, Lblx=X, Lbly=Y, Lblz=Z,
Tilt=65, Rotate=-55, Attrmap=, Title=
, shadewalls = yes
, outlineaxis = yes
, backwall = yes
, sidewall = yes
, floorwall = yes
, floor_marker_symbol = circlefilled
, floor_marker_size = 5
, showdata = yes
, xnormalscale = 1
, ynormalscale = 2
, znormalscale = 1
, xtic=, xtic_jitter=
, ytic=, ytic_jitter=
, ztic=, ztic_jitter=
);
%local A B C WallData;
%let A=&Tilt;
%let B=0;
%let C=&Rotate;
%let WallData=wall_Axes;
/*--Define walls and axes--*/
filename walldata "work.walldata.source";
options noquotelenmax;
data _null_;
datalines="
| X1-Axis D -1 -1 -1 1 -1 -1 0 -1 -1 1
| X3-Axis L -1 -1 1 1 -1 1 . . . .
| X4-Axis D -1 1 1 1 1 1 . . . .
| Y2-Axis D -1 -1 1 -1 1 1 . . . .
| Y3-Axis D 1 -1 -1 1 1 -1 1 0 -1 2
| Y4-Axis L 1 -1 1 1 1 1 . . . .
| Z1-Axis D -1 -1 -1 -1 -1 1 -1 -1 0 3
| Z2-Axis L 1 -1 -1 1 -1 1 . . . .
| Z4-Axis D 1 1 -1 1 1 1 . . . .
| Bottom D -1 -1 -1 . . . . . . .
| Bottom D 1 -1 -1 . . . . . . .
| Bottom D 1 1 -1 . . . . . . .
| Bottom D -1 1 -1 . . . . . . .
| Back D -1 -1 -1 . . . . . . .
| Back D -1 1 -1 . . . . . . .
| Back D -1 1 1 . . . . . . .
| Back D -1 -1 1 . . . . . . .
| Right D -1 1 -1 . . . . . . .
| Right D 1 1 -1 . . . . . . .
| Right D 1 1 1 . . . . . . .
| Right D -1 1 1 . . . . . . .
";
do _n_ = 1 by 1;
file walldata;
line = scan(datalines,_n_,'|');
if line = '' then leave;
put line;
end;
run;
data wall_Axes;
infile walldata;
input @1 id $ group $ xw yw zw xw2 yw2 zw2 xl yl zl label;
run;
/*--Project the walls and axes--*/
data projected_walls;
keep id group xw yw zw xw2 yw2 zw2 xl yl zl lbx lby lbz label;
array u[4,4] _temporary_; /*--Intermediate Matrix--*/
array v[4,4] _temporary_; /*--Intermediate Matrix--*/
array w[4,4] _temporary_; /*--Final View Matrix--*/
array m[4,4] _temporary_; /*--Projection Matrix--*/
array rx[4,4] _temporary_; /*--X rotation Matrix--*/
array ry[4,4] _temporary_; /*--Y rotation Matrix--*/
array rz[4,4] _temporary_; /*--Z rotation Matrix--*/
array d[4,1] _temporary_; /*--World Data Array --*/
array p[4,1] _temporary_; /*--Projected Data Array --*/
retain r t f n;
r=1; t=1; f=1; n=-1;
pi=constant("PI");
fac=pi/180;
A=&A*fac; B=&B*fac; C=&C*fac;
/*--Set up projection matrix--*/
m[1,1]=1/r; m[1,2]=0.0; m[1,3]=0.0; m[1,4]=0.0;
m[2,1]=0.0; m[2,2]=1/t; m[2,3]=0.0; m[2,4]=0.0;
m[3,1]=0.0; m[3,2]=0.0; m[3,3]=-2/(f-n); m[3,4]=-(f+n)/(f-n);
m[4,1]=0.0; m[4,2]=0.0; m[4,3]=0.0; m[4,4]=1.0;
/*--Set up X rotation matrix--*/
rx[1,1]=1; rx[1,2]=0.0; rx[1,3]=0.0; rx[1,4]=0.0;
rx[2,1]=0.0; rx[2,2]=cos(A); rx[2,3]=-sin(A); rx[2,4]=0.0;
rx[3,1]=0.0; rx[3,2]=sin(A); rx[3,3]=cos(A); rx[3,4]=0.0;
rx[4,1]=0.0; rx[4,2]=0.0; rx[4,3]=0.0; rx[4,4]=1.0;
/*--Set up Y rotation matrix--*/
ry[1,1]=cos(B); ry[1,2]=0.0; ry[1,3]=sin(B); ry[1,4]=0.0;
ry[2,1]=0.0; ry[2,2]=1.0; ry[2,3]=0.0; ry[2,4]=0.0;
ry[3,1]=-sin(B); ry[3,2]=0.0; ry[3,3]=cos(B); ry[3,4]=0.0;
ry[4,1]=0.0; ry[4,2]=0.0; ry[4,3]=0.0; ry[4,4]=1.0;
/*--Set up Z rotation matrix--*/
rz[1,1]=cos(C); rz[1,2]=-sin(C); rz[1,3]=0.0; rz[1,4]=0.0;
rz[2,1]=sin(C); rz[2,2]=cos(C); rz[2,3]=0.0; rz[2,4]=0.0;
rz[3,1]=0.0; rz[3,2]=0.0; rz[3,3]=1.0; rz[3,4]=0.0;
rz[4,1]=0.0; rz[4,2]=0.0; rz[4,3]=0.0; rz[4,4]=1.0;
/*--Build transform matrix--*/
call MatMult(rz, m, u);
call MatMult(ry, u, v);
call MatMult(rx, v, w);
set &WallData;
/*--Set axis labels--*/
if label eq 1 then lbx="&Lblx";
if label eq 2 then lby="&Lbly";
if label eq 3 then lbz="&Lblz";
/*--Transform walls--*/
d[1,1]=xw*&xnormalscale; d[2,1]=yw*&ynormalscale; d[3,1]=zw*&znormalscale; d[4,1]=1;
call MatMult(w, d, p);
xw=p[1,1]; yw=p[2,1]; zw=p[3,1];
/*--Transform axes--*/
d[1,1]=xw2*&xnormalscale; d[2,1]=yw2*&ynormalscale; d[3,1]=zw2*&znormalscale; d[4,1]=1;
call MatMult(w, d, p);
xw2=p[1,1]; yw2=p[2,1]; zw2=p[3,1];
/*--Transform labels--*/
d[1,1]=xl*&xnormalscale; d[2,1]=yl*&ynormalscale; d[3,1]=zl*&znormalscale; d[4,1]=1;
call MatMult(w, d, p);
xl=p[1,1]; yl=p[2,1]; zl=p[3,1];
run;
/**/
/*--Compute data ranges--*/
data _null_;
retain xmin 1e10 xmax -1e10 ymin 1e10 ymax -1e10 zmin 1e10 zmax -1e10;
set &Data end=last;
xmin=min(xmin, &X);
xmax=max(xmax, &X);
ymin=min(ymin, &Y);
ymax=max(ymax, &Y);
zmin=min(zmin, &Z);
zmax=max(zmax, &Z);
if last then do;
call symputx("xmin", xmin); call symputx("xmax", xmax);
call symputx("ymin", ymin); call symputx("ymax", ymax);
call symputx("zmin", zmin); call symputx("zmax", zmax);
end;
run;
/*--Normalize the data to -1 to +1 ranges--*/
data normalized;
keep &Group &Size x y z xf yf zf xb yb zb xb2 yb2 zb2 xs ys zs xs2 ys2 zs2;
xrange=&xmax-&xmin;
yrange=&ymax-&ymin;
zrange=&zmax-&zmin;
set &data;
/*--data points--*/
x=(2*&xnormalscale)*(&X-&xmin)/xrange - &xnormalscale;
y=(2*&ynormalscale)*(&Y-&ymin)/yrange - &ynormalscale;
z=(2*&znormalscale)*(&Z-&zmin)/zrange - &znormalscale;
/*--Floor--*/
xf=x; yf=y; zf=-1;
/*--Back Wall--*/
xb=-1; yb=y; zb=z;
xb2=-1; yb2=y; zb2=-1;
/*--Side Wall--*/
xs=x; ys=1; zs=z;
xs2=x; ys2=1; zs2=-1;
run;
/*--Project the data--*/
data projected_data;
keep &Group &Size xd yd zd xf yf zf xb yb zb xb2 yb2 zb2 xs ys zs xs2 ys2 zs2;
array u[4,4] _temporary_; /*--Intermediate Matrix--*/
array v[4,4] _temporary_; /*--Intermediate Matrix--*/
array w[4,4] _temporary_; /*--Final View Matrix--*/
array m[4,4] _temporary_; /*--Projection Matrix--*/
array rx[4,4] _temporary_; /*--X rotation Matrix--*/
array ry[4,4] _temporary_; /*--Y rotation Matrix--*/
array rz[4,4] _temporary_; /*--Z rotation Matrix--*/
array d[4,1] _temporary_; /*--World Data Array --*/
array p[4,1] _temporary_; /*--Projected Data Array --*/
retain r t f n;
r=1; t=1; f=1; n=-1;
pi=constant("PI");
fac=pi/180;
/* call symput ("X", A); call symput ("Y", B); call symput ("Z", C);*/
A=&A*fac; B=&B*fac; C=&C*fac;
/*--Set up projection matrix--*/
m[1,1]=1/r; m[1,2]=0.0; m[1,3]=0.0; m[1,4]=0.0;
m[2,1]=0.0; m[2,2]=1/t; m[2,3]=0.0; m[2,4]=0.0;
m[3,1]=0.0; m[3,2]=0.0; m[3,3]=-2/(f-n); m[3,4]=-(f+n)/(f-n);
m[4,1]=0.0; m[4,2]=0.0; m[4,3]=0.0; m[4,4]=1.0;
/*--Set up X rotation matrix--*/
rx[1,1]=1; rx[1,2]=0.0; rx[1,3]=0.0; rx[1,4]=0.0;
rx[2,1]=0.0; rx[2,2]=cos(A); rx[2,3]=-sin(A); rx[2,4]=0.0;
rx[3,1]=0.0; rx[3,2]=sin(A); rx[3,3]=cos(A); rx[3,4]=0.0;
rx[4,1]=0.0; rx[4,2]=0.0; rx[4,3]=0.0; rx[4,4]=1.0;
/*--Set up Y rotation matrix--*/
ry[1,1]=cos(B); ry[1,2]=0.0; ry[1,3]=sin(B); ry[1,4]=0.0;
ry[2,1]=0.0; ry[2,2]=1.0; ry[2,3]=0.0; ry[2,4]=0.0;
ry[3,1]=-sin(B); ry[3,2]=0.0; ry[3,3]=cos(B); ry[3,4]=0.0;
ry[4,1]=0.0; ry[4,2]=0.0; ry[4,3]=0.0; ry[4,4]=1.0;
/*--Set up Z rotation matrix--*/
rz[1,1]=cos(C); rz[1,2]=-sin(C); rz[1,3]=0.0; rz[1,4]=0.0;
rz[2,1]=sin(C); rz[2,2]=cos(C); rz[2,3]=0.0; rz[2,4]=0.0;
rz[3,1]=0.0; rz[3,2]=0.0; rz[3,3]=1.0; rz[3,4]=0.0;
rz[4,1]=0.0; rz[4,2]=0.0; rz[4,3]=0.0; rz[4,4]=1.0;
/*--Build transform matris--*/
call MatMult(rz, m, u);
call MatMult(ry, u, v);
call MatMult(rx, v, w);
set normalized;
/*--Transform data--*/
d[1,1]=x; d[2,1]=y; d[3,1]=z; d[4,1]=1;
call MatMult(w, d, p);
xd=p[1,1]; yd=p[2,1]; zd=p[3,1]; wd=p[4,1];
/*--Transform floor drop shadow--*/
d[1,1]=xf; d[2,1]=yf; d[3,1]=zf; d[4,1]=1;
call MatMult(w, d, p);
xf=p[1,1]; yf=p[2,1]; zf=p[3,1]; wf=p[4,1];
/*--Transform back wall shadow--*/
d[1,1]=xb; d[2,1]=yb; d[3,1]=zb; d[4,1]=1;
call MatMult(w, d, p);
xb=p[1,1]; yb=p[2,1]; zb=p[3,1]; wb=p[4,1];
d[1,1]=xb2; d[2,1]=yb2; d[3,1]=zb2; d[4,1]=1;
call MatMult(w, d, p);
xb2=p[1,1]; yb2=p[2,1]; zb2=p[3,1]; wb2=p[4,1];
/*--Transform side wall shadow--*/
d[1,1]=xs; d[2,1]=ys; d[3,1]=zs; d[4,1]=1;
call MatMult(w, d, p);
xs=p[1,1]; ys=p[2,1]; zs=p[3,1]; ws=p[4,1];
d[1,1]=xs2; d[2,1]=ys2; d[3,1]=zs2; d[4,1]=1;
call MatMult(w, d, p);
xs2=p[1,1]; ys2=p[2,1]; zs2=p[3,1]; ws2=p[4,1];
run;
/*--Combine data with walls--*/
data combined;
merge projected_walls projected_data;
run;
%let h=_;
%let suf=&a&h&c;
/*--Draw the graph--*/
options mprint;
title "&Title";
footnote j=l h=0.7 "X:&X-Rotation=&A Y:&Y-Rotation=&B Z:&Z-Rotation=&C";
proc sgplot data=combined nowall noborder aspect=1 noautolegend dattrmap=&Attrmap des="Ortho3D plot";
%if &shadewalls = yes %then %do;
polygon id=id x=xw y=yw / fill lineattrs=(color=lightgray)
group=id transparency=0 attrid=walls;
%end;
%if &outlineaxis = yes %then %do;
vector x=xw2 y=yw2 / xorigin=xw yorigin=yw group=group noarrowheads attrid=Axes;
%end;
text x=xl y=yl text=lbx / position=bottomleft;
text x=xl y=yl text=lby / position=bottomright;
text x=xl y=yl text=lbz / position=left;
%if &backwall = yes %then %do;
* --Back wall shadow--;
vector x=xb y=yb / xorigin=xb2 yorigin=yb2 noarrowheads lineattrs=(color=gray) transparency=0.9;
scatter x=xb y=yb / markerattrs=(symbol=circlefilled size=5) group=&group transparency=0.9;
%end;
%if &sidewall = yes %then %do;
*--Side wall shadow--;
vector x=xs y=ys / xorigin=xs2 yorigin=ys2 noarrowheads lineattrs=(color=gray) transparency=0.9;
scatter x=xs y=ys / markerattrs=(symbol=circlefilled size=5) group=&group transparency=0.9;
%end;
%if &floorwall = yes %then %do;
*--Floor line to data;
vector x=xd y=yd / xorigin=xf yorigin=yf noarrowheads lineattrs=(thickness=&floor_marker_size) group=&group transparency=0.7;
*--Floor shadow--;
scatter x=xf y=yf / markerattrs=(symbol=&floor_marker_symbol size=&floor_marker_size) group=&group transparency=0.7;
%end;
%if &showdata = yes %then %do;
*--Data--;
scatter x=xd y=yd / group=&Group name='s' nomissinggroup dataskin=gloss
filledoutlinedmarkers markerattrs=(symbol=&floor_marker_symbol size=&floor_marker_size) dataskin=gloss;
%end;
keylegend 's' / autoitemsize;
xaxis display=none offsetmin=0.05 offsetmax=0.05 min=-1.8 max=1.8;
yaxis display=none offsetmin=0.05 offsetmax=0.05 min=-1.8 max=1.8;
run;
footnote;
%finished:
%mend Ortho3D_Macro;
/*--Define Attributes map for walls and axes--*/
data attrmap;
length ID $ 9 fillcolor $ 10 linecolor $ 10 linepattern $ 10;
input id $ value $10-20 fillcolor $ linecolor $ linepattern $;
datalines;
Walls Bottom cxdfdfdf cxdfdfdf Solid
Walls Back cxefefef cxefefef Solid
Walls Right cxffffff cxffffff Solid
Axes D white black Solid
Axes L white black ShortDash
;
run;
options source;
options mautosource nomprint nomlogic;
ods listing close;
ods html5 file='c:\temp\sample.html' gpath='c:\temp';
options cmplib=sasuser.funcs;
%let dpi=200;
ods graphics / reset
attrpriority=color
imagefmt=png
imagename="Sample"
width=11in
height=8in
;
%Ortho3D_Macro (
Title=Comparative ODE values by province over period
, Data=work.have_jittered, Attrmap=attrmap
, X=province_id, Lblx=Province, xtic=1 to 2, xtic_jitter=0.1
, Y=period_id, Lbly=Period, ytic=1 to 10, ytic_jitter=0
, Z=value, Lblz=Value, ztic=0 to 1e6 by 2e5, ztic_jitter=0
, Group=Var
, Tilt=55, Rotate=-25
, Size=size
, outlineaxis = no
, shadewalls = yes
, backwall = no
, sidewall = no
, floorwall = yes
, floor_marker_symbol = diamondfilled
, floor_marker_size = 6
, showdata = yes
);
ods html5 close;
As with any 3d plot the question arises "do you need a viewer" for spinning, tilting and zooming the data presentation. SAS ActiveX driver does produce such an output, however, most current browsers have disabled, deprecated or removed plug-ins (needed to run ActiveX or Java applets).
Instead of diving enormously deep into SGPLOT and GTL, perhaps the effort is better spent learning WebGL and the three.js library. Once that understanding is gained, a SAS program can write html pages that contain or refer to the chart data and a viewer that a modern browser will render natively.
I am not up on latest SAS R&D into new presentation drivers, perhaps there is already work afoot on a WebGL
device (similar to ActiveX or Java.) Heck, there is a SAS VRML graphics driver -- but I haven't fiddled with that in years.
Upvotes: 1
Reputation: 3845
*
The procedure G3D allows plotting the type of 3 dimensional graphs you want, but
This does not meet some of your criteria, but I created it at work and could test it.
data IN_CHAR;
infile datalines truncover;
input PROVINCE $13. PERIOD $6. VAR $13. VALUE 8.;
datalines;
Bandundu 2013S1 OUT_BUD_ODE 250000
Bandundu 2013S1 TAR_BUD_ODE 545000
Bandundu 2013S2 OUT_BUD_ODE 283000
Bandundu 2013S2 TAR_BUD_ODE 545000
Bandundu 2014S1 OUT_BUD_ODE 800000
Bandundu 2014S1 TAR_BUD_ODE 645000
Bandundu 2014S2 OUT_BUD_ODE 700000
Bandundu 2014S2 TAR_BUD_ODE 645000
Bandundu 2015S1 OUT_BUD_ODE 369363
Bandundu 2015S1 TAR_BUD_ODE 945288
Bandundu 2015S2 OUT_BUD_ODE 1217449
Bandundu 2015S2 TAR_BUD_ODE 958417
Bandundu 2016S1 OUT_BUD_ODE 96618
Bandundu 2016S1 TAR_BUD_ODE 787740
Bandundu 2016S2 OUT_BUD_ODE 1358568
Bandundu 2016S2 TAR_BUD_ODE 787740
Bandundu 2017S1 OUT_BUD_ODE 1312900
Bandundu 2017S1 TAR_BUD_ODE 311729
Bandundu 2017S2 OUT_BUD_ODE .
Bandundu 2017S2 TAR_BUD_ODE 1299771
Bas-Congo 2013S1 OUT_BUD_ODE 150000
Bas-Congo 2013S1 TAR_BUD_ODE 450000
Bas-Congo 2013S2 OUT_BUD_ODE 145000
Bas-Congo 2013S2 TAR_BUD_ODE 450000
Bas-Congo 2014S1 OUT_BUD_ODE 1000000
Bas-Congo 2014S1 TAR_BUD_ODE 900965
Bas-Congo 2014S2 OUT_BUD_ODE 900000
Bas-Congo 2014S2 TAR_BUD_ODE 900965
Bas-Congo 2015S1 OUT_BUD_ODE 433892
Bas-Congo 2015S1 TAR_BUD_ODE 1115965
Bas-Congo 2015S2 OUT_BUD_ODE 943387
Bas-Congo 2015S2 TAR_BUD_ODE 1115965
Bas-Congo 2016S1 OUT_BUD_ODE 552002
;
run;
*
This could also be achieved with sql select distinct;
proc means data=IN_CHAR noprint;
class province period;
types province period;
output out=CNT_VALUES n=;
run;
data CNTL;
set CNT_VALUES;
* Create a format for the provices *;
if not missing(PROVINCE) then do;
FmtName ='province';
label = PROVINCE;
end;
* Create a format for the periods *;
if not missing(PERIOD) then do;
FmtName ='period';
label = PERIOD;
end;
start = _N_; * Just to have some unique numeric identifier *;
keep FmtName label start;
run;
proc format cntlin=CNTL;
run;
*
proc sql;
create table IN_NUM as
select IN_.VALUE
, PROV.start as PROVINCE format=province.
, PER.start as PERIOD format=period.
, case VAR
when 'TAR_BUD_ODE' then 'blue'
when 'OUT_BUD_ODE' then 'crimson'
end as color
from IN_CHAR as IN_
left join CNTL as PROV on PROV.label = IN_.PROVINCE
left join CNTL as PER on PER.label = IN_.PERIOD
order by IN_.PROVINCE, IN_.PERIOD;
quit;
*
proc g3d data=IN_NUM;
scatter PERIOD * PROVINCE = VALUE / color=color;
run;
I will not test this now, as I should start packing for a vacation, but here are some ideas. There is surely an option of g3d to replace the needles by cilinders or bars. To introduce a shift, I would
create table IN_NUM as
select IN_.VALUE
, case VAR
when 'TAR_BUD_ODE' then PROV.start
when 'OUT_BUD_ODE' then PROV.start + 0.2
end as PROVINCE format=province.
, PROV.start as PROVINCE format=province.
, PER.start as PERIOD format=period.
, case VAR
when 'TAR_BUD_ODE' then 'blue'
when 'OUT_BUD_ODE' then 'crimson'
end as color
...
data CNTL;
set CNT_VALUES;
...
start = _N_;
end = _N_ + 0.5;
keep FmtName label start;
run;
Upvotes: 1
Reputation: 3845
proc sgplot
with proc sgpanel
There is a lot to tune about the solution I propose, but basically it is:
proc sgpanel data=Before;
panelby PROVINCE ;
vbar PERIOD / freq=VALUE group= VAR groupdisplay=cluster;
run;
I loaded the data with:
data Before;
infile datalines truncover;
input PROVINCE $13. year 4. semester $2. VAR $13. VALUE 8.;
format period date9.;
select (semester);
when ('S1') PERIOD = mdy(1, 1, year);
when ('S2') PERIOD = mdy(7, 1, year);
end;
datalines;
Bandundu 2013S1 OUT_BUD_ODE 250000
Bandundu 2013S1 TAR_BUD_ODE 545000
Bandundu 2013S2 OUT_BUD_ODE 283000
Bandundu 2013S2 TAR_BUD_ODE 545000
Bandundu 2014S1 OUT_BUD_ODE 800000
Bandundu 2014S1 TAR_BUD_ODE 645000
Bandundu 2014S2 OUT_BUD_ODE 700000
Bandundu 2014S2 TAR_BUD_ODE 645000
Bandundu 2015S1 OUT_BUD_ODE 369363
Bandundu 2015S1 TAR_BUD_ODE 945288
Bandundu 2015S2 OUT_BUD_ODE 1217449
Bandundu 2015S2 TAR_BUD_ODE 958417
Bandundu 2016S1 OUT_BUD_ODE 96618
Bandundu 2016S1 TAR_BUD_ODE 787740
Bandundu 2016S2 OUT_BUD_ODE 1358568
Bandundu 2016S2 TAR_BUD_ODE 787740
Bandundu 2017S1 OUT_BUD_ODE 1312900
Bandundu 2017S1 TAR_BUD_ODE 311729
Bandundu 2017S2 OUT_BUD_ODE .
Bandundu 2017S2 TAR_BUD_ODE 1299771
Bas-Congo 2013S1 OUT_BUD_ODE 150000
Bas-Congo 2013S1 TAR_BUD_ODE 450000
Bas-Congo 2013S2 OUT_BUD_ODE 145000
Bas-Congo 2013S2 TAR_BUD_ODE 450000
Bas-Congo 2014S1 OUT_BUD_ODE 1000000
Bas-Congo 2014S1 TAR_BUD_ODE 900965
Bas-Congo 2014S2 OUT_BUD_ODE 900000
Bas-Congo 2014S2 TAR_BUD_ODE 900965
Bas-Congo 2015S1 OUT_BUD_ODE 433892
Bas-Congo 2015S1 TAR_BUD_ODE 1115965
Bas-Congo 2015S2 OUT_BUD_ODE 943387
Bas-Congo 2015S2 TAR_BUD_ODE 1115965
Bas-Congo 2016S1 OUT_BUD_ODE 552002
;
Sugestion: if you include your code in the answer, I will tune my sollution to look more as you expected.
Upvotes: 0