{**********************************************}
{   TeeChart Animations                        }
{   Copyright (c) 1999-2020 by Steema Software }
{**********************************************}
unit TeeAnimations;
{$I TeeDefs.inc}

interface

// This unit defines and implements several "Chart Animations".
// Animations appear at the Chart editor "Animations" tab and can be added
// both at design-time and run-time.

// The purpose of an "animation" is to perform some generic functionality
// outside the Chart or Series code. Chart Animations are notified of
// chart drawing (repaints) and chart mouse events (move, up, down).

Uses {$IFNDEF FMX}
     {$IFNDEF LINUX}
     Windows,
     {$ENDIF}
     {$ENDIF}

     SysUtils, Classes,

     {$IFDEF FMX}
     System.UITypes,
     {$ELSE}
     Graphics, Controls, ExtCtrls, Forms,
     {$ENDIF}

     {$IFDEF D6}
     Types,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Canvas, FMXTee.Engine, FMXTee.Chart, FMXTee.Animate,
     FMXTee.Series
     {$ELSE}
     TeCanvas, TeEngine, Chart, Series, TeeAnimate
     {$ENDIF}
     ;

type
  TTeeAnimationTool=class(TTeeCustomAnimation)
  private
    FAutoPlay : Boolean;

    IAnimate : TTeeAnimate;
    IPlayed  : Boolean;

    function GetAnimate:TTeeAnimate;
    function GetAnimations: TAnimations;
    function GetLoop: Boolean;
    function GetOnPlay: TNotifyEvent;
    function GetOnStop: TNotifyEvent;
    function GetSpeed: Integer;

    procedure SetAnimate(const Value: TTeeAnimate);
    procedure SetLoop(const Value: Boolean);
    procedure SetOnPlay(const Value: TNotifyEvent);
    procedure SetOnStop(const Value: TNotifyEvent);
    procedure SetSpeed(const Value: Integer);
  protected
    IKeepAnimate : Boolean;

    Procedure ChartEvent(AEvent:TChartToolEvent); override;
    Procedure GetChildren(Proc:TGetChildProc; Root:TComponent); override;
    function GetChildOwner: TComponent; override;
    procedure ReadState(Reader: TReader); override;
  public
    procedure Continue;
    procedure Pause;
    function Paused:Boolean;
    procedure Play; override;
    function Running:Boolean;
    procedure Stop;

    class Function Description:String; override;
    class Function LongDescription:String; override;

    property Animations:TAnimations read GetAnimations;
  published
    property Animate:TTeeAnimate read GetAnimate write SetAnimate;
    property AutoPlay:Boolean read FAutoPlay write FAutoPlay default False;
    property Loop:Boolean read GetLoop write SetLoop stored False;
    property Speed:Integer read GetSpeed write SetSpeed stored False;

    property OnPlay:TNotifyEvent read GetOnPlay write SetOnPlay;
    property OnStop:TNotifyEvent read GetOnStop write SetOnStop;
  end;

  TAnimationTrigger=( atMouseClick, atMouseOver );

  TCustomExpandAnimation=class(TTeeAnimation)
  private
    FPart    : TChartClickedPart;
    FSizeBy  : Integer;
    FTarget  : TChartClickedPartStyle;
    FTrigger : TAnimationTrigger;

    IOldSize  : {$IFDEF FMX}System.Types.TPoint{$ELSE}TPoint{$ENDIF};
    ILastIndex : Integer;

    function GetPointer:TSeriesPointer;
  protected
    function EndAnimation:Boolean; override;
    procedure NextFrame(const Fraction: Single); override;
  public
    Constructor Create(AOwner: TComponent); override;

    function EditorClass:String; override;
    Procedure StoreValue; override;
  published
    property SizeBy:Integer read FSizeBy write FSizeBy default 10;
    property Target:TChartClickedPartStyle read FTarget write FTarget default cpSeriesPointer;
    property Trigger:TAnimationTrigger read FTrigger write FTrigger default atMouseOver;
    property TwoWay default True;
  end;

  TCustomAnimationChart=class(TTeeAnimation)
  private
    FChart  : TCustomAxisPanel;
    FSeries : TChartSeries;

    procedure SetChart(const Value:TCustomAxisPanel);
    procedure SetSeries(const Value:TChartSeries);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    class function IsValidOwner(const AObject:TObject):Boolean; override;
  published
    property Chart:TCustomAxisPanel read FChart write SetChart;
    property Series:TChartSeries read FSeries write SetSeries;
  end;

  TTransformTranslate=(ttFromLeft, ttFromRight,
                       ttFromTop, ttFromBottom,
                       ttFromTopLeft, ttFromTopRight,
                       ttFromBottomLeft, ttFromBottomRight,
                       ttNone);

  TTransformZoom=(tzNone, tzIn, tzOut);
  TTransformFade=(tfNone, tfIn, tfOut);
  TTransformScale=(tsNone, tsHorizontal, tsVertical, tsBoth);

  TTransformAnimationTarget=TChartClickedPartStyle;

  TTransformAnimation=class(TCustomAnimationChart)
  private
    FAxis      : TChartAxis;
    FClip      : Boolean;
    FFade      : TTransformFade;
    FMaxZoom   : Integer;
    FRotateMin : Integer;
    FRotateMax : Integer;
    FScale     : TTransformScale;
    FTarget    : TTransformAnimationTarget;
    FTranslateStyle  : TTransformTranslate;
    FUseBounds : Boolean;
    FZoomStyle : TTransformZoom;

    IAxisID : Integer;
    IChart : TCustomChart;
    IOldClip : Boolean;
    IOldClipFlag : Boolean;
    IOldTransp : Integer;
    ILastTransform : TTeeTransform;

    function CalcChart:TCustomAxisPanel;
    procedure ReadAxis(Reader: TReader);
    procedure WriteAxis(Writer: TWriter);
  protected
    procedure ChangeTransform(const Fraction:Single; const Transform:TTeeTransform; const R:TRect);
    procedure DefineProperties(Filer: TFiler); override;
    function EndAnimation:Boolean; override;
    procedure NextFrame(const Fraction: Single); override;
    procedure Loaded; override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    procedure AssignTransform(const ATransform:TTransformAnimation);

    Function EditorClass:String; override;

    class Function Description:String; override;
  published
    property Axis:TChartAxis read FAxis write FAxis stored False;
    property Clip:Boolean read FClip write FClip default True;
    property Fade:TTransformFade read FFade write FFade default tfNone;
    property MaxZoom:Integer read FMaxZoom write FMaxZoom default 300; // %
    property RotateMin:Integer read FRotateMin write FRotateMin default 0;
    property RotateMax:Integer read FRotateMax write FRotateMax default 0;
    property Scale:TTransformScale read FScale write FScale default tsNone;
    property TranslateStyle:TTransformTranslate read FTranslateStyle write FTranslateStyle default ttFromLeft;
    property Target:TChartClickedPartStyle read FTarget write FTarget default cpSeries;
    property UseBounds:Boolean read FUseBounds write FUseBounds default True;
    property ZoomStyle:TTransformZoom read FZoomStyle write FZoomStyle default tzNone;
  end;

  TSeriesAnimationLoop=(salNo, salOneWay, salCircular);

  TSeriesAnimation=class(TCustomAnimationChart)
  private
    FDrawEvery  : Integer;
    FLoop       : TSeriesAnimationLoop;
    FStartValue : Double;
    FStartAtMin : Boolean;

    IDirection : Boolean;
    IOnStart   : TNotifyEvent;
    IOnStep    : TNotifyEvent;
    IOnStop    : TNotifyEvent;

    IPrepared  : TCustomAxisPanel;

    OldCustom  : Boolean;

    procedure RemoveAllSeries(const AList:TChartSeriesList);
  protected
    IBackup    : TChartSeriesList;

    function AnimateSeries(const Fraction:Single; const ASeries:TChartSeries; AIndex:Integer):Boolean; virtual;
    function EndAnimation:Boolean; override;
    procedure NextFrame(const Fraction:Single); override;
    procedure SetInitialValues(const ASeries:TChartSeries); virtual;
    procedure StoreSeries(const ASeries:TChartSeries); virtual;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    class Function Description:String; override;
    Function EditorClass:String; override;
    procedure StoreValue; override;
  published
    property DrawEvery:Integer read FDrawEvery write FDrawEvery default 0;
    property Loop:TSeriesAnimationLoop read FLoop write FLoop default salNo;
    property StartAtMin:Boolean read FStartAtMin write FStartAtMin default True;
    property StartValue:Double read FStartValue write FStartValue;
  end;

  // Deprecated. For compilation compatibility with previous TeeChart versions only.
  TTeeLegacyAnimation=class {$IFDEF D11}abstract{$ENDIF} (TTeeAnimationTool)
  private
    IAnimation : TTeeAnimation;
  protected
    procedure DoCreateAnimation; virtual; abstract;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure ReadState(Reader: TReader); override;
    procedure SetAnimation(const Value:TTeeAnimation); virtual;
    function Valid(const Value:TComponent):Boolean; virtual; abstract;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;
  published
    property Active;
  end;

  TSeriesAnimationTool=class;

  TSeriesAnimationStep=procedure(Sender:TSeriesAnimationTool; Step:Integer) of object;

  // Use a TSeriesAnimation inside a generic Animation tool.
  TSeriesAnimationTool=class(TTeeLegacyAnimation)
  private
    FOnStart   : TNotifyEvent;
    FOnStep    : TSeriesAnimationStep;
    FOnStop    : TNotifyEvent;

    procedure DoStart(Sender:TObject);
    procedure DoStep(Sender:TObject);
    procedure DoStop(Sender:TObject);

    function GetAnimation:TSeriesAnimation;

    function GetDelay:Integer;
    function GetDrawEvery:Integer;
    function GetDuration:Integer;
    function GetLoop:TSeriesAnimationLoop;
    function GetSeries:TChartSeries;
    function GetStartAtMin:Boolean;
    function GetStartValue:Double;
    function GetSteps:Integer;

    procedure ReadSteps(Reader:TReader);

    procedure SetDelay(const Value:Integer);
    procedure SetDrawEvery(const Value:Integer);
    procedure SetDuration(const Value:Integer);
    procedure SetLoop(const Value:TSeriesAnimationLoop);
    procedure SetSeries(const Value:TChartSeries);
    procedure SetStartAtMin(const Value:Boolean);
    procedure SetStartValue(const Value:Double);
    procedure SetSteps(const Value:Integer);
    function Running: Boolean;
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure DoCreateAnimation; override;
    class function GetEditorClass: String; override;
    procedure SetAnimation(const Value:TTeeAnimation); override;
    procedure SetParentChart(const Value: TCustomAxisPanel); override;
    function Valid(const Value:TComponent):Boolean; override;
  public
    procedure Assign(Source:TPersistent); override;
    class function Description: String; override;
    procedure Execute; overload;
    procedure Execute(Sender:TObject); overload;
    class function LongDescription: String; override;

    property Animation:TSeriesAnimation read GetAnimation;
    property Steps:Integer read GetSteps write SetSteps; // deprecated, see Duration
  published
    property Delay:Integer read GetDelay write SetDelay stored False;
    property DrawEvery:Integer read GetDrawEvery write SetDrawEvery stored False;
    property Duration:Integer read GetDuration write SetDuration stored False; // Milliseconds
    property Loop:TSeriesAnimationLoop read GetLoop write SetLoop stored False;
    property Series:TChartSeries read GetSeries write SetSeries stored False;
    property StartAtMin:Boolean read GetStartAtMin write SetStartAtMin stored False;
    property StartValue:Double read GetStartValue write SetStartValue stored False;

    { events }
    property OnStart:TNotifyEvent read FOnStart write FOnStart;
    property OnStep:TSeriesAnimationStep read FOnStep write FOnStep;
    property OnStop:TNotifyEvent read FOnStop write FOnStop;
  end;

  TExpandAnimation=class(TTeeLegacyAnimation)
  private
    function GetAnimation:TCustomExpandAnimation;
    function GetExpandSpeed:Integer;
    procedure SetExpandSpeed(const Value:Integer);
    function GetSizeBy: Integer;
    function GetTarget: TChartClickedPartStyle;
    function GetTrigger: TAnimationTrigger;
    procedure SetSizeBy(const Value: Integer);
    procedure SetTarget(const Value: TChartClickedPartStyle);
    procedure SetTrigger(const Value: TAnimationTrigger);
  protected
    Procedure ChartMouseEvent(AEvent: TChartMouseEvent; Button: TMouseButton;
                              Shift: TShiftState; X, Y: Integer); override;
    procedure DoCreateAnimation; override;
    function Valid(const Value:TComponent):Boolean; override;
  public
    procedure Assign(Source:TPersistent); override;
    class Function Description:String; override;
    class Function LongDescription:String; override;

    property Animation:TCustomExpandAnimation read GetAnimation;
  published
    property SizeBy:Integer read GetSizeBy write SetSizeBy stored False;
    property Speed:Integer read GetExpandSpeed write SetExpandSpeed stored False;
    property Target:TChartClickedPartStyle read GetTarget write SetTarget stored False;
    property Trigger:TAnimationTrigger read GetTrigger write SetTrigger stored False;
  end;

  TRotate3DAnimation=class(TCustomAnimationChart)
  private
    IBackup : TView3DOptions;
    IBackupView3D : Boolean;
  protected
    function EndAnimation:Boolean; override;
    procedure NextFrame(const Fraction: Single); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    class function Description: String; override;
    function EditorClass: String; override;

    procedure StoreValue; override;
  end;

  TRandomPointsAnimation=class(TSeriesAnimation)
  private
    IRandom : TChartSeriesList;
  protected
    function AnimateSeries(const Fraction:Single; const ASeries:TChartSeries; AIndex:Integer):Boolean; override;
    procedure SetInitialValues(const ASeries:TChartSeries); override;
    procedure StoreSeries(const ASeries:TChartSeries); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    class Function Description:String; override;
    //Function EditorClass:String; override;
    procedure StoreValue; override;
  end;

  TLineAnimation=class(TCustomAnimationChart)
  private
    function ChangeAll(const Fraction:Single):Boolean;
  protected
    procedure NextFrame(const Fraction: Single); override;
  public
    class function Description: String; override;
    procedure Stop; override;
  end;

  TSeriesTransitionData=Array of Array of {$IFDEF FMX}TRectF{$ELSE}TRect{$ENDIF};

  TSeriesTransitionAnimation=class(TCustomAnimationChart)
  private
    FSource,
    FDest : TSeriesTransitionData;

    IFraction : Single;

    procedure GetBar(Sender:TCustomBarSeries; ValueIndex:Integer;
                     var Rect:{$IFDEF FMX}TRectF{$ELSE}TRect{$ENDIF});
    procedure Remember(var AData:TSeriesTransitionData);
    procedure SetEvent(const AEvent:TGetBarBoundsEvent);
  protected
    Procedure NextFrame(const Fraction:Single); override;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure After;
    procedure Before;
    class Function Description:String; override;
    procedure Stop; override;
    procedure StoreValue; override;
  end;

