Use constant colors in matplotlib axes3d - python-3.x

I am making a 3d scatterplot with Matplotlib, with the following code:
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
for i in range(len(model.ut[0,:]-1)):
x_dp = model.ut[0,i]
y_dp = model.ut[1,i]
z_dp = model.ut[2,i]
ax.scatter(x_dp, y_dp, z_dp, marker='^')
for i in range(len(model.cluster_centers[:,0]-1)):
x_c = model.cluster_centers[i,0]
y_c = model.cluster_centers[i,1]
z_c = model.cluster_centers[i,2]
ax.scatter(x_c, y_c, z_c, marker='o')
ax.set_xlabel('Dimension 0')
ax.set_ylabel('Dimension 1')
ax.set_zlabel('Dimension 2')
ax.set_title('3d')
pyplot.show()
Where model.ut and cluster_center are matrices with the data that I want to visualize.
Currently, the color of each datapoint is different:
Instead, I would like each point with the same marker to be the same color (like it has been done here). How can I do this?

Related

How could I edit my code to plot 4D contour something similar to this example in python?

Similar to many other researchers on stackoverflow who are trying to plot a contour graph out of 4D data (i.e., X,Y,Z and their corresponding value C), I am attempting to plot a 4D contour map out of my data. I have tried many of the suggested solutions in stackover flow. From all of the plots suggested this, and this were the closest to what I want but sill not quite what I need in terms of data interpretation. Here is the ideal plot example: (source)
Here is a subset of the data. I put it on the dropbox. Once this data is downloaded to the directory of the python file, the following code will work. I have modified this script from this post.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
#####Importing the data
df = pd.read_csv('Data_4D_plot.csv')
do_random_pt_example = False;
index_x = 0; index_y = 1; index_z = 2; index_c = 3;
list_name_variables = ['x', 'y', 'z', 'c'];
name_color_map = 'seismic';
if do_random_pt_example:
number_of_points = 200;
x = np.random.rand(number_of_points);
y = np.random.rand(number_of_points);
z = np.random.rand(number_of_points);
c = np.random.rand(number_of_points);
else:
x = df['X'].to_numpy();
y = df['Y'].to_numpy();
z = df['Z'].to_numpy();
c = df['C'].to_numpy();
#end
#-----
# We create triangles that join 3 pt at a time and where their colors will be
# determined by the values of their 4th dimension. Each triangle contains 3
# indexes corresponding to the line number of the points to be grouped.
# Therefore, different methods can be used to define the value that
# will represent the 3 grouped points and I put some examples.
triangles = mtri.Triangulation(x, y).triangles;
choice_calcuation_colors = 2;
if choice_calcuation_colors == 1: # Mean of the "c" values of the 3 pt of the triangle
colors = np.mean( [c[triangles[:,0]], c[triangles[:,1]], c[triangles[:,2]]], axis = 0);
elif choice_calcuation_colors == 2: # Mediane of the "c" values of the 3 pt of the triangle
colors = np.median( [c[triangles[:,0]], c[triangles[:,1]], c[triangles[:,2]]], axis = 0);
elif choice_calcuation_colors == 3: # Max of the "c" values of the 3 pt of the triangle
colors = np.max( [c[triangles[:,0]], c[triangles[:,1]], c[triangles[:,2]]], axis = 0);
#end
#----------
###=====adjust this part for the labeling of the graph
list_name_variables[index_x] = 'X (m)'
list_name_variables[index_y] = 'Y (m)'
list_name_variables[index_z] = 'Z (m)'
list_name_variables[index_c] = 'C values'
# Displays the 4D graphic.
fig = plt.figure(figsize = (15,15));
ax = fig.gca(projection='3d');
triang = mtri.Triangulation(x, y, triangles);
surf = ax.plot_trisurf(triang, z, cmap = name_color_map, shade=False, linewidth=0.2);
surf.set_array(colors); surf.autoscale();
#Add a color bar with a title to explain which variable is represented by the color.
cbar = fig.colorbar(surf, shrink=0.5, aspect=5);
cbar.ax.get_yaxis().labelpad = 15; cbar.ax.set_ylabel(list_name_variables[index_c], rotation = 270);
# Add titles to the axes and a title in the figure.
ax.set_xlabel(list_name_variables[index_x]); ax.set_ylabel(list_name_variables[index_y]);
ax.set_zlabel(list_name_variables[index_z]);
ax.view_init(elev=15., azim=45)
plt.show()
Here would be the output:
Although it looks brilliant, it is not quite what I am looking for (the above contour map example). I have modified the following script from this post in the hope to reach the required graph, however, the chart looks nothing similar to what I was expecting (something similar to the previous output graph). Warning: the following code may take some time to run.
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
df = pd.read_csv('Data_4D_plot.csv')
x = df['X'].to_numpy();
y = df['Y'].to_numpy();
z = df['Z'].to_numpy();
cc = df['C'].to_numpy();
# convert to 2d matrices
Z = np.outer(z.T, z)
X, Y = np.meshgrid(x, y)
C = np.outer(cc.T,cc)
# fourth dimention - colormap
# create colormap according to cc-value
color_dimension = C # change to desired fourth dimension
minn, maxx = color_dimension.min(), color_dimension.max()
norm = matplotlib.colors.Normalize(minn, maxx)
m = plt.cm.ScalarMappable(norm=norm, cmap='jet')
m.set_array([])
fcolors = m.to_rgba(color_dimension)
# plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X,Y,Z, rstride=1, cstride=1, facecolors=fcolors, vmin=minn, vmax=maxx, shade=False)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
Now I was wondering from our kind community and experts if you can help me to plot a contour figure similar to the example graph (image one in this post), where the contours are based on the values within the range of C?

