Thomas Kramer

IT-COW | Zeichnen von Bäumen beliebigen Grades mithilfe der abego TreeLayout-Bibliothek und dem MVC-Design Pattern

Zeichnen von Bäumen beliebigen Grades mithilfe der abego TreeLayout-Bibliothek und dem MVC-Design Pattern

By Administrator at Juni 19, 2012 22:22
Filed Under: Design Patterns, Java, Programmierung allgemein, Projekte

abego TreeLayout ist eine Java-Komponente zum Visualisieren von Bäumen. Sie implementiert den Walker-Algorithmus, mit Optimierungen von Buchheim, Jünger und Leipert um lineare Laufzeit zu erreichen - die Bibliothek berechnet auf diese Weise die x-Koordinaten für jeden einzelnen Knoten. Das Framework steht unter der BSD-Lizenz und lässt sich so auch in kommerziellen Projekten verwenden.

 

Den Blog-Titel bitte nicht zu wörtlich nehmen, denn das Zeichnen muss man schon noch selbst übernehmen. An dieser Stelle möchte ich zunächst das PDF mit der Dokumentation verlinken. Als erstes muss man natürlich die Bibliothek in das eigene Projekt verlinken, unter Eclipse geschieht das über den Menüpunkt Project/Properties/Java Build Path/Libraries/Add External JARs.

 

Die Verwendung funktioniert folgendermaßen:

 

  • 1. Zunächst benötigt man eine Configuration-Klasse, welche das Interface Configuration implementiert. In dieser nimmt man die Konfiguration der Knoten vor - wo der root-Knoten positioniert werden und wieviel Leerraum zwischen den Knoten gelassen werden soll.
  • 2. Eine eigene Provider-Konfiguration, welche das Interface NodeExtentProvider implementiert. In dieser muss man die Methoden getWidth() und getHeight() implementieren, welche Konstanten zurückliefern wie groß und breit der Knoten sein soll. Bei mir ist jeder Knoten gemäß CustomProvider-Klasse gleich groß, aber man könnte da noch eine Fallunterscheidung einbauen.
  • 3. Man erstellt einen Wurzel-Knoten und eine DefaultTreeForTreeLayout-Instanz, welcher der Wurzel-Knoten im Konstruktor übergeben wird.
  • 4. Der DefaultTreeForTreeLayout-Instanz wird jeder Unterknoten mit der Methode addChild(parent, node) bekannt gemacht.
  • 5. Man erstellt eine TreeLayout-Instanz mit diesen Konstruktor-Parametern: (DefaultTreeForTreeLayout, Provider, Configuration).
  • 6. Mit der Methode TreeLayout.getNodeBounds() liest man die Koordinaten aus und iteriert anschließend über die zurückgegebene Map.

 

Wie in meinen vorherigen Binärbaum-Programmen, überwiegend geschrieben in Processing, lasse ich hier zufallsgenerierte Bäume erstellen. Die Einfüge-Methodik ist aber ein wenig anders, da es hier nicht mehr um Binärbäume geht. Ich füge jeden Knoten einer ArrayList hinzu und bestimme einen zufällig Ausgewählten als Vaterknoten.

 

Außerdem nutze ich hier das Model-View-Controller-Design Pattern. Im Controller lasse ich das Model und den View erstellen, und lese anschließend die Daten aus dem Model und übergebe sie an den View. Dadurch sind die drei Schichten relativ unabhängig voneinander.

 

Mein Programm hier hat wieder einen optionalen Zentrierungs-Modus, der aber natürlich nur Sinn macht wenn der Baum komplett auf einen Bildschirm passt und deswegen automatisch deaktiviert wird wenn das nicht zutrifft. Das Programm stellt auch Scrollbalken zur Verfügung was nicht ganz so selbstverständlich ist - denn dafür muss man die am weitesten rechts bzw. unten stehenden Knoten ermitteln und der JScrollPane mit setPreferredSize() entsprechend übermitteln.

 

Damit die Single-Thread-Rule von Swing beachtet bleibt wird das Zeichen der Oberfläche in einem eigenen Runnable-Thread vorgenommen, der über SwingUtilities.invokeLater() in Swing eingereiht wird. Übrigens ist mir aufgefallen dass das Zeichnen in Eclipse (in Java/Swing) deutlich schneller (bei gleicher Knotenmenge auf demselben Rechner) geschieht als damals in Processing mit meinen Binärbaum-Programmen, obwohl Processing auch auf Java basiert (wird aber nur irgendeine Initialisierungssache sein).

 

