Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example: Plot drug binding hotspots w/ NGLView #62

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 119 additions & 9 deletions contact_map/plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,126 @@
try: # try loop for testing
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.colors import LinearSegmentedColormap, Normalize
except ImportError: # pragma: no cover
pass

def _get_cmap(cmap):
"""Get the appropriate matplotlib colormap.

If the input is a string, it finds the associated matplotlib ColorMap
object. If input is already a ColorMap, it is passed through.
"""
if isinstance(cmap, str):
cmap_f = plt.get_cmap(cmap)
else:
cmap_f = cmap

return cmap_f


def _make_norm(rescale_range='auto'):
"""Create the matplotlib Normalize object
"""
if rescale_range is None:
norm = Normalize(vmin=0, vmax=1, clip=True)
elif rescale_range == 'auto':
norm = Normalize()
elif isinstance(rescale_range, Normalize):
norm = rescale_range
else:
vmin, vmax = rescale_range
norm = Normalize(vmin, vmax, clip=True)

return norm


def rgb_colors(data, cmap, rescale_range='auto', bytes=False):
"""RGB values representing colors after applying a colormap to data.

Parameters
----------
data : array-like
Input data, should be 1-dimensional
cmap : str or matplotlib.colors.Colormap
Colormap to use
rescale_range : matplotlib.colors.Normalize or str or None or tuple
Defines the color normalization. If a ``Normalize`` object, that is
passed through. The string 'auto' is interpresent as
``Normalize()``. No other strings are currently allowed. ``None`` is
interpreted as ``Normalize(0, 1)``, meaning that the data is already
mapped to the correct values. A tuple should be a 2-tuple of (vmin,
vmax).
bytes : bool
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add slightly more documentation to this keyword (as it was relatively difficult to find in the matplotlib documentation as well)

"""
cmap_f = _get_cmap(cmap)
norm = _make_norm(rescale_range)
rescaled = norm(data)
colors = cmap_f(rescaled, bytes=bytes)
return colors


def int_colors(data, cmap, rescale_range='auto'):
"""Integers representing colors after applying a colormap to data.

Parameters
----------
data : array-like
Input data, should be 1-dimensional
cmap : str or matplotlib.colors.Colormap
Colormap to use
rescale_range : matplotlib.colors.Normalize or str or None or tuple
Defines the color normalization. If a ``Normalize`` object, that is
passed through. The string 'auto' is interpresent as
``Normalize()``. No other strings are currently allowed. ``None`` is
interpreted as ``Normalize(0, 1)``, meaning that the data is already
mapped to the correct values. A tuple should be a 2-tuple of (vmin,
vmax).

Returns
-------
list of int :
single integer representation of each color
"""
rgb = rgb_colors(data, cmap, rescale_range, bytes=True)
colors = [256*256*c[0] + 256*c[1] + c[2] for c in rgb]
return colors


def hex_colors(data, cmap, rescale_range='auto', style='web'):
"""Hex string representing colors after applying a colormap to data.

Parameters
----------
data : array-like
Input data, should be 1-dimensional
cmap : str or matplotlib.colors.Colormap
Colormap to use
rescale_range : matplotlib.colors.Normalize or str or None or tuple
Defines the color normalization. If a ``Normalize`` object, that is
passed through. The string 'auto' is interpresent as
``Normalize()``. No other strings are currently allowed. ``None`` is
interpreted as ``Normalize(0, 1)``, meaning that the data is already
mapped to the correct values. A tuple should be a 2-tuple of (vmin,
vmax).
style : str
Which string representation to report. Valid values, with their
examples of how they would represent the color red:
* ``'web'``: ``#ff0000``
* ``'python'``: ``0xff0000``
* ``'raw'``: ``ff0000``

Returns
-------
list of str :
hexadecimal representation of each color
"""
prefix = {'web': '#', 'python': '0x', 'raw': ''}[style]
formatter = prefix + "{:06x}"
ints = int_colors(data, cmap, rescale_range)
return [formatter.format(c) for c in ints]


def ranged_colorbar(cmap, norm, cbmin, cbmax, name="Partial Map"):
"""Create a colorbar with given endpoints.

Expand All @@ -29,20 +145,14 @@ def ranged_colorbar(cmap, norm, cbmin, cbmax, name="Partial Map"):
a colorbar restricted to the range given by cbmin, cbmax
"""
# see https://stackoverflow.com/questions/24746231
if isinstance(cmap, str):
cmap_f = plt.get_cmap(cmap)
else:
cmap_f = cmap
cmap_f = _get_cmap(cmap)
cbmin_normed = float(cbmin - norm.vmin) / (norm.vmax - norm.vmin)
cbmax_normed = float(cbmax - norm.vmin) / (norm.vmax - norm.vmin)
n_colors = int(round((cbmax_normed - cbmin_normed) * cmap_f.N))
colors = cmap_f(np.linspace(cbmin_normed, cbmax_normed, n_colors))
new_cmap = LinearSegmentedColormap.from_list(name="Partial Map",
colors=colors)
new_cmap = LinearSegmentedColormap.from_list(name=name, colors=colors)
new_norm = matplotlib.colors.Normalize(vmin=cbmin, vmax=cbmax)
sm = plt.cm.ScalarMappable(cmap=new_cmap, norm=new_norm)
sm._A = []
cb = plt.colorbar(sm)
return cb


