[gelöst] TChart und Bezeichnungen in gestapelte Säulen

Rund um die LCL und andere Komponenten
Antworten
Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

[gelöst] TChart und Bezeichnungen in gestapelte Säulen

Beitrag von willi4willi »

Hallo Zusammen!

Ich habe das Problem, dass ich es nicht hinbekomme, in einem TChart-Diagramm für jeden Wert in einer gestapelten Säule einen Text einzublenden.
.
TChart12345.png
TChart12345.png (41.9 KiB) 4404 mal betrachtet
.

Es sollen also auch für die blaue und die gelbe Säule etwas angezeigt werden.

Code: Alles auswählen

unit unit2;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, TAGraph, TASeries, TAStyles, TACustomSeries;

type

  { TForm1 }

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    Chart1: TChart;
    Chart1BarSeries1: TBarSeries;
    ChartStyles1: TChartStyles;
    procedure ChartGetMark(out AFormattedMark: String; AIndex: Integer);
    procedure CreateStackedSeries;
  end;

var
  Form1: TForm1;

implementation
uses TAChartUtils, TALegend;

{$R *.lfm}

{ TForm1 }

procedure TForm1.ChartGetMark(out AFormattedMark: String; AIndex: Integer);
begin
  AFormattedMark:='Benutzer '+IntToStr(AIndex);
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateStackedSeries;
end;


procedure TForm1.CreateStackedSeries;
var
  i, Balken: Integer;
  x, y1, y2, y3: Double;
  DisplayValue: String;
begin

  Chart1:= TChart.Create(self);
  Chart1.Parent:=self;
  Chart1.AnchorAsAlign(alClient,10);

  // ChartStyles1 erzeugen
  ChartStyles1:= TChartStyles.Create(self);
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color := clRed;  // Farbe des untersten Balkens
    Text := 'Rot';       // Legendeneintrag des untersten Balkens
  end;
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color := clBlue;  // Farbe des mittleren Balkens
    Text := 'Blau';
  end;
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color :=clYellow;  // Farbe des obersten Balkens
    Text := 'Gelb';
  end;

  // Eine BarSeries erzeugen
  Chart1BarSeries1:= TBarSeries.Create(self);
  Chart1.AddSeries(Chart1BarSeries1);

  //Chart1BarSeries1.OnGetMark:=@ChartGetMark;
  Chart1BarSeries1.Styles := ChartStyles1;
  Chart1BarSeries1.Legend.Multiplicity := lmStyle;

  // Daten einfügen: wir haben 3 Balken, also braucht die Series 3 y-Werte!
  Chart1BarSeries1.ListSource.YCount := 3;
  for i:=0 to 5 do
  begin
    x := i;
    y1 := Random;   // Nehme hier als Beispiel nur Zufallszahlen...
    y2 := Random;
    y3 := Random;

    //Chart1BarSeries1.Marks.Style:=smsCustom;
    //Chart1BarSeries1.Marks.Style:=smsNone;
    //Chart1BarSeries1.Marks.Style:=smsValue;
    //Chart1BarSeries1.Marks.Style:=smsPercent;
    Chart1BarSeries1.Marks.Style:=smsLabel;
    //Chart1BarSeries1.Marks.Style:=smsLabelPercent;
    //Chart1BarSeries1.Marks.Style:=smsLabelValue;
    //Chart1BarSeries1.Marks.Style:=smsLegend;
    //Chart1BarSeries1.Marks.Style:=smsPercentTotal;
    //Chart1BarSeries1.Marks.Style:=smsLabelPercentTotal;
    //Chart1BarSeries1.Marks.Style:=smsXValue;

    DisplayValue:='Wert '+FloatToStr(x);                                  //               Mein Problem!!!!
    Chart1BarSeries1.ListSource.AddXYList(x, [y1,y2,y3],DisplayValue);    // <---- hier kann man nur einen Label-Wert übergeben
                                                                          //       ich brauche aber drei (für y1, y2 und y3)
  end;

  // Legende einschalten
  Chart1.Legend.Visible := true;

end;

end.

Hat jemand eine Idee, wie man das lösen kann?

Danke und viele Grüße
Zuletzt geändert von willi4willi am Mi 13. Dez 2023, 15:55, insgesamt 1-mal geändert.
 

Viele Grüße

Willi4Willi

------------

wp_xyz
Beiträge: 4893
Registriert: Fr 8. Apr 2011, 09:01

Re: TChart und Bezeichnungen in gestapelte Säulen

Beitrag von wp_xyz »

