{*******************************************************}
{                                                       }
{ Unit Name: TeeTernary                                 }
{ Purpose  : TeeTernary (Trilinear Piper project)       }
{ Author   : Marc Meumann, marc@steema.com              }
{ History  : v1.0 (Written for TeeChart v9 or later)    }
{                                                       }
{ Copyright (c) 2008-2020 by Steema Software            }
{        All Rights Reserved                            }
{*******************************************************}
unit TeeTernary;
{$I TeeDefs.inc}

interface

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

     {$IFDEF FMX}
     System.UITypes, FMX.Types, System.Types,
     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}
     {$ELSE}

     {$IFDEF D16}
     System.UITypes,
     {$ENDIF}

     Graphics, Controls,
     {$IFDEF D6}
     Types,
     {$ENDIF}

     {$ENDIF}

     Classes, SysUtils,

     {$IFDEF FMX}

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

     FMXTee.Constants, FMXTee.Engine, FMXTee.Chart, FMXTee.Canvas, FMXTee.Procs,
     FMXTee.Series.Point3D, FMXTee.Series, FMXTee.Series.Surface,
     FMXTee.ProConstants
     {$ELSE}
     TeeConst, TeEngine, Chart, TeCanvas, TeeProcs,
     TeePoin3, Series, TeeSurfa, TeeProco
     {$ENDIF};

type
  TTernaryStyle = (tsPoint, tsBubble);

  TTernaryAxisScale = (tlasUnit, tlasPercent);

  TTernaryFormat = (tlfTernary,tlfPiper);

  TTernaryLegendStyle = (tlsValues, tlsValuesBubble, tlsValuesBubbleWeight,
                         tlsBubble, tlsBubbleWeight, tlsWeight);

  TTernaryVertexTitles = Array of TStringList;

  TTernaryMarkStyle = (tmsShort, tmsLong, tmsLongLabelled);

  TTernarySeries=class;

  TSeriesClickPointer3DEvent=Procedure( Sender:TTernarySeries;
                                        ValueIndex:Integer;
                                        X, Y: Integer) of object;

  TTernarySeries = class(TCustom3DPaletteSeries)
  private
    FAxisScale    : TTernaryAxisScale;
    FRadiusValues : TChartValueList; { <-- optional radius storage }
    FWeightValues : TChartValueList; { <-- optional weights storage }

    IPolyList     : TTrianglePoints; // Note: Due to a C++ hpp 2006 bug, this cannot be a dynamic Array

    FPlotFormat   : TTernaryFormat;
    FLinesPen     : TChartPen;

    FLeftPoints,
    FRightPoints,
    FBottomPoints,
    FPlotPoints   : TPointArray;

    FVertexA,
    FVertexB,
    FVertexC      : TPoint;

    FDeriveZ       : Boolean;

    FPointer      : TSeriesPointer;
    FRotateLabels : Boolean;
    FPercentCoeff : Double;
    FTernaryStyle : TTernaryStyle;
    FLegendStyle  : TTernaryLegendStyle;
    FMarkStyle    : TTernaryMarkStyle;

    FVertexTitle  : TTextShape;
    FVertexTitles : TTernaryVertexTitles;

    { events }
    FOnClickPointer    : TSeriesClickPointer3DEvent;
    FOnGetPointerStyle : TOnGetPointerStyle;

    boundingPoly  : TTrianglePoints;

    procedure ClearTitles;

    function GetGradient:TTeeGradient;
    procedure SetAxisScale(const Value: TTernaryAxisScale);

    procedure SetGradient(const Value: TTeeGradient);
    procedure SetLinesPen(const Value: TChartPen);
    procedure SetRotateLabels(const Value: Boolean);
    procedure SetRadiusValues(const Value:TChartValueList);
    procedure SetWeightValues(const Value:TChartValueList);
    procedure SetTernaryStyle(const Value: TTernaryStyle);
    procedure SetTernaryLegendStyle(const Value: TTernaryLegendStyle);
    Procedure SetPointer(const Value:TSeriesPointer);
    procedure SetTernaryMarkStyle(const Value: TTernaryMarkStyle);
    procedure SetVertexTitle(const Value:TTextShape);
    procedure SetVertexTitles(const Value:TTernaryVertexTitles);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    procedure AssignFormatting(Source:TChartSeries); override;
    procedure CalcVerticalMargins(var TopMargin,BottomMargin:Integer); override;
    Function ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             x,y:Integer):Boolean; override;
    Procedure DoBeforeDrawChart; override;
    procedure DoBeforeDrawValues; override;
    function DoGetPointerStyle(ValueIndex:Integer):TSeriesPointerStyle;
    procedure DrawAllValues; override;
    procedure DrawAxes;
    procedure DrawAxis(const aAxis:TChartAxis; const StartPoint,EndPoint:TPoint);
    procedure DrawGrids(const Element:TTrianglePoints);
    procedure DrawLabels(const Element:TTrianglePoints);
    procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    Procedure DrawMark(ValueIndex:Integer; Const St:String;
                       APosition:TSeriesMarkPosition); override;
    procedure DrawTicks(const Element:TTrianglePoints);
    procedure DrawVertexTitles;
    Procedure DrawValue(ValueIndex:Integer); override;
    class Function GetEditorClass:String; override;
    procedure GetMarkText(Sender: TChartSeries; ValueIndex: Integer;
                          var MarkText: String);
    function GetSideOffset(const Hypoteneuse : TCoordinate): TPoint;
    function GetVectorLocation(const StartPoint,EndPoint:TPoint;
                               const PercentOffset:TCoordinate):TPoint;
    Procedure HomogeniseValues(Var x, y, z : Double);
    procedure MouseDown(ValueIndex:Integer; AtPointer:Boolean; Button:TMouseButton;
                        Shift: TShiftState; X, Y: Integer); override;
    Procedure PrepareForGallery; override;
    Procedure PreparePointer(ValueIndex:Integer);
    Procedure SetParentChart(const Value:TCustomAxisPanel); override;

    function RaiseClicked:Boolean; override;

  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    function CalcPointFromValues(const a,b,c:Double):TPoint; overload;
    function CalcPointFromValues(const ValueIndex:Integer):TPoint; overload;
    Function CalcXPos(ValueIndex:Integer):Integer; override;
    Function CalcYPos(ValueIndex:Integer):Integer; override;

    function Clicked(X,Y:Integer):Integer; override;
    Function CountLegendItems:Integer; override;
    Function LegendString( LegendIndex:Integer;
                           LegendTextStyle:TLegendTextStyle ):String; override;
    Function NumSampleValues:Integer; override;

    Function UseAxis:Boolean; override;

    property VertexTitles:TTernaryVertexTitles read FVertexTitles write SetVertexTitles;

    Function AddXYPoint( Const AX,AY : Double; const ALabel:String='';
       AColor:TColor=clTeeColor):Integer;
    Function AddBubbleXY( Const AX,AY,ARadius:Double; AColor:TColor=clTeeColor):Integer; overload;
    Function AddBubbleXY( Const AX,AY,ARadius:Double;
       const ALabel:String=''; AColor:TColor=clTeeColor):Integer; overload;
    Function AddBubbleXYWeight(const AX, AY, ARadius, AWeight: Double;
       const ALabel:String=''; AColor:TColor=clTeeColor):Integer;

    Function AddBubbleXYZ( Const AX,AY,AZ,ARadius:Double; AColor:TColor=clTeeColor):Integer; overload;
    Function AddBubbleXYZ( Const AX,AY,AZ,ARadius:Double;
       const ALabel:String=''; AColor:TColor=clTeeColor):Integer; overload;
    function AddBubbleXYZWeight(const AX, AY, AZ, ARadius, AWeight: Double;
      AColor: TColor): Integer; overload;
    Function AddBubbleXYZWeight(const AX, AY, AZ, ARadius, AWeight: Double;
      const ALabel:String=''; AColor:TColor=clTeeColor):Integer; overload;

    // TPoint published properties not allowed in C++. (They are read-only anyway)
    property VertexA:TPoint read FVertexA;
    property VertexB:TPoint read FVertexB;
    property VertexC:TPoint read FVertexC;

  published
    property Active;
    property AxisScale:TTernaryAxisScale read FAxisScale write SetAxisScale;
    property Brush;
    property Color;
    property ColorEachPoint default True;
    property ColorSource;
    property Gradient:TTeeGradient read GetGradient write SetGradient;
    property LinesPen:TChartPen read FLinesPen write SetLinesPen;
    property RadiusValues:TChartValueList read FRadiusValues write SetRadiusValues;
    property RotateLabels:Boolean read FRotateLabels write SetRotateLabels default True;
    property DeriveZ:Boolean read FDeriveZ write FDeriveZ default True;
    property TernaryLegendStyle:TTernaryLegendStyle read FLegendStyle write SetTernaryLegendStyle default tlsValues;
    property TernaryMarkStyle:TTernaryMarkStyle read FMarkStyle write SetTernaryMarkStyle default tmsShort;
    property TernaryStyle:TTernaryStyle read FTernaryStyle write SetTernaryStyle;
    property VertexTitle:TTextShape read FVertexTitle write SetVertexTitle;
    property WeightValues:TChartValueList read FWeightValues write SetWeightValues;

    // TCustom3DPaletteSeries
    property EndColor;
    property MidColor;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;

    // TChartSeries
    property Cursor;
    property Depth;
    property Marks;
    property ParentChart;

    property DataSource;

    property PercentFormat;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property XLabelsSource;

    property Pointer:TSeriesPointer read FPointer write SetPointer;

    { events }
    property AfterDrawValues;
    property BeforeDrawValues;
    property OnAfterAdd;
    property OnBeforeAdd;
    property OnClearValues;
    property OnClick;
    property OnDblClick;
    property OnGetMarkText;
    property OnMouseEnter;
    property OnMouseLeave;

    property OnGetColor;

    property OnClickPointer:TSeriesClickPointer3DEvent read FOnClickPointer
                                                       write FOnClickPointer;
    property OnGetPointerStyle:TOnGetPointerStyle read FOnGetPointerStyle
                                                  write FOnGetPointerStyle;
  end;

