Coloring plot based on coordinates - python

I'm doing some linear programming for optimization and I need to present the data, using different colors on the surface based on the value of the optimized function. The optimized function requires all three x, y, z coordinates to be passed in, so basically the surface I need to color iw a concave 3D polyhedron. The walls can be vertical as well. My current approach is something like that:
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3
import scipy as sp
import numpy as np
from matplotlib import colors
verts = [(0, 0, 0), (1, 1, 1), (0, 1, 0), (1, 0, 0)]
faces = [[0, 1, 2], [0, 1, 3]]
ax = a3.Axes3D(plt.figure())
ax.dist = 30
ax.azim = -140
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.set_zlim([0, 1])
for face in faces:
triangle = verts[face[0]], verts[face[1]], verts[face[2]]
face = a3.art3d.Poly3DCollection([triangle])
face.set_color(colors.rgb2hex(sp.rand(3)))
ax.add_collection3d(face)
plt.show()
But this allows me only to set a single color to a face. My concept is doing it by simply increasing the number of polygons on each wall, but isn't there a simpler way like passing a 3d function to the plot which would map a color to a point based on all three coordinates (I've seen examples for colormaps taking only x and y arguments, but it does not handle vertical walls accordingly.

Related

How to plot a parallelepiped in matplotlib

I would like to draw a parallelepiped in Python using matplotlib centred in (0,0,0), with the top face of a different color (or each face of a different parametrized color), and with these dimensions:
L = 1
l = 0.7
s = 0.4
This is the code I developed to draw a cube with the same face color.
import matplotlib.pyplot as plt
import numpy as np
# Create axis
axes = [5, 5, 5]
# Create Data
data = np.ones(axes, dtype = np.bool)
# Control Tranperency
alpha = 0.9
# Control colour
colors = np.empty(axes + [4], dtype = np.float32)
colors[:] = [1, 0, 0, alpha] # red
# Plot figure
fig2 = plt.figure()
ax = fig2.add_subplot(111, projection='3d')
ax.voxels(data, facecolors=colors)
Any suggestion to modify it? Considering that I would like to rotate it with a rotation matrix/quaternion operator in a second time, it would be useful to define the coordinates of the vertices or of some ley points of the parallelepiped.
thank you all!

How to determine which cubes the line passes through

I was looking for a way to build cubes of the same size, then draw a line through this space and output the result in the form of coordinates of cubes that this line intersects and paint these cubes with a different color. The line can be either straight or curved.
I used matplotlib to plot cubes and lines. From these sources:
https://www.geeksforgeeks.org/how-to-draw-3d-cube-using-matplotlib-in-python/
Representing voxels with matplotlib
Example code:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
axes = [5, 5, 5]
# Create Data
data = np.ones(axes, dtype=np.bool)
# Controll Tranperency
alpha = 0.3
# Control colour
colors = np.empty(axes + [4], dtype=np.float32)
colors[0] = [1, 0, 0, alpha] # red
colors[1] = [0, 1, 0, alpha] # green
colors[2] = [0, 0, 1, alpha] # blue
colors[3] = [1, 1, 0, alpha] # yellow
colors[4] = [1, 1, 1, alpha] # grey
# Plot figure
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x1 = [1, 4]
y1 = [0, 5]
z1 = [0, 5]
ax.plot3D(x1, y1, z1, 'black', linewidth = 5)
# Voxels is used to customizations of
# the sizes, positions and colors.
ax.voxels(data, facecolors=colors, edgecolors='grey')
result
In short: I need to plot a grid of cubes and draw a line through it. After determine which cubes this line intersects.
Is it possible to do this in Matplotlib or do I need to use another library to solve my question?
God, why do I put myself though this.
Anyways, here is an iterative solution because I do not feel like doing linear algebra. I tried and I failed.
# Here be dragons
def linelamb(x,y,z):
return lambda s: [int(i) for i in [x[0]+s*(x[1]-x[0]), y[0]+s*(y[1]-y[0]), z[0]+s*(z[1]-z[0])]]
line = linelamb(x1,y1,z1)
hitboxes = np.zeros(axes)
x,y,z = 0,0,0
for r in [i for i in np.arange(0,1,0.001)]:
xnew,ynew,znew = line(r)
if not (x == xnew and y == ynew and z == znew):
hitboxes[xnew,ynew,znew] = 1
x,y,z = xnew,ynew,znew
ax.voxels(hitboxes, facecolors=[0,0,0,0.5], edgecolors='black');
I spent some extra time to make this more adaptable but my brain stopped working. You might want to adaptively change the step size of the range but good luck.

