1

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?

JohanC
  • 71,591
  • 8
  • 33
  • 66
drkyeh
  • 15
  • 1
  • 6

2 Answers2

2

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

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Is there something I misunderstood from the question? – JohanC Apr 03 '20 at 08:15
  • Thank you that was very helpful! I would now like to add hover text to each rectangle. The solutions I found, including the one you offered me in another post would only display text when I'm hovering over a point of the graph. Is there any way to set the hover area to the entire rectangle? – drkyeh Apr 03 '20 at 09:46
1

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

quest
  • 3,576
  • 2
  • 16
  • 26