pythonmatplotlibmatplotlib-venn

Plot Venn diagram from a boolean set notation in Python


The following code plots a Venn diagram by interpreting the set input in boolean form and shading the respective region of interest:

from matplotlib import pyplot as plt
from matplotlib_venn import venn3, venn3_circles

def list_regions(formula):
    from itertools import product
    for x in product([False, True], repeat=3):
        if eval(formula, {}, dict(zip('ABC', x))):
            yield ''.join(str(int(x_i)) for x_i in x)

def plot_diagram(formula):
    plt.figure(figsize=(6,6))
    v = venn3(subsets=[1]*7,
              set_colors=['white']*3,
              subset_label_formatter=lambda x: '')
    c = venn3_circles(subsets=[1]*7, linestyle='solid')
    for region in list_regions(formula):
        v.get_patch_by_id(region).set_color('black')
    plt.show()

This code works fine for most of the set formulas:

plot_diagram('A')
plot_diagram('B')
plot_diagram('C')
plot_diagram('A or B')
plot_diagram('A and B and C')
plot_diagram('(A or B) and C')

All of the above diagrams are plotted properly. Moreover, the code also works for complements:

plot_diagram('A and (not B)')
plot_diagram('(A or B) and (not C)')
plot_diagram('(A and B) and (not C)')

However, I encountered some issues. The code does not work for all complements:

plot_diagram('not B')
plot_diagram('A or (not B)')

Trying to plot the complement of a single set, or the union of one set with the complement of another results in the same error for both:

...
/matplotlib_venn/_common.py", line 31, in get_patch_by_id
    return self.patches[self.id2idx[id]]
KeyError: '000'

Googling the exception did not show any relevant results, hence:

Does anyone know if there is another (more correct) way to yield the corresponding id instead of using the product class from the itertools module in order to shade the corresponding region of interest?

The code generally works but is flawed when it comes to the complements mentioned before.

Thanks in advance for any help on that matter.


Solution

  • The key 000 actually corresponds to the universal sample space, therefore:

    from matplotlib import pyplot as plt
    from matplotlib_venn import venn3, venn3_circles
    
    def list_regions(formula):
        from itertools import product    
        for x in product([False, True], repeat=3):
            if eval(formula, {}, dict(zip('ABC', x))):
                yield ''.join(str(int(x_i)) for x_i in x)
    
    def plot_diagram(formula):
        f = plt.figure(figsize=(6,6))
        v = venn3(alpha=1.0,
                  subsets=[1]*7,
                  set_colors=['white']*3,
                  subset_label_formatter=lambda x: '')
        c = venn3_circles(subsets=[1]*7, linestyle='solid')
        for region in list_regions(formula):
          if region == '000':
            f.gca().set_facecolor('grey')
          else:
            v.get_patch_by_id(region).set_color('grey')
        plt.annotate('U', (-0.657, 0.557)); plt.axis('on')
        plt.show()