0

I'm plotting boxplots that are positioned very far apart on the X axis. I'd like to be able to zoom in and out on this graph and have the different boxplots displayed with the same width, independent of the level of zoom.

For comparison, I'd like to achieve something similar with what happens with markers on plot() or scatter() graphs that remain the same size on screen as one zooms in and out of the graph area.

Plotting the boxplots as is displays them as narrow when zoomed out and thicker as you zoom in. Changing the value of the widths parameter when calling boxplot() naturally only changes the width of the boxes, but they still scale with the zoom level.

Here is the code snippet and resulting output.

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(1)

# Generating random data
N = 10
y1 = np.random.randn(N)
y2 = np.random.randn(N)
y3 = np.random.randn(N)

data = [y1,y2,y3]

names = ["Near","Far","Farther"]

x_positions = [1,10,100]

fig,ax = plt.subplots()
ax.boxplot(data,positions =x_positions)
ax.set_xticklabels(names)
plt.xticks(rotation=45, ha='right')

plt.show()

Zoomed out:

enter image description here

Zoomed in:

enter image description here

2 Answers 2

2

Boxplots are drawn very simply by calling ax.plot multiple times so we end up with a dictionary containing a bunch of Line2D instances. We can make a function to update the boxes to any width (note here bp is the return value of boxplot):

def update_boxplot_widths(width):
    for x, b, m in zip(x_positions, bp['boxes'], bp['medians']):
        left = x - width / 2
        right = x + width / 2
        b.set_xdata([left, right, right, left, left])
        m.set_xdata([left, right])

To make the width a function of the axes width we can create a callback function. Here I set the boxplot width to 5% of the x-axis width:

def boxplot_width_callback(ax):
    x_lims = ax.get_xlim()
    new_boxplot_width = (x_lims[1] - x_lims[0]) * 0.05
    update_boxplot_widths(new_boxplot_width)

To have the widths automatically update, we need to add this function to the axes callbacks:

fig, ax = plt.subplots()

bp = ax.boxplot(data, positions=x_positions)
ax.set_xticklabels(names)
plt.xticks(rotation=45, ha='right')

# Apply the desired width for the initial view.
boxplot_width_callback(ax)

# Add to axes callbacks so the width updates when we zoom.
ax.callbacks.connect('xlim_changed', boxplot_width_callback)

plt.show()

Initial view:

enter image description here

Zoom:

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

The cap widths could also be updated within update_boxplot_widths using bp['caps'] but I leave that as an exercise for the reader :)
1

This isn't the cleanest solution one could imagine, but you can set the widths to be in terms of the limits on the x-axis so that they "automatically" undo any scaling. This approach requires that you explicitly set the x-limits before you make the plot (since the width requires the values in advance) and that you not change/update the x-limits after the plot has been made.

ax.set_xlim(0, 101)
ax.boxplot(
    data,
    positions=x_positions,
    widths=0.1 * (ax.get_xlim()[1] - ax.get_xlim()[0]),
)

with xlimits 0 and 101

ax.set_xlim(0, 11)
ax.boxplot(...)

with xlimits 0 and 11

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.