Noch eine kleine Anmerkung, laut abego-Dokumentation liefert ihr Algorithmus platzsparendere Resultate als das Pendant der Netbeans Visual API. Ich habe mir mittlerweile viele generierte Bäume davon angesehen und die Platzausnutzung scheint mir auch perfekt zu sein - besser komprimieren geht nicht.

 

Nachfolgend ein Screenshot mit 20 Knoten und aktivierter Zentrierung:


 

Nachfolgend das Demo-Programm:

 

abego_demo.jar

 

Dieses Demo-Programm ist gegen Java 6 kompiliert und erstellt einen zufallsgenerierten Baum mit 50 oder einer übermittelten Anzahl Knoten (max 1000). Starten kann man es über die Kommandozeile mit dem Befehl

 

java -jar abego_demo.jar [Anzahl Knoten]

 

oder mit einem Doppelklick auf die jar-Datei, wenn die Java Runtime installiert ist und die Verknüpfungen in Windows stimmen.

 

Nachfolgend der Quellcode:

 

Das Projekt ist übrigens unabhängig vom Studium entstanden. Es ist als Weiterführung meiner Binärbaum-Programme - die wie der Name schon sagt auf Bäume zweiten Grades beschränkt sind - zu verstehen, denn ich wollte unbedingt Bäume beliebigen Grades visualisieren können.

 

Update 28.06.2012: Quellcode nochmal überarbeitet, Änderungen:

 

  • Die Programmkonfiguration war noch etwas unübersichtlich über drei Klassen verteilt, jetzt gibt es eine zentrale ProgramConfiguration-Klasse. In der Main-Klasse wird die Konfiguration vorgenommen und an den Controller weitergeleitet, der sie wiederum an das Model weiterleitet.
  • Die Anzahl der Knoten kann jetzt über einen externen Parameter eingestellt werden.
  • Bei der Berechnung der Zentrierung waren noch zwei Fehler drin: Zum einen war eine schließende Klammer an eine falsche Stelle gesetzt, zum anderen wurde dieser Wert nicht für die Scrollbalken aufaddiert.
  • Berechnung der nötigen Werte für die Scrollbalken muss nur einmal geschehen, daher ist sie nun in der setData()-Methode von DrawPanel.
  • Zentrierung wird nun automatisch deaktiviert wenn nicht mehr alle Knoten auf einen Bildschirm passen.
  • Es ist nun nur noch die Core-Bibliothek von abego eingebunden, die Demo war unnötig. Dadurch ist das jar-File nur noch 40 KB groß.

 

Ergänzung 29.06.2012: Bei Oracle gibt es einen Artikel über das MVC-Design Pattern, diesen Artikel möchte ich mir auch noch verlinken. Es gibt durchaus unterschiedliche Ansichten darüber wie man das MVC-Pattern korrekt implementiert. Falls jemand Verbesserungsvorschläge bezüglich meines Quellcodes hat nehme ich diese gerne entgegen.

 

Update 30.06.2012: Noch einen Fehler in DrawPanel beseitigt; die Verbindungslinien zwischen den Knoten müssen natürlich erst nach dem Zeichnen des Wurzel-Knotens erzeugt werden. Die Abfrage dazu geschah über Node.getY() != 0 - aber das ist die absolute y-Koordinate, richtig ist die Abfrage über Node.getLevel() != 0.

 

Update 26.07.2012: Weitere kleinere Änderungen:

 

  • Default-Belegungen für die Parameter in ProgramConfiguration habe ich entfernt, da sie eh alle über den Konstruktor gesetzt werden müssen.
  • In der DrawPanel-Klasse wird das Setzen der Zeichenflächengrößen für die Scrollbalken nun ebenfalls nicht mehr in der Methode paintComponent() vorgenommen, sondern bereits zeitlich vorher in setData(). Denn genaugenommen muss das ebenfalls erst dann neu berechnet werden wenn neue Daten übermittelt werden – und nicht mit jedem Zeichnen.
  • private-Zugriffsmodifizierer für die drawTree-Methode in der DrawPanel-Klasse explizit gesetzt.
  • Hier und da Rechtschreibfehler in den Kommentaren behoben.

 

