Matplotlib contour plot with conditions on the curve - python

Suppose I want to plot the contour of z=0 where z=(19*y^3-6*x*y-1). I can use the following code:
x = np.linspace(-2,2,1000)
y = np.linspace(-1,1,1000)
X,Y = np.meshgrid(x,y)
z = (19)*Y**3-6*X*Y-1
plt.figure()
plt.contour(z,0)
plt.show()
Now I want to display the portion of the curve where 3*19*y^2-6*x<0 in solid line, and the portion where 3*19*y^2-6*x>0 in dashed line. Basically I'm doing some sort of stability analysis of a system, and I want to show different regions of the z=0 curve differently depending on whether dz/dy is positive or negative.
What I can think of is to locate the coordinates of the two parts myself, and to use scatter plot to show the two parts of the curve using different colour (or line style). I also know how to do this easily in Mathematica. I just wonder whether there is a more elegant solution in matplotlib to do this job.

Maybe the following approach is interesting, although not perfect?
A variable z2 is created with for the 3*19*y^2-6*x>0 condition. z2 is erased everywhere except where it is close to z. Then it is colored with a red-blue colormap, red for the positive part, blue for the negative, and white around 0.
The background is set to black and the contour color to white in order to have enough contrast.
Note that both the contour plot and the imshow need the extent parameter to be set in order to get informative axes.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-2, 2, 1000)
y = np.linspace(-1, 1, 1000)
X, Y = np.meshgrid(x, y)
z = (19) * Y ** 3 - 6 * X * Y - 1
z2 = 3 * 19 * Y ** 2 - 6 * X
z2 = np.where(np.abs(z) < 0.2, z2, np.NaN)
plt.gca().set_facecolor('black')
plt.imshow(z2, cmap='coolwarm', vmin=-1, vmax=+1, alpha=1, extent=[-2, 2, -1, 1], origin='lower')
plt.contour(z, 0, extent=[-2, 2, -1, 1], zorder=3, colors='white')
plt.show()

Related

Is there anything in matplotlib that behaves like alpha but reversed?

