drkyeh
drkyeh

Reputation: 15

How to add a subplot to each rectangle in a Tree Map?

I created this tree map using Matplotlib and Squarify: enter image description here

Now I would like to add a line plot on each rectangle in the tree map. Is that possible?

Upvotes: 1

Views: 1386

Answers (2)

JohanC
JohanC

Reputation: 80349

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()

resulting plot

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()

plot with colorbar

Upvotes: 2

quest
quest

Reputation: 3926

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

Upvotes: 1

Related Questions