{********************************************}
{  TeeChart Series DB Virtual DataSet        }
{ Copyright (c) 1996-2020 by Steema Software }
{          All Rights Reserved               }
{********************************************}
unit TeeData;
{$I TeeDefs.inc}

{$R-}

// Special issue with AnsiStrings:
{$IFDEF D27}
{$IFDEF IOS}
{$DEFINE D27_IOS_ANDROID}
{$ENDIF}
{$IFDEF ANDROID}
{$DEFINE D27_IOS_ANDROID}
{$ENDIF}
{$ENDIF}


{ This unit contains two VIRTUAL TDATASET derived components.

  1) The TSeriesDataSet component is an intermediary between a
  Series component and a TDataSource.

  You can show Series values in a DBGrid, for example:

  SeriesDataSet1.Series := Series1;
  DataSource1.DataSet   := SeriesDataSet1;
  DBGrid1.DataSource    := DataSource1;

  To refresh data:

  SeriesDataSet1.Close;
  Series1.Add(....)
  SeriesDataSet1.Open;

  2) The TChartDataSet component is an intermediary between a
  Chart (or DBChart or QRChart) component and a TDataSource.

  ChartDataSet1.Chart := Chart1;
  DataSource1.DataSet := ChartDataSet1;
  DBGrid1.DataSource  := DataSource1;

  Additional information under Delphi \Demos\TextData example project.

  NOTE: This component is not available in Delphi and C++ Builder
        STANDARD versions, because they do not include Database components.

}
interface

uses DB, Classes,

     {$IFDEF D18}
     System.Generics.Collections,

     {$IFNDEF IOS}
     {$IFNDEF ANDROID}
     {$IFNDEF LINUX}
     System.AnsiStrings,
     {$ENDIF}
     {$ENDIF}
     {$ENDIF}

     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Procs, FMXTee.Engine, FMXTee.Chart
     {$ELSE}
     TeeProcs, TeEngine, Chart
     {$ENDIF};

Const
  MaxLabelLen=128;