A good way to show the concentration of the data points in a plot is using a scatter plot with non-unit transparency. As a result, the areas with more concentration would appear darker.
# this is synthetic example
N = 10000 # a very very large number
x = np.random.normal(0, 1, N)
y = np.random.normal(0, 1, N)
plt.scatter(x, y, marker='.', alpha=0.1) # an area full of dots, darker wherever the number of dots is more
which gives something like this:
Imagine the case we want to emphasize on the outliers. So the situation is almost reversed: A plot in which the less-concentrated areas are bolder. (There might be a trick to apply for my simple example, but imagine a general case where a distribution of points are not known prior, or it's difficult to define a rule for transparency/weight on color.)
I was thinking if there's anything handy same as alpha that is designed for this job specifically. Although other ideas for emphasizing on outliers are also welcomed.
UPDATE: This is what happens when more then one data point is scattered on the same area:
I'm looking for something like the picture below, the more data point, the less transparent the marker.
To answer the question: You can calculate the density of points, normalize it and encode it in the alpha channel of a colormap.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
# this is synthetic example
N = 10000 # a very very large number
x = np.random.normal(0, 1, N)
y = np.random.normal(0, 1, N)
fig, (ax,ax2) = plt.subplots(ncols=2, figsize=(8,5))
ax.scatter(x, y, marker='.', alpha=0.1)
values = np.vstack([x,y])
kernel = stats.gaussian_kde(values)
weights = kernel(values)
weights = weights/weights.max()
cols = plt.cm.Blues([0.8, 0.5])
cols[:,3] = [1., 0.005]
cmap = LinearSegmentedColormap.from_list("", cols)
ax2.scatter(x, y, c=weights, s = 1, marker='.', cmap=cmap)
plt.show()
Left is the original image, right is the image where higher density points have a lower alpha.
Note, however, that this is undesireable, because high density transparent points are undistinguishable from low density. I.e. in the right image it really looks as though you have a hole in the middle of your distribution.
Clearly, a solution with a colormap which does not contain the color of the background is a lot less confusing to the reader.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
# this is synthetic example
N = 10000 # a very very large number
x = np.random.normal(0, 1, N)
y = np.random.normal(0, 1, N)
fig, ax = plt.subplots(figsize=(5,5))
values = np.vstack([x,y])
kernel = stats.gaussian_kde(values)
weights = kernel(values)
weights = weights/weights.max()
ax.scatter(x, y, c = weights, s=9, edgecolor="none", marker='.', cmap="magma")
plt.show()
Here, low density points are still emphazised by darker color, but at the same time it's clear to the viewer that the highest density lies in the middle.
As far as I know, there is no "direct" solution to this quite interesting problem. As a workaround, I propose this solution:
N = 10000 # a very very large number
x = np.random.normal(0, 1, N)
y = np.random.normal(0, 1, N)
fig = plt.figure() # create figure directly to be able to extract the bg color
ax = fig.gca()
ax.scatter(x, y, marker='.') # plot all markers without alpha
bgcolor = ax.get_facecolor() # extract current background color
# plot with alpha, "overwriting" dense points
ax.scatter(x, y, marker='.', color=bgcolor, alpha=0.2)
This will plot all points without transparency and then plot all points again with some transparency, "overwriting" those points with the highest density the most. Setting the alpha value to other higher values will put more emphasis to outliers and vice versa.
Of course the color of the second scatter plot needs to be adjusted to your background color. In my example this is done by extracting the background color and setting it as the new scatter plot's color.
This solution is independent of the kind of distribution. It only depends on the density of the points. However it produces twice the amount of points, thus may take slightly longer to render.
Reproducing the edit in the question, my solution is showing exactly the desired behavior. The leftmost point is a single point and is the darkest, the rightmost is consisting of three points and is the lightest color.
x = [0, 1, 1, 2, 2, 2]
y = [0, 0, 0, 0, 0, 0]
fig = plt.figure() # create figure directly to be able to extract the bg color
ax = fig.gca()
ax.scatter(x, y, marker='.', s=10000) # plot all markers without alpha
bgcolor = ax.get_facecolor() # extract current background color
# plot with alpha, "overwriting" dense points
ax.scatter(x, y, marker='.', color=bgcolor, alpha=0.2, s=10000)
Assuming that the distributions are centered around a specific point (e.g. (0,0) in this case), I would use this:
import numpy as np
import matplotlib.pyplot as plt
N = 500
# 0 mean, 0.2 std
x = np.random.normal(0,0.2,N)
y = np.random.normal(0,0.2,N)
# calculate the distance to (0, 0).
color = np.sqrt((x-0)**2 + (y-0)**2)
plt.scatter(x , y, c=color, cmap='plasma', alpha=0.7)
plt.show()
Results:
I don't know if it helps you, because it's not exactly you asked for, but you can simply color points, which values are bigger than some threshold. For example:
import matplotlib.pyplot as plt
num = 100
threshold = 80
x = np.linspace(0, 100, num=num)
y = np.random.normal(size=num)*45
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.scatter(x[np.abs(y) < threshold], y[np.abs(y) < threshold], color="#00FFAA")
ax.scatter(x[np.abs(y) >= threshold], y[np.abs(y) >= threshold], color="#AA00FF")
plt.show()

How to change the length of axes for 3D plots in matplotlib [duplicate]

I have this so far:
x,y,z = data.nonzero()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Which creates:
What I'd like to do is stretch this out to make the Z axis 9 times taller and keep X and Y the same. I'd like to keep the same coordinates though.
So far I tried this guy:
fig = plt.figure(figsize=(4.,35.))
But that just stretches out the plot.png image.
The code example below provides a way to scale each axis relative to the others. However, to do so you need to modify the Axes3D.get_proj function. Below is an example based on the example provided by matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots
(There is a shorter version at the end of this answer)
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
#Make sure these are floating point values:
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0
#Axes are scaled down to fit in scene
max_scale=max(scale_x, scale_y, scale_z)
scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale
#Create scaling matrix
scale = np.array([[scale_x,0,0,0],
[0,scale_y,0,0],
[0,0,scale_z,0],
[0,0,0,1]])
print scale
def get_proj_scale(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(
xmin, xmax,
ymin, ymax,
zmin, zmax)
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / proj3d.mod(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(perspM, M0)
return np.dot(M, scale);
Axes3D.get_proj=get_proj_scale
"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
plt.show()
Standard output:
Scaled by (1, 2, 3):
Scaled by (1, 1, 3):
The reason I particularly like this method,
Swap z and x, scale by (3, 1, 1):
Below is a shorter version of the code.
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
"""
Scaling is done from here...
"""
x_scale=1
y_scale=1
z_scale=2
scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0
def short_proj():
return np.dot(Axes3D.get_proj(ax), scale)
ax.get_proj=short_proj
"""
to here
"""
ax.plot(z, y, x, label='parametric curve')
ax.legend()
plt.show()
Please note that the answer below simplifies the patch, but uses the same underlying principle as the answer by #ChristianSarofeen.
Solution
As already indicated in other answers, it is not a feature that is currently implemented in matplotlib. However, since what you are requesting is simply a 3D transformation that can be applied to the existing projection matrix used by matplotlib, and thanks to the wonderful features of Python, this problem can be solved with a simple oneliner:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
where scale_x, scale_y and scale_z are values from 0 to 1 that will re-scale your plot along each of the axes accordingly. ax is simply the 3D axes which can be obtained with ax = fig.gca(projection='3d')
Explanation
To explain, the function get_proj of Axes3D generates the projection matrix from the current viewing position. Multiplying it by a scaling matrix:
scale_x, 0, 0
0, scale_y, 0
0, 0, scale_z
0, 0, 1
includes the scaling into the projection used by the renderer. So, what we are doing here is substituting the original get_proj function with an expression taking the result of the original get_proj and multiplying it by the scaling matrix.
Example
To illustrate the result with the standard parametric function example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))
ax.plot(x, y, z)
plt.show()
for values 0.5, 0.5, 1, we get:
while for values 0.2, 1.0, 0.2, we get:
In my case I wanted to stretch z-axis 2 times for better point visibility
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# plt.rcParams["figure.figsize"] = (10,200)
# plt.rcParams["figure.autolayout"] = True
ax = plt.axes(projection='3d')
ax.set_box_aspect(aspect = (1,1,2))
ax.plot(dataX,dataY,dataZ)
I looks like by default, mplot3d will leave quite a bit of room at the top and bottom of a very tall plot. But, you can trick it into filling that space using fig.subplots_adjust, and extending the top and bottom out of the normal plotting area (i.e. top > 1 and bottom < 0). Some trial and error here is probably needed for your particular plot.
I've created some random arrays for x, y, and z with limits similar to your plot, and have found the parameters below (bottom=-0.15, top = 1.2) seem to work ok.
You might also want to change ax.view_init to set a nice viewing angle.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random
# Make some random data with similar limits to the OP's example
x,y,z=random.rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200
fig=plt.figure(figsize=(4,35))
# Set the bottom and top outside the actual figure limits,
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)
ax = fig.add_subplot(111, projection='3d')
# Change the viewing angle to an agreeable one
ax.view_init(2,None)
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Sounds like you're trying to adjust the scale of the plot. I don't think there's a way to stretch a linear scale to user specifications, but you can use set_yscale(), set_xscale(), set_zscale() to alter the scales with respect to each other.
Intuitively, set_yscale(log), set_xscale(log), set_zscale(linear) might solve your problems.
A likely better option: specify a stretch, set them all to symlog with the same log base and then specify the Z-axis's symlog scale with the linscalex/linscaley kwargs to your specifications.
More here:
http://matplotlib.org/mpl_toolkits/mplot3d/api.html
I found this while searching on a similar problem. After experimenting a bit, perhaps I can share some of my prelim findings here..matplotlib library is VAST!! (am a newcomer). Note that quite akin to this question, all i wanted was to 'visually' stretch the chart without distorting it.
Background story (only key code snippets are shown to avoid unnecessary clutter for those who know the library, and if you want a run-able code please drop a comment):
I have three 1-d ndarrays representing the X,Y and Z data points respectively. Clearly I can't use plot_surface (as it requires 2d ndarrays for each dim) so I went for the extremely useful plot_trisurf:
fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)
You can think of the plot like a floating barge deforming in waves...As you can see, the axes stretch make it pretty deceiving visually (note that x is supposed to be at x6 times longer than y and >>>>> z). While the plot points are correct, I wanted something more visually 'stretched' at the very least. Was looking for A QUICK FIX, if I may. Long story cut short, I found a bit of success with...'figure.figsize' general setting (see snippet below).
matplotlib.rcParams.update({'font.serif': 'Times New Roman',
'font.size': 10.0,
'axes.labelsize': 'Medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
###########################################
# THIS IS THE IMPORTANT ONE FOR STRETCHING
# default is [6,4] but...i changed it to
'figure.figsize':[15,5] # THIS ONE #
})
For [15,5] I got something like...
Pretty neat!!
So I started to push it.... and got up to [20,6] before deciding to settle there..
If you want to try for visually stretching the vertical axis, try with ratios like... [7,10], which in this case gives me ...
Not too shabby !
Should do it for visual prowess.
Multiply all your z values by 9,
ax.scatter(x, y, 9*z, zdir='z', c= 'red')
And then give the z-axis custom plot labels and spacing.
ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};

