Matplotlib - Expand a string with a specified width in a data block?

My question is a bit like this question, which draws a line with the width given in the data coordinates . What makes my question more complicated is that, unlike the related question, the segment I want to expand has a random orientation.

Say if a line segment goes from (0, 10)to (10, 10), and I want to expand it to a width 6. Then it's just

x = [0, 10]
y = [10, 10]
ax.fill_between(x, y - 3, y + 3)

However, my line segment has a random orientation . That is, it is not necessary along the x axis or the y axis. It has a certain slope .

Line segment sis defined as a list of its start and end points: [(x1, y1), (x2, y2)].

Now I want to expand the line segment to a certain width w. It is expected that the solution will work for the line segment in any orientation. How to do it?

UPDATE: plt.plot(x, y, linewidth=6.0) cannot do the trick, because I want my width to be in the same block as my data.

+5
source share
3 answers

Just add to the previous answer (you cannot comment yet), here is a function that automates this process without the need for equal axes or 0.8 heuristic for labels. The data limits and axis size must be fixed and not changed after calling this function.

def linewidth_from_data_units(linewidth, axis, reference='y'):
    """
    Convert a linewidth in data units to linewidth in points.

    Parameters
    ----------
    linewidth: float
        Linewidth in data units of the respective reference-axis
    axis: matplotlib axis
        The axis which is used to extract the relevant transformation
        data (data limits and size must not change afterwards)
    reference: string
        The axis that is taken as a reference for the data width.
        Possible values: 'x' and 'y'. Defaults to 'y'.

    Returns
    -------
    linewidth: float
        Linewidth in points
    """
    fig = axis.get_figure()
    if reference == 'x':
        length = fig.bbox_inches.width * axis.get_position().width
        value_range = np.diff(axis.get_xlim())
    elif reference == 'y':
        length = fig.bbox_inches.height * axis.get_position().height
        value_range = np.diff(axis.get_ylim())
    # Convert length to points
    length *= 72
    # Scale linewidth to value range
    return linewidth * (length / value_range)
+8
source

Explanation:

  • ( " " ). , x y.

  • point_hei ( ) , 72

  • yrange ( "" , , Y).

  • , linewid linewid

  • , pointlinewid . 80% .

  • , , ( )

? (: , , .)

import matplotlib.pyplot as plt
rez=600
wid=8.0 # Must be proportional to x and y limits below
hei=6.0
fig = plt.figure(1, figsize=(wid, hei))
sp = fig.add_subplot(111)
# # plt.figure.tight_layout() 
# fig.set_autoscaley_on(False)
sp.set_xlim([0,4000])
sp.set_ylim([0,3000])
plt.axes().set_aspect('equal')

# line is in points: 72 points per inch
point_hei=hei*72 

xval=[100,1300,2200,3000,3900]
yval=[10,200,2500,1750,1750]
x1,x2,y1,y2 = plt.axis()
yrange =   y2 - y1
# print yrange

linewid = 500     # in data units

# For the calculation below, you have to adjust width by 0.8
# because the top and bottom 10% of the figure are labels & axis
pointlinewid = (linewid * (point_hei/yrange)) * 0.8  # corresponding width in pts

plt.plot(xval,yval,linewidth = pointlinewid,color="blue",solid_capstyle="butt")
# just for fun, plot the half-width line on top of it
plt.plot(xval,yval,linewidth = pointlinewid/2,color="red",solid_capstyle="butt")

plt.savefig('mymatplot2.png',dpi=rez)

enter image description here

+5

, matplotlib, . ; , Line2D.

.

data_linewidth_plot , plt.plot,

l = data_linewidth_plot(x, y, ax=ax, label='some line', linewidth=1, alpha=0.4)

ax - . ax , . linewidth (y-) .

:

  1. , .
  2. , y.
  3. , ( , , , ).
  4. , , .

.

import matplotlib.pyplot as plt

class data_linewidth_plot():
    def __init__(self, x, y, **kwargs):
        self.ax = kwargs.pop("ax", plt.gca())
        self.fig = self.ax.get_figure()
        self.lw_data = kwargs.pop("linewidth", 1)
        self.lw = 1
        self.fig.canvas.draw()

        self.ppd = 72./self.fig.dpi
        self.trans = self.ax.transData.transform
        self.linehandle, = self.ax.plot([],[],**kwargs)
        if "label" in kwargs: kwargs.pop("label")
        self.line, = self.ax.plot(x, y, **kwargs)
        self.line.set_color(self.linehandle.get_color())
        self._resize()
        self.cid = self.fig.canvas.mpl_connect('draw_event', self._resize)

    def _resize(self, event=None):
        lw =  ((self.trans((1, self.lw_data))-self.trans((0, 0)))*self.ppd)[1]
        if lw != self.lw:
            self.line.set_linewidth(lw)
            self.lw = lw
            self._redraw_later()

    def _redraw_later(self):
        self.timer = self.fig.canvas.new_timer(interval=10)
        self.timer.single_shot = True
        self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
        self.timer.start()

fig1, ax1 = plt.subplots()
#ax.set_aspect('equal') #<-not necessary 
ax1.set_ylim(0,3)
x = [0,1,2,3]
y = [1,1,2,2]

# plot a line, with 'linewidth' in (y-)data coordinates.       
l = data_linewidth_plot(x, y, ax=ax1, label='some 1 data unit wide line', 
                        linewidth=1, alpha=0.4)

plt.legend() # <- legend possible
plt.show()

enter image description here

( , , - )

Line2D

The above solution has some disadvantages. A timer and callbacks are required to update itself when changing the limits of the axis or size of the figure. Below is a solution without such needs. He will use the dynamic property to always calculate the line width in points from the desired line width in the coordinates of the data on the fly. It is much shorter than higher. The disadvantage here is that the legend must be created manually through a proxy artist.

import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

class LineDataUnits(Line2D):
    def __init__(self, *args, **kwargs):
        _lw_data = kwargs.pop("linewidth", 1) 
        super().__init__(*args, **kwargs)
        self._lw_data = _lw_data

    def _get_lw(self):
        if self.axes is not None:
            ppd = 72./self.axes.figure.dpi
            trans = self.axes.transData.transform
            return ((trans((1, self._lw_data))-trans((0, 0)))*ppd)[1]
        else:
            return 1

    def _set_lw(self, lw):
        self._lw_data = lw

    _linewidth = property(_get_lw, _set_lw)


fig, ax = plt.subplots()

#ax.set_aspect('equal') # <-not necessary, if not given, y data is assumed 
ax.set_xlim(0,3)
ax.set_ylim(0,3)
x = [0,1,2,3]
y = [1,1,2,2]

line = LineDataUnits(x, y, linewidth=1, alpha=0.4)
ax.add_line(line)

ax.legend([Line2D([],[], linewidth=3, alpha=0.4)], 
           ['some 1 data unit wide line'])    # <- legend possible via proxy artist
plt.show()
+3
source

All Articles