Intersection of a Shapely polygon with a Matplotlib wedge

In this scenario I am plotting matplotlib.patches.Wedge objects and also buffered shapely.geometry.LineString objects. I need to compute the overlapping areas of these two objects. However, the Wedge is a matplotlib.wedges object and cannot be used with Shapely's .intersection() method.
How can I do this?
Here is some code:
from shapely.geometry import LineString
from matplotlib.patches import Wedge
from matplotlib import pyplot as plt
from descartes.patch import PolygonPatch
width = 5
radius = 1
rich = 1
circle_patch = Wedge((0, 0), radius+3,
0, 360, 3)
fig, ax = plt.subplots()
ax.add_patch(circle_patch)
ax.plot(0, 0, 'xr')
plt.autoscale()
coords = [
[0, 0],
[0, 1],
[0, 2],
[1, 2],
[2, 2]
]
stick = LineString(coords)
stick_patch = PolygonPatch(stick.buffer(0.5))
ax.add_patch(stick_patch)
x, y = stick.xy
ax.plot(x, y, 'r-', zorder=1)
plt.show()
area = stick.buffer(0.5).intersection(circle_patch).area
P.S. It has to be a ring shape, not a circle
Figured it out. There is a ._path.vertices member of the matplotlib.patches class which gives you the array of coordinates of the wedge object which you can then use with Shapely's LinearRing class to create a Shapely object like so:
from shapely.geometry import LineString, LinearRing
from matplotlib.patches import Wedge
width = 5
radius = 1
rich = 1
circle_patch = Wedge((0, 0), radius,
0, 360,)
ring_coords = circle_patch._path.vertices
ring_coords = ring_coords[(ring_coords[:, 0] != 0) & (ring_coords[:, 1] != 0)]
ring = LinearRing(ring_coords)
It does however need manipulation of the coordinate array which I don't think is the most robust method but it will do for me. Also the ring is not entirely smooth but I am sure one could do some smoothing of the coordinate array with some or other Numpy or Scipy function.
EDIT: To create the single wedge line one must remove the width member of the wedge. This can however be re-incorporated later using Shapely's buffer() function.
The simplest solution would be not to work with Matplotlib patches and construct the wedge-polygon with Shapely in the first place:
import matplotlib.pyplot as plt
from descartes.patch import PolygonPatch
from shapely.geometry import LineString, Point
outer_circle = Point(0, 0).buffer(4)
inner_circle = Point(0, 0).buffer(1)
wedge = outer_circle.difference(inner_circle)
stick = LineString([(0, 0), (0, 2), (2, 2)])
buffered_stick = stick.buffer(0.5)
intersection = buffered_stick.intersection(wedge)
wedge_patch = PolygonPatch(wedge)
stick_patch = PolygonPatch(buffered_stick, alpha=0.5, hatch='/')
intersection_patch = PolygonPatch(intersection, alpha=0.5, hatch='.')
fig, ax = plt.subplots()
ax.add_patch(wedge_patch)
ax.add_patch(stick_patch)
ax.add_patch(intersection_patch)
plt.autoscale()
If, for some reason, this is not possible, and you have to work with the Matplotlib's Wedge, then I can think of two ways to get its intersection area with Shapely's polygon. In both of them, I convert the patches to Shapely polygons first. You probably can't get intersection area using only Matplotlib.
1) Using .get_path() method on the Matplotlib's patch from which you can extract vertices as a NumPy array and convert it to a Shapely polygon using asPolygon:
import matplotlib.pyplot as plt
from descartes.patch import PolygonPatch
from matplotlib.patches import Wedge
from shapely.geometry import asPolygon, LineString
wedge_patch = Wedge(center=(0, 0),
r=4,
theta1=0,
theta2=360,
width=3)
stick = LineString([(0, 0), (0, 2), (2, 2)])
buffered_stick = stick.buffer(0.5)
wedge_path = wedge_patch.get_path()
wedge_polygon = asPolygon(wedge_path.vertices).buffer(0)
intersection = buffered_stick.intersection(wedge_polygon)
stick_patch = PolygonPatch(buffered_stick, alpha=0.5, hatch='/')
intersection_patch = PolygonPatch(intersection, alpha=0.5, hatch='.')
fig, ax = plt.subplots()
ax.add_patch(wedge_patch)
ax.add_patch(stick_patch)
ax.add_patch(intersection_patch)
plt.autoscale()
Note the buffer(0) which I apply to the wedge polygon. This is a common trick in Shapely to make a valid polygon out of an invalid. In your answer you do something similar when removing zeros from ring_coords.
2) By accessing Wedge attributes: center, r and width, and using them to recreate a polygon:
import matplotlib.pyplot as plt
from descartes.patch import PolygonPatch
from matplotlib.patches import Wedge
from shapely.geometry import LineString, Point
wedge_patch = Wedge(center=(0, 0),
r=4,
theta1=0,
theta2=360,
width=3)
stick = LineString([(0, 0), (0, 2), (2, 2)])
buffered_stick = stick.buffer(0.5)
outer_circle = Point(wedge_patch.center).buffer(wedge_patch.r)
inner_circle = Point(wedge_patch.center).buffer(wedge_patch.r - wedge_patch.width)
wedge_polygon = outer_circle.difference(inner_circle)
intersection = buffered_stick.intersection(wedge_polygon)
stick_patch = PolygonPatch(buffered_stick, alpha=0.5, hatch='/')
intersection_patch = PolygonPatch(intersection, alpha=0.5, hatch='.')
fig, ax = plt.subplots()
ax.add_patch(wedge_patch)
ax.add_patch(stick_patch)
ax.add_patch(intersection_patch)
plt.autoscale()
All solutions give the same visual output.
And all methods give roughly the same area:
>>> intersection.area
3.3774012986988513 # 1st case
3.3823210603713694 # 2nd case and the original without Matplotlib