Die Main-Klasse:

import org.abego.treelayout.Configuration.AlignmentInLevel;
import org.abego.treelayout.Configuration.Location;

/**
*
* Main-Klasse, erzeugt den Controller des MVC-Patterns.
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  25.06.2012 UTC
*/

public class Main
{
   private static int elements;
   static Controller controller;

   /**
    * Konstruktor.
    *
    * @param args Anzahl Elemente
    */

   public static void main(String[] args)
   {          
      /**
       * Anzahl Knoten aus Parameter auslesen oder selbst setzen.
       */

      if (args.length > 0)
      {
        elements = Integer.parseInt(args[0]);  
      } else {
        elements = 50;
      }
     
      /**
       *
       * Programmkonfiguration festlegen
       *
       * @param elements           Anzahl der Knoten
       * @param randomLowerLimit   Untergrenze für Zufallszahlen
       * @param randomHigherLimit  Obergrenze für Zufallszahlen
       * @param centring           Bildschirmzentrierung       *
       * @param debug              Debuggingausgabe
       * @param rootLocation       Position des Wurzelknotens
       * @param alignmentInLevel   Ausrichtung im Level
       * @param gapBetweenLevels   Lücke zwischen Ebenen
       * @param gapBetweenNodes    Lücke zwischen Knoten
       * @param nodeWidth          Breite der Knoten
       * @param nodeHeight         Höhe der Knoten
       */
                 
      ProgramConfiguration config = new ProgramConfiguration(elements,
                                                             1,
                                                             1001,
                                                             true,
                                                             false,
                                                             Location.Top,
                                                             AlignmentInLevel.TowardsRoot,
                                                             25,
                                                             5,
                                                             160,
                                                             50);
     
      controller = new Controller(config);    
   }  
}

// Ende Main-Klasse

 

Die ProgramConfiguration-Klasse:

import org.abego.treelayout.Configuration.AlignmentInLevel;
import org.abego.treelayout.Configuration.Location;

/**
*
* Globale Programm-Konfiguration.
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  28.06.2012 UTC
*/

public class ProgramConfiguration
{
   private int elements;
   private int randomLowerLimit;
   private int randomHigherLimit;
   private boolean centring;
   private boolean debug;  
   private Location rootLocation;
   private AlignmentInLevel alignmentInLevel;
   private double gapBetweenLevels;
   private double gapBetweenNodes;
   private double nodeWidth;
   private double nodeHeight;
  
   /**
    *
    * Konstruktor für die Programmkonfiguration.
    *
    * @param elements           Anzahl der Knoten
    * @param randomLowerLimit   Untergrenze für Zufallszahlen
    * @param randomHigherLimit  Obergrenze für Zufallszahlen
    * @param centring           Bildschirmzentrierung
    * @param debug              Debuggingausgabe
    * @param rootLocation       Position des Wurzelknotens
    * @param alignmentInLevel   Ausrichtung im Level
    * @param gapBetweenLevels   Lücke zwischen Ebenen
    * @param gapBetweenNodes    Lücke zwischen Knoten
    * @param nodeWidth          Breite der Knoten
    * @param nodeHeight         Höhe der Knoten
    */

   public ProgramConfiguration(int elements, int randomLowerLimit, int randomHigherLimit, boolean centring, boolean debug,
                               Location rootLocation, AlignmentInLevel alignmentInLevel,
                               double gapBetweenLevels, double gapBetweenNodes,
                               double nodeWidth, double nodeHeight)
   {
      this.elements          = elements;
      this.randomLowerLimit  = randomLowerLimit;
      this.randomHigherLimit = randomHigherLimit;
      this.centring          = centring;
      this.debug             = debug;    
      this.rootLocation      = rootLocation;
      this.alignmentInLevel  = alignmentInLevel;
      this.gapBetweenLevels  = gapBetweenLevels;
      this.gapBetweenNodes   = gapBetweenNodes;
      this.nodeWidth         = nodeWidth;
      this.nodeHeight        = nodeHeight;
   }
  
   /**
    * Beginn Getter.
    */
  
   public int getElements()
   {
      return this.elements;
   }
  
   public int getRandomLowerLimit()
   {
      return this.randomLowerLimit;
   }
  
   public int getRandomHigherLimit()
   {
      return this.randomHigherLimit;
   }
  
