Skip to content

Commit

Permalink
Version 0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
konstantint committed Oct 12, 2012
1 parent f87c71d commit df3c094
Show file tree
Hide file tree
Showing 10 changed files with 664 additions and 231 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.txt
@@ -0,0 +1,12 @@
Version 0.2
-----------

- Changed parameterization of venn3 and venn3_circles (now expects 7-element vectors as arguments rather than 8-element).
- 2-set venn diagrams (functions venn2 and venn2_circles)
- Added support for non-intersecting sets ("Euler diagrams")
- Minor fixes here and there.

Version 0.1
-----------

- Initial version, three-circle area-weighted venn diagrams.
2 changes: 1 addition & 1 deletion MANIFEST.in
@@ -1 +1 @@
include README.rst
include README.rst CHANGELOG.txt
48 changes: 30 additions & 18 deletions README.rst
Expand Up @@ -3,7 +3,7 @@ Venn diagram plotting routines for Python/Matplotlib

About
-----
This package contains a rountine for plotting area-weighted three-circle venn diagrams.
This package contains routines for plotting area-weighted two- and three-circle venn diagrams.

Copyright 2012, Konstantin Tretyakov.
http://kt.era.ee/
Expand All @@ -17,45 +17,57 @@ Installable as any other Python package, either through :code:`easy_install`, or

Usage
-----
There are two main functions in the package: :code:`venn3_circles` and :code:`venn3`
There are four main functions here: :code:`venn2`, :code:`venn2_circles`, :code:`venn3`, :code:`venn3_circles`.

Both accept as their only required argument an 8-element list of set sizes,
The :code:`venn2` and :code:`venn2_circles` accept as their only required argument a 3-element list of subset sizes:

:code:`sets = (abc, Abc, aBc, ABc, abC, AbC, aBC, ABC)`
subsets = (Ab, aB, AB)

That is, for example, :code:`sets[1]` contains the size of the set (A and not B and not C),
:code:`sets[3]` contains the size of the set (A and B and not C), etc. Note that the value in :code:`sets[0]` is not used.
That is, for example, subsets[0] contains the size of the subset (A and not B), and
subsets[2] contains the size of the set (A and B), etc.

The function :code:`venn3_circles` simply draws three circles such that their intersection areas would correspond
to the desired set intersection sizes. Note that it is not impossible to achieve exact correspondence, but in
Similarly, the functions :code:`venn3` and :code:`venn3_circles` require a 7-element list:

subsets = (Abc, aBc, ABc, abC, AbC, aBC, ABC)

The functions :code:`venn2_circles` and :code:`venn3_circles` simply draw two or three circles respectively,
such that their intersection areas correspond to the desired set intersection sizes.
Note that for a three-circle venn diagram it is not possible to achieve exact correspondence, although in
most cases the picture will still provide a decent indication.

The function :code:`venn3` draws the venn diagram as a collection of 8 separate colored patches with text labels.
The functions :code:`venn2` and :code:`venn3` draw diagram as a collection of separate colored patches with text labels.

The functions :code:`venn2_circles` and :code:`venn3_circles` return the list of Circle patches that may be tuned further
to your liking.

The function :code:`venn3_circles` returns the list of Circle patches that may be tuned further.
The function :code:`venn3` returns an object of class :code:`Venn3`, which also gives access to diagram patches and text elements.
The functions :code:`venn2` and :code:`venn3` return an object of class :code:`Venn2` or :code:`Venn3` respectively,
which give access to constituent patches and text elements.

Basic Example::
from matplotlib.venn improt venn2
venn2(subsets = (3, 2, 1))
For the three-circle case::

from matplotlib.venn import venn3
venn3(sets = (0, 1, 1, 1, 2, 1, 2, 2), set_labels = ('Set1', 'Set2', 'Set3'))
venn3(subsets = (1, 1, 1, 2, 1, 2, 2), set_labels = ('Set1', 'Set2', 'Set3'))
More elaborate example::

