Gnuplot, conditional vectorstyle gives an error - conditional-statements

I have a comma-separated text file like this:
1386708463,830
1386708473,830
1386708484,830
1386708497,-830
1386708508,-830
1386708518,-840
1386708528,-840
1386708538,-840
1386708686,-950
1386708696,-960
1386708706,-960
1386708716,-940
1386708726,-940
When the value in column 2 is negative the color must be red otherwise green.
I tried with:
plot "< tail -10 meterstanden.txt" using (-100):1:2:0 title "", \
"< tail -11 meterstanden.txt" using (0):1:($2>0 ? $2:0/0):(0) title "Watt/uur " with vectors arrowstyle 2, \
"< tail -11 meterstanden.txt" using (0):1:($2<=0 ? $2:0/0):(0) title "Watt/uur " with vectors arrowstyle 1, \
"< tail -11 meterstanden.txt" using 2:1:2 with labels font "arial, 8" offset 1.5,0.4
But I get this error:
plot "< tail -10 meterstanden.txt" using (-100):1:2:0 title "", "< tail -11 meterstanden.txt" using (0):1:($2>0 ? $2:0/0):(0) title "Watt/uur " with vectors arrowstyle 2, "< tail -11 meterstanden.txt" using (0):1:($2<=0 ? $2:0/0):(0) title "Watt/uur " with vectors arrowstyle 1, "< tail -11 meterstanden.txt" using 2:1:2 with labels font "arial, 8" offset 1.5,0.4
^
"figure02_temp.plt", line 37: warning: Skipping data file with no valid points
I can not figure out what the problem is, what am I doing wrong?
The complete script is:
set output "gnu.png"
set datafile separator ","
set style arrow 1 lw 3 lc rgb "#ff0000"
set style arrow 2 lw 3 lc rgb "#008000"
set style arrow 1 head size screen 0.02,90 # 0.02 is de breedte van het streepje, 90 is een platte streep.
set style arrow 2 head size screen 0.02,90 # 0.02 is de breedte van het streepje, 90 is een platte streep.
set linetype 1 lw 1 pointtype 0 lc rgb"#ff0000"
set bmargin 4 # witruimte onder grafiek
set label font "arial, 8" # grootte font tbv labels in het grafiek
set terminal png notransparent truecolor enhanced
set term png size 500, 450 background rgb "#ffffff"
set ydata time
set timefmt "%s"
set format y "%H:%M:%S" # dit is de opmaak zoals je hem gaat zien
#set key outside bot center
#set key maxrows 1 # aantal regels onder het grafiek (met Watt/uur erin)
set title "Energiestroom" font "arial bold, 14"
set xtics font "arial, 10"
set ytics font "arial, 10"
set ylabel "T i j d - a s" offset 3,1 font "helvetica bold, 14"
set xlabel "W a t t / u u r" offset 0,0.5 font "arial bold, 14"
set grid xtics lc rgb "#dddddd" linewidth 2 lt 1
set grid ytics
set boxwidth 10
set style fill transparent solid 1
unset key
plot "< tail -10 meterstanden.csv" u (-100):1:2:0 title "", \
"< tail -8 meterstanden.csv" u (0):1:($2<0 ?$2:0/0):(0) notitle with vectors arrowstyle 1, \
"< tail -8 meterstanden.csv" u (0):1:($2>0 ?$2:0/0):(0) notitle with vectors arrowstyle 1, \
"< tail -8 meterstanden.csv" u 2:1:2 with labels font "arial, 8" offset 1.5,0.4
The plot is;
http://ccvd.eu/downloads/gnu.png
And after the plot I get the error.
I am VERY glad with your solution. Linux gives me a mail when Gnuplot gives
the trouble. And now thats over, Thanks very much.
And here are the results, a horizontal "histogram" . . . with red and green.
http://ccvd.eu/downloads/gnu1.png

That is a warning, not an error. The reason is, that the last 8 lines of the file (assuming, the data you show is the file meterstanden.csv) all have negative value. Therefore, the third line in your plot, which is supposed to plot the positive arrows, has only 0/0 values. But the plot is correct.
In your case you can use the arrowstyle variable option, which selects the arrow style based on the last column in the using statement. That is easier and you don't get any warnings:
plot "< tail -10 meterstanden.csv" u (-100):1:2:0 title "", \
"< tail -8 meterstanden.csv" u (0):1:2:(0):($2 < 0 ? 1 : 2) notitle with vectors arrowstyle variable,\
"< tail -8 meterstanden.csv" u 2:1:2 with labels font "arial, 8" offset 1.5,0.4

