Berechnung der Position von Kinderelemente von einem Elternknoten

beta20

Top Contributor
Hallo,

ich möchte gerne einen Entscheidungsbaum in einer Web-GUI erstellen. Hierfür habe ich einen Elternknoten und eben die Kinder davon...
Ich stehe nun gerade vor dem Problem, wie ich die Kinder anordnen kann, sodass es korrekt aussieht.

Ich habe dabei folgende Informationen von den Elementen:
- width
- x (Position der X-Achse)
- y
- height

Ich denke folgende Infos sind noch notwendig für die Kinder:
- definierte Breite (bspw. 60px)
- definierter Space zwischen den Kinder (bspw. 20px).

Was ich nun benötige, ist eine Funktion, die mir die Positionen (X-Achse) der Kinder ermittelt, abhängig von der Anzahl an Kindern, sodass alles schön zentriert ist und nicht mehr Elemente auf der rechten oder linken Seite sind...

Im Endeffekte, will ich die grünen generieren:


Folgenden Code habe ich bisher, aber es ist nicht korrekt - die Elemente fangen links an, passen aber nicht wirklich.
Kurze Info: am Anfang erstelle ich immer einen "Else" - Zweig, anschließend dann die "If / Else" - Zweige...

Java:
/**
     * Erstellt dem Tree
     * @param diagramElementBefore
     * @return
     */
    private DiagramElement createDiagramBranchTree(DiagramElement diagramElementBefore) {
       
        int numberOfBranch = diagramElementBefore.getAutomationElement().getDecisionComponent().getIfBranchList().size() + 1;
        int widthBranchElement = 100; // in Pixel
        int spaceBetweenElement = 20;
        int widthTotalAllBranches = ((widthBranchElement + spaceBetweenElement) * numberOfBranch);
        int widthToLeft = (widthTotalAllBranches / 2) - (diagramElementBefore.getWidth() / 2);

        // Else Tree als erstes erstellen
        diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch();
        createDiagramBranchElement(diagramElementBefore, diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch(), widthToLeft, widthBranchElement);
               
        // If Tree erstellen
        for(DecisionBranch decisionBranch : diagramElementBefore.getAutomationElement().getDecisionComponent().getIfBranchList()) {
            widthToLeft = widthToLeft + widthBranchElement;
            createDiagramBranchElement(diagramElementBefore, decisionBranch, widthToLeft, widthBranchElement);
        }
       
    }
    
    private DiagramElement createDiagramBranchElement(DiagramElement diagramElementBefore, DecisionBranch decisionBranch, int x, int widthBranchElement) {

        DiagramElement newDiagramElement = new DiagramElement();
   
        newDiagramElement.setHeight(20); // definierte Höhe
        newDiagramElement.setWidth(widthBranchElement);
        newDiagramElement.setX(diagramElementBefore.getX() - x);
        newDiagramElement.setY(diagramElementBefore.getY() + diagramElementBefore.getHeight() + 30); // definierter Abstand zum Elternknoten
               
        return newDiagramElement;
    }


Irgendwas stimmt nicht mit meiner Logik....
 
Danke... Es kommt dem ganzen sehr nahe, aber ich glaube es passt noch nicht 100%, wenn man sich den mittleren Knoten ansieht:

Java:
    private DiagramElement createDiagramBranchTree(DiagramElement diagramElementBefore) {
        
        int numberOfBranch = diagramElementBefore.getAutomationElement().getDecisionComponent().getIfBranchList().size() + 1;
        int widthBranchElement = 250; // in Pixel
        int spaceBetweenElement = 20;
        
        int width = numberOfBranch * (spaceBetweenElement + widthBranchElement) - widthBranchElement;
        int widthToLeft = (diagramElementBefore.getWidth() / 2) - (width/2);

        // Else Tree erstellen
        diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch();
        createDiagramBranchElement(diagramElementBefore, diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch(), widthToLeft, widthBranchElement);
                
        // If Tree erstellen
        for(DecisionBranch decisionBranch : diagramElementBefore.getAutomationElement().getDecisionComponent().getIfBranchList()) {
            widthToLeft = widthToLeft + widthBranchElement + spaceBetweenElement;
            createDiagramBranchElement(diagramElementBefore, decisionBranch, widthToLeft, widthBranchElement);
        }
        
        // Andere erstellen
        return diagramElementBefore;
    }

    /**
     * Create Branch for Decision Tree
     *
     * @param diagramElementBefore
     * @return
     */
    private DiagramElement createDiagramBranchElement(DiagramElement diagramElementBefore, DecisionBranch decisionBranch, int x, int widthBranchElement) {

        DiagramElement newDiagramElement = new DiagramElement();
        newDiagramElement.setHeight(20);
        newDiagramElement.setWidth(widthBranchElement);
        newDiagramElement.setX(diagramElementBefore.getX() - x);
        newDiagramElement.setY(diagramElementBefore.getY() + diagramElementBefore.getHeight() + 30);

        return newDiagramElement;
    }


 
