m-chart.lua /size: 37 Kb    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['x-flow'] = {
2    version   = 1.001,
3    comment   = "companion to m-flow.mkvi",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- when we can resolve mpcolor at the lua end we will
10-- use metapost.graphic(....) directly
11
12-- todo: labels
13-- todo: named colors
14
15local type, tonumber, rawget, next = type, tonumber, rawget, next
16local gsub, find, lower = string.gsub, string.find, string.lower
17local P, S, C, Cc, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.match
18
19local context           = context
20
21local ctx_startgraphic  = metapost.startgraphic
22local ctx_stopgraphic   = metapost.stopgraphic
23local ctx_tographic     = metapost.tographic
24
25local formatters        = string.formatters
26local setmetatableindex = table.setmetatableindex
27local settings_to_hash  = utilities.parsers.settings_to_hash
28
29moduledata.charts       = moduledata.charts or { }
30
31local report_chart      = logs.reporter("chart")
32
33local variables         = interfaces.variables
34local implement         = interfaces.implement
35
36local v_yes             = variables.yes
37local v_no              = variables.no
38local v_none            = variables.none
39local v_standard        = variables.standard
40local v_overlay         = variables.overlay
41local v_round           = variables.round
42local v_test            = variables.test
43
44local defaults = {
45    chart = {
46        name            = "",
47        option          = "",
48        backgroundcolor = "",
49        width           = 100*65536,
50        height          = 50*65536,
51        dx              = 30*65536,
52        dy              = 30*65536,
53        offset          = 0,
54        bodyfont        = "",
55        dot             = "",
56        hcompact        = variables_no,
57        vcompact        = variables_no,
58        autofocus       = "",
59        focus           = "",
60        labeloffset     = 5*65536,
61        commentoffset   = 5*65536,
62        exitoffset      = 0,
63
64    },
65    shape = { -- FLOS
66        rulethickness   = 65536,
67        default         = "",
68        framecolor      = "darkblue",
69        backgroundcolor = "lightgray",
70    },
71    focus = { -- FLOF
72        rulethickness   = 65536,
73        framecolor      = "darkred",
74        backgroundcolor = "gray",
75    },
76    line = { -- FLOL
77        rulethickness   = 65536,
78        radius          = 10*65536,
79        color           = "darkgreen",
80        corner          = "",
81        dash            = "",
82        arrow           = "",
83        offset          = "",
84    },
85    set = { -- FLOX
86    },
87    split = {
88        nx      = 3,
89        ny      = 3,
90        command = "",
91        marking = "",
92        before  = "",
93        after   = "",
94    }
95}
96
97local validshapes = {
98    ["node"]            = { kind = "shape", number =  0 },
99    ["action"]          = { kind = "shape", number = 24 },
100    ["procedure"]       = { kind = "shape", number =  5 },
101    ["product"]         = { kind = "shape", number = 12 },
102    ["decision"]        = { kind = "shape", number = 14 },
103    ["archive"]         = { kind = "shape", number = 19 },
104    ["loop"]            = { kind = "shape", number = 35 },
105    ["wait"]            = { kind = "shape", number =  6 },
106    ["subprocedure"]    = { kind = "shape", number = 20 },
107    ["singledocument"]  = { kind = "shape", number = 32 },
108    ["multidocument"]   = { kind = "shape", number = 33 },
109
110    ["right"]           = { kind = "line",  number = 66 },
111    ["left"]            = { kind = "line",  number = 67 },
112    ["up"]              = { kind = "line",  number = 68 },
113    ["down"]            = { kind = "line",  number = 69 },
114}
115
116local validlabellocations = {
117    l  = "l",  left   = "l",
118    r  = "r",  right  = "r",
119    t  = "t",  top    = "t",
120    b  = "b",  bottom = "b",
121    lt = "lt",
122    rt = "rt",
123    lb = "lb",
124    rb = "rb",
125    tl = "tl",
126    tr = "tr",
127    bl = "bl",
128    br = "br",
129}
130
131local validcommentlocations = {
132    l  = "l",  left   = "l",
133    r  = "r",  right  = "r",
134    t  = "t",  top    = "t",
135    b  = "b",  bottom = "b",
136    lt = "lt",
137    rt = "rt",
138    lb = "lb",
139    rb = "rb",
140    tl = "tl",
141    tr = "tr",
142    bl = "bl",
143    br = "br",
144}
145
146local validtextlocations = {
147    l  = "l",  left   = "l",
148    r  = "r",  right  = "r",
149    t  = "t",  top    = "t",
150    b  = "b",  bottom = "b",
151    c  = "c",  center = "c",
152    m  = "c",  middle = "m",
153    lt = "lt",
154    rt = "rt",
155    lb = "lb",
156    rb = "rb",
157    tl = "lt",
158    tr = "rt",
159    bl = "lb",
160    br = "rb",
161}
162
163setmetatableindex(validshapes,function(t,k)
164    local l = gsub(lower(k)," ","")
165    local v = rawget(t,l)
166    if not v then
167        local n = tonumber(k)
168        if n then
169            v = { kind = "shape", number = n }
170        else
171            v = rawget(t,"action")
172        end
173    end
174    t[k] = v
175    return v
176end)
177
178local charts = { }
179
180local data, hash, temp, last_x, last_y, name
181
182implement {
183    name      = "flow_start_chart",
184    arguments = "string",
185    actions   = function(chartname)
186        data = { }
187        hash = { }
188        last_x, last_y = 0, 0
189        name = chartname
190    end
191}
192
193implement {
194    name      = "flow_stop_chart",
195    actions   = function()
196        charts[name] = {
197            data = data,
198            hash = hash,
199            last_x = last_x,
200            last_y = last_y,
201        }
202        data, hash, temp = nil, nil, nil
203    end
204}
205
206-- function commands.flow_set(chartname,chartdata)
207--     local hash = { }
208--     local data = { }
209--     charts[name] = {
210--         data = data,
211--         hash = hash,
212--     }
213--     for i=1,#chartdata do
214--         local di = data[i]
215--         local name = di.name or ""
216--         if name then
217--             data[#data+1] = {
218--                 name        = name,
219--                 labels      = di.labels      or { },
220--                 comments    = di.comments    or { },
221--                 exits       = di.exits       or { },
222--                 connections = di.connections or { },
223--                 settings    = di.settings    or { },
224--                 x           = di.x           or 1,
225--                 y           = di.y           or 1,
226--             }
227--             hash[name] = i
228--         end
229--      end
230-- end
231
232implement {
233    name    = "flow_reset",
234    actions = function()
235        charts[name] = nil
236    end
237}
238
239implement {
240    name      = "flow_set_current_cell",
241    arguments = "integer",
242    actions   = function(n)
243        temp = data[n] or { }
244    end
245}
246
247implement {
248    name      = "flow_start_cell",
249    arguments = {
250        {
251            { "shape", {
252                    { "rulethickness", "dimension" },
253                    { "default" },
254                    { "framecolor" },
255                    { "backgroundcolor" },
256                },
257            },
258            { "focus", {
259                    { "rulethickness", "dimension" },
260                    { "framecolor" },
261                    { "backgroundcolor" },
262                },
263            },
264            { "line", {
265                    { "rulethickness", "dimension" },
266                    { "radius", "dimension" },
267                    { "color" },
268                    { "corner" },
269                    { "dash" },
270                    { "arrow" },
271                    { "offset", "dimension" },
272                },
273            },
274        },
275    },
276    actions   = function(settings)
277        temp = {
278            texts       = { },
279            labels      = { },
280            exits       = { },
281            connections = { },
282            settings    = settings,
283            x           = 1,
284            y           = 1,
285            realx       = 1,
286            realy       = 1,
287            name        = "",
288        }
289    end
290}
291
292implement {
293    name    = "flow_stop_cell",
294    actions = function()
295        data[#data+1] = temp
296        hash[temp.name or #data] = temp
297    end
298}
299
300implement {
301    name      = "flow_set_name",
302    arguments = "string",
303    actions   = function(str)
304        temp.name = str
305    end
306}
307
308implement {
309    name      = "flow_set_shape",
310    arguments = "string",
311    actions   = function(str)
312        temp.shape = str
313    end
314}
315
316implement {
317    name      = "flow_set_destination",
318    arguments = "string",
319    actions   = function(str)
320        temp.destination = str
321    end
322}
323
324implement {
325    name      = "flow_set_text",
326    arguments = "2 strings",
327    actions   = function(align,str)
328        temp.texts[#temp.texts+1] = {
329            align = align,
330            text  = str,
331        }
332    end
333}
334
335implement {
336    name      = "flow_set_overlay",
337    arguments = "string",
338    actions   = function(str)
339        temp.overlay = str
340    end
341}
342
343implement {
344    name      = "flow_set_focus",
345    arguments = "string",
346    actions   = function(str)
347        temp.focus = str
348    end
349}
350
351implement {
352    name      = "flow_set_figure",
353    arguments = "string",
354    actions   = function(str)
355        temp.figure = str
356    end
357}
358
359implement {
360    name      = "flow_set_label",
361    arguments = "2 strings",
362    actions   = function(location,text)
363        temp.labels[#temp.labels+1] = {
364            location = location,
365            text = text,
366        }
367    end
368}
369
370implement {
371    name      = "flow_set_comment",
372    arguments = "2 strings",
373    actions   = function(location,text)
374        local connections = temp.connections
375        if connections then
376            local connection = connections[#connections]
377            if connection then
378                local comments = connection.comments
379                if comments then
380                    comments[#comments+1] = {
381                        location = location,
382                        text = text,
383                    }
384                end
385            end
386        end
387    end
388}
389
390implement {
391    name      = "flow_set_exit",
392    arguments = "2 strings",
393    actions   = function(location,text)
394        temp.exits[#temp.exits+1] = {
395            location = location,
396            text = text,
397        }
398    end
399}
400
401implement {
402    name      = "flow_set_include",
403    arguments = { "string", "integer", "integer", "string" },
404    actions   = function(name,x,y,settings)
405        data[#data+1] = {
406            include  = name,
407            x        = x,
408            y        = y,
409         -- settings = settings,
410        }
411    end
412}
413
414local function inject(includedata,data,hash)
415    local subchart = charts[includedata.include]
416    if not subchart then
417        return
418    end
419    local subdata = subchart.data
420    if not subdata then
421        return
422    end
423    local xoffset  = (includedata.x or 1) - 1
424    local yoffset  = (includedata.y or 1) - 1
425    local settings = includedata.settings
426    for i=1,#subdata do
427        local si = subdata[i]
428        if si.include then
429            inject(si,data,hash)
430        else
431            local x = si.x + xoffset
432            local y = si.y + yoffset
433            local t = {
434                x        = x,
435                y        = y,
436                realx    = x,
437                realy    = y,
438                settings = settings,
439            }
440            setmetatableindex(t,si)
441            data[#data+1] = t
442            hash[si.name or #data] = t
443        end
444    end
445end
446
447local function pack(data,field)
448    local list, max = { }, 0
449    for e=1,#data do
450        local d = data[e]
451        local f = d[field]
452        list[f] = true
453        if f > max then
454            max = f
455        end
456    end
457    for i=1,max do
458        if not list[i] then
459            for e=1,#data do
460                local d = data[e]
461                local f = d[field]
462                if f > i then
463                    d[field] = f - 1
464                end
465            end
466        end
467    end
468end
469
470local function expanded(chart,chartsettings)
471    local expandeddata = { }
472    local expandedhash = { }
473    local expandedchart = {
474        data = expandeddata,
475        hash = expandedhash,
476    }
477    setmetatableindex(expandedchart,chart)
478    local data = chart.data
479    local hash = chart.hash
480    for i=1,#data do
481        local di = data[i]
482        if di.include then
483            inject(di,expandeddata,expandedhash)
484        else
485            expandeddata[#expandeddata+1]  = di
486            expandedhash[di.name or #expandeddata] = di
487        end
488    end
489    --
490    expandedchart.settings = chartsettings or { }
491    -- make locals
492    chartsettings.shape = chartsettings.shape or { }
493    chartsettings.focus = chartsettings.focus or { }
494    chartsettings.line  = chartsettings.line  or { }
495    chartsettings.set   = chartsettings.set   or { }
496    chartsettings.split = chartsettings.split or { }
497    chartsettings.chart = chartsettings.chart or { }
498    setmetatableindex(chartsettings.shape,defaults.shape)
499    setmetatableindex(chartsettings.focus,defaults.focus)
500    setmetatableindex(chartsettings.line ,defaults.line )
501    setmetatableindex(chartsettings.set  ,defaults.set  )
502    setmetatableindex(chartsettings.split,defaults.split)
503    setmetatableindex(chartsettings.chart,defaults.chart)
504    --
505    if chartsettings.chart.vcompact == v_yes then
506        pack(expandeddata,"y")
507    end
508    if chartsettings.chart.hcompact == v_yes then
509        pack(expandeddata,"x")
510    end
511    --
512    for i=1,#expandeddata do
513        local cell = expandeddata[i]
514        local settings = cell.settings
515        if not settings then
516            cell.settings = chartsettings
517        else
518            settings.shape = settings.shape or { }
519            settings.focus = settings.focus or { }
520            settings.line  = settings.line  or { }
521            setmetatableindex(settings.shape,chartsettings.shape)
522            setmetatableindex(settings.focus,chartsettings.focus)
523            setmetatableindex(settings.line ,chartsettings.line)
524        end
525    end
526    return expandedchart
527end
528
529local splitter = lpeg.splitat(",")
530
531implement {
532    name      = "flow_set_location",
533    arguments = "string",
534    actions   = function(x,y)
535        if type(x) == "string" and not y then
536            x, y = lpegmatch(splitter,x)
537        end
538        local oldx, oldy = x, y
539        if not x or x == "" then
540            x = last_x
541        elseif type(x) == "number" then
542            -- ok
543        elseif x == "+" then
544            x = last_x + 1
545        elseif x == "-" then
546            x = last_x - 1
547        elseif find(x,"^[%+%-]") then
548            x = last_x + (tonumber(x) or 0)
549        else
550            x = tonumber(x)
551        end
552        if not y or y == "" then
553            y = last_y
554        elseif type(y) == "number" then
555            -- ok
556        elseif y == "+" then
557            y = last_y + 1
558        elseif x == "-" then
559            y = last_y - 1
560        elseif find(y,"^[%+%-]") then
561            y = last_y + (tonumber(y) or 0)
562        else
563            y = tonumber(y)
564        end
565        if x < 1 or y < 1 then
566            report_chart("the cell (%s,%s) ends up at (%s,%s) and gets relocated to (1,1)",oldx or"?", oldy or "?", x,y)
567            if x < 1 then
568                x = 1
569            end
570            if y < 1 then
571                y = 1
572            end
573        end
574        temp.x     = x or 1
575        temp.y     = y or 1
576        temp.realx = x or 1
577        temp.realy = y or 1
578        last_x     = x or last_x
579        last_y     = y or last_y
580    end
581}
582
583implement {
584    name      = "flow_set_connection",
585    arguments = "3 strings",
586    actions   = function(location,displacement,name)
587        local dx, dy = lpegmatch(splitter,displacement)
588        dx = tonumber(dx)
589        dy = tonumber(dy)
590        temp.connections[#temp.connections+1] = {
591            location = location,
592            dx       = dx or 0,
593            dy       = dy or 0,
594            name     = name,
595            comments = { },
596        }
597    end
598}
599
600local function visible(chart,cell)
601    local x, y = cell.x, cell.y
602    return
603        x >= chart.from_x and x <= chart.to_x and
604        y >= chart.from_y and y <= chart.to_y and cell
605end
606
607local function process_cells(g,chart,xoffset,yoffset)
608    local data = chart.data
609    if not data then
610        return
611    end
612    local focus = settings_to_hash(chart.settings.chart.focus or "")
613    for i=1,#data do
614        local cell = visible(chart,data[i])
615        if cell then
616            local settings = cell.settings
617            local shapesettings = settings.shape
618            local shape = cell.shape
619            if not shape or shape == "" then
620                shape = shapesettings.default or "none"
621            end
622            if shape ~= v_none then
623                local shapedata = validshapes[shape]
624                ctx_tographic(g,"flow_begin_sub_chart ;") -- when is this needed
625                if shapedata.kind == "line" then
626                    local linesettings = settings.line
627                    ctx_tographic(g,"flow_shape_line_color := %q ;", linesettings.color)
628                    ctx_tographic(g,"flow_shape_fill_color := %q ;","black")
629                    ctx_tographic(g,"flow_shape_line_width := %p ; ",linesettings.rulethickness)
630                elseif focus[cell.focus] or focus[cell.name] then
631                    local focussettings = settings.focus
632                    ctx_tographic(g,"flow_shape_line_color := %q ;", focussettings.framecolor)
633                    ctx_tographic(g,"flow_shape_fill_color := %q ;", focussettings.backgroundcolor)
634                    ctx_tographic(g,"flow_shape_line_width := %p ; ",focussettings.rulethickness)
635                else
636                    local shapesettings = settings.shape
637                    ctx_tographic(g,"flow_shape_line_color := %q ;", shapesettings.framecolor)
638                    ctx_tographic(g,"flow_shape_fill_color := %q ;", shapesettings.backgroundcolor)
639                    ctx_tographic(g,"flow_shape_line_width := %p ; ",shapesettings.rulethickness)
640                end
641                ctx_tographic(g,"flow_peepshape := false ;")   -- todo
642                ctx_tographic(g,"flow_new_shape(%s,%s,%s) ;",cell.x+xoffset,cell.y+yoffset,shapedata.number)
643                ctx_tographic(g,"flow_end_sub_chart ;")
644            end
645        end
646    end
647end
648
649-- todo : make lpeg for splitter
650
651local sign  = S("+p")  /  "1"
652            + S("-mn") / "-1"
653
654local full  = C(P("left"))
655            + C(P("right"))
656            + C(P("top"))
657            + C(P("bottom"))
658
659local char  = P("l") / "left"
660            + P("r") / "right"
661            + P("t") / "top"
662            + P("b") / "bottom"
663
664local space = P(" ")^0
665
666local what  = space
667            * (sign + Cc("0"))
668            * space
669            * (full + char)
670            * space
671            * (sign + Cc("0"))
672            * space
673            * (full + char)
674            * space
675            * P(-1)
676
677-- print(lpegmatch(what,"lr"))
678-- print(lpegmatch(what,"+l+r"))
679-- print(lpegmatch(what,"+l"))
680-- print(lpegmatch(what,"+ left+r     "))
681
682local function process_connections(g,chart,xoffset,yoffset)
683    local data = chart.data
684    local hash = chart.hash
685    if not data then
686        return
687    end
688    local settings = chart.settings
689    for i=1,#data do
690        local cell = visible(chart,data[i])
691        if cell then
692            local connections = cell.connections
693            for j=1,#connections do
694                local connection = connections[j]
695                local othername = connection.name
696                local othercell = hash[othername]
697                if othercell then -- and visible(chart,data[i]) then
698                    local cellx, celly = cell.x, cell.y
699                    local otherx, othery, location = othercell.x, othercell.y, connection.location
700                    if otherx > 0 and othery > 0 and cellx > 0 and celly > 0 and location then
701                        local what_cell, where_cell, what_other, where_other = lpegmatch(what,location)
702                        if what_cell and where_cell and what_other and where_other then
703                            local linesettings = settings.line
704                            ctx_tographic(g,"flow_smooth := %s ;", linesettings.corner == v_round and "true" or "false")
705                            ctx_tographic(g,"flow_dashline := %s ;", linesettings.dash == v_yes and "true" or "false")
706                            ctx_tographic(g,"flow_arrowtip := %s ;", linesettings.arrow == v_yes and "true" or "false")
707                            ctx_tographic(g,"flow_touchshape := %s ;", linesettings.offset == v_none and "true" or "false")
708                            ctx_tographic(g,"flow_dsp_x := %s ; flow_dsp_y := %s ;",connection.dx or 0, connection.dy or 0)
709                            ctx_tographic(g,"flow_connection_line_color := %q ;",linesettings.color)
710                            ctx_tographic(g,"flow_connection_line_width := %p ;",linesettings.rulethickness)
711                            ctx_tographic(g,"flow_connect_%s_%s (%s) (%s,%s,%s) (%s,%s,%s) ;",where_cell,where_other,j,cellx,celly,what_cell,otherx,othery,what_other)
712                            ctx_tographic(g,"flow_dsp_x := 0 ; flow_dsp_y := 0 ;")
713                        end
714                    end
715                end
716            end
717        end
718    end
719end
720
721local f_texttemplate_t = formatters["\\setvariables[flowcell:text][x=%s,y=%s,n=%i,align={%s},figure={%s},overlay={%s},destination={%s},realx=%s,realy=%s]"]
722local f_texttemplate_l = formatters["\\doFLOWlabel{%i}{%i}{%i}{%i}{%i}"]
723
724local splitter   = lpeg.splitat(":")
725local charttexts = { } -- permits " etc in mp
726
727implement {
728    name      = "flow_get_text",
729    arguments = "integer",
730    actions   = function(n)
731        if n > 0 then
732            context(charttexts[n])
733        end
734    end
735}
736
737local function process_texts(g,chart,xoffset,yoffset)
738    local data = chart.data
739    local hash = chart.hash
740    if not data then
741        return
742    end
743    charttexts = { }
744    for i=1,#data do
745        local cell = visible(chart,data[i])
746        if cell then
747            local x           = cell.x or 1
748            local y           = cell.y or 1
749            local figure      = cell.figure or ""
750            local overlay     = cell.overlay or ""
751            local destination = cell.destination or ""
752            local texts       = cell.texts
753            local noftexts    = #texts
754            local realx       = cell.realx or x
755            local realy       = cell.realy or y
756            if noftexts > 0 then
757                for i=1,noftexts do
758                    local text  = texts[i]
759                    local data  = text.text
760                    local align = text.align or ""
761                    local align = validlabellocations[align] or align
762                    charttexts[#charttexts+1] = data
763                    ctx_tographic(g,'flow_chart_draw_text(%s,%s,textext("%s")) ;',x,y,f_texttemplate_t(x,y,#charttexts,align,figure,overlay,destination,realx,realy))
764                    if i == 1 then
765                        figure      = ""
766                        overlay     = ""
767                        destination = ""
768                    end
769                end
770            elseif figure ~= "" or overlay ~= "" or destination ~= "" then
771                ctx_tographic(g,'flow_chart_draw_text(%s,%s,textext("%s")) ;',x,y,f_texttemplate_t(x,y,0,"",figure,overlay,destination,realx,realy))
772            end
773            local labels = cell.labels
774            for i=1,#labels do
775                local label    = labels[i]
776                local text     = label.text
777                local location = label.location or ""
778                local location = validlabellocations[location] or location
779                if text and text ~= "" then
780                    charttexts[#charttexts+1] = text
781                    ctx_tographic(g,'flow_chart_draw_label(%s,%s,"%s",textext("%s")) ;',x,y,location,f_texttemplate_l(x,y,#charttexts,realx,realy))
782                end
783            end
784            local exits = cell.exits
785            for i=1,#exits do
786                local exit     = exits[i]
787                local text     = exit.text
788                local location = exit.location or ""
789                local location = validlabellocations[location] or location
790                if text ~= "" then
791                    -- maybe make autoexit an option
792                    if location == "l" and x == chart.from_x + 1 or
793                       location == "r" and x == chart.to_x   - 1 or
794                       location == "t" and y == chart.to_y   - 1 or
795                       location == "b" and y == chart.from_y + 1 then
796                        charttexts[#charttexts+1] = text
797                        ctx_tographic(g,'flow_chart_draw_exit(%s,%s,"%s",textext("%s")) ;',x,y,location,f_texttemplate_l(x,y,#charttexts,realx,realy))
798                    end
799                end
800            end
801            local connections = cell.connections
802            for i=1,#connections do
803                local comments = connections[i].comments
804                for j=1,#comments do
805                    local comment  = comments[j]
806                    local text     = comment.text
807                    local location = comment.location or ""
808                    local length   = 0
809                    -- "tl" "tl:*" "tl:0.5"
810                    local loc, len = lpegmatch(splitter,location) -- do the following in lpeg
811                    if len == "*" then
812                        location = validcommentlocations[loc] or ""
813                        if location == "" then
814                            location = "*"
815                        else
816                            location = location .. ":*"
817                        end
818                    elseif loc then
819                        location = validcommentlocations[loc] or "*"
820                        length   = tonumber(len) or 0
821                    else
822                        location = validcommentlocations[location] or ""
823                    end
824                    if text and text ~= "" then
825                        charttexts[#charttexts+1] = text
826                        ctx_tographic(g,'flow_chart_draw_comment(%s,%s,%s,"%s",%s,textext("%s")) ;',x,y,i,location,length,f_texttemplate_l(x,y,#charttexts,realx,realy))
827                    end
828                end
829            end
830        end
831    end
832end
833
834local function getchart(settings,forced_x,forced_y,forced_nx,forced_ny)
835    if not settings then
836        print("no settings given")
837        return
838    end
839    local chartname = settings.chart.name
840    if not chartname then
841        print("no name given")
842        return
843    end
844    local chart = charts[chartname]
845    if not chart then
846        print("no such chart",chartname)
847        return
848    end
849    chart = expanded(chart,settings)
850    local chartsettings = chart.settings.chart
851    local autofocus = chart.settings.chart.autofocus
852    if autofocus then
853        autofocus = settings_to_hash(autofocus)
854        if not next(autofocus) then
855            autofocus = false
856        end
857    end
858    -- check natural window
859    local x  = forced_x  or tonumber(chartsettings.x)
860    local y  = forced_y  or tonumber(chartsettings.y)
861    local nx = forced_nx or tonumber(chartsettings.nx)
862    local ny = forced_ny or tonumber(chartsettings.ny)
863    --
864    local minx, miny, maxx, maxy = 0, 0, 0, 0
865    local data = chart.data
866    for i=1,#data do
867        local cell = data[i]
868        if not autofocus or autofocus[cell.name] then -- offsets probably interfere with autofocus
869            local x = cell.realx -- was bug: .x
870            local y = cell.realy -- was bug: .y
871            if minx == 0 or x < minx then minx = x end
872            if miny == 0 or y < miny then miny = y end
873            if minx == 0 or x > maxx then maxx = x end
874            if miny == 0 or y > maxy then maxy = y end
875        end
876    end
877    -- optional:
878    if x + nx > maxx then
879        nx = maxx - x + 1
880    end
881    if y + ny > maxy then
882        ny = maxy - y + 1
883    end
884    --
885    -- check if window should be larger (maybe autofocus + nx/ny?)
886    if autofocus then
887        -- x and y are ignored
888        if nx and nx > 0 then
889            maxx = minx + nx - 1
890        end
891        if ny and ny > 0 then
892            maxy = miny + ny - 1
893        end
894    else
895        if x and x > 0 then
896            minx = x
897        end
898        if y and y > 0 then
899            miny = y
900        end
901        if nx and nx > 0 then
902            maxx = minx + nx - 1
903        end
904        if ny and ny > 0 then
905            maxy = miny + ny - 1
906        end
907    end
908-- print("3>",minx, miny, maxx, maxy)
909    --
910    local nx = maxx - minx + 1
911    local ny = maxy - miny + 1
912    -- relocate cells
913    for i=1,#data do
914        local cell = data[i]
915        cell.x = cell.realx - minx + 1
916        cell.y = cell.realy - miny + 1
917    end
918    chart.from_x = 1
919    chart.from_y = 1
920    chart.to_x   = nx
921    chart.to_y   = ny
922    chart.nx     = nx
923    chart.ny     = ny
924    --
925    chart.shift_x = minx + 1
926    chart.shift_y = miny + 1
927    --
928    return chart
929end
930
931local function makechart_indeed(chart)
932    local settings      = chart.settings
933    local chartsettings = settings.chart
934    --
935    local g = ctx_startgraphic {
936        instance    = "metafun",
937        format      = "metafun",
938        method      = "scaled",
939        definitions = "",
940        wrapped     = true,
941    }
942    --
943    ctx_tographic(g,"if unknown context_flow : input mp-char.mpiv ; fi ;")
944    ctx_tographic(g,"flow_begin_chart(0,%s,%s);",chart.nx,chart.ny)
945    --
946    if chartsettings.option == v_test or chartsettings.dot == v_yes then
947        ctx_tographic(g,"flow_show_con_points := true ;")
948        ctx_tographic(g,"flow_show_mid_points := true ;")
949        ctx_tographic(g,"flow_show_all_points := true ;")
950    elseif chartsettings.dot ~= "" then -- no checking done, private option
951        ctx_tographic(g,"flow_show_%s_points := true ;",chartsettings.dot)
952    end
953    --
954    local backgroundcolor = chartsettings.backgroundcolor
955    if backgroundcolor and backgroundcolor ~= "" then
956        ctx_tographic(g,"flow_chart_background_color := %q ;",backgroundcolor)
957    end
958    --
959    local shapewidth    = chartsettings.width
960    local gridwidth     = shapewidth + 2*chartsettings.dx
961    local shapeheight   = chartsettings.height
962    local gridheight    = shapeheight + 2*chartsettings.dy
963    local chartoffset   = chartsettings.offset
964    local labeloffset   = chartsettings.labeloffset
965    local exitoffset    = chartsettings.exitoffset
966    local commentoffset = chartsettings.commentoffset
967    local clipoffset    = chartsettings.clipoffset
968    ctx_tographic(g,"flow_grid_width     := %p ;", gridwidth)
969    ctx_tographic(g,"flow_grid_height    := %p ;", gridheight)
970    ctx_tographic(g,"flow_shape_width    := %p ;", shapewidth)
971    ctx_tographic(g,"flow_shape_height   := %p ;", shapeheight)
972    ctx_tographic(g,"flow_chart_offset   := %p ;", chartoffset)
973    ctx_tographic(g,"flow_label_offset   := %p ;", labeloffset)
974    ctx_tographic(g,"flow_exit_offset    := %p ;", exitoffset)
975    ctx_tographic(g,"flow_comment_offset := %p ;", commentoffset)
976    --
977    local radius = settings.line.radius
978    local rulethickness = settings.line.rulethickness
979    local dx = chartsettings.dx
980    local dy = chartsettings.dy
981    if radius < rulethickness then
982        radius = 2.5*rulethickness
983        if radius > dx then
984            radius = dx
985        end
986        if radius > dy then
987            radius = dy
988        end
989    end
990    ctx_tographic(g,"flow_connection_line_width  := %p ;", rulethickness)
991    ctx_tographic(g,"flow_connection_smooth_size := %p ;", radius)
992    ctx_tographic(g,"flow_connection_arrow_size  := %p ;", radius)
993    ctx_tographic(g,"flow_connection_dash_size   := %p ;", radius)
994    --
995    local offset = chartsettings.offset -- todo: pass string
996    if offset == v_none or offset == v_overlay or offset == "" then
997        offset = -2.5 * radius -- or rulethickness?
998    elseif offset == v_standard then
999        offset = radius -- or rulethickness?
1000    end
1001    ctx_tographic(g,"flow_chart_offset := %p ;",offset)
1002    ctx_tographic(g,"flow_chart_clip_offset := %p ;",clipoffset)
1003    --
1004    ctx_tographic(g,"flow_reverse_y := true ;")
1005    if chartsettings.option == v_test then
1006        ctx_tographic(g,"flow_draw_test_shapes ;")
1007    end
1008    --
1009    process_cells(g,chart,0,0)
1010    process_connections(g,chart,0,0)
1011    process_texts(g,chart,0,0)
1012    --
1013 -- ctx_tographic(g,"clip_chart(%s,%s,%s,%s) ;",x,y,nx,ny) -- todo: draw lines but not shapes
1014    ctx_tographic(g,"flow_end_chart ;")
1015    ctx_stopgraphic(g)
1016    --
1017end
1018
1019-- We need to wrap because of tex.runlocal!
1020
1021local function makechart(chart)
1022    context.hbox()
1023    context.bgroup()
1024    context.forgetall()
1025    context(function() makechart_indeed(chart) end)
1026    context.egroup()
1027end
1028
1029local function splitchart(chart)
1030    local settings      = chart.settings
1031    local splitsettings = settings.split
1032    local chartsettings = settings.chart
1033    --
1034    local name = chartsettings.name
1035    --
1036    local from_x = chart.from_x
1037    local from_y = chart.from_y
1038    local to_x   = chart.to_x
1039    local to_y   = chart.to_y
1040    --
1041    local step_x  = splitsettings.nx or to_x
1042    local step_y  = splitsettings.ny or to_y
1043    local delta_x = splitsettings.dx or 0
1044    local delta_y = splitsettings.dy or 0
1045    --
1046    report_chart("spliting %a from (%s,%s) upto (%s,%s) with steps (%s,%s) and overlap (%s,%s)",
1047        name,from_x,from_y,to_x,to_y,step_x,step_y,delta_x,delta_y)
1048    --
1049    local part_x = 0
1050    local first_x = from_x
1051    while true do
1052        part_x = part_x + 1
1053        local last_x = first_x + step_x - 1
1054        local done = last_x >= to_x
1055        if done then
1056            last_x = to_x
1057        end
1058     -- if first_x >= to_x then
1059     --     break
1060     -- end
1061        local part_y = 0
1062        local first_y = from_y
1063        while true do
1064            part_y = part_y + 1
1065            local last_y = first_y + step_y - 1
1066            local done = last_y >= to_y
1067            if done then
1068                last_y = to_y
1069            end
1070         -- if first_y >= to_y then
1071         --     break
1072         -- end
1073            --
1074            local data = chart.data
1075            for i=1,#data do
1076                local cell = data[i]
1077            --     inspect(cell)
1078                local cx, cy = cell.x, cell.y
1079                if cx >= first_x and cx <= last_x then
1080                    if cy >= first_y and cy <= last_y then
1081                        report_chart("part (%s,%s) of %a is split from (%s,%s) -> (%s,%s)",part_x,part_y,name,first_x,first_y,last_x,last_y)
1082                        local x  = first_x
1083                        local y  = first_y
1084                        local nx = last_x - first_x + 1
1085                        local ny = last_y - first_y + 1
1086                        context.beforeFLOWsplit()
1087                        context.handleFLOWsplit(function()
1088                            makechart(getchart(settings,x,y,nx,ny)) -- we need to pass frozen settings !
1089                        end)
1090                        context.afterFLOWsplit()
1091                        break
1092                    end
1093                end
1094            end
1095            --
1096            if done then
1097                break
1098            else
1099                first_y = last_y + 1 - delta_y
1100            end
1101        end
1102        if done then
1103            break
1104        else
1105            first_x = last_x + 1 - delta_x
1106        end
1107    end
1108end
1109
1110implement {
1111    name      = "flow_make_chart",
1112    arguments = {
1113        {
1114            { "chart", {
1115                    { "name" },
1116                    { "option" },
1117                    { "backgroundcolor" },
1118                    { "width", "dimension" },
1119                    { "height", "dimension" },
1120                    { "dx", "dimension" },
1121                    { "dy", "dimension" },
1122                    { "offset", "dimension" },
1123                 -- { "bodyfont" },
1124                    { "dot" },
1125                    { "hcompact" },
1126                    { "vcompact" },
1127                    { "focus" },
1128                    { "autofocus" },
1129                    { "nx", "integer" },
1130                    { "ny", "integer" },
1131                    { "x", "integer" },
1132                    { "y", "integer" },
1133                    { "clipoffset", "dimension" },
1134                    { "labeloffset", "dimension" },
1135                    { "commentoffset", "dimension" },
1136                    { "exitoffset", "dimension" },
1137                    { "split" },
1138                },
1139            },
1140            { "shape", {
1141                    { "rulethickness", "dimension" },
1142                    { "default" },
1143                    { "framecolor" },
1144                    { "backgroundcolor" },
1145                },
1146            },
1147            { "focus", {
1148                    { "rulethickness", "dimension" },
1149                    { "framecolor" },
1150                    { "backgroundcolor" },
1151                },
1152            },
1153            { "line", {
1154                    { "rulethickness", "dimension" },
1155                    { "radius", "dimension" },
1156                    { "color" },
1157                    { "corner" },
1158                    { "dash" },
1159                    { "arrow" },
1160                    { "offset" },
1161                },
1162            },
1163            { "split", {
1164                    { "nx", "integer" },
1165                    { "ny", "integer" },
1166                    { "dx", "integer" },
1167                    { "dy", "integer" },
1168                    { "command" },
1169                    { "marking" },
1170                    { "before" },
1171                    { "after" },
1172                },
1173            },
1174         -- { "set" },
1175        }
1176    },
1177    actions   = function(settings)
1178        local chart = getchart(settings)
1179        if chart then
1180            local settings = chart.settings
1181            if settings then
1182                local chartsettings = settings.chart
1183                if chartsettings and chartsettings.split == v_yes then
1184                    splitchart(chart)
1185                else
1186                    makechart(chart)
1187                end
1188            else
1189                makechart(chart)
1190            end
1191        end
1192    end
1193}
1194