   public boolean getCentring()
   {
      return this.centring;
   }  
  
   public boolean getDebug()
   {
      return this.debug;
   }
  
   public Location getRootLocation()
   {
      return this.rootLocation;
   }
  
   public AlignmentInLevel getAlignmentInLevel()
   {
      return this.alignmentInLevel;
   }
  
   public double getGapBetweenLevels()
   {
      return this.gapBetweenLevels;
   }
  
   public double getGapBetweenNodes()
   {
      return this.gapBetweenNodes;
   }
  
   public double getNodeWidth()
   {
      return this.nodeWidth;
   }
  
   public double getNodeHeight()
   {
      return this.nodeHeight;
   }                  
}

// Ende ProgramConfiguration-Klasse

 

Die Controller-Klasse:

import javax.swing.SwingUtilities;

/**
*
* Controller-Klasse des MVC-Patterns, erzeugt den Model und den View.
* Zieht Daten aus dem Model und übergibt sie an den View.
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  24.06.2012 UTC
*/

public class Controller
{
   private Model model;
   private View view;
   private ProgramConfiguration config;  
  
   /**
    *
    * Konstruktor.
    *
    * @param config Programmkonfiguration
    */

   public Controller(ProgramConfiguration config)
   {    
     if (config.getElements() > java.lang.Math.abs(config.getRandomHigherLimit() - config.getRandomLowerLimit()))
        throw new IllegalArgumentException("Fehler! Es werden einmalige Zufallszahlen benötigt und die Anzahl Knoten ist größer als das Zufallszahlen-Intervall!");
       
     if ((config.getElements() * 1.2) > java.lang.Math.abs(config.getRandomHigherLimit() - config.getRandomLowerLimit()))
       System.out.println("Achtung, die Anzahl Baumknoten ist nicht mindestens 20% größer als das Zufallszahlen-Intervall, das kann die Geschwindigkeit deutlich herabsetzen!");
    
     /**
      * Übernahme der Konfiguration
      */

     this.config = config;
    
     /**
      * Erzeugung des Models.     
        (und Übergabe der Konfiguration an das Model) */
    
     this.model = new Model(config);
    
     /**
      * Erzeugung des Views und Übertragung der Daten.
      * (unter Beachtung der Single-Thread-Rule für Swing).
      */

     SwingUtilities.invokeLater(new Runnable()
     {
        public void run()
        {
           /* View erzeugen */
           View viewFrame = new View();
          
           /* View im Controller speichern */
           view = viewFrame;
          
           /* nachdem der View aufgebaut ist Daten im View aktualisieren
            * Übergabe der Daten ausdrücklich nicht im Konstruktor des Views,
            * falls Daten geändert werden.
            */

           commitData();          
          
           /* View sichtbar machen */
           viewFrame.setVisible(true);          
        }
     });     
   }
  
   /**
    *
    * weiterreichen der Daten an den View.
    *
    */

   public void commitData()
   {
      view.setData(model.getRootNode(),
                   model.getMaxX(),
                   model.getMaxXWidth(), 
                   model.getMaxY(),
                   model.getMaxYHeight(),
                   this.config.getCentring());
     
      view.repaint();
   }
}

// Ende Controller-Klasse

 

Die Model-Klasse:

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.abego.treelayout.Configuration.AlignmentInLevel;
import org.abego.treelayout.Configuration.Location;
import org.abego.treelayout.TreeLayout;
import org.abego.treelayout.util.DefaultTreeForTreeLayout;

/**
*
* Das Model erstellt den zufallsgenerierten Baum.
*
* @author $Author: Thomas Kramer
* @version $Revision:  0.2, $Date:  24.06.2012 UTC
*/

public class Model
{
   private TreeLayout                treeLayout    = null;
   private CustomTreeNode            root          = null;  
   private HashSet<Integer>          randomNumbers = null;  
   private ArrayList<CustomTreeNode> pointerList   = null;
   private Double                    maxX          = 0.0;
   private Double                    maxXWidth     = 0.0;
   private Double                    maxY          = 0.0;
   private Double                    maxYHeight    = 0.0;
  