type
  TColor=Cardinal;
  {$NODEFINE TColor}

  {$IFDEF NEXTGEN}
  TRecordBuffer=NativeInt;
  {$ENDIF}
  
  TTeeRecordBuffer={$IFDEF D17}TRecordBuffer{$ELSE}{$IFDEF D12}PByte{$ELSE}PChar{$ENDIF}{$ENDIF};
  {$NODEFINE TTeeRecordBuffer}

  TTeeAddRecordBuffer={$IFDEF D17}TTeeRecordBuffer{$ELSE}Pointer{$ENDIF};
  {$NODEFINE TTeeAddRecordBuffer}

  TTeeRecordBufferP={$IFDEF D17}TRecordBuffer{$ELSE}{$IFDEF D12}PByte{$ELSE}Pointer{$ENDIF}{$ENDIF};
  {$NODEFINE TTeeRecordBufferP}

  TTeeBookmark={$IFDEF D17}TBookmark{$ELSE}Pointer{$ENDIF};
  {$NODEFINE TTeeBookmark}

  TTeeValueBuffer={$IFDEF D17}TValueBuffer{$ELSE}Pointer{$ENDIF};
  {$NODEFINE TTeeValueBuffer}

  PSeriesPoint=^TSeriesPoint;
  TSeriesPoint=record
    Color  : TColor; { 4 bytes (TColor is 4 bytes Integer in x86) }
    X      : TChartValue;  { 8 bytes by default TChartValue is a Double }
    Values : Array[0..10] of TChartValue; { 10*SizeOf(TChartValue) (88 bytes by default) }
    ALabel : {$IFDEF NEXTGEN}String{$ELSE}String[MaxLabelLen]{$ENDIF}; { 128 bytes }
  end;

  PSeriesPoints=^TSeriesPoints;
  TSeriesPoints=packed Array[0..0] of TSeriesPoint;

  PRecInfo = ^TRecInfo;
  TRecInfo = packed record
    Bookmark     : Integer;
    BookmarkFlag : TBookmarkFlag;
  end;

  TTeeDataSet=class {$IFDEF D11}abstract{$ENDIF} (TDataSet)
  private
    FBookMarks    : TList{$IFDEF D18}<Integer>{$ENDIF};
    FCurRec       : Integer;
    FLastBookmark : Integer;

    function BufferRecord(const Buffer:TTeeRecordBuffer):PRecInfo;
    Procedure DoCreateField(const AFieldName:String; AType:TFieldType; ASize:Integer);
    procedure DoInternalGotoBookmark(BookMark:Integer);
    function FieldsAreAutomatic:Boolean;
    Function RecBufSize: Integer;
  protected
    procedure AddSeriesFields(const Series:TChartSeries; PrefixSeriesName:Boolean=False);

    {$IFDEF NEXTGEN}
    function AllocRecBuf: TRecBuf; override;
    procedure FreeRecBuf(var Buffer: TRecBuf); override;
    {$ELSE}
    function AllocRecordBuffer: TTeeRecordBuffer; override;
    procedure FreeRecordBuffer(var Buffer: TTeeRecordBuffer); override;
    {$ENDIF}

    Procedure DoAddSeriesPoint(const Buffer:TSeriesPoint; const Series:TChartSeries);
    Procedure DoFillBuffer(const Buffer: TTeeRecordBuffer); virtual; abstract;
    Procedure FillSeriesBuffer(var Buffer:TSeriesPoint; const Series:TChartSeries);
    procedure GetBookmarkData(Buffer: TTeeRecordBuffer; Data: TTeeBookmark); override;
    function GetBookmarkFlag(Buffer: TTeeRecordBuffer): TBookmarkFlag; override;
    function GetRecNo: Integer; override;
    function GetRecord(Buffer: TTeeRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
    function GetSeriesBuffer(FieldIndex:Integer; var Active:TSeriesPoint; {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer; const Series:TChartSeries):Boolean;

    procedure InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean); override;
    procedure InternalClose; override;
    procedure InternalDelete; override;
    procedure InternalFirst; override;
    procedure InternalGotoBookmark(Bookmark:TTeeBookmark); override;
    procedure InternalHandleException; override;
    procedure InternalInitRecord(Buffer: TTeeRecordBuffer); override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: TTeeRecordBuffer); override;
    function IsCursorOpen: Boolean; override;
    procedure PostToSeries(const Series:TChartSeries; const Buffer:TSeriesPoint);
    Function RecInfoOfs:Integer; virtual; abstract;
    procedure SetBookmarkData(Buffer: TTeeRecordBuffer; Data: TTeeBookmark); override;
    procedure SetBookmarkFlag(Buffer: TTeeRecordBuffer; Value: TBookmarkFlag); override;

    {$IFNDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}

    procedure SetRecNo(Value: Integer); override;

    procedure SetSeriesBuffer(FieldIndex:Integer; var Active:TSeriesPoint; Buffer:Pointer; const Series:TChartSeries);
    function ValidActiveBuffer:Boolean;
  public
    {$IFDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}
  published
    property Active;
    property AutoCalcFields;
    property BeforeOpen;
    property AfterOpen;
    property BeforeClose;
    property AfterClose;
    property BeforeInsert;
    property AfterInsert;
    property BeforeEdit;
    property AfterEdit;
    property BeforePost;
    property AfterPost;
    property BeforeCancel;
    property AfterCancel;
    property BeforeDelete;
    property AfterDelete;
    property BeforeScroll;
    property AfterScroll;
    property AfterRefresh;
    property BeforeRefresh;

    property OnCalcFields;
    property OnDeleteError;
    property OnEditError;
    property OnNewRecord;
    property OnPostError;
  end;

  {$IFDEF D16}
  [ComponentPlatformsAttribute(TeeAllComponentPlatformIDs)]
  {$ENDIF}
  TSeriesDataSet = class(TTeeDataSet,ITeeEventListener)
  private
    FSeries       : TChartSeries;

    procedure TeeEvent(Event: TTeeEvent);
  protected

    { Overriden abstract methods (required) }
    Procedure DoFillBuffer(const Buffer: TTeeRecordBuffer); override;
    function GetRecordSize: Word; override;

    procedure InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean); override;
    procedure InternalDelete; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    function IsCursorOpen: Boolean; override;

    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;

    {$IFNDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}

    { Additional overrides (optional) }
    function GetRecordCount: Integer; override;
    Function RecInfoOfs: Integer; override;
    Procedure SetSeries(ASeries:TChartSeries); virtual;
  public
    function GetFieldData(Field: TField; {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer): Boolean; override;
    {$IFDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}
  published
    property Series: TChartSeries read FSeries write SetSeries stored True;
  end;

  {$IFDEF D16}
  [ComponentPlatformsAttribute(TeeAllComponentPlatformIDs)]
  {$ENDIF}
  TChartDataSet = class(TTeeDataSet,ITeeEventListener)
  private
    FChart : TCustomChart;

    procedure CalcFieldSeries(Field:Integer; out Index,Series:Integer);
    procedure SetChart(const Value:TCustomChart);
    procedure TeeEvent(Event: TTeeEvent);
  protected
    Procedure DoFillBuffer(const Buffer: TTeeRecordBuffer); override;
    function GetRecordCount: Integer; override;
    function GetRecordSize: Word; override;
    procedure InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean); override;
    procedure InternalDelete; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    function IsCursorOpen: Boolean; override;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;
    Function RecInfoOfs: Integer; override;

    {$IFNDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}
  public
    function GetFieldData(Field: TField; {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer): Boolean; override;
    {$IFDEF LCL}
    procedure SetFieldData(Field: TField; Buffer: TTeeValueBuffer); override;
    {$ENDIF}
  published
    property Chart:TCustomChart read FChart write SetChart;
  end;

implementation

uses SysUtils,

     {$IFDEF FMX}
     FMXTee.Constants, FMXTee.Canvas
     {$ELSE}

     {$IFNDEF D6}
     Forms,
     {$ENDIF}

     TeeConst, TeCanvas
     {$ENDIF};

type
  PChartValue=^TChartValue;

{ TTeeDataset }

Function TTeeDataSet.RecBufSize: Integer;
begin
  result:=RecInfoOfs + SizeOf(TRecInfo);
end;

function TTeeDataSet.FieldsAreAutomatic:Boolean;
begin
  {$IFDEF D20}
  result:=True; // (Fields.CreatedModes=[]) or (cfAutomatic in  Fields.CreatedModes);
  {$ELSE}
  result:=DefaultFields;
  {$ENDIF}
end;

procedure TTeeDataSet.InternalClose;
begin
  FreeAndNil(FBookMarks);

  if FieldsAreAutomatic then
     DestroyFields;

  FLastBookmark:=0;
  FCurRec:=-1;
end;

Procedure TTeeDataSet.DoCreateField(Const AFieldName:String; AType:TFieldType; ASize:Integer);
begin
  {$IFDEF C3D4}
  With TFieldDef.Create(FieldDefs) do
  begin
    Name      := AFieldName;
    Size      := ASize;
    Required  := False;
    DataType  := AType;
  end;
  {$ELSE}
  TFieldDef.Create(FieldDefs, AFieldName, AType, ASize, False, FieldDefs.Count+1)
  {$ENDIF}
end;

procedure TTeeDataSet.InternalHandleException;
begin
  {$IFDEF D6}
  if Assigned(Classes.ApplicationHandleException) then
    Classes.ApplicationHandleException(Self);
  {$ELSE}
  Application.HandleException(Self);
  {$ENDIF}
end;

procedure TTeeDataSet.InternalDelete;
begin
  FBookMarks.Delete(FCurRec);

  if FCurRec >= RecordCount then
     Dec(FCurRec);
end;

{$IFNDEF D6}
type
  PInteger=^Integer;
{$ENDIF}

procedure TTeeDataSet.DoInternalGotoBookmark(BookMark:Integer);
var Index: Integer;
begin
  Index := FBookMarks.IndexOf({$IFNDEF D18}Pointer{$ENDIF}(BookMark));
  if Index = -1 then
     DatabaseError('Bookmark not found')
  else
     FCurRec := Index;
end;

procedure TTeeDataSet.InternalGotoBookmark(Bookmark:TTeeBookmark);
begin
  DoInternalGotoBookMark(PInteger(BookMark)^);
end;

function TTeeDataSet.BufferRecord(const Buffer:TTeeRecordBuffer):PRecInfo;
begin
  result:=PRecInfo({$IFDEF D17}PByte{$ENDIF}(Buffer) + RecInfoOfs);
end;

procedure TTeeDataSet.InternalSetToRecord(Buffer: TTeeRecordBuffer);
begin
  DoInternalGotoBookmark(BufferRecord(Buffer).Bookmark);
end;

procedure TTeeDataSet.InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean);
begin
  Inc(FLastBookmark);
  if Append then InternalLast;
