-- Plotserver.lua -- This file is launched by plot module to create a plotting server -- All plotting requests go to this file once launched using LuaSocket -- This allows user to have plots simultaneously with the lua interpreter -- using the standard lua interpreter -- Milind Gupta 6/6/2014 -- SERVER SOCKET COMMANDS -- END - Shutdown and exit -- PLOT - Create a plot object, 2nd index contains the table of Attributes for the plot -- ADD DATA - Add data points to a plot, 2nd index is the plot index, 3rd 4th and 5th can be xvalues, yvalues and options or just 3rd and 4th -- index parameters can be table of x and y values {{x1,y1},{x2,y2}...{xn,yn}} and options -- LIST PLOTS - List all plot numbers and objects in memory -- WINDOW - Create a Window object for multiple plots. 2nd index contains a table with -- list of numbers specifying the number of spaces in each row of the window where plots can be shown and key value pairs -- for the window dialog attributes -- ADD PLOT - Add a plot to a slot in a window, 2nd index contains the window index, 3rd index contains the plot index, -- 4th index contains the coordinate table -- SHOW WINDOW - Display the window on screen, 2nd index is the index of the window object -- DESTROY WIN - Command to mark a window object for destruction, 2nd index is the index of the window object local iup = require("iuplua") require("iuplua_pplot") local timer local managedPlots = {} -- table of plot objects with numeric keys incremented for each new plot. -- The plots may be removed from between if they are garbage collected in -- the parent and closed here without changing the indices of the other plots local plot2Dialog = {} -- Mapping from the plot object to the dialog object it is contained in listed in managedDialogs local managedDialogs = {} -- Table of created dialogs with numeric keys incremented for each new dialog -- When a dialog is closed then the key automatically becomes nil so no need to destroy it. local managedWindows = {} -- table of window objects with numeric keys incremented for each new window. -- The windows may be removed from between if they are garbage collected in -- the parent and closed here without changing the indices of the other windows local exitProg local DBG function window(tbl) local winObj = {} winObj.rowBoxes = {} -- To hold the hboxes for each row winObj.colBoxes = {} -- To hold the hboxes for each slot winObj.slots = {} -- Table to map which slot contains which plots winObj.colBox = iup.vbox{} -- One vertical addition box to correspond to number of rows desired winObj.colBox.homogeneous = "YES" -- distribute space evenly for i = 1,#tbl do winObj.slots[i] = {} winObj.rowBoxes[i] = iup.hbox{} winObj.rowBoxes[i].homogeneous = "YES" -- distribute space evenly winObj.colBoxes[i] = {} for j = 1,tbl[i] do winObj.colBoxes[i][j] = iup.hbox{} winObj.colBoxes[i][j].expand = "YES" iup.Append(winObj.rowBoxes[i],winObj.colBoxes[i][j]) end iup.Append(winObj.colBox,winObj.rowBoxes[i]) end winObj.dialog = iup.dialog{winObj.colBox} for k,v in pairs(tbl) do if type(k) ~= "number" then -- This is a dialog attribute winObj.dialog[k] = v end end --[[ local dlgObject = winObj.dialog function dlgObject:close_cb() return iup.IGNORE end]] return winObj end function pplot (tbl) if not tbl then tbl = {} end if tbl.AXS_BOUNDS then local t = tbl.AXS_BOUNDS tbl.AXS_XMIN = t[1] tbl.AXS_YMIN = t[2] tbl.AXS_XMAX = t[3] tbl.AXS_YMAX = t[4] tbl.AXS_BOUNDS = nil end -- the defaults for these values are too small, at least on my system! if not tbl.MARGINLEFT then tbl.MARGINLEFT = 40 end if not tbl.MARGINBOTTOM then tbl.MARGINBOTTOM = 45 end if not tbl.MARGINTOP then tbl.MARGINTOP = 45 end if not tbl.MARGINRIGHT then tbl.MARGINRIGHT = 40 end -- Setting these 2 parameters allows axis to be shown even is the origin is not visible in the dataset area if not tbl.AXS_XCROSSORIGIN then tbl.AXS_XCROSSORIGIN = "NO" end if not tbl.AXS_YCROSSORIGIN then tbl.AXS_YCROSSORIGIN = "NO" 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.pplot(tbl) plot.End = iup.PPlotEnd plot.Add = iup.PPlotAdd function plot.Begin () return iup.PPlotBegin(plot,0) end function plot:AddSeries(xvalues,yvalues,options) plot:Begin() if type(xvalues[1]) == "table" then 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 -- Main function to launch the iup loop local function setupTimer() -- Setup timer to run housekeeping timer = iup.timer{time = 100, run = "YES"} -- run timer with every 100ms action local retry local destroyQ = {} local destroyWinQ = {} local currCount = 0 ins = { -- Create plot 1 {"PLOT" }, -- Add data {"ADD DATA", 1, { {0,0}, {1,1}, {2,2}, {3,3} } }, -- Create plot 2 {"PLOT" }, -- Add data {"ADD DATA", 2, { {0,0}, {1,2}, {2,4}, {3,6} } }, -- Create plot 3 {"PLOT" }, -- Add data {"ADD DATA", 3, { {0,0}, {1,3}, {2,6}, {3,9} } }, -- create window 1 {"WINDOW" }, -- create window 2 {"WINDOW" }, -- create window 3 {"WINDOW" }, -- Add plot 1 to window 1 {"ADD PLOT",1,1,{1,1} }, -- Add plot 2 to window 2 {"ADD PLOT",2,2,{1,1} }, -- Add plot 3 to window 3 {"ADD PLOT",3,3,{1,1} }, -- Show window 1 {"SHOW WINDOW",1 }, -- Show window 2 {"SHOW WINDOW", 2 }, -- Show window 3 {"SHOW WINDOW", 3 }, -- Destroy window 1 instruction {"DESTROY WIN",1 }, -- Destroy window 2 instruction {"DESTROY WIN",2 }, -- Destroy window 3 instruction {"DESTROY WIN",3 } } function timer:action_cb() local err,retmsg timer.run = "NO" -- Check if any plots in destroyQ and if they can be destroyed to free up memory if #destroyQ > 0 then local i = 1 while i<=#destroyQ do --[[ if DBG then print("i is"..i) end]] -- check if the plot is not tied to any window local found for k,v in pairs(managedWindows) do for n = 1,#v.slots do for m,j in pairs(v.slots[n]) do if j == destroyQ[i] then found = true break end end if found then break end end if found then break end end if not plot2Dialog[destroyQ[i]] and not found then --print("Destroy Q length="..#destroyQ) --print("Destroying object:"..tostring(destroyQ[i])) -- destroy the plot data for k,v in pairs(managedPlots) do if v == destroyQ[i] then managedPlots[k] = nil break end end --print("Destroying Plot:"..tostring(destroyQ[i])) iup.Destroy(destroyQ[i]) --DBG = destroyQ[i] table.remove(destroyQ,i) --print("Both destroyed and entry removed from destroyQ. destroyQ length is now="..#destroyQ) else i = i + 1 end end end -- if #destroyQ > 0 then -- Check if any windows in destroyWinQ and if they can be destroyed to free up memory if #destroyWinQ > 0 then local i = 1 while i <= #destroyWinQ do --print("#destroyWinQ: "..#destroyWinQ) if destroyWinQ[i].dialog.visible == "NO" then print("destroying: "..tostring(destroyWinQ[i])) -- first detach all the plots for j = 1,#destroyWinQ[i].slots do for k,v in pairs(destroyWinQ[i].slots[j]) do iup.Detach(v) end end local dlgIndex for k,v in pairs(managedWindows) do if v == destroyWinQ[i] then dlgIndex = k break end end iup.Destroy(destroyWinQ[i].dialog) managedWindows[dlgIndex] = nil table.remove(destroyWinQ,i) else i = i + 1 end end -- while i <= destroyQ do ends end -- if #destroyWinQ > 0 then ends -- Execute the next set of instructions currCount = currCount + 1 local msg if currCount == #ins + 1 then print("Now close any window") end if currCount > #ins+2 then currCount = #ins + 2 end if currCount < #ins+1 then print("Command is:",ins[currCount][1],ins[currCount][2]) msg = ins[currCount] end if msg then if msg then if msg[1] == "END" then exitProg = true iup.Close() elseif msg[1] == "PLOT" then -- Create a plot and return the plot index managedPlots[#managedPlots + 1] = pplot(msg[2]) print([[{"ACKNOWLEDGE",]]..tostring(#managedPlots).."}\n") elseif msg[1] == "ADD DATA" then if managedPlots[msg[2]] then -- Add the data to the plot managedPlots[msg[2]]:AddSeries(msg[3],msg[4],msg[5]) print([[{"ACKNOWLEDGE"}]].."\n") else print([[{"ERROR","No Plot present at that index"}]].."\n") end elseif msg[1] == "WINDOW" then -- Create a window and return the window index if not msg[2] or not type(msg[2]) == "table" then msg[2] = {1,title="Window "..tostring(#managedWindows + 1),size="HALFxHALF"} else if msg[2].title then msg[2].title = "Window "..tostring(#managedWindows + 1)..":"..msg[2].title else msg[2].title = "Window "..tostring(#managedWindows + 1) end if not msg[2].size then msg[2].size = "HALFxHALF" end if not msg[2][1] then msg[2][1] = 1 end end managedWindows[#managedWindows + 1] = window(msg[2]) print([[{"ACKNOWLEDGE",]]..tostring(#managedWindows).."}\n") elseif msg[1] == "ADD PLOT" then if managedWindows[msg[2]] then if managedPlots[msg[3]] then if not msg[4] or type(msg[4]) ~= "table" then -- find the next empty slot msg[4] = nil for i = 1,#managedWindows[msg[2]].colBoxes do for j = 1,#managedWindows[msg[2]].colBoxes[i] do if not managedWindows[msg[2]].slots[i][j] then msg[4] = {i,j} -- this slot is empty break end end if msg[4] then break end end end -- Check if the slot is available and empty if managedWindows[msg[2]].colBoxes[msg[4][1]] and managedWindows[msg[2]].colBoxes[msg[4][1]][msg[4][2]] and not managedWindows[msg[2]].slots[msg[4][1]][msg[4][2]] then -- slot is empty and available -- Now add the indicated plot to the appropriate colBox iup.Append(managedWindows[msg[2]].colBoxes[msg[4][1]][msg[4][2]],managedPlots[msg[3]]) managedWindows[msg[2]].slots[msg[4][1]][msg[4][2]] = managedPlots[msg[3]] print([[{"ACKNOWLEDGE"}]].."\n") else print([[{"ERROR","Slot not available."}]].."\n") end else print([[{"ERROR","No Plot present at that index"}]].."\n") end -- Plot index check else print([[{"ERROR","No Window present at that index"}]].."\n") end -- window index check elseif msg[1] == "SHOW WINDOW" then --print("PLOTSERVER: SHOW WINDOW - "..msg[2]) if managedWindows[msg[2]] then --print("PLOTSERVER: FOUND WINDOW - "..tostring(managedWindows[msg[2]])) managedWindows[msg[2]].dialog:show() print([[{"ACKNOWLEDGE"}]].."\n") else print( [[{"ERROR","No window present at that index"}]].."\n") end elseif msg[1] == "DESTROY WIN" then if managedWindows[msg[2]] then if managedWindows[msg[2]].dialog.visible == "NO" then -- detach all plots first for j = 1,#managedWindows[msg[2]].slots do for k,v in pairs(managedWindows[msg[2]].slots[j]) do iup.Detach(v) end end print("Destroy window "..tostring(managedWindows[msg[2]].dialog)) iup.Destroy(managedWindows[msg[2]].dialog) managedWindows[msg[2]] = nil else destroyWinQ[#destroyWinQ + 1] = managedWindows[msg[2]] end print( [[{"ACKNOWLEDGE"}]].."\n") else print([[{"ERROR","No window present at that index"}]].."\n") end elseif msg[1] == "LIST PLOTS" then collectgarbage() print("Plotserver list:") print("Plots:") for k,v in pairs(managedPlots) do print(k,v) end print("Dialogs:") for k,v in pairs(managedDialogs) do print(k,v) end print("Windows:") for k,v in pairs(managedWindows) do print(k,v) end else end end -- if msg then (If stringToTable returned something) elseif err == "closed" then -- Exit this program as well exitProg = true iup.Close() end --[[ if DBG then print("restart timer") end]] timer.run = "YES" --[[ if DBG then print("Exit function") end]] end -- function timer:action_cb() ends end setupTimer() --print("Timer is setup. Now starting mainloop") while not exitProg do iup.MainLoop() end