浏览代码

This closes #785, support to change tab color; new formula function: FISHER, FISHERINV, GAMMA, GAMMALN, MIN, MINA, PERMUT

xuri 4 年之前
父节点
当前提交
bddea1262b
共有 5 个文件被更改,包括 380 次插入19 次删除
  1. 267 11
      calc.go
  2. 76 7
      calc_test.go
  3. 2 1
      drawing.go
  4. 26 0
      sheetpr.go
  5. 9 0
      sheetpr_test.go

+ 267 - 11
calc.go

@@ -249,9 +249,13 @@ var tokenPriority = map[string]int{
 //    FACT
 //    FACTDOUBLE
 //    FALSE
+//    FISHER
+//    FISHERINV
 //    FLOOR
 //    FLOOR.MATH
 //    FLOOR.PRECISE
+//    GAMMA
+//    GAMMALN
 //    GCD
 //    HLOOKUP
 //    IF
@@ -278,6 +282,8 @@ var tokenPriority = map[string]int{
 //    MAX
 //    MDETERM
 //    MEDIAN
+//    MIN
+//    MINA
 //    MOD
 //    MROUND
 //    MULTINOMIAL
@@ -286,6 +292,7 @@ var tokenPriority = map[string]int{
 //    NOT
 //    ODD
 //    OR
+//    PERMUT
 //    PI
 //    POWER
 //    PRODUCT
@@ -295,6 +302,7 @@ var tokenPriority = map[string]int{
 //    RAND
 //    RANDBETWEEN
 //    REPT
+//    ROMAN
 //    ROUND
 //    ROUNDDOWN
 //    ROUNDUP
@@ -1798,7 +1806,7 @@ func (fn *formulaFuncs) FACT(argsList *list.List) formulaArg {
 	if number.Number < 0 {
 		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
 	}
-	return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", fact(number.Number))))
+	return newNumberFormulaArg(fact(number.Number))
 }
 
 // FACTDOUBLE function returns the double factorial of a supplied number. The
@@ -2552,7 +2560,8 @@ func (fn *formulaFuncs) RANDBETWEEN(argsList *list.List) formulaArg {
 	if top.Number < bottom.Number {
 		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
 	}
-	return newNumberFormulaArg(float64(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number-bottom.Number+1)) + int64(bottom.Number)))
+	num := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number - bottom.Number + 1))
+	return newNumberFormulaArg(float64(num + int64(bottom.Number)))
 }
 
 // romanNumerals defined a numeral system that originated in ancient Rome and
@@ -2563,11 +2572,34 @@ type romanNumerals struct {
 	s string
 }
 