   public Model(ProgramConfiguration config)
   {
     /*-----------------------------------------------------------------------------
      *  einmalige Zufallszahlen erzeugen
      *-----------------------------------------------------------------------------*/

     Random random = new Random();    
     randomNumbers = new HashSet<Integer>();

     int tempRandomHigherLimit = config.getRandomHigherLimit() + 1;
     /* root wird separat generiert und zählt vorerst nicht mit */
     int tempElements = config.getElements() - 1;
     while (randomNumbers.size() < tempElements)
        randomNumbers.add(random.nextInt(tempRandomHigherLimit) + config.getRandomLowerLimit());
         
     /*-----------------------------------------------------------------------------
      *  Knoten erzeugen
      *-----------------------------------------------------------------------------*/

     pointerList = new ArrayList<CustomTreeNode>();     
     root = new CustomTreeNode(null, 0);
     pointerList.add(root);   
    
     /* Konfiguration für abego */
     DefaultTreeForTreeLayout<CustomTreeNode> tree = new DefaultTreeForTreeLayout<CustomTreeNode>(root);
     CustomProvider ownProv = new CustomProvider();    
     ownProv.setWidth(config.getNodeWidth());
     ownProv.setHeight(config.getNodeHeight());
     CustomConfiguration ownConf = new CustomConfiguration();    
     ownConf.setRootLocation(config.getRootLocation());
     ownConf.setAlignmentInLevel(config.getAlignmentInLevel());
     ownConf.setGapBetweenLevels(config.getGapBetweenLevels());
     ownConf.setGapBetweenNodes(config.getGapBetweenNodes());
         
     /* jetzt restliche Knoten erzeugen */
     CustomTreeNode node;    
     CustomTreeNode parentNode;
     Iterator<Integer> it = randomNumbers.iterator();   
     while (it.hasNext())
     {     
       /* bestimme Position im Baum zufällig */
       parentNode = pointerList.get(random.nextInt(pointerList.size()));
       /* erstelle neuen Knoten */
       node = createNode(parentNode, it.next());
       tree.addChild(parentNode, node);      
       pointerList.add(node);      
     }
         
     /* Koordinaten über abego-Bibliothek berechnen */
     treeLayout = new TreeLayout (tree, ownProv, ownConf);    
     Map coordinates = treeLayout.getNodeBounds();
    
     /* debug-Ausgabe des Baumes auf der Konsole */
     if (config.getDebug())
     {
       treeLayout.dumpTree(System.out);
     }
    
     /* Koordinaten direkt in CustomTreeNode-Knoten übernehmen */    
     Set<CustomTreeNode> keys = coordinates.keySet();    
     for (CustomTreeNode singleKey : keys)
     {
       Rectangle2D data = (Rectangle2D) coordinates.get(singleKey);
       singleKey.setCoordinates(data.getX(), data.getY());
       singleKey.setDimensions(data.getWidth(), data.getHeight());
      
       /* Maxima setzen - Ermittlung der am weitesten rechts/unten stehenden Knoten.
        * diese Werte werden für die Scrollbalken und die Zentrierung beim Zeichnen benötigt.
        */

       if (data.getX() > maxX)
       {         
         maxX = data.getX(); 
         maxXWidth = data.getWidth();
       }
      
       if (data.getY() > maxY)
       {         
         maxY = data.getY(); 
         maxYHeight = data.getHeight();
       }      
     }    
   }  
  
   /**
    *
    * Erstellen eines Knotens.
    *
    * @param parent  Vater-Knoten
    * @param content Inhalt des Knotens
    * @return
    */

   private CustomTreeNode createNode(CustomTreeNode parent, int content)
   {
      return new CustomTreeNode(parent, content);     
   }
  
   /**
    *
    * holt Wurzel-Knoten.
    *
    * @return root-node
    */

   public CustomTreeNode getRootNode()
   {
      CustomTreeNode result = null;
     
      if (root != null)
      {
         result = root;
      }
     
      return result;
   }
  
   /**
    *
    * X-Maximum holen, für Scrollbar im View.
    *
    * @return X-Maximum
    */

   public Double getMaxX()
   {
      return this.maxX;
   }
  
   /**
    *
    * vom weitesten rechts stehenden Knoten die Breite holen.
    *
    * @return Breite
    */

   public Double getMaxXWidth()
   {
      return this.maxXWidth; 
   }  

   /**
    *
    * Y-Maximum holen, für Scrollbar im View.
    *
    * @return Y-Maximum
    */
  
   public Double getMaxY()
   {
      return this.maxY;
   }    
  
