Skip to content

fill_between() misbehaves with logarithmic X axis #7371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zevweiss opened this issue Nov 1, 2016 · 14 comments
Closed

fill_between() misbehaves with logarithmic X axis #7371

zevweiss opened this issue Nov 1, 2016 · 14 comments
Labels
status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action topic: transforms and scales

Comments

@zevweiss
Copy link

zevweiss commented Nov 1, 2016

I'm seeing fill_between() drawing polygons incorrectly on a logarithmic X axis.

import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import sys

plt.xscale('log')

xs = np.array([10, 100])
ys = np.array([10, -25])
zeros = np.array([0, 0])

plt.plot(xs, ys)

plt.fill_between(xs, zeros, ys, where=ys > zeros, facecolor='green', interpolate=True)
plt.fill_between(xs, zeros, ys, where=ys < zeros, facecolor='red', interpolate=True)

plt.savefig(sys.argv[1])

If I comment out the plt.xscale('log') line everything comes out as expected:

lin

But with the log axis the fill_between() polygons don't align with the plt.plot(xs, ys) line:

log

(This is with matplotlib 1.5.3, installed via pip.)

@efiring
Copy link
Member

efiring commented Nov 1, 2016

Fill_between is using linear interpolation to find the zero-crossing. To get the behavior you are expecting it would need to transform from data to screen space (or any equivalent coordinate system), interpolate, and then transform back to data space to get the zero-crossing. Normally with fill_between this doesn't make much difference because the curves have enough points in x that the interpolation is over a small interval.

@zevweiss
Copy link
Author

zevweiss commented Nov 1, 2016

Thanks for the prompt response. I may be missing something obvious, but is there any reason why interpolated zero-crossings are "special"? I notice it seems to interpolate non-zero-crossing segments fine, for what it's worth:

more

And since I don't have any sense of it -- is what you described a feasible solution that could be incorporated fairly easily, or would that be overly complex/computationally-expensive?

@efiring
Copy link
Member

efiring commented Nov 1, 2016

Yes, the zero-crossings are special because they require inserting a data point in the array. That point is needed to define the polygon being filled. It's the start or end point of the line segment along the axis.
It looks like it would not be very difficult to handle the case where the y-axis is linear and the x-axis is one of the standard named scales (log, symlog, or logit). The axis has a _scale attribute, which has a get_transform method. The transform has methods for forward and inverse transformations. The transformations of x-coordinates would be done inside the get_interp_point function that is defined inside the Axes.fill_between, using transform_non_affine before the interpolation and inverted afterwards.

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Nov 1, 2016
@tacaswell tacaswell added Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues and removed Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues labels Nov 1, 2016
@tacaswell tacaswell removed this from the 2.1 (next point release) milestone Nov 1, 2016
@tacaswell
Copy link
Member

I think the problem here is actually with plot and fill_between is behaving correctly.

import numpy as np
import matplotlib

import matplotlib.pyplot as plt


plt.xscale('log')

xs = np.array([10, 100])
ys = np.array([10, -25])
zeros = np.array([0, 0])

x_lots = np.linspace(10, 100, 512)
y_lots = np.linspace(10, -25, 512)
plt.plot(x_lots, y_lots)

plt.fill_between(xs, zeros, ys, where=ys > zeros, facecolor='green', interpolate=True)
plt.fill_between(xs, zeros, ys, where=ys < zeros, facecolor='red', interpolate=True)
plt.show()

so

The problem is that there are enough points in your data and the plot is giving you an incorrect estimate of where the zero crossing is.

@efiring
Copy link
Member

efiring commented Nov 1, 2016

@tacaswell, that was also my first reaction, but that is looking at the problem with the assumption that a line between two points on a log scale should be a log curve. This is not what we want, however, as a plotting library. We want to preserve the ability to make a straight line between any two points. Consider a spectrum on a log-log plot. If the theory says the PSD should go as the inverse second power of the frequency, you want to be able to plot a straight-line segment with a slope of -2.

Fill_between is a bit different because it is inserting a data point--or a point that is most likely to be interpreted visually as a data point. It's debatable, but I came around to the idea that is should indeed do that interpolation linearly in data space, not in screen space as it does now. Yes, it would also make sense for the user in such an instance to make sure that the data points are closely spaced, and we could respond to this by leaving out the complexity of the transform-interpolate-inverse transform sequence. I'm close to neutral on this.

@tacaswell
Copy link
Member

I am confused, I think fill_between does do the interpolation in data-space currently and the OP here is asking for it to be done in screen space (is there a comma missing?).

Both of these methods behave better if you up the density:

import numpy as np
import matplotlib

import matplotlib.pyplot as plt


plt.xscale('log')

xs = np.array([10, 100])
ys = np.array([10, -25])


x_lots = np.linspace(10, 100, 512)
y_lots = np.linspace(10, -25, 512)
plt.plot(x_lots, y_lots)

plt.fill_between(xs, zeros, ys, where=ys > zeros, facecolor='green', interpolate=True, alpha=.5)
plt.fill_between(xs, zeros, ys, where=ys < zeros, facecolor='red', interpolate=True, alpha=.5)