implementation

uses
  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Constants,
  {$ELSE}
  TeeProcs, TeeConst, {$IFDEF TEEOCX}TeeStore,{$ENDIF}
  {$ENDIF}
  Math;

{ TTeeAnimationTool }

function TTeeAnimationTool.GetChildOwner: TComponent;
begin
  result:=Self;
end;

procedure TTeeAnimationTool.ReadState(Reader: TReader);
var t : Integer;
    tmpAnim : TTeeAnimation;
begin
  if Assigned(IAnimate) and (not IKeepAnimate) and
     (not (csLoading in IAnimate.ComponentState))
     and (not (csAncestor in IAnimate.ComponentState)) then
          FreeAndNil(IAnimate);

  inherited;

  // Relink Animate and its Animations:

  if ComponentCount>0 then
  begin
    if Components[0] is TTeeAnimate then
       IAnimate:=TTeeAnimate(Components[0]);

    for t:=0 to ComponentCount-1 do
    begin
      if Components[t] is TTeeAnimation then
      begin
        tmpAnim:=TTeeAnimation(Components[t]);
        Animate.Animations.Add(tmpAnim);
      end;

      if Components[t] is TPropertyAnimation then
         TPropertyAnimation(Components[t]).Instance:=ParentChart;
    end;
  end;
