Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   konzeptionelle Gedanken optische Darstellung Stammbaum (https://www.delphipraxis.net/186117-konzeptionelle-gedanken-optische-darstellung-stammbaum.html)

hansklok 5. Aug 2015 02:34

konzeptionelle Gedanken optische Darstellung Stammbaum
 
Ein herzliches Hallo an alle,

Ich würde gern einen Stammbaum grafisch und interaktiv ausgeben. Jedes Element soll anklickbar sein.

Ich dachte mir, dass ich dazu zwei Klassen brauche: TFamilyTree (abgeleitet von TCanvas) und TTreeItem.

TFamilyTree hat folgende Eigenschaften, da ich bis zu 4 Generationen (optional) abbilden möchte:
  • Proband: TTreeItem
  • Children: TObjectList (TTreeItem)
  • Father: TTreeItem
  • Mother: TTreeItem
  • GrandPaA: TTreeItem
  • GrandPaB: TTreeItem
  • GrandMaA: TTreeItem
  • GrandMaB: TreeItem
  • Offset: TBitmap
Später könnten auch noch eine Liste mit den Partnern (Spouses) und Geschwistern (Siblings) hinzugefügt werden. Es geht aber erstmal ums Prinzip.

TTreeItem hat folgende Eigenschaften:
  • ID: Integer
  • Offset: TBitmap
  • Width: Integer = 200
  • Height: Integer = 75

Nun soll TFamilyTree beispielsweise nie größer als 800x600 Pixel sein. Die Größe des Offsets soll sich dynamisch generieren. Dazu muss folgendes gecheckt werden:
  • gibt es einen Probanden?
  • hat der Proband Eltern? Einen Teil, oder beide (Breite würde sich verdoppeln!)
  • hat der Proband Kinder? (Breite generiert sich dynamisch aus der Anzahl der Kinder + 20 Pixeln Abstand dazwischen)
  • so in etwa, mit den Großeltern wäre es dasselbe, wobei in der 1. Generation da ja maximal 4 TTreeItems nebeneinander stünden

Hat ein Proband nun sehr viele Kinder, wir das Offset schnell breiter, als 800 Pixel.

Mich würde nun interessieren, wie man möglichst sinnvoll und WIE die Breite und Höhe des Offsets vorab berechnen kann und wie man bei einer festen Breite und Höhe von TFamilyTree diesen via Scrollbars scrollen kann, aber so, dass man die Objekte immer noch richtig anklicken kann (also eine Koordinatenfrage!).

Hat jemand etwas ähnliches schon einmal versucht? Könnt ihr mir folgen? Habe ich mir die richtigen Gedanken gemacht?

Danke Euch im Voraus.

idefix2 5. Aug 2015 06:42

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Meines Erachtens ist der Ansatz grundlegend falsch. Du vermengst hier die Datenhaltung und die Bildschirmdarstellung.

Die Daten selbst sollten völlig I/O-unabhängig gespeichert werden, und für die Darstellung eines Knotens am Bildschirm definierst du ein TFrame.
Die Frames erzeugst du dynamisch nach Bedarf. Anklickbar sind die ganz von selbst, wenn sie einen scrollbaren Container als Parent haben (die Form selbst oder eine Scrollbox auf der Form), da brauchst du nichts selbst zu programmieren.

Übrigens: Was du mit
Delphi-Quellcode:
offset: TBitmap
vorhast, versteh ich überhaupt nicht.

bcvs 5. Aug 2015 06:59

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von hansklok (Beitrag 1311041)
  • Proband: TTreeItem
  • Children: TObjectList (TTreeItem)
  • Father: TTreeItem
  • Mother: TTreeItem
  • GrandPaA: TTreeItem
  • GrandPaB: TTreeItem
  • GrandMaA: TTreeItem
  • GrandMaB: TreeItem
  • Offset: TBitmap

Die Großeltern kannst du dir sparen. GrandPaA ist doch einfach Father.Father

TiGü 5. Aug 2015 08:46

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Ich denke auch, dass du da den falschen Ansatz wählst.
Wenn überhaupt, wird ein Teilbereiches des Baumes auf einen Canvas gemalt (ggf. über eine dritte Klasse), aber sollte sich nicht selber zeichnen, indem es von Canvas abgeleitet ist.
Warum eigentlich diese Beschränkung auf 800 x 600 Pixel in der Ausgabe?

Um mir Ideen zu holen, würde ich immer bei den großen Playern schauen, wie die bestimmte Sachen lösen.
Kennst du myheritage.com?
Deren Clientsoftware ist zwar etwas altbacken, aber die Darstellung eines Stammbaumes in der Web-Darstellung finde ich sehr gelungen.

hansklok 5. Aug 2015 12:36

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von idefix2 (Beitrag 1311053)
Die Daten selbst sollten völlig I/O-unabhängig gespeichert werden, und für die Darstellung eines Knotens am Bildschirm definierst du ein TFrame.

...die Daten werden aus einer SQLite-Datenbank geholt, deswegen hat TTreeItem eine ID-Property!
Zitat:

Zitat von TiGü (Beitrag 1311083)
Ich denke auch, dass du da den falschen Ansatz wählst.
Wenn überhaupt, wird ein Teilbereiches des Baumes auf einen Canvas gemalt (ggf. über eine dritte Klasse), aber sollte sich nicht selber zeichnen, indem es von Canvas abgeleitet ist.
Warum eigentlich diese Beschränkung auf 800 x 600 Pixel in der Ausgabe?

Um mir Ideen zu holen, würde ich immer bei den großen Playern schauen, wie die bestimmte Sachen lösen.
Kennst du myheritage.com?
Deren Clientsoftware ist zwar etwas altbacken, aber die Darstellung eines Stammbaumes in der Web-Darstellung finde ich sehr gelungen.

Genau, es geht ja nur um die Darstellung eines Teilbereiches. Gern würde ich alle möglichen Generationen iterativ darstellen, nur handelt es sich bei meinen Vorstellungen um eine Art "Sanduhrdiagramm". Die Darstellung müsste ja immer ausgehend vom Probanden generiert werden. Alles oberhalb hat immer einen Vater und eine Mutter, unterhalb haben alle Einträge 0..* Kinder.
Natürlich kenne ich MyHeritage :wink:
Von welcher dritten Klasse sprichst Du?
Die Beschränkung war ein Beispiel. Es get nur darum, dass das ganze Scrollbar sein soll.

Ich bin für Input weiterhin sehr dankbar, weil es zum Beispiel auch darum gehen wird, wie man dann die einzelnen Verbindungsknoten unter den Einträgen zeichnet etc. Äußerst komplex. :(

Namenloser 5. Aug 2015 13:18

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von hansklok (Beitrag 1311115)
Genau, es geht ja nur um die Darstellung eines Teilbereiches. Gern würde ich alle möglichen Generationen iterativ darstellen, nur handelt es sich bei meinen Vorstellungen um eine Art "Sanduhrdiagramm". Die Darstellung müsste ja immer ausgehend vom Probanden generiert werden. Alles oberhalb hat immer einen Vater und eine Mutter, unterhalb haben alle Einträge 0..* Kinder.

Und wo ist das Problem?

Ich würde TTreeItem (oder besser TPerson) so definieren:

Delphi-Quellcode:
TPerson = class
  Mother: TPerson;
  Father: TPerson;
  Name: String;
end;
Und mehr brauchst du gar nicht. Die Kinder musst du gar nicht speichern, weil sie sich implizit ergeben: Das sind die Personen, wo entweder Father oder Mother das jeweilige Subjekt sind.

Dann noch TFamilyTree in etwa so:
Delphi-Quellcode:
TFamilyTree = class
  Family: TList<TPerson>;
  function TreeToLayers(Subject: TPerson; Depth: Integer): TList<TLayer>;
end;
Um den Baum (eigentlich ein Graph) zu zeichnen, musst du über den Graphen iterieren und ihn in „Schichten“ (= Generationen) anordnen.
Delphi-Quellcode:
type
  TLayer = TList<TPerson>;

function TFamilyTree.TreeToLayers(Subject: TPerson; Depth: Integer): TList<TLayer>;
  procedure AssignPerson(Person: TPerson; Layers: TList<TLayer>; LayerIndex: Integer);
  begin
    if LayerIndex >= Layers.Count then exit;
    Layers[CurrentLayerIndex].Add(Person);
    AssignPerson(Person.Mother, Layers, LayerIndex+1);
    AssignPerson(Person.Father, Layers, LayerIndex+1);
  end;
var
  i: Integer;
begin
  Result := TList<TLayer>.Create;
  for i := 0 to Depth-1 do
    Result.Add(TLayer.Create);
  AssignPerson(Subject, 0);
end;
In dem Beispielcode werden nur die Ahnen berücksichtigt, aber es ist nicht schwer es so zu erweitern, dass auch Kinder, Geschwichter, Cousins etc. in die jeweiligen Schichten eingefügt werden. Man muss dann nur aufpassen, dass man einen Knoten nicht mehrfach hinzufügt.

Die Schichten kannst du dann problemlos in einer Richtung zeichnen.

Bentissimo 5. Aug 2015 13:20

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
In einem meiner kommerziellen Programme habe ich etwas vergleichbares für Schach, speziell Eröffnungsvarianten, gebaut.

http://project-it.de/horst/images/pr...antenbaum2.gif

Dafür habe ich das TdxOrgChart von DexExpress verwendet. Diese Komponente bietet eigentlich alles, was Du für Deine Anforderung benötigst und natürlich gibt es auch eine datenbasierte Variante. Nachteil ist aber natürlich der nicht unerhebliche Kostenfaktor, da es das TdxOrgChart nur als Teil der VCL Subscription gibt.

idefix2 5. Aug 2015 13:34

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von hansklok (Beitrag 1311115)
Es get nur darum, dass das ganze Scrollbar sein soll.

Worin besteht das Problem?
Für die Darstellung der einzelnen Knoten nimmst du geeignete visuelle Komponenten. Die Komponenten wissen auch, wenn sie gescrollt werden, wann sie angeklickt werden, und du kannst in den entsprechenden Ereignisroutinen (onclick etc.) darauf reagieren. Um das Scrollen brauchst du dich nicht zu kümmern.

Die Bildschirmdarstellung sollte ausser der Bildschirmdarstellung nichts tun, insbesonders keine Strukturdaten enthalten. Auf Grund der Strukturdaten, die du in eigenen Klassen bereit hältst, die mit der Darstellung am Schirm gar nichts zu tun haben, erstellst du dann die visuellen Komponenten.

Also nicht: Visuelle Komponente enthält Referenz auf Datenbankwert
sondern - Aus der Datenbank erzeuge Baum bzw. Netzstruktur, und dann erzeuge für jeden Netzknoten adäquate visuelle Controls mit geeigneten Namen. Wenn ein Control angeklickt wurde, weisst du auf Grund des Parameters "Sender" in allen Ereignisbehandlungsroutinen, wer angeklickt wurde.

Zitat:

Zitat von Namenloser (Beitrag 1311117)
Und mehr brauchst du gar nicht. Die Kinder musst du gar nicht speichern, weil sie sich implizit ergeben: Das sind die Personen, wo entweder Father oder Mother das jeweilige Subjekt sind.

Es kann schon ganz praktisch sein, wenn von jedem Knoten weg in alle Richtungen verkettet wird:
Person -> Vater
Person -> Mutter
Person -> nächstälteres Geschwister
Person -> nächstjüngeres Geschwister
Person -> jüngstes (oder ältestes) Kind

edit
Wobei das mit den Geschwistern komplizierter ist, es gibt ja auch Halbgeschwister und dergleichen. Da ist es vielleicht wirklich am besten, wie Namenloser schreibt, nur die Eltern zu speichern und für alles andere die Liste aller Personen durchzugehen. So viele Leute können das nicht sein, dass da Performance Probleme drohen würden.

Namenloser 5. Aug 2015 13:39

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von idefix2 (Beitrag 1311120)
Zitat:

Zitat von Namenloser (Beitrag 1311117)
Und mehr brauchst du gar nicht. Die Kinder musst du gar nicht speichern, weil sie sich implizit ergeben: Das sind die Personen, wo entweder Father oder Mother das jeweilige Subjekt sind.

Es kann schon ganz praktisch sein, wenn von jedem Knoten weg in alle Richtungen verkettet wird:
Person -> Vater
Person -> Mutter
Person -> nächstälteres Geschwister
Person -> nächstjüngeres Geschwister
Person -> jüngstes (oder ältestes) Kind

Redundanz ist in meiner Erfahrung immer schlecht und gilt es so gut es geht zu vermeiden. Man kann sich Methoden bauen, die einem die entsprechenden Familienmitglieder zurückgeben. Aber man sollte sie nicht explizit speichern.

mkinzler 5. Aug 2015 13:44

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
In einer modernen Welt könnte man auch Father/Mother durch SPouse1/Spouse2 ersetzten oder noch besser die Eltereigenschaft getrennt setzen.

hansklok 5. Aug 2015 14:04

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Liste der Anhänge anzeigen (Anzahl: 1)
So, um eines Vorweg zu nehmen, danke für Eure bisherigen Antworten.
Ich arbeite mich grade erst in die Materie ein. ANFÄNGER!!!! Ich bin wieder in die Delhpipraxis-Community zurückgekehrt, da ich früher hier sehr gute Erfahrungen gemacht habe. Ich entwickle unter OS X mit Xojo. An sich gibt es da sehr!!! viele Parallelen zu Delphi. Deswegen versuche ich es hier im Forum.

Von vorne: Die Beispieldatenbank hat folgende Tabellen:
  1. Personen
    • Name
    • Geburtsdatum
  2. Familien
    • MutterID
    • VaterID
  3. Kinder
    • FamilienID
So, folgendes Szenario:
  • Max Mustermann solle der Proband sein
  • Seine Eltern sind Hans Mustermann und Elke Musterfrau
  • Max ist das Zweitgeborene von 4 Kindern der Familie
  • Max war zweimal verheiratet. Mit Maria Streifenmuster und Petra Kairomuster
  • aus der ersten Ehe gingen 1 Kind (Ludwig) und aus der zweiten keines hervor.
An sich eine einfache Familienkonstellation. Via SQL-Abfrage wird nun erstmal der Proband innerhalb der Geschwister eingeordnet (Geburtsdatum), diese ordnen sich dann auf der gleichen Ebene links und rechts um ihn. Da er aber auch zwei Frauen hatte, werden diese rechts von ihm vor seinen 3. und 4. Geschwisterteil platziert. Unter die Frauen dann jeweils die Kinder. Ich finde, das ist bei einem solchen Beispiel schon ein sehr komplexes Ding.
Was mich also beschäftigt ist zum einen, wie baue ich um meinen Probanden, der stets zentriert im Fokus der grafischen Ausgabe sein soll, alles andere um ihn herum? Deswegen auch die Frage mit den Scrollbars!

Vielleicht besteht die Möglichkeit, dass wir hier Step by Step, Tutorialartig so etwas gemeinsam definieren können, das fände ich klasse ;)

So würde die grafische Ausgabe zum Beispiel mal ausschauen:

TiGü 5. Aug 2015 15:37

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Was hast du denn bisher?
Zeig mal Quelltext!
Der Ansatz von Namenloser mit den Layern ist eigentlich ganz knuffig.

hansklok 5. Aug 2015 16:34

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Meine Gedanken (der Einfachheit halber mal eingedeutscht):
Delphi-Quellcode:
// es gibt mehrere Möglichkeiten der Darstellung, die mir vorschweben
//
// 1. der Proband ist eine Partnerschaft
// 2. der Proband ist eine einzelne Person

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// zu 1. (Nachteil hier, es werden keine weiteren Partner & Geschwister angezeigt)
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

TPerson = class
  const
    Width: 200;
    Height: 75;
  public
    Name: String;
    Vater: TPerson;
    Mutter: TPerson;
    procedure Draw(Canvas, TCanvas; x, y: Integer);
end;

TPartnerschaft = class // im Falle 1. wäre Partnerschaft dann der "Proband"
  public
    PartnerA: TPerson; // Mann oder Frau bei gleichgeschlechtlichen Partnerschaften
    PartnerB: TPerson; // Frau "
    Kinder: TList<TPerson>; // Reihenfolge der Kinder kann schon bei der SQL-Abfrage erfolgen
    procedure Draw(Canvas: TCanvas);
end;

procedure TPerson.Draw(Canvas: TCanvas; x, y: Integer);
begin
  Canvas.DrawRectangle(x, y, Width, Height);
  // später kann hier noch der Name der Person ausgegeben werden

  if Vater <> nil and Mother <> nil then begin
    // Eltern zeichnen dementsprechend versetzt über dem Probanden (x, y müssen angepasst werden)
  end else
  if Vater <> nil and Mother = nil then begin
    // nur Vater zeichnen
  end else
  if Vater = nil and Mother <> Nil then begin
    // nur Mutter zeichnen
  end;
end;

procedure TPartnerschaft.Draw(Canvas: TCanvas);
begin
  // hier wird es nun kompliziert, da zunächst alle Kinder gezählt werden müssen, um den Probanden oberhalb zu zentrieren, ABER
  // sollte die Breite der Elterngenerationen (Eltern, Großeltern) breiter sein, als die der Kinder, so orientiert sich die Ausrichtung an diesen

  if PartnerA <> nil and PartnerB <> nil then begin
    // iterativ alle Elterngenerationen zeichnen, aber wie finde ich heraus, wieviele das sind, also durch Iteration?
    PartnerA.Draw(Canvas, x, y); // ???????
  end else
  if PartnerA <> nil and PartnerB = nil then begin
    // nur PartnerA zeichnen
  end else
  if PartnerA = nil and PartnerB <> Nil then begin
    // nur PartnerB zeichnen
  end;

  for i as Integer = 0 to Kinder.Count do begin
    // auch hier müssen Koordinaten ausfindig gemacht werden, damit die Kinder unter dem Probanden und jeweils nebeneinander gezeichnet werden können
  end;
end;

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// zu 2. (Partner & Geschwister können angezeigt werden)
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// um diesen soll sich dann alles drehen, also der Stammbaum aufbauen
TProband = class(TPerson)
  public
    Geschwister: TList<TPerson>;
    Partner: TList<TPerson>;
    Kinder: TList<TPerson>;
    // Draw-Methoden müssten analog zu oben sein
end;

TStammbaum = class(TCanvas)
  puclic
    Proband: TProband; // oder eben TPartnerschaft
    // Draw-Methoden müssten analog zu oben sein
end;
Ist das ein prinzipiell falscher Ansatz?

Sir Rufo 5. Aug 2015 17:49

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Wenn ich mir das Bild so anschaue, dann kann man die Daten zu dieser Anzeige hiermit abbilden (die Bilder habe ich jetzt mal weggelassen):
Delphi-Quellcode:
unit Unit2;

interface

uses
  System.Generics.Collections;

type
  TLocationDateTime = record
    Location: string;
    Timestamp: TDateTime;
  end;

  TPersonViewModel = class
  private
    FID: TGUID;
    FLastName: string;
    FFirstName: string;
    FBorn: TLocationDateTime;
    FDied: TLocationDateTime;
  public
    property ID: TGUID read FID write FID;
    property LastName: string read FLastName;
    property FirstName: string read FFirstName;
    property Born: TLocationDateTime read FBorn;
    property Died: TLocationDateTime read FDied;
  end;

  TParentViewModel = class( TPersonViewModel )
  private
    FFather: TPersonViewModel;
    FMother: TPersonViewModel;
  public
    property Father: TPersonViewModel read FFather;
    property Mother: TPersonViewModel read FMother;
  end;

  TChildViewModel = class( TPersonViewModel )
  private
    FEngagedTo: TList<TPersonViewModel>;
  public
    property EngagedTo: TList<TPersonViewModel> read FEngagedTo;
  end;

  TRelationViewModel = class( TPersonViewModel )
  private
    FChildren: TList<TChildViewModel>;
  public
    property Children: TList<TChildViewModel> read FChildren;
  end;

  TSelectedPersonViewModel = class( TPersonViewModel )
  private
    FFather: TParentViewModel;
    FMother: TParentViewModel;
    FRelations: TList<TRelationViewModel>;
    FSiblings: TList<TChildViewModel>;
  public
    property Father: TParentViewModel read FFather;
    property Mother: TParentViewModel read FMother;
    property Siblings: TList<TChildViewModel> read FSiblings;
    property Relations: TList<TRelationViewModel> read FRelations;
  end;

implementation

end.

hansklok 5. Aug 2015 18:06

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von Sir Rufo (Beitrag 1311148)
Wenn ich mir das Bild so anschaue, dann kann man die Daten zu dieser Anzeige hiermit abbilden (die Bilder habe ich jetzt mal weggelassen):
Delphi-Quellcode:
unit Unit2;

interface

uses
  System.Generics.Collections;

type
  TLocationDateTime = record
    Location: string;
    Timestamp: TDateTime;
  end;

  TPersonViewModel = class
  private
    FID: TGUID;
    FLastName: string;
    FFirstName: string;
    FBorn: TLocationDateTime;
    FDied: TLocationDateTime;
  public
    property ID: TGUID read FID write FID;
    property LastName: string read FLastName;
    property FirstName: string read FFirstName;
    property Born: TLocationDateTime read FBorn;
    property Died: TLocationDateTime read FDied;
  end;

  TParentViewModel = class( TPersonViewModel )
  private
    FFather: TPersonViewModel;
    FMother: TPersonViewModel;
  public
    property Father: TPersonViewModel read FFather;
    property Mother: TPersonViewModel read FMother;
  end;

  TChildViewModel = class( TPersonViewModel )
  private
    FEngagedTo: TList<TPersonViewModel>;
  public
    property EngagedTo: TList<TPersonViewModel> read FEngagedTo;
  end;

  TRelationViewModel = class( TPersonViewModel )
  private
    FChildren: TList<TChildViewModel>;
  public
    property Children: TList<TChildViewModel> read FChildren;
  end;

  TSelectedPersonViewModel = class( TPersonViewModel )
  private
    FFather: TParentViewModel;
    FMother: TParentViewModel;
    FRelations: TList<TRelationViewModel>;
    FSiblings: TList<TChildViewModel>;
  public
    property Father: TParentViewModel read FFather;
    property Mother: TParentViewModel read FMother;
    property Siblings: TList<TChildViewModel> read FSiblings;
    property Relations: TList<TRelationViewModel> read FRelations;
  end;

implementation

end.

Genau Rufo, dein Entwurf ist sehr gut nachvollziehbar und Bilder genau das ab, worum es mir ging. Dankeschön.
Bleibt nach wie vor zu klären, wie man den Baum "günstig" grafisch in einer Paintbox ausgibt, sodass er sich auch scrollen lässt (Zoom wäre natürlich super). ;) und, wie lässt es sich umsetzen, bei so einem verschachtelten Modell, dass jeder Eintrag sich auf der Zeichenfläche anklicken lässt?

