// TITLE     : legends_mc()    stands for legends_MultiColumns
//             for graphical axes with many curves and related legends
// AUTHOR (C): Samuel GOUGEON - Le Mans (France) - sgougeon@free.fr
// LICENSE   : CeCILL-C
//   http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
// SOFTWARE  : for Scilab > 5.0  http://www.scilab.org/
// DEPENDENCY: none
// RELEASE   : 1.1 (2013-02-25)
// DISCLAIMER: No warranty of any kind is provided. Use "as is"
// HELP      : run legends_mc() without input arguments,
//             to display the help and examples for demos.
// HISTORY   : 
//   2013-02-24 : First release
//   2013-02-25 : Line style and marker color added
//                Styles (and thicknesses)can be provided by a set of 
//                 handles containing polylines to be legended.
//                Inline help revised.
//   2013-02-26 : legends_mc() renamed legends_mc()

// ----------------------------
function legends_mc(Texts, Styles, Lpc, font_size, thickness, pos, framed?)
// For many curves & related legends, displays a multicolumn bloc of legends:
//  - The shape of the bloc can be specified (Lpc)
//  - Positionning with Logarithmic or/and reversed axes is supported
//  - Lines styles and Markers styles are supported and can be mixed.
//    A set of polyline handles or having polyline children can be
//    alternatively provided
//  - Lines thickness(es) can be specified (ignored if handles are 
//    given: read out from the polylines properties)
//
// HELP: run legends_mc() without any parameter
// DEMO: run legends_mc(..) without specifying Texts
//
// Texts  : vector of legends
// Styles : a) vector of related lines or markers styles (integer indices)
//          Styles(i)>0 -> line color (only solid style supported)
//          -14<= Styles(i) <=0 -> marker (overlay with line unsupported)
//         b) (2,n) matrix: 
//             Styles(1,:) = as in a)
//             Styles(2,:) = line style, or color of marker
//         c) vector of graphical handles. Then all Polyline children
//            are searched (in chronological order of creation).
//            Texts must have as many entries as there are available
//            polylines.
//            If only 1 handle is given and is an axes, the legends is 
//            set in this axes. When returning, the focus is restored 
//            to the axes priorly active.
//            If a set of handles or an handle not being an axes is given,
//            the legends are set in the currently active axes.
//         Default Styles : gca()
// Lpc>0 :  (maximal) number of Lines Per Column (integer)
// Lpc<0 : -(maximal) number of Columns per line (integer)
// framed? : boolean: if %T, draws the global box of legends (default)
// pos : position of the block:
//      "ur" | 1 : in the upper right corner (default)
//      "ul" | 2 : in the upper left corner
//      "ll" | 3 : in the lower left corner
//      "lr" | 4 : in the lower right corner
//      "?"  | 5 : interactive positionning with the mouse
//      [xr,yr]: relative coordinates 0 <= xr,yr <= 1 of the upper left 
//         corner of the block, with respect to the upper left corner
//         of the data bounds area. [0,0] is equivalent to "ul"
// thickness: scalar or vector of lines thickness. 
//            If a vector is provided, its length must = Styles one.
//            If styles are from handles, thickness vector is ignored.
// 
// DEMOS: run legends_mc(..) without Texts of legends:
//   clf, legends_mc( framed?=%f )
//   clf, legends_mc( Lpc=7, pos="?" )
//   clf, legends_mc( Lpc=-3, pos="lr" )
//   clf, legends_mc( Lpc=-2, pos=[0.15 0.3] )
//   clf, legends_mc( font_size=2, pos="ll" )
//   clf, legends_mc( pos="?", thickness=2 )
//   clf, legends_mc( pos="lr", thickness=1+round(rand(1:19)) )
//   clf, plot2d(), legends_mc( Lpc=-3, pos="ll" )
//   clf, plot(), f=gcf(); legends_mc(Styles=f.children($), pos="?")
//
// EXAMPLE: 
//   clf, plot2d(), legends_mc("line #"+string(1:3), Lpc=-3, pos="?" )
// 
    if  argn(2)==0
        head_comments("legends_mc")
        return
    end
    
    ax = gca();
    axm = ax;
    
    if ~isdef("Styles","local") then
        Styles = gca()
    end
    if ~isdef("Lpc","local"), Lpc = -2,   end

    demo = ~isdef("Texts","local")
    
    styles_from_handles = (typeof(Styles)=="handle")
    if styles_from_handles then
        if length(Styles)==1 & Styles.type=="Axes" then
            ax = Styles
            sca(ax)
        end
        
        H = []
        for i = 1:length(Styles)
            H = [H %h_find(Styles(i),"type","Polyline")]
        end
        if H==[] & ~demo then
           error("legends_mc: no Polyline is defined in given handles")
        end
        s = []  // vector of styles 
        t = []  // vector of thicknesses
        for i = 1:length(H)
            h = H(i)
            t = [t h.thickness]
            if h.line_mode=="off" then
                s = [s [-h.mark_style ; h.mark_foreground ]]
            else
                s = [s [h.foreground ; h.line_style]]
            end
        end
        Styles = s
        if ~demo, thickness = t, end
    else    // Explicit Styles
        if min(size(Styles))==1 then
            Styles = Styles(:)'              // lines colors
            Styles = [Styles ; ones(Styles)] // 1 = solid
        end
    end

    if demo then // demos defaults
        nL = size(Styles,2);   // number of Legends
        if nL==0 & styles_from_handles
            nL = 17
            styles_from_handles = %f
            maxNc = max([nL min([40 size(ax.parent.color_map,1)]) ])
            s = grand(1,"prm",(-14:maxNc)')
            s = s(1:nL)'
            s2 = ones(s)
            tmp = find(s<=0)
            s2(tmp) = grand(tmp,"uin",1,maxNc)  // markers colors
            Styles = [s ; s2]
        end
        Texts = "legend#"+string(1:nL)  // Text of legends
    end
    
    // number of legends and Styles must match
    nL = size(Texts,"*")
    if nL~=size(Styles,2) then
        error(sprintf("%s: %d legends for %d Polylines",..
           "legends_mc", nL, size(Styles,2)))
    end

    if ~isdef("pos","local"), pos = "ur", end
    if ~isdef("thickness","local"), thickness = ax.thickness, end
    if ~isdef("font_size","local"), font_size = ax.font_size, end
    if ~isdef("framed?","local"), framed? = %t, end
    
    // EXAMPLES = DEMOS
    if demo then
        if ~styles_from_handles then
            x = logspace(-2,2,100)
            plot2d("ll",x,exp(x).*(1.01+sin(x)))
            xtitle("Positionning on Log or/and reversed axes is supported")
            ax = gca()
            ax.axes_reverse(1:2) = "on"
        end
        legends_mc(Texts, Styles, Lpc, font_size=font_size, pos=pos, thickness=thickness, framed?=framed?);
        if is_handle_valid(axm), sca(axm), end
        return
    end
    // ----------------------------------------------------------
    if Lpc<0 then
        Lpc = ceil(-nL/Lpc)
    end
    x = 0
    i = 1
    j = min([Lpc, nL])
    ymin = %inf
    ymax = -%inf
    L = []  // vector of handles of colums
    drawlater()
    while i<=nL
        legends(Texts(i:j), Styles(:,i:j), font_size=font_size, opt="ul")
        idL = gce()
        move(idL, [x,0])
        L = [idL L]
        frame = idL.children($)
        if i==1, Frame = frame; end // for the global frame
        w = strange(frame.data(:,1)) // width of this block
        ymin = min([ymin ; frame.data(:,2)])
        ymax = max([ymax ; frame.data(:,2)])
        if ~framed? | i>1  // masks the frame
            idL.children($).line_mode = "off"
        end

        // Set the thickess of line style
        if length(thickness)==1 then
           idL.children(2:2:$).thickness = thickness
        else
           for k = i:j
               idL.children(2*(k-i+1)).thickness = thickness(k)
           end
        end
        
        x = x + w    // updates the position of next block
        i = i + Lpc
        j = min([j+Lpc, nL])
    end
    
    // GLOBAL BOX
    W = (x + w)*1.025   // Total width
    H = ymax - ymin 
    dh = (ymax - ymin)/70
    H = H + 2*dh
    if framed? then
        Frame.data([2 3],1) = W
        Frame.data([1 2],2) = ymax + dh
        Frame.data([3 4],2) = ymin - dh
        Frame.foreground = color("grey")
        //Frame.fill_mode = "off"
    end
    
    // POSITIONNING
    // ------------
    db = ax.data_bounds   // db(1,:)=mins  db(2,:)=maxs
    m = ax.margins
    // Horizontal shift to the right
    if pos=="ur" | pos==1 | pos=="lr" | pos==4 then
        s = 0.995 - m(2)- W
        for i = 1:length(L)
            move(L(i), [s,0])
        end
    end
    // Vertical shift to the bottom
    if pos=="ll" | pos==3 | pos=="lr" | pos==4
        s = (0.98 - m(4)) - (m(3)+H)
        for i = 1:length(L)
            move(L(i), [0,-s])
        end
    end
    // Explicit [x,y] position (relative coordinates in the axes,
    //  w.r.t the upper left corner of the axes):
    if typeof(pos)=="constant" & length(pos)==2 then
        w = 1 - sum(m(1:2))
        s = w * pos(1)
        for i = 1:length(L)
            move(L(i), [s,0])
        end
        // vertically:
        h = 1 - sum(m(3:4))
        s = h * pos(2)
        for i = 1:length(L)
            move(L(i), [0,-s])
        end

    end
    // Interactive positionning
    if pos=="?" | pos==5 then
        disp("Please drag the bloc of legends and set it where appropriated...")
        axm2 = gca()
        tmp = Frame, while tmp.type~="Axes", tmp = tmp.parent, end
        sca(tmp)
        drawnow()
        d = Frame.data
        r0 = [min(d(:,1)) max(d(:,2)) strange(d(:,1)) strange(d(:,2)) ]
        r = dragrect(r0)
        drawlater()
        for i = 1:length(L)
            move(L(i), [r(1)-r0(1) r(2)-r0(2)])
        end
        sca(axm2)
    end

    drawnow()
    // restoring the focus to the entering axes
    if is_handle_valid(axm), sca(axm), end 
endfunction

//=========================

function H = %h_find(h, prop, values, depth)
// h: handle of the root object from which the search starts
// prop: property according to which the search is done (scalar string)
// values: values that the property may have (vector of strings)
// depth: number of nested levels along which the search is done.
//        depth<0 looks for parents (only along the main lineage).
//                (not yet implemented) 
//        depth>0 looks for children. default is %inf (all children)
// H: vector of handles of objects found with the required property

    if argn(2)<4 then
        depth = %inf
    end
    // h itself:
    command = "ok = or(h."+prop+"==["""+strcat(values,""""",")+"""])"
    execstr(command);
    H = []
    if  ok then
        H = [h H]
    end
    // h children:
    if h.children~=[] & depth>0 then
        nc = length(h.children)
        for i = nc:-1:1 // reverse = restoring the chronological order
            H = [H %h_find(h.children(i), prop, values, depth-1)]
        end
    end
endfunction
