#!/usr/bin/wish -f ##+######################################################################## ## ## SCRIPT: tkCircleCircumferenceByPolygons.tk ## ## PURPOSE: This Tk script is meant to demonstrate how one can 'zero in' on ## the value of pi (the ratio of the circumference of a circle ## to its diameter) using polygons that inscribe and circumscribe ## a circle. ## ## This script is meant to 'dynamically' demonstrate the method ## by which Archimedes estimated the value of pi (the ## ratio of circumference to diameter = twice radius). ## ## By 'dynamically' is meant the user can quickly ## 1) DRAW, with a click on a button, the 2 polygons ## (inner and outer) using a user-selected number of sides, ## as well as quickly ## 2) SHOW the calculated circumferences of the 2 polygons ## --- and the ratios of those circumferences to the ## diameter of the circle. ## ## We allow the user to specify a wide variety of radius values ## --- to demonstrate that the ratio (pi) is independent of the ## radius or diameter of the circle. ## ## ON ACCURACY AND THE NUMBER OF SIDES: ## ## NOTE that Archimedes (the 'Sand Reckoner') limited himself to ## using polygons based on a hexagon and doubled the number of ## sides of the polygons at each step, up to a maximum of ## 96 (6 * 16) sides. ## ## According to some accounts, he used hexagon-based polygons so ## that he could use his knowledge of the value of the square root ## of simple integers (such as 2 and 3) to compute the length ## of each side, and add up (or multiply by the number of sides) ## to get the circumference. ## ## This script uses the fact that we can use 'regular' polygons of ## any number of sides (3,4,5,6,7,8,9,10,...) and we can use ## the sine,cosine functions to calculate the length of the side ## of any of the polygons. ## ## Although we are using a computer, this mimics the fact that ## back in the Middle Ages (when they had tables of sines and ## cosines accurate to at least 5 digits), those ancient ## mathematicians could have computed the circumferences of ## these polygons to much better accuracy than Archimedes ## achieved --- by laboriously using their trigonometry tables. ## ## GUI FEATURES: ## This GUI uses a Tk 'canvas' widget to show the circle and ## the polygons inscribed within or circumscribed about the circle. ## ## A 'scale' widget on the GUI allows the user to easily choose ## the number of sides to be drawn. ## ## The line segments of the polygons are drawn with 'create line' ## commands on the canvas and the circle is drawn with a ## 'create oval' command. ## ## We use a square drawing area, and we allow the user to specify ## the size (in pixels) of that drawing area. ## ## In addition, we put a couple of checkbuttons on the GUI ## by which the user can choose whether to draw ## - inscribed polygons ## - circumscribed polygons ## - or both. ## ## When the user clicks a 'ReDraw' button on the GUI ## - the circle and, optionally, one or two polygons are drawn ## and ## - the computed circumference of the one or two polygons ## is displayed --- along with their ratios to the ## diameter of the circle (twice the user-selected radius). ## ## A 'Help' button on the GUI describes how the computations ## are performed using the sine and cosine functions, and the ## user-selected radius and number-of-sides. ## ##+######################### ## PLANNED LAYOUT OF THE GUI: ## ## FrameNames ## VVVVVVVVVV ## ----------------------------------------------------------------------------- ## tkCircleCircumferenceByPolygons ## [window title] ## ----------------------------------------------------------------------------- ## ## .fRbuttons {Exit} {Help} {ReDraw} {Backgd {Circle {Inscribed {Circumscribed ## Color} Color} Polygon Color} Polygon Color} ## ## .fRmsg [ .......... Message line --- for advice to the user .................. ] ## ## 50 3000 ## .fRcontrols1 Image width&height (pixels): <-----O----> Polygon to draw: X Inner X Outer ## ## 3 360 0.1 300.0 ## .fRcontrols2 Number of sides: <------O-----> Radius: <--------O---------> ## ## .fRresult [ .... Calculated Circumferences and Ratios and Errors are shown here .... ] ## ## .fRcanvas ----------------------------------------------------------------------------- ## | | ## | | ## | | ## | 'Canvas' for displaying the circle and polygons | ## | in a square image area. | ## | | ## | | ## | | ## | | ## ----------------------------------------------------------------------------- ## ## SKETCH CONVENTIONS for this GUI sketch: ## ## 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-X indicates a Tk 'checkbutton' widget. ## CAPITAL-O indicates a Tk 'radiobutton' widget (if any). ## UNDERSCORES indicate a Tk 'entry' widget (if any). ## ## A LINE (HYPHENS or VERTICAL-BARS) WITH AN 'ARROW-HEAD' AT EACH END indicates ## a Tk 'scale' widget. ## ## A combination of VERTICAL-BAR CHARACTERS AND HYPHEN (or UNDERSCORE) CHARACTERS, ## that outline a RECTANGULAR SHAPE, are used to indicate either a Tk 'canvas' or ## a Tk 'listbox' widget or a Tk 'text' widget. ## ## SCROLL-BAR 'ARROW-HEADS' (for a 'canvas', 'listbox', or 'text' Tk widget) ## are drawn as follows: ## ## UP ARROW-HEAD is drawn with a CAPITAL-A. ## DOWN ARROW-HEAD is drawn with a CAPITAL-V. ## LEFT ARROW-HEAD is drawn with a LESS-THAN sign. ## RIGHT ARROW-HEAD is drawn with a GREATER-THAN sign. ## ## UP-and-DOWN ARROW-HEADS at the right/left of the box shape indicate ## a VERTICAL SCROLL-BAR there. ## ## LEFT-and-RIGHT ARROW-HEADS at the bottom/top of the box shape indicate ## a HORIZONTAL SCROLL-BAR there. ## ## The arrow-heads on a horizontal scrollbar are joined by hyphens, rather than ## underscores. ## ##+################## ## GUI WIDGET SUMMARY: ## ## This GUI will contain about: ## ## 7 'button' widgets ## 4 'label' widgets ## 3 'scale' widgets ## 1 'canvas' widget (with no x-y scrollbars) ## 2 'checkbutton' widgets ## 0 'radiobutton' widgets ## 0 'entry' widgets ## 0 'listbox' widgets ## 0 'text' widgets ## ##+################################## ## METHOD USED to perform the drawing: ## ## A square image area (canvas) is defined in the canvas frame. ## ## A set of drawing procs are used apply 'world coordinates' to ## the pixel coordinates of the Tk canvas. ## ## Two procs -- 'Xwc2px' and 'Ywc2px' -- are used to convert world ## coordinates to pixel coordinates that are to be used in ## 'create arc' and 'create line' commands --- to draw the ## circle and the polygons, respectively. ## ## The center of the circle is defined in the center of ## the image area at (0.0,0.0) in 'world coordinates'. ## ## If R is the radius chosen by the user with the radius scale ## widget, the world coordinates of the four corners of the ## square image area will be ## - top left (-H,+H) ## - top right (+H,+H) ## - bottom left (-H,-H) ## - bottom right (+H,-H) ## where H is somewhat bigger than R, such as 1.2 * R. ## The factor 1.2 is a 'margin factor'. ## ## Of course, the polygons are also drawn centered at the origin. ## If N is the number of sides chosen by the user with a scale widget, ## the world coordinates of the vertices of the polygons are ## computed with N and sin() and cos() functions. ## ## Some text items might be put on the canvas, such as labels ## for point coordinates or values of angles. ## ##+####################### ## CAPTURING THE GUI IMAGE: ## ## A screen/window capture utility (like 'gnome-screenshot' ## on Linux) can be used to capture the GUI image in a PNG ## or GIF file, say. ## ## If necessary, an image editor (like 'mtpaint' on Linux) ## can be used to crop the window capture image. The image ## could also be down-sized --- say to make a smaller image ## suitable for use in a web page or an email. ## ##+####################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name, win-position, win-color-scheme, ## fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack the frames. ## ## 2) Define & pack all widgets in the frames, frame by frame. ## After all the widgets for a frame are defined, pack them in the frame. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event' ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or more of ## the procs), if needed. ## ##+################################# ## Some detail of the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : ## '.fRbuttons' - to contain 'Exit', 'Help', 'Redraw' and ## several color buttons. ## ## '.fRmsg' - to contain a label widget ## ## '.fRcontrols1' - to contain a couple of checkbutton widgets ## ## '.fRcontrols2' - to contain a couple of scale widgets ## ## '.fRresult' - to contain a label widget ## ## '.fRcanvas' - to contain a canvas widget, which will display ## the circle and polygon(s). ## ## 1b) Pack ALL frames. ## ## 2) Define & pack all widgets in the frames -- basically going through ## frames & their interiors in left-to-right, or top-to-bottom order. ## ## 3) Define BINDINGS: such as button1-release on the 'scale' widget ## ## 4) Define PROCS: (for more details on these procs, see the PROCS section below) ## ## 'setMappingVars_for_px2wc' - can be called in drawing proc(s) below to set ## up parameters to be used in converting between ## 'world coordinates' and 'pixel coordinates' ## on the Tk canvas. ## ## 'set_MappingVars_forImageSquare' - In this app/script, we use a square drawing area. ## This proc uses the 'setMappingVars_for_px2wc' ## proc to set up the conversion parameters ## to use in this square drawing area. ## ************* ## Drawing procs: ## ************* ## ## 'Xwc2px' - To convert an x world-coordinate to pixels, ## to be used in the following 'draw_*' procs. ## ## 'Ywc2px' - To convert a y world-coordinate to pixels, ## to be used in the following 'draw_*' procs. ## ## 'draw_circle' - Given radius R in 'world coordinates', ## this proc draws a circle on the canvas. ## ## 'draw_inner_polygon' - Given N, number of sides, this proc draws ## an inscribed polygon on the canvas. ## ## 'draw_outer_polygon' - Given N, number of sides, this proc draws ## an circumscribed polygon on the canvas. ## ##--------- End of 'draw' utility procs. ## ## 'Redraw' - This proc can be used to erase and redraw the ## circle, polygons, etc. on the canvas. ## This proc calls the 'draw_circle', ## 'draw_inner_polygon', and 'draw_outer_polygon' procs. ## ## This proc is called by the 'Redraw' button and ## in the 'Additional GUI Initialization' section ## at the bottom of this script to put an initial ## drawing on the canvas. ## ## Other utility procs: ## ## 'set_background_color' - Sets the color for the canvas (background). ## ## 'set_circle_color' - Sets the color for the circle on the canvas. ## ## 'set_inscribed_color' - Sets the color for the inscribed polygon. ## ## 'set_circumscribed_color' - Sets the color for the circumscribed polygon. ## ## 'update_button_colors' - Sets the color of the 4 color buttons. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## ## ## 5) Additional GUI Initialization: ## - call 'Redraw' to put the drawing on the canvas, for a given ## initial settings of the Tk scale widgets and the checkbuttons. ## ##+####################################################################### ## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala') ## ## $ wish ## % puts "$tcl_version $tk_version" ## ## showed ## 8.5 8.5 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+####################################################################### ## MAINTENANCE HISTORY: ## Started by: Blaise Montandon 2016mar14 ## Changed by: Blaise Montandon 2016mar23 Added a checkbutton for a new ## VARrotatePoly0or1 variable. ##+######################################################################## ##+###################################################### ## Set WINDOW TITLE and POSITION. ##+###################################################### wm title . "tkCircleCircumferenceByPolygons" wm iconname . "CircleAndPolygons" wm geometry . +8+30 ##+###################################################### ## Set the COLOR SCHEME for the window and its widgets --- ## such as listbox and entry field background color. ##+###################################################### tk_setPalette "#f0f0f0" set scaleBKGD "#f0f0f0" set chkbuttBKGD "#c0c0c0" # set radbuttBKGD "#c0c0c0" # set entryBKGD "#ffffff" # set listboxBKGD "#ffffff" ##+######################################################## ## DEFINE (temporary) FONT NAMES. ## ## We use a VARIABLE-WIDTH font for text on LABEL and ## BUTTON widgets. ## ## We use a FIXED-WIDTH font for LISTBOX lists, ## for Help-text in a TEXT widget, and for ## the text in 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) ##+########################################################### ## LABEL widget geom settings: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 set RELIEF_label_lo "flat" ## BUTTON widget geom settings: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## We generally default to relief "raised" for all 'button' widgets. ## BUT, in case you want to experiment: set RELIEF_button "raised" ## CHECKBUTTON geom parameters: set PADXpx_chkbutt 0 set PADYpx_chkbutt 0 set BDwidthPx_chkbutt 1 set RELIEF_chkbutt_hi "raised" ## SCALE widget geom parameters: set BDwidthPx_scale 2 set scaleThicknessPx 10 ## CANVAS widget geom settings: set initCanWidthPx 300 set initCanHeightPx 300 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 set RELIEF_canvas "flat" ## ENTRY widget geom settings: # set BDwidthPx_entry 2 ##+############################################################## ## 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 the '.fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonREDRAW) "ReDraw" set aRtext(buttonCOLORbkgd) "Backgrnd Color" set aRtext(buttonCOLORcircle) "Circle Color" set aRtext(buttonCOLORinner) "Inscribed Polygon Color" set aRtext(buttonCOLORouter) "Circumscribed Polygon Color" set aRtext(chkbuttROTATEpoly) "Rotate outer polygon 1/2 sector" ## For the '.fRcontrols1' frame: set aRtext(labelIMGSIZE) "Image width & height (in pixels):" set aRtext(labelPOLYDRAW) "Polygon to draw:" set aRtext(chkbuttINNERPOLY) "Inscribed" set aRtext(chkbuttOUTERPOLY) "Circumscribed" ## For the '.fRcontrols2' frame: set aRtext(labelNUMSIDES) "Number of sides on the polygons:" set aRtext(labelRADIUS) "Circle Radius:" ## END OF if { "$VARlocale" == "en"} ##+###################################################################### ## Set a MIN-SIZE of the window (roughly). ## ## For WIDTH, allow for the min-width of the '.fRbuttons' frame. ## ## For HEIGHT, allow for the stacked frames: ## 2 chars high for the '.fRbuttons' frame, ## 1 char high for the '.fRmsg' frame, ## 2 chars high for the '.fRcontrols1' frame, ## 2 chars high for the '.fRcontrols2' frame, ## 1 char high for the '.fRresult' frame, ## at least 50 pixels high for the '.fRcanvas' frame. ##+##################################################################### ## FOR WIDTH: set minWidthPx [font measure fontTEMP_varwidth \ " $aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonREDRAW) \ Color Color Color Color "] ## We add some pixels to account for right-left-size of ## window-manager decoration (~8 pixels) and some pixels for ## frame/widget borders (7 widgets x 4 pixels/widget = 28 pixels). set minWinWidthPx [expr {36 + $minWidthPx}] ## For HEIGHT --- for ## 2 chars high for the '.fRbuttons' frame, ## 1 char high for the '.fRmsg' frame, ## 2 chars high for the '.fRcontrols1' frame, ## 2 chars high for the '.fRcontrols2' frame, ## 1 char high for the '.fRresult' frame, ## 50 pixels high for the '.fRcanvas' frame. set charHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {8 * $charHeightPx}] ## Add about 50 pixels for height of the canvas ## AND add about 20 pixels for top-bottom window decoration -- ## and some pixels for top-and-bottom of frame/widget borders ## (5 widgets x 4 pixels/widget = 20 pixels). set minWinHeightPx [expr {90 + $minWinHeightPx}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRmsg' ## '.fRcontrols1' '.fRcontrols2' ## '.fRresult' '.fRcanvas' ## ## Sub-frames: none ##+################################################################ ## 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 -bd $BDwidthPx_frame # frame .fRmsg -relief $RELIEF_frame -bd $BDwidthPx_frame frame .fRmsg -relief raised -bd 2 frame .fRcontrols1 -relief $RELIEF_frame -bd $BDwidthPx_frame frame .fRcontrols2 -relief $RELIEF_frame -bd $BDwidthPx_frame # frame .fRresult -relief $RELIEF_frame -bd $BDwidthPx_frame frame .fRresult -relief raised -bd 2 # frame .fRcanvas -relief $RELIEF_frame -bd $BDwidthPx_frame frame .fRcanvas -relief raised -bd 2 ##+############################## ## PACK the FRAMES. ##+############################## pack .fRbuttons \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRmsg \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcontrols1 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcontrols2 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRresult \ -side top \ -anchor nw \ -fill both \ -expand 1 pack .fRcanvas \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+########################################################## ## The FRAMES ARE PACKED. START PACKING WIDGETS IN THE FRAMES. ##+########################################################## ##+########################################################## ## In FRAME '.fRbuttons' - ## DEFINE-and-PACK 'BUTTON' WIDGETS ## --- Exit, Help, Redraw --- and 4 color buttons. ##+########################################################## 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" +150+50} button .fRbuttons.buttREDRAW \ -text "$aRtext(buttonREDRAW)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {Redraw} button .fRbuttons.buttCOLORbkgd \ -text "$aRtext(buttonCOLORbkgd)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_background_color" button .fRbuttons.buttCOLORcircle \ -text "$aRtext(buttonCOLORcircle)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_circle_color" button .fRbuttons.buttCOLORinner \ -text "$aRtext(buttonCOLORinner)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_inscribed_color" button .fRbuttons.buttCOLORouter \ -text "$aRtext(buttonCOLORouter)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_circumscribed_color" set VARrotatePoly0or1 0 checkbutton .fRbuttons.chkbuttROTATEpoly \ -text "$aRtext(chkbuttROTATEpoly)" \ -font fontTEMP_varwidth \ -variable VARrotatePoly0or1 \ -selectcolor "$chkbuttBKGD" \ -padx $PADXpx_chkbutt \ -pady $PADYpx_chkbutt \ -relief $RELIEF_chkbutt_hi \ -bd $BDwidthPx_chkbutt ## Pack the widgets in frame '.fRbuttons'. pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREDRAW \ .fRbuttons.buttCOLORbkgd \ .fRbuttons.buttCOLORcircle \ .fRbuttons.buttCOLORinner \ .fRbuttons.buttCOLORouter \ .fRbuttons.chkbuttROTATEpoly \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRmsg' - ## DEFINE one LABEL widget. Then PACK it. ##+####################################################### label .fRmsg.labelMSG \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bg "#ff9999" \ -bd $BDwidthPx_label ## Pack the widgets in frame '.fRmsg'. pack .fRmsg.labelMSG \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+########################################################## ## In FRAME '.fRcontrols1' - ## DEFINE a LABEL widget and a SCALE widget, and ## a LABEL widget and 2 CHECKBUTTON widgets. ## Then PACK THEM. ##+########################################################## label .fRcontrols1.labelIMGSIZE \ -text "$aRtext(labelIMGSIZE)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARimgpixels 300 scale .fRcontrols1.scaleIMGSIZE \ -from 50 -to 3000 \ -resolution 1 \ -font fontTEMP_SMALL_varwidth \ -variable VARimgpixels \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 180 \ -width $scaleThicknessPx label .fRcontrols1.labelPOLYDRAW \ -text "$aRtext(labelPOLYDRAW)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label set VARinnerPoly0or1 1 checkbutton .fRcontrols1.chkbuttINNERPOLY \ -text "$aRtext(chkbuttINNERPOLY)" \ -font fontTEMP_varwidth \ -variable VARinnerPoly0or1 \ -selectcolor "$chkbuttBKGD" \ -padx $PADXpx_chkbutt \ -pady $PADYpx_chkbutt \ -relief $RELIEF_chkbutt_hi \ -bd $BDwidthPx_chkbutt set VARouterPoly0or1 1 checkbutton .fRcontrols1.chkbuttOUTERPOLY \ -text "$aRtext(chkbuttOUTERPOLY)" \ -font fontTEMP_varwidth \ -variable VARouterPoly0or1 \ -selectcolor "$chkbuttBKGD" \ -padx $PADXpx_chkbutt \ -pady $PADYpx_chkbutt \ -relief $RELIEF_chkbutt_hi \ -bd $BDwidthPx_chkbutt ## Pack ALL widgets in the '.fRcontrols1' frame. pack .fRcontrols1.labelIMGSIZE \ .fRcontrols1.scaleIMGSIZE \ .fRcontrols1.labelPOLYDRAW \ .fRcontrols1.chkbuttINNERPOLY \ .fRcontrols1.chkbuttOUTERPOLY \ -side left \ -anchor w \ -fill none \ -expand 0 ##+########################################################## ## In FRAME '.fRcontrols2' - ## DEFINE 2 pairs of LABEL and SCALE widgets. Then PACK THEM. ##+########################################################## label .fRcontrols2.labelNUMSIDES \ -text "$aRtext(labelNUMSIDES)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARnumsides 6 scale .fRcontrols2.scaleNUMSIDES \ -from 3 -to 360 \ -resolution 1 \ -font fontTEMP_SMALL_varwidth \ -variable VARnumsides \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 180 \ -width $scaleThicknessPx label .fRcontrols2.labelRADIUS \ -text "$aRtext(labelRADIUS)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARradius 3.0 scale .fRcontrols2.scaleRADIUS \ -from 0.1 -to 300.0 \ -resolution 0.1 \ -font fontTEMP_SMALL_varwidth \ -variable VARradius \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 180 \ -width $scaleThicknessPx ## PACK the widgets in the '.fRcontrols2' frame. pack .fRcontrols2.labelNUMSIDES \ .fRcontrols2.scaleNUMSIDES \ .fRcontrols2.labelRADIUS \ .fRcontrols2.scaleRADIUS \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRresult' - ## DEFINE one LABEL widget. Then PACK it. ##+####################################################### label .fRresult.labelRESULT \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Pack the widgets in frame '.fRresult'. pack .fRresult.labelRESULT \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+######################################################## ## In FRAME '.fRcanvas' - ## DEFINE a CANVAS WIDGET (no scrollbars). Then PACK it. ## ## We 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 DEFINITION of the GUI widgets. ##+################################################## ## Start of BINDINGS, PROCS, Added-GUI-INIT sections. ##+################################################## ##+################################################################## ##+################################################################## ## BINDINGS SECTION: ## Since the user may want to make changes to several controls ## on the GUI before doing a redraw, we do not do any 'auto-redraws' ## here. We let the user decide when to do a redraw, by clicking ## on the 'ReDraw' button. ##+################################################################## bind .fRbuttons.chkbuttROTATEpoly {advise_user \ "** The Rotate-Outer-Poly checkbutton was changed. Click 'ReDraw' when ready to recalculate. **"} # bind .fRcontrols1.chkbuttINNERPOLY {Redraw} # bind .fRcontrols1.chkbuttOUTERPOLY {Redraw} ##+################################################################## ##+################################################################## ## PROCS SECTION: ## ## 'setMappingVars_for_px2wc' - to set up constants to be used in converting ## between 'world coordinates' and 'pixel coordinates' ## on the Tk canvas. ## ## Called by 'set_MappingVars_forImageSquare' proc below. ## ## 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 be used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit, ## in global variable 'PXperWC'. ## ## The global variables set by this proc are used ## in the 'Xwc2px' and 'Ywc2px' procs below. ## ## For more detail on this proc, see the ## comments in the proc (below). ## ## 'set_MappingVars_forImageSquare' - Uses the 'setMappingVars_for_px2wc' proc ## to set up mapping constants for the ## *square* image area that is used in this app. ## ## Called in the 'draw_*' procs below, if an ## argument 'mapcoords0or1' is set to 1. ## ##-------- Draw-canvas-item procs: ## ## 'Xwc2px' - Converts an x world-coordinate to pixel units. ## Called in the following 'draw' procs. ## ## 'Ywc2px' - Converts a y world-coordinate to pixel units. ## Called in the following 'draw' procs. ## ## (Note: The following 'draw' procs take world-coordinates ## as input. The 'Xwc2px' and 'Ywc2px' procs are used ## by the 'draw' procs to calculate pixel coordinates for ## the Tk canvas 'create' commands.) ## ## 'draw_circle' - Given radius R in 'world coordinates', ## this proc draws a circle on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the circle.) ## (The circle drawn may be passed a tag, such as ## 'TAGcircle'.) ## ## 'draw_inner_polygon' - Given N, number of sides, and R, this proc draws ## an inscribed polygon on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the polygon lines.) ## (The polygon drawn may be passed a tag, such as ## 'TAGinnerpoly'.) ## ## 'draw_outer_polygon' - Given N, number of sides, and R, this proc draws ## an circumscribed polygon on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the polygon lines.) ## (The polygon drawn may be passed a tag, such as ## 'TAGouterpoly'.) ## ##--------- End of 'draw-canvas-item' procs. ## ## 'Redraw' - This proc is used to erase and redraw the ## circle and polygons on the canvas --- for example, ## after the user has specified a new size for the ## image square --- or after the number-of-sides ## (or radius) scale widget slider is moved. ## ## This proc is called by the 'Redraw' button and ## in the 'Additional GUI Initialization' section ## at the bottom of this script to put an initial drawing ## on the canvas. ## ## Other utility procs: ## ## 'set_background_color' - Sets the color for the canvas (background). ## ## 'set_circle_color' - Sets the color for the circle on the canvas. ## ## 'set_inscribed_color' - Sets the color for the inscribed polygon. ## ## 'set_circumscribed_color' - Sets the color for the circumscribed polygon. ## ## 'update_button_colors' - Sets the color of the 4 color buttons. ## ## 'advise_user' - Used by various procs to put a message for ## the user on a message line of the GUI. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## ##+################################################################# ##+######################################################################## ## 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 'setMappingVars_forImageSquare' 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 "" puts "proc 'setMappingVars_for_px2wc':" puts "ULwcX: $ULwcX ULwcY: $ULwcY LRwcX: $LRwcX LRwcY: $LRwcY" puts "PXperWCx: $PXperWCx" puts "ULpxX: $ULpxX ULpxY: $ULpxY LRpxX: $LRpxX LRpxY: $LRpxY" 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 'set_MappingVars_forImageSquare' ##+######################################################################## ## PURPOSE: Sets world-coordinates of the 4 corners of the ## square image-drawing area and calls on the proc ## 'setMappingVars_for_px2wc' to set the global variables ## for mapping between 'world coordinates' and pixel coordinates. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc set_MappingVars_forImageSquare {} { global marginFactor VARradius VARimgpixels ################################################################## ## Set height parameter of the image square, in world coordinates. ################################################################## set tempH [expr {$marginFactor * $VARradius}] ################################################################ ## Initialize the width & height of the image area that we ## are going to use --- from the 'imgsize' scale widget variable. ################################################################ set imgWidthPx $VARimgpixels set imgHeightPx $VARimgpixels .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 . {} ################################################################ ## Set the variables for converting pixels to world-coords ## or vice versa. ## ## We do this is in case the user changed the requested ## image size before the next draw on the image area ## and since the last time we set the mapping parms. ################################################################ ## Recall input vars for proc 'setMappingVars_for_px2wc' are: ## xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY ## ## We map world-coord X-limits -tempH and +tempH ## TO pixel-coord X-limits 0 and imgWidthPx ## ## AND ## ## We map world-coord Y-limits +tempH and -tempH (top and bottom) ## TO pixel-coord Y-limits 0 and imgHeightPx (top and bottom) ## ## See code in 'setMappingVars_for_px2wc' for details. ######################################################################## set XwcUL [expr {-$tempH}] set YwcUL $tempH set XwcLR $tempH set YwcLR [expr {-$tempH}] ## FOR TESTING: if {0} { puts "" puts "proc 'set_MappingVars_forImageSquare':" puts "XwcUL: $XwcUL YwcUL: $YwcUL XwcLR: $XwcLR YwcLR: $YwcLR" puts "imgWidthPx: $imgWidthPx imgHeightPx: $imgHeightPx" } setMappingVars_for_px2wc xy $XwcUL $YwcUL 0 0 $XwcLR $YwcLR $imgWidthPx $imgHeightPx } ## END OF PROC 'set_MappingVars_forImageSquare' ##+######################################################################## ## 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 '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 'draw_circle' ##+######################################################################## ## PURPOSE: Draws the circle on the square image area on the canvas. ## ## METHOD: We draw the circle at the center of the image area --- using the ## user-specified radius (in the 'world coordinates'). ## ## We use 'create arc' on the square canvas, after converting ## the bounding box from world coordinates to pixel coordinates. ## ## CALLED BY: proc 'Redraw' ##+####################################################################### proc draw_circle {mapcoords0or1} { ## FOR TESTING: (to dummy out this proc) # return global VARradius COLORCIRCLEhex lineWidthPx ################################################################## ## Set the mapping parameters for the drawing square, ## if the 'mapcoords0or1' indicator calls for this. ################################################################## if {$mapcoords0or1 == 1} {set_MappingVars_forImageSquare} ################################################################## ## In preparation for the 'create arc' command below: ## Set coords for the corners of the square circumscribing the ## circle (the bounding square), in world coordinates. ################################################################## set topleftX [expr {-$VARradius}] set topleftY $VARradius set botrightX $VARradius set botrightY [expr {-$VARradius}] ############################################### ## Convert world-coords to pixels. ############################################### set topleftXpx [Xwc2px $topleftX] set topleftYpx [Ywc2px $topleftY] set botrightXpx [Xwc2px $botrightX] set botrightYpx [Ywc2px $botrightY] ## FOR TESTING: if {0} { puts "" puts "proc 'draw_circle':" puts "topleftX: $topleftX topleftY: $topleftY" puts "botrightX: $botrightX botrightY: $botrightY" puts "topleftXpx: $topleftXpx topleftYpx: $topleftYpx" puts "botrightXpx: $botrightXpx botrightYpx: $botrightYpx" } ##################################################### ## Draw a circle at origin (0.0,0.0) in world coords, ## using pixel coords. Use 'create arc' ################################################ .fRcanvas.can create arc \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ -start 0 -extent 359.9 \ -style arc -outline $COLORCIRCLEhex \ -width $lineWidthPx -tag TAGcircle } ## END OF PROC 'draw_circle' ##+######################################################################## ## PROC 'draw_inner_polygon' ##+######################################################################## ## PURPOSE: Draws the inscribed polygon on the canvas using ## the user-selected num-sides and radius. ## ## MEHOD: We draw these polygon faces with vertices touching (lying on) ## the circle. ## ## The 'increment angle' for generating the faces is (twopi/num-sides). ## ## We start the drawing with the vertex at angle 0 ## (on the right extreme of the circle). ## ## CALLED BY: proc 'Redraw' ##+######################################################################## proc draw_inner_polygon {mapcoords0or1} { ## FOR TESTING: (dummy out this proc) # return ## INPUT vars: global VARnumsides twopi VARradius lineWidthPx dashPattern COLORINNERPOLYhex ## OUTPUT vars: global innerCIRCUM ################################################################## ## Set the mapping parameters for the drawing square, ## if the 'mapcoords0or1' indicator calls for this. ################################################################## if {$mapcoords0or1 == 1} {set_MappingVars_forImageSquare} ############################################### ## Set the angle increment (in radians). ############################################### set incrANGrads [expr {$twopi / $VARnumsides}] ####################################################### ## Set the coordinates of the first point of the ## inscribed polygon --- world-coords and pixel-coords ## --- from which we will start drawing. ####################################################### set x1 $VARradius set x1Px [Xwc2px $x1] set y1 0.0 set y1Px [Ywc2px $y1] ############################################## ## Zero the circumference accumulator. ############################################## set innerCIRCUM 0.0 ####################################################### ## LOOP to draw the inscribed polygon. ####################################################### for {set i 1} {$i <= $VARnumsides} {incr i} { ############################################## ## Set (x2,y2) --- the world-coords of the next ## point on the inscribed polygon. ############################################## set tempANG [expr {$i * $incrANGrads}] set x2 [expr {$VARradius * cos($tempANG)}] set y2 [expr {$VARradius * sin($tempANG)}] ############################################## ## Add to the circumference accumulator. ############################################## set tempDIST [expr {hypot($x2 - $x1, $y2 - $y1)}] set innerCIRCUM [expr {$innerCIRCUM + $tempDIST}] ############################################### ## Convert x2,y2 world-coord units to pixels. ############################################### set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] ############################################## ## Draw the line from x1,y1 to x2,y2 --- ## using pixel coordinates. ############################################## .fRcanvas.can create line \ $x1Px $y1Px $x2Px $y2Px \ -fill $COLORINNERPOLYhex -width $lineWidthPx \ -dash $dashPattern -tags TAGinnerpoly ## FOR TESTING: if {0} { puts "" puts "proc 'draw_inner_polygon':" puts "incrANGrads: $incrANGrads" puts "tempANG: $tempANG" puts "x1: $x1 y1: $y1" puts "x1Px: $x1Px y1Px: $y1Px" puts "x2: $x2 y2: $y2" puts "x2Px: $x2Px y2Px: $y2Px" } ########################################### ## Set x1,y1 from x2,y2 to prepare for next ## iteration of this line-drawing loop. ########################################### set x1 $x2 set x1Px $x2Px set y1 $y2 set y1Px $y2Px } ## END OF LOOP to draw the inscribed polygon. } ## END OF PROC 'draw_inner_polygon' ##+######################################################################## ## PROC 'draw_outer_polygon' ##+######################################################################## ## PURPOSE: Draws the circumscribed polygon on the canvas using ## the user-selected num-sides and radius. ## ## METHOD: We draw these polygon faces with the middle of each face ## touching the circle. ## ## The 'increment angle' for generating the faces is (twopi/num-sides). ## ## The vertices of this outer polygon line on an 'outer circle' ## of radius Douter = VARradius/cos(half-the-increment-angle). ## ## We start drawing with the vertex at half-the-increment-angle ## below the postive x-axis, i.e. on the right of the circle. ## ## CALLED BY: proc 'Redraw' ##+######################################################################## proc draw_outer_polygon {mapcoords0or1} { ## FOR TESTING: (dummy out this routine) # return ## INPUT vars: global VARnumsides twopi VARradius lineWidthPx dashPattern \ COLOROUTERPOLYhex VARrotatePoly0or1 ## OUTPUT vars: global outerCIRCUM ################################################################## ## Set the mapping parameters for the drawing square, ## if the 'mapcoords0or1' indicator calls for this. ################################################################## if {$mapcoords0or1 == 1} {set_MappingVars_forImageSquare} ############################################### ## Set the angle increment (in radians). ############################################### set incrANGrads [expr {$twopi / $VARnumsides}] ############################################### ## Set the radius of the 'outer circle'. ############################################### set halfincrANGrads [expr {-$incrANGrads / 2.0}] set Douter [expr {$VARradius / cos($halfincrANGrads)}] ####################################################### ## Set the coordinates of the first point of the ## circumscribed polygon --- world-coords and pixel-coords ## --- from which we will start drawing. This depends ## on the checkbutton setting of VARrotatePoly0or1. ## ## Note that the circumscribed polygon has each side ## being tangent to the circle with the TANGENT POINT ## in the MIDDLE of each polygon side. ####################################################### if {$VARrotatePoly0or1 == 0} { ## We imagine a vertical outer-poly side that is tangent ## to the circle at the point where the positive x-axis ## crosses the circle. We start at the bottom point ## of that vertical side. ## This point is below the positive x-axis on the ## right of the bounded circle --- displaced from the ## x-axis by half the 'increment angle'. ## Let 'r' be the radius of the circle and 'Douter' is ## the distance from the circle-center to the end-points ## of the outer polygon. ## Then the (x1,y1) coordinate of the end-point is ## (VARradius, Douter*sin(-halfincrANGrads). set x1 $VARradius set x1Px [Xwc2px $x1] set initANGrads [expr {-$halfincrANGrads}] set y1 [expr {$Douter * sin($initANGrads)}] set y1Px [Ywc2px $y1] } else { ## We imagine a side with an end-point that is ## on the positive x-axis. If 'r' is the radius of ## the circle and D is the distance from the circle-center ## to that end-point, r/D = cos(halfincrANGrads). ## So D = r/cos(halfincrANGrads). ## Then the (x1,y1) coordinate of the end-point is (D,0.0). set x1 [expr {$VARradius / cos($halfincrANGrads)}] set x1Px [Xwc2px $x1] set initANGrads 0.0 set y1 0.0 set y1Px [Ywc2px $y1] } ############################################## ## Zero the circumference accumulator. ############################################## set outerCIRCUM 0.0 ################################################### ## LOOP to draw the sides of the outer polygon. ################################################### for {set i 1} {$i <= $VARnumsides} {incr i} { ############################################## ## Set (x2,y2) --- the world-coords of the next ## point on the inscribed polygon. ############################################## set tempANG [expr {$initANGrads + ($i * $incrANGrads)}] set x2 [expr {$Douter * cos($tempANG)}] set y2 [expr {$Douter * sin($tempANG)}] ############################################## ## Add to the circumference accumulator. ############################################## set tempDIST [expr {hypot($x2 - $x1, $y2 - $y1)}] set outerCIRCUM [expr {$outerCIRCUM + $tempDIST}] ############################################### ## Convert x2,y2 world-coord units to pixels. ############################################### set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] ############################################## ## Draw the line from x1,y1 to x2,y2 --- ## using pixel coordinates. ############################################## .fRcanvas.can create line \ $x1Px $y1Px $x2Px $y2Px \ -fill $COLOROUTERPOLYhex -width $lineWidthPx \ -dash $dashPattern -tags TAGouterpoly ## FOR TESTING: if {0} { puts "" puts "proc 'draw_outer_polygon':" puts "incrANGrads: $incrANGrads" puts "tempANG: $tempANG" puts "x1: $x1 y1: $y1" puts "x1Px: $x1Px y1Px: $y1Px" puts "x2: $x2 y2: $y2" puts "x2Px: $x2Px y2Px: $y2Px" } ########################################### ## Set x1,y1 from x2,y2 to prepare for next ## iteration of this line-drawing loop. ########################################### set x1 $x2 set x1Px $x2Px set y1 $y2 set y1Px $y2Px } ## END OF THE LOOP to draw the outer polygon. } ## END OF PROC 'draw_outer_polygon' ##+############################################################# ## PROC 'Redraw' ##+############################################################# ## PURPOSE: Used to erase and redraw the circle, polygons, etc. ## on the canvas --- for example, ## after the user has resized the GUI window. ## ## This proc also is used to update the text in ## the label of the '.fRresult' frame. ## ## CALLED BY: the ReDraw button and by ## the 'Additional GUI Initialization' section to ## draw an initial circle and polygon(s) on the canvas. ##+############################################################# proc Redraw {} { ## FOR TESTING: (dummy out this routine) # return global innerCIRCUM outerCIRCUM pi decimalDIGITS \ VARradius VARinnerPoly0or1 VARouterPoly0or1 ############################################# ## Clear the canvas and the msg line. ## Also zero the cirumference variable.s ############################################# .fRcanvas.can delete all .fRresult.labelRESULT configure -text "" set innerCIRCUM 0.0 set outerCIRCUM 0.0 ################################################ ## Redraw the circle. Include a request to reset ## the world-coord/pixel-coord mapping parms. ################################################ # .fRcanvas.can delete -tag TAGcircle draw_circle 1 ################################################### ## Redraw the inner-polygon - WITHOUT a request to ## reset the world-coord/pixel-coord mapping parms. ################################################### if {$VARinnerPoly0or1 == 1} { # .fRcanvas.can delete -tag TAGinnerpoly draw_inner_polygon 0 } ################################################### ## Redraw the outer-polygon - WITHOUT a request to ## reset the world-coord/pixel-coord mapping parms. ################################################### if {$VARouterPoly0or1 == 1} { # .fRcanvas.can delete -tag TAGouterpoly draw_outer_polygon 0 } ## FOR TESTING: if {0} { puts "" puts "proc 'Redraw':" puts "innerCIRCUM: $innerCIRCUM outerCIRCUM: $outerCIRCUM" puts "VARinnerPoly0or1: $VARinnerPoly0or1 VARouterPoly0or1: $VARouterPoly0or1" } ################################################################ ## Put the circumference of the polygons in the message label. ## --- along with their ratio to the radius (or twice the radius) ## if they are non-zero. ################################################################ set diam [expr {2.0 * $VARradius}] set innerTEXT "" set outerTEXT "" set innerERRORtext "" set outerERRORtext "" if {$innerCIRCUM > 0.0 } { set innerCIRCUMfmt [format "%.${decimalDIGITS}f" $innerCIRCUM] set innerRATIO [expr {$innerCIRCUM /$diam}] set innerRATIOfmt [format "%.${decimalDIGITS}f" $innerRATIO] set innerERROR [expr {$innerRATIO - $pi}] set innerERRORfmt [format "%.${decimalDIGITS}f" $innerERROR] set innerERRORtext "InnerError: $innerERRORfmt" set innerTEXT "InnerPolygonCircumference: $innerCIRCUMfmt CircumOverDiam: $innerRATIOfmt" } if {$outerCIRCUM > 0.0 } { set outerCIRCUMfmt [format "%.${decimalDIGITS}f" $outerCIRCUM] set outerRATIO [expr {$outerCIRCUM /$diam}] set outerRATIOfmt [format "%.${decimalDIGITS}f" $outerRATIO] set outerERROR [expr {$outerRATIO - $pi}] set outerERRORfmt [format "%.${decimalDIGITS}f" $outerERROR] set outerERRORtext "OuterError: $outerERRORfmt" set outerTEXT "OuterPolygonCircumference: $outerCIRCUMfmt CircumOverDiam: $outerRATIOfmt" } set PIfmt [format "%.${decimalDIGITS}f" $pi] .fRresult.labelRESULT configure -text "$innerTEXT $outerTEXT PI: $PIfmt $innerERRORtext $outerERRORtext" advise_user \ "** Change the number of polygon sides to try different approximations. **" } ## END OF proc 'Redraw' ##+##################################################################### ## PROC 'set_background_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 the (background) color of the canvas --- ## on which all the geometry is to be drawn. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLORbkgd button ##+##################################################################### proc set_background_color {} { global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex ColorSelectorScript ## FOR TESTING: # puts "COLORBKGDr: $COLORBKGDr" # puts "COLORBKGDg: $COLORBKGDb" # 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 color of background-color button. update_button_colors ## Set the color of the canvas. .fRcanvas.can config -bg $COLORBKGDhex } ## END OF PROC 'set_background_color' #+##################################################################### ## PROC 'set_circle_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 the color of the circle to be ## drawn on the canvas. ## ## Arguments: none (global variables are used) ## ## CALLED BY: .fRbuttons.buttCOLORcircle button ##+##################################################################### proc set_circle_color {} { global COLORCIRCLEr COLORCIRCLEg COLORCIRCLEb COLORCIRCLEhex ColorSelectorScript ## FOR TESTING: # puts "COLORCIRCLEr: $COLORCIRCLEr" # puts "COLORCIRCLEg: $COLORCIRCLEb" # puts "COLORCIRCLEb: $COLORCIRCLEb" set TEMPrgb [ exec $ColorSelectorScript $COLORCIRCLEr $COLORCIRCLEg $COLORCIRCLEb] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLORCIRCLEhex "#$hexRGB" set COLORCIRCLEr $r255 set COLORCIRCLEg $g255 set COLORCIRCLEb $b255 ## Set color of the draw-color button. update_button_colors ## Redraw the circle. draw_circle 1 } ## END OF PROC 'set_circle_color' #+##################################################################### ## PROC 'set_inscribed_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 the color of the inscribed polygon ## drawn on the canvas. ## ## Arguments: none (global variables are used) ## ## CALLED BY: .fRbuttons.buttCOLORinner button ##+##################################################################### proc set_inscribed_color {} { global COLORINNERPOLYr COLORINNERPOLYg COLORINNERPOLYb COLORINNERPOLYhex ColorSelectorScript ## FOR TESTING: # puts "COLORINNERPOLYr: $COLORINNERPOLYr" # puts "COLORINNERPOLYg: $COLORINNERPOLYb" # puts "COLORINNERPOLYb: $COLORINNERPOLYb" set TEMPrgb [ exec $ColorSelectorScript $COLORINNERPOLYr $COLORINNERPOLYg $COLORINNERPOLYb] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLORINNERPOLYhex "#$hexRGB" set COLORINNERPOLYr $r255 set COLORINNERPOLYg $g255 set COLORINNERPOLYb $b255 ## Set color of the draw-color button. update_button_colors ## Redraw the inner polygon. draw_inner_polygon 1 } ## END OF PROC 'set_inscribed_color' #+##################################################################### ## PROC 'set_circumscribed_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 the color of the circumscribed polygon ## drawn on the canvas. ## ## Arguments: none (global variables are used) ## ## CALLED BY: .fRbuttons.buttCOLORouter button ##+##################################################################### proc set_circumscribed_color {} { global COLOROUTERPOLYr COLOROUTERPOLYg COLOROUTERPOLYb COLOROUTERPOLYhex ColorSelectorScript ## FOR TESTING: # puts "COLOROUTERPOLYr: $COLOROUTERPOLYr" # puts "COLOROUTERPOLYg: $COLOROUTERPOLYb" # puts "COLOROUTERPOLYb: $COLOROUTERPOLYb" set TEMPrgb [ exec $ColorSelectorScript $COLOROUTERPOLYr $COLOROUTERPOLYg $COLOROUTERPOLYb] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOROUTERPOLYhex "#$hexRGB" set COLOROUTERPOLYr $r255 set COLOROUTERPOLYg $g255 set COLOROUTERPOLYb $b255 ## Set color of the draw-color button. update_button_colors ## Redraw the outer polygon. draw_outer_polygon 1 } ## END OF PROC 'set_circumscribed_color' ##+##################################################################### ## PROC 'update_button_colors' ##+##################################################################### ## PURPOSE: ## This procedure is invoked to set the background color of each of ## 4 color buttons to its current color --- and sets foreground color, ## for text on the 4 buttons, to a suitable(?) black or white color, ## so that the label text is readable. ## ## (We might need to weight the RGB colors differently when summing ## them, to get a better choice of black or white for the wide ## range of colors that are possible on these color buttons.) ## ## Arguments: global color vars ## ## CALLED BY: 4 colors procs: ## 'set_background_color' 'set_draw_color' ## 'set_inscribed_color' 'set_circumscribed_color' ## and ## in the additional-GUI-initialization section at ## the bottom of this script to initialize the color of ## the buttons. ##+##################################################################### proc update_button_colors {} { global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex \ COLORCIRCLEr COLORCIRCLEg COLORCIRCLEb COLORCIRCLEhex \ COLORINNERPOLYr COLORINNERPOLYg COLORINNERPOLYb COLORINNERPOLYhex \ COLOROUTERPOLYr COLOROUTERPOLYg COLOROUTERPOLYb COLOROUTERPOLYhex # set colorBREAK 300 set colorBREAK 375 .fRbuttons.buttCOLORbkgd configure -bg $COLORBKGDhex set sumCOLOR [expr {$COLORBKGDr + $COLORBKGDg + $COLORBKGDb}] if {$sumCOLOR > $colorBREAK} { .fRbuttons.buttCOLORbkgd configure -fg "#000000" } else { .fRbuttons.buttCOLORbkgd configure -fg "#f0f0f0" } .fRbuttons.buttCOLORcircle configure -bg $COLORCIRCLEhex set sumCOLOR [expr {$COLORCIRCLEr + $COLORCIRCLEg + $COLORCIRCLEb}] if {$sumCOLOR > $colorBREAK} { .fRbuttons.buttCOLORcircle configure -fg "#000000" } else { .fRbuttons.buttCOLORcircle configure -fg "#f0f0f0" } .fRbuttons.buttCOLORinner configure -bg $COLORINNERPOLYhex set sumCOLOR [expr {$COLORINNERPOLYr + $COLORINNERPOLYg + $COLORINNERPOLYb}] if {$sumCOLOR > $colorBREAK} { .fRbuttons.buttCOLORinner configure -fg "#000000" } else { .fRbuttons.buttCOLORinner configure -fg "#f0f0f0" } .fRbuttons.buttCOLORouter configure -bg $COLOROUTERPOLYhex set sumCOLOR [expr {$COLOROUTERPOLYr + $COLOROUTERPOLYg + $COLOROUTERPOLYb}] if {$sumCOLOR > $colorBREAK} { .fRbuttons.buttCOLORouter configure -fg "#000000" } else { .fRbuttons.buttCOLORouter configure -fg "#f0f0f0" } } ## END OF PROC 'update_button_colors' ##+##################################################################### ## PROC 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in the additional-GUI-initialization section at ## the bottom of this script ## and in some procs. ##+##################################################################### proc advise_user {text} { .fRmsg.labelMSG 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 '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' ##+######################## ## END of PROC definitions. ##+######################## ##+######################## ## Set the 'HELPtext' var. ##+######################## set HELPtext "\ \ \ ** HELP for this 'tkCircleCircumferenceByPolygons' App ** An app which gives upper and lower bounds on PI, which is the ratio of circle circumference to the diameter of the circle. This Tk script is meant to demonstrate how one can 'zero in' on the value of pi (the ratio of the circumference of a circle to its diameter) using polygons that inscribe and circumscribe a circle. This script is meant to 'dynamically' demonstrate the method by which Archimedes estimated the value of pi (the ratio of circumference to diameter = twice radius). By 'dynamically' is meant the user can quickly 1) DRAW, with a click on a button, the 2 polygons (inner and outer) using a user-selected number of sides, as well as quickly 2) SHOW the calculated circumferences of the 2 polygons --- and the ratios of those circumferences to the diameter of the circle. We allow the user to specify a wide variety of radius values --- to demonstrate that the ratio (pi) is independent of the radius or diameter of the circle. In particular, for ANY given number of polygon sides (selected by the user via a 'scale' widget on the GUI), the 2 ratios of the 2 polygon circumferences to the diameter of the circle do not change as the user changes the radius via the radius 'scale' widget. Hence, once you have 'pinned' the known value of pi (to many decimal places) between the 2 ratios determined from the polygons, we see that the known value of pi STAYS pinned between those 2 values NO MATTER WHAT RADIUS you choose to use for the computations. (The method by which the computations are done can be seen in the two procs 'draw_inner_polygon' and 'draw_outer_polygon' in this Tk script. It involves determining the vertex points of the 2 polygons using simple application of sine and cosine functions.) The 'scale' widgets make it easy and quick for the user to set the drawing parameters: - size of the image square (in pixels) - number of sides in the 2 'regular' polygons - circle radius (in 'world coordinates' rather than pixel coordinates). Some reasonable max and min limits are set for the 3 'scale' widgets, but if the user wants, those limits are easily changed by changing values of the '-from' and '-to' parameters of the 3 statements defining the 'scale' widgets. ******************************************* ON ACCURACY AND THE NUMBER OF POLYGON SIDES: ******************************************* NOTE that Archimedes (the 'Sand Reckoner') limited himself to using polygons based on a hexagon and doubled the number of sides of the polygons at each step, up to a maximum of 96 (6 * 16) sides. According to some accounts, he used hexagon-based polygons so that he could use his knowledge of the value of the square root of simple integers (such as 2 and 3) to compute the length of each side, and add up (or multiply by the number of sides) to get the circumference. This script uses the fact that we can use 'regular' polygons of any number of sides (3,4,5,6,7,8,9,10,...) and we can use the sine,cosine functions to calculate the length of the side of any of the polygons. Although we are speeding up the computations tremendously by using a computer, this Tk script mimics the fact that back in the Middle Ages (when they had tables of sines and cosines accurate to at least 5 digits), those ancient mathematicians could have computed the circumferences of these polygons to much better accuracy than Archimedes achieved --- by laboriously using their trigonometry tables. ************************************ METHOD USED to perform the drawing: ************************************ A square image area (canvas) is defined in a frame for the canvas. The pixel coordinates of the Tk canvas are mapped to 'world coordinates'. If R is the radius chosen by the user with the radius scale widget, the 'world coordinates' of the four corners of the square image area will be - top left (-H,+H) - top right (+H,+H) - bottom left (-H,-H) - bottom right (+H,-H) where H is somewhat bigger than R, such as 1.2 * R. We use a fixed 'margin factor' of 1.2 to allow for some margin around the circle. This factor can be changed in a 'set' statement near the bottom of the script. Two procs -- 'Xwc2px' and 'Ywc2px' -- are used to convert world x,y coordinates to pixel x,y coordinates that are to be used in the 'create arc' and 'create line' commands that draw the circle and polygons on the image area. Those commands require pixel coordinates. The center of the circle is defined in the center of the image area at (0.0,0.0) in 'world coordinates'. Of course, the polygons are also drawn centered at the origin. If N is the number of sides chosen by the user with a scale widget, the world coordinates of the vertices of the polygons are computed with N and sin() and cos() functions --- and a radius (in world coordinates) selected by the user. ***************************** POSITIONING THE OUTER POLYGON: ***************************** The image of the inner and outer polygons in many math books and web pages on Archimedes is shown with the outer-polygon 'sectors' rotated by half the polygon-sectors' angle from the inner-polygon sectors. However, this configuration does not show that the inner polygon sectors fit exactly into the outer polygon sectors with the end-points of the inner polygon. The latter configuration shows very convincingly that the circumference of the outer-polygon is always bigger than the circumference of the inner polygon, for any given N. Let us compare the length of the segments of the inner and outer polygons when the sectors 'coincide'. Let 'r' be the radius of the circle, and 'R' be the distance to the end-points of the sides of the outer-polygon. Let N be the number of sides in the two polygons. Let ANG = 2*pi/N be the interior angle of the sectors. Let 'd' be the side-lengths of the inner-polygon. Let 'D' be the side-lengths of the outer-polygon. Then ... 1) d = 2 * r * sin(ANG/2) 2) D = 2 * R * sin(ANG/2) 3) A diagram and the definition of the cosine gives us the relation: r/R = cos(ANG/2) So R = r/cos(ANG/2) 4) Equation-2 becomes D = 2 * r * sin(ANG/2)/cos(ANG/2) = 2 * r * tan(ANG/2) 5) D - d = 2 * r * sin(ANG/2) * (1/cos(ANG/2) - 1) So (D - d) approaches zero as ANG goes to zero. Note that because BOTH of the 2 factors on the far right go to zero as ANG goes to zero, the difference (D - d) goes to zero 'quadratically' as ANG goes to zero. However, the question to be asked is: Does the difference in the CIRCUMFERENCES of the 2 polygons go to zero as ANG goes to zero?? The difference in circumferences, 'C' and 'c', say, is N*D - N*d = N * (D - d) = N * 2 * r * sin(ANG/2) * (1/cos(ANG/2) - 1) where ANG = 2*pi/N. Some arguments using power series approximations to sin(ANG/2) show that sin(ANG/2) approaches ANG/2 as ANG goes to zero. So the N factor in front cancels with the N in the denominator of ANG = 2*pi/N --- and we are left with (1/cos(ANG/2) - 1) going to zero as ANG goes to zero. (This can be expressed even more rigorously.) So ... YES ... (C - c) does go to zero as ANG goes to zero --- and the two circumferences DO converge to the same number. That is, the upper-limit of the little-c's and the lower-limit of the big-C's is the same number. (We are using here 'boundedness' theorems from math of numeric sequences: An increasing sequence that is bounded above converges to a limit. A decreading sequence that is bounded below converges to a limit. We just showed that the 2 limits must be the same number.) --- Note that whichever of the two positions you use for the outer polygon, all the results of computations for the circumferences and ratios and errors remain the same, for a given N. ********************** IMAGE RESIZE: ********************** We allow the user to resize the square image area rather than using a fixed drawing size. If the user chooses a different image size (via a 'scale' widget on the GUI), the 'Redraw' button can be used to force the image area and the drawing to be resized according to the new requested image size. ******************************************************** USING THE SLIDER ON THE 'SCALE' WIDGETS: ******************************************************** You can click MB1 (mouse-button-1) on the 'slider' of the 'scale' widgets and (while holding the button down) drag the slider. If you want FINE CONTROL of moving the slider, there are a couple of other options for moving the slider, based on clicking in the trough on either side of the slider: 1) Repeatedly click-and-release on either side of the slider, to change the figure by one increment of the scale per click-release. OR 2) Click-and-hold on either side of the slider. The slider moves until you release MB1. ******************************** CAPTURING THE GUI IMAGE: ******************************** A screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG file, say. If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a smaller image suitable for use in a web page or an email. " ##+################################################################ ##+################################################################ ## ADDITIONAL GUI INITIALIZATION SECTION: Mainly to ## - Draw an initial circle and polygon(s) on the canvas. ##+################################################################ ##+################################################################ ##+##################################################### ## 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 4 drawing colors --- for background, circle, ## and 2 polygons. ############################################################### set COLORBKGDr 90 set COLORBKGDg 90 set COLORBKGDb 255 set COLORBKGDhex [format "#%02X%02X%02X" $COLORBKGDr $COLORBKGDg $COLORBKGDb] set COLORCIRCLEr 255 set COLORCIRCLEg 255 set COLORCIRCLEb 255 set COLORCIRCLEhex [format "#%02X%02X%02X" $COLORCIRCLEr $COLORCIRCLEg $COLORCIRCLEb] set COLORINNERPOLYr 255 set COLORINNERPOLYg 0 set COLORINNERPOLYb 0 set COLORINNERPOLYhex [format "#%02X%02X%02X" $COLORINNERPOLYr $COLORINNERPOLYg $COLORINNERPOLYb] set COLOROUTERPOLYr 0 set COLOROUTERPOLYg 255 set COLOROUTERPOLYb 0 set COLOROUTERPOLYhex [format "#%02X%02X%02X" $COLOROUTERPOLYr $COLOROUTERPOLYg $COLOROUTERPOLYb] ###################################### ## Set the color of the color buttons. ###################################### update_button_colors ##+############################################################ ## We need following command because the 'Redraw' proc ## (called below) does not (re)set the background/canvas color. ## Only the background-color button-proc sets the canvas color. ##+############################################################ .fRcanvas.can config -bg $COLORBKGDhex ##+################################################### ## Initialize the scale widget variables for ## number-of-sides and radius and image size. ##+################################################### set VARnumsides 6 set VARradius 50.0 set VARimgpixels 300 ##+###################################################### ## Set a 'margin-factor' to apply to VARradius ## to set the world coordinates of the square image area. ##+###################################################### set marginFactor 1.2 ##+################################################ ## Set a width for lines (circle and polygons). ##+################################################ # set lineWidthPx 1 set lineWidthPx 2 ##+################################################ ## Set dash-pattern for circle and the 2 polygons. ##+################################################ set dashPattern {} # set dashPattern {3 3} ##+################################################# ## Set constants to use for angle computations --- ## and display of value of pi. ##+################################################ set pi [expr {4.0 * atan(1.0)}] set twopi [expr {2.0 * $pi}] ##+################################################# ## Set a constants to use in 'format' statements ## used to display the results. ##+################################################ # set decimalDIGITS 6 set decimalDIGITS 8 ##+################################################# ## Set initial values of the checkbutton variables. ##+################################################ set VARinnerPoly0or1 1 set VARouterPoly0or1 1 ##+################################################# ## Draw the circle and polygon(s), for the current ## canvas (image area) size. ##+################################################# advise_user \ "** Change the number of polygon sides to try different approximations. **" Redraw