Friday, September 11, 2015

Beginning graphing

One of the simplest graphs I have seen can be done in bash. You will need a data file and a one line executable to extract the data from the file and then display it.

data:
Irene     10
Karen     37
Andreas   41
Beatrice  23

Code:
SCALE=1; WIDTHL=10; WIDTHR=60; BAR="12345678"; BAR="${BAR//?/==========}"; while read LEFT RIGHT rest ; do RIGHT=$((RIGHT/SCALE)); printf "%${WIDTHL}s: %-${WIDTHR}s\n" "${LEFT:0:$WIDTHL}" "|${BAR:0:$RIGHT}*"; done < data

Result:
   Irene: |==========*                                              
   Karen: |=====================================*                       Andreas: |=========================================*               
  Beatrice: |=======================*


Another script:


 bar="=================================================="
  barlength=${#bar}
  i=0
  while ((i < 100)); do
    n=$((i*barlength / 100))       # Number of bar segments to draw $((i/2))
    printf "\r[%-${barlength}s]" "${bar:0:n}"
    ((i += RANDOM%5+2))            # i = percentage done
    sleep 1
  done
  echo

The you could get a bit more fancy with a bit of animation.

[code]
 # !/bin/sh
#
# Bargraph_Generator.sh
#
# A DEMO 6 bit coloured bargraph animation for a default Bash and Terminal window on OSX 10.7.5...
# A simple Shell script to display an _AT_A_GLANCE_ real time analogue bargraph generator. It
# starts off with GREEN for OK, then YELLOW for warning and finally ending with RED for danger
# with a critical beep for values 61 to 63 inclusive.
# It assumes an 8 bit value being injected into the script which is then divided by 4 to give
# a 6 bit value which is 64 spaces width inside the Terminal. The DEMO uses a random number
# generator to give a representation of an 8 bit value so you can see it working...
#
# A shell derivative of my Python code:-
# http://code.activestate.com/recipes/577612-seven-bit-colored-analogue-bar-graph-generator-dem/?in=user-4177147
#
# To run, ensure the script is executable and change if required, then type from a Terminal:-
#
# xxxxx$ /full/path/to/Bargrapth_Generator.sh<CR>
#
# And away you go...
#
# Written in such a way that kids and newbies can understand what is going on.
#
# Originally written for a Macbook Pro 13 inch, OSX 10.7.5 using the default Terminal.
# It MIGHT work on some Linux variants but WAS intended for MacOS OSX 10.7.x and above only.
#
# The Terminal colours WILL be changed to Black background and Various foreground colours.
# It will NOT be returned back to its original state although it can be easily. If you
# need to rerturn back to default state then there are a couple of easy methods the
# simplest being type:-
#
# xxxxx$ reset<CR>
#
# And all will be corrected...
#
# Issued entirely as Public Domain and you may do with it as you please
#
# $VER Bargraph_Generator.sh_Version_0.00.10_(C)2012_B.Walker_G0LCU.
#
# Enjoy finding simple solutions to often very difficult problems...

# The required _varibales_ for ease of coding, these are the colours...
# White On Black.
WOB="\x1B[1;37;40m"
# Black On Green.
BOG="\x1B[1;30;42m"
# Black On Yellow.
BOY="\x1B[1;30;43m"
# Black On red.
BOR="\x1B[1;30;41m"
# Green On Black.
GOB="\x1B[1;32;40m"
# Yellow On Black.
YOB="\x1B[1;33;40m"
# Red On Black.
ROB="\x1B[1;31;40m"

# Set the pseudo 6 bit value to zero.
SIX_BIT_DEPTH=0

# Do a clear screen to White On Black.
printf $WOB
clear

while true
do
    # Set up the screen per scan and prepare for the bargraph.
    clear
    printf $WOB"\n \$VER: Bargraph_Generator.sh_Version_0.00.10_(C)2012_B.Walker_G0LCU.\n\n"
    printf " A horizontal, at a glance, coloured, analogue bargraph display for\n"
    printf " a default Terminal inside OSX 10.7.5..\n\n\n\n\n"
    printf "        0         10        20        30        40        50        60"
    printf $GOB"\n        +----+----+----+----+----+----+----+----+----+"$YOB"----+----+"$ROB"----+--\n"
    printf $GOB"       (|                                                              "$ROB")\n"
    printf $GOB"        +----+----+----+----+----+----+----+----+----+"$YOB"----+----+"$ROB"----+--\n\n\n\n"
    # If the 6 bit value is 0, zero, do no more until printing the 6 bit value and generating another 6 bit value...
    # Anything greater than or equal to 1 enters this conditional branch.
    if [ "$SIX_BIT_DEPTH" -ge "1" ]
    then
        # If the 6 bit value is less than or equal to 46 then _plot_ the green section only.
        # The '\x1B[12;8f' is the ANSI 'Esc' code that forces the print position to 12 lines by 8 columns.
        if [ "$SIX_BIT_DEPTH" -le "46" ]
        then
            BARGRAPH=$GOB"\x1B[12;8f("$BOG
            for green in $(seq 1 "$SIX_BIT_DEPTH")
            do
                BARGRAPH=$BARGRAPH" "
            done
        fi
        # If the 6 bit value is greater than or equal to 47 then print the green section and _plot_ the yellow section.
        if [ "$SIX_BIT_DEPTH" -ge "47" ]
        then
            BARGRAPH=$GOB"\x1B[12;8f("$BOG"                                              "$BOY
            for yellow in $(seq 47 "$SIX_BIT_DEPTH")
            do
                BARGRAPH=$BARGRAPH" "
            done
        fi
        # If the 6 bit value is greater than or equal to 57 then print the green and yellow section and _plot_ the red section.
        if [ "$SIX_BIT_DEPTH" -ge "57" ]
        then
            BARGRAPH=$GOB"\x1B[12;8f("$BOG"                                              "$BOY"          "$BOR
            for red in $(seq 57 "$SIX_BIT_DEPTH")
            do
                BARGRAPH=$BARGRAPH" "
            done
        fi
        printf "$BARGRAPH"$GOB"\n\n\n\n\n"
    fi
    # When the 6 bit value is greater than or equal to 61 sound a system error beep.
    if [ "$SIX_BIT_DEPTH" -ge "61" ]
    then
        printf "\a"
    fi
    # Print the 6 bit value in White On Black...
    printf $WOB" Random number generated "$SIX_BIT_DEPTH"...\n\n"
    printf " Press Ctrl-C to stop the program...\n\n"
    # Generate another 6 bit value as though from an 8 bit value...
    SIX_BIT_DEPTH=$[($RANDOM % (256/4))]
    # A practical lower limit for the sleep command is 'sleep 0.05'...
    sleep 1
done

# End of Bargraph_Generator.sh DEMO.
# Enjoy finding simple solutions to often very difficult problems... ;o)

[/code]

One step farther:




 
Or then you could use a built in program such as gnuplot. It is well documented and there are a zillion examples on the web.

Here is a simple data file:

# sample 2-column data file
# -------------------------
1     1
2     4
3     9
4    16
5    25
6    36
7    49
8    64
9    81
10  100

The you want to fire up gnuplot

$ gnuplot

    G N U P L O T
    Version 4.6 patchlevel 0    last modified 2012-03-04
    Build System: Linux i686

    Copyright (C) 1986-1993, 1998, 2004, 2007-2012
    Thomas Williams, Colin Kelley and many others

    gnuplot home:     http://www.gnuplot.info
    faq, bugs, etc:   type "help FAQ"
    immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'unknown'
gnuplot>


Set a simple terminal for now

gnuplot> set term dumb

Now let's use that data file

gnuplot> plot '2col.dat'


Or if you have Imagemagick installed you can get a bit fancier.

gnuplot> set term png
Terminal type set to 'png'
Options are 'nocrop font "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf,12" fontscale 1.0 size 640,480 '

gnuplot>               set output '| display png:-'

Then run your plot again

gnuplot> plot '2col.dat' with linespoints


Try a different data file


1,    1,   2,   5
2,    4,   4,  10
3,    9,   6,  15
4,   16,   8,  20
5,   25,  10,  25
6,   36,  12,  30
7,   49,  14,  35
8,   64,  16,  40
9,   81,  18,  45
10, 100,  20,  50
 
gnuplot> plot '4col.csv' using 1:2 with lines, '4col.csv' using 1:3 with lines
gnuplot> plot '4col.csv' using 1:2 with lines, '4col.csv' using 1:3 with lines, '4col.csv' using 1:4 with lines
 



gnuplot>               set term png
Terminal type set to 'png'
Options are 'nocrop font "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf,12" fontscale 1.0 size 640,480 '
gnuplot>               set output '| display png:-'
gnuplot> set style line 1 lc rgb "red"
gnuplot> set style line 2 lc rgb "blue"
gnuplot>
gnuplot> set style fill solid
gnuplot> set boxwidth 0.5
gnuplot>
gnuplot> plot "data.dat" every ::0::0 using 1:3:xtic(2) with boxes ls 1, \
>     "data.dat" every ::1::2 using 1:3:xtic(2) with boxes ls 2
gnuplot>





 gnuplot>               set term png
Terminal type set to 'png'
Options are 'nocrop font "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf,12" fontscale 1.0 size 640,480 '
gnuplot>               set output '| display png:-'
gnuplot>
gnuplot> set xrange [-pi/2:pi/2]; set yrange [-pi/2:pi/2]
gnuplot> set contour
gnuplot> set isosamples 100,100; set samples 100,100
gnuplot> splot sin(x)*cos(y) with lines palette linewidth 4
smooth palette in png: using 160 of 160 available color positions
gnuplot> pause -1

The sky is the limit have fun.
------------------------------------------
Update:

Each script takes in a csv file as input and “plots” the values as an ASCII graph in the terminal. It’s questionable how useful this actually is, but it’s a bit of fun at least. The SCALE variable controls the amount of characters used for the width of the plot area (not including the table containing labels and values).
Simple: Plot Values of each item
input file: sales
january,140
february,29
march,26
april,54
may,72
june,86

output: ./vgraph sales
 
 Relative Value Chart
Name      Value (Max is 140)
____________________0_________._________|_________._________|100% (140)
january      140    |========================================
february     29     |========
march        26     |=======
april        54     |===============
may          72     |====================
june         86     |========================

Percentage graph using value and max for each item

input file: stock-list
apples,20,100
pears,40,50
cherries,100,150
mangoes,45,85
tomatoes,30,30

output: ./pgraph stock-list
 
 Percentage Chart
____________________0_________._________|_________._________|100%
apples       ( 20%) |========
pears        ( 80%) |================================
cherries     ( 66%) |==========================
mangoes      ( 52%) |=====================
tomatoes     (100%) |========================================

And the scripts…. They are both very similar, just some minor tweaks to make them do what I wanted.

vgraph

 Relative Value Chart

Name      Value (Max is 140)
____________________0_________._________|_________._________|100% (140)
january      140    |========================================
february     29     |========
march        26     |=======
april        54     |===============
may          72     |====================
june         86     |========================

 
#!/bin/bash
#
# Value Graph (vgraph)
# Basic ASCII Graphing Tool
#
# Plot values and scale to max
#
# CSV format: Name, Value
#
# Awk isn't perfect at rounding.. .5 rounds down
#
# v1.1 sol@subnetzero.org

if [ -z $1 ]; then
        printf "Usage: pgraph [datafile]\n"
        exit 1
fi

# Set Vars
# FILLER and ENDDELIM are used for drawing bars.
ENDDELIM="="
FILLER="="
SCALE=40
INPUTFILE=$1
NAME=(`awk -F"," '{print $1}' < "$INPUTFILE"`)
TOTAL=(`awk -F"," '{print $2}' < "$INPUTFILE"`)

# Get Max qty for scaling
MAXQTY=0
for VALUE in ${TOTAL[*]}
do
        if [ "$VALUE" -gt "$MAXQTY" ]; then
                MAXQTY=$VALUE
        fi
done

# Make graph header and markings
printf "\n Relative Value Chart\n"
printf "\nName      Value (Max is $MAXQTY)\n"
printf "____________________0"
QTRSCALE=`echo "$SCALE / 4" | bc -l | awk '{printf("%.0f",$0)}'`
HALFSCALE=`echo "$SCALE / 2" | bc -l | awk '{printf("%.0f",$0)}'`
THRSCALE=`echo "$SCALE * 0.75" | bc -l | awk '{printf("%.0f",$0)}'`
LCNT=1
while [ "$LCNT" -le "$SCALE" ];
do
        case $LCNT in
                $QTRSCALE)      printf ".";;
                $HALFSCALE)     printf "|";;
                $THRSCALE)      printf ".";;
                $SCALE)         printf "|100%% ($MAXQTY)\n";;
                *)              printf "_";;
        esac
        LCNT=$(( $LCNT + 1 ))