Ich fürchte, da hast du eine Lücke in TAChart gefunden... Wahrscheinlich müsste man die verschiedenen Level-Texte, durch geeignete Trennzeichen separiert, alle in dem als Label definierten Text unterbringen und diesen intern vor der Ausgabe wieder zerlegen. Muss mir bei Gelegenheit mal etwas überlegen...

P.S.
Wenn du nur die y-Werte pro Stack-Ebene anzeigen willst, das geht: Setze BarSeries.Marks.Style = smsValue und BarSeries.Marks.YIndex := MARKS_YINDEX_ALL (in unit TATypes, oder -1 - das ist dasselbe).

Mit einer indizierten Format-Anweisung (siehe https://wiki.lazarus.freepascal.org/TAC ... ark_labels) kann man damit schon recht komplexe Labels bauen, z.B.

Code: Alles auswählen

BarSeries.Marks.Format := 'Wert bei %4:g' + LineEnding + '%0:.2f';
BarSeries.Marks.Style := smsCustom;

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 155
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows, Linux (debian) / Lazarus 3.2 / FPC 3.2.2
CPU-Target: i386, win64, arm

[gelöst!] Re: TChart und Bezeichnungen in gestapelte Säulen

Beitrag von willi4willi »

Hallo zusammen

Super! Danke an wp_xyz! Diese Tipps haben mir sehr geholfen.

Mit der Verwendung von ChartGetMark konnte ich mir somit folgende Lösung erstellen:
.

Code: Alles auswählen

unit unit2;

{$mode objfpc}{$H+}

interface

uses Classes
   , SysUtils
   , Forms
   , Controls
   , Graphics
   , TAGraph
   , TASeries
   , TAStyles
   ;

type

  { TForm1 }

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    YIndex:integer;
    MeineLabels : array of array of String;
    Chart1: TChart;
    Chart1BarSeries1: TBarSeries;
    ChartStyles1: TChartStyles;
    procedure ChartGetMark(out AFormattedMark: String; AIndex: Integer);
    procedure ChartBeforeDrawBar(ASender: TBarSeries; ACanvas: TCanvas; const ARect: TRect; APointIndex,
                                 AStackIndex: Integer; var ADoDefaultDrawing: Boolean);
  public
    procedure CreateStackedSeries;
    procedure MeinAddXYLabel(const AX: Double; const ALabels: array of String);
  end;

var
  Form1: TForm1;

implementation
uses  TACustomSeries
    , TAChartUtils
    , TALegend
    , TATypes
    ;

{$R *.lfm}

{ TForm1 }

procedure TForm1.ChartGetMark(out AFormattedMark: String; AIndex: Integer);
begin
  AFormattedMark:=MeineLabels[AIndex,YIndex];
  YIndex:=(YIndex+1) mod 3;
end;

procedure TForm1.ChartBeforeDrawBar(ASender: TBarSeries; ACanvas: TCanvas; const ARect: TRect; APointIndex, AStackIndex: Integer; var ADoDefaultDrawing: Boolean);
begin
  YIndex:=0;
end;

procedure TForm1.MeinAddXYLabel(const AX: Double; const ALabels: array of String);
var
  y: Integer;
begin
  SetLength(MeineLabels,TRUNC(AX)+1);
  SetLength(MeineLabels[TRUNC(AX)],Length(ALabels));
  for y:=low(ALabels) to high(ALabels) do
  begin
    MeineLabels[TRUNC(AX),y]:=ALabels[y];
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateStackedSeries;
end;

procedure TForm1.CreateStackedSeries;
var
  i, Balken: Integer;
  x, y1, y2, y3: Double;
  DisplayValue, MeinLabel1, MeinLabel2, MeinLabel3: String;
begin
  Chart1:= TChart.Create(self);
  Chart1.Parent:=self;
  Chart1.AnchorAsAlign(alClient,10);
  Chart1.AllowZoom:=FALSE;

  // ChartStyles1 erzeugen
  ChartStyles1:= TChartStyles.Create(self);
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color := clRed;  // Farbe des untersten Balkens
    Text := 'Rot';       // Legendeneintrag des untersten Balkens
  end;
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color := clBlue;  // Farbe des mittleren Balkens
    Text := 'Blau';
  end;
  with TChartStyle(ChartStyles1.Styles.Add) do
  begin
    Brush.Color :=clYellow;  // Farbe des obersten Balkens
    Text := 'Gelb';
  end;

  // Eine BarSeries erzeugen
  Chart1BarSeries1:= TBarSeries.Create(self);
  Chart1.AddSeries(Chart1BarSeries1);

  YIndex:=0;                                                // YIndex für gestapelte Säulen initialisieren
  Chart1BarSeries1.OnGetMark:=@ChartGetMark;                // in diesem Ereignis wird das Label definiert
  Chart1BarSeries1.OnBeforeDrawBar:=@ChartBeforeDrawBar;    // hier setze ich "YIndex" zurück
                                                            // ist deprecated :( aber in OnCustomDrawBar werden die Balken nicht dargestellt

  Chart1BarSeries1.Styles := ChartStyles1;
  Chart1BarSeries1.Legend.Multiplicity := lmStyle;

  // Daten einfügen: wir haben 3 Balken, also braucht die Series 3 y-Werte!
  Chart1BarSeries1.ListSource.YCount := 3;
  for i:=0 to 5 do
  begin
    x := i;
    y1 := Random;   // Nehme hier als Beispiel nur Zufallszahlen...
    y2 := Random;
    y3 := Random;

    MeinLabel1:='ZufallLabel'+IntToStr(random(100));
    MeinLabel2:='ZufallLabel'+IntToStr(random(100));
    MeinLabel3:='ZufallLabel'+IntToStr(random(100));

    Chart1BarSeries1.Marks.Format:='.';                     // da muss irgendwas drinnenstehen
    Chart1BarSeries1.Marks.YIndex:=MARKS_YINDEX_ALL;        // das ist entscheiden, damit die gestapelte Säulen angezeigt werden
    //Chart1BarSeries1.MarkPositions:=lmpInside;

    Chart1BarSeries1.ListSource.AddXYList(x, [y1,y2,y3]);
    MeinAddXYLabel(x,[MeinLabel1,MeinLabel2,MeinLabel3]);   // damit schreibe ich die Labels in ein Array, dass ich in  "ChartGetMark" verwende
  end;

  // Legende einschalten
  Chart1.Legend.Visible := true;

end;


end.
.
Das Ergebnis sieht dann so aus:
.
TChart20231213.png
TChart20231213.png (47.83 KiB) 4292 mal betrachtet
.
Zuletzt geändert von willi4willi am Mi 13. Dez 2023, 20:16, insgesamt 1-mal geändert.
 

Viele Grüße

Willi4Willi

------------

wp_xyz
Beiträge: 4893
Registriert: Fr 8. Apr 2011, 09:01

Re: [gelöst!] Re: TChart und Bezeichnungen in gestapelte Säulen

Beitrag von wp_xyz »

willi4willi hat geschrieben:
Mi 13. Dez 2023, 15:54
Mit der Verwendung von ChartGetMark konnte ich mir somit folgende Lösung erstellen:
Ja, so konn man's machen. Baut allerdings auf einem Implementierungs-Detail auf, nämlich dass bei der Series zuerst jeder Balken mit allen "Stockwerken" fertig gezeichnet wird, erst dann kommt der nächste dran. Das könnte sich ändern - ich meine bei einer Line-Series werden pro Level alle Punkte gezeichnet, bevor es mit dem nächsten Level weitergeht. Da müsstest du etwas anders vorgehen.

Ich denke, es wäre besser, wenn ein erweitertes OnGetMark-Event verfügbar wäre, mit dem aktuell verwendeten YIndex als zusätzlichem Parameter, und wenn schon, dann auch gleich mit einem XIndex, denn auch X kann mehrere Werte bereitstellen. Nur fällt mir kein passender Name ein... OnGetMarkEx?
willi4willi hat geschrieben:
Mi 13. Dez 2023, 15:54

Code: Alles auswählen

  Chart1BarSeries1.OnBeforeDrawBar:=@ChartBeforeDrawBar;    // hier setze ich "YIndex" zurück
                                                            // ist deprecated :( aber in OnCustomDrawBar werden die Balken nicht dargestellt
Es ist schwierig mit den vor Urzeiten eingeführen Events, die kann man kaum mehr ändern ohne irgendjemandem weh zu tun... OnBeforeDrawBar hat das Problem, dass es den Canvas verwendet - wenn man den Chart auf einem Backend ausgibt, wo es keinen Canvas gibt (z.B. SVG-Datei) hat man ein Problem. Und OnCustomDraw ist dafür gedacht, dass man hier den Balken komplett selbst malen kann - gut gedacht, aber es wäre ein boolscher Parameter "DefaultDraw" gut gewesen, dann hättest du deinen Code reinzwängen können.

Antworten