Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/vb.net/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用SAS 9.4并排创建3d聚集条形图2个变量_Sas - Fatal编程技术网

使用SAS 9.4并排创建3d聚集条形图2个变量

使用SAS 9.4并排创建3d聚集条形图2个变量,sas,Sas,更新:这里有一个完整数据集的链接 我有一个数据集,包括11个省份、半年期、指标、实际值和目标值。我正在尝试创建一个带有axises省X时间段X值的三维条形图,然后为每个省和时间段显示两个条形图,一个用于实际,一个用于目标 我的数据集如下所示: PROVINCE PERIOD VAR VALUE Bandundu 2013S1 OUT_BUD_ODE 250000 Bandundu 2013S1 TAR_BUD_ODE 545000 Bandundu 2013S2

更新:这里有一个完整数据集的链接

我有一个数据集,包括11个省份、半年期、指标、实际值和目标值。我正在尝试创建一个带有axises省X时间段X值的三维条形图,然后为每个省和时间段显示两个条形图,一个用于实际,一个用于目标

我的数据集如下所示:

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
.
.
.
如果我们只是绘制一个省的图表,在2D中会像下面这样,我们有并排的条形图、值和时间段。我们需要将省添加为第三个轴,并使条形图成为3d:

我们希望轴是省、周期、值,但对于每个点,我们有两个条形图,一个用于OUT\u BOD\u ODE,一个用于TAR\u BAR\u ODE,如果它们也是3d的,则这些条形图可能会更好

proc sgpanel
替换
proc sgplot
我提出的解决方案有很多地方需要调整,但基本上是:

proc sgpanel data=Before;
    panelby PROVINCE ;
    vbar PERIOD / freq=VALUE group= VAR groupdisplay=cluster;
run;
准备数据 我加载了以下数据:

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
;
建议:如果您在答案中包含您的代码,我将调整我的解决方案,使其看起来更符合您的预期。

*

关于这个答案 G3D程序允许绘制所需的三维图形类型,但

  • 您需要数值类变量,所以请使用一种格式使它们显示为字符
  • 它不覆盖两个绘图,但允许为数据点着色
试液 这不符合您的一些标准,但我在工作中创建了它,并可以对其进行测试

把颂歌和颂歌放在一起; *

为类变量创建格式 这也可以通过sqlselectdistinct实现

*

使类变量为数字,但显示为文本; *

创建图表;

我将如何进行 我现在不会测试这个,因为我应该开始打包度假,但这里有一些想法。 g3d肯定有一种选择,即用针或棒代替针。 为了引入一种转变,我会

转移实际的省份,即移出巴德 扩展带有结束符的格式,以便正确显示OUT_BUD_ODE的省
没有简单的内置SAS语句可以以类似块的方式并排执行3d条。GCHART BLOCK语句使用“子组”选项在块交点处生成堆叠条,但不能生成分组条

步骤1-数据和图表 不完全有方块图

proc gchart data=have;
  block province / group=period subgroup=var;
run;
quit;

努力 Sanjay Matange的“Graphically Speaking March 10,2015”博客中的代码显示了如何在SGPLOT中三维表示数据

经过一些数据工作和对宏的小修改后,可以呈现此图表:

其他更复杂的渲染是可能的,但可能在当前SAS产品之外找到:

步骤2-数据准备,将分类值转换为单调标识值 抖动省标识值,以便在每个时段内分离ODE类型

data have_jittered;
  set have_mapped;
  if var_id = 2 then province_id = province_id + 0.2;
  size = 2;
run;
三维散点图宏 如果您想按原样提交博客条目代码,以下操作将完成:

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;
步骤3-修改的宏 我修改了Sanjay的宏以处理更多的输入,从而可以生成更“条形”的渲染

矩阵支持没有变化:

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宏,设置标记大小和符号以及3D框的相对比例。对于添加tic标记,有一些未来的论证(tic图纸不在此版本中)

步骤4-使用宏 其他考虑 与任何3d绘图一样,在旋转、倾斜和缩放数据显示时会出现“是否需要查看器”的问题。SAS ActiveX驱动程序确实会产生这样的输出,但是,大多数当前浏览器都禁用、弃用或删除了插件(运行ActiveX或Java小程序所需)

与其深入研究SGPLOT和GTL,不如把精力花在学习WebGL和GTL上。一旦了解了这一点,SAS程序就可以编写包含或引用图表数据的html页面以及现代浏览器本机呈现的查看器


我对最新的SAS研发新的演示驱动程序不感兴趣,也许在
WebGL
设备(类似于ActiveX或Java)上已经有了工作。见鬼,有一个SAS VRML图形驱动程序——但我已经多年没用过了。

您的数据和代码不一致,所以很难想象或看到哪里出了问题。好的,我删除了不太有用的代码,将代码与数据对齐会很有帮助。就目前情况而言,问题不再符合SO准则,具体来说,您尝试了什么?我尝试将其与示例代码对齐,但不起作用。使用3D聚集条形图意味着什么?它应该看起来像或像或像其他东西吗?这和我想要的不太接近,但是谢谢你的尝试。我正在寻找一个三维条形图,其中x轴设置为周期,y轴设置为值,z轴设置为省。然后,每个点有两条横线,一条横线表示OUT\u BUD\u ODE,一条横线表示TAR\u BUD\u ODE。这些条也应该是3d的。因此我在你帖子下面的评论中提出了我的问题。我不知道如何在3d中做到这一点。我可以复制代码,将做一个并排条形图在二维,但没有看到任何东西,将在3dno它没有这样做。我可以这样做,但是你如何把它们并排展示而不是堆积如山呢?首先把你的问题清楚地提出来怎么样?
    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;
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;
proc gchart data=have;
  block province / group=period subgroup=var;
run;
quit;
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;
data have_jittered;
  set have_mapped;
  if var_id = 2 then province_id = province_id + 0.2;
  size = 2;
run;
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;
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;
%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;