{**********************************************}
{   TeeChart Axis Breaks Tool                  }
{   Copyright (c) 2000-2020 by Steema Software }
{**********************************************}
unit TeeAxisBreaks;
{$I TeeDefs.inc}

interface

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

  {$IFDEF FMX}
  System.UITypes, FMX.Types,

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

  {$ELSE}

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

  Graphics, Controls,
  {$ENDIF}

  {$IFDEF D9}
  Types,
  {$ENDIF}

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

type
  TAxisBreakStyle=(tabSmallZigZag,tabZigZag,tabLine,tabNone);

  TAxisBreak=class(TCollectionItem)
  private
    FAxisBreakStyle : TAxisBreakStyle;
    FEnabled : Boolean;
    FEnd     : TAxisValue;
    FStart   : TAxisValue;

    Procedure SetAxisBreakStyle(Value:TAxisBreakStyle);
    procedure SetEnabled(const Value: Boolean);
    procedure SetEndValue(const Value: TAxisValue);
    procedure SetStartValue(const Value: TAxisValue);
  public
    Constructor Create(Collection: TCollection); override;

    procedure Assign(Source:TPersistent); override;
  published
    property AxisBreakStyle:TAxisBreakStyle read FAxisBreakStyle
                                    write SetAxisBreakStyle default tabZigZag;
    property Enabled:Boolean read FEnabled write SetEnabled default True;
    property EndValue:TAxisValue read FEnd write SetEndValue;
    property StartValue:TAxisValue read FStart write SetStartValue;
  end;

  TAxisBreaksTool=class;

  TAxisBreaks=class(TOwnedCollection)
  private
    ITool : TAxisBreaksTool;

    Function Get(Index:Integer):TAxisBreak;
    Procedure Put(Index:Integer; Const Value:TAxisBreak);
  protected
    procedure Update(Item: TCollectionItem); override;
  public
    function Add(const AStart, AEnd: TAxisValue):TAxisBreak; overload;

    property Item[Index:Integer]:TAxisBreak read Get write Put; default;
  end;

  TAxisBreaksTool=class(TTeeCustomToolAxis)
  private
    FBreaks  : TAxisBreaks;
    FGapSize : Integer;
    FScaleAdjustment : TAxisValue;

    tmpPoints : TPointArray;

    OldCalcX : TAxisCalcPos;
    OldCalcY : TAxisCalcPos;
    OldCalcPosPoint : TAxisCalcPosPoint;

    IOldLabel : TAxisOnGetLabel;
    IOldAxisAllowScroll : TChartAllowScrollEvent;

    procedure AxisCalcMaxLabel(Sender: TChartAxis;
              LabelIndex: Integer; var LabelValue: TAxisValue; var Stop: Boolean);
    procedure AxisDrawLine(Sender: TObject);

    function BreakSizeUpTo(const Value: TAxisValue): Integer;

    Function CalcXBreaks(Const Value:TChartValue):Integer;
    Function CalcYBreaks(Const Value:TChartValue):Integer;
    procedure ChartGetAxisLabel(Sender: TChartAxis;
             Series: TChartSeries; ValueIndex: Integer; var LabelText: String);
    procedure DrawValue(Series:TChartSeries; Index:Integer);
    procedure GetMarkText(Sender:TChartSeries; ValueIndex:Integer; var MarkText:String);
    procedure SetBreaks(const Value: TAxisBreaks);
    procedure SetGapSize(const Value: Integer);
    procedure SetScaleAdjustment(const Value: TAxisValue);
    procedure CheckNextLabelValue(Sender: TChartAxis; var tmpValue : TAxisValue);
  protected
    Procedure ChartEvent(AEvent:TChartToolEvent); override;
    class function GetEditorClass: String; override;
    procedure SetAxis(const Value: TChartAxis); override;
    function AdjustCalcPosPoint(const pixelVal: Integer) : TAxisValue;
    procedure AxisBreakDoAllowScroll( Sender:TChartAxis; var AMin,AMax:TAxisValue;
                                              var AllowScroll:Boolean );
    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;
    class Function LongDescription:String; override;
    function TotalLength(const AFrom,ATo:TAxisValue):TAxisValue;
  published
    property Brush;
    property Breaks:TAxisBreaks read FBreaks write SetBreaks;
    property ScaleAdjustment:TAxisValue read FScaleAdjustment write SetScaleAdjustment;
    property GapSize:Integer read FGapSize write SetGapSize default 5;
  end;

var
  TeeMaxPixelPos:Integer=$7FFF;

implementation

uses
  Math;

{ TAxisBreaksTool }

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

  FScaleAdjustment := 0.0000001;
  FBreaks:=TAxisBreaks.Create(Self,TAxisBreak);
  FBreaks.ITool:=Self;
  FGapSize:=5;

  Pen.EndStyle:=esFlat;
end;

Destructor TAxisBreaksTool.Destroy;
begin
  Axis:=nil;
  ParentChart:=nil;

  FBreaks.Clear;
  FBreaks.Free;

  inherited;
end;

procedure TAxisBreaksTool.Assign(Source: TPersistent);
begin
  if Source is TAxisBreaksTool then
  begin
    Breaks:=TAxisBreaksTool(Source).Breaks;
    FGapSize:=TAxisBreaksTool(Source).FGapSize;
  end;

  inherited;
end;

class function TAxisBreaksTool.Description: String;
begin
  result:=TeeMsg_AxisBreaksTool;
end;

function TAxisBreaksTool.BreakSizeUpTo(const Value: TAxisValue): Integer;
var t : Integer;
    tmp : TAxisValue;
begin
  result:=0;

  //if (Value <= Axis.Maximum) then
  Begin
    for t:=0 to Breaks.Count-1 do
    with Breaks[t] do
    if Enabled and ((EndValue-StartValue)<>0) then
    begin
      if StartValue<Value then
      begin
        if Axis.Horizontal then
        begin
          if StartValue>Axis.Minimum then
             if EndValue<=Value then
                tmp:=(EndValue-StartValue)
             else
                tmp:=StartValue-Value
          else
        if StartValue+(EndValue-StartValue)>Axis.Minimum then
            if StartValue+(EndValue-StartValue)<=Value then
                tmp:=StartValue+(EndValue-StartValue)-Axis.Minimum
             else
                tmp:=StartValue-Value
          else
             tmp:=0;
        end
        else begin
          if StartValue>Axis.Minimum then
             if StartValue+(EndValue-StartValue)<=Value then
                tmp:=(EndValue-StartValue)
             else
                tmp:=StartValue-Value
          else
          if StartValue+(EndValue-StartValue)>Axis.Minimum then
            if StartValue+(EndValue-StartValue)<=Value then
                tmp:=StartValue+(EndValue-StartValue)-Axis.Minimum
             else
                tmp:=StartValue-Value
          else
             tmp:=0;
        end;

        if Abs(tmp)>0 then
          if Axis.Horizontal then
          begin
           if Axis.Inverted then
              Dec(result,Axis.CalcSizeValue(Abs(tmp))-GapSize)
           else
           begin
              Inc(result,Axis.CalcSizeValue(Abs(tmp))-GapSize);
           end;
          end
          else begin
           if Axis.Inverted then
              Dec(result,Axis.CalcSizeValue(Abs(tmp))-GapSize)
           else
           begin
              Inc(result,Axis.CalcSizeValue(Abs(tmp))-GapSize);
           end;
          end;
      end;
    end;
  end;
end;

type
  TAxisAccess=class(TChartAxis);

function TAxisBreaksTool.CalcXBreaks(const Value: TChartValue): Integer;
Var OldUseTeeMaxPixelPos : Boolean;
begin
  OldUseTeeMaxPixelPos := TAxisAccess(Axis).IUseTeeMaxPixelPos;
  TAxisAccess(Axis).IUseTeeMaxPixelPos := False;

  result:=OldCalcX(Value);
  TAxisAccess(Axis).IUseTeeMaxPixelPos := OldUseTeeMaxPixelPos;

  if Active and (Breaks.Count>0) and Axis.Horizontal then
     Dec(result,BreakSizeUpTo(Value));

  if result> TeeMaxPixelPos  then
    result:=TeeMaxPixelPos
    else
    if result<-TeeMaxPixelPos then
      result:=-TeeMaxPixelPos
    else
       result:=Round(result);
end;

function TAxisBreaksTool.CalcYBreaks(const Value: TChartValue): Integer;
Var OldUseTeeMaxPixelPos : Boolean;
begin
  OldUseTeeMaxPixelPos := TAxisAccess(Axis).IUseTeeMaxPixelPos;
  TAxisAccess(Axis).IUseTeeMaxPixelPos := False;

  result:=OldCalcY(Value);
  TAxisAccess(Axis).IUseTeeMaxPixelPos := OldUseTeeMaxPixelPos;

  if Active and (Breaks.Count>0) and (not Axis.Horizontal) then
     Inc(result,BreakSizeUpTo(Value));

  if result> TeeMaxPixelPos  then result:=TeeMaxPixelPos
    else
    if result<-TeeMaxPixelPos then result:=-TeeMaxPixelPos
    else
       result:=Round(result);
end;

procedure TAxisBreaksTool.ChartEvent(AEvent: TChartToolEvent);

  procedure DrawRectangle(const tmpR: TRect);
  var tmpRect : TRect;
  begin
    with ParentChart.Canvas do
    begin
      tmpRect := ParentChart.ChartRect;

      {$IFDEF FMX}
      tmpRect.Inflate(-1,-1);
      {$ELSE}
      Dec(tmpRect.Bottom,1);
      Dec(tmpRect.Right,1);
      Inc(tmpRect.Top,1);
      Inc(tmpRect.Left,1);
      {$ENDIF}

      ClipRectangle( RectFromRectZ(tmpRect, 0)); //tmpZ));
      AssignVisiblePen(Self.Pen);
      if ParentChart.View3D then Rectangle(tmpR,0)//,tmpZ)
                            else Rectangle(tmpR);
      UnClipRectangle;
    end;
  end;

  procedure DrawPolygon;
  var tmpRect : TRect;
  begin
    with ParentChart.Canvas do
    begin
      tmpRect := ParentChart.ChartRect;

      {$IFDEF FMX}
      tmpRect.Inflate(-1,-1);
      {$ELSE}
      Dec(tmpRect.Bottom,1);
      Dec(tmpRect.Right,1);
      Inc(tmpRect.Top,1);
      Inc(tmpRect.Left,1);
      {$ENDIF}

      ClipRectangle( RectFromRectZ(tmpRect, 0)); //tmpZ));
      AssignVisiblePen(Self.Pen);
      if ParentChart.View3D then PolygonWithZ(tmpPoints,0)//,tmpZ)
                            else Polygon(tmpPoints);
      UnClipRectangle;
    end;
  end;

  procedure DoDrawZigZag(tmp : integer; SmallZigZag : Boolean);
  var
    tmpii,tmpi  : Integer;
    i,i2,c      : Integer;
    ZigZagCount : Integer;
    p,p1        : TPoint;
    ldown       : Boolean;
    tmpGapSize  : Integer;
  begin
    tmpii:=0;
    tmpi :=0;

    if not Axis.Horizontal then
    begin
      if Axis.Inverted then
        tmpGapSize := FGapSize
      else
        tmpGapSize := -FGapSize;

      ParentChart.Canvas.DoHorizLine(Axis.PosAxis-5,Axis.PosAxis+5,tmp);
      ParentChart.Canvas.DoHorizLine(Axis.PosAxis-5,Axis.PosAxis+5,tmp+tmpGapSize);

      p := ParentChart.Canvas.Calculate3DPosition(ParentChart.ChartRect.Left-5,tmp + tmpGapSize,0);
      p1 :=ParentChart.Canvas.Calculate3DPosition(ParentChart.ChartRect.Right,tmp + tmpGapSize,0);

      if not Axis.Inverted then
        tmp := tmp-FGapSize;

      i2 :=tmp;
      c:=0;
      ldown :=false;
      if SmallZigZag then
        ZigZagCount := 5
      else
        ZigZagCount := 15;

      for i:= {$IFDEF FMX}Round{$ENDIF}(p.X) to {$IFDEF FMX}Round{$ENDIF}(p1.x) do
      begin
        if (i-p.X)>10 then
          if not ldown then
          begin
            if c < ZigZagCount then
            begin
              ldown := false;
              inc(c,1);
            end
            else
              ldown := true;
          end
          else
          begin
            if c > 0 then
            begin
              ldown := true;
              dec(c,1);
            end
            else
              ldown := false;
          end;

          if (c=ZigZagCount) or (c=0) or (i=p1.X) or (i=p.X) then
          begin
            SetLength(tmpPoints,length(tmpPoints)+1);
            tmpPoints[length(tmpPoints)-1].X:=i;
            tmpPoints[length(tmpPoints)-1].Y:=i2+c;
          end;
      end;

      SetLength(tmpPoints,length(tmpPoints)+1);
      tmpPoints[length(tmpPoints)-1].X:=p1.x;
      tmpPoints[length(tmpPoints)-1].Y:=i2+c-tmpii+FGapSize;

      ldown := not ldown;

      p.Y:= p.Y + FGapSize;

      for i := {$IFDEF FMX}Round{$ENDIF}(p1.X) downto {$IFDEF FMX}Round{$ENDIF}(p.x) do
      begin
        if ((p1.X-i)>0) and ((i-p.X)>9)  then
          if not ldown then
          begin
            if c < ZigZagCount then
            begin
              ldown := false;
              inc(c,1);
            end
            else
              ldown := true;
          end
          else
          begin
            if c > 0 then
            begin
              ldown := true;
              dec(c,1);
            end
            else
              ldown := false;
          end;

          if (c=ZigZagCount) or (c=0) or (i=p.X) or (i=p1.X) then
          begin
            SetLength(tmpPoints,length(tmpPoints)+1);
            tmpPoints[length(tmpPoints)-1].X:=i;
            tmpPoints[length(tmpPoints)-1].Y:=i2+c+FGapSize;
          end;
      end;

      ParentChart.Canvas.AssignBrush(Brush);

      DrawPolygon;
      tmpPoints:=nil;
    end
    else
    begin  // horizontal
      if Axis.Inverted then
        tmpGapSize := -FGapSize
      else
        tmpGapSize := FGapSize;

      ParentChart.Canvas.DoVertLine(tmp,Axis.PosAxis-5,Axis.PosAxis+5);
      ParentChart.Canvas.DoVertLine(tmp+tmpGapSize,Axis.PosAxis-5,Axis.PosAxis+5);

      p := ParentChart.Canvas.Calculate3DPosition(tmp-tmpGapSize,ParentChart.ChartRect.Top,0);
      p1 :=ParentChart.Canvas.Calculate3DPosition(tmp,ParentChart.ChartRect.Bottom+5,0);

      if Axis.Inverted then
        tmp := tmp-FGapSize;

      i2 :=tmp;
      c:=0;
      ldown :=false;

     if SmallZigZag then
        ZigZagCount := 5
      else
        ZigZagCount := 15;

      for i := {$IFDEF FMX}Round{$ENDIF}(p1.Y) downto {$IFDEF FMX}Round{$ENDIF}(p.Y) do
      begin
        if ((i+10)<p1.Y) then
          if not ldown then
          begin
            if c < ZigZagCount then
            begin
              ldown := false;
              inc(c,1);
            end
            else
              ldown := true;
          end
          else begin
            if c > 0 then
            begin
              ldown := true;
              dec(c,1);
            end
            else
              ldown := false;
          end;

          if (c=ZigZagCount) or (c=0) or (i=p1.y) or (i=p.y) then
          begin
            SetLength(tmpPoints,length(tmpPoints)+1);
            tmpPoints[length(tmpPoints)-1].Y:=i;
            tmpPoints[length(tmpPoints)-1].X:=i2+c;
          end;

        tmpi:=i;
      end;

      SetLength(tmpPoints,length(tmpPoints)+1);
      tmpPoints[length(tmpPoints)-1].Y:=tmpi;
      tmpPoints[length(tmpPoints)-1].X:=i2+c-tmpii+FGapSize;

      ldown := not ldown;

      p.X := p.X + FGapSize;

      for i := {$IFDEF FMX}Round{$ENDIF}(p.Y) to {$IFDEF FMX}Round{$ENDIF}(p1.Y) do
      begin
        if ((p1.Y-i)>0) and ((p1.Y-i)>10) then
          if not ldown then
          begin
            if c < ZigZagCount then
            begin
              ldown := false;
              inc(c,1);
            end
            else
              ldown := true;
          end
          else
          begin
            if c > 0 then
            begin
              ldown := true;
              dec(c,1);
            end
            else
              ldown := false;
          end;

          if (c=ZigZagCount) or (c=0) or (i=p.Y) or (i=p1.Y) then
          begin
            SetLength(tmpPoints,length(tmpPoints)+1);
            tmpPoints[length(tmpPoints)-1].Y:=i;
            tmpPoints[length(tmpPoints)-1].X:=i2+c+FGapSize;
          end;
      end;

      ParentChart.Canvas.AssignBrush(Brush);

      DrawPolygon;
      tmpPoints:=nil;
    end;
  end;

  procedure DoDrawLine(tmp:Integer);
  var
    p,p1 : Tpoint;
    tmpGapSize : integer;
  begin
    if not Axis.Horizontal then
    begin
      if Axis.Inverted then
        tmpGapSize := -FGapSize
      else
        tmpGapSize := FGapSize;

      ParentChart.Canvas.DoHorizLine(Axis.PosAxis-5,Axis.PosAxis+5,tmp);
      ParentChart.Canvas.DoHorizLine(Axis.PosAxis-5,Axis.PosAxis+5,tmp-tmpGapSize);

      p := ParentChart.Canvas.Calculate3DPosition(Axis.PosAxis-5,tmp - tmpGapSize,0);
      p1 :=ParentChart.Canvas.Calculate3DPosition(ParentChart.ChartRect.Right,tmp - tmpGapSize,0);

      if Axis.Inverted then
        tmp := tmp+FGapSize;

      ParentChart.Canvas.AssignBrush(Brush);
      DrawRectangle(TeeRect(p.X-Pen.Width,tmp-FGapSize,p1.x+Pen.Width,tmp));
    end
    else
    begin   // Horizontal
      if Axis.Inverted then
        tmpGapSize := -FGapSize
      else
        tmpGapSize := FGapSize;

      ParentChart.Canvas.DoVertLine(tmp,Axis.PosAxis-5,Axis.PosAxis+5);
      ParentChart.Canvas.DoVertLine(tmp+tmpGapSize,Axis.PosAxis-5,Axis.PosAxis+5);

      p := ParentChart.Canvas.Calculate3DPosition(0 ,ParentChart.ChartRect.Top,0);
      p1 :=ParentChart.Canvas.Calculate3DPosition(0 ,ParentChart.ChartRect.Bottom+5,0);

      if Axis.Inverted then
        tmp := tmp-FGapSize;

      ParentChart.Canvas.AssignBrush(Brush);
      DrawRectangle(TeeRect(tmp,p.y-Pen.Width,tmp + FGapSize,p1.y+Pen.Width));
    end;
  end;

var t : Integer;
    tmp : Integer;
begin
  if Active and (AEvent=cteAfterDraw) then
     for t:=0 to Breaks.Count-1 do
     with Breaks[t] do
       if Enabled and ((EndValue-StartValue)<>0) then
          if (StartValue>=Axis.Minimum) and (StartValue<=Axis.Maximum) then
          begin
            tmp:=Axis.CalcPosValue(StartValue);

            ParentChart.Canvas.AssignVisiblePen(Self.Pen);

            Case AxisBreakStyle of
              tabSmallZigZag : DoDrawZigZag(tmp, true);
              tabZigZag : DoDrawZigZag(tmp, false);
              tabLine  : DoDrawLine(tmp);
            else
              {talNone : } ;
            end
          end;

  inherited;
end;

class function TAxisBreaksTool.GetEditorClass: String;
begin
  result:='TAxisBreaksEditor';
end;

class function TAxisBreaksTool.LongDescription: String;
begin
  result:=TeeMsg_AxisBreaksToolDesc;
end;

procedure TAxisBreaksTool.AxisCalcMaxLabel(Sender: TChartAxis;
      LabelIndex: Integer; var LabelValue: TAxisValue; var Stop: Boolean);
begin
  if Active then
     LabelValue:=TotalLength(Sender.Minimum,Sender.Maximum)
  else
     LabelValue:=0;
end;

function TAxisBreaksTool.AdjustCalcPosPoint(const PixelVal: Integer) : TAxisValue;
var
  scaleVal,
  tmpStep : TAxisValue;
  hiddenBreaks, i : Integer;
Begin
  scaleVal := OldCalcPosPoint(pixelVal);

  if Active and (Breaks.Count > 0) then
  Begin
    tmpStep := 0;
    hiddenBreaks := 0;

    for i:=0 to Breaks.Count - 1 do
    Begin
      if (Breaks[i].EndValue > Axis.Minimum) then
      Begin
        if (scaleVal > Breaks[i].StartValue) then
        Begin
          tmpStep := tmpStep + ((Breaks[i].EndValue) - (Breaks[i].StartValue));

          if (((Axis.Horizontal) and (not Axis.Inverted)) or
              ((not Axis.Horizontal) and (Axis.Inverted))) then
          Begin
            if (pixelVal > (Axis.CalcPosValue(Breaks[i].StartValue)) + GapSize) then
                scaleVal := OldCalcPosPoint(pixelVal - (GapSize * (i + 1 - hiddenBreaks)))
                            + tmpStep
            else
              break;
          end
          else
          Begin
            if (pixelVal < (Axis.CalcPosValue(Breaks[i].StartValue)) - GapSize) then
                scaleVal := OldCalcPosPoint(pixelVal + (GapSize * (i + 1 - hiddenBreaks)))
                            - tmpStep;
          end;
        end
        else
          break;
      end
      else
        Inc(hiddenBreaks);

    end;
  end;

  result := scaleVal;
end;

type
  TAxisItemsAccess=class(TAxisItems);

procedure TAxisBreaksTool.SetAxis(const Value: TChartAxis);
var
  i : integer;
begin
  if Axis<>Value then
  begin
    OldCalcX:=nil;
    OldCalcY:=nil;
    OldCalcPosPoint:=nil;

    if Assigned(Axis) then
    begin
      Axis.Horizontal:=Axis.Horizontal; // force reset "calc" methods

      TAxisAccess(Axis).OnCalcMaxLabel:=nil;
      TAxisAccess(Axis).OnDrawAxis:=nil;
      TAxisAccess(Axis).OnNextLabelValue:=nil;

      TAxisItemsAccess(Axis.Items).IDrawAll:=False;
    end;

    inherited;

    if Assigned(Axis) then
    begin
      TAxisAccess(Axis).OnCalcMaxLabel:=AxisCalcMaxLabel;
      TAxisAccess(Axis).OnDrawAxis:=AxisDrawLine;
      TAxisAccess(Axis).OnNextLabelValue:=CheckNextLabelValue;

      with Axis do
      begin
        OldCalcX:=CalcXPosValue;
        OldCalcY:=CalcYPosValue;

        CalcXPosValue:=CalcXBreaks;
        CalcYPosValue:=CalcYBreaks;

        OldCalcPosPoint := CalcPosPoint;
        CalcPosPoint := AdjustCalcPosPoint;

        for i := 0 to ParentChart.SeriesCount-1 do
            ParentChart.Series[i].OnGetMarkText := GetMarkText;

        if Horizontal then CalcPosValue:=CalcXPosValue
                      else CalcPosValue:=CalcYPosValue;

        TAxisItemsAccess(Items).IDrawAll:=True;
      end;
    end;
  end;
end;

type
  TSeriesAccess=class(TChartSeries);

{ Experimental code ...... }
procedure TAxisBreaksTool.DrawValue(Series:TChartSeries; Index:Integer);
var t        : Integer;
    tt       : Integer;
    tmpBreak : TAxisBreaksTool;
    AList    : TChartValueList;
begin
  for t:=0 to ParentChart.Tools.Count -1 do
  if ParentChart.Tools.Items[t] is TAxisBreaksTool then
  begin
    tmpBreak:=TAxisBreaksTool(ParentChart.Tools.Items[t]);

    if tmpBreak.Active and (tmpBreak.Breaks.Count > 0) then
    begin
      if tmpBreak.Axis.Horizontal then AList:=Series.XValues
                                  else AList:=Series.YValues;

      for tt:=0 to tmpBreak.Breaks.Count-1 do
      with tmpBreak.Breaks[tt] do
        if (AList.Value[Index] > StartValue) and
           (AList.Value[Index] < EndValue) then
        Begin
          TSeriesAccess(Series).DrawHiddenValue(Index,StartValue,EndValue,
                                                tmpBreak.GapSize,tmpBreak.Axis);
          Exit;
        end;
    end;
  end;

  TSeriesAccess(Series).DrawValue(Index);
end;

procedure TAxisBreaksTool.SetBreaks(const Value: TAxisBreaks);
begin
  FBreaks.Assign(Value);
end;

{$IFNDEF D6}
function TryStrToDateTime(const S: string; out Value: TDateTime): Boolean;
begin
  result:=False;

  try
    Value:=StrToDateTime(S);
    result:=True;
  except
    on EConvertError do ;  // Skip exception
  end;
end;

function InRange(const AValue, AMin, AMax: TAxisValue): Boolean;
begin
  Result := (AValue >= AMin) and (AValue <= AMax);
end;
{$ENDIF}

// Removes axis labels that are "inside" breaks
procedure TAxisBreaksTool.ChartGetAxisLabel(Sender: TChartAxis;
         Series: TChartSeries; ValueIndex: Integer; var LabelText: String);
var tmp : TChartValue;
    t   : Integer;
    tmpValid : Boolean;
    tmpDate : TDateTime;
begin
  if Active and (Sender=Axis) and (LabelText<>'') then
  begin
    if Sender.IsDateTime then
    begin
      tmpValid:=TryStrToDateTime(LabelText,tmpDate);

      if tmpValid then
         tmp:=tmpDate;
    end
    else
       tmpValid:=TryStrToFloat(LabelText,tmp);

    if tmpValid then
       for t:=0 to Breaks.Count-1 do
       with Breaks[t] do
          if Enabled and ((EndValue-StartValue)<>0) then
          begin
            if (tmp>StartValue) and (tmp<=(StartValue+(EndValue-StartValue))) then
            begin
              LabelText:='';
              break;
            end;
         end;
  end;
end;

type
  TCustomChartAccess=class(TCustomAxisPanel);

procedure TAxisBreaksTool.SetParentChart(const Value: TCustomAxisPanel);
var tmp : Boolean;
begin
  tmp:=ParentChart<>Value;

  if tmp and Assigned(ParentChart) then
  begin
    ParentChart.OnGetAxisLabel:=IOldLabel;
    TCustomChart(ParentChart).OnAllowScroll:=IOldAxisAllowScroll;
    TCustomChartAccess(ParentChart).FOnDrawValue:=nil;
  end;

  inherited;

  if tmp and Assigned(ParentChart) then
  begin
    IOldLabel:=ParentChart.OnGetAxisLabel;
    ParentChart.OnGetAxisLabel:=ChartGetAxisLabel;

    IOldAxisAllowScroll:=TCustomChart(ParentChart).OnAllowScroll;
    TCustomChart(ParentChart).OnAllowScroll:=AxisBreakDoAllowScroll;
    TCustomChartAccess(ParentChart).FOnDrawValue:=DrawValue;
  end;
end;

// Returns sum of all breaks "Length" that are between AFrom and ATo range
function TAxisBreaksTool.TotalLength(const AFrom, ATo: TAxisValue): TAxisValue;
var t,
    gapCounter : Integer;
begin
  result:=0;

  if Breaks.Count > 0 then
  Begin
    gapCounter :=0;

    for t:=0 to Breaks.Count-1 do
    with Breaks[t] do
    begin
      if (StartValue > AFrom) and (EndValue < ATo) then
      Begin
        Inc(gapCounter);
        result:=result + (EndValue-StartValue);
      end;
    end;

    TAxisAccess(Axis).IGapSize := GapSize * gapCounter;
  end
  else
    TAxisAccess(Axis).IGapSize := 0;
end;

procedure TAxisBreaksTool.AxisDrawLine(Sender: TObject);
var tmpStart : Integer;
    tmpEnd   : Integer;
    tmpPos   : Integer;
    t        : Integer;
begin
  tmpStart:=Axis.IStartPos;
  tmpEnd:=Axis.IEndPos;

  if Active and (Breaks.Count>0) then
  begin
    for t:=0 to Breaks.Count-1 do
    with Breaks[t] do
    begin
       if Enabled and (StartValue<=Axis.Maximum) and ((EndValue-StartValue)<>0) then
       begin
         tmpEnd:=Axis.CalcPosValue(StartValue);

         if (tmpEnd<=Axis.IEndPos) and (tmpEnd >= Axis.IStartPos) then
         begin
           tmpPos:=tmpEnd;

           if Axis.Horizontal then
           begin
             if Axis.Inverted then
                Dec(tmpPos,FGapSize);
           end
           else
             if not Axis.Inverted then
                Dec(tmpPos,FGapSize);

           TAxisAccess(Axis).DrawLineSegment(tmpStart,tmpPos);

           tmpStart:=tmpEnd;
         end;
       end;
    end;

    if tmpStart<Axis.IEndPos then
    begin
      tmpPos:=tmpStart;

      if Axis.Horizontal then
      begin
        if not Axis.Inverted then
           Inc(tmpPos,FGapSize);
      end
      else
        if Axis.Inverted then
           Inc(tmpPos,FGapSize);

      TAxisAccess(Axis).DrawLineSegment(tmpPos,Axis.IEndPos);
    end;
  end
  else
    TAxisAccess(Axis).DrawLineSegment(tmpStart,tmpEnd);
end;

procedure TAxisBreaksTool.SetGapSize(const Value: Integer);
begin
  SetIntegerProperty(FGapSize,Value);
end;

procedure TAxisBreaksTool.GetMarkText(Sender: TChartSeries;
  ValueIndex: Integer; var MarkText: String);
var t   : Integer;
    tmp : TChartValue;
begin
  if Active then
  begin
    tmp:=Sender.MandatoryValueList.Value[ValueIndex];

    for t:=0 to Breaks.Count-1 do
        if InRange(tmp,Breaks[t].StartValue,Breaks[t].EndValue) then
        begin
          MarkText:='';
          break;
        end;
  end;
end;

/// <summary>
/// Default 0.0000001. This variable avoids jump/flicker when scrolling across Break boundaries
/// by moving scroll to 'just' outside of Break zone when traversing Axis Min or Max.
/// Adjust if your Axis scale requires different sensitivity.
/// </summary>
procedure TAxisBreaksTool.SetScaleAdjustment(const Value : TAxisValue);
Begin
  FScaleAdjustment := Value;
end;

procedure TAxisBreaksTool.AxisBreakDoAllowScroll( Sender:TChartAxis; var AMin,AMax:TAxisValue;
                                                  var AllowScroll:Boolean );
Var aBreak : TAxisBreak;
    Delta  : TAxisValue;
    t      : Integer;
Begin
  if Assigned(IOldAxisAllowScroll) then
     IOldAxisAllowScroll(Sender,AMin,AMax,AllowScroll);

  if Active and AllowScroll then
  begin
    if (Axis = Sender) then
    Begin
      if (AMin > Sender.Minimum) then
        Delta := -1
      else
        Delta := +1;

      for t := 0 to Breaks.Count-1 do
      Begin
        aBreak := Breaks[t];

        if (Delta < 0) then
        Begin
          if Axis.MinimumOffset<>0 then
          if ((aBreak.Enabled) and ((AMin > aBreak.StartValue) and (AMin < aBreak.EndValue))) then
            AMin := aBreak.EndValue + ScaleAdjustment;

          if Axis.MaximumOffset<>0 then
          if ((aBreak.Enabled) and ((AMax > aBreak.StartValue) and (AMax < aBreak.EndValue))) then
            AMax := aBreak.EndValue + ScaleAdjustment;
        end
        else
        Begin
          if Axis.MinimumOffset<>0 then
          if ((aBreak.Enabled) and ((AMin < aBreak.EndValue) and (AMin > aBreak.StartValue))) then
            AMin := aBreak.StartValue - ScaleAdjustment;

          if Axis.MaximumOffset<>0 then
          if ((aBreak.Enabled) and ((AMax < aBreak.EndValue) and (AMax > aBreak.StartValue))) then
            AMax := aBreak.StartValue - ScaleAdjustment;
        end;
      end;
    end;
  end;
end;

procedure TAxisBreaksTool.CheckNextLabelValue(sender : TChartAxis;
                                                     var tmpValue : TAxisValue);
var aBreak : TAxisBreak;
  t : Integer;
Begin
  if tmpValue <= breaks.ITool.Axis.Maximum then
  for t := 0 to breaks.Count -1 do
  Begin
    aBreak := breaks[t];

    if tmpValue > aBreak.EndValue then
       Exit;

    if aBreak.Enabled and ((aBreak.EndValue - aBreak.StartValue) <> 0) then
    Begin
      if ((aBreak.StartValue < tmpValue) and (aBreak.EndValue > tmpValue)
        and (aBreak.StartValue > breaks.ITool.Axis.Minimum)) then
      Begin
        tmpValue := aBreak.StartValue;
      End;
    End;
  End;
end;

{ TAxisBreak }

Constructor TAxisBreak.Create(Collection: TCollection);
begin
  inherited;
  FEnabled:=True;
  FAxisBreakStyle:=tabZigZag;
end;

procedure TAxisBreak.Assign(Source: TPersistent);
begin
  if Source is TAxisBreak then
  with TAxisBreak(Source) do
  begin
    Self.FAxisBreakStyle:=FAxisBreakStyle;
    Self.FEnabled:=FEnabled;
    Self.FEnd:=FEnd;
    Self.FStart:=FStart;
  end
  else
    inherited;
end;

procedure TAxisBreak.SetEnabled(const Value: Boolean);
begin
  TAxisBreaks(Collection).ITool.SetBooleanProperty(FEnabled,Value);
end;

procedure TAxisBreak.SetEndValue(const Value: TAxisValue);
begin
  if FEnd<>Value then
  begin
    FEnd:=Value;
    TAxisBreaks(Collection).ITool.Repaint;
  end;
end;

procedure TAxisBreak.SetStartValue(const Value: TAxisValue);
begin
  if FStart<>Value then
  begin
    FStart:=Value;
    TAxisBreaks(Collection).ITool.Repaint;
  end;
end;

procedure TAxisBreak.SetAxisBreakStyle(Value: TAxisBreakStyle);
begin
  if FAxisBreakStyle<>Value then
  begin
    FAxisBreakStyle:=Value;
    TAxisBreaks(Collection).ITool.Repaint;
  end;
end;

{ TAxisBreaks }

function TAxisBreaks.Add(const AStart, AEnd: TAxisValue):TAxisBreak;
begin
  result:=inherited Add as TAxisBreak;
  result.StartValue:=AStart;
  result.EndValue:=AEnd;
end;

function TAxisBreaks.Get(Index: Integer): TAxisBreak;
begin
  result:=TAxisBreak(GetItem(Index));
end;

procedure TAxisBreaks.Put(Index: Integer; const Value: TAxisBreak);
begin
  SetItem(Index,Value);
end;

procedure TAxisBreaks.Update(Item: TCollectionItem);
begin
  inherited;
  ITool.Repaint;
end;

initialization
  RegisterTeeTools([TAxisBreaksTool]);
finalization
  UnRegisterTeeTools([TAxisBreaksTool]);
end.
