1

I am trying to draw a horizontal ray instead of horizontal line. Matplotlib has axhline() function. But it starts drawing from at the beginning of the chart. I am setting points at the high and low of the bar as you can see in the attached picture.

Is there a way to draw a line on a specific point?

I have tried fillstyle="right" but i think this is not something i am looking for.

Horizontal ray

Don Coder
  • 526
  • 5
  • 24

2 Answers2

-1

Yes, one can draw horizontal rays using plt.axhline. For example, the following code produces a box-plot with a horizontal ray:

import matplotlib.pyplot as plt
import numpy as np

mu, sigma = 50, 25

value1 = 1.5*mu + sigma*np.random.randn(100)
value2 = mu + 1.5*sigma*np.random.randn(100)
value3 = 0.5*mu + sigma*np.random.randn(100)
value4 = mu + 0.5*sigma*np.random.randn(100)

box_plot_data=[value1,value2,value3,value4]
plt.boxplot(box_plot_data)
plt.axhline(y=90, xmin=0.380, xmax=1)
plt.show()

Boxplot with ray

The data is random and the colors are default, but the idea is the same. The xrange on a default boxplot is [0, 1], with separated into equally spaced regions for each box. Each box is centered in its region, so the four boxes are centered at 0.125, 0.375, .625, and 0.875.

To draw a horizonal line from the second boxplot to the right, one gives the xmin value to be approximately 0.375. I say approximately because matplotlib will slightly overextend based on the linewidth, so it's usually a good idea to add 0.005 (or a number depending on the linewidth you use in your axhline). This is why I actually used xmin=0.380 instead of xmin=0.375.

davidlowryduda
  • 2,404
  • 1
  • 25
  • 29
  • Hi. My xrange is 70. y is 1.168 and the point is at 59. I don't know how you calculated these values. – Don Coder Jul 25 '18 at 15:32
  • @DonCoder If your point is at 59, and the range is 70, then you should start your xline at approximately 59/70. I typically expect (59/70 + 0.005) to account for the width of the line. If you give your actual code, it's easier to fill in the differences. – davidlowryduda Jul 26 '18 at 11:39
-2

I guess the easiest is to plot a line from (n,y0) to (xend,y0), where xend is the coordinate of the right edge of the axes. One may find out e.g. via

xstart, xend = ax.get_xlim()

after having plotted the boxplot. n would be the boxplot number and y0 the y coordinate where to plot the line (I suppose the latter is known?) For n=2 and y0=90, plotting the line is straight forward,

ax.plot([2,xend],[90,90])

Complete example (taking over the boxplot case from @davidlowryduda's answer):

import numpy as np
import matplotlib.pyplot as plt

mu, sigma = 50, 25

value1 = 1.5*mu + sigma*np.random.randn(100)
value2 = mu + 1.5*sigma*np.random.randn(100)
value3 = 0.5*mu + sigma*np.random.randn(100)
value4 = mu + 0.5*sigma*np.random.randn(100)

box_plot_data=[value1,value2,value3,value4]

fig,ax = plt.subplots()
ax.boxplot(box_plot_data)

xstart, xend = ax.get_xlim()
ax.plot([2,xend],[90,90])
plt.show()

enter image description here

The drawback of this is that the line will not be preseved upon zooming or panning. If that is required a callback needs to be used.

import numpy as np
import matplotlib.pyplot as plt

mu, sigma = 50, 25

value1 = 1.5*mu + sigma*np.random.randn(100)
value2 = mu + 1.5*sigma*np.random.randn(100)
value3 = 0.5*mu + sigma*np.random.randn(100)
value4 = mu + 0.5*sigma*np.random.randn(100)

box_plot_data=[value1,value2,value3,value4]

fig,ax = plt.subplots()
ax.boxplot(box_plot_data)

xstart, xend = ax.get_xlim()
line, = ax.plot([2,xend],[90,90])

def callback(evt):
    xstart, xend = ax.get_xlim()
    line.set_xdata((2,xend))

ax.callbacks.connect('xlim_changed', callback)

plt.show()

With this, even after zooming or panning the line stays where it should be.

A different option is to enhance the ConnectionPatch to allow for arbitrary transformations, such that you can provide a transform which would take the x coordinate in terms of axes coordinates and the y coordinate in terms of data coordinates. This transform is given by the ax.get_yaxis_transform(). With the subclassed ConnectionPatch shown below this allows to achieve the same as above without the need of a callback.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch

class EnhancedConnector(ConnectionPatch):
    def _get_xy(self, x, y, s, axes=None):
        p = super()._get_xy(x, y, s, axes=axes)
        if p is None:
            return s.transform_point((x, y))
        else:
            return p

mu, sigma = 50, 25

value1 = 1.5*mu + sigma*np.random.randn(100)
value2 = mu + 1.5*sigma*np.random.randn(100)
value3 = 0.5*mu + sigma*np.random.randn(100)
value4 = mu + 0.5*sigma*np.random.randn(100)

box_plot_data=[value1,value2,value3,value4]

fig,ax = plt.subplots()
ax.boxplot(box_plot_data)

transB = ax.get_yaxis_transform() 
p = EnhancedConnector(xyA=(2,90), xyB=(1,90), coordsA="data", coordsB=transB, 
                    axesA=ax, axesB=ax)
ax.add_patch(p)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712