[Ipe-discuss] Plot data extraction from PDF

Jan Hlavacek jhlavace at svsu.edu
Mon Dec 28 17:02:16 CET 2009


On Mon, Dec 28, 2009 at 03:49:06PM +0000, T T wrote:
> Hi,
> 
> I'm in need of a tool that can extract XY data from (vector!) plots in
> PDF documents. Failing to find such a tool I'm considering writing one
> and Ipe looks attractive, because:
> - it can read PDF files (with pdftoipe tool)
> - it is scriptable with Lua and I happen to know this language a little bit
> 
> As I'm unfamiliar with implementation and object hierarchy of Ipe, I
> would appreciate some guidance in how to implement a plug-in with the
> following methods:
> (1) Coordinate system: establish the plot coordinate system from two
> selected marks (if not given use canvas coordinate system)

This will probably not help you with the rest, but my ipeplots ipelet does
something like this.  Given a selection, it establishes a coordinate system
from the bounding box of the selection, if there is no selection, it uses the
canvas coordinate system.  I have a new version written, right now I am in the
process of writing documentation.  I will attach the ipelet to this file, so
you can have a look at it.  Hopefully it will be of help. 

-- 
Jan Hlavacek (jhlavace at svsu.edu, (989) 964-2004)
Department of Mathematical Sciences, Saginaw Valley State University
http://www.svsu.edu/~jhlavace/
-------------- next part --------------
----------------------------------------------------------------------
-- plot ipelet
----------------------------------------------------------------------
--[[
 
   This file is an extension of the drawing editor Ipe (ipe7.sourceforge.net)

   Copyright (c) 2009 Jan Hlavacek

   This file can be distributed and modified under the terms of the GNU General
   Public License as published by the Free Software Foundation; either version
   3, or (at your option) any later version.

   This file is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
   details.

   You can find a copy of the GNU General Public License at
   "http://www.gnu.org/copyleft/gpl.html", or write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   Basic documentation (bug: better documentation needs to be written)

   All functions provided by this ipelet use a selection to describe the plot
   "viewport" on the page.  The only thing that is used from the selection is
   its bounding rectangle.  This rectangle will represent the "viewport" of the
   plot on your page. Each function will present a dialog where you, in
   addition to other things, specify the corresponding plot coordinates for
   this viewport. 

   For example, assume that you start with a selection that is a rectangle.
   You choose Parametric plot, type in cos(t) and sin(t) for x and y, t from
   -3.14 to 3.14, and viewport coordinates as x from -1 to 1, y from -1 to 1. 

   This will make the originally selected rectangle represent a part of
   coordinate plane with corners (-1,-1), (-1,1), (1,1) and (1,-1), and, in
   effect, draw an inscribed ellipse into the rectangle. 

   There is no clipping going on right now, so if your plot is larger than the
   specified viewport, it will simply stick out of the rectangle. 

   After creating a coordinate system with the "Coordinate system" menu item,
   you can use this coordinate system as your initial selection for your plots,
   and they will be correctly placed on this coordinate system. 

   When creating coordinate system, you can specify location of ticks on both
   axes.  Here you can use expressions such as pi, 2*pi, etc.  If you leave
   this empty, ticks will be placed at every integer. Ticks will only be drawn
   if the tick size is not 0. 

   I am planning to write a more expensive documentation for this.  In the mean
   time, if you have questions, please contact me at jhlavace at svsu.edu. Also
   please contact me if you have any suggestions for improvement. 

   This is my first attempt to write something in lua, and it has been put
   together quite in a hurry, so I am sure there are lot of places where things
   can be done better. 

   Future plans:  Add coordinate grid generating function, make general lua
   math expressions work at other places than just tick lists (so you don't
   have to enter things like 3.14 for t), add polar plots, ...

--]]

label = "Plots"

about = [[
Parametric curves, plots of functions, coordinate systems
]]

-- we will prepend this every time we use loadstring, so user does not have to
-- type math.foo for foo all the time:
local mathdefs = "local abs = math.abs; local acos = math.acos; local asin = math.asin; local atan = math.atan; local atan2 = math.atan2; local ceil = math.ceil; local cos = math.cos; local cosh = math.cosh; local deg = math.deg; local exp = math.exp; local floor = math.floor; local fmod = math.fmod; local log = math.log; local log10 = math.log10; local max = math.max; local min = math.min; local modf = math.modf; local pi = math.pi; local pow = math.pow; local rad = math.rad; local sin = math.sin; local sinh = math.sinh; local sqrt = math.sqrt; local tan = math.tan; local tanh = math.tanh;"

