Generate area-proportional Venn/Euler diagrams in Julia. This is based, in part, on algorithms from Leland Wilkinson.
Wilkinson, Leland. "Exact and approximate area-proportional circular Venn and Euler diagrams." IEEE Transactions on Visualization and Computer Graphics 18, no. 2 (2012): 321-331.
GitHub: HarlanH/VennEuler.jl
See LICENSE.md for the MIT license.
Area-proportional Venn/Euler Diagrams show the overlap between sets in such a way that the size of the shapes is proportional to the size of the sets, and the size of the overlaps on the page is proportional to the size of the overlaps of the sets. In general, using circles, you can only do this perfectly if you have two sets -- there will always be some residual error, where the sizes are not perfectly proportional.
Wilkinson developed a straightforward method of approximately fitting area-proportional diagrams, but the code was written in Java and was difficult to extend. This Julia package re-implements the algorithm, with the following additions:
- You can use other shapes -- currently squares, triangles, and rectangles, in addition to circles.
- You can use 3-parameter (X, Y, Q) shapes, such as axis-parallel rectangles, in addition to 2-parameter (X, Y) shapes, such as squares.
- There is a relatively easy-to-use specification structure that lets you mix and match shapes.
- You can lock any parameters you'd like and prevent them from being improved, which is handy for putting the largest shape in the middle.
- It should be easy/easier for others to collaborate and extend.
You define a structure that specifies the shapes and additional constraints you'd like on the fitting. That gets translated into a "state" vector, which has 2 (or more) values per shape. A set of lower and upper bounds is defined that keeps the shapes all inside the lines. Then the state vector and bounds are optimized, currently with a rather brute-force optimizer. To compute the cost function, the shapes are drawn onto in-memory bitmaps, the number of overlapping pixels is counted, and the distance between the resulting counts and the actual target overlap vector is computed. That cost function is minimized.
The results can be rendered as an SVG file.
(v1.2) pkg> add VennEuler
julia> using VennEuler
julia> data = Bool[
0 1 0
1 1 0
0 0 1
0 1 0
0 1 0
0 1 1
0 0 1
1 0 1
1 1 0
1 0 1
];
julia> eo = make_euler_object(["a","b","c"], data, EulerSpec(:circle), sizesum=0.5)
EulerObject(6, ["a", "b", "c"], [0.2060129077457011, 0.2060129077457011, 0.252313252202016, 0.252313252202016, 0.23032943298089031, 0.23032943298089031], [0.7939870922542989, 0.7939870922542989, 0.747686747797984, 0.747686747797984, 0.7696705670191097, 0.7696705670191097], [0.2060129077457011, 0.252313252202016, 0.23032943298089031], VennEuler.DisjointSet(["a", "b", "c"], [0, 2, 3, 1, 0, 2, 2, 0]), EulerSpec[EulerSpec(:circle, [NaN, NaN], [1, 2]), EulerSpec(:circle, [NaN, NaN], [3, 4]), EulerSpec(:circle, [NaN, NaN], [5, 6])], getfield(VennEuler, Symbol("##6#9")){EulerObject}(EulerObject(#= circular reference @-2 =#)))
julia> loss, state, result = optimize(eo, random_state(eo))
(0.024647421240312717, [0.5307496857847521, 0.3158423607955026, 0.747686747797984, 0.2905651018373946, 0.3429581864192766, 0.33534561404792806], :XTOL_REACHED)
julia> render("three-circles.svg", eo, state)
For more examples see the unit tests.
Pull requests welcome! See the issues list!