2

I want to use a matplotlib.collections.LineCollection object, starting from the two Numpy arrays x and y

>>> from matplotlib.collections import LineCollection
>>> from numpy import array, linspace

>>> x = linspace(0, 2, 5)
>>> y = 1-(1-x)**2

The single thing that's strictly required to instantiate a LineCollection is a data structure composed of a list of segments, each segment being a list of points, each point being a tuple.

Using my two vectors x and y I can do

>>> segments = np.array(list(zip( zip(x, x[1:]), zip(y, y[1:])))) .transpose((0,2,1))
>>> print(segments)
[[[0.   0.  ]
  [0.5  0.75]]

 [[0.5  0.75]
  [1.   1.  ]]

 [[1.   1.  ]
  [1.5  0.75]]

 [[1.5  0.75]
  [2.   0.  ]]]

My question. Is it possible to construct segments in a less cryptic manner?

gboffi
  • 22,939
  • 8
  • 54
  • 85

2 Answers2

3

I always like to think of the segments as a 3D array, with the first axis being the linesegments, the second being the coordinates along the segments and the third being the x and y coordinates for each.

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

Or equally,

segments = np.stack((np.c_[x[:-1],x[1:]],np.c_[y[:-1],y[1:]]), axis=2)
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • I like the second solution, that maps evenly your description. In your opinion, is this task common enough to deserve a convenience function in matplotlib? – gboffi Nov 15 '19 at 21:17
  • Both map the description. It's more if you want to stack the shifted points, or if you want to stack the x coordinates to the y coordinates. Convenience function in the sense of `ax.plot_multiline(x,y)`? I'd say yes, but possibly such function would fall short of user's expectations, because individual lines aren't connected, resulting in whitespace between segments, as in [this problem](https://stackoverflow.com/a/47856091/4124317). – ImportanceOfBeingErnest Nov 15 '19 at 22:09
2

One possibility is the following:

xy = np.array([x, y])
segments = np.array([[xy[:, i], xy[:, i+1]] for i in range(xy.shape[1]-1)])

though I think it might be less elegant since it has more lines of code, an ugly iteration using indexes and it is slightly slower. That being said, if your priority is having a less cryptic solution, this might work for you: you avoid 3 zip functions, a transpose, and it is overall more readable I think.

Andrea
  • 2,932
  • 11
  • 23