P.S. Was macht EngagedTo?

Sir Rufo 5. Aug 2015 18:14

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Das war nur eine begriffliche Entgleisung und sollte MarriedWith, oder welche Art der Beziehung du dort abbilden möchtest.

Siehe Geschwister und Kinder auf dem Bild, da sind noch 0..2 Personen darunter.

Sir Rufo 5. Aug 2015 18:43

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zum Zeichnen:

Die Paintbox in eine ScrollBox packen und schon scrollt das, wenn die ScrollBox kleiner als die Paintbox wird.

Die Größe der Paintbox lässt sich einfach berechnen, denn jeder Eintrag hat eine feste Größe und somit ist es fast ein simples Multiplizieren und Addieren.

hansklok 13. Mai 2018 14:11

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Hallo lieber Community,

ich möchte dieses Thema erneut aufgreifen, da ich keinen Schritt weitergekommen bin. Wie gesagt, ich bin Hobby-Entwickler und kann daher oft nicht jeden Gedanken problemlos nachvollziehen. Ich sage das nur, damit die Antworten gern „normalofreundlich“ ausfallen, ohne Fachbegriffe bzw. diese erklärt.

Zitat:

Zitat von Namenloser (Beitrag 1311117)
Ich würde TTreeItem (oder besser TPerson) so definieren:

