summaryrefslogtreecommitdiff
path: root/silx/gui/plot
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@debian.org>2017-11-25 16:55:20 +0100
committerPicca Frédéric-Emmanuel <picca@debian.org>2017-11-25 16:55:20 +0100
commite19c96eff0c310c06c4f268c8b80cb33bd08996f (patch)
treef2b4a365ed899be04766f3937bcc2d58d22be065 /silx/gui/plot
parentbfa4dba15485b4192f8bbe13345e9658c97ecf76 (diff)
New upstream version 0.6.1+dfsg
Diffstat (limited to 'silx/gui/plot')
-rw-r--r--silx/gui/plot/ImageView.py17
-rw-r--r--silx/gui/plot/LegendSelector.py7
-rw-r--r--silx/gui/plot/PlotTools.py11
-rw-r--r--silx/gui/plot/PlotWidget.py130
-rw-r--r--silx/gui/plot/Profile.py9
-rw-r--r--silx/gui/plot/backends/BackendMatplotlib.py134
-rw-r--r--silx/gui/plot/items/axis.py2
-rw-r--r--silx/gui/plot/items/core.py7
-rw-r--r--silx/gui/plot/items/curve.py3
-rw-r--r--silx/gui/plot/test/__init__.py4
-rw-r--r--silx/gui/plot/test/testImageView.py136
-rw-r--r--silx/gui/plot/test/testPlotWidget.py21
12 files changed, 365 insertions, 116 deletions
diff --git a/silx/gui/plot/ImageView.py b/silx/gui/plot/ImageView.py
index 803a2fc..46e56e6 100644
--- a/silx/gui/plot/ImageView.py
+++ b/silx/gui/plot/ImageView.py
@@ -311,16 +311,13 @@ class ImageView(PlotWindow):
def _initWidgets(self, backend):
"""Set-up layout and plots."""
- # Monkey-patch for histogram size
- # alternative: create a layout that does not use widget size hints
- def sizeHint():
- return qt.QSize(self.HISTOGRAMS_HEIGHT, self.HISTOGRAMS_HEIGHT)
-
self._histoHPlot = PlotWidget(backend=backend)
+ self._histoHPlot.getWidgetHandle().setMinimumHeight(
+ self.HISTOGRAMS_HEIGHT)
+ self._histoHPlot.getWidgetHandle().setMaximumHeight(
+ self.HISTOGRAMS_HEIGHT)
self._histoHPlot.setInteractiveMode('zoom')
self._histoHPlot.sigPlotSignal.connect(self._histoHPlotCB)
- self._histoHPlot.getWidgetHandle().sizeHint = sizeHint
- self._histoHPlot.getWidgetHandle().minimumSizeHint = sizeHint
self.setPanWithArrowKeys(True)
@@ -330,10 +327,12 @@ class ImageView(PlotWindow):
self.sigActiveImageChanged.connect(self._activeImageChangedSlot)
self._histoVPlot = PlotWidget(backend=backend)
+ self._histoVPlot.getWidgetHandle().setMinimumWidth(
+ self.HISTOGRAMS_HEIGHT)
+ self._histoVPlot.getWidgetHandle().setMaximumWidth(
+ self.HISTOGRAMS_HEIGHT)
self._histoVPlot.setInteractiveMode('zoom')
self._histoVPlot.sigPlotSignal.connect(self._histoVPlotCB)
- self._histoVPlot.getWidgetHandle().sizeHint = sizeHint
- self._histoVPlot.getWidgetHandle().minimumSizeHint = sizeHint
self._radarView = RadarView()
self._radarView.visibleRectDragged.connect(self._radarViewCB)
diff --git a/silx/gui/plot/LegendSelector.py b/silx/gui/plot/LegendSelector.py
index 31bc3db..e9cfd1d 100644
--- a/silx/gui/plot/LegendSelector.py
+++ b/silx/gui/plot/LegendSelector.py
@@ -29,7 +29,7 @@ This widget is meant to work with :class:`PlotWindow`.
__authors__ = ["V.A. Sole", "T. Rueter", "T. Vincent"]
__license__ = "MIT"
-__data__ = "08/08/2016"
+__data__ = "16/10/2017"
import logging
@@ -971,9 +971,10 @@ class LegendsDockWidget(qt.QDockWidget):
def renameCurve(self, oldLegend, newLegend):
"""Change the name of a curve using remove and addCurve
- :param str oldLegend: The legend of the curve to be change
+ :param str oldLegend: The legend of the curve to be changed
:param str newLegend: The new legend of the curve
"""
+ is_active = self.plot.getActiveCurve(just_legend=True) == oldLegend
curve = self.plot.getCurve(oldLegend)
self.plot.remove(oldLegend, kind='curve')
self.plot.addCurve(curve.getXData(copy=False),
@@ -992,6 +993,8 @@ class LegendsDockWidget(qt.QDockWidget):
selectable=curve.isSelectable(),
fill=curve.isFill(),
resetzoom=False)
+ if is_active:
+ self.plot.setActiveCurve(newLegend)
def _legendSignalHandler(self, ddict):
"""Handles events from the LegendListView signal"""
diff --git a/silx/gui/plot/PlotTools.py b/silx/gui/plot/PlotTools.py
index 85dcc31..ed62d48 100644
--- a/silx/gui/plot/PlotTools.py
+++ b/silx/gui/plot/PlotTools.py
@@ -29,7 +29,7 @@ from __future__ import division
__authors__ = ["V.A. Sole", "T. Vincent"]
__license__ = "MIT"
-__date__ = "02/10/2017"
+__date__ = "16/10/2017"
import logging
@@ -151,13 +151,16 @@ class PositionInfo(qt.QWidget):
"""
if event['event'] == 'mouseMoved':
x, y = event['x'], event['y']
- self._updateStatusBar(x, y)
+ xPixel, yPixel = event['xpixel'], event['ypixel']
+ self._updateStatusBar(x, y, xPixel, yPixel)
- def _updateStatusBar(self, x, y):
+ def _updateStatusBar(self, x, y, xPixel, yPixel):
"""Update information from the status bar using the definitions.
:param float x: Position-x in data
:param float y: Position-y in data
+ :param float xPixel: Position-x in pixels
+ :param float yPixel: Position-y in pixels
"""
styleSheet = "color: rgb(0, 0, 0);" # Default style
@@ -180,8 +183,6 @@ class PositionInfo(qt.QWidget):
closestInPixels = self.plot.dataToPixel(
xClosest, yClosest, axis=activeCurve.getYAxis())
if closestInPixels is not None:
- xPixel, yPixel = event['xpixel'], event['ypixel']
-
if (abs(closestInPixels[0] - xPixel) < 5 and
abs(closestInPixels[1] - yPixel) < 5):
# Update label style sheet
diff --git a/silx/gui/plot/PlotWidget.py b/silx/gui/plot/PlotWidget.py
index 8fd5a5e..5bf2b59 100644
--- a/silx/gui/plot/PlotWidget.py
+++ b/silx/gui/plot/PlotWidget.py
@@ -175,7 +175,7 @@ from __future__ import division
__authors__ = ["V.A. Sole", "T. Vincent"]
__license__ = "MIT"
-__date__ = "30/08/2017"
+__date__ = "18/10/2017"
from collections import OrderedDict, namedtuple
@@ -612,8 +612,7 @@ class PlotWidget(qt.QMainWindow):
if (kind == 'curve' and not self.getAllCurves(just_legend=True,
withhidden=True)):
- self._colorIndex = 0
- self._styleIndex = 0
+ self._resetColorAndStyle()
self.notify('contentChanged', action='remove',
kind=kind, legend=legend)
@@ -780,6 +779,9 @@ class PlotWidget(qt.QMainWindow):
# Check if curve was previously active
wasActive = self.getActiveCurve(just_legend=True) == legend
+ if replace:
+ self._resetColorAndStyle()
+
# Create/Update curve object
curve = self.getCurve(legend)
mustBeAdded = curve is None
@@ -2319,7 +2321,7 @@ class PlotWidget(qt.QMainWindow):
flag = bool(flag)
self._backend.setKeepDataAspectRatio(flag=flag)
self._setDirtyPlot()
- self.resetZoom()
+ self._forceResetZoom()
self.notify('setKeepDataAspectRatio', state=flag)
def getGraphGrid(self):
@@ -2431,6 +2433,10 @@ class PlotWidget(qt.QMainWindow):
"""
return Colormap.getSupportedColormaps()
+ def _resetColorAndStyle(self):
+ self._colorIndex = 0
+ self._styleIndex = 0
+
def _getColorAndStyle(self):
color = self.colorList[self._colorIndex]
style = self._styleList[self._styleIndex]
@@ -2614,6 +2620,66 @@ class PlotWidget(qt.QMainWindow):
self._backend.replot()
self._dirty = False # reset dirty flag
+ def _forceResetZoom(self, dataMargins=None):
+ """Reset the plot limits to the bounds of the data and redraw the plot.
+
+ This method forces a reset zoom and does not check axis autoscale.
+
+ Extra margins can be added around the data inside the plot area
+ (see :meth:`setDataMargins`).
+ Margins are given as one ratio of the data range per limit of the
+ data (xMin, xMax, yMin and yMax limits).
+ For log scale, extra margins are applied in log10 of the data.
+
+ :param dataMargins: Ratios of margins to add around the data inside
+ the plot area for each side (default: no margins).
+ :type dataMargins: A 4-tuple of float as (xMin, xMax, yMin, yMax).
+ """
+ if dataMargins is None:
+ dataMargins = self._defaultDataMargins
+
+ # Get data range
+ ranges = self.getDataRange()
+ xmin, xmax = (1., 100.) if ranges.x is None else ranges.x
+ ymin, ymax = (1., 100.) if ranges.y is None else ranges.y
+ if ranges.yright is None:
+ ymin2, ymax2 = None, None
+ else:
+ ymin2, ymax2 = ranges.yright
+
+ # Add margins around data inside the plot area
+ newLimits = list(_utils.addMarginsToLimits(
+ dataMargins,
+ self._xAxis._isLogarithmic(),
+ self._yAxis._isLogarithmic(),
+ xmin, xmax, ymin, ymax, ymin2, ymax2))
+
+ if self.isKeepDataAspectRatio():
+ # Use limits with margins to keep ratio
+ xmin, xmax, ymin, ymax = newLimits[:4]
+
+ # Compute bbox wth figure aspect ratio
+ plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
+ plotRatio = plotHeight / plotWidth
+
+ if plotRatio > 0.:
+ dataRatio = (ymax - ymin) / (xmax - xmin)
+ if dataRatio < plotRatio:
+ # Increase y range
+ ycenter = 0.5 * (ymax + ymin)
+ yrange = (xmax - xmin) * plotRatio
+ newLimits[2] = ycenter - 0.5 * yrange
+ newLimits[3] = ycenter + 0.5 * yrange
+
+ elif dataRatio > plotRatio:
+ # Increase x range
+ xcenter = 0.5 * (xmax + xmin)
+ xrange_ = (ymax - ymin) / plotRatio
+ newLimits[0] = xcenter - 0.5 * xrange_
+ newLimits[1] = xcenter + 0.5 * xrange_
+
+ self.setLimits(*newLimits)
+
def resetZoom(self, dataMargins=None):
"""Reset the plot limits to the bounds of the data and redraw the plot.
@@ -2631,9 +2697,6 @@ class PlotWidget(qt.QMainWindow):
the plot area for each side (default: no margins).
:type dataMargins: A 4-tuple of float as (xMin, xMax, yMin, yMax).
"""
- if dataMargins is None:
- dataMargins = self._defaultDataMargins
-
xLimits = self._xAxis.getLimits()
yLimits = self._yAxis.getLimits()
y2Limits = self._yRightAxis.getLimits()
@@ -2641,52 +2704,19 @@ class PlotWidget(qt.QMainWindow):
xAuto = self._xAxis.isAutoScale()
yAuto = self._yAxis.isAutoScale()
+ # With log axes, autoscale if limits are <= 0
+ # This avoids issues with toggling log scale with matplotlib 2.1.0
+ if self._xAxis.getScale() == self._xAxis.LOGARITHMIC and xLimits[0] <= 0:
+ xAuto = True
+ if self._yAxis.getScale() == self._yAxis.LOGARITHMIC and (yLimits[0] <= 0 or y2Limits[0] <= 0):
+ yAuto = True
+
if not xAuto and not yAuto:
_logger.debug("Nothing to autoscale")
else: # Some axes to autoscale
+ self._forceResetZoom(dataMargins=dataMargins)
- # Get data range
- ranges = self.getDataRange()
- xmin, xmax = (1., 100.) if ranges.x is None else ranges.x
- ymin, ymax = (1., 100.) if ranges.y is None else ranges.y
- if ranges.yright is None:
- ymin2, ymax2 = None, None
- else:
- ymin2, ymax2 = ranges.yright
-
- # Add margins around data inside the plot area
- newLimits = list(_utils.addMarginsToLimits(
- dataMargins,
- self._xAxis._isLogarithmic(),
- self._yAxis._isLogarithmic(),
- xmin, xmax, ymin, ymax, ymin2, ymax2))
-
- if self.isKeepDataAspectRatio():
- # Use limits with margins to keep ratio
- xmin, xmax, ymin, ymax = newLimits[:4]
-
- # Compute bbox wth figure aspect ratio
- plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
- plotRatio = plotHeight / plotWidth
-
- if plotRatio > 0.:
- dataRatio = (ymax - ymin) / (xmax - xmin)
- if dataRatio < plotRatio:
- # Increase y range
- ycenter = 0.5 * (ymax + ymin)
- yrange = (xmax - xmin) * plotRatio
- newLimits[2] = ycenter - 0.5 * yrange
- newLimits[3] = ycenter + 0.5 * yrange
-
- elif dataRatio > plotRatio:
- # Increase x range
- xcenter = 0.5 * (xmax + xmin)
- xrange_ = (ymax - ymin) / plotRatio
- newLimits[0] = xcenter - 0.5 * xrange_
- newLimits[1] = xcenter + 0.5 * xrange_
-
- self.setLimits(*newLimits)
-
+ # Restore limits for axis not in autoscale
if not xAuto and yAuto:
self.setGraphXLimits(*xLimits)
elif xAuto and not yAuto:
@@ -2696,8 +2726,6 @@ class PlotWidget(qt.QMainWindow):
if yLimits is not None:
self.setGraphYLimits(yLimits[0], yLimits[1], axis='left')
- self._setDirtyPlot()
-
if (xLimits != self._xAxis.getLimits() or
yLimits != self._yAxis.getLimits() or
y2Limits != self._yRightAxis.getLimits()):
diff --git a/silx/gui/plot/Profile.py b/silx/gui/plot/Profile.py
index ff85695..4a74fa7 100644
--- a/silx/gui/plot/Profile.py
+++ b/silx/gui/plot/Profile.py
@@ -31,6 +31,8 @@ __license__ = "MIT"
__date__ = "17/08/2017"
+import weakref
+
import numpy
from silx.image.bilinear import BilinearImage
@@ -348,7 +350,7 @@ class ProfileToolBar(qt.QToolBar):
title='Profile Selection'):
super(ProfileToolBar, self).__init__(title, parent)
assert plot is not None
- self.plot = plot
+ self._plotRef = weakref.ref(plot)
self._overlayColor = None
self._defaultOverlayColor = 'red' # update when active image change
@@ -443,6 +445,11 @@ class ProfileToolBar(qt.QToolBar):
self.getProfileMainWindow().sigClose.connect(self.clearProfile)
@property
+ def plot(self):
+ """The :class:`.PlotWidget` associated to the toolbar."""
+ return self._plotRef()
+
+ @property
@deprecated(since_version="0.6.0")
def browseAction(self):
return self._browseAction
diff --git a/silx/gui/plot/backends/BackendMatplotlib.py b/silx/gui/plot/backends/BackendMatplotlib.py
index 59e753e..b41f20e 100644
--- a/silx/gui/plot/backends/BackendMatplotlib.py
+++ b/silx/gui/plot/backends/BackendMatplotlib.py
@@ -28,7 +28,7 @@ from __future__ import division
__authors__ = ["V.A. Sole", "T. Vincent, H. Payno"]
__license__ = "MIT"
-__date__ = "16/08/2017"
+__date__ = "18/10/2017"
import logging
@@ -306,6 +306,14 @@ class BackendMatplotlib(BackendBase.BackendBase):
ystep = 1 if scale[1] >= 0. else -1
data = data[::ystep, ::xstep]
+ if matplotlib.__version__ < "2.1":
+ # matplotlib 1.4.2 do not support float128
+ dtype = data.dtype
+ if dtype.kind == "f" and dtype.itemsize >= 16:
+ _logger.warning("Your matplotlib version do not support "
+ "float128. Data converted to floa64.")
+ data = data.astype(numpy.float64)
+
image.set_data(data)
self.ax.add_artist(image)
@@ -602,16 +610,32 @@ class BackendMatplotlib(BackendBase.BackendBase):
# Graph axes
def setXAxisLogarithmic(self, flag):
- if matplotlib.__version__ >= "2.0.0":
- self.ax.cla()
- self.ax2.cla()
+ # Workaround for matplotlib 2.1.0 when one tries to set an axis
+ # to log scale with both limits <= 0
+ # In this case a draw with positive limits is needed first
+ if flag and matplotlib.__version__ >= '2.1.0':
+ xlim = self.ax.get_xlim()
+ if xlim[0] <= 0 and xlim[1] <= 0:
+ self.ax.set_xlim(1, 10)
+ self.draw()
+
self.ax2.set_xscale('log' if flag else 'linear')
self.ax.set_xscale('log' if flag else 'linear')
def setYAxisLogarithmic(self, flag):
- if matplotlib.__version__ >= "2.0.0":
- self.ax.cla()
- self.ax2.cla()
+ # Workaround for matplotlib 2.1.0 when one tries to set an axis
+ # to log scale with both limits <= 0
+ # In this case a draw with positive limits is needed first
+ if flag and matplotlib.__version__ >= '2.1.0':
+ redraw = False
+ for axis in (self.ax, self.ax2):
+ ylim = axis.get_ylim()
+ if ylim[0] <= 0 and ylim[1] <= 0:
+ axis.set_ylim(1, 10)
+ redraw = True
+ if redraw:
+ self.draw()
+
self.ax2.set_yscale('log' if flag else 'linear')
self.ax.set_yscale('log' if flag else 'linear')
@@ -700,12 +724,12 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
"""Signal handling automatic asynchronous replot"""
def __init__(self, plot, parent=None):
- self._insideResizeEventMethod = False
-
BackendMatplotlib.__init__(self, plot, parent)
FigureCanvasQTAgg.__init__(self, self.fig)
self.setParent(parent)
+ self._limitsBeforeResize = None
+
FigureCanvasQTAgg.setSizePolicy(
self, qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
FigureCanvasQTAgg.updateGeometry(self)
@@ -806,55 +830,83 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
# replot control
def resizeEvent(self, event):
- self._insideResizeEventMethod = True
- # Need to dirty the whole plot on resize.
- self._plot._setDirtyPlot()
+ # Store current limits
+ self._limitsBeforeResize = (
+ self.ax.get_xbound(), self.ax.get_ybound(), self.ax2.get_ybound())
+
FigureCanvasQTAgg.resizeEvent(self, event)
- self._insideResizeEventMethod = False
+ if self.isKeepDataAspectRatio() or self._overlays or self._graphCursor:
+ # This is needed with matplotlib 1.5.x and 2.0.x
+ self._plot._setDirtyPlot()
+
+ def _drawOverlays(self):
+ """Draw overlays if any."""
+ if self._overlays or self._graphCursor:
+ # There is some overlays or crosshair
+
+ # This assume that items are only on left/bottom Axes
+ for item in self._overlays:
+ self.ax.draw_artist(item)
+
+ for item in self._graphCursor:
+ self.ax.draw_artist(item)
def draw(self):
- """Override canvas draw method to support faster draw of overlays."""
- if self._plot._getDirtyPlot(): # Need a full redraw
- # Store previous limits
- xLimits = self.ax.get_xbound()
- yLimits = self.ax.get_ybound()
- yRightLimits = self.ax2.get_ybound()
+ """Overload draw
+ It performs a full redraw (including overlays) of the plot.
+ It also resets background and emit limits changed signal.
+
+ This is directly called by matplotlib for widget resize.
+ """
+ # Starting with mpl 2.1.0, toggling autoscale raises a ValueError
+ # in some situations. See #1081, #1136, #1163,
+ if matplotlib.__version__ >= "2.0.0":
+ try:
+ FigureCanvasQTAgg.draw(self)
+ except ValueError as err:
+ _logger.debug(
+ "ValueError caught while calling FigureCanvasQTAgg.draw: "
+ "'%s'", err)
+ else:
FigureCanvasQTAgg.draw(self)
- self._background = None # Any saved background is dirty
- # Check if limits changed due to a resize of the widget
+ if self._overlays or self._graphCursor:
+ # Save background
+ self._background = self.copy_from_bbox(self.fig.bbox)
+ else:
+ self._background = None # Reset background
+
+ # Check if limits changed due to a resize of the widget
+ if self._limitsBeforeResize is not None:
+ xLimits, yLimits, yRightLimits = self._limitsBeforeResize
+ self._limitsBeforeResize = None
+
if xLimits != self.ax.get_xbound():
self._plot.getXAxis()._emitLimitsChanged()
if yLimits != self.ax.get_ybound():
self._plot.getYAxis(axis='left')._emitLimitsChanged()
if yRightLimits != self.ax2.get_ybound():
- self._plot.getYAxis(axis='left')._emitLimitsChanged()
-
- if (self._overlays or self._graphCursor or
- self._plot._getDirtyPlot() == 'overlay'):
- # There are overlays or crosshair, or they is just no more overlays
+ self._plot.getYAxis(axis='right')._emitLimitsChanged()
- # Specific case: called from resizeEvent:
- # avoid store/restore background, just draw the overlay
- if not self._insideResizeEventMethod:
- if self._background is None: # First store the background
- self._background = self.copy_from_bbox(self.fig.bbox)
+ self._drawOverlays()
- self.restore_region(self._background)
-
- # This assume that items are only on left/bottom Axes
- for item in self._overlays:
- self.ax.draw_artist(item)
+ def replot(self):
+ BackendMatplotlib.replot(self)
- for item in self._graphCursor:
- self.ax.draw_artist(item)
+ dirtyFlag = self._plot._getDirtyPlot()
+ if dirtyFlag == 'overlay':
+ # Only redraw overlays using fast rendering path
+ if self._background is None:
+ self._background = self.copy_from_bbox(self.fig.bbox)
+ self.restore_region(self._background)
+ self._drawOverlays()
self.blit(self.fig.bbox)
- def replot(self):
- BackendMatplotlib.replot(self)
- self.draw()
+ elif dirtyFlag: # Need full redraw
+ self.draw()
+
# cursor
diff --git a/silx/gui/plot/items/axis.py b/silx/gui/plot/items/axis.py
index 56fd762..ff36512 100644
--- a/silx/gui/plot/items/axis.py
+++ b/silx/gui/plot/items/axis.py
@@ -220,7 +220,7 @@ class Axis(qt.QObject):
for item in self._plot._getItems(withhidden=True):
item._updated()
self._plot._invalidateDataRange()
- self._plot.resetZoom()
+ self._plot._forceResetZoom()
self.sigScaleChanged.emit(self._scale)
if emitLog:
diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py
index 0f4ffb9..34ac700 100644
--- a/silx/gui/plot/items/core.py
+++ b/silx/gui/plot/items/core.py
@@ -543,7 +543,7 @@ class ColorMixIn(object):
def getColor(self):
"""Returns the RGBA color of the item
- :rtype: 4-tuple of float in [0, 1]
+ :rtype: 4-tuple of float in [0, 1] or array of colors
"""
return self._color
@@ -566,9 +566,8 @@ class ColorMixIn(object):
else: # Array of colors
assert color.ndim == 2
- if self._color != color:
- self._color = color
- self._updated(ItemChangedType.COLOR)
+ self._color = color
+ self._updated(ItemChangedType.COLOR)
class YAxisMixIn(object):
diff --git a/silx/gui/plot/items/curve.py b/silx/gui/plot/items/curve.py
index ce7f03e..0ba475d 100644
--- a/silx/gui/plot/items/curve.py
+++ b/silx/gui/plot/items/curve.py
@@ -31,6 +31,7 @@ __date__ = "06/03/2017"
import logging
+import numpy
from .. import Colors
from .core import (Points, LabelsMixIn, ColorMixIn, YAxisMixIn,
@@ -75,7 +76,7 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn):
xFiltered, yFiltered, xerror, yerror = self.getData(
copy=False, displayed=True)
- if len(xFiltered) == 0:
+ if len(xFiltered) == 0 or not numpy.any(numpy.isfinite(xFiltered)):
return None # No data to display, do not add renderer to backend
return backend.addCurve(xFiltered, yFiltered, self.getLegend(),
diff --git a/silx/gui/plot/test/__init__.py b/silx/gui/plot/test/__init__.py
index 2c2943e..07338b6 100644
--- a/silx/gui/plot/test/__init__.py
+++ b/silx/gui/plot/test/__init__.py
@@ -51,6 +51,7 @@ from . import testItem
from . import testUtilsAxis
from . import testLimitConstraints
from . import testComplexImageView
+from . import testImageView
def suite():
@@ -77,5 +78,6 @@ def suite():
testItem.suite(),
testUtilsAxis.suite(),
testLimitConstraints.suite(),
- testComplexImageView.suite()])
+ testComplexImageView.suite(),
+ testImageView.suite()])
return test_suite
diff --git a/silx/gui/plot/test/testImageView.py b/silx/gui/plot/test/testImageView.py
new file mode 100644
index 0000000..641d438
--- /dev/null
+++ b/silx/gui/plot/test/testImageView.py
@@ -0,0 +1,136 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Basic tests for PlotWindow"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "22/09/2017"
+
+
+import unittest
+import numpy
+
+from silx.gui import qt
+from silx.gui.test.utils import TestCaseQt
+
+from silx.gui.plot import ImageView
+from silx.gui.plot.Colormap import Colormap
+
+
+class TestImageView(TestCaseQt):
+ """Tests of ImageView widget."""
+
+ def setUp(self):
+ super(TestImageView, self).setUp()
+ self.plot = ImageView()
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+
+ def tearDown(self):
+ self.qapp.processEvents()
+ self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
+ self.plot.close()
+ del self.plot
+ self.qapp.processEvents()
+ super(TestImageView, self).tearDown()
+
+ def testSetImage(self):
+ """Test setImage"""
+ image = numpy.arange(100).reshape(10, 10)
+
+ self.plot.setImage(image, reset=True)
+ self.qWait(100)
+ self.assertEqual(self.plot.getXAxis().getLimits(), (0, 10))
+ self.assertEqual(self.plot.getYAxis().getLimits(), (0, 10))
+
+ # With reset=False
+ self.plot.setImage(image[::2, ::2], reset=False)
+ self.qWait(100)
+ self.assertEqual(self.plot.getXAxis().getLimits(), (0, 10))
+ self.assertEqual(self.plot.getYAxis().getLimits(), (0, 10))
+
+ self.plot.setImage(image, origin=(10, 20), scale=(2, 4), reset=False)
+ self.qWait(100)
+ self.assertEqual(self.plot.getXAxis().getLimits(), (0, 10))
+ self.assertEqual(self.plot.getYAxis().getLimits(), (0, 10))
+
+ # With reset=True
+ self.plot.setImage(image, origin=(1, 2), scale=(1, 0.5), reset=True)
+ self.qWait(100)
+ self.assertEqual(self.plot.getXAxis().getLimits(), (1, 11))
+ self.assertEqual(self.plot.getYAxis().getLimits(), (2, 7))
+
+ self.plot.setImage(image[::2, ::2], reset=True)
+ self.qWait(100)
+ self.assertEqual(self.plot.getXAxis().getLimits(), (0, 5))
+ self.assertEqual(self.plot.getYAxis().getLimits(), (0, 5))
+
+ def testColormap(self):
+ """Test get|setColormap"""
+ image = numpy.arange(100).reshape(10, 10)
+ self.plot.setImage(image)
+
+ # Colormap as dict
+ self.plot.setColormap({'name': 'viridis',
+ 'normalization': 'log',
+ 'autoscale': False,
+ 'vmin': 0,
+ 'vmax': 1})
+ colormap = self.plot.getColormap()
+ self.assertEqual(colormap.getName(), 'viridis')
+ self.assertEqual(colormap.getNormalization(), 'log')
+ self.assertEqual(colormap.getVMin(), 0)
+ self.assertEqual(colormap.getVMax(), 1)
+
+ # Colormap as keyword arguments
+ self.plot.setColormap(colormap='magma',
+ normalization='linear',
+ autoscale=True,
+ vmin=1,
+ vmax=2)
+ self.assertEqual(colormap.getName(), 'magma')
+ self.assertEqual(colormap.getNormalization(), 'linear')
+ self.assertEqual(colormap.getVMin(), None)
+ self.assertEqual(colormap.getVMax(), None)
+
+ # Update colormap with keyword argument
+ self.plot.setColormap(normalization='log')
+ self.assertEqual(colormap.getNormalization(), 'log')
+
+ # Colormap as Colormap object
+ cmap = Colormap()
+ self.plot.setColormap(cmap)
+ self.assertIs(self.plot.getColormap(), cmap)
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(
+ unittest.defaultTestLoader.loadTestsFromTestCase(TestImageView))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/gui/plot/test/testPlotWidget.py b/silx/gui/plot/test/testPlotWidget.py
index deeb198..ccee428 100644
--- a/silx/gui/plot/test/testPlotWidget.py
+++ b/silx/gui/plot/test/testPlotWidget.py
@@ -371,6 +371,27 @@ class TestPlotCurve(PlotWidgetTestCase):
color=color, linestyle="-", symbol='o')
self.plot.resetZoom()
+ # Test updating color array
+
+ # From array to array
+ newColors = numpy.ones((len(self.xData), 3), dtype=numpy.float32)
+ self.plot.addCurve(self.xData, self.yData,
+ legend="curve 2",
+ replace=False, resetzoom=False,
+ color=newColors, symbol='o')
+
+ # Array to single color
+ self.plot.addCurve(self.xData, self.yData,
+ legend="curve 2",
+ replace=False, resetzoom=False,
+ color='green', symbol='o')
+
+ # single color to array
+ self.plot.addCurve(self.xData, self.yData,
+ legend="curve 2",
+ replace=False, resetzoom=False,
+ color=color, symbol='o')
+
class TestPlotMarker(PlotWidgetTestCase):
"""Basic tests for add*Marker"""