Skip to content

Commit b630230

Browse files
authored
Merge pull request #71 from petercorke/fix/highlight-3d
fix: highlight_edge, highlight_path and highlight_vertex in 3D
2 parents 5fea937 + 0e7e668 commit b630230

2 files changed

Lines changed: 145 additions & 10 deletions

File tree

‎src/pgraph/PGraph.py‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,8 +2765,10 @@ def highlight_edge(
27652765
import matplotlib.pyplot as plt
27662766
if edge.v1 is None or edge.v2 is None:
27672767
raise ValueError("edge does not connect two vertices")
2768-
# over the edge as plot() draws it, so that it follows an arc
2768+
# over the edge as plot() draws it, so that it follows an arc, and in
2769+
# three dimensions if that is what the axes are
27692770
ax = plt.gca()
2771+
ndim = 3 if getattr(ax, "name", None) == "3d" else 2
27702772
rads, loopno = self._edge_layout()
27712773
ax.add_patch(
27722774
self._edge_artist(
@@ -2775,7 +2777,7 @@ def highlight_edge(
27752777
rads.get(edge, 0.0),
27762778
loopno.get(edge, 0),
27772779
12,
2778-
2,
2780+
ndim,
27792781
arrowstyle="-",
27802782
color=color,
27812783
linewidth=3 * scale,
@@ -2832,15 +2834,20 @@ def highlight_vertex(
28322834
28332835
"""
28342836
import matplotlib.pyplot as plt
2835-
if isinstance(vertex, Iterable):
2836-
for n in vertex:
2837-
if isinstance(n, str):
2838-
n = self[n]
2839-
plt.plot(n.x, n.y, "o", color=color, markersize=12 * scale, alpha=alpha)
2840-
else:
2841-
plt.plot(
2842-
vertex.x, vertex.y, "o", color=color, markersize=12 * scale, alpha=alpha
2837+
2838+
ax = plt.gca()
2839+
is3d = getattr(ax, "name", None) == "3d"
2840+
vertices = vertex if isinstance(vertex, Iterable) else [vertex]
2841+
for n in vertices:
2842+
if isinstance(n, str):
2843+
n = self[n]
2844+
style: dict[str, Any] = dict(
2845+
color=color, markersize=12 * scale, alpha=alpha
28432846
)
2847+
if is3d:
2848+
ax.plot([n.x], [n.y], [n.z], "o", **style)
2849+
else:
2850+
ax.plot(n.x, n.y, "o", **style)
28442851

28452852
def export(
28462853
self,

‎tests/test_highlight_3d.py‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
Highlighting in three dimensions: ``highlight_edge``, ``highlight_path`` and
3+
``highlight_vertex`` draw in the axes that ``plot()`` made.
4+
5+
``highlight_edge`` assumed two dimensions, so on a 3D graph it added a flat
6+
patch to a 3D axes, which raised ``AttributeError`` when the figure was drawn,
7+
and ``highlight_vertex`` drew its marker at z = 0. In 0.6.5 ``highlight_path``
8+
worked in 3D.
9+
"""
10+
11+
import unittest
12+
import warnings
13+
14+
import matplotlib
15+
16+
matplotlib.use("Agg")
17+
import matplotlib.pyplot as plt
18+
import numpy as np
19+
20+
from pgraph import DGraph, UGraph
21+
from pgraph._plot import _Arrow3D, _Loop3D
22+
23+
24+
def graph(cls, dim=3):
25+
g = cls(dim=dim)
26+
coords = [[0, 0, 0], [1, 1, 0], [2, 0, 1], [0, 2, 2]]
27+
v = [g.add_vertex(c[:dim], name=n) for n, c in zip("abcd", coords)]
28+
edges = [g.add_edge(v[p], v[q]) for p, q in [(0, 1), (1, 2), (2, 3), (3, 0), (0, 1), (2, 2)]]
29+
return g, v, edges
30+
31+
32+
class TestHighlight3D(unittest.TestCase):
33+
def setUp(self):
34+
plt.close("all")
35+
warnings.simplefilter("ignore")
36+
37+
def draws(self):
38+
fig = plt.gcf()
39+
fig.canvas.draw() # the failure was here, not when the patch was added
40+
return fig.axes[0]
41+
42+
def test_highlight_edge(self):
43+
for cls in (UGraph, DGraph):
44+
with self.subTest(cls=cls.__name__):
45+
plt.close("all")
46+
g, v, edges = graph(cls)
47+
g.plot(block=None)
48+
ax = plt.gca()
49+
before = len(ax.patches)
50+
g.highlight_edge(edges[1])
51+
self.assertEqual(len(ax.patches), before + 1)
52+
self.assertIsInstance(ax.patches[-1], _Arrow3D)
53+
self.draws()
54+
55+
def test_highlight_a_loop(self):
56+
g, v, edges = graph(DGraph)
57+
g.plot(block=None)
58+
g.highlight_edge(edges[5]) # the loop on c
59+
self.assertIsInstance(plt.gca().patches[-1], _Loop3D)
60+
self.draws()
61+
62+
def test_highlight_a_parallel_edge(self):
63+
g, v, edges = graph(UGraph)
64+
g.plot(block=None)
65+
g.highlight_edge(edges[4]) # the second edge from a to b
66+
self.assertIsInstance(plt.gca().patches[-1], _Arrow3D)
67+
self.draws()
68+
69+
def test_highlight_path(self):
70+
g, v, edges = graph(UGraph)
71+
g.plot(block=None)
72+
g.highlight_path([v[0], v[1], v[2]], block=None)
73+
ax = self.draws()
74+
# the 5 edges that are not a loop, and the 2 of the path over them
75+
self.assertEqual(sum(isinstance(p, _Arrow3D) for p in ax.patches), 5 + 2)
76+
self.assertEqual(sum(isinstance(p, _Loop3D) for p in ax.patches), 1)
77+
78+
def test_highlight_vertex_is_at_the_z_of_the_vertex(self):
79+
g, v, edges = graph(UGraph)
80+
g.plot(block=None)
81+
g.highlight_vertex(v[3])
82+
ax = self.draws()
83+
x, y, z = ax.lines[-1].get_data_3d()
84+
np.testing.assert_allclose([x[0], y[0], z[0]], [0, 2, 2])
85+
86+
def test_highlight_several_vertices_by_name(self):
87+
g, v, edges = graph(UGraph)
88+
g.plot(block=None)
89+
n = len(plt.gca().lines)
90+
g.highlight_vertex(["a", v[2]])
91+
self.assertEqual(len(plt.gca().lines), n + 2)
92+
self.draws()
93+
94+
def test_a_3d_graph_plotted_in_2d_is_highlighted_in_2d(self):
95+
g, v, edges = graph(UGraph)
96+
g.plot(block=None, force_2d=True)
97+
g.highlight_path([v[0], v[1], v[2]], block=None)
98+
g.highlight_vertex(v[1])
99+
ax = self.draws()
100+
self.assertNotEqual(getattr(ax, "name", None), "3d")
101+
np.testing.assert_allclose(ax.lines[-1].get_xydata(), [[1, 1]])
102+
103+
104+
class TestHighlight2DUnchanged(unittest.TestCase):
105+
def setUp(self):
106+
plt.close("all")
107+
warnings.simplefilter("ignore")
108+
109+
def test_vertex_marker(self):
110+
g, v, edges = graph(DGraph, dim=2)
111+
g.plot(block=None)
112+
g.highlight_vertex(v[2], color="g", scale=1.5, alpha=0.25)
113+
line = plt.gca().lines[-1]
114+
np.testing.assert_allclose(line.get_xydata(), [[2, 0]])
115+
self.assertEqual(line.get_color(), "g")
116+
self.assertEqual(line.get_markersize(), 12 * 1.5)
117+
self.assertEqual(line.get_alpha(), 0.25)
118+
119+
def test_edge_patch_is_flat(self):
120+
g, v, edges = graph(DGraph, dim=2)
121+
g.plot(block=None)
122+
g.highlight_edge(edges[0])
123+
self.assertNotIsInstance(plt.gca().patches[-1], _Arrow3D)
124+
plt.gcf().canvas.draw()
125+
126+
127+
if __name__ == "__main__":
128+
unittest.main()

0 commit comments

Comments
 (0)