Scale errorbar transparency with the size in matplotlib

I have quite a messy plot and so, in order to tidy it up, I want to make those points with larger error bars less significant by reducing their alpha value. Preferably, I'd like to map a continuous scale of alpha values (like a colourmap) to each point and its errorbar according to their errorbar size - I'm not too sure what the best/efficient way to go about doing this would be.
You can certainly set the alpha of the errorbars, but I think you need to plot each one separately because Matplotlib won't set the opacity (or color) of the vertical lines and caplines to a sequence (as far as I know).
If you want the markers to have their opacity matching the errorbars, its probably easier to build a sequence of colors based on some normalization:
import numpy as np
import matplotlib.pyplot as plt
n = 20
x = np.linspace(1, 10, n)
y = x - x**2
minerr = 2
yerr = abs(np.random.randn(n) * 15) + minerr
maxerr = max(yerr)
err_range = maxerr - minerr
alphas = [1 - (err-minerr)/(err_range) for err in yerr]
colors = np.asarray([(1,0,0, alpha) for alpha in alphas])
plt.scatter(x,y, c=colors, edgecolors=colors)
for pos, ypt, err, color in zip(x, y, yerr, colors):
plotline, caplines, (barlinecols,) = plt.errorbar(pos, ypt, err, lw=2, color=color, capsize=5, capthick=2)
plt.xlim(0,11)
plt.show()
However, you might want to think about whether the effect you create might misrepresent your data (i.e. make it look more accurate than it is by emphasizing only the points with small error bars).
You can set a colour as the third variable on a scatter graph (see this answer). To change alpha, you could change only the fourth value (transparency) in color based on the scaled range. As a minimal example,
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 20, 100)
y = np.sin(x)
z = x + 20 * y
scaled_z = (z - z.min()) / z.ptp()
colors = [[0., 0., 0., i] for i in scaled_z]
plt.scatter(x, y, marker='x', edgecolors=colors, s=150, linewidths=4)
plt.show()
Which looks like

