Source code for xontrib.mplhooks
"""Matplotlib hooks, for what its worth."""
from io import BytesIO
import shutil
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from xonsh.tools import print_color, ON_WINDOWS
from xonsh.built_ins import XSH
try:
# Use iterm2_tools as an indicator for the iterm2 terminal emulator
from iterm2_tools.images import display_image_bytes
except ImportError:
_use_iterm = False
else:
_use_iterm = True
XONTRIB_MPL_MINIMAL_DEFAULT = True
def _get_buffer(fig, **kwargs):
b = BytesIO()
fig.savefig(b, **kwargs)
b.seek(0)
return b
[docs]def figure_to_rgb_array(fig, shape=None):
"""Converts figure to a numpy array
Parameters
----------
fig : matplotlib.figure.Figure
the figure to be plotted
shape : iterable
with the shape of the output array. by default this attempts to use the
pixel height and width of the figure
Returns
-------
array : np.ndarray
An RGBA array of the image represented by the figure.
Note: the method will throw an exception if the given shape is wrong.
"""
array = np.frombuffer(
_get_buffer(fig, dpi=fig.dpi, format="raw").read(), dtype="uint8"
)
if shape is None:
w, h = fig.canvas.get_width_height()
shape = (h, w, 4)
return array.reshape(*shape)
[docs]def figure_to_tight_array(fig, width, height, minimal=True):
"""Converts figure to a numpy array of rgb values of tight value
Parameters
----------
fig : matplotlib.figure.Figure
the figure to be plotted
width : int
pixel width of the final array
height : int
pixel height of the final array
minimal : bool
whether or not to reduce the output array to minimized margins/whitespace
text is also eliminated
Returns
-------
array : np.ndarray
An RGBA array of the image represented by the figure.
"""
# store the properties of the figure in order to restore it
w, h = fig.canvas.get_width_height()
dpi_fig = fig.dpi
if minimal:
# perform reversible operations to produce an optimally tight layout
dpi = dpi_fig
subplotpars = {
k: getattr(fig.subplotpars, k)
for k in ["wspace", "hspace", "bottom", "top", "left", "right"]
}
# set the figure dimensions to the terminal size
fig.set_size_inches(width / dpi, height / dpi, forward=True)
width, height = fig.canvas.get_width_height()
# remove all space between subplots
fig.subplots_adjust(wspace=0, hspace=0)
# move all subplots to take the entirety of space in the figure
# leave only one line for top and bottom
fig.subplots_adjust(bottom=1 / height, top=1 - 1 / height, left=0, right=1)
# reduce font size in order to reduce text impact on the image
font_size = matplotlib.rcParams["font.size"]
matplotlib.rcParams.update({"font.size": 0})
else:
dpi = min([width * fig.dpi // w, height * fig.dpi // h])
fig.dpi = dpi
width, height = fig.canvas.get_width_height()
# Draw the renderer and get the RGB buffer from the figure
array = figure_to_rgb_array(fig, shape=(height, width, 4))
if minimal:
# cleanup after tight layout
# clean up rcParams
matplotlib.rcParams.update({"font.size": font_size})
# reset the axis positions and figure dimensions
fig.set_size_inches(w / dpi, h / dpi, forward=True)
fig.subplots_adjust(**subplotpars)
else:
fig.dpi = dpi_fig
return array
[docs]def buf_to_color_str(buf):
"""Converts an RGB array to a xonsh color string."""
space = " "
pix = "{{bg#{0:02x}{1:02x}{2:02x}}} "
pixels = []
for h in range(buf.shape[0]):
last = None
for w in range(buf.shape[1]):
rgb = buf[h, w]
if last is not None and (last == rgb).all():
pixels.append(space)
else:
pixels.append(pix.format(*rgb))
last = rgb
pixels.append("{RESET}\n")
pixels[-1] = pixels[-1].rstrip()
return "".join(pixels)
[docs]def display_figure_with_iterm2(fig):
"""Displays a matplotlib figure using iterm2 inline-image escape sequence.
Parameters
----------
fig : matplotlib.figure.Figure
the figure to be plotted
"""
print(display_image_bytes(_get_buffer(fig, format="png", dpi=fig.dpi).read()))
[docs]def show():
"""Run the mpl display sequence by printing the most recent figure to console"""
try:
minimal = XSH.env["XONTRIB_MPL_MINIMAL"]
except KeyError:
minimal = XONTRIB_MPL_MINIMAL_DEFAULT
fig = plt.gcf()
if _use_iterm:
display_figure_with_iterm2(fig)
else:
# Display the image using terminal characters to fit into the console
w, h = shutil.get_terminal_size()
if ON_WINDOWS:
w -= 1 # @melund reports that win terminals are too thin
h -= 1 # leave space for next prompt
buf = figure_to_tight_array(fig, w, h, minimal)
s = buf_to_color_str(buf)
print_color(s)