How to export data errorbars to matplotlib window?

91 views
Skip to first unread message

Trifon Trifonov

unread,
Jan 7, 2019, 7:37:54 AM1/7/19
to pyqtgraph

Hello, this is a duplicate question from the git hub repository:
https://github.com/pyqtgraph/pyqtgraph/issues/804


After some problems I reported earlier I finally made the matplotlib exporter to work for me.

However, data are exported without errorbars. See screenshot:


in /exporters/Matplotlib.py

one can see:


                pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), 
                        linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor,
                        markersize=markersize)


which probably needs to be changed to:

                pl = ax.errorbar(x, y, yerr=yerr, marker=symbol, color=color, linewidth=pen.width(), 
                        linestyle=linestyle, 
                        markeredgecolor=markeredgecolor, 
                        markerfacecolor=markerfacecolor,
                        markersize=markersize)
 

but how do I get the yerr values within the Matplotlib.py?

BTW, errorbars were plotted in pyqtgraph with (e.g. part of my code):

            err1 = pg.ErrorBarItem(x=fit.fit_results.rv_model.jd[fit.filelist.idset==i], 
                                   y=fit.fit_results.rv_model.rvs[fit.filelist.idset==i],symbol='o', 
            height=error_list[fit.filelist.idset==i], beam=0.0, pen=colors[i])  

            p1.addItem(err1)  

Any ideas?

Thanks a lot!
Trifon
50728846-90fb4700-1130-11e9-9e51-ada5b72b690c.png

Patrick

unread,
Jan 16, 2019, 11:14:40 PM1/16/19
to pyqtgraph
Hi,