   /**
    *
    * vom weitesten unten stehenden Knoten die Höhe holen.
    *
    * @return Höhe
    */

   public Double getMaxYHeight()
   {
      return this.maxYHeight; 
   }
}

// Ende Model-Klasse

 

Die View-Klasse:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;

/**
* View des MVC-Patterns.
*
* @author $Author: Thomas Kramer
* @version $Revision: 1.0, $Date: 04.06.2012
*/

@SuppressWarnings("serial")
public class View extends JFrame
{  
   private Container contentPane;
   private DrawPanel paintArea;
   private JScrollPane scrollPane;
 
   /**
    * Konstruktor.
    */

   public View()
   {
      /* Text in Titelzeile setzen */
      super("Zeichnen von Bäumen beliebigen Grades mithilfe der abego TreeLayout-Bibliothek - von Thomas Kramer");
     
      // Make sure we have nice windows decorations.
      JFrame.setDefaultLookAndFeelDecorated(true);
      //dafür sorgen das das Programm beendet wird wenn man das 'X' anklickt
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);          
      /* auf Vollbildmodus setzen */
      setExtendedState(JFrame.MAXIMIZED_BOTH);
      setLayout(new BorderLayout());

      /* evtl. auch aus http://www.java-forum.org/codeschnipsel-u-projekte/61172-wahre-nutzbare-bildschirmgroesse-richtig-zentiert.html
       * nehmen
       * */
     
      contentPane = getContentPane();
      /* in DrawPanel sind die Zeichenroutinen */
      paintArea = new DrawPanel();     
      paintArea.setBackground(Color.gray);
      /* wäre an Fenstergröße von Vollbildmodus angepasst */
      //paintArea.setPreferredSize(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize());
     
      scrollPane = new JScrollPane(paintArea);           
      scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);     
      contentPane.add(scrollPane, BorderLayout.CENTER);     
   }  
  
   /* Daten weiterreichen */
   public void setData(CustomTreeNode root, Double maxX, Double maxXWidth, Double maxY, Double maxYHeight, boolean centring)
   {
      paintArea.setData(root, maxX, maxXWidth, maxY, maxYHeight, centring);     
   }
}

// Ende View-Klasse

 

Die DrawPanel-Klasse:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.util.Enumeration;
import javax.swing.JPanel;

/**
*
* eigenes Panel, dass das Zeichnen übernimmt.
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  25.06.2012 UTC
*/

public class DrawPanel extends JPanel
{
   private CustomTreeNode root;
   private Double maxX;
   private Double maxXWidth;
   private Double maxY;
   private Double maxYHeight;
   private boolean centring;  
   private int addValue;
   private boolean dataReceived;
  
   /** Code für Serialisierung <code>serialVersionUID</code> */
   private static final long serialVersionUID = 1L;
   private Graphics g = null;

   /**
    *
    * parameterloser Konstruktor.
    *
    */

   public DrawPanel()
   {                   
      root       = null;
      maxX       = 0.0;
      maxXWidth  = 0.0;
      maxY       = 0.0;
      maxYHeight = 0.0;
      centring   = false;
      addValue   = 0;
     
      dataReceived = false;
   }
  
   /**
    *
    * Übermittlung wichtiger Daten
    *
    * @param root       Wurzelknoten, wichtig für das Iterieren über den Baum.
    * @param maxX       Beginn x-Koordinate für den am weitesten rechts stehenden Knoten
    * @param maxXWidth  Breite des am weitesten rechts stehenden Knoten
    * @param maxY       Beginn y-Koordinate für den am weitesten unten stehenden Knoten
    * @param maxYHeight Höhe des am weitesten unten stehenden Knoten
    * @param centring   Bildschirmzentrierung
    */
  
