Files
lazarus-ccr/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas
2020-10-12 13:43:51 +00:00

458 lines
11 KiB
ObjectPascal

// Use file "cansas.laz" for testing
unit PlotXYUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, ComCtrls, Grids,
MainUnit, Globals, MathUnit, BasicStatsReportAndChartFormUnit,
ReportFrameUnit, ChartFrameUnit;
type
{ TPlotXYForm }
TPlotXYForm = class(TBasicStatsReportAndChartForm)
ConfEdit: TEdit;
Label4: TLabel;
LineChk: TCheckBox;
MeansChk: TCheckBox;
ConfChk: TCheckBox;
OptionsGroup: TGroupBox;
YEdit: TEdit;
Label3: TLabel;
XEdit: TEdit;
Label2: TLabel;
XInBtn: TBitBtn;
XOutBtn: TBitBtn;
YInBtn: TBitBtn;
YOutBtn: TBitBtn;
Label1: TLabel;
VarList: TListBox;
procedure VarListDblClick(Sender: TObject);
procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean);
procedure XInBtnClick(Sender: TObject);
procedure XOutBtnClick(Sender: TObject);
procedure YInBtnClick(Sender: TObject);
procedure YOutBtnClick(Sender: TObject);
private
procedure PlotXY(XPoints, YPoints: DblDyneVec;
const ARegressionResults: TBivariateRegressionResults);
function PrepareData(ADataGrid: TStringGrid;
out xCol, ycol: Integer; out XData, YData: DblDyneVec;
out ColNoSelected: IntDyneVec): Boolean;
procedure WriteToReport(ARegressionResults: TBivariateRegressionResults);
protected
procedure AdjustConstraints; override;
procedure Compute; override;
procedure UpdateBtnStates; override;
function Validate(out AMsg: String; out AControl: TWinControl): Boolean; override;
public
constructor Create(AOwner: TComponent); override;
procedure Reset; override;
end;
var
PlotXYForm: TPlotXYForm;
implementation
{$R *.lfm}
uses
TAChartUtils, TAChartAxisUtils, TALegend, TASources, TACustomSeries, TASeries,
GridProcs, Utils;
{ TPlotXYForm }
constructor TPlotXYForm.Create(AOwner: TComponent);
begin
inherited;
FChartFrame.Chart.Legend.Alignment := laBottomCenter;
FChartFrame.Chart.Legend.ColumnCount := 3;
FChartFrame.Chart.Legend.TextFormat := tfHTML;
with FChartFrame.Chart.AxisList.Add do
begin
Alignment := calRight;
Marks.Source := TListChartSource.Create(self);
Marks.Style := smsLabel;
Grid.Visible := false;
TickColor := clNone;
end;
with FChartFrame.Chart.AxisList.Add do
begin
Alignment := calTop;
Marks.Source := TListChartSource.Create(self);
Marks.Style := smsLabel;
Grid.Visible := false;
TickColor := clNone;
end;
PageControl.ActivePage := ChartPage;
end;
procedure TPlotXYForm.AdjustConstraints;
begin
ParamsPanel.Constraints.MinHeight := OptionsGroup.Top + OptionsGroup.Height +
OptionsGroup.BorderSpacing.Bottom + ButtonBevel.Height +
CloseBtn.Height + CloseBtn.BorderSpacing.Top;
ParamsPanel.Constraints.MinWidth := OptionsGroup.Width * 2 - XInBtn.Width div 2 - XInBtn.BorderSpacing.Left;
Constraints.MinHeight := ParamsPanel.Constraints.MinHeight + ParamsPanel.BorderSpacing.Around*2;
Constraints.MinWidth := ParamsPanel.Constraints.MinWidth + 200;
end;
procedure TPlotXYForm.Compute;
var
xValues: DblDyneVec = nil;
yValues: DblDyneVec = nil;
ColNoSelected: IntDyneVec= nil;
xCol, yCol: Integer;
confBand: Double;
regressionRes: TBivariateRegressionResults;
C: TWinControl;
msg: String;
begin
// Validation: Make sure that XEdit and YEdit are not empty
if not Validate(msg, C) then
begin
C.SetFocus;
ErrorMsg(msg);
ModalResult := mrNone;
exit;
end;
// Extract data from Grid
if not PrepareData(OS3MainFrm.DataGrid, xCol, yCol, xValues, yValues, ColNoSelected) then
exit;
// Sort on x values
SortOnX(xValues, yValues);
// Calculate regression
confBand := StrToFloat(ConfEdit.Text) / 100.0;
Calc_BivariateRegression(xValues, yValues, confBand, regressionRes);
// Print the descriptive statistics to the output frame
WriteToReport(regressionRes);
// Plot the values (and optional line and confidence band if elected)
PlotXY(xValues, yValues, regressionRes);
end;
procedure TPlotXYForm.PlotXY(XPoints, YPoints: DblDyneVec;
const ARegressionResults: TBivariateRegressionResults);
var
tmpX: DblDyneVec = nil;
tmpY: DblDyneVec = nil;
conf: DblDyneVec = nil;
ext: TDoubleRect;
xmin, xmax, ymin, ymax: Double;
rightLabels: TListChartSource;
topLabels: TListChartSource;
ser: TChartSeries;
i: Integer;
begin
rightLabels := FChartFrame.Chart.AxisList[2].Marks.Source as TListChartSource;
rightLabels.Clear;
topLabels := FChartFrame.Chart.AxisList[3].Marks.Source as TListChartSource;
topLabels.Clear;
FChartFrame.Clear;
// Titles
FChartFrame.SetTitle('X vs. Y plot using file ' + OS3MainFrm.FileNameEdit.Text);
with ARegressionResults do
FChartFrame.SetFooter(Format('R(X,Y) = %.3f, Slope = %.3f, Intercept = %.3f', [
R, Slope, Intercept
]));
FChartFrame.SetXTitle(XEdit.Text);
FChartFrame.SetYTitle(YEdit.Text);
// Draw upper confidence band
if ConfChk.Checked then
begin
SetLength(conf, ARegressionResults.Count);
for i := 0 to High(conf) do
conf[i] := ARegressionResults.ConfidenceLimits(XPoints[i], true);
ser := FChartFrame.PlotXY(ptLines, XPoints, conf, nil, nil, 'Upper confidence band', clRed);
rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'UCL');
end;
// Plot data points
FChartFrame.PlotXY(ptSymbols, XPoints, YPoints, nil, nil, 'Data values', clNavy);
// Draw lower confidence band
if ConfChk.Checked then
begin
SetLength(conf, ARegressionResults.Count);
for i := 0 to High(conf) do
conf[i] := ARegressionResults.ConfidenceLimits(XPoints[i], false);
ser := FChartFrame.PlotXY(ptLines, XPoints, conf, nil, nil, 'Lower confidence band', clRed);
rightLabels.Add(ser.yValue[ser.Count-1], ser.YValue[ser.Count-1], 'LCL');
end;
{
FChartFrame.Chart.Prepare;
FChartFrame.GetXRange(xmin, xmax, false);
FChartFrame.GetYRange(ymin, ymax, false);
}
// Draw means
if MeansChk.Checked then
with ARegressionResults do
begin
FChartFrame.VertLine(XMean, clGreen, psDashDot, 'Mean ' + XEdit.Text);
topLabels.Add(XMean, XMean, 'Mean ' + XEdit.Text);
FChartFrame.HorLine(YMean, clGreen, psDash, 'Mean ' + YEdit.Text);
rightLabels.Add(YMean, YMean, 'Mean ' + YEdit.Text);
end;
// Draw regression line
if LineChk.Checked then
begin
ext := FChartFrame.Chart.GetFullExtent;
SetLength(tmpX, 2);
SetLength(tmpY, 2);
tmpX[0] := ext.a.x;
tmpX[1] := ext.b.x;
with ARegressionResults do
begin
tmpY[1] := tmpX[1] * Slope + Intercept;
tmpY[0] := tmpX[0] * Slope + Intercept;
end;
ser := FChartFrame.PlotXY(ptLines, tmpX, tmpY, nil, nil, 'Predicted', clBlack);
rightLabels.Add(tmpY[1], tmpY[1], 'Predicted');
end;
FChartFrame.Chart.Legend.Visible := false;
end;
function TPlotXYForm.PrepareData(ADataGrid: TStringGrid;
out xCol, ycol: Integer; out XData, YData: DblDyneVec;
out ColNoSelected: IntDyneVec): Boolean;
var
N: Integer;
begin
Result := false;
ColNoSelected := nil;
XData := nil;
YData := nil;
xCol := GetVariableIndex(ADataGrid, XEdit.Text);
yCol := GetVariableIndex(ADataGrid, YEdit.Text);
if xCol = -1 then
begin
ErrorMsg('X variable not found.');
exit;
end;
if yCol = -1 then
begin
ErrorMsg('Y variable not found.');
exit;
end;
SetLength(ColNoSelected, 2);
ColNoSelected[0] := xCol;
ColNoSelected[1] := yCol;
XData := CollectValues(ADataGrid, xCol, colNoSelected);
YData := CollectValues(ADataGrid, ycol, colNoSelected);
N := Length(XData);
if N < 3 then
begin
ErrorMsg('At least three data points required.');
exit;
end;
if N <> Length(YData) then
begin
ErrorMsg('Equal count of cases required for x and y variables.');
exit;
end;
Result := true;
end;
procedure TPlotXYForm.Reset;
var
i: integer;
begin
inherited;
XEdit.Text := '';
YEdit.Text := '';
ConfEdit.Text := FormatFloat('0.0', DEFAULT_CONFIDENCE_LEVEL_PERCENT);
LineChk.Checked := false;
MeansChk.Checked := false;
ConfChk.Checked := false;
VarList.Items.Clear;
for i := 1 to NoVariables do
VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]);
UpdateBtnStates;
end;
procedure TPlotXYForm.UpdateBtnStates;
begin
inherited;
XInBtn.Enabled := (VarList.ItemIndex > -1) and (XEdit.Text = '');
XoutBtn.Enabled := (XEdit.Text <> '');
YinBtn.Enabled := (VarList.ItemIndex > -1) and (YEdit.Text = '');
YoutBtn.Enabled := (YEdit.Text <> '');
end;
function TPlotXYForm.Validate(out AMsg: String; out AControl: TWinControl): Boolean;
begin
Result := false;
if XEdit.Text = '' then
begin
AMsg := 'No variable selected for X.';
exit;
end;
if YEdit.Text = '' then
begin
AControl := YEdit;
AMsg := 'No variable selected for Y.';
exit;
end;
Result := true;
end;
procedure TPlotXYForm.VarListDblClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if index > -1 then
begin
if XEdit.Text = '' then
XEdit.Text := VarList.Items[index]
else
YEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TPlotXYForm.VarListSelectionChange(Sender: TObject; User: boolean);
begin
UpdateBtnStates;
end;
procedure TPlotXYForm.WriteToReport(ARegressionResults: TBivariateRegressionResults);
var
lReport: TStrings;
begin
lReport := TStringList.Create;
try
lReport.Add('X vs. Y PLOT');
lReport.Add('');
lReport.Add('Data file: %s', [OS3MainFrm.FileNameEdit.Text]);
lReport.Add('');
lReport.Add('Variables:');
lReport.Add(' X: %s', [XEdit.Text]);
lReport.Add(' Y: %s', [YEdit.Text]);
lReport.Add('');
lReport.Add('Variable Mean Variance Std.Dev.');
lReport.Add('---------- -------- -------- --------');
with ARegressionResults do
begin
lReport.Add('%-10s %8.2f %8.2f %8.2f', [XEdit.Text, XMean, XVariance, XStdDev]);
lReport.Add('%-10s %8.2f %8.2f %8.2f', [YEdit.Text, YMean, YVariance, YStdDev]);
lReport.Add('');
lReport.Add('Regression:');
lReport.Add(' Correlation: %8.3f', [R]);
lReport.Add(' Slope: %8.3f', [Slope]);
lReport.Add(' Intercept: %8.3f', [Intercept]);
lReport.Add(' Standard Error of Estimate: %8.3f', [StdErrorPredicted]);
lReport.Add(' Number of good cases: %8d', [Count]);
end;
FReportFrame.DisplayReport(lReport);
finally
lReport.Free;
end;
end;
procedure TPlotXYForm.XInBtnClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (XEdit.Text = '') then
begin
XEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TPlotXYForm.XOutBtnClick(Sender: TObject);
begin
if XEdit.Text <> '' then
begin
VarList.Items.Add(XEdit.Text);
XEdit.Text := '';
UpdateBtnStates;
end;
end;
procedure TPlotXYForm.YInBtnClick(Sender: TObject);
var
index: integer;
begin
index := VarList.ItemIndex;
if (index > -1) and (YEdit.Text = '') then
begin
YEdit.Text := VarList.Items[index];
VarList.Items.Delete(index);
UpdateBtnStates;
end;
end;
procedure TPlotXYForm.YOutBtnClick(Sender: TObject);
begin
if YEdit.Text <> '' then
begin
VarList.Items.Add(YEdit.Text);
YEdit.Text := '';
UpdateBtnStates;
end;
end;
end.