Set centre of geopandas map

I can plot a world map with geopandas with:
world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres'))
fig, ax = plt.subplots()
world.plot(ax=ax, color=(0.8,0.5,0.5))
and it works fine, but I would like to center the map on a different longitude than the Prime Meridian. How do I do this?
This is how you can do it:
from shapely.geometry import LineString
from shapely.ops import split
from shapely.affinity import translate
import geopandas
world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres'))
def shift_map(shift):
shift -= 180
moved_map = []
splitted_map = []
border = LineString([(shift,90),(shift,-90)])
for row in world["geometry"]:
splitted_map.append(split(row, border))
for element in splitted_map:
items = list(element)
for item in items:
minx, miny, maxx, maxy = item.bounds
if minx >= shift:
moved_map.append(translate(item, xoff=-180-shift))
else:
moved_map.append(translate(item, xoff=180-shift))
gdf = geopandas.GeoDataFrame({"geometry":moved_map})
fig, ax = plt.subplots()
gdf.plot(ax=ax)
plt.show()
In the first step, you create your world and split it on a pre defined border of yours.
Then you get the bounds of all elements and check if the bounds match your desired shift. Afterwards you translate every element bigger than your border to the left side of the map and move all other elements to the right side, so that they aling with +180°.
This gives you for example:
A map shifted by 120°
Like in this question I needed to reset the centre of the map, but I also needed to move scatter plot network node positions that where bound to (long,lat) coordinates too.
I am hoping to save someone some time, as it's probably not obvious initially that to solve this problem you will have to wrangle some unfamiliar types.
Here is a method for shifting both the underlying map and some additional points:
import geopandas
world =
geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres'))
import matplotlib.pyplot as plt
import geopandas as gpd
from shapely.geometry import LineString
from shapely.ops import split
from shapely.affinity import translate
def shift_geom(shift, gdataframe,pos_all, plotQ=True):
# this code is adapted from answer found in SO
# will be credited here: ???
shift -= 180
moved_geom = []
splitted_geom = []
border = LineString([(shift,90),(shift,-90)])
for row in gdataframe["geometry"]:
splitted_geom.append(split(row, border))
for element in splitted_geom:
items = list(element)
for item in items:
minx, miny, maxx, maxy = item.bounds
if minx >= shift:
moved_geom.append(translate(item, xoff=-180-shift))
else:
moved_geom.append(translate(item, xoff=180-shift))
# got `moved_geom` as the moved geometry
moved_geom_gdf = gpd.GeoDataFrame({"geometry": moved_geom})
# can change crs here
if plotQ:
fig1, ax1 = plt.subplots(figsize=[8,6])
moved_geom_gdf.plot(ax=ax1)
plt.show()
df = pd.DataFrame({'Latitude': [xy[1] for xy in pos_all.values()],
'Longitude': [xy[0] for xy in pos_all.values()]})
gdf = geopandas.GeoDataFrame(df, geometry=geopandas.points_from_xy(df.Longitude, df.Latitude))
border2 = LineString([(shift,90),(shift,-90)])
geom = gdf.geometry.values
moved_map_points = []
moved_map_dict = {}
for element,key in zip(geom,list(pos_all.keys())):
if float(element.x) >= shift:
moved_map_points.append(translate(element, xoff=-180-shift))
else:
moved_map_points.append(translate(element, xoff=180-shift))
moved_map_dict[key] = (moved_map_points[-1].x,moved_map_points[-1].y)
return moved_geom_gdf,moved_map_dict
In this context pos_all are networkx node positions made of [(lat,long)]
shifted_world,moved_map_points = shift_geom(300, world,pos_all,plotQ= False)

