Change shape of Ellipse handle in legend - python

I'm trying to plot the labels of some contours and an ellipse in a single legend. I'm almost there (code below), but I'd like the shape associated to the ellipse in the legend to be a straight line, instead of a rectangle as it is by default.
How can I change this?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
# Random data
ndim, nsamples = 2, 1000
samples = np.random.randn(ndim * nsamples).reshape([nsamples, ndim])
fig, ax = plt.subplots()
x, y = samples.T
H, X, Y = plt.hist2d(x, y, bins=20, cmap=plt.get_cmap('Greys'))[:-1]
# Plot two contours
contour = ax.contour(
H.T, levels=(5, 10), extent=[x.min(), x.max(), y.min(), y.max()])
# Plot ellipse
ellipse = Ellipse(xy=(0., 0.), width=3., height=2, angle=45, edgecolor='r', fc='None', label='ellipse')
ax.add_patch(ellipse)
# Get ellipse's handle and label
ellip, ellip_lbl = ax.get_legend_handles_labels()
# Plot legend
plt.legend(ellip + list(reversed(contour.collections)), ellip_lbl + ['1s', '2s'])
plt.show()

Below is the solution based on this answer. The main idea here is to use ls="-", by plotting an empty list and grabbing its handle. Store the ellipse's patch in ax1 and use it to get the label.
ellipse = Ellipse(xy=(0., 0.), width=3., height=2, angle=45, edgecolor='r', fc='None', label='ellipse')
ax1 = ax.add_patch(ellipse)
# Get ellipse's handle and label
ellip, ellip_lbl = ax.get_legend_handles_labels()
plt.legend(handles = [plt.plot([],ls="-", color='r')[0]] + list(reversed(contour.collections)),
labels=[ax1.get_label()] + ['1s', '2s'])

Related

White borders left out after moving the axes

I'm trying to make the matplotlib graph look more like the ones used in graphing calculators. When moving the axes to the center of the plot, the place they used to be is left empty, making it look like it has white borders.
NORMAL GRAPH
MODIFIED GRAPH
I don't know if this is related to the axes or to the labels which were removed.
I am aware of an argument used when saving the image, something like
plt.savefig("image.png",bbox_inches='tight')
but regardless of whether that method works for this situation or for other kind of borders, my intention is to show the image inside a tkinter window, not saving it to the users computer.
Is there a way of removing these left out "borders"?
Here's the code
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import random
import numpy as np
root = Tk()
root.geometry("500x500")
# CREATE X VALUES
x_list = np.arange(-20, 20, .01)
# GENERATE 'RANDOM' VALUES FOR THE QUADRATIC EQUATION'S PARAMETERS
a = random.choice([-1,-0.6,0.6,1])
b = random.uniform(-5,5)
c = random.randint(-5,5)
# CREATE FIGURE
fig = Figure(figsize = (5, 5), dpi = 100)
# PLOT
ax = fig.add_subplot(111)
ax.plot(x_list, a * np.power(x_list,2) + b * x_list + c)
ax.axis([-20,20,-20,20])
# CENTER TWO AXES
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('zero')
# HIDE THE OTHER TWO AXES
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# SET TICKS POSITION
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# HIDE NUMBER LABELS
ax.set_xticklabels([])
ax.set_yticklabels([])
# -------------------------- COLORS
# I want to remove the green part of the figure
# (UNCOMMENT TO SEE THE COLORS IN THE FIGURE)
#fig.set_facecolor('#68C45F')
#ax.set_facecolor('#E56361')
# CREATE CANVAS
canvas = FigureCanvasTkAgg(fig, master = root)
canvas.draw()
canvas.get_tk_widget().pack()
root.mainloop()
hereimport numpy as np
import matplotlib.pyplot as plt
# Select length of axes and the space between tick labels
xmin, xmax, ymin, ymax = -5, 5, -5, 5
ticks_frequency = 1
# Plot points
fig, ax = plt.subplots(figsize=(10, 10))
#//////////////////////////////////////////////////////////////////////////////
x = np.arange(-5, 5, 0.01)
y = x**2 - 1
plt.plot(x, y, color = "red", alpha = 0.8, lw = 1)
#//////////////////////////////////////////////////////////////////////////////
# Set identical scales for both axes
ax.set(xlim=(xmin-1, xmax+1), ylim=(ymin-1, ymax+1), aspect='equal')
# Set bottom and left spines as x and y axes of coordinate system
ax.spines['bottom'].set_position('zero')
ax.spines['left'].set_position('zero')
# Remove top and right spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Create 'x' and 'y' labels placed at the end of the axes
ax.set_xlabel('x', size=14, labelpad=-24, x=1.03)
ax.set_ylabel('y', size=14, labelpad=-21, y=1.02, rotation=0)
# Create custom major ticks to determine position of tick labels
x_ticks = np.arange(xmin, xmax+1, ticks_frequency)
y_ticks = np.arange(ymin, ymax+1, ticks_frequency)
ax.set_xticks(x_ticks[x_ticks != 0])
ax.set_yticks(y_ticks[y_ticks != 0])
# Create minor ticks placed at each integer to enable drawing of minor grid
# lines: note that this has no effect in this example with ticks_frequency=1
ax.set_xticks(np.arange(xmin, xmax+1), minor=True)
ax.set_yticks(np.arange(ymin, ymax+1), minor=True)
# Draw major and minor grid lines
ax.grid(which='both', color='grey', linewidth=1, linestyle='-', alpha=0.2)
# Draw arrows
arrow_fmt = dict(markersize=4, color='black', clip_on=False)
ax.plot((1), (0), marker='>', transform=ax.get_yaxis_transform(), **arrow_fmt)
ax.plot((0), (1), marker='^', transform=ax.get_xaxis_transform(), **arrow_fmt)
plt.show()
here you have a nice looking graph i hope that helps