Delphi-Quellcode:
TPerson = class
  Mother: TPerson;
  Father: TPerson;
  Name: String;
end;
Und mehr brauchst du gar nicht. Die Kinder musst du gar nicht speichern, weil sie sich implizit ergeben: Das sind die Personen, wo entweder Father oder Mother das jeweilige Subjekt sind.

Ist die Argumentation richtig? Weil es wären doch dann immer Mutter und Vater das Subjekt, oder sind mit dem oder Halbgeschwister gemeint?

Zitat:

Zitat von Namenloser (Beitrag 1311117)
Dann noch TFamilyTree in etwa so:
Delphi-Quellcode:
TFamilyTree = class
  Family: TList<TPerson>;
  function TreeToLayers(Subject: TPerson; Depth: Integer): TList<TLayer>;
end;

Ist TFamilyTree jetzt jeweils ein TLayer oder der vollständige Baum nachher? Was wird jetzt alles in der Family Liste dargestellt? Nur immer ein TLayer, also Ausgangsperson und deren Eltern, sowie die Geschwister der Ausgangsperson? Die Kinder der Ausgangsperson und deren Geschwister müssten ja dann wieder in einen neuen TLayer, oder? Vielleicht kann jemand dazu bitte eine kleine Zeichnung machen, um zu veranschaulichen, was in welcher Klasse jeweils ausgegeben wird. Das sollte dem Verständnis meinerseits helfen :)

