-
-
Notifications
You must be signed in to change notification settings - Fork 35
/
bar.py
158 lines (126 loc) · 5.65 KB
/
bar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# -----------------------------------------------------------------------------
# Copyright (c) 2020 Nicolas P. Rougier. All rights reserved.
# Distributed under the (new) BSD License.
# -----------------------------------------------------------------------------
# This example shows how to render bars
# -----------------------------------------------------------------------------
from mpl3d import glm
from mpl3d.camera import Camera
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
class Bar:
""" Bar (histogram) """
def __init__(self, ax, transform, Z,
facecolors="white", edgecolors="black", linewidth=0, clip=False):
""" """
self.Z = Z
if isinstance(facecolors, np.ndarray):
shape = facecolors.shape
facecolors = facecolors.reshape(-1,shape[-1])
facecolors = mpl.colors.to_rgba_array(facecolors)
self.facecolors = facecolors.reshape(shape[0], shape[1], 4)
else:
shape = Z.shape
self.facecolors = np.zeros((shape[0], shape[1], 4))
self.facecolors[...] = mpl.colors.to_rgba(facecolors)
if isinstance(edgecolors, np.ndarray):
shape = edgecolors.shape
edgecolors = edgecolors.reshape(-1,shape[-1])
edgecolors = mpl.colors.to_rgba_array(edgecolors)
self.edgecolors = edgecolors.reshape(shape[0], shape[1], 4)
else:
shape = Z.shape
self.edgecolors = np.zeros((shape[0], shape[1], 4))
self.edgecolors[...] = mpl.colors.to_rgba(edgecolors)
self.linewidth = linewidth
self.xlim = -0.5, +0.50
self.ylim = -0.5, +0.50
self.zlim = -0.5, +0.50
self.clip = clip
# Because all the bars have the same orientation, we can use a hack to
# shade each face at once instead of computing individual face lighting.
self.shade = np.array([[1.00, 1.00, 0.75, 1.00, 0.50, 1.00]])
self.collection = PolyCollection([], clip_on=self.clip, snap=False)
self.update(transform)
ax.add_collection(self.collection, autolim=False)
def update(self, transform):
""" """
Z = self.Z
xmin, xmax = self.xlim
ymin, ymax = self.ylim
zmin, zmax = self.zlim
dx, dy = 0.5 * 1/Z.shape[0], 0.5 * 1/Z.shape[1]
# Each bar is described by 8 vertices and 6 faces
V = np.zeros((Z.shape[0], Z.shape[1], 8, 3))
F = np.zeros((Z.shape[0], Z.shape[1], 6, 4), dtype=int)
# Face and edge colors for the six faces
FC = np.zeros((Z.shape[0], Z.shape[1], 6, 4))
FC[:,:] = self.facecolors.reshape(Z.shape[0], Z.shape[1], 1, 4)
FC *= self.shade.T
FC[:,:,:,3] = 1
EC = np.zeros((Z.shape[0], Z.shape[1], 6, 4))
EC[:,:] = self.edgecolors.reshape(Z.shape[0], Z.shape[1], 1, 4)
# Build vertices
X,Y = np.meshgrid(np.linspace(xmin, xmax, Z.shape[0]),
np.linspace(ymin, ymax, Z.shape[1]))
V[...,0] = X.reshape(Z.shape[0], Z.shape[1],1)
V[...,1] = Y.reshape(Z.shape[0], Z.shape[1],1)
V[:,:,0] += [+dx, +dy, zmin]
V[:,:,1] += [+dx, -dy, zmin]
V[:,:,2] += [-dx, -dy, zmin]
V[:,:,3] += [-dx, +dy, zmin]
V[:,:,4] += [+dx, +dy, zmin]
V[:,:,5] += [+dx, -dy, zmin]
V[:,:,6] += [-dx, -dy, zmin]
V[:,:,7] += [-dx, +dy, zmin]
V[:,:,4:,2] += Z.reshape(Z.shape[0], Z.shape[1],1)
# Build faces
I = 8*np.arange(Z.shape[0]*Z.shape[1])
F[:,:] = I.reshape(Z.shape[0], Z.shape[1], 1, 1)
F[:,:] += [ [0, 1, 2, 3], # -Z
[0, 1, 5, 4], # +X
[2, 3, 7, 6], # -X
[1, 2, 6, 5], # -Y
[0, 3, 7, 4], # +Y
[4, 5, 6, 7]] # +Z
# Actual transformation
V = V.reshape(-1,3)
V = glm.transform(V[F], transform) #[...,:2]
# Depth computation
# We combine the global "depth" of the bar (depth of the bottom face)
# and the local depth of each face. This trick avoids problems when
# sorting all the different faces.
Z1 = (V[:,:,0,:,2].mean(axis=2)).reshape(Z.shape[0], Z.shape[1],1)
Z2 = (V[...,2].mean(axis=3) + 10*Z1).ravel()
# Sorting
I = np.argsort(-Z2)
V = (V[...,:2].reshape(Z.shape[0]*Z.shape[1]*6, 4, 2))
self.collection.set_verts(V[I])
self.collection.set_facecolors(FC.reshape(-1,4)[I])
self.collection.set_edgecolors(EC.reshape(-1,4)[I])
self.collection.set_linewidths(self.linewidth)
if self.linewidth == 0.0:
self.collection.set_antialiased(False)
else:
self.collection.set_antialiased(True)
# -----------------------------------------------------------------------------
if __name__ == '__main__':
import imageio
from matplotlib.patches import Circle
Z = imageio.imread("data/island.png")[::10,::10,0]
Z = (Z-Z.min())/(Z.max()-Z.min())
Z += 0.05*np.random.uniform(0, 1, Z.shape)
Z = 0.25*Z*Z
cmap = plt.get_cmap("Reds")
norm = mpl.colors.Normalize(vmin=Z.min(),vmax=Z.max())
facecolors = cmap(norm(Z))
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1], xlim=[-1,+1], ylim=[-1,0], aspect=1)
ax.axis("off")
camera = Camera("perspective", 65, -125)
bars = Bar(ax, camera.transform, Z, facecolors=facecolors)
camera.connect(ax, bars.update)
plt.savefig("bar.png", dpi=300)
plt.show()