matplotlib path linewidth connected to figure zoom

后端 未结 1 1750
一向
一向 2021-01-14 09:26

Is it possible to tie the linewidth of a matplotlib path to the figure zoom/scale level?

I am drawing a map where the matplotlib path (with bezier curves) draws the

相关标签:
1条回答
  • 2021-01-14 09:36

    To the best of my knowledge, there's no way to do this in matplotlib, as the stroke width of a line cannot be directly tied to data coordinates. (As you mentioned, you could connect a callback to the draw event and accomplish this. It would incur a large performance penalty, though.)

    However, a quick workaround would be to use shapely to generate polygons by buffering your street paths.

    As a quick example:

    import shapely.geometry
    import descartes
    import matplotlib.pyplot as plt
    
    lines = ([(0, 0), (1, 0), (0, 1)],
             [(0, 0), (1, 1)],
             [(0.5, 0.5), (1, 0.5)],
             )
    lines = shapely.geometry.MultiLineString(lines)
    # "0.05" is the _radius_ in data coords, so the width will be 0.1 units.
    poly = lines.buffer(0.05)
    
    fig, ax = plt.subplots()
    patch = descartes.PolygonPatch(poly, fc='gray', ec='black')
    ax.add_artist(patch)
    
    # Rescale things to leave a bit of room around the edges...
    ax.margins(0.1)
    
    plt.show()
    

    enter image description here

    If you did want to take the callback route, you might do something like this:

    import matplotlib.pyplot as plt
    
    def main():
        lines = ([(0, 0), (1, 0), (0, 1)],
                 [(0, 0), (1, 1)],
                 [(0.5, 0.5), (1, 0.5)],
                 )
    
        fig, ax = plt.subplots()
        artists = []
        for verts in lines:
            x, y = zip(*verts)
            line, = ax.plot(x, y)
            artists.append(line)
    
        scalar = StrokeScalar(artists, 0.1)
        ax.callbacks.connect('xlim_changed', scalar)
        ax.callbacks.connect('ylim_changed', scalar)
    
        # Rescale things to leave a bit of room around the edges...
        ax.margins(0.05)
    
        plt.show()
    
    class StrokeScalar(object):
        def __init__(self, artists, width):
            self.width = width
            self.artists = artists
            # Assume there's only one axes and one figure, for the moment...
            self.ax = artists[0].axes
            self.fig = self.ax.figure
    
        def __call__(self, event):
            """Intended to be connected to a draw event callback."""
            for artist in self.artists:
                artist.set_linewidth(self.stroke_width)
    
        @property
        def stroke_width(self):
            positions = [[0, 0], [self.width, self.width]]
            to_inches = self.fig.dpi_scale_trans.inverted().transform
            pixels = self.ax.transData.transform(positions)
            points = to_inches(pixels) * 72
            return points.ptp(axis=0).mean() # Not quite correct...
    
    main()
    

    enter image description here

    0 讨论(0)
提交回复
热议问题