Page 1 of 2

Clone Series from a TmpChart ?

Posted: Tue May 12, 2009 8:42 am
by 10545590
Hi !

I use this code to Clone Series from a Temp Chart to an available chart:

Code: Select all

    // Dummy Chart erzeugen
    tmpEmpty := TChart.Create(nil);    { Create an empty chart }
    tmpEmpty.Parent := Self;
    try
      LoadChartFromStream(TCustomChart(tmpEmpty), Stream);     // Dummy Chart mit Stream füllen
      // Serien kopieren
      while Serien <> '' do
      begin
        Serie  := StrToInt(Copy(Serien, 1, pos('#', Serien) - 1));
        Serien := Copy(Serien, Pos('#', Serien) + 1, length(Serien));
        TargetChart.AddSeries(CloneChartSeries(tmpEmpty[Serie]));
        TargetChart[TargetChart.SeriesCount - 1].Assign( tmpEmpty[Serie] ) ;
      end;
    finally
      Stream.Free;
      tmpEmpty.Free;                   // Dummy Chart löschen
      TargetListBox.UpdateSeries;
      TargetChart.Refresh;
    end;
This Part is only to get the Series Number:
Serie := StrToInt(Copy(Serien, 1, pos('#', Serien) - 1));
Serien := Copy(Serien, Pos('#', Serien) + 1, length(Serien));
The code runs without errors, but I got no series cloend to my TargetChart. Did I miss anything?

The Stream is ok. If I read the Stream directly to the TargetChart all works fine. But I want to clone only some series (including the data).

Posted: Tue May 12, 2009 10:15 am
by 10545590
I also tried this:

Code: Select all

    tmpEmpty := TChart.Create(nil);    { Create an empty chart }
    tmpEmpty.Parent := Self;
    try
      LoadChartFromStream(TCustomChart(tmpEmpty), Stream);     // Dummy Chart mit Stream füllen
      // Serien kopieren
      while Serien <> '' do
      begin
        Serie  := StrToInt(Copy(Serien, 1, pos('#', Serien) - 1));
        Serien := Copy(Serien, Pos('#', Serien) + 1, length(Serien));

        tmpEmpty[Serie].ParentChart := TargetChart;

      end;
    finally
      Stream.Free;
      tmpEmpty.Free;                   // Dummy Chart löschen
      TargetListBox.UpdateSeries;
      TargetChart.Refresh;
    end;
The Series is in the TargetChart. But after tmpEmpty.Free the series is gone from the TargetChart.

So I hape you can give me a solution how to copy/clone or move a complete series from a Temp Chart to another chart.