local function isfinite(x)
   return (x > - math.huge) and (x < math.huge)
end

local function bounding_box(p)
  local box = ipe.Rect()
  for i,obj,sel,layer in p:objects() do
    if sel then box:add(p:bbox(i)) end
  end
  return box
end

local function calculate_transform (model, x0, y0, x1, y1)
   local box = bounding_box(model:page())
   if box:isEmpty() then 
        if model.snap.with_axes then
	   --ui:explain("Selection seems to be empty. Using coordinates with origin.")
	   return ipe.Translation(model.snap.origin)
	else
	   --ui:explain("Selection seems to be empty. Using global coordinates.")
	   return ipe.Matrix()
	end
   end
   -- Selection is given.  Calculate transformation to change real coordinates
   -- to canvas coordinates relative to the selection.
   local cstart = box:bottomLeft()
   local cend = box:topRight()
   local cdif = cend-cstart
   local cxlen = cdif.x
   local cylen = cdif.y
   local start = ipe.Vector(x0,y0)
   local xlen = x1-x0
   local ylen = y1-y0
   local scalem = ipe.Matrix(cxlen/xlen,0,0,cylen/ylen)
   local trans = ipe.Translation(cstart-scalem*start) * scalem
   return trans
end

local function get_number (model, string, error_msg) 
   if string == "" then
      model:warning ("You need to specify " .. error_msg)
      return
   end
   lstring = mathdefs .. "return " .. string
   local f,err = _G.loadstring(lstring,error_msg)
   if not f then
      model:warning("Could not compile " .. error_msg)
      return
   end
   local stat,num = _G.pcall(f)
   if not stat then
      model:warning(num) -- bug: error messages will be cryptic
      return
   end
   if not num then
      model:warning(string .. " is not a valid value for " .. error_msg)
      return
   end
   return num
end

-- helpful functions for creating dialogs
-- Set up a line counter so that we don't have to use absolute line numbers for
-- dialogs. 
function line_counter()
   local line_no = 0
   local function same_line()
      return line_no
   end
   local function new_line()
      line_no = line_no + 1
      return line_no
   end
   return same_line, new_line
end