   public void setData(CustomTreeNode root, Double maxX, Double maxXWidth, Double maxY, Double maxYHeight, boolean centring)
   {
      this.root = root;
      this.maxX = maxX;
      this.maxXWidth = maxXWidth;
      this.maxY = maxY;
      this.maxYHeight = maxYHeight;
      this.centring = centring;
     
      /**
       * Größe des Fensters nehmen, ist nicht zwingend identisch mit der Bildschirmauflösung.
       */

      Dimension dimension = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize();
     
      /* addValue enthält den aufzuaddierenden Wert für die Zentrierung
       * dazu nehmen wir die Breite des Fensters und subtrahieren den am weitesten rechts stehenden Knoten,
       * und dividieren das Ergebnis durch 2.
       * */

      this.addValue = 0;
      if (centring)
      {
         /**
          * Zentrierung macht nur Sinn wenn alle Knoten auf dem Bildschirm gleichzeitig zu sehen sind.
          * In anderen Fällen automatisch deaktivieren.
          */

         if (((int) Math.round(maxX) + (int) Math.round(maxXWidth)) < dimension.getWidth())
         {
           this.addValue = (dimension.width - ((int) Math.round(maxX) + (int) Math.round(maxXWidth))) / 2;
         }
      }   
     
      /*
       * Größe für Scrollbalken setzen - hierfür werden die Werte der am weitesten rechts bzw. unten stehenden
       * Knoten benötigt.
       */

      setPreferredSize(new Dimension(addValue + (int) Math.round(maxX) + (int) Math.round(maxXWidth) + 5,
                                     (int) Math.round(maxY) + (int) Math.round(maxYHeight) + 5));
     
      /**
       * alle Daten empfangen und berechnet, Zeichnen kann losgehen.
       */

      this.dataReceived = true;           
   }    
  
   /**
    * Zeichnen-Funktion
    */

   @Override  
   public void paintComponent(Graphics g) {
     super.paintComponent(g);
    
     this.g = g;
    
     /*
      * nur zeichnen wenn Daten übermittelt
      */

     if (dataReceived)
     {
        drawTree(root, 0, 0);               
     }              
   }

   /**
    *
    * Zeichnen des Baumes.
    *
    * @param node                  Knoten
    * @param parentMiddlePosition  Mittelposition des parent-Knotens
    * @param parentDownPosition    untere Position des parent-Knotens
    */

   private void drawTree(CustomTreeNode node, int parentMiddlePosition, int parentDownPosition)
   {
     if ((node == null) || (g == null))   
       return;  
        
     /**
      * zeichne Block um jeden Knoten
      */

     g.setColor(Color.black);    
     g.drawRect(addValue + (int) Math.round(node.getX()),
                (int) Math.round(node.getY()),
                (int) Math.round(node.getWidth()),
                (int) Math.round(node.getHeight()));    
    
     /**
      * zeichne Inhalt jedes Knotens ein
      */

     g.setColor(Color.white);
     g.drawString(node.getContent().toString(), addValue + (int) Math.round(node.getX()) + 5,
                                                (int) Math.round(node.getY()) + 15);
     if (node.getLevel() != 0)
     {            
        /**
         * zeichne Verbindungslinie ausgehend vom aktuellen Knoten zum Vorgänger-Knoten.
         * Beim Wurzelknoten natürlich noch nicht benötigt.
         */

        g.drawLine(addValue + (int) Math.round(node.getX()) + ((int) Math.round(node.getWidth()) /2),
                   (int) Math.round(node.getY()),
                   addValue + parentMiddlePosition,
                   parentDownPosition);        
     }
    
     /**
      * rekursiver Aufruf, damit alle Knoten gezeichnet werden.
      */

     Enumeration<CustomTreeNode> childs = node.children();
     while (childs.hasMoreElements())
     {   
       CustomTreeNode children = childs.nextElement();
       drawTree(children,
                (int) Math.round(node.getX()) + ((int) Math.round(node.getWidth()) /2),
                (int) Math.round(node.getY()) + (int) Math.round(node.getHeight())
               );
     }
   } 
}

// Ende DrawPanel-Klasse

 

Die CustomTreeNode-Klasse:

import javax.swing.tree.DefaultMutableTreeNode;

/**
*
* eigene TreeNode-Klasse, abgeleitet von DefaultMutableTreeNode
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  25.06.2012 UTC
*/

public class CustomTreeNode extends DefaultMutableTreeNode
{
   /** TODO Add comment for <code>serialVersionUID</code> */
   private static final long serialVersionUID = 1L;  
   private Integer content = 0;
   /* Koordinaten */
   private Double x = 0.0;
   private Double y = 0.0; 
   private Double width = 0.0;
   private Double height = 0.0;
  
   /**
    *
    * Konstruktor.
    *
    * @param parent Vater-Knoten
    * @param content Inhalt des Knotens
    */