If you have a handle to the pyqtgraph ErrorBarItem, then you can extract the error bar lengths from the opts dictionary (http://www.pyqtgraph.org/documentation/_modules/pyqtgraph/graphicsItems/ErrorBarItem.html#ErrorBarItem). You might need to do a bit of code since opts["height"] is supposed to override opts["top"] and opts["bottom"] etc.

This probably isn't helpful, but in my code base I have written a separate "plots.py" with various matplotlib routines to generate custom plot figures/files since it is simple enough, gives much more flexible/customisable plots, and bypasses limitations of pyqtgraph exports.

Patrick

Trifon Trifonov

unread,
Jan 17, 2019, 9:46:48 AM1/17/19
to pyqtgraph

Dear Patrick,

Thank you for your answer! Can you please provide one very minimal example how you did it!
It will help me to accelerate a bit.



Thanks!
Trifon

Patrick

unread,
Jan 17, 2019, 7:41:57 PM1/17/19
to pyqtgraph
Hi,

Sure, here's a working example. Obviously a very simple plot layout, but you can fancy it up as required:

test_matplotlib.py
#!/usr/bin/env python3

import sys
from PyQt5 import QtCore, QtWidgets, uic
from PyQt5.QtWidgets import QFileDialog
import numpy as np
import pyqtgraph as pg
import test_matplotlib_plots as plots


class PlotWindow(QtWidgets.QMainWindow):

   
def __init__(self):
       
super().__init__()

        pg
.setConfigOptions(antialias=True)

       
# Load GUI layout from .ui file (named same as this source file, but with .ui extension)
        uic
.loadUi(__file__.split('.py')[0] + '.ui', self)

       
# Finish up the GUI and connections
       
self.statusbar.showMessage("Welcome!")
       
self.actionExport.triggered.connect(self.export_plot)
       
self.plot = self.glw.addPlot()

       
# Make some test data
       
self.xcoords = np.arange(0, 100, 3)
       
self.data = 50.0*np.random.random((5, self.xcoords.shape[0]))
       
self.data += 1.0 + 2.0*self.xcoords

       
# Plot test data
       
self.errorbars = pg.ErrorBarItem(x=self.xcoords, y=np.mean(self.data, axis=0), height=np.std(self.data, axis=0), beam=1)
       
self.plot.addItem(self.errorbars)
       
self.plot.plot(x=self.xcoords, y=np.mean(self.data, axis=0), symbol="o")
       
self.plot.setLabels(bottom="Time, (s)", left="Intensity (a.u.)")

   
def export_plot(self):
       
self.statusbar.showMessage('Exporting plot...')
        filename
, _ = QFileDialog.getSaveFileName(self, "Export As...", "", "PDF Image (*.pdf);;All Files (*)")
       
if filename:
           
# Remove any duplicated file extensions that Qt might add
            filename
= filename.partition(".pdf")[0] + ".pdf"
            plots
.testdata_image(self.xcoords, self.data, filename, label="Test data", xlim=self.plot.viewRange()[0], ylim=self.plot.viewRange()[1])
           
self.statusbar.showMessage("Data saved to {}.".format(filename))
       
else:
           
self.statusbar.showMessage("Export cancelled.")

def main():
    app
= QtWidgets.QApplication(sys.argv)
    mainwindow
= PlotWindow()
    mainwindow
.show()
    sys
.exit(app.exec_())


if __name__ == '__main__':
    main
()

test_matplotlib_plots.py
import numpy as np
import matplotlib.pyplot as plt
from cycler import cycler
from matplotlib.ticker import AutoMinorLocator


def testdata_figure(xcoords, data, **kwargs):
   
"""Plot data, returning a matplotlib figure object."""

    args
= {"xlim": None,
           
"ylim": None,
           
"label": "",
           
"xlabel": "Time (s)",
           
"ylabel": "Intensity (a.u.)",
           
"ccycle": ['#0000a0', '#a00000', '#00a000', '#000000', '#a000a0', '#a0a000', '#00a0a0', '#808080'],
           
"figsize": (5.0, 3.75),
           
"legendloc": "best"}
    args
.update(kwargs)

   
# Configure figure, axes
    fig
, ax = plt.subplots(figsize=args["figsize"])
    ax
.set_prop_cycle(cycler('color', args["ccycle"]))
    ax
.set_xlabel(args["xlabel"])
    ax
.set_ylabel(args["ylabel"])
   
if args["xlim"] is not None:
        ax
.set_xlim(args["xlim"])
   
if args["ylim"] is not None:
        ax
.set_ylim(args["ylim"])
    ax
.xaxis.set_minor_locator(AutoMinorLocator(2))
    ax
.yaxis.set_minor_locator(AutoMinorLocator(2))

    ax
.errorbar(xcoords, np.mean(data, axis=0), yerr=np.std(data, axis=0), capsize=3, label=args["label"])

    ax
.legend(fontsize='medium', frameon=False, loc=args["legendloc"])

    plt
.tight_layout()
   
return fig

def testdata_image(xcoords, data, output_filename, **kwargs):
   
"""Plot data to an image file."""

    args
= {"dpi": 300}
    args
.update(kwargs)
    fig
= testdata_figure(xcoords, data, **kwargs)
    fig
.savefig(output_filename, dpi=args["dpi"])
    plt
.close(fig)

test_matplotlib.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 
<class>MainWindow</class>
 
<widget class="QMainWindow" name="MainWindow">
 
<property name="geometry">
   
<rect>
   
<x>0</x>
   
<y>0</y>
   
<width>800</width>
   
<height>600</height>
   
</rect>
 
</property>
 
<property name="windowTitle">
   
<string>Test MPL Export</string>
 
</property>
 
<widget class="QWidget" name="centralwidget">
   
<layout class="QHBoxLayout" name="horizontalLayout">
   
<item>
     
<widget class="GraphicsLayoutWidget" name="glw"/>
   
</item>
   
</layout>
 
</widget>
 
<widget class="QMenuBar" name="menubar">
   
<property name="geometry">
   
<rect>
     
<x>0</x>
     
<y>0</y>
     
<width>800</width>
     
<height>19</height>
   
</rect>
   
</property>
   
<widget class="QMenu" name="menuFile">
   
<property name="title">
     
<string>&amp;File</string>
   
</property>
   
<addaction name="actionExport"/>
   
<addaction name="separator"/>
   
<addaction name="actionExit"/>
   
</widget>
   
<addaction name="menuFile"/>
 
</widget>
 
<widget class="QStatusBar" name="statusbar"/>
 
<action name="actionExport">
   
<property name="text">
   
<string>&amp;Export plot...</string>
   
</property>
 
</action>
 
<action name="actionExit">
   
<property name="text">
   
<string>E&amp;xit</string>
   
</property>
 
</action>
 
</widget>
 
<customwidgets>
 
<customwidget>
   
<class>GraphicsLayoutWidget</class>
   
<extends>QGraphicsView</extends>
   
<header>pyqtgraph</header>
 
</customwidget>
 
</customwidgets>
 
<resources/>
 
<connections>
 
<connection>
   
<sender>actionExit</sender>
   
<signal>triggered()</signal>
   
<receiver>MainWindow</receiver>
   
<slot>close()</slot>
   
<hints>
   
<hint type="sourcelabel">
     
<x>-1</x>
     
<y>-1</y>
   
</hint>
   
<hint type="destinationlabel">
     
<x>399</x>
     
<y>299</y>
   
</hint>
   
</hints>
 
</connection>
 
</connections>
</ui>

Patrick

Trifon Trifonov

unread,
Jan 20, 2019, 4:01:38 AM1/20/19
to pyqtgraph
Dear Patrik,


Thank you so much for your code! It was really refreshing!

Meanwhile I sort out how to modify the "Matplotlib.py" exporter from pyqtgraph so
one can plot the errors on the "matplot gui window: for further plot customization.
 
Very minimal example (that so far works for me) is:

in "Matplotlib.py" add the following loop inside the  export() function:


   
def export(self, fileName=None):
       
       
if isinstance(self.item, PlotItem):
            mpw
= MatplotlibWindow()
           
MatplotlibExporter.windows.append(mpw)

            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

            ax
= fig.add_subplot(111, title=title)
            ax
.clear()
           
self.cleanAxes(ax)
           
#ax.grid(True)
 
           
for indx, item in enumerate(self.item.curves):
                x
, y = item.getData()
               
               
if x is None:
                   
continue

                opts
= item.opts
               
#print(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)

               
                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)
               
               
           
