diff --git a/applications/lazstats/source/LazStats.lpi b/applications/lazstats/source/LazStats.lpi
index f262e789d..f6570482f 100644
--- a/applications/lazstats/source/LazStats.lpi
+++ b/applications/lazstats/source/LazStats.lpi
@@ -122,7 +122,7 @@
-
+
@@ -1534,6 +1534,11 @@
+
+
+
+
+
diff --git a/applications/lazstats/source/LazStats.lpr b/applications/lazstats/source/LazStats.lpr
index f02b58669..42339e2dc 100644
--- a/applications/lazstats/source/LazStats.lpr
+++ b/applications/lazstats/source/LazStats.lpr
@@ -8,7 +8,7 @@ uses
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, tachartlazaruspkg, tachartprint, lhelpcontrolpkg, Globals, LicenseUnit,
- OptionsUnit, MainDM, MainUnit;
+ OptionsUnit, MainDM, MainUnit, GridProcs;
{$R LazStats.res}
diff --git a/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas b/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas
index 4b3a686f0..59038ecf3 100644
--- a/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas
+++ b/applications/lazstats/source/forms/analysis/descriptive/crosstabunit.pas
@@ -65,7 +65,7 @@ implementation
uses
Math, Grids,
- Utils, DictionaryUnit;
+ Utils, DictionaryUnit, GridProcs;
{ TCrossTabCalculator }
@@ -232,13 +232,13 @@ begin
for i := 1 to no_in_list do
begin
j := FVarList[i-1];
- if not GoodRecord(FDataGrid, 1, NoSelected, FColNoSelected) then continue;
+ if not GoodRecord(FDataGrid, 1, FColNoSelected) then continue;
value := StrToFloat(FDataGrid.Cells[j, 1]);
min_value[i-1] := round(value);
max_value[i-1] := round(value);
for k := 2 to NoCases do
begin
- if not GoodRecord(FDataGrid, k, NoSelected, FColNoSelected) then continue;
+ if not GoodRecord(FDataGrid, k, FColNoSelected) then continue;
value := StrToFloat(FDataGrid.Cells[j, k]);
if value < min_value[i-1] then
min_value[i-1] := round(value);
@@ -292,11 +292,11 @@ begin
freq[i] := 0;
for i := 1 to NoCases do
begin
- if IsFiltered(i) then
+ if IsFiltered(FDataGrid, i) then
continue;
for j := 1 to no_in_list do
begin
- if not GoodRecord(i, NoSelected, FColNoSelected) then continue;
+ if not GoodRecord(FDataGrid, i, FColNoSelected) then continue;
k := FVarList[j-1];
value := StrToFloat(FDataGrid.Cells[k,i]);
x := round(value);
@@ -453,6 +453,9 @@ var
noSelected: Integer;
cellValue: String;
begin
+ AVarList := nil; // Silence the compiler
+ AColNoSelected := nil;
+
SetLength(AVarList, SelList.Count);
SetLength(AColNoSelected, SelList.Count);
diff --git a/applications/lazstats/source/units/dataprocs.pas b/applications/lazstats/source/units/dataprocs.pas
index 3ba850874..51be08b59 100644
--- a/applications/lazstats/source/units/dataprocs.pas
+++ b/applications/lazstats/source/units/dataprocs.pas
@@ -7,10 +7,11 @@ interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Clipbrd, Grids,
- Globals, OptionsUnit, DictionaryUnit;
+ Globals, OptionsUnit, DictionaryUnit, GridProcs;
-Function GoodRecord(Row, NoVars: integer; const GridPos: IntDyneVec): boolean; overload;
-Function GoodRecord(AGrid: TStringGrid; Row, NoVars: integer; const GridPos: IntDyneVec): boolean; overload;
+function GoodRecord(Row, NoVars: integer; const GridPos: IntDyneVec): boolean;
+function IsFiltered(GridRow: integer): boolean; overload;
+function ValidValue(row, col: integer): boolean;
procedure FormatCell(Col, Row : integer);
procedure FormatGrid;
@@ -29,10 +30,6 @@ procedure CopyRow;
procedure PasteRow;
procedure PrintDict(AReport: TStrings);
procedure PrintData(AReport: TStrings);
-function ValidValue(row, col: integer): boolean; overload;
-function ValidValue(AGrid: TStringGrid; row, col: integer): boolean; overload;
-function IsFiltered(GridRow: integer): boolean; overload;
-function IsFiltered(AGrid: TStringGrid; GridRow: integer): boolean; overload;
procedure MatRead(const a: DblDyneMat; out NoRows, NoCols: integer;
const Means, StdDevs: DblDyneVec; out NCases: integer;
@@ -66,25 +63,21 @@ implementation
uses
Utils, MainUnit;
-function GoodRecord(Row, NoVars: Integer; const GridPos: IntDyneVec): boolean;
-begin
- Result := GoodRecord(OS3MainFrm.DataGrid, Row, NoVars, GridPos);
-end;
-function GoodRecord(AGrid: TStringGrid; Row, NoVars: integer;
- const GridPos: IntDyneVec): boolean;
+// NOTE: Do not call GridProcs.GoodRecord here because this old function may
+// use an over-dimensioned GridPos array.
+function GoodRecord(Row, NoVars: Integer; const GridPos: IntDyneVec): boolean;
var
i, j: integer;
begin
Result := true;
- for i := 0 to NoVars-1 do
+ for i := 0 to NoVars - 1 do
begin
j := GridPos[i];
- if not ValidValue(AGrid, Row, j) then
+ if not ValidValue(Row, j) then
Result := false;
end;
end;
-//-------------------------------------------------------------------
procedure FormatCell(Col, Row: integer);
var
@@ -802,33 +795,7 @@ end;
function ValidValue(row, col: Integer): Boolean;
begin
- Result := ValidValue(OS3MainFrm.DataGrid, row, col);
-end;
-
-function ValidValue(AGrid: TStringGrid; row, col: integer): boolean;
-var
- valid: boolean;
- xvalue: string;
- cellstring: string;
-begin
- valid := true;
- if FilterOn then
- begin
- cellstring := Trim(AGrid.Cells[FilterCol, row]);
- if cellstring = 'NO' then valid := false;
- Result := valid;
- exit;
- end;
-
- xvalue := Trim(AGrid.Cells[col,row]);
- if (xvalue = '') and (DictionaryFrm.DictGrid.Cells[4, col] <> 'S') then
- valid := false;
- if valid then // check for user-defined missing value
- begin
- if Trim(DictionaryFrm.DictGrid.Cells[6, col]) = xvalue then
- valid := false;
- end;
- Result := valid;
+ Result := GridProcs.ValidValue(OS3MainFrm.DataGrid, row, col);
end;
function IsFiltered(GridRow: Integer): Boolean;
@@ -836,12 +803,6 @@ begin
Result := IsFiltered(OS3MainFrm.DataGrid, GridRow);
end;
-function IsFiltered(AGrid: TStringGrid; GridRow: integer): boolean;
-begin
- Result := FilterOn and (Trim(AGrid.Cells[FilterCol,GridRow]) = 'NO');
-end;
-
-
procedure MatRead(const a: DblDyneMat; out NoRows, NoCols: integer;
const means, stddevs: DblDyneVec; out NCases: integer;
const RowLabels, ColLabels: StrDyneVec; const AFileName: string);
diff --git a/applications/lazstats/source/units/gridprocs.pas b/applications/lazstats/source/units/gridprocs.pas
new file mode 100644
index 000000000..97a97b2f1
--- /dev/null
+++ b/applications/lazstats/source/units/gridprocs.pas
@@ -0,0 +1,157 @@
+unit GridProcs;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Grids,
+ Globals, DictionaryUnit;
+
+function CollectValues(AGrid: TStringGrid; AColIndex: Integer;
+ AColCheck: IntDyneVec): DblDyneVec;
+
+procedure GetMinMax(AGrid: TStringGrid; AColIndex: Integer;
+ const AColCheck: IntDyneVec; out AMin, AMax: Double);
+
+function GoodRecord(AGrid: TStringGrid; ARow: integer;
+ const AColCheck: IntDyneVec): boolean;
+
+function IsEmptyNumericValue(AGrid: TStringGrid; ARow, ACol: Integer): Boolean;
+
+function IsFiltered(AGrid: TStringGrid; ARow: integer): boolean;
+
+function IsMissingValueCode(AGrid: TStringGrid; ARow, ACol: Integer): Boolean;
+
+function ValidValue(AGrid: TStringGrid; ARow, ACol: integer): boolean;
+
+
+implementation
+
+uses
+ Math;
+
+{ Extracts the values in the given column from the grid and returns them as an
+ array.
+ Cells which are filtered or empty are not considered. This check is extended
+ over all columns specified by the column indices in AColCheck; AColCheck
+ should be empty to consider only the current column.
+ Non-numeric values in the considered cell will raise an exception.
+
+ NOTE: AColCheck must not be overdimensioned! }
+function CollectValues(AGrid: TStringGrid; AColIndex: Integer; AColCheck: IntDyneVec): DblDyneVec;
+var
+ row, n: Integer;
+begin
+ SetLength(Result, AGrid.RowCount);
+ n := 0;
+ for row := 1 to AGrid.RowCount-1 do
+ begin
+ if Length(AColCheck) = 0 then
+ begin
+ if not ValidValue(AGrid, row, AColIndex) then continue;
+ end else
+ begin
+ if not GoodRecord(AGrid, row, AColCheck) then continue;
+ end;
+ Result[n] := StrToFloat(trim(AGrid.Cells[AColIndex, row]));
+ inc(n);
+ end;
+ SetLength(Result, n);
+end;
+
+
+{ Determines the minimum and maximum of the values in the specified column of
+ the grid. Rows with "invalid" data are ignored. If AColCheck contains other
+ column indices these cells must be "valid", too. }
+procedure GetMinMax(AGrid: TStringGrid; AColIndex: Integer;
+ const AColCheck: IntDyneVec; out AMin, AMax: Double);
+var
+ row: Integer;
+ value: Double;
+begin
+ AMin := Infinity;
+ AMax := -Infinity;
+ for row := 1 to AGrid.RowCount-1 do
+ begin
+ if Length(AColCheck) = 0 then
+ begin
+ if not ValidValue(AGrid, row, AColIndex) then continue;
+ end else
+ begin
+ if not GoodRecord(AGrid, row, AColCheck) then continue;
+ end;
+ value := StrToFloat(trim(AGrid.Cells[AColIndex, row]));
+ if value < AMin then AMin := value;
+ if value > AMax then AMax := value;
+ end;
+end;
+
+
+{ Checks whether all cells specified for the given row in the columns listed in
+ the GridPos array are "valid": not filtered and not empty }
+function GoodRecord(AGrid: TStringGrid; ARow: integer;
+ const AColCheck: IntDyneVec): boolean;
+var
+ i, j: integer;
+begin
+ Result := true;
+ for i := 0 to High(AColCheck) do
+ begin
+ j := AColCheck[i];
+ if not ValidValue(AGrid, ARow, j) then
+ Result := false;
+ end;
+end;
+
+
+{ Checks whether the cell in the given row in the given numeric column is empty. }
+function IsEmptyNumericValue(AGrid: TStringGrid; ARow, ACol: Integer): Boolean;
+var
+ value: String;
+ isStringField: Boolean;
+begin
+ value := Trim(AGrid.Cells[ACol, ARow]);
+ isStringField := DictionaryFrm.DictGrid.Cells[4, ACol] = 'S';
+ Result := not IsStringField and (value = '');
+end;
+
+
+{ Checks whether the specified row is "filtered". Two criteria are needed for
+ a row to be filtered:
+ - The cell in column FilterCol (global value) must contain the text 'NO'.
+ - The global variable "FilterOn" must be TRUE. }
+function IsFiltered(AGrid: TStringGrid; ARow: integer): boolean;
+begin
+ Result := FilterOn and (Trim(AGrid.Cells[FilterCol, ARow]) = 'NO');
+end;
+
+
+{ Checks whether specified cell contains the "missing value code" defined by
+ the Dictionary }
+function IsMissingValueCode(AGrid: TStringGrid; ARow, ACol: Integer): Boolean;
+var
+ missingCode: String;
+ value: String;
+begin
+ missingCode := Trim(DictionaryFrm.DictGrid.Cells[6, ACol]);
+ value := Trim(AGrid.Cells[ACol, ARow]);
+ Result := (value = missingCode);
+end;
+
+
+{ Checks wheter the value in cell at the given column and row is a not-filtered,
+ non-empty number.
+ NOTE: non-numeric characters in a numeric field are not taken into account! }
+function ValidValue(AGrid: TStringGrid; ARow, ACol: integer): boolean;
+begin
+ Result := not (
+ IsFiltered(AGrid, ARow) or // filtering is active and row is marked to be excluded
+ IsEmptyNumericValue(AGrid, ARow, aCol) or // column is numeric, but cell is empty
+ IsMissingValueCode(AGrid, ARow, ACol) // cell contains the "missing value code"
+ );
+end;
+
+
+end.
+