Posted: Tue May 12, 2009 10:40 am
by 10545590
This won´t work, too: :(

Code: Select all

var
  SerieCopy : TChartSeries;
......
    tmpEmpty := TChart.Create(nil);    { Create an empty chart }
    tmpEmpty.Parent := Self;
    try
      LoadChartFromStream(TCustomChart(tmpEmpty), Stream);     // Dummy Chart mit Stream füllen
      // Serien kopieren
      while Serien <> '' do
      begin
        Serie  := StrToInt(Copy(Serien, 1, pos('#', Serien) - 1));
        Serien := Copy(Serien, Pos('#', Serien) + 1, length(Serien));

        SerieCopy := CloneChartSeries(tmpEmpty[Serie]);
        SerieCopy.ParentChart := TargetChart;
        TargetChart.AddSeries(SerieCopy );
      end;
    finally
      Stream.Free;
      tmpEmpty.Free;                   // Dummy Chart löschen
      TargetListBox.UpdateSeries;
      TargetChart.Refresh;
    end;
So I really need a solution for that. Hope an any help.

Posted: Wed May 13, 2009 8:35 am
by 10545590
Hi !

Can anyone help me with this case ?

Posted: Wed May 13, 2009 10:23 am
by yeray
Hi Dominik,

I'm trying to test your code but I'm not sure to understand how do you initialize Serien string before testing its value in the while condition.
Please, could you send us a simple example project we can run "as-is" to reproduce the problem here?
You can either post your files at news://www.steema.net/steema.public.attachments newsgroup or at our upload page.

Posted: Wed May 13, 2009 10:31 am
by 10545590
Hi Yeray,
Serie := StrToInt(Copy(Serien, 1, pos('#', Serien) - 1));
Serien := Copy(Serien, Pos('#', Serien) + 1, length(Serien));
Serien ist just a string which contains series indexes. For example:
0#3#
Serie is the first number of this string - in this case 0. I use this number to select the correct series from the temp chart (tmpEmpty[Serie]).

I loop through the "serien" string until it is empty.

Reproducing my situation is easy:
- create a nonvisible tempchart (tmpEmpty in my case)
- load a stream into the temp chart
- clone (or move) some series (including the data & properties like color, width, ...) from the temp chart to the existing and visible chart.
- delete the temp chart

That´s all.

The code should work with all kinds of series.

Posted: Wed May 13, 2009 12:16 pm
by yeray
Hi Dominik,

It seems that deleting the temp chart deletes the cloned series. Probably there is a problem with shared pointers references. Here there is an example that shows it:

Code: Select all

uses series, teestore, teeeditpro;

var Stream: TMemoryStream;
    tmpEmpty: TChart;

procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
  for i:=0 to 3 do
  begin
    Chart1.AddSeries(TLineSeries.Create(self));
    Chart1[i].FillSampleValues(25);
    Chart1[i].Title := 'Series' + IntToStr(i);
  end;

  Stream := TMemoryStream.Create;
  SaveChartToStream(TCustomChart(Chart1), Stream, true, false);
end;

procedure TForm1.Button1Click(Sender: TObject);
var SerieCopy: TChartSeries;
begin
  tmpEmpty := TChart.Create(self);

  try
    Stream.Position := 0;
    LoadChartFromStream(TCustomChart(tmpEmpty), Stream);
    SerieCopy := CloneChartSeries(tmpEmpty[0]);
    Chart2.AddSeries(SerieCopy);
  finally
    Stream.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  tmpEmpty.Free;
end;
So, to avoid this, you could not to delete your tmpEmpty chart until the exit of your application. Or a little bit more tricky, you could use another stream to save the final chart before freeing the tmpEmpty chart:

Code: Select all

procedure TForm1.Button1Click(Sender: TObject);
var SerieCopy: TChartSeries;
    tmpStream: TMemoryStream;
begin
  tmpEmpty := TChart.Create(self);

  try
    Stream.Position := 0;
    LoadChartFromStream(TCustomChart(tmpEmpty), Stream);
    SerieCopy := CloneChartSeries(tmpEmpty[0]);
    Chart2.AddSeries(SerieCopy);
    tmpStream := TMemoryStream.Create;
    SaveChartToStream(TCustomChart(Chart2), tmpStream, true, false);
    tmpStream.Position := 0;
    LoadChartFromStream(TCustomChart(Chart2), tmpStream);
  finally
    Stream.Free;
    tmpStream.Free;
    tmpEmpty.Free;
  end;
end;
I hope this helps!

Posted: Wed May 13, 2009 12:32 pm
by 10545590
Hi Yeray,
to avoid this, you could not to delete your tmpEmpty chart until the exit of your application
That´s not a good solution ...
Or a little bit more tricky, you could use another stream to save the final chart before freeing the tmpEmpty chart
Well that are a lot of Save and Load operations ...

Is there no solution to copy the series? I don´t need a real clone. A copy would be ok because I don´t need the tempchart after the copy action.

Could you think about something like this:
- get the series type in TmpChart
- create the same series in Target Chart
- copy data and settings to the new series
- delete Tmpchart

Would this be possible ?

Posted: Thu May 14, 2009 6:40 am
by 10545590
Hi Yeray,

well I could solve one issue :D
I can copy the settings using RTTI. This code works well:

Code: Select all

procedure CopyObject(ObjFrom, ObjTo: TObject);
var
  PropInfos: PPropList;
  PropInfo: PPropInfo;
  Count, Loop: Integer;
  OrdVal: Longint;
  StrVal: String;
  FloatVal: Extended;
  MethodVal: TMethod;
begin
  { Iterate thru all published fields and properties of source }
  { copying them to target }

  { Find out how many properties we'll be considering }
  Count := GetPropList(ObjFrom.ClassInfo, tkAny, nil);
  { Allocate memory to hold their RTTI data }
  GetMem(PropInfos, Count * SizeOf(PPropInfo));
  try
    { Get hold of the property list in our new buffer }
    GetPropList(ObjFrom.ClassInfo, tkAny, PropInfos);
    { Loop through all the selected properties }
    for Loop := 0 to Count - 1 do
    begin
      PropInfo := GetPropInfo(ObjTo.ClassInfo, PropInfos^[Loop]^.Name);
      { Check the general type of the property }
      { and read/write it in an appropriate way }
      case PropInfos^[Loop]^.PropType^.Kind of
        tkInteger, tkChar, tkEnumeration,
        tkSet, tkClass{$ifdef Win32}, tkWChar{$endif}:
        begin
          if UpperCase(PropInfos^[Loop]^.Name) <> 'PARENTCHART' then begin
            OrdVal := GetOrdProp(ObjFrom, PropInfos^[Loop]);
            if Assigned(PropInfo) then
              SetOrdProp(ObjTo, PropInfo, OrdVal);
          end;
        end;
        tkFloat:
        begin
          FloatVal := GetFloatProp(ObjFrom, PropInfos^[Loop]);
          if Assigned(PropInfo) then
            SetFloatProp(ObjTo, PropInfo, FloatVal);
        end;
        {$ifndef DelphiLessThan3}
        tkWString,
        {$endif}
        {$ifdef Win32}
        tkLString,
        {$endif}
        tkString:
        begin
          { Avoid copying 'Name' - components must have unique names }
          if UpperCase(PropInfos^[Loop]^.Name) = 'NAME' then
            Continue;
          StrVal := GetStrProp(ObjFrom, PropInfos^[Loop]);
          if Assigned(PropInfo) then
            SetStrProp(ObjTo, PropInfo, StrVal);
        end;
        tkMethod:
        begin
          MethodVal := GetMethodProp(ObjFrom, PropInfos^[Loop]);
          if Assigned(PropInfo) then
            SetMethodProp(ObjTo, PropInfo, MethodVal);
        end
      end
    end
  finally
    FreeMem(PropInfos, Count * SizeOf(PPropInfo));
  end;
end;
But now there are two problems left and I hope you can help me with that.

1) How can I get the Class of a series?
Example ... Lets say we have a chart with one TFastLineSeries. Now I have to create the series in the target chart first. This can be done with this code:

Code: Select all

var series1: TFastLineSeries; 
begin
  series1 := TFastLineSeries.Create(nil);
  Chart2.AddSeries(series1);
But the is fixed to TFastLine. I need a general procedure for creating the series in the TargetChart. Something like this:

Code: Select all

var series1: TChartSeries; 
begin
  series1 := TXXXXXXXXXXXSeries.Create(nil);
  Chart2.AddSeries(series1);
TXXXXXXXXXXXSeries is the class I need to know.

Could you give me a piece of code which detects the class in the SourceChart and create the same kind of Series in the TargetChart?

2) What´s the best way to copy the data from Sourceseries to the Targetseries? Changing the DataSource isn´t a good solution.
Again I need a general solution to copy the data from series to series - no matter what kind of series I use.