plt.fill_between(x_lots, 0, y_lots, where=y_lots > 0, facecolor='green', interpolate=True, alpha=.5)
plt.fill_between(x_lots, 0, y_lots, where=y_lots < 0, facecolor='red', interpolate=True, alpha=.5)

so

I am pretty strongly 👎 on doing the zero-crossing logic in screen space.

I think it also makes sense for us to continue to connect point in screen-space with strait lines independent of the underlying transform, anything else will lead to excessive complication (for now).

@WeatherGod
Copy link
Member

don't we have a transform_curve() method in the transform stack? Consider
the case of drawing a curve in polar space, I am pretty sure that draws the
transformed line. It was all part of the bezier curve feature added by Mike
way back when, IIRC.

On Tue, Nov 1, 2016 at 4:02 PM, Thomas A Caswell notifications@github.com
wrote:

I am confused, I think fill_between does do the interpolation in
data-space currently and the OP here is asking for it to be done in screen
space (is there a comma missing?).

Both of these methods behave better if you up the density:

import numpy as npimport matplotlib
import matplotlib.pyplot as plt

plt.xscale('log')

xs = np.array([10, 100])
ys = np.array([10, -25])

x_lots = np.linspace(10, 100, 512)
y_lots = np.linspace(10, -25, 512)
plt.plot(x_lots, y_lots)

plt.fill_between(xs, zeros, ys, where=ys > zeros, facecolor='green', interpolate=True, alpha=.5)
plt.fill_between(xs, zeros, ys, where=ys < zeros, facecolor='red', interpolate=True, alpha=.5)

plt.fill_between(x_lots, 0, y_lots, where=y_lots > 0, facecolor='green', interpolate=True, alpha=.5)
plt.fill_between(x_lots, 0, y_lots, where=y_lots < 0, facecolor='red', interpolate=True, alpha=.5)

[image: so]
https://cloud.githubusercontent.com/assets/199813/19905103/b32fabe8-a04b-11e6-9c00-06a8ac6d9643.png

I am pretty strongly 👎 on doing the zero-crossing logic in screen space.

I think it also makes sense for us to continue to connect point in
screen-space with strait lines independent of the underlying transform,
anything else will lead to excessive complication (for now).


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#7371 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AARy-CG4psQafnwoDxjFLvoi7VTV7nHBks5q55rSgaJpZM4KlzIT
.

@efiring
Copy link
Member

efiring commented Nov 1, 2016

On 2016/11/01 10:26 AM, Benjamin Root wrote:

don't we have a transform_curve() method in the transform stack? Consider
the case of drawing a curve in polar space, I am pretty sure that draws the
transformed line. It was all part of the bezier curve feature added by Mike
way back when, IIRC.

Yes, that occurred to me also--but adding in that sort of functionality
as a general option is a big change.

@efiring
Copy link
Member

efiring commented Nov 1, 2016

On 2016/11/01 10:02 AM, Thomas A Caswell wrote:

I am confused, I think |fill_between| does do the interpolation in
data-space currently and the OP here is asking for it to be done in
screen space (is there a comma missing?).

Yes, that is what the OP is expecting, and the behavior for which I was
trying to describe an algorithm. I might have described something
backwards in an earlier comment.

@efiring
Copy link
Member

efiring commented Nov 6, 2016

@tacaswell, I think your statements,
"I am pretty strongly 👎 on doing the zero-crossing logic in screen space."
and
"I think it also makes sense for us to continue to connect point in screen-space with strait lines independent of the underlying transform, anything else will lead to excessive complication (for now)."
are inconsistent. What the OP is requesting is exactly the latter. We are inserting a point in the fill_between case only because we need it in order to make the polygons to be filled. The line should still be straight between the two data points that the user has supplied. In other words, we should not be trying to generate a data point via linear interpolation in data space; we should be finding the zero-crossing in screen space, and transforming it to data coordinates only because we need them for the polygons.

@github-actions
Copy link

github-actions bot commented Apr 1, 2023

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Apr 1, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 1, 2023
@QuLogic
Copy link
Member

QuLogic commented May 2, 2023

This seems to still be broken unfortunately.

@QuLogic QuLogic reopened this May 2, 2023
@QuLogic QuLogic added topic: transforms and scales and removed status: inactive Marked by the “Stale” Github Action labels May 2, 2023
@timhoffm
Copy link
Member

timhoffm commented May 2, 2023

I suppose, interpolation should be done in screen space

plt.fill_between(xs, zeros, ys, where=ys > zeros, facecolor='green', interpolate=True)
plt.fill_between(xs, zeros, ys, where=ys < zeros, facecolor='red', interpolate=True)

should cover the same areas as

plt.fill_between(xs, zeros, ys, facecolor='green', interpolate=True)

Note: The interpolation is done on execution of fill_between(). This means the result will depend on whether we set the log scale before or after the command. But IMHO that's something we can live with.

The relevant code section is
https://github.com/matplotlib/matplotlib/blob/8166596ff7f6d1797d97b8b50e8b41d8e7fe373c/lib/matplotlib/axes/_axes.py#LL5399-L5421

Copy link

github-actions bot commented May 3, 2024

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label May 3, 2024
@github-actions github-actions bot added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label Jun 3, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action topic: transforms and scales
Projects
None yet
Development

No branches or pull requests

6 participants