Создание диаграммы распределения данных в виде кривой Bell в FastReport из Delphi
При портировании домашней отчетности Delphi в FastReport может возникнуть потребность в диаграмме, показывающей распределение значений поля в наборе данных в виде кривой Bell. В этом руководстве мы рассмотрим, как создать такую диаграмму с помощью FastReport и Object Pascal (Delphi).
Проблема
Разработчик портирует домашнюю отчетность Delphi в FastReport и нуждается в диаграмме, показывающей распределение значений поля в наборе данных в виде кривой Bell. Ранее разработчик писал код для сортировки значений поля в ячейки (например, 100) и создавал гистограмму TChart на основе подсчета ячеек (Y) против 1-100 (X). FastReport имеет хорошую интеграцию с TChart, и разработчик может легко выводить линии значений поля. Но существует ли встроенный способ создания диаграммы распределения в FastReport, или нужно создавать новый набор данных отсортированных ячеек и выводить его?
Решение
Разработчик создал класс, который может быть полезен для других пользователей. Этот класс принимает набор данных и выполняет всю сложную работу по созданию списка частотных ячеек для определенного имени поля, а затем кэширует эти данные, чтобы позволить вызов 'GetValue' из TfrxUserDataSet под названием 'Distributions'. Пользователь отчета может просто разместить диаграмму баров в отчете, указать 'Distributions' в качестве набора данных и выбрать требуемое поле для 'Y-значений'. 'X-значения' должны быть установлены на то же имя поля, но с '-X' в конце. Класс разработчика затем прозрачно возвращает X- и Y-значения для диаграммы, создавая частотные ячейки при первом вызове. В этом примере не используется никакой код FastReport.
Хотя этот код и работает, его можно улучшить, например, в настоящее время X-значения охватывают минимум и максимум. Более подходящим отображением было бы использование 3 или 6 сигм (стандартное отклонение), но это легко можно Modify.
Ниже приведен пример кода на Object Pascal (Delphi), реализующий описанное решение:
unit UartFastReportsDistribution;
interface
uses
DB,
Classes;
const
CellCount = 101;
type
TCellArray = array[0..CellCount-1] of integer;
TXValues = array[0..CellCount-1] of double;
TDistributionCells = class(TObject)
private
FDataSet: TDataSet;
FFieldName: string;
FCells: TCellArray;
FLastRecNo: integer;
FCellsMax: integer;
FDataMin, FDataMax: double;
procedure BuildCells;
function XValue(AIndex: integer): double;
function YValue(AIndex: integer): double;
function DataMean: double;
function DataDevPk: double;
end;
TArtFastReportsDistribution = class(TObject)
private
FDataSet: TDataSet;
FDistributions: TStringList;
function NameToDistribution(const AFieldName: string): TDistributionCells;
public
constructor Create(ADataSet: TDataSet);
destructor Destroy; override;
procedure DoGetData(const AFieldName: string; ARecNo: integer; var Value: Variant);
function RecordCount: integer;
end;
implementation
uses
Math,
SysUtils;
{ TArtFastReportsDistribution }
function TArtFastReportsDistribution.NameToDistribution(const AFieldName: string): TDistributionCells;
var
I: integer;
begin
I := FDistributions.IndexOf(AFieldName);
if I = -1 then
begin
Result := TDistributionCells.Create(FDataSet, AFieldName);
FDistributions.AddObject(AFieldName, Result);
end
else
Result := FDistributions.Objects[I] as TDistributionCells;
end;
constructor TArtFastReportsDistribution.Create(ADataSet: TDataSet);
begin
inherited Create;
FDataSet := ADataSet;
FDistributions := TStringList.Create;
FDistributions.OwnsObjects := True;
end;
destructor TArtFastReportsDistribution.Destroy;
begin
FreeAndNil(FDistributions);
inherited;
end;
procedure TArtFastReportsDistribution.DoGetData(const AFieldName: string; ARecNo: integer; var Value: Variant);
var
sFieldName: string;
bIsXValue: boolean;
I: integer;
Dist: TDistributionCells;
begin
sFieldName := AFieldName;
I := Pos('-X', sFieldName);
bIsXValue := I > 0;
if bIsXValue then
Delete(sFieldName, I, MaxInt);
Dist := NameToDistribution(sFieldName);
If (ARecNo = 1) and (Dist.FLastRecNo <> 1) then
Dist.BuildCells;
Dist.FLastRecNo := ARecNo;
if bIsXValue then
Value := Dist.XValue(ARecNo - 1)
else
Value := Dist.YValue(ARecNo - 1);
end;
function TArtFastReportsDistribution.RecordCount: integer;
begin
Result := CellCount;
end;
{ TDistributionCells }
procedure TDistributionCells.BuildCells;
procedure ClearCells;
var
I: integer;
begin
for I := 0 to CellCount - 1 do
FCells[I] := 0;
FCellsMax := 0;
FDataMin := 0.0;
FDataMax := 0.0;
end;
function GetDataSetFieldValues: TFloatArray;
var
I: integer;
Field: TField;
begin
Field := FDataSet.FieldByName(FFieldName);
if not Assigned(Field) then
Raise Exception.CreateFmt('Missing distribution field "%s"', [FFieldName]);
SetLength(Result, FDataSet.RecordCount);
FDataSet.First;
I := 0;
While not FDataset.EOF do
begin
Result[I] := Field.AsFloat;
Inc(I);
FDataSet.Next;
end;
end;
var
I,
iCellCount,
iOffset: integer;
F: double;
Data: TFloatArray;
begin
ClearCells;
If FDataSet.RecordCount = 0 then
Exit;
Data := GetDataSetFieldValues;
FDataMin := MinValue(Data);
FDataMax := MaxValue(Data);
FCellsMax := 0;
iCellCount := Length(FCells);
for I := 0 to Length(Data) - 1 do
begin
F := Data[I];
F := (F - DataMean + DataDevPk) / (2 * DataDevPk);
iOffset := Trunc(iCellCount * F);
If iOffset < 0 then
iOffset := 0
else
If iOffset > iCellCount - 1 then
iOffset := CellCount - 1;
FCells[iOffset] := FCells[iOffset] + 1;
If I = 0 then
FCellsMax := FCells[iOffset]
else
FCellsMax := Max(FCells[iOffset], FCellsMax);
end;
end;
constructor TDistributionCells.Create(ADataSet: TDataSet; const AFieldName: string);
begin
inherited Create;
FDataSet := ADataSet;
FFieldName := AFieldName;
end;
function TDistributionCells.DataDevPk: double;
begin
Result := FDataMax - DataMean;
end;
function TDistributionCells.DataMean: double;
begin
Result := (FDataMin + FDataMax) / 2;
end;
function TDistributionCells.XValue(AIndex: integer): double;
begin
Result := AIndex;
Result := (Result / CellCount) - 0.5;
Result := DataMean + (Result * 2 * DataDevPk);
end;
function TDistributionCells.YValue(AIndex: integer): double;
begin
Result := FCells[AIndex];
end;
end.
Используя этот код, вы можете создать диаграмму распределения данных в виде кривой Bell в FastReport из Delphi, не прибегая к созданию нового набора данных отсортированных ячеек.
Краткое описание: "Создание диаграммы распределения данных в виде кривой Bell в FastReport из Delphi."
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.