5
$\begingroup$

I am looking for a way to plot e.g. a legended DensityPlot with a BarLegend, where the colorbar shows two differently scaled ticks and labels. Take the following example:

enter image description here

or take a look at the folloowing example from Python/matplotlib: https://stackoverflow.com/questions/27151098/draw-colorbar-with-twin-scales.

Idealy I would like to provide a scaling relation to to apply to the data values in order to get the second scale.

I was hoping that there is already some hidden functionality in place, But I couldn't coerce "Charting'TickSide" to allow for other arguments than Left or Right. After some fiddling around I could at least convince Mathematica to plot two BarLegends and hacked Column for a partial overlap (the -2 spacing argument). I also tried to fiddle around with the InputForm but the BarLegend is not converted to Graphics primitives. But that is as far as I got.

Legended[
 MatrixPlot[
   Table[i*j, {i, 1, 10}, {j, 1, 10}],
   ColorFunction -> (ColorData["SolarColors"][Rescale[#, {1, 100}]] &),
   ColorFunctionScaling -> False], 
 Placed[
   Column[{
     BarLegend[{"SolarColors", {1, 100}}, 
       LegendMarkerSize -> 300, 
       Charting`TickSide -> Left, 
       LegendLabel -> "\[Degree]F", 
       LegendLayout -> "Row"],
     BarLegend[{"SolarColors", {1, 100}}, 
       LegendMarkerSize -> 300, 
       Charting`TickSide -> Right, 
       LegendLabel -> Placed["\[Degree]C", Below], 
       LegendLayout -> "Row"]}, 
   Automatic, -2], 
   Below]
] 

enter image description here

Interesting remark: the quotation marks aropund the °C in the lower LegendLabel ("°C") only appear in the jpg, not on the screen in the notebook ouotput.

$\endgroup$
  • 1
    $\begingroup$ Before I dig into this let me ask: what is the reason that you do not draw the legend entirely manually? I ask because I would not want to work on a solution only to find that it did not avoid the same thing. $\endgroup$ – Mr.Wizard Jan 27 '16 at 21:29
  • $\begingroup$ @Mr.Wizard I was hoping that the 'new' PlotLegends functionality would allow for some easy implementation (given the huge number of undocumented Charting`* functions. I also will need to combine this plot with others I already did using PlotLegends and I would like to keep the same style. $\endgroup$ – Markus Roellig Jan 27 '16 at 22:20
3
$\begingroup$

Here's a functional solution to make a general 2-scale legend.

Options[twoScaleLegend] = {"LegendOrientation" -> "Horizontal"};
twoScaleLegend[{xmin_, xmax_}, {label1_, label2_}, scale2_, 
  opts : OptionsPattern[{twoScaleLegend, DensityPlot}]] :=

 Module[{ticks, aspectratio, framelabel, ticklength},
  ticklength = 
   If[OptionValue["LegendOrientation"] === "Vertical", .2, .01];
  framelabel = {{None, None}, {label1, label2}};
  If[OptionValue["LegendOrientation"] === "Vertical", 
   framelabel = Reverse@framelabel];
  aspectratio = 
   If[OptionValue["LegendOrientation"] === "Vertical", 20, 1/20];
  ticks = {{None, None},
    {Function[{min, max}, {#, N@#, ticklength {1, 1}} & /@ 
       FindDivisions[{min, max}, 5] ],
     Function[{min, 
       max}, {InverseFunction[scale2]@#, N@#, ticklength {1, 1}} & /@ 
       FindDivisions[scale2 /@ {min, max}, 5] ]}};
  If[OptionValue["LegendOrientation"] === "Vertical", 
   ticks = Reverse@ticks];
  DensityPlot[
   If[OptionValue["LegendOrientation"] === "Vertical", y, x],
   {x, xmin, xmax}, {y, xmin, xmax},
   Evaluate[FilterRules[{opts}, Options[DensityPlot]]],
   AspectRatio -> aspectratio,
   PlotRangePadding -> None,
   FrameTicks -> ticks,
   FrameLabel -> framelabel,
   RotateLabel -> False]
  ]

It's a little bit clunky, I couldn't figure out how to get the tick mark length to be the same in either vertical or horizontal orientation. Here are two examples of how it would be used,

twoScaleLegend[{0, 100}, {"°C", "°F"}, (1.8 # + 32 &),
  ColorFunction -> "SolarColors"]
twoScaleLegend[{0, 100}, {"°C", "°F"}, (1.8 # + 32 &),
  "LegendOrientation" -> "Vertical", BaseStyle -> 15]

enter image description here

And here it is with your matrix plot,

{Legended[
  MatrixPlot[Table[i*j, {i, 1, 10}, {j, 1, 10}], 
   ColorFunction -> (ColorData["SolarColors"][
       Rescale[#, {1, 100}]] &), ColorFunctionScaling -> False, 
   ImageSize -> 300],
  Placed[
   twoScaleLegend[{0, 100}, {"°C", 
     "°F"}, (1.8 # + 32 &), ColorFunction -> "SolarColors"]
   , Below]],
 Legended[
  MatrixPlot[Table[i*j, {i, 1, 10}, {j, 1, 10}], 
   ColorFunction -> (ColorData["SolarColors"][
       Rescale[#, {1, 100}]] &), ColorFunctionScaling -> False, 
   ImageSize -> 300],
  Placed[
   twoScaleLegend[{0, 100}, {"°C", 
     "°F"}, (1.8 # + 32 &), ColorFunction -> "SolarColors", 
    "LegendOrientation" -> "Vertical"]
   , Right]]}

enter image description here

$\endgroup$
  • $\begingroup$ That's neat. Thank you! $\endgroup$ – Markus Roellig Feb 1 '16 at 19:30
2
$\begingroup$

I found a solution that is not general but working for me at the moment. Since I was looking for a simple linear scaling I just applied it to the range argument 1.8*{1, 100} + 32 in BarLegend

Legended[
 MatrixPlot[Table[i*j, {i, 1, 10}, {j, 1, 10}], 
   ColorFunction -> (ColorData["SolarColors"][Rescale[#, {1, 100}]] &),
   ColorFunctionScaling -> False], 
   Placed[
     Column[{
       BarLegend[{"SolarColors", 1.8*{1, 100} + 32}, 
         LegendMarkerSize -> 300, Charting`TickSide -> Left, 
         LegendLabel -> "\[Degree]F", LegendLayout -> "Row"],
       BarLegend[{"SolarColors", {1, 100}}, 
         LegendMarkerSize -> 300, Charting`TickSide -> Right, 
         LegendLabel -> Placed["\[Degree]C", Below], 
         LegendLayout -> "Row"]
             }, Automatic, -2], 
     Below]]

enter image description here

The length of the two BarLegends does not match because of the placement of the ticks. One quick hack is to provide other limits, e.g. {1,99}

Legended[
 MatrixPlot[Table[i*j, {i, 1, 10}, {j, 1, 10}], 
   ColorFunction -> (ColorData["SolarColors"][Rescale[#, {1, 99}]] &),
   ColorFunctionScaling -> False], 
   Placed[
     Column[{
       BarLegend[{"SolarColors", 1.8*{1, 99} + 32}, 
         LegendMarkerSize -> 300, Charting`TickSide -> Left, 
         LegendLabel -> "\[Degree]F", LegendLayout -> "Row"],
       BarLegend[{"SolarColors", {1, 99}}, 
         LegendMarkerSize -> 300, Charting`TickSide -> Right, 
         LegendLabel -> Placed["\[Degree]C", Below], 
         LegendLayout -> "Row"]
             }, Automatic, -2], 
     Below]]

enter image description here

I am still interested in a more general and less ugly solution.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.