{**********************************************}
{   TeeChart - TSubChartTool                   }
{   Copyright (c) 2006-2020 by Steema Software }
{**********************************************}
unit TeeSubChart;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows, Messages,
  {$ENDIF}
  {$ENDIF}
  SysUtils, Classes,

  {$IFDEF D26}
  System.Generics.Collections,
  {$ENDIF}
  
  {$IFDEF FMX}
  FMX.Types, System.UITypes,

  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}

  {$IFDEF D17}
  System.UIConsts,
  {$ENDIF}
  {$ELSE}
  Graphics, Controls,
  {$ENDIF}

  {$IFDEF D6}
  Types,
  {$ENDIF}

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

type
  TSubChart=class(TCollectionItem)
  private
    FAllowDrag   : Boolean;
    FAllowResize : Boolean;
    FChart       : TChart;

    FOnDragged   : TNotifyEvent;
    FOnDragging  : TNotifyEvent;
    FOnResized   : TNotifyEvent;
    FOnResizing  : TNotifyEvent;

    P            : TPoint;
    IDrag        : Boolean;
    IResize      : Boolean;
    IEdge        : Integer;
    ITempRect    : TRect;
    ITool        : TTeeCustomTool;

    function GetBounds:TRect;
    function GetChart:TChart;
    function GetHeight: Integer;
    function GetLeft: Integer;
    function GetTop: Integer;
    function GetWidth: Integer;
    function GetResizing:Boolean;
    procedure ReadChart(Stream: TStream);
    procedure SetChart(const Value: TChart);
    procedure SetHeight(const Value: Integer);
    procedure SetLeft(const Value: Integer);
    procedure SetTop(const Value: Integer);
    procedure SetWidth(const Value: Integer);
    procedure WriteChart(Stream: TStream);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    Constructor Create(Collection:TCollection); overload; override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    property Bounds:TRect read GetBounds;
    Function Clicked(x,y:Integer):Boolean;
    Function ClickedEdge(x,y:Integer):Integer;
    property Dragging:Boolean read IDrag;
    property Resizing:Boolean read GetResizing;
  published
    property AllowDrag:Boolean read FAllowDrag write FAllowDrag default False;
    property AllowResize:Boolean read FAllowResize write FAllowResize default False;

    property Chart:TChart read GetChart write SetChart stored False;
    property Height:Integer read GetHeight write SetHeight;
    property Left:Integer read GetLeft write SetLeft;
    property Top:Integer read GetTop write SetTop;
    property Width:Integer read GetWidth write SetWidth;

    property OnDragged:TNotifyEvent read FOnDragged write FOnDragged;
    property OnDragging:TNotifyEvent read FOnDragging write FOnDragging;
    property OnResized:TNotifyEvent read FOnResized write FOnResized;
    property OnResizing:TNotifyEvent read FOnResizing write FOnResizing;
  end;

  TSubChartTool=class;

  TChartCollection=class(TOwnedCollection)
  private
    IOwner : TSubChartTool;

    Function Get(Index:Integer):TSubChart;
    function GetLast:TSubChart;
    Procedure Put(Index:Integer; Const Value:TSubChart);
  protected
    {$IFDEF D6}
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
    {$ELSE}
    procedure Update(Item: TCollectionItem); override;
    {$ENDIF}
  public
    function AddChart(const AName:String=''):TChart;
    function Clone(const AChart:TChart):TSubChart;

    property Last:TSubChart read GetLast;
    property Items[Index:Integer]:TSubChart read Get write Put; default;
  end;

  TSubChartEvent=procedure(Sender:TSubChartTool; Item:TSubChart) of object;

  {$IFNDEF D6}
  TCollectionNotification=(cnAdded, cnDeleting);
  {$ENDIF}

  TSubChartTool=class(TTeeCustomTool)
  private
    FCharts  : TChartCollection;

    FOnAdded : TSubChartEvent;
    FOnDeleting : TSubChartEvent;
    FIsScroller : Boolean;

    IToDestroy : Array of TCustomChart;

    procedure AddToDestroy(const AChart:TCustomChart);
    procedure DestroyPending;
    function GetCursor:TCursor;
    procedure Notify(Item:TCollectionItem; Action:TCollectionNotification);
    procedure SetCharts(const Value: TChartCollection);
    procedure SetCursor(const Value:TCursor);
  protected
    Procedure ChartEvent(AEvent:TChartToolEvent); override;
    Procedure ChartMouseEvent( AEvent: TChartMouseEvent;
                               Button:TMouseButton;
                               Shift: TShiftState; X, Y: Integer); override;
    Procedure ChartDragDropEvent(AEvent: TChartDragDropEvent; Source: TObject;
      X, Y: Integer; State: TDragState; var Accept: Boolean;
      var DragObject: TDragObject); override;
    procedure ChartKeyEvent(AEvent: TChartKeyEvent; var Key: Word;
      var Key1: Char; Shift: TShiftState); override;

    class Function GetEditorClass:String; override;
    procedure Loaded; override;
    procedure SetParentChart(const Value: TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    class Function Description:String; override;
    procedure GridLayout(const AColumns:Integer=0); // AColumns=0 -> Automatic
    class Function LongDescription:String; override;
  published
    property Active;
    property Charts:TChartCollection read FCharts write SetCharts;
    property Cursor:TCursor read GetCursor write SetCursor default crDefault;
    property IsScroller:Boolean read FIsScroller write FIsScroller;

    // events
    property OnAdded:TSubChartEvent read FOnAdded write FOnAdded;
    property OnDeleting:TSubChartEvent read FOnDeleting write FOnDeleting;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Themes, FMXTee.Constants, FMXTee.ProConstants
  {$ELSE}
  TeeThemes, TeeProCo, TeeConst
  {$ENDIF};

type
  TInnerChart=class(TChart)
  private
    {$IFDEF D18}[weak]{$ENDIF} IParent : TSubChart;
  protected
    procedure ReadState(Reader: TReader); override;

    {$IFNDEF FMX}
    {$IFNDEF LCL}
    Function GetParentComponent: TComponent; override;
    Function HasParent:Boolean; override;
    {$ENDIF}
    {$ENDIF}
    procedure SetParentComponent(AParent: TComponent); override;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure Invalidate; override;

    {$IFDEF FMX}
    Function GetParentComponent: TComponent; override;
    Function HasParent:Boolean; override;
    procedure SetBounds(X, Y, AWidth, AHeight: Single); override;
    {$ELSE}
    {$IFDEF LCL}
    Function GetParentComponent: TComponent; override;
    Function HasParent:Boolean; override;
    {$ENDIF}
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    {$ENDIF}
  published
    property BevelOuter default bvNone;
    property Color default clNone;
    property MarginBottom default 0;
    property MarginLeft default 0;
    property MarginRight default 0;
    property MarginTop default 0;

    property OnDragDrop;
  end;

{ TInnerChart }

type
  TShapeAccess=class(TTeeCustomShape);

Constructor TInnerChart.Create(AOwner:TComponent);

  procedure HideShape(const AShape:TTeeCustomShape);
  begin
    AShape.Hide;
    TShapeAccess(AShape).DefaultVisible:=False;
  end;

begin
  inherited;

  HideShape(Legend);

  HideShape(Title);
  HideShape(Foot);
  HideShape(SubTitle);
  HideShape(SubFoot);

  MarginLeft:=0;
  MarginTop:=0;
  MarginRight:=0;
  MarginBottom:=0;

  BevelOuter:=bvNone;
  Color:=clNone;

  {$IFNDEF FMX}
  TabStop:=False;
  {$ENDIF}
end;

procedure TInnerChart.Invalidate;
begin
  if AutoRepaint then
     if Assigned(IParent) and Assigned(IParent.ITool) then
        IParent.ITool.Repaint
     else
        inherited;
end;

function TInnerChart.HasParent: Boolean;
begin
  result:=True;
end;

procedure TInnerChart.ReadState(Reader: TReader);
begin
  inherited;

  if Reader.Parent is TSubChartTool then
     SetParentComponent(TSubChartTool(Reader.Parent));
end;

function TInnerChart.GetParentComponent: TComponent;
begin
  result:=IParent.ITool;
end;

procedure TInnerChart.SetParentComponent(AParent: TComponent);
begin
  if Assigned(AParent) and (AParent is TSubChartTool) then
     if (not Assigned(IParent)) or
        (IParent.ITool<>AParent) or
        (Assigned(IParent) and (IParent.FChart<>Self)) then
     begin
       Self.IParent.ITool:=TSubChartTool(AParent);

       if IParent.FChart<>Self then
       begin
         FreeAndNil(IParent.FChart);
         IParent.FChart:=Self;
       end;
     end;
end;

{ TSubChart }

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

  FCharts:=TChartCollection.Create(Self,TSubChart);
  FCharts.IOwner:=Self;
  FIsScroller:=False;
end;

procedure TSubChartTool.DestroyPending;
var t : integer;
begin
  for t:=0 to Length(IToDestroy)-1 do
      IToDestroy[t].Free;

  IToDestroy:=nil;
end;

Destructor TSubChartTool.Destroy;
begin
  FCharts.Free;
  DestroyPending;

  inherited;
end;

procedure TSubChartTool.Assign(Source: TPersistent);
begin
  if Source is TSubChartTool then
  with TSubChartTool(Source) do
  begin
    Self.Charts:=Charts;
  end;

  inherited;
end;

type
  TChartAccess=class(TInnerChart);

procedure TSubChartTool.ChartDragDropEvent(AEvent: TChartDragDropEvent; Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean; var DragObject: TDragObject);
var t : Integer;
    {$IFDEF D20}
    {$IFDEF FMX}
    Operation : TDragOperation;
    {$ENDIF}
    {$ENDIF}
begin
  if Active then
  begin

    for t:=0 to Charts.Count-1 do
    with Charts[t] do
    if Chart.Visible then
    begin
      if {$IFDEF FMX}PointInRect{$ELSE}PtInRect{$ENDIF}(Bounds,TeePoint(X,Y)) then
      begin
        Chart.Canvas.ReferenceCanvas:=Self.ParentChart.Canvas.ReferenceCanvas;

        case AEvent of
          cdeOver: TChartAccess(FChart).DragOver({$IFDEF FMX}DragObject, TeePoint(X,Y),{$ELSE}Source, X, Y, State,{$ENDIF}
                     {$IFDEF FMX}{$IFDEF D20}Operation{$ELSE}Accept{$ENDIF}{$ELSE}Accept{$ENDIF});
          cdeDrop: TChartAccess(FChart).DragDrop({$IFDEF FMX}DragObject, TeePoint(X,Y){$ELSE}Source, X, Y {$ENDIF});

          {$IFNDEF FMX}
          cdeStart: TChartAccess(FChart).DoStartDrag(DragObject);
          cdeEnd: TChartAccess(FChart).DoEndDrag(Source,X,Y);
          {$ENDIF}
        end;
      end;
    end;
  end
  else inherited;
end;

type
  TCustomTeePanelAccess=class(TCustomTeePanel);

procedure TSubChartTool.ChartEvent(AEvent: TChartToolEvent);

  {$IFNDEF FMX}
  procedure ApplyMetaScale(var R:TRect);
  var tmp : TRect;
      tmpR : TRect;
      tmpW,
      tmpH,
      tmpX,
      tmpY : Single;
  begin
    tmp:=ParentChart.ChartPrintRect;
    tmpR:=ParentChart.GetRectangle;

    tmpW:=(tmpR.Right-tmpR.Left);
    tmpX:=(tmp.Right-tmp.Left)/tmpW;

    R.Left:=Round(R.Left*tmpX);
    R.Right:=Round(R.Right*tmpX);

    tmpH:=(tmpR.Bottom-tmpR.Top);
    tmpY:=(tmp.Bottom-tmp.Top)/tmpH;

    R.Top:=Round(R.Top*tmpY);
    R.Bottom:=Round(R.Bottom*tmpY);
  end;
  {$ENDIF}

var t : Integer;
    tmp : TCanvas;
    tmpR : TRect;

    {$IFNDEF FMX}
    tmpScale : Boolean;
    {$ENDIF}
begin
  inherited;

  if Active and (AEvent=cteAfterDraw) and Assigned(ParentChart) then
  begin
    tmp:=ParentChart.Canvas.ReferenceCanvas;

    {$IFNDEF FMX}
    tmpScale:=ParentChart.Printing and
              ParentChart.Canvas.Metafiling and
              TCustomTeePanelAccess(ParentChart).IsPrinterCanvas;
    {$ENDIF}

    for t:=0 to Charts.Count-1 do
    with Charts[t] do
    if Chart.Visible then
    begin
      Chart.BufferedDisplay:=False;

      tmpR:=Bounds;
      OffsetRect(tmpR,ParentChart.ChartBounds.Left,
                      ParentChart.ChartBounds.Top);

      {$IFNDEF FMX}
      if tmpScale then
         ApplyMetaScale(tmpR);
      {$ENDIF}

      Chart.Draw(tmp,tmpR);
    end;
  end;
end;

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

var idChart : Integer;

  Function GuessEdgeCursor:Boolean;

    procedure TrySet(ACursor:TCursor);
    begin
      Cursor:=ACursor;
      result:=True;
    end;

  begin
    result:=False;

    if Charts[idChart].AllowResize then
      case Charts[idChart].ClickedEdge(x,y) of
        0,2: TrySet(crSizeWE);
        1,3: TrySet(crSizeNS);
        5,6: TrySet(crSizeNESW);
        4,7: TrySet(crSizeNWSE);
      end;

    if not result then
       if Charts[idChart].AllowDrag then
          if Charts[idChart].Clicked(x,y) then
          begin
            Cursor:=crHandPoint;
            Result:=True;
          end;
  end;

  procedure StartResizing;
  var tmpX, tmpY: TCoordinate;
  begin
    if (Charts[idChart].IEdge=2) or (Charts[idChart].IEdge=6) or (Charts[idChart].IEdge=7) then
       tmpX:=X-Charts[idChart].Bounds.Right
    else
       tmpX:=X-Charts[idChart].Bounds.Left;

    if (Charts[idChart].IEdge=3) or (Charts[idChart].IEdge=5) or (Charts[idChart].IEdge=7) then
       tmpY:=Y-Charts[idChart].Bounds.Bottom
    else
       tmpY:=Y-Charts[idChart].Bounds.Top;

    Charts[idChart].P:=TeePoint(tmpX, tmpY);
    Charts[idChart].Chart.CancelMouse:=True;
    Charts[idChart].IResize:=True;
  end;

  procedure StartDragging;
  begin
    Charts[idChart].P:=TeePoint(X-Charts[idChart].Bounds.Left, Y-Charts[idChart].Bounds.Top);

    Charts[idChart].IDrag:=True;
    Charts[idChart].Chart.CancelMouse:=True;
  end;

  procedure ChangeLeftTop(const ALeft,ATop:TCoordinate);
  var tmpW : Integer;
      tmpH : Integer;
  begin
    tmpW:=Charts[idChart].Width;
    tmpH:=Charts[idChart].Height;

    Charts[idChart].Left:={$IFDEF FMX}Round{$ENDIF}(ALeft);
    Charts[idChart].Top:={$IFDEF FMX}Round{$ENDIF}(ATop);

    Charts[idChart].Width:=tmpW;
    Charts[idChart].Height:=tmpH;

    {$IFDEF FMX}
    Repaint;
    {$ENDIF}
  end;

  procedure TryDrag;
  begin
    if (X<>Charts[idChart].P.X) or (Y<>Charts[idChart].P.Y) then
    begin
      ChangeLeftTop(X-Charts[idChart].P.X,Y-Charts[idChart].P.Y);

      if Assigned(Charts[idChart].FOnDragging) then
         Charts[idChart].FOnDragging(Self);
    end;
  end;

  procedure DoResize;
  var
    tmpR : TRect;

    procedure ChangeLeft;
    begin
      tmpR.Left:=Min(tmpR.Right-3,X-Charts[idChart].P.X);
    end;

    procedure ChangeTop;
    begin
      tmpR.Top:=Min(tmpR.Bottom-3,Y-Charts[idChart].P.Y);
    end;

    procedure ChangeRight;
    begin
      tmpR.Right:=Max(tmpR.Left+3,X-Charts[idChart].P.X);
    end;

    procedure ChangeBottom;
    begin
      tmpR.Bottom:=Max(tmpR.Top+3,Y-Charts[idChart].P.Y);
    end;

  begin
    tmpR:=Charts[idChart].Bounds;

    case Charts[idChart].IEdge of
      0: ChangeLeft;
      1: ChangeTop;
      2: ChangeRight;
      3: ChangeBottom;
      4: begin
           ChangeLeft;
           ChangeTop;
         end;
      5: begin
           ChangeLeft;
           ChangeBottom;
         end;
      6: begin
           ChangeRight;
           ChangeTop;
         end;
      7: begin
           ChangeRight;
           ChangeBottom;
         end;
    end;

    Charts[idChart].Chart.BoundsRect:=tmpR;

    Repaint;

    if Assigned(Charts[idChart].FOnResizing) then
       Charts[idChart].FOnResizing(Self);
  end;

  procedure StopDrag;
  begin
    if Assigned(Charts[idChart].FOnDragged) then
       Charts[idChart].FOnDragged(Self);

    Charts[idChart].IDrag:=False;
  end;

  procedure StopResize;
  begin
    if Assigned(Charts[idChart].FOnResized) then
       Charts[idChart].FOnResized(Self);

    Charts[idChart].IEdge:=-1;
    Charts[idChart].IResize:=False;
  end;

var t : Integer;
begin
  if Active then
  begin
    for t:=0 to Charts.Count-1 do
    with Charts[t] do
    if Chart.Visible then
    begin
      idChart:=t;

      if {$IFDEF FMX}PointInRect{$ELSE}PtInRect{$ENDIF}(Bounds,TeePoint(X,Y)) or IResize or IDrag then
      begin
        Chart.Canvas.ReferenceCanvas:=Self.ParentChart.Canvas.ReferenceCanvas;

        {$IFDEF FMX}
        Chart.SetNewScene(Self.ParentChart.Scene);
        {$ENDIF}

        case AEvent of
          cmeDown:
          begin
             if (not AllowDrag) then
               TChartAccess(FChart).MouseDown(Button,Shift,X,Y);

             if Button={$IFDEF FMX}TMouseButton.{$ENDIF}mbLeft then
             begin
               if AllowResize then
                  IEdge:=ClickedEdge(x,y)
               else
                  IEdge:=-1;

               if IEdge<>-1 then
                  StartResizing
               else
               if AllowDrag and Clicked(x,y) then
                  StartDragging;
             end;
          end;
          cmeUp:
          begin
             if IDrag then
                 StopDrag
             else
             begin
               if (not AllowDrag) then
                 TChartAccess(FChart).MouseUp(Button,Shift,X,Y);

               if (IEdge<>-1) or IResize then
                  StopResize;
             end;
          end;
          cmeMove:
          begin
             if IDrag then
                TryDrag
             else
             begin
               if (not AllowDrag) then
                 TChartAccess(FChart).MouseMove(Shift,X,Y);

               if (IEdge<>-1) or IResize then
               begin
                 {$IFNDEF FMX}
                 Charts[idChart].Chart.AutoSize:=False;
                 {$ENDIF}

                 DoResize;
               end
               else
               if Assigned(ParentChart) then
                  FChart.CancelMouse:=GuessEdgeCursor;
             end;
          end;
        end;

        Self.ParentChart.CancelMouse:= FChart.CancelMouse or FChart.Zoom.Active
           or FChart.Panning.Active;

        if Self.ParentChart.CancelMouse then
           break;
      end;
    end;
  end
  else inherited;
end;

procedure TSubChartTool.ChartKeyEvent(AEvent: TChartKeyEvent;
                     var Key: Word; var Key1: Char; Shift: TShiftState);
var t : Integer;
    {$IFDEF FPC}
    WideKey : WideChar;
    {$ENDIF}
begin
  if Active then
  begin
    for t:=0 to Charts.Count-1 do
      with Charts[t] do
      if Chart.Visible then
      begin
        Chart.Canvas.ReferenceCanvas:=Self.ParentChart.Canvas.ReferenceCanvas;

        case AEvent of
          ckeDown: begin
                     {$IFDEF FPC}
                     {$IFDEF LCL}
                     TChartAccess(FChart).KeyDown(Key, {$IFDEF FMX}Key1,{$ENDIF} Shift);
                     {$ELSE}
                     WideKey:=Key1;
                     TChartAccess(FChart).KeyDown(Key, WideKey, Shift);
                     {$ENDIF}
                     {$ELSE}
                     TChartAccess(FChart).KeyDown(Key, {$IFDEF FMX}Key1,{$ENDIF} Shift);
                     {$ENDIF}
                   end;

            ckeUp: begin
                     {$IFDEF FPC}
                     {$IFDEF LCL}
                     TChartAccess(FChart).KeyUp(Key, {$IFDEF FMX}Key1,{$ENDIF} Shift);
                     {$ELSE}
                     WideKey:=Key1;
                     TChartAccess(FChart).KeyUp(Key, WideKey, Shift);
                     {$ENDIF}
                     {$ELSE}
                     TChartAccess(FChart).KeyUp(Key, {$IFDEF FMX}Key1,{$ENDIF} Shift);
                     {$ENDIF}
                   end;

          {$IFNDEF FMX}
         ckePress: TChartAccess(FChart).KeyPress(Key1);
          {$ENDIF}
        end;
      end;
  end
  else
    inherited;
end;

function TSubChart.GetChart:TChart;
begin
  if not Assigned(FChart) then
  begin
    //Be careful! Assigning the FChart.Parent property causes undesired
    //behaviour when assigning tools to the subchart.
    if Assigned(ITool) then FChart:=TInnerChart.Create(ITool.ParentChart)
                       else FChart:=TInnerChart.Create(nil);

    FChart.BoundsRect:=ITempRect;
    TInnerChart(FChart).IParent:=Self;

    if Assigned(ITool) then
       ITool.Repaint;
  end;

  result:=FChart;
end;

function TSubChart.GetResizing: Boolean;
begin
  result:=IEdge=-1;
end;

class function TSubChartTool.Description: String;
begin
  result:=TeeMsg_SubChartTool;
end;

function EqualChart(const A,B:TCustomChart):Boolean;
var SA,SB : TMemoryStream;
begin
  SA:=TMemoryStream.Create;
  SB:=TMemoryStream.Create;
  try
    SA.WriteComponent(A);
    SB.WriteComponent(B);

    result:=(SA.Size=SB.Size) and CompareMem(SA.Memory,SB.Memory,SA.Size);
  finally
    SB.Free;
    SA.Free;
  end;
end;

procedure TSubChartTool.Loaded;
begin
  inherited;
  DestroyPending;
end;

procedure TSubChartTool.AddToDestroy(const AChart:TCustomChart);
var l : Integer;
begin
  l:=Length(IToDestroy);
  SetLength(IToDestroy,l+1);
  IToDestroy[l]:=AChart;
end;

procedure TSubChartTool.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
  if Item is TSubChart then
     if (Action=cnAdded) and Assigned(FOnAdded) then
        FOnAdded(Self,TSubChart(Item))
     else
     if (Action=cnDeleting) and Assigned(FOnDeleting) then
        FOnDeleting(Self,TSubChart(Item));
end;

class function TSubChartTool.GetEditorClass: String;
begin
  result:='TSubChartEditor';
end;

procedure TSubChartTool.GridLayout(const AColumns: Integer);

  function TitleHeight(const ATitle:TChartTitle): TCoordinate;
  begin
    if ATitle.Visible and (ATitle.Caption<>'') then
    begin
      if ATitle.Height=0 then
         ATitle.ParentChart.Draw;

      result:=ATitle.TitleRect.Bottom-ATitle.TitleRect.Top;
    end
    else
      result:=0;
  end;

var t,
    tmp,
    x,y,
    tmpCols,
    tmpRows : Integer;

    tmpW,
    tmpH,
    tmpX,
    tmpY,
    tmpLeft,
    tmpTop,
    tmpMarginX,
    tmpMarginY,
    tmpTitle,
    tmpFooter,
    tmpRowHeight : TCoordinate;

    tmpChart : TCustomChart;
begin
  // Split sub charts
  tmp:=Charts.Count;

  if AColumns<=0 then
     tmpCols:=Round(Sqrt(tmp))
  else
     tmpCols:=AColumns;

  tmpRows:=(tmp div tmpCols);

  if tmp>(tmpCols*tmpRows) then
     Inc(tmpRows);

  if ParentChart is TCustomChart then
  begin
    tmpTitle:=TitleHeight(TCustomChart(ParentChart).Title);
    tmpFooter:=TitleHeight(TCustomChart(ParentChart).Foot);
  end
  else
  begin
    tmpTitle:=0;
    tmpFooter:=0;
  end;

  tmpRowHeight:=(ParentChart.Height-tmpTitle-tmpFooter) {$IFDEF FMX}/{$ELSE}div{$ENDIF} tmpRows;

  tmpW:=Round(0.9*(ParentChart.Width {$IFDEF FMX}/{$ELSE}div{$ENDIF} tmpCols));
  tmpH:=Round(0.9*tmpRowHeight);

  x:=0;
  y:=0;

  tmpMarginX:=Round(0.05*tmpW);
  tmpMarginY:=Round(0.05*tmpH);

  if ParentChart is TInnerChart then
  begin
    tmpLeft:=ParentChart.{$IFDEF FMX}Position.X{$ELSE}Left{$ENDIF};
    tmpTop:=ParentChart.{$IFDEF FMX}Position.Y{$ELSE}Top{$ENDIF};
  end
  else
  begin
    tmpLeft:=0;
    tmpTop:=0;
  end;

  for t:=0 to tmp-1 do
  begin
    tmpChart:=Charts[t].Chart;

    tmpX:=tmpLeft + tmpMarginX + (x*(ParentChart.Width {$IFDEF FMX}/{$ELSE}div{$ENDIF} tmpCols));

    tmpChart.{$IFDEF FMX}Position.X{$ELSE}Left{$ENDIF}:=tmpX;
    tmpChart.Width:=tmpW;

    tmpY:=tmpTop + tmpTitle+ tmpMarginY + (y*tmpRowHeight);

    tmpChart.{$IFDEF FMX}Position.Y{$ELSE}Top{$ENDIF}:=tmpY;
    tmpChart.Height:=tmpH;

    if Assigned(tmpChart.OnResize) then
       tmpChart.OnResize(tmpChart);

    Inc(x);

    if x>=tmpCols then
    begin
      x:=0;
      Inc(y);
    end;
  end;
end;

class function TSubChartTool.LongDescription: String;
begin
  result:=TeeMsg_SubChartDesc;
end;

function TSubChartTool.GetCursor: TCursor;
begin
  result:=ParentChart.Cursor;
end;

procedure TSubChartTool.SetCursor(const Value: TCursor);
begin
  ParentChart.Cursor:=Value;
end;

procedure TSubChartTool.SetCharts(const Value: TChartCollection);
begin
  FCharts.Assign(Value);
end;

procedure TSubChartTool.SetParentChart(const Value: TCustomAxisPanel);
begin
  inherited;
  Repaint;
end;

type
  TOwnedCollectionAccess=class(TOwnedCollection);

{ TSubChart }

Constructor TSubChart.Create(Collection: TCollection);
var tmp : TPersistent;
begin
  inherited;
  IEdge:=-1;

  // Default rectangle
  ITempRect:=TeeRect(50,50,200,150);

  if Assigned(Collection) then
  begin
    tmp:=TOwnedCollectionAccess(Collection).GetOwner;

    if tmp is TSubChartTool then
       ITool:=TSubChartTool(tmp);
  end;

  if (not Assigned(ITool)) or
     (not (csLoading in ITool.ComponentState)) then
          GetChart;
end;

Destructor TSubChart.Destroy;
begin
  if Assigned(FChart) then
  begin
    // Note: Not destroying FChart here to avoid AV at NotifyGlobalLoading,
    // when reading SubCharts from inherited TFrames (inline).
    // This will produce a memory leak in this special case.

    if (not Assigned(ITool)) or (not (csLoading in ITool.ComponentState)) then
       FreeAndNil(FChart)
    else
    if Assigned(ITool) and (ITool is TSubChartTool) then
       TSubChartTool(ITool).AddToDestroy(FChart);
  end;

  if Assigned(ITool) and (not (csDestroying in ITool.ComponentState))  then
     ITool.Repaint;

  inherited;
end;

procedure TSubChart.Assign(Source: TPersistent);
begin
  if Source is TSubChart then
     Chart.Assign(TSubChart(Source).Chart)
  else
  if Source is TCustomChart then
     Chart.Assign(TCustomChart(Source))
  else
     inherited;
end;

function TSubChart.GetBounds: TRect;
begin
  result:=Chart.{$IFDEF FMX}{$IFDEF D22}BoundsRect{$ELSE}ParentedRect{$ENDIF}{$ELSE}BoundsRect{$ENDIF};
end;

procedure TSubChart.SetChart(const Value: TChart);
begin
  Chart.Assign(Value);
end;

Function TSubChart.Clicked(x,y:Integer):Boolean;
begin
  result:=PointInRect(GetBounds(),x,y);
end;

Function TSubChart.ClickedEdge(x,y:Integer):Integer;
const
  Tolerance=4;

var R : TRect;
begin
  result:=-1;

  if Clicked(x,y) then
  begin
    R:=Bounds;

    if (Abs(x-R.Left)<Tolerance) then
       if Abs(y-R.Top)<Tolerance then
          result:=4
       else
       if Abs(y-R.Bottom)<Tolerance then
          result:=5
       else
          result:=0
    else
    if Abs(y-R.Top)<4 then
       if Abs(x-R.Right)<Tolerance then
          result:=6
       else
          result:=1
    else
    if Abs(x-R.Right)<4 then
       if Abs(y-R.Bottom)<Tolerance then
          result:=7
       else
          result:=2
    else
    if Abs(y-R.Bottom)<4 then
       result:=3;
  end;
end;

function TSubChart.GetHeight: Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(Chart.Height);
end;

function TSubChart.GetLeft: Integer;
begin
  result:={$IFDEF FMX}Round(Chart.Position.X){$ELSE}Chart.Left{$ENDIF};
end;

function TSubChart.GetTop: Integer;
begin
  result:={$IFDEF FMX}Round(Chart.Position.Y){$ELSE}Chart.Top{$ENDIF};
end;

function TSubChart.GetWidth: Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(Chart.Width);
end;

procedure TSubChart.SetHeight(const Value: Integer);
begin
  if Assigned(FChart) then
     FChart.Height:=Value
  else
     ITempRect.Bottom:=ITempRect.Top+Value;
end;

procedure TSubChart.SetLeft(const Value: Integer);
var tmp : TCoordinate;
begin
  if Assigned(FChart) then
     FChart.{$IFDEF FMX}Position.X{$ELSE}Left{$ENDIF}:=Value
  else
  begin
    tmp:=ITempRect.Right-ITempRect.Left;
    ITempRect.Left:=Value;
    ITempRect.Right:=Value+tmp;
  end;
end;

procedure TSubChart.SetTop(const Value: Integer);
var tmp : TCoordinate;
begin
  if Assigned(FChart) then
     FChart.{$IFDEF FMX}Position.Y{$ELSE}Top{$ENDIF}:=Value
  else
  begin
    tmp:=ITempRect.Bottom-ITempRect.Top;
    ITempRect.Top:=Value;
    ITempRect.Bottom:=Value+tmp;
  end;
end;

procedure TSubChart.SetWidth(const Value: Integer);
begin
  if Assigned(FChart) then
     FChart.Width:=Value
  else
     ITempRect.Right:=ITempRect.Left+Value;
end;

procedure TSubChart.ReadChart(Stream: TStream);
begin
  if Assigned(FChart) then
     FChart:=Stream.ReadComponent(FChart) as TInnerChart
  else
     FChart:=Stream.ReadComponent(nil) as TInnerChart;

  FChart.BoundsRect:=ITempRect;
  TInnerChart(FChart).IParent:=Self;
end;

procedure TSubChart.WriteChart(Stream: TStream);
begin
  Stream.WriteComponent(Chart);
end;

procedure TSubChart.DefineProperties(Filer: TFiler);

  function SaveInnerChart:Boolean;
  begin
    result:=(not Assigned(Filer.Ancestor)) //or
            //(not EqualChart(FChart,TSubChart(Filer.Ancestor).FChart));
  end;

begin
  inherited;
  Filer.DefineBinaryProperty('InnerChart',ReadChart,WriteChart,SaveInnerChart);
end;

{ TChartCollection }

function TChartCollection.AddChart(const AName: String=''): TChart;
var tmp : TCustomTeePanel;
begin
  result:=(Add as TSubChart).Chart;
  result.Name:=AName;

  if Assigned(IOwner) and (IOwner.ParentChart<>nil) then
  begin
    tmp:=TCustomTeePanelAccess(IOwner.ParentChart).Theme;

    if tmp is TCustomChart then
       TThemesList.Apply(result,tmp as TCustomChart);
  end;
end;

function TChartCollection.Clone(const AChart:TChart):TSubChart;
var tmp : TChart;
begin
  result:=Add as TSubChart;
  tmp:=result.Chart;
  CloneChart(tmp,AChart,tmp.Owner,False);
end;

function TChartCollection.Get(Index: Integer): TSubChart;
begin
  result:=TSubChart(inherited Items[Index]);
end;

procedure TChartCollection.Put(Index: Integer; const Value: TSubChart);
begin
  inherited Items[Index]:=Value;
end;

{$IFDEF D6}
procedure TChartCollection.Notify(Item: TCollectionItem; Action: TCollectionNotification);
{$ELSE}
procedure TChartCollection.Update(Item: TCollectionItem);
var Action : TCollectionNotification;
{$ENDIF}
begin
  if Assigned(Item) then
  begin
    {$IFNDEF D6}
    if Item.Collection=nil then Action:=cnDeleting else Action:=cnAdded;
    {$ENDIF}

    inherited;

    if Assigned(IOwner) then
       IOwner.Notify(Item,Action);
  end
  else
    inherited;
end;

{$IFDEF FMX}
procedure TInnerChart.SetBounds(X, Y, AWidth, AHeight: Single);
{$ELSE}
procedure TInnerChart.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
{$ENDIF}
begin
  inherited;
  Invalidate;
end;

function TChartCollection.GetLast:TSubChart;
begin
  result:=Get(Count-1);
end;

initialization
  TeeActivateGroup;

  {$IFDEF FMX}
  RegisterFmxClasses([TInnerChart]);
  {$ELSE}
  RegisterClass(TInnerChart);
  {$ENDIF}

  RegisterTeeTools([TSubChartTool]);
finalization
  TeeActivateGroup;
  UnRegisterTeeTools([TSubChartTool]);
  UnRegisterClass(TInnerChart);
end.

