Drawing a cuboid to image - python

I'm in the process of moving a significant application to Python from PHP. All is well, except one particular issue that I can't seem to get to the bottom of.
The old application uses a PHP library Image_3D to draw some cuboids to an image (SVG, though that's not important) and display them on the web page.
I've migrated almost everything to Flask(w/SQLAlchemy) except fo the imaging. I've tried matplotlib, vpython and almost got desperate enough to reach for OpenGL bindings.
This is how PHP currently renders a few examples:
You can see that it remains readable enough even in extreme situations.
My best attempt at Python does fairly poorly:
(source: offblast.si)
(source: offblast.si)
The image is entirely unreadable. I didn't fill in the content, because, frankly, there's no point to it - you can't make anything out. Any attempt at fixing the view with ax.init_view was even worse, no matter the elevation or azimuth.
The code is just a bunch of points, rendered with
fig = plt.figure(frameon=False)
ax = fig.gca(projection='3d')
ax.set_axis_off()
ax.set_aspect('equal')
ax.plot_wireframe(points[:, 0], points[:, 1], points[:, 2], color="b")
plt.savefig(filename, bbox_inches='tight', pad_inches=0)
I'm sure I'm doing something wrong, but I can't figure out how to make readable renders work in Python / matplotlib or if there's a different library better suited for the task.
Update 1
It occurs to me I haven't described the problem correctly - it's a business-restricted subset of the knapsack problem. The container and cuboids within are thus arbitrarily positioned. The visualization module recieves dimensions and positions and needs to render them. The regularity of the previous examples is just a first-order optimal solution and is found often, but not in the majority of situations.
Some more examples to illustrate:
Update 2
A better solution with matplotlib, folowing the answer below. It doesn't seem easy to get MPL to rotate the entire plot as to show a useful overview.
ax.view_init should allow for changing the height, but it seems the FOV is too wide for such a narrow plot and all it does is transform the edges slightly.
(source: offblast.si)

You could use either VPython of matplotlib for this. In matplotlib I'd use a 3d box for your bars, and than another for the frame, and turn off the axes. Here's an examples:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection = "3d")
ax.autoscale(enable=False)
xpos = np.arange(5)
ypos = np.zeros(5)
zpos = np.zeros(5)
dx = np.ones(5)
dy = 5*np.ones(5)
dz = 5 + 5*np.random.random(5)
colors = ['r', 'c', 'g', 'y', 'm']
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, alpha=1)
ax.bar3d(0, 0, 0, 5, 5, 25, color=(1, 1, 1, 0))
ax.set_axis_off()
plt.show()

Related

Make all data points of a matplotlib plot homogeneously transparent

I'd like to plot two scatter plots into the same Axes and turn the upper one's data points transparent such that the other plot shines through. However, I want the whole upper plot to have a homogeneous transparency level, such that superimposed markers of the upper plot do not add up their opacity as they would do if I simply set alpha=0.5.
In other words, I'd like both scatter plots to be rendered first and being set to one constant transparency level. Technically this should be possible for both raster and vector graphics (as SVG supports layer transparency, afaik), but either would be fine for me.
Here is some example code that displays what I do not want to achieve. ;)
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax = fig.gca()
s1 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="b", edgecolors="none")
s2 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="g", edgecolors="none")
s2.set_alpha(0.5) # sadly the same as setting `alpha=0.5`
fig.show() # or display(fig)
I'd like the green markers around (2,2) to not be darker where they superimpose, for example. Is this possible with matplotlib?
Thanks for your time! :)
After searching some more, I found related questions and two solutions, of which at least one kind of works for me:
As I hoped one can render one layer and then afterwards blend them together like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax1 = fig.gca()
s1 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#3355ff", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
img1 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
ax1.clear()
s2 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#11aa44", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
img2 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
fig.clf()
plt.imshow(np.minimum(img1,img2))
plt.subplots_adjust(0, 0, 1, 1)
plt.axis("off")
plt.show()
I may have to come up with better methods than just taking the np.minimum of both layers to keep more color options (and probably save the axes and labels to a third layer).
I did not try mplcairo as suggested in the other linked answer as it has too many dependencies for my use case (my solution should be portable).
I am still happy for additional input. :)

Matplotlib subplot2grid doesn't work properly