How to change the axis dimension from pixel to length in matplotlib? is there any code in general?

Since the complete simulation is to big to post it right here only the code to plot the spectrum is given (I think this is enough)
d = i.sum(axis=2)
pylab.figure(figsize=(15,15))
pylab = imshow(d)
plt.axis('tight')
pylab.show()
This spectrum is given in pixel. But I would like to have this in the units of length. I will hope you may give me some advices.
Do you mean that you want axis ticks to show your custom dimensions instead of the number of pixels in d? If yes, use the extent keyword of imshow:
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
d = numpy.random.normal(size=(20, 40))
fig = plt.figure()
s = fig.add_subplot(1, 1, 1)
s.imshow(d, extent=(0, 1, 0, 0.5), interpolation='none')
fig.tight_layout()
fig.savefig('tt.png')
I'm guess a bit at what your problem is, so let's start by stating my interpretation/ You have some 2D data d that you plot using imshow and the units on the x and y axes are in the number of pixels. For example in the following we see the x axis labelled from 0 -> 10 for the number of data points:
import numpy as np
import matplotlib.pyplot as plt
# Generate a fake d
x = np.linspace(-1, 1, 10)
y = np.linspace(-1, 1, 10)
X, Y = np.meshgrid(x, y)
d = np.sin(X**2 + Y**2)
plt.imshow(d)
If this correctly describes your issue, then the solution is to avoid using imshow, which is designed to plot images. Firstly this will help as imshow attemps to interpolate to give a smoother image (which may hide features in the spectrum) and second because it is an image, there is no meaningful x and y data so it doesn't plot it.
The best alternative would be to use plt.pcolormesh which generate a psuedocolor plot of a 2D array and takes as arguments X and Y, which are both 2D arrays of points to which the values of d correspond.
For example:
# Generate a fake d
x = np.linspace(-1, 1, 10)
y = np.linspace(-1, 1, 10)
X, Y = np.meshgrid(x, y)
d = np.sin(X**2 + Y**2)
plt.pcolormesh(X, Y, d)
Now the x and y values correspond to the values of X and Y.

