Skip to content

Commit

Permalink
Improve axis margin handling in QwtPlot
Browse files Browse the repository at this point in the history
Fix #94
  • Loading branch information
PierreRaybaut committed Dec 31, 2024
1 parent 582289a commit 0e44c89
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Version 0.14.3

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

Expand Down
15 changes: 0 additions & 15 deletions qwt/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,18 +396,3 @@ def extend(self, value):
if not self.isValid():
return self
return QwtInterval(min([value, self.__minValue]), max([value, self.__maxValue]))

def extend_fraction(self, value):
"""
Extend the interval by a fraction of its width
:param float value: Fraction
:return: extended interval
"""
if not self.isValid():
return self
return QwtInterval(
self.__minValue - value * self.width(),
self.__maxValue + value * self.width(),
self.__borderFlags,
)
20 changes: 9 additions & 11 deletions qwt/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ def axisStepSize(self, axisId):
def axisMargin(self, axisId):
"""
:param int axisId: Axis index
:return: Margin in % of the canvas size
:return: Relative margin of the axis, as a fraction of the full axis range
.. seealso::
Expand Down Expand Up @@ -871,15 +871,17 @@ def setAxisMaxMajor(self, axisId, maxMajor):

def setAxisMargin(self, axisId, margin):
"""
Set the margin of the scale widget
Set the relative margin of the axis, as a fraction of the full axis range
:param int axisId: Axis index
:param float margin: Margin in % of the canvas size
:param float margin: Relative margin (float between 0 and 1)
.. seealso::
:py:meth:`axisMargin()`
"""
if not isinstance(margin, float) or margin < 0.0 or margin > 1.0:
raise ValueError("margin must be a float between 0 and 1")
if self.axisValid(axisId):
d = self.__axisData[axisId]
if margin != d.margin:
Expand Down Expand Up @@ -946,16 +948,12 @@ def updateAxes(self):
minValue = d.minValue
maxValue = d.maxValue
stepSize = d.stepSize
if d.margin is not None:
intv_i = intv[axisId].extend_fraction(d.margin)
else:
intv_i = intv[axisId]
if d.doAutoScale and intv_i.isValid():
if d.doAutoScale and intv[axisId].isValid():
d.isValid = False
minValue = intv_i.minValue()
maxValue = intv_i.maxValue()
minValue = intv[axisId].minValue()
maxValue = intv[axisId].maxValue()
minValue, maxValue, stepSize = d.scaleEngine.autoScale(
d.maxMajor, minValue, maxValue, stepSize
d.maxMajor, minValue, maxValue, stepSize, d.margin
)
if not d.isValid:
d.scaleDiv = d.scaleEngine.divideScale(
Expand Down
30 changes: 23 additions & 7 deletions qwt/scale_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
from qwt.transform import QwtLogTransform, QwtTransform

DBL_MAX = sys.float_info.max
LOG_MIN = 1.0e-100
LOG_MAX = 1.0e100
LOG_MIN = 1.0e-150
LOG_MAX = 1.0e150


def qwtLogInterval(base, interval):
Expand Down Expand Up @@ -198,14 +198,15 @@ def __init__(self, base=10):
self.__data = QwtScaleEngine_PrivateData()
self.setBase(base)

def autoScale(self, maxNumSteps, x1, x2, stepSize):
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
"""
Align and divide an interval
:param int maxNumSteps: Max. number of steps
:param float x1: First limit of the interval (In/Out)
:param float x2: Second limit of the interval (In/Out)
:param float stepSize: Step size
:param float relative_margin: Margin as a fraction of the interval width
:return: tuple (x1, x2, stepSize)
"""
pass
Expand Down Expand Up @@ -473,20 +474,27 @@ class QwtLinearScaleEngine(QwtScaleEngine):
def __init__(self, base=10):
super(QwtLinearScaleEngine, self).__init__(base)

def autoScale(self, maxNumSteps, x1, x2, stepSize):
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
"""
Align and divide an interval
:param int maxNumSteps: Max. number of steps
:param float x1: First limit of the interval (In/Out)
:param float x2: Second limit of the interval (In/Out)
:param float stepSize: Step size
:param float relative_margin: Margin as a fraction of the interval width
:return: tuple (x1, x2, stepSize)
.. seealso::
:py:meth:`setAttribute()`
"""
# Apply the relative margin (fraction of the interval width) in linear space:
if relative_margin > 0.0:
margin = (x2 - x1) * relative_margin
x1 -= margin
x2 += margin

interval = QwtInterval(x1, x2)
interval = interval.normalized()
interval.setMinValue(interval.minValue() - self.lowerMargin())
Expand Down Expand Up @@ -640,14 +648,15 @@ def __init__(self, base=10):
super(QwtLogScaleEngine, self).__init__(base)
self.setTransformation(QwtLogTransform())

def autoScale(self, maxNumSteps, x1, x2, stepSize):
def autoScale(self, maxNumSteps, x1, x2, stepSize, relative_margin=0.0):
"""
Align and divide an interval
:param int maxNumSteps: Max. number of steps
:param float x1: First limit of the interval (In/Out)
:param float x2: Second limit of the interval (In/Out)
:param float stepSize: Step size
:param float relative_margin: Margin as a fraction of the interval width
:return: tuple (x1, x2, stepSize)
.. seealso::
Expand All @@ -657,11 +666,18 @@ def autoScale(self, maxNumSteps, x1, x2, stepSize):
if x1 > x2:
x1, x2 = x2, x1
logBase = self.base()

