Skip to content

Commit 7d28ac9

Browse files
Merge branch 'develop'
2 parents 4673204 + 7154606 commit 7d28ac9

8 files changed

Lines changed: 163 additions & 4 deletions

File tree

‎doc/release_notes/release_2.11.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Version 2.11 #
2+
3+
## PlotPy Version 2.11.0 ##
4+
5+
✨ New features:
6+
* **Enhance contour builder with style parameters** — Add color, linestyle and linewidth parameters to ContourLine item class
7+
8+
🛠️ Bug fixes:
9+
10+
* **Image cross sections** — Restored live cross-section updates when using `Alt+Mousemove` over an image. This regression had been introduced in v2.1.0: the X/Y cross-section panels no longer received marker-change updates because method resolution order was shadowing the mixin hook that connects `SIG_MARKER_CHANGED` (closes [Issue #68](https://github.com/PlotPyStack/PlotPy/issues/68))
11+
12+
* **Empty Label ledend box crash prevent** — When plot items list is empty, LegendBoxItem could crashes while clicking on it. Fix and tests added

‎plotpy/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
.. _GitHub: https://github.com/PierreRaybaut/plotpy
2121
"""
2222

23-
__version__ = "2.10.0"
23+
__version__ = "2.11.0"
2424
__VERSION__ = tuple([int(number) for number in __version__.split(".")])
2525

2626
# --- Important note: DATAPATH and LOCALEPATH are used by guidata.configtools

‎plotpy/builder/image.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,9 @@ def contours(
826826
levels: float | np.ndarray,
827827
X: np.ndarray | None = None,
828828
Y: np.ndarray | None = None,
829+
color: str | None = None,
830+
linestyle: str | None = None,
831+
linewidth: float | None = None,
829832
) -> list[ContourItem]:
830833
"""Make a contour curves
831834
@@ -845,11 +848,24 @@ def contours(
845848
``numpy.meshgrid``), or it must both be 1-D such that ``len(Y) == N``
846849
is the number of rows in *Z*.
847850
If none, they are assumed to be integer indices, i.e. ``Y = range(N)``.
851+
color: contour color name. Default is None
852+
linestyle: contour line style (MATLAB-like string or "SolidLine",
853+
"DashLine", "DotLine", "DashDotLine", "DashDotDotLine", "NoPen").
854+
Default is None
855+
linewidth: contour line width (pixels). Default is None
848856
849857
Returns:
850858
list of :py:class:`.ContourItem` objects
851859
"""
852-
return create_contour_items(Z, levels, X, Y)
860+
return create_contour_items(
861+
Z,
862+
levels,
863+
X,
864+
Y,
865+
color=color,
866+
linestyle=linestyle,
867+
linewidth=linewidth,
868+
)
853869

854870
def histogram2D(
855871
self,

‎plotpy/items/contour.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from plotpy.config import _
3131
from plotpy.items.shape.polygon import PolygonShape
32-
from plotpy.styles import ShapeParam
32+
from plotpy.styles import COLORS, ShapeParam
3333

3434

3535
class ContourLine(gds.DataSet):
@@ -123,6 +123,9 @@ def create_contour_items(
123123
levels: float | np.ndarray,
124124
X: np.ndarray | None = None,
125125
Y: np.ndarray | None = None,
126+
color: str | None = None,
127+
linestyle: str | None = None,
128+
linewidth: float | None = None,
126129
) -> list[ContourItem]:
127130
"""Create contour items
128131
@@ -142,6 +145,11 @@ def create_contour_items(
142145
``numpy.meshgrid``), or it must both be 1-D such that ``len(Y) == N``
143146
is the number of rows in *Z*.
144147
If none, they are assumed to be integer indices, i.e. ``Y = range(N)``.
148+
color: contour color name. Default is None
149+
linestyle: contour line style (MATLAB-like string or "SolidLine",
150+
"DashLine", "DotLine", "DashDotLine", "DashDotDotLine", "NoPen").
151+
Default is None
152+
linewidth: contour line width (pixels). Default is None
145153
146154
Returns:
147155
A list of :py:class:`.ContourItem` instances.
@@ -153,6 +161,14 @@ def create_contour_items(
153161
param = ShapeParam("Contour", icon="contour.png")
154162
item = ContourItem(points=cline.vertices, shapeparam=param)
155163
item.set_style("plot", "shape/contour")
164+
if color is not None:
165+
item.shapeparam.line.color = COLORS.get(color, color)
166+
if linestyle is not None:
167+
item.shapeparam.line.set_style_from_matlab(linestyle)
168+
if linewidth is not None:
169+
item.shapeparam.line.width = linewidth
170+
if color is not None or linestyle is not None or linewidth is not None:
171+
item.shapeparam.update_item(item)
156172
item.setTitle(_("Contour") + f"[Z={cline.level}]")
157173
items.append(item)
158174
return items

‎plotpy/items/label.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ def click_inside(self, locx: float, locy: float) -> tuple[float, float, bool, ty
828828
for item in self.plot().get_items()
829829
if self.include_item(item) and isinstance(item, CurveItem)
830830
]
831-
if line < len(items):
831+
if 0 <= line < len(items):
832832
return 1000.0, None, False, items[line]
833833
return 2.0, 1, True, None
834834

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Licensed under the terms of the BSD 3-Clause
4+
# (see plotpy/LICENSE for details)
5+
6+
"""Contour builder test"""
7+
8+
# guitest: show
9+
10+
import numpy as np
11+
from guidata.qthelpers import qt_app_context
12+
13+
from plotpy.builder import make
14+
from plotpy.items import ContourItem
15+
from plotpy.tests import vistools as ptv
16+
17+
18+
def create_two_gaussians() -> np.ndarray:
19+
"""Create two nearby 2D Gaussians with different shapes."""
20+
axis = np.linspace(-5.0, 5.0, 300)
21+
x, y = np.meshgrid(axis, axis)
22+
wide = np.exp(-((x + 0.9) ** 2 + (y + 0.2) ** 2) / (2.0 * 1.2**2))
23+
narrow = 0.65 * np.exp(-((x - 1.0) ** 2 + (y - 0.4) ** 2) / (2.0 * 0.6**2))
24+
return wide + narrow
25+
26+
27+
def test_contours():
28+
"""Contour plotting on two nearby 2D Gaussians"""
29+
with qt_app_context(exec_loop=True):
30+
image = create_two_gaussians()
31+
contour_items = make.contours(
32+
image,
33+
np.linspace(0.2, 1.0, 5),
34+
color="y",
35+
linestyle=":",
36+
linewidth=2.0,
37+
)
38+
39+
assert contour_items
40+
assert all(isinstance(item, ContourItem) for item in contour_items)
41+
42+
_win = ptv.show_items(
43+
[make.image(image, colormap="cool")] + contour_items,
44+
wintitle=test_contours.__doc__,
45+
curve_antialiasing=False,
46+
lock_aspect_ratio=True,
47+
)
48+
49+
50+
if __name__ == "__main__":
51+
test_contours()

‎plotpy/tests/items/test_label.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Label and legend item tests."""
4+
5+
import numpy as np
6+
from guidata.qthelpers import qt_app_context
7+
8+
from plotpy.builder import make
9+
10+
11+
def test_legend_click_inside_out_of_range_row() -> None:
12+
"""Clicking outside a legend row must not index the curve list negatively."""
13+
with qt_app_context(exec_loop=False):
14+
window = make.dialog(edit=False, type="curve", size=(640, 480))
15+
plot = window.manager.get_plot()
16+
plot.add_item(make.curve(np.arange(3), np.arange(3), title="Signal"))
17+
legend = make.legend("TR")
18+
plot.add_item(legend)
19+
legend.get_text_rect()
20+
21+
result = legend.click_inside(6.0, -100.0)
22+
23+
assert result == (2.0, 1, True, None)
24+
window.close()
25+
26+
27+
def test_empty_legend_click_inside_does_not_crash() -> None:
28+
"""Clicking an empty legend must not index an empty curve list."""
29+
with qt_app_context(exec_loop=False):
30+
window = make.dialog(edit=False, type="curve", size=(640, 480))
31+
plot = window.manager.get_plot()
32+
legend = make.legend("TR")
33+
plot.add_item(legend)
34+
legend.get_text_rect()
35+
36+
result = legend.click_inside(6.0, -100.0)
37+
38+
assert result == (2.0, 1, True, None)
39+
window.close()

‎plotpy/tests/unit/test_contour.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import unittest
22

33
import numpy as np
4+
from qtpy.QtCore import Qt
45

6+
from plotpy.builder import make
57
from plotpy.items.contour import compute_contours
68
from plotpy.tests.data import gen_xyz_data
79

@@ -54,3 +56,26 @@ def test_contour_abs_level_1(self):
5456
for line in lines:
5557
assert np.all(np.equal(line.vertices[0], line.vertices[-1]))
5658
assert line.level == 1.0
59+
60+
def test_contour_item_style(self):
61+
"""Test contour builder line style parameters."""
62+
items = make.contours(
63+
self.Z,
64+
1.0,
65+
self.X,
66+
self.Y,
67+
color="r",
68+
linestyle="--",
69+
linewidth=2.5,
70+
)
71+
assert items
72+
for item in items:
73+
assert item.pen.color().name() == "#ff0000"
74+
assert item.pen.style() == Qt.DashLine
75+
assert item.pen.widthF() == 2.5
76+
77+
default_item = make.contours(self.Z, 1.0, self.X, self.Y)[0]
78+
color_item = make.contours(self.Z, 1.0, self.X, self.Y, color="b")[0]
79+
assert color_item.pen.color().name() == "#0000ff"
80+
assert color_item.pen.style() == default_item.pen.style()
81+
assert color_item.pen.widthF() == default_item.pen.widthF()

0 commit comments

Comments
 (0)