1"""
2Stacked area plot for 1D arrays inspired by Douglas Y'barbo's stackoverflow
3answer:
4https://stackoverflow.com/q/2225995/
5
6(https://stackoverflow.com/users/66549/doug)
7"""
8
9import itertools
10
11import numpy as np
12
13from matplotlib import _api
14
15__all__ = ['stackplot']
16
17
18def stackplot(axes, x, *args,
19 labels=(), colors=None, hatch=None, baseline='zero',
20 **kwargs):
21 """
22 Draw a stacked area plot or a streamgraph.
23
24 Parameters
25 ----------
26 x : (N,) array-like
27
28 y : (M, N) array-like
29 The data is assumed to be unstacked. Each of the following
30 calls is legal::
31
32 stackplot(x, y) # where y has shape (M, N)
33 stackplot(x, y1, y2, y3) # where y1, y2, y3, y4 have length N
34
35 baseline : {'zero', 'sym', 'wiggle', 'weighted_wiggle'}
36 Method used to calculate the baseline:
37
38 - ``'zero'``: Constant zero baseline, i.e. a simple stacked plot.
39 - ``'sym'``: Symmetric around zero and is sometimes called
40 'ThemeRiver'.
41 - ``'wiggle'``: Minimizes the sum of the squared slopes.
42 - ``'weighted_wiggle'``: Does the same but weights to account for
43 size of each layer. It is also called 'Streamgraph'-layout. More
44 details can be found at http://leebyron.com/streamgraph/.
45
46 labels : list of str, optional
47 A sequence of labels to assign to each data series. If unspecified,
48 then no labels will be applied to artists.
49
50 colors : list of :mpltype:`color`, optional
51 A sequence of colors to be cycled through and used to color the stacked
52 areas. The sequence need not be exactly the same length as the number
53 of provided *y*, in which case the colors will repeat from the
54 beginning.
55
56 If not specified, the colors from the Axes property cycle will be used.
57
58 hatch : list of str, default: None
59 A sequence of hatching styles. See
60 :doc:`/gallery/shapes_and_collections/hatch_style_reference`.
61 The sequence will be cycled through for filling the
62 stacked areas from bottom to top.
63 It need not be exactly the same length as the number
64 of provided *y*, in which case the styles will repeat from the
65 beginning.
66
67 .. versionadded:: 3.9
68 Support for list input
69
70 data : indexable object, optional
71 DATA_PARAMETER_PLACEHOLDER
72
73 **kwargs
74 All other keyword arguments are passed to `.Axes.fill_between`.
75
76 Returns
77 -------
78 list of `.PolyCollection`
79 A list of `.PolyCollection` instances, one for each element in the
80 stacked area plot.
81 """
82
83 y = np.vstack(args)
84
85 labels = iter(labels)
86 if colors is not None:
87 colors = itertools.cycle(colors)
88 else:
89 colors = (axes._get_lines.get_next_color() for _ in y)
90
91 if hatch is None or isinstance(hatch, str):
92 hatch = itertools.cycle([hatch])
93 else:
94 hatch = itertools.cycle(hatch)
95
96 # Assume data passed has not been 'stacked', so stack it here.
97 # We'll need a float buffer for the upcoming calculations.
98 stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32))
99
100 _api.check_in_list(['zero', 'sym', 'wiggle', 'weighted_wiggle'],
101 baseline=baseline)
102 if baseline == 'zero':
103 first_line = 0.
104
105 elif baseline == 'sym':
106 first_line = -np.sum(y, 0) * 0.5
107 stack += first_line[None, :]
108
109 elif baseline == 'wiggle':
110 m = y.shape[0]
111 first_line = (y * (m - 0.5 - np.arange(m)[:, None])).sum(0)
112 first_line /= -m
113 stack += first_line
114
115 elif baseline == 'weighted_wiggle':
116 total = np.sum(y, 0)
117 # multiply by 1/total (or zero) to avoid infinities in the division:
118 inv_total = np.zeros_like(total)
119 mask = total > 0
120 inv_total[mask] = 1.0 / total[mask]
121 increase = np.hstack((y[:, 0:1], np.diff(y)))
122 below_size = total - stack
123 below_size += 0.5 * y
124 move_up = below_size * inv_total
125 move_up[:, 0] = 0.5
126 center = (move_up - 0.5) * increase
127 center = np.cumsum(center.sum(0))
128 first_line = center - 0.5 * total
129 stack += first_line
130
131 # Color between x = 0 and the first array.
132 coll = axes.fill_between(x, first_line, stack[0, :],
133 facecolor=next(colors),
134 hatch=next(hatch),
135 label=next(labels, None),
136 **kwargs)
137 coll.sticky_edges.y[:] = [0]
138 r = [coll]
139
140 # Color between array i-1 and array i
141 for i in range(len(y) - 1):
142 r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :],
143 facecolor=next(colors),
144 hatch=next(hatch),
145 label=next(labels, None),
146 **kwargs))
147 return r