Iterative Calculation

Iterative calculations can help with finding solutions to certain calculations by running them over and over using the previous result. You can also perform what-if analysis with the CalcEngine.goalSeek function, which uses iterative calculations in SpreadJS.

Description
app.vue
index.html
Copy to CodeMine

Users can enable/disable iterative calculation in the workbook options.

When iterative calculation is disabled, all the circular referenced cells will have 0 for the value, and the other cells referencing them will be 0. When iterative calculation is enabled, all the circular references will calculate iteratively until all the value changes are smaller than the iterativeCalculationMaximumChange or the iteratation count is iterativeCalculationMaximumIterations.

API is as follows:

The isCircularReference property is added for the Events.UserFormulaEntered, it will be true if user entered a circular reference. For example:

activeSheet.bind(GC.Spread.Sheets.Events.UserFormulaEntered, function (e, info) {
    if (info.isCircularReference && !spread.options.iterativeCalculation) { // alert if input formula is circular reference and the iterativeCalculation is disable.
        alert("There are circular references refers to its own cell either directly or indirectly. This might cause them to calculate incorrectly");
    }
});

You can get all the circular references in the workbook by function getCircularReference. For example:

sheet1.setFormula(0,0,"=Sheet2!A1");
sheet2.setFormula(0,0,"=Sheet1!A1");
sheet1.setFormula(1,0,"=(A2+1)/2");
sheet1.setFormula(0,1,"=SUM(B:B)");
var cellInfos = spread.getCircularReference();
//  cellInfos: [{sheetName:"Sheet1", row: 0, col: 0, rowCount:1, colCount:1},
//              {sheetName:"Sheet1", row: 1, col: 0, rowCount:1, colCount:1},
//              {sheetName:"Sheet1", row: 0, col: 1, rowCount:1, colCount:1},
//              {sheetName:"Sheet2", row: 0, col: 0, rowCount:1, colCount:1},]
Users can enable/disable iterative calculation in the workbook options. When iterative calculation is disabled, all the circular referenced cells will have 0 for the value, and the other cells referencing them will be 0. When iterative calculation is enabled, all the circular references will calculate iteratively until all the value changes are smaller than the iterativeCalculationMaximumChange or the iteratation count is iterativeCalculationMaximumIterations. API is as follows: iterativeCalculation: Enable or Disable the Iterative Calculation iterativeCalculationMaximumIterations: The Maximum Iterations when Iterative Calculation, default value is 1000, value range is 1~32767 iterativeCalculationMaximumChange: The Maximum Change when Iterative Calculation, default 0.01, value range is 0~MaxDouble(1.79769313486232e308) The isCircularReference property is added for the Events.UserFormulaEntered, it will be true if user entered a circular reference. For example: You can get all the circular references in the workbook by function getCircularReference. For example:
<template> <div class="sample-tutorial"> <gc-spread-sheets class="sample-spreadsheets" @workbookInitialized="initSpread"> <gc-worksheet> </gc-worksheet> </gc-spread-sheets> <div class="options-container"> <label>Change the <b>Maximum Iterations</b> and <b>Maximum Change</b> options below then press <b>Recalculate</b> to see how this affects the calculations in cell F7.</label> <div class="option-row"> <input style="width: 20px;float: left;" type="checkbox" id="IterativeCalculation" checked="checked" @change="setIterativeCalculation($event)"/> <label for="IterativeCalculation">Iterative Calculation</label> </div> <div class="option-row"> <label for="MaximumIterations">Maximum Iterations:</label> <input type="number" id="MaximumIterations" value="1000" @change="setMaximumIterations($event)" > </div> <div class="option-row"> <label for="MaximumChange">Maximum Change:</label> <input type="number" id="MaximumChange" value="0.01" @change="setMaximumChange($event)" > </div> <div class="option-row"> <button id="RecalcAll" @click="recalcAll($event)" >Recalculate</button> </div> </div> </div> </template> <script setup> import GC from "@mescius/spread-sheets"; import { ref } from 'vue'; const spreadRef = ref(null); function initSpread (spread) { spreadRef.value = spread; var sheet = spread.getActiveSheet(); spread.suspendPaint(); spread.suspendCalcService(); sheet.setColumnWidth(0, 120); sheet.setColumnWidth(1, 100); sheet.setColumnWidth(2, 120); sheet.setColumnWidth(4, 120); sheet.setColumnWidth(5, 130); sheet.getCell(1, 1).foreColor("blue"); sheet.getCell(5, 1).foreColor("blue"); sheet.getCell(9, 1).foreColor("blue").formatter("0.0%"); sheet.getRange(1, 1, 7, 1).formatter("0.0"); sheet.setFormula(6, 5, '=F7+1'); sheet.setValue(0, 0, "Details"); sheet.setValue(1, 0, "Cash Revenue"); sheet.setValue(2, 0, "Interest Expense"); sheet.setValue(3, 0, "Cash Profit"); sheet.setValue(5, 0, "Beginning Debt"); sheet.setValue(6, 0, "Ending Debt"); sheet.setValue(7, 0, "Average Debt"); sheet.setValue(9, 0, "Interest"); sheet.setValue(0, 1, "Amount"); sheet.setValue(1, 1, 100); sheet.setFormula(2, 1, '=B10*B8'); sheet.setFormula(3, 1, '=B2-B3'); sheet.setValue(5, 1, 150); sheet.setFormula(6, 1, '=B6-B4'); sheet.setFormula(7, 1, '=AVERAGE(B6:B7)'); sheet.setValue(9, 1, 0.05); sheet.setValue(0, 2, "Formula") sheet.setFormula(2, 2, '=FORMULATEXT(B3)'); sheet.setFormula(3, 2, '=FORMULATEXT(B4)'); sheet.setFormula(6, 2, '=FORMULATEXT(B7)'); sheet.setFormula(7, 2, '=FORMULATEXT(B8)'); sheet.getRange(0, 0, 1, 3).backColor("#f2f2f2").foreColor("black"); sheet.getRange(6, 5, 1, 1).backColor("#009e00").foreColor("white"); sheet.getRange(1, 4, 1, 2).backColor("#f2f2f2").foreColor("black"); sheet.getRange(1, 0, 9, 3).setBorder(new GC.Spread.Sheets.LineBorder("#f2f2f2", GC.Spread.Sheets.LineStyle.thin), {all: true}); sheet.getRange(2, 4, 3, 2).setBorder(new GC.Spread.Sheets.LineBorder("#f2f2f2", GC.Spread.Sheets.LineStyle.thin), {all: true}); sheet.setValue(1, 4, "Use the Leibniz formula to approximate π") sheet.setValue(2, 4, "n: 1→∞") sheet.setValue(3, 4, {"richText":[{"text":"Pn: 4*(-1)"},{"style":{"vertAlign":1},"text":"n+1"},{"text":"/(2n-1)"}],"text":"Pn=4*(-1)n+1/(2n-1)"}); sheet.setValue(4, 4, "π: P1+P2+P3+...+Pn") sheet.setFormula(4, 5, '=IFERROR(F4,0)+F5'); sheet.setFormula(3, 5, '=IF(F3<1,0,4/(2*F3-1)*POWER(-1,F3+1))'); sheet.setFormula(2, 5, '=F3+1'); // set the n in the last to make sure that added from n=1 spread.resumeCalcService(); spread.resumePaint(); } function setIterativeCalculation(e) { spreadRef.value.options.iterativeCalculation = e.target.checked; } function setMaximumIterations(e) { spreadRef.value.options.iterativeCalculationMaximumIterations = e.target.value; } function setMaximumChange(e) { spreadRef.value.options.iterativeCalculationMaximumChange = e.target.value; } function recalcAll(e) { spreadRef.value.getActiveSheet().recalcAll(true); } </script> <style scoped> #app { height: 100%; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: calc(100% - 280px); height: 100%; overflow: hidden; float: left; } .options-container { float: right; width: 280px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; } .option-row { font-size: 14px; padding: 5px; } input { display:block; width: 100%; margin: 8px 0; box-sizing: border-box; } input[type=checkbox] { display: inline-block; width: initial; margin: 0; vertical-align: middle; } label, input { padding: 4px 6px; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } </style>
<!DOCTYPE html> <html lang="en" style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>SpreadJS VUE</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/vue3/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/en/vue3/node_modules/systemjs/dist/system.src.js"></script> <script src="./systemjs.config.js"></script> <script src="./compiler.js" type="module"></script> <script> var System = SystemJS; System.import("./src/app.js"); System.import('$DEMOROOT$/en/lib/vue3/license.js'); </script> </head> <body> <div id="app"></div> </body> </html>
(function (global) { SystemJS.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, packageConfigPaths: [ './node_modules/*/package.json', "./node_modules/@mescius/*/package.json", "./node_modules/@babel/*/package.json", "./node_modules/@vue/*/package.json" ], map: { '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-vue': 'npm:@mescius/spread-sheets-vue/index.js', 'vue': "npm:vue/dist/vue.esm-browser.js", 'tiny-emitter': 'npm:tiny-emitter/index.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', "systemjs-babel-build": "npm:systemjs-plugin-babel/systemjs-babel-browser.js", }, meta: { '*.css': { loader: 'systemjs-plugin-css' }, '*.vue': { loader: "../plugin-vue/index.js" } } }); })(this);