weka.gui.treevisualizer.PlaceNode2.java Source code

Java tutorial

Introduction

Here is the source code for weka.gui.treevisualizer.PlaceNode2.java

Source

/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    PlaceNode2.java
 *    Copyright (C) 1999-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.treevisualizer;

import java.util.Vector;

/**
 * This class will place the Nodes of a tree.
 * <p>
 * 
 * It will place these nodes so that they fall at evenly below their parent. It
 * will then go through and look for places where nodes fall on the wrong side
 * of other nodes when it finds one it will trace back up the tree to find the
 * first common sibling group these two nodes have And it will adjust the
 * spacing between these two siblings so that the two nodes no longer overlap.
 * This is nasty to calculate with , and takes a while with the current
 * algorithm I am using to do this.
 * <p>
 * 
 * 
 * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
 * @version $Revision$
 */
public class PlaceNode2 implements NodePlace {
    /** The space each row will take up. */
    private double m_yRatio;

    /** An array that lists the groups and information about them. */
    private Group[] m_groups;

    /** An array that lists the levels and information about them. */
    private Level[] m_levels;

    /** The Number of groups the tree has */
    private int m_groupNum;

    /** The number of levels the group tree has */
    private int m_levelNum;

    /**
     * The Funtion to call to have the nodes arranged.
     * 
     * @param r The top node of the tree to arrange.
     */
    @Override
    public void place(Node r) {
        // note i might write count groups into the node class as well as
        // it may be useful too;

        m_groupNum = Node.getGCount(r, 0); // i could swap over to the node class
        // group count,but this works os i'm not gonna
        m_groups = new Group[m_groupNum];

        for (int noa = 0; noa < m_groupNum; noa++) {
            m_groups[noa] = new Group();
            m_groups[noa].m_gap = 3;
            m_groups[noa].m_start = -1;
        }

        groupBuild(r);
        m_levelNum = Node.getHeight(r, 0);
        m_yRatio = 1 / (double) (m_levelNum + 1);

        m_levels = new Level[m_levelNum];

        for (int noa = 0; noa < m_levelNum; noa++) {
            m_levels[noa] = new Level();
        }
        r.setTop(m_yRatio);
        yPlacer();
        r.setCenter(0);
        xPlacer(0);

        // ok now i just have to untangle then scale down
        // note instead of starting with coords between 1 and 0 i will
        // use ints then scale them down
        // i will scale them down either by all relative to the largest
        // line or by each line individually

        untangle2();

        scaleByMax();
        // scaleByInd();
    }

    /*
     * private void thinner() { //what this function does is it retains the
     * symmetry of the // parent node about the children but the children are no
     * longer evenly //spaced this stops children from being pushed too far to the
     * sides //,note this algorithm may need the method altered as it may //
     * require heavy optimisation to go at any decent speed
     * 
     * Node r,s; Edge e; double parent_x; for (int noa = group_num - 1;noa >=
     * 0;noa--) { Vector shifts = new Vector(20,10); shifts.addElement(0); int
     * g_num = 0;//this is the offset from groups.m_start to get the right 1 r =
     * groups[noa].m_p; parent_x = r.getCenter(); for (int nob = 1;(e =
     * r.getChild(nob)) != null;nob++) { double margin; s = e.getTarget(); margin
     * = s_getCenter - r.getChild(nob - 1).getTarget().getCenter-1 -
     * shift.elementAt(nob-1); if (margin > 0) { margin =
     * check_down(s,g_num,margin); if (margin > 0) { shift.addElement(-margin); }
     * else { shift.addElement(0); } } else { shift.addElement(0); } if
     * (s.getChild(0) != null) { g_num++; } } } }
     * 
     * 
     * private double check_down(Node r,int gn,double m) { //note i need to know
     * where the children of the //other changers are to properly overlap check
     * //to do this i think the best way is to go up the other group //parents
     * line and see if it goes through the current group //this means to save time
     * i need to know the level that is being //worked with along with the group
     * 
     * Edge e; for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
     * 
     * } }
     */

