-rw-r--r-- | agg-plot/lua-plot.cpp | 45 | ||||
-rw-r--r-- | demos/nlinfit.lua | 3 | ||||
-rw-r--r-- | demos/plot.lua | 26 | ||||
-rw-r--r-- | doc/user-manual/graphics.rst | 19 | ||||
-rw-r--r-- | graph-init.lua | 70 |
diff --git a/agg-plot/lua-plot.cpp b/agg-plot/lua-plot.cpp index 8a6f186a..834b7c41 100644 --- a/agg-plot/lua-plot.cpp +++ b/agg-plot/lua-plot.cpp @@ -142,13 +142,25 @@ static const struct luaL_Reg plot_properties_set[] = { __END_DECLS +static void +configure_plot_env(lua_State* L, int index) +{ + INDEX_SET_ABS(L, index); + lua_newtable(L); /* create a new table for the environment */ + + lua_pushstring(L, "__legend"); + lua_newtable(L); + lua_rawset(L, -3); /* set the field __legend to a new table in the env */ + + lua_setfenv (L, index); +} + int plot_new (lua_State *L) { sg_plot *p = push_new_object<sg_plot_auto>(L, GS_PLOT); - lua_newtable (L); - lua_setfenv (L, -2); + configure_plot_env(L, -1); if (lua_isstring (L, 1)) { @@ -165,8 +177,7 @@ canvas_new (lua_State *L) { sg_plot *p = push_new_object<sg_plot>(L, GS_PLOT); - lua_newtable (L); - lua_setfenv (L, -2); + configure_plot_env(L, -1); p->sync_mode(false); @@ -694,6 +705,18 @@ char_to_placement_enum(lua_State* L, const char *s) return pos; } +static void set_legend_ref(lua_State* L, sg_plot::placement_e pos, + int plot_index, int legend_index) +{ + INDEX_SET_ABS_2(L, plot_index, legend_index); + lua_getfenv(L, plot_index); /* env = getfenv(plot) */ + lua_pushstring(L, "__legend"); + lua_rawget(L, -2); /* leg_table = env.__legend */ + lua_pushvalue(L, legend_index); + lua_rawseti(L, -2, pos); /* leg_table[pos] = legend */ + lua_pop(L, 1); /* pop environment table */ +} + int plot_set_legend(lua_State *L) { @@ -702,10 +725,7 @@ plot_set_legend(lua_State *L) const char* placement = luaL_optstring(L, 3, "r"); sg_plot::placement_e pos = char_to_placement_enum(L, placement); - int ref_index = (1 << 16) + (int)pos; - lua_getfenv(L, 1); - lua_pushvalue(L, 2); - lua_rawseti(L, -2, ref_index); + set_legend_ref(L, pos, 1, 2); AGG_LOCK(); p->add_legend(mp, pos); @@ -722,9 +742,12 @@ plot_get_legend(lua_State *L) object_check<sg_plot>(L, 1, GS_PLOT); const char* placement = luaL_optstring(L, 2, "r"); sg_plot::placement_e pos = char_to_placement_enum(L, placement); - int ref_index = (1 << 16) + (int)pos; - lua_getfenv(L, 1); - lua_rawgeti(L, -1, ref_index); + + lua_getfenv(L, 1); /* env = getfenv(plot) */ + lua_pushstring(L, "__legend"); + lua_rawget(L, -2); /* leg_table = env.__legend */ + lua_rawgeti(L, -1, pos); /* push leg_table[pos] */ + return 1; } diff --git a/demos/nlinfit.lua b/demos/nlinfit.lua index fc6fcb30..dd86d4b0 100644 --- a/demos/nlinfit.lua +++ b/demos/nlinfit.lua @@ -83,6 +83,7 @@ local function demo2() local pl = graph.plot('Non-linear fit / A * exp(a t) sin(w t)') pl:addline(graph.xyline(x, y), 'blue', {{'marker', size= 5, mark="triangle"}}) + pl:legend('data', 'blue', 'triangle', {{'stroke'}}) local s = num.nlinfit {n= n, p= #p0} @@ -90,6 +91,7 @@ local function demo2() print(s.x, s.chisq) pl:addline(graph.fxline(|x| fmodel(s.x, x), 0, xs(n)), 'red', {{'dash', 7, 3, 3, 3}}) + pl:legend('seed', 'red', 'line', {{'stroke'},{'dash',7,3}}) for i=1, 10 do s:iterate() @@ -98,6 +100,7 @@ local function demo2() end pl:addline(graph.fxline(|x| fmodel(s.x, x), 0, xs(n)), 'red') + pl:legend('best fit', 'red', 'line') pl.pad = true pl:show() diff --git a/demos/plot.lua b/demos/plot.lua index aa5273dd..6b53453d 100644 --- a/demos/plot.lua +++ b/demos/plot.lua @@ -114,34 +114,28 @@ local function demo_plot() end local function barplot_demo() - local t = {{'ode', 14, 17, 8}, {'integ', 21, 19, 7}, {'nlfit', 8,12,6}} + local t = {{'ode', 14, 17, 8}, {'integ', 21, 19, 7}, {'nlfit', 8,12,6}, legend={'Case A', 'Case B', 'Case C'}} local p = barplot(t) p.xtitle = 'Test' p.ytitle = 'Execution time, ms' p.title = 'Benchmark results' + p:save_svg('barplot.svg', 600, 400) return p end local function legend_demo() - local p = graph.legend { - {'sinus', 'red', 'line'}, - {'cosinus', 'blue', 'line', {{'dash', 7, 3}}}, - } - p:show() - - local pi = math.pi local NS = 64 - local mp = graph.fxplot(math.sin, 0, 2*pi, 'red', 32) - mp:addline(graph.fxline(math.cos, 0, 2*pi, 32), 'blue', {{'dash', 7,3}}) - mp.title = 'Plot example' - mp.xtitle = 'x axis title' + local p = graph.fxplot(sin, 0, 2*pi, 'red', 32) + p:legend('sin', 'red', 'line') + p:addline(graph.fxline(cos, 0, 2*pi, 32), 'blue', {{'dash', 7,3}}) + p:legend('cos', 'blue', 'line', {{'stroke'},{'dash',7,3}}) + p.title = 'Plot example' + p.xtitle = 'x axis title' - mp:set_legend(p) - mp:save_svg('demo.svg', 600, 400) - p:save_svg('legend.svg', 400, 200) + p:save_svg('demo.svg', 600, 400) - echo('Plot saved in "demo.svg" and "legend.svg".') + echo('Plot saved in "demo.svg".') end return {'Plotting', { diff --git a/doc/user-manual/graphics.rst b/doc/user-manual/graphics.rst index 6b4c9e22..86a6a7f4 100644 --- a/doc/user-manual/graphics.rst +++ b/doc/user-manual/graphics.rst @@ -551,6 +551,25 @@ You can add elements to a plot in any moments even when it is already shown. GSL Return the plot legend stored in the given ``placement``. The placement parameter is interpreted as in the :meth:`~Plot.set_legend` method. + .. method:: legend(text, color, symbol[, trans]) + + Add to the plot a new legend item with the given ``text``. + The symbol used is determinated by the string ``symbol``. + Possible values are 'line', 'square' or anything accepted by :func:`graph.marker`. + The optional ``trans`` parameter should be a :ref:`graphical transform <graphics-transforms>`. + If omitted the appropriate default is chosen based on the symbol type. + + Example:: + + use 'math' + + p = graph.plot('plot example') + p:addline(graph.fxline(sin, 0, 2*pi), 'red') + p:legend('sinus', 'red', 'line') + p:addline(graph.fxline(cos, 0, 2*pi), 'blue', {{'dash',7,3}}) + p:legend('cosinus', 'blue', 'line', {{'stroke'},{'dash',7,3}}) + p:show() + .. method:: set_categories(axis, categories) Configure the given ``axis`` (a letter, 'x' or 'y') to use a custom set of labels specified by ``categories``. diff --git a/graph-init.lua b/graph-init.lua index 4cc311e8..8e9076b9 100644 --- a/graph-init.lua +++ b/graph-init.lua @@ -118,6 +118,7 @@ end function graph.barplot(t) local nr, nc = #t, #t[1] - 1 + local legend_text = t.legend local pad = 0.1 local dx = (1-2*pad)/nc local cat = {} @@ -135,6 +136,13 @@ function graph.barplot(t) p:add(rect, graph.webcolor(j)) p:add(rect, 'black', {{'stroke', width= 0.5}}) end + + end + + if legend_text then + for j = 1, nc do + p:legend(legend_text[j], graph.webcolor(j), 'square') + end end p:set_categories('x', cat) @@ -283,34 +291,48 @@ end local function legend_symbol(sym, dx, dy) if sym == 'square' then return graph.rect(5+dx, 5+dy, 15+dx, 15+dy) - elseif sym == 'circle' then - return graph.ellipse(10+dx, 10+dy, 5, 5) elseif sym == 'line' then - return graph.segment(2+dx, 10+dy, 18+dx, 10+dy), {'stroke'} + return graph.segment(2+dx, 10+dy, 18+dx, 10+dy), {{'stroke'}} else - error('invalid legend symbol: ' .. sym) + return graph.marker(10+dx, 10+dy, sym, 8) end end -function graph.legend(entries) - local n = #entries - local p = graph.plot() - p.units, p.clip = false, false - for k= 1, n do - local text, color, symspec, trans = unpack(entries[k]) - local y = (k-1) * 20 - local sym, symtr = legend_symbol(symspec, 0, y) - local tr - if symtr then - tr = { symtr } - if trans then - for j, xtr in ipairs(trans) do tr[#tr+1] = xtr end - end - else - tr = trans - end - p:add(sym, color, tr) - p:add(graph.textshape(25, y + 6, text, 14), 'black') +local function plot_legend(self, text, color, symspec, trans) + local lg = self:get_legend() + local env = debug.getfenv(self) + + if not lg then + lg = graph.plot() + lg.units = false + self:set_legend(lg) end - return p + + local k = env.__lg_count or 0 + local y = -k * 20 + + local sym, symtr = legend_symbol(symspec, 0, y) + + local tr = (trans and trans or symtr) + + lg:add(sym, color, tr) + lg:add(graph.textshape(25, y + 6, text, 14), 'black') + + env.__lg_count = k+1 + self:update() +end + +local function redirect_plot() + local reg = debug.getregistry() + local mt = reg['GSL.plot'] + local plot_index = mt.__index + + local function index_redirect(t, k) + if k == 'legend' then return plot_legend end + return plot_index(t, k) + end + + mt.__index = index_redirect end + +redirect_plot() |