done

# Draw graph bars
i=0
for ITEM in ${NAME[*]}
do
        # Print Category name in format along with info and bars
        LENGTH=`echo "scale=2;(( ${TOTAL[$i]} / $MAXQTY ) * $SCALE )" |\
                bc |\
                awk '{printf("%.0f",$0)}'`
        printf "%-12.12s %-6.6s |" "$ITEM" "${TOTAL[$i]}"
        BLOCKS=""
        while [ "$LENGTH" -gt "0" ]; do
                if [ "$LENGTH" -eq "1" ]; then
                        BLOCKS="$BLOCKS$ENDDELIM"
                else
                        BLOCKS="$BLOCKS$FILLER"
                fi
                LENGTH=$(( $LENGTH - 1 ))
        done
        printf "$BLOCKS\n"
        i=$(( $i + 1 ))
done
printf "\n\n"
 
pgraph
 
 
 ./pgraph.sh stock-list
max is 150

 Percentage Chart
____________________0_________._________|_________._________|100%
apples       ( 20%) |========
pears        ( 80%) |================================
cherries     ( 66%) |==========================
mangoes      ( 52%) |=====================
tomatoes     (100%) |========================================
 
#!/bin/bash
#
# PercentageGraph (pgraph)
# Basic ASCII Graphing Tool
#
# CSV format: Name,Used,Total (or Maximum)
#
# Awk isn't perfect at rounding.. .5 rounds down
#
# v1.1 sol@subnetzero.org

