3 Answers
3

I updated the definitions of reportColorRange and colorLegend: added more comments in the code, allowed more customization options for the legend. Color gradients are produced by VertexColors for better-looking PDF export; gradients can also be replaced by color bands (using the "ColorSwathes" option). The labels on the color bar can be specified by adding the option Contours -> n where n is the number of subdivisions of the color legend. I'll add some examples at the end ot illustrate these new features.

End Edit

This question needs two parts to be answered fully:

How to extract the range of height values from a ListDensityPlot

How to get a legend, because ShowLegend has many problems: e.g., the numerical labels on the color bar are cut off, and it's just plain ugly.

So I'll address both points here.

Extract height range

This is a particular challenge if the plot range is clipped, as happens for example when there is a near-divergence in the plot. To get the necessary values, one could do what Michael suggested in his answer. Instead, I collect the height values at the time the plot is created. My rationale is that this may make it possible to apply the same extraction algorithm not just to ListDensityPlot but also to other types of plot that work with ColorFunction, such as ContourPlot, in a more robust way that doesn't depend precisely on the internal tree structure of the plot output. I'll test that below, but first let's define a test list that has a near-singularity:

The last line means that you can provide an argument to this function that represents a density plot command, but the command isn't executed right away. Instead, the command is stored in the variable plotFunction and then pre-processed by changing the color function temporarily. The original, user-specified (or default) color function is restored to the plot result after extracting the height values.

Here is what the function does to a ListDensityPlot of the test table t:

{plot, colors, range} =
reportColorRange[
ListContourPlot[t]
]

This will work with DensityPlot or ListDensityPlot as well. The last tuple is the desired range information, and the first element is the plot. In the middle, we also receive the color function that has been used. I keep that information because it's needed to make the plot legend next.

Important usage note: As mentioned above, reportColorRange monitors the plotting function as it creates the plot. Therefore, you have to provide the actual plotting function as the argument: reportColorRange[DensityPlot[...]] and not something like this: p = DensityPlot[...]; reportColorRange[p]. The latter won't work because the variable p then contains the already finished plot, and this can't be used to monitor the true minima and maxima of the function range that was explored during the creation of p.

Color bar legend

The legend first needs a color bar, and the output of the above test shows us how to get started: just look at the InputForm of the bar appearing in ColorDataFunction and copy the code. That's essentially what I did to define the colorLegend function listed below. There are a couple of other functions required for the display that I'll also list here:

The variables plot, colors and range are the ones returned by reportColorRange above.

The first argument of at is the desired position of the bottom left corner of the Graphics object preceding them. These positions are measured in a range from 0 to 1 across the horizontal and vertical dimensions of the output.

The second argument of at is the scale at which the preceding graphic is to be placed in the output. It could be be specified as a single number (e.g, 0.8 above), as a tuple {width, height} or as a Scaled tuple (e.g., Scaled[{.15, .5}] above). The Scaled is useful only if you plan to include the final result in another plot, because then Scaled will be measured relative to that new size. I'm using that for the colorLegend, but it's not required.

The colorLegend is very malleable, in that you can stretch or squeeze it arbitrarily using the second (scale) argument to at.

Here, I placed the legend inside the plot. That happened because the width for plot is specified by at to be 1 (corresponding to 100%), and the bottom right corner of the legend has been placed at position {0.8, .1}.

The plot legend can sometimes become too ugly if you include too many digits of the height extrema. That's why I added an option "Digits" -> 2 to the function that specifies the number of significant digits to display in the labels. The other options accepted by colorLegend are "LeftLabel" (if True, make labels on the left of the color bar), LabelStyle (color etc. for the labels), Background (to change from LightGray to something else), FrameStyle and RoundingRadius.

The central tool for combining plots and legends above is Inset. To show how much flexibility that approach gives you for positioning graphics (compared to GraphicsGrid, e.g.), I'll just combine the two examples from above with some arbitrary arrangement:

The actual plot is contained in the variable densityPlot, and it is combined with the legend as in the previous examples. You could try to play with the settings to see what the options do.

I also added some extra calculations to determine the positions of the plot and legend above. The goal was to give two parameters: plotWidth and aspectRatio, and determine how to fit the plot and legend into the resulting image so as to use up all available space.

The plotWidth is specified as a fraction of the horizontal plot width. Since by default the aspect ratio of the plot (densityPlot) is fixed, there will be wasted space or clipping if we play around with the aspectRatio variable which applies to the entire image including the legend. For example, try setting aspectRatio -> 1 or .5. This shows that there may still be some manual adjustment required to find the best positioning of the legend.

Here, I also tried a different way of calculating the positioning of the legend: I assumed the width and height of the legend are given as fractions of the image dimensions in labelWidth and labelHeight, and decided to align the legend with the left side, the plot with the right side of the image.

So now you will see that the alignments behave differently when you change the value of aspectRatio. Try making it smaller, e.g., aspectRatio = .6, and you'l see extra white space between the legend and plot. Also try increasing labelHeight to 1.

Finally, here is an example with ContourPlot where I would like to show a legend that has color bands instead of gradients. This goes back to the first example of the post (see above):

As in the first example, note that the white region in the middle isn't part of the color range - it's an exclusion due to the singularity in the data. That's why the color legend doesn't have white at the top.

The important option here is "ColorSwathes" which says how many color bands there are. To figure out that number isn't completely straightforward, though. That's why I don't have an automated way of doing it, except what I added in the Block above. The idea is that numberOfContours can be obtained by counting the number of Tooltip occurrences, assuming that the ContourPlot follows the default setting which wraps each contour in Tooltip. This would be easier if the contours could be extracted using AbsoluteOptions, but that's unfortunateley not the case.

I still use the results of reportColorRange to determine what the correct labels for the legend should be. But here, too, there is some work required because with the Automatic setting for the contours in ContourPlot, the values will be at "nice" divisions that don't have to be rationally related to the minimum or maximum function values. I used FindDivisions to guess what the contour divisions should be.

Edit

The last example with a banded color legend was also the topic of another question where I added a different variant of colorLegend. That definition can be added to the one in this answer without conflict. The linked version simply has a third argument n corresponding to the number of discrete tick marks in the legend. This may sometimes be more convenient that the "ColorSwathes" option.

I finally got around to reading this answer. +1
–
Mr.Wizard♦Jul 1 '12 at 6:59

Very nice. I may have overlooked it, but would it be possible to specify more than two value labels next to the color scale, and perhaps using 'nice' values (FindDivisions may be useful)?
–
Sjoerd C. de VriesJul 1 '12 at 7:39

I took the plot, extracted the color values of the points, found the min and max of the colors and the value of the corresponding data point. I started with a grayscale plot to make it easier but I think it finds the correct range:

The question is a little unclear. I wouldn't try to report in the Legend; I'd report separately. I'm not sure what your source data looks like, but you can sort it and look for outlyers by hand or just pass PlotRange -> All as an option to the ListDensityPlot[] function.

Mathematica is a registered trademark of Wolfram Research, Inc. While the mark is used herein with the limited permission of Wolfram Research, Stack Exchange and this site disclaim all affiliation therewith.