implementation

uses
  {$IFDEF LCL}
  LCLType,
  {$ENDIF}

  Math;

{ TTernarySeries }

Constructor TTernarySeries.Create(AOwner: TComponent);

  function NewStringList(const ATitle:String):TStringList;
  begin
    result:=TStringList.Create;
    result.OnChange:=CanvasChanged;
    result.Add(ATitle);
  end;

begin
  inherited;

  OnGetMarkText := GetMarkText;
  ColorEachPoint:=True;
  FLinesPen:=CreateChartPen;
  FRotateLabels:=True;
  FTernaryStyle:=tsPoint;
  FAxisScale:=tlasPercent;

  FDeriveZ := False;

  FVertexTitle:=TTextShape.Create(nil);
  FVertexTitle.Transparent:=True;

  SetLength(FVertexTitles,3);

  FVertexTitles[0]:=NewStringList('A');
  FVertexTitles[1]:=NewStringList('B');
  FVertexTitles[2]:=NewStringList('C');

  Gradient.Visible:=True;
  Gradient.MidColor:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};

  FPlotFormat:=tlfTernary;

  FRadiusValues:=TChartValueList.Create(Self,TeeMsg_ValuesBubbleRadius); { <-- radius storage }
  FWeightValues:=TChartValueList.Create(Self,TeeMsg_ValuesWeighting); { <-- weight storage }
  MandatoryValueList:= self.YValues; //  FWeightValues;

  FPointer:=TSeriesPointer.Create(Self);
  FPointer.Style:=psCircle;

  VertAxis:=aBothVertAxis;
  HorizAxis:=aBottomAxis;
end;

Destructor TTernarySeries.Destroy;
begin
  FreeAndNil(FPointer);

  FVertexTitle.Free;

  ClearTitles;

  FLinesPen.Free;

  FLeftPoints:=nil;
  FRightPoints:=nil;
  FBottomPoints:=nil;
  FPlotPoints:=nil;

  inherited;
end;

Function TTernarySeries.ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                                       x,y:Integer):Boolean;
begin
  if TernaryStyle=tsBubble Then
     PreparePointer(ValueIndex);

  result:=Pointer.Clicked(DoGetPointerStyle(ValueIndex),TeePoint(X,Y),tmpX,tmpY);
end;

function TTernarySeries.Clicked(X, Y: Integer): Integer;

  function ClickedPoint(Index:Integer):Integer;
  var tmpX, tmpY: Integer;
  begin
    result:=TeeNoPointClicked;

    if (Index>=Length(FPlotPoints)) or (Index<0) then
       Exit;  // prevent re-entrancy if series is cleared.

    tmpX:={$IFDEF FMX}Round{$ENDIF}(FPlotPoints[Index].X);
    tmpY:={$IFDEF FMX}Round{$ENDIF}(FPlotPoints[Index].Y);

    if FPointer.Visible and ClickedPointer(Index,tmpX,tmpY,x,y) then
       result:=Index
    else
    if (tmpX=X) and (tmpY=Y) then
       result:=Index
  end;

var t : Integer;
begin
  result:=TeeNoPointClicked;

  for t:=LastValueIndex downto FirstValueIndex do
  begin
    result:=ClickedPoint(t);

    if result<>TeeNoPointClicked then
       break;
  end;
end;

procedure TTernarySeries.ClearTitles;
var t : Integer;
begin
  for t:=0 to Length(FVertexTitles)-1 do
      FVertexTitles[t].Free;

  FVertexTitles:=nil;
end;

procedure TTernarySeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TTernarySeries then
  with TTernarySeries(Source) do
  begin
    Self.LinesPen     :=FLinesPen;
    Self.Pointer      :=FPointer;
    Self.FRotateLabels:=FRotateLabels;
    Self.FTernaryStyle:=FTernaryStyle;
    Self.FLegendStyle :=FLegendStyle;
    Self.FMarkStyle   :=FMarkStyle;
    Self.VertexTitle  :=FVertexTitle;
  end;

  inherited;
