lazarus/components/tachart/tadbsource.pas
2021-08-08 00:20:43 +02:00

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.