Was ist in dem Fall, dass eine Ausgangsperson bzw. deren Geschwister mehrere Partner hatte? Die müssen ja dann auch in dem Layer dargestellt werden und die Kinder der einzelnen Beziehungen darunter.

Zitat:

Zitat von Namenloser (Beitrag 1311117)
Um den Baum (eigentlich ein Graph) zu zeichnen, musst du über den Graphen iterieren und ihn in „Schichten“ (= Generationen) anordnen.
Delphi-Quellcode:
type
  TLayer = TList<TPerson>;

function TFamilyTree.TreeToLayers(Subject: TPerson; Depth: Integer): TList<TLayer>;
  procedure AssignPerson(Person: TPerson; Layers: TList<TLayer>; LayerIndex: Integer);
  begin
    if LayerIndex >= Layers.Count then exit;
    Layers[CurrentLayerIndex].Add(Person);
    AssignPerson(Person.Mother, Layers, LayerIndex+1);
    AssignPerson(Person.Father, Layers, LayerIndex+1);
  end;
var
  i: Integer;
begin
  Result := TList<TLayer>.Create;
  for i := 0 to Depth-1 do
    Result.Add(TLayer.Create);
  AssignPerson(Subject, 0);
end;
In dem Beispielcode werden nur die Ahnen berücksichtigt, aber es ist nicht schwer es so zu erweitern, dass auch Kinder, Geschwichter, Cousins etc. in die jeweiligen Schichten eingefügt werden. Man muss dann nur aufpassen, dass man einen Knoten nicht mehrfach hinzufügt.