matplotlib (mplot3d) - how to increase the size of an axis (stretch) in a 3D Plot?

I have this so far:
x,y,z = data.nonzero()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Which creates:
What I'd like to do is stretch this out to make the Z axis 9 times taller and keep X and Y the same. I'd like to keep the same coordinates though.
So far I tried this guy:
fig = plt.figure(figsize=(4.,35.))
But that just stretches out the plot.png image.
The code example below provides a way to scale each axis relative to the others. However, to do so you need to modify the Axes3D.get_proj function. Below is an example based on the example provided by matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots
(There is a shorter version at the end of this answer)
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
#Make sure these are floating point values:
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0
#Axes are scaled down to fit in scene
max_scale=max(scale_x, scale_y, scale_z)
scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale
#Create scaling matrix
scale = np.array([[scale_x,0,0,0],
[0,scale_y,0,0],
[0,0,scale_z,0],
[0,0,0,1]])
print scale
def get_proj_scale(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(
xmin, xmax,
ymin, ymax,
zmin, zmax)
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / proj3d.mod(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(perspM, M0)
return np.dot(M, scale);
Axes3D.get_proj=get_proj_scale
"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
plt.show()
Standard output:
Scaled by (1, 2, 3):
Scaled by (1, 1, 3):
The reason I particularly like this method,
Swap z and x, scale by (3, 1, 1):
Below is a shorter version of the code.
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
"""
Scaling is done from here...
"""
x_scale=1
y_scale=1
z_scale=2
scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0
def short_proj():
return np.dot(Axes3D.get_proj(ax), scale)
ax.get_proj=short_proj
"""
to here
"""
ax.plot(z, y, x, label='parametric curve')
ax.legend()
plt.show()
Please note that the answer below simplifies the patch, but uses the same underlying principle as the answer by #ChristianSarofeen.
Solution
As already indicated in other answers, it is not a feature that is currently implemented in matplotlib. However, since what you are requesting is simply a 3D transformation that can be applied to the existing projection matrix used by matplotlib, and thanks to the wonderful features of Python, this problem can be solved with a simple oneliner:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
where scale_x, scale_y and scale_z are values from 0 to 1 that will re-scale your plot along each of the axes accordingly. ax is simply the 3D axes which can be obtained with ax = fig.gca(projection='3d')
Explanation
To explain, the function get_proj of Axes3D generates the projection matrix from the current viewing position. Multiplying it by a scaling matrix:
scale_x, 0, 0
0, scale_y, 0
0, 0, scale_z
0, 0, 1
includes the scaling into the projection used by the renderer. So, what we are doing here is substituting the original get_proj function with an expression taking the result of the original get_proj and multiplying it by the scaling matrix.
Example
To illustrate the result with the standard parametric function example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))
ax.plot(x, y, z)
plt.show()
for values 0.5, 0.5, 1, we get:
while for values 0.2, 1.0, 0.2, we get:
In my case I wanted to stretch z-axis 2 times for better point visibility
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# plt.rcParams["figure.figsize"] = (10,200)
# plt.rcParams["figure.autolayout"] = True
ax = plt.axes(projection='3d')
ax.set_box_aspect(aspect = (1,1,2))
ax.plot(dataX,dataY,dataZ)
I looks like by default, mplot3d will leave quite a bit of room at the top and bottom of a very tall plot. But, you can trick it into filling that space using fig.subplots_adjust, and extending the top and bottom out of the normal plotting area (i.e. top > 1 and bottom < 0). Some trial and error here is probably needed for your particular plot.
I've created some random arrays for x, y, and z with limits similar to your plot, and have found the parameters below (bottom=-0.15, top = 1.2) seem to work ok.
You might also want to change ax.view_init to set a nice viewing angle.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random
# Make some random data with similar limits to the OP's example
x,y,z=random.rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200
fig=plt.figure(figsize=(4,35))
# Set the bottom and top outside the actual figure limits,
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)
ax = fig.add_subplot(111, projection='3d')
# Change the viewing angle to an agreeable one
ax.view_init(2,None)
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Sounds like you're trying to adjust the scale of the plot. I don't think there's a way to stretch a linear scale to user specifications, but you can use set_yscale(), set_xscale(), set_zscale() to alter the scales with respect to each other.
Intuitively, set_yscale(log), set_xscale(log), set_zscale(linear) might solve your problems.
A likely better option: specify a stretch, set them all to symlog with the same log base and then specify the Z-axis's symlog scale with the linscalex/linscaley kwargs to your specifications.
More here:
http://matplotlib.org/mpl_toolkits/mplot3d/api.html
I found this while searching on a similar problem. After experimenting a bit, perhaps I can share some of my prelim findings here..matplotlib library is VAST!! (am a newcomer). Note that quite akin to this question, all i wanted was to 'visually' stretch the chart without distorting it.
Background story (only key code snippets are shown to avoid unnecessary clutter for those who know the library, and if you want a run-able code please drop a comment):
I have three 1-d ndarrays representing the X,Y and Z data points respectively. Clearly I can't use plot_surface (as it requires 2d ndarrays for each dim) so I went for the extremely useful plot_trisurf:
fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)
You can think of the plot like a floating barge deforming in waves...As you can see, the axes stretch make it pretty deceiving visually (note that x is supposed to be at x6 times longer than y and >>>>> z). While the plot points are correct, I wanted something more visually 'stretched' at the very least. Was looking for A QUICK FIX, if I may. Long story cut short, I found a bit of success with...'figure.figsize' general setting (see snippet below).
matplotlib.rcParams.update({'font.serif': 'Times New Roman',
'font.size': 10.0,
'axes.labelsize': 'Medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
###########################################
# THIS IS THE IMPORTANT ONE FOR STRETCHING
# default is [6,4] but...i changed it to
'figure.figsize':[15,5] # THIS ONE #
})
For [15,5] I got something like...
Pretty neat!!
So I started to push it.... and got up to [20,6] before deciding to settle there..
If you want to try for visually stretching the vertical axis, try with ratios like... [7,10], which in this case gives me ...
Not too shabby !
Should do it for visual prowess.
Multiply all your z values by 9,
ax.scatter(x, y, 9*z, zdir='z', c= 'red')
And then give the z-axis custom plot labels and spacing.
ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};

Categories