| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,14 @@ | |||
| 1 | + Collection color specification and mapping | ||
| 2 | + ------------------------------------------ | ||
| 3 | + | ||
| 4 | + Reworking the handling of color mapping and the keyword arguments for facecolor | ||
| 5 | + and edgecolor has resulted in three behavior changes: | ||
| 6 | + | ||
| 7 | + 1. Color mapping can be turned off by calling ``Collection.set_array(None)``. | ||
| 8 | + Previously, this would have no effect. | ||
| 9 | + 2. When a mappable array is set, with ``facecolor='none'`` and | ||
| 10 | + ``edgecolor='face'``, both the faces and the edges are left uncolored. | ||
| 11 | + Previously the edges would be color-mapped. | ||
| 12 | + 3. When a mappable array is set, with ``facecolor='none'`` and | ||
| 13 | + ``edgecolor='red'``, the edges are red. This addresses Issue #1302. | ||
| 14 | + Previously the edges would be color-mapped. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6154,7 +6154,7 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||
| 6154 | 6154 | if shading is None: | |
| 6155 | 6155 | shading = rcParams['pcolor.shading'] | |
| 6156 | 6156 | shading = shading.lower() | |
| 6157 | - kwargs.setdefault('edgecolors', 'None') | ||
| 6157 | + kwargs.setdefault('edgecolors', 'none') | ||
| 6158 | 6158 | ||
| 6159 | 6159 | X, Y, C, shading = self._pcolorargs('pcolormesh', *args, | |
| 6160 | 6160 | shading=shading, kwargs=kwargs) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -366,7 +366,7 @@ def set_array(self, A): | |||
| 366 | 366 | ||
| 367 | 367 | Parameters | |
| 368 | 368 | ---------- | |
| 369 | - A : ndarray | ||
| 369 | + A : ndarray or None | ||
| 370 | 370 | """ | |
| 371 | 371 | self._A = A | |
| 372 | 372 | self._update_dict['array'] = True | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,8 @@ | |||
| 20 | 20 | import warnings | |
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | + # "color" is excluded; it is a compound setter, and its docstring differs | ||
| 24 | + # in LineCollection. | ||
| 23 | 25 | @cbook._define_aliases({ | |
| 24 | 26 | "antialiased": ["antialiaseds", "aa"], | |
| 25 | 27 | "edgecolor": ["edgecolors", "ec"], | |
@@ -168,8 +170,10 @@ def __init__(self, | |||
| 168 | 170 | # list of unbroadcast/scaled linewidths | |
| 169 | 171 | self._us_lw = [0] | |
| 170 | 172 | self._linewidths = [0] | |
| 171 | - self._is_filled = True # May be modified by set_facecolor(). | ||
| 172 | - | ||
| 173 | + # Flags set by _set_mappable_flags: are colors from mapping an array? | ||
| 174 | + self._face_is_mapped = None | ||
| 175 | + self._edge_is_mapped = None | ||
| 176 | + self._mapped_colors = None # calculated in update_scalarmappable | ||
| 173 | 177 | self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color']) | |
| 174 | 178 | self.set_facecolor(facecolors) | |
| 175 | 179 | self.set_edgecolor(edgecolors) | |
@@ -586,6 +590,10 @@ def get_offset_position(self): | |||
| 586 | 590 | """ | |
| 587 | 591 | return self._offset_position | |
| 588 | 592 | ||
| 593 | + def _get_default_linewidth(self): | ||
| 594 | + # This may be overridden in a subclass. | ||
| 595 | + return mpl.rcParams['patch.linewidth'] # validated as float | ||
| 596 | + | ||
| 589 | 597 | def set_linewidth(self, lw): | |
| 590 | 598 | """ | |
| 591 | 599 | Set the linewidth(s) for the collection. *lw* can be a scalar | |
@@ -597,9 +605,7 @@ def set_linewidth(self, lw): | |||
| 597 | 605 | lw : float or list of floats | |
| 598 | 606 | """ | |
| 599 | 607 | if lw is None: | |
| 600 | - lw = mpl.rcParams['patch.linewidth'] | ||
| 601 | - if lw is None: | ||
| 602 | - lw = mpl.rcParams['lines.linewidth'] | ||
| 608 | + lw = self._get_default_linewidth() | ||
| 603 | 609 | # get the un-scaled/broadcast lw | |
| 604 | 610 | self._us_lw = np.atleast_1d(np.asarray(lw)) | |
| 605 | 611 | ||
@@ -730,10 +736,14 @@ def set_antialiased(self, aa): | |||
| 730 | 736 | aa : bool or list of bools | |
| 731 | 737 | """ | |
| 732 | 738 | if aa is None: | |
| 733 | - aa = mpl.rcParams['patch.antialiased'] | ||
| 739 | + aa = self._get_default_antialiased() | ||
| 734 | 740 | self._antialiaseds = np.atleast_1d(np.asarray(aa, bool)) | |
| 735 | 741 | self.stale = True | |
| 736 | 742 | ||
| 743 | + def _get_default_antialiased(self): | ||
| 744 | + # This may be overridden in a subclass. | ||
| 745 | + return mpl.rcParams['patch.antialiased'] | ||
| 746 | + | ||
| 737 | 747 | def set_color(self, c): | |
| 738 | 748 | """ | |
| 739 | 749 | Set both the edgecolor and the facecolor. | |
@@ -750,16 +760,14 @@ def set_color(self, c): | |||
| 750 | 760 | self.set_facecolor(c) | |
| 751 | 761 | self.set_edgecolor(c) | |
| 752 | 762 | ||
| 763 | + def _get_default_facecolor(self): | ||
| 764 | + # This may be overridden in a subclass. | ||
| 765 | + return mpl.rcParams['patch.facecolor'] | ||
| 766 | + | ||
| 753 | 767 | def _set_facecolor(self, c): | |
| 754 | 768 | if c is None: | |
| 755 | - c = mpl.rcParams['patch.facecolor'] | ||
| 769 | + c = self._get_default_facecolor() | ||
| 756 | 770 | ||
| 757 | - self._is_filled = True | ||
| 758 | - try: | ||
| 759 | - if c.lower() == 'none': | ||
| 760 | - self._is_filled = False | ||
| 761 | - except AttributeError: | ||
| 762 | - pass | ||
| 763 | 771 | self._facecolors = mcolors.to_rgba_array(c, self._alpha) | |
| 764 | 772 | self.stale = True | |
| 765 | 773 | ||
@@ -775,6 +783,8 @@ def set_facecolor(self, c): | |||
| 775 | 783 | ---------- | |
| 776 | 784 | c : color or list of colors | |
| 777 | 785 | """ | |
| 786 | + if isinstance(c, str) and c.lower() in ("none", "face"): | ||
| 787 | + c = c.lower() | ||
| 778 | 788 | self._original_facecolor = c | |
| 779 | 789 | self._set_facecolor(c) | |
| 780 | 790 | ||
@@ -787,29 +797,24 @@ def get_edgecolor(self): | |||
| 787 | 797 | else: | |
| 788 | 798 | return self._edgecolors | |
| 789 | 799 | ||
| 800 | + def _get_default_edgecolor(self): | ||
| 801 | + # This may be overridden in a subclass. | ||
| 802 | + return mpl.rcParams['patch.edgecolor'] | ||
| 803 | + | ||
| 790 | 804 | def _set_edgecolor(self, c): | |
| 791 | 805 | set_hatch_color = True | |
| 792 | 806 | if c is None: | |
| 793 | - if (mpl.rcParams['patch.force_edgecolor'] or | ||
| 794 | - not self._is_filled or self._edge_default): | ||
| 795 | - c = mpl.rcParams['patch.edgecolor'] | ||
| 807 | + if (mpl.rcParams['patch.force_edgecolor'] | ||
| 808 | + or self._edge_default | ||
| 809 | + or cbook._str_equal(self._original_facecolor, 'none')): | ||
| 810 | + c = self._get_default_edgecolor() | ||
| 796 | 811 | else: | |
| 797 | 812 | c = 'none' | |
| 798 | 813 | set_hatch_color = False | |
| 799 | - | ||
| 800 | - self._is_stroked = True | ||
| 801 | - try: | ||
| 802 | - if c.lower() == 'none': | ||
| 803 | - self._is_stroked = False | ||
| 804 | - except AttributeError: | ||
| 805 | - pass | ||
| 806 | - | ||
| 807 | - try: | ||
| 808 | - if c.lower() == 'face': # Special case: lookup in "get" method. | ||
| 809 | - self._edgecolors = 'face' | ||
| 810 | - return | ||
| 811 | - except AttributeError: | ||
| 812 | - pass | ||
| 814 | + if cbook._str_lower_equal(c, 'face'): | ||
| 815 | + self._edgecolors = 'face' | ||
| 816 | + self.stale = True | ||
| 817 | + return | ||
| 813 | 818 | self._edgecolors = mcolors.to_rgba_array(c, self._alpha) | |
| 814 | 819 | if set_hatch_color and len(self._edgecolors): | |
| 815 | 820 | self._hatch_color = tuple(self._edgecolors[0]) | |
@@ -825,6 +830,11 @@ def set_edgecolor(self, c): | |||
| 825 | 830 | The collection edgecolor(s). If a sequence, the patches cycle | |
| 826 | 831 | through it. If 'face', match the facecolor. | |
| 827 | 832 | """ | |
| 833 | + # We pass through a default value for use in LineCollection. | ||
| 834 | + # This allows us to maintain None as the default indicator in | ||
| 835 | + # _original_edgecolor. | ||
| 836 | + if isinstance(c, str) and c.lower() in ("none", "face"): | ||
| 837 | + c = c.lower() | ||
| 828 | 838 | self._original_edgecolor = c | |
| 829 | 839 | self._set_edgecolor(c) | |
| 830 | 840 | ||
@@ -853,36 +863,81 @@ def get_linewidth(self): | |||
| 853 | 863 | def get_linestyle(self): | |
| 854 | 864 | return self._linestyles | |
| 855 | 865 | ||
| 866 | + def _set_mappable_flags(self): | ||
| 867 | + """ | ||
| 868 | + Determine whether edges and/or faces are color-mapped. | ||
| 869 | + | ||
| 870 | + This is a helper for update_scalarmappable. | ||
| 871 | + It sets Boolean flags '_edge_is_mapped' and '_face_is_mapped'. | ||
| 872 | + | ||
| 873 | + Returns | ||
| 874 | + ------- | ||
| 875 | + mapping_change : bool | ||
| 876 | + True if either flag is True, or if a flag has changed. | ||
| 877 | + """ | ||
| 878 | + # The flags are initialized to None to ensure this returns True | ||
| 879 | + # the first time it is called. | ||
| 880 | + edge0 = self._edge_is_mapped | ||
| 881 | + face0 = self._face_is_mapped | ||
| 882 | + # After returning, the flags must be Booleans, not None. | ||
| 883 | + self._edge_is_mapped = False | ||
| 884 | + self._face_is_mapped = False | ||
| 885 | + if self._A is not None: | ||
| 886 | + if not cbook._str_equal(self._original_facecolor, 'none'): | ||
| 887 | + self._face_is_mapped = True | ||
| 888 | + if cbook._str_equal(self._original_edgecolor, 'face'): | ||
| 889 | + self._edge_is_mapped = True | ||
| 890 | + else: | ||
| 891 | + if self._original_edgecolor is None: | ||
| 892 | + self._edge_is_mapped = True | ||
| 893 | + | ||
| 894 | + mapped = self._face_is_mapped or self._edge_is_mapped | ||
| 895 | + changed = (edge0 is None or face0 is None | ||
| 896 | + or self._edge_is_mapped != edge0 | ||
| 897 | + or self._face_is_mapped != face0) | ||
| 898 | + return mapped or changed | ||
| 899 | + | ||
| 856 | 900 | def update_scalarmappable(self): | |
| 857 | - """Update colors from the scalar mappable array, if it is not None.""" | ||
| 858 | - if self._A is None: | ||
| 859 | - return | ||
| 860 | - # QuadMesh can map 2d arrays (but pcolormesh supplies 1d array) | ||
| 861 | - if self._A.ndim > 1 and not isinstance(self, QuadMesh): | ||
| 862 | - raise ValueError('Collections can only map rank 1 arrays') | ||
| 863 | - if not self._check_update("array"): | ||
| 901 | + """ | ||
| 902 | + Update colors from the scalar mappable array, if any. | ||
| 903 | + | ||
| 904 | + Assign colors to edges and faces based on the array and/or | ||
| 905 | + colors that were directly set, as appropriate. | ||
| 906 | + """ | ||
| 907 | + if not self._set_mappable_flags(): | ||
| 864 | 908 | return | |
| 865 | - if np.iterable(self._alpha): | ||
| 866 | - if self._alpha.size != self._A.size: | ||
| 867 | - raise ValueError(f'Data array shape, {self._A.shape} ' | ||
| 868 | - 'is incompatible with alpha array shape, ' | ||
| 869 | - f'{self._alpha.shape}. ' | ||
| 870 | - 'This can occur with the deprecated ' | ||
| 871 | - 'behavior of the "flat" shading option, ' | ||
| 872 | - 'in which a row and/or column of the data ' | ||
| 873 | - 'array is dropped.') | ||
| 874 | - # pcolormesh, scatter, maybe others flatten their _A | ||
| 875 | - self._alpha = self._alpha.reshape(self._A.shape) | ||
| 876 | - | ||
| 877 | - if self._is_filled: | ||
| 878 | - self._facecolors = self.to_rgba(self._A, self._alpha) | ||
| 879 | - elif self._is_stroked: | ||
| 880 | - self._edgecolors = self.to_rgba(self._A, self._alpha) | ||
| 909 | + # Allow possibility to call 'self.set_array(None)'. | ||
| 910 | + if self._check_update("array") and self._A is not None: | ||
| 911 | + # QuadMesh can map 2d arrays (but pcolormesh supplies 1d array) | ||
| 912 | + if self._A.ndim > 1 and not isinstance(self, QuadMesh): | ||
| 913 | + raise ValueError('Collections can only map rank 1 arrays') | ||
| 914 | + if np.iterable(self._alpha): | ||
| 915 | + if self._alpha.size != self._A.size: | ||
| 916 | + raise ValueError( | ||
| 917 | + f'Data array shape, {self._A.shape} ' | ||
| 918 | + 'is incompatible with alpha array shape, ' | ||
| 919 | + f'{self._alpha.shape}. ' | ||
| 920 | + 'This can occur with the deprecated ' | ||
| 921 | + 'behavior of the "flat" shading option, ' | ||
| 922 | + 'in which a row and/or column of the data ' | ||
| 923 | + 'array is dropped.') | ||
| 924 | + # pcolormesh, scatter, maybe others flatten their _A | ||
| 925 | + self._alpha = self._alpha.reshape(self._A.shape) | ||
| 926 | + self._mapped_colors = self.to_rgba(self._A, self._alpha) | ||
| 927 | + | ||
| 928 | + if self._face_is_mapped: | ||
| 929 | + self._facecolors = self._mapped_colors | ||
| 930 | + else: | ||
| 931 | + self._set_facecolor(self._original_facecolor) | ||
| 932 | + if self._edge_is_mapped: | ||
| 933 | + self._edgecolors = self._mapped_colors | ||
| 934 | + else: | ||
| 935 | + self._set_edgecolor(self._original_edgecolor) | ||
| 881 | 936 | self.stale = True | |
| 882 | 937 | ||
| 883 | 938 | def get_fill(self): | |
| 884 | - """Return whether fill is set.""" | ||
| 885 | - return self._is_filled | ||
| 939 | + """Return whether face is colored.""" | ||
| 940 | + return not cbook._str_lower_equal(self._original_facecolor, "none") | ||
| 886 | 941 | ||
| 887 | 942 | def update_from(self, other): | |
| 888 | 943 | """Copy properties from other to self.""" | |
@@ -1350,18 +1405,9 @@ class LineCollection(Collection): | |||
| 1350 | 1405 | ||
| 1351 | 1406 | _edge_default = True | |
| 1352 | 1407 | ||
| 1353 | - def __init__(self, segments, # Can be None. | ||
| 1354 | - linewidths=None, | ||
| 1355 | - colors=None, | ||
| 1356 | - antialiaseds=None, | ||
| 1357 | - linestyles='solid', | ||
| 1358 | - offsets=None, | ||
| 1359 | - transOffset=None, | ||
| 1360 | - norm=None, | ||
| 1361 | - cmap=None, | ||
| 1362 | - pickradius=5, | ||
| 1363 | - zorder=2, | ||
| 1364 | - facecolors='none', | ||
| 1408 | + def __init__(self, segments, # Can be None. | ||
| 1409 | + *args, # Deprecated. | ||
| 1410 | + zorder=2, # Collection.zorder is 1 | ||
| 1365 | 1411 | **kwargs | |
| 1366 | 1412 | ): | |
| 1367 | 1413 | """ | |
@@ -1394,29 +1440,25 @@ def __init__(self, segments, # Can be None. | |||
| 1394 | 1440 | `~.path.Path.CLOSEPOLY`. | |
| 1395 | 1441 | ||
| 1396 | 1442 | **kwargs | |
| 1397 | - Forwareded to `.Collection`. | ||
| 1443 | + Forwarded to `.Collection`. | ||
| 1398 | 1444 | """ | |
| 1399 | - if colors is None: | ||
| 1400 | - colors = mpl.rcParams['lines.color'] | ||
| 1401 | - if linewidths is None: | ||
| 1402 | - linewidths = (mpl.rcParams['lines.linewidth'],) | ||
| 1403 | - if antialiaseds is None: | ||
| 1404 | - antialiaseds = (mpl.rcParams['lines.antialiased'],) | ||
| 1405 | - | ||
| 1406 | - colors = mcolors.to_rgba_array(colors) | ||
| 1445 | + argnames = ["linewidths", "colors", "antialiaseds", "linestyles", | ||
| 1446 | + "offsets", "transOffset", "norm", "cmap", "pickradius", | ||
| 1447 | + "zorder", "facecolors"] | ||
| 1448 | + if args: | ||
| 1449 | + argkw = {name: val for name, val in zip(argnames, args)} | ||
| 1450 | + kwargs.update(argkw) | ||
| 1451 | + cbook.warn_deprecated( | ||
| 1452 | + "3.4", message="Since %(since)s, passing LineCollection " | ||
| 1453 | + "arguments other than the first, 'segments', as positional " | ||
| 1454 | + "arguments is deprecated, and they will become keyword-only " | ||
| 1455 | + "arguments %(removal)s." | ||
| 1456 | + ) | ||
| 1457 | + # Unfortunately, mplot3d needs this explicit setting of 'facecolors'. | ||
| 1458 | + kwargs.setdefault('facecolors', 'none') | ||
| 1407 | 1459 | super().__init__( | |
| 1408 | - edgecolors=colors, | ||
| 1409 | - facecolors=facecolors, | ||
| 1410 | - linewidths=linewidths, | ||
| 1411 | - linestyles=linestyles, | ||
| 1412 | - antialiaseds=antialiaseds, | ||
| 1413 | - offsets=offsets, | ||
| 1414 | - transOffset=transOffset, | ||
| 1415 | - norm=norm, | ||
| 1416 | - cmap=cmap, | ||
| 1417 | 1460 | zorder=zorder, | |
| 1418 | 1461 | **kwargs) | |
| 1419 | - | ||
| 1420 | 1462 | self.set_segments(segments) | |
| 1421 | 1463 | ||
| 1422 | 1464 | def set_segments(self, segments): | |
@@ -1468,19 +1510,32 @@ def _add_offsets(self, segs): | |||
| 1468 | 1510 | segs[i] = segs[i] + offsets[io:io + 1] | |
| 1469 | 1511 | return segs | |
| 1470 | 1512 | ||
| 1513 | + def _get_default_linewidth(self): | ||
| 1514 | + return mpl.rcParams['lines.linewidth'] | ||
| 1515 | + | ||
| 1516 | + def _get_default_antialiased(self): | ||
| 1517 | + return mpl.rcParams['lines.antialiased'] | ||
| 1518 | + | ||
| 1519 | + def _get_default_edgecolor(self): | ||
| 1520 | + return mpl.rcParams['lines.color'] | ||
| 1521 | + | ||
| 1522 | + def _get_default_facecolor(self): | ||
| 1523 | + return 'none' | ||
| 1524 | + | ||
| 1471 | 1525 | def set_color(self, c): | |
| 1472 | 1526 | """ | |
| 1473 | - Set the color(s) of the LineCollection. | ||
| 1527 | + Set the edgecolor(s) of the LineCollection. | ||
| 1474 | 1528 | ||
| 1475 | 1529 | Parameters | |
| 1476 | 1530 | ---------- | |
| 1477 | 1531 | c : color or list of colors | |
| 1478 | - Single color (all patches have same color), or a | ||
| 1479 | - sequence of rgba tuples; if it is a sequence the patches will | ||
| 1532 | + Single color (all lines have same color), or a | ||
| 1533 | + sequence of rgba tuples; if it is a sequence the lines will | ||
| 1480 | 1534 | cycle through the sequence. | |
| 1481 | 1535 | """ | |
| 1482 | 1536 | self.set_edgecolor(c) | |
| 1483 | - self.stale = True | ||
| 1537 | + | ||
| 1538 | + set_colors = set_color | ||
| 1484 | 1539 | ||
| 1485 | 1540 | def get_color(self): | |
| 1486 | 1541 | return self._edgecolors | |
@@ -1851,7 +1906,6 @@ def __init__(self, triangulation, **kwargs): | |||
| 1851 | 1906 | super().__init__(**kwargs) | |
| 1852 | 1907 | self._triangulation = triangulation | |
| 1853 | 1908 | self._shading = 'gouraud' | |
| 1854 | - self._is_filled = True | ||
| 1855 | 1909 | ||
| 1856 | 1910 | self._bbox = transforms.Bbox.unit() | |
| 1857 | 1911 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments