5
$\begingroup$

Here's a very bizarre inconsistency I've just struggled with and I'm wondering why it exists or if I'm missing something.

I have some noisy data and I wish to make a framed plot of the data but allow the data to extend outside the vertical limits of the frame (for stylistic reasons). Like so:

    xs = Range[0, 0.5, 0.005];
    data = Transpose[{xs, Sin[Pi xs]^2 + 0.05 RandomReal[{-1, 1}, Length[xs]]}];

    opts = Sequence[PlotRange -> {{0, 0.5}, {0, 1}}, Frame -> True,
       PlotRangeClipping -> False, ImagePadding -> 20];

    ListLinePlot[{}, opts,
     Prolog -> First@ListLinePlot[data, PlotRange -> All]
    ]

enter image description here

However, my dataset contains points outside my desired x limits, thus my data is more accurately given by:

    xs = Range[-0.1, 0.6, 0.005];
    data = Transpose[{xs, Sin[Pi xs]^2 + 0.05 RandomReal[{-1, 1}, Length[xs]]}];

Which when plotted with exactly the same code gives:

enter image description here

which obviously extends in both the x and y directions when I only want the extension in the y.

My solution is to change the value of PlotRange -> All in the 'Prolog' 'ListLinePlot'. However, this only works in the y-direction, observe:

    Grid[{{
       ListLinePlot[{}, opts,
        Prolog -> First@ListLinePlot[data, PlotRange -> {All, {0, 1}}]
        ],
       ListLinePlot[{}, opts,
        Prolog -> First@ListLinePlot[data, PlotRange -> {{0, 1}, All}]
        ],
       ListLinePlot[{}, opts,
        Prolog -> First@ListLinePlot[data, PlotRange -> {{0, 1}, {0, 1}}]
        ]
       }}]

enter image description here

As you can see the data never obeys the PlotRange in the x direction! Looking into the content of the First@ListLinePlot[data, ...] we can see that the graphics items describing the data do get clipped in the y-direction:

enter image description here

You can see there are several instances near the beginning where the Line's y-coordinate has been clipped to 0. and many at the end where it is 1..

However if we try to restrict the graphics in the x-direction:

enter image description here

We see no such clipping occurs, leading to the problems described above.

Why is this happening? Is there an elegant workaround? My current method to circumvent this problem is to create and Interpolation of may data and then use Plot as opposed to ListLinePlot in the Prolog but this seems like overkill for what should be a simple fix?

I note that merely taking a subset of my data won't work for my real data as the x-values do not lie nicely on my coordinates, ie I might not have a value at 0 so I would be left with a gap either side.

Many thanks.