How to add a color gradient line(collection) to the legend? [duplicate]

Following the example on how to draw multicolored lines I can draw lines that change color along their length based on some color map. Trying to add a legend to the plot I added this code:
plt.legend([lc], ["test"],\
handler_map={lc: matplotlib.legend_handler.HandlerLineCollection()})
This adds a legend to the plot (figure below) but the color of the icon in the legend does not relate at all to the colors of the line. Is this the wrong way to try to add a legend to this plot, or is this a limitation of matplotlib?
The idea would be to show a line collection in the legend as well. There is no inbuilt way to do that but one may subclass HandlerLineCollection and create the respective LineCollection within its create_artists method.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLineCollection
from matplotlib.collections import LineCollection
class HandlerColorLineCollection(HandlerLineCollection):
def create_artists(self, legend, artist ,xdescent, ydescent,
width, height, fontsize,trans):
x = np.linspace(0,width,self.get_numpoints(legend)+1)
y = np.zeros(self.get_numpoints(legend)+1)+height/2.-ydescent
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=artist.cmap,
transform=trans)
lc.set_array(x)
lc.set_linewidth(artist.get_linewidth())
return [lc]
t = np.linspace(0, 10, 200)
x = np.cos(np.pi * t)
y = np.sin(t)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=plt.get_cmap('copper'),
norm=plt.Normalize(0, 10), linewidth=3)
lc.set_array(t)
fig, ax = plt.subplots()
ax.add_collection(lc)
plt.legend([lc], ["test"],\
handler_map={lc: HandlerColorLineCollection(numpoints=4)}, framealpha=1)
ax.autoscale_view()
plt.show()

Get X,Y Position in Figure (not in Plot)

I have a question about the X and Y Position in figures. How you can see I am working with gridspec for a better layout and adding Text to a figure. The problem is that I am trying to get the exact Position manually. Which means I am changing the X and Y in fig.text(0.2, 0.5, 'matplotlib') until I get the final figure.
import matplotlib.pylab as plt
import numpy as np
vector = np.arange(0,100)
time = np.arange(0,vector.shape[0])
fig = plt.figure(figsize=(10,10))
plt.rcParams['axes.grid'] = True
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(time,vector)
fig.text(0.2, 0.5, 'matplotlib')
At Link I already found an interactive solution but its only working for the Plot.
Does someone have an idea how to manage this?
You can create a blended transform, where the y-coordinates have a figure transform. And the x-coordinates have a axes transform. The figure transform is measured 0 at the left/bottom and 1 at the right/top of the figure. The axes transform is similar, but regarding the axes. The parameter clip_on=False allows to draw outside the axes region (text allows this by default).
import matplotlib.transforms as mtransforms
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(gridspec_kw={})
# the x coords of this transformation are axes, and the y coord are fig
trans = mtransforms.blended_transform_factory(ax.transAxes, fig.transFigure)
x, w = 1, -0.3 # axes coordinates
y, h = 0.04, 0.06 # figure coordinates
ax.text(x + w / 2, y + h / 2, 'hello', transform=trans, ha='center', va='center')
rect = mpatches.Rectangle((x, y), w, h, transform=trans, edgecolor='crimson', facecolor='yellow', clip_on=False)
ax.add_patch(rect)
fig.tight_layout(pad=2)
plt.show()
PS: You can set the vertical alignment va='right' to have the right margin of the text align with the right axis. You can also use transform=ax.transAxes with negative y-coordinates to plot everything relative to the axes.

