Logarithmic scale in a DensityPlot and its legend
Giving the density plot a logarithmic scale must always involve - unless some future version of Mathematica includes it by default - overriding the ColorFunctionScaling of the original plotting command and supplying a custom scaling function. The simplest logarithmic scaling is of the form $$ \mathrm{scaling}(x)=\frac{\log(x/\mathrm{min})}{\log(\mathrm{max}/ \mathrm{min})}, $$ which is given the parameters $\rm min$ and $\rm max$, and maps them respectively to 0 and 1, which are the limits of the standard input range of any colour function. This scaling function is implemented as
LogarithmicScaling[x_, min_, max_] := Log[x/min]/Log[max/min]
To include this in the plotting command, one needs to set ColorFunctionScaling
to False
, and supply LogarithmicScaling
to some appropriate ColorFunction
, which looks like
plotter[min_, max_] := DensityPlot[Sinc[x]^2 Sinc[y]^2, {x, -20, 20}, {y, -20, 20}
, PlotPoints -> 100, PlotRange -> Full
, ColorFunctionScaling -> False
, ColorFunction -> (ColorData["DeepSeaColors"][LogarithmicScaling[#, min, max]] &)
]
plotter[0.00003, 1]
and produces
Getting the legend to work, on the other hand, is more tricky. The standard way to include a legend for a DensityPlot is to set some nontrivial option for PlotLegends
; as the examples on the DensityPlot
documentation show, the Automatic
setting usually does a good job. For a logarithmic scale, on the other hand, the resulting BarLegend
needs to be modified.
More specifically, the colour function provided to BarLegend
must vary linearly with the input it is given (which goes linearly from bottom to top of the scale), and it is the ticks themselves that must be rescaled. This requires the inverse of the $\rm scaling$ function, which is given by
$$
x=\mathrm{min} \left({\mathrm{max}}\over{ \mathrm{min}}\right)^{\mathrm{scaling}(x)}.
$$
The strategy is to find those values of $x$ for which $\rm{scaling}(x)$ is 0, 1, and a given number of values evenly spread between the two. Thus:
- We set
PlotLegends
toBarLegend
, - we give
BarLegend
the unadulterated colour function we gave to theDensityPlot
, - we tell
BarLegend
to take 0 and 1 as the minimum and maximum values to be fed to the colour function, to generate the full colour spectrum linearly, - we generate the list of positions of the ticks using
min (max/min)^(Range[0, 1, 1/NumberOfTicks])
, - we generate the colour-function-input they correspond to, and their labels, by mapping
{LogarithmicScaling[#, min, max], ScientificForm[#, 2]} &
over that list, - and we feed that to the
Ticks
option ofBarLegend
.
The resulting code looks like
plotter[min_, max_, NumberOfTicks_] := DensityPlot[Sinc[x]^2 Sinc[y]^2
, {x, -20, 20}, {y, -20, 20}
, PlotPoints -> 100, PlotRange -> Full
, ColorFunctionScaling -> False
, ColorFunction -> (ColorData["DeepSeaColors"][LogarithmicScaling[#, min, max]] &)
, PlotLegends -> BarLegend[{ColorData["DeepSeaColors"], {0, 1}}, LegendMarkerSize -> 370
, Ticks -> ({LogarithmicScaling[#, min, max], ScientificForm[#, 2]} & /@ (
min (max/min)^Range[0, 1, 1/NumberOfTicks]))]
]
plotter[0.00003, 1, 5]
and produces output like
The highlighter colours the Ticks
option inside BarLegend
in red, but it works just fine as far as I can tell. The error class this is assigned to (visible e.g. by changing the colour setting in Edit > Preferences > Appearance > Syntax Coloring > Errors and Warnings
) is "Unrecognized option names". I don't think this is particularly bad, but rather reflects the fact that the highlighter is not perfect, and should not really be expected to be.
Addendum: minor ticks.
While the above is perfectly fine, the ticks do not make it immediately clear that the scale is logarithmic in the way that appropriately placed minor ticks will do. To implement these, the best option is to take advantage of the built-in capability to make nice ticks in log scale plots.
The essential part of this is to extract, using AbsoluteOptions
, the Ticks
of an appropriate LogPlot
. Unfortunately, the linearized coordinates of the ticks are rather inconveniently placed, and have an arbitrary linear scale of their own. The code below is therefore rather long, but I've made it verbose so that hopefully it's clear what's going on.
LogScaleLegend[min_, max_, colorfunction_, height_: 400] := Module[
{bareTicksList, numberedTicks, m, M, ml, Ml, minInArbitraryScale,
maxInArbitraryScale, linearScaling},
bareTicksList =
First[Ticks /. AbsoluteOptions[LogLogPlot[x, {x, min, max}]]];
numberedTicks = (
Select[
bareTicksList /. {Superscript -> Power},
NumberQ[#[[2]]] &
]
)[[All, {1, 2}]];
m = Min[numberedTicks[[All, 2]]];
M = Max[numberedTicks[[All, 2]]];
ml = Min[numberedTicks[[All, 1]]];
Ml = Max[numberedTicks[[All, 1]]];
{minInArbitraryScale, maxInArbitraryScale} =
ml + (Ml - ml) Log[{min, max}/m]/Log[M/m];
linearScaling[x_] := (x - minInArbitraryScale)/(
maxInArbitraryScale - minInArbitraryScale);
DensityPlot[y
, {x, 0, 0.04}, {y, 0, 1}
, AspectRatio -> Automatic, PlotRangePadding -> 0
, ImageSize -> {Automatic, height}
, ColorFunction -> colorfunction
, FrameTicks -> {{None,
Select[
Table[{
linearScaling[r[[1]]],
r[[2]] /. {Superscript[10., n_] -> Superscript[10, n]},
{0, If[r[[2]] === "", 0.15, 0.3]},
{If[r[[2]] === "", Thickness[0.03], Thickness[0.06]]}
}, {r, bareTicksList}]
, (#[[1]] (1 - #[[1]]) >= 0 &)]
}, {None, None}}
]
]
With this function, then, the code
plotter[min_, max_, NumberOfTicks_] := DensityPlot[
Sinc[x]^2 Sinc[y]^2
, {x, -20, 20}, {y, -20, 20}
, PlotPoints -> 100
, PlotRange -> Full
, ColorFunctionScaling -> False
, ColorFunction -> (ColorData["DeepSeaColors"][
LogarithmicScaling[#, min, max]] &)
, PlotLegends ->
LogScaleLegend[min, max, ColorData["DeepSeaColors"], 350]
]
plotter[0.00003, 1, 5]
produces the output
With version 11, you can use the ScalingFunctions
in both DensityPlot
and BarLegend
sf = Log[#/0.00003]/Log[1/0.00003] &;
isf = InverseFunction[sf];
DensityPlot[Sinc[x]^2 Sinc[y]^2, {x, -20, 20}, {y, -20, 20},
PlotRange -> All, PlotPoints -> 100,
ScalingFunctions -> {sf, isf},
ColorFunction -> "DeepSeaColors",
PlotRange -> {0.00003, 1},
ColorFunctionScaling -> False,
PlotLegends -> BarLegend[{"DeepSeaColors", {0.00003, 1}},
ScalingFunctions -> {sf, isf}]]