xonsh/xontrib/mplhooks.py
Noorhteen Raja NJ 38295a1dd9
Remove globals (#4280)
* refactor: remove usage of global variables in abbrevs.py

* chore: add flake8-mutable to prevent mutable defaults

* fix: abbrevs expand test

* refactor: add xonsh session singleton

* refactor: fix circular errors when using xonshSession as singleton

* refactor: remove black magicked builtin attributes

* style: black format tests as well

* refactor: update tests to use xonsh-session singleton

* refactor: update abbrevs to not use builtins

* test: remove DummyCommandsCache and patch orig class

* fix: failing test_command_completers

* test: use monkeypatch to update xession fixture

* fix: failing test_pipelines

* fix: failing test_main

* chore: run test suit as single invocation

* test: fix tests/test_xonsh.xsh

* refactor: remove builtins from docs/conf.py

* fix: mypy error in jobs

* fix: test error from test_main

* test: close xession error in test_command_completers

* chore: use pytest-cov for reporting coverage

this will include subprocess calls, and will increase coverage

* style:
2021-05-20 13:14:26 +03:00

172 lines
5.1 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
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
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("{RESET}\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 = 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)