A Basic Guide to using IupLua

by Steve Donovan

IupLua is a cross-platform kit for creating GUI applications in Lua. There are particularly powerful facilities for getting user input that don't require complicated coding, so it is particularly good for utility scripts.

Attributes are an important concept in IUP. You set and get them just like table fields, but they are different from fields in several crucial ways. First, case is not significant, SIZE is just as good as size (but try to be consistent!). Second, writing to a non-existent attribute will not give you an error, so proof-read carefully. Third, writing to an attribute often causes some action; e.g the visible attribute of controls can be used to hide them. It is best to think of them as a special kind of function call.

Functions which create IupLua objects (i.e. constructors) take tables as arguments. Lua allows you to drop the usual parentheses in such a case, but remember that something like iup.fill{} is not the same as iup.fill(); it is actually short for iup.fill({}). A Lua table can contain an array-like part (just items separated by commas) and a map-like part (attribute-value pairs); the convention is to put the array part first, and separate the map part from it with a semicolon. (See Attributes/Guide/IupLua in the Manual for a good discussion.)

All the examples presented here and some utilities can be found at the "misc" folder in the IupLua examples.

Simple Output

Even simple scripts need to give the user some feedback. Otherwise people get anxious and start worrying if their files really have been backed up, for example. This is easy in IUPLua, and takes exactly one line. Note that all IUP scripts must at least have a require 'iuplua' statement at the begining:

require( "iuplua" )

iup.Message('YourApp','Finished Successfully!')

Of course, many operations require confirmation from the user. iup.Alarm is designed for this:

require( "iuplua" )

b = iup.Alarm("IupAlarm Example", "File not saved! Save it now?" ,"Yes" ,"No" ,"Cancel")

-- Shows a message for each selected button
if b == 1 then
  iup.Message("Save file", "File saved sucessfully - leaving program")
elseif b == 2 then
  iup.Message("Save file", "File not saved - leaving program anyway")
elseif b == 3 then
  iup.Message("Save file", "Operation canceled")
end

Like iup.Message, the first parameter appears in the title bar of the dialog box, the second parameter appears above the buttons, but iup.Alarm allows you to specify a number of buttons. The return code will then tell you which button has been pressed, starting at 1 (which is always the Lua way.)

Simple Input

Asking for a Filename

The most common thing an interactive script will require from a user is a file, or set of files. For simple cases, iup.GetFile will do the job:

require( "iuplua" )

f, err = iup.GetFile("*.txt")
if err == 1 then
  iup.Message("New file", f)
elseif err == 0 then
  iup.Message("File already exists", f)
elseif err == -1 then
  iup.Message("IupFileDlg", "Operation canceled")
end

This will present you with the standard Windows File Open dialog box, and allow you to either choose a filename, or cancel the operation. Notice that this function returns two values, the filename and a code. The code will tell you whether the file does not exist yet (if for instance you typed a new filename into the file dialog box.)

Asking for Multiline Text

The simplest way of getting general text is to use iup.GetText:

require 'iuplua'

res = iup.GetText("Give me your name","")

if res ~= "" then
    iup.Message("Thanks!",res)
end

Using this dialog, you can enter as many lines as you like, and press OK.

Asking for a Single String, or Number

A better option for asking for a single string is the very versatile iup.GetParam:

require( "iuplua" )
require( "iupluacontrols" )

res, name = iup.GetParam("Title", nil,
    "Give your name: %s\n","")

iup.Message("Hello!",name)

prompt

This has two advantages over plain GetText; you can give a prompt line, and you can press Enter after entering text.

The %s code requires some explanation. Although you might at first think it is a C-style formating code, as you would use in string.format, it actually describes how the value is going to edited; %s here merely means that a regular text box is used; if you had used %m, then a multiline edit box (like that used by iup.GetText) would be used.

If there is a limited set of choices, then the %l format is useful:

res, prof = iup.GetParam("Title", nil,
    "Give your profession: %l|Teacher|Explorer|Engineer|\n",0)