end;

procedure TTernarySeries.Assign(Source:TPersistent);
begin
  if Source is TTernarySeries then
  with TTernarySeries(Source) do
  begin
    Self.FAxisScale  :=FAxisScale;
    Self.FPlotFormat :=FPlotFormat;
    Self.FPercentCoeff:=FPercentCoeff;
    Self.VertexTitles:=FVertexTitles;
  end;

  inherited;
end;

function TTernarySeries.NumSampleValues: Integer;
begin
  result:=15;
end;

procedure TTernarySeries.SetGradient(const Value: TTeeGradient);
begin
  Gradient.Assign(Value);
end;

procedure TTernarySeries.SetLinesPen(const Value: TChartPen);
begin
  FLinesPen.Assign(Value);
end;

function TTernarySeries.UseAxis: Boolean;
begin
  result:=False;
end;

type
  TPointerAccess=class(TSeriesPointer);

procedure TTernarySeries.DrawAllValues;

  procedure DrawBack;
  const
    Tan60=1.7320508076; // Tan(DegToRad(60))

  var boundingRect: TRect;
      HalfHeight,
      HalfWidth,
      topBound ,
      midX,
      tmpWidth,
      yShift : Integer;

      tmpR : TRect;
      tmpWall : TChartWall;
  begin
    tmpR:=ParentChart.ChartRect;

    HalfHeight:={$IFDEF FMX}Round{$ENDIF}(tmpR.Bottom-tmpR.Top) div 2;
    HalfWidth:={$IFDEF FMX}Round{$ENDIF}(tmpR.Right-tmpR.Left) div 2;

    midX:=ParentChart.ChartXCenter;

    if HalfWidth>HalfHeight Then
       boundingRect:=TeeRect(midX-HalfHeight,
                             ParentChart.ChartRect.Top,
                             midX+HalfHeight,
                             ParentChart.ChartRect.Bottom)
    else
       boundingRect:=TeeRect(ParentChart.ChartRect.Left,
                             ParentChart.ChartYCenter-HalfWidth,
                             ParentChart.ChartRect.Right,
                             ParentChart.ChartYCenter+HalfWidth);

    tmpWidth:={$IFDEF FMX}Round{$ENDIF}(boundingRect.Right-boundingRect.Left) div 2;
    topBound:=Round(Tan60*tmpWidth);
    yShift:={$IFDEF FMX}Round{$ENDIF}(boundingRect.Bottom-boundingRect.Top-topBound) div 2;

    boundingPoly[0]:=TeePoint(midX,boundingRect.Bottom-topBound-yShift); //top middle
    boundingPoly[1]:=TeePoint(boundingRect.Left,boundingRect.Bottom-yShift); //bottom left
    boundingPoly[2]:=TeePoint(boundingRect.Right,boundingRect.Bottom-yShift); //bottom right

    FVertexA:=boundingPoly[0];
    FVertexB:=boundingPoly[2];
    FVertexC:=boundingPoly[1];

    tmpWall:=(ParentChart as TCustomChart).Walls.Back;

    if tmpWall.Visible then
    begin
      if tmpWall.Transparent then
         ParentChart.Canvas.Brush.Style:=bsClear
      else
         ParentChart.Canvas.AssignBrush(tmpWall.Brush);

      ParentChart.Canvas.AssignVisiblePen(tmpWall.Pen);
      ParentChart.Canvas.Polygon(boundingPoly);
    end;

    IPolyList:=boundingPoly;
  end;

var tmpBlend : TTeeBlend;

    OldX,
    OldY : Integer;
begin
  if ParentChart.View3D then
     ParentChart.View3D:=False;

  FPlotPoints:=nil;

  if FPlotFormat=tlfTernary then
     DrawBack;

  DrawAxes;

  if Transparency>0 then
     tmpBlend:=ParentChart.Canvas.BeginBlending(ParentChart.ChartRect,Transparency)
  else
     tmpBlend:=nil;

  if FTernaryStyle=tsBubble then
  begin
    OldX:=Pointer.HorizSize;
    OldY:=Pointer.VertSize;
  end
  else
  begin
    OldX:=0;
    OldY:=0;
  end;

  inherited;

  if FTernaryStyle=tsBubble then
  begin
    TPointerAccess(Pointer).ChangeHorizSize(OldX);
    TPointerAccess(Pointer).ChangeVertSize(OldY);
  end;

  if Transparency>0 then
     ParentChart.Canvas.EndBlending(tmpBlend);
end;

procedure TTernarySeries.DrawAxes;
Var idx : Integer;
begin
  if FPlotFormat=tlfTernary Then
  begin
    if ParentChart.Axes.Visible then
    begin
      idx := self.ParentChart.SeriesList.IndexOf(self);   //when more than one ternary, only plot axes once
      if (idx>0) and (ParentChart.Series[idx-1] is TTernarySeries) then
        exit;

      DrawAxis(ParentChart.Axes.Bottom,IPolyList[1],IPolyList[2]);
      DrawAxis(ParentChart.Axes.Left,IPolyList[0],IPolyList[1]);
      DrawAxis(ParentChart.Axes.Right,IPolyList[2],IPolyList[0]);

      DrawGrids(IPolyList);
      DrawTicks(IPolyList);
      DrawLabels(IPolyList);
    end;

    if FVertexTitle.Visible then
       DrawVertexTitles;
  end;
end;

type
  TShapeAccess=class(TCustomTextShape);
  TTeePanelAccess=class(TCustomTeePanel);

procedure TTernarySeries.DrawAxis(const aAxis:TChartAxis; const StartPoint,EndPoint:TPoint);

  procedure DrawTitle(const X,Y,Angle:Integer);
  begin
    aAxis.Title.ShapeBounds.Left:=X;
    aAxis.Title.ShapeBounds.Top:=Y;
    TShapeAccess(aAxis.Title).CalcBounds(ParentChart);

    aAxis.Title.Angle:=Angle;
    aAxis.Title.Draw(aAxis.Title.ShapeBounds,Angle);
  end;

var x,y,width,height: Integer;
    tmpPoint,tmpPoint2 : TPoint;