Oops, ich habe mich verschrieben: am Ende muss das Space abgezogen werden und nicht die Breite.

Gesamtbreite = n*(definierterSpace+definierteBreite) - definierterSpace;
 
Ne, das passt noch weniger...


Ich habe diese Zeile bei mir geändert:

int width = numberOfBranch * (spaceBetweenElement + widthBranchElement) - spaceBetweenElement;
 
Ja, das ist korrekt: bei 3 Elementen hast Du 3 x die Breite der Elemente und 2 x den Abstand dazwischen. Das wäre die Gesamtbreite für 3 Elemente.


Die x-Koordinate der Mitte des Elements ganz links ist centerX-width/2, wobei centerX die x-Koordinate der Mitte des "Graphen" (bzw. des Elternelements) ist.
 
hm, das mache ich doch hier?
int widthToLeft = (diagramElementBefore.getWidth() / 2) - (width / 2);

Oder wie sollte man die " x-Koordinate der Mitte des "Graphen" (bzw. des Elternelements) ist." berechnen?
 
Wenn diagramElementBefore das Elternelement ist, und es eine getX()-Methode gibt, die die x-Koordinate der linken Seite der BoundingBox des Elements angibt, dann wäre centerX=diagramElementBefore.getX() + diagramElementBefore.getWidth()/2
 
Also das passt immer noch nicht, habe die Änderung hier vorgenommen

Code:
    private DiagramElement createDiagramBranchTree(DiagramElement diagramElementBefore) {

        int numberOfBranch = diagramElementBefore.getAutomationElement().getDecisionComponent().getIfBranchList().size()
                + 1;
        int widthBranchElement = 250; // in Pixel
        int spaceBetweenElement = 20;

        int width = numberOfBranch * (spaceBetweenElement + widthBranchElement) - spaceBetweenElement;
        int centerXParent = diagramElementBefore.getX() + diagramElementBefore.getWidth()/2;
        int widthToLeft = centerXParent - (width / 2);

    // Else Tree erstellen
        diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch();
        createDiagramBranchElement(diagramElementBefore,
                diagramElementBefore.getAutomationElement().getDecisionComponent().getElseBranch(), widthToLeft,
                widthBranchElement);

        // If Tree erstellen
        for (DecisionBranch decisionBranch : diagramElementBefore.getAutomationElement().getDecisionComponent()
                .getIfBranchList()) {
            widthToLeft = widthToLeft + widthBranchElement + spaceBetweenElement;
            createDiagramBranchElement(diagramElementBefore, decisionBranch, widthToLeft, widthBranchElement);
        }

    }

    /**
     * Create Branch for Decision Tree
     *
     * @param diagramElementBefore
     * @return
     */
    private DiagramElement createDiagramBranchElement(DiagramElement diagramElementBefore,
            DecisionBranch decisionBranch, int x, int widthBranchElement) {

        DiagramElement newDiagramElement = new DiagramElement();
        newDiagramElement.setDiagramElementBefore(diagramElementBefore);

        newDiagramElement.setHeight(20);
        newDiagramElement.setWidth(widthBranchElement);
        newDiagramElement.setX(x);
        newDiagramElement.setY(diagramElementBefore.getY() + diagramElementBefore.getHeight() + 30);

        return newDiagramElement;
    }
 
widthToLeft deutet auf eine andere Bedeutung hin. Es geht um die absolute x-Koordinate. Das kann man ausrechnen: sagen wir mal das Elternelement befindet sich bei x=400 (linke Kante) und jedes Element hat eine Breite von 100. Dann ist centerX = 400 + 100/2 = 450.