Note the |item1|item2|...| list after the %l format; these are the choices presented to the user. The initial value you give it, and the value you receive from it, are going to be an index into this list of choices. Somewhat confusingly, they start at 0 (which is not the Lua way!) So in this case, 0 means that 'Teacher' is to be selected, and if I then selected 'Engineer', the resulting value of prof would be 2.

The %i code allows you to enter an integer value, with up/down arrows for incrementing/decrementing the value.

require( "iuplua" )
require( "iupluacontrols" )

res, age = iup.GetParam("Title", nil,
    "Give your age: %i\n",0)

if res ~= 0 then    -- the user cooperated!
    iup.Message("Really?",age)
end

Dialogs

Constructing General Layouts

GetParam is a very versatile facility for asking for data, but it is not very interactive. In general, you want to present something back to the user that is more complicated than a simple message. Up to now we have used the predefined dialogs available to IupLua; it is now time to go beyond that and examine custom dialogs. The structure of a simple IupLua program is straightforward:

require 'iuplua'

text = iup.multiline{expand = "YES"}

dlg = iup.dialog{text; title="Simple Dialog",size="QUARTERxQUARTER"}

dlg:show()

iup.MainLoop()

A multiline edit control is created, and put inside a window frame with a given size, which is then made visible with the show method. We then enter the main loop of the application with MainLoop, which will only finish when the window is closed.

Controls are also windows, but without the frame and decorations of a top-level window; they are always meant to be inside some window frame or other container. We set the expand attribute of multiline to force it to use up all the available space in the frame, so that it takes its size from its container. The dialog's size attribute is a string of the form "XSIZExYSIZE", where sizes can be expressed as fractions of the desktop window size, in this case a quarter of the width and height. (You can of course also use numerical sizes like "100x301" but these will not always scale well on displays with different resolutions. See Attributes/Common/SIZE in the manual for these units.)

text

It's good to pause a moment to look at the resulting application in action; it is fully responsive and you can enter text, paste, etc. into the edit control. Common keyboard shortcuts like Ctrl+V and Ctrl+C work as expected. All this functionality comes with the windowing system you are currently using. On my system, Task Manager shows that this program uses 3.8 Meg of memory, and an instance of Notepad uses 3.3 Meg, which represents all the common code necessary to support a simple GUI application; you are not actually paying much for using IupLua at all. The equivalent C program using the Windows API would be about 150 lines, so the gain in programmer efficiency is tremendous!

Of course, there is not much interaction possible with such a simple program. To make a program respond to the user we define callbacks which the system calls when some event takes place. For example, we can put a button control in the dialog, and define its action callback:

require 'iuplua'

btn = iup.button{title = "Click me!"}

function btn:action ()
    iup.Message("Note","I have been clicked!")
    return iup.DEFAULT
end

dlg = iup.dialog{btn; title="Simple Dialog",size="QUARTERxQUARTER"}

dlg:show()

iup.MainLoop()

This is perfectly responsive, although not very useful! The button sizes itself to its natural size since expand is not set (try setting expand to see the button fill the whole window frame.) Callbacks usually return the special value iup.DEFAULT, although in IupLua this is not really necessary.

