Page 1 of 1

Get the visible points

Posted: Wed Dec 16, 2015 2:28 am
by 16577003
I insert 1000 values to my candle series, X-axis is DateTime


1. i want to get the number of currently displaying points when i zoom in so i can scale candles widths, problem is when i use VisibleCount of series it will return 1000 which is all my data, but after i zoomed in i have like only 5 points visible so that value is incorrect.

2. when i zoom with mouse wheel it will keep zooming, how may i limit the zoom to for example not zoomin more than 2 days gap in x-axis and not zoomout when all points are displaying and with left and right of chart.

3. how can i force x-axis to only display the dates i have inserted and not any data in between? for example i add data with 5 days in gap but when i zoomin it will expand data range to all dates in between (see img)

Re: Get the visible points

Posted: Wed Dec 16, 2015 4:47 pm
by yeray
Hello,
n2n wrote:I insert 1000 values to my candle series, X-axis is DateTime
This is the basic application I've started with to test this (note I've tried this with VCL but it shouldn't be different in FMX):

Code: Select all

uses CandleCh;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Chart1.View3D:=false;
  Chart1.Legend.Visible:=false;

  with Chart1.AddSeries(TCandleSeries) as TCandleSeries do
  begin
    XValues.DateTime:=true;
    FillSampleValues(1000);
  end;
end;
n2n wrote:1. i want to get the number of currently displaying points when i zoom in so i can scale candles widths, problem is when i use VisibleCount of series it will return 1000 which is all my data, but after i zoomed in i have like only 5 points visible so that value is incorrect.
Try forcing a chart repaint at OnZoom event before retrieving the VisibleCount. Ie:

Code: Select all

procedure TForm1.Chart1Zoom(Sender: TObject);
begin
  Chart1.Draw;
  Caption:=IntToStr(Chart1[0].VisibleCount);
end;
n2n wrote:2. when i zoom with mouse wheel it will keep zooming, how may i limit the zoom to for example not zoomin more than 2 days gap in x-axis and not zoomout when all points are displaying and with left and right of chart.
You can also force the axis scale (minimum, maximum or both) at OnZoom event. Ie:

Code: Select all

procedure TForm1.Chart1Zoom(Sender: TObject);
begin
  if (Chart1.Axes.Bottom.Maximum-Chart1.Axes.Bottom.Minimum
      < DateTimeStep[dtOneDay]*2) then
  begin
    Chart1.Axes.Bottom.Maximum:=Chart1.Axes.Bottom.Minimum+DateTimeStep[dtOneDay]*2;
  end;

  Chart1.Draw;
  Caption:=IntToStr(Chart1[0].VisibleCount);
end;
n2n wrote:3. how can i force x-axis to only display the dates i have inserted and not any data in between? for example i add data with 5 days in gap but when i zoomin it will expand data range to all dates in between (see img)
Adding this at OnCreate seems to do what you describe:

Code: Select all

  Chart1.Axes.Bottom.LabelStyle:=talPointValue;

Re: Get the visible points

Posted: Thu Dec 17, 2015 9:46 am
by 16577003
Thanks for the reply,

Last problem solved, however how may i delete the space between missing dates? for example i have value for 1st dec and 5th dec and 3 days in between with no data, currently it will create empty space for them, how can i remove them?

Re: Get the visible points

Posted: Thu Dec 17, 2015 11:33 am
by yeray
Hello,
n2n wrote:how may i delete the space between missing dates? for example i have value for 1st dec and 5th dec and 3 days in between with no data, currently it will create empty space for them, how can i remove them?
This usually indicates the points are being added with an XValue - in your case a TDateTime.
I'd suggest you to convert your datetimes to strings (to use as labels) and add the values in consecutive XValues but with label. Ie:

Code: Select all

uses CandleCh, DateUtils;

procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
    tmpDate: TDateTime;
begin
  Chart1.View3D:=false;
  Chart1.Legend.Visible:=false;

  with Chart1.AddSeries(TCandleSeries) as TCandleSeries do
  begin
    XValues.DateTime:=true;
    FillSampleValues(10);

    tmpDate:=XValue[0];
    for i:=1 to Count-1 do
    begin
      tmpDate:=IncDay(tmpDate,1+Round(random*4));
      XValue[i]:=tmpDate;
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var tmpDate: TDateTime;
    tmpS: string;
    i: Integer;
begin
  with Chart1[0] as TCandleSeries do
  begin
    tmpDate:=XValue[0];
    for i:=0 to Count-1 do
    begin
      tmpS:=FormatDateTime(Chart1.Axes.Bottom.DateTimeFormat, XValue[i]);
      Labels[i]:=tmpS;

      XValue[i]:=tmpDate;
      tmpDate:=IncDay(tmpDate);
    end;
  end;
end;

Re: Get the visible points

Posted: Fri Dec 18, 2015 1:38 am
by 16577003
Hi,
Changing my Xvalues to string will cause me alot of problems, i do actually use the xvalue double data for some calculations, would there be any other workaround to overcome this with maintaining Xvalue with double data and not using Labels? also im currently using OnGetAxisLabel, to convert TDateTime to string due to some conditions when i zoom in i require to use different FormatDateTime.

Re: Get the visible points

Posted: Fri Dec 18, 2015 9:31 am
by yeray
Hello,

The XValue is the array of doubles TeeChart takes to calculate the x pixel position to draw each value in the series. That's why the easiest way to change the later is by changing the first.

An alternative to keep your DateTimes next to each value would be overriding TCandleSeries adding a new array of to it. Then, your values could be drawn using XValue as always (0, 1, 2,...) and you can make the axis to show the values in the array of DateTimes in the series.
Here it is a simple example doing it:

Code: Select all

uses CandleCh, DateUtils, TeeProCo, TeeConst;

type
  TMyCandleSeries = class(TCandleSeries)
  private
    FDate : TChartValueList;

    procedure SetDateValues(const Value:TChartValueList);

  protected
    //Changes the axis from showing the values in XValues to show the values in FDate
    function ValueListOfAxis(const Axis:TChartAxis):TChartValueList; override;

    //Use custom AddDateCandle function instead of AddOHLC fucntion
    procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;

  public
    property DateValues:TChartValueList read FDate write SetDateValues;

    constructor Create(AOwner: TComponent); override;

    //Adds a candle without XValue, so consecutive
    function AddDateCandle(const ADate:TDateTime;
                           const AOpen,AHigh,ALow,AClose:Double;
                           const ALabel:String='';
                           AColor:TColor=clTeeColor ):Integer;
  end;

  TValueListAccess=class(TChartValueList);

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

  TValueListAccess(XValues).InitDateTime(False);
  XValues.Name:=TeeMsg_ValuesX;

  FDate:=TChartValueList.Create(Self,TeeMsg_ValuesDate);
  TValueListAccess(DateValues).InitDateTime(True);

  NotMandatoryValueList:=FDate;
end;

Procedure TMyCandleSeries.SetDateValues(const Value:TChartValueList);
Begin
  SetChartValueList(FDate,Value);
end;

function TMyCandleSeries.ValueListOfAxis(const Axis:TChartAxis):TChartValueList;
begin
  if AssociatedToAxis(Axis) then
     if Axis.Horizontal then result:=FDate
                        else result:=YValues
  else
     result:=nil;
end;

Function TMyCandleSeries.AddDateCandle(const ADate:TDateTime;
                                      const AOpen,AHigh,ALow,AClose:Double;
                                      const ALabel:String='';
                                      AColor:TColor=clTeeColor ):Integer;
begin
  HighValues.TempValue:=AHigh;
  LowValues.TempValue:=ALow;
  OpenValues.TempValue:=AOpen;
  DateValues.TempValue:=ADate;
  result:=AddY(AClose,ALabel,AColor);
end;

Procedure TMyCandleSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
Var AOpen  : Double;
    AHigh  : Double;
    ALow   : Double;
    AClose : Double;
    t      : Integer;
    s      : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);

  with s do
  begin
    tmpX:=Date;
    AOpen:=MinY+RandomValue(Round(DifY)); { starting open price }

    t:=1;

    while t<=NumValues do
    Begin
      // Generate random figures
      GetRandomOHLC(AOpen,AClose,AHigh,ALow,DifY);

      AddDateCandle(tmpX,AOpen,AHigh,ALow,AClose);
      Inc(t);

      tmpX:=tmpX+StepX;  { <-- next point X value }

      // Tomorrow, the market will open at today's close plus/minus something }
      AOpen:=AClose+RandomValue(10)-5;
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
    tmpDate: TDateTime;
begin
  Chart1.View3D:=false;
  Chart1.Legend.Visible:=false;

  with Chart1.AddSeries(TMyCandleSeries) as TMyCandleSeries do
  begin
    FillSampleValues(10);

    //Modifying the DateValues array that only affects the labels, not the x positions of the candles
    tmpDate:=DateValues[0];
    for i:=1 to Count-1 do
    begin
      tmpDate:=IncDay(tmpDate,1+Round(random*4));
      DateValues[i]:=tmpDate;
    end;
  end;

  Chart1.Axes.Bottom.LabelStyle:=talPointValue;
end;

Re: Get the visible points

Posted: Mon Dec 21, 2015 6:56 am
by 16577003
Hi,

I just realized after using:

Code: Select all

LabelStyle := talPointValue;
To limit labels with only the ones with value, it will significantly reduce the performance when we have 1000 over points on our BottomAxis, and even though only few of the labels are showing actually but it will trigger the OnGetAxisLabel for every single point and this will even will trigger on every occasion even on mouse move. is it possible to improve performance on this issue?

Re: Get the visible points

Posted: Mon Dec 21, 2015 1:16 pm
by yeray
Hello,
n2n wrote:I just realized after using:

Code: Select all

LabelStyle := talPointValue;
To limit labels with only the ones with value, it will significantly reduce the performance when we have 1000 over points on our BottomAxis, and even though only few of the labels are showing actually but it will trigger the OnGetAxisLabel for every single point and this will even will trigger on every occasion even on mouse move. is it possible to improve performance on this issue?
If you are using OnGetAxisLabel only to format the bottom axis labels depending on the axis scale after zooming:
n2n wrote:also im currently using OnGetAxisLabel, to convert TDateTime to string due to some conditions when i zoom in i require to use different FormatDateTime.
You could try changing the bottom axis FormatDateTime at OnZoom/OnUndoZoom:

Code: Select all

procedure TForm1.Chart1Zoom(Sender: TObject);
var i: Integer;
    tmpFirstIndex, tmpLastIndex: Integer;
    visibleRange: double;
begin
  Chart1.Draw;
  tmpFirstIndex:=-1;
  tmpLastIndex:=-1;

  if Chart1[0] is TMyCandleSeries then
    with TMyCandleSeries(Chart1[0]) do
    begin
      for i:=0 to Count-1 do
      begin
        if (XValue[i]>=Chart1.Axes.Bottom.Minimum) and
           (XValue[i]<=Chart1.Axes.Bottom.Maximum) then
        begin
          tmpFirstIndex:=i;
          break;
        end;
      end;

      for i:=Count-1 downto 0 do
      begin
        if (XValue[i]>=Chart1.Axes.Bottom.Minimum) and
           (XValue[i]<=Chart1.Axes.Bottom.Maximum) then
        begin
          tmpLastIndex:=i;
          break;
        end;
      end;

      if (tmpFirstIndex>-1) and (tmpLastIndex>-1) then
      begin
        visibleRange:=DateValues[tmpLastIndex]-DateValues[tmpFirstIndex];

        if visibleRange < DateTimeStep[dtOneMonth] then
        begin
          Chart1.Title.Text.Text:=FormatDateTime('MMM yyyy', DateValues[LastDisplayedIndex]);
          Chart1.Axes.Bottom.DateTimeFormat:='dd';
        end
        else
        if visibleRange < DateTimeStep[dtOneYear] then
        begin
          Chart1.Title.Text.Text:=FormatDateTime('yyyy', DateValues[LastDisplayedIndex]);
          Chart1.Axes.Bottom.DateTimeFormat:='dd/MM';
        end;
      end;
    end;
end;

procedure TForm1.Chart1UndoZoom(Sender: TObject);
begin
  Chart1.Title.Text.Text:='TeeChart';
  Chart1.Axes.Bottom.DateTimeFormat:='dd/MM/yyyy';
end;

Re: Get the visible points

Posted: Tue Dec 22, 2015 1:46 am
by 16577003
Hi,

This will certainly do the trick for dateformat, however i still need

Code: Select all

LabelStyle := talPointValue;
To only show xvalues that i have added. would it be possible to calculate this manually aswell?

Re: Get the visible points

Posted: Tue Dec 22, 2015 8:25 am
by yeray
Hello,
n2n wrote:would it be possible to calculate this manually aswell?
I'm not sure to understand what do you mean here, or where do you need to get the labels.
However, if you know the value to get the label for, you can just call the axis LabelValue:

Code: Select all

  mystring:=Chart1.Axes.Bottom.LabelValue(Chart1[0].XValue[10]);
If you are using the customized TMyCandleSeries I suggested above, you would use this instead:

Code: Select all

  mystring:=Chart1.Axes.Bottom.LabelValue(Chart1[0].DateValues[10]);
Just note the chart needs to be drawn at least once to use those functions.