-var romanTable = [][]romanNumerals{{{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}},
-	{{1000, "M"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {95, "VC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}},
-	{{1000, "M"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}},
-	{{1000, "M"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}},
-	{{1000, "M"}, {999, "IM"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {499, "ID"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}}}
+var romanTable = [][]romanNumerals{
+	{
+		{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"},
+		{50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
+	},
+	{
+		{1000, "M"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {450, "LD"}, {400, "CD"},
+		{100, "C"}, {95, "VC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
+		{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
+	},
+	{
+		{1000, "M"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {490, "XD"},
+		{450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"},
+		{45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
+	},
+	{
+		{1000, "M"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"},
+		{495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"},
+		{90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"},
+		{5, "V"}, {4, "IV"}, {1, "I"},
+	},
+	{
+		{1000, "M"}, {999, "IM"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"},
+		{500, "D"}, {499, "ID"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"},
+		{100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
+		{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
+	},
+}
 
 // ROMAN function converts an arabic number to Roman. I.e. for a supplied
 // integer, the function returns a text string depicting the roman numeral
@@ -3191,6 +3223,112 @@ func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg {
 	return newNumberFormulaArg(float64(count))
 }
 
+// FISHER function calculates the Fisher Transformation for a supplied value.
+// The syntax of the function is:
+//
+//    FISHER(x)
+//
+func (fn *formulaFuncs) FISHER(argsList *list.List) formulaArg {
+	if argsList.Len() != 1 {
+		return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
+	}
+	token := argsList.Front().Value.(formulaArg)
+	switch token.Type {
+	case ArgString:
+		arg := token.ToNumber()
+		if arg.Type == ArgNumber {
+			if arg.Number <= -1 || arg.Number >= 1 {
+				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+			}
+			return newNumberFormulaArg(0.5 * math.Log((1+arg.Number)/(1-arg.Number)))
+		}
+	case ArgNumber:
+		if token.Number <= -1 || token.Number >= 1 {
+			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+		}
+		return newNumberFormulaArg(0.5 * math.Log((1+token.Number)/(1-token.Number)))
+	}
+	return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
+}
+
+// FISHERINV function calculates the inverse of the Fisher Transformation and
+// returns a value between -1 and +1. The syntax of the function is:
+//
+//    FISHERINV(y)
+//
+func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg {
+	if argsList.Len() != 1 {
+		return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
+	}
+	token := argsList.Front().Value.(formulaArg)
+	switch token.Type {
+	case ArgString:
+		arg := token.ToNumber()
+		if arg.Type == ArgNumber {
+			return newNumberFormulaArg((math.Exp(2*arg.Number) - 1) / (math.Exp(2*arg.Number) + 1))
+		}
+	case ArgNumber:
+		return newNumberFormulaArg((math.Exp(2*token.Number) - 1) / (math.Exp(2*token.Number) + 1))
+	}
+	return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
+}
+
+// GAMMA function returns the value of the Gamma Function, Γ(n), for a
+// specified number, n. The syntax of the function is:
+//
+//    GAMMA(number)
+//
+func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg {
+	if argsList.Len() != 1 {
+		return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
+	}
+	token := argsList.Front().Value.(formulaArg)
+	switch token.Type {
+	case ArgString:
+		arg := token.ToNumber()
+		if arg.Type == ArgNumber {
+			if arg.Number <= 0 {
+				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+			}
+			return newNumberFormulaArg(math.Gamma(arg.Number))
+		}
+	case ArgNumber:
+		if token.Number <= 0 {
+			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+		}
+		return newNumberFormulaArg(math.Gamma(token.Number))
+	}
+	return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
+}
+
+// GAMMALN function returns the natural logarithm of the Gamma Function, Γ
+// (n). The syntax of the function is:
+//
+//    GAMMALN(x)
+//
+func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg {
+	if argsList.Len() != 1 {
+		return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
+	}
+	token := argsList.Front().Value.(formulaArg)
+	switch token.Type {
+	case ArgString:
+		arg := token.ToNumber()
+		if arg.Type == ArgNumber {
+			if arg.Number <= 0 {
+				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+			}
+			return newNumberFormulaArg(math.Log(math.Gamma(arg.Number)))
+		}
+	case ArgNumber:
+		if token.Number <= 0 {
+			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+		}
+		return newNumberFormulaArg(math.Log(math.Gamma(token.Number)))
+	}
+	return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
+}
+
 // MAX function returns the largest value from a supplied set of numeric
 // values. The syntax of the function is:
 //
@@ -3203,7 +3341,10 @@ func (fn *formulaFuncs) MAX(argsList *list.List) formulaArg {
 	return fn.max(false, argsList)
 }
 
-// MAXA function returns the largest value from a supplied set of numeric values, while counting text and the logical value FALSE as the value 0 and counting the logical value TRUE as the value 1. The syntax of the function is:
+// MAXA function returns the largest value from a supplied set of numeric
+// values, while counting text and the logical value FALSE as the value 0 and
+// counting the logical value TRUE as the value 1. The syntax of the function
+// is:
 //
 //    MAXA(number1,[number2],...)
 //
@@ -3317,6 +3458,112 @@ func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg {
 	return newNumberFormulaArg(median)
 }
 
+// MIN function returns the smallest value from a supplied set of numeric
+// values. The syntax of the function is:
+//
+//    MIN(number1,[number2],...)
+//
+func (fn *formulaFuncs) MIN(argsList *list.List) formulaArg {
+	if argsList.Len() == 0 {
+		return newErrorFormulaArg(formulaErrorVALUE, "MIN requires at least 1 argument")
+	}
+	return fn.min(false, argsList)
+}
+
+// MINA function returns the smallest value from a supplied set of numeric
+// values, while counting text and the logical value FALSE as the value 0 and
+// counting the logical value TRUE as the value 1. The syntax of the function
+// is:
+//
+//    MINA(number1,[number2],...)
+//
+func (fn *formulaFuncs) MINA(argsList *list.List) formulaArg {
+	if argsList.Len() == 0 {
+		return newErrorFormulaArg(formulaErrorVALUE, "MINA requires at least 1 argument")
+	}
+	return fn.min(true, argsList)
+}
+
+// min is an implementation of the formula function MIN and MINA.
+func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg {
+	min := math.MaxFloat64
+	for token := argsList.Front(); token != nil; token = token.Next() {
+		arg := token.Value.(formulaArg)
+		switch arg.Type {
+		case ArgString:
+			if !mina && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
+				continue
+			} else {
+				num := arg.ToBool()
+				if num.Type == ArgNumber && num.Number < min {
+					min = num.Number
+					continue
+				}
+			}
+			num := arg.ToNumber()
+			if num.Type != ArgError && num.Number < min {
+				min = num.Number
+			}
+		case ArgNumber:
+			if arg.Number < min {
+				min = arg.Number
+			}
+		case ArgList, ArgMatrix:
+			for _, row := range arg.ToList() {
+				switch row.Type {
+				case ArgString:
+					if !mina && (row.Value() == "TRUE" || row.Value() == "FALSE") {
+						continue
+					} else {
+						num := row.ToBool()
+						if num.Type == ArgNumber && num.Number < min {
+							min = num.Number
+							continue
+						}
+					}
+					num := row.ToNumber()
+					if num.Type != ArgError && num.Number < min {
+						min = num.Number
+					}
+				case ArgNumber:
+					if row.Number < min {
+						min = row.Number
+					}
+				}
+			}
+		case ArgError:
+			return arg
+		}
+	}
+	if min == math.MaxFloat64 {
+		min = 0
+	}
+	return newNumberFormulaArg(min)
+}
+
+// PERMUT function calculates the number of permutations of a specified number
+// of objects from a set of objects. The syntax of the function is:
+//
+//    PERMUT(number,number_chosen)
+//
+func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg {
+	if argsList.Len() != 2 {
+		return newErrorFormulaArg(formulaErrorVALUE, "PERMUT requires 2 numeric arguments")
+	}
+	number := argsList.Front().Value.(formulaArg).ToNumber()
+	chosen := argsList.Back().Value.(formulaArg).ToNumber()
+	if number.Type != ArgNumber {
+		return number
+	}
+	if chosen.Type != ArgNumber {
+		return chosen
+	}
+	if number.Number < chosen.Number {
+		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+	}
+	return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number)))
+}
+
 // Information Functions
 
 // ISBLANK function tests if a specified cell is blank (empty) and if so,
@@ -3356,7 +3603,11 @@ func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg {
 	token := argsList.Front().Value.(formulaArg)
 	result := "FALSE"
 	if token.Type == ArgError {
-		for _, errType := range []string{formulaErrorDIV, formulaErrorNAME, formulaErrorNUM, formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA} {
+		for _, errType := range []string{
+			formulaErrorDIV, formulaErrorNAME, formulaErrorNUM,
+			formulaErrorVALUE, formulaErrorREF, formulaErrorNULL,
+			formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA,
+		} {
 			if errType == token.String {
 				result = "TRUE"
 			}
@@ -3378,7 +3629,11 @@ func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg {
 	token := argsList.Front().Value.(formulaArg)
 	result := "FALSE"
 	if token.Type == ArgError {
-		for _, errType := range []string{formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM, formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA} {
+		for _, errType := range []string{
+			formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM,
+			formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL,
+			formulaErrorCALC, formulaErrorGETTINGDATA,
+		} {
 			if errType == token.String {
 				result = "TRUE"
 			}
@@ -4413,5 +4668,6 @@ func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg {
 	if argsList.Len() != 1 {
 		return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument")
 	}
-	return newStringFormulaArg(strings.Replace(url.QueryEscape(argsList.Front().Value.(formulaArg).Value()), "+", "%20", -1))
+	token := argsList.Front().Value.(formulaArg).Value()
+	return newStringFormulaArg(strings.Replace(url.QueryEscape(token), "+", "%20", -1))
 }

+ 76 - 7
calc_test.go

@@ -205,7 +205,7 @@ func TestCalcCellValue(t *testing.T) {
 		// FACT
 		"=FACT(3)":       "6",
 		"=FACT(6)":       "720",
-		"=FACT(10)":      "3.6288E+06",
+		"=FACT(10)":      "3.6288e+06",
 		"=FACT(FACT(3))": "720",
 		// FACTDOUBLE
 		"=FACTDOUBLE(5)":             "15",
@@ -490,6 +490,23 @@ func TestCalcCellValue(t *testing.T) {
 		"=COUNTBLANK(1)":        "0",
 		"=COUNTBLANK(B1:C1)":    "1",
 		"=COUNTBLANK(C1)":       "1",
+		// FISHER
+		"=FISHER(-0.9)":   "-1.47221948958322",
+		"=FISHER(-0.25)":  "-0.255412811882995",
+		"=FISHER(0.8)":    "1.09861228866811",
+		"=FISHER(INT(0))": "0",
+		// FISHERINV
+		"=FISHERINV(-0.2)":   "-0.197375320224904",
+		"=FISHERINV(INT(0))": "0",
+		"=FISHERINV(2.8)":    "0.992631520201128",
+		// GAMMA
+		"=GAMMA(0.1)":    "9.513507698668732",
+		"=GAMMA(INT(1))": "1",
+		"=GAMMA(1.5)":    "0.886226925452758",
+		"=GAMMA(5.5)":    "52.34277778455352",
+		// GAMMALN
+		"=GAMMALN(4.5)":    "2.453736570842443",
+		"=GAMMALN(INT(1))": "0",
 		// MAX
 		"=MAX(1)":          "1",
 		"=MAX(TRUE())":     "1",
@@ -509,6 +526,25 @@ func TestCalcCellValue(t *testing.T) {
 		"=MEDIAN(A1:A5,12)":               "2",
 		"=MEDIAN(A1:A5)":                  "1.5",
 		"=MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2",
+		// MIN
+		"=MIN(1)":           "1",
+		"=MIN(TRUE())":      "1",
+		"=MIN(0.5,FALSE())": "0",
+		"=MIN(FALSE())":     "0",
+		"=MIN(MUNIT(2))":    "0",
+		"=MIN(INT(1))":      "1",
+		// MINA
+		"=MINA(1)":           "1",
+		"=MINA(TRUE())":      "1",
+		"=MINA(0.5,FALSE())": "0",
+		"=MINA(FALSE())":     "0",
+		"=MINA(MUNIT(2))":    "0",
+		"=MINA(INT(1))":      "1",
+		"=MINA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "0",
+		// PERMUT
+		"=PERMUT(6,6)":  "720",
+		"=PERMUT(7,6)":  "5040",
+		"=PERMUT(10,6)": "151200",
 		// Information Functions
 		// ISBLANK
 		"=ISBLANK(A1)": "FALSE",
@@ -940,6 +976,24 @@ func TestCalcCellValue(t *testing.T) {
 		// COUNTBLANK
 		"=COUNTBLANK()":    "COUNTBLANK requires 1 argument",
 		"=COUNTBLANK(1,2)": "COUNTBLANK requires 1 argument",
+		// FISHER
+		"=FISHER()":         "FISHER requires 1 numeric argument",
+		"=FISHER(2)":        "#N/A",
+		"=FISHER(INT(-2)))": "#N/A",
+		"=FISHER(F1)":       "FISHER requires 1 numeric argument",
+		// FISHERINV
+		"=FISHERINV()":   "FISHERINV requires 1 numeric argument",
+		"=FISHERINV(F1)": "FISHERINV requires 1 numeric argument",
+		// GAMMA
+		"=GAMMA()":       "GAMMA requires 1 numeric argument",
+		"=GAMMA(F1)":     "GAMMA requires 1 numeric argument",
+		"=GAMMA(0)":      "#N/A",
+		"=GAMMA(INT(0))": "#N/A",
+		// GAMMALN
+		"=GAMMALN()":       "GAMMALN requires 1 numeric argument",
+		"=GAMMALN(F1)":     "GAMMALN requires 1 numeric argument",
+		"=GAMMALN(0)":      "#N/A",
+		"=GAMMALN(INT(0))": "#N/A",
 		// MAX
 		"=MAX()":     "MAX requires at least 1 argument",
 		"=MAX(NA())": "#N/A",
@@ -950,6 +1004,17 @@ func TestCalcCellValue(t *testing.T) {
 		"=MEDIAN()":      "MEDIAN requires at least 1 argument",
 		"=MEDIAN(\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
 		"=MEDIAN(D1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax",
+		// MIN
+		"=MIN()":     "MIN requires at least 1 argument",
+		"=MIN(NA())": "#N/A",
+		// MINA
+		"=MINA()":     "MINA requires at least 1 argument",
+		"=MINA(NA())": "#N/A",
+		// PERMUT
+		"=PERMUT()":       "PERMUT requires 2 numeric arguments",
+		"=PERMUT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=PERMUT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=PERMUT(6,8)":    "#N/A",
 		// Information Functions
 		// ISBLANK
 		"=ISBLANK(A1,A2)": "ISBLANK requires 1 argument",
@@ -1315,16 +1380,20 @@ func TestCalcVLOOKUP(t *testing.T) {
 	}
 }
 
-func TestCalcMAX(t *testing.T) {
+func TestCalcMAXMIN(t *testing.T) {
 	cellData := [][]interface{}{
-		{0.5, "TRUE"},
+		{0.5, "TRUE", -0.5, "FALSE"},
 	}
 	f := prepareCalcData(cellData)
 	formulaList := map[string]string{
-		"=MAX(0.5,B1)":  "0.5",
-		"=MAX(A1:B1)":   "0.5",
-		"=MAXA(A1:B1)":  "1",
-		"=MAXA(0.5,B1)": "1",
+		"=MAX(0.5,B1)":   "0.5",
+		"=MAX(A1:B1)":    "0.5",
+		"=MAXA(A1:B1)":   "1",
+		"=MAXA(0.5,B1)":  "1",
+		"=MIN(-0.5,D1)":  "-0.5",
+		"=MIN(C1:D1)":    "-0.5",
+		"=MINA(C1:D1)":   "-0.5",
+		"=MINA(-0.5,D1)": "-0.5",
 	}
 	for formula, expected := range formulaList {
 		assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))

+ 2 - 1
drawing.go

@@ -939,7 +939,8 @@ func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls {
 // given format sets.
 func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls {
 	dLbls := f.drawChartDLbls(formatSet)
-	chartSeriesDLbls := map[string]*cDLbls{Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil}
+	chartSeriesDLbls := map[string]*cDLbls{
+		Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil}
 	if _, ok := chartSeriesDLbls[formatSet.Type]; ok {
 		return nil
 	}

+ 26 - 0
sheetpr.go

@@ -11,6 +11,8 @@
 
 package excelize
 
+import "strings"
+
 // SheetPrOption is an option of a view of a worksheet. See SetSheetPrOptions().
 type SheetPrOption interface {
 	setSheetPrOption(view *xlsxSheetPr)
@@ -31,6 +33,8 @@ type (
 	Published bool
 	// FitToPage is a SheetPrOption
 	FitToPage bool
+	// TabColor is a SheetPrOption
+	TabColor string
 	// AutoPageBreaks is a SheetPrOption
 	AutoPageBreaks bool
 	// OutlineSummaryBelow is an outlinePr, within SheetPr option
@@ -125,6 +129,28 @@ func (o *FitToPage) getSheetPrOption(pr *xlsxSheetPr) {
 	*o = FitToPage(pr.PageSetUpPr.FitToPage)
 }
 
+// setSheetPrOption implements the SheetPrOption interface and specifies a
+// stable name of the sheet.
+func (o TabColor) setSheetPrOption(pr *xlsxSheetPr) {
+	if pr.TabColor == nil {
+		if string(o) == "" {
+			return
+		}
+		pr.TabColor = new(xlsxTabColor)
+	}
+	pr.TabColor.RGB = getPaletteColor(string(o))
+}
+
+// getSheetPrOption implements the SheetPrOptionPtr interface and get the
+// stable name of the sheet.
+func (o *TabColor) getSheetPrOption(pr *xlsxSheetPr) {
+	if pr == nil || pr.TabColor == nil {
+		*o = ""
+		return
+	}
+	*o = TabColor(strings.TrimPrefix(pr.TabColor.RGB, "FF"))
+}
+
 // setSheetPrOption implements the SheetPrOption interface.
 func (o AutoPageBreaks) setSheetPrOption(pr *xlsxSheetPr) {
 	if pr.PageSetUpPr == nil {

+ 9 - 0
sheetpr_test.go

@@ -13,6 +13,7 @@ var _ = []SheetPrOption{
 	EnableFormatConditionsCalculation(false),
 	Published(false),
 	FitToPage(true),
+	TabColor("#FFFF00"),
 	AutoPageBreaks(true),
 	OutlineSummaryBelow(true),
 }
@@ -22,6 +23,7 @@ var _ = []SheetPrOptionPtr{
 	(*EnableFormatConditionsCalculation)(nil),
 	(*Published)(nil),
 	(*FitToPage)(nil),
+	(*TabColor)(nil),
 	(*AutoPageBreaks)(nil),
 	(*OutlineSummaryBelow)(nil),
 }
@@ -35,6 +37,7 @@ func ExampleFile_SetSheetPrOptions() {
 		EnableFormatConditionsCalculation(false),
 		Published(false),
 		FitToPage(true),
+		TabColor("#FFFF00"),
 		AutoPageBreaks(true),
 		OutlineSummaryBelow(false),
 	); err != nil {
@@ -52,6 +55,7 @@ func ExampleFile_GetSheetPrOptions() {
 		enableFormatConditionsCalculation EnableFormatConditionsCalculation
 		published                         Published
 		fitToPage                         FitToPage
+		tabColor                          TabColor
 		autoPageBreaks                    AutoPageBreaks
 		outlineSummaryBelow               OutlineSummaryBelow
 	)
@@ -61,6 +65,7 @@ func ExampleFile_GetSheetPrOptions() {
 		&enableFormatConditionsCalculation,
 		&published,
 		&fitToPage,
+		&tabColor,
 		&autoPageBreaks,
 		&outlineSummaryBelow,
 	); err != nil {
@@ -71,6 +76,7 @@ func ExampleFile_GetSheetPrOptions() {
 	fmt.Println("- enableFormatConditionsCalculation:", enableFormatConditionsCalculation)
 	fmt.Println("- published:", published)
 	fmt.Println("- fitToPage:", fitToPage)
+	fmt.Printf("- tabColor: %q\n", tabColor)
 	fmt.Println("- autoPageBreaks:", autoPageBreaks)
 	fmt.Println("- outlineSummaryBelow:", outlineSummaryBelow)
 	// Output:
@@ -79,6 +85,7 @@ func ExampleFile_GetSheetPrOptions() {
 	// - enableFormatConditionsCalculation: true
 	// - published: true
 	// - fitToPage: false
+	// - tabColor: ""
 	// - autoPageBreaks: false
 	// - outlineSummaryBelow: true
 }
@@ -94,6 +101,7 @@ func TestSheetPrOptions(t *testing.T) {
 		{new(EnableFormatConditionsCalculation), EnableFormatConditionsCalculation(false)},
 		{new(Published), Published(false)},
 		{new(FitToPage), FitToPage(true)},
+		{new(TabColor), TabColor("FFFF00")},
 		{new(AutoPageBreaks), AutoPageBreaks(true)},
 		{new(OutlineSummaryBelow), OutlineSummaryBelow(false)},
 	}
@@ -147,6 +155,7 @@ func TestSheetPrOptions(t *testing.T) {
 
 func TestSetSheetrOptions(t *testing.T) {
 	f := NewFile()
+	assert.NoError(t, f.SetSheetPrOptions("Sheet1", TabColor("")))
 	// Test SetSheetrOptions on not exists worksheet.
 	assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
 }