   public CustomTreeNode(CustomTreeNode parent, Integer content)
   {
      super(content);
      this.content=content;
     
      if (parent != null)
      {
        parent.add(this);
      }
   }    
  
   /**
    *
    * setzen der x- und y-Koordinaten.
    *
    * @param x
    * @param y
    */

   public void setCoordinates(Double x, Double y)
   {
      this.x = x;
      this.y = y;
   }
  
   /**
    *
    * setzen der Abmessungen des Knotens. Normalerweise für jeden Knoten identisch.
    *
    * @param width
    * @param height
    */

   public void setDimensions(Double width, Double height)
   {
      this.width = width;
      this.height = height;
   }  
  
   /**
    *
    * Beginn Getter
    *
    *
    */

  
   public Double getX()
   {
      return this.x;
   }
  
   public Double getY()
   {
      return this.y;
   } 
  
   public Double getWidth()
   {
      return this.width;
   }
  
   public Double getHeight()
   {
      return this.height;
   }
  
   public Integer getContent()
   {
      return this.content;
   }  
}

// Ende CustomTreeNode-Klasse

 

Die CustomConfiguration-Klasse für das abego-Framework:

import org.abego.treelayout.Configuration;

/**
*
* Konfiguration für das abego-Framework, Teil 1.
* (auch Konfiguration in CustomProvider beachten).
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  25.06.2012 UTC
*/

public class CustomConfiguration implements Configuration<CustomTreeNode>

   /* Default-Konfiguration */
   private Location rootLocation             = Location.Top;
   private AlignmentInLevel alignmentInLevel = AlignmentInLevel.TowardsRoot;
   private double gapBetweenLevels           = 25;
   private double gapBetweenNodes            = 5;
     
   public void setRootLocation(Location rootLocation)
   {
      this.rootLocation = rootLocation;
   }
  
   /* Festlegung der Position des Wurzel-Knotens */
   public Location getRootLocation()
   {
      return this.rootLocation;
   }
  
   public void setAlignmentInLevel(AlignmentInLevel alignmentInLevel)
   {
      this.alignmentInLevel = alignmentInLevel;
   }  
    
   /* Ausrichtung */
   public AlignmentInLevel getAlignmentInLevel()
   {
      return this.alignmentInLevel;
   }
  
   public void setGapBetweenLevels(double gapBetweenLevels)
   {
      this.gapBetweenLevels = gapBetweenLevels;
   }     
  
   /* Lücken zwischen Ebenen des Baumes */
   public double getGapBetweenLevels(int arg0)
   {
      return this.gapBetweenLevels;
   }
  
   public void setGapBetweenNodes(double gapBetweenNodes)
   {
      this.gapBetweenNodes = gapBetweenNodes;
   }     
  
   /* Lücken zwischen Knoten des Baumes */
   public double getGapBetweenNodes(CustomTreeNode arg0, CustomTreeNode arg1)
   {
      return this.gapBetweenNodes;
   }
}

// Ende CustomConfiguration-Klasse

 

Die CustomProvider-Klasse für das abego-Framework:

import org.abego.treelayout.NodeExtentProvider;

/**
*
* Konfiguration für das abego-Framework, Teil 2.
* (auch Konfiguration in CustomConfiguration beachten).
*
* @author $Author:  Thomas Kramer
* @version $Revision:  0.2, $Date:  25.06.2012 UTC
*/

public class CustomProvider implements NodeExtentProvider<CustomTreeNode> {  
   private double width  = 160.0;
   private double height = 50;
  
   /**
    *
    * Breite der Knoten setzen,
    *
    * @param width Breite
    */

   public void setWidth(double width)
   {
      this.width = width;
   }
  
   /**
    * Breite auslesen.
    */

   @Override  
   public double getWidth(CustomTreeNode treeNode) {
      return this.width;
      //return treeNode.getContent().toString().length()*10;           
   }
  
   /**
    *
    * Höhe der Knoten setzen.
    *
    * @param height Höhe
    */
   public void setHeight(double height)
   {
      this.height = height;
   }  

   /**
    * Höhe auslesen.
    */

   @Override
   public double getHeight(CustomTreeNode treeNode) {
      return this.height;
   } 
}

// Ende CustomProvider-Klasse

 

Pingbacks and trackbacks (1)+

Kommentar schreiben




  Country flag
biuquote
  • Kommentar
  • Live Vorschau
Loading


Monats-Liste