125
128
property YMin: Double read FYMin write SetYMin;
128
{ TIntervalChartSource }
130
TIntervalChartSource = class(TCustomChartSource)
132
function GetCount: Integer; override;
133
function GetItem(AIndex: Integer): PChartDataItem; override;
134
procedure SetYCount(AValue: Cardinal); override;
136
procedure ValuesInRange(
137
AMin, AMax: Double; const AFormat: String; AUseY: Boolean;
138
var AValues: TDoubleDynArray; var ATexts: TStringDynArray); override;
142
dtsCentury, dtsDecade, dtsYear, dtsQuarter, dtsMonth, dtsWeek, dtsDay,
143
dtsHour, dtsTenMinutes, dtsMinute, dtsTenSeconds, dtsSecond, dtsMillisecond
145
TDateTimeSteps = set of TDateTimeStep;
148
DATE_TIME_STEPS_ALL = [Low(TDateTimeStep) .. High(TDateTimeStep)];
152
{ TDateTimeIntervalChartSource }
154
TDateTimeIntervalChartSource = class(TIntervalChartSource)
156
FDateTimeFormat: String;
157
FSteps: TDateTimeSteps;
159
constructor Create(AOwner: TComponent); override;
160
procedure ValuesInRange(
161
AMin, AMax: Double; const AFormat: String; AUseY: Boolean;
162
var AValues: TDoubleDynArray; var ATexts: TStringDynArray); override;
164
property DateTimeFormat: String read FDateTimeFormat write FDateTimeFormat;
165
property Steps: TDateTimeSteps
166
read FSteps write FSteps default DATE_TIME_STEPS_ALL;
169
131
TUserDefinedChartSource = class;
171
133
TGetChartDataItemEvent = procedure (
589
592
procedure TListChartSource.SetYValue(AIndex: Integer; AValue: Double);
594
oldY := Item[AIndex]^.Y;
595
Item[AIndex]^.Y := AValue;
596
if FValuesTotalIsValid then
597
FValuesTotal += AValue - oldY;
599
if FExtentIsValid then begin
600
if AValue <= FExtent.a.Y then FExtent.a.Y := AValue
601
else if AValue >= FExtent.b.Y then FExtent.b.Y := AValue;
596
procedure UpdateExtent;
600
if not FExtentIsValid then exit;
602
if not IsNan(AValue) then begin
603
if AValue <= FExtent.a.Y then
604
FExtent.a.Y := AValue
605
else if AValue >= FExtent.b.Y then
606
FExtent.b.Y := AValue;
609
if IsNan(oldY) then exit;
602
610
if oldY = FExtent.b.Y then begin
603
611
FExtent.b.Y := NegInfinity;
604
for i := 0 to Count - 1 do
605
FExtent.b.Y := Max(FExtent.b.Y, Item[i]^.Y);
613
if not IsNan(it^.Y) then
614
FExtent.b.Y := Max(FExtent.b.Y, it^.Y);
607
616
if oldY = FExtent.a.Y then begin
608
617
FExtent.a.Y := SafeInfinity;
609
for i := 0 to Count - 1 do
610
FExtent.a.Y := Min(FExtent.a.Y, Item[i]^.Y);
619
if not IsNan(it^.Y) then
620
FExtent.a.Y := Min(FExtent.a.Y, it^.Y);
625
oldY := Item[AIndex]^.Y;
626
if IsEquivalent(oldY, AValue) then exit;
627
Item[AIndex]^.Y := AValue;
628
if FValuesTotalIsValid then
629
FValuesTotal += NumberOr(AValue) - NumberOr(oldY);
811
{ TIntervalChartSource }
813
function TIntervalChartSource.GetCount: Integer;
818
function TIntervalChartSource.GetItem(AIndex: Integer): PChartDataItem;
824
procedure TIntervalChartSource.SetYCount(AValue: Cardinal);
827
raise EYCountError.Create('Can not set YCount');
830
procedure TIntervalChartSource.ValuesInRange(
831
AMin, AMax: Double; const AFormat: String; AUseY: Boolean;
832
var AValues: TDoubleDynArray; var ATexts: TStringDynArray);
837
if AMin > AMax then exit;
838
AValues := GetIntervals(AMin, AMax, false);
839
SetLength(ATexts, Length(AValues));
840
for i := 0 to High(AValues) do
841
// Extra format arguments for compatibility with FormatItem.
842
ATexts[i] := Format(AFormat, [AValues[i], 0.0, '', 0.0, 0.0]);
845
{ TDateTimeIntervalChartSource }
847
constructor TDateTimeIntervalChartSource.Create(AOwner: TComponent);
849
inherited Create(AOwner);
850
FSteps := DATE_TIME_STEPS_ALL;
853
procedure TDateTimeIntervalChartSource.ValuesInRange(AMin, AMax: Double;
854
const AFormat: String; AUseY: Boolean; var AValues: TDoubleDynArray;
855
var ATexts: TStringDynArray);
858
STEP_INTERVALS: array [TDateTimeStep] of Double = (
859
100 * YEAR, 10 * YEAR, YEAR, YEAR / 4, YEAR / 12, 7, 1,
860
OneHour, 10 * OneMinute, OneMinute, 10 * OneSecond, OneSecond, OneMillisecond
866
si, x, start: TDateTime;
869
function FormatLabel: String;
873
if DateTimeFormat <> '' then
874
exit(FormatDateTime(DateTimeFormat, x));
875
DateTimeToSystemTime(x, st);
877
dtsCentury, dtsDecade, dtsYear:
878
Result := FormatDateTime('yyyy', x);
880
Result := FormatDateTime('yyyy/', x) + IntToStr(Floor(x / si) mod 4 + 1);
882
Result := FormatDateTime(
883
IfThen(st.Year = prevSt.Year, 'mm', 'mm/yyyy'), x);
885
Result := FormatDateTime('dd/mm', x);
887
Result := FormatDateTime(
888
IfThen(st.Month = prevSt.Month, 'dd', 'dd/mm'), x);
890
Result := FormatDateTime(
891
IfThen(st.Day = prevSt.Day, 'hh:00', 'dd hh:00'), x);
892
dtsTenMinutes, dtsMinute:
893
Result := FormatDateTime(
894
IfThen(st.Hour = prevSt.Hour, 'nn', 'hh:nn'), x);
895
dtsTenSeconds, dtsSecond:
896
Result := FormatDateTime(
897
IfThen(st.Minute = prevSt.Minute, 'ss', 'nn:ss'), x);
899
Result := IntToStr(st.Millisecond) + 'ms';
907
if (AMax - AMin) / STEP_INTERVALS[dtsCentury] > MAX_STEPS then begin
908
inherited ValuesInRange(
909
AMin / STEP_INTERVALS[dtsYear], AMax / STEP_INTERVALS[dtsYear],
910
AFormat, AUseY, AValues, ATexts);
914
while s < High(s) do begin
915
si := STEP_INTERVALS[s];
916
if (s in Steps) and ((AMax - AMin) / si > MIN_STEPS) then
920
start := Int(AMin / si - 1) * si;
923
while x <= AMax do begin
928
i := Length(AValues);
929
SetLength(AValues, i + cnt);
930
SetLength(ATexts, i + cnt);
932
FillChar(prevSt, SizeOf(prevSt), $FF);
934
while x <= AMax do begin
935
if x >= AMin then begin
937
ATexts[i] := Format(AFormat, [x, 0.0, FormatLabel, 0.0, 0.0]);
941
dtsCentury: x := IncYear(x, 100);
942
dtsDecade: x := IncYear(x, 10);
943
dtsYear: x := IncYear(x);
944
dtsMonth: x := IncMonth(x);
950
830
{ TUserDefinedChartSource }
952
832
procedure TUserDefinedChartSource.EndUpdate;
1009
889
procedure TCalculatedChartSource.CalcAccumulation(AIndex: Integer);
891
lastItemIndex: Integer = -1;
893
function GetOriginItem(AItemIndex: Integer): PChartDataItem;
896
if lastItemIndex = AItemIndex then exit;
897
ExtractItem(AItemIndex);
898
lastItemIndex := AItemIndex;
902
i, oldLeft, oldRight, newLeft, newRight: Integer;
1013
FHistory.Capacity := AccumulationRange;
1014
ar := IfThen(AccumulationRange = 0, MaxInt, AccumulationRange);
1015
if FIndex = AIndex - 1 then begin
1016
ExtractItem(FItem, AIndex);
1017
FHistory.AddLast(FItem);
904
if AccumulationDirection = cadCenter then
905
FHistory.Capacity := EffectiveAccumulationRange * 2
907
FHistory.Capacity := EffectiveAccumulationRange;
908
RangeAround(FIndex, oldLeft, oldRight);
909
RangeAround(AIndex, newLeft, newRight);
911
(FIndex < 0) or (Abs(oldLeft - newLeft) > 1) or
912
(Abs(oldRight - newRight) > 1)
915
for i := newLeft to newRight do
916
FHistory.AddLast(GetOriginItem(i)^);
1019
else if FIndex = AIndex + 1 then begin
1020
if AccumulationRange = 0 then begin
1021
ExtractItem(FItem, FIndex);
1022
FHistory.RemoveValue(FItem);
1023
ExtractItem(FItem, AIndex);
919
if FHistory.Capacity = 0 then
920
for i := oldLeft to newLeft - 1 do
921
FHistory.RemoveValue(GetOriginItem(i)^)
923
for i := oldLeft to newLeft - 1 do
924
FHistory.RemoveFirst;
925
if FHistory.Capacity = 0 then
926
for i := oldRight downto newRight + 1 do
927
FHistory.RemoveValue(GetOriginItem(i)^)
929
for i := oldRight downto newRight + 1 do
931
for i := oldLeft - 1 downto newLeft do
932
FHistory.AddFirst(GetOriginItem(i)^);
933
for i := oldRight + 1 to newRight do
934
FHistory.AddLast(GetOriginItem(i)^);
936
GetOriginItem(AIndex);
937
case AccumulationMethod of
939
FHistory.GetSum(FItem);
941
FHistory.GetSum(FItem);
942
FItem.MultiplyY(1 / (newRight - newLeft + 1));
944
camDerivative, camSmoothDerivative:
945
CalcDerivative(AIndex);
950
procedure TCalculatedChartSource.CalcDerivative(AIndex: Integer);
952
procedure WeightedSum(const ACoeffs: array of Double; ADir, ACount: Integer);
955
prevItem: PChartDataItem;
957
for j := 0 to ACount - 1 do begin
958
prevItem := FHistory.GetPtr(AIndex + ADir * j);
959
FItem.Y += prevItem^.Y * ADir * ACoeffs[j];
960
for i := 0 to High(FItem.YList) do
961
FItem.YList[i] += prevItem^.YList[i] * ADir * ACoeffs[j];
965
// Derivative is approximated by finite differences
966
// with accuracy order of (AccumulationRange - 1).
967
// Smoothed derivative coefficients are based on work
968
// by Pavel Holoborodko (http://www.holoborodko.com/pavel/).
970
COEFFS_BF: array [Boolean, 2..7, 0..6] of Double = (
971
( ( -1, 1, 0, 0, 0, 0, 0),
972
( -3/2, 2, -1/2, 0, 0, 0, 0),
973
( -11/6, 3, -3/2, 1/3, 0, 0, 0),
974
( -25/12, 4, -3, 4/3, -1/4, 0, 0),
975
(-137/60, 5, -5, 10/3, -5/4, 1/5, 0),
976
( -49/20, 6, -15/2, 20/3, -15/4, 6/5, -1/6)
978
( ( -1, 1, 0, 0, 0, 0, 0),
979
( -1/2, 0, 1/2, 0, 0, 0, 0),
980
( -1/4, -1/4, 1/4, 1/4, 0, 0, 0),
981
( -1/8, -1/4, 0, 1/4, 1/8, 0, 0),
982
(-1/16, -3/16, -1/8, 1/8, 3/16, 1/16, 0),
983
(-1/32, -1/8, -5/32, 0, 5/32, 1/8, 1/32)
985
COEFFS_C: array [Boolean, 2..5, 0..4] of Double = (
987
(0, 2/3, -1/12, 0, 0),
988
(0, 3/4, -3/20, 1/60, 0),
989
(0, 4/5, -1/5, 4/105, -1/280)
993
(0, 5/32, 1/8, 1/32, 0),
994
(0, 7/64, 7/64, 3/64, 1/128)
997
ar, iLeft, iRight, dir: Integer;
1001
RangeAround(AIndex, iLeft, iRight);
1002
case CASE_OF_TWO[iLeft = AIndex, iRight = AIndex] of
1005
FItem.X - FHistory.GetPtr(AIndex - iLeft - 1)^.X,
1006
FHistory.GetPtr(AIndex - iLeft + 1)^.X - FItem.X);
1007
ar := Min(Min(AIndex - iLeft, iRight - AIndex) + 1, High(COEFFS_C[false]));
1011
dx := FHistory.GetPtr(1)^.X - FItem.X;
1012
ar := Min(iRight - AIndex + 1, High(COEFFS_BF[false]));
1016
dx := FItem.X - FHistory.GetPtr(AIndex - iLeft - 1)^.X;
1017
ar := Min(AIndex - iLeft + 1, High(COEFFS_BF[false]));
1021
FItem.SetY(SafeNan);
1026
i := AIndex - AccumulationRange + 1;
1030
ExtractItem(FItem, i);
1031
FHistory.AddFirst(FItem);
1033
FItem := FHistory.GetPLast^;
1025
if dx = 0 then begin
1026
FItem.SetY(SafeNan);
1031
isSmooth := AccumulationMethod = camSmoothDerivative;
1032
if dir = 0 then begin
1033
WeightedSum(COEFFS_C[isSmooth][ar], -1, ar);
1034
WeightedSum(COEFFS_C[isSmooth][ar], +1, ar);
1038
for i := Max(AIndex - ar + 1, 0) to AIndex do begin
1039
ExtractItem(FItem, i);
1040
FHistory.AddLast(FItem);
1043
FHistory.GetSum(FItem);
1044
if AccumulationMethod = camAverage then begin
1045
FItem.Y /= Min(ar, AIndex + 1);
1046
for i := 0 to High(FItem.YList) do
1047
FItem.YList[i] /= Min(ar, AIndex + 1);
1037
WeightedSum(COEFFS_BF[isSmooth][ar], dir, ar);
1038
FItem.MultiplyY(1 / dx);
1051
1041
procedure TCalculatedChartSource.CalcPercentage;
1056
1045
if not Percentage then exit;
1057
1046
s := (FItem.Y + Sum(FItem.YList)) * PERCENT;
1059
for i := 0 to High(FItem.YList) do
1060
FItem.YList[i] /= s;
1048
FItem.MultiplyY(1 / s);
1063
1051
procedure TCalculatedChartSource.Changed(ASender: TObject);