if [ -z $1 ]; then
        printf "Usage: pgraph [datafile]\n"
        exit 1
fi

# Set Vars
# FILLER and ENDDELIM are used for drawing bars.
ENDDELIM="="
FILLER="="
SCALE=40
INPUTFILE=$1
NAME=(`awk -F"," '{print $1}' < "$INPUTFILE"`)
USED=(`awk -F"," '{print $2}' < "$INPUTFILE"`)
TOTAL=(`awk -F"," '{print $3}' < "$INPUTFILE"`)

# Get Max qty for scaling
MAXQTY=0
for VALUE in ${TOTAL[*]}
do
        if [ "$VALUE" -gt "$MAXQTY" ]; then
                MAXQTY=$VALUE
        fi
done

echo "max is $MAXQTY"

# Make graph header and markings
printf "\n Percentage Chart\n"
printf "____________________0"
QTRSCALE=`echo "$SCALE / 4" | bc -l | awk '{printf("%.0f",$0)}'`
HALFSCALE=`echo "$SCALE / 2" | bc -l | awk '{printf("%.0f",$0)}'`
THRSCALE=`echo "$SCALE * 0.75" | bc -l | awk '{printf("%.0f",$0)}'`
LCNT=1
while [ "$LCNT" -le "$SCALE" ];
do
        case $LCNT in
                $QTRSCALE)      printf ".";;
                $HALFSCALE)     printf "|";;
                $THRSCALE)      printf ".";;
                $SCALE)         printf "|100%%\n";;
                *)              printf "_";;
        esac
        LCNT=$(( $LCNT + 1 ))