Jetzt hast Du drei Kindelemente mit einem Abstand von 20. Macht eine Gesamtbreite von 3*(100+20)-20 = 340.

Dann fängt die Mitte(!) des linken Kindelements bei x = 450 - 340/2, also bei 280 an. Das zweite Element befindet sich bei 280+120 = 400 (== centerX) und das dritte bei 400+120=520. Jeweils Mitte!
 
also, ich habe das jetzt mal mit den gleichen Zahlen durchgespielt....Das passt und sieht korrekt aus...
Wenn die Kinderelemente nun aber eine Breite von 150 bekommen (int widthBranchElement = 150😉 dann passt es nicht mehr...
Das zweite Element sieht nicht korrekt aus...
Der Elternknoten hat immer noch eine Breite von 400...

Passt das hier in der Schleife?
widthToLeft = widthToLeft + widthBranchElement + spaceBetweenElement;

Java:
        // If Tree erstellen
        for (DecisionBranch decisionBranch : diagramElementBefore.getAutomationElement().getDecisionComponent()
                .getIfBranchList()) {
            widthToLeft = widthToLeft + widthBranchElement + spaceBetweenElement;
            createDiagramBranchElement(diagramElementBefore, decisionBranch, widthToLeft, widthBranchElement);
        }

Und in der createDiagramBranchElement() - Methode setze ich eben den Wert von Paramater

Java:
private DiagramElement createDiagramBranchElement(DiagramElement diagramElementBefore,
            DecisionBranch decisionBranch, int x, int widthBranchElement) {
...
newDiagramElement.setX(x);
 
Wenn die Kinderelemente nun aber eine Breite von 150 bekommen (int widthBranchElement = 150😉 dann passt es nicht mehr...
Das spielt keine Rolle, da Du die Kindelemente komplett unter dem Parent zentrierst.

Was macht denn createDiagramBranchElement? Setzt das das Element oder nur den Strich? Wenn das Element, dann musst Du natürlich berücksichtigen, dass das x die Mitte ist.
 
Hab mal schnell was zusammengeschustert.
Java:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;

import javax.swing.*;

import java.util.*;
import java.util.function.Consumer;


public class Test {
    static class Node {
        private Rectangle bbox;
        private List<Node> children = new ArrayList<>();

        public Node(Rectangle bbox) { this.bbox = bbox; }
        public void add(Node child) { children.add(child); }
        public int getX() { return bbox.x; }
        public int getY() { return bbox.y; }
        public int getWidth() { return bbox.width; }
        public int getHeight() { return bbox.height; }

        public void draw(Graphics2D g) {
            g.draw(bbox);
        }

        public void forEachChild(Consumer<Node> c) {
            children.forEach(c);
        }
    }

    static class Canvas extends JComponent {
        Node root;

        public void setRoot(Node root) {
            this.root = root;
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            paint(g2, root);
        }

        private void paint(Graphics2D g, Node node) {
            if (node != null) {
                node.draw(g);
                node.forEachChild(child -> paint(g, child));
            }
        }
    }

    private Canvas canvas;

    public Test() {
        Rectangle r = new Rectangle(300, 10, 600, 20);
        Node root = new Node(r);
        addChildren(root, 3, 150, 50, 10, 50);
        canvas = new Canvas();
        canvas.setRoot(root);
    }

    private void addChildren(Node node, int count, int width, int height, int hGap, int vGap) {
        int totalWidth = count*(width + hGap) - hGap;
        int centerX = node.getX() + node.getWidth() / 2;
        int cx = centerX - totalWidth / 2;
        int cy = node.getY() + node.getHeight() + vGap;        
        for (int i = 0; i < count; i++) {
            Rectangle rect = new Rectangle(cx, cy, width, height);
            node.add(new Node(rect));
            cx += width + hGap;
        }
    }


    public void run() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(canvas);
        frame.setSize(1000, 600);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new Test().run());
    }
}
 
Also irgendwas passt einfach nicht...
Hier mal von meinem Debugger:

int numberOfBranch = 2
int widthBranchElement = 150; // in Pixel
int spaceBetweenElement = 20;

totalWidth = 2 * ( 150 + 20) - 20;
-> 320

centerX = 400 + 400 / 2;
-> 600

cx = 600 - 320/2;
-> 440

Heißt das erste Kindelement ist bei:
x = 440

Das zweite bei:
x = 610;
 

Zurück
Oben