############################################################################
#
#	File:     barchart.icn
#
#	Subject:  Procedures for dynamically growing barchart
#
#	Author:   Gregg M. Townsend
#
#	Date:     August 14, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	These procedures draw barcharts that can grow dynamically.
#
#	barchart(win, x, y, dx, dy, sf, n, l, w, b) creates a barchart.
#
#	setbar(bc, n, v)	sets the value of a bar.
#
#	rebar(bc, sf)		redraws a barchart with a new scaling factor.
#
############################################################################
#
#   barchart(win, x, y, dx, dy, sf, n, l, w, b) -- establish a barchart
#
#	win      window
#	x,y      position of base of first bar
#	dx,dy    distance to base of second bar (either dx or dy should be
#		  zero)
#	sf       scaling (pixels per unit of value, + or -, need not be
#		  integer)
#	n        number of bars
#	l,w      length (maximum) and width of one bar
#	b        logarithmic base, if bars are to be scaled logarithmically
#
#	barchart() establishes structures for building a barchart.  Any of the
#	eight possible orthogonal orientations can be selected depending on the
#	signs of dx, dy, and sf.
#
#	The absolute value of sf establishes a linear scaling from barchart
#	values to number of pixels.  Scaling is handled such that a value of 1
#	makes the first mark on a bar and then each increment of sf lengthens
#	the bar by one pixel.  If a bar would exceed the limit then the entire
#	chart is rescaled so that only half the range is then used.
#
#   setbar(bc, n, v) - set bar n of barchart bc to represent value v
#
#	It is assumed that v>0 and that bars never shrink; but they may grow.
#
#   rebar(bc, sf) - redraw barchart with new scaling factor sf.
#
#	sf is assumed to be of the same sign as the previous scaling factor.
#
#   Example:
#
#	Suppose "scores" is a list of scores ranging from 0 to 100.
#	This code fragment dynamically draws a histogram using 21 bins.
#
#	The call to barchart() specifies:
#	    The lower left-hand corner of the barchart is (10, 190).
#	    The next bar is 10 pixels to its right, which would be (20, 190).
#	    The bars grow upward, to smaller y values, so the scaling factor
#	    is negative; each score will grow its bar by 5 pixels.
#	    Each bar grows to a maximum length of 180 pixels; the width is 8.
#	    No base is given, so scaling is linear.
#
#	bc := barchart(win, 10, 190, 10, 0, -5, 21, 180, 8)
#	b := list(21, 0)		# histogram bins
#	every n := !scores do {
#	    i := n / 5			# bin (and bar) number
#	    b[i] +:= 1			# increment bin count
#	    setbar(bc, i, b[i])		# update display
#	    }
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################

record BC_rec(win, x, y, dx, dy, sf, n, l, w, b, len, val, round)

procedure barchart(win, x, y, dx, dy, sf, n, l, w, b)	#: draw barchart
   local bc
   bc := BC_rec(win, x, y, dx, dy, sf, n, l, w, b)
   bc.len := list(n, 0)
   bc.val := list(n)
   if sf > 0 then
      bc.round := 0.99999
   else
      bc.round := -0.99999
   rebar(bc, sf)				# clear area
   return bc
end


## setbar(bc, n, v) - set bar n of barchart bc to represent value v
#
#  It is assumed that v>0 and that bars never shrink; but they may grow.

procedure setbar(bc, n, v)			#: set bar value on barchart
   local x, y, o, oldlen, newlen, incr

   v := log(v, \bc.b)
   oldlen := bc.len[n] | fail
   newlen := integer(v * bc.sf + bc.round)

   if abs(newlen) > bc.l then {
      # need to rescale first
      rebar(bc, 0.5 * bc.sf * real(bc.l) / real(abs(newlen-1)))
      return setbar(bc, n, v)
      }

   # lengthen the bar
   if (incr := newlen - oldlen) ~= 0 then {
      if bc.dx ~= 0 then {

         # horizontal baseline
         x := bc.x + (n - 1) * bc.dx
         y := bc.y + oldlen
         if incr < 0 then
            FillRectangle(bc.win, x, y + incr, bc.w, -incr)
         else
            FillRectangle(bc.win, x, y, bc.w, incr)
         }

      else {

         # vertical baseline
         x := bc.x + oldlen
         y := bc.y + (n - 1) * bc.dy
         if incr < 0 then
            FillRectangle(bc.win, x + incr, y, -incr, bc.w)
         else
            FillRectangle(bc.win, x, y, incr, bc.w)
         }
      bc.len[n] := newlen
      bc.val[n] := v
      }
   return
end


## rebar(bc, sf) - redraw barchart with new scaling factor sf.
#
#  sf is assumed to be of the same sign as the previous scaling factor.

procedure rebar(bc, sf)				#: redraw barchart
   local i, l, x, y, dx, dy

   if bc.sf > 0 then
      l := bc.l
   else
      l := -bc.l
   x := bc.x
   y := bc.y

   if bc.dx ~= 0 then {
      dx := bc.n * bc.dx
      dy := l
      }
   else {
      dx := l
      dy := bc.n * bc.dy
      }

   # force all values positive (negative is wrong, but works under OpenWindows!)
   if dx < 0 then {
      x +:= dx
      dx := -dx
      }
   if dy < 0 then {
      y +:= dy
      dy := -dy
      }
   EraseArea(bc.win, x, y, dx, dy)

   bc.len := list(bc.n, 0)
   bc.sf := sf
   every i := 1 to *bc.len do
      setbar(bc, i, \bc.val[i])
   return
end


#  ## test program
#  #
#  #  usage:  barchart [dx [dy [sf]]]
#  #
#  #  background is deliberately different in order to see what gets cleared
#
#  procedure main(args)
#     local dx, dy, sf, win, n, l, bc, i
#     dx := args[1] | 5
#     dy := args[2] | 0
#     sf := args[3] | -1
#     win := open("bars", "g", "width=500", "height=500")
#     l := list(50, 0)
#     bc := barchart(win, 250, 250, dx, dy, sf, *l, 200, 4)
#     Fg(win, "papayawhip")
#     FillRectangle(win, 0, 0, 500, 500)
#     Fg(win, "black")
#     every 1 to 5000 do {
#        i := ?5 + ?5 + integer(10 * log(1+20*?0))	# nonuniform random bar
#        setbar(bc, i, l[i] +:= 1)
#        flush(win)
#        }
#     while not upto('qQ', reads(win))
#     end
