3

I want to do something similar to 1 Plot, 2 Scale/Axis but for the X-axis.

The aim is to have physical units below, but array indices at the top for easy access to the discrete data range.

So far I tried:

XY = Table[{i, Sin[i]}, {i, -10, 10, 0.5}];

DoubleXAxisPlotExample[XY : {{_?NumberQ, _?NumberQ} ..}] :=
  Overlay[{
    ListLinePlot[XY,
     ImagePadding -> 25,
     Frame -> True,
     FrameTicks -> {{All, None}, {All, None}},
     PlotRange -> All],
    ListLinePlot[Transpose[{Range[Length[XY]], Part[XY, All, 2]}],
     ImagePadding -> 25,
     Frame -> True,
     FrameTicks -> {{All, None}, {None, All}},
     PlotRange -> All]
    }];

DoubleXAxisPlotExample@XY returns:

enter image description here

However, with:

XY = Table[{i, 100000*Sin[i]}, {i, -10, 10, 0.5}];

DoubleXAxisPlotExample@XY returns:

enter image description here

-> the left ticks have been truncated

I understand that ImagePadding -> 25 is there to let enough arbitrary space on the left, however in my code this is a fixed absolute value. To solve the problem, I think that this value should be computed according to the actual plot Y-range, but I do not know how to do that.

Any idea?

Picaud Vincent
  • 2,463
  • 13
  • 20

2 Answers2

4

You can use my function GraphicsInformation to obtain this information (I think GraphicsInformation is more reliable than Image + BorderDimensions). Install the paclet/function:

PacletInstall[
    "GraphicsInformation",
    "Site"->"http://raw.githubusercontent.com/carlwoll/GraphicsInformation/master"
];

Then, load it:

<<GraphicsInformation`

For your example:

plots = {
    ListLinePlot[
        XY,
        Frame -> True,
        FrameTicks -> {{All, None}, {All, None}},
        PlotRange -> All
    ],
    ListLinePlot[Transpose[{Range[Length[XY]], Part[XY, All, 2]}],
        Frame -> True,
        FrameTicks -> {{All, None}, {None, All}},
        PlotRange -> All
    ]
};
padding = "ImagePadding" /. GraphicsInformation[plots]

{{{44., 1.5}, {17., 0.5}}, {{44., 1.5}, {1.5, 16.}}}

This gives the actual ImagePadding used in the two plots. You can use MapThread to come up with the total ImagePadding that is needed:

MapThread[Max, padding, 2]

{{44., 1.5}, {17., 16.}}

I will leave it to you to include GraphicsInformation in your DoubleXAxisPlotExample function.

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
1

One can automate the image padding with a hack described here and built upon here.

Define two padding functions

padding[g_Graphics] := With[{im = Image[Show[g, LabelStyle -> White, Background -> White]]}, BorderDimensions[im]]
maxPadding[graphicsSequence__Graphics] := 1 + {Max /@ Transpose@(First /@ #), Max /@ Transpose@(Last /@ #)} &@(padding /@ List@graphicsSequence)

Then,

DoubleXAxisPlotExample[XY : {{_?NumberQ, _?NumberQ} ..}] := Module[{plt1, plt2, pad},
  plt1 = ListLinePlot[XY,
    PlotRange -> All,
    Frame -> True, FrameTicks -> {{All, None}, {All, None}}];
  plt2 = ListLinePlot[Transpose[{Range[Length[XY]], Part[XY, All, 2]}],
    PlotRange -> All,
    Frame -> True, FrameTicks -> {{All, None}, {None, All}}];
  pad = maxPadding[plt1, plt2];
  plt = Overlay[{
     Show[plt1, ImagePadding -> pad],
     Show[plt2, ImagePadding -> pad]
     }]
 ]

Then:

XY = Table[{i, 100000*Sin[i]}, {i, -10, 10, 0.5}];
DoubleXAxisPlotExample@XY

enter image description here

march
  • 23,399
  • 2
  • 44
  • 100