mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
171 lines
5 KiB
Python
171 lines
5 KiB
Python
"""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
|
|
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
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
|
|
|
|
|
|
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("{NO_COLOR}\n")
|
|
pixels[-1] = pixels[-1].rstrip()
|
|
return "".join(pixels)
|
|
|
|
|
|
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()))
|
|
|
|
|
|
def show():
|
|
"""Run the mpl display sequence by printing the most recent figure to console"""
|
|
try:
|
|
minimal = __xonsh__.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)
|