I wish to produce a single line plot in Matplotlib that has variable transparency, i.e. it starts from solid color to full transparent color.
I tried this but it didn't work.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alphas = np.linspace(1, 0, 500)
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, alpha=alphas)
Matplotlib's "LineCollection" allows you to split the line to be plotted into individual line segments and you can assign a color to each segment. The code example below shows how each horizontal "x" value can be assigned an alpha (transparency) value that indexes into a sequential colormap that runs from transparent to a given color. A suitable colormap "myred" was created using Matplotlib's "colors" module.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.colors as colors
redfade = colors.to_rgb("red") + (0.0,)
myred = colors.LinearSegmentedColormap.from_list('my',[redfade, "red"])
x = np.linspace(0,1, 1000)
y = np.sin(x * 4 * np.pi)
alphas = x * 4 % 1
points = np.vstack((x, y)).T.reshape(-1, 1, 2)
segments = np.hstack((points[:-1], points[1:]))
fig, ax = plt.subplots()
lc = LineCollection(segments, array=alphas, cmap=myred, lw=3)
line = ax.add_collection(lc)
ax.autoscale()
plt.show()
If you are using the standard white background then you can save a few lines by using one of Matplotlib's builtin sequential colormaps that runs from white to a given color. If you remove the lines that created the colormap above and just put the agument cmap="Reds" in the LineCollection function, it creates a visually similar result.
The only solution I found was to plot each segment independently with varying transparency
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alphas = np.linspace(1, 0, 499)
fig, ax = plt.subplots(1, 1)
for i in range(499):
ax.plot(x[i:i+2], y[i:i+2], 'k', alpha=alphas[i])
But I don't like it... Maybe this is enough for someone
I don't know how to do this in matplotlib, but it's possible in Altair:
import numpy as np
import pandas as pd
import altair as alt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alt.Chart(
pd.DataFrame({"x": x, "y": y, "o": np.linspace(0, 1, len(x))}),
).mark_point(
).encode(
alt.X("x"),
alt.Y("y"),
alt.Opacity(field="x", type="quantitative", scale=alt.Scale(range=[1, 0]), legend=None),
)
Result:
Related
I am trying to get my 3D python plot into 3 different colors based on the value of Z from a CSV file. I am trying to color a point one specific color, and then points below one color and points above one color. I can get the plot into a color above and below the point, but I can't seem to figure out how to get it into 3 colors.
I have tried to split the Z value into 3 different 3 subsets, but when I tried to plot the plot was just empty. I also tried to write it through an if statement assigning Z to the color but that did not work either. This is the code that works for the 2 color:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
headers = ['name','ra','x rads','x par','dec','y rads','Parallax','Parallax Error','central distance','Z Max','Z Min']
mergeddata = pd.read_csv(r'C:\Users\GregL\Downloads\mergedata - no neg parallax #s (2).csv')
mergeddata.z = mergeddata['central distance']
mergeddata.x = mergeddata['x par']
mergeddata.y = mergeddata['y rads']
x= mergeddata.x
y= mergeddata.y
z = mergeddata.z
colors = [z <= 1956.783590]
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
surf=ax.scatter3D(x,y,z,c=colors, cmap='coolwarm',s=.5,marker='^')
ax.set_title('3D Data Distance Plot')
ax.set_zlim(-100,10000)
ax.set_xlim(-50,50)
ax.set_ylim(-50,50)
ax.set_xlabel('RA')
ax.set_ylabel('DEC')
ax.set_zlabel('CENTRAL DISTANCE')
plt.show()
Which gives me this plot
As mentioned by Claudio on the comment, you can create a color value and then assign a proper colormap. Here, I'm going to create a discrete color map based on Matplotlib's Tab10:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as col
fig = plt.figure()
ax = plt.axes(projection ='3d')
z = np.linspace(0, 1, 100)
x = z * np.sin(25 * z)
y = z * np.cos(25 * z)
# values for color
c = [int(zv / 0.4) for zv in z]
# discrete colormap with 3 colors
cmap=col.ListedColormap(cm.tab10.colors[:len(np.unique(c))])
ax.scatter(x, y, z, c=c, cmap=cmap)
plt.show()
Alternatively, you can create multiple ax.scatter commands, each one plotting a subset. The advantage of this approach is that you can set custom labels or rendering properties to each subset:
fig = plt.figure()
ax = plt.axes(projection ='3d')
i1 = z < 0.3
i2 = (z >= 0.3) & (z < 0.6)
i3 = z >= 0.6
ax.scatter(x[i1], y[i1], z[i1], label="a")
ax.scatter(x[i2], y[i2], z[i2], label="b")
ax.scatter(x[i3], y[i3], z[i3], label="c")
ax.legend()
plt.show()
Based on the matplotlib example code I constructed a 3D version of a multicolored line. I am working in a jupyter notebook and by using %matplotlib notebook I may zoom into the plot and the corner edges are rendered smoothly in my browser - perfect! However, when I export the plot as png or pdf file for further usage the corner edges are "jagged".
Any ideas how to smoothen the 3D-multicolored line?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap, BoundaryNorm
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Line3DCollection
%matplotlib notebook
# Generate random data
np.random.seed(1)
n = 20 # number of data points
#set x,y,z data
x = np.random.uniform(0, 1, n)
y = np.random.uniform(0, 1, n)
z = np.arange(0,n)
# 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)
#################
### 3D Figure ###
#################
# Create a set of line segments
points = np.array([x, y, z]).T.reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# Create the 3D-line collection object
lc = Line3DCollection(segments, cmap=plt.get_cmap('copper'),
norm=plt.Normalize(0, n))
lc.set_array(z)
lc.set_linewidth(2)
#plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_zlim(0, max(z))
plt.title('3D-Figure')
ax.add_collection3d(lc, zs=z, zdir='z')
#save plot
plt.savefig('3D_Line.png', dpi=600, facecolor='w', edgecolor='w',
orientation='portrait')
I think join style is what controls the look of segment joints. Line3DCollection does have a set_joinstyle() function, but that doesn't seem to make any difference. So I've to abandon Line3DCollection and plot the line segment by segment, and for each segment, call its set_solid_capstyle('round').
Below is what works for me:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Generate random data
np.random.seed(1)
n = 20 # number of data points
#set x,y,z data
x = np.random.uniform(0, 1, n)
y = np.random.uniform(0, 1, n)
z = np.arange(0,n)
#################
### 3D Figure ###
#################
# Create a set of line segments
points = np.array([x, y, z]).T.reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
cmap=plt.get_cmap('copper')
colors=[cmap(float(ii)/(n-1)) for ii in range(n-1)]
#plot
fig = plt.figure()
ax = fig.gca(projection='3d')
for ii in range(n-1):
segii=segments[ii]
lii,=ax.plot(segii[:,0],segii[:,1],segii[:,2],color=colors[ii],linewidth=2)
#lii.set_dash_joinstyle('round')
#lii.set_solid_joinstyle('round')
lii.set_solid_capstyle('round')
ax.set_zlim(0, max(z))
plt.title('3D-Figure')
#save plot
plt.savefig('3D_Line.png', dpi=600, facecolor='w', edgecolor='w',
orientation='portrait')
Output image at zoom:
I am running this sample script, with the following modifications:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
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)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
ax.set_xlabel('$X$', fontsize=20, rotation=150)
ax.set_ylabel('$Y$')
ax.set_zlabel(r'$\gamma$', fontsize=30, rotation=60)
ax.yaxis._axinfo['label']['space_factor'] = 3.0
plt.show()
How do I adjust the axis ticks to that of my choosing? I.e., how would I get the z-axis to only label 2, 0, and -2, and in the font size that I want? I know how to do this in 2D but not 3D.
The script above produces the following:
Why is the x-axis label distorted, which I wanted to do with this script, but not the z-axis label (gamma)? This does not make sense. I need this axis labeled in the Greek letter. How do I fix this?
How do I adjust the axis ticks to that of my choosing? I.e., how would
I get the z-axis to only label 2, 0, and -2, and in the font size that
I want? I know how to do this in 2D but not 3D.
You have to change properties of zticks.
Why is the x-axis label distorted, which I wanted to do with this
script, but not the z-axis label (gamma)? This does not make sense. I
need this axis labeled in the Greek letter. How do I fix this?
You have to disable autorotation for z axis labels. Look at the code below:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
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)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
ax.set_xlabel('$X$', fontsize=20)
ax.set_ylabel('$Y$')
ax.yaxis._axinfo['label']['space_factor'] = 3.0
# set z ticks and labels
ax.set_zticks([-2, 0, 2])
# change fontsize
for t in ax.zaxis.get_major_ticks(): t.label.set_fontsize(10)
# disable auto rotation
ax.zaxis.set_rotate_label(False)
ax.set_zlabel('$\gamma$', fontsize=30, rotation = 0)
plt.show()
The for loop is not necessary, to change the size of your ticks you can use:
ax.zaxis.set_tick_params(labelsize=10)
I want to plot a donut and my script is
import numpy as np
import matplotlib.pyplot as plt
pi,sin,cos = np.pi,np.sin,np.cos
r1 = 1
r2 = 2
theta = np.linspace(0,2*pi,36)
x1 = r1*cos(theta)
y1 = r1*sin(theta)
x2 = r2*cos(theta)
y2 = r2*sin(theta)
How to get a donut with red filled area ?
You can traverse the boundaries of the area in closed curve, and use fill method to fill the area inside this closed area:
import numpy as np
import matplotlib.pyplot as plt
n, radii = 50, [.7, .95]
theta = np.linspace(0, 2*np.pi, n, endpoint=True)
xs = np.outer(radii, np.cos(theta))
ys = np.outer(radii, np.sin(theta))
# in order to have a closed area, the circles
# should be traversed in opposite directions
xs[1,:] = xs[1,::-1]
ys[1,:] = ys[1,::-1]
ax = plt.subplot(111, aspect='equal')
ax.fill(np.ravel(xs), np.ravel(ys), edgecolor='#348ABD')
plt.show()
This can easily be applied to any shape, for example, a pentagon inside or outside of a circle:
You can do this by plotting the top and bottom halves separately:
import numpy as np
import matplotlib.pyplot as plt
inner = 5.
outer = 10.
x = np.linspace(-outer, outer, 1000, endpoint=True)
yO = outer*np.sin(np.arccos(x/outer)) # x-axis values -> outer circle
yI = inner*np.sin(np.arccos(x/inner)) # x-axis values -> inner circle (with nan's beyond circle)
yI[np.isnan(yI)] = 0. # yI now looks like a boulder hat, meeting yO at the outer points
ax = plt.subplot(111)
ax.fill_between(x, yI, yO, color="red")
ax.fill_between(x, -yO, -yI, color="red")
plt.show()
Or you can use polar coordinates, though whether this is beneficial depends on the broader context:
import numpy as np
import matplotlib.pyplot as plt
theta = np.linspace(0., 2.*np.pi, 80, endpoint=True)
ax = plt.subplot(111, polar=True)
ax.fill_between(theta, 5., 10., color="red")
plt.show()
It's a bit of a hack but the following works:
import numpy as np
import matplotlib.pyplot as plt
pi,sin,cos = np.pi,np.sin,np.cos
r1 = 1
r2 = 2
theta = np.linspace(0,2*pi,36)
x1 = r1*cos(theta)
y1 = r1*sin(theta)
x2 = r2*cos(theta)
y2 = r2*sin(theta)
fig, ax = plt.subplots()
ax.fill_between(x2, -y2, y2, color='red')
ax.fill_between(x1, y1, -y1, color='white')
plt.show()
It plots the whole area of your donut in red and then plots the central "hole" in white.
The answer given by tom10 is ten ;)
But if you want to define the circle (donut) origin is simple, just add the position x,y in the x, yI, yO and -yO and -yI, like this:
...
pos = [4,2]
ax.fill_between(x+pos[0], yI+pos[1], yO+pos[1], color=color)
ax.fill_between(x+pos[0], -yO+pos[1], -yI+pos[1], color=color)
...
REF Example: https://pastebin.com/8Ew4Vthb
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.