How can I programatically generate venn diagram images with labels on top of the image?

左心房为你撑大大i 提交于 2020-01-14 13:49:24

问题


I'm trying to generate Venn diagrams for a pdf report, with text on top of the distinct regions.

We're using htmldoc to generate pdfs, which precludes text on top of background images.

We use the google charts api for other images, but their Venn diagrams don't support text on top of the diagram (from what I can tell).

The easiest path would be some way to generate an image of the venn on our server using a 3rd party library, and then link the image into the document, I just don't know any software packages that would support our use case.

Any links/pointers would be appreciated.


回答1:


Here's some example code. This seems like a decent tutorial:

http://paulbourke.net/dataformats/postscript/

If you're on Linux, you can use the gv command to view it. There are various utilities to convert it to PDF too; ps2pdf on Linux, and I think Acrobat Distiller on Windows.

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 144 144

% CenterText - paint text centered on x with baseline on y
% x y s CenterText
/CenterText
{
   << >> begin
   /s exch def /y exch def /x exch def
   newpath x s stringwidth pop 2 div sub y moveto s show
   end
} bind def

2 setlinewidth
54 72 36 0 360 arc stroke
90 72 36 0 360 arc stroke

/Helvetica 10 selectfont
36 72 (A) CenterText
108 72 (B) CenterText
72 72 (A^B) CenterText

Here's the three-circle one. It works but I don't vouch for the quality of the coding, I haven't done any serious PS code in years.

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 216 216

% CenterText - paint text centered on x with baseline on y
% x y s CenterText
/CenterText
{
   << >> begin
   /s exch def /y exch def /x exch def
   newpath x s stringwidth pop 2 div sub y moveto s show
   end
} bind def

% Set center of bounding box at 0,0 and rotate 90 degrees cw
108 108 translate
gsave
180 rotate

% Draw 3 circles at 120-degree intervals
/ct 3 def
/offset 36 def
/radius 60 def
0 1 ct 1 sub   % for
{
    gsave
    360 mul ct div rotate
    0 offset translate
    0 0 radius 0 360 arc stroke
    grestore
} for

grestore

/Helvetica 10 selectfont
-54 36 (A) CenterText
54 36 (B) CenterText
0 -72 (C) CenterText

0 36 (A^B) CenterText
-36 -24 (A^C) CenterText
36 -24 (B^C) CenterText

0 -6 (A^B^C) CenterText



回答2:


Here's a two-cell diagram in pic. I found ellipses easier to squeeze the text into than circles.

.PS
ellipse
"A" at 1st ellipse - (.2, 0)
ellipse with .w at 1st ellipse.e - (.4, 0)
"B" at 2nd ellipse + (.2, 0)
"A^B" at 1st ellipse.e - (.2, 0)
.PE

And a three-cell diagram:

.PS
ellipsewid = 1
ellipseht = .75
ellipse
ellipse at 1st ellipse + (.5, 0)
ellipse at 1st ellipse + (.25, .35)
"A" at 1st ellipse - (.2, .1)
"B" at 2nd ellipse + (.2, -.1)
"C" at 3rd ellipse + (0, .1)
"A^B" at 3rd ellipse - (0, .5)
"A^C" at 3rd ellipse - (.3, .1)
"B^C" at 3rd ellipse + (.3, -.1)
"A^B^C" at 3rd ellipse - (0, .25)
.PE

Convert to ps: groff -p ven.pic > ven.ps.

I haven't found a nifty way to produce the .eps, yet. Stay tuned! Edit: sudo apt-get install ps2eps!

Edit:

It's much easier to construct everything relative to the compass-points on a central invisible box.

Two-cell:

.PS
box invis "A^B"
ellipse wid 1st box.wid*1.5 at 1st box.w + (.1, 0)
ellipse wid 1st box.wid*1.5 at 1st box.e - (.1, 0)
"A  " at 2nd ellipse.w rjust
"  B" at 1st ellipse.e ljust
.PE

Three-cell:

.PS
box invis "A^B^C" below wid .5 ht .3
ellipse at 1st box.sw
ellipse at 1st box.se
ellipse at 1st box.n
"A  " at 2st ellipse.w rjust below
"  B" at 1nd ellipse.e ljust below
"C" "" "" at 3rd ellipse above
"A^B" at 3rd ellipse.s below
"A^C   " at 2nd ellipse.nw rjust
"   B^C" at 1nd ellipse.ne ljust
.PE

Still requires tweaking, though. But there are far fewer numbers! The width and height of the box define an isosceles triangle used for placing the centers of the ellipses.

Edit:

This last idea suggests a method for making a four-cell diagram. I had to shrink the font for the wedges.

.PS
box invis "A^B^C^D" wid .65 ht .5
ellipsewid = 2
ellipseht = 1.25
ellipse at 1st box.ne
ellipse at 1st box.se
ellipse at 1st box.sw
ellipse at 1st box.nw
"A" at 1st box.ne + (.4,  .4)
"B" at 1st box.se + (.4, -.4)
"C" at 1st box.sw - (.4,  .4)
"D" at 1st box.nw - (.4, -.4)
"A^B" at 1st box.e + (.4, 0) ljust
"B^C" at 1st box.s - (0, .2) below
"C^D" at 1st box.w - (.4, 0) rjust
"A^D" at 1st box.n + (0, .2) above
"\s-1A^B^D\s+1" at 1st box.ne + (.15,  .03)
"\s-1A^B^C\s+1" at 1st box.se + (.15, -.03)
"\s-1B^C^D\s+1" at 1st box.sw - (.15,  .03)
"\s-1A^C^D\s+1" at 1st box.nw - (.15, -.03)
.PE

Here's a jpg of the output. I might have lost some resolution when cropping to the box.




回答3:


Having gone as far as is practical with pic, postscript really is the natural choice for this.

Alright, I haven't solved the labelling yet, but here's the generalized diagram. Turns out you just place the centers on the vertices of the regular polygon for that n.

But some of those spaces get reeeally small. So I'm thinking about some pattern of labelled arcs, spiralling out. Perhaps the radius of the label should reflect the depth of the designated partition...

Edit: I've redesigned the code, so there's a pretty 15-diagram page in revision 1.

Edit: I just got schooled by Wikipedia. It turns out that what I've been calling a 4-cell Venn diagram is not, in fact, a Venn diagram at all.

It's an Euler diagram. The problem is that nowhere can you get the intersection of two regions alone from opposite sides of the diagram. The real 4-cell diagram gets weird no matter how you do it. So the scope of the answer is reduced from what I've pursued in the last two edits.

For the 2-circle diagram, the best placement I can find is defined by the intersection of the radii from the diagram center through the circle centers to the edges, with defining circles placed on the circle centers.

For the 3-circle diagram, the best placement I can find is defined by the intersections of the radii (and rotated radii) with rotated triangle approximations to the circles and unrotated triangles, respectively.

A version of the code can be found in the previous revision of this answer. I posted an expanded version to usenet in the thread geodesic flowers. But since it's overkill for this answer (and still doesn't actually draw any labels or return their locations), and underkill for real generalized Venn diagrams, I'll need to trim most of the baggage before subjecting this question to any more long blocks of code.

Edit: I think I've got this just about licked. This program contains only those parts of the previous program necessary to produce 2- and 3- Venn diagrams with little circles at the "ideal" label locations. For the 2-cell diagram the solution really is trivial (double the defining radius). For the 3-cell diagram the solution is cos(60) * circle-radius + defining radius, either multiplying first or adding first.

Edit: At long last, labels. There was some last-minute trickiness required since I used matrix rotations to find the points. That meant that when I tried printing labels, they were all at strange orientations. So the "centershow" procedure has a little more to it that usual. It has to reset the scaling portions of the current transformation matrix while leaving the translation components alone. That means somewhere earlier in the execution we need to stash an oriented matrix at the correct scale.

(Edit: Another way to get the text upright without modifying a matrix would be to transform the location to device coordinates, install the oriented matrix (at any scale or translation!), itransform the point back to the "new" user coordinates, and then moveto.)

%!

%cp:xy  rad  circ  -
/circ {
    currentpoint newpath
        2 copy 5 -1 roll 0 360 arc stroke
    moveto
} def

%rad n  poly  [pointlist]
/poly {
    1 dict begin exch /prad exch def
    [ exch
        0 exch 360 exch div 359.9 {
            [ exch
                dup cos prad mul exch
                sin prad mul
            ]
        } for
    ]
    end
} def

%[list] rad  subcirc  -
/subcirc {
    1 dict begin /crad exch def gsave
        currentpoint translate
        { aload pop moveto crad circ } forall
    grestore end
} def

%[list]  locate  -
%draw little circles around each point
/locate {
    gsave
        currentpoint translate
        0 0 moveto 5 circ
        { aload pop moveto 5 circ } forall
    grestore
} def

%cp:xy  (string)  cshow  -
/cshow {
    gsave
        currentpoint translate %0 0 moveto
        matrix currentmatrix
        dup 0 normal 0 4 getinterval %reset rotation, keep translation
        putinterval setmatrix
        dup true charpath flattenpath pathbbox
        3 -1 roll sub 3 1 roll sub
        2 div exch -2 div moveto show
    grestore
} def

%[list] [labels]  label  -
%print label text centered on each point
/label {
    gsave
        currentpoint translate
        0 1 3 index length 1 sub {
            2 index 1 index get aload pop moveto
            2 copy get cshow pop
        } for
        pop pop
    grestore
} def

%[x0 y0] [x1 y1]  pyth-dist  radius
/pyth-dist {
    aload pop 3 -1 roll aload pop % x1 y1 x0 y0
    exch % x1 y1 y0 x0
    3 1 roll sub dup mul % x1 x0 dy^2
    3 1 roll sub dup mul % dy^2 dx^2
    add sqrt
} def

/rotw { 180 n div rotate } def

%cp:xy rad n  venn  -
%make the circles intersect the opposite point of def poly
/venn {
    3 dict begin /n exch def /vrad exch def
        vrad n poly
        dup 0 get exch
        dup length 2 idiv get
        pyth-dist /crad exch def
        %vrad crad n ven
        vrad n poly crad subcirc  %the Venn circles
        [[0 0]] [(All)] label
        n 2 eq {
            %vrad 2 mul n poly locate
            vrad 2 mul n poly
            [(A) (B)] label
        }{
            n 3 eq {
                %vrad crad 60 cos mul add n poly locate
                vrad crad 60 cos mul add n poly
                [ (A) (B) (C) ] label
                %gsave rotw vrad crad add 60 cos mul n poly locate grestore
                gsave rotw vrad crad add 60 cos mul n poly
                [ (A^B) (B^C) (A^C) ] label
                grestore
            } if
        } ifelse
    end
} def

/normal matrix currentmatrix def
/in{72 mul}def
/Palatino-Roman 20 selectfont
4.25 in 8.25 in moveto
1 in 2 venn
4.25 in 3.5 in moveto
1 in 3 venn
showpage

And ghostscript produces (gs -sDEVICE=jpeggray -sOutputFile=venlabel.jpg v4.ps):




回答4:


Why not just use LaTeX?

Much simpler then manually writing up ps:

\tikz \fill[even odd rule] (0,0) circle (1) (1,0) circle (1);



来源:https://stackoverflow.com/questions/7478666/how-can-i-programatically-generate-venn-diagram-images-with-labels-on-top-of-the

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!