Plotting randomly stacked cubes in 3D- mplot3d?

I am exploring random stackings of cubes.
I started with 2D and can generate random packings within a rectangle like this:
Now I have the code to generalize the stacking to 3D, but I am struggling to generalize the visualization. An example data set is, filling a 3x3x3 cube with 1x1x1 and 2x2x2 cubes,
#the coordinates of a corner vertex of the 19 1x1x1 cubes
x1 = [1, 0, 2, 0, 0, 0, 2, 1, 0, 1, 2, 2, 0, 0, 0, 2, 0, 1, 1]
y1 = [1, 1, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0]
z1 = [2, 1, 1, 0, 1, 2, 2, 2, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2]
#the coordinates of a corner vertex of the 1 2x2x2 cube
x2 = [1]
y2 = [1]
z2 = [0]
# I believe the random filling is working because
# the total volumes equal: 19 + 2**3 = 3**3
#I would like to start with the lists
X = [x1,x2]
Y = [y1,y2]
Z = [z1,z2]
sizes = [1,2]
#because I want to generalize the visualization to n sizes
So far, all I have the knowledge to do is plot a 3D scatter of the data
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for ii in range(len(sizes)):
ax.scatter(X[ii],Y[ii],Z[ii])
plt.show()
I would like to make a plot more like this, except with variable sizes.
Any help would be greatly appreciated! I have a lot to learn about matplotlib/pyplot and so on.
I have made a little bit of progress:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
def cube(a,b,c,l):
for zz in [c,c+l]:
for i in ["x","y","z"]:
side = Rectangle((a, b), l,l)
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zz, zdir=i)
fig = plt.figure()
ax=fig.gca(projection='3d')
cube(0,0,0,1)
ax.set_xlim3d(-2, 2)
ax.set_ylim3d(-2, 2)
ax.set_zlim3d(-2, 2)
plt.show()
This plots a single cube.
EDIT:
More progress, I am now very close
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
cmap = plt.get_cmap('spring') #define the colors of the plot
colors = [cmap(i) for i in np.linspace(0.1, 0.9, n+1)]
def cube(a,b,c,l): #plots a cube of side l at (a,b,c)
for ll in [0,l]:
for i in range(3):
dire= ["x","y","z"]
xdire = [b,a,a]
ydire = [c,c,b]
zdire = [a,b,c]
side = Rectangle((xdire[i], ydire[i]),facecolors[np.where(sizes == l)[0]],edgecolor='black')
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zdire[i]+ll, zdir=dire[i])
def plotter3D(X,Y,Z,sizes): #run cube(a,b,c,l) over the whole data set
for iX in range(len(X)):
x = X[iX]
y = Y[iX]
z = Z[iX]
for ix in range(len(x)):
cube(x[ix],y[ix],z[ix],sizes[iX])
fig = plt.figure() #open a figure
ax=fig.gca(projection='3d') #make it 3d
plotter3D(X,Y,Z,sizes) #generate the cubes from the data set
ax.set_xlim3d(0, length) #set the plot ranges
ax.set_ylim3d(0, width)
ax.set_zlim3d(0, height)
plt.show()
This generates the desired output, although it seems to be see-through in some places when viewed from certain angles. You can see this in the small cube-- dead center at coordinates (1.5,2,3) Any idea how to fix this?
Another edit:
The solution outined above has two problems: (1) I can't get equal aspect ratios for the three axes, and (2) The cubes are see-through from certain angles. Here's what the output looks like for a larger system

