Skip to content

Commit 0e44c89

Browse files
committed
Improve axis margin handling in QwtPlot
Fix #94
1 parent 582289a commit 0e44c89

File tree

5 files changed

+87
-33
lines changed

5 files changed

+87
-33
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Version 0.14.3
44

5+
- Fixed [Issue #94](https://github.com/PlotPyStack/PythonQwt/issues/94) - Different logarithmic scale behavior when compared to Qwt
56
- Merged [PR #91](https://github.com/PlotPyStack/PythonQwt/pull/91): Fix: legend now showing up when enabled later - thanks to @nicoddemus
67
- Removed `QwtPlotItem.setIcon` and `QwtPlotItem.icon` methods (introduced in 0.9.0 but not used in PythonQwt)
78

qwt/interval.py

-15
Original file line numberDiff line numberDiff line change
@@ -396,18 +396,3 @@ def extend(self, value):
396396
if not self.isValid():
397397
return self
398398
return QwtInterval(min([value, self.__minValue]), max([value, self.__maxValue]))
399-
400-
def extend_fraction(self, value):
401-
"""
402-
Extend the interval by a fraction of its width
403-
404-
:param float value: Fraction
405-
:return: extended interval
406-
"""
407-
if not self.isValid():
408-
return self
409-
return QwtInterval(
410-
self.__minValue - value * self.width(),
411-
self.__maxValue + value * self.width(),
412-
self.__borderFlags,
413-
)

qwt/plot.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ def axisStepSize(self, axisId):
588588
def axisMargin(self, axisId):
589589
"""
590590
:param int axisId: Axis index
591-
:return: Margin in % of the canvas size
591+
:return: Relative margin of the axis, as a fraction of the full axis range
592592
593593
.. seealso::
594594
@@ -871,15 +871,17 @@ def setAxisMaxMajor(self, axisId, maxMajor):
871871

872872
def setAxisMargin(self, axisId, margin):
873873
"""
874-
Set the margin of the scale widget
874+
Set the relative margin of the axis, as a fraction of the full axis range
875875
876876
:param int axisId: Axis index
877-
:param float margin: Margin in % of the canvas size
877+
:param float margin: Relative margin (float between 0 and 1)
878878
879879
.. seealso::
880880
881881
:py:meth:`axisMargin()`
882882
"""
883+
if not isinstance(margin, float) or margin < 0.0 or margin > 1.0:
884+
raise ValueError("margin must be a float between 0 and 1")
883885
if self.axisValid(axisId):
884886
d = self.__axisData[axisId]
885887
if margin != d.margin:
@@ -946,16 +948,12 @@ def updateAxes(self):
946948
minValue = d.minValue
947949
maxValue = d.maxValue
948950
stepSize = d.stepSize
949-
if d.margin is not None:
950-
intv_i = intv[axisId].extend_fraction(d.margin)
951-
else:
952-
intv_i = intv[axisId]
953-
if d.doAutoScale and intv_i.isValid():
951+
if d.doAutoScale and intv[axisId].isValid():
954952
d.isValid = False
955-
minValue = intv_i.minValue()
956-
maxValue = intv_i.maxValue()
953+
minValue = intv[axisId].minValue()
954+
maxValue = intv[axisId].maxValue()
957955
minValue, maxValue, stepSize = d.scaleEngine.autoScale(
958-
d.maxMajor, minValue, maxValue, stepSize
956+
d.maxMajor, minValue, maxValue, stepSize, d.margin
959957
)
960958
if not d.isValid:
961959
d.scaleDiv = d.scaleEngine.divideScale(

qwt/scale_engine.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
from qwt.transform import QwtLogTransform, QwtTransform
3838

3939
DBL_MAX = sys.float_info.max
40-
LOG_MIN = 1.0e-100
41-
LOG_MAX = 1.0e100
40+
LOG_MIN = 1.0e-150
41+
LOG_MAX = 1.0e150
4242

4343

4444
def qwtLogInterval(base, interval):
@@ -198,14 +198,15 @@ def __init__(self, base=10):
198198
self.__data = QwtScaleEngine_PrivateData()
199199
self.setBase(base)
200200

201-
def autoScale(self, maxNumSteps, x1, x2, stepSize):
201+
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
202202
"""
203203
Align and divide an interval
204204
205205
:param int maxNumSteps: Max. number of steps
206206
:param float x1: First limit of the interval (In/Out)
207207
:param float x2: Second limit of the interval (In/Out)
208208
:param float stepSize: Step size
209+
:param float relative_margin: Margin as a fraction of the interval width
209210
:return: tuple (x1, x2, stepSize)
210211
"""
211212
pass
@@ -473,20 +474,27 @@ class QwtLinearScaleEngine(QwtScaleEngine):
473474
def __init__(self, base=10):
474475
super(QwtLinearScaleEngine, self).__init__(base)
475476

476-
def autoScale(self, maxNumSteps, x1, x2, stepSize):
477+
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
477478
"""
478479
Align and divide an interval
479480
480481
:param int maxNumSteps: Max. number of steps
481482
:param float x1: First limit of the interval (In/Out)
482483
:param float x2: Second limit of the interval (In/Out)
483484
:param float stepSize: Step size
485+
:param float relative_margin: Margin as a fraction of the interval width
484486
:return: tuple (x1, x2, stepSize)
485487
486488
.. seealso::
487489
488490
:py:meth:`setAttribute()`
489491
"""
492+
# Apply the relative margin (fraction of the interval width) in linear space:
493+
if relative_margin > 0.0:
494+
margin = (x2 - x1) * relative_margin
495+
x1 -= margin
496+
x2 += margin
497+
490498
interval = QwtInterval(x1, x2)
491499
interval = interval.normalized()
492500
interval.setMinValue(interval.minValue() - self.lowerMargin())
@@ -640,14 +648,15 @@ def __init__(self, base=10):
640648
super(QwtLogScaleEngine, self).__init__(base)
641649
self.setTransformation(QwtLogTransform())
642650

643-
def autoScale(self, maxNumSteps, x1, x2, stepSize):
651+
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
644652
"""
645653
Align and divide an interval
646654
647655
:param int maxNumSteps: Max. number of steps
648656
:param float x1: First limit of the interval (In/Out)
649657
:param float x2: Second limit of the interval (In/Out)
650658
:param float stepSize: Step size
659+
:param float relative_margin: Margin as a fraction of the interval width
651660
:return: tuple (x1, x2, stepSize)
652661
653662
.. seealso::
@@ -657,11 +666,18 @@ def autoScale(self, maxNumSteps, x1, x2, stepSize):
657666
if x1 > x2:
658667
x1, x2 = x2, x1
659668
logBase = self.base()
669+
670+
# Apply the relative margin (fraction of the interval width) in logarithmic
671+
# space, and convert back to linear space.
672+
if relative_margin is not None:
673+
log_margin = math.log(x2 / x1, logBase) * relative_margin
674+
x1 /= math.pow(logBase, log_margin)
675+
x2 *= math.pow(logBase, log_margin)
676+
660677
interval = QwtInterval(
661678
x1 / math.pow(logBase, self.lowerMargin()),
662679
x2 * math.pow(logBase, self.upperMargin()),
663680
)
664-
interval = interval.limited(LOG_MIN, LOG_MAX)
665681
if interval.maxValue() / interval.minValue() < logBase:
666682
linearScaler = QwtLinearScaleEngine()
667683
linearScaler.setAttributes(self.attributes())
@@ -674,7 +690,7 @@ def autoScale(self, maxNumSteps, x1, x2, stepSize):
674690
linearInterval = linearInterval.limited(LOG_MIN, LOG_MAX)
675691

676692
if linearInterval.maxValue() / linearInterval.minValue() < logBase:
677-
# The min / max interval is too short to be represented as a log scale.
693+
# The min / max interval is too short to be represented as a log scale.
678694
# Set the step to 0, so that a new step is calculated and a linear scale is used.
679695
stepSize = 0.0
680696
return x1, x2, stepSize

qwt/tests/test_relativemargin.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
from qtpy import QtWidgets as QW
12+
from qtpy.QtCore import Qt
13+
14+
import qwt
15+
from qwt.tests import utils
16+
17+
18+
class RelativeMarginDemo(QW.QWidget):
19+
def __init__(self, *args):
20+
QW.QWidget.__init__(self, *args)
21+
layout = QW.QGridLayout(self)
22+
x = [1, 2, 3, 4]
23+
y = [1, 500, 1000, 1500]
24+
for i_row, log_scale in enumerate((False, True)):
25+
for i_col, relative_margin in enumerate((0.0, None, 0.2)):
26+
plot = qwt.QwtPlot(self)
27+
qwt.QwtPlotGrid.make(
28+
plot, color=Qt.lightGray, width=0, style=Qt.DotLine
29+
)
30+
def_margin = plot.axisMargin(qwt.QwtPlot.yLeft)
31+
scale_str = "lin/lin" if not log_scale else "log/lin"
32+
if relative_margin is None:
33+
margin_str = f"default ({def_margin*100:.0f}%)"
34+
else:
35+
margin_str = f"{relative_margin*100:.0f}%"
36+
plot.setTitle(f"{scale_str}, margin: {margin_str}")
37+
if relative_margin is not None:
38+
plot.setAxisMargin(qwt.QwtPlot.yLeft, relative_margin)
39+
plot.setAxisMargin(qwt.QwtPlot.xBottom, relative_margin)
40+
color = "red" if i_row == 0 else "blue"
41+
qwt.QwtPlotCurve.make(x, y, "", plot, linecolor=color)
42+
layout.addWidget(plot, i_row, i_col)
43+
if log_scale:
44+
engine = qwt.QwtLogScaleEngine()
45+
plot.setAxisScaleEngine(qwt.QwtPlot.yLeft, engine)
46+
47+
48+
def test_relative_margin():
49+
"""Test relative margin."""
50+
utils.test_widget(RelativeMarginDemo, size=(400, 300), options=False)
51+
52+
53+
if __name__ == "__main__":
54+
test_relative_margin()

0 commit comments

Comments
 (0)