From b61c350fac4bf405b7268b78f014a85daaf8fbad Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sun, 23 Aug 2020 17:51:36 +0000 Subject: [PATCH] LazStats: Use TAChart in BoxPlotUnit. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7632 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../lazstats/docs/HelpNDoc/LazStats.hnd | Bin 11337728 -> 11345920 bytes .../analysis/descriptive/boxplotunit.pas | 568 +++++++----------- .../forms/analysis/descriptive/plotxyunit.pas | 2 +- 3 files changed, 222 insertions(+), 348 deletions(-) diff --git a/applications/lazstats/docs/HelpNDoc/LazStats.hnd b/applications/lazstats/docs/HelpNDoc/LazStats.hnd index e025326d4b874d97b67c99b83bae27f5399dbf73..97a4f87705102aad1f255ab5f10f53ed20c0dee0 100644 GIT binary patch delta 13425 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 U6VGm?{FnbHLp&mxy?FTl0jZY{bN~PV delta 2340 zcmZY9dsGzH9S88c^VnUMWoG5It0Fs~fKLj(F+p5Zh%u;Wv=u=Y6BFsEAetDg)y3dl z6(1;(UwtHE5z#h=wN>wFaw;hJ02Mdq)Oebt9u$*5OnTHdJ?UxsN52qH`$uQb=g!>w zyT9Muxie=2?dR1%;JjKlG%AiUN0>3odWPn}$SIb%YX1(u<$_;2nPZQMc#L63SU6`$ zsZxDr;GpO{sHED&<2jP&Yhy86A2l@=c_n(TPWnUrnBJx@*H7qW`bOQO2g%5gZ;K}x za=K=Tq{JL~N~XS@S*ypOD30cIG!LssqWyR>V^Xks8ZO(HpwhHGm*55bKVmi)mV`KxYWNTOz`WrA>F%T_f8&PoE z-9k5g9aZHTRE|DI25PNA#yTq9>WYi_Z&`Y9Zdi3VB~ z>_q=>R$IaLcNY{e`e2!C5WhQk6*kzJLeDayiyYOIUhTz%vqiLEnl)K`@?ESil6upA zd<*k=$iqW}d1wd^4dtN(9va3&i9F=J7&5F+t>>BEY&(xn_nzo$O!~-5_nwvww?A2~ z+Pu~G%f#W9q_1qam*YyXxitxrVK|Hs&8;cLy$G?XLaj@E&!Xlj1qBjSbTk{{(t*@G zO_{)I7&#hK-;ktqQPG&X!Y=8R;?vWqC#?N>`SkSQ%p^MQ*pWuQY{dxPZ%u_X7zv|b zG>n0k;Cy)V|FdinrM3@AVVG2xzESLr_z>AO#(;)|DfDdwECd`7_kO%qj63l^@ zVJ^G^1uzfh!vZLTg|G--g~hN0ehNQ>rBEd9w=O#pmK>(o94MY{QEW%qU@FW~;>4%b z#oekBv5TcrPJ+XbEMvvxVDU=HGM9WKuo<&<7efgwhZW$5QdkME!RxRJ-hek@HI%{6 z#jM@tA17_Im~0YjV7BqrZ&*WAPgGF#+1jIg+9T%=`X6jn)|1W)&T{q_=O}5qx>LR% z*{6@Od@qyT7I{pL(JLbtt9K$b$8|?Qx@8GTI~>y@zEjpH|BCoXd*523q(r8QHa++}KZzb~+UKDaU1k9-bXR%lcsDXS+*Jns(1DEPA+=sA zxoRd;<7G3So^@iWxv!cAZRx?hYu)Bc)ab^P&#$BVcTcTBhp!-Qc&C|1zUVmy?Y)IS zP9o~j9kYyDZ<+JR-Gk_(p}1Ri7oyKRi1uE=x@TkE)gG*S+hsFpp!elMDVdyC%p?`D z0t>a;1{<`l!(2`0{L)-HWlDL}eHG=tx@LYx(haQf-VM_?keiu+N=v)VB$|H{GrqiO z?imK5sj0`@sp8G#O0=&s!=O3k(l8o#1)Jl0RO6*x zozFK{i>XSc)3>oLf9*2!2lhMAKeYM=9$wducxfntJZ$pt8XjKD!|QmsoQErTcs&nS z^6&;8-pIpm@$lO`yy;^29c6}GB(;;xRaH;NTqvYZUF;DrcD8(L3ARU9!DjdcY=K|G zR@epssD|zEu4s?0nRPpUh}_21CUvxOP7msuc1TNzYKj_53B5JR(#%{jy|>0LlhJX; zMund=8ML-x?%88ypVh?TYF`s;T(y5z>t_T;5|)(`tc$OO zJ+K${!G5TN18@-P;XOD64M6ZKI1ESNC>(?L;R84hjc@{f4NY(on&FhFi*MO@#;ItG z=>eT-0gZ_*y*1sEz|%}th3iI3g-Y{1wRXDbJo^ssY2iJmdCwW%^P5vWzs>FckM5wl zt`S3p*Y%-WrGmh&MPzgh*2G5DUFPVIcT(MsC5a;YK~2Q}R~4w5Cij0JYzvjXeAJM$ hGq%wBXQK^~;yQav5wUS+zjZV`wJJiH@!`MQ{sZenYODYN diff --git a/applications/lazstats/source/forms/analysis/descriptive/boxplotunit.pas b/applications/lazstats/source/forms/analysis/descriptive/boxplotunit.pas index da82e02b1..df913d07b 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/boxplotunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/boxplotunit.pas @@ -3,13 +3,14 @@ unit BoxPlotUnit; {$mode objfpc}{$H+} +{$I ../../../LazStats.inc} interface uses - Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, + Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Printers, - MainUnit, Globals, DataProcs, OutputUnit, BlankFrmUnit, ContextHelpUnit; + MainUnit, Globals, DataProcs, OutputUnit, ContextHelpUnit; type @@ -41,29 +42,14 @@ type private { private declarations } FAutoSized: Boolean; - function Percentile(nscrgrps : integer; - pcnt : double; - VAR freq : DblDyneVec; - VAR cumfreq : DblDyneVec; - VAR scores : DblDyneVec) : double; - { - procedure pBoxPlot(nbars : integer; - max, min : double; - VAR lowqrtl : DblDyneVec; - VAR hiqrtl : DblDyneVec; - VAR tenpcnt : DblDyneVec; - VAR ninetypcnt : DblDyneVec; - VAR means : DblDyneVec; - VAR median : DblDyneVec); - } - procedure BoxPlot(nbars : integer; - max, min : double; - VAR lowqrtl : DblDyneVec; - VAR hiqrtl : DblDyneVec; - VAR tenpcnt : DblDyneVec; - VAR ninetypcnt : DblDyneVec; - VAR means : DblDyneVec; - VAR median : DblDyneVec); + function Percentile(nScoreGrps: integer; APercentile: Double; + const Freq, CumFreq, Scores: DblDyneVec) : double; + {$IFDEF USE_TACHART} + procedure BoxPlot(const LowQrtl, HiQrtl, TenPcnt, NinetyPcnt, Medians: DblDyneVec); + {$ELSE} + procedure BoxPlot(NBars: integer; AMax, AMin: double; + const LowQrtl, HiQrtl, TenPcnt, NinetyPcnt, Means, Median: DblDyneVec); + {$ENDIF} public { public declarations } @@ -74,8 +60,20 @@ var implementation +{$R *.lfm} + uses - Math; + {$IFDEF USE_TACHART} + TAChartUtils, TAMultiSeries, + ChartUnit, + {$ELSE} + BlankFrmUnit, + {$ENDIF} + Math, Utils; + +const + BOX_COLORS: Array[0..3] of TColor = (clBlue, clGreen, clFuchsia, clLime); + { TBoxPlotFrm } @@ -90,10 +88,10 @@ begin VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); end; + procedure TBoxPlotFrm.VarListClick(Sender: TObject); var index: integer; - begin index := VarList.ItemIndex; if index > -1 then @@ -105,6 +103,7 @@ begin end; end; + procedure TBoxPlotFrm.HelpBtnClick(Sender: TObject); begin if ContextHelpForm = nil then @@ -112,23 +111,30 @@ begin ContextHelpForm.HelpMessage((Sender as TButton).tag); end; + procedure TBoxPlotFrm.ComputeBtnClick(Sender: TObject); var - i, j, k, GrpVar, MeasVar, mingrp, maxgrp, G, NoGrps, cnt : integer; - nscrgrps : integer; - X, tenpcnt, ninepcnt, qrtile1, qrtile2, qrtile3 : double; - minscr, maxscr, intvlsize, lastX : double; - cellstring: string; - means, lowqrtl, hiqrtl, tenpcntile, ninetypcntile, median : DblDyneVec; - freq : DblDyneVec; - Scores : DblDyneVec; - cumfreq : DblDyneVec; - prank : DblDyneVec; - grpsize : IntDyneVec; - done : boolean; - NoSelected : integer; - ColNoSelected : IntDyneVec; lReport: TStrings; + i, j, k, GrpVar, MeasVar, mingrp, maxgrp, G, NoGrps, cnt: integer; + nScoreGrps: integer; + X, tmp: Double; +// X, tenpcnt, ninepcnt, qrtile1, qrtile2, qrtile3: double; + MinScore, MaxScore, IntervalSize, lastX: double; + cellstring: string; + done: boolean; + NoSelected: integer; + Freq: DblDyneVec = nil; + Scores: DblDyneVec = nil; + CumFreq: DblDyneVec = nil; + pRank: DblDyneVec = nil; + GrpSize: IntDyneVec = nil; + Means: DblDyneVec = nil; + LowQrtl: DblDyneVec = nil; + HiQrtl: DbldyneVec = nil; + TenPcntile: DblDyneVec = nil; + NinetyPcntile: DblDyneVec = nil; + Median: DblDyneVec = nil; + ColNoSelected: IntDyneVec = nil; begin lReport := TStringList.Create; try @@ -145,12 +151,12 @@ begin end; if GrpVar = 0 then begin - MessageDlg('Group variable not selected.', mtError, [mbOK], 0); + ErrorMsg('Group variable not selected.'); exit; end; if MeasVar = 0 then begin - MessageDlg('Measurement variable not selected.', mtError, [mbOK], 0); + ErrorMsg('Measurement variable not selected.'); exit; end; @@ -160,183 +166,182 @@ begin ColNoSelected[1] := MeasVar; // get minimum and maximum group values - mingrp := 10000; - maxgrp := -10000; + minGrp := MaxInt; + maxGrp := -MaxInt; for i := 1 to NoCases do begin - if not GoodRecord(i,NoSelected,ColNoSelected) then continue; - G := round(StrToFloat(OS3MainFrm.DataGrid.Cells[GrpVar,i])); - if G < mingrp then mingrp := G; - if G > maxgrp then maxgrp := G; + if not GoodRecord(i, NoSelected, ColNoSelected) then continue; + G := round(StrToFloat(OS3MainFrm.DataGrid.Cells[GrpVar, i])); + minGrp := Min(G, minGrp); + maxGrp := Max(G, maxGrp); end; - NoGrps := maxgrp - mingrp + 1; + NoGrps := maxGrp - minGrp + 1; if NoGrps > 30 then begin - MessageDlg('Too many groups for a meaningful plot.', mtError, [mbOK], 0); + ErrorMsg('Too many groups for a meaningful plot (max: 20)'); exit; end; - SetLength(freq,2 * NoCases + 1); - SetLength(Scores,2 * NoCases + 1); - SetLength(cumfreq,2 * NoCases + 1); - SetLength(prank,2 * NoCases + 1); - - SetLength(grpsize,NoGrps+1); - SetLength(means,NoGrps+1); - SetLength(lowqrtl,NoGrps+1); - SetLength(hiqrtl,NoGrps+1); - SetLength(tenpcntile,NoGrps+1); - SetLength(ninetypcntile,NoGrps+1); - SetLength(median,NoGrps+1); - - // initialize - for j := 1 to NoGrps do - begin - means[j-1] := 0.0; - grpsize[j-1] := 0; - end; - // get minimum and maximum scores and score interval - intvlsize := 10000.0; + IntervalSize := Infinity; lastX := 0.0; - X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar,1]); - minscr := X; - maxscr := X; + X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar, 1]); + MinScore := X; + MaxScore := X; for i := 1 to NoCases do begin - if not GoodRecord(i,NoSelected,ColNoSelected) then continue; - X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar,i]); - if X > maxscr then maxscr := X; - if X < minscr then minscr := X; + if not GoodRecord(i, NoSelected ,ColNoSelected) then continue; + X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar, i]); + MaxScore := Max(MaxScore, X); + MinScore := Min(MinScore, X); if i > 1 then // get interval size as minimum difference between 2 scores begin - if (X <> lastX) and (abs(X - lastX) < intvlsize) then - intvlsize := abs(X - lastX); + if (X <> lastX) and (abs(X - lastX) < IntervalSize) then + IntervalSize := abs(X - lastX); lastX := X; end else lastX := X; end; + SetLength(Scores, 2*NoCases + 1); // over-dimensioned, will be trimmed later. + // check for excess no. of intervals and reset if needed - nscrgrps := round((maxscr - minscr) / intvlsize); - if nscrgrps > 2 * NoCases then - intvlsize := (maxscr - minscr) / NoCases; + nScoreGrps := round((MaxScore - MinScore) / IntervalSize); + if nScoreGrps > 2 * NoCases then + Intervalsize := (MaxScore - MinScore) / NoCases; // setup score groups done := false; - Scores[0] := minscr - intvlsize / 2.0; - nscrgrps := 0; - lastX := maxscr + intvlsize + intvlsize / 2.0; + Scores[0] := MinScore - IntervalSize / 2.0; + nScoreGrps := 0; + lastX := MaxScore + IntervalSize + IntervalSize / 2.0; while not done do begin - nscrgrps := nscrgrps + 1; - Scores[nscrgrps] := minscr + (nscrgrps * intvlsize) - intvlsize / 2.0; - if Scores[nscrgrps] > lastX then done := true; + inc(nScoreGrps); + Scores[nScoreGrps] := MinScore + (nScoreGrps * IntervalSize) - IntervalSize / 2.0; + if Scores[nScoreGrps] > lastX then done := true; end; - Scores[nscrgrps+1] := Scores[nscrgrps] + intvlsize; - if Scores[0] < minscr then minscr := Scores[0]; - if Scores[nscrgrps] > maxscr then maxscr := Scores[nscrgrps]; + Scores[nScoreGrps + 1] := Scores[nScoreGrps] + IntervalSize; + if Scores[0] < MinScore then MinScore := Scores[0]; + if Scores[nScoreGrps] > MaxScore then MaxScore := Scores[nScoreGrps]; + + SetLength(Scores, nScoreGrps+1); // trim to used length + SetLength(Freq, nScoreGrps); + SetLength(CumFreq, nScoreGrps); + SetLength(pRank, nScoreGrps); + + SetLength(GrpSize, NoGrps); + SetLength(Means, NoGrps); + SetLength(LowQrtl, NoGrps); + SetLength(HiQrtl, NoGrps); + SetLength(TenPcntile, NoGrps); + SetLength(NinetyPcntile, NoGrps); + SetLength(Median, NoGrps); // do analysis for each group - for j := 1 to NoGrps do // group + for j := 0 to NoGrps-1 do // group begin + Means[j] := 0.0; + GrpSize[j] := 0; + // get score groups for this group j - for i := 0 to nscrgrps do + for i := 0 to nScoreGrps-1 do begin - cumfreq[i] := 0.0; - freq[i] := 0.0; + CumFreq[i] := 0.0; + Freq[i] := 0.0; end; cnt := 0; for i := 1 to NoCases do begin // get scores for this group j - if not GoodRecord(i,NoSelected,ColNoSelected) then continue; - G := round(StrToFloat(OS3MainFrm.DataGrid.Cells[GrpVar,i])); - G := G - mingrp + 1; - if G = j then // subject in this group + if not GoodRecord(i,NoSelected, ColNoSelected) then continue; + G := round(StrToFloat(OS3MainFrm.DataGrid.Cells[GrpVar, i])); + G := G - minGrp + 1; + if G = j+1 then // subject in this group begin - cnt := cnt + 1; - X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar,i]); - means[j-1] := means[j-1] + X; + inc(cnt); + X := StrToFloat(OS3MainFrm.DataGrid.Cells[MeasVar, i]); + Means[j] := Means[j] + X; // find score interval and add to the frequency - for k := 0 to nscrgrps do + for k := 0 to nScoreGrps do if (X >= Scores[k]) and (X < Scores[k+1]) then - freq[k] := freq[k] + 1; + Freq[k] := Freq[k] + 1.0; end; end; - grpsize[j-1] := cnt; - if grpsize[j-1] > 0 then means[j-1] := means[j-1] / grpsize[j-1]; + GrpSize[j] := cnt; + if GrpSize[j] > 0 then Means[j] := Means[j] / GrpSize[j]; // accumulate frequencies - cumfreq[0] := freq[0]; - for i := 1 to nscrgrps-1 do - cumfreq[i] := cumfreq[i-1] + freq[i]; - cumfreq[nscrgrps] := cumfreq[nscrgrps-1]; + CumFreq[0] := Freq[0]; + for i := 1 to nScoreGrps-1 do + CumFreq[i] := CumFreq[i-1] + Freq[i]; + CumFreq[nScoreGrps] := CumFreq[nScoreGrps-1]; // get percentile ranks - prank[0] := ((cumfreq[0] / 2.0) / grpsize[j-1]) * 100.0; - for i := 1 to nscrgrps-1 do - prank[i] := ((cumfreq[i-1] + (freq[i] / 2.0)) / grpsize[j-1]) * 100.0; + pRank[0] := ((CumFreq[0] / 2.0) / GrpSize[j]) * 100.0; + for i := 1 to nScoreGrps-1 do + pRank[i] := ((CumFreq[i-1] + (Freq[i] / 2.0)) / GrpSize[j]) * 100.0; // get centiles required. - tenpcnt := 0.10 * grpsize[j-1]; - tenpcntile[j-1] := Percentile(nscrgrps,tenpcnt,freq,cumfreq,scores); - ninepcnt := 0.90 * grpsize[j-1]; - ninetypcntile[j-1] := Percentile(nscrgrps,ninepcnt,freq,cumfreq,scores); - qrtile1 := 0.25 * grpsize[j-1]; - lowqrtl[j-1] := Percentile(nscrgrps,qrtile1,freq,cumfreq,scores); - qrtile2 := 0.50 * grpsize[j-1]; - median[j-1] := Percentile(nscrgrps,qrtile2,freq,cumfreq,scores); - qrtile3 := 0.75 * grpsize[j-1]; - hiqrtl[j-1] := Percentile(nscrgrps,qrtile3,freq,cumfreq,scores); + TenPcntile[j] := Percentile(nScoreGrps, 0.10 * GrpSize[j], Freq, CumFreq, Scores); + NinetyPcntile[j] := Percentile(nScoreGrps, 0.90 * GrpSize[j], Freq, CumFreq, Scores); + LowQrtl[j] := Percentile(nScoreGrps, 0.25 * GrpSize[j], Freq, CumFreq, Scores); + Median[j] := Percentile(nScoreGrps, 0.50 * GrpSize[j], Freq, CumFreq, Scores); + HiQrtl[j] := Percentile(nScoreGrps, 0.75 * GrpSize[j], Freq, CumFreq, Scores); if ShowChk.Checked then begin - if j > 1 then lReport.Add(''); - lReport.Add('RESULTS FOR GROUP %d, MEAN = %.3f', [j, means[j-1]]); + if j > 0 then lReport.Add(''); + lReport.Add('RESULTS FOR GROUP %d, MEAN = %.3f', [j+1, Means[j]]); lReport.Add(''); lReport.Add('Centile Value'); lReport.Add('------------ ------'); - lReport.Add('Ten %6.3f', [tenpcntile[j-1]]); - lReport.Add('Twenty five %6.3f', [lowqrtl[j-1]]); - lReport.Add('Median %6.3f', [median[j-1]]); - lReport.Add('Seventy five %6.3f', [hiqrtl[j-1]]); - lReport.Add('Ninety %6.3f', [ninetypcntile[j-1]]); + lReport.Add('Ten %6.3f', [TenPcntile[j]]); + lReport.Add('Twenty five %6.3f', [LowQrtl[j]]); + lReport.Add('Median %6.3f', [Median[j]]); + lReport.Add('Seventy five %6.3f', [HiQrtl[j]]); + lReport.Add('Ninety %6.3f', [NinetyPcntile[j]]); lReport.Add(''); lReport.Add('Score Range Frequency Cum.Freq. Percentile Rank'); lReport.Add('--------------- --------- --------- ---------------'); - for i := 0 to nscrgrps-1 do + for i := 0 to nScoreGrps-1 do lReport.Add('%6.2f - %6.2f %6.2f %6.2f %6.2f', [ - Scores[i], Scores[i+1], freq[i], cumfreq[i], prank[i] + Scores[i], Scores[i+1], Freq[i], CumFreq[i], pRank[i] ]); lReport.Add(''); end; end; // get values for next group + // Show the report with the frequencies if ShowChk.Checked then DisplayReport(lReport); - // plot the boxes - BoxPlot(NoGrps, maxscr, minscr, lowqrtl, hiqrtl, tenpcntile, ninetypcntile, means, median); + // Plot the boxes + {$IFDEF USE_TACHART} + BoxPlot(LowQrtl, HiQrtl, TenPcntile, NinetyPcntile, Median); + {$ELSE} + BoxPlot(NoGrps, MaxScore, MinScore, LowQrtl, HiQrtl, TenPcntile, NinetyPcntile, Means, Median); + {$ENDIF} finally lReport.Free; // Clean up - median := nil; - ninetypcntile := nil; - tenpcntile := nil; - hiqrtl := nil; - lowqrtl := nil; - means := nil; - grpsize := nil; - cumfreq := nil; - scores := nil; - freq := nil; + Median := nil; + NinetyPcntile := nil; + TenPcntile := nil; + HiQrtl := nil; + LowQrtl := nil; + Means := nil; + GrpSize := nil; + CumFreq := nil; + Scores := nil; + Freq := nil; ColNoSelected := nil; end; end; + procedure TBoxPlotFrm.FormActivate(Sender: TObject); var w: Integer; @@ -356,30 +361,29 @@ begin FAutoSized := true; end; + procedure TBoxPlotFrm.FormCreate(Sender: TObject); begin Assert(OS3MainFrm <> nil); - if BlankFrm = nil then Application.CreateForm(TBlankFrm, BlankFrm); end; + procedure TBoxPlotFrm.FormShow(Sender: TObject); begin ResetBtnClick(self); end; -function TBoxPlotFrm.Percentile(nscrgrps: integer; - pcnt: double; - var freq: DblDyneVec; - var cumfreq: DblDyneVec; - var scores: DblDyneVec) : double; + +function TBoxPlotFrm.Percentile(nScoreGrps: integer; + APercentile: double; const Freq, CumFreq, Scores: DblDyneVec) : double; var i, interval: integer; - pcntile, Llimit, Ulimit, cumlower, intvlfreq: double; + LLimit, ULimit, cumLower, intervalFreq: double; begin interval := 0; - for i := 0 to nscrgrps-1 do + for i := 0 to nScoreGrps-1 do begin - if cumfreq[i] > pcnt then + if CumFreq[i] > APercentile then begin interval := i; Break; @@ -388,195 +392,68 @@ begin if interval > 0 then begin - Llimit := Scores[interval]; - Ulimit := Scores[interval+1]; - cumlower := cumfreq[interval-1]; - intvlfreq := freq[interval]; + LLimit := Scores[interval]; + ULimit := Scores[interval+1]; + cumLower := CumFreq[interval-1]; + intervalFreq := Freq[interval]; end else begin // Percentile in first interval - Llimit := Scores[0]; - Ulimit := Scores[1]; - cumlower := 0.0; - intvlfreq := freq[0]; + LLimit := Scores[0]; + ULimit := Scores[1]; + cumLower := 0.0; + intervalFreq := Freq[0]; end; - if intvlfreq > 0 then - pcntile := Llimit + ((pcnt - cumlower) / intvlfreq) * (Ulimit- Llimit) + if intervalFreq > 0 then + Result := LLimit + ((APercentile - cumLower) / intervalFreq) * (ULimit- LLimit) else - pcntile := Llimit; - - Result := pcntile; + Result := LLimit; end; -//------------------------------------------------------------------- -{ -procedure TBoxPlotFrm.pBoxPlot(nbars : integer; - max, min : double; - VAR lowqrtl : DblDyneVec; - VAR hiqrtl : DblDyneVec; - VAR tenpcnt : DblDyneVec; - VAR ninetypcnt : DblDyneVec; - VAR means : DblDyneVec; - VAR median : DblDyneVec); + +{$IFDEF USE_TACHART} +procedure TBoxPlotFrm.BoxPlot(const LowQrtl, HiQrtl, TenPcnt, NinetyPcnt, Medians: DblDyneVec); var - i, HTickSpace, imagewide, imagehi, vtop, vbottom, offset : integer; - vhi, hleft, hright, hwide, barwidth, Xpos, Ypos, strhi, strwide : integer; -// coords : array [1..5] of TPoint; - X, Y, colcycle : integer; - X1, X2, X3, X9, X10 : integer; // X coordinates for box and lines - Y1, Y2, Y3, Y4, Y9 : integer; // Y coordinates for box and lines - Title : string; - valincr, Yvalue : double; - + i: Integer; + ser: TBoxAndWhiskerSeries; + clr: TColor; + nBars: Integer; begin - Printer.Orientation := poLandscape; - Printer.BeginDoc; - Title := 'BOXPLOT FOR : ' + OS3MainFrm.FileNameEdit.Text; - imagewide := Printer.PageWidth; - imagehi := Printer.PageHeight; - vtop := 400; - vbottom := round(imagehi) - 400; -// vhi := vbottom - vtop; - hleft := 400; - hright := imagewide - 40; - hwide := hright - hleft; + nBars := Length(LowQrtl); + if (nBars <> Length(HiQrtl)) or (nBars <> Length(TenPcnt)) or + (nBars <> Length(NinetyPcnt)) or (nBars <> Length(Medians)) then + begin + ErrorMsg('Box-Plot: all data arrays must have the same lengths.'); + exit; + end; - // show title - Printer.Canvas.Brush.Color := clWhite; - strhi := Printer.Canvas.TextWidth(Title) div 2; - strhi := imagewide div 2 - strhi; - Printer.Canvas.TextOut(strhi,50,Title); + if ChartForm = nil then + ChartForm := TChartForm.Create(Application) + else + ChartForm.Clear; - // show legend - Y := Printer.Canvas.TextHeight(Title) * 2; - Y := Y + 50; - Title := 'RED: mean, BLACK: median, BOX: 25th to 75th percentile, WISKERS: 10th and 90th percentile'; - X := imagewide div 2 - Printer.Canvas.TextWidth(Title) div 2; - Printer.Canvas.TextOut(X,Y,Title); + // Titles + ChartForm.SetTitle('Box-and-Whisker Plot for ' + OS3MainFrm.FileNameEdit.Text); + ChartForm.SetFooter('BLACK: median, BOX: 25th to 75th percentile, WHISKERS: 10th and 90th percentile'); + ChartForm.SetXTitle(GroupEdit.Text); + ChartForm.SetYTitle(MeasEdit.Text); - Printer.Canvas.Pen.Color := clBlack; - Printer.Canvas.Brush.Color := clWhite; + ser := TBoxAndWhiskerSeries.create(ChartForm); + for i := 0 to nBars-1 do + begin + clr := BOX_COLORS[i mod Length(BOX_COLORS)]; + ser.AddXY(i+1, TenPcnt[i], LowQrtl[i], Medians[i], HiQrtl[i], NinetyPcnt[i], '', clr); + end; + ChartForm.Chart.BottomAxis.Marks.Source := ser.ListSource; + ChartForm.Chart.BottomAxis.Marks.Style := smsXValue; + ChartForm.Chart.AddSeries(ser); - // Draw chart border - Printer.Canvas.Rectangle(hleft,vtop,hright,vbottom); - vbottom := vbottom - 400; // decrease bottom - vhi := vbottom - vtop; - - // Draw vertical axis - valincr := (max - min) / 20.0; - for i := 1 to 21 do - begin - Title := format('%8.2f',[max - ((i-1)*valincr)]); - strwide := Printer.Canvas.TextWidth(Title); - strhi := Printer.Canvas.TextHeight(Title); - xpos := 20 + hleft; - Yvalue := max - (valincr * (i-1)); - ypos := round(vhi * ( (max - Yvalue) / (max - min))); - ypos := ypos + vtop - strhi div 2; - Printer.Canvas.TextOut(xpos,ypos,Title); - end; - Printer.Canvas.MoveTo(hleft + strwide + 50,vtop); - Printer.Canvas.LineTo(hleft + strwide + 50,vbottom+20); - hwide := hwide - (strwide + 50); - hleft := hleft + strwide + 50; - HTickSpace := hwide div (nbars + 1); - barwidth := HTickSpace div 2; - - // draw horizontal axis - Printer.Canvas.MoveTo(hleft,vbottom + 20); - Printer.Canvas.LineTo(hright,vbottom + 20); - for i := 1 to nbars do - begin - ypos := vbottom + 10; - xpos := round((hwide / (nbars+1))* i + hleft); - Printer.Canvas.MoveTo(xpos,ypos); - ypos := ypos + 10; - Printer.Canvas.LineTo(xpos,ypos); - Title := format('%d',[i]); - offset := Printer.Canvas.TextWidth(Title) div 2; - strhi := Printer.Canvas.TextHeight(Title); - xpos := xpos - offset; - ypos := ypos + strhi; - Printer.Canvas.Pen.Color := clBlack; - Printer.Canvas.TextOut(xpos,ypos,Title); - xpos := hleft; - Printer.Canvas.TextOut(xpos,ypos,'GROUPS:'); - end; - - for i := 1 to nbars do - begin - colcycle := i mod 4; // select a color for box - if (colcycle = 0) then Printer.Canvas.Brush.Color := clBlue; - if (colcycle = 1) then Printer.Canvas.Brush.Color := clGreen; - if (colcycle = 2) then Printer.Canvas.Brush.Color := clFuchsia; - if (colcycle = 3) then Printer.Canvas.Brush.Color := clLime; - - // plot the box front face - X9 := round(hleft + ((i) * HTickSpace) - (barwidth / 2)); - X10 := X9 + barwidth; - X1 := X9; - X2 := X10; - Ypos:= round((((max - hiqrtl[i-1]) / (max - min)) * vhi) + vtop); - Y1 := Ypos; - Ypos := round((((max - lowqrtl[i-1]) / (max - min)) * vhi) + vtop); - Y2 := Ypos; - Printer.Canvas.Rectangle(X1,Y1,X2,Y2); - - // draw upper 90th percentile line and end - X3 := round(X1 + barwidth / 2); - Printer.Canvas.MoveTo(X3,Y1); - Ypos := round((((max - ninetypcnt[i-1]) / (max - min)) * vhi) + vtop); - Y3 := Ypos; - Printer.Canvas.LineTo(X3,Y3); - Printer.Canvas.MoveTo(X1,Y3); - Printer.Canvas.LineTo(X2,Y3); - - // draw lower 10th percentile line and end - Printer.Canvas.MoveTo(X3,Y2); - Ypos := round((((max - tenpcnt[i-1]) / (max - min)) * vhi) + vtop); - Y4 := Ypos; - Printer.Canvas.LineTo(X3,Y4); - Printer.Canvas.MoveTo(X1,Y4); - Printer.Canvas.LineTo(X2,Y4); - - //plot the mean line - Printer.Canvas.Pen.Width := 10; - Printer.Canvas.Pen.Color := clRed; - Printer.Canvas.Pen.Style := psDot; - Ypos := round((((max - means[i-1]) / (max - min)) * vhi) + vtop); - Y9 := Ypos; - Printer.Canvas.MoveTo(X9,Y9); - Printer.Canvas.LineTo(X10,Y9); - Printer.Canvas.Pen.Color := clBlack; - Printer.Canvas.Pen.Style := psSolid; - - //plot the median line - Printer.Canvas.Pen.Color := clBlack; - Ypos := round((((max - median[i-1]) / (max - min)) * vhi) + vtop); - Y9 := Ypos; - Printer.Canvas.MoveTo(X9,Y9); - Printer.Canvas.LineTo(X10,Y9); - Printer.Canvas.Pen.Color := clBlack; - - end; - Printer.EndDoc; - Printer.Orientation := poPortrait; + ChartForm.Show; end; -} - -//-------------------------------------------------------------------------- - -procedure TBoxPlotFrm.BoxPlot(nbars: integer; - max, min: double; - var lowqrtl: DblDyneVec; - var hiqrtl: DblDyneVec; - var tenpcnt: DblDyneVec; - var ninetypcnt: DblDyneVec; - var means: DblDyneVec; - var median: DblDyneVec); -const - BOX_COLORS: Array[0..3] of TColor = (clBlue, clGreen, clFuchsia, clLime); +{$ELSE} +procedure TBoxPlotFrm.BoxPlot(NBars: integer; AMax, AMin: double; + const LowQrtl, HiQrtl, TenPcnt, NinetyPcnt, Means, Median: DblDyneVec); var i, HTickSpace, imagewide, imagehi, vtop, vbottom, offset: integer; vhi, hleft, hright, hwide, barwidth, Xpos, Ypos, strhi: integer; @@ -587,8 +464,8 @@ var Title: string; valincr, Yvalue: double; begin + if BlankFrm = nil then Application.CreateForm(TBlankFrm, BlankFrm); BlankFrm.Show; - //BlankFrm.Image1.Canvas.Clear; imagewide := BlankFrm.Image1.width; imagehi := BlankFrm.Image1.Height; @@ -604,7 +481,6 @@ begin HTickSpace := hwide div nbars; barwidth := HTickSpace div 2; - // Show title Title := 'BOXPLOT FOR : ' + OS3MainFrm.FileNameEdit.Text; BlankFrm.Caption := Title; @@ -629,14 +505,14 @@ begin BlankFrm.Image1.Canvas.TextOut(X,Y,Title); // Draw vertical axis - valincr := (max - min) / 20.0; + valincr := (AMax - AMin) / 20.0; for i := 1 to 21 do begin - Title := format('%8.2f',[max - ((i-1)*valincr)]); + Title := format('%8.2f',[AMax - ((i-1)*valincr)]); strhi := BlankFrm.Image1.Canvas.TextHeight(Title); xpos := XOffset; - Yvalue := max - (valincr * (i-1)); - ypos := round(vhi * ( (max - Yvalue) / (max - min))); + Yvalue := AMax - (valincr * (i-1)); + ypos := round(vhi * ( (AMax - Yvalue) / (AMax - AMin))); ypos := ypos + vtop - strhi div 2; BlankFrm.Image1.Canvas.TextOut(xpos,ypos,Title); end; @@ -664,30 +540,30 @@ begin BlankFrm.Image1.Canvas.TextOut(xpos,ypos,'GROUPS:'); end; - for i := 1 to nbars do + for i := 0 to NBars - 1 do begin - BlankFrm.Image1.Canvas.Brush.Color := BOX_COLORS[i mod 4]; + BlankFrm.Image1.Canvas.Brush.Color := BOX_COLORS[i mod Length(BOX_COLORS)]; // plot the box front face - X9 := round(hleft + ((i) * HTickSpace) - (barwidth / 2)); + X9 := round(hleft + ((i+1) * HTickSpace) - (barwidth / 2)); X10 := X9 + barwidth; X1 := X9; X2 := X10; - Y1 := round((((max - hiqrtl[i-1]) / (max - min)) * vhi) + vtop); - Y2 := round((((max - lowqrtl[i-1]) / (max - min)) * vhi) + vtop); + Y1 := round((((AMax - HiQrtl[i]) / (AMax - AMin)) * vhi) + vtop); + Y2 := round((((AMax - LowQrtl[i]) / (AMax - AMin)) * vhi) + vtop); BlankFrm.Image1.Canvas.Rectangle(X1,Y1,X2,Y2); // draw upper 90th percentile line and end X3 := round(X1 + barwidth / 2); BlankFrm.Image1.Canvas.MoveTo(X3,Y1); - Y3 := round((((max - ninetypcnt[i-1]) / (max - min)) * vhi) + vtop); + Y3 := round((((AMax - NinetyPcnt[i]) / (AMax - AMin)) * vhi) + vtop); BlankFrm.Image1.Canvas.LineTo(X3,Y3); BlankFrm.Image1.Canvas.MoveTo(X1,Y3); BlankFrm.Image1.Canvas.LineTo(X2,Y3); // draw lower 10th percentile line and end BlankFrm.Image1.Canvas.MoveTo(X3,Y2); - Y4 := round((((max - tenpcnt[i-1]) / (max - min)) * vhi) + vtop); + Y4 := round((((AMax - TenPcnt[i]) / (AMax - AMin)) * vhi) + vtop); BlankFrm.Image1.Canvas.LineTo(X3,Y4); BlankFrm.Image1.Canvas.MoveTo(X1,Y4); BlankFrm.Image1.Canvas.LineTo(X2,Y4); @@ -695,7 +571,7 @@ begin //plot the means line BlankFrm.Image1.Canvas.Pen.Color := clRed; BlankFrm.Image1.Canvas.Pen.Style := psDot; - Y9 := round((((max - means[i-1]) / (max - min)) * vhi) + vtop); + Y9 := round((((AMax - Means[i]) / (AMax - AMin)) * vhi) + vtop); BlankFrm.Image1.Canvas.MoveTo(X9,Y9); BlankFrm.Image1.Canvas.LineTo(X10,Y9); BlankFrm.Image1.Canvas.Pen.Color := clBlack; @@ -703,16 +579,14 @@ begin //plot the median line BlankFrm.Image1.Canvas.Pen.Color := clBlack; - Y9 := round((((max - median[i-1]) / (max - min)) * vhi) + vtop); + Y9 := round((((AMax - Median[i]) / (AMax - AMin)) * vhi) + vtop); BlankFrm.Image1.Canvas.MoveTo(X9,Y9); BlankFrm.Image1.Canvas.LineTo(X10,Y9); BlankFrm.Image1.Canvas.Pen.Color := clBlack; end; end; +{$ENDIF} -initialization - {$I boxplotunit.lrs} - end. diff --git a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas index f24cb0a8b..0c9c4e721 100644 --- a/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas +++ b/applications/lazstats/source/forms/analysis/descriptive/plotxyunit.pas @@ -363,7 +363,7 @@ begin R, Slope, Intercept ])); ChartForm.SetXTitle(XEdit.Text); - chartForm.SetYTitle(YEdit.Text); + ChartForm.SetYTitle(YEdit.Text); // Draw upper confidence band if ConfChk.Checked then