PLplot integration - vmagnin/gtk-fortran GitHub Wiki
Scientific plotting and gtk-fortran
Introduction
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 Fortran iso_c_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 build system is being used. The source codes are from the GTK 4 branch.
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 -dev
or -devel
package and the cairo drivers are selected the other required packages will be installed as well.
The 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=true
option to cmake ..
).
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. The module plplot_extra
provides an interface to the pl_cmd
routine.
The examples
Several examples from the PLplot examples page have been adapted to work within gtk-fortran, and 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. (
hl_plplot1e.f90
) - Example 4: Two logarithmic x-y plots. Displayed in separate drawing areas in a notebook. (
hl_plplot4e.f90
) - Example 8: A 3-D surface plot. With controls to set the options. The window can be resized. (
hl_plplot8e.f90
) - Example 17: A constantly updating strip plot. (
hl_plplot17e.f90
) - Example 30: A demonstration of gradients and transparency. (
hl_plplot30.f90
)
Note that on some systems, it can be necessary to resize the window to see the plots when the surface is greater than the size of the window.
Elements of a PLplot + gtk-fortran program
In the following section we will highlight the key features of the first PLplot example hl_plplot1e.f90
.
Globals
These variables are shared by all of the other modules and the main program.
module common_ex1
use, intrinsic :: iso_c_binding ! Enable the c-binding routines & constants
! These are the gtk & glib routines used explicitly in the code:
use gtk, only: gtk_widget_show, gtk_window_set_child, gtk_window_destroy
! These are the high-level drawing area modules:
use gtk_draw_hl
! This makes the low-level pl_cmd routine accessible:
use plplot_extra
! The size of the drawing area:
integer(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
Main program
The main program creates the interface and does the initial drawing of the plots via the activate()
callback function.
module handlers_ex1
...
subroutine activate(app, gdata) bind(c)
! This gives the main program access to the plotting code
use plplot_code_ex1
use gtk, only: gtk_application_window_new, gtk_window_set_title
implicit none
type(c_ptr), value, intent(in) :: app, gdata
! Pointers toward our GTK widgets:
type(c_ptr) :: drawing, scroll_w, base, qbut
! Set the size of the drawing area (these are global variables)
height = 1000
width = 1200
! Create a top-level window and then pack a column box into it:
window = gtk_application_window_new(app)
call gtk_window_set_title(window, "PLplot x01 / gtk-fortran (extcairo)"//c_null_char)
base = hl_gtk_box_new()
call gtk_window_set_child(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(window)
! Call the plotting routine:
call x01f95(drawing)
end subroutine activate
end module handlers_ex1
program cairo_plplot_ex1
use, intrinsic :: iso_c_binding, only: c_ptr, c_funloc, c_null_char
! Set the size of the drawing area (these are global variables)
use handlers_ex1
use gtk_hl_container, only: hl_gtk_application_new
implicit none
type(c_ptr) :: my_app
! Initalize GTK, create the GUI and launch the main loop:
my_app = hl_gtk_application_new("gtk-fortran.plplot.hl_plplot1e"//c_null_char, &
& c_funloc(activate))
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)
type(c_ptr), intent(in) :: area
type(c_ptr) :: cc
character(len=80) :: version
character(len=20) :: geometry
integer :: digmax
! needed for use as functions instead of subroutines
integer :: plparseopts_rc
integer :: plsetopt_rc
! 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
plparseopts_rc = plparseopts(PL_PARSE_FULL)
if (plparseopts_rc /= 0) stop "plparseopts error"
! 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
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"
plsetopt_rc = plsetopt("drvopt", "set_background=1")
if (plsetopt_rc /= 0) stop "plsetopt error"
! The "extcairo" device doesn't read the size from the context.
write(geometry, "(I0,'x',I0)") width, height
plsetopt_rc = plsetopt( 'geometry', geometry)
if (plsetopt_rc /= 0) stop "plsetopt error"
! 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 (
hl_gtk_drawing_area_cairo_new
). - The selection of the
extcairo
driver. 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 themem
driver 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 usecairo_get_target
to get the cairo context's drawing surface and thencairo_image_surface_get_width
andcairo_image_surface_get_height
, orgtk_widget_get_allocated_width
andgtk_widget_get_allocated_height
to find the geometry. - The call to
pl_cmd
: this must come after the call toplinit
orplstar
. 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).
- GTK 3: if you draw after starting
gtk_main
you also need a call togtk_widget_queue_draw
after theplend
call, see examplehl_plplot8e.f90
.
Callbacks
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
use gtk_hl_container
use gtk_hl_button
use gtk_draw_hl
use, intrinsic :: iso_c_binding
implicit none
integer(c_int) :: run_status = TRUE
real(c_double), parameter :: pi = acos(-1.0_c_double)
contains
subroutine quit_cb(widget, gdata) bind(c)
type(c_ptr), value, intent(in) :: widget, gdata
call gtk_window_destroy(window)
end subroutine quit_cb
...
This is pretty much as simple as it gets. There is just a callback for the clicked
signal from the Quit
button, as in GTK 4 the destroy
signal emitted when you close the window is automatically managed by the GtkApplication.
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
examples
directory 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
FALSE
. - GTK 3: if you do not provide at least a handler for the
destroy
signal or thedelete-event
event then killing the window via the window manager will cause the window to disappear but the program will not exit.