-- parametric plot
function curve(model)
   local box = bounding_box(model:page())
   local has_viewport = not box:isEmpty()
   local same, nxt = line_counter()
   local d = ipeui.Dialog(model.ui, "Parametric plot")
   d:add("label1", "label", {label="Enter parametric equations. Use t as a parameter."}, nxt(), 1, 1, 4)
   d:add("label2", "label", {label="x="}, nxt(), 1)
   d:add("xeq", "input", {}, same(), 2, 1, 3)
   d:add("label3", "label", {label="y="}, nxt(), 1)
   d:add("yeq", "input", {}, same(), 2, 1, 3)
   d:add("label4", "label", {label="Set the domain for t:"}, nxt(), 1, 1, 4)
   d:add("label5", "label", {label="from:"}, nxt(), 1, 1, 1)
   d:add("tfrom", "input", {}, same(), 2, 1, 1)
   d:add("label6", "label", {label="to:"}, same(), 3, 1, 1)
   d:add("tto", "input", {}, same(), 4, 1, 1)
   if has_viewport then
      d:add("label7", "label", {label="Set coordinates for viewport:"}, nxt(), 1, 1, 4)
      d:add("label8", "label", {label="from x="}, nxt(), 1, 1, 1)
      d:add("xfrom", "input", {}, same(), 2, 1, 1)
      d:add("label9", "label", {label="to x="}, same(), 3, 1, 1)
      d:add("xto", "input", {}, same(), 4, 1, 1)
      d:add("label10", "label", {label="from y="}, nxt(), 1, 1, 1)
      d:add("yfrom", "input", {}, same(), 2, 1, 1)
      d:add("label11", "label", {label="to y="}, 8, 3, 1, 1)
      d:add("yto", "input", {}, same(), 4, 1, 1)
   end
   d:add("label12", "label", {label="number of points"}, nxt(), 1, 1, 1)
   d:add("points", "input", {}, same(), 2, 1, 1)
   d:add("ok", "button", {label="&Ok", action="accept"}, nxt(), 4)
   d:add("cancel", "button", {label="&Cancel", action="reject"}, same(), 3)
   d:setStretch("column", 2, 1)
   d:setStretch("row", 5, 1)
   if xeqstore then d:set("xeq",xeqstore) end
   if yeqstore then d:set("yeq",yeqstore) end
   if has_viewport then
      if x0store then d:set("xfrom",x0store) end
      if x1store then d:set("xto",x1store) end
      if y0store then d:set("yfrom",y0store) end
      if y1store then d:set("yto",y1store) end
   end
   if t0store then d:set("tfrom",t0store) end
   if t1store then d:set("tto",t1store) end
   if not pointsstore then pointsstore = 100 end
   d:set("points",pointsstore) 
   if not d:execute() then return end
   local s1 = d:get("xeq")
   local s2 = d:get("yeq")
   xeqstore = s1
   yeqstore = s2
   if has_viewport then
      x0store = d:get("xfrom")
      x1store = d:get("xto")
      y0store = d:get("yfrom")
      y1store = d:get("yto")
   end
   t0store = d:get("tfrom")
   t1store = d:get("tto")
   pointsstore = d:get("points")

   -- real coordinates
   local x0, x1, y0, y1
   if has_viewport then
      x0 = get_number(model,x0store,"lower x limit")
      if not x0 then return end
      x1 = get_number(model,x1store,"upper x limit")
      if not x1 then return end
      y0 = get_number(model,y0store,"lower y limit")
      if not y0 then return end
      y1 = get_number(model,y1store,"upper y limit")
      if not y1 then return end
   else
      x0 = 0
      y0 = 0
      x1 = 1
      y1 = 1
   end

   -- parameter
   local t0 = get_number(model,t0store,"initial value of t")
   if not t0 then return end
   local t1 = get_number(model,t1store,"final value of t")
   if not t0 then return end

   -- check validity of t limits:
   if t0 > t1 then
      t0, t1 = t1, t0
   end
   if t0 == t1 then
      model:warning("Limits for t cannot be equal")
      return
   end

   -- check validity of x and y limits:
   if x0 > x1 then
      x0, x1 = x1, x0
   end
   if x0 == x1 then
      model:warning("Limits for x cannot be equal")
      return
   end
   if y0 > y1 then
      y0, y1 = y1, y0
   end
   if y0 == y1 then
      model:warning("Limits for y cannot be equal")
      return
   end

   local trans = calculate_transform(model,x0,y0,x1,y1)
   local tlen = t1-t0
   local t = t0

   -- create user function
   local coordstr = s1 .. "," .. s2
   coordstr =  mathdefs .. "return function (t) local v = ipe.Vector(" .. coordstr .. "); return v end"
   local f,err = _G.loadstring(coordstr,"parametric_plot")
   if not f then
      model:warning("Could not compile coordinate functions")
      return
   end

   local curve = { type="curve", closed=false }
   local v0 = trans*f()(t)
   local v1 = v0
   for i = 1,pointsstore do
      t = t + tlen/pointsstore
      v1 = trans*f()(t)
      curve[#curve + 1] = { type="segment", v0, v1 }
      v0 = v1
   end

   local graph = ipe.Path(model.attributes, { curve } )
   model:creation("create graph", graph)
end

-- plot of a function:
function func_plot(model)
   local box = bounding_box(model:page())
   local has_viewport = not box:isEmpty()
   local same, nxt = line_counter()
   local d = ipeui.Dialog(model.ui, "Function Plot")
   d:add("label1", "label", {label="Enter y as a function of x"}, nxt(), 1, 1, 4)
   d:add("label2", "label", {label="y="}, nxt(), 1)
   d:add("xeq", "input", {}, same(), 2, 1, 3)
   d:add("label4", "label", {label="Set the domain for x:"}, nxt(), 1, 1, 4)
   d:add("label5", "label", {label="from:"}, nxt(), 1, 1, 1)
   d:add("tfrom", "input", {}, same(), 2, 1, 1)
   d:add("label6", "label", {label="to:"}, same(), 3, 1, 1)
   d:add("tto", "input", {}, same(), 4, 1, 1)
   if has_viewport then
      d:add("label7", "label", {label="Set coordinates for viewport:"}, nxt(), 1, 1, 4)
      d:add("label8", "label", {label="from x="}, nxt(), 1, 1, 1)
      d:add("xfrom", "input", {}, same(), 2, 1, 1)
      d:add("label9", "label", {label="to x="}, same(), 3, 1, 1)
      d:add("xto", "input", {}, same(), 4, 1, 1)
      d:add("label10", "label", {label="from y="}, nxt(), 1, 1, 1)
      d:add("yfrom", "input", {}, same(), 2, 1, 1)
      d:add("label11", "label", {label="to y="}, same(), 3, 1, 1)
      d:add("yto", "input", {}, same(), 4, 1, 1)
   end
   d:add("label12", "label", {label="number of points"}, nxt(), 1, 1, 1)
   d:add("points", "input", {}, same(), 2, 1, 1)
   d:add("ok", "button", {label="&Ok", action="accept"}, nxt(), 4)
   d:add("cancel", "button", {label="&Cancel", action="reject"}, same(), 3)
   d:setStretch("column", 2, 1)
   d:setStretch("row", 5, 1)
   if fstore then d:set("xeq",fstore) end
   if has_viewport then
      if x0store then d:set("xfrom",x0store) end
      if x1store then d:set("xto",x1store) end
      if y0store then d:set("yfrom",y0store) end
      if y1store then d:set("yto",y1store) end
   end
   if dom0store then d:set("tfrom",dom0store) end
   if dom1store then d:set("tto",dom1store) end
   if not pointsstore then pointsstore = 100 end
   d:set("points",pointsstore) 
   if not d:execute() then return end
   local s1 = d:get("xeq")
   fstore = s1
   if has_viewport then
      x0store = d:get("xfrom")
      x1store = d:get("xto")
      y0store = d:get("yfrom")
      y1store = d:get("yto")
   end
   dom0store = d:get("tfrom")
   dom1store = d:get("tto")
   pointsstore = d:get("points")

   -- real coordinates
   local x0, x1, y0, y1
   if has_viewport then
      x0 = get_number(model,x0store,"lower x limit")
      if not x0 then return end
      x1 = get_number(model,x1store,"upper x limit")
      if not x1 then return end
      y0 = get_number(model,y0store,"lower y limit")
      if not y0 then return end
      y1 = get_number(model,y1store,"upper y limit")
      if not y1 then return end
   else
      x0=0
      y0=0
      x1=1
      y1=1
   end

   -- independent variable
   local t0 = get_number(model,dom0store,"initial value of x")
   if not t0 then return end
   local t1 = get_number(model,dom1store,"final value of x")
   if not t0 then return end

   -- check validity of t limits:
   if t0 > t1 then
      t0, t1 = t1, t0
   end
   if t0 == t1 then
      model:warning("Limits for x cannot be equal")
      return
   end

   -- check validity of x and y limits:
   if x0 > x1 then
      x0, x1 = x1, x0
   end
   if x0 == x1 then
      model:warning("Limits for x cannot be equal")
      return
   end
   if y0 > y1 then
      y0, y1 = y1, y0
   end
   if y0 == y1 then
      model:warning("Limits for y cannot be equal")
      return
   end

   -- scaling calculations
   local trans = calculate_transform(model,x0,y0,x1,y1)
   local tlen = t1-t0
   local t = t0

   -- create user function
   local coordstr = s1
   coordstr =  mathdefs .. "return function (x) local v = ipe.Vector(x," .. coordstr .. "); return v end"
   -- attempt to load this string.  Give a warning and quit if it fails. 
   local f,err = _G.loadstring(coordstr,"function plot")
   if not f then
      model:warning(err) -- bug: error messages will be cryptic
      return
   end
   -- execute the function obtained from the string.  That should create the
   -- actual function usable for our calculations.  Warn and quit if it fails.
   stat,f = _G.pcall(f)
   if not stat then
      model:warning(f) -- bug: error messages will be cryptic
      return
   end

   local curve = { type="curve", closed=false }
   local v0
   -- try to evaluate the function.  Warn and quit if it fails. 
   stat, v0 = _G.pcall(f,t)
   if not stat then
      model:warning(v0) -- bug: error messages will be cryptic
      return
   end
   if not isfinite(v0.x*v0.y) then
      model:warning("domain error")
      return
   end
   v0 = trans*v0
   local v1 = v0
   for i = 1,pointsstore do
      t = t + tlen/pointsstore
      stat, v1 = _G.pcall(f,t)
      if not stat then
	 model:warning(v1) -- bug: error messages will be cryptic
	 return
      end
      if not isfinite(v1.x*v1.y) then
	 model:warning("domain error")
	 return
      end
      v1 = trans*v1
      curve[#curve + 1] = { type="segment", v0, v1 }
      v0 = v1
   end

   local graph = ipe.Path(model.attributes, { curve } )
   model:creation("create graph", graph)
end

-- coordinate system
function make_axes(model, num)
   same, nxt = line_counter()
   local d = ipeui.Dialog(model.ui, "Coordinate System")
   d:add("label3", "label", {label="Set coordinates for viewport:"}, nxt(), 1, 1, 4)
   d:add("label8", "label", {label="from x="}, nxt(), 1, 1, 1)
   d:add("xfrom", "input", {}, same(), 2, 1, 1)
   d:add("label9", "label", {label="to x="}, same(), 3, 1, 1)
   d:add("xto", "input", {}, same(), 4, 1, 1)
   d:add("label10", "label", {label="from y="}, nxt(), 1, 1, 1)
   d:add("yfrom", "input", {}, same(), 2, 1, 1)
   d:add("label11", "label", {label="to y="}, same(), 3, 1, 1)
   d:add("yto", "input", {}, same(), 4, 1, 1)
   if num == 1 then
      d:add("label27", "label", {label="Size of x-ticks (in pt):"}, nxt(), 1, 1, 1)
      d:add("xticksize", "input", {}, same(), 2, 1, 1)
      d:add("label28", "label", {label="Size of y-ticks (in pt):"}, same(), 3, 1, 1)
      d:add("yticksize", "input", {}, same(), 4, 1, 1)
      d:add("label84", "label", {label="Locations of x-ticks:"},nxt(),1,1,1)
      d:add("xticklist", "input", {}, same(), 2, 1, 3)
      d:add("label85", "label", {label="Locations of y-ticks:"},nxt(),1,1,1)
      d:add("yticklist", "input", {}, same(), 2, 1, 3)
   else
      d:add("label84", "label", {label="Locations of vertical grid lines:"},nxt(),1,1,1)
      d:add("xticklist", "input", {}, same(), 2, 1, 3)
      d:add("label85", "label", {label="Locations of horizontal grid lines:"},nxt(),1,1,1)
      d:add("yticklist", "input", {}, same(), 2, 1, 3)
   end
   d:add("ok", "button", {label="&Ok", action="accept"}, nxt(), 4)
   d:add("cancel", "button", {label="&Cancel", action="reject"}, same(), 3)
   d:setStretch("column", 2, 1)
   d:setStretch("row", 5, 1)
   if x0store then d:set("xfrom",x0store) end
   if x1store then d:set("xto",x1store) end
   if y0store then d:set("yfrom",y0store) end
   if y1store then d:set("yto",y1store) end
   if not xticksizestore then xticksizestore = 0 end
   if not yticksizestore then yticksizestore = 0 end
   if (num == 1) then 
      d:set("xticksize", xticksizestore)
      d:set("yticksize", yticksizestore)
   end
   if xtickstore then d:set("xticklist", xtickstore) end
   if ytickstore then d:set("yticklist", ytickstore) end
   if not d:execute() then return end
   x0store = d:get("xfrom")
   x1store = d:get("xto")
   y0store = d:get("yfrom")
   y1store = d:get("yto")
   if num == 1 then
      xticksizestore = d:get("xticksize")
      yticksizestore = d:get("yticksize")
   end
   xtickstore = d:get("xticklist")
   ytickstore = d:get("yticklist")

   -- real coordinates
   local x0 = get_number(model,x0store,"lower x limit")
   if not x0 then return end
   local x1 = get_number(model,x1store,"upper x limit")
   if not x1 then return end
   local y0 = get_number(model,y0store,"lower y limit")
   if not y0 then return end
   local y1 = get_number(model,y1store,"upper y limit")
   if not y1 then return end

   -- check validity of x and y limits:
   if x0 > x1 then
      x0, x1 = x1, x0
   end
   if x0 == x1 then
      model:warning("Limits for x cannot be equal")
      return
   end
   if y0 > y1 then
      y0, y1 = y1, y0
   end
   if y0 == y1 then
      model:warning("Limits for y cannot be equal")
      return
   end

   local trans = calculate_transform(model,x0,y0,x1,y1)

   -- ticks:
   xticksize = tonumber(xticksizestore)
   if not xticksize then xticksize = 0 end
   yticksize = tonumber(yticksizestore)
   if not yticksize then yticksize = 0 end

   -- tick locations:
   local xticks = {}
   local yticks = {}

   if xtickstore and (xtickstore ~= "") then
      local tickliststr = mathdefs .. "return {" .. xtickstore .. "}"
      -- attempt to load this string.  Give a warning and quit if it fails. 
      local f,err = _G.loadstring(tickliststr,"x-ticks")
      if not f then
	 model:warning(err) -- bug: error messages will be cryptic
	 return
      end
      local xticklist 
      stat, xticklist = _G.pcall(f,t)
      if not stat then
	 model:warning(xticklist) -- bug: error messages will be cryptic
	 return
      end
      for i,x in pairs(xticklist) do
	 if isfinite(x) then
	    if (x > x0) and (x < x1) then
	       xticks[#xticks + 1] = x
	    end
	 end
      end
   else -- place ticks at every integer
      for i = math.floor(x0) + 1, math.ceil(x1)-1 do
	 xticks[#xticks + 1] = i
      end
   end

   -- do the same for y-ticks
   if ytickstore and (ytickstore ~= "") then
      local tickliststr = mathdefs .. "return {" .. ytickstore .. "}"
      -- attempt to load this string.  Give a warning and quit if it fails. 
      local f,err = _G.loadstring(tickliststr,"y-ticks")
      if not f then
	 model:warning(err) -- bug: error messages will be cryptic
	 return
      end
      local yticklist 
      stat, yticklist = _G.pcall(f,t)
      if not stat then
	 model:warning(yticklist) -- bug: error messages will be cryptic
	 return
      end
      for i,y in pairs(yticklist) do
	 if isfinite(y) then
	    if (y > y0) and (y < y1) then
	       yticks[#yticks + 1] = y
	    end
	 end
      end
   else -- place ticks at every integer
      for i = math.floor(y0) + 1, math.ceil(y1)-1 do
	 yticks[#yticks + 1] = i
      end
   end

   if (num == 1) then
      local axes = { }

      -- only make x-axis if y0<=0<=y1
      if y0*y1 <= 0 then
	 local v0 = trans*ipe.Vector(x0,0)
	 local v1 = trans*ipe.Vector(x1,0)
	 local curve = { type="curve", closed=false; { type="segment", v0, v1 }}
	 local xaxis = ipe.Path(model.attributes, {curve})
	 xaxis:set("farrow",true)
	 xaxis:set("pen","fat")
	 axes[#axes + 1] = xaxis
	 if xticksize ~= 0 then
	    local half_tick = ipe.Vector(0,xticksize/2)
	    for n,i in pairs(xticks) do
	       v0 = trans*ipe.Vector(i,0)
	       local tick = { type="curve", closed=false; { type="segment", v0+half_tick, v0-half_tick }}
	       axes[#axes + 1] = ipe.Path(model.attributes, {tick})
	    end
	 end
      end
      if x0*x1 <= 0 then
	 local v0 = trans*ipe.Vector(0,y0)
	 local v1 = trans*ipe.Vector(0,y1)
	 curve = { type="curve", closed=false; { type="segment", v0, v1 }}
	 local yaxis = ipe.Path(model.attributes, {curve})
	 yaxis:set("farrow",true)
	 yaxis:set("pen","fat")
	 axes[#axes + 1] = yaxis
	 if yticksize ~= 0 then
	    local half_tick = ipe.Vector(yticksize/2,0)
	    for n,i in pairs(yticks) do
	       v0 = trans*ipe.Vector(0,i)
	       local tick = { type="curve", closed=false; { type="segment", v0+half_tick, v0-half_tick }}
	       axes[#axes + 1] = ipe.Path(model.attributes, {tick})
	    end
	 end
      end

      if #axes > 0 then 
	 local coordsys = ipe.Group(axes)
	 model:creation("create coordinate system", coordsys)
      end
   else
      local grid = {}
      for n,i in pairs(xticks) do
	 local v0 = trans*ipe.Vector(i,y0)
	 local v1 = trans*ipe.Vector(i,y1)
	 local line = { type="curve", closed=false; { type="segment", v0, v1 }}
	 grid[#grid + 1] = ipe.Path(model.attributes, {line})
      end
      for n,i in pairs(yticks) do
	 local v0 = trans*ipe.Vector(x0,i)
	 local v1 = trans*ipe.Vector(x1,i)
	 local line = { type="curve", closed=false; { type="segment", v0, v1 }}
	 grid[#grid + 1] = ipe.Path(model.attributes, {line})
      end

      if #grid > 0 then 
	 local coordsys = ipe.Group(grid)
	 model:creation("create coordinate grid", coordsys)
      end
   end
end

methods = {
   { label = "Coordinate system", run=make_axes },
   { label = "Coordinate grid", run=make_axes },
   { label = "Parametric plot", run=curve },
   { label = "Function plot", run=func_plot },
}

----------------------------------------------------------------------


More information about the Ipe-discuss mailing list