end;

function TTeeDataSet.GetBookmarkFlag(Buffer: TTeeRecordBuffer): TBookmarkFlag;
begin
  Result := BufferRecord(Buffer).BookmarkFlag;
end;

procedure TTeeDataSet.SetBookmarkFlag(Buffer: TTeeRecordBuffer; Value: TBookmarkFlag);
begin
  BufferRecord(Buffer).BookmarkFlag := Value;
end;

procedure TTeeDataSet.GetBookmarkData(Buffer: TTeeRecordBuffer; Data: TTeeBookmark);
begin
  PInteger(Data)^ := BufferRecord(Buffer).Bookmark;
end;

procedure TTeeDataSet.SetBookmarkData(Buffer: TTeeRecordBuffer; Data: TTeeBookmark);
begin
  BufferRecord(Buffer).Bookmark := PInteger(Data)^;
end;

{$IFDEF NEXTGEN}
function TTeeDataSet.AllocRecBuf: TRecBuf;
begin
  Result := TRecBuf(AllocMem(RecBufSize));
end;

procedure TTeeDataSet.FreeRecBuf(var Buffer: TRecBuf);
begin
  FreeMem(Pointer(Buffer));
end;

{$ELSE}
function TTeeDataSet.AllocRecordBuffer: TTeeRecordBuffer;
begin
  GetMem(Result, RecBufSize);
