-
Notifications
You must be signed in to change notification settings - Fork 43
PLplot integration
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.
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 ..
).
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.
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.
In the following section we will highlight the key features of the first PLplot example hl_plplot1e.f90
.
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
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
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
.
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.
- Installation
- My first gtk-fortran application
- Drawing an image in a PNG file (without GUI)
- A program also usable without GUI
- Using Glade3 and gtkf-sketcher (GTK 3)
- Using gtk-fortran as a fpm dependency
- Debugging with GtkInspector
- Learning from examples
- Video tutorials
- How to start my own project from a gtk-fortran example
- git basics
- CMake basics
- Alternatives to CMake
- How to migrate to GTK 4
- How to contribute to gtk-fortran
- How to hack the cfwrapper