Rachejen Rachejen - 14 days ago 5
Python Question

Matplotlib_Venn, Multiple Venn diagrams same scale

I currently have a figure with 5 venn diagram subplots. All 5 diagrams are 2 circle venns and each have a different sum of elements.Refer to this figure.

I would like to know if it is possible to have these 6 subplots all to the same scale? For example, my entire first venn would be scaled to be smaller than the fifth one.

Code below.
Any help most appreciated.

from matplotlib_venn import venn2
from matplotlib import pyplot as plt
import numpy as np

figure, axes = plt.subplots(2, 3, figsize=(11.69,8.27))


BSL_20=(313,10,76)
BSL_40=(384,17,150)
BSL_100=(665,8,378)
BSL_100CC=(860,23,879)
BSL_200=(585,17,758)

v1=venn2(BSL_20, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[0][0])
v2=venn2(BSL_40, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[0][1])
v3=venn2(BSL_100, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[0][2])
v4=venn2(BSL_100CC, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[1][0])
v5=venn2(BSL_200, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[1][1])
v6=venn2(BSL_200, set_labels = ('150mm at 50%', '400mm at 25%'), ax=axes[1][1])
axes[1,2].axis('off')

plt.show()

KT. KT.
Answer

Try adding this to your code before plt.show():

from matplotlib.cbook import flatten

data = [BSL_20, BSL_40, BSL_100, BSL_100CC, BSL_200]
max_area = max(map(sum, data))

def set_venn_scale(ax, true_area, reference_area=max_area):
    s = np.sqrt(float(reference_area)/true_area)
    ax.set_xlim(-s, s)
    ax.set_ylim(-s, s)

for a, d in zip(flatten(axes), data):
    set_venn_scale(a, sum(d))

Explanation:

  • The patches that matplotlib_venn draws are scaled so that their total area is 1. The diagram is positioned with its center near the point (0, 0) and axis limits are configured so that the diagram would fit tightly inside.
  • This means that if you would simply set xlim(-1, 1) and ylim(-1, 1) for all the subplots, you would get diagrams that all have the same total area (assuming all subplots are shown at the same scale).
  • If you now need to "scale down" one of the diagrams in order to reduce its area by 2, one way to achieve it is to simply scale up all axis limits by sqrt(2).
  • This is what the code above does: it first identifies the circle-set with the largest actual total area, and then simply scales up the limits of other diagrams proportionally to sqrt(max_area/required_area).

You could also cut some slack on the Y axis and pack the code and the diagram tighter as follows:

from matplotlib_venn import venn2
from matplotlib.cbook import flatten
from matplotlib import pyplot as plt
import numpy as np

figure, axes = plt.subplots(2, 3, figsize=(11.69,5.5))

BSL_20=(313,10,76)
BSL_40=(384,17,150)
BSL_100=(665,8,378)
BSL_100CC=(860,23,879)
BSL_200=(585,17,758)

data = [BSL_20, BSL_40, BSL_100, BSL_100CC, BSL_200]
max_area = max(map(sum, data))

def set_venn_scale(vd, ax, true_area, reference_area=max_area):
    sx = np.sqrt(float(reference_area)/true_area)
    sy = max(vd.radii)*1.3
    ax.set_xlim(-sx, sx)
    ax.set_ylim(-sy, sy)

for a, d in zip(flatten(axes), data):
    vd = venn2(d, set_labels = ('150mm at 50%', '400mm at 25%'), ax=a)
    set_venn_scale(vd, a, sum(d))

axes[1,2].axis('off')
figure.tight_layout(pad=0.1)
plt.show()

Note, though, that tight_layout will start rescaling your subplots if it feels there is not enough space, so check the results (e.g by adding axes around your subplots via ax.set_axis_on() and making sure all the subplots have the same width).