# Apply the relative margin (fraction of the interval width) in logarithmic
# space, and convert back to linear space.
if relative_margin is not None:
log_margin = math.log(x2 / x1, logBase) * relative_margin
x1 /= math.pow(logBase, log_margin)
x2 *= math.pow(logBase, log_margin)

interval = QwtInterval(
x1 / math.pow(logBase, self.lowerMargin()),
x2 * math.pow(logBase, self.upperMargin()),
)
interval = interval.limited(LOG_MIN, LOG_MAX)
if interval.maxValue() / interval.minValue() < logBase:
linearScaler = QwtLinearScaleEngine()
linearScaler.setAttributes(self.attributes())
Expand All @@ -674,7 +690,7 @@ def autoScale(self, maxNumSteps, x1, x2, stepSize):
linearInterval = linearInterval.limited(LOG_MIN, LOG_MAX)

if linearInterval.maxValue() / linearInterval.minValue() < logBase:
# The min / max interval is too short to be represented as a log scale.
# The min / max interval is too short to be represented as a log scale.
# Set the step to 0, so that a new step is calculated and a linear scale is used.
stepSize = 0.0
return x1, x2, stepSize
Expand Down
54 changes: 54 additions & 0 deletions qwt/tests/test_relativemargin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
# Licensed under the terms of the PyQwt License
# Copyright (C) 2003-2009 Gerard Vermeulen, for the original PyQwt example
# Copyright (c) 2015 Pierre Raybaut, for the PyQt5/PySide port and further
# developments (e.g. ported to PythonQwt API)
# (see LICENSE file for more details)

SHOW = True # Show test in GUI-based test launcher

from qtpy import QtWidgets as QW
from qtpy.QtCore import Qt

import qwt
from qwt.tests import utils


class RelativeMarginDemo(QW.QWidget):
def __init__(self, *args):
QW.QWidget.__init__(self, *args)
layout = QW.QGridLayout(self)
x = [1, 2, 3, 4]
y = [1, 500, 1000, 1500]
for i_row, log_scale in enumerate((False, True)):
for i_col, relative_margin in enumerate((0.0, None, 0.2)):
plot = qwt.QwtPlot(self)
qwt.QwtPlotGrid.make(
plot, color=Qt.lightGray, width=0, style=Qt.DotLine
)
def_margin = plot.axisMargin(qwt.QwtPlot.yLeft)
scale_str = "lin/lin" if not log_scale else "log/lin"
if relative_margin is None:
margin_str = f"default ({def_margin*100:.0f}%)"
else:
margin_str = f"{relative_margin*100:.0f}%"
plot.setTitle(f"{scale_str}, margin: {margin_str}")
if relative_margin is not None:
plot.setAxisMargin(qwt.QwtPlot.yLeft, relative_margin)
plot.setAxisMargin(qwt.QwtPlot.xBottom, relative_margin)
color = "red" if i_row == 0 else "blue"
qwt.QwtPlotCurve.make(x, y, "", plot, linecolor=color)
layout.addWidget(plot, i_row, i_col)
if log_scale:
engine = qwt.QwtLogScaleEngine()
plot.setAxisScaleEngine(qwt.QwtPlot.yLeft, engine)


def test_relative_margin():
"""Test relative margin."""
utils.test_widget(RelativeMarginDemo, size=(400, 300), options=False)


if __name__ == "__main__":
test_relative_margin()

0 comments on commit 0e44c89

Please sign in to comment.