begin
  if aAxis.Visible then
  begin
    if aAxis.Axis.Visible then
    begin
      ParentChart.Canvas.AssignVisiblePen(aAxis.Axis);
      ParentChart.Canvas.Line(StartPoint,EndPoint);
    end;

    if aAxis.Title.Visible and (aAxis.Title.Caption<>'') then
    begin
      TTeePanelAccess(ParentChart).TryAssignFont(aAxis.Title.Font);

      width := ParentChart.Canvas.TextWidth(aAxis.Title.Caption);
      height := ParentChart.Canvas.FontHeight;

      ParentChart.Canvas.BackMode:=cbmTransparent;
      ParentChart.Canvas.TextAlign:=ta_Left;

      if aAxis.Horizontal and (not aAxis.OtherSide) then
      Begin  //bottom
        x := {$IFDEF FMX}Round{$ENDIF}(StartPoint.X) +
             ({$IFDEF FMX}Round{$ENDIF}(EndPoint.X-StartPoint.X) div 2) - (width div 2);

        y := {$IFDEF FMX}Round{$ENDIF}(StartPoint.Y) + Round(height * 0.5) +
             {$IFDEF FMX}Round{$ENDIF}(aAxis.LabelsFont.Size);

        DrawTitle(x,y,0);
      end
      else
      if (not aAxis.Horizontal) and (not aAxis.OtherSide) then
      Begin  //left
        tmpPoint:=GetVectorLocation(EndPoint,StartPoint,50);

        tmpPoint2 := GetSideOffset(width div 2);
        tmpPoint.X := tmpPoint.X - (tmpPoint2.X) - {$IFNDEF FMX}Round{$ENDIF}(height * 1.4);
        tmpPoint.Y := tmpPoint.Y + tmpPoint2.Y;
        tmpPoint2 := GetSideOffset(aAxis.LabelsFont.Size * 3);

        x:={$IFDEF FMX}Round{$ENDIF}(tmpPoint.X - tmpPoint2.X);
        y:={$IFDEF FMX}Round{$ENDIF}(tmpPoint.Y - tmpPoint2.Y);

        DrawTitle(x,y,60);
      end
      else
      if (not aAxis.Horizontal) and aAxis.OtherSide then
      Begin  //right
        tmpPoint:=GetVectorLocation(StartPoint,EndPoint,50);

        tmpPoint2 := GetSideOffset(width div 2);

        (*
        tmpPoint.X := tmpPoint.X - (tmpPoint2.X) + {$IFNDEF FMX}Round{$ENDIF}(height * 1.6);
        tmpPoint.Y := tmpPoint.Y - tmpPoint2.Y;

        tmpPoint2 := GetSideOffset(aAxis.LabelsFont.Size * 3);
        *)

        x:={$IFDEF FMX}Round{$ENDIF}(tmpPoint.X + tmpPoint2.X);
        y:={$IFDEF FMX}Round{$ENDIF}(tmpPoint.Y - tmpPoint2.Y);

        DrawTitle(x,y,300);
      end;
    end;
  end;
end;

function TTernarySeries.GetVectorLocation(const StartPoint, EndPoint: TPoint;
         const PercentOffset: TCoordinate): TPoint;
var xVal,
    yVal : TCoordinate;
begin
  xVal := Abs(EndPoint.X - StartPoint.X);

  xVal := xVal-
          xVal*
          PercentOffset {$IFDEF FMX}*0.01{$ELSE}div 100{$ENDIF};

  yVal := Abs(EndPoint.Y - StartPoint.Y) *
          PercentOffset {$IFDEF FMX}*0.01{$ELSE}div 100{$ENDIF};

  if (EndPoint.X < StartPoint.X) and (EndPoint.Y < StartPoint.Y) Then
      yVal := (Abs(EndPoint.Y - StartPoint.Y))-yVal;

  result:=TeePoint(xVal+Min(StartPoint.X,EndPoint.X),yVal+Min(StartPoint.Y,EndPoint.Y));
end;

function TTernarySeries.GetSideOffset(const Hypoteneuse: TCoordinate): TPoint;
const Sin60=0.86602540378; // Sin(DegToRad(60))
      Cos60=0.5; // Cos(DegToRad(60))
begin
  result:=TeePoint({$IFNDEF FMX}Round{$ENDIF}(Cos60*Hypoteneuse),
                   {$IFNDEF FMX}Round{$ENDIF}(Sin60*Hypoteneuse));
end;

procedure TTernarySeries.DrawGrids(const Element:TTrianglePoints);

  function getRoundedAxisIncr(desiredIncr : Integer) : Integer;
  Begin
    if (desiredIncr=1) or (100 mod desiredIncr <> 0) Then
      result:=10 //set 10 if not equi-incr.
    else
      result:=desiredIncr;
  end;

  procedure TryDrawGrid(const Axis:TChartAxis; const A,B:TPointArray);
  var t : Integer;
  begin
    if Axis.Visible and Axis.Grid.Visible then
    begin
      ParentChart.Canvas.AssignVisiblePen(Axis.Grid);

      for t:=0 to Length(FLeftPoints)-1 do
          ParentChart.Canvas.Line(A[t],B[t]);
    end;
  end;

var TickCount,
    tmpL,
    i : Integer;

    tmpDifX,
    tmpOffset : TCoordinate;
begin
  if ParentChart.Axes.Bottom.Increment = 0 Then
     TickCount:= 10
  else
     TickCount:= getRoundedAxisIncr(Round(100.0 / ParentChart.Axes.Bottom.Increment));

  SetLength(FLeftPoints,TickCount-1);
  SetLength(FRightPoints,TickCount-1);
  SetLength(FBottomPoints,TickCount-1);

  tmpDifX:=(Element[2].X-Element[1].X);

  for i:=0 to TickCount-2 do
  Begin
    FBottomPoints[i]:=TeePoint({$IFNDEF FMX}Round{$ENDIF}(Element[1].X+((Abs(i-(TickCount-1))) / TickCount)*tmpDifX),Element[1].Y);

    tmpOffset:={$IFNDEF FMX}Round{$ENDIF}(((i+1) / TickCount)*100);

    FLeftPoints[i]:=GetVectorLocation(Element[1],Element[0],tmpOffset);
    FRightPoints[i]:=GetVectorLocation(Element[2],Element[0],tmpOffset);
  end;

  if ParentChart.Axes.Visible then
  begin
    TryDrawGrid(ParentChart.Axes.Bottom,FBottomPoints,FLeftPoints);
    TryDrawGrid(ParentChart.Axes.Right,FBottomPoints,FRightPoints);

    if ParentChart.Axes.Left.Visible and ParentChart.Axes.Left.Grid.Visible then
    begin
      ParentChart.Canvas.AssignVisiblePen(ParentChart.Axes.Left.Grid);

      tmpL:=Length(FLeftPoints)-1;

      for i:=0 to tmpL do
          ParentChart.Canvas.Line(FLeftPoints[tmpL-i].x,FLeftPoints[tmpL-i].y,
                                  FRightPoints[i].x,FLeftPoints[tmpL-i].y); //rounding compensation
    end;
  end;
end;

procedure TTernarySeries.DrawTicks(const Element: TTrianglePoints);
var
  tmpPoint  : TPoint;

  function DoDraw(const Axis:TChartAxis):Boolean;
  begin
    result:=Axis.Visible and Axis.Ticks.Visible;

    if result then
    begin
      tmpPoint:=GetSideOffset(Axis.TickLength);
      ParentChart.Canvas.AssignVisiblePen(Axis.Ticks);
    end;
  end;

var i   : Integer;
    tmpL,
    tmpR,
    tmp : TPoint;