end;

procedure TTeeDataSet.FreeRecordBuffer(var Buffer: TTeeRecordBuffer);
begin
  FreeMem(Buffer, RecBufSize);
end;
{$ENDIF}

procedure TTeeDataSet.InternalInitRecord(Buffer: TTeeRecordBuffer);
begin
  FillChar({$IFDEF D17}PByte{$ENDIF}(Buffer)^, RecordSize, 0);
end;

procedure TTeeDataSet.InternalFirst;
begin
  FCurRec := -1;
end;

function TTeeDataSet.GetRecNo: Integer;
begin
  UpdateCursorPos;

  if (FCurRec = -1) and (RecordCount > 0) then
     Result := 1
  else
     Result := FCurRec + 1;
end;

procedure TTeeDataSet.SetRecNo(Value: Integer);
begin
  if (Value >= 0) and (Value <= RecordCount) then
  begin
    FCurRec := Value - 1;
    Resync([]);
  end;
end;

procedure TTeeDataSet.InternalOpen;
var t : Integer;
begin
  { Fabricate integral bookmark values }
  FBookMarks:=TList{$IFDEF D18}<Integer>{$ENDIF}.Create;

  for t:=1 to RecordCount do
      FBookMarks.Add({$IFNDEF D18}Pointer{$ENDIF}(t));

  FLastBookmark:=RecordCount;

  FCurRec:=-1;

  BookmarkSize := SizeOf(Integer);

  InternalInitFieldDefs;

  if FieldsAreAutomatic then
     CreateFields;

  BindFields(True);
end;

procedure TTeeDataSet.InternalLast;
begin
  FCurRec:=RecordCount;
end;

function TTeeDataSet.IsCursorOpen: Boolean;
begin
  Result:=Assigned(FBookMarks);
end;

procedure TTeeDataSet.AddSeriesFields(const Series:TChartSeries; PrefixSeriesName:Boolean=False);

  Function GetFieldName(Const ADefault,AName:String):String;
  begin
    if PrefixSeriesName then
    begin
      result:=Series.Name;

      if (result='') and Assigned(Series.ParentChart) then
         result:=TeeMsg_Series+TeeStr(1+Series.SeriesIndex);

      result:=result+'.';
    end
    else
       result:='';

    if AName='' then result:=result+ADefault
                else result:=result+AName;

  end;

  Procedure AddField(IsDateTime:Boolean; Const FieldName:String);
  begin
    if IsDateTime then DoCreateField(FieldName,ftDateTime,0)
                  else DoCreateField(FieldName,ftFloat,0);
  end;

var tmp:String;
    t:Integer;
