Clone this wiki locally
Scientific plotting and gtk-fortran
Since Fortran is primarily a scientific programming language, it is useful to have scientific data plotting tools available to draw into a gtk window created by gtk-fortran. The plplot library can potentially provide that functionality as it has a Fortran95 binding and also support for drawing to external cairo surfaces.
For this reason the necessary tools for integrating plplot with gtk-fortran are distributed as part of the gtk-fortran tree.
Requirements and installation.
In the following it will be assumed that the
cmake-based build system is being used.
For most Linux distributions, plplot is distributed as a number of packages (the precise division depends on the distribution) however for most distributions if the
-devel package and the cairo drivers are selected the other required packages will be installed as well.
cmake scripts for gtk-fortran include a search for plplot's libraries and Fortran module files. If these are found, then the interface module and the examples will be build (Unless you explicitly prevent it by adding the
-D EXCLUDE_PLPLOT=Y option to
The examples and module have mainly been tested using plplot version 5.9.9. Version 5.9.5 (which is the version in Ubuntu 11.10) lacks a number of features needed to use the external cairo driver effectively.
The interface module.
Correctly configuring plplot's external cairo driver requires the use of the low-level
pl_cmd routine to connect the cairo context to the driver. This routine is not available to the Fortran95 binding of plplot (at least in part because it requires Fortran 2003's c-binding capabilities, and also until gtk-fortran no-one had used cairo surfaces and contexts explicitly from Fortran).
plplot_extra provides an interface to the
pl_cmd routine and also mnemonic definitions of the command codes.
In plplot 5.9.7 a number of definitions are missing from the Fortran modules which prevent some of the examples from building. In this case the following lines in plplot_extra.f90 should be uncommented:
integer, parameter :: DRAW_LINEX = 1 integer, parameter :: DRAW_LINEY = 2 integer, parameter :: DRAW_LINEXY = 3 integer, parameter :: MAG_COLOR = 4 integer, parameter :: BASE_CONT = 8 integer, parameter :: TOP_CONT = 16 integer, parameter :: SURF_CONT = 32 integer, parameter :: DRAW_SIDES = 64 integer, parameter :: FACETED = 128 integer, parameter :: MESH = 256
Four examples from the plplot examples page have been adapted to work within gtk-fortran, these are, included in the
plplot subdirectory of gtk-fortran.
- Example 1: Four basic x-y plots in a 2x2 layout. These are embedded in a scrolled window. (
- Example 4: Two logarithmic x-y plots. Displayed in separate drawing areas in a notebook. (
- Example 8: A 3-D surface plot. With controls to set the options. The window can be resized. (
- Example 17: A constantly updating strip plot. (
- Example 30: A demonstration of gradients and transparency. (
Elements of a plplot + gtk-fortran program
In the following section we will highlight the key features of the first plplot example.
These variables are shared by all of the other modules and the main program.
module common_ex1 use iso_c_binding ! Enable the c-binding routines & constants. ! These are the gtk & glib routines used explicitly in the code. use gtk, only: gtk_button_new, gtk_container_add, gtk_drawing_area& &_new, gtk_main, gtk_main_quit, & & gtk_widget_show, gtk_widget_show_all, gtk_window_new, gtk_init use g, only: g_object_get_data ! These are the general high-level gtk routines and also the high-level ! drawing area modules. use gtk_draw_hl use gtk_hl ! This makes the low-level pl_cmd routine accessible use plplot_extra ! The size of the drawing area integer(kind=c_int) :: height, width ! The top-level window must be here as its destroy signal need not come from it. type(c_ptr) :: window end module common_ex1
For convenience all the gtk and system
use statements are in this module.
The main program creates the interface and does the initial drawing of the plots.
program cairo_plplot_ex1 use handlers_ex1 ! This gives the main program access to the signal handlers use plplot_code_ex1 ! This gives the main program access to the plotting code implicit none ! These widgets do not need to be explicitly accessed beyond the main program type(c_ptr) :: drawing, scroll_w, base, qbut ! Set the size of the drawing area (these are global variables) height = 1000 width = 1200 ! Initialize gtk call gtk_init() ! Create a top-level window and the pack a column box into it. window = hl_gtk_window_new("PLplot x01 / gtk-fortran (extcairo)"& & //c_null_char, & & delete_event = c_funloc(delete_cb)) base = hl_gtk_box_new() call gtk_container_add(window, base) ! Create a drawing area, in a 600x500 scrolled window. ! The high-level drawing area creator automatically adds a cairo surface as ! backing store. Here we use the default expose/draw callback which ! Just copies the backing store to the drawing surface. ! Pack it into the vertical box. drawing = hl_gtk_drawing_area_new(size=(/width, height/), & & has_alpha = FALSE, & & scroll = scroll_w, & & ssize=(/ 600, 500 /)) call hl_gtk_box_pack(base, scroll_w) ! Add a quit button, and pack that into the box as well. qbut = hl_gtk_button_new("Quit"//c_null_char, clicked=c_funloc(quit_cb)) call hl_gtk_box_pack(base, qbut, expand=FALSE) ! Display the widgets. call gtk_widget_show_all (window) ! Call the plotting routine. call x01f95(drawing) ! The event loop runs until it is exited. call gtk_main() print *, "All done" end program cairo_plplot_ex1
The plotting code
Here we will only discuss how to set up the plplot to access the gtk-fortran drawing surface. A discussion of how to make plots using plplot is beyond the scope of this document. The actual plotting code is take straight from the Fortran 95 version of example 1 on the plplot examples page.
module plplot_code_ex1 use plplot, PI => PL_PI use common_ex1 implicit none real(plflt) :: xscale, yscale, xoff, yoff contains subroutine x01f95(area) ! The area argument is the gtk drawing area created by hl_gtk_drawing_area_new type(c_ptr), intent(in) :: area ! cc is a cairo context connected with the drawing area. type(c_ptr) :: cc character(len=80) :: version character(len=20) :: geometry integer :: digmax ! Define colour map 0 to match the "GRAFFER" colour table in ! place of the PLPLOT default. integer, parameter, dimension(16) :: rval = (/255, 0, 255, & & 0, 0, 0, 255, 255, 255, 127, 0, 0, 127, 255, 85, 170/),& & gval = (/ 255, 0, 0, 255, 0, 255, 0, 255, 127, 255, 255, 127,& & 0, 0, 85, 170/), & & bval = (/ 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 127, 255, 255,& & 127, 85, 170/) ! Process command-line arguments call plparseopts(PL_PARSE_FULL) ! Print plplot version call plgver(version) write (*,'(a,a)') 'PLplot library version: ', trim(version) ! Get a cairo context from the drawing area. cc = hl_gtk_drawing_area_cairo_new(area) ! Initialize plplot colour tables and driver call plscmap0(rval, gval, bval) call plsdev("extcairo") ! By default the "extcairo" driver does not reset the background ! This is equivalent to the command line option "-drvopt set_background=1" call plsetopt("drvopt", "set_background=1") ! The "extcairo" device doesn't read the size from the context. write(geometry, "(I0,'x',I0)") width, height call plsetopt("geometry", geometry) ! Divide page into 2x2 plots call plstar(2,2) ! Tell the "extcairo" driver where the context is located. This must be ! done AFTER the plstar or plinit call. call pl_cmd(PLESC_DEVINIT, cc) ! Actual plotting code . . . ! Don't forget to call PLEND to finish off, and then delete the ! cairo context. call plend() call hl_gtk_drawing_area_cairo_destroy(cc) end subroutine x01f95
The crucial points here are:
- Getting a cairo context for the cairo surface used by the drawing area (
- The selection of the
extcairodriver. This is the only driver that can be used to write to a gtk_drawing_area (actually there is a kludgy way to do it using the
memdriver and pixbufs but it is messy and not compatible with the refresh management of the high-level drawing area).
- Setting the driver options with
plsetopt, most notably the geometry. Rather than using globals for the geometry, it would also be possible to use
cairo_get_targetto get the cairo context's drawing surface and then
gtk_widget_get_allocated_heightto find the geometry.
- The call to
pl_cmdthis must come after the call to
plstar. This call connects the driver's output to the cairo context.
- Destroying the cairo context after drawing (N.B. This just releases the reference obtained at the start).
- If you draw after starting
gtk_mainyou also need a call to
plendcall, see example
The callback routines (also sometimes called event handlers or signal handlers) specify actions to be taken when GTK+ signals or GDK events are emitted by widgets.
module handlers_ex1 use common_Ex1 implicit none contains function delete_cb (widget, event, gdata) result(ret) bind(c) integer(c_int) :: ret type(c_ptr), value :: widget, event, gdata call gtk_widget_destroy(window) call gtk_main_quit () ret = FALSE end function delete_cb subroutine quit_cb(widget, gdata) bind(c) type(c_ptr), value :: widget, gdata call gtk_widget_destroy(window) call gtk_main_quit () end subroutine quit_cb end module handlers_ex1
This is pretty much as simple as it gets. There are 2 callbacks one for the
delete-event event from the main window (caused by clicking the window-manager delete/close button on the main window) and one for the
clicked signal from the
Quit button. (In fact we could use the
destroy signal rather than the delete event which would allow both methods of exiting to share a callback).
The key points to note here:
- All callbacks must be declared with the
bind(c)property so that they can be correctly called by GTK+.
- Most (all?) callback arguments are C-pointers passed by value.
- Signal callbacks are subroutines with two arguments, the first is the widget emitting the signal and the second is user data. Some signals do have extra arguments--see the
examplesdirectory for some such cases.
- Event callbacks are functions and have at least three arguments, the second being the event structure. The function should normally return
- If you do not provide at least a handler for the
destroysignal or the
delete-eventevent then killing the window via the window manager will cause the window to disappear but the program will not exit.