begin
  if DoDraw(ParentChart.Axes.Bottom) then
  for i:=0 to Length(FBottomPoints)-1 do
  begin
    tmp:=FBottomPoints[i];

    ParentChart.Canvas.Line(tmp.x,tmp.y,tmp.x+tmpPoint.X,tmp.y-tmpPoint.Y);
    ParentChart.Canvas.Line(tmp.x,tmp.y,tmp.x-tmpPoint.X,tmp.y-tmpPoint.Y);
  end;

  if DoDraw(ParentChart.Axes.Left) then
  for i:=0 to Length(FLeftPoints)-1 do
  begin
    tmp:=FLeftPoints[i];

    ParentChart.Canvas.Line(tmp.x,tmp.y,tmp.x+tmpPoint.X,tmp.y+tmpPoint.Y);
    ParentChart.Canvas.Line(tmp.x,tmp.y,tmp.x+ParentChart.Axes.Left.TickLength,tmp.y);
  end;

  if DoDraw(ParentChart.Axes.Right) then
  for i:=0 to Length(FRightPoints)-1 do
  begin
    tmpR:=FRightPoints[i];
    tmpL:=FLeftPoints[Length(FBottomPoints)-1-i];

    ParentChart.Canvas.Line(tmpR.x,tmpL.y,tmpR.x-tmpPoint.X,tmpR.y+tmpPoint.Y);
    ParentChart.Canvas.Line(tmpR.x,tmpL.y,tmpR.x-ParentChart.Axes.Right.TickLength,tmpL.y); //vert rounding resolved by use of left co-ords.
  end;
end;

type
  TCanvasAccess=class(TTeeCanvas);

procedure TTernarySeries.DrawLabels(const Element: TTrianglePoints);

  function GetValStr(const Value:TChartValue):String;
  Begin
    if FAxisScale=tlasUnit Then
       result := FormatFloat('0.0', Value * 0.01)
    else
       result := FloatToStr(Value);
  end;

var maxLabelWidth  : Integer;
    Increment  : Double;
    labelStr   : String;

    procedure WriteBottomLabel(const Location:TPoint; const Angle:Integer);
    var tmp : Integer;
    Begin
      with ParentChart.Canvas do
      Begin
        tmp:=TextWidth(labelStr) div 2;

        if Self.RotateLabels then
        begin
          TCanvasAccess(ParentChart.Canvas).IUseTextCenter:=True;
          TCanvasAccess(ParentChart.Canvas).ITextCenter:=Location;

          RotateLabel({$IFDEF FMX}Round{$ENDIF}(Location.X{+tmp}),
                      {$IFDEF FMX}Round{$ENDIF}(Location.Y), labelStr, Angle);

          TCanvasAccess(ParentChart.Canvas).IUseTextCenter:=False;
        end
        else
           TextOut(Location.X-tmp, Location.Y + 2,labelStr);
      end;
    end;

    procedure WriteRightLabel(const Location:TPoint; const Angle:Integer);
    begin
      if RotateLabels Then
      begin
        TCanvasAccess(ParentChart.Canvas).IUseTextCenter:=True;
        TCanvasAccess(ParentChart.Canvas).ITextCenter:=Location;

        ParentChart.Canvas.RotateLabel({$IFDEF FMX}Round{$ENDIF}(Location.X) {- 2},
                                       {$IFDEF FMX}Round{$ENDIF}(Location.Y) {- 5},labelStr,Angle);
      end
      else
        ParentChart.Canvas.TextOut(location.X{+maxLabelWidth}+5,
                     location.Y -(ParentChart.Canvas.TextHeight(labelStr) div 4 * 3),labelStr);
    end;

    function ShouldDrawAxis(const Axis:TChartAxis):Boolean;
    begin
      result:=Axis.Visible and Axis.Labels;

      if result then
      begin
        TTeePanelAccess(ParentChart).TryAssignFont(Axis.Texts.Font);
        ParentChart.Canvas.BackMode:=cbmTransparent;
        ParentChart.Canvas.TextAlign:=ta_Left;
      end;
    end;

var tmpVal : TChartValue;
    i      : Integer;
begin
  Increment:=100 / (Length(FBottomPoints)+1);

  With ParentChart.Canvas do
  Begin
    maxLabelWidth:=TextWidth('');

    if ShouldDrawAxis(ParentChart.Axes.Bottom) then
    Begin
      if GetHorizAxis.LabelsOnAxis Then
      Begin
        labelStr:=getValStr(0);
        writeBottomLabel(VertexB, 300);
      end;

      tmpVal:=0;

      for i:=0 to Length(FBottomPoints)-1 do
      Begin
        tmpVal:=tmpVal+Increment;
        labelStr:=getValStr(tmpVal);

        if Assigned(ParentChart.OnGetAxisLabel) then
           ParentChart.OnGetAxisLabel(ParentChart.Axes.Bottom,nil,i,labelStr);

        writeBottomLabel(FBottomPoints[i], 300);

        if maxLabelWidth < TextWidth(labelStr) Then
           maxLabelWidth := TextWidth(labelStr);
      end;

      if GetHorizAxis.LabelsOnAxis Then
      begin
        labelStr:=getValStr(100);
        writeBottomLabel(VertexC, 300);
      end;
    end;

    if ShouldDrawAxis(ParentChart.Axes.Left) then
    Begin
      if ParentChart.Axes.Left.LabelsOnAxis Then
      Begin
        labelStr:=getValStr(100);
        TextOut(VertexA.X-(TextWidth(labelStr)) - 5, VertexA.Y -(TextHeight(labelStr) div 4 * 3),labelStr);
      end;

      tmpVal:= 100;

      for i:=0 to Length(FLeftPoints)-1 do
      Begin
        tmpVal:=tmpVal-Increment;
        labelStr:=getValStr(tmpVal);

        if Assigned(ParentChart.OnGetAxisLabel) then
           ParentChart.OnGetAxisLabel(ParentChart.Axes.Bottom,nil,i,labelStr);

        TextOut(FLeftPoints[i].X-(TextWidth(labelStr)) - 5, FLeftPoints[i].Y -(TextHeight(labelStr) div 4 * 3),labelStr);
      end;

      if ParentChart.Axes.Left.LabelsOnAxis Then
      Begin
        labelStr:=getValStr(0);
        TextOut(VertexC.X-(TextWidth(labelStr)) - 5, VertexC.Y -(TextHeight(labelStr) div 4 * 3),labelStr);
      end;
    end;

    if ShouldDrawAxis(ParentChart.Axes.Right) then
    Begin
      if ParentChart.Axes.Right.LabelsOnAxis Then
      Begin
        labelStr:=getValStr(100);
        writeRightLabel(VertexB, 60);
      end;

      tmpVal:= 100;

      for i:=0 to Length(FRightPoints)-1 do
      Begin
        tmpVal:=tmpVal-Increment;
        labelStr:=getValStr(tmpVal);

        if Assigned(ParentChart.OnGetAxisLabel) then
           ParentChart.OnGetAxisLabel(ParentChart.Axes.Bottom,nil,i,labelStr);

        writeRightLabel(FRightPoints[i], 60);
      end;

      if ParentChart.Axes.Right.LabelsOnAxis Then
      Begin
        labelStr:=getValStr(0);
        writeRightLabel(VertexA, 60);
      end;
    end;
  end;