Adding a legend to a matplotlib plot with a multicolored line

Following the example on how to draw multicolored lines I can draw lines that change color along their length based on some color map. Trying to add a legend to the plot I added this code:
plt.legend([lc], ["test"],\
handler_map={lc: matplotlib.legend_handler.HandlerLineCollection()})
This adds a legend to the plot (figure below) but the color of the icon in the legend does not relate at all to the colors of the line. Is this the wrong way to try to add a legend to this plot, or is this a limitation of matplotlib?
The idea would be to show a line collection in the legend as well. There is no inbuilt way to do that but one may subclass HandlerLineCollection and create the respective LineCollection within its create_artists method.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLineCollection
from matplotlib.collections import LineCollection
class HandlerColorLineCollection(HandlerLineCollection):
def create_artists(self, legend, artist ,xdescent, ydescent,
width, height, fontsize,trans):
x = np.linspace(0,width,self.get_numpoints(legend)+1)
y = np.zeros(self.get_numpoints(legend)+1)+height/2.-ydescent
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=artist.cmap,
transform=trans)
lc.set_array(x)
lc.set_linewidth(artist.get_linewidth())
return [lc]
t = np.linspace(0, 10, 200)
x = np.cos(np.pi * t)
y = np.sin(t)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=plt.get_cmap('copper'),
norm=plt.Normalize(0, 10), linewidth=3)
lc.set_array(t)
fig, ax = plt.subplots()
ax.add_collection(lc)
plt.legend([lc], ["test"],\
handler_map={lc: HandlerColorLineCollection(numpoints=4)}, framealpha=1)
ax.autoscale_view()
plt.show()

Dynamic marker colour in matplotlib

I have two lists containing the x and y coordinates of some points. There is also a list with some values assigned to each of those points. Now my question is, I can always plot the points (x,y) using markers in python. Also I can select colour of the marker manually (as in this code).
import matplotlib.pyplot as plt
x=[0,0,1,1,2,2,3,3]
y=[-1,3,2,-2,0,2,3,1]
colour=['blue','green','red','orange','cyan','black','pink','magenta']
values=[2,6,10,8,0,9,3,6]
for i in range(len(x)):
plt.plot(x[i], y[i], linestyle='none', color=colour[i], marker='o')
plt.axis([-1,4,-3,4])
plt.show()
But is it possible to choose a colour for the marker marking a particular point according to the value assigned to that point (using cm.jet, cm.gray or similar other color schemes) and provide a colorbar with the plot ?
For example, this is the kind of plot I am looking for
where the red dots denote high temperature points and the blue dots denote low temperature ones and others are for temperatures in between.
You are most likely looking for matplotlib.pyplot.scatter. Example:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
# Generate data:
N = 10
x = np.linspace(0, 1, N)
y = np.linspace(0, 1, N)
x, y = np.meshgrid(x, y)
colors = np.random.rand(N, N) # colors for each x,y
# Plot
circle_size = 200
cmap = matplotlib.cm.viridis # replace with your favourite colormap
fig, ax = plt.subplots(figsize=(4, 4))
s = ax.scatter(x, y, s=circle_size, c=colors, cmap=cmap)
# Prettify
ax.axis("tight")
fig.colorbar(s)
plt.show()
Note: viridis may fail on older version of matplotlib.
Resulting image:
Edit
scatter does not require your input data to be 2-D, here are 4 alternatives that generate the same image:
import matplotlib
import matplotlib.pyplot as plt
x = [0,0,1,1,2,2,3,3]
y = [-1,3,2,-2,0,2,3,1]
values = [2,6,10,8,0,9,3,6]
# Let the colormap extend between:
vmin = min(values)
vmax = max(values)
cmap = matplotlib.cm.viridis
norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
fig, ax = plt.subplots(4, sharex=True, sharey=True)
# Alternative 1: using plot:
for i in range(len(x)):
color = cmap(norm(values[i]))
ax[0].plot(x[i], y[i], linestyle='none', color=color, marker='o')
# Alternative 2: using scatter without specifying norm
ax[1].scatter(x, y, c=values, cmap=cmap)
# Alternative 3: using scatter with normalized values:
ax[2].scatter(x, y, c=cmap(norm(values)))
# Alternative 4: using scatter with vmin, vmax and cmap keyword-arguments
ax[3].scatter(x, y, c=values, vmin=vmin, vmax=vmax, cmap=cmap)
plt.show()

Categories

Resources