Chapter 3: Writing a graphics program Previous chapter LLUs Home Next chapter Index Before we can talk about the structure of a graphics program, we need to discuss the structure of NCAR Graphics. Once you understand the structure of NCAR Graphics, we will discuss writing a program in Fortran. The following is a listing and description of the example programs used in Comp Sci 201 (Fortran).
Part of the WikiBookThe following Fortran code examples or sample programs show different situations depending on the compiler. The first set of examples are for the Fortran II, IV, and 77 compilers. The remaining examples can be compiled and run with any newer standard Fortran compiler (see the for lists of compilers). By convention most contemporary Fortran compilers select the language standard to use during compilation based on source code file name suffix: FORTRAN 77 for.f (or the less common.for), Fortran 90 for.f90, Fortran 95 for.f95. Other standards, if supported, may be selected manually with a command line option.
Contents.FORTRAN II, IV, and 77 compilers NOTE: Before FORTRAN 90, most FORTRAN compilers enforced fixed-format source code, a carryover from. comments must begin with a. or C! C AREA OF A TRIANGLE - HERON'S FORMULA C INPUT - CARD READER UNIT 5, INTEGER INPUT C OUTPUT - C INTEGER VARIABLES START WITH I,J,K,L,M OR N READ ( 5, 501 ) IA, IB, IC 501 FORMAT ( 3 I5 ) IF ( IA. 0 ) STOP 1 S = ( IA + IB + IC ) / 2.0 AREA = SQRT ( S. ( S - IA ).
( S - IB ). ( S - IC ) ) WRITE ( 6, 601 ) IA, IB, IC, AREA 601 FORMAT ( 4 H A =, I5, 5 H B =, I5, 5 H C =, I5, 8 H AREA =, F10. 2, $ 13 H SQUARE UNITS ) STOP END Simple Fortran IV program Multiple data card inputThis program has two input checks: one for a blank card to indicate end-of-data, and the other for a zero value within the input data. Either condition causes a message to be. C AREA OF A TRIANGLE - HERON'S FORMULA C INPUT - CARD READER UNIT 5, INTEGER INPUT, ONE BLANK CARD FOR END-OF-DATA C OUTPUT - LINE PRINTER UNIT 6, REAL OUTPUT C INPUT ERROR DISPAY ERROR MESSAGE ON OUTPUT 501 FORMAT ( 3 I5 ) 601 FORMAT ( 4 H A =, I5, 5 H B =, I5, 5 H C =, I5, 8 H AREA =, F10. 2, $ 13 H SQUARE UNITS ) 602 FORMAT ( 10 HNORMAL END ) 603 FORMAT ( 23 HINPUT ERROR, ZERO VALUE ) INTEGER A, B, C 10 READ ( 5, 501 ) A, B, C IF ( A.
0 ) GO TO 50 IF ( A. 0 ) GO TO 90 S = ( A + B + C ) / 2.0 AREA = SQRT ( S. ( S - A ).
( S - B ). ( S - C ) ) WRITE ( 6, 601 ) A, B, C, AREA GO TO 10 50 WRITE ( 6, 602 ) STOP 90 WRITE ( 6, 603 ) STOP END Simple Fortran 77 program Multiple data card inputThis program has two input checks in the READ statement with the END and ERR parameters, one for a blank card to indicate end-of-data; and the other for zero value along with valid data. In either condition, a message will be printed.
C AREA OF A TRIANGLE - HERON'S FORMULA C INPUT - CARD READER UNIT 5, INTEGER INPUT, NO BLANK CARD FOR END OF DATA C OUTPUT - LINE PRINTER UNIT 6, REAL OUTPUT C INPUT ERROR DISPAYS ERROR MESSAGE ON OUTPUT 501 FORMAT ( 3 I5 ) 601 FORMAT ( ' A= ', I5, ' B= ', I5, ' C= ', I5, ' AREA= ', F10. 2, $ 'SQUARE UNITS' ) 602 FORMAT ( 'NORMAL END' ) 603 FORMAT ( 'INPUT ERROR OR ZERO VALUE ERROR' ) INTEGER A, B, C 10 READ ( 5, 501, END = 50, ERR = 90 ) A, B, C IF ( A = 0. C = 0 ) GO TO 90 S = ( A + B + C ) / 2.0 AREA = SQRT ( S. ( S - A ).
( S - B ). ( S - C ) ) WRITE ( 6, 601 ) A, B, C, AREA GO TO 10 50 WRITE ( 6, 602 ) STOP 90 WRITE ( 6, 603 ) STOP END 'Retro' FORTRAN IV A retro example of a FORTRAN IV (later evolved into FORTRAN 66) program deck is available on the page, including the IBM 1130 DM2 JCL required for compilation and execution. An IBM 1130 emulator is available at that will allow the FORTRAN IV program to be compiled and run on a.Hello, World program In keeping with computing tradition, the first example presented is a simple program to display the words 'Hello, world' on the screen (or printer).FORTRAN 66 (also FORTRAN IV). C FORTRAN IV WAS ONE OF THE FIRST PROGRAMMING C LANGUAGES TO SUPPORT SOURCE COMMENTS WRITE ( 6, 7 ) 7 FORMAT ( 13 H HELLO, WORLD ) STOP ENDThis program prints 'HELLO, WORLD' to Fortran unit number 6, which on most machines was the. (The or was usually connected as unit 5). The number 7 in the WRITE statement refers to the statement number of the corresponding FORMAT statement. FORMAT statements may be placed anywhere in the same program or function/subroutine block as the WRITE statements which reference them.
Typically a FORMAT statement is placed immediately following the WRITE statement which invokes it; alternatively, FORMAT statements are grouped together at the end of the program or subprogram block. If execution flows into a FORMAT statement, it is a; thus, the example above has only two executable statements, WRITE and STOP.The initial 13H in the FORMAT statement in the above example defines a, here meaning that the 13 characters immediately following are to be taken as a character constant (note that the Hollerith constant is not surrounded by delimiters). (Some compilers also supported character literals enclosed in, a practice that came to be standard with FORTRAN 77.)The space immediately following the 13H is a carriage control character, telling the I/O system to advance to a new line on the output. A zero in this position advances two lines (double space), a 1 advances to the top of a new page and + character will not advance to a new line, allowing overprinting.FORTRAN 77 As of FORTRAN 77, single quotes are used to delimit character literals, and inline character strings may be used instead of references to FORMAT statements.
Comment lines may be indicated with either a C or an asterisk (.) in column 1. euclid.f (FORTRAN 77). Find greatest common divisor using the Euclidean algorithm PROGRAM EUCLID PRINT., 'A?'
READ., NA IF ( NA. 0 ) THEN PRINT., 'A must be a positive integer.' STOP END IF PRINT., 'B?' READ., NB IF ( NB.
0 ) THEN PRINT., 'B must be a positive integer.' STOP END IF PRINT., 'The GCD of', NA, ' and', NB, ' is', NGCD ( NA, NB ), '.' STOP END FUNCTION NGCD ( NA, NB ) IA = NA IB = NB 1 IF ( IB. 0 ) THEN ITEMP = IA IA = IB IB = MOD ( ITEMP, IB ) GOTO 1 END IF NGCD = IA RETURN ENDThe above example is intended to illustrate the following:. The PRINT and READ statements in the above use '.'
as a format, specifying list-directed formatting. List-directed formatting instructs the compiler to make an educated guess about the required input or output format based on the following arguments. As the earliest machines running Fortran had restricted character sets, FORTRAN 77 uses abbreviations such as.EQ.,.NE.,.LT.,.GT.,.LE., and.GE. To represent the relational operators =, ≠, ≤, and ≥, respectively.
This example relies on the to specify the INTEGER types of NA, NB, IA, IB, and ITEMP. In the function NGCD(NA, NB), the values of the function arguments NA and NB are copied into the local variables IA and IB respectively. This is necessary as the values of IA and IB are altered within the function. Because argument passing in Fortran functions and subroutines utilize by default (rather than, as is the default in languages such as ), modifying NA and NB from within the function would effectively have modified the corresponding actual arguments in the main PROGRAM unit which called the function.The following shows the results of compiling and running the program. $ cmplxde.(j.0.pi/4) = 1.0000000 + j0.0000000 e.(j.1.pi/4) = 0.7071068 + j0.7071068 e.(j.2.pi/4) = 0.0000000 + j1.0000000 e.(j.3.pi/4) = -0.7071068 + j0.7071068 e.(j.4.pi/4) = -1.0000000 - j0.0000001 e.(j.5.pi/4) = -0.7071066 - j0.7071069 e.(j.6.pi/4) = 0.0000000 - j1.0000000 e.(j.7.pi/4) = 0.7071070 - j0.7071065Error can be seen occurring in the last decimal place in some of the numbers above, a result of the COMPLEX data type representing its real and imaginary components in single precision.
Incidentally, Fortran 90 also made standard a double-precision complex-number data type (although several compilers provided such a type even earlier).FORTRAN program to find the area of a triangle. C AREA OF A TRIANGLE READ., A, B, C S = ( A + B + C ) / 2 A = SQRT ( S. ( S - A ). ( S - B ).
( S - C )) PRINT., 'AREA=', A STOP END Fortran 90/95 examples Summations with a DO loop In this example of Fortran 90 code, the programmer has written the bulk of the code inside of a DO loop. Upon execution, instructions are printed to the screen and a SUM variable is initialized to zero outside the loop. Once the loop begins, it asks the user to input any number. This number is added to the variable SUM every time the loop repeats.
If the user inputs 0, the EXIT statement terminates the loop, and the value of SUM is displayed on screen.Also apparent in this program is a data file. Before the loop begins, the program creates (or opens, if it has already been run before) a text file called 'SumData.DAT'. During the loop, the WRITE statement stores any user-inputted number in this file, and upon termination of the loop, also saves the answer. Performs summations using in a loop using EXIT statement!
Saves input information and the summation in a data file program summation implicit none integer:: sum, a print., 'This program performs summations. Enter 0 to stop.' Open ( unit = 10, file = 'SumData.DAT' ) sum = 0 do print., 'Add:' read., a if ( a 0 ) then exit else sum = sum + a end if write ( 10,. ) a end do print., 'Summation =', sum write ( 10,. ) 'Summation =', sum close ( 10 ) endWhen executed, the console would display the following. Program cylinder! Calculate the surface area of a cylinder.!!
Declare variables and constants.! Variables=radius squared and height implicit none! Require all variables to be explicitly declared integer:: ierr character ( 1 ):: yn real:: radius, height, area real, parameter:: pi = 3.89793 interactiveloop: do! Prompt the user for radius and height!
And read them. Write (.,. ) 'Enter radius and height.' Read (.,., iostat = ierr ) radius, height! If radius and height could not be read from input,! Then cycle through the loop.
If ( ierr /= 0 ) then write (.,. ) 'Error, invalid input.' Cycle interactiveloop end if! Compute area. The. means 'raise to a power.' Area = 2.
pi. ( radius. 2 + radius. height )! Write the input variables (radius, height)! And output (area) to the screen. Write (., '(1x,a7,f6.2,5x,a7,f6.2,5x,a5,f6.2)' ) & 'radius=', radius, 'height=', height, 'area=', area yn = ' ' ynloop: do write (.,.
) 'Perform another calculation? Yn' read (., '(a1)' ) yn if ( yn 'y'. Yn 'Y' ) exit ynloop if ( yn 'n'.
Yn ' ' ) exit interactiveloop end do ynloop end do interactiveloop end program cylinder Dynamic memory allocation and arrays The following program illustrates dynamic memory allocation and array-based operations, two features introduced with Fortran 90. Particularly noteworthy is the absence of DO loops and IF/ THEN statements in manipulating the array; mathematical operations are applied to the array as a whole.
Also apparent is the use of descriptive variable names and general code formatting that comport with contemporary programming style. This example computes an average over data entered interactively. Program average! Read in some numbers and take the average!
As written, if there are no data points, an average of zero is returned! While this may not be desired behavior, it keeps this example simple implicit none integer:: numberofpoints real, dimension (:), allocatable:: points real:: averagepoints = 0., positiveaverage = 0., negativeaverage = 0. Write (.,. ) 'Input number of points to average:' read (.,. ) numberofpoints allocate ( points ( numberofpoints )) write (.,. ) 'Enter the points to average:' read (.,.
) points! Take the average by summing points and dividing by numberofpoints if ( numberofpoints 0 ) averagepoints = sum ( points ) / numberofpoints! Now form average over positive and negative points only if ( count ( points 0. ) 0 ) positiveaverage = sum ( points, points 0. ) & / count ( points 0. ) if ( count ( points 0 ) negativeaverage = sum ( points, points. Function gausssparse ( numiter, tol, b, A, x, actualiter ) result ( tolmax )!
This function solves a system of equations (Ax = b) by using the Gauss-Seidel Method implicit none real:: tolmax! Input: its value cannot be modified from within the function integer, intent ( in ):: numiter real, intent ( in ):: tol real, intent ( in ), dimension (:):: b, A (:,:)! Input/Output: its input value is used within the function, and can be modified real, intent ( inout ):: x (:)!
Output: its value is modified from within the function, only if the argument is required integer, optional, intent ( out ):: actualiter! Locals integer:: i, n, iter real:: xk! Initialize values n = size ( b )!
Size of array, obtained using size intrinsic function tolmax = 2. tol iter = 0! Compute solution until convergence convergenceloop: do while ( tolmax = tol. Program testgausssparse implicit none! Explicit interface to the gausssparse function interface function gausssparse ( numiter, tol, b, A, x, actualiter ) result ( tolmax ) real:: tolmax integer, intent ( in ):: numiter real, intent ( in ):: tol real, intent ( in ), dimension (:):: b, A (:,:) real, intent ( inout ):: x (:) integer, optional, intent ( out ):: actualiter end function end interface! Declare variables integer:: i, N = 3, actualiter real:: residue real, allocatable:: A (:,:), x (:), b (:)!
Allocate arrays allocate ( A ( N, N ), b ( N ), x ( N ))! Initialize matrix A = reshape (( real ( i ), i = 1, size ( A )), shape ( A ))! Make matrix diagonally dominant do i = 1, size ( A, 1 ) A ( i, i ) = sum ( A ( i,:)) + 1 enddo! Initialize b b = ( i, i = 1, size ( b ))! Initial (guess) solution x = b!
Invoke the gausssparse function residue = gausssparse ( numiter = 100, & tol = 1 E - 5, & b = b, & A = a, & x = x, & actualiter = actualiter )! Output print '(/ 'A = ')' do i = 1, size ( A, 1 ) print '(100f6.1)', A ( i,:) enddo print '(/ 'b = ' / (f6.1))', b print '(/ 'residue = ', g10.3 / 'iterations = ', i0 / 'solution = '/ (11x, g10.3))', & residue, actualiter, x end program testgausssparse Writing subroutines In those cases where it is desired to return values via a procedure's arguments, a subroutine is preferred over a function; this is illustrated by the following subroutine to swap the contents of two arrays. Subroutine swapreal ( a1, a2 ) implicit none! Input/Output real, intent ( inout ):: a1 (:), a2 (:)!
Locals integer:: i real:: a! Swap do i = 1, min ( size ( a1 ), size ( a2 )) a = a1 ( i ) a1 ( i ) = a2 ( i ) a2 ( i ) = a enddo end subroutine swaprealAs in the previous example, an explicit interface to this routine must be available to its caller so that the is known.
As before, this is preferably done by placing the function in a MODULE and then USEing the module in the calling routine. An alternative is to use a INTERFACE block.Internal and Elemental Procedures An alternative way to write the swapreal subroutine from the previous example, is. Subroutine swapreal ( a1, a2 ) implicit none! Input/Output real, intent ( inout ):: a1 (:), a2 (:)! Locals integer:: N! Swap, using the internal subroutine N = min ( size ( a1 ), size ( a2 )) call swape ( a1 (: N ), a2 (: N )) contains elemental subroutine swape ( a1, a2 ) real, intent ( inout ):: a1, a2 real:: a a = a1 a1 = a2 a2 = a end subroutine swape end subroutine swaprealIn the example, the swape subroutine is elemental, i.e., it acts upon its array arguments, on an element-by-element basis.
Elemental procedures must be (i.e., they must have no side effects and can invoke only pure procedures), and all the arguments must be scalar. Since swape is internal to the swapreal subroutine, no other program unit can invoke it.The following program serves as a test for any of the two swapreal subroutines presented. Program testswapreal implicit none! Explicit interface to the swapreal subroutine interface subroutine swapreal ( a1, a2 ) real, intent ( inout ):: a1 (:), a2 (:) end subroutine swapreal end interface! Declare variables integer:: i real:: a ( 10 ), b ( 10 )!
Initialize a, b a = ( real ( i ), i = 1, 20, 2 ) b = a + 1! Output before swap print '(/'before swap:')' print '('a = ', 10f6.1, ')', a print '('b = ', 10f6.1, ')', b! Call the swapreal subroutine call swapreal ( a, b )! Output after swap print '(// 'after swap:')' print '('a = ', 10f6.1, ')', a print '('b = ', 10f6.1, ')', b end program testswapreal Pointers and targets methods In Fortran, the concept of differs from that in -like languages. A Fortran 90 pointer does not merely store the memory address of a target variable; it also contains additional descriptive information such as the target's rank, the upper and lower bounds of each dimension, and even strides through memory. This allows a Fortran 90 pointer to point at submatrices.Fortran 90 pointers are 'associated' with well-defined 'target' variables, via either the pointer assignment operator ( =) or an ALLOCATE statement. When appearing in expressions, pointers are always dereferenced; no 'pointer arithmetic' is possible.The following example illustrates the concept.
Module SomeModule implicit none contains elemental function A ( x ) result ( res ) integer:: res integer, intent ( IN ):: x res = x + 1 end function end module SomeModule program Test use SomeModule, DoSomething = A implicit none!Declare variables integer, parameter:: m = 3, n = 3 integer, pointer:: p (:) = null , q (:,:) = null integer, allocatable, target:: A (:,:) integer:: istat = 0, i, j character ( 80 ):: fmt! Write format string for matrices! (/ A / A, ' = ', 3( ',3(i2, 1x), ' / 5x), ' ) write ( fmt, '('(/ A / A, ' = ', ', i0, '( ',', i0, '(i2, 1x), ' / 5x), ' )')' ) m, n allocate ( A ( m, n ), q ( m, n ), stat = istat ) if ( istat /= 0 ) stop 'Error during allocation of A and q'! Matrix A is:! A = 1 4 7 !
A = reshape (( i, i = 1, size ( A )), shape ( A )) q = A write (., fmt ) 'Matrix A is:', 'A', (( A ( i, j ), j = 1, size ( A, 2 )), i = 1, size ( A, 1 ))! P will be associated with the first column of A p = A (:, 1 )!
This operation on p has a direct effect on matrix A p = p. 2! This will end the association between p and the first column of A nullify ( p )! Matrix A becomes:! A = 1 4 7 ! write (., fmt ) 'Matrix A becomes:', 'A', (( A ( i, j ), j = 1, size ( A, 2 )), i = 1, size ( A, 1 ))!
Perform some array operation q = q + A! Matrix q becomes:! Q = 2 8 14 !
write (., fmt ) 'Matrix q becomes:', 'q', (( q ( i, j ), j = 1, size ( A, 2 )), i = 1, size ( A, 1 ))! Use p as an ordinary array allocate ( p ( 1: m. n ), stat = istat ) if ( istat /= 0 ) stop 'Error during allocation of p'! Perform some array operation p = reshape ( DoSomething ( A + A.
2 ), shape ( p ))! Array operation:! P(9) = 91 write (., '('Array operation:' / (4x,'p(',i0,') = ',i0))' ) ( i, p ( i ), i = 1, size ( p )) deallocate ( A, p, q, stat = istat ) if ( istat /= 0 ) stop 'Error during deallocation' end program Test Module programming A is a program unit which contains data definitions, global data, and CONTAINed procedures. Unlike a simple, a module is an independent program unit that can be compiled separately and linked in its binary form. Once compiled, a module's public contents can be made visible to a calling routine via the USE statement.The module mechanism makes the of procedures easily available to calling routines.
In fact, modern Fortran encourages every SUBROUTINE and FUNCTION to be CONTAINed in a MODULE. This allows the programmer to use the newer argument passing options and allows the compiler to perform full type checking on the interface.The following example also illustrates derived types, overloading of operators and generic procedures. Module GlobalModule! Reference to a pair of procedures included in a previously compiled! Module named PortabilityLibrary use PortabilityLibrary, only: GetLastError, &!
Generic procedure Date! Specific procedure! Constants integer, parameter:: dpk = kind ( 1.0d0 )! Double precision kind real, parameter:: zero = ( 0. ) real ( dpk ), parameter:: pi = 3.89793dpk! Variables integer:: n, m, retint logical:: status, retlog character ( 50 ):: AppName! Arrays real, allocatable, dimension (:,:,:):: a, b, c, d complex ( dpk ), allocatable, dimension (:):: z!
Derived type definitions type ijk integer:: i integer:: j integer:: k end type ijk type matrix integer m, n real, allocatable:: a (:,:)! Fortran 2003 feature. For Fortran 95, use the pointer attribute instead end type matrix! All the variables and procedures from this module can be accessed! By other program units, except for AppName public private:: AppName! Generic procedure swap interface swap module procedure swapinteger, swapreal end interface swap interface GetLastError! This adds a new, additional procedure to the!
Generic procedure GetLastError module procedure GetLastErrorGlobalModule end interface GetLastError! Operator overloading interface operator ( + ) module procedure addijk end interface! Prototype for external procedure interface function gausssparse ( numiter, tol, b, A, x, actualiter ) result ( tolmax ) real:: tolmax integer, intent ( in ):: numiter real, intent ( in ):: tol real, intent ( in ), dimension (:):: b, A (:,:) real, intent ( inout ):: x (:) integer, optional, intent ( out ):: actualiter end function gausssparse end interface! Procedures included in the module contains! Internal function function addijk ( ijk1, ijk2 ) type ( ijk ) addijk, ijk1, ijk2 intent ( in ):: ijk1, ijk2 addijk = ijk ( ijk1% i + ijk2% i, ijk1% j + ijk2% j, ijk1% k + ijk2% k ) end function addijk! Include external files include 'swapinteger.f90'! Comments SHOULDN'T be added on include lines include 'swapreal.f90' end module GlobalModule.
Posted: April 14, 2017 Author: Filed under:, With the release of version 2.36, Simply Fortran now includes Aplot, a library for creating simple, two-dimensional plots and charts directly from Fortran. The programming interface provided is designed to be straightforward, and the library is available on Windows, macOS, and GNU/Linux. This short article will walk through creating a non-trivial plot quickly with Aplot.For this example, we’ll attempt to plot 1000 random, uniformly distributed numbers along with a running mean as the sample size grows. The plot will therefore include scatter elements and a line, both with a significant amount of data.In order to use Aplot, the aplot module must be employed in a given procedure: use aplotOn Windows and macOS, this module will be seamlessly included. On GNU/Linux, Simply Fortran might show the module as unavailable until you click Build Project for the first time. There are no other steps to take; Simply Fortran will automatically detect this module’s inclusion and configure the compiler to link to the aplot library.To generate our data set, we’ll need to populate some arrays accordingly.
The code below should be sufficient: real, dimension(1000)::x, randy, meanyinteger::icall RANDOMNUMBER(randy)do i = 1, 1000x(i) = real(i)meany(i) = sum(randy(1:i))/x(i)end doThe above code creates three arrays. Our X-axis data will just be the sample count, though we have to use a REAL variable with Aplot.
The random data is created with a single call to RANDOMNUMBER, which populates the entire randy array in one call.The x array is manually populated inside the loop along with the calculation of a running mean in the meany array. This code generates two datasets that we need to plot.In order to create a plot, we need to define a plot variable, which is of the aplott type: type(aplott)::plotThis variable is used for defining all aspects of our plot at eventually displaying it. Before we can use it, though, we must initialize it: plot = initializeplotOnce initialized, we can define some aspects of our plot: call settitle(plot, 'Uniform Random Numbers')call setxlabel(plot, 'Sample')call setylabel(plot, 'Value')call setyscale(plot, 0.0, 1.2)Most calls above are straightforward, and each requires our plot variable as its first argument. The final call, though, to setyscale might not be as obvious as setting titles and labels. By default, Aplot autoscales both axes. In this case, though, we know the data on the y-axis will fall between 0 and 1. In order to provide a little extra space, we’ll expand the plot slightly so that the y-axis varies from 0 to 1.2.The next step is to configure each dataset for the plot.
To add data, we must pass an X and Y array to the adddataset subroutine: call adddataset(plot, x, randy)Once we’ve provided the first dataset to the plot, we can assign some attributes to the series: call setseriestype(plot, 0, APLOTSTYLEPIXEL)call setserieslabel(plot, 0, 'Random Number')The call to setseriestype configures the style of the series when drawn. The index of zero is passed because Aplot uses 0-indexing for its series identification. In this case, since we’ll be showing 1000 points, we’ve asked for it to be drawn as a scatter plot using single pixels; the user can also use larger dots, lines, or bars to represent a series. The second call to setserieslabel provides a label for this series in the legend.Our second dataset, the running average, can be configured similarly: call adddataset(plot, x, meany)call setseriestype(plot, 1, APLOTSTYLELINE)call setserieslabel(plot, 1, 'Running Mean')Our plot is now entirely ready to display.
The final step is to call: call displayplot(plot)which opens a window displaying the complete plot.The final coding step is to clean up our plot after the user closes it. While not necessarily important in a standalone program, a subroutine that generates plots should always perform final cleanup before exiting. The simple code is: call destroyplot(plot)After this call, all memory associated with the plot is released, and the program can continue normally.The code here should compile in Simply Fortran 2.36 without issue, generating the following on GNU/Linux:While the plot might look slightly different on macOS or Windows, it should still resemble the above chart.
The plot shows the running average, in blue, converging towards 0.5 as more samples are considered.Hopefully the above helps users get started with Aplot. The interface is designed for quickly and easily adding plots to Fortran. Posted: July 22, 2015 Author: Filed under: Simply Fortran version 2.24 has now been available for about one and a half months, and version 2.25 has not yet appeared. Some of our more seasoned users might consider this delay a little longer than normal between official releases, but development certainly hasn’t been static. The latest build (1921) of version 2.24 was released just two days ago. We thought it might be prudent to explain some of the changes in these builds and what other development might be taking place.Bugs and Various FixesA bug existed in the syntax checking engine that cause the syntax engine to fail for users with special characters in their user name on Windows. Anything from a space to a non-ASCII character would cause syntax checking to fail.The Fortran indexing engine was failing on code containing line continuations where a comma was the last character on a given line.
This bug actually arose in recent versions due to improvements and bug fixes for other Fortran-related parsing enhancements and issues.Simply Fortran was attempting to compile Fortran include files ending with the.INC extension as standalone source files. The previous solution was to disable the files in the Project Outline, but Simply Fortran now treats these files properly.AppGraphics was not able to draw vertical text.
A fix is now present, but a user has already. This existing bug will probably necessitate another build of version 2.24.Some of the makefile generation code could intermittently cause error dialogs to appear. The most likely culprit was the handling of search directories for include and library files. The underlying code was greatly simplified for more robust handling of these directory flags.An odd issue was causing Simply Fortran to lock up entirely. A new user reported that, when entering Fortran code that contained a comma on the very first line of an editor tab without said comma being preceded by an opening parentheses or a comment character, Simply Fortran would freeze. This scenario wouldn’t be common because most Fortran files would either begin with comments or a subprogram declaration (which would have an opening parentheses). The lockup was caused by an infinite loop as the calltip engine searched the line for either an opening parentheses or a newline character.
On the very first line in the editor, it would encounter neither as it continued requesting characters at negative document positions. This extremely specific bug has now been fixed.DevelopmentsSimply Fortran version 2.24 has a reasonably robust feature set, which explains why a new version has yet to be created. However, version 2.25 should appear within the next month or so. We plan on including a new version of the underlying compiler and providing an improved automatic code formatter for “beautifying” existing Fortran.We’ve also been working on some new developments. The screenshot below might be of particular interest to our users running Simply Fortran under:No official announcements concerning the above. Posted: June 26, 2015 Author: Filed under:Tags:, A number of users have requested some further information about handling multiple windows in AppGraphics. While the Fortran interface certainly suggests a multi-window program is possible, the details of such a program might be a bit non-obvious.
In this tutorial, we’ll walk through creating a multiple window application.To start this exercise, we’ll need a primary window from which we’ll spawn children. While this design isn’t strictly necessary, it would be a common model for many users. Since we’ve gone over producing such windows in the past, we’ll just post the contents of a “main window” below:program mainuse appgraphicsuse children, only: childids, childcreatetime, redrawchildrenimplicit noneinteger::myscreeninteger::createchildbuttoninteger::scticksinteger::sctickratereal::timenowmyscreen = initwindow(200, 130, closeflag=.TRUE.)!
Add a button for creating childrencreatechildbutton = createbutton(60, 80, 80, 40, 'Create Child', &handlecreatechild)! Initialize the child index arrayschildids = -1childcreatetime = 0.0! Label this as the main windowcall settextstyle(SANSSERIFFONT, HORIZDIR, 60)call outtextxy(15, 5, 'Master')call systemclock(countrate=sctickrate)do while(.true.)! Calculate the system time now in secondscall systemclock(count=scticks)timenow = real(scticks)/real(sctickrate)! Redraw all children and provide them with the system timecall redrawchildren(timenow)!
Set the master to the current window and idle 1 secondcall setcurrentwindow(myscreen)call startidle(1000)end docall closewindow(myscreen)containssubroutine handlecreatechilduse children, only: createchildimplicit nonecall createchildend subroutine handlecreatechildend program mainImmediately, readers may notice an unfamiliar module, children, appearing at the beginning of this program. The children module will appear later in this writeup, and it will contain all the necessary subprograms for creating, managing, and destroying child windows.In our main program, we first create a simple, single window that is labeled “Master” and has a single button labeled “Create Child.” Our goal is to spawn a child window whenever the user clicks said button.After creating the window, we also initialize two arrays, childids and childcreatetime, that are both used for managing child windows. The former array is used to maintain a list of the identifiers to all of our child windows.
When a developer calls the createwindow function, a unique identifier to that window is returned; we need to explicitly keep track of these identifiers while our program is running to allow switching to and update child windows as necessary. The latter array just tracks the system clock time when each child window is created so that we’ll have something interesting to display in our child windows.After all this initialization, our main program enters a simple loop. Within the loop, we’re calculating the system time in seconds and, using the time value, calling a subroutine to update all of our child windows. Once the update is complete, we carefully ensure the current window in AppGraphics is our main window and we idle for one second. Setting the current window is quite important in AppGraphics.
Had we not set the current window to our master window prior to calling startidle, our idling loop would be applied to another window. Basically, as a rule of thumb in AppGraphics programs, any sequence calls to AppGraphics should be preceded by a call to setcurrentwindow to ensure the calls are being applied to the proper window. Without this step, it becomes quite easy to crash your program by attempting an operation, drawing, idling, or querying, on a window that no longer exists.So far we’ve covered the easiest parts. Handling our child windows will be somewhat more complicated.
To start, we’ll need to store some data within our children module:module childrenimplicit none! Allow no more than 32 windowsinteger, parameter::maxwindows = 32! Width and height of our child windowinteger, parameter::wc = 180integer, parameter::hc = 80!
Array of child window ids and the system times at which each was createdinteger, volatile, dimension(maxwindows)::childidsreal, volatile, dimension(maxwindows)::childcreatetimecontains.We have a parameter that defines the maximum number of children we’ll allow; thirty two seems like a reasonable limit. We also define some parameters regarding drawing. The interesting data, though, are our two tracking arrays, childids and childcreatetime. We’ve marked both arrays as “volatile” because there is a possibility the data in these arrays may be accessed by multiple threads.Next, we need a subroutine to create a child window:subroutine createchilduse appgraphicsimplicit noneinteger::childindexcharacter(40)::titleinteger::closebutton! Loop to find an available slotdo childindex=1, maxwindows!
If the array of children is a negative one, it is available for! Storing a child idif(childids(childindex).EQ. Create a title for this windowWrite(title, '(A5,1X,I3)') 'Child', childindex! Create the window, making sure that 'closeflag' is falsechildids(childindex) = initwindow(wc, hc, &title=title, &closeflag=.FALSE.)! Set up some styles for this windowcall settextstyle(WINDOWSFONT, HORIZDIR, 8)call setbkcolor(BLACK)call setcolor(WHITE)! Store a zero as 'when it was created' for nowchildcreatetime(childindex) = 0.0!
One button to close this childclosebutton = createbutton(wc-50, hc-40, 40, 30, &'Close', closechild)exitend ifend doend subroutine createchildThis routine starts with a loop that looks through our childids array for an entry that is equal to -1. We’ll use a -1 to represent an empty slot in our child ids array; one might recall that we initialized the ray to-1 appropriately in our main program. When we do find a -1, we’ll proceed with a call to createwindow and store the new window’s identifier in the childids array. For fun we also create a title with the window’s index in the childids array. There is some basic graphical configuration of this new window as well. With our new window, we’ll save a creation time of 0.0 in the second array.
Finally, we’ll add a close button.Readers might notice that we’ve already violated a rule mentioned earlier concerning calling setcurrentwindow prior to calling a sequence of AppGraphics routines. The only reason we’ve skipped this step is that the createwindow function also implies setting the current window to the latest window.For our “close” button, we’ve attached the button to a subroutine named closechild. This routine requires some consideration here:subroutine closechilduse appgraphicsimplicit noneinteger::childidinteger::childindex! This call determines the id of the window that executed this! Callbackchildid = getsignallingwindow! And we'll retrieve theindex of this window in our global! Arrayschildindex = indexfromid(childid)!
Mark this chold window slot as available for a new childif(childindex.GT.1) thenchildids(childindex) = -1childcreatetime(childindex) = 0.0end ifif(childid.GT.1) thencall closewindow(childid)end ifend subroutine closechildTo close a child, we need to take a few steps. First, we need to determine in which window the close button was clicked. All of our child windows call this routine, so we have to determine which to act upon. The getsignallingwindow function can be used to determine the id of the window from which the callback was made. Once we have the id of the child window, we’ll call another custom function to determine the index of this child in the childids array.
The indexfromid is a simple array search, so we’ll ignore it in this writeup, although it appears in the complete source code.The reason we need the index in the childids array is to set the entry for said window in the array back to -1 so that the slot is once again available for creating a new window.Finally, we can close our window with a call to closewindow, explicitly passing the id of the window we’re closing.At this point, we do indeed have a program with multiple windows, but none of the children do anything. To make this exercise interesting, we’ll perform some updates to our children. One may recall that our main program called a subroutine redrawchildren that accepted a time argument. This subroutine will subsequently be used to call a redraw routine for all our children:subroutine redrawchildren(timenow)implicit nonereal, intent(in)::timenowinteger::ido i = 1, maxwindowscall redrawchild(i, timenow)end doend subroutine redrawchildrenThe above routine isn’t particularly interesting. For each possible window, it calls a far more interesting subroutine, redrawchild:subroutine redrawchild(childindex, timenow)use appgraphicsimplicit noneinteger, intent(in)::childindexreal, intent(in)::timenowinteger::childidcharacter(40)::textinteger::longevityinteger::previouscurrent! Get the child id from our arraychildid = childids(childindex)if(childid.GE.
If its create time is zero, store the system time nowif(childcreatetime(childindex).EQ. 0.0) thenchildcreatetime(childindex) = timenowlongevity = 0! Otherwise, calculate the difference in timeselselongevity = int(timenow - childcreatetime(childindex))end if! Write out how old this window iswrite(text, '(A19,1X,I4,1X,A7)') 'I've been alive for', &longevity, &'seconds'! Store the 'current' window for good measurepreviouscurrent = getcurrentwindow! Select this window as current and display the text in the windowcall setcurrentwindow(childid)call clearviewportcall outtextxy(10, 10, text)! Reset the current window to its previous statecall setcurrentwindow(previouscurrent)end ifend subroutine redrawchildIn the redrawchild subroutine, we first check that the id available in the childids array is greater than or equal to zero.
Recall that a -1 in this array represents “no window” in our scheme. If the id is valid, we’ll proceed with drawing our window.First, we can calculate, based on the time passed in, how long our window has been open. If a 0.0 is stored in childcreatetime, we’ll store the passed time and say that our window has existed for 0 seconds. Otherwise, we can compute how long the window has existed by determining the difference between the current time and the stored time. We’ll use this time to prepare a string to display that states how long the window has been opened.We’re now ready to draw our window.
First, we’ll set our current window appropriately since we’re about to draw. However, prior to doing so, we’ll actually save the current window id in case we were sloppy elsewhere so that we can reset the current window when we’re done. Next, we can clear the window with a call to clearviewport. Then, we can output our text to the window. Finally, before leaving our drawing routine, we’ll reset the current window to its original state like the responsible bookkeepers we should be.The result of all these routines leads to an interesting example program:While this example might seem complex, users can hopefully decipher all the steps taken. Furthermore, creating mukltiple window applications is an inherently complex process, and we’ve tried to make such development as simple as possible.The source code for the example appears below:.
Posted: April 15, 2015 Author: Filed under:, A user on our forums posted a Because Fortran is often relied upon for long-running computations, a graphical progress bar could be very useful. While AppGraphics doesn’t provide a progress bar solution out of the box, creating one isn’t particularly difficult.In it’s simplest form, a progress bar is really just a partially filled rectangle. With a little creativity, we can create a window that provides all the progress bar behaviors we might want.
For maximum portability, we’ll assume that we want a standalone window that can display progress. Ideally, it should also provide:.
An optional “Cancel” button. A way to display text.
A simple 0 to 100 percent update mechanismFor fun, we’ll also design our progress bar window to be Fortran-2003-esque using some type-bound procedures. To start, we need a derived type to describe our progress bar: type progresswindowinteger::wininteger::cancelcharacter(128)::textcontainsprocedure:: close = closeprogressprocedure:: draw = drawprogressend type progresswindowThe type includes integers that refer to our progress window, a possible cancel button, and the text we’re displaying above the progress bar. We’ve also attached two type-bound procedures: a “close” subroutine and a “draw” subroutine.To initialize this window, we’ll basically just need to create the window and set up some drawing styles.
An abbreviated summary is below: p%win = initwindow(400, 100, title=title, dbflag=.FALSE., closeflag=.FALSE.)call setbkcolor(systemcolor(COLORWINDOWBKGD))call setcolor(BLACK)call settextstyle(WINDOWSFONT, HORIZDIR, 10)call setfillstyle(SOLIDFILL, RED)The above code creates a window, configures some colors and fonts to match Windows defaults, and sets up the fill style for a red progress bar.The first type-bound procedure, close, is exceptionally simple, needing only to close this window, so we’ll skip that for now. In contrast, drawing the window is a bit more involved. We’ll need to take a number of steps.
Our type definition pointed to a routine “drawprogress,” which we’ll define as such: subroutine drawprogress(p, completed, text)use appgraphicsimplicit noneclass(progresswindow), volatile::pinteger, intent(in)::completedcharacter(.), intent(in), optional::text.The drawing routine requires a completed percent, which is assumed to be 0 to 100, to be passed in. Optionally, the developer can also provide an updated text string to display above our progress bar. If the text is passed in, we’ll overwrite the text that our “progresswindow” variable is currently storing before we proceed with drawing.First, we need to clear our viewport of existing graphics with an appropriate call: call setcurrentwindow(p%win)call clearviewportNote that prior to clearing the viewport, we’ve set the current AppGraphics window. This operation safeguards against applications where we may have multiple windows present.
The developer, however, must be careful to switch windows back to any other windows when necessary.Next, we can output the text into the window: if(lentrim(p%text) 0) thencall outtextxy(5, 5, trim(p%text))end ifIf the text length is zero, we actually skip that step. Finally, we need to draw the progress bar. The actual progress bar really has two components: an empty rectangle and a filled rectangle whose size relative to the empty rectangle reflects the percent complete.
AppGraphics doesn’t actually provide a quick routine for drawing unfilled rectangles, so we’ll instead use the relative line operations: progresstotal = getmaxx - 10! Below we'll draw a simple box, unfilledcall moveto(5, 25)call lineto(progresstotal+5, 25)call lineto(progresstotal+5, 50)call lineto(5, 50)call lineto(5, 25)While the vertical coordinates aren’t particularly important, one should note that the empty rectangle should be about as wide as our window less five pixels on each side as a margin.
Next, we can compute the width of filled rectangle based on what the developer passes into the drawing function:! Bounds-check our completed valueprogresscompleted = progresstotal.completed/100if(progresscompleted progresstotal) thenprogresscompleted = progresstotalelseif(progresscompleted. Posted: April 3, 2015 Author: Filed under: Tags:, With the release of, AppGraphics now supports resizeable windows. This feature greatly improves the functionality of the library, but there is a bit of a learning curve in creating and managing such windows. We’ll walk through a simple example below that should illustrate how to go about creating an application that uses resizeable windows.The first step is proceed as normal in creating our window.
Of course, we still need to specify an initial size:myscreen = initwindow(320, 240, closeflag=.TRUE.)In the above declaration, one may notice that there is no mention of a resizing capability. Wee need to use another call to enable resizing:call enableresize(handleresize)The subroutine above will enable resizing for the current window. The argument we’ve included, handleresize, is actually the name of a subroutine to be called whenever the window changes size. Such a subroutine would be useful if, for example, we need to reposition window controls or redraw graphic contents of a window.
We’ll try to do both in this example.In our main program, we’ll first add a button to our window in the lower right corner:integer::mybuttonlogical::quit.mybutton = createbutton(270, 210, 40, 20, 'Close', handlebutton)quit =.FALSE.In the code above, we’ve added a “Close” button that will, when clicked, call a subroutine handlebutton that will flip the value of quit to.TRUE. And exit idle mode. We’ll also need an event loop in our code: do while(.NOT.
Quit)call drawwindowcall loopend docall closewindow(myscreen)The event loop above basically draws our window and enters idle mode. If we ever idle mode, it first checks if the quit flag was triggered and, if not, redraws the window.
The above structure means that we need only a simple subroutine for our resize callback handleresize: subroutine handleresizeuse appgraphicsimplicit nonecall stopidleend subroutine handleresizeAll the logic for laying out our window needs to reside in the drawwindow subroutine. We’ll try to do two things in our drawing subroutine:. Position mybutton in the lower right corner. Output the window size, centered, in our windowFor both of these tasks, we’ll need to know the current window size, which can easily be done with calls to getmaxx and getmaxy: integer::w,h.w = getmaxxh = getmaxyBased on the window size, we can actually reposition our button quite easily with a call to setbuttonposition: call setbuttonposition(mybutton, w-50, h-30, 40, 20)The next task is to draw something in our window.
We’ll simply write the window size near the top of the window, erasing any earlier text first: integer::twcharacter(100)::info.write(info, '(A5,1X,I5,A1,I5)') 'Size:', w, 'x', hcall setviewport(0, 0, w, 40,.FALSE.)call clearviewporttw = textwidth(trim(info))call outtextxy( (w/2-tw/2), 5, trim(info))The code above first writes the current window dimensions into the string info. Next, it defines and immediately clears a viewport where previous text would have existed. Finally, it outputs the text, centered in the window.When you first run the program, you should see something like:Unlike other AppGraphics windows, however, this one can be resize by dragging the borders.
Thanks to our design, things will continue to look nice at other sizes:Working with a resizeable window is rather easy as long as you properly handle drawing the window when resize occurs.If you’d like to try the code out for yourself, feel free to do so by clicking the link below:.It should work fine in Simply Fortran version 2.23 or higher. A simple program in AppGraphicsis a simple graphics library designed for creating graphical user interfaces from Fortran. Although based on an older drawing library, AppGraphics has ben enhanced with a number of, or the standard buttons, text boxes, check boxes common in modern Windows applications.
While AppGraphics can be used for drawing shape primitives, it can also be used to easily create a “dialog” experience.In this quick rundown, we will create a simple application for converting common temperature formats in a simple graphical program written entirely in Fortran. Since our task is so simple, we’ll only need a single Fortran source file to build this.In our main program, the first step is to initialize our window. For this example, we’ll create a long, skinny window as shown in the picture:integer::myscreen.myscreen = initwindow(150, 255, title='TempConvert', closeflag=.TRUE.)We now have a 150 by 255 pixel window with an appropriate title. The closeflag argument instructs AppGraphics to end our program whenever the window closes. To make our window match the operating system’s themes, we’ll need to also change the background.
The proper action would be to set the background color to the system’s dialog background color:call setbkcolor(systemcolor(COLORWINDOWBKGD))We also need to make sure our fonts match Windows as one would expect:call settextstyle(WINDOWSFONT, HORIZDIR, 10)Finally, we need to redraw our background with the newly assigned background color by clearing the window and forcing a redraw:call clearviewportAfter combining all of the above code, we should now have a standard blank window with the proper fonts configured. If you want to see everything together, you’ll be able to download the complete program at the end of this article.Next, we need to add our controls to the window. First, we’ll add the text box where a user can enter a temperature:original = createtextbox(5, 5, 140, 20)call settextboxcontents(original, '32.0')Above we’ve created a text box at (5,5) (as measured from the top left) that measures 140 pixels wide and 20 pixels high. These dimensions should leave a consistent 5 pixel distance from the top, left, and right edges of our window. Next, we drop in the temperature of 32 degrees.We now need to provide a method to select the original temperature’s units of measure. Since only one unit can be chosen, using is an obvious choice of control. Posted: May 22, 2014 Author: Filed under: In the upcoming version 2.14 release, Simply Fortran will begin shipping GNU Fortran 4.9.0 for the first time.
This compiler release is considered a major change for Simply Fortran regardless of the numbering convention because it will once again break the compiled module format for Fortran 90+ projects. While this change is conceptually simple for most people, it presents some interesting challenges for Approximatrix.Simply Fortran has been using the GNU Fortran 4.8 series since, which was released in March, 2013. During that period, the number of packages available in the has grown considerably. An issue arises, though, when considering how to handle packages containing pre-compiled modules once the latest release of Simply Fortran is available.We’ve now shipped an that is aware of the compiler version currently available. Behind the scenes, packages will begin shipping with “required compiler versions” to ensure any pre-compiled modules are compatible with the version of GNU Fortran installed. This change should be invisible to the user.
If a package is made available requiring GNU Fortran 4.9 and the user still has 4.8.2 installed, the package will not even be visible in the Package Manager. This configuration allows Approximatrix to begin updating packages as necessary while continuing to allow older installations to operate properly.The second issue is actually rebuilding all necessary packages. The construction of packages for the Package Manager is not particularly simple, hence why the Package Manager exists in the first place. Although Simply Fortran 2.14 is basically ready for final testing, the actual release will be held back until the requisite packages are also upgraded.