end;

procedure TTernarySeries.DrawValue(ValueIndex: Integer);
var tmpColor : TColor;
    tmpStyle : TSeriesPointerStyle;
    tmpPoint : TPoint;
    L        : Integer;
begin
  if Pointer.Visible then
  begin
    tmpColor:=ValueColor[ValueIndex];

    if tmpColor<>clNone then // <-- not IsNull, optimized
    begin
      Pointer.PrepareCanvas(ParentChart.Canvas,tmpColor);

      PrepareHoverPen(ValueIndex);

      if Assigned(FOnGetPointerStyle) then
         tmpStyle:=FOnGetPointerStyle(Self,ValueIndex)
      else
         tmpStyle:=Pointer.Style;

      tmpPoint:=CalcPointFromValues(ValueIndex);

      L:=Length(FPlotPoints);
      SetLength(FPlotPoints,L+1);
      FPlotPoints[L]:=tmpPoint;

      if FTernaryStyle = tsBubble Then
         PreparePointer(ValueIndex);

      Pointer.DrawPointer( ParentChart.Canvas,ParentChart.View3D,
                   {$IFDEF FMX}Round{$ENDIF}(tmpPoint.X),
                   {$IFDEF FMX}Round{$ENDIF}(tmpPoint.Y),
                   Pointer.HorizSize,
                   Pointer.VertSize,tmpColor,tmpStyle);
    end;
  end;
end;

function TTernarySeries.CalcPointFromValues(const a, b, c: Double): TPoint;
const
  InvCos30=1.0/0.86602540378;  // 1/Cos(DegToRad(30))
  Tan30=0.57735026919;  // Tan(DegToRad(30))

var tmpX,tmpY,tmpZ     : Double;
    a1,b1,
    altitude,
    bSide,xPos,yPos,
    xOffsetAtHeighta1 : Double;
begin
  tmpZ:=c;

  if (FDeriveZ) and (c = 0) then
  Begin
    if (a+b) <= 100 then
      tmpZ := 100 - b - a;
      //tmpY := 100 - b - a;
      //tmpZ := b;
  End;


  tmpX:=a; tmpY:=b;
  HomogeniseValues(tmpX,tmpY,tmpZ);

  //  By Viviani's theorem, the sum
  //  a1 + b1 + c1 is independent of P
  //  and is equal to any of the triangle's altitudes.

  //            boundingPoly[0]
  //                  A
  //                  *
  //                 * *
  //                *   *
  //               *  P  *
  //              *b1 . c1*
  //             *   a1    *
  //            * * * * * * *
  //           C             B
  //   boundingPoly[1]    boundingPoly[2]

  altitude := boundingPoly[1].Y - boundingPoly[0].Y;
  a1:=tmpX * altitude * 0.01;
  b1:=tmpY * altitude * 0.01;

  bSide := Round(b1*InvCos30);  //to left of point
  xOffsetAtHeighta1 := Round(a1*Tan30); // x offset from left bound at a1.
  xPos := boundingPoly[1].X + xOffsetAtHeighta1 + bSide;
  yPos := boundingPoly[0].Y + (boundingPoly[1].Y - boundingPoly[0].Y) - a1;
  result:= TeePoint(Round(xPos),Round(yPos));
end;

function TTernarySeries.CalcPointFromValues(const ValueIndex:Integer): TPoint;
begin
  result:=CalcPointFromValues(XValues[ValueIndex],   //A
                              YValues[ValueIndex],   //B
                              ZValues[ValueIndex]);  //C
end;

procedure TTernarySeries.HomogeniseValues(var x, y, z: Double);
var tmpTotal : Double;
begin
  tmpTotal:=x+y+z;

  if tmpTotal=0 Then
  Begin
    FPercentCoeff:=0;
    x:=0;
    y:=0;
    z:=0;
  end
  else
  if tmpTotal<>100 Then
  Begin
     FPercentCoeff:=100/tmpTotal;
     x:=x*FPercentCoeff;
     y:=y*FPercentCoeff;
     z:=z*FPercentCoeff;
  end
  else
    FPercentCoeff:=1;
end;

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

  if Assigned(FPointer) then
     Pointer.ParentChart:=Value;

  if not (csDestroying in ComponentState) then
     FVertexTitle.ParentChart:=Value;

  if Assigned(ParentChart) then
     if csDesigning in ComponentState then
        ParentChart.View3D:=False;
end;

procedure TTernarySeries.SetRotateLabels(const Value: Boolean);
begin
  SetBooleanProperty(FRotateLabels,Value);
end;

function TTernarySeries.DoGetPointerStyle(ValueIndex: Integer): TSeriesPointerStyle;
begin
  if Assigned(FOnGetPointerStyle) then
     result:=FOnGetPointerStyle(Self,ValueIndex)
  else
     result:=Pointer.Style;
end;

procedure TTernarySeries.SetRadiusValues(const Value: TChartValueList);
begin
  SetChartValueList(FRadiusValues,Value);
end;

function TTernarySeries.AddBubbleXYZ(const AX, AY, AZ, ARadius: Double;
   AColor:TColor=clTeeColor): Integer;
begin
  result:=AddBubbleXYZ(AX,AY,AZ,ARadius,'',AColor);
end;

function TTernarySeries.AddBubbleXY(const AX, AY, ARadius: Double;
  AColor: TColor): Integer;
begin
   if (AX+AY)<=100 then
      result:=AddBubbleXYZ(AX,AY,100-AX-AY,ARadius,'',AColor)
   else
      result:=-1;   
end;

function TTernarySeries.AddBubbleXY(const AX, AY, ARadius: Double;
  const ALabel: String; AColor: TColor): Integer;
begin
   if (AX+AY)<=100 then
      result:=AddBubbleXYZ(AX,AY,100-AX-AY,ARadius,ALabel,AColor)
   else
      result:=-1;	  
end;

function TTernarySeries.AddBubbleXYWeight(const AX, AY, ARadius,
  AWeight: Double; const ALabel: String; AColor: TColor): Integer;
begin
   if (AX+AY)<=100 then
      result:=AddBubbleXYZWeight(AX,AY,100-AX-AY,ARadius,AWeight,ALabel,AColor)
   else
      result:=-1;
end;

function TTernarySeries.AddBubbleXYZ(const AX, AY, AZ, ARadius: Double;
  const ALabel: String; AColor: TColor): Integer;
begin
  RadiusValues.TempValue:=ARadius;
  result:=AddXYZ(AX,AY,AZ,ALabel,AColor);
end;