from matplotlib import pyplot as plt
import numpy as np
from matplotlib.venn import venn3, venn3_circles
plt.figure(figsize=(4,4))
v = venn3(sets=(0, 1, 1, 1, 1, 1, 1, 1), set_labels = ('A', 'B', 'C'))
v = venn3(subsets=(1, 1, 1, 1, 1, 1, 1), set_labels = ('A', 'B', 'C'))
v.get_patch_by_id('100').set_alpha(1.0)
v.get_patch_by_id('100').set_color('white')
v.get_text_by_id('100').set_text('Unknown')
v.labels[0].set_text('Set "A"')
c = venn3_circles(sets=(0, 1, 1, 1, 1, 1, 1, 1), linestyle='dashed')
v.get_label_by_id('100').set_text('Unknown')
v.get_label_by_id('A').set_text('Set "A"')
c = venn3_circles(subsets=(1, 1, 1, 1, 1, 1, 1), linestyle='dashed')
c[0].set_lw(1.0)
c[0].set_ls('dotted')
plt.title("Sample Venn diagram")
plt.annotate('Unknown set', xy=v.get_text_by_id('100').get_position() - np.array([0, 0.05]), xytext=(-70,-70),
plt.annotate('Unknown set', xy=v.get_label_by_id('100').get_position() - np.array([0, 0.05]), xytext=(-70,-70),
ha='center', textcoords='offset points', bbox=dict(boxstyle='round,pad=0.5', fc='gray', alpha=0.1),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5',color='gray'))

45 changes: 28 additions & 17 deletions matplotlib/venn/__init__.py
Expand Up @@ -6,41 +6,52 @@
Licensed under MIT license.
This package contains a rountine for plotting area-weighted three-circle venn diagrams.
There are two main functions here: venn3_circles and venn3
This package contains routines for plotting area-weighted two- and three-circle venn diagrams.
There are four main functions here: :code:`venn2`, :code:`venn2_circles`, :code:`venn3`, :code:`venn3_circles`.
Both accept as their only required argument an 8-element list of set sizes,
sets = (abc, Abc, aBc, ABc, abC, AbC, aBC, ABC)
The :code:`venn2` and :code:`venn2_circles` accept as their only required argument a 3-element list of subset sizes:
That is, for example, sets[1] contains the size of the set (A and not B and not C),
sets[3] contains the size of the set (A and B and not C), etc. Note that the value in sets[0] is not used.
subsets = (Ab, aB, AB)
The function venn3_circles simply draws three circles such that their intersection areas would correspond
to the desired set intersection sizes. Note that it is not impossible to achieve exact correspondence, but in
That is, for example, subsets[0] contains the size of the subset (A and not B), and
subsets[2] contains the size of the set (A and B), etc.
Similarly, the functions :code:`venn3` and :code:`venn3_circles` require a 7-element list:
subsets = (Abc, aBc, ABc, abC, AbC, aBC, ABC)
The functions :code:`venn2_circles` and :code:`venn3_circles` simply draw two or three circles respectively,
such that their intersection areas correspond to the desired set intersection sizes.
Note that for a three-circle venn diagram it is not possible to achieve exact correspondence, although in
most cases the picture will still provide a decent indication.
The function venn3 draws the venn diagram as a collection of 8 separate colored patches with text labels.
The functions :code:`venn2` and :code:`venn3` draw diagram as a collection of separate colored patches with text labels.
The functions :code:`venn2_circles` and :code:`venn3_circles` return the list of Circle patches that may be tuned further
to your liking.
The functions :code:`venn2` and :code:`venn3` return an object of class :code:`Venn2` or :code:`Venn3` respectively,
which give access to constituent patches and text elements.
The function venn3_circles returns the list of Circle patches that may be tuned further.
The function venn3 returns an object of class Venn3, which also gives access to diagram patches and text elements.
Example::
Example:
from matplotlib import pyplot as plt
import numpy as np
from matplotlib.venn import venn3, venn3_circles
plt.figure(figsize=(4,4))
v = venn3(sets=(0, 1, 1, 1, 1, 1, 1, 1), set_labels = ('A', 'B', 'C'))
v = venn3(subsets=(1, 1, 1, 1, 1, 1, 1), set_labels = ('A', 'B', 'C'))
v.get_patch_by_id('100').set_alpha(1.0)
v.get_patch_by_id('100').set_color('white')
v.get_text_by_id('100').set_text('Unknown')
v.labels[0].set_text('Set "A"')
c = venn3_circles(sets=(0, 1, 1, 1, 1, 1, 1, 1), linestyle='dashed')
v.get_label_by_id('100').set_text('Unknown')
v.get_label_by_id('A').set_text('Set "A"')
c = venn3_circles(subsets=(1, 1, 1, 1, 1, 1, 1), linestyle='dashed')
c[0].set_lw(1.0)
c[0].set_ls('dotted')
plt.title("Sample Venn diagram")
plt.annotate('Unknown set', xy=v.get_text_by_id('100').get_position() - np.array([0, 0.05]), xytext=(-70,-70),
ha='center', textcoords='offset points', bbox=dict(boxstyle='round,pad=0.5', fc='gray', alpha=0.1),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5',color='gray'))
'''
#from _venn2 import venn2, venn2_circles
from _venn3 import venn3, venn3_circles
___all___ = ['venn3', 'venn3_circles']
___all___ = ['venn2', 'venn2_circles', 'venn3', 'venn3_circles']
38 changes: 33 additions & 5 deletions matplotlib/venn/_math.py
Expand Up @@ -68,27 +68,32 @@ def circle_line_intersection(center, r, a, b):
t2 = (-B - np.sqrt(disc))/2.0/A
return np.array([a + t1*s, a+t2*s])

def find_distance_by_area(r, R, a):
def find_distance_by_area(r, R, a, numeric_correction = 0.0001):
'''
Solves circle_intersection_area(r, R, d) == a for d numerically (analytical solution seems to be too ugly to pursue).
Assumes that a < pi * min(r, R)**2, will fail otherwise.
>>> find_distance_by_area(1, 1, 0)
The numeric correction parameter is used whenever the computed distance is exactly (R - r) (i.e. one circle must be inside another).
In this case the result returned is (R-r+correction). This helps later when we position the circles and need to ensure they intersect.
>>> find_distance_by_area(1, 1, 0, 0.0)
2.0
>>> round(find_distance_by_area(1, 1, 3.1415), 4)
>>> round(find_distance_by_area(1, 1, 3.1415, 0.0), 4)
0.0
>>> d = find_distance_by_area(2, 3, 4)
>>> d = find_distance_by_area(2, 3, 4, 0.0)
>>> d
3.37...
>>> round(circle_intersection_area(2, 3, d), 10)
4.0
>>> find_distance_by_area(1, 2, np.pi)
1.0001
'''
if r > R:
r, R = R, r
if np.abs(a) < tol:
return float(r + R)
if np.abs(min([r, R])**2*np.pi - a) < tol:
return np.abs(R - r)
return np.abs(R - r + numeric_correction)
return brentq(lambda x: circle_intersection_area(r, R, x) - a, R-r, R+r)

def circle_circle_intersection(C_a, r_a, C_b, r_b):
Expand Down Expand Up @@ -154,3 +159,26 @@ def vector_angle_in_degrees(v):
-45.0
'''
return np.arctan2(v[1], v[0])*180/np.pi

def normalize_by_center_of_mass(coords, radii):
'''
Given coordinates of circle centers and radii, as two arrays,
returns new coordinates array, computed such that the center of mass of the
three circles is (0, 0).
>>> normalize_by_center_of_mass(np.array([[0.0, 0.0], [2.0, 0.0], [1.0, 3.0]]), np.array([1.0, 1.0, 1.0]))
array([[-1., -1.],
[ 1., -1.],
[ 0., 2.]])
>>> normalize_by_center_of_mass(np.array([[0.0, 0.0], [2.0, 0.0], [1.0, 2.0]]), np.array([1.0, 1.0, np.sqrt(2.0)]))
array([[-1., -1.],
[ 1., -1.],
[ 0., 1.]])
'''
# Now find the center of mass.
radii = radii**2
sum_r = np.sum(radii)
if sum_r < tol:
return coords
else:
return coords - np.dot(radii, coords)/np.sum(radii)

0 comments on commit df3c094

Please sign in to comment.