I am trying to code a simple nuclear fission simulation and thus far I've got the simulation of the core itself working properly. Now what I am trying to do is to have a second graph near the simulation, that tells the user how much power is being outputted from the core.
With that being said, I am trying to use subtplot2grid but I can't seem to find the right fitting measurements for my program. I also add a patch of plt.Rectangle into the core simulation that I use as bounds to my core, I tried running the program with and without the patch and it seems like it is the problem. Even though, I would like that rectangle to stay, please help me find the right measurements and explain why are the dimensions different with and without the patch.
here's my code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
BOUNDS = [-20,20,-20,20]
fig = plt.figure()
ax = plt.subplot2grid((3,2),(0,0), rowspan = 2, colspan = 2, aspect = 'equal', autoscale_on = False,
xlim = (-51.2,51.2), ylim = (-50.4,50.4))
ax1 = plt.subplot2grid((3,2),(2,1))
ax1.set_xlabel('Time')
ax1.set_ylabel('Jouls')
rect = plt.Rectangle(BOUNDS[::2], #Creates the frame of the board (black rectangle)
BOUNDS[1] - BOUNDS[0],
BOUNDS[3] - BOUNDS[2],
ec='black', lw=2, fc='none')
ax.add_patch(rect)
ax.axis('off')
plt.show()
If you will run the program, you will see this:
Like I said before, I would like the power graph to be near the simulation, which will take up most of the figure, like so:
I would appreciate any help, thanks!
Possibly you want to use an additional column and let the "core" plot span all 3 rows?
ax = plt.subplot2grid((3,3),(0,0), rowspan = 3, colspan = 2, aspect = 'equal',
autoscale_on = False, xlim = (-51.2,51.2), ylim = (-50.4,50.4))
ax1 = plt.subplot2grid((3,3),(1,2))

How to make axes transparent in matplotlib?

So I have the following example that plots a figure and an inset:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
x = np.arange(100).reshape((10, 10))
ax1.imshow(x)
ax2 = fig.add_axes([0.5, 0.5, 0.3, 0.3])
t = np.arange(0, 1, 0.01)
s = np.sin(t)
ax2.plot(t, s, linewidth=2)
This produces the following figure:
What I would like to have thought is the inset to be a little bit transparent (say alpha=0.5). I want something along the lines of what they do in the documentation for the legends in the matplotlib documentation:
http://matplotlib.org/users/recipes.html#transparent-fancy-legends
Is that possible? does anyone know how to do that?
All the best,
P.D. As mentioned in the comments the answer to this questions can be derived from the answer to the linked question. It is just a little bit different conceptually (figure vs axis) and far more straightforward here IMO.
Ok, so just after playing a little bit I discovered that this can be achieved by controlling the patch property or the axes.
Adding the following the above code produces the desired result:
ax2.patch.set_alpha(0.5)
This works because patch is just the figure (the Artist in matplotlib parlance) that represents the background as I understand from the documentation:
Documentation

How to draw intersecting planes?