Related

Awk - print the number of row between a selected range from variable in awk and increment his value when variable change after user keypress

This question is more further developed with reference to the following case awk print the number of columns between a selected range from awk
I have the next short script:
#!/bin/bash
#Control del buffer
#awk en stackowerflow
#https://stackoverflow.com/questions/74483916/awk-print-the-number-of-columns-between-a-selected-range-from-awk/74483975?noredirect=1#comment131485506_74483975
if [ $# -eq 1 ]; then
FICH="${1}"
else
FICH="donaciones"
fi
#_INIT valores globales para mostrar debajo en la ultima linea
#alto_INIT necesario para poner texto abajo en la ultima linea
alto_INIT=`tput lines`
alto=`expr $alto_INIT - 2`
largo=`tput cols`
TOTAL_LINEAS_INIT=`cat "$FICH" | wc -l`
TOTAL_LINEAS=$TOTAL_LINEAS_INIT
#Mostramos solo las lineas dependiendo del alto del terminal
#Numero de paginas que hay que mostrar
LINEA=`expr $TOTAL_LINEAS - $alto`
NUM_PAG=`echo "scale=1; $TOTAL_LINEAS / $alto"|bc`
#Si el sresto de dividdir NPAG entre n lineas es +0 sumanos 1 pag. mas
if [ "${NUM_PAG##*.}" -gt 0 ]; then
NUM_PAG=`echo "scale=0; ${NUM_PAG}" + 1 |bc`
NUM_PAG=${NUM_PAG%%.*}
fi
buffer=`awk -v total_lineas="$TOTAL_LINEAS" -v linea="$LINEA" 'NR>=linea&&NR<=total_lineas' "$FICH"`
function Arriba(){
TOTAL_LINEAS=`expr $TOTAL_LINEAS - $alto`
DESDE=`expr $TOTAL_LINEAS - $alto`
HASTA=$TOTAL_LINEAS
if [[ $DESDE -lt 0 ]] ; then
DESDE=0
HASTA=`expr $DESDE + $alto`
fi
buffer=`awk -v desde="$DESDE" -v hasta="$HASTA" 'NR>=desde&&NR<=hasta' "$FICH"`
TOTAL_LINEAS=`expr $HASTA + 1`
}
function Abajo(){
DESDE=$TOTAL_LINEAS
HASTA=`expr $TOTAL_LINEAS + $alto`
if [[ $HASTA -gt $TOTAL_LINEAS_INIT ]] ; then
HASTA=$TOTAL_LINEAS_INIT
fi
buffer=`awk -v desde="$DESDE" -v hasta="$HASTA" 'NR>=desde&&NR<=hasta' "$FICH"`
TOTAL_LINEAS=$HASTA
}
while true; do
clear
printf "$buffer"
tput cup $alto_INIT 0 && printf "Total Lineas: $TOTAL_LINEAS_INIT | Total Pag: $NUM_PAG |Buffer: De $DESDE hasta $HASTA | $TOTAL_LINEAS | (w) Ayuda"
read -rsn1 TECLA
case $TECLA in
h) Arriba ;;
j) Abajo ;;
w) Help ;;
q) printf "\n" && break ;;
esac
done
exit 0
The goal here is show the number of line from a range of lines invoked by awk.
The program calculate the global lines of terminal and made a pagination from a file.Like
less, but i want to show only the portion of the file when the user press a "h" or "j" key. Every time the user press the key "h" the buffer ( portion of file) change and show
the correct part of file in dependence of number of rows. And when the user press the "j" key the buffer return to the previous key.
The program works ok but i want that when awk show the buffer , give me the number of line that correspond to this global line of the file. For this, i have the variable $TOTAL_LINEAS that increment or decrement every instruction buffer change. And this buffer instruction show from this TOTAL_LINEAS until the lines of terminal , and this every time the user press key. In the previous answer i can to add this number of line
but when the user press a key for change the new buffer text allways print the number for this buffer but not for the global buffer that correspond to real line of the file. In other words, allways print the number of line but in this portion of text not for the global text.
I.E: if i have
1 1:20220413:20:Curso Astrología:5:Vicente Ferrer
2 1:10042022:0:Donación instituto Samye:103:Propia
3 14:20220428:0:Candelario Yeshe Nyimpo Inc:9:Dudjom Tersar
4 1:20220512:60:Ayuda por el Hambre y Violencia:6:Vicente Ferrer
Total Lineas: 43 | Total Pag: 2 |Buffer: De 0 hasta 26 | 27
but in the next keypress for go to the next page i need:
5 1:20220413:20:111
6 1:10042022:0:22
7 14:20220428:0:33
8 1:20220512:60:44
Total Lineas: 43 | Total Pag: 2 |Buffer: De 27 hasta 43 | 43
and not:
1 1:20220413:20:111
2 1:10042022:0:22
3 14:20220428:0:33
4 1:20220512:60:44
Total Lineas: 43 | Total Pag: 2 |Buffer: De 27 hasta 43 | 43
Finally was easy:
buffer=`echo "$buffer" |awk -v i=$TOTAL_LINEAS 'NR==i !NF{print;next} NF{print ++i, $0}'`
Thanks all !!

