1# plotannot.tcl --
2#    Facilities for annotating charts
3#
4# Note:
5#    This source file contains such functions as to draw a
6#    balloon text in an xy-graph.
7#    It is the companion of "plotchart.tcl"
8#
9
10#
11# Static data
12#
13namespace eval ::Plotchart {
14    # Index, three pairs of scale factors to determine xy-coordinates
15    set BalloonDir(north-west) {0  0  1 -2 -2  1  0}
16    set BalloonDir(north)      {1 -1  0  0 -3  1  0}
17    set BalloonDir(north-east) {2 -1  0  2 -2  0  1}
18    set BalloonDir(east)       {3  0 -1  3  0  0  1}
19    set BalloonDir(south-east) {4  0 -1  2  2 -1  0}
20    set BalloonDir(south)      {5  1  0  0  3 -1  0}
21    set BalloonDir(south-west) {6  1  0 -2  2  0 -1}
22    set BalloonDir(west)       {7  0  1 -3  0  0 -1}
23
24    set TextDir(centre)     c
25    set TextDir(center)     c
26    set TextDir(c)          c
27    set TextDir(west)       w
28    set TextDir(w)          w
29    set TextDir(north-west) nw
30    set TextDir(nw)         nw
31    set TextDir(north)      n
32    set TextDir(n)          n
33    set TextDir(north-east) ew
34    set TextDir(ne)         ew
35    set TextDir(east)       e
36    set TextDir(e)          e
37    set TextDir(south-west) nw
38    set TextDir(sw)         sw
39    set TextDir(south)      s
40    set TextDir(s)          s
41    set TextDir(south-east) ew
42    set TextDir(east)       e
43}
44
45# DefaultBalloon --
46#    Set the default properties of balloon text and other types of annotation
47# Arguments:
48#    w           Name of the canvas
49# Result:
50#    None
51# Side effects:
52#    Stores the default settings
53#
54proc ::Plotchart::DefaultBalloon { w } {
55    variable settings
56
57    foreach {option value} {font       fixed
58                            margin     5
59                            textcolour black
60                            justify    left
61                            arrowsize  5
62                            background white
63                            outline    black
64                            rimwidth   1} {
65        set settings($w,balloon$option) $value
66    }
67    foreach {option value} {font       fixed
68                            colour     black
69                            justify    left} {
70        set settings($w,text$option) $value
71    }
72}
73
74# ConfigBalloon --
75#    Configure the properties of balloon text
76# Arguments:
77#    w           Name of the canvas
78#    args        List of arguments
79# Result:
80#    None
81# Side effects:
82#    Stores the new settings for the next balloon text
83#
84proc ::Plotchart::ConfigBalloon { w args } {
85    variable settings
86
87    foreach {option value} $args {
88        set option [string range $option 1 end]
89        switch -- $option {
90            "font" -
91            "margin" -
92            "textcolour" -
93            "justify" -
94            "arrowsize" -
95            "background" -
96            "outline" -
97            "rimwidth" {
98                set settings($w,balloon$option) $value
99            }
100            "textcolor" {
101                set settings($w,balloontextcolour) $value
102            }
103        }
104    }
105}
106
107# ConfigPlainText --
108#    Configure the properties of plain text
109# Arguments:
110#    w           Name of the canvas
111#    args        List of arguments
112# Result:
113#    None
114# Side effects:
115#    Stores the new settings for the next plain text
116#
117proc ::Plotchart::ConfigPlainText { w args } {
118    variable settings
119
120    foreach {option value} $args {
121        set option [string range $option 1 end]
122        switch -- $option {
123            "font" -
124            "textcolour" -
125            "justify" {
126                set settings($w,text$option) $value
127            }
128            "textcolor" {
129                set settings($w,textcolour) $value
130            }
131            "textfont" {
132                # Ugly hack!
133                set settings($w,$option) $value
134            }
135        }
136    }
137}
138
139# DrawBalloon --
140#    Plot a balloon text in a chart
141# Arguments:
142#    w           Name of the canvas
143#    x           X-coordinate of the point the arrow points to
144#    y           Y-coordinate of the point the arrow points to
145#    text        Text in the balloon
146#    dir         Direction of the arrow (north, north-east, ...)
147# Result:
148#    None
149# Side effects:
150#    Text and polygon drawn in the chart
151#
152proc ::Plotchart::DrawBalloon { w x y text dir } {
153    variable settings
154    variable BalloonDir
155
156    #
157    # Create the item and then determine the coordinates
158    # of the frame around the text
159    #
160    set item [$w create text 0 0 -text $text -tag BalloonText \
161                 -font $settings($w,balloonfont) -fill $settings($w,balloontextcolour) \
162                 -justify $settings($w,balloonjustify)]
163
164    if { ![info exists BalloonDir($dir)] } {
165        set dir south-east
166    }
167
168    foreach {xmin ymin xmax ymax} [$w bbox $item] {break}
169
170    set xmin   [expr {$xmin-$settings($w,balloonmargin)}]
171    set xmax   [expr {$xmax+$settings($w,balloonmargin)}]
172    set ymin   [expr {$ymin-$settings($w,balloonmargin)}]
173    set ymax   [expr {$ymax+$settings($w,balloonmargin)}]
174
175    set xcentr [expr {($xmin+$xmax)/2}]
176    set ycentr [expr {($ymin+$ymax)/2}]
177    set coords [list $xmin   $ymin   \
178                     $xcentr $ymin   \
179                     $xmax   $ymin   \
180                     $xmax   $ycentr \
181                     $xmax   $ymax   \
182                     $xcentr $ymax   \
183                     $xmin   $ymax   \
184                     $xmin   $ycentr ]
185
186    set idx    [lindex $BalloonDir($dir) 0]
187    set scales [lrange $BalloonDir($dir) 1 end]
188
189    set factor $settings($w,balloonarrowsize)
190    set extraCoords {}
191
192    set xbase  [lindex $coords [expr {2*$idx}]]
193    set ybase  [lindex $coords [expr {2*$idx+1}]]
194
195    foreach {xscale yscale} $scales {
196        set xnew [expr {$xbase+$xscale*$factor}]
197        set ynew [expr {$ybase+$yscale*$factor}]
198        lappend extraCoords $xnew $ynew
199    }
200
201    #
202    # Insert the extra coordinates
203    #
204    set coords [eval lreplace [list $coords] [expr {2*$idx}] [expr {2*$idx+1}] \
205                          $extraCoords]
206
207    set xpoint [lindex $coords [expr {2*$idx+2}]]
208    set ypoint [lindex $coords [expr {2*$idx+3}]]
209
210    set poly [$w create polygon $coords -tag BalloonFrame \
211                  -fill $settings($w,balloonbackground) \
212                  -width $settings($w,balloonrimwidth)  \
213                  -outline $settings($w,balloonoutline)]
214
215    #
216    # Position the two items
217    #
218    foreach {xtarget ytarget} [coordsToPixel $w $x $y] {break}
219    set dx [expr {$xtarget-$xpoint}]
220    set dy [expr {$ytarget-$ypoint}]
221    $w move $item  $dx $dy
222    $w move $poly  $dx $dy
223    $w raise BalloonFrame
224    $w raise BalloonText
225}
226
227# DrawPlainText --
228#    Plot plain text in a chart
229# Arguments:
230#    w           Name of the canvas
231#    x           X-coordinate of the point the arrow points to
232#    y           Y-coordinate of the point the arrow points to
233#    text        Text to be drawn
234#    anchor      Anchor position (north, north-east, ..., defaults to centre)
235# Result:
236#    None
237# Side effects:
238#    Text drawn in the chart
239#
240proc ::Plotchart::DrawPlainText { w x y text {anchor centre} } {
241    variable settings
242    variable TextDir
243
244    foreach {xtext ytext} [coordsToPixel $w $x $y] {break}
245
246    if { [info exists TextDir($anchor)] } {
247        set anchor $TextDir($anchor)
248    } else {
249        set anchor c
250    }
251
252    $w create text $xtext $ytext -text $text -tag PlainText \
253         -font $settings($w,textfont) -fill $settings($w,textcolour) \
254         -justify $settings($w,textjustify) -anchor $anchor
255
256    $w raise PlainText
257}
258
259# BrightenColour --
260#    Compute a brighter colour
261# Arguments:
262#    color       Original colour
263#    intensity   Colour to interpolate with
264#    factor      Factor by which to brighten the colour
265# Result:
266#    New colour
267# Note:
268#    Adapted from R. Suchenwirths Wiki page on 3D bars
269#
270proc ::Plotchart::BrightenColour {color intensity factor} {
271    foreach i {r g b} n [winfo rgb . $color] d [winfo rgb . $intensity] f [winfo rgb . white] {
272        #checker exclude warnVarRef
273        set $i [expr {int(255.*($n+($d-$n)*$factor)/$f)}]
274    }
275    #checker exclude warnUndefinedVar
276    format #%02x%02x%02x $r $g $b
277}
278
279# DrawGradientBackground --
280#    Add a gradient background to the plot
281# Arguments:
282#    w           Name of the canvas
283#    colour      Main colour
284#    dir         Direction of the gradient (left-right, top-down,
285#                bottom-up, right-left)
286#    intensity   Brighten (white) or darken (black) the colours
287#    rect        (Optional) coordinates of the rectangle to be filled
288# Result:
289#    None
290# Side effects:
291#    Gradient background drawn in the chart
292#
293proc ::Plotchart::DrawGradientBackground { w colour dir intensity {rect {}} } {
294    variable scaling
295
296    set pxmin $scaling($w,pxmin)
297    set pxmax $scaling($w,pxmax)
298    set pymin $scaling($w,pymin)
299    set pymax $scaling($w,pymax)
300
301    if { $rect != {} } {
302        foreach {rxmin rymin rxmax rymax} $rect {break}
303    } else {
304        set rxmin $pxmin
305        set rxmax $pxmax
306        set rymin $pymin
307        set rymax $pymax
308    }
309
310    switch -- $dir {
311        "left-right" {
312            set dir   h
313            set first 0.0
314            set last  1.0
315            set fac   [expr {($pxmax-$pxmin)/50.0}]
316        }
317        "right-left" {
318            set dir   h
319            set first 1.0
320            set last  0.0
321            set fac   [expr {($pxmax-$pxmin)/50.0}]
322        }
323        "top-down" {
324            set dir   v
325            set first 0.0
326            set last  1.0
327            set fac   [expr {($pymin-$pymax)/50.0}]
328        }
329        "bottom-up" {
330            set dir   v
331            set first 1.0
332            set last  0.0
333            set fac   [expr {($pymin-$pymax)/50.0}]
334        }
335        default {
336            set dir   v
337            set first 0.0
338            set last  1.0
339            set fac   [expr {($pymin-$pymax)/50.0}]
340        }
341    }
342
343    if { $dir == "h" } {
344        set x2 $rxmin
345        set y1 $rymin
346        set y2 $rymax
347    } else {
348        set y2 $rymax
349        set x1 $rxmin
350        set x2 $rxmax
351    }
352
353    set n 50
354    if { $dir == "h" } {
355        set nmax [expr {ceil($n*($rxmax-$rxmin)/double($pxmax-$pxmin))}]
356    } else {
357        set nmax [expr {ceil($n*($rymin-$rymax)/double($pymin-$pymax))}]
358    }
359    for { set i 0 } { $i < $nmax } { incr i } {
360        set factor [expr {($first*$i+$last*($n-$i-1))/double($n)}]
361        set gcolour [BrightenColour $colour $intensity $factor]
362
363        if { $dir == "h" } {
364            set x1     $x2
365            set x2     [expr {$rxmin+($i+1)*$fac}]
366            if { $i == $nmax-1 } {
367                set x2 $rxmax
368            }
369        } else {
370            set y1     $y2
371            set y2     [expr {$rymax+($i+1)*$fac}]
372            if { $i == $nmax-1 } {
373                set y2 $rymin
374            }
375        }
376
377        $w create rectangle $x1 $y1 $x2 $y2 -fill $gcolour -outline $gcolour -tag {data background}
378    }
379
380    $w lower data
381    $w lower background
382}
383
384# DrawImageBackground --
385#    Add an image (tilde) to the background to the plot
386# Arguments:
387#    w           Name of the canvas
388#    colour      Main colour
389#    image       Name of the image
390# Result:
391#    None
392# Side effects:
393#    Image appears in the plot area, tiled if needed
394#
395proc ::Plotchart::DrawImageBackground { w image } {
396    variable scaling
397
398    set pxmin $scaling($w,pxmin)
399    set pxmax $scaling($w,pxmax)
400    set pymin $scaling($w,pymin)
401    set pymax $scaling($w,pymax)
402
403    set iwidth  [image width $image]
404    set iheight [image height $image]
405
406    for { set y $pymax } { $y > $pymin } { set y [expr {$y-$iheight}] } {
407        for { set x $pxmin } { $x < $pxmax } { incr x $iwidth } {
408            $w create image $x $y -image $image -anchor sw -tags {data background}
409        }
410    }
411
412    $w lower data
413    $w lower background
414}
415