This page is motivated by the discussion of Mathematica's ContourPlot shading here. Hidden in the color map of any height function in such plots is a polygonal mesh as shown on the right.
Here is a (probably incomplete) list of negative consequences:
PDF
or EPS
.PDF
documents.The solution I'm describing here is to create the color image and line art of the plot separately, and superimpose them only after rasterizing the image portion. The hard part is to make sure that all the usual plotting options work correctly, and that the separate parts are registered (aligned) properly in the final superposition. The following functions should be able to do the job.
ContourPlot
and ListContourPlot
.
DensityPlot
and ContourPlot
ListDensityPlot
and ListContourPlot
ContourPlot
of the given function, a StreamPlot
of the functions gradient field, and a rasterized DensityPlot
of the gradient field strength (the magnitude of the gradient). There is also a version that takes a 2D list as the input potential: listGradientFieldPlot.
By default, these functions draw both a colored height function image and a set of contour lines. This means that two plotting functions are called (density and contour). You can suppress the calculation and plotting of contour lines by simply giving the option Contours->None
. Then you have the equivalent of a pure DensityPlot
(or ListDensityPlot
).
The reason why I don't use a single call to a density plot and simulate the contour lines using MeshFunction
is that options such as Contours
and ContourLabels
wouldn't work in that case, and the resulting graphic wouldn't show tooltips indicating the contour height.
The image in all these functions can be made translucent by setting the option "ShadingOpacity"
to a number between 0
and 1
. This is useful when superimposing the plot onto other graphics with Prolog
, Show
or Overlay
.
There are also plot decorations such as axes, tick marks and grid lines that can be partially revealed or hidden with this "ShadingOpacity"
setting. In the standard built-in plot functions, it's possible to move grid lines or or axes to the front (or back, respectively) by adding the option Method -> {"GridLinesInFront" -> True}
and Method -> {"AxesInFront" -> False}
.
However, using opacity you can do better than this either-or choice: an example is the image on the right where I let the grid lines show through in an unobtrusive way because the plot contains so many other families of lines that should receive most of the attention. The plot was made with the gradientFieldPlot
function listed above.
The default setting AspectRatio->Automatic
for these plot functions can be changed as usual by specifying an explicit ratio of axis lengths. However, you should probably not change this setting for gradientFieldPlot
, because then the intersections of contour lines and gradient field lines will not be at right angles (as they should be if both axes are using the same length yardstick – see the example image).
I set the PlotRangePadding
to zero by default, but you can change that by specifying it explicitly in the plot command. One way to restore the usual behavior of adding 4% padding to a plot whenever the PlotRange
isn't explicitly given is to modify the definition of my plotting functions as follows:
Add PlotRangePadding -> If[FreeQ[{opts}, PlotRange], .04 {-1, 1}.rangeCoords, 0]
to the Show
statement, just before the options list (Evaluate@Apply[Sequence, frameOptions]
). This was motivated by this discussion on stackoverflow.
To make images out of the color shading in the above plots, I use Rasterize
. To display the resulting image in a way that is compatible with standard PDF
export (my main motivation), I decided I cannot use the technique of adding the image as a texture, as I did in this function to paint text onto 3D polygons (vertex colors can lead to export problems).
In composing a plot from a background image and line drawings as is done here, the biggest challenge is to align everything correctly. The density image is included as an Inset
within a rectangle whose dimensions are determined by the PlotRange
. I originally used an enclosing rectangle that was defined independently of the plot frame, but then realized that it's not needed if I fix the PlotRange
based on the size of the density plot. This still allows PlotRangePadding
to be chosen arbitrarily.
In particular when a non-default AspectRatio
is specified, the Inset
contents have to stretch. Here the Inset
documentation states, under Scope > Sizes: "Given both width and height sizes, the inset graphic will be stretched, if the aspect ratio is not fixed." But confusingly, to "not fix" the aspect ratio is not the same as to not specify it. Only by specifying the option AspectRatio → Full
do you allow the inset graphic to stretch.
Another pitfall is the use of Scaled
coordinates in the inset: the scaling of the included image is supposed to be done with respect to the "enclosing graphic" (the rectangle mentioned earlier) — but Mathematica defines the enclosing graphic to be the outermost one, which may include additional padding (and even other objects if you combine plots using Show
). This means that in my code Scaled
coordinates can't be used because they aren't insulated from further manipulations of the plot. So I end up having to work directly with the absolute plot range in order to get everything aligned properly.