mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 20:23:45 +02:00

* 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 -
408 lines
9.3 KiB
ObjectPascal
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.
|
|
|