1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 */ 10 11 #include <memory> 12 #include <sfx2/dispatch.hxx> 13 #include <svl/zforlist.hxx> 14 #include <svl/undo.hxx> 15 16 #include <formulacell.hxx> 17 #include <rangelst.hxx> 18 #include <scitems.hxx> 19 #include <docsh.hxx> 20 #include <document.hxx> 21 #include <uiitems.hxx> 22 #include <reffact.hxx> 23 #include <docfunc.hxx> 24 #include <TableFillingAndNavigationTools.hxx> 25 #include <AnalysisOfVarianceDialog.hxx> 26 #include <scresid.hxx> 27 #include <strings.hrc> 28 29 namespace 30 { 31 32 struct StatisticCalculation { 33 const char* aLabelId; 34 const char* aFormula; 35 const char* aResultRangeName; 36 }; 37 38 static StatisticCalculation const lclBasicStatistics[] = 39 { 40 { STR_ANOVA_LABEL_GROUPS, nullptr, nullptr }, 41 { STRID_CALC_COUNT, "=COUNT(%RANGE%)", "COUNT_RANGE" }, 42 { STRID_CALC_SUM, "=SUM(%RANGE%)", "SUM_RANGE" }, 43 { STRID_CALC_MEAN, "=AVERAGE(%RANGE%)", "MEAN_RANGE" }, 44 { STRID_CALC_VARIANCE, "=VAR(%RANGE%)", "VAR_RANGE" }, 45 { nullptr, nullptr, nullptr } 46 }; 47 48 static const char* lclAnovaLabels[] = 49 { 50 STR_ANOVA_LABEL_SOURCE_OF_VARIATION, 51 STR_ANOVA_LABEL_SS, 52 STR_ANOVA_LABEL_DF, 53 STR_ANOVA_LABEL_MS, 54 STR_ANOVA_LABEL_F, 55 STR_ANOVA_LABEL_P_VALUE, 56 STR_ANOVA_LABEL_F_CRITICAL, 57 nullptr 58 }; 59 60 static const char strWildcardRange[] = "%RANGE%"; 61 62 OUString lclCreateMultiParameterFormula( 63 ScRangeList& aRangeList, const OUString& aFormulaTemplate, 64 const OUString& aWildcard, const ScDocument* pDocument, 65 const ScAddress::Details& aAddressDetails) 66 { 67 OUStringBuffer aResult; 68 for (size_t i = 0; i < aRangeList.size(); i++) 69 { 70 OUString aRangeString(aRangeList[i].Format(ScRefFlags::RANGE_ABS, pDocument, aAddressDetails)); 71 OUString aFormulaString = aFormulaTemplate.replaceAll(aWildcard, aRangeString); 72 aResult.append(aFormulaString); 73 if(i != aRangeList.size() - 1) // Not Last 74 aResult.append(";"); 75 } 76 return aResult.makeStringAndClear(); 77 } 78 79 void lclMakeSubRangesList(ScRangeList& rRangeList, const ScRange& rInputRange, ScStatisticsInputOutputDialog::GroupedBy aGroupedBy) 80 { 81 std::unique_ptr<DataRangeIterator> pIterator; 82 if (aGroupedBy == ScStatisticsInputOutputDialog::BY_COLUMN) 83 pIterator.reset(new DataRangeByColumnIterator(rInputRange)); 84 else 85 pIterator.reset(new DataRangeByRowIterator(rInputRange)); 86 87 for( ; pIterator->hasNext(); pIterator->next() ) 88 { 89 ScRange aRange = pIterator->get(); 90 rRangeList.push_back(aRange); 91 } 92 } 93 94 } 95 96 ScAnalysisOfVarianceDialog::ScAnalysisOfVarianceDialog( 97 SfxBindings* pSfxBindings, SfxChildWindow* pChildWindow, 98 vcl::Window* pParent, ScViewData* pViewData ) : 99 ScStatisticsInputOutputDialog( 100 pSfxBindings, pChildWindow, pParent, pViewData, 101 "AnalysisOfVarianceDialog", "modules/scalc/ui/analysisofvariancedialog.ui" ), 102 meFactor(SINGLE_FACTOR) 103 { 104 get(mpAlphaField, "alpha-spin"); 105 get(mpSingleFactorRadio, "radio-single-factor"); 106 get(mpTwoFactorRadio, "radio-two-factor"); 107 get(mpRowsPerSampleField, "rows-per-sample-spin"); 108 109 mpSingleFactorRadio->SetToggleHdl( LINK( this, ScAnalysisOfVarianceDialog, FactorChanged ) ); 110 mpTwoFactorRadio->SetToggleHdl( LINK( this, ScAnalysisOfVarianceDialog, FactorChanged ) ); 111 112 mpSingleFactorRadio->Check(); 113 mpTwoFactorRadio->Check(false); 114 115 FactorChanged(); 116 } 117 118 ScAnalysisOfVarianceDialog::~ScAnalysisOfVarianceDialog() 119 { 120 disposeOnce(); 121 } 122 123 void ScAnalysisOfVarianceDialog::dispose() 124 { 125 mpAlphaField.clear(); 126 mpSingleFactorRadio.clear(); 127 mpTwoFactorRadio.clear(); 128 mpRowsPerSampleField.clear(); 129 ScStatisticsInputOutputDialog::dispose(); 130 } 131 132 bool ScAnalysisOfVarianceDialog::Close() 133 { 134 return DoClose( ScAnalysisOfVarianceDialogWrapper::GetChildWindowId() ); 135 } 136 137 const char* ScAnalysisOfVarianceDialog::GetUndoNameId() 138 { 139 return STR_ANALYSIS_OF_VARIANCE_UNDO_NAME; 140 } 141 142 IMPL_LINK_NOARG( ScAnalysisOfVarianceDialog, FactorChanged, RadioButton&, void ) 143 { 144 FactorChanged(); 145 } 146 147 void ScAnalysisOfVarianceDialog::FactorChanged() 148 { 149 if (mpSingleFactorRadio->IsChecked()) 150 { 151 mpGroupByRowsRadio->Enable(); 152 mpGroupByColumnsRadio->Enable(); 153 mpRowsPerSampleField->Enable(false); 154 meFactor = SINGLE_FACTOR; 155 } 156 else if (mpTwoFactorRadio->IsChecked()) 157 { 158 mpGroupByRowsRadio->Enable(false); 159 mpGroupByColumnsRadio->Enable(false); 160 mpRowsPerSampleField->Enable(false); // Rows per sample not yet implemented 161 meFactor = TWO_FACTOR; 162 } 163 } 164 165 void ScAnalysisOfVarianceDialog::RowColumn(ScRangeList& rRangeList, AddressWalkerWriter& aOutput, FormulaTemplate& aTemplate, 166 const OUString& sFormula, GroupedBy aGroupedBy, ScRange* pResultRange) 167 { 168 if (pResultRange != nullptr) 169 pResultRange->aStart = aOutput.current(); 170 if (!sFormula.isEmpty()) 171 { 172 for (size_t i = 0; i < rRangeList.size(); i++) 173 { 174 ScRange const & rRange = rRangeList[i]; 175 aTemplate.setTemplate(sFormula); 176 aTemplate.applyRange(strWildcardRange, rRange); 177 aOutput.writeFormula(aTemplate.getTemplate()); 178 if (pResultRange != nullptr) 179 pResultRange->aEnd = aOutput.current(); 180 aOutput.nextRow(); 181 } 182 } 183 else 184 { 185 const char* pLabelId = (aGroupedBy == BY_COLUMN) ? STR_COLUMN_LABEL_TEMPLATE : STR_ROW_LABEL_TEMPLATE; 186 OUString aLabelTemplate(ScResId(pLabelId)); 187 188 for (size_t i = 0; i < rRangeList.size(); i++) 189 { 190 aTemplate.setTemplate(aLabelTemplate); 191 aTemplate.applyNumber("%NUMBER%", i + 1); 192 aOutput.writeString(aTemplate.getTemplate()); 193 if (pResultRange != nullptr) 194 pResultRange->aEnd = aOutput.current(); 195 aOutput.nextRow(); 196 } 197 } 198 } 199 200 void ScAnalysisOfVarianceDialog::AnovaSingleFactor(AddressWalkerWriter& output, FormulaTemplate& aTemplate) 201 { 202 output.writeBoldString(ScResId(STR_ANOVA_SINGLE_FACTOR_LABEL)); 203 output.newLine(); 204 205 double aAlphaValue = mpAlphaField->GetValue() / 100.0; 206 output.writeString(ScResId(STR_LABEL_ALPHA)); 207 output.nextColumn(); 208 output.writeValue(aAlphaValue); 209 aTemplate.autoReplaceAddress("%ALPHA%", output.current()); 210 output.newLine(); 211 output.newLine(); 212 213 // Write labels 214 for(sal_Int32 i = 0; lclBasicStatistics[i].aLabelId; i++) 215 { 216 output.writeString(ScResId(lclBasicStatistics[i].aLabelId)); 217 output.nextColumn(); 218 } 219 output.newLine(); 220 221 // Collect aRangeList 222 ScRangeList aRangeList; 223 lclMakeSubRangesList(aRangeList, mInputRange, mGroupedBy); 224 225 output.push(); 226 227 // Write values 228 for(sal_Int32 i = 0; lclBasicStatistics[i].aLabelId; i++) 229 { 230 output.resetRow(); 231 ScRange aResultRange; 232 OUString sFormula = OUString::createFromAscii(lclBasicStatistics[i].aFormula); 233 RowColumn(aRangeList, output, aTemplate, sFormula, mGroupedBy, &aResultRange); 234 output.nextColumn(); 235 if (lclBasicStatistics[i].aResultRangeName != nullptr) 236 { 237 OUString sResultRangeName = OUString::createFromAscii(lclBasicStatistics[i].aResultRangeName); 238 aTemplate.autoReplaceRange("%" + sResultRangeName + "%", aResultRange); 239 } 240 } 241 242 output.nextRow(); // Blank row 243 244 // Write ANOVA labels 245 output.resetColumn(); 246 for(sal_Int32 i = 0; lclAnovaLabels[i]; i++) 247 { 248 output.writeString(ScResId(lclAnovaLabels[i])); 249 output.nextColumn(); 250 } 251 output.nextRow(); 252 253 aTemplate.autoReplaceRange("%FIRST_COLUMN%", aRangeList[0]); 254 255 // Between Groups 256 { 257 // Label 258 output.resetColumn(); 259 output.writeString(ScResId(STR_ANOVA_LABEL_BETWEEN_GROUPS)); 260 output.nextColumn(); 261 262 // Sum of Squares 263 264 aTemplate.setTemplate("=SUMPRODUCT(%SUM_RANGE%;%MEAN_RANGE%)-SUM(%SUM_RANGE%)^2/SUM(%COUNT_RANGE%)"); 265 aTemplate.autoReplaceAddress("%BETWEEN_SS%", output.current()); 266 output.writeFormula(aTemplate.getTemplate()); 267 output.nextColumn(); 268 269 // Degree of freedom 270 aTemplate.setTemplate("=COUNT(%SUM_RANGE%)-1"); 271 aTemplate.autoReplaceAddress("%BETWEEN_DF%", output.current()); 272 output.writeFormula(aTemplate.getTemplate()); 273 output.nextColumn(); 274 275 // MS 276 aTemplate.setTemplate("=%BETWEEN_SS% / %BETWEEN_DF%"); 277 aTemplate.autoReplaceAddress("%BETWEEN_MS%", output.current()); 278 output.writeFormula(aTemplate.getTemplate()); 279 output.nextColumn(); 280 281 // F 282 aTemplate.setTemplate("=%BETWEEN_MS% / %WITHIN_MS%"); 283 aTemplate.applyAddress("%WITHIN_MS%", output.current(-1, 1)); 284 aTemplate.autoReplaceAddress("%F_VAL%", output.current()); 285 output.writeFormula(aTemplate.getTemplate()); 286 output.nextColumn(); 287 288 // P-value 289 aTemplate.setTemplate("=FDIST(%F_VAL%; %BETWEEN_DF%; %WITHIN_DF%"); 290 aTemplate.applyAddress("%WITHIN_DF%", output.current(-3, 1)); 291 output.writeFormula(aTemplate.getTemplate()); 292 output.nextColumn(); 293 294 // F critical 295 aTemplate.setTemplate("=FINV(%ALPHA%; %BETWEEN_DF%; %WITHIN_DF%"); 296 aTemplate.applyAddress("%WITHIN_DF%", output.current(-4, 1)); 297 output.writeFormula(aTemplate.getTemplate()); 298 } 299 output.nextRow(); 300 301 // Within Groups 302 { 303 // Label 304 output.resetColumn(); 305 output.writeString(ScResId(STR_ANOVA_LABEL_WITHIN_GROUPS)); 306 output.nextColumn(); 307 308 // Sum of Squares 309 OUString aSSPart = lclCreateMultiParameterFormula(aRangeList, "DEVSQ(%RANGE%)", strWildcardRange, mDocument, mAddressDetails); 310 aTemplate.setTemplate("=SUM(%RANGE%)"); 311 aTemplate.applyString(strWildcardRange, aSSPart); 312 aTemplate.autoReplaceAddress("%WITHIN_SS%", output.current()); 313 output.writeFormula(aTemplate.getTemplate()); 314 output.nextColumn(); 315 316 // Degree of freedom 317 aTemplate.setTemplate("=SUM(%COUNT_RANGE%)-COUNT(%COUNT_RANGE%)"); 318 aTemplate.autoReplaceAddress("%WITHIN_DF%", output.current()); 319 output.writeFormula(aTemplate.getTemplate()); 320 output.nextColumn(); 321 322 // MS 323 aTemplate.setTemplate("=%WITHIN_SS% / %WITHIN_DF%"); 324 output.writeFormula(aTemplate.getTemplate()); 325 } 326 output.nextRow(); 327 328 // Total 329 { 330 // Label 331 output.resetColumn(); 332 output.writeString(ScResId(STR_ANOVA_LABEL_TOTAL)); 333 output.nextColumn(); 334 335 // Sum of Squares 336 aTemplate.setTemplate("=DEVSQ(%RANGE_LIST%)"); 337 aTemplate.applyRangeList("%RANGE_LIST%", aRangeList, ';'); 338 output.writeFormula(aTemplate.getTemplate()); 339 output.nextColumn(); 340 341 // Degree of freedom 342 aTemplate.setTemplate("=SUM(%COUNT_RANGE%) - 1"); 343 output.writeFormula(aTemplate.getTemplate()); 344 } 345 output.nextRow(); 346 } 347 348 void ScAnalysisOfVarianceDialog::AnovaTwoFactor(AddressWalkerWriter& output, FormulaTemplate& aTemplate) 349 { 350 output.writeBoldString(ScResId(STR_ANOVA_TWO_FACTOR_LABEL)); 351 output.newLine(); 352 353 double aAlphaValue = mpAlphaField->GetValue() / 100.0; 354 output.writeString("Alpha"); 355 output.nextColumn(); 356 output.writeValue(aAlphaValue); 357 aTemplate.autoReplaceAddress("%ALPHA%", output.current()); 358 output.newLine(); 359 output.newLine(); 360 361 // Write labels 362 for(sal_Int32 i = 0; lclBasicStatistics[i].aLabelId; i++) 363 { 364 output.writeString(ScResId(lclBasicStatistics[i].aLabelId)); 365 output.nextColumn(); 366 } 367 output.newLine(); 368 369 ScRangeList aColumnRangeList; 370 ScRangeList aRowRangeList; 371 372 lclMakeSubRangesList(aColumnRangeList, mInputRange, BY_COLUMN); 373 lclMakeSubRangesList(aRowRangeList, mInputRange, BY_ROW); 374 375 // Write ColumnX values 376 output.push(); 377 for(sal_Int32 i = 0; lclBasicStatistics[i].aLabelId; i++) 378 { 379 output.resetRow(); 380 ScRange aResultRange; 381 OUString sFormula = OUString::createFromAscii(lclBasicStatistics[i].aFormula); 382 RowColumn(aColumnRangeList, output, aTemplate, sFormula, BY_COLUMN, &aResultRange); 383 if (lclBasicStatistics[i].aResultRangeName != nullptr) 384 { 385 OUString sResultRangeName = OUString::createFromAscii(lclBasicStatistics[i].aResultRangeName); 386 aTemplate.autoReplaceRange("%" + sResultRangeName + "_COLUMN%", aResultRange); 387 } 388 output.nextColumn(); 389 } 390 output.newLine(); 391 392 // Write RowX values 393 output.push(); 394 for(sal_Int32 i = 0; lclBasicStatistics[i].aLabelId; i++) 395 { 396 output.resetRow(); 397 ScRange aResultRange; 398 OUString sFormula = OUString::createFromAscii(lclBasicStatistics[i].aFormula); 399 RowColumn(aRowRangeList, output, aTemplate, sFormula, BY_ROW, &aResultRange); 400 401 if (lclBasicStatistics[i].aResultRangeName != nullptr) 402 { 403 OUString sResultRangeName = OUString::createFromAscii(lclBasicStatistics[i].aResultRangeName); 404 aTemplate.autoReplaceRange("%" + sResultRangeName + "_ROW%", aResultRange); 405 } 406 output.nextColumn(); 407 } 408 output.newLine(); 409 410 // Write ANOVA labels 411 for(sal_Int32 i = 0; lclAnovaLabels[i]; i++) 412 { 413 output.writeString(ScResId(lclAnovaLabels[i])); 414 output.nextColumn(); 415 } 416 output.nextRow(); 417 418 // Setup auto-replace strings 419 aTemplate.autoReplaceRange(strWildcardRange, mInputRange); 420 aTemplate.autoReplaceRange("%FIRST_COLUMN%", aColumnRangeList[0]); 421 aTemplate.autoReplaceRange("%FIRST_ROW%", aRowRangeList[0]); 422 423 // Rows 424 { 425 // Label 426 output.resetColumn(); 427 output.writeString("Rows"); 428 output.nextColumn(); 429 430 // Sum of Squares 431 aTemplate.setTemplate("=SUMPRODUCT(%SUM_RANGE_ROW%;%MEAN_RANGE_ROW%) - SUM(%RANGE%)^2 / COUNT(%RANGE%)"); 432 aTemplate.autoReplaceAddress("%ROW_SS%", output.current()); 433 output.writeFormula(aTemplate.getTemplate()); 434 output.nextColumn(); 435 436 // Degree of freedom 437 aTemplate.setTemplate("=MAX(%COUNT_RANGE_COLUMN%) - 1"); 438 aTemplate.autoReplaceAddress("%ROW_DF%", output.current()); 439 output.writeFormula(aTemplate.getTemplate()); 440 output.nextColumn(); 441 442 // MS 443 aTemplate.setTemplate("=%ROW_SS% / %ROW_DF%"); 444 aTemplate.autoReplaceAddress("%MS_ROW%", output.current()); 445 output.writeFormula(aTemplate.getTemplate()); 446 output.nextColumn(); 447 448 // F 449 aTemplate.setTemplate("=%MS_ROW% / %MS_ERROR%"); 450 aTemplate.applyAddress("%MS_ERROR%", output.current(-1, 2)); 451 aTemplate.autoReplaceAddress("%F_ROW%", output.current()); 452 output.writeFormula(aTemplate.getTemplate()); 453 output.nextColumn(); 454 455 // P-value 456 aTemplate.setTemplate("=FDIST(%F_ROW%; %ROW_DF%; %ERROR_DF%"); 457 aTemplate.applyAddress("%ERROR_DF%", output.current(-3, 2)); 458 output.writeFormula(aTemplate.getTemplate()); 459 output.nextColumn(); 460 461 // F critical 462 aTemplate.setTemplate("=FINV(%ALPHA%; %ROW_DF%; %ERROR_DF%"); 463 aTemplate.applyAddress("%ERROR_DF%", output.current(-4, 2)); 464 output.writeFormula(aTemplate.getTemplate()); 465 output.nextColumn(); 466 } 467 output.nextRow(); 468 469 // Columns 470 { 471 // Label 472 output.resetColumn(); 473 output.writeString("Columns"); 474 output.nextColumn(); 475 476 // Sum of Squares 477 aTemplate.setTemplate("=SUMPRODUCT(%SUM_RANGE_COLUMN%;%MEAN_RANGE_COLUMN%) - SUM(%RANGE%)^2 / COUNT(%RANGE%)"); 478 aTemplate.autoReplaceAddress("%COLUMN_SS%", output.current()); 479 output.writeFormula(aTemplate.getTemplate()); 480 output.nextColumn(); 481 482 // Degree of freedom 483 aTemplate.setTemplate("=MAX(%COUNT_RANGE_ROW%) - 1"); 484 aTemplate.autoReplaceAddress("%COLUMN_DF%", output.current()); 485 output.writeFormula(aTemplate.getTemplate()); 486 output.nextColumn(); 487 488 // MS 489 aTemplate.setTemplate("=%COLUMN_SS% / %COLUMN_DF%"); 490 aTemplate.autoReplaceAddress("%MS_COLUMN%", output.current()); 491 output.writeFormula(aTemplate.getTemplate()); 492 output.nextColumn(); 493 494 // F 495 aTemplate.setTemplate("=%MS_COLUMN% / %MS_ERROR%"); 496 aTemplate.applyAddress("%MS_ERROR%", output.current(-1, 1)); 497 aTemplate.autoReplaceAddress("%F_COLUMN%", output.current()); 498 output.writeFormula(aTemplate.getTemplate()); 499 output.nextColumn(); 500 501 // P-value 502 aTemplate.setTemplate("=FDIST(%F_COLUMN%; %COLUMN_DF%; %ERROR_DF%"); 503 aTemplate.applyAddress("%ERROR_DF%", output.current(-3, 1)); 504 output.writeFormula(aTemplate.getTemplate()); 505 output.nextColumn(); 506 507 // F critical 508 aTemplate.setTemplate("=FINV(%ALPHA%; %COLUMN_DF%; %ERROR_DF%"); 509 aTemplate.applyAddress("%ERROR_DF%", output.current(-4, 1)); 510 output.writeFormula(aTemplate.getTemplate()); 511 output.nextColumn(); 512 } 513 output.nextRow(); 514 515 // Error 516 { 517 // Label 518 output.resetColumn(); 519 output.writeString("Error"); 520 output.nextColumn(); 521 522 // Sum of Squares 523 aTemplate.setTemplate("=SUMSQ(%RANGE%)+SUM(%RANGE%)^2/COUNT(%RANGE%) - (SUMPRODUCT(%SUM_RANGE_ROW%;%MEAN_RANGE_ROW%) + SUMPRODUCT(%SUM_RANGE_COLUMN%;%MEAN_RANGE_COLUMN%))"); 524 aTemplate.autoReplaceAddress("%ERROR_SS%", output.current()); 525 output.writeFormula(aTemplate.getTemplate()); 526 output.nextColumn(); 527 528 // Degree of freedom 529 aTemplate.setTemplate("=%TOTAL_DF% - %ROW_DF% - %COLUMN_DF%"); 530 aTemplate.applyAddress("%TOTAL_DF%", output.current(0,1)); 531 aTemplate.autoReplaceAddress("%ERROR_DF%", output.current()); 532 output.writeFormula(aTemplate.getTemplate()); 533 output.nextColumn(); 534 535 // MS 536 aTemplate.setTemplate("=%ERROR_SS% / %ERROR_DF%"); 537 output.writeFormula(aTemplate.getTemplate()); 538 } 539 output.nextRow(); 540 541 // Total 542 { 543 // Label 544 output.resetColumn(); 545 output.writeString("Total"); 546 output.nextColumn(); 547 548 // Sum of Squares 549 aTemplate.setTemplate("=SUM(%ROW_SS%;%COLUMN_SS%;%ERROR_SS%)"); 550 output.writeFormula(aTemplate.getTemplate()); 551 output.nextColumn(); 552 553 // Degree of freedom 554 aTemplate.setTemplate("=COUNT(%RANGE%)-1"); 555 output.writeFormula(aTemplate.getTemplate()); 556 output.nextColumn(); 557 } 558 } 559 560 ScRange ScAnalysisOfVarianceDialog::ApplyOutput(ScDocShell* pDocShell) 561 { 562 AddressWalkerWriter output(mOutputAddress, pDocShell, mDocument, 563 formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_ENGLISH, mAddressDetails.eConv)); 564 FormulaTemplate aTemplate(mDocument); 565 566 if (meFactor == SINGLE_FACTOR) 567 { 568 AnovaSingleFactor(output, aTemplate); 569 } 570 else if (meFactor == TWO_FACTOR) 571 { 572 AnovaTwoFactor(output, aTemplate); 573 } 574 575 return ScRange(output.mMinimumAddress, output.mMaximumAddress); 576 } 577 578 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 579