end;

procedure TTeeAnimationTool.Stop;
begin
  if Running or Paused then
     Animate.Stop;
end;

function TTeeAnimationTool.Running:Boolean;
begin
  result:=Animate.Playing;
end;

function TTeeAnimationTool.Paused:Boolean;
begin
  result:=Animate.Paused;
end;

function TTeeAnimationTool.GetAnimate: TTeeAnimate;
begin
  if not Assigned(IAnimate) then
     IAnimate:=TTeeAnimate.Create(Self);

  result:=IAnimate;
end;

procedure TTeeAnimationTool.Play;
begin
  Animate.Play;
end;

procedure TTeeAnimationTool.Continue;
begin
  Animate.Continue;
end;

procedure TTeeAnimationTool.Pause;
begin
  Animate.Pause;
end;

function TTeeAnimationTool.GetAnimations: TAnimations;
begin
  result:=Animate.Animations;
end;

class function TTeeAnimationTool.Description: String;
begin
  result:=TeeMsg_Animation;
end;

class function TTeeAnimationTool.LongDescription: String;
begin
  result:=Description;
end;

function TTeeAnimationTool.GetLoop: Boolean;
begin
  result:=Animate.Loop;
end;

function TTeeAnimationTool.GetSpeed: Integer;
begin
  result:=Animate.Speed;
end;

procedure TTeeAnimationTool.SetLoop(const Value: Boolean);
begin
  Animate.Loop:=Value;
end;

procedure TTeeAnimationTool.SetSpeed(const Value: Integer);
begin
  Animate.Speed:=Value;
end;

procedure TTeeAnimationTool.SetAnimate(const Value: TTeeAnimate);
begin
  // ?
end;

Procedure TTeeAnimationTool.ChartEvent(AEvent:TChartToolEvent);
begin
  if AutoPlay and (not IPlayed) then
  begin
    IPlayed:=True;
    Play;
  end;
end;

type
  TTeeAnimateAccess=class(TTeeAnimate);

procedure TTeeAnimationTool.GetChildren(Proc: TGetChildProc;
  Root: TComponent);
begin
  inherited;
  TTeeAnimateAccess(Animate).GetChildren(Proc,Root);
end;

function TTeeAnimationTool.GetOnStop: TNotifyEvent;
begin
  result:=Animate.OnStop
end;

procedure TTeeAnimationTool.SetOnStop(const Value: TNotifyEvent);
begin
  Animate.OnStop:=Value;
end;

function TTeeAnimationTool.GetOnPlay: TNotifyEvent;
begin
  result:=Animate.OnPlay;
end;

procedure TTeeAnimationTool.SetOnPlay(const Value: TNotifyEvent);
begin
  Animate.OnPlay:=Value;
end;

{ TCustomExpandAnimation }

Procedure TCustomExpandAnimation.StoreValue;
var tmpItem : TSeriesPointer;
begin
  inherited;

  tmpItem:=GetPointer;

  if Assigned(tmpItem) then
  begin
    IOldSize.X:=tmpItem.HorizSize;
    IOldSize.Y:=tmpItem.VertSize;
  end;
end;

function TCustomExpandAnimation.GetPointer:TSeriesPointer;
var tmp : TCustomSeries;
begin
  result:=nil;

  if FPart.Part=cpSeriesPointer then
     if Assigned(FPart.ASeries) then
        if FPart.ASeries is TCustomSeries then
        begin
          tmp:=TCustomSeries(FPart.ASeries);
          result:=tmp.Pointer[FPart.PointIndex];
        end;
end;

function TCustomExpandAnimation.EndAnimation:Boolean;
var tmpItem : TSeriesPointer;
begin
  result:=inherited EndAnimation;

  tmpItem:=GetPointer;

  if Assigned(tmpItem) then
  begin
    tmpItem.HorizSize:=IOldSize.X;
    tmpItem.VertSize:=IOldSize.Y;
  end;
end;

procedure TCustomExpandAnimation.NextFrame(const Fraction: Single);
var tmpStep : Integer;
    tmpItem : TSeriesPointer;
begin
  tmpItem:=GetPointer;

  if Assigned(tmpItem) then
  begin
    tmpStep:=Round(FSizeBy*Fraction);

    tmpItem.HorizSize:=IOldSize.X+tmpStep;
    tmpItem.VertSize:=IOldSize.Y+tmpStep;
  end;
end;

function TCustomExpandAnimation.EditorClass: String;
begin
  result:='TExpandAnimationEdit';
end;

constructor TCustomExpandAnimation.Create(AOwner: TComponent);
begin
  inherited;

  Duration:=200;

  FTrigger:=atMouseOver;
  FTarget:=cpSeriesPointer;
  FSizeBy:=10;

  TwoWay:=True;
  
  ILastIndex:=-2;
end;

{ TTeeLegacyAnimation }

Constructor TTeeLegacyAnimation.Create(AOwner: TComponent);
begin
  inherited;

  IKeepAnimate:=True;

  if (not Assigned(Owner)) or (not (csLoading in Owner.ComponentState)) then
     DoCreateAnimation;
end;

Destructor TTeeLegacyAnimation.Destroy;
begin
  FreeAndNil(IAnimation);
  IAnimate.Free;
  inherited;
end;

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

  if (Operation=opInsert) and Valid(AComponent) then
    if IAnimation<>AComponent then
       if AComponent.Owner=Self then
          SetAnimation(AComponent as TTeeAnimation);
end;

procedure TTeeLegacyAnimation.ReadState(Reader: TReader);
var tmp : TTeeAnimation;
begin
  inherited;

  if (Animate.Animations.Count>0) and Valid(Animate.Animations[0]) then
  begin
    tmp:=Animate.Animations[0];

    if IAnimation<>tmp then
       SetAnimation(tmp);
  end
  else
    DoCreateAnimation;
end;

procedure TTeeLegacyAnimation.SetAnimation(const Value:TTeeAnimation);
begin
  if IAnimation<>Value then
  begin
    IAnimation.Free;

    IAnimation:=Value;

    if Assigned(IAnimation) then
       IAnimation.Animate:=Animate;
  end;
end;

{ TExpandAnimation }

procedure TExpandAnimation.DoCreateAnimation;
begin
  TCustomExpandAnimation.Create(Self);
end;

function TExpandAnimation.Valid(const Value:TComponent):Boolean;
begin
  result:=Value is TCustomExpandAnimation;
end;

procedure TExpandAnimation.Assign(Source: TPersistent);
begin
  if Source is TExpandAnimation then
  with TExpandAnimation(Source) do
  begin
    Self.SizeBy:=SizeBy;
    Self.Speed:=Speed;
    Self.Target:=Target;
    Self.Trigger:=Trigger;
  end;

  inherited;
end;

function TExpandAnimation.GetAnimation:TCustomExpandAnimation;
begin
  result:=IAnimation as TCustomExpandAnimation;
end;