Matplotlib figure annotations outside of window

I am making a program that implements a matplotlib pie/donut chart into a tkinter window to illustrate some data, however, I have added "annotations" or labels from each wedge of the pie chart. Because of this the window that opens when I execute the code fits the chart itself, but the labels are cut off at the edges of the window. Specifically, it looks like this...
Note the top two arrows don't actually have text attached to the corresponding labels so the situation is actually worse than my screenshot depicts.
Even if I get rid of the code related to generating a tkinter GUI, and just try to execute code to generate a regular figure window the labels are initially cut-off. But, if I use the built in zoom-out functionality I can zoom out the make the labels fit.
I have tried to adjust the figsize here...
fig, ax = plt.subplots(figsize=(6, 4), subplot_kw=dict(aspect="equal"))
yet it makes no difference. Hopefully there is a solution, thanks...
Here is my full code if anyone needs...
import numpy as np
import matplotlib.pyplot as plt
player1_cards = {'Mustard', 'Plum', 'Revolver', 'Rope', 'Ballroom', 'Library'}
player2_cards = {'Scarlet', 'White', 'Candlestick'}
player3_cards = {'Green', 'Library', 'Kitchen', 'Conservatory'}
middle_cards = {'Peacock'}
unknown_cards = {'Lead Pipe', 'Wrench', 'Knife', 'Hall', 'Lounge', 'Dining Room', 'Study'}
player1_string = ', '.join(player1_cards)
player1_string = player1_string.replace(', ', '\n')
player2_string = ', '.join(player2_cards)
player2_string = player2_string.replace(', ', '\n')
player3_string = ', '.join(player3_cards)
player3_string = player3_string.replace(', ', '\n')
fig, ax = plt.subplots(figsize=(6, 4), subplot_kw=dict(aspect="equal"))
recipe = [player1_string, player2_string, player3_string, '', '']
data = [len(player1_cards), len(player2_cards), len(player3_cards), 1, 7]
cols = ['#339E5A', '#26823E', '#0C5D2E', '#98D6AE', '#5EC488']
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=90, colors = cols)
for w in wedges:
w.set_linewidth(4)
w.set_edgecolor('white')
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="white", lw=0.72)
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"), bbox=bbox_props, zorder=0, va="center")
for i, p in enumerate(wedges):
ang = (p.theta2 - p.theta1)/2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(recipe[i], xy=(x, y), xytext=(x + np.sign(x)*.5, y*1.5),
horizontalalignment=horizontalalignment, **kw, family = "Quicksand")
ax.set_title("Matplotlib bakery: A donut")
plt.show()
You would want to play around with the subplot parameters to make space for the text outside the axes.
fig.subplots_adjust(bottom=..., top=..., left=..., right=...)
E.g. in this case
fig.subplots_adjust(bottom=0.2, top=0.9)
seems to give a nice representation

Why is the saved video from FuncAnimation a superpositions of plots?