How to increase the size of bins of a cluster histo, with making sure that the cluster columns do not overlap?

This is the code:
############
reset session
$Data <<EOD
subject 2018/19 2021/22
M 31.46 28.96 -2.5
It 34.83 36.61 +1.8
In 33.71 33.33 -0.4
G 63.79 77.46 +13.7
P 92.86 56.52 -36.3
PS 67.86 60.87 -7.0
A 21.05 65.00 +43.9
D 53.33 54.35 +1.0
EOD
set term svg
set output "prova.svg"
set title "{/Arial:Bold=14 MEAN}\n {/Arial:Bold=14 2018/19 vs 2021/2022}"
set yrange [0:100]
set ytics (0, '10' 10, '20' 20, '30' 30,'40' 40, '50' 50,'60' 60, '70' 70,'80' 80, '90' 90,'100' 100)
set grid y
set ylabel "% TOT" font ',14'
set tics scale 0
set tics font ',14'
set style fill pattern 1 border lt -1
set boxwidth 1 absolute
set key noautotitle
set key font ',14'
set xzeroaxis ls 1 lc "black"
PosX(row,col) = 0
#myColors = "red orange yellow green cyan blue violet deeppink"
myColors = "0xff0000 0xffa500 0xffff00 0x80ff00 0x00ffff 0x0000ff 0x7f00ff 0xff1493"
myColor(i) = int(word(myColors,int(i+1)))
stats $Data u 0 nooutput # get number of columns
N = STATS_columns
plot for [col=2:N] $Data u ($0*N+col):col:(myColor($0)) skip 1 w boxes lc rgb var, \
for [col=2:N] '' u ($0*N+N/2.+1):(NaN):xtic(1) w boxes fill pattern col -1 lc "grey" ti columnheader(col),\
col=2 '' u ($0*N+col-3):col:(sprintf("%2.1f%%",$2)) with labels center offset 0, character 0.5 font ',9' notitle,\
col=3 '' u ($0*N+col-3):col:(sprintf("%+-.1f%%",$4)) with labels center offset 0, character 0.5 font ',9' tc lt 7 notitle
#END code
############
I want to try to increase the width of the bins (of the same size without overlap) in each bin cluster and increase the white gap beetwen the clusters to increase the size of the labels on top of the bins
Here is the slightly modified script:
Comments:
you can minimize the gaps between the groups, but if you have to show 16 bars, the space for the labels will be limited. So, in order to make the labels larger I would skip the percent sign in the label. It is already on the y-axis.
you don*t have to set the ytics manually, you can simply set ytics 10 (check help xtics)
column 4 in your data did not have a header. I added a header, but then I have to correct the automatic determination of number of columns via stats, i.e. N=STATS_columns-1 since the last column will not be used for plotting bars.
for the labels you have to skip 1 line because you are not using the header line.
define a function PosX() for the x-position of the bar and the labels. Use the variable Gap to tune the gap (Gap=0 no gap).
use the ternary operator (check help ternary) to define a color which depends on the value. And apply the color via tc rgb var (check help lc variable).
The script certainly can be further optimized.
Script:
### grouped bar chart with adjustable gaps
reset session
$Data <<EOD
subject 2018/19 2021/22 change
M 31.46 28.96 -2.5
It 34.83 36.61 +1.8
In 33.71 33.33 -0.4
G 63.79 77.46 +13.7
P 92.86 56.52 -36.3
PS 67.86 60.87 -7.0
A 21.05 65.00 +43.9
D 53.33 54.35 +1.0
EOD
set title "{/Arial:Bold=14 MEAN}\n {/Arial:Bold=14 2018/19 vs 2021/2022}"
set xrange[1:]
set ylabel "% TOT" font ',14'
set yrange [0:100]
set ytics 10
set grid y
set tics scale 0
set tics font ',14'
set style fill pattern 1 border lt -1
set boxwidth 1 absolute
set key noautotitle font ',14'
#myColors = "red orange yellow green cyan blue violet deeppink"
myColors = "0xff0000 0xffa500 0xffff00 0x80ff00 0x00ffff 0x0000ff 0x7f00ff 0xff1493"
myColor(i) = int(word(myColors,int(i+1)))
myLabelColor(v) = v>0? 0x00aa00 : v<0 ? 0xff0000 : 0x777777 # green,red,grey
stats $Data u 0 nooutput # get number of columns
N = STATS_columns-1
Gap = 0.25
PosX(col) = (column(col))*(N-1+Gap)
set term svg
set output "SO74502026.svg"
plot for [col=2:N] $Data u (PosX(0)+col):col:(myColor($0)) skip 1 w boxes lc rgb var, \
for [col=2:N] '' u (PosX(0)+N/2.+1):(NaN):xtic(1) w boxes fill pattern col-1 lc "grey" ti columnheader(col),\
col=2 '' u (PosX(0)+col):col:(sprintf("%2.1f",$2)) skip 1 w labels center offset 0, 0.5 font ',10',\
col=3 '' u (PosX(0)+col):col:(sprintf("%+-.1f",$4)):(myLabelColor($4)) skip 1 w labels center offset 0, 0.5 font ',10' tc rgb var
set output
### end of script
Result:

Using awk to color the output in bash

I have two files.
First one is csv while other one is plain text file.
I want to print all the lines of file2 which contains column 1 of first file with font color column2 and background color column3.
for example:
f1 contains
Basic Engineering,BLACK,WHITE
Science,RED,BLUE
f2 contains with field width of 20 each:
foo abc Science AA
bar cde Basic Engineering AP
baz efgh Science AB
expected output:
foo abc Science AA (Red font, Blue background)
bar cde Basic Engineering AP (Black font, White background)
baz efgh Science AB (Red font, Blue background)
I have already defined color in a seperate file defineColors.sh as:
BLACK_FONT=`tput setaf 0`
RED_FONT=`tput setaf 1`
WHITE_BACKGROUND=`tput setab 7`
BLUE_BACKGROUND=`tput setab 4`
RESET_ALL=`tput sgr0`
My try :
awk -F, '{sed "/$1/p" f2 | sed -e 's/^\(.*\)$/'"$2_FONT"''"$3_BACKGROUND"'\1/' }' f1
$ cat tst.awk
BEGIN {
split("BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE",tputColors)
for (i in tputColors) {
colorName = tputColors[i]
colorNr = i-1
cmd = "tput setaf " colorNr
fgEscSeq[colorName] = ( (cmd | getline escSeq) > 0 ? escSeq : "<" colorName ">" )
close(cmd)
cmd = "tput setab " colorNr
bgEscSeq[colorName] = ( (cmd | getline escSeq) > 0 ? escSeq : "<" colorName ">" )
close(cmd)
}
cmd = "tput sgr0"
colorOff = ( (cmd | getline escSeq) > 0 ? escSeq : "<sgr0>" )
close(cmd)
FS = ","
}
NR == FNR {
key = $1
fgColor[key] = fgEscSeq[$2]
bgColor[key] = bgEscSeq[$3]
next
}
{
# change this to substr($0,41,20) for your real 20-char fields data
key = substr($0,15,20)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",key)
print bgColor[key] fgColor[key] $0 colorOff
}
Using the pipe to cat -v so you can see color code escape sequences are being output:
$ awk -f tst.awk f1 f2 | cat -v
^[[44m^[[31mfoo abc Science AA^[(B^[[m
^[[47m^[[30mbar cde Basic Engineering AP^[(B^[[m
^[[44m^[[31mbaz efgh Science AB^[(B^[[m
I see you updated your question to say I have already defined color in a seperate file defineColors.sh as: and showed a shell script - just don't use that, it's not needed.

Splitting single page into two pages with ghostscript

I have a pdf with something like presentations slides and multiple slides per page. How can I use ghostscript to split the file so that there is one slide per page?
A long time ago I wrote some code for someone on comp.lang.postscript to do this, again it was for PowerPoint slides. This PostScript code assumes that all the 'subpages' (ie slides) are the same size and location on the PDF page and that all the PDF pages are the same size. Save the following as a file called pdf_slice.ps and follow the usage as described in the comments.
%!PS
% Copyright (C) 2011 Artifex Software, Inc. All rights reserved.
%
% This software is provided AS-IS with no warranty, either express or
% implied.
%
% This software is distributed under license and may not be copied,
% modified or distributed except as expressly authorized under the terms
% of the license contained in the file LICENSE in this distribution.
%
% For more information about licensing, please refer to
% http://www.ghostscript.com/licensing/. For information on
% commercial licensing, go to http://www.artifex.com/licensing/ or
% contact Artifex Software, Inc., 101 Lucas Valley Road #110,
% San Rafael, CA 94903, U.S.A., +1(415)492-9861.
%
% Slice up a PDF file
%
% usage: gs -sFile=____.pdf -dSubPagesX= -dSubPagesY= [-dSubPageOrder=] [-dVerbose=]pdf_slice.ps
%
% SubPageOrder is a bit field;
% Default = 0
% Bit 0 - 0 = top to bottom
% 1 = bottom to top
% Bit 1 - 0 = left to right
% 1 = right to left
% Bit 3 - 0 = increase x then y
% - 1 = increase y then x
%
% 0 - page 1 at top left, increasing left to right, top to bottom
% 1 - page 1 at bottom left increasing left to right, bottom to top
% 2 - page 1 at top right, increasing right to left, top to bottom
% 3 - page 1 at bottom right increasing right to left, bottom to top
% 4 - page 1 at top left, increasing top to bottom, left to right
% 5 - page 1 at bottom left increasing bottom to top, left to right
% 6 - page 1 at top right, increasing top to bottom, right to left
% 7 - page 1 at bottom right increasing bottom to top, right to left
%
% Check the parameters to see they are present and of the correct type
%
/Usage {
( usage: gs -dNODISPLAY -q -sFile=____.pdf \n) =
( -dSubPagesX= -dSubPagesY= [-dSubPageOrder=] pdf_slice.ps \n) =
(Please see comments in pdf_slice.ps for more details) =
flush
quit
} bind def
/Verbose where not {
/Verbose false def
}{
pop /Verbose true def
} ifelse
/File where not {
(\n *** Missing source file. \(use -sFile=____.pdf\)\n) =
Usage
} {
pop
}ifelse
/SubPagesX where not {
(\n *** SubPagesX not integer! \(use -dSubPagesX=\)\n) =
Usage
} {
Verbose { (SubPagesX ) print } if
SubPagesX type
Verbose { dup == } if
/integertype eq not {
(\n *** SubPagesX not integer! \(use -dSubPagesX=\)\n) =
Usage
}
pop
}ifelse
/SubPagesY where not {
(\n *** SubPagesY not integer! \(use -dSubPagesY=\)\n) =
Usage
} {
Verbose { (SubPagesY ) print } if
SubPagesY type
Verbose { dup == } if
/integertype eq not {
(\n *** SubPagesY not integer! \(use -dSubPagesY=\)\n) =
Usage
}
pop
}ifelse
/SubPageOrder where not {
/SubPageOrder 0 def
} {
Verbose { (SubPageOrder ) print } if
SubPageOrder type
Verbose { dup == } if
dup ==
/integertype eq not {
(\n *** SubPageOrder not integer! \(use -dSubPageOrder=\)\n) =
Usage
}
pop
}ifelse
%
% Turns off most messages
%
/QUIET true def % in case they forgot
%() =
%
% Open the PDF file and tell the PDF interpreter to start dealing with it
%
File dup (r) file runpdfbegin pop
/PDFPageCount pdfpagecount def
%
% Set up our bookkeeping
%
% First get the size of the page from page 1 of the PDF file
% We assume that all PDF pages are the same size.
%
1 pdfgetpage currentpagedevice
1 index get_any_box
exch pop dup 2 get exch 3 get
/PDFHeight exch def
/PDFWidth exch def
%
% Now get the page size of the current device. We are assuming that
% this is the size of the individual sub-pages in the original PDF. NB
% This assumes no margins between sub-pages, all sub-pages the same size.
%
currentpagedevice /PageSize get
dup 0 get /SubPageWidth exch def
1 get /SubPageHeight exch def
%
% Calculate the margins. This is the margin between the page border and
% the enclosed group of sub-pages, we assume there are no borders
% between sub pages.
%
/TopMargin PDFHeight SubPageHeight SubPagesY mul sub 2 div def
/LeftMargin PDFWidth SubPageWidth SubPagesX mul sub 2 div def
Verbose {
(PDFHeight = ) print PDFHeight ==
(PDFWidth = ) print PDFWidth ==
(SubPageHeight = ) print SubPageHeight ==
(SubPageWidth = ) print SubPageWidth ==
(TopMargin = ) print TopMargin ==
(LeftMmargin = ) print LeftMargin ==
} if
%
% This rouitne calculates and sets the PageOffset in the page device
% dictionary for each subpage, so that the PDF page is 'moved' in such
% a way that the required sub page is under the 'window' which is the current
% page being imaged.
%
/NextPage {
SubPageOrder 2 mod 0 eq {
/H SubPagesY SubPageY sub SubPageHeight mul TopMargin add def
}{
/H SubPageY 1 sub SubPageHeight mul TopMargin add def
} ifelse
SubPageOrder 2 div floor cvi 2 mod 0 eq {
/W SubPageX 1 sub SubPageWidth mul LeftMargin add def
}{
/W SubPagesX SubPageX sub SubPageWidth mul LeftMargin add def
} ifelse
<< /PageOffset [W neg H neg]>> setpagedevice
Verbose {
(SubPageX ) print SubPageX ==
(SubPageY ) print SubPageY ==
(X Offset ) print W ==
(Y Offset ) print H == flush
} if
PDFPage
} bind def
%
% The main loop
% For every page in the original PDF file
%
1 1 PDFPageCount
{
/PDFPage exch def
% Do the gross ordering here rather than in
% NextPage. We eiither process rows and then
% columns, or columns then rows, depending on
% Bit 3 of SubPageorder
SubPageOrder 3 le {
1 1 SubPagesY {
/SubPageY exch def
1 1 SubPagesX {
/SubPageX exch def
NextPage
pdfgetpage
pdfshowpage
} for
} for
} {
1 1 SubPagesX {
/SubPageX exch def
1 1 SubPagesY {
/SubPageY exch def
NextPage
pdfgetpage
pdfshowpage
} for
} for
} ifelse
} for
The answer of KenS is the one which should be accepted by #howardh. KenS uses a very clever PostScript language program to achieve the result. (Always keep in mind what KenS said: his solution will work well only 'if all the 'subpages' (ie slides) are the same size and location on the PDF page and that all the PDF pages are the same size).
However, for completeness' sake, let me link to a few other previous answers (some of which are illustrated), which solved similar problems:
Convert PDF 2 sides per page to 1 side per page (SuperUser.com)
How can I split a PDF's pages down the middle? (SuperUser.com)
Cropping a PDF using Ghostscript 9.01 (StackOverflow.com)
PDF - Remove White Margins (StackOverflow.com)
Split one PDF page into two (StackOverflow.com)
Freeware to split a pdf's pages down the middle? (SuperUser.com)
These answers also use PostScript code, but only as 'snippets' which are passed to Ghostscript on the commandline. (If you are not PostScript-savvy, these may be more easy to modify and adapt for cases where the 'subpages' are not of the same size and location on PDF pages, and where PDF pages are of different sizes.)
I would like to propose one solution, that actually
1) splits one PS or PDF page to many separate pages and
2) then merges *.pdf to multipage pdf.
But this solution don't process margins.
This script works in Linux BASH:
INPUT="input.ps" ;
RESOLUTION=72 ;
WHOLE_WIDTH=598 ; # current size of portrait A4
WHOLE_HEIGHT=843 ;
COLOUMNS=2 ; # split vertically
ROWS=1 ; # split horizontally
PAGE_WIDTH=$((WHOLE_WIDTH/COLOUMNS)) ;
PAGE_HEIGHT=$((WHOLE_HEIGHT/ROWS)) ;
# Split:
for x in `seq 1 ${COLOUMNS}` ; do
for y in `seq 1 ${ROWS}` ; do
gs -dBATCH -dNOPAUSE -dSAFER \
-o gramps_tmp_${x},${y}.pdf \
-r${RESOLUTION} \
-sDEVICE=pdfwrite \
-g${PAGE_WIDTH}x${PAGE_HEIGHT} \
-c "<</PageOffset [$(((x - 1)*(0 - PAGE_WIDTH))) \
$(((y - 1)*(0 - PAGE_HEIGHT)))]>> setpagedevice" \
-f "$INPUT" ;
done ;
done ;
# Merge:
gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=singleCombinedPdfFile.pdf -dBATCH gramps_tmp_*.pdf ;
But we may arrange pages in desired order:
ORDERED="tmp_1,1.pdf tmp_1,2.pdf" ;
gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=singleCombinedMultipagePdfFile.pdf -dBATCH ${ORDERED};

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.
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.
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);