I'm creating a filled contour plot, using contourplotparm in GTL, and I want to precisely control the color for each filled-in area. SAS, however, takes my color choices only as an input in some algorithm to determine the colors it actually uses. Here's an example:
* GTL template for a filled contour plot, with 5 colors. The contour levels must
be specified when calling SGRENDER.;
proc template;
define statgraph ContourPlotParm;
dynamic _levels _title;
begingraph;
entrytitle _title;
layout overlay;
contourplotparm x=Height y=Weight z=Density /
contourtype=fill
levels=_levels
colormodel=(red yellow green blue purple)
name="Contour";
continuouslegend "Contour";
endlayout;
endgraph;
end;
run;
* render a contour plot with 6 contour levels, and thus with 5 filled-in areas
between the levels;
ods graphics / width=3in;
proc sgrender data=sashelp.gridded template=ContourPlotParm;
dynamic _levels='0 0.0003 0.0007 0.001 0.0014 0.0017' _title='Figure 1';
run;
* same as above, but different contour levels;
proc sgrender data=sashelp.gridded template=ContourPlotParm;
dynamic _levels='0 0.0005 0.0007 0.001 0.0014 0.0017' _title='Figure 2';
run;
Here's the two resulting figures:
First, note that neither figure uses exactly the colors I wanted: there's no red, there's no yellow, and I'm guessing that the other colors aren't exactly what I specified. Second, note that the two figures have different colors from each other even though I did not change the colors that I specified; the only difference is the contour level values.
My question is, how do I get exactly the colors I want? I think another way of asking this is, given the contour levels and the colors that I want, is it possible to come up with a different set of colors such that, when I specify these colors in the colormodel option, SAS ends up actually using the colors I want?
The SAS documentation for contourplotparm offers the following hint, but I'm not sure what to make of it:
For a contour plot of type FILL, LINEFILL, or LABELEDLINEFILL, the color that is chosen for the first segment is the color for the N+1 inflection point and not the starting color in the color model. |
Thanks for any help.
Try using the
LEVELS=(contour-value-list) or NLEVELS= option to make sure that the number of areas matches the number of colors in the color ramp. Here is an example that seems to work, but I had to add a fake color (WHITE) at the lower end of the color ramp to get it to work. Not sure why.
proc template;
define statgraph ContourPlotParm2;
dynamic _X _Y _Z _TITLE;
begingraph;
entrytitle _TITLE;
layout overlay;
contourplotparm x=_X y=_Y z=_Z /
contourtype=fill nlevels=8
colormodel=(white red orange yellow green blue magenta grey) name="Contour";
continuouslegend "Contour" / title=_Z;
endlayout;
endgraph;
end;
run;
data Have;
do y=-5 to 5 by 0.1;
do x=-5 to 5 by 0.1;
z = x*x/20 + y*y/10;
output;
end;
end;
run;
proc means data=Have;run;
proc sgrender data=Have template=ContourPlotParm2;
dynamic _TITLE="Contour Plot"
_X="x" _Y="y" _Z="z";
run;
Thanks, @Rick_SAS. I think your example works because you used nlevels to create 8 evenly spaced levels. However, in your code, if you replace nlevels=8 with levels=(0 0.3 1 1.3 2 2.5 3 3.75) to create 8 irregularly spaced levels, then the resulting figure no longer uses the colors you chose. In my real use case, I do want to pick my own level values and they are not evenly spaced, so unfortunately your suggestion doesn't work for me. (For context, I'm plotting hazard ratios as a function of two predictor variables. I'm using the levels to bin the HRs into meaningful ranges and they are not evenly spaced.)
I suspect part of the issue revolves around your "levels", 6 boundaryies=>7 groups to display, not aligning with colors, 5, forcing an added interpolation somewhere. Probably compounded by the last group, at least with the level choices you show has very few records compared to the last level.
I can get this to use the 5 colors PLUS one, this way. But since it requires a data step to force your increments onto the density value I suspect it isn't as nice as you hoped.
proc template; define statgraph ContourPlotParm; dynamic _levels _title; begingraph; entrytitle _title; layout overlay; contourplotparm x=Height y=Weight z=Density / contourtype=fill levels=_levels colormodel=(white red yellow green blue purple) name="Contour"; continuouslegend "Contour"; endlayout; endgraph; end; run; data toplot; set sashelp.gridded; array _l(6) _temporary_( 0,0.0003,0.0007,0.001,0.0014,0.0017); array _z(6); do i= 1 to dim(_l); _z[i]= abs(_l[i] - density); end; tl= whichn(min(of _z(*)),of _z(*)); density=_l[tl]; run; proc sgrender data=toplot template=ContourPlotParm; dynamic _levels='0 0.0003 0.0007 0.001 0.0014 0.0017' _title='Figure 1' ; run;
This code
proc format; value density 0 =' 0' 0 <- 0.0003=' 0<- 0.0003' 0.0003<- 0.0007='0.0003<- 0.0007' 0.0007<- 0.001 ='0.0007<- 0.001' 0.001 <- 0.0014='0.001 <- 0.0014' 0.0014<- 0.0017='0.0014<- 0.0017' 0.0017<- high ='0.0017+' ; run; ods listing; proc freq data=sashelp.gridded; tables density; format density density.; run; ods listing close;
Shows the groups with your boundary values:
Density Frequency Percent Frequency Percent ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ 0 138 3.83 138 3.83 0<- 0.0003 2858 79.39 2996 83.22 0.0003<- 0.0007 284 7.89 3280 91.11 0.0007<- 0.001 124 3.44 3404 94.56 0.001 <- 0.0014 128 3.56 3532 98.11 0.0014<- 0.0017 61 1.69 3593 99.81 0.0017+ 7 0.19 3600 100.00
I am thinking of the old GMAPS Levels option where the interpolation was sometimes other than desired.
Anyway that lead me to trying the starting data set with the format.
proc sgrender data=sashelp.gridded template=ContourPlotParm; dynamic _levels='0 0.0003 0.0007 0.001 0.0014 0.0017' _title='Figure 1' ; format density density.; run;
Which works, as I think you intended IF there are (at least?) 6 colors in the colorlist as in my version above.
So you may have to create a format for each different level list. And if you go to 8 levels I would expect this to break again.
Thanks, @ballardw. I think your code *appears* to work at first glance, but that's because the level values (0 0.0003 0.0007 0.001 0.0014 0.0017) are close-to-but-not-quite evenly spaced. If you inspect the color that should be pure red (i.e., cxFF0000) in the figure, it's actually cxFC282E; the other colors are all slightly off too. If you change the levels to (0 0.0005 0.0007 0.001 0.0014 0.0017) so that they are less evenly spaced, then it becomes more obvious that your code has the same shortcomings as mine.
@dagremu wrote:
Thanks, @ballardw. I think your code *appears* to work at first glance, but that's because the level values (0 0.0003 0.0007 0.001 0.0014 0.0017) are close-to-but-not-quite evenly spaced. If you inspect the color that should be pure red (i.e., cxFF0000) in the figure, it's actually cxFC282E; the other colors are all slightly off too. If you change the levels to (0 0.0005 0.0007 0.001 0.0014 0.0017) so that they are less evenly spaced, then it becomes more obvious that your code has the same shortcomings as mine.
When the requirement is "red" and the error is within monitor display size/ gamma/ brightness or other color correction settings affect I call that close enough. If the object is paper print and Pantone type settings that's past my non-paid grade.
I don't use contour plots so have never had to try to force appearance, especially with uneven range values.
I agree, @ballardw, and I didn't mean to seem nitpicky about tiny differences in color. In my real use case, the levels are unevenly spaced, and so the differences in color (between what I specify and what SAS uses) are more pronounced.
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.
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.
Ready to level-up your skills? Choose your own adventure.