lazarus/components/tachart/tasources.pas
ask d8bc38d03f TAChart: Improve Extent processing for series.
* Move Extent calculation and caching to TListChartSource class.
* Replace TChartSeries.{X/Y}Graph{Min/Max} by Extent property.
* Various fixes related to extent cache coherence.

git-svn-id: trunk@20136 -
2009-05-23 04:01:13 +00:00

408 lines
9.3 KiB
ObjectPascal

unit TASources;
{$mode objfpc}{$H+}
interface
uses
Classes, Graphics, SysUtils, TAChartUtils;
type
EFixedSourceRequired = class(EChartError);
TChartDataItem = record
X, Y: Double;
Color: TColor;
Text: String;
end;
PChartDataItem = ^TChartDataItem;
TSimpleNotifyEvent = procedure of object;
{ TCustomChartSource }
TCustomChartSource = class(TComponent)
protected
function GetCount: Integer; virtual; abstract;
function GetItem(AIndex: Integer): PChartDataItem; virtual; abstract;
public
function Extent: TDoubleRect; virtual; abstract;
function ValuesTotal: Double; virtual; abstract;
function XOfMax: Double;
function XOfMin: Double;
property Count: Integer read GetCount;
property Item[AIndex: Integer]: PChartDataItem read GetItem; default;
end;
{ TListChartSource }
TListChartSource = class(TCustomChartSource)
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);
protected
function GetCount: Integer; override;
function GetItem(AIndex: Integer): PChartDataItem; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
function Add(AX, AY: Double; const ALabel: String; AColor: TColor): Integer;
procedure BeginUpdate;
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;
published
property DataPoints: TStrings read FDataPoints write SetDataPoints;
end;
function DoublePoint(const ACoord: TChartDataItem): TDoublePoint; inline; overload;
procedure Register;
implementation
uses
Math, StrUtils;
type
{ TListChartSourceStrings }
TListChartSourceStrings = class(TStrings)
private
FSource: TListChartSource;
procedure Parse(const AString: String; ADataItem: PChartDataItem);
protected
function Get(Index: Integer): string; override;
function GetCount: Integer; override;
procedure Put(Index: Integer; const S: string); override;
public
procedure Clear; override;
procedure Delete(Index: Integer); override;
procedure Insert(Index: Integer; const S: string); override;
end;
function DoublePoint(const ACoord: TChartDataItem): TDoublePoint;
begin
Result.X := ACoord.X;
Result.Y := ACoord.Y;
end;
procedure Register;
begin
RegisterComponents(CHART_COMPONENT_IDE_PAGE, [TListChartSource]);
end;
{ TCustomChartSource }
function TCustomChartSource.XOfMax: Double;
var
i: Integer;
begin
for i := 0 to Count - 1 do
with Item[i]^ do
if Y = Extent.b.Y then exit(X);
Result := 0.0;
end;
function TCustomChartSource.XOfMin: Double;
var
i: Integer;
begin
for i := 0 to Count - 1 do
with Item[i]^ do
if Y = Extent.a.Y then exit(X);
Result := 0.0;
end;
{ TListChartSourceStrings }
procedure TListChartSourceStrings.Clear;
begin
FSource.Clear;
end;
procedure TListChartSourceStrings.Delete(Index: Integer);
begin
FSource.Delete(Index);
end;
function TListChartSourceStrings.Get(Index: Integer): string;
begin
with FSource[Index]^ do
Result := Format('%g|%g|%d|%s', [X, Y, Color, Text]);
end;
function TListChartSourceStrings.GetCount: Integer;
begin
Result := FSource.Count;
end;
procedure TListChartSourceStrings.Insert(Index: Integer; const S: string);
var
item: PChartDataItem;
begin
New(item);
FSource.FData.Insert(Index, item);
Parse(S, item);
FSource.UpdateCachesAfterAdd(item^.X, item^.Y);
end;
procedure TListChartSourceStrings.Parse(
const AString: String; ADataItem: PChartDataItem);
var
p: Integer;
function NextPart: String;
begin
Result := ExtractSubstr(AString, p, ['|']);
end;
begin
p := 1;
with ADataItem^ do begin
X := StrToFloatDef(NextPart, 0.0);
Y := StrToFloatDef(NextPart, 0.0);
Color := StrToIntDef(NextPart, 0);
Text := NextPart;
end;
end;
procedure TListChartSourceStrings.Put(Index: Integer; const S: string);
begin
Parse(S, FSource[Index]);
end;
{ TListChartSource }
function TListChartSource.Add(
AX, AY: Double; const ALabel: String; AColor: TColor): Integer;
var
pcc: PChartDataItem;
begin
New(pcc);
pcc^.X := AX;
pcc^.Y := AY;
pcc^.Color := AColor;
pcc^.Text := ALabel;
UpdateCachesAfterAdd(AX, AY);
// We keep data points ordered by X coordinate.
// Note that this leads to O(N^2) time except
// for the case of adding already ordered points.
// So, is the user wants to add many (>10000) points to a graph,
// he should pre-sort them to avoid performance penalty.
Result := FData.Count;
while (Result > 0) and (Item[Result - 1]^.X > AX) do
Dec(Result);
FData.Insert(Result, pcc);
end;
procedure TListChartSource.BeginUpdate;
begin
Inc(FUpdateCount);
FValuesTotalIsValid := false;
FExtentIsValid := false;
end;
procedure TListChartSource.Clear; inline;
var
i: Integer;
begin
for i := 0 to FData.Count - 1 do
Dispose(Item[i]);
FData.Clear;
ClearCaches;
end;
procedure TListChartSource.ClearCaches;
begin
FExtent := EmptyExtent;
FExtentIsValid := true;
FValuesTotal := 0;
FValuesTotalIsValid := true;
end;
constructor TListChartSource.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FData := TList.Create;
FDataPoints := TListChartSourceStrings.Create;
TListChartSourceStrings(FDataPoints).FSource := Self;
ClearCaches;
end;
procedure TListChartSource.Delete(AIndex: Integer);
begin
with Item[AIndex]^ do begin
FExtentIsValid :=
(FExtent.a.X < X) and (X < FExtent.b.X) and
(FExtent.a.Y < Y) and (Y < FExtent.b.Y);
if FValuesTotalIsValid then
FValuesTotal -= Y;
end;
Dispose(Item[AIndex]);
FData.Delete(AIndex);
end;
destructor TListChartSource.Destroy;
begin
Clear;
FDataPoints.Free;
FData.Free;
inherited Destroy;
end;
procedure TListChartSource.EndUpdate;
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;
end;
function TListChartSource.GetItem(AIndex: Integer): PChartDataItem;
begin
Result := PChartDataItem(FData.Items[AIndex]);
end;
procedure TListChartSource.InvalidateValues; inline;
begin
FValuesTotalIsValid := false;
end;
function TListChartSource.IsUpdating: Boolean; inline;
begin
Result := FUpdateCount > 0;
end;
procedure TListChartSource.SetDataPoints(AValue: TStrings);
begin
if FDataPoints = AValue then exit;
BeginUpdate;
try
FDataPoints.Assign(AValue);
finally
EndUpdate;
end;
if Assigned(FOnSetDataPoints) then
FOnSetDataPoints;
end;
procedure TListChartSource.SetXValue(AIndex: Integer; AValue: Double);
var
i: Integer;
oldX: Double;
begin
// TODO: Ensure that points are sorted by X.
oldX := Item[AIndex]^.X;
Item[AIndex]^.X := AValue;
if not FExtentIsValid then exit;
if AValue <= FExtent.a.X then FExtent.a.X := AValue
else if AValue >= FExtent.b.X then FExtent.b.X := AValue
else begin
if oldX = FExtent.b.X then begin
FExtent.b.X := NegInfinity;
for i := 0 to Count - 1 do
FExtent.b.X := Max(FExtent.b.X, Item[i]^.X);
end;
if oldX = FExtent.a.X then begin
FExtent.a.X := Infinity;
for i := 0 to Count - 1 do
FExtent.a.X := Min(FExtent.a.X, Item[i]^.X);
end;
end;
end;
procedure TListChartSource.SetYValue(AIndex: Integer; AValue: Double);
var
i: Integer;
oldY: Double;
begin
oldY := Item[AIndex]^.Y;
Item[AIndex]^.Y := AValue;
if FValuesTotalIsValid then
FValuesTotal += AValue - oldY;
if not FExtentIsValid then exit;
if AValue <= FExtent.a.Y then FExtent.a.Y := AValue
else if AValue >= FExtent.b.Y then FExtent.b.Y := AValue
else begin
if oldY = FExtent.b.Y then begin
FExtent.b.Y := NegInfinity;
for i := 0 to Count - 1 do
FExtent.b.Y := Max(FExtent.b.Y, Item[i]^.Y);
end;
if oldY = FExtent.a.Y then begin
FExtent.a.Y := Infinity;
for i := 0 to Count - 1 do
FExtent.a.Y := Min(FExtent.a.Y, Item[i]^.Y);
end;
end;
end;
procedure TListChartSource.UpdateCachesAfterAdd(AX, AY: Double);
begin
if FExtentIsValid then begin
UpdateMinMax(AX, FExtent.a.X, FExtent.b.X);
UpdateMinMax(AY, FExtent.a.Y, FExtent.b.Y);
end;
if FValuesTotalIsValid then
FValuesTotal += AY;
end;
function TListChartSource.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;
end.