TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Rund um die LCL und andere Komponenten
Antworten
Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Beitrag von Helios »

Hallo liebe Forenmitglieder,

basierend auf die Multi-Pane und Runtime TChart Demos in einer Standardinstallation (Lazarus 1.8, Windws7 64bit)
möchte ich jetzt eine (fast) beliebige (naja maximal 25) Anzahl Y-Achsen übereinander in einem Chart darstellen.
Das soll/kann natürlich nur zur Laufzeit (bei Button1Cklick) passiern.
Probleme habe ich dabei wahrscheinlich mit der AxisList der TChart Komponente.

Im Anhang ein Beispiel, wie ich es mir vorstellen würde.
Leider klappt der Aufbau des Charts nach Button1 Click nicht so wie gewünscht.

Im Voraus vielen Dank für jede Hilfe/Unterstützung.

Gruß
Arne


Sorry, das Anhängen scheint nicht zu klappen, dann versuche ich es so:

Code: Alles auswählen

 
unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  TAGraph, TASeries, TATransformations, TAChartAxis, TAChartAxisUtils;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1:  TButton;
    Button2: TButton;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
    procedure PlotSin(cnt: integer);
  private
 
  public
 
  end;
 
var
  Form1:                    TForm1;
  Chart1:                   TChart;
  Line:                     array[1..50] of TLineSeries;
  Axis:                     array[0..50] of TChartAxis; // one more for x/bottom axis
  MyAxisList:               TChartAxisList;
  ChartAxisTransformations: array[1..50] of TChartAxisTransformations;
  AutoScaleAxisTransform:   array[1..50] of TAutoScaleAxisTransform;
  x, y:                     array[0..1000] of Double;
  seriescount:              integer = 0;
  minAxisVal:               integer = 0;
  maxAxisVal:               integer = 0;
  delta:                    integer = 2;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.PlotSin(cnt: integer);
var
  i:   integer;
  min: double;
  max: double;
begin
  min := y[0];
  max := y[0];
  Line[cnt].SeriesColor := clRed;
  for i := low(x) to high(y) do
  begin
    x[i] := 0.01 * i + seriescount;
    y[i] := sin(x[i]);
    if y[i] < min then
      min := y[i];
    if y[i] > max then
      max := y[i];
    Line[cnt].AddXY(x[i], y[i]);
  end;
end;
 
 
procedure TForm1.Button1Click(Sender: TObject);
begin
 
  if seriescount = 0 then
  begin
    Chart1 := TChart.Create(Form1);
    Chart1.Align       := alClient;
    Chart1.Anchors     := [akTop, akLeft,akRight, akBottom];
    Chart1.Visible     := true;
    Chart1.AxisVisible := true;
    Chart1.Enabled     := true;
    Chart1.Height      := 450;
    Chart1.Width       := 700;
 
    MyAxisList := TChartAxisList.Create(Chart1);
    Axis[0]  := TChartAxis.Create(MyAxisList);
    Axis[0].Alignment := calBottom;
    Axis[0].Title.Caption := 'Bottom axis';
    Axis[0].Title.Visible := true;
    Axis[0].Group := 0;
    Chart1.BottomAxis := Axis[0];
  end
  else
  begin
    maxAxisVal := maxAxisVal + delta;
    Axis[seriescount] := TChartAxis.Create(MyAxisList);
    Axis[seriescount].Alignment := calBottom;
    Axis[seriescount].Title.Caption := IntToStr(seriescount) + '. axis';
    Axis[seriescount].Title.LabelFont.Orientation := 900;
    Axis[seriescount].Title.Visible := true;
    Axis[seriescount].Title.PositionOnMarks := true;
    Axis[seriescount].Group := 1;
    Axis[seriescount].Marks.Range.Max := 50;
    Axis[seriescount].Marks.Range.Min := 0;
    Axis[seriescount].Marks.Range.UseMax := True;
    Axis[seriescount].Marks.Range.UseMin := True;
    if seriescount = 1 then
    begin
      Chart1.BottomAxis := Axis[0];
      Chart1.LeftAxis   := Axis[1];
    end;
 
    Line[seriescount] := TLineSeries.Create(Chart1);
    Line[seriescount].AxisIndexY := seriescount;
    Line[seriescount].AxisIndexX := 0;
 
    ChartAxisTransformations[seriescount] := TChartAxisTransformations.Create(Chart1);
    Axis[seriescount].Transformations := ChartAxisTransformations[seriescount];
 
    AutoScaleAxisTransform[seriescount]   := TAutoScaleAxisTransform.Create(Axis[seriescount].Transformations);
    AutoScaleAxisTransform[seriescount].MinValue := MinAxisVal;
    AutoScaleAxisTransform[seriescount].MaxValue := MaxAxisVal;
    minAxisVal := maxAxisVal;
 
    AutoScaleAxisTransform[seriescount].Enabled  := true;
 
    Chart1.AxisList := MyAxisList;
    Chart1.AddSeries(Line[seriescount]);
 
    PlotSin(seriescount);
 
  end;
  inc(seriescount);
 
