Friday, October 16, 2009

HTML grapher 2: Inline HTML grapher written in bash

New in this version:

o Cleaned up the code a bit. It's still a bit of a mess, though...
o Added scaling to graphs
o Only outputs the graph div, so it can be called by something else making the rest of the webpage.

Note: the code to html converter zaps the indenting. You may want to pass it through a pretty printer.


#!/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.

INPUTFILE="undef"

SCALE="scale=1"

#Function to print Usage

usage () {
echo "$0
{-h|-help}
{
-f |--file=2-column-ACSII-csv-file
-h |--height=height-in-pixels
-w |--width=width-in-pixels
-c |--colors=comma-separated-list-of-css-colors
-bg|--background-color=css-backgroundcolor
-t |--title=title
}
"
}
# 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 maximum of column two.

get_max_column_value () {
FILE="${1}"
awk -F, '{gsub("[^0-9]", "", $2);if($2>a)a=$2}END{print a}' "${FILE}"
}

get_normalized_height () {
height="${1}"
local_minimum_column_value="${2}"
local_maximum_column_value="${3}"

if [ -z "${3}" ]; then
echo "${0}: needs three arguments: column_2_value minium_column_2_value max_column_2_value" 1>&2
fi

echo "${SCALE}; (${height} - ${minimum_column_value})/(${maximum_column_value} - ${minimum_column_value})" | bc -l
}

get_column_height () {
sigma="$1"
maximum_graph_height="$2"
echo "${sigma} * ${maximum_graph_height}" | 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
;;
-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|--height=*)
if [ "${1}" = "-h" ] ; then
shift
MAXIMUM_GRAPH_HEIGHT="${1}"
else
MAXIMUM_GRAPH_HEIGHT="`echo $1 | sed s,--height=,,`"
fi
;;
-w|--width=*)
if [ "${1}" = "-w" ] ; then
shift
COLUMN_WIDTH="${1}"
else
COLUMN_WIDTH=="`echo $1 | sed s,--width=,,`"
fi
;;
-t|--title=*)
if [ "$1" = "-t" ] ; then
shift
TITLE=$1
else
TITLE="`echo $1 | sed s,--title=,,`"
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

#Get graph width

WIDTH="`cat ${INPUTFILE} | wc -l `"

# CSS Style functions

emit_h2_style () {
echo -n "line-height: 1.5em; font-family: sans-serif; font-weight: normal;"
}

emit_graph_div_style () {
echo -n "width: `echo "${SCALE};${WIDTH}*(${COLUMN_WIDTH}+10)" | bc`px; height: `echo "${SCALE};${MAXIMUM_GRAPH_HEIGHT}" + 20 | bc`px; position: relative; background: $BGCOLOR;"
}
emit_graph_div_ul_style () {
echo -n "width: `echo "${SCALE};${WIDTH}*(${COLUMN_WIDTH}+10)" | bc`px; height: `echo "${SCALE};${MAXIMUM_GRAPH_HEIGHT}" + 20 | bc`px; margin: 0; padding: 0;"
}
emit_graph_div_li_style () {
echo -n "position: absolute; width: ${COLUMN_WIDTH}px; 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;"
}

# Functions to print html

emit_title () {
echo -n "<h2 style=\"`emit_h2_style`\" >"${TITLE}"</h2>"
}

emit_open_graph_div () {
echo -n "<div style=\"`emit_graph_div_style`\">"
}

emit_open_graph_ul () {
echo -n "<ul style=\"`emit_graph_div_ul_style`\">"
}

emit_close_graph_ul () {
echo -n "</ul>"
}

emit_close_graph_div () {
echo -n "</div>"
}

emit_graph_li_set () {

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

LINENUMBER="0"
COLORNUMBER="1"

# Get maximum column two value

maximum_column_value="`get_max_column_value ${INPUTFILE}`"

# Set minimum column two value to one

minimum_column_value="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

NORMALIZED_HEIGHT="`get_normalized_height ${HEIGHTRAW} ${minimum_column_value} ${maximum_column_value}`"

HEIGHT="`get_column_height ${NORMALIZED_HEIGHT} ${MAXIMUM_GRAPH_HEIGHT}`"

# 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 -e "<li style=\" `emit_graph_div_li_style` height: "${HEIGHT}"px ; left: `echo "${SCALE}; ${LINENUMBER}*(${COLUMN_WIDTH}+10)" | bc`px; background-color: $COLOR;\" >`echo $LINE|awk -F, '{print $1}'`</li>\n\n"


# Increment the counters

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

done

}


# Write the graph


echo "
`emit_title`
`emit_open_graph_div`

`emit_open_graph_ul`

`emit_graph_li_set`

`emit_close_graph_ul`

`emit_close_graph_div`

"