begin
  {$IFDEF C3D4}
  With TFieldDef.Create(FieldDefs) do
  begin
    Name:=GetFieldName('','Color');
    DataType:=ftInteger;
    Size:=0;
    Required:=False;
    FieldNo:=1;
  end;
  {$ELSE}
  TFieldDef.Create(FieldDefs, GetFieldName('','Color'), ftInteger, 0, False, 1);
  {$ENDIF}

  With Series.XValues do AddField(DateTime,GetFieldName('X',Name));
  With Series.YValues do AddField(DateTime,GetFieldName('Y',Name));

  {$IFDEF C3D4}
  With TFieldDef.Create(FieldDefs) do
  begin
    Name:=GetFieldName('','Label');
    DataType:=ftString;
    Size:=MaxLabelLen;
    Required:=False;
    FieldNo:=4;
  end;
  {$ELSE}
  TFieldDef.Create(FieldDefs, GetFieldName('','Label'), ftString, MaxLabelLen, False, 4);
  {$ENDIF}

  for t:=2 to Series.ValuesList.Count-1 do
  With Series.ValuesList[t] do
  begin
    tmp:=Name;

    if Name='' then tmp:='Value'+TeeStr(t)
               else tmp:=Name;

    AddField(DateTime,GetFieldName('',tmp));
  end;
end;

procedure TTeeDataSet.SetSeriesBuffer(FieldIndex:Integer; var Active:TSeriesPoint;
                                      Buffer:Pointer; const Series:TChartSeries);

   Function GetAValue(IsDateTime:Boolean):TChartValue;
   begin
     result:=PChartValue(Buffer)^;

     if IsDateTime then
        result:=TimeStampToDateTime(MSecsToTimeStamp({$IFDEF LCL}Round{$ENDIF}(result)));
   end;

begin
  with Active do
  Case FieldIndex of
    1: Color:=PInteger(Buffer)^;
    2: X:=GetAValue(Series.XValues.DateTime);
    3: Values[0]:=GetAValue(Series.YValues.DateTime);
    4: {$IFDEF NEXTGEN}
       ALabel:=TMarshal.ReadStringAsAnsi(TPtrWrapper.Create(Buffer));
       {$ELSE}
       ALabel:={$IFDEF D12}PAnsiChar{$ELSE}PChar{$ENDIF}(Buffer);
       {$ENDIF}
  else
    Values[FieldIndex-4]:=GetAValue(Series.ValuesList[FieldIndex-3].DateTime);
  end;
end;

procedure TTeeDataSet.SetFieldData(Field: TField; Buffer: TTeeValueBuffer);
begin
  DataEvent(deFieldChange, Integer(Field));
end;

