diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 549cc350e4f4..ad281f6c24d7 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -27,54 +27,39 @@ "linewidth": ["linewidths", "lw"], }) class Collection(artist.Artist, cm.ScalarMappable): + r""" + Base class for Collections. Must be subclassed to be usable. + + A Collection represents a sequence of `.Patch`\es that can be drawn + more efficiently together than individually. For example, when a single + path is being drawn repeatedly at different offsets, the renderer can + typically execute a ``draw_marker()`` call much more efficiently than a + series of repeated calls to ``draw_path()`` with the offsets put in + one-by-one. + + Most properties of a collection can be configured per-element. Therefore, + Collections have "plural" versions of many of the properties of a `.Patch` + (e.g. `.Collection.get_paths` instead of `.Patch.get_path`). Exceptions are + the *zorder*, *hatch*, *pickradius*, *capstyle* and *joinstyle* properties, + which can only be set globally for the whole collection. + + Besides these exceptions, all properties can be specified as single values + (applying to all elements) or sequences of values. The property of the + ``i``\th element of the collection is:: + + prop[i % len(prop)] + + Each Collection can optionally be used as its own `.ScalarMappable` by + passing the *norm* and *cmap* parameters to its constructor. If the + Collection's `.ScalarMappable` matrix ``_A`` has been set (via a call + to `.Collection.set_array`), then at draw time this internal scalar + mappable will be used to set the ``facecolors`` and ``edgecolors``, + ignoring those that were manually passed in. """ - Base class for Collections. Must be subclassed to be usable. - - All properties in a collection must be sequences or scalars; - if scalars, they will be converted to sequences. The - property of the ith element of the collection is:: - - prop[i % len(props)] - - Exceptions are *capstyle* and *joinstyle* properties, these can - only be set globally for the whole collection. - - Keyword arguments and default values: - - - *edgecolors*: None - - *facecolors*: None - - *linewidths*: None - - *capstyle*: None - - *joinstyle*: None - - *antialiaseds*: None - - *offsets*: None - - *transOffset*: transforms.IdentityTransform() - - *offset_position* (deprecated): 'screen' (default) or 'data' (deprecated) - - *norm*: None (optional for `matplotlib.cm.ScalarMappable`) - - *cmap*: None (optional for `matplotlib.cm.ScalarMappable`) - - *hatch*: None - - *zorder*: 1 - - *offsets* and *transOffset* are used to translate the patch after - rendering (default no offsets). If offset_position is 'screen' - (default) the offset is applied after the master transform has - been applied, that is, the offsets are in screen coordinates. If - offset_position is 'data' (deprecated), the offset is applied before the - master transform, i.e., the offsets are in data coordinates. - - If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* are - None, they default to their `.rcParams` patch setting, in sequence form. - - The use of `~matplotlib.cm.ScalarMappable` functionality is optional. If - the `~matplotlib.cm.ScalarMappable` matrix ``_A`` has been set (via a call - to `~.ScalarMappable.set_array`), at draw time a call to scalar mappable - will be made to set the face colors. - """ - _offsets = np.zeros((0, 2)) _transOffset = transforms.IdentityTransform() - #: Either a list of 3x3 arrays or an Nx3x3 array of transforms, suitable - #: for the `all_transforms` argument to + #: Either a list of 3x3 arrays or an Nx3x3 array (representing N + #: transforms), suitable for the `all_transforms` argument to #: `~matplotlib.backend_bases.RendererBase.draw_path_collection`; #: each 3x3 array is used to initialize an #: `~matplotlib.transforms.Affine2D` object. @@ -106,9 +91,72 @@ def __init__(self, **kwargs ): """ - Create a Collection + Parameters + ---------- + edgecolors : color or list of colors, default: :rc:`patch.edgecolor` + Edge color for each patch making up the collection. The special + value 'face' can be passed to make the edgecolor match the + facecolor. + facecolors : color or list of colors, default: :rc:`patch.facecolor` + Face color for each patch making up the collection. + linewidths : float or list of floats, default: :rc:`patch.linewidth` + Line width for each patch making up the collection. + linestyles : str or tuple or list thereof, default: 'solid' + Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', '-', + '--', '-.', ':']. Dash tuples should be of the form:: + + (offset, onoffseq), - %(Collection)s + where *onoffseq* is an even length tuple of on and off ink lengths + in points. For examples, see + :doc:`/gallery/lines_bars_and_markers/linestyles`. + capstyle : str, default: :rc:`patch.capstyle` + Style to use for capping lines for all paths in the collection. + See :doc:`/gallery/lines_bars_and_markers/joinstyle` for + a demonstration of each of the allowed values. + joinstyle : str, default: :rc:`patch.joinstyle` + Style to use for joining lines for all paths in the collection. + See :doc:`/gallery/lines_bars_and_markers/joinstyle` for + a demonstration of each of the allowed values. + antialiaseds : bool or list of bool, default: :rc:`patch.antialiased` + Whether each pach in the collection should be drawn with + antialiasing. + offsets : (float, float) or list thereof, default: (0, 0) + A vector by which to translate each patch after rendering (default + is no translation). The translation is performed in screen (pixel) + coordinates (i.e. after the Artist's transform is applied). + transOffset : `~.transforms.Transform`, default: `.IdentityTransform` + A single transform which will be applied to each *offsets* vector + before it is used. + offset_position : {'screen' (default), 'data' (deprecated)} + If set to 'data' (deprecated), *offsets* will be treated as if it + is in data coordinates instead of in screen coordinates. + norm : `~.colors.Normalize`, optional + Forwarded to `.ScalarMappable`. The default of + ``None`` means that the first draw call will set ``vmin`` and + ``vmax`` using the minimum and maximum values of the data. + cmap : `~.colors.Colormap`, optional + Forwarded to `.ScalarMappable`. The default of + ``None`` will result in :rc:`image.cmap` being used. + hatch : str, optional + Hatching pattern to use in filled paths, if any. Valid strings are + ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*']. See + :doc:`/gallery/shapes_and_collections/hatch_demo` for the meaning + of each hatch type. + pickradius : float, default: 5.0 + If ``pickradius <= 0``, then `.Collection.contains` will return + ``True`` whenever the test point is inside of one of the polygons + formed by the control points of a Path in the Collection. On the + other hand, if it is greater than 0, then we instead check if the + test point is contained in a stroke of width ``2*pickradius`` + following any of the Paths in the Collection. + urls : list of str, default: None + A URL for each patch to link to once drawn. Currently only works + for the SVG backend. See :doc:`/gallery/misc/hyperlinks_sgskip` for + examples. + zorder : float, default: 1 + The drawing order, shared by all Patches in the Collection. See + :doc:`/gallery/misc/zorder_demo` for all defaults and examples. """ artist.Artist.__init__(self) cm.ScalarMappable.__init__(self, norm, cmap) @@ -182,20 +230,21 @@ def get_offset_transform(self): return t def get_datalim(self, transData): - - # Get the automatic datalim of the collection. + # Calculate the data limits and return them as a `.Bbox`. # # This operation depends on the transforms for the data in the - # collection and whether the collection has offsets. + # collection and whether the collection has offsets: # - # 1) offsets = None, transform child of transData: use the paths for + # 1. offsets = None, transform child of transData: use the paths for # the automatic limits (i.e. for LineCollection in streamline). - # 2) offsets != None: offset_transform is child of transData: - # a) transform is child of transData: use the path + offset for + # 2. offsets != None: offset_transform is child of transData: + # + # a. transform is child of transData: use the path + offset for # limits (i.e for bar). - # b) transform is not a child of transData: just use the offsets + # b. transform is not a child of transData: just use the offsets # for the limits (i.e. for scatter) - # 3) otherwise return a null Bbox. + # + # 3. otherwise return a null Bbox. transform = self.get_transform() transOffset = self.get_offset_transform() @@ -405,6 +454,11 @@ def contains(self, mouseevent): transform, transOffset, offsets, paths = self._prepare_points() + # Tests if the point is contained on one of the polygons formed + # by the control points of each of the paths. A point is considered + # "on" a path if it would lie within a stroke of width 2*pickradius + # following the path. If pickradius <= 0, then we instead simply check + # if the point is *inside* of the path instead. ind = _path.point_in_path_collection( mouseevent.x, mouseevent.y, pickradius, transform.frozen(), paths, self.get_transforms(), @@ -417,12 +471,23 @@ def set_urls(self, urls): """ Parameters ---------- - urls : List[str] or None + urls : list of str or None + + Notes + ----- + URLs are currently only implemented by the SVG backend. They are + ignored by all other backends. """ self._urls = urls if urls is not None else [None] self.stale = True def get_urls(self): + """ + Return a list of URLs, one for each element of the collection. + + The list contains *None* for elements without a URL. See + :doc:`/gallery/misc/hyperlinks_sgskip` for an example. + """ return self._urls def set_hatch(self, hatch): @@ -528,7 +593,7 @@ def set_linewidth(self, lw): Parameters ---------- - lw : float or sequence of floats + lw : float or list of floats """ if lw is None: lw = mpl.rcParams['patch.linewidth'] @@ -563,8 +628,10 @@ def set_linestyle(self, ls): Parameters ---------- - ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} - The line style. + ls : str or tuple or list thereof + Valid values for individual linestyles include {'-', '--', '-.', + ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a + complete description. """ try: if isinstance(ls, str): @@ -594,7 +661,7 @@ def set_capstyle(self, cs): Parameters ---------- cs : {'butt', 'round', 'projecting'} - The capstyle + The capstyle. """ mpl.rcsetup.validate_capstyle(cs) self._capstyle = cs @@ -609,7 +676,7 @@ def set_joinstyle(self, js): Parameters ---------- js : {'miter', 'round', 'bevel'} - The joinstyle + The joinstyle. """ mpl.rcsetup.validate_joinstyle(js) self._joinstyle = js @@ -661,7 +728,7 @@ def set_antialiased(self, aa): Parameters ---------- - aa : bool or sequence of bools + aa : bool or list of bools """ if aa is None: aa = mpl.rcParams['patch.antialiased'] @@ -674,7 +741,7 @@ def set_color(self, c): Parameters ---------- - c : color or sequence of rgba tuples + c : color or list of rgba tuples See Also -------- @@ -707,7 +774,7 @@ def set_facecolor(self, c): Parameters ---------- - c : color or sequence of colors + c : color or list of colors """ self._original_facecolor = c self._set_facecolor(c) @@ -755,7 +822,7 @@ def set_edgecolor(self, c): Parameters ---------- - c : color or sequence of colors or 'face' + c : color or list of colors or 'face' The collection edgecolor(s). If a sequence, the patches cycle through it. If 'face', match the facecolor. """ @@ -817,29 +884,6 @@ def update_from(self, other): self.stale = True -# these are not available for the object inspector until after the -# class is built so we define an initial set here for the init -# function and they will be overridden after object defn -docstring.interpd.update(Collection="""\ - Valid Collection keyword arguments: - - - *edgecolors*: None - - *facecolors*: None - - *linewidths*: None - - *antialiaseds*: None - - *offsets*: None - - *transOffset*: transforms.IdentityTransform() - - *norm*: None (optional for `matplotlib.cm.ScalarMappable`) - - *cmap*: None (optional for `matplotlib.cm.ScalarMappable`) - - *offsets* and *transOffset* are used to translate the patch after - rendering (default no offsets) - - If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* are - None, they default to their `.rcParams` patch setting, in sequence form. -""") - - class _CollectionWithSizes(Collection): """ Base class for collections that have an array of sizes. @@ -888,19 +932,25 @@ def draw(self, renderer): class PathCollection(_CollectionWithSizes): - """ - The most basic `Collection` subclass, created e.g. by `~.Axes.scatter`. + r""" + A collection of `~.path.Path`\s, as created by e.g. `~.Axes.scatter`. """ - @docstring.dedent_interpd def __init__(self, paths, sizes=None, **kwargs): """ - *paths* is a sequence of `matplotlib.path.Path` instances. - - %(Collection)s + Parameters + ---------- + paths : list of `.path.Path` + The paths that will make up the `.Collection`. + sizes : array-like + The factor by which to scale each drawn `~.path.Path`. One unit + squared in the Path's data space is scaled to be ``sizes**2`` + points when rendered. + **kwargs + Forwarded to `.Collection`. """ - Collection.__init__(self, **kwargs) + super().__init__(**kwargs) self.set_paths(paths) self.set_sizes(sizes) self.stale = True @@ -917,12 +967,18 @@ def legend_elements(self, prop="colors", num="auto", """ Create legend handles and labels for a PathCollection. + Each legend handle is a `.Line2D` representing the Path that was drawn, + and each label is a string what each Path represents. + This is useful for obtaining a legend for a `~.Axes.scatter` plot; e.g.:: scatter = plt.scatter([1, 2, 3], [4, 5, 6], c=[7, 2, 3]) plt.legend(*scatter.legend_elements()) + creates three legend elements, one for each color with the numerical + values passed to *c* as the labels. + Also see the :ref:`automatedlegendcreation` example. Parameters @@ -930,7 +986,8 @@ def legend_elements(self, prop="colors", num="auto", prop : {"colors", "sizes"}, default: "colors" If "colors", the legend handles will show the different colors of the collection. If "sizes", the legend will show the different - sizes. + sizes. To set both, use *kwargs* to directly edit the `.Line2D` + properties. num : int, None, "auto" (default), array-like, or `~.ticker.Locator`, Target number of elements to create. If None, use all unique elements of the mappable array. If an @@ -953,7 +1010,7 @@ def legend_elements(self, prop="colors", num="auto", function here allows that pre-processing to be inverted, so that the legend labels have the correct values; e.g. ``func = lambda x: 10**x``. - kwargs : further parameters + **kwargs Allowed keyword arguments are *color* and *size*. E.g. it may be useful to set the color of the markers if *prop="sizes"* is used; similarly to set the size of the markers if *prop="colors"* is @@ -963,9 +1020,10 @@ def legend_elements(self, prop="colors", num="auto", Returns ------- - tuple (handles, labels) - with *handles* being a list of `.Line2D` objects - and *labels* a matching list of strings. + handles : list of `.Line2D` + Visual representation of each element of the legend. + labels : list of str + The string labels for elements of the legend. """ handles = [] labels = [] @@ -1042,12 +1100,11 @@ def legend_elements(self, prop="colors", num="auto", class PolyCollection(_CollectionWithSizes): - @docstring.dedent_interpd def __init__(self, verts, sizes=None, closed=True, **kwargs): """ Parameters ---------- - verts : sequence + verts : list of array-like The sequence of polygons [*verts0*, *verts1*, ...] where each element *verts_i* defines the vertices of polygon *i* as a 2D array-like of of shape (M, 2). @@ -1056,13 +1113,12 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs): polygon *verts_i* are multiplied by the square-root of the corresponding entry in *sizes* (i.e., *sizes* specify the scaling of areas). The scaling is applied before the Artist master - transform. If *sizes* is shorter than *verts*, the additional - values will be taken cyclically from the *sizes*. + transform. closed : bool, default: True Whether the polygon should be closed by adding a CLOSEPOLY connection at the end. **kwargs - %(Collection)s + Forwarded to `.Collection`. """ Collection.__init__(self, **kwargs) self.set_sizes(sizes) @@ -1075,7 +1131,7 @@ def set_verts(self, verts, closed=True): Parameters ---------- - verts : sequence + verts : list of array-like The sequence of polygons [*verts0*, *verts1*, ...] where each element *verts_i* defines the vertices of polygon *i* as a 2D array-like of of shape (M, 2). @@ -1136,17 +1192,16 @@ class BrokenBarHCollection(PolyCollection): A collection of horizontal bars spanning *yrange* with a sequence of *xranges*. """ - @docstring.dedent_interpd def __init__(self, xranges, yrange, **kwargs): """ Parameters ---------- - xranges : sequence of (float, float) + xranges : list of (float, float) The sequence of (left-edge-position, width) pairs for each bar. yrange : (float, float) The (lower-edge, height) common to all bars. **kwargs - %(Collection)s + Forwarded to `.Collection`. """ ymin, ywidth = yrange ymax = ymin + ywidth @@ -1160,7 +1215,7 @@ def __init__(self, xranges, yrange, **kwargs): @classmethod def span_where(cls, x, ymin, ymax, where, **kwargs): """ - Return a `BrokenBarHCollection` that plots horizontal bars from + Return a `.BrokenBarHCollection` that plots horizontal bars from over the regions in *x* where *where* is True. The bars range on the y-axis from *ymin* to *ymax* @@ -1181,7 +1236,6 @@ class RegularPolyCollection(_CollectionWithSizes): _path_generator = mpath.Path.unit_regular_polygon _factor = np.pi ** (-1/2) - @docstring.dedent_interpd def __init__(self, numsides, rotation=0, @@ -1196,12 +1250,8 @@ def __init__(self, The rotation of the polygon in radians. sizes : tuple of float The area of the circle circumscribing the polygon in points^2. - - Other Parameters - ---------------- **kwargs - Other keyword arguments. - %(Collection)s + Forwarded to `.Collection`. Examples -------- @@ -1254,15 +1304,21 @@ class AsteriskPolygonCollection(RegularPolyCollection): class LineCollection(Collection): - """ - All parameters must be sequences or scalars; if scalars, they will - be converted to sequences. The property of the ith line - segment is:: + r""" + Represents a sequence of `.Line2D`\s that should be drawn together. + + This class extends `.Collection` to represent a sequence of + `~.Line2D`\s instead of just a sequence of `~.Patch`\s. + Just as in `.Collection`, each property of a *LineCollection* may be either + a single value or a list of values. This list is then used cyclically for + each element of the LineCollection, so the property of the ``i``\th element + of the collection is:: - prop[i % len(props)] + prop[i % len(prop)] - i.e., the properties cycle if the ``len`` of props is less than the - number of segments. + The properties of each member of a *LineCollection* default to their values + in :rc:`lines.*` instead of :rc:`patch.*`, and the property *colors* is + added in place of *edgecolors*. """ _edge_default = True @@ -1284,70 +1340,33 @@ def __init__(self, segments, # Can be None. """ Parameters ---------- - segments + segments: list of array-like A sequence of (*line0*, *line1*, *line2*), where:: linen = (x0, y0), (x1, y1), ... (xm, ym) or the equivalent numpy array with two columns. Each line - can be a different length. - - colors : sequence, optional - A sequence of RGBA tuples (e.g., arbitrary color - strings, etc, not allowed). - - antialiaseds : sequence, optional - A sequence of ones or zeros. - - linestyles : str or tuple, default: 'solid' - Either one of {'solid', 'dashed', 'dashdot', 'dotted'}, or - a dash tuple. The dash tuple is:: - - (offset, onoffseq) - - where ``onoffseq`` is an even length tuple of on and off ink - in points. - - norm : Normalize, optional - `~.colors.Normalize` instance. - - cmap : str or Colormap, optional - Colormap name or `~.colors.Colormap` instance. - - pickradius : float, default: 5pt - The tolerance in points for mouse clicks picking a line. - + can have a different number of segments. + linewidths : float or list of float, default: :rc:`lines.linewidth` + The width of each line in points. + colors : color or list of color, default: :rc:`lines.color` + A sequence of RGBA tuples (e.g., arbitrary color strings, etc, not + allowed). + antialiaseds : bool or list of bool, default: :rc:`lines.antialiased` + Whether to use antialiasing for each line. zorder : int, default: 2 - zorder of the LineCollection. - - facecolors : default: 'none' + zorder of the lines once drawn. + facecolors : color or list of color, default: 'none' The facecolors of the LineCollection. - Setting to a value other than 'none' will lead to a filled - polygon being drawn between points on each line. - - Notes - ----- - If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* are - None, they default to their `.rcParams` patch setting, in sequence - form. - - If *offsets* and *transOffset* are not None, then - *offsets* are transformed by *transOffset* and applied after - the segments have been transformed to display coordinates. - - If *offsets* is not None but *transOffset* is None, then the - *offsets* are added to the segments before any transformation. - In this case, a single offset can be specified as:: - - offsets=(xo, yo) - - and this value will be added cumulatively to each successive - segment, so as to produce a set of successively offset curves. - - The use of `~matplotlib.cm.ScalarMappable` functionality is optional. - If the `~matplotlib.cm.ScalarMappable` matrix ``_A`` has been set (via - a call to `~.ScalarMappable.set_array`), at draw time a call to scalar - mappable will be made to set the face colors. + Setting to a value other than 'none' will lead to each line being + "filled in" as if there was an implicit line segment joining the + last and first points of that line back around to each other. In + order to manually specify what should count as the "interior" of + each line, please use `.PathCollection` instead, where the + "interior" can be specified by appropriate usage of + `~.path.Path.CLOSEPOLY`. + **kwargs + Forwareded to `.Collection`. """ if colors is None: colors = mpl.rcParams['lines.color'] @@ -1368,7 +1387,6 @@ def __init__(self, segments, # Can be None. transOffset=transOffset, norm=norm, cmap=cmap, - pickradius=pickradius, zorder=zorder, **kwargs) @@ -1445,10 +1463,9 @@ def get_color(self): class EventCollection(LineCollection): """ - A collection of discrete events. + A collection of locations along a single axis at which an "event" occured. - The events are given by a 1-dimensional array, usually the position of - something along an axis, such as time or length. They do not have an + The events are given by a 1-dimensional array. They do not have an amplitude and are displayed as parallel lines. """ @@ -1470,27 +1487,21 @@ def __init__(self, ---------- positions : 1D array-like Each value is an event. - orientation : {'horizontal', 'vertical'}, default: 'horizontal' The sequence of events is plotted along this direction. The marker lines of the single events are along the orthogonal direction. - lineoffset : float, default: 0 The offset of the center of the markers from the origin, in the direction orthogonal to *orientation*. - linelength : float, default: 1 The total height of the marker (i.e. the marker stretches from ``lineoffset - linelength/2`` to ``lineoffset + linelength/2``). - - linewidth : float, default: :rc:`lines.linewidth` + linewidth : float or list thereof, default: :rc:`lines.linewidth` The line width of the event lines, in points. - color : color or list of colors, default: :rc:`lines.color` The color of the event lines. - - linestyle : str or tuple, default: 'solid' + linestyle : str or tuple or list thereof, default: 'solid' Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', '-', '--', '-.', ':']. Dash tuples should be of the form:: @@ -1498,14 +1509,10 @@ def __init__(self, where *onoffseq* is an even length tuple of on and off ink in points. - - antialiased : bool, default: :rc:`lines.antialiased` + antialiased : bool or list thereof, default: :rc:`lines.antialiased` Whether to use antialiasing for drawing the lines. - **kwargs - Other keyword arguments are line collection properties. See - :class:`~matplotlib.collections.LineCollection` for a list of - the valid properties. + Forwarded to `.LineCollection`. Examples -------- @@ -1659,13 +1666,14 @@ class CircleCollection(_CollectionWithSizes): _factor = np.pi ** (-1/2) - @docstring.dedent_interpd def __init__(self, sizes, **kwargs): """ - *sizes* - Gives the area of the circle in points^2 - - %(Collection)s + Parameters + ---------- + sizes : float or array-like + The area of each circle in points^2. + **kwargs + Forwarded to `.Collection`. """ Collection.__init__(self, **kwargs) self.set_sizes(sizes) @@ -1676,7 +1684,6 @@ def __init__(self, sizes, **kwargs): class EllipseCollection(Collection): """A collection of ellipses, drawn using splines.""" - @docstring.dedent_interpd def __init__(self, widths, heights, angles, units='points', **kwargs): """ Parameters @@ -1697,15 +1704,10 @@ def __init__(self, widths, heights, angles, units='points', **kwargs): refer to the *offsets* data units. 'xy' differs from all others in that the angle as plotted varies with the aspect ratio, and equals the specified angle only when the aspect ratio is unity. Hence - it behaves the same as the `~matplotlib.patches.Ellipse` with + it behaves the same as the `~.patches.Ellipse` with ``axes.transData`` as its transform. - - Other Parameters - ---------------- **kwargs - Additional kwargs inherited from the base :class:`Collection`. - - %(Collection)s + Forwarded to `Collection`. """ Collection.__init__(self, **kwargs) self._widths = 0.5 * np.asarray(widths).ravel()