Skip to content Skip to sidebar Skip to footer

Matplotlib - Control Capstyle Of Line Collection/large Number Of Lines

Similarly to a previous question of mine, I'd like to control the capstyle of lines being drawn using matplotlib. However, I have an extremely large number of lines, and drawing wi

Solution 1:

Since you mention in your question that you don't mind "dirty" solutions, one option would as follows.

The "drawing process" of a particular LineCollection is handled by the draw method defined in the Collection class (the base of LineCollection). This method creates an instance of GraphicsContextBase (defined in backend_bases.py) via the statement gc = renderer.new_gc(). It seems to be exactly this object which governs among other things the properties controlling the capstyle (property _capstyle). Therefore, one could subclass GraphicsContextBase, override the _capstyle property, and inject a new new_gc method into the RendererBase class so that consequent calls to new_gc return the customized instance:

Borrowing the example from the answer by @florisvb (assuming Python3):

#!/usr/bin/env python
import types

import numpy as np
from matplotlib.backend_bases import GraphicsContextBase, RendererBase
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection

class GC(GraphicsContextBase):
    def __init__(self):
        super().__init__()
        self._capstyle = 'round'

def custom_new_gc(self):
    return GC()

RendererBase.new_gc = types.MethodType(custom_new_gc, RendererBase)
#----------------------------------------------------------------------
np.random.seed(42)

x = np.random.random(10)
y = np.random.random(10)

points = np.array([x, y]).T.reshape((-1, 1, 2))
segments = np.concatenate([points[:-1], points[1:]], axis=1)

fig = plt.figure()
ax = fig.add_subplot(111)

linewidth = 10
lc = LineCollection(segments, linewidths=linewidth)
ax.add_collection(lc)

fig.savefig('fig.png')

This produces: enter image description here


Solution 2:

To update the answer from @ewcz as this thread still comes up in search results.
One can now use path_effects instead of defining their own GraphicsContextBase.

e.g.

import numpy as np
import matplotlib.patheffects as path_effects
from matplotlib.collections import LineCollection

np.random.seed(42)

x = np.random.random(10)
y = np.random.random(10)

points = np.array([x, y]).T.reshape((-1, 1, 2))
segments = np.concatenate([points[:-1], points[1:]], axis=1)

fig = plt.figure()
ax = fig.add_subplot(111)

linewidth = 10

### Stroke redraws the segment passing kwargs down to the GC renderer
lc = LineCollection(segments, linewidths=linewidth, 
    path_effects=[path_effects.Stroke(capstyle="round")])

ax.add_collection(lc)

fig.show()

Example png output with smooth lines and it also seems to work well with pdf output


Solution 3:

I was struggling with the same issue. I ended up plotting a scatter plot on top of my line collection. It's not perfect, but it may work for your application. There's a few subtleties - below is a working example.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection

x = np.random.random(10)
y = np.random.random(10)
z = np.arange(0,10)

points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

fig = plt.figure()
ax = fig.add_subplot(111)

linewidth = 10
cmap = plt.get_cmap('jet')
norm = plt.Normalize(np.min(z), np.max(z))
color = cmap(norm(z))

lc = LineCollection(segments, linewidths=linewidth, cmap=cmap, norm=norm)
lc.set_array(z)
lc.set_zorder(z.tolist())
ax.add_collection(lc)

ax.scatter(x,y,color=color,s=linewidth**2,edgecolor='none', zorder=(z+2).tolist())

Post a Comment for "Matplotlib - Control Capstyle Of Line Collection/large Number Of Lines"