I created this tree map using Matplotlib and Squarify:
Now I would like to add a line plot on each rectangle in the tree map. Is that possible?
Squarify's plot is a convenience function to directly plot a treemap given values and labels. But, this process can also be executed step-by-step. One of the steps is to calculate the positions of the rectangles, for which we suppose a figure which has coordinates from 0,0 to 1,1 from lower left to upper right.
With these rectangles we can manually position axes to draw on. It is unclear whether ticks are needed. If needed, they can be placed inside. Or the axes could be moved completely to the center of each subplot. Or only have ticks without labels.
Here is some demonstrating code:
import numpy as np
import matplotlib.pyplot as plt
import squarify
values = [500, 433, 331, 254, 119]
values.sort(reverse=True) # values must be sorted descending (and positive)
# the sum of the values must equal the total area to be laid out; i.e., sum(values) == width * height
values = squarify.normalize_sizes(values, 1, 1)
rects = squarify.squarify(values, 0, 0, 1, 1)
fig = plt.figure(figsize=(7, 5))
axes = [fig.add_axes([rect['x'], rect['y'], rect['dx'], rect['dy'], ]) for rect in rects]
for ax, color in zip(axes, plt.cm.Pastel1.colors):
x = np.linspace(0, 10, 100)
y = np.random.normal(0.01, 0.1, 100).cumsum()
ax.plot(x, y)
ax.tick_params(axis="y", direction="in", pad=-15)
ax.tick_params(axis="x", direction="in", pad=-15)
plt.setp(ax.get_yticklabels(), ha="left")
ax.set_facecolor(color)
plt.show()
Here is another example resembling the image in the question, with a main plot and a colorbar. The default mplcursors gets confused with all these axes, but annotations while hovering can also be added manually.
import numpy as np
import matplotlib.pyplot as plt
import squarify
values = [4000, 1500, 1500, 1200, 1000, 500]
fig, mainax = plt.subplots(figsize=(6, 4))
mainax.set_xlim(0, 1000)
mainax.set_ylim(0, 1000)
mainax.grid(False)
cmap = plt.cm.get_cmap('Greens')
norm = plt.Normalize(vmin=0, vmax=max(values))
plt.colorbar(plt.cm.ScalarMappable(cmap=cmap, norm=norm))
pos = mainax.get_position()
values.sort(reverse=True)
normalized_values = squarify.normalize_sizes(values, pos.width, pos.height)
rects = squarify.squarify(normalized_values, pos.x0, pos.y0, pos.width, pos.height)
axes = [fig.add_axes([rect['x'], rect['y'], rect['dx'], rect['dy'], ]) for rect in rects]
for ax, val in zip(axes, values):
x = np.linspace(0, 10, 100)
y = np.random.normal(0.01, 0.1, 100).cumsum()
ax.plot(x, y)
ax.set_xticks([])
ax.set_yticks([])
ax.set_facecolor(cmap(norm(val)))
mainax.set_facecolor('none') # prevent that the mainax blocks the other axes
mainax.set_zorder(20) # high z-order because the annotations are drawn using this ax
labels = ['a', 'b', 'c', 'd', 'e', 'f']
sum_val = sum(values)
annotations = [mainax.annotate(f"'{lbl}': {val}\n{val * 100.0 / sum_val:.1f} %",
xy=(0, 0), xycoords='figure pixels',
xytext=(0, 0), textcoords='offset points',
bbox=dict(boxstyle='round', fc='lemonchiffon'),
ha='center', va='bottom')
for ax, val, lbl in zip(axes, values, labels)]
for annot in annotations:
annot.set_visible(False)
def hover(event):
for ax, annot in zip(axes, annotations):
if ax.bbox.contains(event.x, event.y):
annot.xy = (event.x, event.y)
annot.set_visible(True)
else:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
Yes it is possible. You will have to write the code to extract the exact positions where you want to place the new plot in.
You need to set the position of the new figure using f.canvas.manager.window.SetPosition
This answer will greatly help https://stackoverflow.com/a/37999370/4551984
Related
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
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()
I created a scatter plot using matplotlib but I am somehow unable to get the labels to center into the boxes within the colorbar..
This is the code I have so far:
cMap = ListedColormap(['Orange', 'Purple', 'Blue','Red','Green'])
fig, ax = plt.subplots()
plt.figure(figsize=(12,12),dpi = 80)
#data
dist = np.random.rand(1900,1900)
#legend
cbar = plt.colorbar(scatter)
cbar.ax.get_yaxis().set_ticks([])
for j, lab in enumerate(['$Training$','$None$','$GS$','$ML$','$Both$']):
cbar.ax.text( .5, j - .985, lab, ha='left', va='center', rotation = 270)
cbar.ax.get_yaxis().labelpad = 15
cbar.ax.set_ylabel('Outliers', rotation=270)
indices = np.where(outlier_label != -2)[0]
plt.scatter(dist[indices, 0], dist[indices, 1], c=outlier_label[indices], cmap=cMap, s=20)
plt.gca().set_aspect('equal', 'datalim')
plt.title('Projection of the data', fontsize=24)
Thanks!
In line cbar.ax.text( .5, j - .985, lab, ha='left', va='center', rotation = 270) you have to work and change with '.985' with try and error to get better results.
You can extract the y limits of the colorbar to know its top and bottom. Dividing that area into 11 equally spaced positions, will have the 5 centers at the odd positions of that list. Similarly, you can extract the x limits to find the horizontal center.
Some remarks:
If you already called plt.subplots(), then plt.figure() will create a new figure, leaving the first plot empty. You can set the figsize directly via plt.subplots(figsize=...)
You are mixing matplotlib's "object-oriented interface" with the pyplot interface. This can lead to a lot of confusion. It is best to stick to one or the other. (The object-oriented interface is preferred, especially when you are creating non-trivial plots.)
You set dist = np.random.rand(1900,1900) of dimensions 1900x1900 while you are only using dimensions 1900x2.
The code nor the text give an indication of the values inside outlier_label. The code below assumes they are 5 equally-spaced numbers, and that both the lowest and the highest value are present in the data.
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np
colors = ['Orange', 'Purple', 'Blue', 'Red', 'Green']
cmap = ListedColormap(colors)
fig, ax = plt.subplots(figsize=(12, 12), dpi=80)
# data
dist = np.random.randn(1900, 2).cumsum(axis=0)
outlier_label = np.repeat(np.arange(5), 1900 // 5)
indices = outlier_label != -2
scatter = ax.scatter(dist[indices, 0], dist[indices, 1], c=outlier_label[indices], cmap=cmap, s=20)
# legend
cbar = plt.colorbar(scatter, ax=ax)
cbar.ax.get_yaxis().set_ticks([])
cb_xmin, cb_xmax = cbar.ax.get_xlim()
cb_ymin, cb_ymax = cbar.ax.get_ylim()
num_colors = len(colors)
for j, lab in zip(np.linspace(cb_ymin, cb_ymax, 2 * num_colors + 1)[1::2],
['$Training$', '$None$', '$GS$', '$ML$', '$Both$']):
cbar.ax.text((cb_xmin + cb_xmax) / 2, j, lab, ha='center', va='center', rotation=270, color='white', fontsize=16)
cbar.ax.get_yaxis().labelpad = 25
cbar.ax.set_ylabel('Outliers', rotation=270, fontsize=18)
ax.set_aspect('equal', 'datalim')
ax.set_title('Projection of the data', fontsize=24)
plt.show()
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'])
Is there a programmatic way to force the appearance of a single tickline at the additional tick location shown below?
Requirements:
Tickline should be pointing down from x-axis
Tickline should extend to label 103 regardless of padding
Tickline should be the same color and thickness of axes
No changes to other ticks (ticklines or tick labels)
Code and sample image:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig, ax = plt.subplots(figsize=(6, 8 / 3))
fig.tight_layout()
x_limit = 103
x = [0, 10, 50, 100, x_limit]
y = [6, 2, 5, 2, 20]
ax.plot(x, y)
# Add a tick which represents the maximum x-value
xticks = ax.xaxis.get_majorticklocs()
ax.xaxis.set_ticks(np.append(xticks, x_limit))
# Change padding of tick in the event other ticks get too close
tick = ax.get_xaxis().get_major_ticks()[-1]
tick.set_pad(14)
tick.label1 = tick._get_text1()
# Set tight axes bounds around data
ax.set_ylim(0, max(y) + 1)
ax.set_xlim(0, x_limit)
EDIT: Tried tcaswell's solution and ended up with an annotation in the right place. I do notice some aliasing as if it doesn't look like an extension of the y-axis. Any ideas on how to clean this up?
You can do this with annotate and a bit of transform magic.
import matplotlib.transforms as mtransforms
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(9, 3), tight_layout=True)
ax.set_xlim(90, 105)
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
tt = ax.annotate('103', (103, 0), xytext=(0, -12), transform=trans,
arrowprops={'arrowstyle': '-'}, ha='center', va='top',
textcoords='offset points')