I actually like your own solution as a way to generalize the RegionPlot
approach, and the answer by @tkott!
Edit with improved version of RegionPlot trick
Since @tkott's solution was still rough around the edges, I hacked together a version of it that should be able to emulate all the features of ContourPlot
- i.e., be usable as a drop-in replacement with the same syntax and options. Here it is:
contourRegionPlot[f_, rx_, ry_, opts : OptionsPattern[]] :=
Module[{cont, contourOptions, frameOptions, colList, levelList, lab,
gr, pOpt, allLines, contourstyle, regionPlotOptions,
contourStyleList},
contourstyle =
ContourStyle /. {opts} /.
None -> Opacity[0] /. {ContourStyle -> Automatic};
contourOptions =
Join[FilterRules[{opts},
FilterRules[Options[ContourPlot],
Except[{Prolog, Epilog, Background, ContourShading,
ContourLabels, ContourStyle, RegionFunction}]]], {Background -> None,
ContourShading -> True, ContourLabels -> Automatic,
ContourStyle -> contourstyle}];
regionPlotOptions =
Join[FilterRules[{opts},
FilterRules[Options[RegionPlot],
Except[{Prolog, Epilog, Background,RegionFunction}]]], {Background -> None,
PlotStyle -> None}];
cont = Normal@
ContourPlot[f, rx, ry, Evaluate@Apply[Sequence, contourOptions]];
colList =
Reverse@Cases[
cont, {EdgeForm[___], ___,
r_?(MemberQ[{RGBColor, Hue, CMYKColor, GrayLevel},
Head[#]] &), ___} :> r, Infinity];
{contourStyleList, levelList} =
Transpose@
Cases[cont, Tooltip[{gr_, __}, lab_] -> {gr, lab}, Infinity];
frameOptions = FilterRules[{opts}, Options[Graphics]];
allLines =
Flatten@Prepend[
Table[RegionPlot[Evaluate[f < levelList[[i]]], rx, ry,
Evaluate@Apply[Sequence, regionPlotOptions]], {i,
Length[levelList]}],
RegionPlot[f > levelList[[-1]], rx, ry,
Evaluate@Apply[Sequence, regionPlotOptions]]];
pOpt = allLines[[1, 2]];
Show[Graphics[
MapThread[{EdgeForm[#1], FaceForm@Directive[Opacity[1], #2],
FilledCurve[
List /@ Cases[Normal[#3], _Line, Infinity]]} &, {Append[
contourStyleList, Opacity[0]], colList, allLines}], pOpt],
frameOptions]]
Edit 3
I streamlined some inefficient code, and made the ContourStyle
option work. The function is clearly slower than my rasterized approach. But it comes close to the old version-5 behavior.
Edit 4
In the above code, one can now also use Epilog
, and it is allowed to specify ContourStyle
as a list with separate directives for each contour.
That's probably all I'll do with this method, since there are sufficiently many alternatives that could be used if the above doesn't do what you want. I personally still prefer the continuous gradients of DensityPlot
and contourDensityPlot
(see below). Meanwhile there is also @Szabolcs' answer, which is seen in his example to accept the RegionFunction
argument. Instead of implementing this option, I chose to ignore it in the above solution (it does work in the contourDensityPlot
solution below).
Here is another example:
contourRegionPlot[Sin[x^2 + y^2]/(x^2 + y^2), {x, -2, 2}, {y, -2, 2},
ContourStyle -> Black, ColorFunction -> Hue]
If you leave out the styling, you'll get the version-8 default styles.
Alternative image-based approach
I don't know if my solution (based on rasterization, not your proposal) is saner, but it offers some additional possibilities. In case you haven't already tried it, you may want to look at my answer on stackOverflow. The post links to a page whose parent page has several different functions that all use rasterized images for the shading. In this case, the relevant one would be rasterContourPlot
. Since it uses images, one can apply opacity or potentially other image effects to the output.
But as I said, your approach of stacking different image levels seems very sane to me.
Edit
Among the rasterized solutions I list above, the first one I did was the one listed below. My rationale for it was: if I am going to try and fix the shading problem for ContourPlot
, why not add a feature that I was looking for anyway:
While contour lines are good, I also like to have the color fill to have a smooth gradient representing the function more faithfully. With the uniform ContourShading
of ContourPlot
, it seems to me that you're sometimes losing too much information about the function. Of course if I just want smooth color gradients, I could use DensityPlot
instead. But wanted both, gradients and contours. That's originally why I decided it was time to write my own replacement for ContourPlot
, listed here:
contourDensityPlot[f_, rx_, ry_,
opts : OptionsPattern[]] :=
(* Created by Jens U.Nöckel for Mathematica 8,revised 12/2011*)
Module[{img, cont, p, plotRangeRule, densityOptions, contourOptions,
frameOptions, rangeCoords},
densityOptions =
Join[FilterRules[{opts},
FilterRules[Options[DensityPlot],
Except[{Prolog, Epilog, FrameTicks, PlotLabel, ImagePadding,
GridLines, Mesh, AspectRatio, PlotRangePadding, Frame,
Axes}]]], {PlotRangePadding -> None, ImagePadding -> None,
Frame -> None, Axes -> None}];
p = DensityPlot[f, rx, ry, Evaluate@Apply[Sequence, densityOptions]];
plotRangeRule = FilterRules[Quiet@AbsoluteOptions[p], PlotRange];
contourOptions =
Join[FilterRules[{opts},
FilterRules[Options[ContourPlot],
Except[{Prolog, Epilog, FrameTicks, Background, ContourShading,
Frame, Axes}]]], {Frame -> None, Axes -> None,
ContourShading -> False}];
(* //The density plot img and contour plot cont are created here:*)
img = Rasterize[p];
cont = If[
MemberQ[{0,
None}, (Contours /. FilterRules[{opts}, Contours])], {},
ContourPlot[f, rx, ry,
Evaluate@Apply[Sequence, contourOptions]]];
(* //Before showing the plots,
set the PlotRange for the frame which will be drawn separately:*)
frameOptions =
Join[FilterRules[{opts},
FilterRules[Options[Graphics],
Except[{PlotRangeClipping, PlotRange}]]], {plotRangeRule,
Frame -> True, PlotRangeClipping -> True}];
rangeCoords = Transpose[PlotRange /. plotRangeRule];
(* //To align the image img with the contour plot,enclose img in a//
bounding box rectangle of the same dimensions as cont,
//and then combine with cont using Show:*)
Show[Graphics[{Inset[
Show[SetAlphaChannel[img,
"ShadingOpacity" /. {opts} /. {"ShadingOpacity" -> 1}],
AspectRatio -> Full], rangeCoords[[1]], {0, 0},
rangeCoords[[2]] - rangeCoords[[1]]]},
PlotRangePadding -> None], cont,
Evaluate@Apply[Sequence, frameOptions]]]
With your example, it produces the following output. To get this result, using the rasterized image of the DensityPlot
was the only "sane" alternative.
contourDensityPlot[Cos[x] + Cos[y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]
I should add a note about the options: you can feed this function with the options for ContourPlot
and DensityPlot
. You can completely suppress the ContourPlot output by giving the option Contours -> None
.
I also added an option "ShadingOpacity"
that can be used to make the shaded background transparent. The shading is fully opaque for "ShadingOpacity" -> 1
and fully transparent for "ShadingOpacity" -> 0
. This option is useful if you want to combine the plot with a Prolog, or if you have added Gridlines -> Automatic which usually will be hidden behind the density plot shading.
Edit 2
As mentioned in the comment to the question, I also tried polygone
(a command-line tool you have to run in Terminal), and got mixed results with EPS exported from Mathematica version 8.