It would be great if you could help me with this two questions.

Posted: Thu May 14, 2009 9:45 am
by yeray
Hi Dominik,

1. You could do as follows:

Code: Select all

  var series: TChartSeries;
  //...
  series := TChartSeriesClass(Chart1[0].ClassType).Create(self);
  Chart1.AddSeries(series);
2. Here I think that you have two options:
a. Export and Import the data using a file (txt, xml,...)
b. Assign ValueList, Colors and Labels from one series to the other.

Here you have an example:

Code: Select all

procedure TForm1.FormCreate(Sender: TObject);
begin
  Chart1.AddSeries(TPointSeries.Create(self));
  Chart1[0].FillSampleValues(25);
  Chart1[0].Color := clRed;
  Chart1[0].Labels[10] := 'my custom label';
end;

procedure TForm1.Button1Click(Sender: TObject);
var SeriesSource, SeriesCopy: TChartSeries;
    i: Integer;
begin
  SeriesSource := Chart1[0];
  SeriesCopy := TChartSeriesClass(SeriesSource.ClassType).Create(self);
  Chart2.AddSeries(SeriesCopy);
  SeriesCopy.FillSampleValues(25);

  for i:=0 to SeriesSource.ValuesList.Count-1 do
    with SeriesCopy.ValuesList[i] do
    begin
      Value:=SeriesSource.ValuesList[i].Value;
      Count:=SeriesSource.ValuesList[i].Count;
      Modified:=true;
    end;


  for i:=0 to SeriesSource.Count-1 do
  begin
    SeriesCopy.ValueColor[i] := SeriesSource.ValueColor[i];
    SeriesCopy.Labels[i] := SeriesSource.Labels[i];
  end;

  SeriesCopy.Repaint;
end;

Posted: Thu May 14, 2009 10:25 am
by 10545590
Hi Yeray,

tested it with some FastLine series and it works. But please see the sample. I got an error:
Received Copy Object RTTI.zip Content Type application/x-zip-compressed Length 13381

Erste Gelegenheit für Exception bei $7C812AFB. Exception-Klasse EListError mit Meldung 'Listenindex überschreitet das Maximum (-1)'. Prozess CopyEg.exe (3416)

Posted: Thu May 14, 2009 10:47 am
by yeray
Hi Dominik,

I think you forgot to include the dpr file in the zip. Without that delphi isn't able to open the project.

Posted: Thu May 14, 2009 10:53 am
by 10545590
Hi Yeray,

sorry for that.

New upload:
Received Copy Object RTTI.zip Content Type application/x-zip-compressed Length 18798

Posted: Thu May 14, 2009 11:22 am
by yeray
Hi Dominik,

I'm afraid I cannot reproduce any error in your application. Could you please tell me the steps I should follow to reproduce it?

Thanks in advance

Posted: Thu May 14, 2009 11:31 am
by 10545590
Hi Yeray,

as soon as I press the "Copy ->" Button I got the error (EListError) in TeEngine (Function TChartSeries.GetValueColor(ValueIndex:Integer):TColor;)

I used Delphi 2007 with TChart 8.04.