end;
 
end.
 
 


und

Code: Alles auswählen

 
object Form1: TForm1
  Left = 663
  Height = 462
  Top = 376
  Width = 700
  Caption = 'Form1'
  ClientHeight = 462
  ClientWidth = 700
  LCLVersion = '1.8.0.6'
  object Panel1: TPanel
    Left = 0
    Height = 28
    Top = 0
    Width = 700
    Align = alTop
    Caption = 'Panel1'
    ClientHeight = 28
    ClientWidth = 700
    TabOrder = 0
    object Button1: TButton
      Left = 12
      Height = 25
      Top = 1
      Width = 107
      Caption = 'Button1'
      OnClick = Button1Click
      TabOrder = 0
    end
  end
end
 
 

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

Re: TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Beitrag von wp_xyz »

Ich hab's noch nicht im Detail angesehen. Aber mir fällt auf, dass du die Achsen, Linien, Transformationen usw. in eigenen Variablen verwaltest. Das ist nicht nötig und wahrscheinlich fehlerträchtig, wenn die zur Laufzeit erzeugten Objekte nicht richtig an den Chart weitergereicht werden. TChart hat eine eigene AxisList und SeriesList. Die beiden Standardachsen sind bereits enthalten: Chart.LeftAxis = Chart.axisList[0], Chart.BottomAxis = Chart.AxisList[1] (oder evtl. umgekehrt).

Eine abgespeckte Version deines Projekts (nur 3 Achsen) findest du im Ordner "components\tachart\demo\runtime\dualaxes" der Lazarus-Installation. Evtl. auch interessant das Demo-Projekt "plotunit" im Schwester-Ordner.

Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

Re: TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Beitrag von Helios »

Hallo wp_xyz, Danke für Deine Antwort die Demos habe ich gesehen und ich hoffe davon erkennt man in meinem Code zumindest etwas (korrekt) wieder. Das Hauptproblem ist für mich, daß in der dynamischen Version die Achsen nicht übereinander platziert werden. Achse 0 soll die x-Achse (horizontal) werden und alle weiteren Achsen ab Achsen Index 1 vertikal (y) schön equidistant übereinander aufbauen. Ich kämpfe damit schon ein paar Tage und den Schlüssel zum Erfolg habe ich noch nicht gefunden. Die AxisList habe ich auch schon mit Clear gelöscht, damit ich meine Achsen beginnend mit der x-Achse und folgend alle y-Achsen aufbauen kann. Ab der 2. y-Achse überschreiben sich alle nachkommenden y-Achsen. :-(

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

Re: TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Beitrag von wp_xyz »

Wiegesagt, ich sehe nirgendwo ein axis := Chart.AxisList.Add, was die dabei erzeugte Achse in die Achsenliste des Charts einhängt. Verzichte auf deinen eigenen Variablen für AxisList etc., dann kommst du weniger in Versuchung, am Chart vorbeizuarbeiten.

Wenn du mir eine PM mit deiner E-Mail-Adresse schickst, kann ich dir gerne eine überbeitete Version des Demoprogramms für beliebig viele Achsen zukommen lassen (leider kann man hier ja nichts mehr hochladen). Aber mit 50 Achsen wirst du nicht sehr froh werden, ab 10 wird's sehr unübersichtlich.

Helios
Lazarusforum e. V.
Beiträge: 106
Registriert: Mi 29. Jun 2011, 22:36
OS, Lazarus, FPC: Lazarus 2.2.4 Windows 10 64Bit / Lazarus 2.0.12 Debian 11.7 „Bullseye" 64Bit
CPU-Target: 64Bit
Wohnort: Leonberg

Re: TChart+LineSeries+AxisTransformat zur Laufzeit erstellen

Beitrag von Helios »

Ich nehme nochmal den Thread hier auf, um die wunderbare Testapplikation von wp_xyz hier öffentlich bereit zu stellen.
wp_xyz besten Dank nochmal dafür.
Zur Erinnerung: Es war eine Applikation gesucht, die mehrere Serien übereinander in einem TChart mit der Achsenbeschriftungen
links zur Laufzeit erstellt. Weiterhin sollten die Serien auch zur Laufzeit zu löschen sein.
Hier das Ergebnis:

unit1.pas:

Code: Alles auswählen

 
unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, TAGraph, Forms, Controls, Graphics,
  Dialogs, ExtCtrls, ComCtrls, StdCtrls, TASeries, TAChartAxis;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    ButtonAdd: TButton;
    ButtonDelete: TButton;
    Chart: TChart;
    ComboBox1: TComboBox;
    Panel: TPanel;
    StatusBar: TStatusBar;
    procedure ButtonAddClick(Sender: TObject);
    procedure ButtonDeleteClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FSeriesCounter: Integer;
    procedure ArrangeYAxis;
    procedure MyAddSeries(ASeries: TLineSeries; AxisTitle: String; AxisColor: TColor);
  public
 
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
uses
  LclIntf, TAChartAxisUtils, TALegend, TATransformations, TACustomSeries;
 
const
  GROUPED     = true;
  GROUP_INDEX = 10;
  NUM_VALUES  = 1000;
  h           = 1.0;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  ButtonAddClick(nil);
  Chart.Legend.Alignment := laBottomCenter;
  Chart.Legend.Visible := true;
  Chart.Legend.ColumnCount := 5;
  Combobox1.ItemIndex := 0;
end;
 
{ Fügt die Series in den Chart ein.
  Erzeugt eine neue y-Achse für die Series und die zugehörigen AxisTransformation
  Aktualisiert die Anordnung der einzelnen y-Achsen.
  Wird aufgerufen, wenn Series erzeugt bzw. gelöscht werden. }

procedure TForm1.MyAddSeries(ASeries: TLineSeries; AxisTitle: String;
  AxisColor: TColor);
var
  axis: TChartAxis;
  t: TAxisTransform;
begin
  if Chart.SeriesCount = 0 then
    axis := Chart.LeftAxis        // bei der ersten Series die vorhandene Y-Achse verwenden
  else
    axis := Chart.AxisList.Add;   // neue Axis erzeugen
  axis.Alignment := calLeft;
  axis.Title.Caption := AxisTitle;
  axis.Marks.LabelFont.Color := AxisColor;
  axis.Title.LabelFont.Color := AxisColor;
  axis.Title.Visible := true;
  axis.Title.LabelFont.Orientation := 900// in 1/10 degrees
  axis.AxisPen.Color := AxisColor;
  axis.AxisPen.Visible := true;
  if GROUPED then
    axis.Group := GROUP_INDEX;
 
  // Draw marks and axis title at the data part of the axis
  axis.Marks.AtDataOnly := true;
  axis.AtDataOnly := true;
  axis.Title.PositionOnMarks := true;
 
  // AxisTransform
  axis.Transformations := TChartAxisTransformations.Create(self);
  t := TAutoscaleAxisTransform.Create(axis.Transformations);
  t.Transformations := axis.Transformations;
 
  // Series in Chart einfügen
  Chart.AddSeries(ASeries);
 
  // Min/MaxValue für die Transformations einrichten, AxisIndex in der Series
  // richtig setzen (kann sich nach dem Löschen geändert haben)
  ArrangeYAxis;
end;
 
// Die y-Achse neu anordnen
procedure TForm1.ArrangeYAxis;
var
  y: Double;
  axis: TChartAxis;
  ser: TBasicChartSeries;
  t: TAutoscaleAxisTransform;
  xaxisIdx, yaxisIdx: Integer;
begin
  y := 0;
  for axis in Chart.AxisList do begin
    if (axis.Transformations = nil) or (axis.Alignment <> calLeft) then
      continue;
    if axis.Transformations.List.Count = 0 then
      continue;
    t := TAxisTransform(axis.Transformations.List[0]) as TAutoscaleAxisTransform;
      // ACHTUNG: Das kann schief gehen, wenn mehrere Transformations pro
      // Achse verwendet werden!
    t.MinValue := y;
    t.MaxValue := y + h;
    y := y + h;
  end;
 
  // AxisIndexY aktualisieren, kann sich beim Löschen verändert haben!
  xAxisIdx := Chart.BottomAxis.Index;
  yAxisIdx := 0;
  for ser in Chart.Series do begin
    TChartSeries(ser).AxisIndexX := xAxisIdx;
    if yAxisIdx = xAxisIdx then inc(yAxisIdx);
    TChartSeries(ser).AxisIndexY := yAxisIdx;
    inc(yAxisIdx);
  end;
end;
 
procedure TForm1.ButtonAddClick(Sender: TObject);
var
  series: TLineSeries;
  c: TColor;
  axisTitle: String;
  i: Integer;
begin
  inc(FSeriesCounter);
 
  // Hier nur die Series erzeugen, nicht Chart.AddSeries aufrufen. Das wird
  // mit anderem in MyAddSeries erledigt.
  c := RGB(Random(256), Random(256), Random(256));
  series := TLineSeries.Create(self);
  series.SeriesColor := c;
  series.Title := 'Series #' + IntToStr(FSeriesCounter);
  for i:=0 to NUM_VALUES - 1 do
    series.AddXY(i, random*10);
 
  // Series in den Chart einfügen und zugehörige Axis und Transformation erzeugen
  axisTitle := 'Axis #' + IntToStr(FSeriesCounter);
  MyAddSeries(series, AxisTitle, c);
 
  // In Combobox einhängen
  Combobox1.Items.AddObject(AxisTitle, series);
end;
 
procedure TForm1.ButtonDeleteClick(Sender: TObject);
var
  series: TLineseries;
  axis: TChartAxis;
  t: TChartAxisTransformations;
  idx: Integer;
begin
  idx := Combobox1.ItemIndex;
  if Combobox1.ItemIndex = -1 then
    exit;
 
  series := TLineSeries(Combobox1.Items.Objects[idx]);
  axis := Chart.AxisList[series.AxisIndexY];
  t := axis.Transformations;
 
  // Alles zur Series löschen
  t.Free;
  if Combobox1.Items.Count > 1 then  // Die "normale" y-Achse muss bleiben
    axis.Free;
  series.Free;
  Combobox1.Items.Delete(idx);
 
  // y-Achse neu aufbauen
  ArrangeYAxis;
 
  // Neuen Combobox-Eintrag auswählen
  if idx >= Combobox1.Items.Count then idx := Combobox1.Items.Count - 1;
  Combobox1.ItemIndex := idx;
 
  Chart.Legend.Visible := Chart.SeriesCount > 0;
end;
 
end.
 



project1.lpr:

Code: Alles auswählen

 
program project1;
 
{$mode objfpc}{$H+}
 
uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Interfaces, // this includes the LCL widgetset
  Forms, Unit1, tachartlazaruspkg;
 
{$R *.res}
 
begin
  RequireDerivedFormResource := True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
 


project1.lpi:

Code: Alles auswählen

 
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
  <ProjectOptions>
    <Version Value="11"/>
    <PathDelim Value="\"/>
    <General>
      <SessionStorage Value="
InProjectDir"/>
      <MainUnit Value="
0"/>
      <Title Value="
project1"/>
      <ResourceType Value="
res"/>
      <UseXPManifest Value="
True"/>
    </General>
    <BuildModes Count="
1">
      <Item1 Name="
Default" Default="True"/>
    </BuildModes>
    <PublishOptions>
      <Version Value="
2"/>
    </PublishOptions>
    <RunParams>
      <FormatVersion Value="
2"/>
      <Modes Count="
1">
        <Mode0 Name="
default"/>
      </Modes>
    </RunParams>
    <RequiredPackages Count="
2">
      <Item1>
        <PackageName Value="
TAChartLazarusPkg"/>
      </Item1>
      <Item2>
        <PackageName Value="
LCL"/>
      </Item2>
    </RequiredPackages>
    <Units Count="
2">
      <Unit0>
        <Filename Value="
project1.lpr"/>
        <IsPartOfProject Value="
True"/>
      </Unit0>
      <Unit1>
        <Filename Value="
unit1.pas"/>
        <IsPartOfProject Value="
True"/>
        <ComponentName Value="
Form1"/>
        <HasResources Value="
True"/>
        <ResourceBaseClass Value="
Form"/>
        <UnitName Value="
Unit1"/>
      </Unit1>
    </Units>
  </ProjectOptions>
  <CompilerOptions>
    <Version Value="
11"/>
    <PathDelim Value="
\"/>
    <Target>
      <Filename Value="
project1"/>
    </Target>
    <SearchPaths>
      <IncludeFiles Value="
$(ProjOutDir)"/>
      <UnitOutputDirectory Value="
lib\$(TargetCPU)-$(TargetOS)"/>
    </SearchPaths>
    <Linking>
      <Debugging>
        <UseExternalDbgSyms Value="
True"/>
      </Debugging>
      <Options>
        <Win32>
          <GraphicApplication Value="
True"/>
        </Win32>
      </Options>
    </Linking>
  </CompilerOptions>
  <Debugging>
    <Exceptions Count="
3">
      <Item1>
        <Name Value="
EAbort"/>
      </Item1>
      <Item2>
        <Name Value="
ECodetoolError"/>
      </Item2>
      <Item3>
        <Name Value="
EFOpenError"/>
      </Item3>
    </Exceptions>
  </Debugging>
</CONFIG>



unit1.lfm:

Code: Alles auswählen

 
object Form1: TForm1
  Left = 871
  Height = 477
  Top = 378
  Width = 780
  Caption = 'Form1'
  ClientHeight = 477
  ClientWidth = 780
  OnCreate = FormCreate
  LCLVersion = '1.9.0.0'
  object Panel: TPanel
    Left = 0
    Height = 32
    Top = 0
    Width = 780
    Align = alTop
    ClientHeight = 32
    ClientWidth = 780
    TabOrder = 0
    object ButtonAdd: TButton
      Left = 21
      Height = 25
      Top = 3
      Width = 75
      Caption = 'Add'
      OnClick = ButtonAddClick
      TabOrder = 0
    end
    object ButtonDelete: TButton
      Left = 110
      Height = 25
      Top = 3
      Width = 75
      Caption = 'Delete'
      OnClick = ButtonDeleteClick
      TabOrder = 1
    end
    object ComboBox1: TComboBox
      Left = 203
      Height = 23
      Top = 3
      Width = 100
      ItemHeight = 15
      Style = csDropDownList
      TabOrder = 2
    end
  end
  object StatusBar: TStatusBar
    Left = 0
    Height = 23
    Top = 454
    Width = 780
    Panels = <>
  end
  object Chart: TChart
    Left = 0
    Height = 422
    Top = 32
    Width = 780
    AxisList = <   
      item
        Marks.LabelBrush.Style = bsClear
        Minors = <>
        Title.LabelFont.Orientation = 900
        Title.LabelBrush.Style = bsClear
      end   
      item
        Alignment = calBottom
        Marks.LabelBrush.Style = bsClear
        Minors = <>
        Title.LabelBrush.Style = bsClear
      end>
    BackColor = clWhite
    Foot.Brush.Color = clBtnFace
    Foot.Font.Color = clBlue
    Title.Brush.Color = clBtnFace
    Title.Font.Color = clBlue
    Title.Text.Strings = (
      'TAChart'
    )
    Align = alClient
  end
end
 
 

Antworten