    /**
     * This will set initial places for the x coord of the nodes.
     * 
     * @param start The `number for the first group to start on (I think).
     */
    private void xPlacer(int start) {
        // this can be one of a few x_placers (the first)
        // it will work by placing 1 space inbetween each node
        // ie the first at 0 the second at 1 and so on
        // then it will add to this value the place of the parent
        // node - half of the size
        // i will break this up into several functions
        // first the gap setter;
        // then the shifter
        // it will require a vector shift function added to the node class
        // i will write an additional shifter for the untangler
        // for its particular situation

        Node r;
        Edge e;
        if (m_groupNum > 0) {
            m_groups[0].m_p.setCenter(0);
            for (int noa = start; noa < m_groupNum; noa++) {
                int nob, alter = 0;
                double c = m_groups[noa].m_gap;
                r = m_groups[noa].m_p;
                for (nob = 0; (e = r.getChild(nob)) != null; nob++) {
                    if (e.getTarget().getParent(0) == e) {
                        e.getTarget().setCenter(nob * c);
                    } else {
                        alter++;
                    }
                }
                m_groups[noa].m_size = (nob - 1 - alter) * c;
                xShift(noa);
            }
        }
    }

    /**
     * This will shift a group of nodes to be aligned under their parent.
     * 
     * @param n The group number to shift
     */
    private void xShift(int n) {
        Edge e;
        Node r = m_groups[n].m_p;
        double h = m_groups[n].m_size / 2;
        double c = m_groups[n].m_p.getCenter();
        double m = c - h;
        m_groups[n].m_left = m;
        m_groups[n].m_right = c + h;

        for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
            if (e.getTarget().getParent(0) == e) {
                e.getTarget().adjustCenter(m);
            }
        }
    }

    /**
     * This scales all the x values to be between 0 and 1.
     */
    private void scaleByMax() {
        // ammendment to what i may have commented before
        // this takes the lowest x and highest x and uses that as the scaling
        // factor
        double l_x = 5000, h_x = -5000;
        for (int noa = 0; noa < m_groupNum; noa++) {
            if (l_x > m_groups[noa].m_left) {
                l_x = m_groups[noa].m_left;
            }

            if (h_x < m_groups[noa].m_right) {
                h_x = m_groups[noa].m_right;
            }
        }

        Edge e;
        Node r, s;
        double m_scale = h_x - l_x + 1;
        if (m_groupNum > 0) {
            r = m_groups[0].m_p;
            r.setCenter((r.getCenter() - l_x) / m_scale);
            // System.out.println("from scaler " + l_x + " " + m_scale);
            for (int noa = 0; noa < m_groupNum; noa++) {
                r = m_groups[noa].m_p;
                for (int nob = 0; (e = r.getChild(nob)) != null; nob++) {
                    s = e.getTarget();
                    if (s.getParent(0) == e) {
                        s.setCenter((s.getCenter() - l_x) / m_scale);
                    }
                }
            }
        }
    }

    /**
     * This untangles the nodes so that they will will fall on the correct side of
     * the other nodes along their row.
     */
    private void untangle2() {
        Ease a;
        Edge e;
        Node r, nf = null, ns = null, mark;
        int l = 0; // ,times = 0; NOT USED
        int f, s, tf = 0, ts = 0, pf, ps;
        while ((a = overlap(l)) != null) {
            // times++; NOT USED
            // System.out.println("from untang 2 " + group_num);
            f = a.m_place;
            s = a.m_place + 1;
            while (f != s) {
                a.m_lev--;
                tf = f;
                ts = s;
                f = m_groups[f].m_pg;
                s = m_groups[s].m_pg;
            }
            l = a.m_lev;
            pf = 0;
            ps = 0;
            r = m_groups[f].m_p;
            mark = m_groups[tf].m_p;
            nf = null;
            ns = null;
            for (int noa = 0; nf != mark; noa++) {
                pf++;
                nf = r.getChild(noa).getTarget();
            }
            mark = m_groups[ts].m_p;
            for (int noa = pf; ns != mark; noa++) {
                ps++; // the number of gaps between the two nodes
                ns = r.getChild(noa).getTarget();
            }
            // m_groups[f].gap =
            // Math.ceil((a.amount / (double)ps) + m_groups[f].gap);
            // note for this method i do not need the group gap ,but i will leave
            // it for the other methods;
            Vector<Double> o_pos = new Vector<Double>(20, 10);
            for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
                if (e.getTarget().getParent(0) == e) {
                    Double tem = new Double(e.getTarget().getCenter());
                    o_pos.addElement(tem);
                }
            }

            pf--;
            double inc = a.m_amount / ps;
            for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
                ns = e.getTarget();
                if (ns.getParent(0) == e) {
                    if (noa > pf + ps) {
                        ns.adjustCenter(a.m_amount);
                    } else if (noa > pf) {
                        ns.adjustCenter(inc * (noa - pf));
                    }
                }
            }

            nf = r.getChild(0).getTarget();
            inc = ns.getCenter() - nf.getCenter();
            m_groups[f].m_size = inc;
            m_groups[f].m_left = r.getCenter() - inc / 2;
            m_groups[f].m_right = m_groups[f].m_left + inc;
            inc = m_groups[f].m_left - nf.getCenter();

            double shift;
            int g_num = 0;
            for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
                ns = e.getTarget();
                if (ns.getParent(0) == e) {
                    ns.adjustCenter(inc);
                    shift = ns.getCenter() - o_pos.elementAt(noa).doubleValue();
                    if (ns.getChild(0) != null) {
                        moveSubtree(m_groups[f].m_start + g_num, shift);
                        g_num++;
                    }
                }
                // ns.adjustCenter(-shift);
            }
            // zero_offset(r);

            // x_placer(f);
        }
    }

    /**
     * This will recursively shift a sub there to be centered about a particular
     * value.
     * 
     * @param n The first group in the sub tree.
     * @param o The point to start shifting the subtree.
     */
    private void moveSubtree(int n, double o) {
        Edge e;
        Node r = m_groups[n].m_p;
        for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
            if (e.getTarget().getParent(0) == e) {
                e.getTarget().adjustCenter(o);
            }
        }
        m_groups[n].m_left += o;
        m_groups[n].m_right += o;
        if (m_groups[n].m_start != -1) {
            for (int noa = m_groups[n].m_start; noa <= m_groups[n].m_end; noa++) {
                moveSubtree(noa, o);
            }
        }
    }

    /**
     * This will find an overlap and then return information about that overlap
     * 
     * @param l The level to start on.
     * @return null if there was no overlap , otherwise an object containing the
     *         group number that overlaps (only need one) how much they overlap
     *         by, and the level they overlap on.
     */
    private Ease overlap(int l) {
        Ease a = new Ease();
        for (int noa = l; noa < m_levelNum; noa++) {
            for (int nob = m_levels[noa].m_start; nob < m_levels[noa].m_end; nob++) {
                a.m_amount = m_groups[nob].m_right - m_groups[nob + 1].m_left + 2;
                // System.out.println(m_groups[nob].m_right + " + " +
                // m_groups[nob+1].m_left + " = " + a.amount);
                if (a.m_amount >= 0) {
                    a.m_amount++;
                    a.m_lev = noa;
                    a.m_place = nob;
                    return a;
                }
            }
        }
        return null;
    }

    /*
     * private int count_m_groups(Node r,int l) { Edge e; if (r.getChild(0) !=
     * null) { l++; } for (int noa = 0;(e = r.getChild(noa)) != null;noa++) { l =
     * count_groups(e.getTarget(),l); }
     * 
     * return l; }
     */

    /**
     * This function sets up the height of each node, and also fills the levels
     * array with information about what the start and end groups on that level
     * are.
     */
    private void yPlacer() {
        // note this places the y height and sets up the levels array
        double changer = m_yRatio;
        int lev_place = 0;
        if (m_groupNum > 0) {
            m_groups[0].m_p.setTop(m_yRatio);
            m_levels[0].m_start = 0;

            for (int noa = 0; noa < m_groupNum; noa++) {
                if (m_groups[noa].m_p.getTop() != changer) {
                    m_levels[lev_place].m_end = noa - 1;
                    lev_place++;
                    m_levels[lev_place].m_start = noa;
                    changer = m_groups[noa].m_p.getTop();
                }
                nodeY(m_groups[noa].m_p);
            }
            m_levels[lev_place].m_end = m_groupNum - 1;
        }
    }

    /**
     * This will set all of the children node of a particular node to their
     * height.
     * 
     * @param r The parent node of the children to set their height.
     */
    private void nodeY(Node r) {
        Edge e;
        double h = r.getTop() + m_yRatio;
        for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
            if (e.getTarget().getParent(0) == e) {
                e.getTarget().setTop(h);
                if (!e.getTarget().getVisible()) {
                    // System.out.println("oh bugger");
                }
            }
        }
    }

    /**
     * This starts to create the information about the sibling groups. As more
     * groups are created the for loop in this will check those groups for lower
     * groups.
     * 
     * @param r The top node.
     */
    private void groupBuild(Node r) {
        if (m_groupNum > 0) {
            m_groupNum = 0;
            m_groups[0].m_p = r;
            m_groupNum++;
            // note i need to count up the num of groups first
            // woe is me
            for (int noa = 0; noa < m_groupNum; noa++) {
                groupFind(m_groups[noa].m_p, noa);
            }
        }
    }

    /**
     * This is called to build the rest of the grouping information.
     * 
     * @param r The parent of the group.
     * @param pg The number for the parents group.
     */
    private void groupFind(Node r, int pg) {
        Edge e;
        boolean first = true;
        for (int noa = 0; (e = r.getChild(noa)) != null; noa++) {
            if (e.getTarget().getParent(0) == e) {
                if (e.getTarget().getChild(0) != null && e.getTarget().getCVisible()) {
                    if (first) {
                        m_groups[pg].m_start = m_groupNum;
                        first = false;
                    }
                    m_groups[pg].m_end = m_groupNum;
                    m_groups[m_groupNum].m_p = e.getTarget();
                    m_groups[m_groupNum].m_pg = pg;
                    // m_groups[m_groupNum].m_id = m_groupNum; //just in case I ever need
                    // this info NOT USED
                    m_groupNum++;
                }
            }
        }
    }

    // note these three classes are only to help organise the data and are
    // inter related between each other and this placer class
    // so don't mess with them or try to use them somewhere else
    // (because that would be a mistake and I would pity you)

    /**
     * Inner class for containing the level data.
     */
    private class Level {
        /** The number for the group on the left of this level. */
        public int m_start;
        /** The number for the group on the right of this level. */
        public int m_end;

        /** These two params would appear to not be used. */
        // public int m_left; NOT USED
        // public int m_right; NOT USED
    }

    /**
     * Inner class for containing the grouping data.
     */
    private class Group {
        /** The parent node of this group. */
        public Node m_p;

        /** The group number for the parent of this group. */
        public int m_pg;

        /** The gap size for the distance between the nodes in this group. */
        public double m_gap;

        /** The leftmost position of this group. */
        public double m_left;

        /** The rightmost position of this group. */
        public double m_right;

        /** The size of this group. */
        public double m_size;

        /** The start node of this group. */
        public int m_start;

        /** The end node of this group. */
        public int m_end;

        /** The group number for this group. (may not be used!?). */
        // public int m_id; NOT USED
    }

    /**
     * An inner class used to report information about any tangles found.
     */
    private class Ease {
        /** The number of the group on the left of the tangle. */
        public int m_place;
        /** The distance they were tangled. */
        public double m_amount;
        /** The level on which they were tangled. */
        public int m_lev;
    }
}