Die Schichten kannst du dann problemlos in einer Richtung zeichnen.

Jetzt wird es kompliziert. Der eigentliche Baum ist ist also dann eine Liste von TLayer? Wie kann ich das integrieren, damit die richtigen Kinder jeweils unter dem richtigen Elternpaar abgebildet werden?

Zitat:

Zitat von Sir Rufo (Beitrag 1311148)
Wenn ich mir das Bild so anschaue, dann kann man die Daten zu dieser Anzeige hiermit abbilden (die Bilder habe ich jetzt mal weggelassen):

Vermutlich ist der Vorschlag von Namensloser, den ich wie gesagt noch nicht ganz durchblicke günstiger, weil er nicht nur auf vier Generationen begrenzt ist. Oder kann man Ihre Struktur dynamisch erweitern?

Ich würde mich sehr freuen, wenn wir das Step-by-Step behandeln könnten, damit ich es verstehe und dann auch zu dem, juhu, wie ich Mathematik geliebt habe :( , Berechnungen der Koordinaten kommen.

Ich danke sehr im Voraus.

p80286 13. Mai 2018 22:20

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Also erst einmal hierzu:
Zitat:

Zitat von Namenloser (Beitrag 1311117)
Ich würde TTreeItem (oder besser TPerson) so definieren:

Delphi-Quellcode:
TPerson = class
  Mother: TPerson;
  Father: TPerson;
  Name: String;
end;
Und mehr brauchst du gar nicht. Die Kinder musst du gar nicht speichern, weil sie sich implizit ergeben: Das sind die Personen, wo entweder Father oder Mother das jeweilige Subjekt sind.

Wenn Du Dich auf rein biologische Vorgänge beschränkst (keine Adoption o.ä.) ist diese Struktur ausreichend frei nach "Mater semper certa est " ist die "Mutter" in den meisten Fällen bekannt. Wobei es auch hierbei Ausnahmen geben kann (z.B. Findelkinder).D.h in den meisten Fällen ist Mother/Father nicht Nil. Geschwister einer Person sind somit alle Personen deren Father oder Mother mit Father oder Mother der Person übereinstimmen.
Willst Du hingegen die Möglichkeiten die sich juristisch ergeben können, wirst Du nicht umhin kommen deine Daten in einer solchen (oder ähnlichen) Struktur abzulegen:
Delphi-Quellcode:
TRelPerson =class
  relatedPersID : tID;
  reltype : Treltype;
end;


TPerson = class
  relpersonslist: tlistofRelPerson;
  Name: String;
end;
Gruß
K-H

hansklok 13. Mai 2018 22:39

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von p80286 (Beitrag 1401989)
Wenn Du Dich auf rein biologische Vorgänge beschränkst (keine Adoption o.ä.) ist diese Struktur ausreichend frei nach "Mater semper certa est " ist die "Mutter" in den meisten Fällen bekannt. Wobei es auch hierbei Ausnahmen geben kann (z.B. Findelkinder).D.h in den meisten Fällen ist Mother/Father nicht Nil. Geschwister einer Person sind somit alle Personen deren Father oder Mother mit Father oder Mother der Person übereinstimmen.
Willst Du hingegen die Möglichkeiten die sich juristisch ergeben können, wirst Du nicht umhin kommen deine Daten in einer solchen (oder ähnlichen) Struktur abzulegen:

Vielen Dank für den Input. Genau, das hatte ich mir auch schon so in die Richtung gedacht, da ich auch Adoptivkinder darstellen möchte.

Wie gesagt, mir bereitet eher die Datenstruktur und deren grafische Ausgabe Kopfzerbrechen, ein normaler Baum kommt hier nicht in Frage und ich denke, Namenloser hat mit seiner Graphen-Argumentation recht. Nur ist diese Materie mir völlig fremd. Ich weiß nur, dass man da nicht unbedingt auf Rekursion zurückgreifen muss.

TiGü 14. Mai 2018 08:25

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Ich betreibe Ahnenforschung seit 2013 und verstehe also das, was du da machen willst.

Mal abgesehen von der Datenstruktur, die mit dem beispielhaften TFamilyTree und TPerson erstmal völlig ausreichend ist (Adoptivkinder und andere Spezialfälle würde ich mir wirklich für später aufsparen), was genau bereitet dir Kopfzerbrechen?

Das reine Zeichnen an und für sich, also Kästchen in der und der Farbe mit Schrift können wir dir anhand von Delphi-Code zeigen, aber wahrscheinlich nicht anhand von Bespielen mit Xojo (wenn du damit noch entwickelst).

Wie man etwas anordnen und zeichen kann - also die Frage nach dem Konzept - wäre es gut sich Inspiration bei Ahnenblatt, MyHeritage, Ancestry oder eine der 13264 anderen Stammbaumprogrammen zu holen und das einfach mal nachzuzeichnen.

hansklok 14. Mai 2018 11:23

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Diese TFamilyTree Klasse soll vor der grafischen Ausgabe automatisch die benötigte Breite und Höhe des Baums anhand seiner Graphen (Partner, Geschwister, Kinder) und die Anzahl der Generationen in ein Bitmap zeichnen. Anhand der Größe des Bitmap kann man ja dann auch die Größe des Canvas einstellen. Was mich interessiert ist also, wie man das ganze ohne viel manuelles Zutun mathematisch arrangiert.

Delphi-Code geht in Ordnung für mich, die Syntax kann ich lesen und verstehen.

Und wie ich mir das optisch vorstelle, davon habe ich im ersten Beitrag auf Seite 2 ein Bild gepostet.

hansklok 14. Mai 2018 11:54

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Die Struktur von SirRufo scheint sehr gut. Vielleicht kann man anhand derer die dynamische Berechnung der Positionen der einzelnen Elemente und der sich daraus ergebenden Gesamtbreite und -höhe erklären.
Was mir dort aber noch etwas Kopfzerbrechen bereitet, wie kann man dann mit dieser Struktur mittels MouseMove Event die aktuelle Person abfragen? Denn es gibt ja nicht nur reine TPerson, sondern in anderen Klassen enthaltene TPerson.

TiGü 14. Mai 2018 12:34

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Ich möchte dir nicht zu Nahe treten, aber vielleicht ist das Vorhaben auch etwas über deinen Möglichkeiten.
Fang mal ganz klein an. Du brauchst eine Datenstruktur, die erstmal Eltern und Kinder aufnehmen kann. Die haben wir gegeben.
Diese füllst du erstmal mit Daten und dann machen wir den allereinfachsten Fall:

Wir haben zwei Elternteile und meinetwegen vier Kinder. Schreibe auf den Canvas oben zentriert die Namen der Eltern (Erst Vater, dann Mutter) und in der nächsten "Zeile" (ich nenne es mal so) die Namen der Kinder. Reine Textausgabe. Kästchen, Verbindungslinien und Anfassen und Verschieben heben wir uns mal für später auf.

Wenn du das hast, melde dich wieder.

TiGü 14. Mai 2018 12:44

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Vielleicht noch als Hinweis:
Weder die verknüpfende Baumstruktur, noch die einzelnen Personen-Klassen sollten irgendwelche Methoden haben die was grafisches machen, wie Draw zum Beispiel.

Stattdessen baue dir eine weitere Klasse, z.B. namens TGraphBuilder, der du einen Canvas/Bitmap gibst und dazu die Personenliste, die du darstellen möchtest.
Schon wird vieles klarer und einfacher.

hansklok 14. Mai 2018 13:28

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von TiGü (Beitrag 1402044)
Wenn du das hast, melde dich wieder.

OK, here we go.

Delphi-Quellcode:
unit Test;

interface

uses
  System.Generics.Collections;

type
  TPerson = class
  public
    Name: String;
    X, Y: Integer;
    constructor Create(name: String);
    procedure SetKoordinaten(x, y: Integer);
  end;

  TFamilie = class
  public
    Vater: TPerson;
    Mutter: TPerson;
    Kinder: TList<TPerson>;
    constructor Create(vater: TPerson, mutter: TPerson);
  end;
 
  TGraphBuilder = class
  public
    Familie: TFamilie;
    Picture: TCanvas;
    constructor Create(familie: TFamilie)
    procedure Draw;
  end;
 
  Const
    Abstand: Integer = 50;
    Breite: Integer = 100;
    Hoehe: Integer = 40;

implementation

// TPerson
constructor TPerson.Create(name: String);
begin
  inherited;
  Name = name;
end;

procedure SetKoordinaten(x, y: Integer);
begin
  X = x;
  Y = y;
end;

// TFamilie
constructor TFamilie.Create(vater: TPerson, mutter: TPerson);
begin
  inherited;
  Vater = vater;
  Mutter = mutter;
  Kinder = TList.Create;
end;

// TGraphBuilder
constructor TGraphBuilder.Create(familie: TFamilie);
begin
  inherited;
  Familie = familie;
end;

procedure TGraphBuilder.Draw;
var
  i: Integer;
begin
  Familie.Vater.SetKoordinaten(0, 0);
  Familie.Mutter.SetKoordinaten(Vater.X + Breite + Abstand, 0);
 
  // Vater/Mutter zeichnen
  Picture.Rectangle(Vater.X, Vater.Y, Vater.X + Breite, Vater.Y + Hoehe);
  Picture.Rectangle(Mutter.X, Vater.Y, Mutter.X + Breite, Mutter.Y + Hoehe);
 
  // Kinder
  for i:= Low(Familie.Kinder) To High(Familie.Kinder)
  do begin
    if i = 0 then
      Kinder(i).SetKoordinaten(i * Breite, Vater.Y + Hoehe + Abstand);
    else  
      Kinder(i).SetKoordinaten(i * Breite + Abstand, Vater.Y + Hoehe + Abstand);
    end;
    Picture.Rectangle(Kinder(i).X, Kinder(i).Y, Kinder(i).X + Breite, Kinder(i).Y + Hoehe);
    Picture.Textout(Kinder(i).X, Kinder(i).Y, Kinder(i).Name);
  end;
end;

end.
Und die Test-Daten:
Delphi-Quellcode:
procedure GeneriereTestDaten;
var
  familie: TFamilie;
  vater, mutter, proband, kind2, kind3, kind4: TPerson
  graph: TGraphBuilder;
begin
  // Eltern
  vater = TPerson.Create('Vater Mustermann');
  mutter = TPerson.Create('Mutter Musterfrau');
  // Proband
  proband = TPerson.Create('Max Mustermann');
  // Geschwister
  kind2 = TPerson.Create('Heidi Mustermann')
  kind3 = TPerson.Create('Friedrich Mustermann')
  kind4 = TPerson.Create('Jonas Mustermann')
 
  // Familie
  familie = TFamilie.Create(vater, mutter)
  familie.Kinder.Add(proband)
  familie.Kinder.Add(kind2)
  familie.Kinder.Add(kind3)
  familie.Kinder.Add(kind4)
 
  // Familie zeichnen
  graph = TGraphBuilder.Create(familie);
  graph.Picture = Canvas1;
  graph.Draw;
end;

freimatz 14. Mai 2018 13:57

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Der Code compiliert sicher nicht.

Daniel 14. Mai 2018 14:07

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von freimatz (Beitrag 1402060)
Der Code compiliert sicher nicht.

Ja, und wirklich wertvoll wird der Beitrag erst, wenn Du auch schreibst, warum der Code Deiner Ansicht nach nicht kompiliert werden kann. Möglicherweise möchtest Du auf die doppelten Anführungszeichen bei den Strings hinaus.

freimatz 14. Mai 2018 14:12

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Hm, ich sehe jetzt nicht warum mein Beitrag wertvoller werden würde wenn ich angebe warum er nicht compiliert. Der Poster kann es ja selber versuchen und stellt es so schneller fest.
Wie dem auch sei - ich sah nur das "TCanvas", das nirgendwo definiert wurde.

hansklok 14. Mai 2018 14:33

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Zitat:

Zitat von freimatz (Beitrag 1402060)
Der Code compiliert sicher nicht.

Es handelt sich um Pseudo-Code, um einen Strukturentwurf. Im Beitrag steht, dass ich lange nicht mit Delphi entwickelt habe! Bitte bleiben Sie fair! Ich habe mich hier an das Forum gewandt, da ich Unterstützung erhoffe.

Canvas1 ist ein Canvas auf einer TForm.

Vielen Dank.

freimatz 14. Mai 2018 14:56

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Ach so, tut mir leid. Dass Sie noch nicht mit Delphi entwickelt haben habe ich zwar gelesen, bin dabei aber ausgegangen dass Sie es dann aaber jetzt tun werden. Bei den Inhalten mit "unit", "interface", "uses" und "end." bin ich davon ausgegangen, dass es sich um eine fertige Datei handeln soll.

TiGü 14. Mai 2018 15:08

AW: konzeptionelle Gedanken optische Darstellung Stammbaum
 
Um mal wieder produktiv zu werden, folgender Code für ein leeres Formular als Ausgangsbasis. Damit können wir grundlegende Sachen klären und uns dann später darum kümmern, wie man die gezeichneten Objekte anfassen und verschieben kann. Dazu noch die obligatorischen Verbindungslinien. Vielleicht das als erstes. Wer will, wer hat Ideen?

Delphi-Quellcode:
unit StammbaumFrm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Generics.Collections;

type
  TPerson = class
  public
    Name: string;
    X, Y: Integer;
    constructor Create(const AName: string);
    procedure SetKoordinaten(AX, AY: Integer);
  end;

  TFamilie = class
  public
    Vater: TPerson;
    Mutter: TPerson;
    Kinder: TList<TPerson>;
    constructor Create(AVater: TPerson; AMutter: TPerson);
  end;

  TGraphBuilder = class
  public
    Familie: TFamilie;
    Picture: TCanvas;
    constructor Create(AFamilie: TFamilie);
    procedure Draw;
  end;

const
  Abstand: Integer = 50;
  Breite: Integer = 100;
  Hoehe: Integer = 40;

type
  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    Familie: TFamilie;
    graph: TGraphBuilder;
    FBitmap: TBitmap;
  public

  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

// TPerson
constructor TPerson.Create(const AName: string);
begin
  inherited Create;
  name := AName;
end;

procedure TPerson.SetKoordinaten(AX, AY: Integer);
begin
  X := AX;
  Y := AY;
end;

// TFamilie
constructor TFamilie.Create(AVater: TPerson; AMutter: TPerson);
begin
  inherited Create;
  Vater := AVater;
  Mutter := AMutter;
  Kinder := TList<TPerson>.Create;
end;

// TGraphBuilder
constructor TGraphBuilder.Create(AFamilie: TFamilie);
begin
  inherited Create;
  Familie := AFamilie;
end;

procedure TGraphBuilder.Draw;
var
  i: Integer;
  TextSize: TSize;
  Vater, Mutter, Kind: TPerson;
  X, Y, W, H: Integer;
  PersonName: string;
begin
  Vater := Familie.Vater;
  Mutter := Familie.Mutter;

  Familie.Vater.SetKoordinaten(0, 0);
  X := Vater.X + Breite + Abstand;
  Familie.Mutter.SetKoordinaten(X, 0);

  // Picture.Font.Name := 'Tahoma';
  // Picture.Font.Size := 12;
  // Picture.Font.Color := clBlack;

  // Vater/Mutter zeichnen
  PersonName := Vater.Name;
  TextSize := Picture.TextExtent(PersonName);
  X := Vater.X;
  Y := Vater.Y;
  W := X + Breite;
  H := Y + Hoehe;

  Picture.Rectangle(X, Y, W + TextSize.cx div 2, H);
  X := X + 5;
  Y := Y + TextSize.cy div 2;
  Picture.Textout(X, Y, PersonName);

  PersonName := Mutter.Name;
  TextSize := Picture.TextExtent(PersonName);
  X := Mutter.X + TextSize.cx div 2;
  Y := Mutter.Y;
  W := X + Breite;
  H := Y + Hoehe;

  Picture.Rectangle(X, Y, W + TextSize.cx div 2, H);
  X := X + 5;
  Y := Y + TextSize.cy div 2;
  Picture.Textout(X, Y, PersonName);

  // Kinder
  for i := 0 to Familie.Kinder.Count - 1 do
  begin
    Kind := Familie.Kinder[i];
    PersonName := Kind.Name;
    TextSize := Picture.TextExtent(PersonName);

    if i = 0 then
    begin
      X := i * Breite;
      Y := Vater.Y + Hoehe + Abstand;
      Kind.SetKoordinaten(X, Y);
    end
    else
    begin
      X := i * (Breite + (2 * Abstand));
      Y := Vater.Y + Hoehe + Abstand;
      Kind.SetKoordinaten(X, Y);
    end;

    Y := Kind.Y;
    W := X + TextSize.cx + 10;
    H := Y + Hoehe;

    Picture.Rectangle(X, Y, W, H);
    X := X + 5;
    Y := Y + TextSize.cy div 2;
    Picture.Textout(X, Y, PersonName);
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  Vater, Mutter, proband, kind2, kind3, kind4: TPerson;
begin
  // Eltern
  Vater := TPerson.Create(' Vater Mustermann ');
  Mutter := TPerson.Create(' Mutter Musterfrau ');
  // Proband
  proband := TPerson.Create(' Max Mustermann ');
  // Geschwister
  kind2 := TPerson.Create(' Heidi Mustermann ');
  kind3 := TPerson.Create(' Friedrich Mustermann ');
  kind4 := TPerson.Create(' Jonas Mustermann ');

  // Familie
  Familie := TFamilie.Create(Vater, Mutter);
  Familie.Kinder.Add(proband);
  Familie.Kinder.Add(kind2);
  Familie.Kinder.Add(kind3);
  Familie.Kinder.Add(kind4);

  FBitmap := TBitmap.Create;

  // Familie zeichnen
  graph := TGraphBuilder.Create(Familie);
end;

procedure TForm2.FormPaint(Sender: TObject);
begin
  graph.Picture := FBitmap.Canvas; // Canvas1;
  graph.Draw;

  Self.Canvas.Draw(0, 0, FBitmap);
end;

procedure TForm2.FormResize(Sender: TObject);
begin
  FBitmap.SetSize(Self.ClientWidth, Self.ClientHeight);
end;

end.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:25 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz