How can I programatically generate venn diagram images with labels on top of the image?
I'm trying to generate Venn diagrams for a pdf report, with text on top of the distinct regions.
开发者_如何学CWe'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.
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
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.
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
):
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);
精彩评论