This question already has an answer here:
How to draw the union shape of rectangles in python
(1 answer)
Closed 7 years ago.
I am trying to plot the union of a couple of polygons in matplotlib, with a certain level of alpha. My current code below has a darker color at the intersections. Would there be anyway to make the intersections the same color with other places?
import matplotlib.pyplot as plt
fig, axs = plt.subplots()
axs.fill([0, 0, 1, 1], [0, 1, 1, 0], alpha=.25, fc='r', ec='none')
axs.fill([0.5, 0.5, 1.5, 1.5], [0.5, 1.5, 1.5, 0.5], alpha=.25, fc='r', ec='none')
As #unutbu and #Martin Valgur's comments indicate, I think shapely is the way to go. This question may be somewhat redundant with an earlier one, but here is a clean code snippet that should do what you need.
The strategy is to first create the union of your various shapes (rectangles) and then plot the union. That way you have 'flattened' your various shapes into a single one, so you don't have the alpha problem in overlapping regions.
import shapely.geometry as sg
import shapely.ops as so
import matplotlib.pyplot as plt
#constructing the first rect as a polygon
r1 = sg.Polygon([(0,0),(0,1),(1,1),(1,0),(0,0)])
#a shortcut for constructing a rectangular polygon
r2 = sg.box(0.5,0.5,1.5,1.5)
#cascaded union can work on a list of shapes
new_shape = so.cascaded_union([r1,r2])
#exterior coordinates split into two arrays, xs and ys
# which is how matplotlib will need for plotting
xs, ys = new_shape.exterior.xy
#plot it
fig, axs = plt.subplots()
axs.fill(xs, ys, alpha=0.5, fc='r', ec='none')
plt.show() #if not interactive
Related
I've been reading about multiple lines plotting and multicolored lines, but every time I read a post about it people use continuous set of data, like some trigonometrical function:
x = np.linspace(0, 3 * np.pi, 500)
y = np.sin(x)
So, here is my problem: I'm making a plotting script for 1D finite element problems like the image attached. I'm plotting the elements as individual lines with the X and Y coordinates array, and I would like to color the lines based on a third array like the axial stress or temperature, or any other
The problem is when I try to follow the examples I've found, every line has local color distribution, instead of a global distribution. I'm thinking about defining a global color scale based on the maximum and mininum values of the third array, let's say temperature, and passing the coordinates of each element + the current average temperature may do the job, but I don't know if something alike is possible
Anyone can help?
You can map values to a particular colormap, and plot each element as a single line with a particular color, like this:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import Normalize
import numpy as np
# roto-translation matrix
T = lambda t, u, v: np.array([[np.cos(t), -np.sin(t), u], [np.sin(t), np.cos(t), 0], [0, 0, 1]])
# coordinates of a triangle
triangle = np.array([[-0.5, -0.5, 1], [0.5, -0.5, 1], [-0.5, 0.5, 1], [-0.5, -0.5, 1]]).T
n_elements = 10
values = np.linspace(-5, 5, n_elements)
# create a normalizer
norm = Normalize(vmin=values.min(), vmax=values.max())
# normalize values
norm_values = norm(values)
# choose a colormap
cmap = cm.magma
# create colors
colors = cmap(norm_values)
# map values to a colorbar
mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
mappable.set_array(values)
f, ax = plt.subplots(1)
ax.set_aspect("equal")
for i in range(n_elements):
tr = np.matmul(T(i * np.pi, int(i / 2), 0), triangle)
ax.plot(tr[0, :], tr[1, :], color=colors[i])
cb = f.colorbar(mappable)
cb.set_label("Value")
This seems like a common graphics problem, but I don't seem to find anything that does what I want in python:
I have many 4-vertex polygons in 3D space and need to calculate for a given ray which of the polygons is intercepted first and where. So it is basically ray-tracing of those surfaces to see which is in front. I want to do that for a x-y grid of rays to calculate an image. Below is some simple sample data and I would want to basically see two squares where one overlaps the other. I can do that with a 3D plot, but I need the actual values for a given ray, so for example for a y-directed ray from x=0.75, z=0.75, I need to find the y value where the foremost polygon is hit.
Hope this problem is clear. Ideally only common libraries like scipy, numpy, ... are used, but any help is appreciated!
The 5th point in the example-polygons below is just to plot a closed shape.
square1 = np.array([
[0, 0, 0],
[1, 0, 0],
[1, 0.5, 1],
[0, 0.5, 1],
[0, 0, 0]])
square2 = square1 + 0.5 * np.ones_like(square1)
# 3D plot of this projection
fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(projection='3d', proj_type='ortho')
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)
ax.set_zlim(-1, 2)
#ax.axis('off')
ax.plot(*square1[:,:].T)
ax.plot(*square2[:,:].T)
ax.view_init(elev=0., azim=-90)
# 2D plot of the projection
f, ax = plt.subplots()
ax.set_aspect('equal')
ax.plot(*square1[:,[0,2]].T)
ax.plot(*square2[:,[0,2]].T)
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)
I am attempting to use http://scikit-learn.org/stable/auto_examples/decomposition/plot_pca_iris.html for my own data to construct a 3D PCA plot. The tutorial, however, did not specify how I can add a legend. Another page, https://matplotlib.org/users/legend_guide.html did, but I cannot see how I can apply the information in the second tutorial to the first.
How can I modify the code below to add a legend?
# Code source: Gae"l Varoquaux
# License: BSD 3 clause
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import decomposition
from sklearn import datasets
np.random.seed(5)
centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data#the floating point values
y = iris.target#unsigned integers specifying group
fig = plt.figure(1, figsize=(4, 3))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
plt.cla()
pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)
for name, label in [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]:
ax.text3D(X[y == label, 0].mean(),
X[y == label, 1].mean() + 1.5,
X[y == label, 2].mean(), name,
horizontalalignment='center',
bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap=plt.cm.spectral,
edgecolor='k')
ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])
plt.show()
There are some issues with the other answer on which neither the OP, nor the answerer seem to be clear about; this is hence not a complete answer, but rather an appendix to the existing answer.
The spectral colormap has been removed from matplotlib in version 2.2,
use Spectral or nipy_spectral or any other valid colormap.
Any colormap in matplotlib ranges from 0 to 1. If you call it with any value outside that range,
it will just give your the outmost color. To get a color from a colormap you hence need to normalize the values.
This is done via a Normalize instance. In this case this is internal to scatter.
Hence use sc = ax.scatter(...) and then sc.cmap(sc.norm(value)) to get a value according to the same mapping that is used within the scatter.
Therefore the code should rather use
[sc.cmap(sc.norm(i)) for i in [1, 2, 0]]
The legend is outside the figure. The figure is 4 x 3 inches in size (figsize=(4, 3)).
The axes takes 95% of that space in width (rect=[0, 0, .95, 1]).
The call to legend places the legend's right center point at 1.7 times the axes width = 4*0.95*1.7 = 6.46 inches. (bbox_to_anchor=(1.7,0.5)).
Alternative suggestion from my side: Make the figure larger (figsize=(5.5, 3)), such that the legend will fit in, make the axes take only 70% of the figure width, such that you have 30% left for the legend. Position the legend's left side close to the axes boundary (bbox_to_anchor=(1.0, .5)).
For more on this topic see How to put the legend out of the plot.
The reason you still see the complete figure including the legend in a jupyter notebook is that jupyter will just save everything inside the canvas, even if it overlaps and thereby enlarge the figure.
In total the code may then look like
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np; np.random.seed(5)
from sklearn import decomposition, datasets
centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data #the floating point values
y = iris.target #unsigned integers specifying group
fig = plt.figure(figsize=(5.5, 3))
ax = Axes3D(fig, rect=[0, 0, .7, 1], elev=48, azim=134)
pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)
labelTups = [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]
for name, label in labelTups:
ax.text3D(X[y == label, 0].mean(),
X[y == label, 1].mean() + 1.5,
X[y == label, 2].mean(), name,
horizontalalignment='center',
bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
sc = ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap="Spectral", edgecolor='k')
ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])
colors = [sc.cmap(sc.norm(i)) for i in [1, 2, 0]]
custom_lines = [plt.Line2D([],[], ls="", marker='.',
mec='k', mfc=c, mew=.1, ms=20) for c in colors]
ax.legend(custom_lines, [lt[0] for lt in labelTups],
loc='center left', bbox_to_anchor=(1.0, .5))
plt.show()
and produce
Needed a few tweaks (plt.cm.spectral is the danged weirdest colormap I've ever dealt with), but it seems to be good now:
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from sklearn import decomposition
from sklearn import datasets
np.random.seed(5)
centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data#the floating point values
y = iris.target#unsigned integers specifying group
fig = plt.figure(1, figsize=(4, 3))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
plt.cla()
pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)
labelTups = [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]
for name, label in labelTups:
ax.text3D(X[y == label, 0].mean(),
X[y == label, 1].mean() + 1.5,
X[y == label, 2].mean(), name,
horizontalalignment='center',
bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap=plt.cm.spectral, edgecolor='k')
ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])
colors = [plt.cm.spectral(np.float(i/2)) for i in [1, 2, 0]]
custom_lines = [Line2D([0], [0], linestyle="none", marker='.', markeredgecolor='k', markerfacecolor=c, markeredgewidth=.1, markersize=20) for c in colors]
ax.legend(custom_lines, [lt[0] for lt in labelTups], loc='right', bbox_to_anchor=(1.7, .5))
plt.show()
Here's a link to an online Jupyter notebook with a live version of the script (requires an account for rerunning, though).
Short explanation
You're trying to add three legend markers for a single plot, which is nonstandard behavior. Thus, you need to manually create the shapes that your legend will display.
Longer explanation
This line of code recreates the colors you used in your plot:
colors = [plt.cm.spectral(np.float(i/2)) for i in [1, 2, 0]]
and then this line of code draws some appropriate-looking dots that we'll eventually display on your legend:
custom_lines = [Line2D([0], [0], linestyle="none", marker='.', markeredgecolor='k', markerfacecolor=c, markeredgewidth=.1, markersize=20) for c in colors]
The first two args are just the (internal) x and y coords of the single dot that will be drawn, linestyle="none" suppresses the line that Line2D would normally draw by default, and the rest of the args create and style the dot itself (referred to as a marker in the terminology of the matplotlib api).
Finally, this statement actually creates the legend:
ax.legend(custom_lines, [lt[0] for lt in labelTups], loc='right', bbox_to_anchor=(1.7, .5))
The first arg is of course a list of the dots we just drew, and the second arg is a list of the labels (one per dot). The remaining two args tell matplotlib where to draw the actual box containing the legend. The last arg, bbox_to_anchor, is basically a way to manually fiddle with the positioning of the legend, which I had to do since matplotlib support for 3D anything is still a little behind the curve. On 2D plots you typically don't need it, and, since matplotlib usually does a decent job of automatically positioning the legend on 2D plots in the first place, you often don't even need the loc arg either.
Some colormap weirdness
Don't quite know what was going on with plt.cm.spectral, but in order to get it to behave, for every value I fed it I had to:
a) first cast the value to float
b) then divide the value by 2
a) does occur explicitly in the OP's original code, right before they plot. The divide by 2 thing, I don't know where that comes from. Somehow the call to ax.scatter is implicitly normalizing all of the y values so that the maximum is 1? I guess?
I have two list as below:
latt=[42.0,41.978567980875397,41.96622693388357,41.963791391892457,...,41.972407378075879]
lont=[-66.706920989908909,-66.703116557977069,-66.707351643324543,...-66.718218142021925]
now I want to plot this as a line, separate each 10 of those 'latt' and 'lont' records as a period and give it a unique color.
what should I do?
There are several different ways to do this. The "best" approach will depend mostly on how many line segments you want to plot.
If you're just going to be plotting a handful (e.g. 10) line segments, then just do something like:
import numpy as np
import matplotlib.pyplot as plt
def uniqueish_color():
"""There're better ways to generate unique colors, but this isn't awful."""
return plt.cm.gist_ncar(np.random.random())
xy = (np.random.random((10, 2)) - 0.5).cumsum(axis=0)
fig, ax = plt.subplots()
for start, stop in zip(xy[:-1], xy[1:]):
x, y = zip(start, stop)
ax.plot(x, y, color=uniqueish_color())
plt.show()
If you're plotting something with a million line segments, though, this will be terribly slow to draw. In that case, use a LineCollection. E.g.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
xy = (np.random.random((1000, 2)) - 0.5).cumsum(axis=0)
# Reshape things so that we have a sequence of:
# [[(x0,y0),(x1,y1)],[(x0,y0),(x1,y1)],...]
xy = xy.reshape(-1, 1, 2)
segments = np.hstack([xy[:-1], xy[1:]])
fig, ax = plt.subplots()
coll = LineCollection(segments, cmap=plt.cm.gist_ncar)
coll.set_array(np.random.random(xy.shape[0]))
ax.add_collection(coll)
ax.autoscale_view()
plt.show()
For both of these cases, we're just drawing random colors from the "gist_ncar" coloramp. Have a look at the colormaps here (gist_ncar is about 2/3 of the way down): http://matplotlib.org/examples/color/colormaps_reference.html
Copied from this example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
x = np.linspace(0, 3 * np.pi, 500)
y = np.sin(x)
z = np.cos(0.5 * (x[:-1] + x[1:])) # first derivative
# Create a colormap for red, green and blue and a norm to color
# f' < -0.5 red, f' > 0.5 blue, and the rest green
cmap = ListedColormap(['r', 'g', 'b'])
norm = BoundaryNorm([-1, -0.5, 0.5, 1], cmap.N)
# Create a set of line segments so that we can color them individually
# This creates the points as a N x 1 x 2 array so that we can stack points
# together easily to get the segments. The segments array for line collection
# needs to be numlines x points per line x 2 (x and y)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# Create the line collection object, setting the colormapping parameters.
# Have to set the actual values used for colormapping separately.
lc = LineCollection(segments, cmap=cmap, norm=norm)
lc.set_array(z)
lc.set_linewidth(3)
fig1 = plt.figure()
plt.gca().add_collection(lc)
plt.xlim(x.min(), x.max())
plt.ylim(-1.1, 1.1)
plt.show()
See the answer here to generate the "periods" and then use the matplotlib scatter function as #tcaswell mentioned. Using the plot.hold function you can plot each period, colors will increment automatically.
Cribbing the color choice off of #JoeKington,
import numpy as np
import matplotlib.pyplot as plt
def uniqueish_color(n):
"""There're better ways to generate unique colors, but this isn't awful."""
return plt.cm.gist_ncar(np.random.random(n))
plt.scatter(latt, lont, c=uniqueish_color(len(latt)))
You can do this with scatter.
I have been searching for a short solution how to use pyplots line plot to show a time series coloured by a label feature without using scatter due to the amount of data points.
I came up with the following workaround:
plt.plot(np.where(df["label"]==1, df["myvalue"], None), color="red", label="1")
plt.plot(np.where(df["label"]==0, df["myvalue"], None), color="blue", label="0")
plt.legend()
The drawback is you are creating two different line plots so the connection between the different classes is not shown. For my purposes it is not a big deal. It may help someone.
I've got a bunch of regularly distributed points (θ = n*π/6, r=1...8), each having a value in [0, 1]. I can plot them with their values in matplotlib using
polar(thetas, rs, c=values)
But rather then having just a meagre little dot I'd like to shade the corresponding 'cell' (ie. everything until halfway to the adjacent points) with the colour corresponding to the point's value:
(Note that here my values are just [0, .5, 1], in really they will be everything between 0 and 1. Is there any straight-forward way of realising this (or something close enough) with matplotlib? Maybe it's easier to think about it as a 2D-histogram?
This can be done quite nicely by treating it as a polar stacked barchart:
import matplotlib.pyplot as plt
import numpy as np
from random import choice
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
for i in xrange(12*8):
color = choice(['navy','maroon','lightgreen'])
ax.bar(i * 2 * np.pi / 12, 1, width=2 * np.pi / 12, bottom=i / 12,
color=color, edgecolor = color)
plt.ylim(0,10)
ax.set_yticks([])
plt.show()
Produces:
Sure! Just use pcolormesh on a polar axes.
E.g.
import matplotlib.pyplot as plt
import numpy as np
# Generate some data...
# Note that all of these are _2D_ arrays, so that we can use meshgrid
# You'll need to "grid" your data to use pcolormesh if it's un-ordered points
theta, r = np.mgrid[0:2*np.pi:20j, 0:1:10j]
z = np.random.random(theta.size).reshape(theta.shape)
fig, (ax1, ax2) = plt.subplots(ncols=2, subplot_kw=dict(projection='polar'))
ax1.scatter(theta.flatten(), r.flatten(), c=z.flatten())
ax1.set_title('Scattered Points')
ax2.pcolormesh(theta, r, z)
ax2.set_title('Cells')
for ax in [ax1, ax2]:
ax.set_ylim([0, 1])
ax.set_yticklabels([])
plt.show()
If your data isn't already on a regular grid, then you'll need to grid it to use pcolormesh.
It looks like it's on a regular grid from your plot, though. In that case, gridding it is quite simple. If it's already ordered, it may be as simple as calling reshape. Otherwise, a simple loop or exploiting numpy.histogram2d with your z values as weights will do what you need.
Well, it's fairly unpolished overall, but here's a version that rounds out the sections.
from matplotlib.pylab import *
ax = subplot(111, projection='polar')
# starts grid and colors
th = array([pi/6 * n for n in range(13)]) # so n = 0..12, allowing for full wrapping
r = array(range(9)) # r = 0..8
c = array([[random_integers(0, 10)/10 for y in range(th.size)] for x in range(r.size)])
# The smoothing
TH = cbook.simple_linear_interpolation(th, 10)
# Properly padding out C so the colors go with the right sectors (can't remember the proper word for such segments of wedges)
# A much more elegant version could probably be created using stuff from itertools or functools
C = zeros((r.size, TH.size))
oldfill = 0
TH_ = TH.tolist()
for i in range(th.size):
fillto = TH_.index(th[i])
for j, x in enumerate(c[:,i]):
C[j, oldfill:fillto].fill(x)
oldfill = fillto
# The plotting
th, r = meshgrid(TH, r)
ax.pcolormesh(th, r, C)
show()