|
3 | 3 | import itertools
|
4 | 4 | import logging
|
5 | 5 | import math
|
| 6 | +import operator |
6 | 7 | from numbers import Number
|
7 | 8 | import warnings
|
8 | 9 |
|
@@ -4004,6 +4005,151 @@ def dopatch(xs, ys, **kwargs):
|
4004 | 4005 | return dict(whiskers=whiskers, caps=caps, boxes=boxes,
|
4005 | 4006 | medians=medians, fliers=fliers, means=means)
|
4006 | 4007 |
|
| 4008 | + def _parse_scatter_color_args(self, c, edgecolors, kwargs, xshape, yshape): |
| 4009 | + """ |
| 4010 | + Helper function to process color related arguments of `.Axes.scatter`. |
| 4011 | +
|
| 4012 | + Argument precedence for facecolors: |
| 4013 | +
|
| 4014 | + - c (if not None) |
| 4015 | + - kwargs['facecolors'] |
| 4016 | + - kwargs['facecolor'] |
| 4017 | + - kwargs['color'] (==kwcolor) |
| 4018 | + - 'b' if in classic mode else next color from color cycle |
| 4019 | +
|
| 4020 | + Argument precedence for edgecolors: |
| 4021 | +
|
| 4022 | + - edgecolors (is an explicit kw argument in scatter()) |
| 4023 | + - kwargs['edgecolor'] |
| 4024 | + - kwargs['color'] (==kwcolor) |
| 4025 | + - 'face' if not in classic mode else None |
| 4026 | +
|
| 4027 | + Arguments |
| 4028 | + --------- |
| 4029 | + c : color or sequence or sequence of color or None |
| 4030 | + See argument description of `.Axes.scatter`. |
| 4031 | + edgecolors : color or sequence of color or {'face', 'none'} or None |
| 4032 | + See argument description of `.Axes.scatter`. |
| 4033 | + kwargs : dict |
| 4034 | + Additional kwargs. If these keys exist, we pop and process them: |
| 4035 | + 'facecolors', 'facecolor', 'edgecolor', 'color' |
| 4036 | + Note: The dict is modified by this function. |
| 4037 | + xshape, yshape : tuple of int |
| 4038 | + The shape of the x and y arrays passed to `.Axes.scatter`. |
| 4039 | +
|
| 4040 | + Returns |
| 4041 | + ------- |
| 4042 | + c |
| 4043 | + The input *c* if it was not *None*, else some color specification |
| 4044 | + derived from the other inputs or defaults. |
| 4045 | + colors : array(N, 4) or None |
| 4046 | + The facecolors as RGBA values or *None* if a colormap is used. |
| 4047 | + edgecolors |
| 4048 | + The edgecolor specification. |
| 4049 | +
|
| 4050 | + """ |
| 4051 | + xsize = functools.reduce(operator.mul, xshape, 1) |
| 4052 | + ysize = functools.reduce(operator.mul, yshape, 1) |
| 4053 | + |
| 4054 | + facecolors = kwargs.pop('facecolors', None) |
| 4055 | + facecolors = kwargs.pop('facecolor', facecolors) |
| 4056 | + edgecolors = kwargs.pop('edgecolor', edgecolors) |
| 4057 | + |
| 4058 | + kwcolor = kwargs.pop('color', None) |
| 4059 | + |
| 4060 | + if kwcolor is not None and c is not None: |
| 4061 | + raise ValueError("Supply a 'c' argument or a 'color'" |
| 4062 | + " kwarg but not both; they differ but" |
| 4063 | + " their functionalities overlap.") |
| 4064 | + |
| 4065 | + if kwcolor is not None: |
| 4066 | + try: |
| 4067 | + mcolors.to_rgba_array(kwcolor) |
| 4068 | + except ValueError: |
| 4069 | + raise ValueError("'color' kwarg must be an mpl color" |
| 4070 | + " spec or sequence of color specs.\n" |
| 4071 | + "For a sequence of values to be color-mapped," |
| 4072 | + " use the 'c' argument instead.") |
| 4073 | + if edgecolors is None: |
| 4074 | + edgecolors = kwcolor |
| 4075 | + if facecolors is None: |
| 4076 | + facecolors = kwcolor |
| 4077 | + |
| 4078 | + if edgecolors is None and not rcParams['_internal.classic_mode']: |
| 4079 | + edgecolors = 'face' |
| 4080 | + |
| 4081 | + c_is_none = c is None |
| 4082 | + if c is None: |
| 4083 | + if facecolors is not None: |
| 4084 | + c = facecolors |
| 4085 | + else: |
| 4086 | + c = ('b' if rcParams['_internal.classic_mode'] else |
| 4087 | + self._get_patches_for_fill.get_next_color()) |
| 4088 | + |
| 4089 | + # After this block, c_array will be None unless |
| 4090 | + # c is an array for mapping. The potential ambiguity |
| 4091 | + # with a sequence of 3 or 4 numbers is resolved in |
| 4092 | + # favor of mapping, not rgb or rgba. |
| 4093 | + # Convenience vars to track shape mismatch *and* conversion failures. |
| 4094 | + valid_shape = True # will be put to the test! |
| 4095 | + n_elem = -1 # used only for (some) exceptions |
| 4096 | + |
| 4097 | + if (c_is_none or |
| 4098 | + kwcolor is not None or |
| 4099 | + isinstance(c, str) or |
| 4100 | + (isinstance(c, collections.abc.Iterable) and |
| 4101 | + isinstance(c[0], str))): |
| 4102 | + c_array = None |
| 4103 | + else: |
| 4104 | + try: # First, does 'c' look suitable for value-mapping? |
| 4105 | + c_array = np.asanyarray(c, dtype=float) |
| 4106 | + n_elem = c_array.shape[0] |
| 4107 | + if c_array.shape in [xshape, yshape]: |
| 4108 | + c = np.ma.ravel(c_array) |
| 4109 | + else: |
| 4110 | + if c_array.shape in ((3,), (4,)): |
| 4111 | + _log.warning( |
| 4112 | + "'c' argument looks like a single numeric RGB or " |
| 4113 | + "RGBA sequence, which should be avoided as value-" |
| 4114 | + "mapping will have precedence in case its length " |
| 4115 | + "matches with 'x' & 'y'. Please use a 2-D array " |
| 4116 | + "with a single row if you really want to specify " |
| 4117 | + "the same RGB or RGBA value for all points.") |
| 4118 | + # Wrong size; it must not be intended for mapping. |
| 4119 | + valid_shape = False |
| 4120 | + c_array = None |
| 4121 | + except ValueError: |
| 4122 | + # Failed to make a floating-point array; c must be color specs. |
| 4123 | + c_array = None |
| 4124 | + if c_array is None: |
| 4125 | + try: # Then is 'c' acceptable as PathCollection facecolors? |
| 4126 | + colors = mcolors.to_rgba_array(c) |
| 4127 | + n_elem = colors.shape[0] |
| 4128 | + if colors.shape[0] not in (0, 1, xsize, ysize): |
| 4129 | + # NB: remember that a single color is also acceptable. |
| 4130 | + # Besides *colors* will be an empty array if c == 'none'. |
| 4131 | + valid_shape = False |
| 4132 | + raise ValueError |
| 4133 | + except ValueError: |
| 4134 | + if not valid_shape: # but at least one conversion succeeded. |
| 4135 | + raise ValueError( |
| 4136 | + "'c' argument has {nc} elements, which is not " |
| 4137 | + "acceptable for use with 'x' with size {xs}, " |
| 4138 | + "'y' with size {ys}." |
| 4139 | + .format(nc=n_elem, xs=xsize, ys=ysize) |
| 4140 | + ) |
| 4141 | + # Both the mapping *and* the RGBA conversion failed: pretty |
| 4142 | + # severe failure => one may appreciate a verbose feedback. |
| 4143 | + raise ValueError( |
| 4144 | + "'c' argument must either be valid as mpl color(s) " |
| 4145 | + "or as numbers to be mapped to colors. " |
| 4146 | + "Here c = {}." # <- beware, could be long depending on c. |
| 4147 | + .format(c) |
| 4148 | + ) |
| 4149 | + else: |
| 4150 | + colors = None # use cmap, norm after collection is created |
| 4151 | + return c, colors, edgecolors |
| 4152 | + |
4007 | 4153 | @_preprocess_data(replace_names=["x", "y", "s", "linewidths",
|
4008 | 4154 | "edgecolors", "c", "facecolor",
|
4009 | 4155 | "facecolors", "color"],
|
@@ -4117,128 +4263,27 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
|
4117 | 4263 |
|
4118 | 4264 | """
|
4119 | 4265 | # Process **kwargs to handle aliases, conflicts with explicit kwargs:
|
4120 |
| - facecolors = None |
4121 |
| - edgecolors = kwargs.pop('edgecolor', edgecolors) |
4122 |
| - fc = kwargs.pop('facecolors', None) |
4123 |
| - fc = kwargs.pop('facecolor', fc) |
4124 |
| - if fc is not None: |
4125 |
| - facecolors = fc |
4126 |
| - co = kwargs.pop('color', None) |
4127 |
| - if co is not None: |
4128 |
| - try: |
4129 |
| - mcolors.to_rgba_array(co) |
4130 |
| - except ValueError: |
4131 |
| - raise ValueError("'color' kwarg must be an mpl color" |
4132 |
| - " spec or sequence of color specs.\n" |
4133 |
| - "For a sequence of values to be color-mapped," |
4134 |
| - " use the 'c' argument instead.") |
4135 |
| - if edgecolors is None: |
4136 |
| - edgecolors = co |
4137 |
| - if facecolors is None: |
4138 |
| - facecolors = co |
4139 |
| - if c is not None: |
4140 |
| - raise ValueError("Supply a 'c' argument or a 'color'" |
4141 |
| - " kwarg but not both; they differ but" |
4142 |
| - " their functionalities overlap.") |
4143 |
| - if c is None: |
4144 |
| - if facecolors is not None: |
4145 |
| - c = facecolors |
4146 |
| - else: |
4147 |
| - if rcParams['_internal.classic_mode']: |
4148 |
| - c = 'b' # The original default |
4149 |
| - else: |
4150 |
| - c = self._get_patches_for_fill.get_next_color() |
4151 |
| - c_none = True |
4152 |
| - else: |
4153 |
| - c_none = False |
4154 |
| - |
4155 |
| - if edgecolors is None and not rcParams['_internal.classic_mode']: |
4156 |
| - edgecolors = 'face' |
4157 | 4266 |
|
4158 | 4267 | self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)
|
4159 | 4268 | x = self.convert_xunits(x)
|
4160 | 4269 | y = self.convert_yunits(y)
|
4161 | 4270 |
|
4162 | 4271 | # np.ma.ravel yields an ndarray, not a masked array,
|
4163 | 4272 | # unless its argument is a masked array.
|
4164 |
| - xy_shape = (np.shape(x), np.shape(y)) |
| 4273 | + xshape, yshape = np.shape(x), np.shape(y) |
4165 | 4274 | x = np.ma.ravel(x)
|
4166 | 4275 | y = np.ma.ravel(y)
|
4167 | 4276 | if x.size != y.size:
|
4168 | 4277 | raise ValueError("x and y must be the same size")
|
4169 | 4278 |
|
4170 | 4279 | if s is None:
|
4171 |
| - if rcParams['_internal.classic_mode']: |
4172 |
| - s = 20 |
4173 |
| - else: |
4174 |
| - s = rcParams['lines.markersize'] ** 2.0 |
4175 |
| - |
| 4280 | + s = (20 if rcParams['_internal.classic_mode'] else |
| 4281 | + rcParams['lines.markersize'] ** 2.0) |
4176 | 4282 | s = np.ma.ravel(s) # This doesn't have to match x, y in size.
|
4177 | 4283 |
|
4178 |
| - # After this block, c_array will be None unless |
4179 |
| - # c is an array for mapping. The potential ambiguity |
4180 |
| - # with a sequence of 3 or 4 numbers is resolved in |
4181 |
| - # favor of mapping, not rgb or rgba. |
4182 |
| - |
4183 |
| - # Convenience vars to track shape mismatch *and* conversion failures. |
4184 |
| - valid_shape = True # will be put to the test! |
4185 |
| - n_elem = -1 # used only for (some) exceptions |
4186 |
| - |
4187 |
| - if (c_none or |
4188 |
| - co is not None or |
4189 |
| - isinstance(c, str) or |
4190 |
| - (isinstance(c, collections.abc.Iterable) and |
4191 |
| - isinstance(c[0], str))): |
4192 |
| - c_array = None |
4193 |
| - else: |
4194 |
| - try: # First, does 'c' look suitable for value-mapping? |
4195 |
| - c_array = np.asanyarray(c, dtype=float) |
4196 |
| - n_elem = c_array.shape[0] |
4197 |
| - if c_array.shape in xy_shape: |
4198 |
| - c = np.ma.ravel(c_array) |
4199 |
| - else: |
4200 |
| - if c_array.shape in ((3,), (4,)): |
4201 |
| - _log.warning( |
4202 |
| - "'c' argument looks like a single numeric RGB or " |
4203 |
| - "RGBA sequence, which should be avoided as value-" |
4204 |
| - "mapping will have precedence in case its length " |
4205 |
| - "matches with 'x' & 'y'. Please use a 2-D array " |
4206 |
| - "with a single row if you really want to specify " |
4207 |
| - "the same RGB or RGBA value for all points.") |
4208 |
| - # Wrong size; it must not be intended for mapping. |
4209 |
| - valid_shape = False |
4210 |
| - c_array = None |
4211 |
| - except ValueError: |
4212 |
| - # Failed to make a floating-point array; c must be color specs. |
4213 |
| - c_array = None |
4214 |
| - |
4215 |
| - if c_array is None: |
4216 |
| - try: # Then is 'c' acceptable as PathCollection facecolors? |
4217 |
| - colors = mcolors.to_rgba_array(c) |
4218 |
| - n_elem = colors.shape[0] |
4219 |
| - if colors.shape[0] not in (0, 1, x.size, y.size): |
4220 |
| - # NB: remember that a single color is also acceptable. |
4221 |
| - # Besides *colors* will be an empty array if c == 'none'. |
4222 |
| - valid_shape = False |
4223 |
| - raise ValueError |
4224 |
| - except ValueError: |
4225 |
| - if not valid_shape: # but at least one conversion succeeded. |
4226 |
| - raise ValueError( |
4227 |
| - "'c' argument has {nc} elements, which is not " |
4228 |
| - "acceptable for use with 'x' with size {xs}, " |
4229 |
| - "'y' with size {ys}." |
4230 |
| - .format(nc=n_elem, xs=x.size, ys=y.size) |
4231 |
| - ) |
4232 |
| - # Both the mapping *and* the RGBA conversion failed: pretty |
4233 |
| - # severe failure => one may appreciate a verbose feedback. |
4234 |
| - raise ValueError( |
4235 |
| - "'c' argument must either be valid as mpl color(s) " |
4236 |
| - "or as numbers to be mapped to colors. " |
4237 |
| - "Here c = {}." # <- beware, could be long depending on c. |
4238 |
| - .format(c) |
4239 |
| - ) |
4240 |
| - else: |
4241 |
| - colors = None # use cmap, norm after collection is created |
| 4284 | + c, colors, edgecolors = \ |
| 4285 | + self._parse_scatter_color_args(c, edgecolors, kwargs, |
| 4286 | + xshape, yshape) |
4242 | 4287 |
|
4243 | 4288 | # `delete_masked_points` only modifies arguments of the same length as
|
4244 | 4289 | # `x`.
|
|
0 commit comments