Skip to content

Commit 361a52b

Browse files
committed
Merge pull request #29 from PierreRaybaut/Inf_NaN
Better handling of infinite and NaN values
2 parents aa8542f + bdf8d09 commit 361a52b

File tree

8 files changed

+129
-26
lines changed

8 files changed

+129
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# History of changes
22

3+
## Version 0.5.3
4+
5+
Better handling of infinity and NaN values in scales (removed NumPy warnings)
6+
Now handling infinity and NaN values in series data: removing points that can't be drawn
7+
Fixed logarithmic scale engine: presence of values <= 0 was slowing down series data plotting
8+
39
## Version 0.5.2
410

511
Added CHM documentation to wheel package

qwt/plot_curve.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -813,51 +813,99 @@ def legendIcon(self, index, size):
813813
self.__data.symbol.drawSymbol(painter, r)
814814
return graphic
815815

816-
def setData(self, *args):
817-
"""Compatibility with Qwt5"""
818-
if len(args) == 1:
816+
def setData(self, *args, **kwargs):
817+
"""
818+
Initialize data with a series data object or an array of points.
819+
820+
.. py:method:: setData(data):
821+
822+
:param data: Series data (e.g. `QwtPointArrayData` instance)
823+
:type data: qwt.plot_series.QwtSeriesData
824+
825+
.. py:method:: setData(xData, yData, [size=None], [finite=True]):
826+
827+
Initialize data with `x` and `y` arrays.
828+
829+
This signature was removed in Qwt6 and is temporarily maintained here to ensure compatibility with Qwt5.
830+
831+
Same as `setSamples(x, y, [size=None], [finite=True])`
832+
833+
:param x: List/array of x values
834+
:param y: List/array of y values
835+
:param size: size of xData and yData
836+
:type size: int or None
837+
:param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
838+
839+
.. seealso::
840+
841+
:py:meth:`setSamples()`
842+
"""
843+
if len(args) == 1 and not kwargs:
819844
super(QwtPlotCurve, self).setData(*args)
820-
elif len(args) == 2:
821-
self.setSamples(*args)
845+
elif len(args) in (2, 3, 4):
846+
self.setSamples(*args, **kwargs)
822847
else:
823-
raise TypeError("%s().setData() takes 1 or 2 argument(s) (%s given)"\
848+
raise TypeError("%s().setData() takes 1, 2, 3 or 4 argument(s) (%s given)"\
824849
% (self.__class__.__name__, len(args)))
825850

826-
def setSamples(self, *args):
851+
def setSamples(self, *args, **kwargs):
827852
"""
828853
Initialize data with an array of points.
829854
855+
.. py:method:: setSamples(data):
856+
857+
:param data: Series data (e.g. `QwtPointArrayData` instance)
858+
:type data: qwt.plot_series.QwtSeriesData
859+
860+
830861
.. py:method:: setSamples(samples):
831862
832863
Same as `setData(QwtPointArrayData(samples))`
833864
834865
:param samples: List/array of points
835866
836-
.. py:method:: setSamples(xData, yData, [size=None]):
867+
.. py:method:: setSamples(xData, yData, [size=None], [finite=True]):
837868
838869
Same as `setData(QwtPointArrayData(xData, yData, [size=None]))`
839870
840871
:param xData: List/array of x values
841872
:param yData: List/array of y values
842873
:param size: size of xData and yData
843874
:type size: int or None
875+
:param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
844876
845877
.. seealso::
846878
847-
:py:class:`qwt.plot_series.QwtPointArrayData`,
879+
:py:class:`qwt.plot_series.QwtPointArrayData`
848880
"""
849-
if len(args) == 1:
881+
if len(args) == 1 and not kwargs:
850882
samples, = args
851883
if isinstance(samples, QwtSeriesData):
852884
self.setData(samples)
853885
else:
854886
self.setData(QwtPointArrayData(samples))
855-
elif len(args) == 3:
856-
xData, yData, size = args
857-
self.setData(QwtPointArrayData(xData, yData, size))
858-
elif len(args) == 2:
859-
xData, yData = args
860-
self.setData(QwtPointArrayData(xData, yData))
887+
elif len(args) >= 2:
888+
xData, yData = args[:2]
889+
try:
890+
size = kwargs.pop('size')
891+
except KeyError:
892+
size = None
893+
try:
894+
finite = kwargs.pop('finite')
895+
except KeyError:
896+
finite = None
897+
if kwargs:
898+
raise TypeError("%s().setSamples(): unknown %s keyword "\
899+
"argument(s)"\
900+
% (self.__class__.__name__,
901+
", ".join(list(kwargs.keys()))))
902+
for arg in args[2:]:
903+
if isinstance(arg, bool):
904+
finite = arg
905+
elif isinstance(arg, int):
906+
size = arg
907+
self.setData(QwtPointArrayData(xData, yData,
908+
size=size, finite=finite))
861909
else:
862910
raise TypeError("%s().setSamples() takes 1, 2 or 3 argument(s) "\
863911
"(%s given)" % (self.__class__.__name__, len(args)))

qwt/plot_series.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,16 @@ class QwtPointArrayData(QwtSeriesData):
185185
"""
186186
Interface for iterating over two array objects
187187
188-
.. py:class:: QwtCQwtPointArrayDataolorMap(x, y, [size=None])
188+
.. py:class:: QwtPointArrayData(x, y, [size=None], [finite=True])
189189
190190
:param x: Array of x values
191191
:type x: list or tuple or numpy.array
192192
:param y: Array of y values
193193
:type y: list or tuple or numpy.array
194194
:param int size: Size of the x and y arrays
195+
:param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
195196
"""
196-
def __init__(self, x=None, y=None, size=None):
197+
def __init__(self, x=None, y=None, size=None, finite=None):
197198
QwtSeriesData.__init__(self)
198199
if x is None and y is not None:
199200
x = np.arange(len(y))
@@ -210,8 +211,13 @@ def __init__(self, x=None, y=None, size=None):
210211
if size is not None:
211212
x = np.resize(x, (size, ))
212213
y = np.resize(y, (size, ))
213-
self.__x = x
214-
self.__y = y
214+
if finite if finite is not None else True:
215+
indexes = np.logical_and(np.isfinite(x), np.isfinite(y))
216+
self.__x = x[indexes]
217+
self.__y = y[indexes]
218+
else:
219+
self.__x = x
220+
self.__y = y
215221

216222
def boundingRect(self):
217223
"""
@@ -343,4 +349,4 @@ def swapData(self, series):
343349
"""
344350
swappedSeries = self.__series
345351
self.__series = series
346-
return swappedSeries
352+
return swappedSeries

qwt/scale_draw.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from qwt.scale_map import QwtScaleMap
2424
from qwt.text import QwtText
2525
from qwt.math import qwtRadians
26-
from qwt.painter import QwtPainter
2726

2827
from qwt.qt.QtGui import QPalette, QFontMetrics, QTransform
2928
from qwt.qt.QtCore import (Qt, qFuzzyCompare, QLocale, QRectF, QPointF, QRect,

qwt/scale_engine.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ def autoScale(self, maxNumSteps, x1, x2, stepSize):
652652
logBase = self.base()
653653
interval = QwtInterval(x1/np.power(logBase, self.lowerMargin()),
654654
x2*np.power(logBase, self.upperMargin()))
655+
interval = interval.limited(LOG_MIN, LOG_MAX)
655656
if interval.maxValue()/interval.minValue() < logBase:
656657
linearScaler = QwtLinearScaleEngine()
657658
linearScaler.setAttributes(self.attributes())
@@ -887,4 +888,4 @@ def align(self, interval, stepSize):
887888

888889
return qwtPowInterval(self.base(), QwtInterval(x1, x2))
889890

890-
891+

qwt/tests/CurveBenchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def __init__(self, title, xdata, ydata, style, symbol=None, *args):
5555
if symbol is not None:
5656
curve.setSymbol(symbol)
5757
curve.attach(self)
58-
curve.setData(xdata, ydata*idx)
58+
curve.setData(xdata, ydata*idx, finite=False)
5959
# self.setAxisScale(self.yLeft, -1.5, 1.5)
6060
# self.setAxisScale(self.xBottom, 9.9, 10.)
6161
self.replot()

qwt/tests/LogCurveDemo.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Licensed under the terms of the PyQwt License
4+
# Copyright (C) 2003-2009 Gerard Vermeulen, for the original PyQwt example
5+
# Copyright (c) 2015 Pierre Raybaut, for the PyQt5/PySide port and further
6+
# developments (e.g. ported to PythonQwt API)
7+
# (see LICENSE file for more details)
8+
9+
SHOW = True # Show test in GUI-based test launcher
10+
11+
import numpy as np
12+
13+
np.seterr(all='raise')
14+
15+
from qwt.qt.QtGui import QApplication, QPen
16+
from qwt.qt.QtCore import Qt
17+
from qwt import QwtPlot, QwtPlotCurve, QwtLogScaleEngine
18+
19+
20+
def create_log_plot():
21+
plot = QwtPlot('LogCurveDemo.py (or how to handle -inf values)')
22+
plot.enableAxis(QwtPlot.xBottom)
23+
plot.setAxisScaleEngine(QwtPlot.yLeft, QwtLogScaleEngine())
24+
curve = QwtPlotCurve()
25+
curve.setRenderHint(QwtPlotCurve.RenderAntialiased)
26+
pen = QPen(Qt.magenta)
27+
pen.setWidth(1.5)
28+
curve.setPen(pen)
29+
curve.attach(plot)
30+
x = np.arange(0.0, 10.0, 0.1)
31+
y = 10*np.cos(x)**2-.1
32+
print("y<=0:", y<=0)
33+
curve.setData(x, y)
34+
plot.replot()
35+
return plot
36+
37+
38+
if __name__ == '__main__':
39+
app = QApplication([])
40+
plot = create_log_plot()
41+
plot.resize(800, 500)
42+
plot.show()
43+
app.exec_()

qwt/transform.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def bounded(self, value):
166166
Modify value to be a valid value for the transformation.
167167
168168
:param float value: Value to be bounded
169-
:return: Value unmodified
169+
:return: Value modified
170170
"""
171171
return np.clip(value, self.LogMin, self.LogMax)
172172

@@ -181,7 +181,7 @@ def transform(self, value):
181181
182182
:py:meth:`invTransform()`
183183
"""
184-
return np.log(value)
184+
return np.log(self.bounded(value))
185185

186186
def invTransform(self, value):
187187
"""

0 commit comments

Comments
 (0)