mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-26 22:23:49 +02:00
385 lines
9.8 KiB
ObjectPascal
385 lines
9.8 KiB
ObjectPascal
{
|
|
|
|
*****************************************************************************
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Authors: Alexander Klenin
|
|
|
|
}
|
|
unit TADbSource;
|
|
|
|
{$H+}
|
|
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
|
|
interface
|
|
|
|
uses
|
|
Classes, Db, TAChartUtils, TACustomSource;
|
|
|
|
type
|
|
|
|
TDbChartSourceOptions = set of (dcsoDateTimeX, dcsoDateTimeY);
|
|
|
|
TDbChartSource = class;
|
|
|
|
TDbChartSourceGetItemEvent = procedure (
|
|
ASender: TDbChartSource; var AItem: TChartDataItem) of object;
|
|
|
|
{ TDbChartSource }
|
|
|
|
TDbChartSource = class(TCustomChartSource)
|
|
strict private
|
|
FBookmark: TBookmark;
|
|
FCurItem: TChartDataItem;
|
|
FDataLink: TDataLink;
|
|
FDateTimeFormat: String;
|
|
FFieldColor: String;
|
|
FFieldText: String;
|
|
FFieldX: String;
|
|
FFieldY: String;
|
|
FFieldXList: TStringList;
|
|
FFieldYList: TStringList;
|
|
FOnGetItem: TDbChartSourceGetItemEvent;
|
|
FOptions: TDbChartSourceOptions;
|
|
|
|
function GetDataSource: TDataSource; inline;
|
|
procedure SetDataSource(AValue: TDataSource);
|
|
procedure SetFieldColor(const AValue: String);
|
|
procedure SetFieldText(const AValue: String);
|
|
procedure SetFieldX(const AValue: String);
|
|
procedure SetFieldY(const AValue: String);
|
|
procedure SetOnGetItem(AValue: TDbChartSourceGetItemEvent);
|
|
procedure SetOptions(AValue: TDbChartSourceOptions);
|
|
protected
|
|
function GetCount: Integer; override;
|
|
function GetItem(AIndex: Integer): PChartDataItem; override;
|
|
procedure SetXCount(AValue: Cardinal); override;
|
|
procedure SetYCount(AValue: Cardinal); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
public
|
|
procedure AfterDraw; override;
|
|
procedure BeforeDraw; override;
|
|
function DataSet: TDataSet; inline;
|
|
procedure DefaultGetItem(var AItem: TChartDataItem);
|
|
procedure Reset;
|
|
published
|
|
property DataSource: TDataSource read GetDataSource write SetDataSource;
|
|
property DateTimeFormat: String read FDateTimeFormat write FDateTimeFormat;
|
|
property FieldColor: String read FFieldColor write SetFieldColor;
|
|
property FieldText: String read FFieldText write SetFieldText;
|
|
property FieldX: String read FFieldX write SetFieldX;
|
|
property FieldY: String read FFieldY write SetFieldY;
|
|
property Options: TDbChartSourceOptions read FOptions write SetOptions default [];
|
|
published
|
|
property OnGetItem: TDbChartSourceGetItemEvent read FOnGetItem write SetOnGetItem;
|
|
end;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math, SysUtils, DateUtils, TAMath;
|
|
|
|
type
|
|
|
|
{ TDbChartSourceDataLink }
|
|
|
|
TDbChartSourceDataLink = class(TDataLink)
|
|
strict private
|
|
FChartSrc: TDbChartSource;
|
|
protected
|
|
procedure ActiveChanged; override;
|
|
procedure DataSetChanged; override;
|
|
procedure DataSetScrolled(ADistance: Integer); override;
|
|
procedure UpdateData; override;
|
|
public
|
|
constructor Create(ASrc: TDbChartSource);
|
|
end;
|
|
|
|
// FIXME: This is a workaround for issue #19887.
|
|
// Remove when dataset gains the capability to turn data events off.
|
|
var
|
|
VLockedDatasets: TFPList;
|
|
|
|
{ TDbChartSourceDataLink }
|
|
|
|
procedure TDbChartSourceDataLink.ActiveChanged;
|
|
begin
|
|
inherited ActiveChanged;
|
|
// Make associated series check XCount and YCount.
|
|
if (FChartSrc.ComponentState = []) and Assigned(Dataset) and (Dataset.State <> dsInactive) then
|
|
FChartSrc.Reset;
|
|
end;
|
|
|
|
constructor TDbChartSourceDataLink.Create(ASrc: TDbChartSource);
|
|
begin
|
|
FChartSrc := ASrc;
|
|
VisualControl := true;
|
|
end;
|
|
|
|
procedure TDbChartSourceDataLink.DataSetChanged;
|
|
begin
|
|
inherited DataSetChanged;
|
|
if DataSet.State = dsBrowse then
|
|
FChartSrc.Reset;
|
|
end;
|
|
|
|
procedure TDbChartSourceDataLink.DataSetScrolled(ADistance: Integer);
|
|
begin
|
|
Unused(ADistance); // No need to react on scrolling.
|
|
end;
|
|
|
|
procedure TDbChartSourceDataLink.UpdateData;
|
|
begin
|
|
inherited UpdateData;
|
|
FChartSrc.Reset;
|
|
end;
|
|
|
|
|
|
{ TDbChartSource }
|
|
|
|
procedure TDbChartSource.AfterDraw;
|
|
begin
|
|
inherited AfterDraw;
|
|
try
|
|
if not FDataLink.Active or (FBookmark = nil) then exit;
|
|
FDataLink.Dataset.EnableControls;
|
|
FDataLink.DataSet.GotoBookmark(FBookmark);
|
|
FDataLink.DataSet.FreeBookmark(FBookmark);
|
|
finally
|
|
FBookmark := nil;
|
|
VLockedDatasets.Remove(FDataLink.DataSet);
|
|
end;
|
|
end;
|
|
|
|
procedure TDbChartSource.BeforeDraw;
|
|
begin
|
|
inherited BeforeDraw;
|
|
VLockedDatasets.Add(FDataLink.DataSet);
|
|
FDataLink.Dataset.DisableControls;
|
|
if FDataLink.Active and (FBookmark = nil) then
|
|
FBookmark := FDataLink.DataSet.GetBookmark;
|
|
end;
|
|
|
|
constructor TDbChartSource.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FDataLink := TDbChartSourceDataLink.Create(Self);
|
|
FFieldXList := TStringList.Create;
|
|
FFieldXList.StrictDelimiter := true;
|
|
FFieldYList := TStringList.Create;
|
|
FFieldYList.StrictDelimiter := true;
|
|
FXCount := 1; // Even when no FieldX is specified there is an x value (sequential counter).
|
|
FYCount := 0; // Has been set to 1 by inherited constructor
|
|
end;
|
|
|
|
function TDbChartSource.DataSet: TDataSet;
|
|
begin
|
|
Result := FDataLink.DataSet;
|
|
end;
|
|
|
|
procedure TDbChartSource.DefaultGetItem(var AItem: TChartDataItem);
|
|
|
|
function FieldValueOrNaN(
|
|
ADataset: TDataSet; const AFieldName: String; ADateTime: Boolean): Double;
|
|
begin
|
|
with ADataset.FieldByName(AFieldName) do
|
|
if IsNull then
|
|
Result := SafeNan
|
|
else if ADateTime then
|
|
begin
|
|
if (DataType = ftString) and (FDateTimeFormat <> '') then
|
|
Result := ScanDateTime(FDateTimeFormat, AsString)
|
|
else
|
|
Result := AsDateTime
|
|
end else
|
|
Result := AsFloat;
|
|
end;
|
|
|
|
var
|
|
ds: TDataSet;
|
|
i: Integer;
|
|
begin
|
|
ds := DataSet;
|
|
|
|
if FFieldXList.Count > 0 then begin
|
|
AItem.X := FieldValueOrNaN(ds, FFieldXList[0], dcsoDateTimeX in Options);
|
|
for i := 0 to High(AItem.XList) do
|
|
AItem.XList[i] :=
|
|
FieldValueOrNaN(ds, FFieldXList[i + 1], false); // no date/time in extra x values
|
|
end else
|
|
AItem.X := ds.RecNo;
|
|
|
|
if FYCount > 0 then begin
|
|
AItem.Y := FieldValueOrNaN(ds, FFieldYList[0], dcsoDateTimeY in Options);
|
|
for i := 0 to High(AItem.YList) do
|
|
AItem.YList[i] :=
|
|
FieldValueOrNaN(ds, FFieldYList[i + 1], false); // not date/time in extra y values!
|
|
end;
|
|
|
|
if FieldColor <> '' then
|
|
AItem.Color := ds.FieldByName(FieldColor).AsInteger;
|
|
|
|
if FieldText <> '' then
|
|
AItem.Text := ds.FieldByName(FieldText).AsString;
|
|
end;
|
|
|
|
destructor TDbChartSource.Destroy;
|
|
begin
|
|
FreeAndNil(FDataLink);
|
|
FreeAndNil(FFieldXList);
|
|
FreeAndNil(FFieldYList);
|
|
inherited;
|
|
end;
|
|
|
|
function TDbChartSource.GetCount: Integer;
|
|
begin
|
|
if FDataLink.Active then
|
|
Result := DataSource.DataSet.RecordCount
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TDbChartSource.GetDataSource: TDataSource;
|
|
begin
|
|
Result := FDataLink.DataSource;
|
|
end;
|
|
|
|
function TDbChartSource.GetItem(AIndex: Integer): PChartDataItem;
|
|
var
|
|
ds: TDataSet;
|
|
begin
|
|
Result := @FCurItem;
|
|
SetDataItemDefaults(FCurItem);
|
|
if not FDataLink.Active then exit;
|
|
|
|
Inc(AIndex); // RecNo is counted from 1
|
|
ds := DataSet;
|
|
if ds.IsUniDirectional then begin
|
|
if ds.RecNo < AIndex then
|
|
ds.First;
|
|
end
|
|
else begin
|
|
if AIndex > ds.RecNo - AIndex then
|
|
while (ds.RecNo > AIndex) and not ds.BOF do
|
|
ds.Prior
|
|
else
|
|
ds.First;
|
|
end;
|
|
while (ds.RecNo < AIndex) and not ds.EOF do
|
|
ds.Next;
|
|
if ds.RecNo <> AIndex then begin
|
|
// Either the requested item is out of range, or the dataset is filtered.
|
|
FCurItem.X := SafeNaN;
|
|
FCurItem.Y := SafeNaN;
|
|
exit;
|
|
end;
|
|
if Assigned(OnGetItem) then
|
|
// Data in unusual format, e.g. dates in non-current locale, will cause
|
|
// errors in DefaultGetItem -- so don't call it before the handler.
|
|
// User may call it himself if he deems it safe and necessary.
|
|
OnGetItem(Self, FCurItem)
|
|
else
|
|
DefaultGetItem(FCurItem);
|
|
end;
|
|
|
|
procedure TDbChartSource.Reset;
|
|
begin
|
|
InvalidateCaches;
|
|
if VLockedDatasets.IndexOf(FDataLink.DataSet) >= 0 then exit;
|
|
Notify;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetDataSource(AValue: TDataSource);
|
|
begin
|
|
if DataSource = AValue then exit;
|
|
FDataLink.DataSource := AValue;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetFieldColor(const AValue: String);
|
|
begin
|
|
if FFieldColor = AValue then exit;
|
|
FFieldColor := AValue;
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetFieldText(const AValue: String);
|
|
begin
|
|
if FFieldText = AValue then exit;
|
|
FFieldText := AValue;
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetFieldX(const AValue: String);
|
|
begin
|
|
if FFieldX = AValue then exit;
|
|
FFieldX := AValue;
|
|
if FFieldX = '' then
|
|
FFieldXList.Clear
|
|
else
|
|
FFieldXList.CommaText := FFieldX;
|
|
FXCount := Min(1, FFieldXList.Count);
|
|
// There is always one x value even if FieldX is not specified (sequential counter).
|
|
SetLength(FCurItem.XList, Max(FXCount - 1, 0));
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetFieldY(const AValue: String);
|
|
begin
|
|
if FFieldY = AValue then exit;
|
|
FFieldY := AValue;
|
|
if FFieldY = '' then
|
|
FFieldYList.Clear
|
|
else
|
|
FFieldYList.CommaText := FFieldY;
|
|
FYCount := FFieldYList.Count;
|
|
SetLength(FCurItem.YList, Max(FYCount - 1, 0));
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetOnGetItem(AValue: TDbChartSourceGetItemEvent);
|
|
begin
|
|
if TMethod(FOnGetItem) = TMethod(AValue) then exit;
|
|
FOnGetItem := AValue;
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetOptions(AValue: TDbChartSourceOptions);
|
|
begin
|
|
if FOptions = AValue then exit;
|
|
FOptions := AValue;
|
|
Reset;
|
|
end;
|
|
|
|
procedure TDbChartSource.SetXCount(AValue: Cardinal);
|
|
begin
|
|
Unused(AValue);
|
|
raise EXCountError.Create('Set FieldX instead');
|
|
end;
|
|
|
|
procedure TDbChartSource.SetYCount(AValue: Cardinal);
|
|
begin
|
|
Unused(AValue);
|
|
raise EYCountError.Create('Set FieldY instead');
|
|
end;
|
|
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents(CHART_COMPONENT_IDE_PAGE, [TDbChartSource]);
|
|
end;
|
|
|
|
|
|
initialization
|
|
VLockedDatasets := TFPList.Create;
|
|
|
|
finalization
|
|
FreeAndNil(VLockedDatasets);
|
|
|
|
end.
|
|
|