How to add counts of points as a label in a sparse scatter plot

I have sparse scatter plot to visualize the comparison of predicted vs actual values. The range of the values are 1-4 and there are no decimal points.
I have tried plotly so far with hte following code (but I can also use a matplotlib solution):
my_scatter = go.Scatter(
x = y_actual, y = y_pred, mode = 'markers',
marker = dict(color = 'rgb(240, 189, 89)', opacity=0.5)
)
This prints the graph nicely (see below). I use opacity to see the density at each point. I.e. if two points lie on top of each other, the point will be shown in darker color. However, this is not explanatory enough. Is it possible to add the counts at each point as a label? There are some overlaps at certain intersections. I want to display how many points intersects. Can this be done automatically using matplotlib or plotly?
This answer uses matplotlib.
To answer the initial question first: You need to find out how often the data produces a point at a given coordinate to be able to annotate the points. If all values are integers this can easily be done using a 2d histogram. Out of the hstogram one would then select only those bins where the count value is nonzero and annotate the respective values in a loop:
x = [3, 0, 1, 2, 2, 0, 1, 3, 3, 3, 4, 1, 4, 3, 0]
y = [1, 0, 4, 3, 2, 1, 4, 0, 3, 0, 4, 2, 3, 3, 1]
import matplotlib.pyplot as plt
import numpy as np
x = np.array(x)
y = np.array(y)
hist, xbins,ybins = np.histogram2d(y,x, bins=range(6))
X,Y = np.meshgrid(xbins[:-1], ybins[:-1])
X = X[hist != 0]; Y = Y[hist != 0]
Z = hist[hist != 0]
fig, ax = plt.subplots()
ax.scatter(x,y, s=49, alpha=0.4)
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(4,0),
textcoords="offset points" )
plt.show()
You may then decide not to plot all points but the result from the histogramming which offers the chance to change the color and size of the scatter points,
ax.scatter(X,Y, s=(Z*20)**1.4, c = Z/Z.max(), cmap="winter_r", alpha=0.4)
Since all values are integers, you may also opt for an image plot,
fig, ax = plt.subplots()
ax.imshow(hist, cmap="PuRd")
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(0,0), color="w",
ha="center", va="center", textcoords="offset points" )
Without the necesity to calculate the number of occurances, another option is to use a hexbin plot. This gives slightly inaccurate positions of the dots, du to the hexagonal binning, but I still wanted to mention this option.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.array(x)
y = np.array(y)
fig, ax = plt.subplots()
cmap = plt.cm.PuRd
cmaplist = [cmap(i) for i in range(cmap.N)]
cmaplist[0] = (1.0,1.0,1.0,1.0)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mcm',cmaplist, cmap.N)
ax.hexbin(x,y, gridsize=20, cmap=cmap, linewidth=0 )
plt.show()

Categories