From 492c3ad9b0bec58c50da0635ee6a74144a9c8587 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Wed, 10 Jun 2020 12:20:24 +0100 Subject: [PATCH 01/11] bug fix on color handling for showProjection --- prody/dynamics/plotting.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 060b68b15..973e8c0a9 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -11,7 +11,7 @@ import numpy as np from prody import LOGGER, SETTINGS, PY3K -from prody.utilities import showFigure, addEnds, showMatrix +from prody.utilities import showFigure, addEnds, showMatrix, isListLike from prody.atomic import AtomGroup, Selection, Atomic, sliceAtoms, sliceAtomicData from .nma import NMA @@ -260,15 +260,16 @@ def showProjection(ensemble, modes, *args, **kwargs): c = kwargs.pop('c', 'blue') colors = kwargs.pop('color', c) - if isinstance(colors, np.ndarray): - colors = tuple(colors) - if isinstance(colors, (str, tuple)) or colors is None: + + if isListLike(colors) and not len(colors) in [3, 4]: + colors = list(colors) + elif isinstance(colors, str) or colors is None or isListLike(colors): colors = [colors] * num - elif isinstance(colors, list): - if len(colors) != num: - raise ValueError('length of color must be {0}'.format(num)) else: - raise TypeError('color must be a string or a list') + raise TypeError('color must be string or list-like or None') + + if len(colors) != num: + raise ValueError('length of color must be {0}'.format(num)) color_norm = None if isinstance(colors[0], Number): From 8325bb4dd2879665eed355918913d669a0cce36b Mon Sep 17 00:00:00 2001 From: James Krieger Date: Wed, 10 Jun 2020 12:28:19 +0100 Subject: [PATCH 02/11] added check for 2 elements --- prody/dynamics/plotting.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 973e8c0a9..f2fc9202f 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -261,7 +261,9 @@ def showProjection(ensemble, modes, *args, **kwargs): c = kwargs.pop('c', 'blue') colors = kwargs.pop('color', c) - if isListLike(colors) and not len(colors) in [3, 4]: + if isListLike(colors) and len(colors) == 2: + raise ValueError('each entry of color should have 1, 3 or 4 values not 2') + elif isListLike(colors) and not len(colors) in [3, 4]: colors = list(colors) elif isinstance(colors, str) or colors is None or isListLike(colors): colors = [colors] * num @@ -269,7 +271,7 @@ def showProjection(ensemble, modes, *args, **kwargs): raise TypeError('color must be string or list-like or None') if len(colors) != num: - raise ValueError('length of color must be {0}'.format(num)) + raise ValueError('final length of color must be {0}'.format(num)) color_norm = None if isinstance(colors[0], Number): From 6708a3b8cacf1e98a193af197e145c2c8eae7753 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 13:55:44 +0200 Subject: [PATCH 03/11] improve showProjection docs --- prody/dynamics/plotting.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 0970a7666..cdfa59e35 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -218,13 +218,20 @@ def showProjection(ensemble=None, modes=None, projection=None, *args, **kwargs): Default is to use ensemble.getData('size') :type weights: int, list, :class:`~numpy.ndarray` - :keyword color: a color name or a list of color names or values, + :keyword color: a color name or value or a list of length ensemble.numConfs() of these, + or a dictionary with these with keys corresponding to labels provided by keyword label default is ``'blue'`` + Color values can have 1 element to be mapped with cmap or 3 as RGB or 4 as RGBA. + See https://matplotlib.org/stable/users/explain/colors/colors.html#colors-def :type color: str, list - :keyword label: label or a list of labels + :keyword label: label or a list of labels :type label: str, list + :keyword use_labels: whether to use labels for coloring subsets. + These can also be taken from an LDA or LRA model. + :type use_labels: bool + :keyword marker: a marker or a list of markers, default is ``'o'`` :type marker: str, list @@ -278,10 +285,10 @@ def showProjection(ensemble=None, modes=None, projection=None, *args, **kwargs): if labels is None and use_labels and modes is not None: if isinstance(modes, (LDA, LRA)): labels = modes._labels.tolist() - LOGGER.info('using labels from LDA modes') + LOGGER.info('using labels from {0} modes'.format(type(modes))) elif isinstance(modes.getModel(), (LDA, LRA)): labels = modes.getModel()._labels.tolist() - LOGGER.info('using labels from LDA model') + LOGGER.info('using labels from {0} modes'.format(type(modes.getModel()))) if labels is not None and len(labels) != num: raise ValueError('label should have the same length as ensemble') From 5bac96cd6cfd8d7dbba9b70a146aca4ffcc25fa4 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 15:23:34 +0200 Subject: [PATCH 04/11] proper checkColors --- prody/dynamics/plotting.py | 103 +++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index cdfa59e35..dc72c14e0 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -7,6 +7,8 @@ and keyword arguments are passed to the Matplotlib functions.""" from collections import defaultdict +from matplotlib.colors import is_color_like + from numbers import Number import numpy as np @@ -218,14 +220,14 @@ def showProjection(ensemble=None, modes=None, projection=None, *args, **kwargs): Default is to use ensemble.getData('size') :type weights: int, list, :class:`~numpy.ndarray` - :keyword color: a color name or value or a list of length ensemble.numConfs() of these, + :keyword color: a color name or value or a list of length ensemble.numConfs() or projection.shape[0] of these, or a dictionary with these with keys corresponding to labels provided by keyword label default is ``'blue'`` Color values can have 1 element to be mapped with cmap or 3 as RGB or 4 as RGBA. See https://matplotlib.org/stable/users/explain/colors/colors.html#colors-def :type color: str, list - :keyword label: label or a list of labels + :keyword label: label or a list of labels :type label: str, list :keyword use_labels: whether to use labels for coloring subsets. @@ -295,21 +297,7 @@ def showProjection(ensemble=None, modes=None, projection=None, *args, **kwargs): c = kwargs.pop('c', 'b') colors = kwargs.pop('color', c) - colors_dict = {} - if isinstance(colors, np.ndarray): - colors = tuple(colors) - if isinstance(colors, (str, tuple)) or colors is None: - colors = [colors] * num - elif isinstance(colors, list): - if len(colors) != num: - raise ValueError('length of color must be {0}'.format(num)) - elif isinstance(colors, dict): - if labels is None: - raise TypeError('color must be a string or a list unless labels are provided') - colors_dict = colors - colors = [colors_dict[label] for label in labels] - else: - raise TypeError('color must be a string or a list or a dict if labels are provided') + colors, colors_dict = checkColors(colors, num, labels, allowNumbers=True) if labels is not None and len(colors_dict) == 0: cycle_colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] @@ -364,21 +352,6 @@ def showProjection(ensemble=None, modes=None, projection=None, *args, **kwargs): else: raise TypeError('marker must be a string or a list') - c = kwargs.pop('c', 'blue') - colors = kwargs.pop('color', c) - - if isListLike(colors) and len(colors) == 2: - raise ValueError('each entry of color should have 1, 3 or 4 values not 2') - elif isListLike(colors) and not len(colors) in [3, 4]: - colors = list(colors) - elif isinstance(colors, str) or colors is None or isListLike(colors): - colors = [colors] * num - else: - raise TypeError('color must be string or list-like or None') - - if len(colors) != num: - raise ValueError('final length of color must be {0}'.format(num)) - color_norm = None if isinstance(colors[0], Number): color_norm = matplotlib.colors.Normalize(vmin=min(colors), vmax=max(colors)) @@ -529,7 +502,7 @@ def showCrossProjection(ensemble, mode_x, mode_y, scale=None, *args, **kwargs): :keyword scalar: scalar factor for projection onto selected mode :type scalar: float - :keyword color: a color name or a list of color name, default is ``'blue'`` + :keyword color: a color spec or a list of color specs, default is ``'blue'`` :type color: str, list :keyword label: label or a list of labels @@ -578,13 +551,6 @@ def showCrossProjection(ensemble, mode_x, mode_y, scale=None, *args, **kwargs): raise TypeError('marker must be a string or a list') colors = kwargs.pop('color', 'blue') - if isinstance(colors, str) or colors is None: - colors = [colors] * num - elif isinstance(colors, list): - if len(colors) != num: - raise ValueError('length of color must be {0}'.format(num)) - else: - raise TypeError('color must be a string or a list') labels = kwargs.pop('label', None) if isinstance(labels, str) or labels is None: @@ -597,21 +563,7 @@ def showCrossProjection(ensemble, mode_x, mode_y, scale=None, *args, **kwargs): kwargs['ls'] = kwargs.pop('linestyle', None) or kwargs.pop('ls', 'None') - colors_dict = {} - if isinstance(colors, np.ndarray): - colors = tuple(colors) - if isinstance(colors, (str, tuple)) or colors is None: - colors = [colors] * num - elif isinstance(colors, list): - if len(colors) != num: - raise ValueError('length of color must be {0}'.format(num)) - elif isinstance(colors, dict): - if labels is None: - raise TypeError('color must be a string or a list unless labels are provided') - colors_dict = colors - colors = [colors_dict[label] for label in labels] - else: - raise TypeError('color must be a string or a list or a dict if labels are provided') + colors, colors_dict = checkColors(colors, num, labels) if labels is not None and len(colors_dict) == 0: cycle_colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] @@ -2403,3 +2355,44 @@ def showTree_networkx(tree, node_size=20, node_color='red', node_shape='o', showFigure() return mpl.gca() + + +def checkColors(colors, num, labels, allowNumbers=False): + """Check colors and process them if needed""" + + colors_dict = {} + + if isinstance(colors, np.ndarray): + colors = tuple(colors) + + if is_color_like(colors) or colors is None: + colors = [colors] * num + elif isListLike(colors): + colors = list(colors) + + if isinstance(colors, list): + if len(colors) != num and not is_color_like(colors): + raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') + + if np.any([not is_color_like(color) for color in colors]): + if not allowNumbers: + raise ValueError('each element of colors should satisfy matplotlib color rules') + elif np.any([not isinstance(color, Number) for color in colors]): + raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') + + elif isinstance(colors, dict): + if labels is None: + raise TypeError('color must be a string or a list unless labels are provided') + colors_dict = colors + colors = [colors_dict[label] for label in labels] + + if np.any([not is_color_like(color) for color in colors]): + if not allowNumbers: + raise ValueError('each element of colors should satisfy matplotlib color rules') + elif np.any([not isinstance(color, Number) for color in colors]): + raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') + + elif not (isinstance(colors, Number) or is_color_like(colors)): + raise TypeError('color must be a number, string, list, matplotlib color spec, or a dict if labels are provided') + + return colors, colors_dict From 85e7c5fd3fb185728d1d9ededc771971b1646a92 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 15:28:51 +0200 Subject: [PATCH 05/11] import is_color_like in checkColors --- prody/dynamics/plotting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index dc72c14e0..94142afc5 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -7,7 +7,6 @@ and keyword arguments are passed to the Matplotlib functions.""" from collections import defaultdict -from matplotlib.colors import is_color_like from numbers import Number import numpy as np @@ -2360,6 +2359,8 @@ def showTree_networkx(tree, node_size=20, node_color='red', node_shape='o', def checkColors(colors, num, labels, allowNumbers=False): """Check colors and process them if needed""" + from matplotlib.colors import is_color_like + colors_dict = {} if isinstance(colors, np.ndarray): From a9ccb56c0593c68070036be44609251c45cbb958 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 15:53:45 +0200 Subject: [PATCH 06/11] improve checks --- prody/dynamics/plotting.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 94142afc5..cdbbe295a 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -2360,7 +2360,7 @@ def checkColors(colors, num, labels, allowNumbers=False): """Check colors and process them if needed""" from matplotlib.colors import is_color_like - + colors_dict = {} if isinstance(colors, np.ndarray): @@ -2371,29 +2371,26 @@ def checkColors(colors, num, labels, allowNumbers=False): elif isListLike(colors): colors = list(colors) - if isinstance(colors, list): - if len(colors) != num and not is_color_like(colors): - raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') - - if np.any([not is_color_like(color) for color in colors]): - if not allowNumbers: - raise ValueError('each element of colors should satisfy matplotlib color rules') - elif np.any([not isinstance(color, Number) for color in colors]): - raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') - - elif isinstance(colors, dict): + if isinstance(colors, dict): if labels is None: raise TypeError('color must be a string or a list unless labels are provided') colors_dict = colors colors = [colors_dict[label] for label in labels] + if isinstance(colors, list): + if len(colors) != num and not is_color_like(colors): + raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') + if np.any([not is_color_like(color) for color in colors]): if not allowNumbers: raise ValueError('each element of colors should satisfy matplotlib color rules') elif np.any([not isinstance(color, Number) for color in colors]): raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') - elif not (isinstance(colors, Number) or is_color_like(colors)): + if len(colors) > 1 and np.any([not isinstance(color, type(colors[0])) for color in colors]): + raise TypeError('each element of colors should have the same type') + + elif not ((allowNumbers and isinstance(colors, Number)) or is_color_like(colors)): raise TypeError('color must be a number, string, list, matplotlib color spec, or a dict if labels are provided') return colors, colors_dict From 6e8af98b1ffdf9c3f5b5f638a44ff0d789894cd4 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 18:25:08 +0200 Subject: [PATCH 07/11] add color-def to showCrossProj docs --- prody/dynamics/plotting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index cdbbe295a..57316ed9c 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -502,6 +502,7 @@ def showCrossProjection(ensemble, mode_x, mode_y, scale=None, *args, **kwargs): :type scalar: float :keyword color: a color spec or a list of color specs, default is ``'blue'`` + See https://matplotlib.org/stable/users/explain/colors/colors.html#colors-def :type color: str, list :keyword label: label or a list of labels From ff6384a05a0300f1a15dcb2bf25ecb8d0b7fb6e7 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Mon, 29 Jul 2024 19:14:26 +0200 Subject: [PATCH 08/11] improvements --- prody/dynamics/plotting.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 57316ed9c..2d1b29649 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -2364,22 +2364,18 @@ def checkColors(colors, num, labels, allowNumbers=False): colors_dict = {} - if isinstance(colors, np.ndarray): - colors = tuple(colors) - - if is_color_like(colors) or colors is None: + if is_color_like(colors) or colors is None or isinstance(colors, Number): colors = [colors] * num elif isListLike(colors): colors = list(colors) - - if isinstance(colors, dict): + elif isinstance(colors, dict): if labels is None: - raise TypeError('color must be a string or a list unless labels are provided') + raise TypeError('color cannot be a dict unless labels are provided') colors_dict = colors colors = [colors_dict[label] for label in labels] if isinstance(colors, list): - if len(colors) != num and not is_color_like(colors): + if len(colors) != num: raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') if np.any([not is_color_like(color) for color in colors]): @@ -2388,10 +2384,9 @@ def checkColors(colors, num, labels, allowNumbers=False): elif np.any([not isinstance(color, Number) for color in colors]): raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') - if len(colors) > 1 and np.any([not isinstance(color, type(colors[0])) for color in colors]): + if np.any([not isinstance(color, type(colors[0])) for color in colors]): raise TypeError('each element of colors should have the same type') - - elif not ((allowNumbers and isinstance(colors, Number)) or is_color_like(colors)): - raise TypeError('color must be a number, string, list, matplotlib color spec, or a dict if labels are provided') + else: + raise TypeError('colors should be a colour spec or a list of color specs') return colors, colors_dict From dc10bca4a688eaf902ee62d25f9ed85f3938e996 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Wed, 31 Jul 2024 17:48:43 +0200 Subject: [PATCH 09/11] add for loop --- prody/dynamics/plotting.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 2d1b29649..995b0455e 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -2378,15 +2378,16 @@ def checkColors(colors, num, labels, allowNumbers=False): if len(colors) != num: raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') - if np.any([not is_color_like(color) for color in colors]): - if not allowNumbers: - raise ValueError('each element of colors should satisfy matplotlib color rules') - elif np.any([not isinstance(color, Number) for color in colors]): - raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') - - if np.any([not isinstance(color, type(colors[0])) for color in colors]): - raise TypeError('each element of colors should have the same type') + for color in colors: + if not is_color_like(color): + if not allowNumbers: + raise ValueError('each element of colors should satisfy matplotlib color rules') + elif not isinstance(color, Number): + raise ValueError('each element of colors should be a number or satisfy matplotlib color rules') + + if not isinstance(color, type(colors[0])): + raise TypeError('each element of colors should have the same type') else: - raise TypeError('colors should be a colour spec or a list of color specs') + raise TypeError('colors should be a colour spec or convertible to a list of color specs') return colors, colors_dict From fdfb2ae0d4a826768fe3fb3280b9a76b04c5c06b Mon Sep 17 00:00:00 2001 From: James Krieger Date: Wed, 31 Jul 2024 18:34:26 +0200 Subject: [PATCH 10/11] check for None below --- prody/dynamics/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 995b0455e..59d2afac8 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -2379,7 +2379,7 @@ def checkColors(colors, num, labels, allowNumbers=False): raise ValueError('colors should have the length of the set to be colored or satisfy matplotlib color rules') for color in colors: - if not is_color_like(color): + if not is_color_like(color) and color is not None: if not allowNumbers: raise ValueError('each element of colors should satisfy matplotlib color rules') elif not isinstance(color, Number): From 3453d212d4e31137ee7d90274e07173660417d03 Mon Sep 17 00:00:00 2001 From: James Krieger Date: Wed, 31 Jul 2024 18:35:11 +0200 Subject: [PATCH 11/11] colour to color --- prody/dynamics/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prody/dynamics/plotting.py b/prody/dynamics/plotting.py index 59d2afac8..ee0252796 100644 --- a/prody/dynamics/plotting.py +++ b/prody/dynamics/plotting.py @@ -2388,6 +2388,6 @@ def checkColors(colors, num, labels, allowNumbers=False): if not isinstance(color, type(colors[0])): raise TypeError('each element of colors should have the same type') else: - raise TypeError('colors should be a colour spec or convertible to a list of color specs') + raise TypeError('colors should be a color spec or convertible to a list of color specs') return colors, colors_dict