Saturday, August 30, 2008

HTML Grapher in Bash- A bash (awk et al) script for graphing two columns to HTML/CSS


#!/bin/sh
#
# * Copyright (c) 2008
# * Adam Keck. All rights reserved.
# *
# * Redistribution and use in source and binary forms, with or without
# * modification, are permitted provided that the following conditions
# * are met:
# * 1. Redistributions of source code must retain the above copyright
# * notice, this list of conditions and the following disclaimer.
# * 2. Redistributions in binary form must reproduce the above copyright
# * notice, this list of conditions and the following disclaimer in the
# * documentation and/or other materials provided with the distribution.
# * 3. The name Adam Keck may not be used to endorse or promote products derived from this software
# * without specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# * SUCH DAMAGE.

#Print Usage

usage () {
echo "$0 {-h|-help} {-f |--file=2-column-ACSII-csv-file -o|--outfile=HTML-output-file -c|--colors=comma-separated-list-of-css-colors -bg|--background-color=css-backgroundcolor }"
}

# Make sure file exists and is ASCII

validatefile () {

if [ -z $1 ]
then
usage
return 1
fi

if [ -f $1 ]
then
if [ "`file $1| awk '{print $2}'`" = "ASCII" ]
then
return 0
else
echo "File is not ASCII text." 2>&1
return 1
fi
else
echo "File does not exist." 2>&1
return 1
fi
}

# Get the height of the whole graph by getting the maximum value of column two after stripping out non-numeric characters

graphheight () {
FILE=$1
echo "`awk -F, '{gsub("[^0-9]", "", $2);if($2>a)a=$2}END{print a}' $FILE` * 10 + 30 " | bc
}

# Look up the color from the list of CSS colors supplied on the command line

lookupcolor () {
COLORNUMBER=$1
echo $COLORNUMBER,$2 | awk -F, '{print $($1+1)}'
}


# Process arguments

# If no arguments have been specified on the command line then print the usage

if [ $# = 0 ]
then
usage
exit 1
fi

# Loop through the arguments setting variables used the script.

while [ $# -gt 0 ] ; do

case "$1" in
-f|--file=*)
if [ "$1" = "-f" ] ; then
shift
if validatefile $1
then
INPUTFILE=$1
else
exit 1
fi
else
FILEHOLDER="`echo $1 | sed s,--file=,,`"
if validatefile $1
then
INPUTFILE=$FILEHOLDER
else
exit 1
fi
fi
;;
-o|--outfile=*)
if [ "$1" = "-o" ] ; then
shift
GRAPHFILE=$1
else
GRAPHFILE="`echo $1 | sed s,--outfile=,,`"
fi
;;
-c|--colors=*)
if [ "$1" = "-c" ] ; then
shift
COLORS=$1
else
COLORS="`echo $1 | sed s#--colors=##`"
fi
;;
-bg|--background-color=*)
if [ "$1" = "-bg" ] ; then
shift
BGCOLOR=$1
else
BGCOLOR="`echo $1 | sed s,--background-color,,`"
fi
;;
-h|-help)
usage
exit 0
;;
*)
usage
exit 1
;;
esac

shift

done

# Set some reasonable default colors in case they were not
# specified on the command line.

if [ -z $COLORS ]
then
COLORS=black
fi

if [ -z $BGCOLOR ]
then
BGCOLOR=lightgrey
fi


# Write out the HTML header

echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
<html xmlns=\"http://www.w3.org/1999/xhtml\">
<head> <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" /> <title>`echo $INPUTFILE | awk -F. '{print $1}'`</title>" > $GRAPHFILE


# Generate graph

# First, get the number of columns

WIDTH=`cat $INPUTFILE | wc -l `

# write the global CSS

echo "
<style>
h2 {
line-height: 1.5em;
font-family: sans-serif;
font-weight: normal;
}

#vertgraph {
width: `echo "${WIDTH}*55" | bc`px;
height: `graphheight $INPUTFILE`px;
position: relative;
background: $BGCOLOR;
}
#vertgraph ul {
width: `echo "${WIDTH}*65" | bc`px;
height: 200px;
margin: 0;
padding: 0;
}
#vertgraph ul li {
position: absolute;
width: 45px;
bottom: 20px;
padding: 0 !important;
margin: 0 !important;
text-align: center;
font-weight: normal;
font-size: 9px;
color: white;
line-height: 2.5em;
list-style-type: none;
font-family: sans-serif;
}

" >> $GRAPHFILE

echo "
</style>
" >> $GRAPHFILE

echo "</head> <body>" >> $GRAPHFILE

echo "<h2>`echo $INPUTFILE | awk -F. '{print $1}'`</h2>" >> $GRAPHFILE

# Write the graph div

echo "

<div id=\"vertgraph\">
<ul>
" >> $GRAPHFILE

# Initialize the line counter and the position of the first color in
# color list.

LINENUMBER=0
COLORNUMBER=1

# Loop through file creating list elements

cat $INPUTFILE | while read LINE
do

# Remove non-numeric characters from the column two value of each line.
# If no characters remain, set the raw height to .1 so that in the
# creation of the list element for the column, the column will get 1 pixel
# for height

HEIGHTRAW=`echo $LINE | awk -F, '{gsub("[^0-9]", "", $2);print $2}'`
if [ -z $HEIGHTRAW ]
then
HEIGHTRAW=".1"
fi

# Check the color number, i.e. the position in the color list
# from the command line. If it's more than the number of colors
# in the list, reset it to one.

if [ $COLORNUMBER -gt `echo $COLORS | awk -F, '{print NF}'` ]
then
COLORNUMBER=1
fi

# Get the color from the list of colors corresponding to the position in
# the list specified by the value of color number.

COLOR=`lookupcolor $COLORNUMBER $COLORS`

# Write the list element corresponding to the line we are processing in the
# input file. The height of the column will be the second column times 10
# pixels. Each column is placed to right of the last one by 55 pixels.
# The header for the column is the value of the first column.
# The column one value is stripped of all non-alpha numberic characters.

echo "<li style=\"height: `echo "$HEIGHTRAW * 10" | bc`px ; left: `echo "(${LINENUMBER}*50) + 5" | bc`px; background-color: $COLOR;\" >`echo $LINE|awk -F, '{print $1}'|sed 's/\W//g'`</li> " >> $GRAPHFILE

# Increment the counters

LINENUMBER=`expr ${LINENUMBER} + 1`
COLORNUMBER=`expr ${COLORNUMBER} + 1`

done

# Close out the list,div, and HTML code

echo "
</ul>
</div>
" >> $GRAPHFILE

echo "</body> </html>" >> $GRAPHFILE



====================================================

That is, for

A,4
B,5
C,3
D,5

generate a bar graph using only CSS and HTML.

Javascript would have been more useful, but this is a bash blog. If you need javascript, this is "BSD" licensed, so have at it :-).



Cheers,

-Adam