Skip to content

Plplot integration

jtappin edited this page Sep 25, 2012 · 7 revisions

Scientific plotting and gtk-fortran


Under construction


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 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 -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=Y option to cmake.

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).

The module plplot_extra provides an interface to the pl_cmd routine and also mnemonic definitions of the command codes.

Plplot 5.9.7

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

The examples

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. (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)

Elements of a plplot + gtk-fortran program

In the following section we will highlight the key features of the first plplot example.

Globals

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.

Main program

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 (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 the mem 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 use cairo_get_target to get the cairo context's drawing surface and then cairo_image_surface_get_width and cairo_image_surface_get_height, or gtk_widget_get_allocated_width and gtk_widget_get_allocated_height to find the geometry.
  • The call to pl_cmd this must come after the call to plinit or 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_main you also need a call to gtk_widget_queue_draw after the plend call, see example hl_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

  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 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.
  • If you do not provide at least a handler for the destroy signal or the delete-event event then killing the window via the window manager will cause the window to disappear but the program will not exit.
Something went wrong with that request. Please try again.