###### This is the simple hack: the loops looks for ErrorBarItem is inside "items" and plots them using the mpl errorbar()    
           
for indx, item in enumerate(self.item.items):
               
if indx <=1:
                   
continue
               
               
if "height" in self.item.items[indx].opts:
                         ax
.errorbar(x=self.item.items[indx].opts["x"], y=self.item.items[indx].opts["y"],
                                     yerr
=self.item.items[indx].opts["height"]/2., linestyle='', marker='o',markersize=0.5, linewidth=1.0,
                                     color
=self.item.items[indx].opts["pen"], capsize = 0, elinewidth=1,mew=0.0, zorder=-10)
                               
           
################### end of the hack ################################    
               
            ax
.set_xlabel(xlabel)  # place the labels.
            ax
.set_ylabel(ylabel)

            ax
.spines['top'].set_color('k')
            ax
.spines['right'].set_color('k')
                     
            mpw
.draw()
       
else:
           
raise Exception("Matplotlib export currently only works with plot items")

Fairly easy but one must be more familiar with the pyqtgraph architecture, while I am still learning,,,,,

However, there are still problems.

For example the "edit axis, curves and image parameters" option in the matplotlib gui window can modify only the
curves created with ax.plot() but not the errorbars introduced with ax.errorbar(). Thus, if one wants to change the color
can do it only for the data point but not for the erorrbar, which is annoying.

Also, some control on the axis labels and "dpi" would have been nice to have.


Hopefully the new pyqtgraph will warp the matplotlib more flexibly!

Thanks a lot again!
Trifon

                              
Reply all
Reply to author
Forward
0 new messages