I have a simplified test scenario created where I have a spreadsheet with two cells (C2/C3) having an array formula:
{=NaNTest()}
My simplified CustomFunction is as follows:
public class NaNTest : CustomFunctions.Function
{
public NaNTest() : this( "NaNTest" ) { }
public NaNTest( string name ) : base( name, CustomFunctions.Volatility.Invariant, CustomFunctions.ValueType.Variant ) { }
public override void Evaluate( CustomFunctions.IArguments a, CustomFunctions.IValue r )
{
var result = new double[ 1, 2 ];
result[ 0, 0 ] = double.NaN;
result[ 0, 1 ] = 0d;
r.SetArray( result );
}
}
This sets both C2 and C3 to #NUM! when I'd expect only C2 to be. Is there a way to make it correctly* assign C3 to 0?
Thanks in advance.
* I say correctly because we have to implement an Excel add-in that our clients use to author spreadsheets and it provides same 'functionality' that we provide on 'our servers' when we open/process the spreadsheet in our 'SpreadsheetGear calculations' (i.e. the NaNTest() function above). The libraries we use to create the add-in only assign C2 to #NUM! and having the two implementations (client side add-in vs server side SpreadsheetGear) behaving differently makes maintenance/debugging difficult.
This behavior is by design. Note the comment in the documentation for the IValue.SetArray(...) method:
If any of the numbers in the array are not valid numbers, the result
of the custom function will be ValueError.Num.
Since NaN isn't a valid number the entire array will resolve to #NUM! instead. Actually, if you try to set a cell value on its out (outside of a custom function), such as...
worksheet.Cells["A1"].Value = double.NaN;
...you should find that cell evaluates to #NUM! as well. If such cases can occur in your custom function, you'll likely just need to write a check for this condition and respond in whatever manner is required by your application.
Related
I have a table in Tabulator and would like to show all 0 values in the cells as blank. What's the best way to achieve this?
My first attempt was to write a custom mutator:
mutator: function(value, data, type, params, component){
if (value == 0){
return "";
}
else{
return value;
}
}
This works but feels wrong. Further, I would like to create calculated fields on top of this and this doesn't work properly with the blank strings. Then I need to re-convert them back to zeroes in the next function.
Thanks for your help
you can create a function that returns a copy of the original array but with all 0 values in the cells as blank then you display it, and each time you need to calculate or update you do it based on/in the original one then you always display the copy
Just use a cell formatter. A mutator will change the data but I don't think you want this - you just want to change the display of the data.
See my Codepen here and take a look at the moneyColFormatter function.
Specifically, in the cell definition:
...
formatter: (cell) => this.moneyColFormatter(cell),
and then implement it somehow
private moneyColFormatter(cell): string {
// If we want to show 0 values as blank:
if (!cell.getValue()) {
return "";
}
return cell.getValue();
}
The Codepen also contains aggregated functions, calculated cols and custom dynamic columns.
I have recently discovered Tabulator.js and its possibility to have e. g. the average of all values of a column at the bottom.
I wondered whether one can also have the minimum and maximum instead of just one thing. I did not find that in the docs, but it might be possible by extending the library? (I would not want to have one at the top, and as I need more than two calculations, that would not be a solution anyway.)
This is similar to another answer recently for the topCalc property.
You can put, effectively, whatever you want in a footer row. Use the bottomCalc and bottomCalcFormatter properties of the column definition along with a custom function to get the appropriate content.
In the custom function you can use bult in functions such as the table.getCalcResult() method.
eg: return an array of sum, average, count
let table = new Tabulator("#my-table-id", {
...
columns:[
...
{
title: 'My col',
...,
bottomCalc: function(v, d, p) {
// v - array of column values
// d - all table data
// p - params passed from the column definition object
let res = table.getCalcResults();
// eg Get sum and count for a column with id 'C1'
let c1_calc = 0, c1_count = 0;
d.forEach(function(row) {
c1_calc += row["c1"];
c1_count++;
});
return [
(parseInt(res.bottom["COL_1_ID"], 10) + parseInt(res.bottom["COL_2_ID"], 10)),
(parseInt(res.bottom["COL_1_ID"], 10) + parseInt(res.bottom["COL_2_ID"], 10)) / 2,
c1_calc,
c1_count
];
}
}
],
...
);
Once you have the content you can use a custom formatter to display it nicely.
See Custom Calculation Function and Calculation Results in the docs.
I have updated my Codepen again to add a bottomCalc function and formatter. See the definition for the AggregateFn column, the getBottomAggregate function that implements it and the bottomAggregateFormatter function that formats it.
Hope that helps.
I am using the EPPlus library in my ASP.Net application.
What I am trying to do is open a spreadsheet, input some values into cells and then read another cell which contains the result of a calculation. The spreadsheet itself is confidential so I can't provide many details on it.
In order to get my calculations to work I have had to modify the source code for EPplus, changing the Compile function in the ExcelAddressExpression.cs file to ignore the ParentIsLookupFunction bool as shown at the bottom of the question.
so it was able to evaluate the term 5*$f$7
What I want to know is what situations is it useful to keep the CompileResult as an ExcelAddress, so I do not run into any incorrect calculations or errors in other parts of the spreadsheet.
For reference here are the steps I went though to get here:
My code is something like this
using (ExcelPackage p = new ExcelPackage(FilePath, true))
{
ExcelWorksheet ws = p.Workbook.Worksheets["Calculations"];
ws.Cells["b7"].Value = 50;
ws.Cells["f9"].Value = 500000;
ws.Cells["j216"].Calculate();
string result = ws.Cells["j216"].Value.ToString();
}
The formula in cell J216 is
=VLOOKUP($B$7+$F$221+$K$13-$F$8-1,Sheet2!$A$4:$T$103,5*$F$7+2*$B$8+$B$9-5,FALSE)
and the result I got was '#VALUE!'
I have attached a log file and found the issue is in the VLookup function
Worksheet: Calculations
Address: J216
OfficeOpenXml.FormulaParsing.Exceptions.ExcelErrorValueException: #VALUE!
at OfficeOpenXml.FormulaParsing.Excel.Functions.IntArgumentParser.Parse(Object obj)
at OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup.LookupArguments..ctor(IEnumerable`1 arguments, ArgumentParsers argumentParsers, ParsingContext context)
at OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup.VLookup.Execute(IEnumerable`1 arguments, ParsingContext context)
at OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers.LookupFunctionCompiler.Compile(IEnumerable`1 children, ParsingContext context)
at OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionExpression.Compile()
The next step I took was to download the source code for EPPlus, and debug through the code as it executed, eventually finding the problem was at line 165 of Operator.cs
l = l ?? new CompileResult(0, DataType.Integer);
r = r ?? new CompileResult(0, DataType.Integer);
if (l.DataType == DataType.Integer && r.DataType == DataType.Integer)
{
return new CompileResult(l.ResultNumeric*r.ResultNumeric, DataType.Integer);
}
else if ((l.IsNumeric || l.IsNumericString || l.IsDateString || l.Result is ExcelDataProvider.IRangeInfo) &&
(r.IsNumeric || r.IsNumericString || r.IsDateString || r.Result is ExcelDataProvider.IRangeInfo))
{
return new CompileResult(l.ResultNumeric*r.ResultNumeric, DataType.Decimal);
}
return new CompileResult(eErrorType.Value);
When evaluating the equation 5*$F$7, the second parameter was of DataType ExcelAddress which is results in a compile result exception being thrown.
The root cause of this is in the Compile function of the ExcelAddressExpression.cs file the ParentIsLookupFunction boolean controls whether the cell is evaluated or left as an address.
public override CompileResult Compile()
{
if (ParentIsLookupFunction)
{
return new CompileResult(ExpressionString, DataType.ExcelAddress);
}
else
{
return CompileRangeValues();
}
}
I have modified my version of the code to simply be
public override CompileResult Compile()
{
return CompileRangeValues();
}
As said at the top of the question, what I want to know is why would you want to return the ExcelAddress CompileResult, It was obviously put there for a reason and I do not want to break some other calculations in my spreadsheet.
I can confirm though that for at least this calculation it is now working correctly.
I wrote the formula engine of EPPlus some years ago, but don't work much on the project these days. If I recall it correctly there are cases where you don't want to compile the excel address in the expression, but rather pass it on to the executing function. Have you tried to run the unit tests? The formula engine is covered by hundreds of tests and the test result could provide some guidance.
I was able to answer this after experimenting with different spreadsheets.
Returning the value as an ExcelAddress is useful in the event that the address is not operated on like in my earlier example 5*$F$7 such as an IF statement inside a VLookup.
In the event that there is an operator you will want to return the contents of the cell.
I modified the EPPlus code to now check for any operators separating terms in the formula
public override CompileResult Compile()
{
if (ParentIsLookupFunction &&
Operator == null &&
(Prev == null || Prev.Operator == null))
{
return new CompileResult(ExpressionString, DataType.ExcelAddress);
}
else
{
return CompileRangeValues();
}
}
I could definitely use some help here. I have an excel sheet from where I am getting values and doing some validations on them, I am checking if both values match. My code is as follows:
#Unroll ("For #calcToCheck.tr_date_class")
def "I check flag value #calcToCheck.tr_date "(CalculationClass calcToCheck) {
expect:
flag==calcToCheck.result
where:
calcToCheck << calInputParameters()
}
def calInputParameters() {
//some logic to get values from SQL and getting flag
return calcsToCheck
}
This runs fine for one row but when I insert multiple rows in excel sheet, I just get output as one result. I would like to see each row's result. I thought adding #Unroll would take care of it showing me what rows it is displaying the results but it does not.
Here is some sample code you can run and then modify. Because you did not explain where flag comes from, I was just making up something, assuming it is also an unrolled parameter checked against whatever your helper method delivers.
package de.scrum_master.stackoverflow
import spock.lang.Specification
import spock.lang.Unroll
/**
* See https://stackoverflow.com/q/48410722/1082681
*/
class PseudoExcelTableTest extends Specification {
#Unroll//("For #calcToCheck.tr_date_class")
def "I check flag value #calcToCheck.tr_date"(CalculationClass calcToCheck, int flag) {
expect:
flag == calcToCheck.result
where:
calcToCheck << calInputParameters()
flag << [11, 22, 33]
}
def calInputParameters() {
def calcsToCheck = new ArrayList<CalculationClass>()
calcsToCheck.addAll(
new CalculationClass(result: 11, tr_date: "eleven", tr_date_class: "short"),
new CalculationClass(result: 22, tr_date: "twenty-two", tr_date_class: "long"),
new CalculationClass(result: 33, tr_date: "thirty-three", tr_date_class: "normal")
)
return calcsToCheck
}
static class CalculationClass {
int result
String tr_date
String tr_date_class
}
}
As for the unrolled method names, you mix up two things:
Naming the methods via #Unroll parameter, which in my example would result in method names For short, For long, For normal. This has preference over the second option:
Naming the methods via normal method name, which in my example would result in method names I check flag value eleven, I check flag value twenty-two, I check flag value thirty-three.
I chose option 2 by commenting out the unroll parameter.
I can not find a formulat that multiplies chances the way i want.
I can not use additional cells or VBA because this is a company excel sheet that is locked to a certain format.
Here is some pseudocode that illustrates what i want to do;
value oneminus(value) //typeof delegate
{
return 1 - value;
}
value ProductDelegate(range, delegate)
{
Result result = 1;
foreach value in range
{
result *= delegate(value);
}
return result;
}
What i would want to call is a prefabricated version of the ProductDelegate. I would call it like so =ProductDelegate(J56:J73, "1-"&J). I do not think that ProductDelegate actually exists so i feel like what i am asking is not implemented in excel. Are there any options for this particular usecase? Any statistics function i am missing?
You don't need VBA for this. Just type the following formula but hold down CTRL-SHFT when hitting enter:
=PRODUCT(1-J56:J73)