Handle axis SI prefix scaling in MatplotlibExporter (#1282)

* Handle axis SI prefix scaling in MatplotlibExporter

* Added some MatplotlibExporter tests and added matplotlib to CI deps

* Install mpl with pip instead of conda

* Cleanup
This commit is contained in:
Kenneth Lyons 2020-06-28 08:51:34 -07:00 committed by GitHub
parent 96d1ef986f
commit 4110b3e539
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 50 deletions

View File

@ -106,8 +106,9 @@ jobs:
fi
conda info
conda install $(qt.bindings) numpy scipy pyopengl h5py six --yes --quiet
pip install matplotlib
else
pip install $(qt.bindings) numpy scipy pyopengl h5py six
pip install $(qt.bindings) numpy scipy pyopengl h5py six matplotlib
fi
pip install pytest pytest-cov coverage pytest-xdist
if [ $(python.version) == "2.7" ]

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore
from .Exporter import Exporter
from .. import PlotItem
@ -31,6 +32,7 @@ publication. Fonts are not vectorized (outlined), and window colors are white.
class MatplotlibExporter(Exporter):
Name = "Matplotlib Window"
windows = []
def __init__(self, item):
Exporter.__init__(self, item)
@ -55,58 +57,70 @@ class MatplotlibExporter(Exporter):
ax.xaxis.set_ticks_position('bottom')
def export(self, fileName=None):
if not isinstance(self.item, PlotItem):
raise Exception("MatplotlibExporter currently only works with PlotItem")
mpw = MatplotlibWindow()
MatplotlibExporter.windows.append(mpw)
fig = mpw.getFigure()
xax = self.item.getAxis('bottom')
yax = self.item.getAxis('left')
if isinstance(self.item, PlotItem):
mpw = MatplotlibWindow()
MatplotlibExporter.windows.append(mpw)
# get labels from the graphic item
xlabel = xax.label.toPlainText()
ylabel = yax.label.toPlainText()
title = self.item.titleLabel.text
stdFont = 'Arial'
fig = mpw.getFigure()
# get labels from the graphic item
xlabel = self.item.axes['bottom']['item'].label.toPlainText()
ylabel = self.item.axes['left']['item'].label.toPlainText()
title = self.item.titleLabel.text
# if axes use autoSIPrefix, scale the data so mpl doesn't add its own
# scale factor label
xscale = yscale = 1.0
if xax.autoSIPrefix:
xscale = xax.autoSIPrefixScale
if yax.autoSIPrefix:
yscale = yax.autoSIPrefixScale
ax = fig.add_subplot(111, title=title)
ax.clear()
self.cleanAxes(ax)
#ax.grid(True)
for item in self.item.curves:
x, y = item.getData()
opts = item.opts
pen = fn.mkPen(opts['pen'])
if pen.style() == QtCore.Qt.NoPen:
linestyle = ''
else:
linestyle = '-'
color = tuple([c/255. for c in fn.colorTuple(pen.color())])
symbol = opts['symbol']
if symbol == 't':
symbol = '^'
symbolPen = fn.mkPen(opts['symbolPen'])
symbolBrush = fn.mkBrush(opts['symbolBrush'])
markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())])
markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())])
markersize = opts['symbolSize']
if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
fillBrush = fn.mkBrush(opts['fillBrush'])
fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())])
ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(),
linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor,
markersize=markersize)
xr, yr = self.item.viewRange()
ax.set_xbound(*xr)
ax.set_ybound(*yr)
ax.set_xlabel(xlabel) # place the labels.
ax.set_ylabel(ylabel)
mpw.draw()
else:
raise Exception("Matplotlib export currently only works with plot items")
ax = fig.add_subplot(111, title=title)
ax.clear()
self.cleanAxes(ax)
for item in self.item.curves:
x, y = item.getData()
x = x * xscale
y = y * yscale
opts = item.opts
pen = fn.mkPen(opts['pen'])
if pen.style() == QtCore.Qt.NoPen:
linestyle = ''
else:
linestyle = '-'
color = tuple([c/255. for c in fn.colorTuple(pen.color())])
symbol = opts['symbol']
if symbol == 't':
symbol = '^'
symbolPen = fn.mkPen(opts['symbolPen'])
symbolBrush = fn.mkBrush(opts['symbolBrush'])
markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())])
markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())])
markersize = opts['symbolSize']
if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
fillBrush = fn.mkBrush(opts['fillBrush'])
fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())])
ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(),
linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor,
markersize=markersize)
xr, yr = self.item.viewRange()
ax.set_xbound(xr[0]*xscale, xr[1]*xscale)
ax.set_ybound(yr[0]*yscale, yr[1]*yscale)
ax.set_xlabel(xlabel) # place the labels.
ax.set_ylabel(ylabel)
mpw.draw()
MatplotlibExporter.register()

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
import pytest
import pyqtgraph as pg
from pyqtgraph.exporters import MatplotlibExporter
pytest.importorskip("matplotlib")
app = pg.mkQApp()
def test_MatplotlibExporter():
plt = pg.plot()
# curve item
plt.plot([0, 1, 2], [0, 1, 2])
# scatter item
plt.plot([0, 1, 2], [1, 2, 3], pen=None, symbolBrush='r')
# curve + scatter
plt.plot([0, 1, 2], [2, 3, 4], pen='k', symbolBrush='r')
exp = MatplotlibExporter(plt.getPlotItem())
exp.export()
def test_MatplotlibExporter_nonplotitem():
# attempting to export something other than a PlotItem raises an exception
plt = pg.plot()
plt.plot([0, 1, 2], [2, 3, 4])
exp = MatplotlibExporter(plt.getPlotItem().getViewBox())
with pytest.raises(Exception):
exp.export()
@pytest.mark.parametrize('scale', [1e10, 1e-9])
def test_MatplotlibExporter_siscale(scale):
# coarse test to verify that plot data is scaled before export when
# autoSIPrefix is in effect (so mpl doesn't add its own multiplier label)
plt = pg.plot([0, 1, 2], [(i+1)*scale for i in range(3)])
# set the label so autoSIPrefix works
plt.setLabel('left', 'magnitude')
exp = MatplotlibExporter(plt.getPlotItem())
exp.export()
mpw = MatplotlibExporter.windows[-1]
fig = mpw.getFigure()
ymin, ymax = fig.axes[0].get_ylim()
if scale < 1:
assert ymax > scale
else:
assert ymax < scale