Dave Griffin (1-Aug-2003) writes:
I recently needed to dynamically generate some graphics for inclusion on a web page. There are a couple of techniques for doing this with various tradeoffs:
Anyway, because we can't reuse all of the great stuff in BLT, there is a need to build some of the basic tools. There are many examples of drawing line and bar charts in this Wiki (Simple data plotting example, Shuffle a list: graph results), and at one level it is pretty simple to produce a custom graphic. When you get into the more general-purpose stuff, it gets a bit harder (as I'm sure George Howlett could attest).
As I build this, I'm going to keep track of some of the support routines I'm developing to create the charting package. I hope that I do this in a way that you can use other examples here in the Wiki and combine them with these routines to create better charts and graphs. Comments and improvements welcome (neither will be hard to come by).
namespace eval chart {} # # nice_number # # Reference: Paul Heckbert, "Nice Numbers for Graph Labels", # Graphics Gems, pp 61-63. # # Finds a "nice" number approximately equal to x. # # Args: x -- target number # round -- If non-zero, round. Otherwise take ceiling of value. proc chart::nice_number {x round} { # expt -- Exponent of x # frac -- Fractional part of x # nice -- Nice, rounded fraction set expt [expr floor(log10($x))] set frac [expr $x / pow(10.0, double($expt))] if ($round) { if {$frac < 1.5} { set nice 1.0 } elseif {$frac < 3.0} { set nice 2.0 } elseif {$frac < 7.0} { set nice 5.0 } else { set nice 10.0 } } else { if {$frac <= 1.0} { set nice 1.0 } elseif {$frac <= 2.0} { set nice 2.0 } elseif {$frac <= 5.0} { set nice 5.0 } else { set nice 10.0 } } return [expr $nice * pow(10.0, double($expt))] } # # loose_label # # Reference: Paul Heckbert, "Nice Numbers for Graph Labels", # Graphics Gems # # Returns a set of graph labelling attributes given a range of numbers # that need to be graphed. # # Args: min -- Lower range boundary # max -- Upper range boundary # steps -- Number of major tick marks desired # proc chart::loose_label {min max {steps 5}} { if {$steps < 2} { set steps 2 } # We expect min!=max # Try and be nice by raising max by 1. This obviously # fails miserably for small values. if {$min == $max} { set max [expr $max + 1] } set range [nice_number [expr $max - $min] 0] # tick mark spacing set d [nice_number [expr $range / ($steps - 1)] 1] set graphmin [expr floor($min/$d) * $d] set graphmax [expr ceil($max/$d) * $d] set nfx [expr int(-floor(log10($d)))] set nfrac [expr ($nfx > 0) ? $nfx : 0] set stepfmt [format "%%.%df" $nfrac] set ticks [list] for {set x $graphmin} {$x < [expr $graphmax + 0.5 * $d]} {set x [expr $x + $d]} { lappend ticks $x } return [list graphmin $graphmin graphmax $graphmax step $d stepfmt $stepfmt ticks $ticks] } # # range_and_step # # Given a range (and a maximum number of steps), returns a # "nice" range and "nice" step (tick) size # proc chart::range_and_step { range {steps 5}} { if {$steps < 2} { set steps 2 } set range [nice_number $range 0] set step [nice_number [expr $range / ($steps - 1)] 1] return [list $range $step] }
Here's a sample of how the results of the chart routines can be used to draw a chart axis:
proc draw_y_axis { canvas x y height axisProps {fgcolor black} } { array set ap $axisProps set y1 [expr $y - $height] $canvas create line $x $y $x $y1 set nTicks [llength $ap(ticks)] set tickIncr [expr $height / $nTicks] set ty $y foreach t $ap(ticks) { $canvas create line $x $ty [expr $x - 5] $ty $canvas create text [expr $x - 7] $ty -justify right -anchor e -text [format $ap(stepfmt) $t] set ty [expr $ty - $tickIncr] } } # A non-functioning example (not a good Wiki code example yet) set yap [chart::loose_label 0 $maxRange] draw_y_axis .c $x_origin $y_origin 100 $yap
If these prove to be of some value and are stable, they may be useful additions to the TclLib math::statistics package, which has some basic plotting tools in it.
See also A little function plotter