#!/usr/bin/wish -f ## ## SCRIPT: tkBrownianMotionAnimation.tk ## ## PURPOSE: This Tk GUI script generates an animation of a 'random walk' ## that resembles 'Brownian motion' in 2D --- like a tiny, ## light-weight particles on the surface of water being observed ## under a microscope as the particles are being pummeled by the ## water molecules on which the particles are floating. ## ## The animation is shown on a rectangular Tk 'canvas' widget, ## using Tk 'create line' commands (along with the 'rand' function) ## to create short line segments of random lengths in random ## directions (between 0 and 360 degrees). ## ############################################# ## METHOD - MATH MODELLING OF THE RANDOM WALK: ## ## We assume that there is a maximum distance that the particle can ## move after each collision with a water molecule. We assume, for ## example, that the max distance is about 10% of a distance ## (width or length or diameter) of the canvas area. ## ## We assume that after each collision, the direction in which ## the particle moves is equally likely at any of the angles ## between 0 and 360 degrees. ## ## We will assume that physical factors such as density of the ## the fluid medium, viscosity, gravity, whatever, limits the ## travel of the particle to a maximum distance. ## ## As a simple approximation, we could confine the particle to ## a square area, and model random motions in x and y directions. ## Given a 'start' point (x,y), we could add dx and dy to get the ## location of the end-point of the next line segment, (x+dx,y+dx), ## where ## dx = rand(0,1) * DISTmax ## dy = rand(0,1) * DISTmax ## and where rand(0,1) represents a random number between 0.0 and 1.0. ## ## However, this would bias the movement according to the choice ## of the direction of the x and y axes, and it would allow the ## particle to move to the corners of the square, beyond the proposed ## maximum distance. ## ## Hence, we will confine the particle's travel within any given ## time period to a circle rather than a square. ## ## With that assumption, we could generate a random radius 'r' ## and a random angle 'A' and set ## ## r = rand(0,0) * DISTmax ## A = rand(0,1) * 2 * pi ## and then set ## dx = r * cos(A) ## dy = r * sin(A) ## ## We will assume the particle always starts at the center point ## of the canvas rectangle. We establish a rectangular coordinate system ## with origin (0.0,0.0) at the center point. ## ## We offer 'Start' and 'Stop' buttons on the GUI by which the ## user can start and stop the animation. Also we offer a 'Clear' ## button by which to clear the canvas and start a new simulation. ## ## To allow the user to control the speed of the generation of the ## 'poly-line' which traces the Brownian motion, we provide ## a widget on the GUI by which the user can specify a 'wait time' ## between drawing each line segment. ## ## We also provide a couple of 'color buttons' on the GUI by which ## the user can specify a background color for the canvas and ## a color for the line segments. ## ## During the animation, the GUI could display some statistics, ## such as ## - the total distance traveled so far, which should be ## a monotonically increasing function. ## - the average distance from the center point (0,0), ## which should be a montonically (slowly) increasing function. ## ############################################# ## REFERENCES: ## The simulation method outlined above (for Brownian motion) is ## probably too simplified to correspond properly with reality. ## The statistical methods used by Einstein in his remarkable 1905 ## paper is outlined on pages 255-256 of the book 'Great Calculations' ## by Colin Pask, 2015. ## ## Page 255 of this book shows the image seen at ## https://commons.wikimedia.org/wiki/File:PerrinPlot2.svg ## which is described as follows: ## ## "Three tracings of the motion of colloidal particles of ## radius 0.53 µm, as seen under the microscope, are displayed. ## Successive positions every 30 seconds are joined by straight ## line segments. (The mesh size is 3.2 µm.) ## Reproduced from Jean Baptiste Perrin, "Mouvement brownien et ## réalité moléculaire," Ann. de Chimie et de Physique (VIII) 18, ## 5-114, 1909 (a slightly different version appeared in the book ## Les Atomes)." ## ## The simple method used in this Tk script may be modified someday to use ## a more sophisticated 'probability distribution function' with which ## to generate the randomized path. ## ## Note that each line segment of this animation could be interpreted ## as representing what we would get if we observed the position of ## the particle every 30 seconds (or whatever) and joined the positions ## by straight line segments. In other words, each line segment ## represents travel over a constant (fixed) time interval. ## ############################# ## PERFORMANCE CONSIDERATIONS: ## ## Since each 'create line' completes in about a milli-second, ## this Tk script can probably draw a Brownian motion 'path' ## of about 100 line segments in less than one second. ## ##+############## ## THE GUI LAYOUT: ## ## One way the user can specify these parameters is indicated by ## the following 'sketch' of the GUI: ## ## FRAMEnames ## VVVVVVVVVV ## ------------------------------------------------------------------------------------------ ## tkBrownianMotionAnimation ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} Animate: O Start O Stop {Clear {Background {Line ## Canvas} Color} Color} ## ## DrawTime Control 0 1000 Max-distance 0 100 ## .fRcontrols (millisecs): <---------O--------> (percent): <---------O--------> ## ## .fRimgsize ImageSize - Xpixels: <---------O--------> Ypixels: <--------O-------> ## ## .fRmsg [ .......... [Messages go here] .......................................] ## ## .fRcanvas |------------------------------------------------------------------------A ## | | ## | [This scrollable canvas contains the generated 'poly-line'.] | ## | | ## | | ## | | ## | | ## | | ## | | ## | | ## | | ## |<---------------------------------------------------------------------->V ## ## ------------------------------------------------------------------- ## ## In the above sketch of the GUI: ## ## SQUARE BRACKETS indicate a comment (not to be placed on the GUI). ## BRACES indicate a Tk 'button' widget. ## A COLON indicates that the text before the colon is on a 'label' widget. ## <---O---> indicates a Tk 'scale' widget. ## CAPITAL-O indicates a Tk 'radiobutton' widget. ## CAPITAL-X indicates a Tk 'checkbutton' widget (if any). ## UNDERSCORES indicate a Tk 'entry' widget. ## Vertical bars (and horizontal hyphens) outline a 'canvas' or 'listbox' or 'text widget. ## Less-than and greater-than signs indicate the left and right ends of a horizontal 'scrollbar'. ## Capital-V and Capital-A letters indicate the bottom and top ends of a vertical 'scrollbar'. ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of ## about ## ## - 5 button widgets ## - 1 label widget ## - 1 scale widget ## - 1 canvas widget with 2 scrollbars ## - 2 radiobutton widgets in 1 group ## - 0 entry widgets ## - 0 checkbutton widgets ## - 0 listbox widgets ## - 0 text widgets ## ##+######################################################################## ## 'CANONICAL' STRUCTURE OF THIS TK CODE: ## ## 0) Set general window & widget parms (win-name, win-position, ## win-color-scheme, fonts, widget-geometry-parms, win-size-control). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack ALL frames and sub-frames. ## ## 2) Define all widgets in the frames. Pack them. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen action ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or two of ## the procs), if needed. ## ## ## Some detail about the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : '.fRbuttons' ## '.fRcontrols' ## '.fRimgsize' ## '.fRmsg' ## '.fRcanvas' ## No sub-frames. ## ## 1b) Pack ALL frames. ## ## 2) Define all widgets in the frames (and pack them): ## ## - In '.fRbuttons': ## 2 button widgets ('Exit', 'Help') ## 2 radiobutton widgets ('Start', 'Stop') ## and ## 2 buttons (for setting 2 colors). ## ## - In '.fRcontrols': ## 1 'label' and 1 'scale' widget each for 'wait time' ## ## - In '.fRimgsize': ## 1 'label' and 1 'scale' widget each for XmaxPx, YmaxPx ## ## - In '.fRmsg': ## 1 label widget to display messages to the user, such as ## 'random walk' statistics, as well as an initial instruction ## msg or other msgs. ## ## - In '.fRcanvas': 1 'canvas' widget (with 2 scrollbars) ## ## 3) Define BINDINGS: see the BINDINGS section for bindings, if any ## ## 4) Define procs: ## ## - 'animate' - called by the 'StartAnimation' button ## ## - 'advise_user' - called by the 'animate' proc ## ## - 'setMappingVars_for_px2wc' - called by proc 'animate' ## ## - 'Xwc2px' - called by proc 'animate' ## - 'Ywc2px' - called by proc 'animate' ## ## - 'set_scale_Ymax_equal_Xmax' - called by button1-release on the X-scale ## ## - 'set_bkgd_color' - called by the background-color button ## ## - 'set_line_color' - called by the line-color button ## ## - 'update_color_button' - sets background & foreground color of any ## of the 3 color buttons ## ## - 'reset_parms' - called by the 'ResetParms' button ## ## - 'clear_canvas' - called by the 'ClearCanvas' button ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button ## ## 5) Additional GUI initialization: Set some inital values of parameters ## colors. ## ##+######################################################################## ## DEVELOPED WITH: ## Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala'). ## ## $ wish ## % puts "$tcl_version $tk_version" ## showed 8.5 8.5 on Ubuntu 9.10 ## after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts. ##+####################################################################### ## MAINTENANCE HISTORY: ## Created by: Blaise Montandon 2016mar11 ## Changed by: Blaise Montandon 2016 ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "tkBrownianMotionSimulation" wm iconname . "Brownian" wm geometry . +15+30 ##+###################################################### ## Set the color scheme for the window and set the ## background color for the 'trough' in some widgets. ##+###################################################### tk_setPalette "#e0e0e0" set scaleBKGD "#f0f0f0" set radbuttBKGD "#f0f0f0" # set entryBKGD "#f0f0f0" # set chkbuttBKGD "#f0f0f0" # set listboxBKGD "#f0f0f0" ##+######################################################## ## Use a VARIABLE-WIDTH FONT for label and button widgets. ## ## Use a FIXED-WIDTH FONT for listboxes (and ## entry fields, if any). ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## BUTTON geom parameters: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 set RELIEF_button "raised" ## LABEL geom parameters: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 set RELIEF_label "ridge" ## RADIOBUTTON widget geom settings: set BDwidthPx_radbutt 2 # set RELIEF_radbutt "ridge" set RELIEF_radbutt "raised" ## ENTRY widget geom settings: set BDwidthPx_entry 2 set xyrangevarEntryWidthChars 7 ## SCALE geom parameters: set BDwidthPx_scale 2 set scaleLengthPx 250 set scaleThicknessPx 10 set scaleRepeatDelayMillisecs 800 ## CANVAS geom parameters: set initCanWidthPx 300 set initCanHeightPx 200 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ##+################################################################### ## Set a MINSIZE of the window. ## ## For width, allow for the minwidth of the '.fRbuttons' frame: ## about 6 buttons (Exit,Help,StartAnimation,Color,Color,Color). ## We want to at least be able to see the Exit button. ## ## For height, allow ## 2 chars high for the '.fRbuttons' frame, ## 2 chars high for the '.fRcontrols' frame, ## 2 chars high for the '.fRimgsize' frame, ## 1 char high for the '.fRmsg' frame, ## 24 pixels high for the '.fRcanvas' frame. ##+################################################################### ## MIN WIDTH: set minWinWidthPx [font measure fontTEMP_varwidth \ "Exit Help Animation: Start Stop Background Line"] ## Add some pixels to account for right-left-side window decoration ## (about 8 pixels), about 6 x 4 pixels/widget for borders/padding for ## 6 widgets --- 6 buttons. set minWinWidthPx [expr {32 + $minWinWidthPx}] ## MIN HEIGHT --- allow ## 2 chars high for 'fRbuttons' ## 2 chars high for 'fRcontrols' ## 2 chars high for 'fRimgsize' ## 1 char high for 'fRmsg' ## 24 pixels high for 'fRcanvas' set CharHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {24 + (7 * $CharHeightPx)}] ## Add about 28 pixels for top-bottom window decoration, ## about 5x4 pixels for each of the 5 stacked frames and their ## widgets (their borders/padding). set minWinHeightPx [expr {$minWinHeightPx + 48}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We allow the window to be resizable and we pack the canvas with ## '-fill both -expand 1' so that the canvas can be enlarged by enlarging ## the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+#################################################################### ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+#################################################################### ## if { "$VARlocale" == "en"} ## For widgets in 'fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(labelSTARTSTOP) "Animation:" set aRtext(radbuttSTART) "Start" set aRtext(radbuttSTOP) "Stop" set aRtext(buttonCLEAR) "Clear Canvas" set aRtext(buttonRESET) "Reset Parms" set aRtext(buttonCOLORbkgd) "Background Color" set aRtext(buttonCOLORline) "Line Color" ## For widgets in 'fRcontrols' frame: set aRtext(labelWAITTIME) "DrawTime Control (wait millisecs):" set aRtext(labelMAXDIST) "MaxDistance (% of img width):" set aRtext(labelLINEWIDTH) "LineWidth (pixels):" ## For widgets in 'fRimgsize' frame: set aRtext(labelXmaxPx) "Image size Xpixels:" set aRtext(labelYmaxPx) " Ypixels:" ## For some calls to the 'advise_user' proc: set aRtext(STARTmsg) "** Click 'Start' to start the animation **" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRcontrols' ## '.fRimgsize' '.fRmsg' '.fRcanvas' ##+################################################################ ## FOR TESTING change 0 to 1: ## (Example1: To see appearance of frames when borders are drawn.) ## (Example2: To see sizes of frames for various '-fill' options.) ## (Example3: To see how frames expand as window is resized.) if {0} { set RELIEF_frame raised set BDwidthPx_frame 2 } else { set RELIEF_frame flat set BDwidthPx_frame 0 } frame .fRbuttons -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRcontrols -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRimgsize -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRmsg -relief raised -borderwidth 2 # frame .fRcanvas -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRcanvas -relief raised -borderwidth 2 ##+############################## ## PACK the top-level FRAMES. ##+############################## pack .fRbuttons \ .fRcontrols \ .fRimgsize \ .fRmsg \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvas \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+######################################################### ## OK. Now we are ready to define the widgets in the frames. ##+######################################################### ##+##################################################################### ## In the '.fRbuttons' FRAME --- DEFINE-and-PACK ## - an exit-button, a help-button ## - a label and 2 start/stop radiobuttons ## - a clear-canvas button ## and ## - 2 buttons ( to specify colors). ##+##################################################################### button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext" +10+10} label .fRbuttons.labelSTARTSTOP \ -text "$aRtext(labelSTARTSTOP)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## 'VARanimate0or1' is the var for these 2 radiobuttons. set VARanimate0or1 0 radiobutton .fRbuttons.radbuttSTART \ -text "$aRtext(radbuttSTART)" \ -font fontTEMP_varwidth \ -anchor w \ -variable VARanimate0or1 \ -value 1 \ -selectcolor "$radbuttBKGD" \ -relief $RELIEF_radbutt \ -bd $BDwidthPx_radbutt radiobutton .fRbuttons.radbuttSTOP \ -text "$aRtext(radbuttSTOP)" \ -font fontTEMP_varwidth \ -anchor w \ -variable VARanimate0or1 \ -value 0 \ -selectcolor "$radbuttBKGD" \ -relief $RELIEF_radbutt \ -bd $BDwidthPx_radbutt button .fRbuttons.buttCLEAR \ -text "$aRtext(buttonCLEAR)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {clear_canvas} button .fRbuttons.buttRESET \ -text "$aRtext(buttonRESET)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {reset_parms} button .fRbuttons.buttCOLORbkgd \ -text "$aRtext(buttonCOLORbkgd)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_bkgd_color" button .fRbuttons.buttCOLORline \ -text "$aRtext(buttonCOLORline)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_line_color" ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.labelSTARTSTOP \ .fRbuttons.radbuttSTART \ .fRbuttons.radbuttSTOP \ .fRbuttons.buttCLEAR \ .fRbuttons.buttRESET \ .fRbuttons.buttCOLORbkgd \ .fRbuttons.buttCOLORline \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRcontrols' FRAME ---- DEFINE-and-PACK 2 pairs of ## LABEL and SCALE widgets. PACK them. ##+################################################################## ######################################################################## ## DEFINE LABEL-and-SCALE for 'wait time' between line-segment draws. ######################################################################## label .fRcontrols.labelWAITTIME \ -text "$aRtext(labelWAITTIME)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script via proc 'reset_parms'. # set VARwaittime 100 scale .fRcontrols.scaleWAITTIME \ -from 0 -to 1000 \ -resolution 10 \ -bigincrement 100 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 150 \ -font fontTEMP_varwidth \ -variable VARwaittime \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRcontrols.labelWAITTIME \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRcontrols.scaleWAITTIME \ -side left \ -anchor w \ -fill x \ -expand 0 ################################################ ## DEFINE LABEL-and-SCALE pair for MAX-DISTANCE. ################################################ label .fRcontrols.labelMAXDIST \ -text "$aRtext(labelMAXDIST)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script via proc 'reset_parms'. # set VARmaxpcnt 10 scale .fRcontrols.scaleMAXDIST \ -from 0 -to 50 \ -resolution 1 \ -bigincrement 10 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 150 \ -font fontTEMP_varwidth \ -variable VARmaxpcnt \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRcontrols.labelMAXDIST \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRcontrols.scaleMAXDIST \ -side left \ -anchor w \ -fill x \ -expand 0 ###################################################### ## DEFINE LABEL-and-SCALE pair for LINE-WIDTH (pixels). ###################################################### label .fRcontrols.labelLINEWIDTH \ -text "$aRtext(labelLINEWIDTH)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script via proc 'reset_parms'. # set VARlinewidth 1 scale .fRcontrols.scaleLINEWIDTH \ -from 1 -to 10 \ -resolution 1 \ -bigincrement 1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 150 \ -font fontTEMP_varwidth \ -variable VARlinewidth \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRcontrols.labelLINEWIDTH \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRcontrols.scaleLINEWIDTH \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRimgsize' FRAME ---- DEFINE-and-PACK ## 1 LABEL and 1 SCALE widget for Xmax and ## 1 LABEL and 1 SCALE widget for Ymax. ##+################################################################### label .fRimgsize.labelXmaxPx \ -text "$aRtext(labelXmaxPx)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script via proc 'reset_parms'. # set XmaxPx 500 scale .fRimgsize.scaleXmaxPx \ -from 10 -to 3000 \ -resolution 1 \ -bigincrement 1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length $scaleLengthPx \ -font fontTEMP_varwidth \ -variable XmaxPx \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRimgsize.labelXmaxPx \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRimgsize.scaleXmaxPx \ -side left \ -anchor w \ -fill x \ -expand 0 ## DEFINE LABEL-and-SCALE pair for YmaxPx. label .fRimgsize.labelYmaxPx \ -text "$aRtext(labelYmaxPx)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script via proc 'reset_parms'. # set YmaxPx 400 scale .fRimgsize.scaleYmaxPx \ -from 10 -to 3000 \ -resolution 1 \ -bigincrement 1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length $scaleLengthPx \ -font fontTEMP_varwidth \ -variable YmaxPx \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRimgsize.labelYmaxPx \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRimgsize.scaleYmaxPx \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRmsg' FRAME ---- DEFINE-and-PACK 1 LABEL widget. ##+################################################################## label .fRmsg.labelINFO \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bg "#ff9999" \ -bd $BDwidthPx_button pack .fRmsg.labelINFO \ -side left \ -anchor w \ -fill x \ -expand 1 ##+###################################################### ## In the '.fRcanvas' FRAME - ## DEFINE-and-PACK the 'canvas' widget ##+###################################################### ## We set highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+###################################################### canvas .fRcanvas.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 \ -yscrollcommand ".fRcanvas.scrolly set" \ -xscrollcommand ".fRcanvas.scrollx set" scrollbar .fRcanvas.scrolly \ -orient vertical \ -command ".fRcanvas.can yview" scrollbar .fRcanvas.scrollx \ -orient horizontal \ -command ".fRcanvas.can xview" ##+####################################################### ## PACK the widgets in frame '.fRcanvas'. ## ## NOTE: ## NEED TO PACK THE SCROLLBARS BEFORE THE CANVAS WIDGET. ## OTHERWISE THE CANVAS WIDGET TAKES ALL THE FRAME SPACE. ##+####################################################### pack .fRcanvas.scrolly \ -side right \ -anchor e \ -fill y \ -expand 0 pack .fRcanvas.scrollx \ -side bottom \ -anchor s \ -fill x \ -expand 0 ## !!!NEED TO USE '-expand 0' FOR THE X AND Y SCROLLBARS, so that ## the canvas is allowed to fill the remaining frame-space nicely ## --- without a gap between the canvas and its scrollbars. pack .fRcanvas.can \ -side top \ -anchor nw \ -fill none \ -expand 0 ##+######################################## ## END OF the DEFINITION OF THE GUI WIDGETS ##+######################################## ##+############################### ## BINDINGS SECTION: ##+############################### ##+####################################################### ## Start the animation with a button1-release on the ## 'Start' radiobutton. ##+####################################################### bind .fRbuttons.radbuttSTART {animate} ##+####################################################### ## Make ymax-scale move to position of xmax-scale, ## to make it easy to ask for a square image. ##+####################################################### bind .fRimgsize.scaleXmaxPx \ {set_scale_Ymax_equal_Xmax ; advise_user "$aRtext(STARTmsg) with new image size." } ##+##################################################################### ## Remind user to click 'Start' after changing ymax scale value. ##+##################################################################### bind .fRimgsize.scaleYmaxPx \ {advise_user "$aRtext(STARTmsg) with new y-image size."} ##+####################################################### ## Remind user to click 'Start' after changing the ## 'wait time' between drawing each line segment. ##+########################################################## bind .fRcontrols.scaleWAITTIME \ {advise_user "$aRtext(STARTmsg) with new draw-time control."} ##+####################################################### ## Remind user to click 'Start' after changing ## the max-distance (percent of image area size). ##+####################################################### bind .fRcontrols.scaleMAXDIST \ {advise_user "$aRtext(STARTmsg) with new max-distance (percent of image area size)."} ##+###################################################################### ## PROCS SECTION: ## ## - animate - called by a button1-release binding ## on the 'Start' radiobutton. ## ## - setMappingVars_for_px2wc - called by proc 'animate' ## ## - Xwc2px - called by proc 'animate' ## - Ywc2px - called by proc 'animate' ## ## - set_scale_Ymax_equal_Xmax - called by button1-release on the X-scale ## ## - set_bkgd_color - called by bkgd color button '-command' ## ## - set_line_color - called by line color button '-command' ## ## - update_color_button - called by the 'set_*_color*' procs and once ## in the 'Additional GUI Initialization section. ## ## - reset_parms - called by 'Reset' button and in the ## 'Additional GUI Initialization' section, ## to initialize the parms. ## ## - clear_canvas - called by 'ClearCanvas' button ## ## - popup_msgVarWithScroll - called by 'Help' button ## ##+####################################################################### ##+##################################################################### ## PROC: 'animate' ## ## PURPOSE: ## Starts drawing and showing the sequence of randomly generated ## line-segments. ## ## Each new generated point (x,y) is considered to be a snapshot ## of the position of the moving particle taken after a fixed ## time steps, such as 15 seconds or 30 seconds or whatever. ## ## METHOD: ## For each time step, from the previous location (x,y), the new ## location (x+dx,y+dy) is generated by calculating ## dx = rand() * DISTmax ## dy = rand() * DISTmax ## where DISTmax is calculated from VARmaxpcnt, such as ## DISTmax = 2.0 * VARmaxpctn / 100 ## ## We use the Tk command 'create line' on the canvas to draw the ## line segment between (x,y) and (x+dx,y+dy). ## ## CALLED BY: By clicking on the 'Start' animation radiobutton --- ## and, possibly, ## in the 'Additional GUI Initialization' section at the ## bottom of this script, to start with an animation ## going when the GUI first starts up. ##+##################################################################### proc animate {} { ## FOR TESTING: (dummy out this proc) # return global VARanimate0or1 XmaxPx YmaxPx \ VARwaittime VARmaxpcnt VARlinewidth twopi \ COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex \ COLORLINEr COLORLINEg COLORLINEb COLORLINEhex ## Set the current time, for determining elapsed ## time for building the image. set t0 [clock milliseconds] ## Indicate that drawing calculations are starting. advise_user "** PERFORMING CALCULATIONS for first image **" ################################################ ## Clear the canvas of any previous simulation. ################################################ .fRcanvas.can delete all ########################################################## ## Initialize the width & height of the image area that ## we are going to use --- from two scale widget variables. ########################################################## set imgWidthPx $XmaxPx set imgHeightPx $YmaxPx .fRcanvas.can configure -width $imgWidthPx .fRcanvas.can configure -height $imgHeightPx ############################################################ ## Set the 'scrollregion' of the canvas according to the ## requested canvas size. (A simple 'update' does not work.) ############################################################ .fRcanvas.can configure -scrollregion "0 0 $imgWidthPx $imgHeightPx" ######################################################## ## Make the canvas area as big as we can to accomodate ## a large image. ## (We try some 'wm' commands to get the window to resize ## according to the canvas resize --- even after the ## user has manually resized the top window.) ## ## Reference: wiki.tcl.tk/10720 and wiki.tcl.tk/44 ## and page 237 of Ousterhout's book 'Tcl and the Tk Toolkit': ## "If you would like to restore a window to its natural ## size, you can invoke 'wm geometry' with an empty ## geometry string." ######################################################## wm geometry . {} ################################################################ ## Calculate the aspect-ratio of the image area, for use in ## setting world-coordinate limits below. ################################################################ set ASPECTratio [expr { $imgHeightPx / double($imgWidthPx) }] ################################################################ ## In world-coordinates, we imagine the image area ## extending from -1.0 to +1.0 for a total width of 2.0. ## ## We always imagine the width of the image to extend from ## -1.0 to +1.0 in world coordinates. ## We get the image-area height (in world-coordinate units) ## by applying the aspect-ratio to the image-width 2.0. ################################################################ # set imgHeightWC [expr { $ASPECTratio * 2.0}] # set halfImgHeightWC [expr { $ASPECTratio * 1.0}] set halfImgHeightWC $ASPECTratio ################################################################ ## Set the variables for converting pixels to world-coords ## or vice versa. ## ## We do this is in case the user changed the image/canvas ## dimensions before this animation start. ################################################################ ## Recall input vars for proc 'setMappingVars_for_px2wc' are: ## xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY ## ## We map world-coord X-limits -1.0 and 1.0 ## TO pixel-coord X-limits 0 and imgWidthPx ## ## AND ## ## We map world-coord Y-limits -halfImgHeightWC and halfImgHeightWC (top and bottom) ## TO pixel-coord Y-limits 0 and imgHeightPx (top and bottom) ## ## See code in 'setMappingVars_for_px2wc' for details. ######################################################################## set XwcUL -1.0 set YwcUL $halfImgHeightWC set XwcLR 1.0 set YwcLR [expr {-$halfImgHeightWC}] setMappingVars_for_px2wc xy $XwcUL $YwcUL 0 0 $XwcLR $YwcLR $imgWidthPx $imgHeightPx ########################################################################## ## Set the coordinates of the initial point, at origin (0.0,0.0) ## in world-coords --- and in pixel coords. ########################################################################## set curX 0.0 set curY 0.0 set curXpx [Xwc2px $curX] set curYpx [Ywc2px $curY] ########################################################################## ## Initialize a line segment counter and a total distance accumulator. ########################################################################## set segCNT 0 set totDIST 0.0 ########################################################################## ## Set the max distance to move in either direction --- ## in world-coords. ########################################################################## set DISTmax [expr {$VARmaxpcnt * 2.0 / 100.0}] ########################################################################## ## Start the loop for plotting each line segment. ## ## We will break out if VARanimate0or1 is set to 1. ########################################################################## while {1} { ########################################################################## ## Calculate the distance to move to the next point, in the x and y ## directions, in world-coords. ########################################################################## set tempR [expr {rand() * $DISTmax}] set tempA [expr {rand() * $twopi}] set dx [expr {$tempR * cos($tempA)}] set dy [expr {$tempR * sin($tempA)}] ########################################################################## ## Calculate the coordinates of the next point, in world-coords. ########################################################################## set nextX [expr {$curX + $dx}] set nextY [expr {$curY + $dy}] ########################################################################## ## Convert 'next' world coordinates to pixels. ########################################################################## set nextXpx [Xwc2px $nextX] set nextYpx [Ywc2px $nextY] ########################################################################## ## Plot the line segment between (curXpx,curYpx) and (nextXpx,nextYpx). ########################################################################## .fRcanvas.can create line $curXpx $curYpx $nextXpx $nextYpx \ -tags TAGlineseg -fill "$COLORLINEhex" -width $VARlinewidth \ -capstyle round -joinstyle round ###################################################### ## FOR TESTING: ###################################################### if {0} { puts "" puts "Drawing line from" puts "curXpx = $curXpx curYpx = $curYpx" puts "to" puts "nextXpx = $nextXpx nextYpx = $nextYpx" puts "with color $COLORLINEhex" } ###################################### ## Augment the line segment count. ###################################### incr segCNT ############################################################## ## Augment the total distance travelled, in world coordinates. ############################################################## set deltaX [expr {$nextX - $curX}] set deltaY [expr {$nextY - $curY}] set thisDIST [expr {hypot($deltaX,$deltaY)}] # set thisDIST [expr {sqrt($deltaX*$deltaX + $deltaY*$deltaY)}] set totDIST [expr {$totDIST + $thisDIST}] set totDIST [format "%.4f" $totDIST] ############################################################## ## Calculate the current distance from the start point, the origin. ############################################################## set netDIST [expr {hypot($nextX,$nextY)}] # set netDIST [expr {sqrt($nextX*$nextX + $nextY*$nextY)}] set netDIST [format "%.4f" $netDIST] ############################################### ## Show the user the draw-time for this redraw. ############################################### set drawtime [expr {[clock milliseconds] - $t0}] advise_user \ "** $drawtime millisecs elapsed ; SEG# $segCNT ; TotDist $totDIST ; DistFromOrigin $netDIST **" ######################################################################## ## Make sure the image is shown before going on to build the next image. ######################################################################## update ########################################################################## ## Break out of this 'while' loop, if the user requests that the ## animation stop. ########################################################################## if {$VARanimate0or1 == 0} {break} ########################################################################## ## Prepare the cur' variables for the next line segment plot ## both world coordinates and pixel coordinates. ########################################################################## set curX $nextX set curY $nextY set curXpx $nextXpx set curYpx $nextYpx ########################################################################## ## Wait for VARwaittime millisecs before proceeding to plot the next ## line segment. ########################################################################## after $VARwaittime } ## END OF 'while {1}' loop for drawing line segments. ## Reset the cursor from a 'watch' cursor. # . config -cursor {} } ## END OF PROC 'animate' ##+######################################################################## ## PROC 'setMappingVars_for_px2wc' ##+######################################################################## ## PURPOSE: Sets up 'constants' to be used in converting between x,y ## 'world coordinates' and 'pixel coordinates' on a Tk canvas. ## ## Puts the constants in global variables: ## PXperWC BASEwcX BASEwcY BASEpxX BASEpxY ## ## These variables are for use by 'Xwc2px' and 'Ywc2px' procs ## and 'Xpx2wc' and 'Ypx2wc' procs. ## ## The 'BASE' variables are coordinates of the upper-left point ## of the 'plotting rectangle' --- in world coordinates and ## in pixel coordinates. ## ## METHOD: This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit, ## in global variable 'PXperWC' ## ## (This will generally include a fractional amount, ## i.e. it is not necessarily an integer.) ## ## INPUTS: ## ## At least eight numbers are input to this proc, as indicated by: ## ## ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: (for a plot area with x between -1.2 and +1.2 ## and with y between -0.2 and +1.2) ## setMappingVars_for_px2wc xy -1.2 1.2 0 0 1.2 -0.2 $canvasWidthPx $canvasHeightPx ## ## The first argument can be either 'x' or 'y' or 'xy'. This determines whether ## global variable 'PXperWC' is detemined by just the X-numbers, just the Y-numbers, ## or both. In this script, we use 'xy' (both). ## ## An 'adjustYpx' global variable can be used to adjust if the pixels ## on a user's monitor are not square. ## ## OUTPUTS: global variables PXperWC BASEwcX BASEwcY BASEpxX BASEpxY ## ## CALLED BY: by the animate' proc. ##+######################################################################## proc setMappingVars_for_px2wc {xORy ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY} { global PXperWCx PXperWCy BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Calculate PXperWCx and PXperWCy ## (pixels-per-world-coordinate-unit) --- the ratio ## of pixels-per-world-coordinate in the x and y directions, ## for the given UL and LR values. ############################################################ set PXperWCx [expr {abs(($LRpxX - $ULpxX) / ($LRwcX - $ULwcX))}] set PXperWCy [expr {abs(($LRpxY - $ULpxY) / ($LRwcY - $ULwcY))}] ## FOR TESTING: if {0} { puts "proc 'animate':" puts "LRwcY: $LRwcY ULwcY: $ULwcY LRwcX: $LRwcX ULwcX: $ULwcX" puts "PXperWCx: $PXperWCx" puts "LRpxY: $LRpxY ULpxY: $ULpxY LRpxX: $LRpxX ULpxX: $ULpxX" puts "PXperWCy: $PXperWCy" } ############################################################# ## Reset PXperWCx and PXperWCy according to whether input ## variable 'xORy' is 'x' or 'y' or 'min' or 'xy'. ## ## For 'x', we set PXperWCy equal to PCperWcx. ## ## For 'y', we set PXperWCx equal to PCperWcy. ## ## For 'min', we set PXperWCx and PXperWCy to the smaller ## of PXperWCx and PXperWCy. ## ## For 'xy', we will leave PXperWCx and PXperWCy unchanged. ############################################################# if {$xORy == "x"} { set PXperWCy $PXperWCx } elseif {$xORy == "y"} { set PXperWCx $PXperWCy } elseif {$xORy == "min"} { if {$PXperWCx > $PXperWCy} { set PXperWCx $PXperWCy } else { set PXperWCy $PXperWCx } } ## END OF if {$xORy == "x"} ############################################################ ## In case the pixels are not square, provide a factor ## that can be used to adjust in the Y direction. ############################################################ set adjustYpx 1.0 ############################################################ ## Set BASEwcX, BASEwcY, BASEpxX and BASEpxY. ############################################################ set BASEwcX $ULwcX set BASEwcY $ULwcY set BASEpxX $ULpxX set BASEpxY $ULpxY ## FOR TESTING: if {0} { puts "proc 'setMappingVars_for_px2wc':" puts "PXperWCx: $PXperWCx PXperWCy: $PXperWCy" puts "BASEwcX: $BASEwcX BASEwcY: $BASEwcY" puts "BASEpxX: $BASEpxX BASEpxY: $BASEpxY" } } ## END OF PROC 'setMappingVars_for_px2wc' ##+######################################################################## ## PROC 'Xwc2px' ##+######################################################################## ## PURPOSE: Converts an x world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Xwc2px {x} { global PXperWCx BASEwcX BASEpxX set px [expr {($x - $BASEwcX) * $PXperWCx + $BASEpxX}] return $px } ## END OF PROC 'Xwc2px' ##+######################################################################## ## PROC 'Xpx2wc' ##+######################################################################## ## PURPOSE: Converts an x-pixel unit to an x-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Xpx2wc {px} { global PXperWCx BASEwcX BASEpxX set x [expr {( ($px - $BASEpxX) / $PXperWCx ) + $BASEwcX }] return $x } ## END OF PROC 'Xpx2wc' ##+######################################################################## ## PROC 'Ywc2px' ##+######################################################################## ## PURPOSE: Converts an y world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Ywc2px {y} { global PXperWCy BASEwcY BASEpxY adjustYpx set px [expr {($BASEwcY - $y) * $PXperWCy * $adjustYpx + $BASEpxY}] return $px } ## END OF PROC 'Ywc2px' ##+######################################################################## ## PROC 'Ypx2wc' ##+######################################################################## ## PURPOSE: Converts a y-pixel unit to a y-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Ypx2wc {px} { global PXperWCy BASEwcY BASEpxY adjustYpx set y [expr { $BASEwcY - ( ($px - $BASEpxY) / ( $PXperWCy * $adjustYpx ) ) }] return $y } ## END OF PROC 'Ypx2wc' ##+########################################################## ## proc set_scale_Ymax_equal_Xmax ## ## PURPOSE: Make YmaxPx equal XmaxPx when the scale for ## XmaxPx is used. ## ## CALLED BY: a buttoVARfreq1-release binding on .fRimgsize.scaleXmaxPx. ##+########################################################## proc set_scale_Ymax_equal_Xmax {} { global XmaxPx YmaxPx if {$YmaxPx != $XmaxPx} {set YmaxPx $XmaxPx} } ## END OF proc 'set_scale_Ymax_equal_Xmax' ##+##################################################################### ## proc 'set_bkgd_color' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a 'max amplitude' color. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLORbkgd button ##+##################################################################### proc set_bkgd_color {} { global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLORBKGDr: $COLORBKGDr" # puts "COLORBKGDg: $COLORBKGDg" # puts "COLORBKGDb: $COLORBKGDb" set TEMPrgb [ exec $ColorSelectorScript $COLORBKGDr $COLORBKGDg $COLORBKGDb] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLORBKGDhex "#$hexRGB" set COLORBKGDr $r255 set COLORBKGDg $g255 set COLORBKGDb $b255 ## Set the canvas background color. .fRcanvas.can configure -bg "$COLORBKGDhex" ## Set background-and-foreground colors of the indicated color button. update_color_button colorbkgd advise_user "$aRtext(STARTmsg) to use a new color" } ## END OF proc 'set_bkgd_color' ##+##################################################################### ## proc 'set_line_color' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a 'min amplitude' color. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLORline button ##+##################################################################### proc set_line_color {} { global COLORLINEr COLORLINEg COLORLINEb COLORLINEhex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLORLINEr: $COLORLINEr" # puts "COLORLINEg: $COLORLINEg" # puts "COLORLINEb: $COLORLINEb" set TEMPrgb [ exec $ColorSelectorScript $COLORLINEr $COLORLINEg $COLORLINEb] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLORLINEhex "#$hexRGB" set COLORLINEr $r255 set COLORLINEg $g255 set COLORLINEb $b255 ## Set background-and-foreground colors of the indicated color button. update_color_button colorline advise_user "$aRtext(STARTmsg) to use new color." } ## END OF proc 'set_line_color' ##+##################################################################### ## PROC 'update_color_button' ##+##################################################################### ## PURPOSE: ## This procedure is invoked to set the background color of the ## color button, indicated by the 'colorID' string, ## to its currently set 'colorID' color --- and sets ## foreground color, for text on the button, to a suitable black or ## white color, so that the label text is readable. ## ## Arguments: global color vars ## ## CALLED BY: in three 'set_*_color*' procs ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc update_color_button {colorID} { global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex global COLORLINEr COLORLINEg COLORLINEb COLORLINEhex # set colorBREAK 300 set colorBREAK 350 if {"$colorID" == "colorbkgd"} { .fRbuttons.buttCOLORbkgd configure -bg $COLORBKGDhex set sumCOLORBKGD [expr {$COLORBKGDr + $COLORBKGDg + $COLORBKGDb}] if {$sumCOLORBKGD > $colorBREAK} { .fRbuttons.buttCOLORbkgd configure -fg "#000000" } else { .fRbuttons.buttCOLORbkgd configure -fg "#ffffff" } } elseif {"$colorID" == "colorline"} { .fRbuttons.buttCOLORline configure -bg $COLORLINEhex set sumCOLORBKGD [expr {$COLORLINEr + $COLORLINEg + $COLORLINEb}] if {$sumCOLORBKGD > $colorBREAK} { .fRbuttons.buttCOLORline configure -fg "#000000" } else { .fRbuttons.buttCOLORline configure -fg "#ffffff" } } else { ## Seems to be an invalid colorID. return } } ## END OF PROC 'update_color_button' ##+##################################################################### ## PROC 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in three 'set_*_color*' procs, ## in some 'bind' statements in the BIND section above, ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc advise_user {text} { .fRmsg.labelINFO configure -text "$text" ## Make sure the text is displayed on the GUI. update ## Alternatively, we could put the message in the title-bar ## of the GUI window. (But it is easy for the user to ## fail to see the message there. Besides, we have more ## options in displaying the message by putting it on a ## Tk widget in the GUI.) ## # wm title . "$text" } ## END OF PROC 'advise_user' ##+######################################################################## ## PROC 'clear_canvas' ##+######################################################################## ## PURPOSE: To clear the canvas. ## ## CALLED BY: 'ClearCanvas' button ##+######################################################################## proc clear_canvas {} { global aRtext .fRcanvas.can delete all advise_user "$aRtext(STARTmsg) with cleared canvas." } ## END OF PROC 'clear_canvas' ##+######################################################################## ## PROC 'reset_parms' ##+######################################################################## ## PURPOSE: To reset (and initialize) the scale widgets on the GUI. ## ## CALLED BY: 'Reset' button and in the 'Additional GUI Initialization' ## section at the bottom of this script. ##+######################################################################## proc reset_parms {} { global VARwaittime VARmaxpcnt XmaxPx YmaxPx ##+########################################################## ## Set initial value of scale widget variables --- for ## VARwaittime VARmaxpcnt VARlinewidth. ##+########################################################## # set VARwaittime 0 # set VARwaittime 100 set VARwaittime 500 # set VARwaittime 1000 # set VARmaxpcnt 10 set VARmaxpcnt 8 set VARlinewidth 1 ##+################################################# ## Set initial image size. ##+################################################ set XmaxPx 400 set YmaxPx 400 } ## END OF PROC 'reset_parms' ##+######################################################################## ## PROC 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext ULloc} { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 # wm geometry $toplevName +100+50 wm geometry $toplevName $ULloc wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################################################## ## Set the 'HELPtext' var. ##+######################################################## set HELPtext "\ \ ** HELP for this Brownian Motion Animation utility - a 2D simulation ** This Tk GUI script generates an animation of a 'random walk' that resembles 'Brownian motion' in 2D. The intent is to mimic a tiny, light-weight particle, say on the surface of water, being observed under a microscope as the particle is being pummeled, from below, by the water molecules on which the particle is floating. The water molecules pummel the light-weight particle (call it P) from below, striking the particle at various points, not necessarily from directly below the center of the particle. Hence, the particle will be driven in various directions, not just straight up and down. The directions will depend on a direction vector between the striking water molecule and the particle P. Furthermore, the particle will be driven various distances along the 2D plane, depending on the direction of the collision vector, and depending on the velocity of the water molecule (for example, depending on the temperature of the water) and depending on the relative masses of the water molecule and particle P. Alternatively, we could imagine the particle suspended in a fluid and moving in 3D. If we look at the motion from a fixed angle, it will appear to be 2D motion on a planar surface. The animation (a simulation of the moving particle P) is shown in a rectangular image area on a rectangular Tk 'canvas' widget. We will use Tk 'create line' commands (along with the 'rand' function) to create short line segments of random lengths in random directions (between 0 and 360 degrees). ****************************************** METHOD - MATH MODELLING OF THE RANDOM WALK: ****************************************** We assume that there is a maximum distance that the particle can move after each collision with a fluid molecule. We assume, for example, that the max distance is about 10% of a measure of the image area size (say the width). We assume that after each collision between a water molecule and the floating particle, the direction in which the particle moves is equally likely at any of the angles between 0 and 360 degrees. As a simple approximation, we could confine the particle to a square area, and model random motions in x and y directions. Given a 'start' point (x,y), we could add dx and dy to get the location of the end-point of the next line segment, (x+dx,y+dx), where dx = rand(0,1) * DISTmax dy = rand(0,1) * DISTmax and where rand(0,1) represents a random number between 0.0 and 1.0. However, this would bias the movement according to the choice of the direction of the x and y axes, and it would allow the particle to move to the corners of the square, beyond the proposed maximum distance. Hence, we will confine the particle's travel within any given time period to a circle rather than a square. With that assumption, we could generate a random radius 'r' and a random angle 'A' and set r = rand(0,1) * DISTmax A = rand(0,1) * 2 * pi and then set dx = r * cos(A) dy = r * sin(A) We assume the particle always starts at the center point of the image area rectangle. We establish a rectangular coordinate system with origin (0.0,0.0) at the center point. When the image area is square, we map the pixels into a 'world coordinates' system in which the top left of the image area is (-1.0, +1.0) and the lower right of the image area is (+1.0, -1.0). If the image area is not square, we adjust these corner coordinates so that the aspect ratio of the rectangular area is the same in world coordinates as in pixel coordinates. We offer 'Start' and 'Stop' buttons on the GUI by which the user can start and stop the animation. Also we offer a 'Clear' button by which to clear the canvas and continue the simulation. To allow the user to control the speed of the generation of the 'poly-line' which traces the Brownian motion, we provide a widget on the GUI by which the user can specify a 'wait time' between drawing each line segment. We also provide a couple of 'color buttons' on the GUI by which the user can specify a background color for the image area and a color for the line segments. During the animation, the GUI could display some statistics, such as - the total distance traveled so far, which should be an increasing function. - the average distance from the center point (0,0), which will be a slowly increasing function (on average). ************************** PERFORMANCE CONSIDERATIONS: ************************** Since each 'create line' probably completes in less than a millisecond, this Tk script can probably draw a Brownian motion 'path' of about 100 to 1,000 line segments in less than one second --- allowing some time for the computation involved. In fact, on a medium-powered computer of around the year 2015, about 480 lines per second were being drawn --- when the 'wait-time' was set to zero. " ##+##################################################### ##+##################################################### ## ADDITIONAL GUI INITIALIZATION, if needed (or wanted). ##+##################################################### ##+##################################################### ##+##################################################### ## Set the full-name of the RGB color-selector Tk script ## that is used in several procs above. ##+##################################################### ## FOR TESTING: # puts "argv0: $argv0" set DIRthisScript "[file dirname $argv0]" ## For ease of testing in a Linux/Unix terminal and located at the ## directory containing this Tk script. Set the full directory name. if {"$DIRthisScript" == "."} { set DIRthisScript "[pwd]" } set DIRupOne "[file dirname "$DIRthisScript"]" set DIRupTwo "[file dirname "$DIRupOne"]" set ColorSelectorScript "$DIRupTwo/SELECTORtools/tkRGBselector/sho_colorvals_via_sliders3rgb.tk" ## Alternatively: Put the RGB color-selector Tk script in the ## same directory as this Tk script and uncomment the following. # set ColorSelectorScript "$DIRthisScript/sho_colorvals_via_sliders3rgb.tk" #+####################################################### ## Initialize the scale widgets on the GUI --- this ## includes the requested image size. ##+###################################################### reset_parms ##+############################################# ## Configure the canvas size according to the ## initially requested image size. ################################################ .fRcanvas.can configure -width $XmaxPx .fRcanvas.can configure -height $YmaxPx ##+############################################# ## Initialize the bkgd-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative max-color.) ##+############################################# if {1} { ## Black: set COLORBKGDr 0 set COLORBKGDg 0 set COLORBKGDb 0 } else { ## White: set COLORBKGDr 255 set COLORBKGDg 255 set COLORBKGDb 255 } set COLORBKGDhex [format "#%02X%02X%02X" $COLORBKGDr $COLORBKGDg $COLORBKGDb] update_color_button "colorbkgd" .fRcanvas.can configure -bg "$COLORBKGDhex" ##+############################################# ## Initialize the line-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative min-color.) ##+############################################# if {1} { ## White: set COLORLINEr 255 set COLORLINEg 255 set COLORLINEb 255 } else { ## Yellow: set COLORLINEr 255 set COLORLINEg 255 set COLORLINEb 0 } set COLORLINEhex [format "#%02X%02X%02X" $COLORLINEr $COLORLINEg $COLORLINEb] update_color_button "colorline" ##+################################################# ## Set constants to use for angle calculations --- ## in radians (and degrees). ##+################################################ set pi [expr {4.0 * atan(1.0)}] set twopi [expr {2.0 * $pi}] ##+##################################################### ## Advise the user how to start. ##+##################################################### advise_user "** Click Animation 'Start' radiobutton to start animation with current settings. **" ##+##################################################### ## Alternatively, go ahead and start the animation ## as the GUI starts up, using the initial settings. ##+##################################################### if {0} { set VARanimate0or1 1 advise_user "** Click Animation 'Stop' radiobutton to stop animation. **" animate }