function TExpandAnimation.GetExpandSpeed:Integer;
begin
  result:=IAnimation.Duration div 20;
end;

procedure TExpandAnimation.SetExpandSpeed(const Value:Integer);
begin
  IAnimation.Duration:=Value*20;
end;

Procedure TExpandAnimation.ChartMouseEvent(AEvent: TChartMouseEvent;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

var
  Part : TChartClickedPart;

  procedure StartAnimation;
  var tmp : TCustomExpandAnimation;
  begin
    if IAnimation.Playing=asStopped then
    begin
      tmp:=Animation;

      if (Trigger<>atMouseOver) or (tmp.ILastIndex<>Part.PointIndex) then
      begin
        tmp.FPart:=Part;
        tmp.ILastIndex:=Part.PointIndex;
        Animate.Play;
      end;
    end;
  end;

begin
  TCustomChart(ParentChart).CalcClickedPart(TeePoint(X,Y), Part);

  if Part.Part=Target then
  begin
     if (AEvent=cmeDown) and (Trigger=atMouseClick) then
        StartAnimation
     else
     if (AEvent=cmeMove) and (Trigger=atMouseOver) then
        StartAnimation;
  end
  else
    Animation.ILastIndex:=-2;
end;

class function TExpandAnimation.Description: String;
begin
  result:=TeeMsg_ExpandAnimation;
end;

class function TExpandAnimation.LongDescription: String;
begin
  result:=TeeMsg_ExpandAnimationDesc;
end;

function TExpandAnimation.GetSizeBy: Integer;
begin
  result:=Animation.SizeBy;
end;

function TExpandAnimation.GetTarget: TChartClickedPartStyle;
begin
  result:=Animation.Target;
end;

function TExpandAnimation.GetTrigger: TAnimationTrigger;
begin
  result:=Animation.Trigger;
end;

procedure TExpandAnimation.SetSizeBy(const Value: Integer);
begin
  Animation.SizeBy:=Value;
end;

procedure TExpandAnimation.SetTarget(const Value: TChartClickedPartStyle);
begin
  Animation.Target:=Value;
end;

procedure TExpandAnimation.SetTrigger(const Value: TAnimationTrigger);
begin
  Animation.Trigger:=Value;
end;

{ TTransformAnimation }

Constructor TTransformAnimation.Create(AOwner: TComponent);
begin
  inherited;

  IAxisID:=-1;

  FClip:=True;
  FUseBounds:=True;
  FTarget:=cpSeries;
  FMaxZoom:=300;
  IOldTransp:=-1;
end;

procedure TTransformAnimation.ReadAxis(Reader: TReader);
begin
  IAxisID:=Reader.ReadInteger;
end;

function TTransformAnimation.CalcChart:TCustomAxisPanel;
begin
  result:=FChart;

  if (not Assigned(result)) and Assigned(FSeries) then
     result:=FSeries.ParentChart;
end;

procedure TTransformAnimation.WriteAxis(Writer: TWriter);
begin
  Writer.WriteInteger(Axis.ParentChart.Axes.IndexOf(Axis));
end;

procedure TTransformAnimation.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('AxisID',ReadAxis,WriteAxis,Assigned(Axis));
end;

procedure TTransformAnimation.Loaded;
var tmp : TCustomAxisPanel;
begin
  inherited;

  if IAxisID<>-1 then
  begin
    tmp:=CalcChart;

    if Assigned(tmp) then
       Axis:=tmp.Axes[IAxisID];
  end;
end;

procedure TTransformAnimation.AssignTransform(const ATransform:TTransformAnimation);
begin
  with ATransform do
  begin
    Self.FFade:=FFade;
    Self.FMaxZoom:=FMaxZoom;
    Self.FRotateMin:=FRotateMin;
    Self.FRotateMax:=FRotateMax;
    Self.FScale:=FScale;
    Self.FTranslateStyle:=FTranslateStyle;
    Self.FZoomStyle:=FZoomStyle;
  end;
end;

procedure TTransformAnimation.Assign(Source: TPersistent);
begin
  if Source is TTransformAnimation then
  with TTransformAnimation(Source) do
  begin
    Self.Chart:=FChart;
    Self.Series:=FSeries;
    Self.Axis:=FAxis;

    Self.FClip:=FClip;
    Self.FUseBounds:=FUseBounds;
    Self.FTarget:=FTarget;
    Self.AssignTransform(TTransformAnimation(Source));
  end;

  inherited;
end;

function TTransformAnimation.EndAnimation:Boolean;
var t : Integer;
begin
  result:=inherited EndAnimation;

  if IOldClipFlag then
  begin
    if Assigned(Series) then
       Series.Marks.Clip:=IOldClip
    else
    if Assigned(IChart) then
    for t:=0 to IChart.SeriesCount-1 do
        IChart[t].Marks.Clip:=IOldClip;

    IOldClipFlag:=False;
  end;

  IOldTransp:=-1;

  if Assigned(ILastTransform) then
     ILastTransform.Enabled:=False;
end;

class function TTransformAnimation.Description: String;
begin
  result:='Transform';
end;

Destructor TTransformAnimation.Destroy;
begin
  Series:=nil;
  Chart:=nil;
  inherited;
end;

function TTransformAnimation.EditorClass: String;
begin
  result:='TTransformEditor';
end;

procedure TTransformAnimation.ChangeTransform(const Fraction:Single; const Transform:TTeeTransform; const R:TRect);
var tmp : TTeeTransform;
    x,y : Single;
    tmpV : Single;
begin
  tmp:=Transform;

  ILastTransform:=tmp;
  
  tmp.Enabled:=True;

  tmp.Bounds:=R;
  tmp.Clip:=FClip;

  x:=R.Right-R.Left;
  y:=R.Bottom-R.Top;

  tmp.Translate.x:=0;
  tmp.Translate.y:=0;

  tmp.Origin.X:=(R.Left+R.Right)*0.5;
  tmp.Origin.Y:=(R.Top+R.Bottom)*0.5;

  tmp.Scale.x:=1;
  tmp.Scale.y:=1;

  case FTranslateStyle of
    ttFromLeft,
    ttFromTopLeft,
    ttFromBottomLeft  : tmp.Translate.x:= -(1-Fraction)*x;

    ttFromRight,
    ttFromTopRight,
    ttFromBottomRight : tmp.Translate.x:=  (1-Fraction)*x;
  end;

  case FTranslateStyle of
    ttFromTop,
    ttFromTopLeft,
    ttFromTopRight    : tmp.Translate.y:= -(1-Fraction)*y;

    ttFromBottom,
    ttFromBottomLeft,
    ttFromBottomRight : tmp.Translate.y:=  (1-Fraction)*y;
  end;

  if (FScale=tsHorizontal) or (FScale=tsBoth) then
      tmp.Scale.x:=Fraction;

  if (FScale=tsVertical) or (FScale=tsBoth) then
      tmp.Scale.y:=Fraction;

  case FZoomStyle of
    tzIn    : begin
                tmpV:=Fraction;
                tmp.Scale.x:=tmpV;
                tmp.Scale.y:=tmpV;
              end;
    tzOut   : begin
                tmpV:=0.01*(MaxZoom-(MaxZoom-100)*Fraction);
                tmp.Scale.x:=tmpV;
                tmp.Scale.y:=tmpV;
              end;
  end;

  if RotateMin<>RotateMax then
  begin
    if RotateMax>RotateMin then
       tmp.Rotate:=RotateMin+Fraction*(RotateMax-RotateMin)
    else
       tmp.Rotate:=RotateMax+Fraction*(RotateMin-RotateMax);
  end;
end;

class function TCustomAnimationChart.IsValidOwner(const AObject:TObject):Boolean;
begin
  result:=AObject is TCustomAxisPanel;
end;

procedure TCustomAnimationChart.SetChart(const Value: TCustomAxisPanel);
begin
  if FChart<>Value then
  begin
    if Assigned(FChart) then
       FChart.RemoveFreeNotification(Self);

    FChart:=Value;

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

procedure DoInvalidate(const APanel:TCustomTeePanel);
begin
  if Assigned(APanel) then
  begin
    {$IFDEF FMX}
    APanel.Invalidate;
    {$ELSE}
    APanel.Draw; // <-- Problem: Multiple animations will force multiple redraws. Invalidate not working fine with VCL.
    {$ENDIF}
  end;
end;

type
  TShapeAccess=class(TTeeCustomShapeBrushPen);
  TCustomShapeAccess=class(TTeeCustomShape);
  TSeriesAccess=class(TChartSeries);
  TAxisAccess=class(TChartAxis);

procedure TTransformAnimation.NextFrame(const Fraction: Single);
var
  tmpDraw : TCustomTeePanel;

  function CalcFade:TTeeTransparency;
  var tmp : Single;
  begin
    if Fade=tfIn then
       tmp:=1.0-Fraction
    else
       tmp:=Fraction;

    result:=Max(IOldTransp,Round(tmp*100));
  end;

  procedure ChangeAxis(const AAxis:TChartAxis);
  var tmp : TTeeTransform;
      R   : TRect;
  begin
    tmp:=nil;

    if AAxis.Visible then
    begin
      if not Assigned(TAxisAccess(AAxis).Transform) then
         TAxisAccess(AAxis).Transform:=TTeeTransform.Create;

      tmp:=TAxisAccess(AAxis).Transform;
    end;

    if Assigned(tmp) then
    begin
      {  Do not use AxisRect, (is we do so: Grids will be clipped during animation)
      R:=TAxisAccess(AAxis).AxisRect;

      if AAxis.Labels then
         if AAxis.Horizontal then
            R.Bottom:=R.Bottom+AAxis.Texts.Format.Height;
      }

      R:=AAxis.ParentChart.ChartBounds;
      
      ChangeTransform(Fraction,tmp,R);

      if FFade<>tfNone then
      begin
        if IOldTransp=-1 then
           IOldTransp:=AAxis.Shape.Transparency;

        AAxis.Shape.Transparency:=CalcFade;
      end;

      tmpDraw:=AAxis.ParentChart;
    end;
  end;

  procedure ChangeSeries(const ASeries:TChartSeries);
  var tmp : TTeeTransform;
      R : TRect;
  begin
    tmp:=nil;

    if Target=cpSeries then
    begin
      if ASeries.Visible then
      begin
        if not Assigned(TSeriesAccess(ASeries).Transform) then
           TSeriesAccess(ASeries).Transform:=TTeeTransform.Create;

        tmp:=TSeriesAccess(ASeries).Transform;
      end
      else
      if ASeries.Marks.Visible then
      begin
        if not Assigned(TShapeAccess(ASeries.Marks).Transform) then
           TShapeAccess(ASeries.Marks).Transform:=TTeeTransform.Create;

        tmp:=TShapeAccess(ASeries.Marks).Transform;
      end;
    end;

    if Assigned(tmp) then
    begin
      if ASeries.UseAxis then
      begin
        R.Left:=ASeries.GetHorizAxis.IStartPos;
        R.Right:=ASeries.GetHorizAxis.IEndPos;
        R.Top:=ASeries.GetVertAxis.IStartPos;
        R.Bottom:=ASeries.GetVertAxis.IEndPos;
      end
      else
        R:=ASeries.ParentChart.ChartBounds;

      ChangeTransform(Fraction,tmp,R);

      if not IOldClipFlag then
      begin
        IOldClip:=ASeries.Marks.Clip;
        ASeries.Marks.Clip:=ASeries.UseAxis;
        IOldClipFlag:=True;
      end;

      if Fade<>tfNone then
      begin
        if IOldTransp=-1 then
           if Target=cpSeries then
              IOldTransp:=ASeries.Transparency
           else
              IOldTransp:=ASeries.Marks.Transparency;

        if Target=cpSeries then
           ASeries.Transparency:=CalcFade;

        if ASeries.Marks.Visible then
           ASeries.Marks.Transparency:=ASeries.Transparency;
      end;

      tmpDraw:=ASeries.ParentChart;
    end;
  end;

var t : Integer;
    tmp : TTeeCustomShape;
    tmpTrans : TTeeTransform;
begin
  // Protection: 
  if (Assigned(FChart) and (csDestroying in FChart.ComponentState)) or
     (Assigned(FSeries) and (csDestroying in FSeries.ComponentState)) then
       Exit;

  tmpDraw:=nil;
  tmp:=nil;

  case FTarget of
    cpSeries,
    cpSeriesMarks:
               if Assigned(Series) then
                  ChangeSeries(Series)
               else
               if Assigned(FChart) then
                  for t:=0 to FChart.SeriesCount-1 do
                      ChangeSeries(FChart[t]);

    cpLegend  : tmp:=(FChart as TCustomChart).Legend;
    cpTitle   : tmp:=(FChart as TCustomChart).Title;
    cpFoot    : tmp:=(FChart as TCustomChart).Foot;
    cpSubTitle: tmp:=(FChart as TCustomChart).SubTitle;
    cpSubFoot : tmp:=(FChart as TCustomChart).SubFoot;
    cpAxis    : if Assigned(FAxis) then
                  ChangeAxis(FAxis);

    cpAxisTitle : if Assigned(FAxis) then
                     tmp:=FAxis.Title;
  end;

  if Assigned(tmp) then
  begin
    if tmp.Visible then
    begin
      tmpDraw:=tmp.ParentChart;

      if not Assigned(TShapeAccess(tmp).Transform) then
         TShapeAccess(tmp).Transform:=TTeeTransform.Create;

      tmpTrans:=TShapeAccess(tmp).Transform;

      if UseBounds then
         ChangeTransform(Fraction,tmpTrans,tmp.ShapeBounds)
      else
         ChangeTransform(Fraction,tmpTrans,tmpDraw.ChartBounds);

      if Fade<>tfNone then
      begin
         if Target=cpLegend then
         begin
           if IOldTransp=-1 then
              IOldTransp:=TChartLegend(tmp).GlobalTransparency;

           TChartLegend(tmp).GlobalTransparency:=CalcFade;
         end
         else
         begin
           if IOldTransp=-1 then
              IOldTransp:=TCustomShapeAccess(tmp).Transparency;

           TCustomShapeAccess(tmp).Transparency:=CalcFade;
         end;
      end;
    end;
  end;

  DoInvalidate(tmpDraw);
end;

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

  if Operation=opRemove then
  begin
    if AComponent=FSeries then
       FSeries:=nil
    else
    if AComponent=FChart then
       FChart:=nil;

    if (IPlaying=asPlaying) and (not Assigned(Series)) then
       Stop;
  end;
end;

procedure TCustomAnimationChart.SetSeries(const Value: TChartSeries);
begin
  if FSeries<>Value then
  begin
    if Assigned(FSeries) then
       FSeries.RemoveFreeNotification(Self);

    FSeries:=Value;

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

    if (IPlaying=asPlaying) and (not Assigned(Series)) then
       Stop;
  end;
end;

{ TSeriesAnimation }

constructor TSeriesAnimation.Create(AOwner: TComponent);
begin
  inherited;
  FStartAtMin:=True;
end;

Destructor TSeriesAnimation.Destroy;
begin
  RemoveAllSeries(IBackup);
  IBackup.Free;

  inherited;
end;

function TSeriesAnimation.AnimateSeries(const Fraction:Single;
                                  const ASeries:TChartSeries; AIndex:Integer):Boolean;
var t,tt     : Integer;
    tmpBack,
    tmpList : TChartValueList;
    tmpGroups,
    tmpGroup : Integer;
    tmpFraction : Single;
begin
  result:=ASeries.Visible;

  if result then
  begin
    tmpList:=ASeries.MandatoryValueList;
    tmpBack:=IBackup[AIndex].MandatoryValueList;

    if DrawEvery=0 then
       for t:=0 to ASeries.Count-1 do
           tmpList.Value[t]:=Fraction*tmpBack.Value[t]
    else
    begin
      tmpGroups:=ASeries.Count div DrawEvery;

      tmpGroup:=Trunc(Fraction*tmpGroups);

      for t:=0 to tmpGroup-1 do
          for tt:=0 to DrawEvery-1 do
              tmpList.Value[DrawEvery*t+tt]:=tmpBack.Value[DrawEvery*t+tt];

      tmpFraction:=Fraction;

      if tmpGroup>0 then
         tmpFraction:=tmpFraction-(tmpGroup/tmpGroups);

      tmpFraction:=tmpFraction*tmpGroups;

      for tt:=0 to DrawEvery-1 do
          tmpList.Value[tmpGroup*DrawEvery+tt]:=tmpFraction*tmpBack.Value[tmpGroup*DrawEvery+tt];
    end;
  end;
end;

procedure TSeriesAnimation.NextFrame(const Fraction:Single);
var t : Integer;
    tmpRepaint : TCustomTeePanel;
begin
  tmpRepaint:=nil;

  if Assigned(Series) then
  begin
    if AnimateSeries(Fraction,Series,0) then
       tmpRepaint:=Series.ParentChart;
  end
  else
  begin
    if Assigned(Chart) then
    for t:=0 to Chart.SeriesCount-1 do
        if AnimateSeries(Fraction,Chart[t],t) then
           tmpRepaint:=Chart;
  end;

  DoInvalidate(tmpRepaint);

  if Assigned(IOnStep) then
     IOnStep(Self);
end;

type
  TCustomTeePanelAccess=class(TCustomTeePanel);

procedure TSeriesAnimation.StoreSeries(const ASeries:TChartSeries);
var tmpAxis  : TChartAxis;
    tmpSeries: TChartSeries;
begin
  tmpSeries:=TChartSeriesClass(ASeries.ClassType).Create(nil);
  IBackup.Add(tmpSeries);

  if ASeries.Visible then
  begin
    tmpSeries.AssignValues(ASeries);

    // Set non-automatic mandatory axis
    if ASeries.YMandatory then
       tmpAxis:=ASeries.GetVertAxis
    else
       tmpAxis:=ASeries.GetHorizAxis;

    if tmpAxis.Automatic then
    begin
      // Special case. Check axis has been calculated at least once:
      if tmpAxis.Maximum=tmpAxis.Minimum then
      begin
        TCustomTeePanelAccess(ASeries.ParentChart).DontShowImage:=True;
        try
          ASeries.ParentChart.Draw;
        finally
          TCustomTeePanelAccess(ASeries.ParentChart).DontShowImage:=False
        end;
      end;

      tmpSeries.Tag:=0;
    end
    else
       tmpSeries.Tag:=1;

    tmpAxis.Automatic:=False;

    if ASeries.YMandatory and (ASeries.VertAxis=aBothVertAxis) then
       ASeries.ParentChart.RightAxis.Automatic:=False
    else
    if (not ASeries.YMandatory) and (ASeries.HorizAxis=aBothHorizAxis) then
       ASeries.ParentChart.TopAxis.Automatic:=False;

    SetInitialValues(ASeries);

    IPrepared:=ASeries.ParentChart;
  end;
end;

procedure TSeriesAnimation.SetInitialValues(const ASeries:TChartSeries);
var tmpList  : TChartValueList;
    t        : Integer;
    tmpStart : Double;
begin
  // When the animation is "Inverted", it means the Series will start with its
  // current values, so no need to change them to their "start" values:
  if not Inverted then
  begin
    // Prepare the "start" initial value:
    tmpList:=ASeries.MandatoryValueList;

    if StartAtMin then
       if ASeries.YMandatory then
          tmpStart:=ASeries.MinYValue
       else
          tmpStart:=ASeries.MinXValue
    else
       tmpStart:=StartValue;

    // Clear values
    for t:=0 to ASeries.Count-1 do
        tmpList.Value[t]:=tmpStart;
  end;
end;

procedure TSeriesAnimation.RemoveAllSeries(const AList:TChartSeriesList);
var t : Integer;
begin
  if Assigned(AList) then
  begin
    for t:=0 to AList.Count-1 do
        {$IFDEF AUTOREFCOUNT}
        AList[t].DisposeOf;
        {$ELSE}
        AList[t].Free;
        {$ENDIF}

    AList.Clear;
  end;
end;

procedure TSeriesAnimation.StoreValue;

  procedure PrepareChart(const AChart:TCustomAxisPanel);
  var tmpItems : TLegendItems;
  begin
    // Set Legend custom
    tmpItems:=TCustomChart(AChart).Legend.Items;
    OldCustom:=tmpItems.Custom;

    if not OldCustom then
       if tmpItems.Count>0 then
          tmpItems.Custom:=True;
  end;

var t : Integer;
begin
  inherited;

  if Assigned(IBackup) then
     RemoveAllSeries(IBackup)
  else
     IBackup:=TChartSeriesList.Create;

  IPrepared:=nil;

  if Assigned(Series) then
     StoreSeries(Series)
  else
  if Assigned(Chart) then
     for t:=0 to Chart.SeriesCount-1 do
         StoreSeries(Chart[t]);

  if Assigned(IPrepared) then
     PrepareChart(IPrepared);

  if Assigned(IOnStart) then
     IOnStart(Self);
end;

function TSeriesAnimation.EndAnimation:Boolean;

  procedure SetInitialValues(const ASeries:TChartSeries; AIndex:Integer);
  var t : Integer;
      tmpList : TChartValueList;
      tmpBack : TChartValueList;
      tmpAxis : TChartAxis;
  begin
    if ASeries.Visible then
    begin
      tmpList:=ASeries.MandatoryValueList;
      tmpBack:=IBackup[AIndex].MandatoryValueList;

      for t:=0 to ASeries.Count-1 do
          tmpList.Value[t]:=tmpBack.Value[t];

      if ASeries.YMandatory then
         tmpAxis:=ASeries.GetVertAxis
      else
         tmpAxis:=ASeries.GetHorizAxis;

      tmpAxis.Automatic:=(IBackup[AIndex].Tag=0);

      if ASeries.YMandatory and (ASeries.VertAxis=aBothVertAxis) then
         ASeries.ParentChart.RightAxis.Automatic:=tmpAxis.Automatic
      else
      if (not ASeries.YMandatory) and (ASeries.HorizAxis=aBothHorizAxis) then
         ASeries.ParentChart.TopAxis.Automatic:=tmpAxis.Automatic;

      ASeries.EndUpdate;
      DoInvalidate(ASeries.ParentChart);
    end;
  end;

var tmpItems : TLegendItems;
    t : Integer;
begin
  result:=inherited EndAnimation;

  if Assigned(Series) then
     SetInitialValues(Series,0)
  else
  if Assigned(Chart) then
     for t:=0 to Chart.SeriesCount-1 do
         SetInitialValues(Chart[t],t);

  if Assigned(IPrepared) and (not (csDestroying in IPrepared.ComponentState)) then
  begin
    tmpItems:=TCustomChart(IPrepared).Legend.Items;
    tmpItems.Custom:=OldCustom;
  end;

  if Assigned(IOnStop) then
     IOnStop(Self);

  IDirection:=not IDirection;
end;

class function TSeriesAnimation.Description: String;
begin
  result:='Animate Series Values';
end;

function TSeriesAnimation.EditorClass: String;
begin
  result:='TSeriesAnimationEditor';
end;

{ TSeriesAnimationTool }

procedure TSeriesAnimationTool.DoCreateAnimation;
begin
  TSeriesAnimation.Create(Self);
end;

procedure TSeriesAnimationTool.Execute;
begin
  if not Running then
     Play;
end;

class function TSeriesAnimationTool.Description: String;
begin
  result:=TeeMsg_SeriesAnimTool;
end;

class function TSeriesAnimationTool.LongDescription: String;
begin
  result:=TeeMsg_SeriesAnimationDesc;
end;

procedure TSeriesAnimationTool.Execute(Sender:TObject);
begin
  Execute;
end;

procedure TSeriesAnimationTool.DoStep(Sender:TObject);
begin
  if Assigned(FOnStep) then
     FOnStep(Self,Animate.CurrentFrame);
end;

procedure TSeriesAnimationTool.DoStart(Sender:TObject);
begin
  if Assigned(FOnStart) then
     FOnStart(Self);
end;

procedure TSeriesAnimationTool.DoStop(Sender:TObject);
begin
  if Assigned(FOnStop) then
     FOnStop(Self);
end;

const
  FramesPerSec=60.0/1000;

function TSeriesAnimationTool.GetSteps:Integer;
begin
  result:=Max(1,Round(Duration*FramesPerSec));
end;

function TSeriesAnimationTool.GetAnimation:TSeriesAnimation;
begin
  result:=IAnimation as TSeriesAnimation;
end;

function TSeriesAnimationTool.GetDelay:Integer;
begin
  result:=Animation.StartTime;
end;

function TSeriesAnimationTool.GetDuration:Integer;
begin
  result:=Animation.Duration;
end;

procedure TSeriesAnimationTool.SetDelay(const Value:Integer);
begin
  Animation.StartTime:=Value;
end;

procedure TSeriesAnimationTool.SetDuration(const Value:Integer);
begin
  if Assigned(IAnimation) then
     Animation.Duration:=Value;
end;

procedure TSeriesAnimationTool.SetSteps(const Value:Integer);
begin
  Duration:=Round(Value/FramesPerSec);
end;

procedure TSeriesAnimationTool.ReadSteps(Reader:TReader);
begin
  Steps:=Reader.ReadInteger;
end;

procedure TSeriesAnimationTool.Assign(Source: TPersistent);
begin
  inherited;

  if Source is TSeriesAnimationTool then
     Animation.Assign(TSeriesAnimationTool(Source).Animation);
end;

procedure TSeriesAnimationTool.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Steps',ReadSteps,nil,False);
end;

class function TSeriesAnimationTool.GetEditorClass: String;
begin
  result:='TSeriesAnimToolEditor';
end;

function TSeriesAnimationTool.Running: Boolean;
begin
  result:=Assigned(Animate) and Animate.Playing;
end;

{ TSeriesAnimationTool }

function TSeriesAnimationTool.GetDrawEvery: Integer;
begin
  result:=Animation.DrawEvery;
end;

function TSeriesAnimationTool.GetLoop: TSeriesAnimationLoop;
begin
  result:=Animation.Loop;
end;

function TSeriesAnimationTool.GetSeries: TChartSeries;
begin
  result:=Animation.Series;
end;

function TSeriesAnimationTool.GetStartAtMin: Boolean;
begin
  result:=Animation.StartAtMin;
end;

function TSeriesAnimationTool.GetStartValue: Double;
begin
  result:=Animation.StartValue;
end;

procedure TSeriesAnimationTool.SetDrawEvery(const Value: Integer);
begin
  Animation.DrawEvery:=Value;
end;

procedure TSeriesAnimationTool.SetLoop(const Value: TSeriesAnimationLoop);
begin
  Animation.Loop:=Value;
end;

procedure TSeriesAnimationTool.SetSeries(const Value: TChartSeries);
begin
  if Assigned(IAnimation) then
     Animation.Series:=Value;
end;

procedure TSeriesAnimationTool.SetStartAtMin(const Value: Boolean);
begin
  if Assigned(IAnimation) then
     Animation.StartAtMin:=Value;
end;

procedure TSeriesAnimationTool.SetStartValue(const Value: Double);
begin
  Animation.StartValue:=Value;
end;

function TSeriesAnimationTool.Valid(const Value:TComponent):Boolean;
begin
  result:=Value is TSeriesAnimation;
end;

procedure TSeriesAnimationTool.SetAnimation(const Value:TTeeAnimation);
var tmp : TSeriesAnimation;
begin
  inherited;

  if Assigned(IAnimation) then
  begin
    tmp:=Animation;
    tmp.IOnStep:=DoStep;
    tmp.IOnStart:=DoStart;
    tmp.IOnStop:=DoStop;
  end;
end;

procedure TSeriesAnimationTool.SetParentChart(const Value: TCustomAxisPanel);
begin
  inherited;

  if Assigned(Animation) then
     Animation.Chart:=ParentChart;
end;

{ TRotate3DAnimation }

Constructor TRotate3DAnimation.Create(AOwner: TComponent);
begin
  inherited;
  IBackup:=TView3DOptions.Create(nil);
end;

class function TRotate3DAnimation.Description: String;
begin
  result:='Rotate Chart';
end;

Destructor TRotate3DAnimation.Destroy;
begin
  IBackup.Free;
  inherited;
end;

function TRotate3DAnimation.EditorClass: String;
begin
  result:='TRotate3DAnimEditor';
end;

procedure TRotate3DAnimation.StoreValue;
begin
  inherited;

  // Remember original 3D options and force non-orthogonal 3D:
  if Assigned(Chart) then
  begin
    IBackupView3D:=Chart.View3D;
    IBackup.Assign(Chart.View3DOptions);

    Chart.View3D:=True;
    Chart.View3DOptions.Orthogonal:=False;
  end;
end;

function TRotate3DAnimation.EndAnimation:Boolean;
begin
  result:=inherited EndAnimation;

  // Reset back original 3D options:
  if Assigned(Chart) then
  begin
    Chart.View3D:=IBackupView3D;
    Chart.View3DOptions.Orthogonal:=IBackup.Orthogonal;
    Chart.View3DOptions.RotationFloat:=IBackup.RotationFloat;
  end;
end;

procedure TRotate3DAnimation.NextFrame(const Fraction: Single);
var tmp : TCustomTeePanel;
begin
  inherited;

  // Try to obtain Chart:
  tmp:=Chart;
  if not Assigned(tmp) then
     if Assigned(Series) then
        tmp:=Series.ParentChart;

  if Assigned(tmp) then
  begin
    // Force non-orthogonal 3D:
    tmp.View3D:=True;
    tmp.View3DOptions.Orthogonal:=False;

    // Change Rotation:
    tmp.View3DOptions.RotationFloat:=IBackup.RotationFloat+(Fraction*360);
  end;
end;

{ TRandomPointsAnimation }

Constructor TRandomPointsAnimation.Create(AOwner: TComponent);
begin
  inherited;
  IRandom:=TChartSeriesList.Create;
end;

function TRandomPointsAnimation.AnimateSeries(const Fraction:Single; const ASeries: TChartSeries;
  AIndex: Integer):Boolean;
var t : Integer;
    tmpBackMand,
    tmpBackNoMand,
    tmpMand,
    tmpNoMand,
    tmpRand,
    tmpNoRand : TChartValueList;
begin
  // (No call to inherited)

  result:=ASeries.Visible;

  if result then
  begin
    tmpRand:=IRandom[AIndex].MandatoryValueList;
    tmpNoRand:=IRandom[AIndex].NotMandatoryValueList;

    tmpBackMand:=IBackup[AIndex].MandatoryValueList;
    tmpBackNoMand:=IBackup[AIndex].NotMandatoryValueList;

    tmpMand:=ASeries.MandatoryValueList;
    tmpNoMand:=ASeries.NotMandatoryValueList;

    for t:=0 to ASeries.Count-1 do
    begin
      tmpMand.Value[t]:=tmpBackMand.Value[t]+(1-Fraction)*(tmpRand.Value[t]-tmpBackMand.Value[t]);
      tmpNoMand.Value[t]:=tmpBackNoMand.Value[t]+(1-Fraction)*(tmpNoRand.Value[t]-tmpBackNoMand.Value[t]);
    end;
  end;
end;

class function TRandomPointsAnimation.Description: String;
begin
  result:=TeeMsg_RandomPoints;
end;

Destructor TRandomPointsAnimation.Destroy;
begin
  RemoveAllSeries(IRandom);
  IRandom.Free;
  inherited;
end;

procedure TRandomPointsAnimation.SetInitialValues(const ASeries: TChartSeries);
begin
  // Do not call inherited
end;

procedure TRandomPointsAnimation.StoreSeries(const ASeries: TChartSeries);
var tmpSeries : TChartSeries;
    t         : Integer;
    MandMin,
    NoMandMin,
    MandRange,
    NoMandRange : Double;
begin
  inherited;

  tmpSeries:=TChartSeriesClass(ASeries.ClassType).Create(nil);
  IRandom.Add(tmpSeries);

  if ASeries.Visible then
  begin
    tmpSeries.AssignValues(ASeries);

    MandRange:=4*ASeries.MandatoryValueList.Range;
    NoMandRange:=4*ASeries.NotMandatoryValueList.Range;

    MandMin:=ASeries.MandatoryValueList.MinValue;
    NoMandMin:=ASeries.NotMandatoryValueList.MinValue;

    for t:=0 to ASeries.Count-1 do
    begin
      tmpSeries.MandatoryValueList.Value[t]:=MandMin+MandRange*(Random(1000)-500)*0.001;
      tmpSeries.NotMandatoryValueList.Value[t]:=NoMandMin+NoMandRange*(Random(1000)-500)*0.001;
    end;
  end;
end;

procedure TRandomPointsAnimation.StoreValue;
begin
  RemoveAllSeries(IRandom);
  inherited;
end;

{ TLineAnimation }

class function TLineAnimation.Description: String;
begin
  result:=TeeMsg_LineAnimation;
end;

procedure TLineAnimation.Stop;
begin
  inherited;
  ChangeAll(1);
end;

type
  TLine=class(TCustomSeries);

function TLineAnimation.ChangeAll(const Fraction:Single):Boolean;

  procedure ChangeSeries(const ASeries:TCustomSeries);
  begin
    TLine(ASeries).DisplayLast:=Fraction;

    if TLine(ASeries).DrawStyle=dsSegments then
       TLine(ASeries).DrawStyle:=dsAll;

    result:=True;
  end;

var t : Integer;
begin
  result:=False;

  if Assigned(Series) and Series.Visible and (Series is TCustomSeries) then
     ChangeSeries(TCustomSeries(Series))
  else
  if Assigned(Chart) then
     for t:=0 to Chart.SeriesCount-1 do
         if Chart[t].Visible and (Chart[t] is TCustomSeries) then
            ChangeSeries(TCustomSeries(Chart[t]));
end;

procedure TLineAnimation.NextFrame(const Fraction: Single);
begin
  inherited;

  if ChangeAll(Fraction) then
     if Assigned(Series) then
        Series.Repaint
     else
     if Assigned(Chart) then
        Chart.Invalidate;
end;

{ TSeriesTransitionAnimation }

Constructor TSeriesTransitionAnimation.Create(AOwner: TComponent);
begin
  inherited;
end;

class Function TSeriesTransitionAnimation.Description:String;
begin
  result:=TeeMsg_SeriesTransition;
end;

function RectInterpolate(const A,B:{$IFDEF FMX}TRectF{$ELSE}TRect{$ENDIF};
                         const AFraction:Single):{$IFDEF FMX}TRectF{$ELSE}TRect{$ENDIF};
begin
  result.Left:=A.Left+{$IFNDEF FMX}Round{$ENDIF}((B.Left-A.Left)*AFraction);
  result.Top:=A.Top+{$IFNDEF FMX}Round{$ENDIF}((B.Top-A.Top)*AFraction);
  result.Right:=A.Right+{$IFNDEF FMX}Round{$ENDIF}((B.Right-A.Right)*AFraction);
  result.Bottom:=A.Bottom+{$IFNDEF FMX}Round{$ENDIF}((B.Bottom-A.Bottom)*AFraction);
end;

procedure TSeriesTransitionAnimation.GetBar(Sender: TCustomBarSeries;
  ValueIndex: Integer; var Rect: {$IFDEF FMX}TRectF{$ELSE}TRect{$ENDIF});
var tmp : Integer;
begin
  tmp:=Sender.SeriesIndex;

  if High(FSource)>=tmp then
     Rect:=RectInterpolate(FSource[tmp,ValueIndex],FDest[tmp,ValueIndex],IFraction);
end;

procedure TSeriesTransitionAnimation.NextFrame(const Fraction: Single);
begin
  inherited;
  IFraction:=Fraction;
  Chart.Invalidate;
end;

procedure TSeriesTransitionAnimation.Before;
begin
  Remember(FSource);
end;

procedure TSeriesTransitionAnimation.Remember(var AData:TSeriesTransitionData);
var t,tt : Integer;
    s : TChartSeries;
begin
  if Assigned(Chart) then
  begin
    SetLength(AData,Chart.SeriesCount);

    for t:=0 to Chart.SeriesCount-1 do
    begin
      s:=Chart[t];

      if s is TCustomBarSeries then
      begin
        SetLength(AData[t],s.Count);

        for tt:=0 to s.Count-1 do
            AData[t,tt]:=TCustomBarSeries(s).CalcBarBounds(tt);
      end
      else
        AData[t]:=nil;
    end;
  end;
end;

type
  TCustomBarAccess=class(TCustomBarSeries);

procedure TSeriesTransitionAnimation.SetEvent(const AEvent:TGetBarBoundsEvent);
var s : TChartSeries;
    t : Integer;
begin
  for t:=0 to Chart.SeriesCount-1 do
  begin
    s:=Chart[t];

    if s is TCustomBarSeries then
       TCustomBarAccess(s).FOnCalcBounds:=AEvent;
  end;
end;

procedure TSeriesTransitionAnimation.Stop;
begin
  inherited;
  SetEvent(nil);

  FDest:=nil;
  FSource:=nil;
end;

procedure TSeriesTransitionAnimation.StoreValue;

  // Force chart to repaint to refresh all internal series coordinates, etc.
  // Note: Chart.Draw cannot be used here for FMX, as it'll ShowImage the
  // internal canvas buffer bitmap to left-top 0,0 (instead of chart parent offset)
  procedure RedrawChart;
  begin
    Chart.TeeCreateBitmap.{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};
  end;

begin
  inherited;

  SetEvent(nil);

  RedrawChart;
  Remember(FDest);

  SetEvent(GetBar);

  IFraction:=0;
  RedrawChart;
end;

procedure TSeriesTransitionAnimation.After;
begin
  Animate.Play(True);
end;

Const T:Array[0..2] of TTeeCustomAnimationClass=(
                         TExpandAnimation,
                         TTeeAnimationTool,
                         TSeriesAnimationTool
                     );

initialization
  TeeActivateGroup;

  RegisterTeeAnimations(T);

  TeeRegisterAnimation(TCustomExpandAnimation);
  TeeRegisterAnimation(TTransformAnimation);
  TeeRegisterAnimation(TSeriesAnimation);
  TeeRegisterAnimation(TRotate3DAnimation);
  TeeRegisterAnimation(TRandomPointsAnimation);
  TeeRegisterAnimation(TLineAnimation);
  TeeRegisterAnimation(TSeriesTransitionAnimation);
finalization
  UnRegisterTeeAnimations(T);
end.