function TTeeDataSet.GetRecord(Buffer: TTeeRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
var tmp  : Integer;
    tmpR : PRecInfo;
begin
  result:=grError;

  if IsCursorOpen then
  begin
    if RecordCount < 1 then Result := grEOF
    else
    begin
      Result := grOK;

      case GetMode of
        gmNext: if FCurRec >= RecordCount - 1  then result:=grEOF
                                               else Inc(FCurRec);
       gmPrior: if FCurRec <= 0 then result:=grBOF
                                else Dec(FCurRec);

     gmCurrent: if (FCurRec < 0) or (FCurRec >= RecordCount) then
                   result := grError;
      end;

      if result=grOK then
      begin
        DoFillBuffer(Buffer);

        if Assigned(FBookMarks) and (FBookMarks.Count>FCurRec)
           {$IFNDEF D18}and Assigned(FBookMarks[FCurRec]){$ENDIF} then
           tmp:={$IFNDEF D18}Integer{$ENDIF}(FBookMarks[FCurRec])
        else
           tmp:=-1;

        tmpR:=BufferRecord(Buffer);
        tmpR.BookmarkFlag := bfCurrent;
        tmpR.BookMark:=tmp;
      end
      else
      if (Result = grError) and DoCheck then
         DatabaseError('No Records');
    end;
  end
  else
  if DoCheck then DatabaseError('No Records');
end;

function TTeeDataSet.ValidActiveBuffer:Boolean;
begin
  result:=ActiveBuffer<>{$IFDEF D18}0{$ELSE}nil{$ENDIF};
end;

Procedure TTeeDataSet.FillSeriesBuffer(var Buffer:TSeriesPoint; const Series:TChartSeries);
var t : Integer;
begin
  With Buffer do
  if FCurRec<Series.Count then
  begin
    Color:=Series.ValueColor[FCurRec];
    X:=Series.XValues.Value[FCurRec];

    {$IFDEF NEXTGEN}
    ALabel:=Series.Labels[FCurRec];
    {$ELSE}
    ALabel:=ShortString(Series.Labels[FCurRec]);
    {$ENDIF}

    for t:=1 to Series.ValuesList.Count-1 do
        Values[t-1]:=Series.ValuesList[t].Value[FCurRec];
  end
  else
  begin
    Color:=0;
    X:=0;
    ALabel:='';

    for t:=1 to Series.ValuesList.Count-1 do
        Values[t-1]:=0;
  end;
end;

function TTeeDataSet.GetSeriesBuffer(FieldIndex:Integer; var Active:TSeriesPoint;
  {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer; const Series:TChartSeries):Boolean;

   Function GetSeriesValue(const AList:TChartValueList):TChartValue;
   var t : Integer;
   begin
     if AList=Series.XValues then
        result:=Active.X
     else
     begin
       result:=0;

       for t:=1 to Series.ValuesList.Count-1 do
       if AList=Series.ValuesList[t] then
       begin
         result:=Active.Values[t-1];
         break;
       end;
     end;

     if AList.DateTime then
        result:=TimeStampToMSecs(DateTimeToTimeStamp(result));
   end;

{$IFDEF NEXTGEN}
var Len : Integer;
{$ENDIF}
begin
  result:=Series.Count>0;

  if result and Assigned(Buffer) then
  case FieldIndex of
    1: PInteger(Buffer)^:=Active.Color;
    2: PChartValue(Buffer)^:=GetSeriesValue(Series.XValues);
    3: PChartValue(Buffer)^:=GetSeriesValue(Series.YValues);
    4: begin
         result:=Active.ALabel<>'';

         if result then
         {$IFDEF NEXTGEN}
         begin
           Len:=Length(TMarshal.AsAnsi(Active.ALabel))+1;
           SetLength(Buffer, Len);
           TMarshal.Copy(TMarshal.AllocStringAsAnsi(Active.ALabel), Buffer, 0, Len-1);
           Buffer[Len-1] := 0;
         end
         {$ELSE}
           {$IFDEF D27_IOS_ANDROID}
             // Special issue for RAD 10.4 ios Simulator
             StrCopy(Pointer(Buffer),PChar(Active.ALabel));
             Exit;
           {$ELSE}
             {$IFDEF D18}System.AnsiStrings.{$ENDIF}StrPCopy(Pointer(Buffer),Active.ALabel);
           {$ENDIF}
         {$ENDIF}
       end;
    else
       PChartValue(Buffer)^:=GetSeriesValue(Series.ValuesList[FieldIndex-3]);
  end;
end;

Procedure TTeeDataSet.DoAddSeriesPoint(const Buffer:TSeriesPoint; const Series:TChartSeries);
var t : Integer;
begin
  With Buffer do
  begin
    for t:=2 to Series.ValuesList.Count-1 do
        Series.ValuesList[t].TempValue:=Values[t-1];

    Series.AddXY(X,Values[0],String(ALabel),Color);
  end;
end;

procedure TTeeDataSet.PostToSeries(const Series:TChartSeries; const Buffer:TSeriesPoint);
var t : Integer;
begin
  while Series.Count<=FCurRec do
        Series.AddNull;

  with Buffer do
  Begin
    Series.ValueColor[FCurRec]:=Color;
    Series.XValues.Value[FCurRec]:=X;
    Series.YValues.Value[FCurRec]:=Values[0];
    Series.Labels[FCurRec]:=String(ALabel);

    for t:=2 to Series.ValuesList.Count-1 do
        Series.ValuesList[t].Value[FCurRec]:=Values[t-1];
  end;
end;

procedure TTeeDataSet.InternalPost;
begin
  if State <> dsEdit then
  begin
    Inc(FLastBookmark);
    FBookMarks.Add({$IFNDEF D18}Pointer{$ENDIF}(FLastBookMark));
  end;
end;

{ TSeriesDataSet }

type
  TTeePanelAccess=class(TCustomTeePanel);

Procedure TSeriesDataSet.DoFillBuffer(const Buffer: TTeeRecordBuffer);
begin
  FillSeriesBuffer(PSeriesPoint(Buffer)^,FSeries);
end;

Procedure TSeriesDataSet.SetSeries(ASeries:TChartSeries);
Var WasActive : Boolean;
begin
  WasActive:=Active;
  Active:=False;

  if Assigned(FSeries) then
  begin
    FSeries.RemoveFreeNotification(Self);

    if Assigned(FSeries.ParentChart) then
       TTeePanelAccess(FSeries.ParentChart).RemoveListener(Self);
  end;

  FSeries:=ASeries;

  if Assigned(FSeries) then
  begin
    FSeries.FreeNotification(Self);

    if Assigned(FSeries.ParentChart) then
       TTeePanelAccess(FSeries.ParentChart).AddListener(Self);

    if WasActive then
       Active:=True;
  end;
end;

procedure TSeriesDataSet.Notification( AComponent: TComponent;
                            Operation: TOperation);
begin
  inherited;

  if (Operation=opRemove) and Assigned(FSeries) and (AComponent=FSeries) then
     Series:=nil;
end;

Function TSeriesDataSet.RecInfoOfs:Integer;
begin
  result:=SizeOf(TSeriesPoint);
end;

procedure TSeriesDataSet.InternalOpen;
begin
  if not Assigned(FSeries) then
     Raise Exception.Create('Cannot open SeriesDataSet. No Series assigned.');

  inherited;
end;

function TSeriesDataSet.IsCursorOpen: Boolean;
begin
  Result:=(inherited IsCursorOpen) and Assigned(FSeries);
end;

procedure TSeriesDataSet.InternalInitFieldDefs;
begin
  FieldDefs.Clear;

  if Assigned(FSeries) then
     AddSeriesFields(FSeries);
end;

function TSeriesDataSet.GetRecordSize: Word;
begin
  if Assigned(FSeries) then result:=SizeOf(TSeriesPoint)
                       else result:=0;
end;

function TSeriesDataSet.GetFieldData(Field: TField; {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer): Boolean;
begin
  result:=ValidActiveBuffer and GetSeriesBuffer(Field.FieldNo,PSeriesPoint(ActiveBuffer)^,Buffer,Series);
end;

procedure TSeriesDataSet.SetFieldData(Field: TField; Buffer: TTeeValueBuffer);
begin
  if ValidActiveBuffer then
     SetSeriesBuffer(Field.FieldNo,PSeriesPoint(ActiveBuffer)^,Buffer,FSeries);

  inherited;
end;

procedure TSeriesDataSet.InternalPost;
begin
  if State = dsEdit then
     PostToSeries(FSeries,PSeriesPoint(ActiveBuffer)^)
  else
     DoAddSeriesPoint(PSeriesPoint(ActiveBuffer)^,FSeries);

  inherited;
end;

procedure TSeriesDataSet.InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean);
begin
  inherited;
  DoAddSeriesPoint(PSeriesPoint(Buffer)^,FSeries);
  FBookMarks.Add({$IFNDEF D18}Pointer{$ENDIF}(FLastBookMark));
end;

procedure TSeriesDataSet.InternalDelete;
begin
  FSeries.Delete(FCurRec);
  inherited;
end;

function TSeriesDataSet.GetRecordCount: Integer;
begin
  Result:=FSeries.Count;
end;

procedure TSeriesDataSet.TeeEvent(Event: TTeeEvent);
begin
  if Active and (Event is TTeeSeriesEvent) and
     (TTeeSeriesEvent(Event).Event=seDataChanged) then
  begin
    Close;
    Open;
  end;
end;

{ TChartDataset }

procedure TChartDataSet.Notification( AComponent: TComponent;
                                      Operation: TOperation);
begin
  inherited;

  if (Operation=opRemove) and Assigned(FChart) and (AComponent=FChart) then
     Chart:=nil;
end;

Procedure TChartDataSet.SetChart(const Value:TCustomChart);
var WasActive : Boolean;
begin
  if FChart<>Value then
  begin
    WasActive:=Active;

    if not (csDestroying in ComponentState) then
       Active:=False;

    if Assigned(FChart) then
    begin
      FChart.RemoveFreeNotification(Self);

      TTeePanelAccess(FChart).RemoveListener(Self);
    end;

    FChart:=Value;

    if Assigned(FChart) then
    begin
      FChart.FreeNotification(Self);

      TTeePanelAccess(FChart).AddListener(Self);

      if not (csDestroying in ComponentState) then
         if WasActive then
            Active:=True;
    end;
  end;
end;

procedure TChartDataSet.TeeEvent(Event: TTeeEvent);

  function ValidEvent(Value:TChartSeriesEvent):Boolean;
  begin
    result:=(Value=seAdd) or (Value=seAfterRemove) or (Value=seDataChanged);
  end;

begin
  if Active and (Event is TTeeSeriesEvent) and
     ValidEvent(TTeeSeriesEvent(Event).Event) then
  begin
    Close;
    Open;
  end;
end;

function TChartDataSet.GetRecordSize: Word;
begin
  if Assigned(FChart) then
     result:=FChart.SeriesCount*SizeOf(TSeriesPoint)
  else
     result:=0;
end;

procedure TChartDataSet.InternalDelete;
var t : Integer;
begin
  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
      if FChart[t].Count>FCurRec then
         FChart[t].Delete(FCurRec);

  inherited;
end;

function TChartDataSet.GetRecordCount: Integer;
var t : Integer;
begin
  result:=0;

  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
  With FChart[t] do
       if (t=0) or (Count>result) then
           result:=Count;
end;

function TChartDataSet.IsCursorOpen: Boolean;
begin
  Result:=(inherited IsCursorOpen) and Assigned(FChart);
end;

procedure TChartDataSet.InternalOpen;
begin
  if not Assigned(FChart) then
     Raise Exception.Create('Cannot open Chart DataSet. No Chart assigned.');

  inherited;
end;

procedure TChartDataSet.InternalInitFieldDefs;
var t : Integer;

  procedure ValidateFields;
  var t : Integer;
  begin
    if Assigned(Fields) then
    begin
      for t:=Fields.Count-1 downto 0 do
      begin
        if FieldDefs.IndexOf(Fields[t].{$IFDEF LCL}FieldName{$ELSE}FullName{$ENDIF})=-1 then
          Fields.Remove(Fields[t]);
      end;
    end;
  end;

begin
  FieldDefs.Clear;

  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
     AddSeriesFields(FChart[t],FChart.SeriesCount>1);

  ValidateFields;
end;

procedure TChartDataSet.CalcFieldSeries(Field:Integer; out Index,Series:Integer);
begin
  Index:=Field;
  Series:=0;

  if Assigned(FChart) then
  while Index>(2+FChart[Series].ValuesList.Count) do
  begin
    Dec(Index,(2+FChart[Series].ValuesList.Count));
    Inc(Series);
  end;
end;

procedure TChartDataSet.SetFieldData(Field: TField; Buffer: TTeeValueBuffer);
var tmpIndex  : Integer;
    tmpSeries : Integer;
begin
  if ValidActiveBuffer then
  begin
    CalcFieldSeries(Field.FieldNo,tmpIndex,tmpSeries);
    SetSeriesBuffer(tmpIndex,PSeriesPoints(ActiveBuffer)[tmpSeries],Buffer,FChart[tmpSeries]);
  end;

  inherited;
end;

Function TChartDataSet.RecInfoOfs:Integer;
begin
  result:=SizeOf(TSeriesPoint);

  if Assigned(FChart) then
     result:=result*FChart.SeriesCount;
end;

Procedure TChartDataSet.DoFillBuffer(const Buffer: TTeeRecordBuffer);
var t : Integer;
begin
  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
      FillSeriesBuffer(TSeriesPoint(PSeriesPoints(Buffer)[t]),FChart[t]);
end;

function TChartDataSet.GetFieldData(Field: TField; {$IFDEF D18}var {$ENDIF}Buffer: TTeeValueBuffer): Boolean;
var tmpIndex  : Integer;
    tmpSeries : Integer;
begin
  CalcFieldSeries(Field.FieldNo,tmpIndex,tmpSeries);
  result:=ValidActiveBuffer and GetSeriesBuffer(tmpIndex,PSeriesPoints(ActiveBuffer)[tmpSeries],Buffer,FChart[tmpSeries]);
end;

procedure TChartDataSet.InternalPost;
var t : Integer;
begin
  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
  if State = dsEdit then
     PostToSeries(FChart[t],PSeriesPoints(ActiveBuffer)[t])
  else
     DoAddSeriesPoint(PSeriesPoints(ActiveBuffer)[t],FChart[t]);

  inherited;
end;
                          
procedure TChartDataSet.InternalAddRecord(Buffer: TTeeAddRecordBuffer; Append: Boolean);
var t : Integer;
begin
  inherited;

  if Assigned(FChart) then
  for t:=0 to FChart.SeriesCount-1 do
      DoAddSeriesPoint(PSeriesPoints(Buffer)[t],FChart[t]);

  FBookMarks.Add({$IFNDEF D18}Pointer{$ENDIF}(FLastBookMark));
end;

end.
