Skip to content

Commit 290000b

Browse files
committed
New YRangeCursorTool
Fix #45
1 parent ef50467 commit 290000b

File tree

15 files changed

+240
-132
lines changed

15 files changed

+240
-132
lines changed

CHANGELOG.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
💥 New features / Enhancements:
66

7-
* [Issue #45](https://github.com/PlotPyStack/PlotPy/issues/45) - Add support for new curve statistics tools: `XCurveStatsTool` and `YCurveStatsTool`
8-
* These tools are similar to the existing `CurveStatsTool`, but they compute statistics for the X and Y coordinates of the curve points, respectively
9-
* They can be added to the plot widget using `plot_widget.manager.add_tool(XCurveStatsTool)` or `plot_widget.manager.add_tool(YCurveStatsTool)`
10-
* The tools display a table with the computed statistics (mean, median, standard deviation, etc.) for the X or Y coordinates of the curve points
7+
* [Issue #45](https://github.com/PlotPyStack/PlotPy/issues/45) - Add support for new curve Y-range cursor tool `YRangeCursorTool`:
8+
* This tool is similar to the existing `CurveStatsTool`, but it simply shows the Y-range values (min, max and interval).
9+
* It can be added to the plot widget using `plot_widget.manager.add_tool(YRangeCursorTool)`
1110

1211
🛠️ Bug fixes:
1312

doc/features/items/overview.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ The following label items are available:
8686
* :py:class:`.LabelItem`
8787
* :py:class:`.LegendBoxItem`
8888
* :py:class:`.SelectedLegendBoxItem`
89-
* :py:class:`.RangeComputation`
89+
* :py:class:`.XRangeComputation`
90+
* :py:class:`.YRangeComputation`
9091
* :py:class:`.RangeComputation2d`
9192
* :py:class:`.DataInfoLabel`

doc/features/items/reference.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ Labels
138138
:members:
139139
.. autoclass:: plotpy.items.SelectedLegendBoxItem
140140
:members:
141-
.. autoclass:: plotpy.items.RangeComputation
141+
.. autoclass:: plotpy.items.XRangeComputation
142+
:members:
143+
.. autoclass:: plotpy.items.YRangeComputation
142144
:members:
143145
.. autoclass:: plotpy.items.RangeComputation2d
144146
:members:

doc/features/tools/reference.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ Shape tools
9292
Curve tools
9393
^^^^^^^^^^^
9494

95-
.. autoclass:: plotpy.tools.XCurveStatsTool
95+
.. autoclass:: plotpy.tools.CurveStatsTool
9696
:members:
97-
.. autoclass:: plotpy.tools.YCurveStatsTool
97+
.. autoclass:: plotpy.tools.YRangeCursorTool
9898
:members:
9999
.. autoclass:: plotpy.tools.SelectPointTool
100100
:members:

plotpy/builder/label.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@
2929
ImageItem,
3030
LabelItem,
3131
LegendBoxItem,
32-
RangeComputation,
3332
RangeComputation2d,
3433
RangeInfo,
3534
RectangleShape,
3635
SelectedLegendBoxItem,
36+
XRangeComputation,
3737
XRangeSelection,
38+
YRangeComputation,
3839
YRangeSelection,
3940
)
4041
from plotpy.styles import LabelParam, LabelParamWithContents, LegendParam
@@ -137,13 +138,17 @@ def legend(
137138
return SelectedLegendBoxItem(param, restrict_items)
138139

139140
def info_label(
140-
self, anchor: str, comps: list, title: str | None = None
141+
self,
142+
anchor: str,
143+
comps: list[XRangeSelection] | list[YRangeSelection],
144+
title: str | None = None,
141145
) -> DataInfoLabel:
142146
"""Make an info label `plot item`
143147
144148
Args:
145149
anchor: anchor position. See :py:class:`.LabelParam` for details
146-
comps: list of :py:class:`.label.RangeComputation` objects
150+
comps: list of :py:class:`.label.XRangeComputation` or
151+
:py:class:`.label.YRangeComputation` objects
147152
title: label name. Default is None
148153
149154
Returns:
@@ -203,8 +208,8 @@ def computation(
203208
range: XRangeSelection | YRangeSelection,
204209
anchor: str,
205210
label: str,
206-
curve: CurveItem,
207-
function: Callable,
211+
curve: CurveItem | None = None,
212+
function: Callable | None = None,
208213
title: str | None = None,
209214
) -> DataInfoLabel:
210215
"""Make a computation label `plot item`
@@ -213,7 +218,7 @@ def computation(
213218
range: range selection object
214219
anchor: anchor position. See :py:class:`.LabelParam` for details
215220
label: label name. See :py:class:`.DataInfoLabel` for details
216-
curve: curve item
221+
curve: curve item (not used for `YRangeSelection`)
217222
function: function to apply to the range selection object
218223
Default is None (default function is `lambda x, dx: (x, dx)`)
219224
@@ -222,37 +227,59 @@ def computation(
222227
"""
223228
if title is None:
224229
title = curve.param.label
225-
return self.computations(range, anchor, [(curve, label, function)], title=title)
230+
if isinstance(range, XRangeSelection):
231+
specs = [(curve, label, function)]
232+
elif isinstance(range, YRangeSelection):
233+
specs = [(label, function)]
234+
else:
235+
raise TypeError(
236+
"Invalid range selection type: "
237+
f"{type(range).__name__}. "
238+
"Expected XRangeSelection or YRangeSelection."
239+
)
240+
return self.computations(range, anchor, specs, title=title)
226241

227242
def computations(
228243
self,
229-
range: XRangeSelection | YRangeSelection,
244+
rangeselection: XRangeSelection | YRangeSelection,
230245
anchor: str,
231-
specs: list,
246+
specs: list[tuple[CurveItem, str, Callable]] | list[tuple[str, Callable]],
232247
title: str | None = None,
233248
) -> DataInfoLabel:
234249
"""Make computation labels `plot item`
235250
236251
Args:
237-
range: range selection object
252+
rangeselection: range selection object
238253
anchor: anchor position. See :py:class:`.LabelParam` for details
239-
specs: list of (curve, label, function) tuples
254+
specs: list of (curve, label, function) tuples for `XRangeComputation`,
255+
list of (label, function) tuples for `YRangeComputation`
240256
title: label name. Default is None
241257
242258
Returns:
243259
Data info label object
244260
"""
245261
comps = []
246-
same_curve = True
247-
curve0 = None
248-
for curve, label, function in specs:
249-
comp = RangeComputation(label, curve, range, function)
250-
comps.append(comp)
251-
if curve0 is None:
252-
curve0 = curve
253-
same_curve = same_curve and curve is curve0
254-
if title is None and same_curve:
255-
title = curve.param.label
262+
if isinstance(rangeselection, XRangeSelection):
263+
same_curve = True
264+
curve0 = None
265+
for curve, label, function in specs:
266+
comp = XRangeComputation(label, curve, rangeselection, function)
267+
comps.append(comp)
268+
if curve0 is None:
269+
curve0 = curve
270+
same_curve = same_curve and curve is curve0
271+
if title is None and same_curve:
272+
title = curve.param.label
273+
elif isinstance(rangeselection, YRangeSelection):
274+
for label, function in specs:
275+
comp = YRangeComputation(label, rangeselection, function)
276+
comps.append(comp)
277+
else:
278+
raise TypeError(
279+
"Invalid range selection type: "
280+
f"{type(rangeselection).__name__}. "
281+
"Expected XRangeSelection or YRangeSelection."
282+
)
256283
return self.info_label(anchor, comps, title=title)
257284

258285
def computation2d(

plotpy/items/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
LegendBoxItem,
4747
ObjectInfo,
4848
RangeComputation,
49+
XRangeComputation,
50+
YRangeComputation,
4951
RangeComputation2d,
5052
RangeInfo,
5153
SelectedLegendBoxItem,

plotpy/items/label.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -948,23 +948,25 @@ def get_text(self) -> str:
948948
return self.label % self.func(v, dv)
949949

950950

951-
class RangeComputation(ObjectInfo):
951+
class XRangeComputation(ObjectInfo):
952952
"""ObjectInfo showing curve computations relative to a `XRangeSelection`
953-
or `YRangeSelection` shape
953+
shape
954954
955955
Args:
956956
label: formatted string
957957
curve: CurveItem object
958-
xrangeselection: `XRangeSelection` or `YRangeSelection` object
959-
function: input arguments are x, y arrays (extraction of arrays
960-
corresponding to the xrangeselection X-axis range)
958+
rangeselection: `XRangeSelection` object
959+
function: callback function to compute the text to be displayed depending on the
960+
range and eventually the curve data. Input arguments are x, y arrays
961+
(extraction of associated curve data corresponding to the `rangeselection`
962+
X-axis range).
961963
"""
962964

963965
def __init__(
964966
self,
965967
label: str,
966968
curve: CurveItem,
967-
rangeselection: XRangeSelection | YRangeSelection,
969+
rangeselection: XRangeSelection,
968970
function: Callable | None = None,
969971
) -> None:
970972
self.label = str(label)
@@ -987,14 +989,11 @@ def set_curve(self, curve: CurveItem) -> None:
987989

988990
def get_text(self) -> str:
989991
"""Return the text to be displayed"""
990-
# pylint: disable=import-outside-toplevel
991-
from plotpy.items import XRangeSelection
992-
993-
v0, v1 = self.range.get_range()
992+
x0, x1 = self.range.get_range()
994993
data = self.curve.get_data()
995-
vdata = data[0 if isinstance(self.range, XRangeSelection) else 1]
996-
i0 = vdata.searchsorted(v0)
997-
i1 = vdata.searchsorted(v1)
994+
xdata = data[0]
995+
i0 = xdata.searchsorted(x0)
996+
i1 = xdata.searchsorted(x1)
998997
if i0 > i1:
999998
i0, i1 = i1, i0
1000999
vectors = []
@@ -1008,6 +1007,45 @@ def get_text(self) -> str:
10081007
return self.label % self.func(*vectors)
10091008

10101009

1010+
RangeComputation = XRangeComputation # For backward compatibility
1011+
1012+
1013+
class YRangeComputation(ObjectInfo):
1014+
"""ObjectInfo showing computations relative to a `YRangeSelection` shape
1015+
1016+
Args:
1017+
label: formatted string
1018+
rangeselection: `YRangeSelection` object
1019+
function: callback function to compute the text to be displayed.
1020+
1021+
.. note::
1022+
1023+
Unlike the `XRangeSelection`, the `YRangeSelection` does not depend on any
1024+
curve data, the input arguments of the callback function are simply ymin, ymax
1025+
values (extraction of the `rangeselection` Y-axis range).
1026+
"""
1027+
1028+
def __init__(
1029+
self,
1030+
label: str,
1031+
rangeselection: YRangeSelection,
1032+
function: Callable,
1033+
) -> None:
1034+
self.label = str(label)
1035+
self.range = rangeselection
1036+
if function is None:
1037+
1038+
def function(v, dv):
1039+
return v, dv
1040+
1041+
self.func = function
1042+
1043+
def get_text(self) -> str:
1044+
"""Return the text to be displayed"""
1045+
y0, y1 = self.range.get_range()
1046+
return self.label % self.func(y0, y1)
1047+
1048+
10111049
class RangeComputation2d(ObjectInfo):
10121050
"""ObjectInfo showing image computations relative to a rectangular area
10131051

plotpy/locale/fr/LC_MESSAGES/plotpy.po

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ msgid ""
66
msgstr ""
77
"Project-Id-Version: plotpy 2.7.4\n"
88
"Report-Msgid-Bugs-To: [email protected]\n"
9-
"POT-Creation-Date: 2025-06-22 12:17+0200\n"
9+
"POT-Creation-Date: 2025-06-22 16:58+0200\n"
1010
"PO-Revision-Date: 2025-06-02 11:14+0200\n"
1111
"Last-Translator: Christophe Debonnel <[email protected]>\n"
1212
"Language: fr\n"
@@ -1189,6 +1189,9 @@ msgstr "Curseur croix"
11891189
msgid "Signal statistics"
11901190
msgstr "Statistiques du signal"
11911191

1192+
msgid "Y-range"
1193+
msgstr "Intervalle en Y"
1194+
11921195
msgid "Antialiasing (curves)"
11931196
msgstr "Anticrénelage (courbes)"
11941197

@@ -1675,4 +1678,3 @@ msgstr "Rotation et rognage"
16751678

16761679
msgid "Show cropping rectangle"
16771680
msgstr "Afficher le rectangle de rognage"
1678-

plotpy/locale/plotpy.pot

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ msgid ""
77
msgstr ""
88
"Project-Id-Version: plotpy VERSION\n"
99
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10-
"POT-Creation-Date: 2025-06-22 12:17+0200\n"
10+
"POT-Creation-Date: 2025-06-22 16:58+0200\n"
1111
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1212
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1313
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -1176,6 +1176,9 @@ msgstr ""
11761176
msgid "Signal statistics"
11771177
msgstr ""
11781178

1179+
msgid "Y-range"
1180+
msgstr ""
1181+
11791182
msgid "Antialiasing (curves)"
11801183
msgstr ""
11811184

plotpy/plot/manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,8 @@ def register_curve_tools(self) -> None:
549549
# pylint: disable=import-outside-toplevel
550550
import plotpy.tools as tools
551551

552-
self.add_tool(tools.XCurveStatsTool)
553-
self.add_tool(tools.YCurveStatsTool)
552+
self.add_tool(tools.CurveStatsTool)
553+
self.add_tool(tools.YRangeCursorTool)
554554
self.add_tool(tools.AntiAliasingTool)
555555
self.add_tool(tools.AxisScaleTool)
556556
self.add_tool(tools.DownSamplingTool)

plotpy/tests/tools/test_cyclic_import.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ def test_tools_cyclic_import():
1515
from plotpy.tools import (
1616
AnnotatedPointTool, # noqa: F401
1717
AverageCrossSectionTool, # noqa: F401
18+
CurveStatsTool, # noqa: F401
1819
DoAutoscaleTool, # noqa: F401
1920
ItemCenterTool, # noqa: F401
2021
OpenImageTool, # noqa: F401
2122
PointTool, # noqa: F401
2223
PrintTool, # noqa: F401
2324
RectangularActionTool, # noqa: F401
2425
SelectTool, # noqa: F401
25-
XCurveStatsTool, # noqa: F401
26-
YCurveStatsTool, # noqa: F401
26+
YRangeCursorTool, # noqa: F401
2727
)
2828

2929

0 commit comments

Comments
 (0)