procedure TTernarySeries.PreparePointer(ValueIndex: Integer);
var tmpSize : Integer;
begin
  tmpSize:=Round(RadiusValues.Value[ValueIndex]{*FPercentCoeff});

  TPointerAccess(Pointer).ChangeHorizSize(tmpSize);
  TPointerAccess(Pointer).ChangeVertSize(tmpSize);
end;

procedure TTernarySeries.SetTernaryStyle(const Value: TTernaryStyle);
begin
  if FTernaryStyle<>Value then
  begin
    FTernaryStyle:=Value;
    Repaint;
  end;
end;

function TTernarySeries.GetGradient:TTeeGradient;
begin
  result:=Brush.Gradient;
end;

procedure TTernarySeries.SetAxisScale(const Value: TTernaryAxisScale);
begin
  if FAxisScale<>Value then
  begin
    FAxisScale:=Value;
    Repaint;
  end;
end;

procedure TTernarySeries.DrawVertexTitles;

  procedure DrawVertex(const S:TStrings; const X,Y:TCoordinate);
  begin
    FVertexTitle.Text:=S.Text;

    FVertexTitle.ShapeBounds.Left:=X;
    FVertexTitle.ShapeBounds.Top:=Y;

    TShapeAccess(FVertexTitle).CalcBounds(ParentChart);
    FVertexTitle.Draw;
  end;

  function TitleWidth(AIndex:Integer):TCoordinate;
  var t : Integer;
  begin
    result:=0;

    for t:=0 to FVertexTitles[AIndex].Count-1 do
        result:=Max(result,ParentChart.Canvas.TextWidth(FVertexTitles[AIndex][t]));
  end;

var offset: Integer;
    tmpTop,
    tmpBottom : Integer;
begin
  TTeePanelAccess(ParentChart).TryAssignFont(FVertexTitle.Font);

  // Calculate offset in pixels for Titles:
  CalcVerticalMargins(tmpTop,tmpBottom);

  if ParentChart.RightAxis.LabelsOnAxis then
     offset:=tmpTop+ParentChart.Canvas.FontHeight
  else
     offset:=0;

  DrawVertex(FVertexTitles[0],
             VertexA.X - (TitleWidth(0) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF}),
             VertexA.Y - offset);

  if GetHorizAxis.LabelsOnAxis then
     offset:=tmpBottom
  else
     offset:=0;

  DrawVertex(FVertexTitles[1],VertexB.X, VertexB.Y + offset);

  DrawVertex(FVertexTitles[2],
             VertexC.X - TitleWidth(2),
             VertexC.Y + offset);
end;

procedure TTernarySeries.DrawLegendShape(ValueIndex:Integer; Const Rect:TRect);
var tmpColor : TColor;
    tmpStyle : TSeriesPointerStyle;
begin
  tmpColor:=ValueColor[ValueIndex];

  if tmpColor<>clNone then // <-- not IsNull, optimized
  begin
    if Assigned(FOnGetPointerStyle) then
       tmpStyle:=FOnGetPointerStyle(Self,ValueIndex)
    else
       tmpStyle:=Pointer.Style;

    TeePointerDrawLegend(Pointer,tmpColor,Rect,Pointer.Pen.Visible,tmpStyle);
  end;
end;

procedure TTernarySeries.DrawMark(ValueIndex: Integer; const St: String;
  APosition: TSeriesMarkPosition);
begin
  APosition.LeftTop:=CalcPointFromValues(ValueIndex);
  inherited;
end;

procedure TTernarySeries.GetMarkText(Sender: TChartSeries;
  ValueIndex: Integer; var MarkText: String);
var radLabel,
    weightLabel,
    text2 : String;
begin
  if (FMarkStyle = tmsLong) or (FMarkStyle = tmsLongLabelled) then
  begin
    if FMarkStyle = tmsLongLabelled Then
    begin
      radLabel := TeeLineSeparator + TeeMsg_ValuesBubbleRadius + ' ';
      weightLabel := TeeLineSeparator + TeeMsg_ValuesWeighting + ' ';
    end
    else
    begin
      radLabel:=' ';
      weightLabel:=' ';
    end;

    text2:=radLabel+FloatToStr(RadiusValues[ValueIndex])+
           weightLabel+FloatToStr(WeightValues[ValueIndex]);

    MarkText := MarkText + text2;
  end;
end;

function TTernarySeries.LegendString(LegendIndex: Integer;
  LegendTextStyle: TLegendTextStyle): String;

var tmpValue : String;
    tmp : Double;
    ValueIndex : Integer;

  Function CalcPercentSt:String;

    function FormatList(const AList:TChartValueList):String;
    begin
      result:=FormatFloat(PercentFormat,100.0*AList[ValueIndex]/AList.TotalABS);
    end;

  begin
    if MandatoryValueList.TotalAbs=0 then
       result:=FormatFloat(PercentFormat,100)
    else
       result:=FormatList(XValues)+' '+FormatList(YValues)+' '+FormatList(ZValues);
  end;

  Function LegendPaletteIndex(LegendIndex:Integer):Integer;
  begin
    result:=Length(Palette)-(LegendEvery*LegendIndex)-1;
  end;

var tmpRadius,
    tmpWeight : String;
begin
  if ((UseColorRange or UsePalette) and (not ColorEachPoint)) then
    result:=inherited LegendString(LegendIndex,LegendTextStyle)
  else
  begin
    if UseColorRange or UsePalette then
    begin
      if CountLegendItems>LegendIndex then
      begin
        tmp:=Palette[LegendPaletteIndex(LegendIndex)].UpToValue;
        result:=FormatFloat(ValueFormat,tmp);
      end
      else
         result:='';
    end;

    ValueIndex:=LegendToValueIndex(LegendIndex);
    result:=Labels[ValueIndex];

    if LegendTextStyle<>ltsPlain then
    begin
      tmpValue:=FloatToStr(XValues[ValueIndex]) + ' ' +
                FloatToStr(YValues[ValueIndex]) + ' ' +
                FloatToStr(ZValues[ValueIndex]);

      Case LegendTextStyle of
        ltsLeftValue   : begin
                           if result<>'' then result:=TeeColumnSeparator+result;
                           result:=tmpValue+result;
                         end;
        ltsRightValue  : begin
                           if result<>'' then result:=result+TeeColumnSeparator;
                           result:=result+tmpValue;
                         end;
        ltsLeftPercent : begin
                           if result<>'' then result:=TeeColumnSeparator+result;
                           result:=CalcPercentSt+result;
                         end;
        ltsRightPercent: begin
                           if result<>'' then result:=result+TeeColumnSeparator;
                           result:=result+CalcPercentSt;
                         end;
        ltsXValue      : result:=tmpValue;
        ltsValue       : result:=tmpValue;
        ltsPercent     : result:=CalcPercentSt;
        ltsXandValue   : result:=tmpValue;
        ltsXandPercent : result:=tmpValue+TeeColumnSeparator+CalcPercentSt;
        ltsXAndText    : result:=tmpValue+TeeColumnSeparator+result;
      end;

      tmpRadius:=FloatToStr(RadiusValues[ValueIndex]);
      tmpWeight:=FloatToStr(WeightValues[ValueIndex]);

      Case FLegendStyle of
        tlsValuesBubble       : result:=result + ' ' + tmpRadius;
        tlsValuesBubbleWeight : result:=result + ' ' + tmpRadius+ ' ' + tmpWeight;
        tlsBubble             : result:=tmpRadius;
        tlsBubbleWeight       : result:=tmpRadius+ ' ' + tmpWeight;
        tlsWeight             : result:=tmpWeight;
      end;

    end;
  end;
end;

procedure TTernarySeries.AddSampleValues(NumValues: Integer;
  OnlyMandatory: Boolean);
Var t : Integer;
    s : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);

  with s do
  for t:=1 to NumValues do { some sample values to see something in design mode }
  Begin
    AddBubbleXYZWeight( RandomValue(100),  { X }
               RandomValue(100),     { Y }
               RandomValue(100),     { Z }
               30-(RandomValue(25)), { Radius }
               10-(RandomValue(10)), { Weight }
               clTeeColor);

    tmpX:=tmpX+StepX;
  end;
end;

function TTernarySeries.AddXYPoint(const AX, AY: Double; const ALabel: String;
  AColor: TColor): Integer;
begin
  if (AX+AY)<=100 then
    result:=AddXYZ(AX,AY,100-AX-AY,ALabel,AColor)
  else
    result:=-1;  
end;

procedure TTernarySeries.SetPointer(const Value: TSeriesPointer);
begin
  FPointer.Assign(Value);
end;

procedure TTernarySeries.SetWeightValues(const Value: TChartValueList);
begin
  SetChartValueList(FWeightValues,Value);
end;

function TTernarySeries.AddBubbleXYZWeight(const AX, AY, AZ, ARadius,
  AWeight: Double; AColor: TColor): Integer;
begin
  result:=AddBubbleXYZWeight(AX,AY,AZ,ARadius,AWeight,'',AColor);
end;

function TTernarySeries.AddBubbleXYZWeight(const AX, AY, AZ, ARadius,
  AWeight: Double; const ALabel: String; AColor: TColor): Integer;
begin
  RadiusValues.TempValue:=ARadius;
  WeightValues.TempValue:=AWeight;
  result:=AddXYZ(AX,AY,AZ,ALabel,AColor);
end;

function TTernarySeries.CountLegendItems: Integer;
begin
  if UsePalette then
     result:=PaletteSteps
  else
     result:=Count;
end;

procedure TTernarySeries.SetTernaryLegendStyle(const Value: TTernaryLegendStyle);
begin
  if FLegendStyle<>Value then
  begin
    FLegendStyle:=Value;
    Repaint;
  end;
end;

procedure TTernarySeries.DoBeforeDrawChart;
begin
  if FWeightValues.Total=0 then
  Begin
    UsePalette:=False;
    UseColorRange:=False;

    if not Assigned(FColors) Then
       ColorEachPoint:=True;
  end;

  inherited;
end;

// Resize ChartRect to accomodate VertexTitles size:
procedure TTernarySeries.DoBeforeDrawValues;
var tmpTop,
    tmpBottom, idx : Integer;
begin

  idx := self.ParentChart.SeriesList.IndexOf(self);   //when more than one ternary, only calc rect once
  if (idx>0) and (ParentChart.Series[idx-1] is TTernarySeries) then
      exit;

  tmpTop:=0;
  tmpBottom:=0;

  CalcVerticalMargins(tmpTop,tmpBottom);

  ParentChart.ChartRect.Top:=ParentChart.ChartRect.Top+tmpTop;
  ParentChart.ChartRect.Bottom:=ParentChart.ChartRect.Bottom-tmpBottom;

  inherited;
end;

function TTernarySeries.RaiseClicked:Boolean;
begin
  result:=(inherited RaiseClicked) or
          (Pointer.Visible and Assigned(FOnClickPointer));
end;

procedure TTernarySeries.MouseDown(ValueIndex:Integer; AtPointer:Boolean;
                                   Button:TMouseButton;
                                   Shift: TShiftState; X, Y: Integer);
begin
  inherited;

  // Fixes TV52014945 and TV52015075
  if AtPointer and (not ParentChart.CancelMouse) then
     if Assigned(FOnClickPointer) then
        FOnClickPointer(Self,ValueIndex,x,y);
end;

procedure TTernarySeries.PrepareForGallery;
begin
  inherited;

  Gradient.Visible:=True;
  TernaryStyle:=tsPoint;
  FillSampleValues(10);
end;

class function TTernarySeries.GetEditorClass: String;
begin
  result:='TTernarySeriesEditor';
end;

procedure TTernarySeries.SetTernaryMarkStyle(const Value: TTernaryMarkStyle);
begin
  if FMarkStyle<>Value then
  begin
    FMarkStyle:=Value;
    Repaint;
  end;
end;

procedure TTernarySeries.SetVertexTitle(const Value:TTextShape);
begin
  FVertexTitle.Assign(Value);
end;

procedure TTernarySeries.SetVertexTitles(const Value:TTernaryVertexTitles);
var t : Integer;
begin
  ClearTitles;

  SetLength(FVertexTitles,3);

  for t:=0 to Length(FVertexTitles)-1 do
  begin
    FVertexTitles[t]:=TStringList.Create;

    if Length(Value)>t then
       FVertexTitles[t].Assign(Value[t]);

    FVertexTitles[t].OnChange:=CanvasChanged;
  end;

  Repaint;
end;

function TTernarySeries.CalcXPos(ValueIndex: Integer): Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(CalcPointFromValues(ValueIndex).X);
end;

function TTernarySeries.CalcYPos(ValueIndex: Integer): Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(CalcPointFromValues(ValueIndex).Y);
end;

// Return size in pixels of VertexTitles multi-line Text.
// Top size is for VertexTitles[0]
// Bottom size is max of VertexTitles[1] and [2]
procedure TTernarySeries.CalcVerticalMargins(var TopMargin,BottomMargin:Integer);
var tmp : Integer;
begin
  inherited;

  if FVertexTitle.Visible then
  begin
    TTeePanelAccess(ParentChart).TryAssignFont(FVertexTitle.Font);

    tmp:=0;

    if Length(FVertexTitles)>0 then
       tmp:=Max(tmp,FVertexTitles[0].Count);

    Inc(TopMargin,ParentChart.Canvas.FontHeight*tmp);

    tmp:=0;

    if Length(FVertexTitles)>1 then
    begin
      tmp:=FVertexTitles[1].Count;

      if Length(FVertexTitles)>2 then
         tmp:=Max(tmp,FVertexTitles[2].Count);
    end;

    Inc(BottomMargin,ParentChart.Canvas.FontHeight*tmp);
  end;
end;

initialization
  RegisterTeeSeries( TTernarySeries, @TeeMsg_GalleryTernary,
                                     @TeeMsg_GalleryStats, 1 );
finalization
  UnRegisterTeeSeries([TTernarySeries]);
end.