done

# Draw graph bars
i=0
for ITEM in ${NAME[*]}
do
        # Print Category name in format along with info and bars
        LENGTH=`echo "scale=2;(( ${USED[$i]} / ${TOTAL[$i]} ) * $SCALE )" |\
                bc | \
                awk '{printf("%.0f",$0)}'`
        PCT=`echo "scale=2;(( ${USED[$i]} / ${TOTAL[$i]} ) * 100)" |\
             bc |\
             awk '{printf("%.0f",$0)}'`
        printf "%-12.12s (%3.3s%%) |" "$ITEM" "$PCT"
        BLOCKS=""
        while [ "$LENGTH" -gt "0" ]; do
                if [ "$LENGTH" -eq "1" ]; then
                        BLOCKS="$BLOCKS$ENDDELIM"
                else
                        BLOCKS="$BLOCKS$FILLER"
                fi
                LENGTH=$(( $LENGTH - 1 ))
        done
        printf "$BLOCKS\n"
        i=$(( $i + 1 ))
done
printf "\n\n"

3 comments:

  1. Hi! Nice post. I just have a couple of comments about Bargraph_Generator.sh:

    1) On Linux, I had to replace the first line by "#!/bin/bash", as it seems you used some shell features not available in plain old "sh".

    2) In the very likely case that your terminal supports Unicode, you can use semigraphical characters to have a better looking bargraph. Also, using characters in the range U+2589 (left seven eighths block) through U+258F (left one eighth block), you can size the bar with a resolution of 1/8 of a character cell, which is amenable to very smooth animations. It could look roughly like this:

    ░░░░░0░░░░░░░░░10░░░░░░░░20░░░░░░░░30░░
    ░░░░╭┼────┬────┼────┬────┼────┬────┼──╮
    ░░░░│▐██████████████▊░░░░░░░░░░░░░░░░░│
    ░░░░╰┼────┴────┼────┴────┼────┴────┼──╯

    OK, doesn't look so good here: that's because I had to replace the spaces by U+2591 (light shade) in order to keep the alignments. But see http://edgar-bonet.org/misc/unicode-bargraph.png for a proper rendering on a terminal.

    Regards.

    ReplyDelete
    Replies
    1. What is the line number in the article?

      Delete
    2. The “line number in the article”? I am not sure I understand your question. If you are asking about where exactly I saw one of your scripts fail, it's the script near the top, showing an animation on the terminal. The one you named Bargraph_Generator.sh, in a comment inside the script itself. As for how and where it fails, note that:

      If the script starts with " # !/bin/sh" or "# !/bin/sh", it works, as this line is considered a comment and just ignored. If it starts with "#!/bin/sh" (no space before `!'), it is a proper shebang invoking /bin/sh, and it fails with:

      ./Bargraph_Generator.sh: 119: ./Bargraph_Generator.sh: Syntax error: "(" unexpected (expecting "done")

      If it starts with "#!/bin/bash" (a shebang invoking /bin/bash) it also works.

      Delete