$\endgroup$
6
  • $\begingroup$ To allow the data to be plotted outside the frame. Unless I'm missing something I thought that (using Prolog etc) was one of the simplest ways to achieve that. ListLinePlot[data, opts] doesn't produce the first plot which is what I want. $\endgroup$ Jan 13, 2016 at 15:28
  • 2
    $\begingroup$ @Kuba Unfortunately PlotRangeClipping does not allow to specify separate clipping specifications for the axes and in addition ListLinePlot handles this option for them inconsistently. :( $\endgroup$ Jan 13, 2016 at 16:59
  • $\begingroup$ I suggest trying RegionFunction to clip the data on the x axis. No time for a full answer, I'm afraid. $\endgroup$
    – LLlAMnYP
    Jan 13, 2016 at 21:12
  • $\begingroup$ @LLlAMnYP Good idea! RegionFunction is a documented option of ListLinePlot (see under the "Details" field). But this option is not recognized and does not work (version 10.3.1)... :( $\endgroup$ Jan 13, 2016 at 23:23
  • $\begingroup$ @AlexeyPopkov yep, I was only refering to the usage of Prolog, not the problem of OP. $\endgroup$
    – Kuba
    Jan 14, 2016 at 9:32

4 Answers 4

4
$\begingroup$

I, too, could not find a simple, transparent solution, so here is a simple but not transparent solution.

ListLinePlot[Reverse[data, 2], PlotRange -> {All, {0, 0.5}}];
Reverse[Cases[%, Line[{z__}] -> z, Infinity], 2];
ListLinePlot[{}, opts, Prolog -> First@ListLinePlot[%, PlotRange -> All]]

enter image description here

Because ListLinePlot appears to treat PlotRange differently for the two coordinates, plot the data with the coordinates reversed, extract the Line, reverse the coordinates again, and plot the result.

Addendum

A more stringent test is to use data for which no points lie precisely at the edges of the plot in x. For instance, with xs = Range[-0.15, 0.6, 0.1];, the plot still works properly. (A coarse set of data is used for visual clarity.)

enter image description here

Approaches that, directly or indirectly, simply delete data points outside PlotRange would have blank space at either end of the plot.

$\endgroup$
1
  • $\begingroup$ +1 for simple but not transparent solution $\endgroup$
    – Sascha
    Jan 14, 2016 at 11:44
1
$\begingroup$

It is another gedanken functionality in basic plotting functions. Using new in version 10 Region* functionality here is a workaround:

xs = Range[0, 1, 0.005];
data = Transpose[{xs, Sin[Pi xs]^2 + 0.05 RandomReal[{-1, 1}, Length[xs]]}];

inf = 10;
RegionPlot[RegionIntersection[Line[data], Rectangle[{0, -inf}, {0.5, inf}]], 
 PlotRange -> {{0, 0.5}, {0, 1}}, PlotRangeClipping -> False, ImagePadding -> 20]

enter image description here

With older Mathematica versions you could proceed in the following way:

Show[Plot[Interpolation[data, InterpolationOrder -> 1][x], {x, 0, .5}, 
  PlotRange -> {{0, 0.5}, All}], PlotRange -> {{0, 0.5}, {0, 1}}, 
 PlotRangeClipping -> False, ImagePadding -> 20, Frame -> True, PlotRangePadding -> None]
$\endgroup$
1
$\begingroup$

Here's some fake data:

pts = RandomReal[{-.2, .2}, 37] + (Cos[.1 #] & /@ Range[-1, 35]) // Thread[{Range[-1, 35], #}] &

Make a plot without the frame that clips the data in the x direction but not in the y direction (by having an extended y range):

plot = ListPlot[pts, InterpolationOrder -> 3, Joined -> True, 
  Frame -> False, Axes -> False, PlotRangePadding -> None, 
  ImagePadding -> None, PlotRange -> {{2, 32}, {-1.2, 1.2}}, 
  AspectRatio -> Full]

enter image description here

Apparently AspectRatio -> Full is very important here, but I realized that with trial and error and am too lazy to investigate further. ListPlot with Joined -> True seems to behave nicer with Inset and gives the same result.

Make an empty framed plot with the correct frame range (defined by PlotRange) and add the line as an inset:

Plot[{}, {x, 2, 32}, Frame -> True, PlotRange -> {{2, 32}, {-1, 1}}, 
 PlotRangeClipping -> False, 
 Epilog -> Inset[plot, {2, 0}, {2, 0}, {30, 2.4}]]

enter image description here

Here's a functional form:

xClipListPlot[pts_List, {{x1_, x2_}, {y1_, y2_}}, 
  yRange_List: Automatic] := 
 Module[{x, 
   yR = If[yRange == 
      Automatic, {(y1 + y2)/2 - (y1 - y2)/0.4, (y1 + y2)/2 + (
       y1 - y2)/0.4}, yRange], plot},
  plot = ListPlot[pts, InterpolationOrder -> 3, Joined -> True, 
    Frame -> False, Axes -> False, PlotRangePadding -> None, 
    ImagePadding -> None, PlotRange -> {{x1, x2}, yR}, 
    AspectRatio -> Full];
  Plot[{}, {x, x1, x2}, Frame -> True, 
   PlotRange -> {{x1, x2}, {y1, y2}}, PlotRangeClipping -> False, 
   Epilog -> 
    Inset[plot, {0, 0}, {0, 0}, {x2 - x1, First@Differences[yR]}]]]
$\endgroup$
0
$\begingroup$

When using PlotRange -> s you are only specifying the range on the y-axis. Look at the Details section of the PlotRange documentation. This is why only the y-axis range is being truncated.

You can use the format that specifies the range for both the x and y axis but this will make the frame cover those ranges; and you don't want that. You can, however, use Show with an empty ListLinePlot of your desired frame range and a second plot that takes only the points in the x-axis range. Then make use of PlotRangeClipping and ImagePadding in Show to get your desired result.

xs = Range[-0.1, 0.6, 0.005];
data = Transpose[{xs, Sin[Pi xs]^2 + 0.05 RandomReal[{-1, 1}, Length[xs]]}];

Show[{
  ListLinePlot[{}, PlotRange -> {{0, .5}, {0, 1}}, Frame -> True],
  ListLinePlot[Select[Between[First@#, {0, .5}] &]@data,  Axes -> None]},
 PlotRangeClipping -> False, 
 ImagePadding -> {{All, All}, {Scaled[.03], Scaled[.03]}}]

enter image description here

Hope this helps.

PS:- You can extend this by making the Scaled parameter a function of the data y-range above the y-axis max and the range and the y-axis. This would automate the padding so you wouldn't have to eyeball it each time. Similarly for the data y-range below the y-axis min.

$\endgroup$
1
  • 1
    $\begingroup$ With Select you don't interpolate the data at the edges as it happens with the default ListLinePlot (but only at the vertical edges unfortunately, see the question). $\endgroup$ Jan 13, 2016 at 20:57

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.