dialog takes only one control, so IupLua defines containers in which you can pack as many controls as you like. Here vbox is used to pack two buttons into the dialog vertically (To save space I'm leaving out the 'dlg:show...' common code at the bottom)

btn1 = iup.button{title = "Click me!"}
btn2 = iup.button{title = "and me!"}

function btn1:action ()
    iup.Message("Note","I have been clicked!")
end

function btn2:action ()
    iup.Message("Note","Me too!")
end

box = iup.vbox {btn1,btn2}

dlg = iup.dialog{box; title="Simple Dialog",size="QUARTERxQUARTER"}

This does the job, although the buttons are sized differently according to their contents; this program would not win any design contests! Still, you now have two commands in your application. You can actually get a more pleasing result by using a horizontal packing box (hbox) and specifying a non-zero gap between the buttons:

box = iup.hbox {btn1,btn2; gap=4}

You can nest boxes as much as you like, which is the way to construct more complicated layouts. Here are our horizontal buttons packed vertically with a multiline edit control:

bbox = iup.hbox {btn1,btn2; gap=4} text = iup.multiline{expand = "YES"} vbox = iup.vbox{bbox,text}

dlg = iup.dialog{vbox; title="Simple Dialog",size="QUARTERxQUARTER"}

We have effectively implemented a crude but functional toolbar:

buttons

A labeled frame can be put around a control using iup.frame:

edit1 = iup.multiline{expand="YES",value="Number 1"}
edit2 = iup.multiline{expand="YES",value="Number 2?"}

box = iup.hbox {iup.frame{edit1;Title="First"},iup.frame{edit2;Title="Second"}}

A useful way to present various views to a user is to put them in tabs. This places each control in a separate page, accessible through the tabbar at the top. Notice in this example that the titles of the tab pages are actually set as attributes of the pages through tabtitle. This is not one of the standard IUP controls (see Controls/Additional in the manual) so we also need to bring in the iupluacontrols library.

require( "iupluacontrols" )

edit1 = iup.multiline{expand="YES",value="Number 1",tabtitle="First"}
edit2 = iup.multiline{expand="YES",value="Number 2?",tabtitle="Second"}

tabs = iup.tabs{edit1,edit2,expand='YES'}

Timers and Idle Processing

Sometimes a program needs to wake up and perform some operation, such as a scheduled backup or an autosave operation. IupLua provides timers for this purpose. (Note at this point that there is no reason why you can't have print in a IupLua application; sometimes there is no better way to track what's going on. But on Windows you do have to run the program using the regular lua.exe, not wlua.exe.)

-- timer1.lua

require "iuplua"

timer = iup.timer{time=500}

btn = iup.button {title = "Stop",expand="YES"}

function btn:action ()
    if btn.title == "Stop" then
        timer.run = "NO"
        btn.title = "Start"
    else
        timer.run = "YES"
        btn.title = "Stop"
    end
end

function timer:action_cb()
    print 'timer!'
end

timer.run = "YES"

dlg = iup.dialog{btn; title="Timer!"}
dlg:show()
iup.MainLoop()

After a timer has been started by setting its run attribute to "YES", it will continue to call action_cb using the given time in milliseconds. Notice that it is important to set the timer going only after the callback has been defined. It is perfectly permissable to switch a timer off in the callback, which is how you can perform a single action after waiting for some time.

It is a well-known fact that computers spend most of the time doing very little, waiting for incredibly slow humans to type something new. However, when a computer is actually doing intense processing, users become impatient if not told about progress. If you do your lengthy processing directly, then the windows of the application become unresponsive. The proper way to organize such work is to do it when the system is idle.

IupLua provides a gauge control which is intended to show progress; this little program shows that even when the computer is almost completely preoccupied doing work, it is still keeping the user informed and in fact the window remains useable, although a little slow to respond.

-- idle1.lua
require "iuplua"
require "iupluacontrols"

function do_something ()
    for i=1,6e7 do end
end

gauge = iup.gauge{show_text="YES"}

function idle_cb()
    local value = gauge.value
    do_something()
    value = value + 0.1
    if value > 1.0 then
        value = 0.0
    end
    gauge.value = value
    return iup.DEFAULT
end

dlg = iup.dialog{gauge; title = "IupGauge"}

iup.SetIdle(idle_cb)

dlg:showxy(iup.CENTER, iup.CENTER)

iup.MainLoop()

Lists

It is easy to display a list of values in IupLua. The values can be directly specified in the iup.list constructor, like so:

-- list1.lua
require "iuplua"

list = iup.list {"Horses","Dogs","Pigs","Humans"; expand="YES"}

dlg = iup.dialog{list; title="Lists"}
dlg:show()
iup.MainLoop()

(Remember that the single argument to these constructors is just a Lua table, which you can construct in any way you choose, say by reading the values from a file.)

Tracking selection changes is straightforward:

function list:action(t,i,v)
    print(t,i,v)
end

Now, as I move the selection through the list, from the start to the finish, the output is:

Horses  1       1
Horses  1       0
Dogs    2       1
Dogs    2       0
Pigs    3       1
Pigs    3       0
Humans  4       1

So v is 1 if we are selecting an item, 0 if we are deselecting it; i is the one-based index in the list, and t is the actual string value. If you want to associate some other data with each value, then all you need to do is keep a table of that data and look it up using the index i.

To register a double-click is a little more involved. There is (as far as I can tell) no way to detect whether a double-click has happened in the action callback. So we track the selection manually; if two selection events for a given item happen consecutively, then that is understood to be a double-click. It ain't pretty, but it works (except perhaps for the valid case of a person wanting to double-click the same item repeatedly):

local lastidx,doubleclick

function on_double_click (t,i)
    print(t,i)
end

function list:action(t, i, v)
    if v ~= 0 then
        if lastidx == i and doubleclick ~= i then
            on_double_click(t,i)
            doubleclick = i
        end
        lastidx = i
    end
end

Once a list has been created, how does one change the contents? The answer is that the list object behaves like an array. For example, to fill a list with all the entries in a directory, I can use this function:

function fill (path)
    local i = 1
    for f in lfs.dir(path) do
        list[i] = f
        i = i + 1
    end
    list[i] = nil
end

Note that this does not mean that a list object is a table. In particular, you have to explicitly set the end of the list of elements by setting a nil value just after the end.

Trees

The most flexible way to present a hierarchy of information is a tree. A tree has a single root, and several branches. Each of these branches may have leaves, and other branches. All of these are called nodes. Thinking of a family tree, a node may have child nodes, which all share the same parent node.

A good example of this in everyday computer experience is a filesystem, where the leaves are files and the branches are directories. Lua tables naturally express these kind of nested structures easily, and in fact it is easy to present a Lua table as a tree, where array items are leaves, and the branches are named with the special field branchname:

require 'iuplua'
require 'iupluacontrols'

tree = iup.tree{}
tree.addexpanded = "NO"

list = {
  {
    "Horse",
    "Whale";
    branchname = "Mammals"
  },
  {
    "Shrimp",
    "Lobster";
    branchname = "Crustaceans"
  },
  {
    branchname = "Birds"
  },
  branchname = "Animals"
}

iup.TreeAddNodes(tree, list)

f = iup.dialog{tree; title = "Tree Test"}

f:show()

iup.MainLoop()

This example begins with the branches 'collapsed', and you will have to explicitly expand them with a mouse click. By default, trees are presented in their fully expanded form; try taking out the fourth line that sets the addexpanded attribute of the tree object. Note that branches can be empty!

Tree operations are naturally more complicated than list operations, but there is a callback which happens when a node is selected or unselected. Add this to the example:

function tree:selection_cb (id,iselect)
    print(id,iselect,tree.name)
end

You will see that iselect is 0 for the unselection operation, and 1 for selection; id is a tree node index. These indices are always in order of appearance in a tree, starting at 0 for the root node. The name attribute of the tree object is the text of the currently selected node.

A pair of useful callbacks are branchopencb and branchclosecb. If you were displaying a potentially very large tree (like your computer's filesystem) then it would be inefficient to create the whole tree at once, especially considering that you would normally be only interested in a small part of that tree. Trapping branchopencb allows you to add child nodes to your selected node before it is expanded. executeleafcb is called when you double-click on a leaf, as if you were running a program in a file explorer.

In itself, the id is not particularly useful. The id order is always the same in the tree, so as nodes get added and removed, the id of a particular node will change. Generally, there is going to be some deeper data associated with a node. On a filesystem, a node represents a full path to a file or directory, or there may be an ip address associated with a computer name. IUP provides you with a way to associate arbitrary data with nodes even if the id changes. But to use this you will have to understand how to build up a tree from scratch - TreeAddNodes is very convenient, but won't help you if you have to add nodes later. Replace the definition of list and the call to TreeAddNodes with this code:

tree.name = "Animals"
tree.addbranch = "Birds"
tree.addbranch = "Crustaceans"
tree.addbranch = "Mammals"

You will get the top level branches of the tree; notice that they are specified in reverse order, since nodes are always added to the top. Also note the curious way in which the addbranch attribute is used. For a start, it is write-only, and the effect of setting a value to it is to add a new branch to the currently selected node. By default, this starts out as the root (which is set using the name attribute) The id of the root is always 0; when we add "Birds", the new branch has id 1, again when we add "Crustaceans" the new branch also has id 1 - by which time "Birds" has moved to id 2, further down the tree.

To add leaves, a similar process:

tree.name = "Animals"
tree.addbranch = "Mammals"
tree.addleaf1 = "Whale"

The addleaf attribute works like addbranch, and both of them can take an extra parameter, which is the id of the node to add to. In this case, "Whale" is a child leaf of the "Mammals" branch, which has id 1 at this stage. This new leaf gets an id of 2, which is one more than the parent. So this gives us a way to build up arbitrary trees, knowing the id at each point. IUP provides a function TreeSetUserId which can associate a Lua table with a node id. We can choose to put a string value inside this table, but it really can contain anything. Here is the first example, using some helper functions to simplify matters:

-- testtree2.lua

require 'iuplua'
require 'iupluacontrols'
tree = iup.tree{}

function assoc (idx,value)
    iup.TreeSetUserId(tree,idx,{value})
end

function addbranch(self,label,value)
    self.addbranch = label
    assoc(1,value or label)
end

function addleaf(self,label,value)
    self.addleaf1 = label
    assoc(2,value or label)
end

tree.name = "Animals"
addbranch(tree,"Birds")
addbranch(tree,"Crustaceans")
addleaf(tree,"Shrimp")
addleaf(tree,"Lobster")
addbranch(tree,"Mammals")
addleaf(tree,"Horse")
addleaf(tree,"Whale")

function dump (tp,id)
    local t = iup.TreeGetUserId(tree,id)
    -- our string data is always the first element of this table
    print(tp,id,t and t[1])
end

function tree:branchopen_cb(id)
    dump('open',id)
end

function tree:selection_cb (id,iselect)
    if iselect == 1 then dump('select',id) end
end

f = iup.dialog{tree; title = "Tree Test"}

f:show()

iup.MainLoop()

There is a corresponding function TreeGetUserId which accesses the table associated with the node id. There is also a function TreeGetId which will return the id, given the unique table associated with it. You can use this to programmatically select a tree node given its data by setting the value attribute to the returned id.

Now let's do something interesting with a tree control, a simple file browser. It is straightforward to get the files and directories contained within a directory:

require 'lfs'

local append = table.insert

function get_dir (path)
    local files = {}
    local dirs = {}
    for f in lfs.dir(path) do
        if f ~= '.' and f ~= '..' then
            if lfs.attributes(path..'/'..f,'mode') == 'file' then
                append(files,f)
            else
                append(dirs,f)
            end
        end
    end
    return files,dirs
end

We ignore '.' and '..' (the current and parent directory respectively) and check the mode to see if we have file or a directory; this requires the full path to be passed to attributes. This function returns two separate tables containing the names of the files and directories.

It is useful to define two helper functions for setting and getting data to be associated with the tree nodes:

tree = iup.tree {}

function set (id,value,attrib)
    iup.TreeSetUserId(tree,id,{value,attrib})
end

function get(id)
    return iup.TreeGetUserId(tree,id)
end

Filling a tree with the contents of a directory is straightforward. We want the directories before the files, so we put them in last; nodes must be added in reverse order! The id of the new nodes will always be id+1 where id is going to be the directory which we are filling. The fullpath plus a field indicating whether we are a directory is associated with each new item:

function fill (path,id)
    local files,dirs = get_dir(path)
    id = id + 1
    local state = "STATE"..id
    for i = #files,1,-1 do -- put the files in reverse order!
        tree.addleaf = files[i]
        set(id,path..'/'..files[i])
    end
    for i = #dirs,1,-1 do -- ditto for directories!
        tree.addbranch = dirs[i]
        set(id,path..'/'..dirs[i],'dir')
        tree[state] = "COLLAPSED"
    end
end

By default, the directory branches will be created in their expanded form, so we use the STATE attribute to force them into their collapsed state. Normally you would say this in Lua like so state2 = "COLLAPSED" but here we build up the appropriate attribute string with the given id and use array indexing to set the tree attribute.

Just calling fill('.',0) and putting the tree into a dialog as usual will give you a directory listing of the current directory! But it would be cool if expanding a directory node would automatically fill that node; it would obviously be wasteful to fill the whole tree at startup, since your filesystem contains thousands of files. The branchopencb callback is called when a user tries to expand a directory. We use this to fill the directory with its contents, but only on the _first time that we expand this node:

function tree:branchopen_cb(id)
    tree.value = id
    local t = get(id)
    if t[2] == 'dir' then
        fill(t[1],id)
        set(id,t[1],'xdir')
    end
end

This is why directories need to be specially marked, so we can tell later whether we have actually generated the contents of that directory!

The first statement of this function makes the node we are opening to be the selected node of the tree. (Although we are passed the correct id of the node, it seems to be necessary to perform this step to make things work correctly.)

See directory.lua in the examples folder.

directory

Menus

Any application that can perform a number of operations needs a menu. These are not difficult to create in Iuplua, although it can be a little tedious to set up. The basic idea is this: create some items, make a menu out of these items, and set the menu attribute of the dialog. The items have an associated action callback, which actually performs the operation.

-- simple-menu.lua

require( "iuplua" )

-- Creates a text, sets its value and turns on text readonly mode
text = iup.text {readonly = "YES", value = "Show or hide this text"}

item_show = iup.item {title = "Show"}
item_hide = iup.item {title = "Hide"}
item_exit = iup.item {title = "Exit"}

function item_show:action()
  text.visible = "YES"
  return iup.DEFAULT
end

function item_hide:action()
  text.visible = "NO"
  return iup.DEFAULT
end

function item_exit:action()
  return iup.CLOSE
end

menu = iup.menu {item_show,item_hide,item_exit}

-- Creates dialog with a text, sets its title and associates a menu to it
dlg = iup.dialog{text; title="Menu Example", menu=menu}

-- Shows dialog in the center of the screen
dlg:showxy(iup.CENTER,iup.CENTER)

iup.MainLoop()

A menu may contain items and submenus. This example shows a small function which makes creating arbitrarily complicated menus easier:

-- menu.lua

require( "iuplua" )

function default ()
    iup.Message ("Warning", "Only Exit performs an operation")
    return iup.DEFAULT
end

function do_close ()
    return iup.CLOSE
end

mmenu = {
    "File",{
        "New",default,
        "Open",default,
        "Close",default,
        "-",nil,
        "Exit",do_close,
    },
    "Edit",{
        "Copy",default,
        "Paste",default,
        "-",nil,
        "Format",{
            "DOS",default,
            "UNIX",default
        }
    }
}

function create_menu(templ)
    local items = {}
    for i = 1,#templ,2 do
        local label = templ[i]
        local data = templ[i+1]
        if type(data) == 'function' then
            item = iup.item{title = label}
            item.action = data
        elseif type(data) == 'nil' then
            item = iup.separator{}
        else
            item = iup.submenu {create_menu(data); title = label}
        end
        table.insert(items,item)
    end
    return iup.menu(items)
end

-- Creates a text, sets its value and turns on text readonly mode
text = iup.text {value = "Some text", expand = "YES"}

-- Creates dialog with a text, sets its title and associates a menu to it
dlg = iup.dialog {text; title = "Creating Menus With a Table",
      menu = create_menu(mmenu), size = "QUARTERxEIGHTH"}

-- Shows dialog in the center of the screen
dlg:showxy (iup.CENTER,iup.CENTER)

iup.MainLoop()

The function create_menu does all the work; we provide it with a Lua table containing pairs of values; the first value of a pair is always a string, and will be the label. The second value can either be a function, in which case it represents an item to be associated with a callback, or nil, which means that it's a separator, or otherwise must be a table, which represents a submenu. It is a nice example of how recursion can naturally handle nested structures like menus, and how Lua's flexible table definitions can make specifying such structures easy. This useful function is available in the iupx utility library as iupx.menu.

Plotting Data

Many kinds of numerical data are best seen as X-Y plots. iup.plot is a control which can show several kinds of plots; you can have lines between points, show them as markers, or both together. Several series (or datasets) can be shown on a single plot, and a simple legend can be shown. The plot will automatically scale to view all datasets, but the default minimum and maximum x and y values can be changed. It is even possible to select points and edit them on the plot.

A simple plot is straightforward:

-- plot1.lua
require( "iuplua" )
require( "iupluacontrols" )
require( "iuplua_plot51"  )

plot = iup.plot{TITLE = "A simple XY Plot",
                    MARGINBOTTOM="35",
                    MARGINLEFT="35",
                    AXS_XLABEL="X",
                    AXS_YLABEL="Y"
                    }

iup.PlotBegin(plot,0)
iup.PlotAdd(plot,0,0)
iup.PlotAdd(plot,5,5)
iup.PlotAdd(plot,10,7)
iup.PlotEnd(plot)

dlg = iup.dialog{plot; title="Plot Example",size="QUARTERxQUARTER"}

dlg:show()

iup.MainLoop()

Creating a dataset involves calling PlotBegin, a number of calls to PlotAdd to add data points, and finally a call to PlotEnd. You can create multiple datasets (or series) using multiple begin/end calls, and can of course use loops to add points:

iup.PlotBegin(plot,0)
for x = -2,2,0.01 do
    iup.PlotAdd(plot,x,math.sin(x))
end
iup.PlotEnd(plot)

iup.PlotBegin(plot,0)
for x = -2,2,0.01 do
    iup.PlotAdd(plot,x,math.cos(x))
end
iup.PlotEnd(plot)

plot.DS_LINEWIDTH = 3

A limitation of the plot library is that it does not choose appropriate sizes for the plot margins. So I've had to set the bottom and left margins (in pixels) to properly accomodate the axes and their titles. As with all IupLua attributes, you can choose to make them upper case if you like; a full list is found in the manual under Controls/Additional/IupPlot. Some of these attributes refer to the plot as a whole, some to the current dataset. For instance, setting GRID to "YES" will draw gridlines for both axes, but if we set DS_LINEWIDTH to 3 after the construction of the cosine dataset, then only that line is affected.

Some attributes affect others. DS_MODE is used to specify how to draw the dataset; it can be "LINE", "BAR", (for a bar chart) "MARK" (just for marks) or "MARKLINE" (for lines and marks). But it has to be set before any of the other DS_ attributes like DS_MARKSIZE, etc. In another case, you will often find it useful to set an explicit minimum y value by setting AXS_YMIN. But it will only take effect if AXIS_YAUTOMIN has been set to "NO" to disable auto scaling.

As with menus, making a Lua-friendly wrapper around an API is not difficult and can be very labour-saving. It would be clearer if we could work with the plot object in a more object-oriented way:

    plot:Begin()
    for i = 1,#xvalues do
            plot:Add(xvalues[i],yvalues[i])
    end
    plot:End()

And for the common case where you have arrays of values, it would be convenient to be able to say:

plot:AddSeries({{0,1.5},{5,4.5},{10,7.6}},{DS_MODE="MARK"})

Here is a function which wraps the Plot API:

function create_plot (tbl)
    -- don't need to remember this anymore!
    require( "iuplua_plot51"  )

    -- the defaults for these values are too small, at least on my system!
    if not tbl.MARGINLEFT then tbl.MARGINLEFT = 30 end
    if not tbl.MARGINBOTTOM then tbl.MARGINBOTTOM = 35 end

    -- if we explicitly supply ranges, then auto must be switched off for that direction.
    if tbl.AXS_YMIN then tbl.AXS_YAUTOMIN = "NO" end
    if tbl.AXS_YMAX then tbl.AXS_YAUTOMAX = "NO" end
    if tbl.AXS_XMIN then tbl.AXS_XAUTOMIN = "NO" end
    if tbl.AXS_XMAX then tbl.AXS_XAUTOMAX = "NO" end

    local plot = iup.plot(tbl)
    plot.End = iup.PlotEnd
    plot.Add = iup.PlotAdd
    function plot.Begin ()
        return iup.PlotBegin(plot,0)
    end

    function plot:AddSeries(xvalues,yvalues,options)
        plot:Begin()
        -- is xvalues a table of (x,y) pairs?
        if type(xvalues[1]) == "table" then
            -- because there's only one data table, the next must be options
            options = yvalues
            for i,v in ipairs(xvalues) do
                plot:Add(v[1],v[2])
            end
        else
            for i = 1,#xvalues do
                plot:Add(xvalues[i],yvalues[i])
            end
        end
        plot:End()
        -- set any series-specific plot attributes
        if options then
            -- mode must be set before any other attributes!
            if options.DS_MODE then
                plot.DS_MODE = options.DS_MODE
                options.DS_MODE = nil
            end
            for k,v in pairs(options) do
                plot[k] = v
            end
        end
    end

    function plot:Redraw()
        plot.REDRAW='YES'
    end
    return plot
end

This function creates a Plot object as usual, but supplies some more sensible defaults for the margins, makes setting things like AXS_XMAX also set AXS_XAUTOMAX, and adds some new methods to the object. Of these, AddSeries is the interesting one. It allows you to specify the data in two forms; either as two arrays of x and y values, or as a single array of x-y pairs. It also allows optionally setting DS_ attributes, taking care to set the plot mode before any other attributes. In this way, the actual details can be hidden away from the programmer, who has then less things to worry about.

Given this function, we can write a little program which plots some points and draws the linear least-squares fit between them:

-- simple-plot.lua

local xx = {0,2,5,10}
local yy = {1,1.5,6,8}

function least_squares (xx,yy)
    local xsum = 0.0
    local ysum = 0.0
    local xxsum = 0.0
    local yysum = 0.0
    local xysum = 0.0
    local n = #xx
    for i = 1,n do
        local x,y = xx[i], yy[i]
        xsum = xsum + x
        ysum = ysum + y
        xxsum = xxsum + x*x
        yysum = yysum + y*y
        xysum = xysum + x*y
    end
    local m = (xsum*ysum/n - xysum )/(xsum*xsum/n - xxsum)
    local c = (ysum - m*xsum)/n
    return m,c
end

local m,c = least_squares(xx,yy)

function eval (x) return m*x + c end

local plot = create_plot {TITLE = "Simple Data",AXS_YMIN=0,GRID="YES"}

-- the original data
plot:AddSeries(xx,yy,{DS_MODE="MARK",DS_MARKSTYLE="CIRCLE"})

-- the least squares fit
local xmin,xmax = xx[1],xx[#xx]
plot:AddSeries({xmin,xmax},{eval(xmin),eval(xmax)})

plot

create_plot is so useful that I've packaged it as part of the iupx library as iupx.plot. A new pseudo-attribute has been introduced, AXS_BOUNDS, which is a table of four values {xmin,ymin,xmax,ymax}. This example shows that very different ranges can happily exist on the same plot:

-- plot5.lua

require "iupx"

plot = iupx.plot {TITLE = "Simple Data", AXS_BOUNDS={0,0,100,100}}

plot:AddSeries ({{0,0},{10,10},{20,30},{30,45}})
plot:AddSeries ({{40,40},{50,55},{60,60},{70,65}})

iupx.show_dialog{plot; title="Easy Plotting",size="QUARTERxQUARTER"}