Matplotlib fill_between edge effect with `where` argument

六眼飞鱼酱① 提交于 2021-02-08 10:30:45

问题


I want to plot the region between two curves with different colors whether one of the line is positive or negative. I got edge effect with non-continuous coloring of the region when the curve changes of sign. Setting interpolate=True does not really help. The edge effect is related to the resolution (voluntarily coarse in the basic example below) - enhancing it is not really what I want. Any better solution to make smooth transitions? Thanks.

import matplotlib.pyplot as plt
import numpy

plt.figure()
x=numpy.arange(0.,1.05,0.05)
y1=numpy.sin(2*numpy.pi*x)
y2=y1+0.2
y1positive=y1>0
y1negative=y1<=0
plt.plot(x,y1,'r',label='y1')
plt.plot(x,y2,'g',label='y2')
plt.plot(x,x*0,'--k')
plt.fill_between(x,y2,y1,where=y1positive,color='green',alpha=0.5)
plt.fill_between(x,y2,y1,where=y1negative,color='red',alpha=0.5,interpolate=True)
plt.legend()

enter image description here http://i.stack.imgur.com/9Q9Ff.png

** EDIT ** based on pyHazard's answer which is properly addressing the problem above, but I still encounter difficulties:

Revised case (see code) - I need to fill the area between two summed curves if they have the same sign, and between one of them and zero otherwize. The filled area must be continuous when the where= condition changes. Including a tiny margin indeed helps, but does not totally resolve the problem (filled surface still interrupted). What is needed there is really a fill_between where y1=0, which is a condition with no width... I would then just need a kind of condition where y1-eta<=0<=y1+eta but I am foolishly blocked there now. Any idea to make the filled area totally continuous? Thanks!

plt.figure()
x=numpy.arange(0.,3.05,0.05)
y1=numpy.sin(2*numpy.pi*x)
y2=[2.]*len(y1)
y3=[-2.]*len(y1)

eta=1e-6
y1positive=y1+eta>=0
y1negative=y1-eta<=0

plt.plot(x,x*0,'--k')
plt.plot(x,y1,'.-k',label='y1')
plt.plot(x,y2,'.-g',label='y2')
plt.plot(x,y3,'.-r',label='y3')

plt.fill_between(x,y2+y1,y1,where=y1positive,color='green',alpha=0.5,linewidth=0)
plt.fill_between(x,y2,0,where=y1negative,color='green',alpha=0.5,linewidth=0)
plt.fill_between(x,y3+y1,y1,where=y1negative,color='red',alpha=0.5,linewidth=0) 
plt.fill_between(x,y3,0,where=y1positive,color='red',alpha=0.5,linewidth=0) 

plt.legend()

fill_between_2

To make it clearer, here is the original plot (with eta=0. in the code above). Everything is fine there, except those vertical interruptions in the green and red areas. The blank areas between the sine curve and zero are fine. The problematic vertical interruptions come from the definition of the filled areas: between the sine and the horizontal curve if they have the same sign, or between the horizontal curve and zero if the sine and the horizontal curve have opposite signs. So there is a switch in the conditions for filled areas, the blank vertical zones are the undesirable edge effects...

fill_between_3

* FINAL EDIT * [with solution]

Because I cannot find a solution within fill_between, one solution (based on pyHazard's answer) is to recalculate the upper and lower limit of the filled areas to make sure to have them continuous (no more swith in fill_between condition).

plt.figure()
x=numpy.arange(0.,3.05,0.05)
y1=numpy.sin(2*numpy.pi*x)
y2=[2.]*len(y1)
y3=[-2.]*len(y1)

y1positive=y1>=0
y1negative=y1<=0

plt.plot(x,x*0,'--k')
plt.plot(x,y1,'.-k',label='y1')
plt.plot(x,y2,'.-g',label='y2')
plt.plot(x,y3,'.-r',label='y3')

#Solution: recalculate the upper and lower limit of filled areas
#  to have each of them as one continuous line
y0=[0.]*len(y1)
y1pos=numpy.amax(numpy.vstack((y1,y0)),axis=0.)
y1neg=numpy.amin(numpy.vstack((y1,y0)),axis=0.)
y21=y2+y1pos
y31=y3+y1neg

plt.fill_between(x,y21,y1pos,color='green',alpha=0.5,linewidth=0)
plt.fill_between(x,y31,y1neg,color='red',alpha=0.5,linewidth=0) 

plt.legend()

fill_between_4


回答1:


As long as you are only concerned with one line or the other I suggest looking at your comparison. The following produces a more pleasing plot. (Note the plotting style was changed to make the data points clear.)

If you had intended all of the AREA on the negative side to be red and positive side green you will need to add additional data-points to the series that that lie on the axis.

import matplotlib.pyplot as plt
import numpy

plt.figure()
x=numpy.arange(0.,1.05,0.05)
y1=numpy.sin(2*numpy.pi*x)
y2=y1+0.2

eta=1e-6
y1positive=(y1+eta)>=0
y1negative=(y1-eta)<=0

plt.plot(x,y1,'-r',label='y1')
plt.plot(x,y2,'-g',label='y2')
plt.plot(x,x*0,'--k')
plt.fill_between(x,y2,y1,where=y1positive,color='green',alpha=0.5,interpolate=False)
plt.fill_between(x,y2,y1,where=y1negative,color='red',alpha=0.5,interpolate=False)
plt.legend()
plt.show()

enter image description here

** EDIT ** I cannot yet comment so I'm adding this to an edit. I'm new to the forum scene so I'm not sure if this is considered proper etiquette.

I'm not sure I understand the revised situation. Are you simply trying to fill the white areas in with red and green? If so you probably don't need the extra conditionals and just need to to the fill between in the simplest way as shown below.

plt.figure()
x=numpy.arange(0.,3.05,0.05)
y1=numpy.sin(2*numpy.pi*x)
y2=[ 2.]*len(y1)
y3=[-2.]*len(y1)

y21 = numpy.amax(numpy.vstack((y2,y2+y1)), axis=0)
y31 = numpy.amin(numpy.vstack((y3,y3+y1)), axis=0)

plt.plot(x,x*0,'--k')
plt.fill_between(x,y21,y1,color='green',alpha=0.5,linewidth=0)
plt.fill_between(x,y31,y1,color='red',alpha=0.5,linewidth=0) 

plt.show()

enter image description here



来源:https://stackoverflow.com/questions/19214274/matplotlib-fill-between-edge-effect-with-where-argument

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!