I have created a set of heatmaps in a 6x3 subplot grid.
As you can see, I added a shared color bar in the last row that spans over all columns (I used a grid spec to realize this).
While iterating through the rows and columns, I made sure that the Y tick labels are only rendered for the first column and the X tick labels only for the last row.
I am already pretty happy with the results, except for one thing:
Due to the Y tick labels from subplots of the first column, the color bar also starts where the Y tick labels start.
While this is great for the subplots that contain the actual heatmaps, it is unnecessary for the color bar.
I would like to remove this padding on the left side and if possible center the color bar in the middle of the last row.
I tried various approaches like disabling the ticks for the color bar axis using cax.set_yticks(ticks=[])
or removing the margin with cax.set_xmargin(0)
but nothing worked so far.
fig = plt.figure(figsize=(12, 13), layout="constrained")
def render_heatmaps():
# Create a figure with multiple subplots
nrows = (
len(domain_knowledge["DS1: Automotive"]) + 2
) # usually 4 + 2 for infobox and color bar
ncols = len(domain_knowledge.keys()) # usually 3
gs = fig.add_gridspec(nrows, ncols) # Adjust the width ratios
gs.set_height_ratios([0.7, 1, 1, 1, 1, 0.1])
gs.hspace = 0.07
gs.wspace = 0.02
# Render infobox
ax_text: Axes = fig.add_subplot(gs[0, :])
render_infobox(ax_text)
# Create a colorbar based on the min/max values that are in all datasets (contingency matrices for each study object)
norm = mcolors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
colors = ScalarMappable(norm, cmap="GnBu")
for row in range(nrows - 2):
for col in range(ncols):
axis = fig.add_subplot(gs[row + 1, col])
# Render the combined heatmap
sns.heatmap(
datasets[col * 4 + row],
annot=True,
cmap=colors.cmap,
# only show the y labels on the first column
yticklabels=(categories_necessity[:nec_range] if col == 0 else False),
# only show the x labels on the last row
xticklabels=(
categories_temporality[:temp_range]
if row + 1 == nrows - 2
else False
),
ax=axis,
cbar=False, # uses a combined colorbar
)
axis.tick_params(axis="y", labelrotation=0)
axis.set_title(label=f"S{col*4+row}")
# create a combined colorbar
cax = fig.add_subplot(gs[nrows - 1, :]) # Use all columns for the colorbar
cax.set_xmargin(0)
fig.colorbar(
colors,
cax=cax,
orientation="horizontal",
)
cax.set_title("Number of Study Responses")
I have created a set of heatmaps in a 6x3 subplot grid.
As you can see, I added a shared color bar in the last row that spans over all columns (I used a grid spec to realize this).
While iterating through the rows and columns, I made sure that the Y tick labels are only rendered for the first column and the X tick labels only for the last row.
I am already pretty happy with the results, except for one thing:
Due to the Y tick labels from subplots of the first column, the color bar also starts where the Y tick labels start.
While this is great for the subplots that contain the actual heatmaps, it is unnecessary for the color bar.
I would like to remove this padding on the left side and if possible center the color bar in the middle of the last row.
I tried various approaches like disabling the ticks for the color bar axis using cax.set_yticks(ticks=[])
or removing the margin with cax.set_xmargin(0)
but nothing worked so far.
fig = plt.figure(figsize=(12, 13), layout="constrained")
def render_heatmaps():
# Create a figure with multiple subplots
nrows = (
len(domain_knowledge["DS1: Automotive"]) + 2
) # usually 4 + 2 for infobox and color bar
ncols = len(domain_knowledge.keys()) # usually 3
gs = fig.add_gridspec(nrows, ncols) # Adjust the width ratios
gs.set_height_ratios([0.7, 1, 1, 1, 1, 0.1])
gs.hspace = 0.07
gs.wspace = 0.02
# Render infobox
ax_text: Axes = fig.add_subplot(gs[0, :])
render_infobox(ax_text)
# Create a colorbar based on the min/max values that are in all datasets (contingency matrices for each study object)
norm = mcolors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
colors = ScalarMappable(norm, cmap="GnBu")
for row in range(nrows - 2):
for col in range(ncols):
axis = fig.add_subplot(gs[row + 1, col])
# Render the combined heatmap
sns.heatmap(
datasets[col * 4 + row],
annot=True,
cmap=colors.cmap,
# only show the y labels on the first column
yticklabels=(categories_necessity[:nec_range] if col == 0 else False),
# only show the x labels on the last row
xticklabels=(
categories_temporality[:temp_range]
if row + 1 == nrows - 2
else False
),
ax=axis,
cbar=False, # uses a combined colorbar
)
axis.tick_params(axis="y", labelrotation=0)
axis.set_title(label=f"S{col*4+row}")
# create a combined colorbar
cax = fig.add_subplot(gs[nrows - 1, :]) # Use all columns for the colorbar
cax.set_xmargin(0)
fig.colorbar(
colors,
cax=cax,
orientation="horizontal",
)
cax.set_title("Number of Study Responses")
Share
Improve this question
edited Mar 21 at 9:51
jonrsharpe
122k30 gold badges268 silver badges476 bronze badges
asked Mar 21 at 9:49
Mayor MayerMayor Mayer
3624 silver badges15 bronze badges
1
|
3 Answers
Reset to default 1I think the best way is to either just add an explicit (independent) axes for the colorbar with fig.add_axes(...)
or to explicitly use GridSpec
(and GridSpecFromSubplotSpec
) for this.
This way you can adjust all margins independently:
For example:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec
# create the figure
f = plt.figure()
# setup the base-grid (2 areas, one for the subplots and one for the colorbar)
gs = GridSpec(2, 1, height_ratios=(1, .05), figure=f, hspace=.2)
# setup the sub-grid for the plot axes
ax_gs = GridSpecFromSubplotSpec(4, 4, gs[0,0], hspace=1, wspace=1)
axes = ax_gs.subplots()
# add the colorbar axes
cb_ax = f.add_subplot(gs[1, 0])
Which will create a grid like this:
Almost all of this is available just using Matplotlib as it is meant to be used. https://matplotlib./stable/users/explain/axes/colorbar_placement.html
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(4, 4, layout='constrained')
for ax in axs.flat:
pc = ax.pcolormesh(np.random.rand(10, 10))
ax.label_outer()
fig.colorbar(pc, ax=axs, orientation='horizontal', shrink=0.4)
plt.show()
Based on the approach from @raphael, I adjusted my code that is makes use of a sub-grid.
def render_heatmaps():
# Create a figure with multiple subplots
nrows = len(domain_knowledge["DS1: Automotive"])
ncols = len(domain_knowledge.keys())
gs = fig.add_gridspec(3, 1) # Adjust the width ratios
gs.set_height_ratios([0.7, 4, 0.1])
# setup the sub-grid for the plot axes
heatmaps_gs = GridSpecFromSubplotSpec(
nrows, ncols, gs[1, 0], hspace=0.07, wspace=0.02
)
# Render infobox
ax_text: Axes = fig.add_subplot(gs[0, :])
render_infobox(ax_text)
# Create a colorbar based on the min/max values that are in all datasets (contingency matrices for each study object)
norm = mcolors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
colors = ScalarMappable(norm, cmap="GnBu")
for row in range(nrows):
for col in range(ncols):
ax = fig.add_subplot(heatmaps_gs[row, col])
[... same code...]
# create a combined colorbar
cax = fig.add_subplot(gs[2, :]) # Use all columns for the colorbar
fig.colorbar(colors, cax=cax, orientation="horizontal")
cax.set_title("Number of Study Responses")
Looks pretty much like I wanted it:
But since i've seen the result from @Jody's solution, I wonder if it's possible to add a bit padding around the colorbar subplot/ axis (cax
) as well?
If somebody knows how to do that, would be great if you could comment and I will adjust the final solution.
Edit: I found a way to achieve this by increasing the number of columns from the outer grid to 3 and adjusting the width using gs.set_width_ration(...).
The inner sub-grid spans over all three columns, so it is not affected by the margins (unlike using plt.subplots_adjust(...)
for example).
def render_heatmaps():
# Create a figure with multiple subplots
nrows = len(domain_knowledge["DS1: Automotive"])
ncols = len(domain_knowledge.keys())
gs = fig.add_gridspec(3, 3, hspace=0.1) # Adjust the width ratios
gs.set_height_ratios([0.7, 4, 0.1])
gs.set_width_ratios([0.5, 9, 0.5])
# setup the sub-grid for the plot axes (spans over all columns from the outer grid)
heatmaps_gs = GridSpecFromSubplotSpec(
nrows, ncols, gs[1, :], hspace=0.07, wspace=0.02
)
[...]
# Create a colorbar based on the min/max values that are in all datasets (contingency matrices for each study object)
norm = mcolors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
colors = ScalarMappable(norm, cmap="GnBu")
for row in range(nrows):
for col in range(ncols):
ax = fig.add_subplot(heatmaps_gs[row, col])
[...]
# create a combined colorbar
cax = fig.add_subplot(gs[2, 1]) # Use only the middle column for the colorbar
fig.colorbar(colors, cax=cax, orientation="horizontal")
cax.set_title("Number of Study Responses")
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744362492a4570538.html
ax
for the color bar. – JohanC Commented Mar 21 at 11:21