Regards, I would like to ask about Python's FuncAnimation.
In the full code, I was trying to animate bar plots (for integral illustration). The animated output from
ani = FuncAnimation(fig, update, frames=Iter, init_func = init, blit=True);
plt.show(ani);
looks fine.
But the output video from
ani.save("example_new.mp4", fps = 5)
gives a slightly different version from the animation showed in Python. The output gives a video of 'superposition version' compared to the animation. Unlike the animation : in the video, at each frame, the previous plots kept showing together with the current one.
Here is the full code :
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
Num = 20
p = plt.bar([0], [0], 1, color = 'b')
Iter = tuple(range(2, Num+1))
xx = list(np.linspace(0, 2, 200)); yy = list(map(lambda x : x**2,xx));
def init():
ax.set_xlim(0, 2)
ax.set_ylim(0, 4)
return (p)
def update(frame):
w = 2/frame;
X = list(np.linspace(0, 2-w, frame+1));
Y = list(map(lambda x: x**2, X));
X = list(map(lambda x: x + w/2,X));
C = (0, 0, frame/Num);
L = plt.plot(xx , yy, 'y', animated=True)[0]
p = plt.bar(X, Y, w, color = C, animated=True)
P = list(p[:]); P.append(L)
return P
ani = FuncAnimation(fig, update, frames=Iter, init_func = init, interval = 0.25, blit=True)
ani.save("examplenew.mp4", fps = 5)
plt.show(ani)
Any constructive inputs on this would be appreciated. Thanks. Regards, Arief.
When saving the animation, no blitting is used. You can turn off blitting, i.e. blit=False and see the animation the same way as it is saved.
What is happening is that in each iteration a new plot is added without the last one being removed. You basically have two options:
Clear the axes in between, ax.clear() (then remember to set the axes limits again)
update the data for the bars and the plot. Examples to do this:
For plot: Matplotlib Live Update Graph
For bar: Dynamically updating a bar plot in matplotlib

matplotlib: get the subplot layout?

I have a function that creates a grid of similar 2D histograms. So that I can select whether to put this new plot on a pre-existing figure, I do the following:
def make_hist2d(x, y, current_fig=False, layout=(1,1,1),*args):
if current_fig:
fig = _plt.gcf()
ax = fig.add_subplot(*layout) # layout=(nrows, ncols, nplot)
else:
fig, ax = _plt.subplots()
H, x, y = np.histogram2d(...)
# manipulate the histogram, e.g. column normalize.
XX, YY = _np.meshgrid(xedges, yedges)
Image = ax.pcolormesh(XX, YY, Hplot.T, norm=norm, **pcmesh_kwargs)
ax.autoscale(tight=True)
grid_kargs = {'orientation': 'vertical'}
cax, kw = _mpl.colorbar.make_axes_gridspec(ax, **grid_kargs)
cbar = fig.colorbar(Image, cax=cax)
cbar.set_label(cbar_title)
return fig, ax, cbar
def hist2d_grid(data_dict, key_pairs, layout, *args): # ``*args`` are things like xlog, ylog, xlabel, etc.
# that are common to all subplots in the figure.
fig, ax = _plt.subplots()
nplots = range(len(key_pairs) + 1) # key_pairs = ((k1a, k1b), (k2a, k2b), ..., (kna, knb))
ax_list = []
for pair, i in zip(key_pairs, nplots):
fig, ax, cbar = make_hist2d(data[k1a], data[k1b]
ax_list.append(ax)
return fig, ax_list
Then I call something like:
hgrid = hist2d_grid(...)
However, if I want to add a new figure to the grid, I don't know of a good way to get the subplot layout. For example, is there something like:
layout = fig.get_layout()
That would give me something like (nrows, ncols, n_subplots)?
I could do this with something like:
n_plot = len(ax_list) / 2 # Each subplot generates a plot and a color bar.
n_rows = np.floor(np.sqrt(n_ax))
n_cols = np.ceil(np.sqrt(n_ax))
But I have to deal with special cases like a (2,4) subplot array for which I would get n_rows = 2 and n_cols = 3, which means that I would be passing (2,3,8) to ax.add_subplot(), which clearly doesn't work because 8 > 3*2.
As ax returned by fig, ax = plt.subplots(4,2) is a numpy array of axes, then ax.shape will give you the layout information you want, e.g.
nrows, ncols = ax.shape
n_subplots = nrows*ncols
You can also get the locations of the various axes by looping over the children of the figure object,
[[f.colNum, f.rowNum] for f in fig.get_children()[1:]]
and probably get the size from the final element fig.get_children()[-1]
You could also use gridspec to be more explicit about the location of subplots if needed. With gridspec you setup the gridspec object and pass to subplot,
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])
To get the layout you can then use,
gs.get_geometry()

Resources