I want to use matplotlib to draw more or less the figure I attached below, which includes the two intersecting planes with the right amount of transparency indicating their relative orientations, and the circles and vectors in the two planes projected in 2D.
I'm not sure if there is an existing package for doing this, any hints?
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
dim = 10
X, Y = np.meshgrid([-dim, dim], [-dim, dim])
Z = np.zeros((2, 2))
angle = .5
X2, Y2 = np.meshgrid([-dim, dim], [0, dim])
Z2 = Y2 * angle
X3, Y3 = np.meshgrid([-dim, dim], [-dim, 0])
Z3 = Y3 * angle
r = 7
M = 1000
th = np.linspace(0, 2 * np.pi, M)
x, y, z = r * np.cos(th), r * np.sin(th), angle * r * np.sin(th)
ax.plot_surface(X2, Y3, Z3, color='blue', alpha=.5, linewidth=0, zorder=-1)
ax.plot(x[y < 0], y[y < 0], z[y < 0], lw=5, linestyle='--', color='green',
zorder=0)
ax.plot_surface(X, Y, Z, color='red', alpha=.5, linewidth=0, zorder=1)
ax.plot(r * np.sin(th), r * np.cos(th), np.zeros(M), lw=5, linestyle='--',
color='k', zorder=2)
ax.plot_surface(X2, Y2, Z2, color='blue', alpha=.5, linewidth=0, zorder=3)
ax.plot(x[y > 0], y[y > 0], z[y > 0], lw=5, linestyle='--', color='green',
zorder=4)
plt.axis('off')
plt.show()
caveats:
I am running a version very close to the current master, so I am not
sure what will work in older versions
The reason for splitting up the plotting is that 'above' and 'below' are determined in a some what arcane way (I am not strictly sure the zorder actually does anything), and is really dependent on the order the artists are drawn in. Thus surfaces can not intersect (one will be above the other every where), so you need to plot the sections on either side of the intersection separately. (You can see this in the black line which I didn't split at looks like it in 'on top of' the upper blue plane).
The 'proper' ordering of the surfaces also seems to be dependent on the view angle.
Matplotlib does have 3d projection capability, but the dashed lines are drawn with constant width in the final 2D image view, not looking as if laid flat on the tilted planes. If the geometry is simple, and the "orbits" circular, it might work, but if you're wanting to draw ellipses seen at an angle, the viewer may desire more visual clues on the whole 3D arrangement.
If I had to make one nice fancy illustration like that, but even nicer and fancier, and it didn't have to be automated, I'd start by creating the graphics - at least the dashed line circles - for each of the planes as a simple flat 2D image using whatever seems handy at the moment - a vector drawing program like Illustrator or Inkscape, or in matplotlib if there is data to be followed.
Then, I'd use POV-Ray or Blender to model the planes at whatever angles, spheres for the round things (planets?). The 2D graphics already generated would become textures to be mapped to the planes. POV-Ray uses a scripting language allowing a record to be kept, modified, and copied for future projets. If it were really one-time and I didn't mind doing it all by hand, Blender is good. Whichever tool I use, the result is an image showing the desired projection of the 3D geometric elements into 2D.
Are the round things, what I'm calling "planets" supposed to be flat circles in the final work, like in the examples? Then I'd draw them with a vector drawing app over the rendered 3D image. But I suspect you'd prefer spheres in 3D.
The samples shown have no lighting or shadows. Shadows would help with clarifying the geometry in 3D, although the first of those two illustrations isn't too bad. The short green line showing the inclined plane's planet over the red line seems clear enough but a shadow would help. The second illustration does look a bit more confusing as to the shape, location an intersections of the various entities. Here, shadows would help more. POV-Ray or Blender will happily create these with little effort. Even more, inter-reflections, known as radiosity, help with seeing 3D relations in 2D images. This advanced effect is easy to do these days, not needing expertise in optics or graphics, just knowing that it exists.
Of course, this advice is no good unless one is already familiar with 3D graphics and tools such s POV-Ray.
For an automated solution, using OpenGL in some quick and dirty program may be best. Shadows may take some work, though.

how to make a 3d effect on bars in matplotlib?

I have a very simple basic bar's graphic like this one
but i want to display the bars with some 3d effect, like this
I just want the bars to have that 3d effect...my code is:
fig = Figure(figsize=(4.6,4))
ax1 = fig.add_subplot(111,ylabel="Valeur",xlabel="Code",autoscale_on=True)
width = 0.35
ind = np.arange(len(values))
rects = ax1.bar(ind, values, width, color='#A1B214')
ax1.set_xticks(ind+width)
ax1.set_xticklabels( codes )
ax1.set_ybound(-1,values[0] * 1.1)
canvas = FigureCanvas(fig)
response = HttpResponse(content_type='image/png')
canvas.print_png(response)
i've been looking in the gallery of matplotlib,tried a few things but i wasn't lucky, Any ideas? Thxs
I certainly understand your reason for needing a 3d bar plot; i suspect that's why they were created.
The libraries ('toolkits') in Matplotlib required to create 3D plots are not third-party libraries, etc., rather they are included in the base Matplotlib installation.
(This is true for the current stable version, which is 1.0, though i don't believe it was for 0.98, so the change--from 'add-on' to part of the base install--occurred within the past year, i believe)
So here you are:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as PLT
import numpy as NP
fig = PLT.figure()
ax1 = fig.add_subplot(111, projection='3d')
xpos = NP.random.randint(1, 10, 10)
ypos = NP.random.randint(1, 10, 10)
num_elements = 10
zpos = NP.zeros(num_elements)
dx = NP.ones(10)
dy = NP.ones(10)
dz = NP.random.randint(1, 5, 10)
ax1.bar3d(xpos, ypos, zpos, dx, dy, dz, color='#8E4585')
PLT.show()
To create 3d bars in Maplotlib, you just need to do three (additional) things:
import Axes3D from mpl_toolkits.mplot3d
call the bar3d method (in my scriptlet, it's called by ax1 an instance of the Axes class). The method signature:
bar3d(x, y, z, dy, dz, color='b', zsort="average", *args, **kwargs)
pass in an additional argument to add_subplot, projection='3d'
As far as I know Matplotlib doesn't by design support features like the "3D" effect you just mentioned. I remember reading about this some time back. I don't know it has changed in the meantime.
See this discussion thread for more details.
Update
Take a look at John Porter's mplot3d module. This is not a part of standard matplotlib but a custom extension. Never used it myself so can't say much about its usefulness.

Categories

Resources