69 changes: 63 additions & 6 deletions contact_map/tests/test_plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,69 @@
from .utils import *

from contact_map.plot_utils import *
from contact_map.plot_utils import _get_cmap, _make_norm

@pytest.mark.parametrize("cmap_", ["seismic", "cmap"])
def test_get_cmap(cmap_):
matplotlib = pytest.importorskip("matplotlib")
cmapf_f_real = matplotlib.pyplot.get_cmap('seismic')
if cmap_ == "cmap":
cmap_ = cmapf_f_real


cmap = _get_cmap(cmap_)
assert cmap is cmapf_f_real

@pytest.mark.parametrize("rescale_range", [None, 'auto', 'tuple', 'mpl'])
def test_make_norm(rescale_range):
matplotlib = pytest.importorskip("matplotlib")
vmin, vmax, clip = {None: (0, 1, True),
'auto': (None, None, False),
'tuple': (-1, 1, True),
'mpl': (-1, 1, False)}[rescale_range]
expected = matplotlib.colors.Normalize(vmin, vmax, clip)
if rescale_range == 'tuple':
rescale_range = vmin, vmax
elif rescale_range == 'mpl':
rescale_range = expected
norm = _make_norm(rescale_range)
assert norm.vmin == expected.vmin
assert norm.vmax == expected.vmax
assert norm.clip == expected.clip
if rescale_range == 'mpl':
assert norm is expected

@pytest.mark.parametrize('result_type', ['bytes', 'float'])
def test_rgb_colors(result_type):
matplotlib = pytest.importorskip("matplotlib")
values = [0.0, 0.5, 1.0]
use_bytes = {'bytes': True, 'float': False}[result_type]
expected = [(0, 0, 255, 255),
(255, 254, 254, 255), # bwr gives *almost* white at 0.5
(255, 0, 0, 255)]
if not use_bytes:
expected = (np.array(expected) / 255.0).tolist()

colors = rgb_colors(values, cmap='bwr', bytes=use_bytes)
assert_allclose(colors, expected)

def test_int_colors():
matplotlib = pytest.importorskip("matplotlib")
values = [0.0, 0.5, 1.0]
expected = [255, 16776958, 16711680]
colors = int_colors(values, cmap='bwr')
for c, e in zip(colors, expected):
assert c == e

@pytest.mark.parametrize('style', ['raw', 'web', 'python'])
def test_hex_colors(style):
matplotlib = pytest.importorskip("matplotlib")
values = [0.0, 0.5, 1.0]
expected = ['0000ff', 'fffefe', 'ff0000']
prefix = {'raw': '', 'web': '#', 'python': '0x'}[style]
colors = hex_colors(values, cmap='bwr', style=style)
for c, e in zip(colors, expected):
assert c == prefix + e


@pytest.mark.parametrize("val", [0.5, 0.55, 0.6, 0.65, 0.7])
Expand All @@ -26,9 +89,3 @@ def test_ranged_colorbar_cmap(map_type, val):
assert np.allclose([atol], [0.0390625]) # to remind that this is small
assert_allclose(cb.cmap(cb.norm(val)), default_cmap(norm(val)),
atol=atol)






4 changes: 4 additions & 0 deletions docs/_static/fix_pygments.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* somehow, pygments CSS chokes when we have NGLView's CSS around.
* Here are some fixes. */
div.input_area div.highlight { color: #000000 }
div.output_area div.highlight { color: #000000 }
11 changes: 10 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import sphinx_rtd_theme
import pkg_resources
import packaging.version
from ipywidgets.embed import DEFAULT_EMBED_REQUIREJS_URL

# sys.path.insert(0, os.path.abspath('.'))
#sys.path.insert(0, os.path.abspath('../contact_map/'))
sys.path.append(os.path.abspath('_themes'))
Expand Down Expand Up @@ -124,7 +126,7 @@
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store',
'**ipynb-checkpoints']
'**.ipynb_checkpoints']

# The reST default role (used for this markup: `text`) to use for all
# documents.
Expand Down Expand Up @@ -206,6 +208,13 @@
#
# html_extra_path = []

html_js_files = [
'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js',
DEFAULT_EMBED_REQUIREJS_URL,
]

html_css_files = ["fix_pygments.css"]

# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
Expand Down
Binary file added examples/GSK3B_contacts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading