TAChart: Add TCalculatedChartSource.AccumulationDirection property. Add tests.

git-svn-id: trunk@32396 -
This commit is contained in:
ask 2011-09-18 10:13:40 +00:00
parent 60590c7530
commit 27ff423301
4 changed files with 115 additions and 41 deletions

View File

@ -191,10 +191,10 @@ type
procedure AddFirst(const AItem: TChartDataItem);
procedure AddLast(const AItem: TChartDataItem);
procedure Clear; inline;
function GetPLast: PChartDataItem; overload;
function GetPLast(AOffset: Cardinal): PChartDataItem; overload;
function GetPtr(AOffset: Cardinal): PChartDataItem; overload;
procedure GetSum(var AItem: TChartDataItem);
procedure RemoveLast; overload;
procedure RemoveFirst;
procedure RemoveLast;
procedure RemoveValue(const AItem: TChartDataItem);
property Capacity: Cardinal read GetCapacity write SetCapacity;
end;
@ -415,6 +415,7 @@ end;
procedure TChartSourceBuffer.Clear;
begin
FCount := 0;
FStart := 0;
FSum.Y := 0;
FSum.YList := nil;
end;
@ -429,25 +430,28 @@ begin
Result := Length(FBuf);
end;
function TChartSourceBuffer.GetPLast(AOffset: Cardinal): PChartDataItem;
function TChartSourceBuffer.GetPtr(AOffset: Cardinal): PChartDataItem;
begin
if AOffset >= FCount then
raise EBufferError.Create('AOffset');
AOffset := FCount - 1 - AOffset;
Result := @FBuf[(FStart + AOffset + Capacity) mod Capacity];
end;
function TChartSourceBuffer.GetPLast: PChartDataItem;
begin
Result := @FBuf[EndIndex];
end;
procedure TChartSourceBuffer.GetSum(var AItem: TChartDataItem);
begin
AItem.Y := FSum.Y;
AItem.YList := Copy(FSum.YList);
end;
procedure TChartSourceBuffer.RemoveFirst;
begin
if FCount = 0 then
raise EBufferError.Create('Empty');
RemoveValue(FBuf[FStart]);
FCount -= 1;
FStart := (FStart + 1) mod Capacity;
end;
procedure TChartSourceBuffer.RemoveLast;
begin
if FCount = 0 then

View File

@ -158,11 +158,13 @@ type
end;
TChartAccumulationMethod = (camNone, camSum, camAverage, camDerivative);
TChartAccumulationDirection = (cadBackward, cadForward, cadCenter);
{ TCalculatedChartSource }
TCalculatedChartSource = class(TCustomChartSource)
strict private
FAccumulationDirection: TChartAccumulationDirection;
FAccumulationMethod: TChartAccumulationMethod;
FAccumulationRange: Cardinal;
FHistory: TChartSourceBuffer;
@ -179,7 +181,10 @@ type
procedure CalcDerivative(var AIndex: Integer);
procedure CalcPercentage;
procedure Changed(ASender: TObject);
function EffectiveAccumulationRange: Cardinal;
procedure ExtractItem(out AItem: TChartDataItem; AIndex: Integer);
procedure RangeAround(AIndex: Integer; out ALeft, ARight: Integer);
procedure SetAccumulationDirection(AValue: TChartAccumulationDirection);
procedure SetAccumulationMethod(AValue: TChartAccumulationMethod);
procedure SetAccumulationRange(AValue: Cardinal);
procedure SetOrigin(AValue: TCustomChartSource);
@ -196,6 +201,9 @@ type
function IsSorted: Boolean; override;
published
property AccumulationDirection: TChartAccumulationDirection
read FAccumulationDirection write SetAccumulationDirection
default cadBackward;
property AccumulationMethod: TChartAccumulationMethod
read FAccumulationMethod write SetAccumulationMethod default camNone;
property AccumulationRange: Cardinal
@ -214,6 +222,9 @@ implementation
uses
Math, StrUtils, SysUtils;
const
MAX_DERIVATIVE_RANGE = 10;
type
{ TListChartSourceStrings }
@ -854,47 +865,58 @@ end;
procedure TCalculatedChartSource.CalcAccumulation(AIndex: Integer);
var
i, ar: Integer;
i, oldLeft, oldRight, newLeft, newRight: Integer;
begin
if (AccumulationMethod = camDerivative) and (AccumulationRange = 0) then
FHistory.Capacity := 10
if AccumulationDirection = cadCenter then
FHistory.Capacity := EffectiveAccumulationRange * 2
else
FHistory.Capacity := AccumulationRange;
ar := IfThen(AccumulationRange = 0, MaxInt, AccumulationRange);
if FIndex = AIndex - 1 then begin
ExtractItem(FItem, AIndex);
FHistory.AddLast(FItem);
end
else if FIndex = AIndex + 1 then begin
if AccumulationRange = 0 then begin
ExtractItem(FItem, FIndex);
FHistory.RemoveValue(FItem);
ExtractItem(FItem, AIndex);
end
else begin
i := AIndex - AccumulationRange + 1;
if i < 0 then
FHistory.RemoveLast
else begin
ExtractItem(FItem, i);
FHistory.AddFirst(FItem);
end;
FItem := FHistory.GetPLast^;
FHistory.Capacity := EffectiveAccumulationRange;
RangeAround(FIndex, oldLeft, oldRight);
RangeAround(AIndex, newLeft, newRight);
if
(FIndex < 0) or (Abs(oldLeft - newLeft) > 1) or
(Abs(oldRight - newRight) > 1)
then begin
FHistory.Clear;
for i := newLeft to newRight do begin
ExtractItem(FItem, i);
FHistory.AddLast(FItem);
end;
end
else begin
FHistory.Clear;
for i := Max(AIndex - ar + 1, 0) to AIndex do begin
if FHistory.Capacity = 0 then
for i := oldLeft to newLeft - 1 do begin
ExtractItem(FItem, i);
FHistory.RemoveValue(FItem);
end
else
for i := oldLeft to newLeft - 1 do
FHistory.RemoveFirst;
if FHistory.Capacity = 0 then
for i := oldRight downto newRight + 1 do begin
ExtractItem(FItem, i);
FHistory.RemoveValue(FItem);
end
else
for i := oldRight downto newRight + 1 do
FHistory.RemoveLast;
for i := oldLeft - 1 downto newLeft do begin
ExtractItem(FItem, i);
FHistory.AddFirst(FItem);
end;
for i := oldRight + 1 to newRight do begin
ExtractItem(FItem, i);
FHistory.AddLast(FItem);
end;
end;
if (AIndex <> newLeft) and (AIndex <> newRight) then
ExtractItem(FItem, AIndex);
case AccumulationMethod of
camSum:
FHistory.GetSum(FItem);
camAverage: begin
FHistory.GetSum(FItem);
FItem.MultiplyY(1 / Min(ar, AIndex + 1));
FItem.MultiplyY(1 / (newRight - newLeft + 1));
end;
camDerivative:
CalcDerivative(AIndex);
@ -915,14 +937,15 @@ const
( 49/20, -6, 15/2, -20/3, 15/4, -6/5, 1/6));
var
prevItem: PChartDataItem;
i, j, ar: Integer;
i, j, ar, iLeft, iRight: Integer;
dx: Double;
begin
if AIndex = 0 then begin
RangeAround(AIndex, iLeft, iRight);
if (AccumulationDirection <> cadBackward) or (AIndex = 0) then begin
FItem.SetY(SafeNan);
exit;
end;
dx := FItem.X - FHistory.GetPLast(1)^.X;
dx := FItem.X - FHistory.GetPtr(AIndex - iLeft - 1)^.X;
if dx = 0 then begin
FItem.SetY(SafeNan);
exit;
@ -931,7 +954,7 @@ begin
ar := IfThen(AccumulationRange = 0, MaxInt, AccumulationRange);
ar := MinValue([ar, Integer(AIndex + 1), High(COEFFS)]);
for j := 0 to ar - 1 do begin
prevItem := FHistory.GetPLast(j);
prevItem := FHistory.GetPtr(AIndex - iLeft - j);
FItem.Y += prevItem^.Y * COEFFS[ar, j];
for i := 0 to High(FItem.YList) do
FItem.YList[i] += prevItem^.YList[i] * COEFFS[ar, j];
@ -979,6 +1002,14 @@ begin
inherited Destroy;
end;
function TCalculatedChartSource.EffectiveAccumulationRange: Cardinal;
begin
if (AccumulationMethod = camDerivative) and (AccumulationRange = 0) then
Result := MAX_DERIVATIVE_RANGE
else
Result := AccumulationRange;
end;
procedure TCalculatedChartSource.ExtractItem(
out AItem: TChartDataItem; AIndex: Integer);
var
@ -1020,6 +1051,27 @@ begin
Result := false;
end;
procedure TCalculatedChartSource.RangeAround(
AIndex: Integer; out ALeft, ARight: Integer);
var
ar: Integer;
begin
ar := EffectiveAccumulationRange;
ar := IfThen(ar = 0, MaxInt div 2, ar - 1);
ALeft := AIndex - IfThen(AccumulationDirection = cadForward, 0, ar);
ARight := AIndex + IfThen(AccumulationDirection = cadBackward, 0, ar);
ALeft := EnsureRange(ALeft, 0, Count - 1);
ARight := EnsureRange(ARight, 0, Count - 1);
end;
procedure TCalculatedChartSource.SetAccumulationDirection(
AValue: TChartAccumulationDirection);
begin
if FAccumulationDirection = AValue then exit;
FAccumulationDirection := AValue;
Changed(nil);
end;
procedure TCalculatedChartSource.SetAccumulationMethod(
AValue: TChartAccumulationMethod);
begin

View File

@ -84,12 +84,19 @@ begin
AssertEquals(2, FSource[1]^.X);
AssertEquals(102 + 202, FSource[1]^.Y);
AssertEquals(202 + 302, FSource[2]^.Y);
FSource.AccumulationDirection := cadForward;
AssertEquals(202 + 302, FSource[1]^.Y);
AssertEquals(302 + 402, FSource[2]^.Y);
FSource.AccumulationDirection := cadBackward;
FSource.AccumulationMethod := camAverage;
AssertEquals((2002 + 2102) / 2, FSource[20]^.Y);
AssertEquals(1, FSource[0]^.X);
AssertEquals(102, FSource[0]^.Y);
AssertEquals((102 + 202) / 2, FSource[1]^.Y);
AssertEquals(102, FSource[0]^.Y);
FSource.AccumulationDirection := cadCenter;
AssertEquals((1102 + 1202 + 1302) / 3, FSource[11]^.Y);
FSource.AccumulationDirection := cadBackward;
FSource.AccumulationRange := 5;
rng := TMWCRandomGenerator.Create;

View File

@ -67,12 +67,23 @@
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Parsing>
<SyntaxOptions>
<IncludeAssertionCode Value="True"/>
</SyntaxOptions>
</Parsing>
<CodeGeneration>
<Checks>
<RangeChecks Value="True"/>
<OverflowChecks Value="True"/>
</Checks>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="True"/>
<DebugInfoType Value="dsAuto"/>
</Debugging>
</Linking>
<Other>
<CompilerMessages>
<UseMsgFile Value="True"/>