From 0697177d28d125a5c41def07373d646c46130c40 Mon Sep 17 00:00:00 2001 From: ask Date: Sun, 24 May 2009 01:40:52 +0000 Subject: [PATCH] TAChart: Add random series source. * Move Extent and ValuesTotal logic to TCustomChartSource * Add TMWCRandomGenerator class * Add TRandomChartSource class git-svn-id: trunk@20152 - --- components/tachart/tasources.pas | 302 +++++++++++++++++++++++++++---- 1 file changed, 263 insertions(+), 39 deletions(-) diff --git a/components/tachart/tasources.pas b/components/tachart/tasources.pas index cc681fbeb7..5b9b314175 100644 --- a/components/tachart/tasources.pas +++ b/components/tachart/tasources.pas @@ -23,11 +23,16 @@ type TCustomChartSource = class(TComponent) protected + FExtent: TDoubleRect; + FExtentIsValid: Boolean; + FValuesTotal: Double; + FValuesTotalIsValid: Boolean; function GetCount: Integer; virtual; abstract; function GetItem(AIndex: Integer): PChartDataItem; virtual; abstract; + procedure InvalidateCaches; public - function Extent: TDoubleRect; virtual; abstract; - function ValuesTotal: Double; virtual; abstract; + function Extent: TDoubleRect; virtual; + function ValuesTotal: Double; virtual; function XOfMax: Double; function XOfMin: Double; @@ -41,12 +46,8 @@ type private FData: TList; FDataPoints: TStrings; - FExtent: TDoubleRect; - FExtentIsValid: Boolean; FOnSetDataPoints: TSimpleNotifyEvent; FUpdateCount: Integer; - FValuesTotal: Double; - FValuesTotalIsValid: Boolean; procedure ClearCaches; procedure SetDataPoints(AValue: TStrings); procedure UpdateCachesAfterAdd(AX, AY: Double); @@ -62,12 +63,9 @@ type procedure Clear; procedure Delete(AIndex: Integer); inline; procedure EndUpdate; - function Extent: TDoubleRect; override; - procedure InvalidateValues; inline; function IsUpdating: Boolean; inline; procedure SetXValue(AIndex: Integer; AValue: Double); procedure SetYValue(AIndex: Integer; AValue: Double); - function ValuesTotal: Double; override; property OnSetDataPoints: TSimpleNotifyEvent read FOnSetDataPoints write FOnSetDataPoints; @@ -75,13 +73,69 @@ type property DataPoints: TStrings read FDataPoints write SetDataPoints; end; + { TMWCRandomGenerator } + + // Mutliply-with-carry random number generator. + // Algorithm by George Marsaglia. + // A generator is incapsulated in a class to allow using many simultaneous + // random sequences, each determined by its own seed. + TMWCRandomGenerator = class + private + FHistory: array [0..4] of LongWord; + procedure SetSeed(const AValue: Integer); + public + property Seed: Integer write SetSeed; + function Get: LongWord; + function GetInRange(AMin, AMax: Integer): Integer; + end; + + { TRandomChartSource } + + TRandomChartSource = class(TCustomChartSource) + private + FRandomX: Boolean; + FPointsNumber: Integer; + FRandSeed: Integer; + FXMax: Double; + FXMin: Double; + FYMax: Double; + FYMin: Double; + private + FRNG: TMWCRandomGenerator; + FCurIndex: Integer; + FCurItem: TChartDataItem; + + procedure SetPointsNumber(const AValue: Integer); + procedure SetRandomX(const AValue: Boolean); + procedure SetRandSeed(const AValue: Integer); + procedure SetXMax(const AValue: Double); + procedure SetXMin(const AValue: Double); + procedure SetYMax(const AValue: Double); + procedure SetYMin(const AValue: Double); + protected + function GetCount: Integer; override; + function GetItem(AIndex: Integer): PChartDataItem; override; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + published + property PointsNumber: Integer + read FPointsNumber write SetPointsNumber default 0; + property RandomX: Boolean read FRandomX write SetRandomX default false; + property RandSeed: Integer read FRandSeed write SetRandSeed; + property XMax: Double read FXMax write SetXMax; + property XMin: Double read FXMin write SetXMin; + property YMax: Double read FYMax write SetYMax; + property YMin: Double read FYMin write SetYMin; + end; + function DoublePoint(const ACoord: TChartDataItem): TDoublePoint; inline; overload; procedure Register; implementation uses - Math, StrUtils; + LCLIntf, Math, StrUtils; type @@ -109,11 +163,45 @@ end; procedure Register; begin - RegisterComponents(CHART_COMPONENT_IDE_PAGE, [TListChartSource]); + RegisterComponents( + CHART_COMPONENT_IDE_PAGE, [TListChartSource, TRandomChartSource]); end; { TCustomChartSource } +function TCustomChartSource.Extent: TDoubleRect; +var + i: Integer; +begin + if FExtentIsValid then exit(FExtent); + FExtent := EmptyExtent; + for i := 0 to Count - 1 do + with Item[i]^ do begin + UpdateMinMax(X, FExtent.a.X, FExtent.b.X); + UpdateMinMax(Y, FExtent.a.Y, FExtent.b.Y); + end; + FExtentIsValid := true; + Result := FExtent; +end; + +procedure TCustomChartSource.InvalidateCaches; +begin + FExtentIsValid := false; + FValuesTotalIsValid := false; +end; + +function TCustomChartSource.ValuesTotal: Double; +var + i: Integer; +begin + if FValuesTotalIsValid then exit(FValuesTotal); + FValuesTotal := 0; + for i := 0 to Count - 1 do + FValuesTotal += Item[i]^.Y; + FValuesTotalIsValid := true; + Result := FValuesTotal; +end; + function TCustomChartSource.XOfMax: Double; var i: Integer; @@ -236,7 +324,34 @@ end; procedure TListChartSource.ClearCaches; begin - FExtent := EmptyExtent; + FExtent := TMainIDE.ParseCmdLineOptions: + PrimaryConfigPath="C:\Documents and Settings\ask\Local Settings\Application Data\lazarus" + SecondaryConfigPath="C:\lazarus" +TMainIDE.DoLoadLFM A C:\lazarus\components\tachart\demo\3d\main.pas IsPartOfProject=True +QuickCheckLFMBuffer LFMBuffer=C:\lazarus\components\tachart\demo\3d\main.lfm +TApplication.HandleException identifier expected, but end found + Stack trace: + $007DDACF TCUSTOMCODETOOL__RAISEEXCEPTIONINSTANCE, line 2054 of customcodetool.pas + $007DDB25 TCUSTOMCODETOOL__RAISEEXCEPTIONCLASS, line 2063 of customcodetool.pas + $007D9B0A TCUSTOMCODETOOL__RAISEEXCEPTION, line 379 of customcodetool.pas + $007D9C50 TCUSTOMCODETOOL__SAVERAISEEXCEPTION, line 401 of customcodetool.pas + $007D9CC5 TCUSTOMCODETOOL__SAVERAISEEXCEPTIONFMT, line 407 of customcodetool.pas + $007DEC26 TCUSTOMCODETOOL__RAISEIDENTEXPECTEDBUTATOMFOUND, line 2549 of customcodetool.pas + $007DA63B TCUSTOMCODETOOL__ATOMISIDENTIFIER, line 698 of customcodetool.pas + $007EB46F TPASCALREADERTOOL__MOVECURSORTOPROPNAME, line 876 of pascalreadertool.pas + $007EB70D TPASCALREADERTOOL__EXTRACTPROPNAME, line 947 of pascalreadertool.pas + $007BAD0B TCODEEXPLORERVIEW__GETCODENODEDESCRIPTION, line 542 of codeexplorer.pas + $007BB4C4 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 740 of codeexplorer.pas + $007BB597 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 757 of codeexplorer.pas + $007BB597 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 757 of codeexplorer.pas + $007BB597 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 757 of codeexplorer.pas + $007BB597 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 757 of codeexplorer.pas + $007BB597 TCODEEXPLORERVIEW__CREATEIDENTIFIERNODES, line 757 of codeexplorer.pas + $007BDB66 TCODEEXPLORERVIEW__REFRESHCODE, line 1635 of codeexplorer.pas +[TMainIDE.Destroy] A +[TMainIDE.Destroy] B -> inherited Destroy... TMainIDE +[TMainIDE.Destroy] END +EmptyExtent; FExtentIsValid := true; FValuesTotal := 0; FValuesTotalIsValid := true; @@ -277,21 +392,6 @@ begin Dec(FUpdateCount); end; -function TListChartSource.Extent: TDoubleRect; -var - i: Integer; -begin - if FExtentIsValid then exit(FExtent); - FExtent := EmptyExtent; - for i := 0 to Count - 1 do - with Item[i]^ do begin - UpdateMinMax(X, FExtent.a.X, FExtent.b.X); - UpdateMinMax(Y, FExtent.a.Y, FExtent.b.Y); - end; - FExtentIsValid := true; - Result := FExtent; -end; - function TListChartSource.GetCount: Integer; begin Result := FData.Count; @@ -302,11 +402,6 @@ begin Result := PChartDataItem(FData.Items[AIndex]); end; -procedure TListChartSource.InvalidateValues; inline; -begin - FValuesTotalIsValid := false; -end; - function TListChartSource.IsUpdating: Boolean; inline; begin Result := FUpdateCount > 0; @@ -391,16 +486,145 @@ begin FValuesTotal += AY; end; -function TListChartSource.ValuesTotal: Double; +{ TMWCRandomGenerator } + +function TMWCRandomGenerator.Get: LongWord; +const + MULT: array [0..4] of UInt64 = (5115, 1776, 1492, 2111111111, 1); +var + i: Integer; + s: UInt64; +begin + s := 0; + for i := 0 to High(FHistory) do + s += MULT[i] * FHistory[i]; + FHistory[3] := FHistory[2]; + FHistory[2] := FHistory[1]; + FHistory[1] := FHistory[0]; + FHistory[4] := Hi(s); + FHistory[0] := Lo(s); + Result := FHistory[0]; +end; + +function TMWCRandomGenerator.GetInRange(AMin, AMax: Integer): Integer; +var + m: UInt64; +begin + m := AMax - AMin + 1; + m *= Get; + // m is now equidistributed on [0, (2^32-1) * range], + // so it's upper double word is equidistributed on [0, range]. + Result := Integer(Hi(m)) + AMin; +end; + +procedure TMWCRandomGenerator.SetSeed(const AValue: Integer); var i: Integer; begin - if FValuesTotalIsValid then exit(FValuesTotal); - FValuesTotal := 0; - for i := 0 to Count - 1 do - FValuesTotal += Item[i]^.Y; - FValuesTotalIsValid := true; - Result := FValuesTotal; + FHistory[0] := AValue; + // Use trivial LCG for seeding + for i := 0 to High(FHistory) do + FHistory[i] := Lo(Int64(FHistory[i - 1]) * 29943829 - 1); + // Skip some initial values to increase randomness. + for i := 1 to 20 do + Get; +end; + +{ TRandomChartSource } + +constructor TRandomChartSource.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FRNG := TMWCRandomGenerator.Create; + RandSeed := GetTickCount; + FCurItem.Color := clTAColor; + FCurIndex := -1; +end; + +destructor TRandomChartSource.Destroy; +begin + FRNG.Free; + inherited Destroy; +end; + +function TRandomChartSource.GetCount: Integer; +begin + Result := FPointsNumber; +end; + +function TRandomChartSource.GetItem(AIndex: Integer): PChartDataItem; +begin + if FCurIndex > AIndex then begin + FRNG.Seed := FRandSeed; + FCurIndex := -1; + end; + while FCurIndex < AIndex do begin + if XMax <= XMin then + FCurItem.X := XMin + else begin + if FRandomX then + FCurItem.X := FRNG.Get / High(LongWord) + else + FCurItem.X := FCurIndex / (Count - 1); + FCurItem.X := FCurItem.X * (XMax - XMin) + XMin; + end; + if YMax <= YMin then + FCurItem.Y := YMin + else + FCurItem.Y := FRNG.Get / MaxInt * (YMax - YMin) + YMin; + Inc(FCurIndex); + end; + Result := @FCurItem; +end; + +procedure TRandomChartSource.SetPointsNumber(const AValue: Integer); +begin + if FPointsNumber = AValue then exit; + FPointsNumber := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetRandomX(const AValue: Boolean); +begin + if FRandomX = AValue then exit; + FRandomX := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetRandSeed(const AValue: Integer); +begin + if FRandSeed = AValue then exit; + FRandSeed := AValue; + FRNG.Seed := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetXMax(const AValue: Double); +begin + if FXMax = AValue then exit; + FXMax := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetXMin(const AValue: Double); +begin + if FXMin = AValue then exit; + FXMin := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetYMax(const AValue: Double); +begin + if FYMax = AValue then exit; + FYMax := AValue; + InvalidateCaches; +end; + +procedure TRandomChartSource.SetYMin(const AValue: Double); +begin + if FYMin = AValue then exit; + FYMin := AValue; + InvalidateCaches; end; end.