#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guiqwt/__init__.py for details)
"""
guiqwt.plot
-----------
The `plot` module provides the following features:
* :py:class:`guiqwt.plot.PlotManager`: the `plot manager` is an object to
link `plots`, `panels` and `tools` together for designing highly
versatile graphical user interfaces
* :py:class:`guiqwt.plot.CurveWidget`: a ready-to-use widget for curve
displaying with an integrated and preconfigured `plot manager` providing
the `item list panel` and curve-related `tools`
* :py:class:`guiqwt.plot.CurveDialog`: a ready-to-use dialog box for
curve displaying with an integrated and preconfigured `plot manager`
providing the `item list panel` and curve-related `tools`
* :py:class:`guiqwt.plot.ImageWidget`: a ready-to-use widget for curve
and image displaying with an integrated and preconfigured `plot manager`
providing the `item list panel`, the `contrast adjustment` panel, the
`cross section panels` (along X and Y axes) and image-related `tools`
(e.g. colormap selection tool)
* :py:class:`guiqwt.plot.ImageDialog`: a ready-to-use dialog box for
curve and image displaying with an integrated and preconfigured
`plot manager` providing the `item list panel`, the `contrast adjustment`
panel, the `cross section panels` (along X and Y axes) and image-related
`tools` (e.g. colormap selection tool)
.. seealso::
Module :py:mod:`guiqwt.curve`
Module providing curve-related plot items and plotting widgets
Module :py:mod:`guiqwt.image`
Module providing image-related plot items and plotting widgets
Module :py:mod:`guiqwt.tools`
Module providing the `plot tools`
Module :py:mod:`guiqwt.panels`
Module providing the `plot panels` IDs
Module :py:mod:`guiqwt.signals`
Module providing all the end-user Qt SIGNAL objects defined in `guiqwt`
Module :py:mod:`guiqwt.baseplot`
Module providing the `guiqwt` plotting widget base class
Class diagrams
~~~~~~~~~~~~~~
Curve-related widgets with integrated plot manager:
.. image:: images/curve_widgets.png
Image-related widgets with integrated plot manager:
.. image:: images/image_widgets.png
Building your own plot manager:
.. image:: images/my_plot_manager.png
Examples
~~~~~~~~
Simple example *without* the `plot manager`:
.. literalinclude:: ../guiqwt/tests/filtertest1.py
:start-after: SHOW
:end-before: Workaround for Sphinx v0.6 bug: empty 'end-before' directive
Simple example *with* the `plot manager`:
even if this simple example does not justify the use of the `plot manager`
(this is an unnecessary complication here), it shows how to use it. In more
complex applications, using the `plot manager` allows to design highly versatile
graphical user interfaces.
.. literalinclude:: ../guiqwt/tests/filtertest2.py
:start-after: SHOW
:end-before: Workaround for Sphinx v0.6 bug: empty 'end-before' directive
Reference
~~~~~~~~~
.. autoclass:: PlotManager
:members:
:inherited-members:
.. autoclass:: CurveWidget
:members:
.. autoclass:: CurveDialog
:members:
.. autoclass:: ImageWidget
:members:
.. autoclass:: ImageDialog
:members:
"""
import weakref
from guidata.qt.QtGui import (QDialogButtonBox, QVBoxLayout, QGridLayout,
QToolBar, QDialog, QHBoxLayout, QMenu,
QActionGroup, QSplitter, QSizePolicy,
QApplication, QWidget, QMainWindow)
from guidata.qt.QtCore import Qt, SIGNAL, SLOT
from guidata.configtools import get_icon
from guidata.utils import assert_interfaces_valid
from guidata.qthelpers import create_action
# Local imports
from guiqwt.config import _
from guiqwt.baseplot import BasePlot
from guiqwt.curve import CurvePlot, PlotItemList
from guiqwt.image import ImagePlot
from guiqwt.tools import (SelectTool, RectZoomTool, ColormapTool, HelpTool,
ReverseYAxisTool, BasePlotMenuTool, DeleteItemTool,
ItemListPanelTool, AntiAliasingTool, PrintTool,
DisplayCoordsTool, AxisScaleTool, SaveAsTool,
AspectRatioTool, ContrastPanelTool, XCSPanelTool,
YCSPanelTool, SnapshotTool, DummySeparatorTool,
CrossSectionTool, AverageCrossSectionTool,
ImageStatsTool, ExportItemDataTool, EditItemDataTool,
ItemCenterTool, SignalStatsTool, CopyToClipboardTool)
from guiqwt.interfaces import IPlotManager
from guiqwt.signals import (SIG_ITEMS_CHANGED, SIG_ACTIVE_ITEM_CHANGED,
SIG_VISIBILITY_CHANGED, SIG_PLOT_AXIS_CHANGED)
class DefaultPlotID(object):
pass
class PlotManager(object):
[docs] """
Construct a PlotManager object, a 'controller' that organizes relations
between plots (i.e. :py:class:`guiqwt.curve.CurvePlot` or
:py:class:`guiqwt.image.ImagePlot` objects), panels,
tools (see :py:mod:`guiqwt.tools`) and toolbars
"""
__implements__ = (IPlotManager,)
def __init__(self, main):
self.main = main # The main parent widget
self.plots = {} # maps ids to instances of BasePlot
self.panels = {} # Qt widgets that need to know about the plots
self.tools = []
self.toolbars = {}
self.active_tool = None
self.default_tool = None
self.default_plot = None
self.default_toolbar = None
self.synchronized_plots = {}
self.groups = {} # Action groups for grouping QActions
# Keep track of the registration sequence (plots, panels, tools):
self._first_tool_flag = True
def add_plot(self, plot, plot_id=DefaultPlotID):
[docs] """
Register a plot to the plot manager:
* plot: :py:class:`guiqwt.curve.CurvePlot` or
:py:class:`guiqwt.image.ImagePlot` object
* plot_id (default id is the plot object's id: ``id(plot)``):
unique ID identifying the plot (any Python object),
this ID will be asked by the manager to access this plot later.
Plot manager's registration sequence is the following:
1. add plots
2. add panels
3. add tools
"""
if plot_id is DefaultPlotID:
plot_id = id(plot)
assert plot_id not in self.plots
assert isinstance(plot, BasePlot)
assert not self.tools, "tools must be added after plots"
assert not self.panels, "panels must be added after plots"
self.plots[plot_id] = plot
if len(self.plots) == 1:
self.default_plot = plot
plot.set_manager(self, plot_id)
# Connecting signals
plot.connect(plot, SIG_ITEMS_CHANGED, self.update_tools_status)
plot.connect(plot, SIG_ACTIVE_ITEM_CHANGED, self.update_tools_status)
plot.connect(plot, SIG_PLOT_AXIS_CHANGED, self.plot_axis_changed)
def set_default_plot(self, plot):
[docs] """
Set default plot
The default plot is the plot on which tools and panels will act.
"""
self.default_plot = plot
def get_default_plot(self):
[docs] """
Return default plot
The default plot is the plot on which tools and panels will act.
"""
return self.default_plot
def add_panel(self, panel):
[docs] """
Register a panel to the plot manager
Plot manager's registration sequence is the following:
1. add plots
2. add panels
3. add tools
"""
assert panel.PANEL_ID not in self.panels
assert not self.tools, "tools must be added after panels"
self.panels[panel.PANEL_ID] = panel
panel.register_panel(self)
def configure_panels(self):
[docs] """
Return plot associated to `plot_id` (if method is called without
specifying the `plot_id` parameter, return the default plot)
"""
if plot_id is DefaultPlotID:
return self.default_plot
return self.plots[plot_id]
def get_plots(self):
[docs] """
Return all registered plots
"""
return self.plots.values()
def get_active_plot(self):
[docs] """
Return the active plot
The active plot is the plot whose canvas has the focus
otherwise it's the "default" plot
"""
for plot in self.plots.values():
canvas = plot.canvas()
if canvas.hasFocus():
return plot
return self.default_plot
def get_tool_group(self, groupname):
group = self.groups.get(groupname, None)
if group is None:
group = QActionGroup(self.main)
self.groups[groupname] = weakref.ref(group)
return group
else:
return group()
def get_main(self):
[docs] """
Return the main (parent) widget
Note that for py:class:`guiqwt.plot.CurveWidget` or
:py:class:`guiqwt.plot.ImageWidget` objects, this method will
return the widget itself because the plot manager is integrated to it.
"""
return self.main
def set_main(self, main):
self.main = main
def get_panel(self, panel_id):
[docs] """
Return panel from its ID
Panel IDs are listed in module guiqwt.panels
"""
return self.panels.get(panel_id, None)
def get_itemlist_panel(self):
[docs] """
Convenience function to get the `item list panel`
Return None if the item list panel has not been added to this manager
"""
from guiqwt import panels
return self.get_panel(panels.ID_ITEMLIST)
def get_contrast_panel(self):
[docs] """
Convenience function to get the `contrast adjustment panel`
Return None if the contrast adjustment panel has not been added
to this manager
"""
from guiqwt import panels
return self.get_panel(panels.ID_CONTRAST)
def set_contrast_range(self, zmin, zmax):
[docs] """
Convenience function to set the `contrast adjustment panel` range
This is strictly equivalent to the following::
# Here, *widget* is for example a CurveWidget instance
# (the same apply for CurvePlot, ImageWidget, ImagePlot or any
# class deriving from PlotManager)
widget.get_contrast_panel().set_range(zmin, zmax)
"""
self.get_contrast_panel().set_range(zmin, zmax)
def get_xcs_panel(self):
[docs] """
Convenience function to get the `X-axis cross section panel`
Return None if the X-axis cross section panel has not been added
to this manager
"""
from guiqwt import panels
return self.get_panel(panels.ID_XCS)
def get_ycs_panel(self):
[docs] """
Convenience function to get the `Y-axis cross section panel`
Return None if the Y-axis cross section panel has not been added
to this manager
"""
from guiqwt import panels
return self.get_panel(panels.ID_YCS)
def update_cross_sections(self):
[docs] """
Convenience function to update the `cross section panels` at once
This is strictly equivalent to the following::
# Here, *widget* is for example a CurveWidget instance
# (the same apply for CurvePlot, ImageWidget, ImagePlot or any
# class deriving from PlotManager)
widget.get_xcs_panel().update_plot()
widget.get_ycs_panel().update_plot()
"""
self.get_xcs_panel().update_plot()
self.get_ycs_panel().update_plot()
def get_toolbar(self, toolbar_id="default"):
shortcut=None, icon=None, tip=None):
return create_action(self.main, title, triggered=triggered,
toggled=toggled, shortcut=shortcut,
icon=icon, tip=tip)
# The following methods provide some sets of tools that
# are often registered together
def register_standard_tools(self):
for plot_id in plots:
synclist = self.synchronized_plots.setdefault(plot_id, [])
for plot2_id in plots:
if plot_id==plot2_id:
continue
item = (axis,plot2_id)
if item not in synclist:
synclist.append(item)
def plot_axis_changed(self, plot):
plot_id = plot.plot_id
if plot_id not in self.synchronized_plots:
return
for (axis, other_plot_id) in self.synchronized_plots[plot_id]:
scalediv = plot.axisScaleDiv(axis)
map = plot.canvasMap(axis)
other = self.get_plot(other_plot_id)
lb = scalediv.lowerBound()
ub = scalediv.upperBound()
other.setAxisScale(axis, lb, ub)
other.replot()
assert_interfaces_valid(PlotManager)
#===============================================================================
# Curve Plot Widget/Dialog with integrated Item list widget
#===============================================================================
def configure_plot_splitter(qsplit, decreasing_size=True):
qsplit.setChildrenCollapsible(False)
qsplit.setHandleWidth(4)
if decreasing_size:
qsplit.setStretchFactor(0, 1)
qsplit.setStretchFactor(1, 0)
qsplit.setSizes([2, 1])
else:
qsplit.setStretchFactor(0, 0)
qsplit.setStretchFactor(1, 1)
qsplit.setSizes([1, 2])
class SubplotWidget(QSplitter):
"""Construct a Widget that helps managing several plots
together handled by the same manager
Since the plots must be added to the manager before the panels
the add_itemlist method can be called after having declared
all the subplots
"""
def __init__(self, manager, parent=None):
QSplitter.__init__(self, Qt.Horizontal, parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.manager = manager
self.plots = []
self.itemlist = None
main = QWidget()
self.plotlayout = QGridLayout()
main.setLayout(self.plotlayout)
self.addWidget(main)
def add_itemlist(self, show_itemlist=False):
self.itemlist = PlotItemList(self)
self.itemlist.setVisible(show_itemlist)
self.addWidget(self.itemlist)
configure_plot_splitter(self)
self.manager.add_panel(self.itemlist)
def add_subplot(self, plot, i=0, j=0, plot_id=None):
"""Add a plot to the grid of plots"""
self.plotlayout.addWidget(plot, i, j)
self.plots.append(plot)
if plot_id is None:
plot_id = id(plot)
self.manager.add_plot(plot, plot_id)
class BaseCurveWidget(QSplitter):
"""
Construct a BaseCurveWidget object, which includes:
* A plot (:py:class:`guiqwt.curve.CurvePlot`)
* An `item list` panel (:py:class:`guiqwt.curve.PlotItemList`)
This object does nothing in itself because plot and panels are not
connected to each other.
See children class :py:class:`guiqwt.plot.CurveWidget`
"""
def __init__(self, parent=None, title=None,
xlabel=None, ylabel=None, xunit=None, yunit=None,
section="plot", show_itemlist=False, gridparam=None,
curve_antialiasing=None):
QSplitter.__init__(self, Qt.Horizontal, parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.plot = CurvePlot(parent=self, title=title, xlabel=xlabel,
ylabel=ylabel, xunit=xunit, yunit=yunit,
section=section, gridparam=gridparam)
if curve_antialiasing is not None:
self.plot.set_antialiasing(curve_antialiasing)
self.addWidget(self.plot)
self.itemlist = PlotItemList(self)
self.itemlist.setVisible(show_itemlist)
self.addWidget(self.itemlist)
configure_plot_splitter(self)
class CurveWidget(BaseCurveWidget, PlotManager):
def __init__(self, wintitle="guiqwt plot", icon="guiqwt.svg",
toolbar=False, options=None, panels=None):
PlotManager.__init__(self, main=self)
self.plot_layout = QGridLayout()
if options is None:
options = {}
self.plot_widget = None
self.create_plot(options)
if panels is not None:
for panel in panels:
self.add_panel(panel)
self.toolbar = QToolBar(_("Tools"))
if not toolbar:
self.toolbar.hide()
# Configuring widget layout
self.setup_widget_properties(wintitle=wintitle, icon=icon)
self.setup_widget_layout()
# Configuring plot manager
self.add_toolbar(self.toolbar, "default")
self.register_tools()
def setup_widget_layout(self):
raise NotImplementedError
def setup_widget_properties(self, wintitle, icon):
self.setWindowTitle(wintitle)
if isinstance(icon, basestring):
icon = get_icon(icon)
if icon is not None:
self.setWindowIcon(icon)
self.setMinimumSize(320, 240)
self.resize(640, 480)
def register_tools(self):
"""
Register the plotting dialog box tools: the base implementation
provides standard, curve-related and other tools - i.e. calling
this method is exactly the same as calling
:py:meth:`guiqwt.plot.CurveDialog.register_all_curve_tools`
This method may be overriden to provide a fully customized set of tools
"""
self.register_all_curve_tools()
def create_plot(self, options):
"""
Create the plotting widget (which is an instance of class
:py:class:`guiqwt.plot.BaseCurveWidget`), add it to the dialog box
main layout (:py:attr:`guiqwt.plot.CurveDialog.plot_layout`) and
then add the `item list` panel
May be overriden to customize the plot layout
(:py:attr:`guiqwt.plot.CurveDialog.plot_layout`)
"""
self.plot_widget = BaseCurveWidget(self, **options)
self.plot_layout.addWidget(self.plot_widget, 0, 0)
# Configuring plot manager
self.add_plot(self.plot_widget.plot)
self.add_panel(self.plot_widget.itemlist)
class CurveDialog(QDialog, CurveWidgetMixin):
[docs] """
Construct a CurveDialog object: plotting dialog box with integrated
plot manager
* wintitle: window title
* icon: window icon
* edit: editable state
* toolbar: show/hide toolbar
* options: options sent to the :py:class:`guiqwt.curve.CurvePlot` object
(dictionary)
* parent: parent widget
* panels (optional): additionnal panels (list, tuple)
"""
def __init__(self, wintitle="guiqwt plot", icon="guiqwt.svg", edit=False,
toolbar=False, options=None, parent=None, panels=None):
QDialog.__init__(self, parent)
self.edit = edit
self.button_box = None
self.button_layout = None
CurveWidgetMixin.__init__(self, wintitle=wintitle, icon=icon,
toolbar=toolbar, options=options,
panels=panels)
self.setWindowFlags(Qt.Window)
def setup_widget_layout(self):
vlayout = QVBoxLayout(self)
vlayout.addWidget(self.toolbar)
vlayout.addLayout(self.plot_layout)
self.setLayout(vlayout)
if self.edit:
self.button_layout = QHBoxLayout()
self.install_button_layout()
vlayout.addLayout(self.button_layout)
def install_button_layout(self):
"""
Construct a CurveWindow object: plotting window with integrated plot manager
* wintitle: window title
* icon: window icon
* toolbar: show/hide toolbar
* options: options sent to the :py:class:`guiqwt.curve.CurvePlot` object
(dictionary)
* parent: parent widget
* panels (optional): additionnal panels (list, tuple)
"""
def __init__(self, wintitle="guiqwt plot", icon="guiqwt.svg",
toolbar=False, options=None, parent=None, panels=None):
QMainWindow.__init__(self, parent)
CurveWidgetMixin.__init__(self, wintitle=wintitle, icon=icon,
toolbar=toolbar, options=options,
panels=panels)
def setup_widget_layout(self):
self.addToolBar(self.toolbar)
widget = QWidget()
widget.setLayout(self.plot_layout)
self.setCentralWidget(widget)
def closeEvent(self, event):
# Closing panels (necessary if at least one of these panels has no
# parent widget: otherwise, this panel will stay open after the main
# window has been closed which is not the expected behavior)
for panel in self.panels:
self.get_panel(panel).close()
QMainWindow.closeEvent(self, event)
#===============================================================================
# Image Plot Widget/Dialog with integrated Levels Histogram and other widgets
#===============================================================================
class BaseImageWidget(QSplitter):
"""
Construct a BaseImageWidget object, which includes:
* A plot (:py:class:`guiqwt.curve.CurvePlot`)
* An `item list` panel (:py:class:`guiqwt.curve.PlotItemList`)
* A `contrast adjustment` panel
(:py:class:`guiqwt.histogram.ContrastAdjustment`)
* An `X-axis cross section` panel
(:py:class:`guiqwt.histogram.XCrossSection`)
* An `Y-axis cross section` panel
(:py:class:`guiqwt.histogram.YCrossSection`)
This object does nothing in itself because plot and panels are not
connected to each other.
See children class :py:class:`guiqwt.plot.ImageWidget`
"""
def __init__(self, parent=None, title="",
xlabel=("", ""), ylabel=("", ""), zlabel=None,
xunit=("", ""), yunit=("", ""), zunit=None, yreverse=True,
colormap="jet", aspect_ratio=1.0, lock_aspect_ratio=True,
show_contrast=False, show_itemlist=False, show_xsection=False,
show_ysection=False, xsection_pos="top", ysection_pos="right",
gridparam=None, curve_antialiasing=None):
QSplitter.__init__(self, Qt.Vertical, parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.sub_splitter = QSplitter(Qt.Horizontal, self)
self.plot = ImagePlot(parent=self, title=title,
xlabel=xlabel, ylabel=ylabel, zlabel=zlabel,
xunit=xunit, yunit=yunit, zunit=zunit,
yreverse=yreverse, aspect_ratio=aspect_ratio,
lock_aspect_ratio=lock_aspect_ratio,
gridparam=gridparam)
if curve_antialiasing is not None:
self.plot.set_antialiasing(curve_antialiasing)
from guiqwt.cross_section import YCrossSection
self.ycsw = YCrossSection(self, position=ysection_pos,
xsection_pos=xsection_pos)
self.ycsw.setVisible(show_ysection)
from guiqwt.cross_section import XCrossSection
self.xcsw = XCrossSection(self)
self.xcsw.setVisible(show_xsection)
self.connect(self.xcsw, SIG_VISIBILITY_CHANGED, self.xcsw_is_visible)
self.xcsw_splitter = QSplitter(Qt.Vertical, self)
if xsection_pos == "top":
self.xcsw_splitter.addWidget(self.xcsw)
self.xcsw_splitter.addWidget(self.plot)
else:
self.xcsw_splitter.addWidget(self.plot)
self.xcsw_splitter.addWidget(self.xcsw)
self.connect(self.xcsw_splitter, SIGNAL('splitterMoved(int,int)'),
lambda pos, index: self.adjust_ycsw_height())
self.ycsw_splitter = QSplitter(Qt.Horizontal, self)
if ysection_pos == "left":
self.ycsw_splitter.addWidget(self.ycsw)
self.ycsw_splitter.addWidget(self.xcsw_splitter)
else:
self.ycsw_splitter.addWidget(self.xcsw_splitter)
self.ycsw_splitter.addWidget(self.ycsw)
configure_plot_splitter(self.xcsw_splitter,
decreasing_size=xsection_pos == "bottom")
configure_plot_splitter(self.ycsw_splitter,
decreasing_size=ysection_pos == "right")
self.sub_splitter.addWidget(self.ycsw_splitter)
self.itemlist = PlotItemList(self)
self.itemlist.setVisible(show_itemlist)
self.sub_splitter.addWidget(self.itemlist)
# Contrast adjustment (Levels histogram)
from guiqwt.histogram import ContrastAdjustment
self.contrast = ContrastAdjustment(self)
self.contrast.setVisible(show_contrast)
self.addWidget(self.contrast)
configure_plot_splitter(self)
configure_plot_splitter(self.sub_splitter)
def adjust_ycsw_height(self, height=None):
if height is None:
height = self.xcsw.height()-self.ycsw.toolbar.height()
self.ycsw.adjust_height(height)
if height:
QApplication.processEvents()
def xcsw_is_visible(self, state):
if state:
QApplication.processEvents()
self.adjust_ycsw_height()
else:
self.adjust_ycsw_height(0)
class ImageWidget(BaseImageWidget, PlotManager):
def register_tools(self):
"""
Register the plotting dialog box tools: the base implementation
provides standard, image-related and other tools - i.e. calling
this method is exactly the same as calling
:py:meth:`guiqwt.plot.CurveDialog.register_all_image_tools`
This method may be overriden to provide a fully customized set of tools
"""
self.register_all_image_tools()
def create_plot(self, options, row=0, column=0, rowspan=1, columnspan=1):
"""
Create the plotting widget (which is an instance of class
:py:class:`guiqwt.plot.BaseImageWidget`), add it to the dialog box
main layout (:py:attr:`guiqwt.plot.CurveDialog.plot_layout`) and
then add the `item list`, `contrast adjustment` and X/Y axes
cross section panels.
May be overriden to customize the plot layout
(:py:attr:`guiqwt.plot.CurveDialog.plot_layout`)
"""
self.plot_widget = BaseImageWidget(self, **options)
self.plot_layout.addWidget(self.plot_widget,
row, column, rowspan, columnspan)
# Configuring plot manager
self.add_plot(self.plot_widget.plot)
self.add_panel(self.plot_widget.itemlist)
self.add_panel(self.plot_widget.xcsw)
self.add_panel(self.plot_widget.ycsw)
self.add_panel(self.plot_widget.contrast)
class ImageDialog(CurveDialog, ImageWidgetMixin):
[docs] """
Construct a ImageDialog object: plotting dialog box with integrated
plot manager
* wintitle: window title
* icon: window icon
* edit: editable state
* toolbar: show/hide toolbar
* options: options sent to the :py:class:`guiqwt.image.ImagePlot` object
(dictionary)
* parent: parent widget
* panels (optional): additionnal panels (list, tuple)
"""
pass
class ImageWindow(CurveWindow, ImageWidgetMixin):
"""
Construct a ImageWindow object: plotting window with integrated plot manager
* wintitle: window title
* icon: window icon
* toolbar: show/hide toolbar
* options: options sent to the :py:class:`guiqwt.image.ImagePlot` object
(dictionary)
* parent: parent widget
* panels (optional): additionnal panels (list, tuple)
"""
pass