Breaking change: changed the function signature for 11 exported functions

* Change
    `func (f *File) NewConditionalStyle(style string) (int, error)`
    to
    `func (f *File) NewConditionalStyle(style *Style) (int, error)`
* Change
    `func (f *File) NewStyle(style interface{}) (int, error)`
     to
    `func (f *File) NewStyle(style *Style) (int, error)`
* Change
    `func (f *File) AddChart(sheet, cell, opts string, combo ...string) error`
     to
    `func (f *File) AddChart(sheet, cell string, chart *ChartOptions, combo ...*ChartOptions) error`
* Change
    `func (f *File) AddChartSheet(sheet, opts string, combo ...string) error`
     to
    `func (f *File) AddChartSheet(sheet string, chart *ChartOptions, combo ...*ChartOptions) error`
* Change
    `func (f *File) AddShape(sheet, cell, opts string) error`
     to
    `func (f *File) AddShape(sheet, cell string, opts *Shape) error`
* Change
    `func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, file []byte) error`
     to
    `func (f *File) AddPictureFromBytes(sheet, cell, name, extension string, file []byte, opts *PictureOptions) error`
* Change
    `func (f *File) AddTable(sheet, hCell, vCell, opts string) error`
     to
    `func (f *File) AddTable(sheet, reference string, opts *TableOptions) error`
* Change
    `func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error`
     to
    `func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error`
* Change
    `func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error`
     to
    `func (f *File) AutoFilter(sheet, reference string, opts *AutoFilterOptions) error`
* Change
    `func (f *File) SetPanes(sheet, panes string) error`
     to
    `func (f *File) SetPanes(sheet string, panes *Panes) error`
* Change
    `func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error`
     to
    `func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error`
* Change
    `func (f *File) SetConditionalFormat(sheet, reference, opts string) error`
     to
    `func (f *File) SetConditionalFormat(sheet, reference string, opts []ConditionalFormatOptions) error`
* Add exported types:
  * AutoFilterListOptions
  * AutoFilterOptions
  * Chart
  * ChartAxis
  * ChartDimension
  * ChartLegend
  * ChartLine
  * ChartMarker
  * ChartPlotArea
  * ChartSeries
  * ChartTitle
  * ConditionalFormatOptions
  * PaneOptions
  * Panes
  * PictureOptions
  * Shape
  * ShapeColor
  * ShapeLine
  * ShapeParagraph
  * TableOptions
* This added support for set sheet visible as very hidden
* Return error when missing required parameters for set defined name
* Update unit test and comments
This commit is contained in:
xuri 2022-12-30 00:50:08 +08:00
parent a57203a03a
commit f58dabd492
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
47 changed files with 2305 additions and 1968 deletions

View File

@ -121,41 +121,40 @@ import (
)
func main() {
categories := map[string]string{
"A2": "Small", "A3": "Normal", "A4": "Large",
"B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{
"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
f := excelize.NewFile()
for k, v := range categories {
f.SetCellValue("Sheet1", k, v)
for idx, row := range [][]interface{}{
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
} {
cell, err := excelize.CoordinatesToCellName(1, idx+1)
if err != nil {
fmt.Println(err)
return
}
for k, v := range values {
f.SetCellValue("Sheet1", k, v)
f.SetSheetRow("Sheet1", cell, &row)
}
if err := f.AddChart("Sheet1", "E1", `{
"type": "col3DClustered",
"series": [
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
Type: "col3DClustered",
Series: []excelize.ChartSeries{
{
"name": "Sheet1!$A$2",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$2:$D$2"
Name: "Sheet1!$A$2",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$2:$D$2",
},
{
"name": "Sheet1!$A$3",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$3:$D$3"
Name: "Sheet1!$A$3",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$3:$D$3",
},
{
"name": "Sheet1!$A$4",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$4:$D$4"
}],
"title":
{
"name": "Fruit 3D Clustered Column Chart"
}
}`); err != nil {
Name: "Sheet1!$A$4",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$4:$D$4",
}},
Title: excelize.ChartTitle{
Name: "Fruit 3D Clustered Column Chart",
},
}); err != nil {
fmt.Println(err)
return
}
@ -193,22 +192,24 @@ func main() {
}
}()
// Insert a picture.
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
fmt.Println(err)
}
// Insert a picture to worksheet with scaling.
enable, disable, scale := true, false, 0.5
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
`{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
&excelize.PictureOptions{XScale: &scale, YScale: &scale}); err != nil {
fmt.Println(err)
}
// Insert a picture offset in the cell with printing support.
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{
"x_offset": 15,
"y_offset": 10,
"print_obj": true,
"lock_aspect_ratio": false,
"locked": false
}`); err != nil {
if err := f.AddPicture("Sheet1", "H2", "image.gif",
&excelize.PictureOptions{
PrintObject: &enable,
LockAspectRatio: false,
OffsetX: 15,
OffsetY: 10,
Locked: &disable,
}); err != nil {
fmt.Println(err)
}
// Save the spreadsheet with the origin path.

View File

@ -121,41 +121,40 @@ import (
)
func main() {
categories := map[string]string{
"A2": "Small", "A3": "Normal", "A4": "Large",
"B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{
"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
f := excelize.NewFile()
for k, v := range categories {
f.SetCellValue("Sheet1", k, v)
for idx, row := range [][]interface{}{
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
} {
cell, err := excelize.CoordinatesToCellName(1, idx+1)
if err != nil {
fmt.Println(err)
return
}
for k, v := range values {
f.SetCellValue("Sheet1", k, v)
f.SetSheetRow("Sheet1", cell, &row)
}
if err := f.AddChart("Sheet1", "E1", `{
"type": "col3DClustered",
"series": [
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
Type: "col3DClustered",
Series: []excelize.ChartSeries{
{
"name": "Sheet1!$A$2",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$2:$D$2"
Name: "Sheet1!$A$2",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$2:$D$2",
},
{
"name": "Sheet1!$A$3",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$3:$D$3"
Name: "Sheet1!$A$3",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$3:$D$3",
},
{
"name": "Sheet1!$A$4",
"categories": "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$4:$D$4"
}],
"title":
{
"name": "Fruit 3D Clustered Column Chart"
}
}`); err != nil {
Name: "Sheet1!$A$4",
Categories: "Sheet1!$B$1:$D$1",
Values: "Sheet1!$B$4:$D$4",
}},
Title: excelize.ChartTitle{
Name: "Fruit 3D Clustered Column Chart",
},
}); err != nil {
fmt.Println(err)
return
}
@ -193,22 +192,24 @@ func main() {
}
}()
// 插入图片
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的缩放比例
enable, disable, scale := true, false, 0.5
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
`{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
&excelize.PictureOptions{XScale: &scale, YScale: &scale}); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的打印属性
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{
"x_offset": 15,
"y_offset": 10,
"print_obj": true,
"lock_aspect_ratio": false,
"locked": false
}`); err != nil {
if err := f.AddPicture("Sheet1", "H2", "image.gif",
&excelize.PictureOptions{
PrintObject: &enable,
LockAspectRatio: false,
OffsetX: 15,
OffsetY: 10,
Locked: &disable,
}); err != nil {
fmt.Println(err)
}
// 保存工作簿

View File

@ -10,7 +10,7 @@ import (
func TestAdjustMergeCells(t *testing.T) {
f := NewFile()
// Test adjustAutoFilter with illegal cell reference.
// Test adjustAutoFilter with illegal cell reference
assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -57,7 +57,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
}, columns, 1, -1))
// Test adjustMergeCells.
// Test adjust merge cells
var cases []struct {
label string
ws *xlsxWorksheet
@ -68,7 +68,7 @@ func TestAdjustMergeCells(t *testing.T) {
expectRect []int
}
// Test insert.
// Test adjust merged cell when insert rows and columns
cases = []struct {
label string
ws *xlsxWorksheet
@ -139,7 +139,7 @@ func TestAdjustMergeCells(t *testing.T) {
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label)
}
// Test delete,
// Test adjust merged cells when delete rows and columns
cases = []struct {
label string
ws *xlsxWorksheet
@ -292,7 +292,7 @@ func TestAdjustAutoFilter(t *testing.T) {
Ref: "A1:A3",
},
}, rows, 1, -1))
// Test adjustAutoFilter with illegal cell reference.
// Test adjustAutoFilter with illegal cell reference
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A:B1",
@ -307,15 +307,15 @@ func TestAdjustAutoFilter(t *testing.T) {
func TestAdjustTable(t *testing.T) {
f, sheetName := NewFile(), "Sheet1"
for idx, tableRange := range [][]string{{"B2", "C3"}, {"E3", "F5"}, {"H5", "H8"}, {"J5", "K9"}} {
assert.NoError(t, f.AddTable(sheetName, tableRange[0], tableRange[1], fmt.Sprintf(`{
"table_name": "table%d",
"table_style": "TableStyleMedium2",
"show_first_column": true,
"show_last_column": true,
"show_row_stripes": false,
"show_column_stripes": true
}`, idx)))
for idx, reference := range []string{"B2:C3", "E3:F5", "H5:H8", "J5:K9"} {
assert.NoError(t, f.AddTable(sheetName, reference, &TableOptions{
Name: fmt.Sprintf("table%d", idx),
StyleName: "TableStyleMedium2",
ShowFirstColumn: true,
ShowLastColumn: true,
ShowRowStripes: boolPtr(false),
ShowColumnStripes: true,
}))
}
assert.NoError(t, f.RemoveRow(sheetName, 2))
assert.NoError(t, f.RemoveRow(sheetName, 3))
@ -323,31 +323,32 @@ func TestAdjustTable(t *testing.T) {
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx")))
f = NewFile()
assert.NoError(t, f.AddTable(sheetName, "A1", "D5", ""))
// Test adjust table with non-table part.
assert.NoError(t, f.AddTable(sheetName, "A1:D5", nil))
// Test adjust table with non-table part
f.Pkg.Delete("xl/tables/table1.xml")
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with unsupported charset.
// Test adjust table with unsupported charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with invalid table range reference.
// Test adjust table with invalid table range reference
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.NoError(t, f.RemoveRow(sheetName, 1))
}
func TestAdjustHelper(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
})
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
})
// Test adjustHelper with illegal cell reference.
// Test adjustHelper with illegal cell reference
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test adjustHelper on not exists worksheet.
// Test adjustHelper on not exists worksheet
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN does not exist")
}

View File

@ -48,7 +48,7 @@ const (
formulaErrorSPILL = "#SPILL!"
formulaErrorCALC = "#CALC!"
formulaErrorGETTINGDATA = "#GETTING_DATA"
// formula criteria condition enumeration.
// Formula criteria condition enumeration
_ byte = iota
criteriaEq
criteriaLe
@ -100,7 +100,7 @@ const (
)
var (
// tokenPriority defined basic arithmetic operator priority.
// tokenPriority defined basic arithmetic operator priority
tokenPriority = map[string]int{
"^": 5,
"*": 4,

View File

@ -5478,7 +5478,8 @@ func TestCalcSLOP(t *testing.T) {
func TestCalcSHEET(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{
"=SHEET(\"Sheet2\")": "2",
"=SHEET(Sheet2!A1)": "2",
@ -5494,7 +5495,8 @@ func TestCalcSHEET(t *testing.T) {
func TestCalcSHEETS(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{
"=SHEETS(Sheet1!A1:B1)": "1",
"=SHEETS(Sheet1!A1:Sheet1!A1)": "1",

View File

@ -8,7 +8,7 @@ import (
func TestCalcChainReader(t *testing.T) {
f := NewFile()
// Test read calculation chain with unsupported charset.
// Test read calculation chain with unsupported charset
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
_, err := f.calcChainReader()
@ -34,12 +34,12 @@ func TestDeleteCalcChain(t *testing.T) {
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
// Test delete calculation chain with unsupported charset calculation chain.
// Test delete calculation chain with unsupported charset calculation chain
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8")
// Test delete calculation chain with unsupported charset content types.
// Test delete calculation chain with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")

View File

@ -685,8 +685,9 @@ type FormulaOpts struct {
// return
// }
// }
// if err := f.AddTable("Sheet1", "A1", "C2",
// `{"table_name":"Table1","table_style":"TableStyleMedium2"}`); err != nil {
// if err := f.AddTable("Sheet1", "A1:C2", &excelize.TableOptions{
// Name: "Table1", StyleName: "TableStyleMedium2",
// }); err != nil {
// fmt.Println(err)
// return
// }

View File

@ -37,13 +37,20 @@ func TestConcurrency(t *testing.T) {
uint64(1<<32 - 1), true, complex64(5 + 10i),
}))
// Concurrency create style
style, err := f.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
style, err := f.NewStyle(&Style{Font: &Font{Color: "#1265BE", Underline: "single"}})
assert.NoError(t, err)
// Concurrency set cell style
assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style))
// Concurrency add picture
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`))
&PictureOptions{
OffsetX: 10,
OffsetY: 10,
Hyperlink: "https://github.com/xuri/excelize",
HyperlinkType: "External",
Positioning: "oneCell",
},
))
// Concurrency get cell picture
name, raw, err := f.GetPicture("Sheet1", "A1")
assert.Equal(t, "", name)
@ -556,7 +563,7 @@ func TestSetCellFormula(t *testing.T) {
for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} {
assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row))
}
assert.NoError(t, f.AddTable("Sheet1", "A1", "C2", `{"table_name":"Table1","table_style":"TableStyleMedium2"}`))
assert.NoError(t, f.AddTable("Sheet1", "A1:C2", &TableOptions{Name: "Table1", StyleName: "TableStyleMedium2"}))
formulaType = STCellFormulaTypeDataTable
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx")))
@ -874,7 +881,7 @@ func TestSharedStringsError(t *testing.T) {
assert.Equal(t, "1", f.getFromStringItem(1))
// Cleanup undelete temporary files
assert.NoError(t, os.Remove(tempFile.(string)))
// Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows.
// Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err)

467
chart.go
View File

@ -12,7 +12,6 @@
package excelize
import (
"encoding/json"
"encoding/xml"
"fmt"
"strconv"
@ -480,28 +479,41 @@ var (
// parseChartOptions provides a function to parse the format settings of the
// chart with default value.
func parseChartOptions(opts string) (*chartOptions, error) {
options := chartOptions{
Dimension: chartDimensionOptions{
Width: 480,
Height: 290,
},
Format: pictureOptions{
FPrintsWithSheet: true,
XScale: 1,
YScale: 1,
},
Legend: chartLegendOptions{
Position: "bottom",
},
Title: chartTitleOptions{
Name: " ",
},
VaryColors: true,
ShowBlanksAs: "gap",
func parseChartOptions(opts *Chart) (*Chart, error) {
if opts == nil {
return nil, ErrParameterInvalid
}
err := json.Unmarshal([]byte(opts), &options)
return &options, err
if opts.Dimension.Width == nil {
opts.Dimension.Width = intPtr(defaultChartDimensionWidth)
}
if opts.Dimension.Height == nil {
opts.Dimension.Height = intPtr(defaultChartDimensionHeight)
}
if opts.Format.PrintObject == nil {
opts.Format.PrintObject = boolPtr(true)
}
if opts.Format.Locked == nil {
opts.Format.Locked = boolPtr(false)
}
if opts.Format.XScale == nil {
opts.Format.XScale = float64Ptr(defaultPictureScale)
}
if opts.Format.YScale == nil {
opts.Format.YScale = float64Ptr(defaultPictureScale)
}
if opts.Legend.Position == nil {
opts.Legend.Position = stringPtr(defaultChartLegendPosition)
}
if opts.Title.Name == "" {
opts.Title.Name = " "
}
if opts.VaryColors == nil {
opts.VaryColors = boolPtr(true)
}
if opts.ShowBlanksAs == "" {
opts.ShowBlanksAs = defaultChartShowBlanksAs
}
return opts, nil
}
// AddChart provides the method to add chart in a sheet by given chart format
@ -518,66 +530,53 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// )
//
// func main() {
// categories := map[string]string{
// "A2": "Small", "A3": "Normal", "A4": "Large",
// "B1": "Apple", "C1": "Orange", "D1": "Pear"}
// values := map[string]int{
// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
// f := excelize.NewFile()
// for k, v := range categories {
// f.SetCellValue("Sheet1", k, v)
// for idx, row := range [][]interface{}{
// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
// } {
// cell, err := excelize.CoordinatesToCellName(1, idx+1)
// if err != nil {
// fmt.Println(err)
// return
// }
// for k, v := range values {
// f.SetCellValue("Sheet1", k, v)
// f.SetSheetRow("Sheet1", cell, &row)
// }
// if err := f.AddChart("Sheet1", "E1", `{
// "type": "col3DClustered",
// "series": [
// positionBottom := "bottom"
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
// Type: "col3DClustered",
// Series: []excelize.ChartSeries{
// {
// "name": "Sheet1!$A$2",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$2:$D$2"
// Name: "Sheet1!$A$2",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$2:$D$2",
// },
// {
// "name": "Sheet1!$A$3",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$3:$D$3"
// Name: "Sheet1!$A$3",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$3:$D$3",
// },
// {
// "name": "Sheet1!$A$4",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$4:$D$4"
// }],
// "title":
// {
// "name": "Fruit 3D Clustered Column Chart"
// Name: "Sheet1!$A$4",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$4:$D$4",
// },
// "legend":
// {
// "none": false,
// "position": "bottom",
// "show_legend_key": false
// },
// "plotarea":
// {
// "show_bubble_size": true,
// "show_cat_name": false,
// "show_leader_lines": false,
// "show_percent": true,
// "show_series_name": true,
// "show_val": true
// Title: excelize.ChartTitle{
// Name: "Fruit 3D Clustered Column Chart",
// },
// "show_blanks_as": "zero",
// "x_axis":
// {
// "reverse_order": true
// Legend: excelize.ChartLegend{
// None: false, Position: &positionBottom, ShowLegendKey: false,
// },
// "y_axis":
// {
// "maximum": 7.5,
// "minimum": 0.5
// }
// }`); err != nil {
// PlotArea: excelize.ChartPlotArea{
// ShowBubbleSize: true,
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
@ -651,21 +650,21 @@ func parseChartOptions(opts string) (*chartOptions, error) {
//
// The series options that can be set are:
//
// name
// categories
// values
// line
// marker
// Name
// Categories
// Values
// Line
// Marker
//
// name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The name property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1
// Name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The 'Name' property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1
//
// categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the categories property is optional and the chart will just assume a sequential series from 1..n.
// Categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the 'Categories' property is optional and the chart will just assume a sequential series from 1..n.
//
// values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays.
// Values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays.
//
// line: This sets the line format of the line chart. The line property is optional and if it isn't supplied it will default style. The options that can be set are width and color. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt. The value for color should be represented in hex format (e.g., #000000 - #FFFFFF)
// Line: This sets the line format of the line chart. The 'Line' property is optional and if it isn't supplied it will default style. The options that can be set are width and color. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt. The value for color should be represented in hex format (e.g., #000000 - #FFFFFF)
//
// marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'symbol' are (default value is 'auto'):
// Marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'Symbol' are (default value is 'auto'):
//
// circle
// dash
@ -682,13 +681,13 @@ func parseChartOptions(opts string) (*chartOptions, error) {
//
// Set properties of the chart legend. The options that can be set are:
//
// none
// position
// show_legend_key
// None
// Position
// ShowLegendKey
//
// none: Specified if show the legend without overlapping the chart. The default value is 'false'.
// None: Specified if show the legend without overlapping the chart. The default value is 'false'.
//
// position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are:
// Position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are:
//
// top
// bottom
@ -696,15 +695,15 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// right
// top_right
//
// show_legend_key: Set the legend keys shall be shown in data labels. The default value is false.
// ShowLegendKey: Set the legend keys shall be shown in data labels. The default value is false.
//
// Set properties of the chart title. The properties that can be set are:
//
// title
// Title
//
// name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheet name. The name property is optional. The default is to have no chart title.
// Name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheet name. The name property is optional. The default is to have no chart title.
//
// Specifies how blank cells are plotted on the chart by show_blanks_as. The default value is gap. The options that can be set are:
// Specifies how blank cells are plotted on the chart by ShowBlanksAs. The default value is gap. The options that can be set are:
//
// gap
// span
@ -716,80 +715,80 @@ func parseChartOptions(opts string) (*chartOptions, error) {
//
// zero: Specifies that blank values shall be treated as zero.
//
// Specifies that each data marker in the series has a different color by vary_colors. The default value is true.
// Specifies that each data marker in the series has a different color by VaryColors. The default value is true.
//
// Set chart offset, scale, aspect ratio setting and print settings by format, same as function AddPicture.
//
// Set the position of the chart plot area by plotarea. The properties that can be set are:
// Set the position of the chart plot area by PlotArea. The properties that can be set are:
//
// show_bubble_size
// show_cat_name
// show_leader_lines
// show_percent
// show_series_name
// show_val
// ShowBubbleSize
// ShowCatName
// ShowLeaderLines
// ShowPercent
// ShowSerName
// ShowVal
//
// show_bubble_size: Specifies the bubble size shall be shown in a data label. The show_bubble_size property is optional. The default value is false.
// ShowBubbleSize: Specifies the bubble size shall be shown in a data label. The ShowBubbleSize property is optional. The default value is false.
//
// show_cat_name: Specifies that the category name shall be shown in the data label. The show_cat_name property is optional. The default value is true.
// ShowCatName: Specifies that the category name shall be shown in the data label. The ShowCatName property is optional. The default value is true.
//
// show_leader_lines: Specifies leader lines shall be shown for data labels. The show_leader_lines property is optional. The default value is false.
// ShowLeaderLines: Specifies leader lines shall be shown for data labels. The ShowLeaderLines property is optional. The default value is false.
//
// show_percent: Specifies that the percentage shall be shown in a data label. The show_percent property is optional. The default value is false.
// ShowPercent: Specifies that the percentage shall be shown in a data label. The ShowPercent property is optional. The default value is false.
//
// show_series_name: Specifies that the series name shall be shown in a data label. The show_series_name property is optional. The default value is false.
// ShowSerName: Specifies that the series name shall be shown in a data label. The ShowSerName property is optional. The default value is false.
//
// show_val: Specifies that the value shall be shown in a data label. The show_val property is optional. The default value is false.
// ShowVal: Specifies that the value shall be shown in a data label. The ShowVal property is optional. The default value is false.
//
// Set the primary horizontal and vertical axis options by x_axis and y_axis. The properties of x_axis that can be set are:
// Set the primary horizontal and vertical axis options by XAxis and YAxis. The properties of XAxis that can be set are:
//
// none
// major_grid_lines
// minor_grid_lines
// tick_label_skip
// reverse_order
// maximum
// minimum
// font
// None
// MajorGridLines
// MinorGridLines
// TickLabelSkip
// ReverseOrder
// Maximum
// Minimum
// Font
//
// The properties of y_axis that can be set are:
// The properties of YAxis that can be set are:
//
// none
// major_grid_lines
// minor_grid_lines
// major_unit
// tick_label_skip
// reverse_order
// maximum
// minimum
// font
// None
// MajorGridLines
// MinorGridLines
// MajorUnit
// TickLabelSkip
// ReverseOrder
// Maximum
// Minimum
// Font
//
// none: Disable axes.
//
// major_grid_lines: Specifies major grid lines.
// MajorGridLines: Specifies major grid lines.
//
// minor_grid_lines: Specifies minor grid lines.
// MinorGridLines: Specifies minor grid lines.
//
// major_unit: Specifies the distance between major ticks. Shall contain a positive floating-point number. The major_unit property is optional. The default value is auto.
// MajorUnit: Specifies the distance between major ticks. Shall contain a positive floating-point number. The MajorUnit property is optional. The default value is auto.
//
// tick_label_skip: Specifies how many tick labels to skip between label that is drawn. The tick_label_skip property is optional. The default value is auto.
// TickLabelSkip: Specifies how many tick labels to skip between label that is drawn. The TickLabelSkip property is optional. The default value is auto.
//
// reverse_order: Specifies that the categories or values on reverse order (orientation of the chart). The reverse_order property is optional. The default value is false.
// ReverseOrder: Specifies that the categories or values on reverse order (orientation of the chart). The ReverseOrder property is optional. The default value is false.
//
// maximum: Specifies that the fixed maximum, 0 is auto. The maximum property is optional. The default value is auto.
// Maximum: Specifies that the fixed maximum, 0 is auto. The Maximum property is optional. The default value is auto.
//
// minimum: Specifies that the fixed minimum, 0 is auto. The minimum property is optional. The default value is auto.
// Minimum: Specifies that the fixed minimum, 0 is auto. The Minimum property is optional. The default value is auto.
//
// font: Specifies that the font of the horizontal and vertical axis. The properties of font that can be set are:
// Font: Specifies that the font of the horizontal and vertical axis. The properties of font that can be set are:
//
// bold
// italic
// underline
// family
// size
// strike
// color
// vertAlign
// Bold
// Italic
// Underline
// Family
// Size
// Strike
// Color
// VertAlign
//
// Set chart size by dimension property. The dimension property is optional. The default width is 480, and height is 290.
//
@ -806,112 +805,100 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// )
//
// func main() {
// categories := map[string]string{
// "A2": "Small", "A3": "Normal", "A4": "Large",
// "B1": "Apple", "C1": "Orange", "D1": "Pear"}
// values := map[string]int{
// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
// f := excelize.NewFile()
// for k, v := range categories {
// f.SetCellValue("Sheet1", k, v)
// }
// for k, v := range values {
// f.SetCellValue("Sheet1", k, v)
// }
// if err := f.AddChart("Sheet1", "E1", `{
// "type": "col",
// "series": [
// {
// "name": "Sheet1!$A$2",
// "categories": "",
// "values": "Sheet1!$B$2:$D$2"
// },
// {
// "name": "Sheet1!$A$3",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$3:$D$3"
// }],
// "format":
// {
// "x_scale": 1.0,
// "y_scale": 1.0,
// "x_offset": 15,
// "y_offset": 10,
// "print_obj": true,
// "lock_aspect_ratio": false,
// "locked": false
// },
// "title":
// {
// "name": "Clustered Column - Line Chart"
// },
// "legend":
// {
// "position": "left",
// "show_legend_key": false
// },
// "plotarea":
// {
// "show_bubble_size": true,
// "show_cat_name": false,
// "show_leader_lines": false,
// "show_percent": true,
// "show_series_name": true,
// "show_val": true
// }
// }`, `{
// "type": "line",
// "series": [
// {
// "name": "Sheet1!$A$4",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$4:$D$4",
// "marker":
// {
// "symbol": "none",
// "size": 10
// }
// }],
// "format":
// {
// "x_scale": 1,
// "y_scale": 1,
// "x_offset": 15,
// "y_offset": 10,
// "print_obj": true,
// "lock_aspect_ratio": false,
// "locked": false
// },
// "legend":
// {
// "position": "right",
// "show_legend_key": false
// },
// "plotarea":
// {
// "show_bubble_size": true,
// "show_cat_name": false,
// "show_leader_lines": false,
// "show_percent": true,
// "show_series_name": true,
// "show_val": true
// }
// }`); err != nil {
// for idx, row := range [][]interface{}{
// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
// } {
// cell, err := excelize.CoordinatesToCellName(1, idx+1)
// if err != nil {
// fmt.Println(err)
// return
// }
// // Save spreadsheet file by the given path.
// f.SetSheetRow("Sheet1", cell, &row)
// }
// enable, disable, scale := true, false, 1.0
// positionLeft, positionRight := "left", "right"
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
// Type: "col",
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$2",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$2:$D$2",
// },
// },
// Format: excelize.Picture{
// XScale: &scale,
// YScale: &scale,
// OffsetX: 15,
// OffsetY: 10,
// PrintObject: &enable,
// LockAspectRatio: false,
// Locked: &disable,
// },
// Title: excelize.ChartTitle{
// Name: "Clustered Column - Line Chart",
// },
// Legend: excelize.ChartLegend{
// Position: &positionLeft, ShowLegendKey: false,
// },
// PlotArea: excelize.ChartPlotArea{
// ShowBubbleSize: true,
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }, &excelize.Chart{
// Type: "line",
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$4",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$4:$D$4",
// Marker: excelize.ChartMarker{
// Symbol: "none", Size: 10,
// },
// },
// },
// Format: excelize.Picture{
// XScale: &scale,
// YScale: &scale,
// OffsetX: 15,
// OffsetY: 10,
// PrintObject: &enable,
// LockAspectRatio: false,
// Locked: &disable,
// },
// Legend: excelize.ChartLegend{
// Position: &positionRight, ShowLegendKey: false,
// },
// PlotArea: excelize.ChartPlotArea{
// ShowBubbleSize: true,
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
// // Save spreadsheet by the given path.
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddChart(sheet, cell, opts string, combo ...string) error {
// Read sheet data.
func (f *File) AddChart(sheet, cell string, chart *Chart, combo ...*Chart) error {
// Read worksheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
options, comboCharts, err := f.getChartOptions(opts, combo)
opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil {
return err
}
@ -922,11 +909,11 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error {
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
err = f.addDrawingChart(sheet, drawingXML, cell, options.Dimension.Width, options.Dimension.Height, drawingRID, &options.Format)
err = f.addDrawingChart(sheet, drawingXML, cell, *opts.Dimension.Width, *opts.Dimension.Height, drawingRID, &opts.Format)
if err != nil {
return err
}
f.addChart(options, comboCharts)
f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err
}
@ -939,7 +926,7 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error {
// format set (such as offset, scale, aspect ratio setting and print settings)
// and properties set. In Excel a chartsheet is a worksheet that only contains
// a chart.
func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
func (f *File) AddChartSheet(sheet string, chart *Chart, combo ...*Chart) error {
// Check if the worksheet already exists
idx, err := f.GetSheetIndex(sheet)
if err != nil {
@ -948,7 +935,7 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
if idx != -1 {
return ErrExistsSheet
}
options, comboCharts, err := f.getChartOptions(opts, combo)
opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil {
return err
}
@ -975,10 +962,10 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
f.prepareChartSheetDrawing(&cs, drawingID, sheet)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
if err = f.addSheetDrawingChart(drawingXML, drawingRID, &options.Format); err != nil {
if err = f.addSheetDrawingChart(drawingXML, drawingRID, &opts.Format); err != nil {
return err
}
f.addChart(options, comboCharts)
f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err
}
@ -996,8 +983,8 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
// getChartOptions provides a function to check format set of the chart and
// create chart format.
func (f *File) getChartOptions(opts string, combo []string) (*chartOptions, []*chartOptions, error) {
var comboCharts []*chartOptions
func (f *File) getChartOptions(opts *Chart, combo []*Chart) (*Chart, []*Chart, error) {
var comboCharts []*Chart
options, err := parseChartOptions(opts)
if err != nil {
return options, comboCharts, err

View File

@ -41,11 +41,20 @@ func TestChartSize(t *testing.T) {
assert.NoError(t, f.SetCellValue(sheet1, cell, v))
}
assert.NoError(t, f.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
`"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+
`{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+
`{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+
`"title":{"name":"3D Clustered Column Chart"}}`))
width, height := 640, 480
assert.NoError(t, f.AddChart("Sheet1", "E4", &Chart{
Type: "col3DClustered",
Dimension: ChartDimension{
Width: &width,
Height: &height,
},
Series: []ChartSeries{
{Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"},
{Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"},
{Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"},
},
Title: ChartTitle{Name: "3D Clustered Column Chart"},
}))
var buffer bytes.Buffer
@ -98,14 +107,14 @@ func TestAddDrawingChart(t *testing.T) {
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddSheetDrawingChart(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSheetDrawingChart(path, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.addSheetDrawingChart(path, 0, &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteDrawing(t *testing.T) {
@ -129,75 +138,124 @@ func TestAddChart(t *testing.T) {
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
assert.EqualError(t, f.AddChart("Sheet1", "P1", ""), "unexpected end of JSON input")
assert.EqualError(t, f.AddChart("Sheet1", "P1", nil), ErrParameterInvalid.Error())
// Test add chart on not exists worksheet.
assert.EqualError(t, f.AddChart("SheetN", "P1", "{}"), "sheet SheetN does not exist")
assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"none":true,"show_legend_key":true},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"font":{"bold":true,"italic":true,"underline":"dbl","color":"#000000"}},"y_axis":{"font":{"bold":false,"italic":false,"underline":"sng","color":"#777777"}}}`))
assert.NoError(t, f.AddChart("Sheet1", "X1", `{"type":"colStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P16", `{"type":"colPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X16", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P30", `{"type":"col3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X30", `{"type":"col3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X45", `{"type":"radar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF1", `{"type":"col3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF16", `{"type":"col3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF30", `{"type":"col3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF45", `{"type":"col3DCone","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN1", `{"type":"col3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN16", `{"type":"col3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN30", `{"type":"col3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN45", `{"type":"col3DPyramid","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV1", `{"type":"col3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV16", `{"type":"col3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV30", `{"type":"col3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV45", `{"type":"col3DCylinder","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P45", `{"type":"col3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P1", `{"type":"line3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30","marker":{"symbol":"none","size":10}, "line":{"color":"#000000"}},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"3D Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`))
assert.NoError(t, f.AddChart("Sheet2", "X1", `{"type":"scatter","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P16", `{"type":"doughnut","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"right","show_legend_key":false},"title":{"name":"Doughnut Chart"},"plotarea":{"show_bubble_size":false,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero","hole_size":30}`))
assert.NoError(t, f.AddChart("Sheet2", "X16", `{"type":"line","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30","marker":{"symbol":"none","size":10}, "line":{"color":"#000000"}},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`))
assert.NoError(t, f.AddChart("Sheet2", "P32", `{"type":"pie3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X32", `{"type":"pie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"gap"}`))
assert.NoError(t, f.AddChart("Sheet2", "P48", `{"type":"bar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X48", `{"type":"barStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P64", `{"type":"barPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked 100% Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X64", `{"type":"bar3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P80", `{"type":"bar3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"maximum":7.5,"minimum":0.5}}`))
assert.NoError(t, f.AddChart("Sheet2", "X80", `{"type":"bar3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"reverse_order":true,"minimum":0},"y_axis":{"reverse_order":true,"maximum":0,"minimum":0}}`))
// area series charts
assert.NoError(t, f.AddChart("Sheet2", "AF1", `{"type":"area","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN1", `{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF16", `{"type":"areaPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN16", `{"type":"area3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF32", `{"type":"area3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN32", `{"type":"area3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
// Test add chart on not exists worksheet
assert.EqualError(t, f.AddChart("SheetN", "P1", nil), "sheet SheetN does not exist")
positionLeft, positionBottom, positionRight, positionTop, positionTopRight := "left", "bottom", "right", "top", "top_right"
maximum, minimum, zero := 7.5, 0.5, .0
series := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"},
}
series2 := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Marker: ChartMarker{Symbol: "none", Size: 10}, Line: ChartLine{Color: "#000000"}},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Line: ChartLine{Width: 0.25}},
}
series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}
format := PictureOptions{
XScale: float64Ptr(defaultPictureScale),
YScale: float64Ptr(defaultPictureScale),
OffsetX: 15,
OffsetY: 10,
PrintObject: boolPtr(true),
LockAspectRatio: false,
Locked: boolPtr(false),
}
legend := ChartLegend{Position: &positionLeft, ShowLegendKey: false}
plotArea := ChartPlotArea{
ShowBubbleSize: true,
ShowCatName: true,
ShowLeaderLines: false,
ShowPercent: true,
ShowSerName: true,
ShowVal: true,
}
for _, c := range []struct {
sheetName, cell string
opts *Chart
}{
{sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: "col", Series: series, Format: format, Legend: ChartLegend{None: true, ShowLegendKey: true}, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Color: "#000000"}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "#777777"}}}},
{sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: "colStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: "colPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "100% Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: "col3DClustered", Series: series, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Clustered Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: "col3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: "col3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: "radar", Series: series, Format: format, Legend: ChartLegend{Position: &positionTopRight, ShowLegendKey: false}, Title: ChartTitle{Name: "Radar Chart"}, PlotArea: plotArea, ShowBlanksAs: "span"}},
{sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: "col3DConeStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF16", opts: &Chart{Type: "col3DConeClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF30", opts: &Chart{Type: "col3DConePercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF45", opts: &Chart{Type: "col3DCone", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN1", opts: &Chart{Type: "col3DPyramidStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN16", opts: &Chart{Type: "col3DPyramidClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN30", opts: &Chart{Type: "col3DPyramidPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN45", opts: &Chart{Type: "col3DPyramid", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV1", opts: &Chart{Type: "col3DCylinderStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: "col3DCylinderClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: "col3DCylinderPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV45", opts: &Chart{Type: "col3DCylinder", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: "col3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: "line3D", Series: series2, Format: format, Legend: ChartLegend{Position: &positionTop, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Line Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}},
{sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: "scatter", Series: series, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "Scatter Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: "doughnut", Series: series3, Format: format, Legend: ChartLegend{Position: &positionRight, ShowLegendKey: false}, Title: ChartTitle{Name: "Doughnut Chart"}, PlotArea: ChartPlotArea{ShowBubbleSize: false, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "zero", HoleSize: 30}},
{sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: "line", Series: series2, Format: format, Legend: ChartLegend{Position: &positionTop, ShowLegendKey: false}, Title: ChartTitle{Name: "Line Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}},
{sheetName: "Sheet2", cell: "P32", opts: &Chart{Type: "pie3D", Series: series3, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: "pie", Series: series3, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "Pie Chart"}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "gap"}},
// bar series chart
{sheetName: "Sheet2", cell: "P48", opts: &Chart{Type: "bar", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Clustered Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X48", opts: &Chart{Type: "barStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: "barPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked 100% Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: "bar3DClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Clustered Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: "bar3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{Maximum: &maximum, Minimum: &minimum}}},
{sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: "bar3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}},
// area series chart
{sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: "area", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: "areaStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF16", opts: &Chart{Type: "areaPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D 100% Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN16", opts: &Chart{Type: "area3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF32", opts: &Chart{Type: "area3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN32", opts: &Chart{Type: "area3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// cylinder series chart
assert.NoError(t, f.AddChart("Sheet2", "AF48", `{"type":"bar3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF64", `{"type":"bar3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF80", `{"type":"bar3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
{sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: "bar3DCylinderStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: "bar3DCylinderClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: "bar3DCylinderPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// cone series chart
assert.NoError(t, f.AddChart("Sheet2", "AN48", `{"type":"bar3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN64", `{"type":"bar3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN80", `{"type":"bar3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV48", `{"type":"bar3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV64", `{"type":"bar3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV80", `{"type":"bar3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
{sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: "bar3DConeStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: "bar3DConeClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: "bar3DConePercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: "bar3DPyramidStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: "bar3DPyramidClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: "bar3DPyramidPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// surface series chart
assert.NoError(t, f.AddChart("Sheet2", "AV1", `{"type":"surface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`))
assert.NoError(t, f.AddChart("Sheet2", "AV16", `{"type":"wireframeSurface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Wireframe Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`))
assert.NoError(t, f.AddChart("Sheet2", "AV32", `{"type":"contour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "BD1", `{"type":"wireframeContour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Wireframe Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
{sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: "surface3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}},
{sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: "wireframeSurface3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Wireframe Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}},
{sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: "contour", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: "wireframeContour", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Wireframe Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// bubble chart
assert.NoError(t, f.AddChart("Sheet2", "BD16", `{"type":"bubble","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "BD32", `{"type":"bubble3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
{sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: "bubble", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: "bubble3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
// pie of pie chart
assert.NoError(t, f.AddChart("Sheet2", "BD48", `{"type":"pieOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Pie of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
{sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: "pieOfPie", Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Pie of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
// bar of pie chart
assert.NoError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
{sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: "barOfPie", Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
} {
assert.NoError(t, f.AddChart(c.sheetName, c.cell, c.opts))
}
// combo chart
f.NewSheet("Combo Charts")
_, err = f.NewSheet("Combo Charts")
assert.NoError(t, err)
clusteredColumnCombo := [][]string{
{"A1", "line", "Clustered Column - Line Chart"},
{"I1", "bubble", "Clustered Column - Bubble Chart"},
@ -205,7 +263,7 @@ func TestAddChart(t *testing.T) {
{"Y1", "doughnut", "Clustered Column - Doughnut Chart"},
}
for _, props := range clusteredColumnCombo {
assert.NoError(t, f.AddChart("Combo Charts", props[0], fmt.Sprintf(`{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[2]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1])))
assert.NoError(t, f.AddChart("Combo Charts", props[0], &Chart{Type: "col", Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[2]}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1], Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}))
}
stackedAreaCombo := map[string][]string{
"A16": {"line", "Stacked Area - Line Chart"},
@ -214,25 +272,25 @@ func TestAddChart(t *testing.T) {
"Y16": {"doughnut", "Stacked Area - Doughnut Chart"},
}
for axis, props := range stackedAreaCombo {
assert.NoError(t, f.AddChart("Combo Charts", axis, fmt.Sprintf(`{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[0])))
assert.NoError(t, f.AddChart("Combo Charts", axis, &Chart{Type: "areaStacked", Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[1]}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0], Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}))
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
// Test with invalid sheet name
assert.EqualError(t, f.AddChart("Sheet:1", "A1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}]}`), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddChart("Sheet:1", "A1", &Chart{Type: "col", Series: series[:1]}), ErrSheetNameInvalid.Error())
// Test with illegal cell reference
assert.EqualError(t, f.AddChart("Sheet2", "A", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddChart("Sheet2", "A", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), "unsupported chart type unknown")
assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: "unknown", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}), "unsupported chart type unknown")
// Test add combo chart with invalid format set
assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`, ""), "unexpected end of JSON input")
assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}, nil), ErrParameterInvalid.Error())
// Test add combo chart with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown")
assert.EqualError(t, f.AddChart("Sheet2", "BD64", &Chart{Type: "barOfPie", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}, &Chart{Type: "unknown", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}), "unsupported chart type unknown")
assert.NoError(t, f.Close())
// Test add chart with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddChart("Sheet1", "P1", &Chart{Type: "col", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: ChartTitle{Name: "2D Column Chart"}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddChartSheet(t *testing.T) {
@ -245,7 +303,12 @@ func TestAddChartSheet(t *testing.T) {
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
assert.NoError(t, f.AddChartSheet("Chart1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`))
series := []ChartSeries{
{Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"},
{Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"},
{Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"},
}
assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}))
// Test set the chartsheet as active sheet
var sheetIdx int
for idx, sheetName := range f.GetSheetList() {
@ -259,11 +322,12 @@ func TestAddChartSheet(t *testing.T) {
// Test cell value on chartsheet
assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet")
// Test add chartsheet on already existing name sheet
assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), ErrExistsSheet.Error())
assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrExistsSheet.Error())
// Test add chartsheet with invalid sheet name
assert.EqualError(t, f.AddChartSheet("Sheet:1", "A1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrSheetNameInvalid.Error())
// Test with unsupported chart type
assert.EqualError(t, f.AddChartSheet("Chart2", `{"type":"unknown","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "unsupported chart type unknown")
assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: "unknown", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), "unsupported chart type unknown")
assert.NoError(t, f.UpdateLinkedValue())
@ -272,14 +336,43 @@ func TestAddChartSheet(t *testing.T) {
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChartSheet("Chart4", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddChartSheet("Chart4", &Chart{Type: "col", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: ChartTitle{Name: "2D Column Chart"}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.DeleteChart("Sheet1", "A1"))
assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
positionLeft := "left"
series := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"},
}
format := PictureOptions{
XScale: float64Ptr(defaultPictureScale),
YScale: float64Ptr(defaultPictureScale),
OffsetX: 15,
OffsetY: 10,
PrintObject: boolPtr(true),
LockAspectRatio: false,
Locked: boolPtr(false),
}
legend := ChartLegend{Position: &positionLeft, ShowLegendKey: false}
plotArea := ChartPlotArea{
ShowBubbleSize: true,
ShowCatName: true,
ShowLeaderLines: false,
ShowPercent: true,
ShowSerName: true,
ShowVal: true,
}
assert.NoError(t, f.AddChart("Sheet1", "P1", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}))
assert.NoError(t, f.DeleteChart("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx")))
// Test delete chart with invalid sheet name
@ -322,37 +415,22 @@ func TestChartWithLogarithmicBase(t *testing.T) {
for cell, v := range categories {
assert.NoError(t, f.SetCellValue(sheet1, cell, v))
}
series := []ChartSeries{{Name: "value", Categories: "Sheet1!$A$1:$A$19", Values: "Sheet1!$B$1:$B$10"}}
dimension := []int{640, 480, 320, 240}
for _, c := range []struct {
cell string
opts *Chart
}{
{cell: "C1", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[0], Height: &dimension[1]}, Series: series, Title: ChartTitle{Name: "Line chart without log scaling"}}},
{cell: "M1", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[0], Height: &dimension[1]}, Series: series, Title: ChartTitle{Name: "Line chart with log 10.5 scaling"}, YAxis: ChartAxis{LogBase: 10.5}}},
{cell: "A25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1.9 scaling"}, YAxis: ChartAxis{LogBase: 1.9}}},
{cell: "F25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 2 scaling"}, YAxis: ChartAxis{LogBase: 2}}},
{cell: "K25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1000.1 scaling"}, YAxis: ChartAxis{LogBase: 1000.1}}},
{cell: "P25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1000 scaling"}, YAxis: ChartAxis{LogBase: 1000}}},
} {
// Add two chart, one without and one with log scaling
assert.NoError(t, f.AddChart(sheet1, "C1",
`{"type":"line","dimension":{"width":640, "height":480},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"title":{"name":"Line chart without log scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, "M1",
`{"type":"line","dimension":{"width":640, "height":480},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"y_axis":{"logbase":10.5},`+
`"title":{"name":"Line chart with log 10 scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, "A25",
`{"type":"line","dimension":{"width":320, "height":240},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"y_axis":{"logbase":1.9},`+
`"title":{"name":"Line chart with log 1.9 scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, "F25",
`{"type":"line","dimension":{"width":320, "height":240},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"y_axis":{"logbase":2},`+
`"title":{"name":"Line chart with log 2 scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, "K25",
`{"type":"line","dimension":{"width":320, "height":240},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"y_axis":{"logbase":1000.1},`+
`"title":{"name":"Line chart with log 1000.1 scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, "P25",
`{"type":"line","dimension":{"width":320, "height":240},`+
`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
`"y_axis":{"logbase":1000},`+
`"title":{"name":"Line chart with log 1000 scaling"}}`))
assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts))
}
// Export XLSX file for human confirmation
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))

View File

@ -151,7 +151,6 @@ func TestGetColsError(t *testing.T) {
func TestColsRows(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet1")
_, err := f.Cols("Sheet1")
assert.NoError(t, err)
@ -231,7 +230,8 @@ func TestColumnVisibility(t *testing.T) {
// Test set column visible with invalid sheet name
assert.EqualError(t, f.SetColVisible("Sheet:1", "A", false), ErrSheetNameInvalid.Error())
f.NewSheet("Sheet3")
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetColVisible("Sheet3", "E", false))
assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), newInvalidColumnNameError("-1").Error())
assert.EqualError(t, f.SetColVisible("SheetN", "E", false), "sheet SheetN does not exist")
@ -253,7 +253,8 @@ func TestOutlineLevel(t *testing.T) {
assert.Equal(t, uint8(0), level)
assert.NoError(t, err)
f.NewSheet("Sheet2")
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4))
level, err = f.GetColOutlineLevel("Sheet1", "D")
@ -318,7 +319,8 @@ func TestOutlineLevel(t *testing.T) {
func TestSetColStyle(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello"))
styleID, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`)
styleID, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#94d3a2"}, Pattern: 1}})
assert.NoError(t, err)
// Test set column style on not exists worksheet
assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN does not exist")
@ -410,7 +412,7 @@ func TestInsertCols(t *testing.T) {
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3"))
assert.NoError(t, f.AutoFilter(sheet1, "A2", "B2", `{"column":"B","expression":"x != blanks"}`))
assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", &AutoFilterOptions{Column: "B", Expression: "x != blanks"}))
assert.NoError(t, f.InsertCols(sheet1, "A", 1))
// Test insert column with illegal cell reference

View File

@ -51,7 +51,8 @@ func TestDataValidation(t *testing.T) {
assert.NoError(t, f.SaveAs(resultFile))
f.NewSheet("Sheet2")
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet2", "A2", &[]interface{}{"B2", 1}))
assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3}))
dvRange = NewDataValidation(true)

View File

@ -147,6 +147,13 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) {
// Category | A categorization of the content of this package.
// |
// Version | The version number. This value is set by the user or by the application.
// |
// Modified | The created time of the content of the resource which
// | represent in ISO 8601 UTC format, for example "2019-06-04T22:00:10Z".
// |
// Modified | The modified time of the content of the resource which
// | represent in ISO 8601 UTC format, for example "2019-06-04T22:00:10Z".
// |
//
// For example:
//

View File

@ -58,7 +58,7 @@ func TestGetAppProps(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test get application properties with unsupported charset.
// Test get application properties with unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
_, err = f.GetAppProps()
@ -110,7 +110,7 @@ func TestGetDocProps(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test get workbook properties with unsupported charset.
// Test get workbook properties with unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
_, err = f.GetDocProps()

View File

@ -55,7 +55,7 @@ func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet
// addChart provides a function to create chart as xl/charts/chart%d.xml by
// given format sets.
func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) {
func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
count := f.countCharts()
xlsxChartSpace := xlsxChartSpace{
XMLNSa: NameSpaceDrawingML.Value,
@ -139,7 +139,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) {
},
PlotArea: &cPlotArea{},
Legend: &cLegend{
LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])},
LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[*opts.Legend.Position])},
Overlay: &attrValBool{Val: boolPtr(false)},
},
@ -180,7 +180,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) {
},
},
}
plotAreaFunc := map[string]func(*chartOptions) *cPlotArea{
plotAreaFunc := map[string]func(*Chart) *cPlotArea{
Area: f.drawBaseChart,
AreaStacked: f.drawBaseChart,
AreaPercentStacked: f.drawBaseChart,
@ -264,7 +264,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) {
// drawBaseChart provides a function to draw the c:plotArea element for bar,
// and column series charts by given format sets.
func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea {
func (f *File) drawBaseChart(opts *Chart) *cPlotArea {
c := cCharts{
BarDir: &attrValString{
Val: stringPtr("col"),
@ -273,7 +273,7 @@ func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea {
Val: stringPtr("clustered"),
},
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
Shape: f.drawChartShape(opts),
@ -513,7 +513,7 @@ func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea {
// drawDoughnutChart provides a function to draw the c:plotArea element for
// doughnut chart by given format sets.
func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea {
func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea {
holeSize := 75
if opts.HoleSize > 0 && opts.HoleSize <= 90 {
holeSize = opts.HoleSize
@ -522,7 +522,7 @@ func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea {
return &cPlotArea{
DoughnutChart: &cCharts{
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
HoleSize: &attrValInt{Val: intPtr(holeSize)},
@ -532,7 +532,7 @@ func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea {
// drawLineChart provides a function to draw the c:plotArea element for line
// chart by given format sets.
func (f *File) drawLineChart(opts *chartOptions) *cPlotArea {
func (f *File) drawLineChart(opts *Chart) *cPlotArea {
return &cPlotArea{
LineChart: &cCharts{
Grouping: &attrValString{
@ -555,7 +555,7 @@ func (f *File) drawLineChart(opts *chartOptions) *cPlotArea {
// drawLine3DChart provides a function to draw the c:plotArea element for line
// chart by given format sets.
func (f *File) drawLine3DChart(opts *chartOptions) *cPlotArea {
func (f *File) drawLine3DChart(opts *Chart) *cPlotArea {
return &cPlotArea{
Line3DChart: &cCharts{
Grouping: &attrValString{
@ -578,11 +578,11 @@ func (f *File) drawLine3DChart(opts *chartOptions) *cPlotArea {
// drawPieChart provides a function to draw the c:plotArea element for pie
// chart by given format sets.
func (f *File) drawPieChart(opts *chartOptions) *cPlotArea {
func (f *File) drawPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{
PieChart: &cCharts{
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
},
@ -591,11 +591,11 @@ func (f *File) drawPieChart(opts *chartOptions) *cPlotArea {
// drawPie3DChart provides a function to draw the c:plotArea element for 3D
// pie chart by given format sets.
func (f *File) drawPie3DChart(opts *chartOptions) *cPlotArea {
func (f *File) drawPie3DChart(opts *Chart) *cPlotArea {
return &cPlotArea{
Pie3DChart: &cCharts{
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
},
@ -604,14 +604,14 @@ func (f *File) drawPie3DChart(opts *chartOptions) *cPlotArea {
// drawPieOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets.
func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea {
func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{
OfPieChart: &cCharts{
OfPieType: &attrValString{
Val: stringPtr("pie"),
},
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
SerLines: &attrValString{},
@ -621,14 +621,14 @@ func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea {
// drawBarOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets.
func (f *File) drawBarOfPieChart(opts *chartOptions) *cPlotArea {
func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{
OfPieChart: &cCharts{
OfPieType: &attrValString{
Val: stringPtr("bar"),
},
VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors),
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
SerLines: &attrValString{},
@ -638,7 +638,7 @@ func (f *File) drawBarOfPieChart(opts *chartOptions) *cPlotArea {
// drawRadarChart provides a function to draw the c:plotArea element for radar
// chart by given format sets.
func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea {
func (f *File) drawRadarChart(opts *Chart) *cPlotArea {
return &cPlotArea{
RadarChart: &cCharts{
RadarStyle: &attrValString{
@ -661,7 +661,7 @@ func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea {
// drawScatterChart provides a function to draw the c:plotArea element for
// scatter chart by given format sets.
func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea {
func (f *File) drawScatterChart(opts *Chart) *cPlotArea {
return &cPlotArea{
ScatterChart: &cCharts{
ScatterStyle: &attrValString{
@ -684,7 +684,7 @@ func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea {
// drawSurface3DChart provides a function to draw the c:surface3DChart element by
// given format sets.
func (f *File) drawSurface3DChart(opts *chartOptions) *cPlotArea {
func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
Surface3DChart: &cCharts{
Ser: f.drawChartSeries(opts),
@ -706,7 +706,7 @@ func (f *File) drawSurface3DChart(opts *chartOptions) *cPlotArea {
// drawSurfaceChart provides a function to draw the c:surfaceChart element by
// given format sets.
func (f *File) drawSurfaceChart(opts *chartOptions) *cPlotArea {
func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
SurfaceChart: &cCharts{
Ser: f.drawChartSeries(opts),
@ -728,7 +728,7 @@ func (f *File) drawSurfaceChart(opts *chartOptions) *cPlotArea {
// drawChartShape provides a function to draw the c:shape element by given
// format sets.
func (f *File) drawChartShape(opts *chartOptions) *attrValString {
func (f *File) drawChartShape(opts *Chart) *attrValString {
shapes := map[string]string{
Bar3DConeClustered: "cone",
Bar3DConeStacked: "cone",
@ -760,7 +760,7 @@ func (f *File) drawChartShape(opts *chartOptions) *attrValString {
// drawChartSeries provides a function to draw the c:ser element by given
// format sets.
func (f *File) drawChartSeries(opts *chartOptions) *[]cSer {
func (f *File) drawChartSeries(opts *Chart) *[]cSer {
var ser []cSer
for k := range opts.Series {
ser = append(ser, cSer{
@ -790,7 +790,7 @@ func (f *File) drawChartSeries(opts *chartOptions) *[]cSer {
// drawChartSeriesSpPr provides a function to draw the c:spPr element by given
// format sets.
func (f *File) drawChartSeriesSpPr(i int, opts *chartOptions) *cSpPr {
func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr {
var srgbClr *attrValString
var schemeClr *aSchemeClr
@ -823,7 +823,7 @@ func (f *File) drawChartSeriesSpPr(i int, opts *chartOptions) *cSpPr {
// drawChartSeriesDPt provides a function to draw the c:dPt element by given
// data index and format sets.
func (f *File) drawChartSeriesDPt(i int, opts *chartOptions) []*cDPt {
func (f *File) drawChartSeriesDPt(i int, opts *Chart) []*cDPt {
dpt := []*cDPt{{
IDx: &attrValInt{Val: intPtr(i)},
Bubble3D: &attrValBool{Val: boolPtr(false)},
@ -852,7 +852,7 @@ func (f *File) drawChartSeriesDPt(i int, opts *chartOptions) []*cDPt {
// drawChartSeriesCat provides a function to draw the c:cat element by given
// chart series and format sets.
func (f *File) drawChartSeriesCat(v chartSeriesOptions, opts *chartOptions) *cCat {
func (f *File) drawChartSeriesCat(v ChartSeries, opts *Chart) *cCat {
cat := &cCat{
StrRef: &cStrRef{
F: v.Categories,
@ -867,7 +867,7 @@ func (f *File) drawChartSeriesCat(v chartSeriesOptions, opts *chartOptions) *cCa
// drawChartSeriesVal provides a function to draw the c:val element by given
// chart series and format sets.
func (f *File) drawChartSeriesVal(v chartSeriesOptions, opts *chartOptions) *cVal {
func (f *File) drawChartSeriesVal(v ChartSeries, opts *Chart) *cVal {
val := &cVal{
NumRef: &cNumRef{
F: v.Values,
@ -882,7 +882,7 @@ func (f *File) drawChartSeriesVal(v chartSeriesOptions, opts *chartOptions) *cVa
// drawChartSeriesMarker provides a function to draw the c:marker element by
// given data index and format sets.
func (f *File) drawChartSeriesMarker(i int, opts *chartOptions) *cMarker {
func (f *File) drawChartSeriesMarker(i int, opts *Chart) *cMarker {
defaultSymbol := map[string]*attrValString{Scatter: {Val: stringPtr("circle")}}
marker := &cMarker{
Symbol: defaultSymbol[opts.Type],
@ -917,7 +917,7 @@ func (f *File) drawChartSeriesMarker(i int, opts *chartOptions) *cMarker {
// drawChartSeriesXVal provides a function to draw the c:xVal element by given
// chart series and format sets.
func (f *File) drawChartSeriesXVal(v chartSeriesOptions, opts *chartOptions) *cCat {
func (f *File) drawChartSeriesXVal(v ChartSeries, opts *Chart) *cCat {
cat := &cCat{
StrRef: &cStrRef{
F: v.Categories,
@ -929,7 +929,7 @@ func (f *File) drawChartSeriesXVal(v chartSeriesOptions, opts *chartOptions) *cC
// drawChartSeriesYVal provides a function to draw the c:yVal element by given
// chart series and format sets.
func (f *File) drawChartSeriesYVal(v chartSeriesOptions, opts *chartOptions) *cVal {
func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal {
val := &cVal{
NumRef: &cNumRef{
F: v.Values,
@ -941,7 +941,7 @@ func (f *File) drawChartSeriesYVal(v chartSeriesOptions, opts *chartOptions) *cV
// drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize
// element by given chart series and format sets.
func (f *File) drawCharSeriesBubbleSize(v chartSeriesOptions, opts *chartOptions) *cVal {
func (f *File) drawCharSeriesBubbleSize(v ChartSeries, opts *Chart) *cVal {
if _, ok := map[string]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok {
return nil
}
@ -954,7 +954,7 @@ func (f *File) drawCharSeriesBubbleSize(v chartSeriesOptions, opts *chartOptions
// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element
// by given format sets.
func (f *File) drawCharSeriesBubble3D(opts *chartOptions) *attrValBool {
func (f *File) drawCharSeriesBubble3D(opts *Chart) *attrValBool {
if _, ok := map[string]bool{Bubble3D: true}[opts.Type]; !ok {
return nil
}
@ -963,21 +963,21 @@ func (f *File) drawCharSeriesBubble3D(opts *chartOptions) *attrValBool {
// drawChartDLbls provides a function to draw the c:dLbls element by given
// format sets.
func (f *File) drawChartDLbls(opts *chartOptions) *cDLbls {
func (f *File) drawChartDLbls(opts *Chart) *cDLbls {
return &cDLbls{
ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)},
ShowVal: &attrValBool{Val: boolPtr(opts.Plotarea.ShowVal)},
ShowCatName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowCatName)},
ShowSerName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowSerName)},
ShowBubbleSize: &attrValBool{Val: boolPtr(opts.Plotarea.ShowBubbleSize)},
ShowPercent: &attrValBool{Val: boolPtr(opts.Plotarea.ShowPercent)},
ShowLeaderLines: &attrValBool{Val: boolPtr(opts.Plotarea.ShowLeaderLines)},
ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)},
ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)},
ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)},
ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)},
ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)},
ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)},
}
}
// drawChartSeriesDLbls provides a function to draw the c:dLbls element by
// given format sets.
func (f *File) drawChartSeriesDLbls(opts *chartOptions) *cDLbls {
func (f *File) drawChartSeriesDLbls(opts *Chart) *cDLbls {
dLbls := f.drawChartDLbls(opts)
chartSeriesDLbls := map[string]*cDLbls{
Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil,
@ -989,7 +989,7 @@ func (f *File) drawChartSeriesDLbls(opts *chartOptions) *cDLbls {
}
// drawPlotAreaCatAx provides a function to draw the c:catAx element.
func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs {
func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
max := &attrValFloat{Val: opts.XAxis.Maximum}
min := &attrValFloat{Val: opts.XAxis.Minimum}
if opts.XAxis.Maximum == nil {
@ -1025,10 +1025,10 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs {
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
},
}
if opts.XAxis.MajorGridlines {
if opts.XAxis.MajorGridLines {
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.XAxis.MinorGridlines {
if opts.XAxis.MinorGridLines {
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.XAxis.TickLabelSkip != 0 {
@ -1038,7 +1038,7 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs {
}
// drawPlotAreaValAx provides a function to draw the c:valAx element.
func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs {
func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
max := &attrValFloat{Val: opts.YAxis.Maximum}
min := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil {
@ -1076,10 +1076,10 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs {
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
},
}
if opts.YAxis.MajorGridlines {
if opts.YAxis.MajorGridLines {
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.YAxis.MinorGridlines {
if opts.YAxis.MinorGridLines {
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if pos, ok := valTickLblPos[opts.Type]; ok {
@ -1092,7 +1092,7 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs {
}
// drawPlotAreaSerAx provides a function to draw the c:serAx element.
func (f *File) drawPlotAreaSerAx(opts *chartOptions) []*cAxs {
func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
max := &attrValFloat{Val: opts.YAxis.Maximum}
min := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil {
@ -1139,7 +1139,7 @@ func (f *File) drawPlotAreaSpPr() *cSpPr {
}
// drawPlotAreaTxPr provides a function to draw the c:txPr element.
func (f *File) drawPlotAreaTxPr(opts *chartAxisOptions) *cTxPr {
func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr {
cTxPr := &cTxPr{
BodyPr: aBodyPr{
Rot: -60000000,
@ -1242,7 +1242,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) {
// addDrawingChart provides a function to add chart graphic frame by given
// sheet, drawingXML, cell, width, height, relationship index and format sets.
func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *pictureOptions) error {
func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *PictureOptions) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
@ -1250,8 +1250,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
colIdx := col - 1
rowIdx := row - 1
width = int(float64(width) * opts.XScale)
height = int(float64(height) * opts.YScale)
width = int(float64(width) * *opts.XScale)
height = int(float64(height) * *opts.YScale)
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
@ -1293,8 +1293,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
graphic, _ := xml.Marshal(graphicFrame)
twoCellAnchor.GraphicFrame = string(graphic)
twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet,
FPrintsWithSheet: opts.FPrintsWithSheet,
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
f.Drawings.Store(drawingXML, content)
@ -1304,7 +1304,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
// addSheetDrawingChart provides a function to add chart graphic frame for
// chartsheet by given sheet, drawingXML, width, height, relationship index
// and format sets.
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) error {
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *PictureOptions) error {
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
@ -1336,8 +1336,8 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOpt
graphic, _ := xml.Marshal(graphicFrame)
absoluteAnchor.GraphicFrame = string(graphic)
absoluteAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet,
FPrintsWithSheet: opts.FPrintsWithSheet,
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
f.Drawings.Store(drawingXML, content)

View File

@ -26,13 +26,13 @@ func TestDrawingParser(t *testing.T) {
}
f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
// Test with one cell anchor.
// Test with one cell anchor
_, _, err := f.drawingParser("wsDr")
assert.NoError(t, err)
// Test with unsupported charset.
// Test with unsupported charset
_, _, err = f.drawingParser("charset")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test with alternate content.
// Test with alternate content
f.Drawings = sync.Map{}
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`))
_, _, err = f.drawingParser("wsDr")

View File

@ -71,9 +71,10 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns"))
// Test max characters in a cell
assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2)))
f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.")
_, err = f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.")
assert.EqualError(t, err, ErrSheetNameLength.Error())
// Test set worksheet name with illegal name
f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title.")
assert.EqualError(t, f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title."), ErrSheetNameLength.Error())
assert.EqualError(t, f.SetCellInt("Sheet3", "A23", 10), "sheet Sheet3 does not exist")
assert.EqualError(t, f.SetCellStr("Sheet3", "b230", "10"), "sheet Sheet3 does not exist")
assert.EqualError(t, f.SetCellStr("Sheet10", "b230", "10"), "sheet Sheet10 does not exist")
@ -102,13 +103,13 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, err)
getSharedFormula(&xlsxWorksheet{}, 0, "")
// Test read cell value with given illegal rows number.
// Test read cell value with given illegal rows number
_, err = f.GetCellValue("Sheet2", "a-1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error())
_, err = f.GetCellValue("Sheet2", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test read cell value with given lowercase column number.
// Test read cell value with given lowercase column number
_, err = f.GetCellValue("Sheet2", "a5")
assert.NoError(t, err)
_, err = f.GetCellValue("Sheet2", "C11")
@ -145,7 +146,7 @@ func TestOpenFile(t *testing.T) {
assert.EqualError(t, f.SetCellHyperLink("SheetN", "A1", "Sheet1!A40", "Location"), "sheet SheetN does not exist")
// Test boolean write
booltest := []struct {
boolTest := []struct {
value bool
raw bool
expected string
@ -155,7 +156,7 @@ func TestOpenFile(t *testing.T) {
{false, false, "FALSE"},
{true, false, "TRUE"},
}
for _, test := range booltest {
for _, test := range boolTest {
assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value))
val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw})
assert.NoError(t, err)
@ -175,10 +176,12 @@ func TestOpenFile(t *testing.T) {
// Test read cell value with given cell reference large than exists row
_, err = f.GetCellValue("Sheet2", "E231")
assert.NoError(t, err)
// Test get active worksheet of spreadsheet and get worksheet name of spreadsheet by given worksheet index
// Test get active worksheet of spreadsheet and get worksheet name of
// spreadsheet by given worksheet index
f.GetSheetName(f.GetActiveSheetIndex())
// Test get worksheet index of spreadsheet by given worksheet name
f.GetSheetIndex("Sheet1")
_, err = f.GetSheetIndex("Sheet1")
assert.NoError(t, err)
// Test get worksheet name of spreadsheet by given invalid worksheet index
f.GetSheetName(4)
// Test get worksheet map of workbook
@ -339,31 +342,23 @@ func TestBrokenFile(t *testing.T) {
func TestNewFile(t *testing.T) {
// Test create a spreadsheet file
f := NewFile()
f.NewSheet("Sheet1")
f.NewSheet("XLSXSheet2")
f.NewSheet("XLSXSheet3")
_, err := f.NewSheet("Sheet1")
assert.NoError(t, err)
_, err = f.NewSheet("XLSXSheet2")
assert.NoError(t, err)
_, err = f.NewSheet("XLSXSheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetCellInt("XLSXSheet2", "A23", 56))
assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42"))
f.SetActiveSheet(0)
// Test add picture to sheet with scaling and positioning
err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
`{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
scale := 0.5
assert.NoError(t, f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
&PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"}))
// Test add picture to worksheet without options
err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "")
if !assert.NoError(t, err) {
t.FailNow()
}
// Test add picture to worksheet with invalid options
err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), `{`)
if !assert.Error(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewFile.xlsx")))
assert.NoError(t, f.Save())
@ -385,7 +380,7 @@ func TestSetCellHyperLink(t *testing.T) {
assert.NoError(t, f.SetCellHyperLink("Sheet1", "B19", "https://github.com/xuri/excelize", "External"))
// Test add first hyperlink in a work sheet
assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/xuri/excelize", "External"))
// Test add Location hyperlink in a work sheet.
// Test add Location hyperlink in a work sheet
assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location"))
// Test add Location hyperlink with display & tooltip in a work sheet
display, tooltip := "Display value", "Hover text"
@ -429,9 +424,7 @@ func TestSetCellHyperLink(t *testing.T) {
func TestGetCellHyperLink(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
_, _, err = f.GetCellHyperLink("Sheet1", "")
assert.EqualError(t, err, `invalid cell name ""`)
@ -513,9 +506,9 @@ func TestSetSheetBackgroundErrors(t *testing.T) {
assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8")
}
// TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function
// to a workbook. In the resulting file, the lines 2 and 3 as well as 4 and 5 should have matching
// contents.
// TestWriteArrayFormula tests the extended options of SetCellFormula by writing
// an array function to a workbook. In the resulting file, the lines 2 and 3 as
// well as 4 and 5 should have matching contents
func TestWriteArrayFormula(t *testing.T) {
cell := func(col, row int) string {
c, err := CoordinatesToCellName(col, row)
@ -620,15 +613,11 @@ func TestWriteArrayFormula(t *testing.T) {
func TestSetCellStyleAlignment(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
var style int
style, err = f.NewStyle(`{"alignment":{"horizontal":"center","ident":1,"justify_last_line":true,"reading_order":0,"relative_indent":1,"shrink_to_fit":true,"text_rotation":45,"vertical":"top","wrap_text":true}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Alignment: &Alignment{Horizontal: "center", Indent: 1, JustifyLastLine: true, ReadingOrder: 0, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 45, Vertical: "top", WrapText: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style))
@ -651,13 +640,11 @@ func TestSetCellStyleAlignment(t *testing.T) {
func TestSetCellStyleBorder(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
var style int
// Test set border on overlapping range with vertical variants shading styles gradient fill.
// Test set border on overlapping range with vertical variants shading styles gradient fill
style, err = f.NewStyle(&Style{
Border: []Border{
{Type: "left", Color: "0000FF", Style: 3},
@ -668,24 +655,18 @@ func TestSetCellStyleBorder(t *testing.T) {
{Type: "diagonalUp", Color: "A020F0", Style: 8},
},
})
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "J21", "L25", style))
style, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":1}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style))
style, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":4}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 4}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style))
// Test set border and solid style pattern fill for a single cell.
// Test set border and solid style pattern fill for a single cell
style, err = f.NewStyle(&Style{
Border: []Border{
{
@ -725,9 +706,7 @@ func TestSetCellStyleBorder(t *testing.T) {
Pattern: 1,
},
})
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style))
@ -736,30 +715,18 @@ func TestSetCellStyleBorder(t *testing.T) {
func TestSetCellStyleBorderErrors(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
// Set border with invalid style parameter.
_, err = f.NewStyle("")
if !assert.EqualError(t, err, "unexpected end of JSON input") {
t.FailNow()
}
// Set border with invalid style index number.
_, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":-1},{"type":"top","color":"00FF00","style":14},{"type":"bottom","color":"FFFF00","style":5},{"type":"right","color":"FF0000","style":6},{"type":"diagonalDown","color":"A020F0","style":9},{"type":"diagonalUp","color":"A020F0","style":8}]}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// Set border with invalid style index number
_, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: -1}, {Type: "top", Color: "00FF00", Style: 14}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "diagonalDown", Color: "A020F0", Style: 9}, {Type: "diagonalUp", Color: "A020F0", Style: 8}}})
assert.NoError(t, err)
}
func TestSetCellStyleNumberFormat(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
// Test only set fill and number format for a cell.
// Test only set fill and number format for a cell
col := []string{"L", "M", "N", "O", "P"}
data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
@ -781,7 +748,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
} else {
assert.NoError(t, f.SetCellValue("Sheet2", c, val))
}
style, err := f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format": ` + strconv.Itoa(d) + `}`)
style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 5}, NumFmt: d})
if !assert.NoError(t, err) {
t.FailNow()
}
@ -792,10 +759,8 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
}
}
var style int
style, err = f.NewStyle(`{"number_format":-1}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{NumFmt: -1})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx")))
@ -804,23 +769,17 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3))
var style int
style, err = f.NewStyle(`{"number_format": 188, "decimal_places": -1}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: -1})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(`{"number_format": 188, "decimal_places": 31, "negred": true}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: 31, NegRed: true})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
@ -829,34 +788,24 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
_, err = f.NewStyle(`{"number_format": 26, "lang": "zh-tw"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.NewStyle(&Style{NumFmt: 26, Lang: "zh-tw"})
assert.NoError(t, err)
style, err := f.NewStyle(`{"number_format": 27}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err := f.NewStyle(&Style{NumFmt: 27})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(`{"number_format": 31, "lang": "ko-kr"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{NumFmt: 31, Lang: "ko-kr"})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
style, err = f.NewStyle(`{"number_format": 71, "lang": "th-th"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{NumFmt: 71, Lang: "th-th"})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx")))
@ -867,42 +816,40 @@ func TestSetCellStyleCustomNumberFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`)
customNumFmt := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"
style, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@","font":{"color":"#9A0511"}}`)
style, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt, Font: &Font{Color: "#9A0511"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
_, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@"}`)
customNumFmt = "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@"
_, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx")))
}
func TestSetCellStyleFill(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
var style int
// Test set fill for cell with invalid parameter.
style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":6}}`)
// Test set fill for cell with invalid parameter
style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 6}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF"],"shading":1}}`)
style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF"}, Shading: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
style, err = f.NewStyle(`{"fill":{"type":"pattern","color":[],"pattern":1}}`)
style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{}, Shading: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
style, err = f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":19}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 19}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx")))
@ -910,43 +857,31 @@ func TestSetCellStyleFill(t *testing.T) {
func TestSetCellStyleFont(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
var style int
style, err = f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "#777777", Underline: "single"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style))
style, err = f.NewStyle(`{"font":{"italic":true,"underline":"double"}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Font: &Font{Italic: true, Underline: "double"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style))
style, err = f.NewStyle(`{"font":{"bold":true}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Font: &Font{Bold: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style))
style, err = f.NewStyle(`{"font":{"bold":true,"family":"","size":0,"color":"","underline":""}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Family: "", Size: 0, Color: "", Underline: ""}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style))
style, err = f.NewStyle(`{"font":{"color":"#777777","strike":true}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Font: &Font{Color: "#777777", Strike: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style))
@ -955,40 +890,30 @@ func TestSetCellStyleFont(t *testing.T) {
func TestSetCellStyleProtection(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
var style int
style, err = f.NewStyle(`{"protection":{"hidden":true, "locked":true}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
style, err = f.NewStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style))
err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
}
func TestSetDeleteSheet(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
f.DeleteSheet("XLSXSheet3")
assert.NoError(t, f.DeleteSheet("XLSXSheet3"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx")))
})
t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4()
if !assert.NoError(t, err) {
t.FailNow()
}
f.DeleteSheet("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet1"))
assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A1", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx")))
})
@ -996,11 +921,10 @@ func TestSetDeleteSheet(t *testing.T) {
func TestSheetVisibility(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetSheetVisible("Sheet2", false))
assert.NoError(t, f.SetSheetVisible("Sheet2", false, true))
assert.NoError(t, f.SetSheetVisible("Sheet1", false))
assert.NoError(t, f.SetSheetVisible("Sheet1", true))
visible, err := f.GetSheetVisible("Sheet1")
@ -1058,123 +982,231 @@ func TestConditionalFormat(t *testing.T) {
var format1, format2, format3, format4 int
var err error
// Rose format for bad conditional.
format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// Rose format for bad conditional
format1, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
// Light yellow format for neutral conditional.
format2, err = f.NewConditionalStyle(`{"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// Light yellow format for neutral conditional
format2, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1}})
assert.NoError(t, err)
// Light green format for good conditional.
format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// Light green format for good conditional
format3, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#09600B"}, Fill: Fill{Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1}})
assert.NoError(t, err)
// conditional style with align and left border.
format4, err = f.NewConditionalStyle(`{"alignment":{"wrap_text":true},"border":[{"type":"left","color":"#000000","style":1}]}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// conditional style with align and left border
format4, err = f.NewConditionalStyle(&Style{Alignment: &Alignment{WrapText: true}, Border: []Border{{Type: "left", Color: "#000000", Style: 1}}})
assert.NoError(t, err)
// Color scales: 2 color
assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`))
assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10",
[]ConditionalFormatOptions{
{
Type: "2_color_scale",
Criteria: "=",
MinType: "min",
MaxType: "max",
MinColor: "#F8696B",
MaxColor: "#63BE7B",
},
},
))
// Color scales: 3 color
assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`))
assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10",
[]ConditionalFormatOptions{
{
Type: "3_color_scale",
Criteria: "=",
MinType: "min",
MidType: "percentile",
MaxType: "max",
MinColor: "#F8696B",
MidColor: "#FFEB84",
MaxColor: "#63BE7B",
},
},
))
// Highlight cells rules: between...
assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: "between",
Format: format1,
Minimum: "6",
Maximum: "8",
},
},
))
// Highlight cells rules: Greater Than...
assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: ">",
Format: format3,
Value: "6",
},
},
))
// Highlight cells rules: Equal To...
assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10",
[]ConditionalFormatOptions{
{
Type: "top",
Criteria: "=",
Format: format3,
},
},
))
// Highlight cells rules: Not Equal To...
assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10",
[]ConditionalFormatOptions{
{
Type: "unique",
Criteria: "=",
Format: format2,
},
},
))
// Highlight cells rules: Duplicate Values...
assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10",
[]ConditionalFormatOptions{
{
Type: "duplicate",
Criteria: "=",
Format: format2,
},
},
))
// Top/Bottom rules: Top 10%.
assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10",
[]ConditionalFormatOptions{
{
Type: "top",
Criteria: "=",
Format: format1,
Value: "6",
Percent: true,
},
},
))
// Top/Bottom rules: Above Average...
assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10",
[]ConditionalFormatOptions{
{
Type: "average",
Criteria: "=",
Format: format3,
AboveAverage: true,
},
},
))
// Top/Bottom rules: Below Average...
assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10",
[]ConditionalFormatOptions{
{
Type: "average",
Criteria: "=",
Format: format1,
AboveAverage: false,
},
},
))
// Data Bars: Gradient Fill
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`))
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "data_bar",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Use a formula to determine which cells to format
assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1)))
assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10",
[]ConditionalFormatOptions{
{
Type: "formula",
Criteria: "L2<3",
Format: format1,
},
},
))
// Alignment/Border cells rules
assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"0"}]`, format4)))
// Test set invalid format set in conditional format
assert.EqualError(t, f.SetConditionalFormat(sheet1, "L1:L10", ""), "unexpected end of JSON input")
assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: ">",
Format: format4,
Value: "0",
},
},
))
// Test set conditional format on not exists worksheet
assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", "[]"), "sheet SheetN does not exist")
assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist")
// Test set conditional format with invalid sheet name
assert.EqualError(t, f.SetConditionalFormat("Sheet:1", "L1:L10", "[]"), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.SetConditionalFormat("Sheet:1", "L1:L10", nil), ErrSheetNameInvalid.Error())
err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
// Set conditional format with illegal valid type
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`))
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Set conditional format with illegal criteria type
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`))
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "data_bar",
Criteria: "",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Test create conditional format with invalid custom number format
var exp string
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &exp})
assert.EqualError(t, err, ErrCustomNumFmt.Error())
// Set conditional format with file without dxfs element should not return error
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
_, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "", Color: []string{"#FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.Close())
}
func TestConditionalFormatError(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
fillCells(f, sheet1, 10, 15)
// Set conditional format with illegal JSON string should return error.
_, err := f.NewConditionalStyle("")
if !assert.EqualError(t, err, "unexpected end of JSON input") {
t.FailNow()
}
}
func TestSharedStrings(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.Equal(t, "A", rows[0][0])
rows, err = f.GetRows("Sheet2")
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.Equal(t, "Test Weight (Kgs)", rows[0][0])
assert.NoError(t, f.Close())
}
func TestSetSheetCol(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetSheetCol("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()}))
@ -1190,9 +1222,7 @@ func TestSetSheetCol(t *testing.T) {
func TestSetSheetRow(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()}))
@ -1300,10 +1330,8 @@ func TestProtectSheet(t *testing.T) {
func TestUnprotectSheet(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
// Test remove protection on not exists worksheet.
assert.NoError(t, err)
// Test remove protection on not exists worksheet
assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist")
assert.NoError(t, f.UnprotectSheet("Sheet1"))
@ -1343,7 +1371,7 @@ func TestProtectWorkbook(t *testing.T) {
assert.Equal(t, 24, len(wb.WorkbookProtection.WorkbookSaltValue))
assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue))
assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectWorkbook.xlsx")))
// Test protect workbook with password exceeds the limit length
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "MD4",
@ -1354,13 +1382,15 @@ func TestProtectWorkbook(t *testing.T) {
AlgorithmName: "RIPEMD-160",
Password: "password",
}), ErrUnsupportedHashAlgorithm.Error())
// Test protect workbook with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.ProtectWorkbook(nil), "XML syntax error on line 1: invalid UTF-8")
}
func TestUnprotectWorkbook(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
assert.NoError(t, f.UnprotectWorkbook())
assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error())
@ -1382,6 +1412,10 @@ func TestUnprotectWorkbook(t *testing.T) {
assert.NoError(t, err)
wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8")
// Test remove workbook protection with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UnprotectWorkbook(), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDefaultTimeStyle(t *testing.T) {
@ -1409,7 +1443,7 @@ func TestAddVBAProject(t *testing.T) {
}
func TestContentTypesReader(t *testing.T) {
// Test unsupported charset.
// Test unsupported charset
f := NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
@ -1418,7 +1452,7 @@ func TestContentTypesReader(t *testing.T) {
}
func TestWorkbookReader(t *testing.T) {
// Test unsupported charset.
// Test unsupported charset
f := NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
@ -1427,7 +1461,7 @@ func TestWorkbookReader(t *testing.T) {
}
func TestWorkSheetReader(t *testing.T) {
// Test unsupported charset.
// Test unsupported charset
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
@ -1435,7 +1469,7 @@ func TestWorkSheetReader(t *testing.T) {
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
// Test on no checked worksheet.
// Test on no checked worksheet
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
@ -1445,7 +1479,7 @@ func TestWorkSheetReader(t *testing.T) {
}
func TestRelsReader(t *testing.T) {
// Test unsupported charset.
// Test unsupported charset
f := NewFile()
rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil)
@ -1463,7 +1497,7 @@ func TestDeleteSheetFromWorkbookRels(t *testing.T) {
func TestUpdateLinkedValue(t *testing.T) {
f := NewFile()
// Test update lined value with unsupported charset workbook.
// Test update lined value with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
@ -1482,16 +1516,21 @@ func prepareTestBook1() (*File, error) {
return nil, err
}
err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`)
if err != nil {
if err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
&PictureOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}); err != nil {
return nil, err
}
// Test add picture to worksheet with offset, external hyperlink and positioning.
err = f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"),
`{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`)
if err != nil {
// Test add picture to worksheet with offset, external hyperlink and positioning
if err := f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"),
&PictureOptions{
OffsetX: 10,
OffsetY: 10,
Hyperlink: "https://github.com/xuri/excelize",
HyperlinkType: "External",
Positioning: "oneCell",
},
); err != nil {
return nil, err
}
@ -1500,7 +1539,7 @@ func prepareTestBook1() (*File, error) {
return nil, err
}
err = f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".jpg", file)
err = f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".jpg", file, nil)
if err != nil {
return nil, err
}
@ -1510,9 +1549,12 @@ func prepareTestBook1() (*File, error) {
func prepareTestBook3() (*File, error) {
f := NewFile()
f.NewSheet("Sheet1")
f.NewSheet("XLSXSheet2")
f.NewSheet("XLSXSheet3")
if _, err := f.NewSheet("XLSXSheet2"); err != nil {
return nil, err
}
if _, err := f.NewSheet("XLSXSheet3"); err != nil {
return nil, err
}
if err := f.SetCellInt("XLSXSheet2", "A23", 56); err != nil {
return nil, err
}
@ -1520,18 +1562,14 @@ func prepareTestBook3() (*File, error) {
return nil, err
}
f.SetActiveSheet(0)
err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
`{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`)
if err != nil {
scale := 0.5
if err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
&PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"}); err != nil {
return nil, err
}
err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "")
if err != nil {
if err := f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil); err != nil {
return nil, err
}
return f, nil
}

15
lib.go
View File

@ -313,15 +313,15 @@ func sortCoordinates(coordinates []int) error {
// coordinatesToRangeRef provides a function to convert a pair of coordinates
// to range reference.
func (f *File) coordinatesToRangeRef(coordinates []int) (string, error) {
func (f *File) coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) {
if len(coordinates) != 4 {
return "", ErrCoordinates
}
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1])
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...)
if err != nil {
return "", err
}
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3])
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...)
if err != nil {
return "", err
}
@ -493,15 +493,6 @@ func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
return nil
}
// fallbackOptions provides a method to convert format string to []byte and
// handle empty string.
func fallbackOptions(opts string) []byte {
if opts != "" {
return []byte(opts)
}
return []byte("{}")
}
// namespaceStrictToTransitional provides a method to convert Strict and
// Transitional namespaces.
func namespaceStrictToTransitional(content []byte) []byte {

View File

@ -37,7 +37,8 @@ func TestMergeCell(t *testing.T) {
assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value)
assert.NoError(t, err)
f.NewSheet("Sheet3")
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.MergeCell("Sheet3", "D11", "F13"))
assert.NoError(t, f.MergeCell("Sheet3", "G10", "K12"))

View File

@ -13,7 +13,6 @@ package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"image"
"io"
@ -26,14 +25,28 @@ import (
// parsePictureOptions provides a function to parse the format settings of
// the picture with default value.
func parsePictureOptions(opts string) (*pictureOptions, error) {
format := pictureOptions{
FPrintsWithSheet: true,
XScale: 1,
YScale: 1,
func parsePictureOptions(opts *PictureOptions) *PictureOptions {
if opts == nil {
return &PictureOptions{
PrintObject: boolPtr(true),
Locked: boolPtr(false),
XScale: float64Ptr(defaultPictureScale),
YScale: float64Ptr(defaultPictureScale),
}
err := json.Unmarshal(fallbackOptions(opts), &format)
return &format, err
}
if opts.PrintObject == nil {
opts.PrintObject = boolPtr(true)
}
if opts.Locked == nil {
opts.Locked = boolPtr(false)
}
if opts.XScale == nil {
opts.XScale = float64Ptr(defaultPictureScale)
}
if opts.YScale == nil {
opts.YScale = float64Ptr(defaultPictureScale)
}
return opts
}
// AddPicture provides the method to add picture in a sheet by given picture
@ -44,6 +57,7 @@ func parsePictureOptions(opts string) (*pictureOptions, error) {
// package main
//
// import (
// "fmt"
// _ "image/gif"
// _ "image/jpeg"
// _ "image/png"
@ -54,15 +68,33 @@ func parsePictureOptions(opts string) (*pictureOptions, error) {
// func main() {
// f := excelize.NewFile()
// // Insert a picture.
// if err := f.AddPicture("Sheet1", "A2", "image.jpg", ""); err != nil {
// if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil {
// fmt.Println(err)
// }
// // Insert a picture scaling in the cell with location hyperlink.
// if err := f.AddPicture("Sheet1", "D2", "image.png", `{"x_scale": 0.5, "y_scale": 0.5, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`); err != nil {
// enable, scale := true, 0.5
// if err := f.AddPicture("Sheet1", "D2", "image.png",
// &excelize.PictureOptions{
// XScale: &scale,
// YScale: &scale,
// Hyperlink: "#Sheet2!D8",
// HyperlinkType: "Location",
// },
// ); err != nil {
// fmt.Println(err)
// }
// // Insert a picture offset in the cell with external hyperlink, printing and positioning support.
// if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "print_obj": true, "lock_aspect_ratio": false, "locked": false, "positioning": "oneCell"}`); err != nil {
// if err := f.AddPicture("Sheet1", "H2", "image.gif",
// &excelize.PictureOptions{
// PrintObject: &enable,
// LockAspectRatio: false,
// OffsetX: 15,
// OffsetY: 10,
// Hyperlink: "https://github.com/xuri/excelize",
// HyperlinkType: "External",
// Positioning: "oneCell",
// },
// ); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
@ -70,42 +102,42 @@ func parsePictureOptions(opts string) (*pictureOptions, error) {
// }
// }
//
// The optional parameter "autofit" specifies if you make image size auto-fits the
// The optional parameter "Autofit" specifies if you make image size auto-fits the
// cell, the default value of that is 'false'.
//
// The optional parameter "hyperlink" specifies the hyperlink of the image.
// The optional parameter "Hyperlink" specifies the hyperlink of the image.
//
// The optional parameter "hyperlink_type" defines two types of
// The optional parameter "HyperlinkType" defines two types of
// hyperlink "External" for website or "Location" for moving to one of the
// cells in this workbook. When the "hyperlink_type" is "Location",
// coordinates need to start with "#".
//
// The optional parameter "positioning" defines two types of the position of an
// The optional parameter "Positioning" defines two types of the position of an
// image in an Excel spreadsheet, "oneCell" (Move but don't size with
// cells) or "absolute" (Don't move or size with cells). If you don't set this
// parameter, the default positioning is move and size with cells.
//
// The optional parameter "print_obj" indicates whether the image is printed
// The optional parameter "PrintObject" indicates whether the image is printed
// when the worksheet is printed, the default value of that is 'true'.
//
// The optional parameter "lock_aspect_ratio" indicates whether lock aspect
// The optional parameter "LockAspectRatio" indicates whether lock aspect
// ratio for the image, the default value of that is 'false'.
//
// The optional parameter "locked" indicates whether lock the image. Locking
// The optional parameter "Locked" indicates whether lock the image. Locking
// an object has no effect unless the sheet is protected.
//
// The optional parameter "x_offset" specifies the horizontal offset of the
// The optional parameter "OffsetX" specifies the horizontal offset of the
// image with the cell, the default value of that is 0.
//
// The optional parameter "x_scale" specifies the horizontal scale of images,
// The optional parameter "XScale" specifies the horizontal scale of images,
// the default value of that is 1.0 which presents 100%.
//
// The optional parameter "y_offset" specifies the vertical offset of the
// The optional parameter "OffsetY" specifies the vertical offset of the
// image with the cell, the default value of that is 0.
//
// The optional parameter "y_scale" specifies the vertical scale of images,
// The optional parameter "YScale" specifies the vertical scale of images,
// the default value of that is 1.0 which presents 100%.
func (f *File) AddPicture(sheet, cell, picture, format string) error {
func (f *File) AddPicture(sheet, cell, picture string, opts *PictureOptions) error {
var err error
// Check picture exists first.
if _, err = os.Stat(picture); os.IsNotExist(err) {
@ -117,7 +149,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
}
file, _ := os.ReadFile(filepath.Clean(picture))
_, name := filepath.Split(picture)
return f.AddPictureFromBytes(sheet, cell, format, name, ext, file)
return f.AddPictureFromBytes(sheet, cell, name, ext, file, opts)
}
// AddPictureFromBytes provides the method to add picture in a sheet by given
@ -143,24 +175,21 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
// if err != nil {
// fmt.Println(err)
// }
// if err := f.AddPictureFromBytes("Sheet1", "A2", "", "Excel Logo", ".jpg", file); err != nil {
// if err := f.AddPictureFromBytes("Sheet1", "A2", "Excel Logo", ".jpg", file, nil); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, file []byte) error {
func (f *File) AddPictureFromBytes(sheet, cell, name, extension string, file []byte, opts *PictureOptions) error {
var drawingHyperlinkRID int
var hyperlinkType string
ext, ok := supportedImageTypes[extension]
if !ok {
return ErrImgExt
}
options, err := parsePictureOptions(opts)
if err != nil {
return err
}
options := parsePictureOptions(opts)
img, _, err := image.DecodeConfig(bytes.NewReader(file))
if err != nil {
return err
@ -276,20 +305,20 @@ func (f *File) countDrawings() int {
// addDrawingPicture provides a function to add picture by given sheet,
// drawingXML, cell, file name, width, height relationship index and format
// sets.
func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, hyperlinkRID int, img image.Config, opts *pictureOptions) error {
func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, hyperlinkRID int, img image.Config, opts *PictureOptions) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
width, height := img.Width, img.Height
if opts.Autofit {
if opts.AutoFit {
width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
if err != nil {
return err
}
} else {
width = int(float64(width) * opts.XScale)
height = int(float64(height) * opts.YScale)
width = int(float64(width) * *opts.XScale)
height = int(float64(height) * *opts.YScale)
}
col--
row--
@ -313,7 +342,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID,
twoCellAnchor.From = &from
twoCellAnchor.To = &to
pic := xlsxPic{}
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.NoChangeAspect
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio
pic.NvPicPr.CNvPr.ID = cNvPrID
pic.NvPicPr.CNvPr.Descr = file
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
@ -342,8 +371,8 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID,
twoCellAnchor.Pic = &pic
twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet,
FPrintsWithSheet: opts.FPrintsWithSheet,
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.Lock()
defer content.Unlock()
@ -682,7 +711,7 @@ func (f *File) drawingsWriter() {
}
// drawingResize calculate the height and width after resizing.
func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pictureOptions) (w, h, c, r int, err error) {
func (f *File) drawingResize(sheet, cell string, width, height float64, opts *PictureOptions) (w, h, c, r int, err error) {
var mergeCells []MergeCell
mergeCells, err = f.GetMergeCells(sheet)
if err != nil {
@ -725,6 +754,6 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pi
height, width = float64(cellHeight), width*asp
}
width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY)
w, h = int(width*opts.XScale), int(height*opts.YScale)
w, h = int(width**opts.XScale), int(height**opts.YScale)
return
}

View File

@ -25,7 +25,7 @@ func BenchmarkAddPictureFromBytes(b *testing.B) {
}
b.ResetTimer()
for i := 1; i <= b.N; i++ {
if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "excel", ".png", imgFile); err != nil {
if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "excel", ".png", imgFile, nil); err != nil {
b.Error(err)
}
}
@ -37,32 +37,33 @@ func TestAddPicture(t *testing.T) {
// Test add picture to worksheet with offset and location hyperlink
assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`))
&PictureOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}))
// Test add picture to worksheet with offset, external hyperlink and positioning
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`))
&PictureOptions{OffsetX: 10, OffsetY: 10, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell"}))
file, err := os.ReadFile(filepath.Join("test", "images", "excel.png"))
assert.NoError(t, err)
// Test add picture to worksheet with autofit
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), `{"x_offset": 10, "y_offset": 10, "autofit": true}`))
f.NewSheet("AddPicture")
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{OffsetX: 10, OffsetY: 10, AutoFit: true}))
_, err = f.NewSheet("AddPicture")
assert.NoError(t, err)
assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9"))
assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1"))
assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}))
// Test add picture to worksheet from bytes
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".png", file, nil))
// Test add picture to worksheet from bytes with illegal cell reference
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", "", "Excel Logo", ".png", file), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", "Excel Logo", ".png", file, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, f.AddPicture("Sheet1", "Q8", filepath.Join("test", "images", "excel.gif"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q8", filepath.Join("test", "images", "excel.gif"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), nil))
// Test write file to given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx")))
@ -72,10 +73,10 @@ func TestAddPicture(t *testing.T) {
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".png", file, nil), "XML syntax error on line 1: invalid UTF-8")
// Test add picture with invalid sheet name
assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), ""), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), nil), ErrSheetNameInvalid.Error())
}
func TestAddPictureErrors(t *testing.T) {
@ -83,14 +84,14 @@ func TestAddPictureErrors(t *testing.T) {
assert.NoError(t, err)
// Test add picture to worksheet with invalid file path
assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), ""))
assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), nil))
// Test add picture to worksheet with unsupported file type
assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), ""), ErrImgExt.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1)), ErrImgExt.Error())
assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), nil), ErrImgExt.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "Excel Logo", "jpg", make([]byte, 1), nil), ErrImgExt.Error())
// Test add picture to worksheet with invalid file data
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1)), image.ErrFormat.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "Excel Logo", ".jpg", make([]byte, 1), nil), image.ErrFormat.Error())
// Test add picture with custom image decoder and encoder
decode := func(r io.Reader) (image.Image, error) { return nil, nil }
@ -100,18 +101,19 @@ func TestAddPictureErrors(t *testing.T) {
image.RegisterFormat("emz", "", decode, decodeConfig)
image.RegisterFormat("wmz", "", decode, decodeConfig)
image.RegisterFormat("svg", "", decode, decodeConfig)
assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", `{"x_scale": 2.1}`))
assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), nil))
xScale := 2.1
assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &PictureOptions{XScale: &xScale}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx")))
assert.NoError(t, f.Close())
}
func TestGetPicture(t *testing.T) {
f := NewFile()
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
name, content, err := f.GetPicture("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 13233, len(content))
@ -196,19 +198,20 @@ func TestGetPicture(t *testing.T) {
func TestAddDrawingPicture(t *testing.T) {
// Test addDrawingPicture with illegal cell reference
f := NewFile()
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
opts := &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)}
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, opts), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, opts), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddPictureFromBytes(t *testing.T) {
f := NewFile()
imgFile, err := os.ReadFile("logo.png")
assert.NoError(t, err, "Unable to load logo for test")
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "logo", ".png", imgFile, nil))
imageCount := 0
f.Pkg.Range(func(fileName, v interface{}) bool {
if strings.Contains(fileName.(string), "media/image") {
@ -217,16 +220,16 @@ func TestAddPictureFromBytes(t *testing.T) {
return true
})
assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.")
assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN does not exist")
assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil), "sheet SheetN does not exist")
// Test add picture from bytes with invalid sheet name
assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil), ErrSheetNameInvalid.Error())
}
func TestDeletePicture(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.DeletePicture("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx")))
// Test delete picture on not exists worksheet
@ -251,7 +254,7 @@ func TestDrawingResize(t *testing.T) {
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
func TestSetContentTypePartImageExtensions(t *testing.T) {

View File

@ -27,26 +27,26 @@ import (
// PivotStyleDark1 - PivotStyleDark28
type PivotTableOptions struct {
pivotTableSheetName string
DataRange string `json:"data_range"`
PivotTableRange string `json:"pivot_table_range"`
Rows []PivotTableField `json:"rows"`
Columns []PivotTableField `json:"columns"`
Data []PivotTableField `json:"data"`
Filter []PivotTableField `json:"filter"`
RowGrandTotals bool `json:"row_grand_totals"`
ColGrandTotals bool `json:"col_grand_totals"`
ShowDrill bool `json:"show_drill"`
UseAutoFormatting bool `json:"use_auto_formatting"`
PageOverThenDown bool `json:"page_over_then_down"`
MergeItem bool `json:"merge_item"`
CompactData bool `json:"compact_data"`
ShowError bool `json:"show_error"`
ShowRowHeaders bool `json:"show_row_headers"`
ShowColHeaders bool `json:"show_col_headers"`
ShowRowStripes bool `json:"show_row_stripes"`
ShowColStripes bool `json:"show_col_stripes"`
ShowLastColumn bool `json:"show_last_column"`
PivotTableStyleName string `json:"pivot_table_style_name"`
DataRange string
PivotTableRange string
Rows []PivotTableField
Columns []PivotTableField
Data []PivotTableField
Filter []PivotTableField
RowGrandTotals bool
ColGrandTotals bool
ShowDrill bool
UseAutoFormatting bool
PageOverThenDown bool
MergeItem bool
CompactData bool
ShowError bool
ShowRowHeaders bool
ShowColHeaders bool
ShowRowStripes bool
ShowColStripes bool
ShowLastColumn bool
PivotTableStyleName string
}
// PivotTableField directly maps the field settings of the pivot table.
@ -69,12 +69,12 @@ type PivotTableOptions struct {
// Name specifies the name of the data field. Maximum 255 characters
// are allowed in data field name, excess characters will be truncated.
type PivotTableField struct {
Compact bool `json:"compact"`
Data string `json:"data"`
Name string `json:"name"`
Outline bool `json:"outline"`
Subtotal string `json:"subtotal"`
DefaultSubtotal bool `json:"default_subtotal"`
Compact bool
Data string
Name string
Outline bool
Subtotal string
DefaultSubtotal bool
}
// AddPivotTable provides the method to add pivot table by given pivot table

View File

@ -109,7 +109,8 @@ func TestAddPivotTable(t *testing.T) {
ShowLastColumn: true,
PivotTableStyleName: "PivotStyleLight19",
}))
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet2!$A$1:$AR$15",
@ -232,7 +233,7 @@ func TestAddPivotTable(t *testing.T) {
Rows: []PivotTableField{{Data: "Year"}},
}), ErrSheetNameInvalid.Error())
// Test adjust range with invalid range
_, _, err := f.adjustRange("")
_, _, err = f.adjustRange("")
assert.EqualError(t, err, ErrParameterRequired.Error())
// Test adjust range with incorrect range
_, _, err = f.adjustRange("sheet1!")

View File

@ -189,7 +189,8 @@ func TestRowHeight(t *testing.T) {
// Test set row height with custom default row height with prepare XML
assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10"))
f.NewSheet("Sheet2")
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet2", "A2", true))
height, err = f.GetRowHeight("Sheet2", 1)
assert.NoError(t, err)
@ -258,10 +259,9 @@ func TestSharedStringsReader(t *testing.T) {
func TestRowVisibility(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
f.NewSheet("Sheet3")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, f.SetRowVisible("Sheet3", 2, true))
visible, err := f.GetRowVisible("Sheet3", 2)
@ -320,7 +320,7 @@ func TestRemoveRow(t *testing.T) {
t.FailNow()
}
err = f.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
err = f.AutoFilter(sheet1, "A2:A2", &AutoFilterOptions{Column: "A", Expression: "x != blanks"})
if !assert.NoError(t, err) {
t.FailNow()
}
@ -990,9 +990,9 @@ func TestCheckRow(t *testing.T) {
func TestSetRowStyle(t *testing.T) {
f := NewFile()
style1, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#63BE7B"],"pattern":1}}`)
style1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#63BE7B"}, Pattern: 1}})
assert.NoError(t, err)
style2, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`)
style2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#E0EBF5"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1))
assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error())

102
shape.go
View File

@ -12,26 +12,38 @@
package excelize
import (
"encoding/json"
"strconv"
"strings"
)
// parseShapeOptions provides a function to parse the format settings of the
// shape with default value.
func parseShapeOptions(opts string) (*shapeOptions, error) {
options := shapeOptions{
Width: 160,
Height: 160,
Format: pictureOptions{
FPrintsWithSheet: true,
XScale: 1,
YScale: 1,
},
Line: lineOptions{Width: 1},
func parseShapeOptions(opts *Shape) (*Shape, error) {
if opts == nil {
return nil, ErrParameterInvalid
}
err := json.Unmarshal([]byte(opts), &options)
return &options, err
if opts.Width == nil {
opts.Width = intPtr(defaultShapeSize)
}
if opts.Height == nil {
opts.Height = intPtr(defaultShapeSize)
}
if opts.Format.PrintObject == nil {
opts.Format.PrintObject = boolPtr(true)
}
if opts.Format.Locked == nil {
opts.Format.Locked = boolPtr(false)
}
if opts.Format.XScale == nil {
opts.Format.XScale = float64Ptr(defaultPictureScale)
}
if opts.Format.YScale == nil {
opts.Format.YScale = float64Ptr(defaultPictureScale)
}
if opts.Line.Width == nil {
opts.Line.Width = float64Ptr(defaultShapeLineWidth)
}
return opts, nil
}
// AddShape provides the method to add shape in a sheet by given worksheet
@ -39,33 +51,29 @@ func parseShapeOptions(opts string) (*shapeOptions, error) {
// print settings) and properties set. For example, add text box (rect shape)
// in Sheet1:
//
// err := f.AddShape("Sheet1", "G6", `{
// "type": "rect",
// "color":
// width, height, lineWidth := 180, 90, 1.2
// err := f.AddShape("Sheet1", "G6",
// &excelize.Shape{
// Type: "rect",
// Color: excelize.ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
// Paragraph: []excelize.ShapeParagraph{
// {
// "line": "#4286F4",
// "fill": "#8eb9ff"
// Text: "Rectangle Shape",
// Font: excelize.Font{
// Bold: true,
// Italic: true,
// Family: "Times New Roman",
// Size: 36,
// Color: "#777777",
// Underline: "sng",
// },
// "paragraph": [
// {
// "text": "Rectangle Shape",
// "font":
// {
// "bold": true,
// "italic": true,
// "family": "Times New Roman",
// "size": 36,
// "color": "#777777",
// "underline": "sng"
// }
// }],
// "width": 180,
// "height": 90,
// "line":
// {
// "width": 1.2
// }
// }`)
// },
// },
// Width: &width,
// Height: &height,
// Line: excelize.ShapeLine{Width: &lineWidth},
// },
// )
//
// The following shows the type of shape supported by excelize:
//
@ -277,7 +285,7 @@ func parseShapeOptions(opts string) (*shapeOptions, error) {
// wavy
// wavyHeavy
// wavyDbl
func (f *File) AddShape(sheet, cell, opts string) error {
func (f *File) AddShape(sheet, cell string, opts *Shape) error {
options, err := parseShapeOptions(opts)
if err != nil {
return err
@ -313,7 +321,7 @@ func (f *File) AddShape(sheet, cell, opts string) error {
// addDrawingShape provides a function to add preset geometry by given sheet,
// drawingXMLand format sets.
func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOptions) error {
func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error {
fromCol, fromRow, err := CellNameToCoordinates(cell)
if err != nil {
return err
@ -321,8 +329,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
colIdx := fromCol - 1
rowIdx := fromRow - 1
width := int(float64(opts.Width) * opts.Format.XScale)
height := int(float64(opts.Height) * opts.Format.YScale)
width := int(float64(*opts.Width) * *opts.Format.XScale)
height := int(float64(*opts.Height) * *opts.Format.YScale)
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY,
width, height)
@ -381,9 +389,9 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
},
},
}
if opts.Line.Width != 1 {
if *opts.Line.Width != 1 {
shape.SpPr.Ln = xlsxLineProperties{
W: f.ptToEMUs(opts.Line.Width),
W: f.ptToEMUs(*opts.Line.Width),
}
}
defaultFont, err := f.GetDefaultFont()
@ -391,7 +399,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
return err
}
if len(opts.Paragraph) < 1 {
opts.Paragraph = []shapeParagraphOptions{
opts.Paragraph = []ShapeParagraph{
{
Font: Font{
Bold: false,
@ -443,8 +451,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
}
twoCellAnchor.Sp = &shape
twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.Format.FLocksWithSheet,
FPrintsWithSheet: opts.Format.FPrintsWithSheet,
FLocksWithSheet: *opts.Format.Locked,
FPrintsWithSheet: *opts.Format.PrintObject,
}
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
f.Drawings.Store(drawingXML, content)

View File

@ -12,97 +12,88 @@ func TestAddShape(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddShape("Sheet1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`))
assert.NoError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`))
assert.NoError(t, f.AddShape("Sheet1", "C30", `{"type":"rect","paragraph":[]}`))
assert.EqualError(t, f.AddShape("Sheet3", "H1", `{
"type": "ellipseRibbon",
"color":
{
"line": "#4286f4",
"fill": "#8eb9ff"
shape := &Shape{
Type: "rect",
Paragraph: []ShapeParagraph{
{Text: "Rectangle", Font: Font{Color: "CD5C5C"}},
{Text: "Shape", Font: Font{Bold: true, Color: "2980B9"}},
},
"paragraph": [
{
"font":
{
"bold": true,
"italic": true,
"family": "Times New Roman",
"size": 36,
"color": "#777777",
"underline": "single"
}
}],
"height": 90
}`), "sheet Sheet3 does not exist")
assert.EqualError(t, f.AddShape("Sheet3", "H1", ""), "unexpected end of JSON input")
assert.EqualError(t, f.AddShape("Sheet1", "A", `{
"type": "rect",
"paragraph": [
assert.NoError(t, f.AddShape("Sheet1", "A30", shape))
assert.NoError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}))
assert.NoError(t, f.AddShape("Sheet1", "C30", &Shape{Type: "rect"}))
assert.EqualError(t, f.AddShape("Sheet3", "H1",
&Shape{
Type: "ellipseRibbon",
Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
Paragraph: []ShapeParagraph{
{
"text": "Rectangle",
"font":
{
"color": "CD5C5C"
}
Font: Font{
Bold: true,
Italic: true,
Family: "Times New Roman",
Size: 36,
Color: "#777777",
Underline: "single",
},
{
"text": "Shape",
"font":
{
"bold": true,
"color": "2980B9"
}
}]
}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
},
},
},
), "sheet Sheet3 does not exist")
assert.EqualError(t, f.AddShape("Sheet3", "H1", nil), ErrParameterInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet1", "A", shape), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))
// Test add first shape for given sheet
f = NewFile()
assert.NoError(t, f.AddShape("Sheet1", "A1", `{
"type": "ellipseRibbon",
"color":
width, height := 1.2, 90
assert.NoError(t, f.AddShape("Sheet1", "A1",
&Shape{
Type: "ellipseRibbon",
Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
Paragraph: []ShapeParagraph{
{
"line": "#4286f4",
"fill": "#8eb9ff"
Font: Font{
Bold: true,
Italic: true,
Family: "Times New Roman",
Size: 36,
Color: "#777777",
Underline: "single",
},
"paragraph": [
{
"font":
{
"bold": true,
"italic": true,
"family": "Times New Roman",
"size": 36,
"color": "#777777",
"underline": "single"
}
}],
"height": 90,
"line":
{
"width": 1.2
}
}`))
},
},
Height: &height,
Line: ShapeLine{Width: &width},
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test add shape with invalid sheet name
assert.EqualError(t, f.AddShape("Sheet:1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet:1", "A30", shape), ErrSheetNameInvalid.Error())
// Test add shape with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
// Test add shape with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddDrawingShape(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", &shapeOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1",
&Shape{
Width: intPtr(defaultShapeSize),
Height: intPtr(defaultShapeSize),
Format: PictureOptions{
PrintObject: boolPtr(true),
Locked: boolPtr(false),
XScale: float64Ptr(defaultPictureScale),
YScale: float64Ptr(defaultPictureScale),
},
},
), "XML syntax error on line 1: invalid UTF-8")
}

125
sheet.go
View File

@ -13,7 +13,6 @@ package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
@ -45,7 +44,7 @@ func (f *File) NewSheet(sheet string) (int, error) {
if index != -1 {
return index, err
}
f.DeleteSheet(sheet)
_ = f.DeleteSheet(sheet)
f.SheetCount++
wb, _ := f.workbookReader()
sheetID := 0
@ -682,19 +681,24 @@ func (f *File) copySheet(from, to int) error {
return err
}
// SetSheetVisible provides a function to set worksheet visible by given worksheet
// name. A workbook must contain at least one visible worksheet. If the given
// worksheet has been activated, this setting will be invalidated. Sheet state
// values as defined by https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues
//
// visible
// hidden
// veryHidden
// getSheetState returns sheet visible enumeration by given hidden status.
func getSheetState(visible bool, veryHidden []bool) string {
state := "hidden"
if !visible && len(veryHidden) > 0 && veryHidden[0] {
state = "veryHidden"
}
return state
}
// SetSheetVisible provides a function to set worksheet visible by given
// worksheet name. A workbook must contain at least one visible worksheet. If
// the given worksheet has been activated, this setting will be invalidated.
// The third optional veryHidden parameter only works when visible was false.
//
// For example, hide Sheet1:
//
// err := f.SetSheetVisible("Sheet1", false)
func (f *File) SetSheetVisible(sheet string, visible bool) error {
func (f *File) SetSheetVisible(sheet string, visible bool, veryHidden ...bool) error {
if err := checkSheetName(sheet); err != nil {
return err
}
@ -710,9 +714,9 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error {
}
return err
}
count := 0
count, state := 0, getSheetState(visible, veryHidden)
for _, v := range wb.Sheets.Sheet {
if v.State != "hidden" {
if v.State != state {
count++
}
}
@ -726,45 +730,37 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error {
tabSelected = ws.SheetViews.SheetView[0].TabSelected
}
if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected {
wb.Sheets.Sheet[k].State = "hidden"
wb.Sheets.Sheet[k].State = state
}
}
return err
}
// parsePanesOptions provides a function to parse the panes settings.
func parsePanesOptions(opts string) (*panesOptions, error) {
format := panesOptions{}
err := json.Unmarshal([]byte(opts), &format)
return &format, err
}
// setPanes set create freeze panes and split panes by given options.
func (ws *xlsxWorksheet) setPanes(panes string) error {
opts, err := parsePanesOptions(panes)
if err != nil {
return err
func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
if panes == nil {
return ErrParameterInvalid
}
p := &xlsxPane{
ActivePane: opts.ActivePane,
TopLeftCell: opts.TopLeftCell,
XSplit: float64(opts.XSplit),
YSplit: float64(opts.YSplit),
ActivePane: panes.ActivePane,
TopLeftCell: panes.TopLeftCell,
XSplit: float64(panes.XSplit),
YSplit: float64(panes.YSplit),
}
if opts.Freeze {
if panes.Freeze {
p.State = "frozen"
}
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
}
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p
if !(opts.Freeze) && !(opts.Split) {
if !(panes.Freeze) && !(panes.Split) {
if len(ws.SheetViews.SheetView) > 0 {
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil
}
}
var s []*xlsxSelection
for _, p := range opts.Panes {
for _, p := range panes.Panes {
s = append(s, &xlsxSelection{
ActiveCell: p.ActiveCell,
Pane: p.Pane,
@ -772,17 +768,17 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
})
}
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s
return err
return nil
}
// SetPanes provides a function to create and remove freeze panes and split panes
// by given worksheet name and panes options.
//
// activePane defines the pane that is active. The possible values for this
// ActivePane defines the pane that is active. The possible values for this
// attribute are defined in the following table:
//
// Enumeration Value | Description
// --------------------------------+-------------------------------------------------------------
// ---------------------------------+-------------------------------------------------------------
// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal
// | splits are applied.
// |
@ -816,7 +812,7 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
// Pane state type is restricted to the values supported currently listed in the following table:
//
// Enumeration Value | Description
// --------------------------------+-------------------------------------------------------------
// ---------------------------------+-------------------------------------------------------------
// frozen (Frozen) | Panes are frozen, but were not split being frozen. In
// | this state, when the panes are unfrozen again, a single
// | pane results, with no split.
@ -826,40 +822,74 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
// split (Split) | Panes are split, but not frozen. In this state, the split
// | bars are adjustable by the user.
//
// x_split (Horizontal Split Position): Horizontal position of the split, in
// XSplit (Horizontal Split Position): Horizontal position of the split, in
// 1/20th of a point; 0 (zero) if none. If the pane is frozen, this value
// indicates the number of columns visible in the top pane.
//
// y_split (Vertical Split Position): Vertical position of the split, in 1/20th
// YSplit (Vertical Split Position): Vertical position of the split, in 1/20th
// of a point; 0 (zero) if none. If the pane is frozen, this value indicates the
// number of rows visible in the left pane. The possible values for this
// attribute are defined by the W3C XML Schema double datatype.
//
// top_left_cell: Location of the top left visible cell in the bottom right pane
// TopLeftCell: Location of the top left visible cell in the bottom right pane
// (when in Left-To-Right mode).
//
// sqref (Sequence of References): Range of the selection. Can be non-contiguous
// SQRef (Sequence of References): Range of the selection. Can be non-contiguous
// set of ranges.
//
// An example of how to freeze column A in the Sheet1 and set the active cell on
// Sheet1!K16:
//
// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`)
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: true,
// Split: false,
// XSplit: 1,
// YSplit: 0,
// TopLeftCell: "B1",
// ActivePane: "topRight",
// Panes: []excelize.PaneOptions{
// {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
// },
// })
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
// ranges on Sheet1!A11:XFD11:
//
// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`)
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: true,
// Split: false,
// XSplit: 0,
// YSplit: 9,
// TopLeftCell: "A34",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
// },
// })
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
// f.SetPanes("Sheet1", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`)
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: false,
// Split: true,
// XSplit: 3270,
// YSplit: 1800,
// TopLeftCell: "N57",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
// {SQRef: "I36", ActiveCell: "I36"},
// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
// {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
// {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"},
// },
// })
//
// An example of how to unfreeze and remove all panes on Sheet1:
//
// f.SetPanes("Sheet1", `{"freeze":false,"split":false}`)
func (f *File) SetPanes(sheet, panes string) error {
// err := f.SetPanes("Sheet1", &excelize.Panes{Freeze: false, Split: false})
func (f *File) SetPanes(sheet string, panes *Panes) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
@ -870,7 +900,7 @@ func (f *File) SetPanes(sheet, panes string) error {
// GetSheetVisible provides a function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1:
//
// f.GetSheetVisible("Sheet1")
// visible, err := f.GetSheetVisible("Sheet1")
func (f *File) GetSheetVisible(sheet string) (bool, error) {
var visible bool
if err := checkSheetName(sheet); err != nil {
@ -1509,6 +1539,9 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
// Scope: "Sheet2",
// })
func (f *File) SetDefinedName(definedName *DefinedName) error {
if definedName.Name == "" || definedName.RefersTo == "" {
return ErrParameterInvalid
}
wb, err := f.workbookReader()
if err != nil {
return err

View File

@ -15,14 +15,15 @@ import (
func TestNewSheet(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
sheetID, err := f.NewSheet("sheet2")
assert.NoError(t, err)
f.SetActiveSheet(sheetID)
// Test delete original sheet
idx, err := f.GetSheetIndex("Sheet1")
assert.NoError(t, err)
f.DeleteSheet(f.GetSheetName(idx))
assert.NoError(t, f.DeleteSheet(f.GetSheetName(idx)))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
// Test create new worksheet with already exists name
sheetID, err = f.NewSheet("Sheet2")
@ -38,24 +39,79 @@ func TestNewSheet(t *testing.T) {
func TestSetPanes(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`))
f.NewSheet("Panes 2")
assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
f.NewSheet("Panes 3")
assert.NoError(t, f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`))
f.NewSheet("Panes 4")
assert.NoError(t, f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`))
assert.EqualError(t, f.SetPanes("Panes 4", ""), "unexpected end of JSON input")
assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN does not exist")
assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false}))
_, err := f.NewSheet("Panes 2")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 2",
&Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
},
))
_, err = f.NewSheet("Panes 3")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 3",
&Panes{
Freeze: false,
Split: true,
XSplit: 3270,
YSplit: 1800,
TopLeftCell: "N57",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
{SQRef: "I36", ActiveCell: "I36"},
{SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
{SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
{SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"},
},
},
))
_, err = f.NewSheet("Panes 4")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 4",
&Panes{
Freeze: true,
Split: false,
XSplit: 0,
YSplit: 9,
TopLeftCell: "A34",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
{SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
},
},
))
assert.EqualError(t, f.SetPanes("Panes 4", nil), ErrParameterInvalid.Error())
assert.EqualError(t, f.SetPanes("SheetN", nil), "sheet SheetN does not exist")
// Test set panes with invalid sheet name
assert.EqualError(t, f.SetPanes("Sheet:1", `{"freeze":false,"split":false}`), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
// Test add pane on empty sheet views worksheet
f = NewFile()
f.checked = nil
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
assert.NoError(t, f.SetPanes("Sheet1",
&Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
},
))
}
func TestSearchSheet(t *testing.T) {
@ -108,7 +164,7 @@ func TestSearchSheet(t *testing.T) {
assert.EqualError(t, err, "invalid cell reference [1, 0]")
assert.Equal(t, []string(nil), result)
// Test search sheet with unsupported charset shared strings table.
// Test search sheet with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.SearchSheet("Sheet1", "A")
@ -204,6 +260,14 @@ func TestDefinedName(t *testing.T) {
assert.EqualError(t, f.DeleteDefinedName(&DefinedName{
Name: "No Exist Defined Name",
}), ErrDefinedNameScope.Error())
// Test set defined name without name
assert.EqualError(t, f.SetDefinedName(&DefinedName{
RefersTo: "Sheet1!$A$2:$D$5",
}), ErrParameterInvalid.Error())
// Test set defined name without reference
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
}), ErrParameterInvalid.Error())
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
assert.NoError(t, f.DeleteDefinedName(&DefinedName{
Name: "Amount",
@ -228,7 +292,8 @@ func TestGroupSheets(t *testing.T) {
f := NewFile()
sheets := []string{"Sheet2", "Sheet3"}
for _, sheet := range sheets {
f.NewSheet(sheet)
_, err := f.NewSheet(sheet)
assert.NoError(t, err)
}
assert.EqualError(t, f.GroupSheets([]string{"Sheet1", "SheetN"}), "sheet SheetN does not exist")
assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet")
@ -242,7 +307,8 @@ func TestUngroupSheets(t *testing.T) {
f := NewFile()
sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"}
for _, sheet := range sheets {
f.NewSheet(sheet)
_, err := f.NewSheet(sheet)
assert.NoError(t, err)
}
assert.NoError(t, f.UngroupSheets())
}
@ -276,7 +342,8 @@ func TestRemovePageBreak(t *testing.T) {
assert.NoError(t, f.RemovePageBreak("Sheet1", "B3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A3"))
f.NewSheet("Sheet2")
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.InsertPageBreak("Sheet2", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet2", "C2"))
assert.NoError(t, f.RemovePageBreak("Sheet2", "B2"))
@ -381,20 +448,23 @@ func TestDeleteSheet(t *testing.T) {
idx, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
f.SetActiveSheet(idx)
f.NewSheet("Sheet3")
f.DeleteSheet("Sheet1")
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet1"))
assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx")))
// Test with auto filter defined names
f = NewFile()
f.NewSheet("Sheet2")
f.NewSheet("Sheet3")
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A"))
assert.NoError(t, f.AutoFilter("Sheet1", "A1", "A1", ""))
assert.NoError(t, f.AutoFilter("Sheet2", "A1", "A1", ""))
assert.NoError(t, f.AutoFilter("Sheet3", "A1", "A1", ""))
assert.NoError(t, f.AutoFilter("Sheet1", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet2", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet3", "A1:A1", nil))
assert.NoError(t, f.DeleteSheet("Sheet2"))
assert.NoError(t, f.DeleteSheet("Sheet1"))
// Test delete sheet with invalid sheet name
@ -408,9 +478,10 @@ func TestDeleteAndAdjustDefinedNames(t *testing.T) {
}
func TestGetSheetID(t *testing.T) {
file := NewFile()
file.NewSheet("Sheet1")
id := file.getSheetID("sheet1")
f := NewFile()
_, err := f.NewSheet("Sheet1")
assert.NoError(t, err)
id := f.getSheetID("sheet1")
assert.NotEqual(t, -1, id)
}
@ -444,7 +515,7 @@ func TestGetSheetIndex(t *testing.T) {
func TestSetContentTypes(t *testing.T) {
f := NewFile()
// Test set content type with unsupported charset content types.
// Test set content type with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8")
@ -452,7 +523,7 @@ func TestSetContentTypes(t *testing.T) {
func TestDeleteSheetFromContentTypes(t *testing.T) {
f := NewFile()
// Test delete sheet from content types with unsupported charset content types.
// Test delete sheet from content types with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8")
@ -468,9 +539,8 @@ func BenchmarkNewSheet(b *testing.B) {
func newSheetWithSet() {
file := NewFile()
file.NewSheet("sheet1")
for i := 0; i < 1000; i++ {
_ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i)
_ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), i)
}
file = nil
}
@ -485,9 +555,8 @@ func BenchmarkFile_SaveAs(b *testing.B) {
func newSheetWithSave() {
file := NewFile()
file.NewSheet("sheet1")
for i := 0; i < 1000; i++ {
_ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i)
_ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), i)
}
_ = file.Save()
}
@ -520,12 +589,13 @@ func TestAttrValToFloat(t *testing.T) {
func TestSetSheetBackgroundFromBytes(t *testing.T) {
f := NewFile()
f.SetSheetName("Sheet1", ".svg")
assert.NoError(t, f.SetSheetName("Sheet1", ".svg"))
for i, imageTypes := range []string{".svg", ".emf", ".emz", ".gif", ".jpg", ".png", ".tif", ".wmf", ".wmz"} {
file := fmt.Sprintf("excelize%s", imageTypes)
if i > 0 {
file = filepath.Join("test", "images", fmt.Sprintf("excel%s", imageTypes))
f.NewSheet(imageTypes)
_, err := f.NewSheet(imageTypes)
assert.NoError(t, err)
}
img, err := os.Open(file)
assert.NoError(t, err)

View File

@ -28,10 +28,10 @@ func TestSetView(t *testing.T) {
opts, err := f.GetSheetView("Sheet1", 0)
assert.NoError(t, err)
assert.Equal(t, expected, opts)
// Test set sheet view options with invalid view index.
// Test set sheet view options with invalid view index
assert.EqualError(t, f.SetSheetView("Sheet1", 1, nil), "view index 1 out of range")
assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 out of range")
// Test set sheet view options on not exists worksheet.
// Test set sheet view options on not exists worksheet
assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist")
}
@ -39,12 +39,12 @@ func TestGetView(t *testing.T) {
f := NewFile()
_, err := f.getSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get sheet view options with invalid view index.
// Test get sheet view options with invalid view index
_, err = f.GetSheetView("Sheet1", 1)
assert.EqualError(t, err, "view index 1 out of range")
_, err = f.GetSheetView("Sheet1", -2)
assert.EqualError(t, err, "view index -2 out of range")
// Test get sheet view options on not exists worksheet.
// Test get sheet view options on not exists worksheet
_, err = f.GetSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
}

View File

@ -9,10 +9,11 @@ import (
)
func TestAddSparkline(t *testing.T) {
f := prepareSparklineDataset()
f, err := prepareSparklineDataset()
assert.NoError(t, err)
// Set the columns widths to make the output clearer
style, err := f.NewStyle(`{"font":{"bold":true}}`)
style, err := f.NewStyle(&Style{Font: &Font{Bold: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
viewOpts, err := f.GetSheetView("Sheet1", 0)
@ -291,7 +292,7 @@ func TestAppendSparkline(t *testing.T) {
assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8")
}
func prepareSparklineDataset() *File {
func prepareSparklineDataset() (*File, error) {
f := NewFile()
sheet2 := [][]int{
{-2, 2, 3, -1, 0},
@ -307,8 +308,12 @@ func prepareSparklineDataset() *File {
{3, -1, 0, -2, 3, 2, 1, 0, 2, 1},
{0, -2, 3, 2, 1, 0, 1, 2, 3, 1},
}
f.NewSheet("Sheet2")
f.NewSheet("Sheet3")
if _, err := f.NewSheet("Sheet2"); err != nil {
return f, err
}
if _, err := f.NewSheet("Sheet3"); err != nil {
return f, err
}
for row, data := range sheet2 {
if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err)
@ -319,5 +324,5 @@ func prepareSparklineDataset() *File {
fmt.Println(err)
}
}
return f
return f, nil
}

View File

@ -134,18 +134,19 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
// AddTable creates an Excel table for the StreamWriter using the given
// cell range and format set. For example, create a table of A1:D5:
//
// err := sw.AddTable("A1", "D5", "")
// err := sw.AddTable("A1:D5", nil)
//
// Create a table of F2:H6 with format set:
//
// err := sw.AddTable("F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
// disable := false
// err := sw.AddTable("F2:H6", &excelize.TableOptions{
// Name: "table",
// StyleName: "TableStyleMedium2",
// ShowFirstColumn: true,
// ShowLastColumn: true,
// ShowRowStripes: &disable,
// ShowColumnStripes: true,
// })
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique.
@ -154,13 +155,9 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
// called after the rows are written but before Flush.
//
// See File.AddTable for details on the table format.
func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
options, err := parseTableOptions(opts)
if err != nil {
return err
}
coordinates, err := cellRefsToCoordinates(hCell, vCell)
func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error {
options := parseTableOptions(opts)
coordinates, err := rangeRefToCoordinates(reference)
if err != nil {
return err
}
@ -192,7 +189,7 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
tableID := sw.file.countTables() + 1
name := options.TableName
name := options.Name
if name == "" {
name = "Table" + strconv.Itoa(tableID)
}
@ -211,10 +208,10 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
TableColumn: tableColumn,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: options.TableStyle,
Name: options.StyleName,
ShowFirstColumn: options.ShowFirstColumn,
ShowLastColumn: options.ShowLastColumn,
ShowRowStripes: options.ShowRowStripes,
ShowRowStripes: *options.ShowRowStripes,
ShowColumnStripes: options.ShowColumnStripes,
},
}
@ -462,7 +459,7 @@ func (sw *StreamWriter) InsertPageBreak(cell string) error {
// SetPanes provides a function to create and remove freeze panes and split
// panes by giving panes options for the StreamWriter. Note that you must call
// the 'SetPanes' function before the 'SetRow' function.
func (sw *StreamWriter) SetPanes(panes string) error {
func (sw *StreamWriter) SetPanes(panes *Panes) error {
if sw.sheetWritten {
return ErrStreamSetPanes
}

View File

@ -41,12 +41,12 @@ func TestStreamWriter(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test max characters in a cell.
// Test max characters in a cell
row := make([]interface{}, 1)
row[0] = strings.Repeat("c", TotalCellChars+2)
assert.NoError(t, streamWriter.SetRow("A1", row))
// Test leading and ending space(s) character characters in a cell.
// Test leading and ending space(s) character characters in a cell
row = make([]interface{}, 1)
row[0] = " characters"
assert.NoError(t, streamWriter.SetRow("A2", row))
@ -55,7 +55,7 @@ func TestStreamWriter(t *testing.T) {
row[0] = []byte("Word")
assert.NoError(t, streamWriter.SetRow("A3", row))
// Test set cell with style and rich text.
// Test set cell with style and rich text
styleID, err := file.NewStyle(&Style{Font: &Font{Color: "#777777"}})
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A4", []interface{}{
@ -85,14 +85,14 @@ func TestStreamWriter(t *testing.T) {
}
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path.
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
// Test set cell column overflow.
// Test set cell column overflow
assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber)
assert.NoError(t, file.Close())
// Test close temporary file error.
// Test close temporary file error
file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
@ -114,7 +114,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, streamWriter.rawData.tmp.Close())
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
// Test create stream writer with unsupported charset.
// Test create stream writer with unsupported charset
file = NewFile()
file.Sheet.Delete("xl/worksheets/sheet1.xml")
file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
@ -122,7 +122,7 @@ func TestStreamWriter(t *testing.T) {
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, file.Close())
// Test read cell.
// Test read cell
file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
@ -132,7 +132,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "Data", cellValue)
// Test stream reader for a worksheet with huge amounts of data.
// Test stream reader for a worksheet with huge amounts of data
file, err = OpenFile(filepath.Join("test", "TestStreamWriter.xlsx"))
assert.NoError(t, err)
rows, err := file.Rows("Sheet1")
@ -166,14 +166,24 @@ func TestStreamSetColWidth(t *testing.T) {
}
func TestStreamSetPanes(t *testing.T) {
file, paneOpts := NewFile(), `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`
file, paneOpts := NewFile(), &Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
}
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetPanes(paneOpts))
assert.EqualError(t, streamWriter.SetPanes(""), "unexpected end of JSON input")
assert.EqualError(t, streamWriter.SetPanes(nil), ErrParameterInvalid.Error())
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.ErrorIs(t, streamWriter.SetPanes(paneOpts), ErrStreamSetPanes)
}
@ -185,19 +195,20 @@ func TestStreamTable(t *testing.T) {
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Write some rows. We want enough rows to force a temp file (>16MB).
// Test add table without table header
assert.EqualError(t, streamWriter.AddTable("A1:C2", nil), "XML syntax error on line 2: unexpected EOF")
// Write some rows. We want enough rows to force a temp file (>16MB)
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
row := []interface{}{1, 2, 3}
for r := 2; r < 10000; r++ {
assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row))
}
// Write a table.
assert.NoError(t, streamWriter.AddTable("A1", "C2", ""))
// Write a table
assert.NoError(t, streamWriter.AddTable("A1:C2", nil))
assert.NoError(t, streamWriter.Flush())
// Verify the table has names.
// Verify the table has names
var table xlsxTable
val, ok := file.Pkg.Load("xl/tables/table1.xml")
assert.True(t, ok)
@ -206,17 +217,15 @@ func TestStreamTable(t *testing.T) {
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)
assert.NoError(t, streamWriter.AddTable("A1", "C1", ""))
assert.NoError(t, streamWriter.AddTable("A1:C1", nil))
// Test add table with illegal options.
assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell reference.
assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with unsupported charset content types.
// Test add table with illegal cell reference
assert.EqualError(t, streamWriter.AddTable("A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, streamWriter.AddTable("A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with unsupported charset content types
file.ContentTypes = nil
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.AddTable("A1", "C2", ""), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, streamWriter.AddTable("A1:C2", nil), "XML syntax error on line 1: invalid UTF-8")
}
func TestStreamMergeCells(t *testing.T) {
@ -227,10 +236,10 @@ func TestStreamMergeCells(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.MergeCell("A1", "D1"))
// Test merge cells with illegal cell reference.
// Test merge cells with illegal cell reference
assert.EqualError(t, streamWriter.MergeCell("A", "D1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path.
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamMergeCells.xlsx")))
}
@ -243,7 +252,7 @@ func TestStreamInsertPageBreak(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, streamWriter.InsertPageBreak("A1"))
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path.
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamInsertPageBreak.xlsx")))
}
@ -270,7 +279,7 @@ func TestStreamMarshalAttrs(t *testing.T) {
}
func TestStreamSetRow(t *testing.T) {
// Test error exceptions.
// Test error exceptions
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
@ -278,10 +287,10 @@ func TestStreamSetRow(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set row with non-ascending row number.
// Test set row with non-ascending row number
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error())
// Test set row with unsupported charset workbook.
// Test set row with unsupported charset workbook
file.WorkBook = nil
file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8")
@ -367,13 +376,13 @@ func TestStreamWriterOutlineLevel(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test set outlineLevel in row.
// Test set outlineLevel in row
assert.NoError(t, streamWriter.SetRow("A1", nil, RowOpts{OutlineLevel: 1}))
assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7}))
assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8}))
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path.
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")))
file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))

434
styles.go
View File

@ -13,7 +13,6 @@ package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
@ -1076,35 +1075,25 @@ func (f *File) sharedStringsWriter() {
// parseFormatStyleSet provides a function to parse the format settings of the
// cells and conditional formats.
func parseFormatStyleSet(style interface{}) (*Style, error) {
fs := Style{}
func parseFormatStyleSet(style *Style) (*Style, error) {
var err error
switch v := style.(type) {
case string:
err = json.Unmarshal([]byte(v), &fs)
case *Style:
fs = *v
default:
err = ErrParameterInvalid
if style.Font != nil {
if len(style.Font.Family) > MaxFontFamilyLength {
return style, ErrFontLength
}
if fs.Font != nil {
if len(fs.Font.Family) > MaxFontFamilyLength {
return &fs, ErrFontLength
}
if fs.Font.Size > MaxFontSize {
return &fs, ErrFontSize
if style.Font.Size > MaxFontSize {
return style, ErrFontSize
}
}
if fs.CustomNumFmt != nil && len(*fs.CustomNumFmt) == 0 {
if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 {
err = ErrCustomNumFmt
}
return &fs, err
return style, err
}
// NewStyle provides a function to create the style for cells by given structure
// pointer or JSON. This function is concurrency safe. Note that
// the 'Font.Color' field uses an RGB color represented in 'RRGGBB' hexadecimal
// notation.
// NewStyle provides a function to create the style for cells by given style
// options. This function is concurrency safe. Note that the 'Font.Color' field
// uses an RGB color represented in 'RRGGBB' hexadecimal notation.
//
// The following table shows the border types used in 'Border.Type' supported by
// excelize:
@ -1983,13 +1972,16 @@ func parseFormatStyleSet(style interface{}) (*Style, error) {
// err = f.SetCellStyle("Sheet1", "A6", "A6", style)
//
// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017
func (f *File) NewStyle(style interface{}) (int, error) {
func (f *File) NewStyle(style *Style) (int, error) {
var (
fs *Style
font *xlsxFont
err error
cellXfsID, fontID, borderID, fillID int
)
if style == nil {
return cellXfsID, err
}
fs, err = parseFormatStyleSet(style)
if err != nil {
return cellXfsID, err
@ -2123,9 +2115,8 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) {
// NewConditionalStyle provides a function to create style for conditional
// format by given style format. The parameters are the same with the NewStyle
// function. Note that the color field uses RGB color code and only support to
// set font, fills, alignment and borders currently.
func (f *File) NewConditionalStyle(style string) (int, error) {
// function.
func (f *File) NewConditionalStyle(style *Style) (int, error) {
s, err := f.stylesReader()
if err != nil {
return 0, err
@ -2836,51 +2827,51 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
//
// Type | Parameters
// ---------------+------------------------------------
// cell | criteria
// | value
// | minimum
// | maximum
// date | criteria
// | value
// | minimum
// | maximum
// time_period | criteria
// text | criteria
// | value
// average | criteria
// cell | Criteria
// | Value
// | Minimum
// | Maximum
// date | Criteria
// | Value
// | Minimum
// | Maximum
// time_period | Criteria
// text | Criteria
// | Value
// average | Criteria
// duplicate | (none)
// unique | (none)
// top | criteria
// | value
// bottom | criteria
// | value
// top | Criteria
// | Value
// bottom | Criteria
// | Value
// blanks | (none)
// no_blanks | (none)
// errors | (none)
// no_errors | (none)
// 2_color_scale | min_type
// | max_type
// | min_value
// | max_value
// | min_color
// | max_color
// 3_color_scale | min_type
// | mid_type
// | max_type
// | min_value
// | mid_value
// | max_value
// | min_color
// | mid_color
// | max_color
// data_bar | min_type
// | max_type
// | min_value
// | max_value
// | bar_color
// formula | criteria
// 2_color_scale | MinType
// | MaxType
// | MinValue
// | MaxValue
// | MinColor
// | MaxColor
// 3_color_scale | MinType
// | MidType
// | MaxType
// | MinValue
// | MidValue
// | MaxValue
// | MinColor
// | MidColor
// | MaxColor
// data_bar | MinType
// | MaxType
// | MinValue
// | MaxValue
// | BarColor
// formula | Criteria
//
// The criteria parameter is used to set the criteria by which the cell data
// The 'Criteria' parameter is used to set the criteria by which the cell data
// will be evaluated. It has no default value. The most common criteria as
// applied to {"type""cell"} are:
//
@ -2902,22 +2893,51 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// value: The value is generally used along with the criteria parameter to set
// the rule by which the cell data will be evaluated:
//
// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format))
// err := f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: ">",
// Format: format,
// Value: "6",
// },
// },
// )
//
// The value property can also be an cell reference:
//
// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"$C$1"}]`, format))
// err := f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: ">",
// Format: format,
// Value: "$C$1",
// },
// },
// )
//
// type: format - The format parameter is used to specify the format that will
// be applied to the cell when the conditional formatting criterion is met. The
// format is created using the NewConditionalStyle() method in the same way as
// format is created using the NewConditionalStyle function in the same way as
// cell formats:
//
// format, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
// format, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "#9A0511"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1,
// },
// },
// )
// if err != nil {
// fmt.Println(err)
// }
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format))
// err = f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {Type: "cell", Criteria: ">", Format: format, Value: "6"},
// },
// )
//
// Note: In Excel, a conditional format is superimposed over the existing cell
// format and not all cell format properties can be modified. Properties that
@ -2929,19 +2949,50 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// These can be replicated using the following excelize formats:
//
// // Rose format for bad conditional.
// format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
// format1, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "#9A0511"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1,
// },
// },
// )
//
// // Light yellow format for neutral conditional.
// format2, err = f.NewConditionalStyle(`{"font":{"color":"#9B5713"},"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`)
// format2, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "#9B5713"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1,
// },
// },
// )
//
// // Light green format for good conditional.
// format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`)
// format3, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "#09600B"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1,
// },
// },
// )
//
// type: minimum - The minimum parameter is used to set the lower limiting value
// when the criteria is either "between" or "not between".
//
// // Highlight cells rules: between...
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format))
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: "between",
// Format: format,
// Minimum: "6",
// Maximum: "8",
// },
// },
// )
//
// type: maximum - The maximum parameter is used to set the upper limiting value
// when the criteria is either "between" or "not between". See the previous
@ -2951,98 +3002,184 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// conditional format:
//
// // Top/Bottom rules: Above Average...
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format1))
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "average",
// Criteria: "=",
// Format: format1,
// AboveAverage: true,
// },
// },
// )
//
// // Top/Bottom rules: Below Average...
// f.SetConditionalFormat("Sheet1", "B1:B10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format2))
// err := f.SetConditionalFormat("Sheet1", "B1:B10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "average",
// Criteria: "=",
// Format: format2,
// AboveAverage: false,
// },
// },
// )
//
// type: duplicate - The duplicate type is used to highlight duplicate cells in a range:
//
// // Highlight cells rules: Duplicate Values...
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format))
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {Type: "duplicate", Criteria: "=", Format: format},
// },
// )
//
// type: unique - The unique type is used to highlight unique cells in a range:
//
// // Highlight cells rules: Not Equal To...
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format))
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {Type: "unique", Criteria: "=", Format: format},
// },
// )
//
// type: top - The top type is used to specify the top n values by number or percentage in a range:
//
// // Top/Bottom rules: Top 10.
// f.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6"}]`, format))
// err := f.SetConditionalFormat("Sheet1", "H1:H10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "top",
// Criteria: "=",
// Format: format,
// Value: "6",
// },
// },
// )
//
// The criteria can be used to indicate that a percentage condition is required:
//
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format))
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "top",
// Criteria: "=",
// Format: format,
// Value: "6",
// Percent: true,
// },
// },
// )
//
// type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2
// Color Scale" style conditional format:
//
// // Color scales: 2 color.
// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "2_color_scale",
// Criteria: "=",
// MinType: "min",
// MaxType: "max",
// MinColor: "#F8696B",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// This conditional type can be modified with min_type, max_type, min_value,
// max_value, min_color and max_color, see below.
// This conditional type can be modified with MinType, MaxType, MinValue,
// MaxValue, MinColor and MaxColor, see below.
//
// type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3
// Color Scale" style conditional format:
//
// // Color scales: 3 color.
// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "3_color_scale",
// Criteria: "=",
// MinType: "min",
// MidType: "percentile",
// MaxType: "max",
// MinColor: "#F8696B",
// MidColor: "#FFEB84",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// This conditional type can be modified with min_type, mid_type, max_type,
// min_value, mid_value, max_value, min_color, mid_color and max_color, see
// This conditional type can be modified with MinType, MidType, MaxType,
// MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see
// below.
//
// type: data_bar - The data_bar type is used to specify Excel's "Data Bar"
// style conditional format.
//
// min_type - The min_type and max_type properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_type is available for 3_color_scale. The properties are used as follows:
// MinType - The MinType and MaxType properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The MidType is available for 3_color_scale. The properties are used as follows:
//
// // Data Bars: Gradient Fill.
// f.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
// err := f.SetConditionalFormat("Sheet1", "K1:K10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "data_bar",
// Criteria: "=",
// MinType: "min",
// MaxType: "max",
// BarColor: "#638EC6",
// },
// },
// )
//
// The available min/mid/max types are:
//
// min (for min_type only)
// min (for MinType only)
// num
// percent
// percentile
// formula
// max (for max_type only)
// max (for MaxType only)
//
// mid_type - Used for 3_color_scale. Same as min_type, see above.
// MidType - Used for 3_color_scale. Same as MinType, see above.
//
// max_type - Same as min_type, see above.
// MaxType - Same as MinType, see above.
//
// min_value - The min_value and max_value properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The
// mid_value is available for 3_color_scale.
//
// mid_value - Used for 3_color_scale. Same as min_value, see above.
//
// max_value - Same as min_value, see above.
//
// min_color - The min_color and max_color properties are available when the
// MinValue - The MinValue and MaxValue properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
// The mid_color is available for 3_color_scale. The properties are used as
// follows:
//
// MidValue - The MidValue is available for 3_color_scale. Same as MinValue,
// see above.
//
// MaxValue - Same as MinValue, see above.
//
// MinColor - The MinColor and MaxColor properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
//
// MidColor - The MidColor is available for 3_color_scale. The properties
// are used as follows:
//
// // Color scales: 3 color.
// f.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
// err := f.SetConditionalFormat("Sheet1", "B1:B10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "3_color_scale",
// Criteria: "=",
// MinType: "min",
// MidType: "percentile",
// MaxType: "max",
// MinColor: "#F8696B",
// MidColor: "#FFEB84",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// mid_color - Used for 3_color_scale. Same as min_color, see above.
// MaxColor - Same as MinColor, see above.
//
// max_color - Same as min_color, see above.
//
// bar_color - Used for data_bar. Same as min_color, see above.
func (f *File) SetConditionalFormat(sheet, reference, opts string) error {
var format []*conditionalOptions
err := json.Unmarshal([]byte(opts), &format)
if err != nil {
return err
}
drawContFmtFunc := map[string]func(p int, ct string, fmtCond *conditionalOptions) *xlsxCfRule{
// BarColor - Used for data_bar. Same as MinColor, see above.
func (f *File) SetConditionalFormat(sheet, reference string, opts []ConditionalFormatOptions) error {
drawContFmtFunc := map[string]func(p int, ct string, fmtCond *ConditionalFormatOptions) *xlsxCfRule{
"cellIs": drawCondFmtCellIs,
"top10": drawCondFmtTop10,
"aboveAverage": drawCondFmtAboveAverage,
@ -3059,7 +3196,7 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error {
return err
}
var cfRule []*xlsxCfRule
for p, v := range format {
for p, v := range opts {
var vt, ct string
var ok bool
// "type" is a required parameter, check for valid validation types.
@ -3070,7 +3207,7 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error {
if ok || vt == "expression" {
drawFunc, ok := drawContFmtFunc[vt]
if ok {
cfRule = append(cfRule, drawFunc(p, ct, v))
cfRule = append(cfRule, drawFunc(p, ct, &v))
}
}
}
@ -3086,21 +3223,21 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error {
// extractCondFmtCellIs provides a function to extract conditional format
// settings for cell value (include between, not between, equal, not equal,
// greater than and less than) by given conditional formatting rule.
func extractCondFmtCellIs(c *xlsxCfRule) *conditionalOptions {
format := conditionalOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID}
func extractCondFmtCellIs(c *xlsxCfRule) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID}
if len(c.Formula) == 2 {
format.Minimum, format.Maximum = c.Formula[0], c.Formula[1]
return &format
return format
}
format.Value = c.Formula[0]
return &format
return format
}
// extractCondFmtTop10 provides a function to extract conditional format
// settings for top N (default is top 10) by given conditional formatting
// rule.
func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions {
format := conditionalOptions{
func extractCondFmtTop10(c *xlsxCfRule) ConditionalFormatOptions {
format := ConditionalFormatOptions{
Type: "top",
Criteria: "=",
Format: *c.DxfID,
@ -3110,14 +3247,14 @@ func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions {
if c.Bottom {
format.Type = "bottom"
}
return &format
return format
}
// extractCondFmtAboveAverage provides a function to extract conditional format
// settings for above average and below average by given conditional formatting
// rule.
func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions {
return &conditionalOptions{
func extractCondFmtAboveAverage(c *xlsxCfRule) ConditionalFormatOptions {
return ConditionalFormatOptions{
Type: "average",
Criteria: "=",
Format: *c.DxfID,
@ -3128,8 +3265,8 @@ func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions {
// extractCondFmtDuplicateUniqueValues provides a function to extract
// conditional format settings for duplicate and unique values by given
// conditional formatting rule.
func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions {
return &conditionalOptions{
func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) ConditionalFormatOptions {
return ConditionalFormatOptions{
Type: map[string]string{
"duplicateValues": "duplicate",
"uniqueValues": "unique",
@ -3142,8 +3279,8 @@ func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions {
// extractCondFmtColorScale provides a function to extract conditional format
// settings for color scale (include 2 color scale and 3 color scale) by given
// conditional formatting rule.
func extractCondFmtColorScale(c *xlsxCfRule) *conditionalOptions {
var format conditionalOptions
func extractCondFmtColorScale(c *xlsxCfRule) ConditionalFormatOptions {
var format ConditionalFormatOptions
format.Type, format.Criteria = "2_color_scale", "="
values := len(c.ColorScale.Cfvo)
colors := len(c.ColorScale.Color)
@ -3172,35 +3309,35 @@ func extractCondFmtColorScale(c *xlsxCfRule) *conditionalOptions {
}
format.MaxColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[2].RGB), "FF")
}
return &format
return format
}
// extractCondFmtDataBar provides a function to extract conditional format
// settings for data bar by given conditional formatting rule.
func extractCondFmtDataBar(c *xlsxCfRule) *conditionalOptions {
format := conditionalOptions{Type: "data_bar", Criteria: "="}
func extractCondFmtDataBar(c *xlsxCfRule) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="}
if c.DataBar != nil {
format.MinType = c.DataBar.Cfvo[0].Type
format.MaxType = c.DataBar.Cfvo[1].Type
format.BarColor = "#" + strings.TrimPrefix(strings.ToUpper(c.DataBar.Color[0].RGB), "FF")
}
return &format
return format
}
// extractCondFmtExp provides a function to extract conditional format settings
// for expression by given conditional formatting rule.
func extractCondFmtExp(c *xlsxCfRule) *conditionalOptions {
format := conditionalOptions{Type: "formula", Format: *c.DxfID}
func extractCondFmtExp(c *xlsxCfRule) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "formula", Format: *c.DxfID}
if len(c.Formula) > 0 {
format.Criteria = c.Formula[0]
}
return &format
return format
}
// GetConditionalFormats returns conditional format settings by given worksheet
// name.
func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) {
extractContFmtFunc := map[string]func(c *xlsxCfRule) *conditionalOptions{
func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) {
extractContFmtFunc := map[string]func(c *xlsxCfRule) ConditionalFormatOptions{
"cellIs": extractCondFmtCellIs,
"top10": extractCondFmtTop10,
"aboveAverage": extractCondFmtAboveAverage,
@ -3211,20 +3348,19 @@ func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) {
"expression": extractCondFmtExp,
}
conditionalFormats := make(map[string]string)
conditionalFormats := make(map[string][]ConditionalFormatOptions)
ws, err := f.workSheetReader(sheet)
if err != nil {
return conditionalFormats, err
}
for _, cf := range ws.ConditionalFormatting {
var opts []*conditionalOptions
var opts []ConditionalFormatOptions
for _, cr := range cf.CfRule {
if extractFunc, ok := extractContFmtFunc[cr.Type]; ok {
opts = append(opts, extractFunc(cr))
}
}
options, _ := json.Marshal(opts)
conditionalFormats[cf.SQRef] = string(options)
conditionalFormats[cf.SQRef] = opts
}
return conditionalFormats, err
}
@ -3248,7 +3384,7 @@ func (f *File) UnsetConditionalFormat(sheet, reference string) error {
// drawCondFmtCellIs provides a function to create conditional formatting rule
// for cell value (include between, not between, equal, not equal, greater
// than and less than) by given priority, criteria type and format settings.
func drawCondFmtCellIs(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtCellIs(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
c := &xlsxCfRule{
Priority: p + 1,
Type: validType[format.Type],
@ -3268,7 +3404,7 @@ func drawCondFmtCellIs(p int, ct string, format *conditionalOptions) *xlsxCfRule
// drawCondFmtTop10 provides a function to create conditional formatting rule
// for top N (default is top 10) by given priority, criteria type and format
// settings.
func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtTop10(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
c := &xlsxCfRule{
Priority: p + 1,
Bottom: format.Type == "bottom",
@ -3286,7 +3422,7 @@ func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule
// drawCondFmtAboveAverage provides a function to create conditional
// formatting rule for above average and below average by given priority,
// criteria type and format settings.
func drawCondFmtAboveAverage(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtAboveAverage(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
return &xlsxCfRule{
Priority: p + 1,
Type: validType[format.Type],
@ -3298,7 +3434,7 @@ func drawCondFmtAboveAverage(p int, ct string, format *conditionalOptions) *xlsx
// drawCondFmtDuplicateUniqueValues provides a function to create conditional
// formatting rule for duplicate and unique values by given priority, criteria
// type and format settings.
func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtDuplicateUniqueValues(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
return &xlsxCfRule{
Priority: p + 1,
Type: validType[format.Type],
@ -3309,7 +3445,7 @@ func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptio
// drawCondFmtColorScale provides a function to create conditional formatting
// rule for color scale (include 2 color scale and 3 color scale) by given
// priority, criteria type and format settings.
func drawCondFmtColorScale(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtColorScale(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
minValue := format.MinValue
if minValue == "" {
minValue = "0"
@ -3346,7 +3482,7 @@ func drawCondFmtColorScale(p int, ct string, format *conditionalOptions) *xlsxCf
// drawCondFmtDataBar provides a function to create conditional formatting
// rule for data bar by given priority, criteria type and format settings.
func drawCondFmtDataBar(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtDataBar(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
return &xlsxCfRule{
Priority: p + 1,
Type: validType[format.Type],
@ -3359,7 +3495,7 @@ func drawCondFmtDataBar(p int, ct string, format *conditionalOptions) *xlsxCfRul
// drawCondFmtExp provides a function to create conditional formatting rule
// for expression by given priority, criteria type and format settings.
func drawCondFmtExp(p int, ct string, format *conditionalOptions) *xlsxCfRule {
func drawCondFmtExp(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
return &xlsxCfRule{
Priority: p + 1,
Type: validType[format.Type],

View File

@ -1,7 +1,6 @@
package excelize
import (
"fmt"
"math"
"path/filepath"
"strings"
@ -13,15 +12,15 @@ import (
func TestStyleFill(t *testing.T) {
cases := []struct {
label string
format string
format *Style
expectFill bool
}{{
label: "no_fill",
format: `{"alignment":{"wrap_text":true}}`,
format: &Style{Alignment: &Alignment{WrapText: true}},
expectFill: false,
}, {
label: "fill",
format: `{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`,
format: &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}},
expectFill: true,
}}
@ -40,9 +39,9 @@ func TestStyleFill(t *testing.T) {
}
}
f := NewFile()
styleID1, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`)
styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}})
assert.NoError(t, err)
styleID2, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`)
styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}})
assert.NoError(t, err)
assert.Equal(t, styleID1, styleID2)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx")))
@ -51,23 +50,23 @@ func TestStyleFill(t *testing.T) {
func TestSetConditionalFormat(t *testing.T) {
cases := []struct {
label string
format string
format []ConditionalFormatOptions
rules []*xlsxCfRule
}{{
label: "3_color_scale",
format: `[{
"type":"3_color_scale",
"criteria":"=",
"min_type":"num",
"mid_type":"num",
"max_type":"num",
"min_value": "-10",
"mid_value": "0",
"max_value": "10",
"min_color":"ff0000",
"mid_color":"00ff00",
"max_color":"0000ff"
}]`,
format: []ConditionalFormatOptions{{
Type: "3_color_scale",
Criteria: "=",
MinType: "num",
MidType: "num",
MaxType: "num",
MinValue: "-10",
MidValue: "0",
MaxValue: "10",
MinColor: "ff0000",
MidColor: "00ff00",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
@ -93,16 +92,16 @@ func TestSetConditionalFormat(t *testing.T) {
}},
}, {
label: "3_color_scale default min/mid/max",
format: `[{
"type":"3_color_scale",
"criteria":"=",
"min_type":"num",
"mid_type":"num",
"max_type":"num",
"min_color":"ff0000",
"mid_color":"00ff00",
"max_color":"0000ff"
}]`,
format: []ConditionalFormatOptions{{
Type: "3_color_scale",
Criteria: "=",
MinType: "num",
MidType: "num",
MaxType: "num",
MinColor: "ff0000",
MidColor: "00ff00",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
@ -128,14 +127,14 @@ func TestSetConditionalFormat(t *testing.T) {
}},
}, {
label: "2_color_scale default min/max",
format: `[{
"type":"2_color_scale",
"criteria":"=",
"min_type":"num",
"max_type":"num",
"min_color":"ff0000",
"max_color":"0000ff"
}]`,
format: []ConditionalFormatOptions{{
Type: "2_color_scale",
Criteria: "=",
MinType: "num",
MaxType: "num",
MinColor: "ff0000",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
@ -177,18 +176,18 @@ func TestSetConditionalFormat(t *testing.T) {
}
func TestGetConditionalFormats(t *testing.T) {
for _, format := range []string{
`[{"type":"cell","format":1,"criteria":"greater than","value":"6"}]`,
`[{"type":"cell","format":1,"criteria":"between","minimum":"6","maximum":"8"}]`,
`[{"type":"top","format":1,"criteria":"=","value":"6"}]`,
`[{"type":"bottom","format":1,"criteria":"=","value":"6"}]`,
`[{"type":"average","above_average":true,"format":1,"criteria":"="}]`,
`[{"type":"duplicate","format":1,"criteria":"="}]`,
`[{"type":"unique","format":1,"criteria":"="}]`,
`[{"type":"3_color_scale","criteria":"=","min_type":"num","mid_type":"num","max_type":"num","min_value":"-10","mid_value":"50","max_value":"10","min_color":"#FF0000","mid_color":"#00FF00","max_color":"#0000FF"}]`,
`[{"type":"2_color_scale","criteria":"=","min_type":"num","max_type":"num","min_color":"#FF0000","max_color":"#0000FF"}]`,
`[{"type":"data_bar","criteria":"=","min_type":"min","max_type":"max","bar_color":"#638EC6"}]`,
`[{"type":"formula","format":1,"criteria":"="}]`,
for _, format := range [][]ConditionalFormatOptions{
{{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}},
{{Type: "cell", Format: 1, Criteria: "between", Minimum: "6", Maximum: "8"}},
{{Type: "top", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}},
{{Type: "duplicate", Format: 1, Criteria: "="}},
{{Type: "unique", Format: 1, Criteria: "="}},
{{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}},
{{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6"}},
{{Type: "formula", Format: 1, Criteria: "="}},
} {
f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A1:A2", format)
@ -210,9 +209,9 @@ func TestUnsetConditionalFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
format, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: format, Value: "6"}}))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
// Test unset conditional format on not exists worksheet
assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist")
@ -224,7 +223,7 @@ func TestUnsetConditionalFormat(t *testing.T) {
func TestNewStyle(t *testing.T) {
f := NewFile()
styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
styleID, err := f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "#777777"}})
assert.NoError(t, err)
styles, err := f.stylesReader()
assert.NoError(t, err)
@ -234,8 +233,8 @@ func TestNewStyle(t *testing.T) {
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
_, err = f.NewStyle(&Style{})
assert.NoError(t, err)
_, err = f.NewStyle(Style{})
assert.EqualError(t, err, ErrParameterInvalid.Error())
_, err = f.NewStyle(nil)
assert.NoError(t, err)
var exp string
_, err = f.NewStyle(&Style{CustomNumFmt: &exp})
@ -326,7 +325,7 @@ func TestNewConditionalStyle(t *testing.T) {
// Test create conditional style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
_, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
@ -378,13 +377,13 @@ func TestThemeReader(t *testing.T) {
func TestSetCellStyle(t *testing.T) {
f := NewFile()
// Test set cell style on not exists worksheet.
// Test set cell style on not exists worksheet
assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN does not exist")
// Test set cell style with invalid style ID.
// Test set cell style with invalid style ID
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", -1), newInvalidStyleID(-1).Error())
// Test set cell style with not exists style ID.
// Test set cell style with not exists style ID
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 10), newInvalidStyleID(10).Error())
// Test set cell style with unsupported charset style sheet.
// Test set cell style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8")
@ -395,7 +394,7 @@ func TestGetStyleID(t *testing.T) {
styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil)
assert.NoError(t, err)
assert.Equal(t, -1, styleID)
// Test get style ID with unsupported charset style sheet.
// Test get style ID with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.getStyleID(&xlsxStyleSheet{
@ -429,11 +428,11 @@ func TestThemeColor(t *testing.T) {
func TestGetNumFmtID(t *testing.T) {
f := NewFile()
fs1, err := parseFormatStyleSet(`{"protection":{"hidden":false,"locked":false},"number_format":10}`)
fs1, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 10})
assert.NoError(t, err)
id1 := getNumFmtID(&xlsxStyleSheet{}, fs1)
fs2, err := parseFormatStyleSet(`{"protection":{"hidden":false,"locked":false},"number_format":0}`)
fs2, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 0})
assert.NoError(t, err)
id2 := getNumFmtID(&xlsxStyleSheet{}, fs2)

122
table.go
View File

@ -12,7 +12,6 @@
package excelize
import (
"encoding/json"
"encoding/xml"
"fmt"
"regexp"
@ -22,64 +21,54 @@ import (
// parseTableOptions provides a function to parse the format settings of the
// table with default value.
func parseTableOptions(opts string) (*tableOptions, error) {
options := tableOptions{ShowRowStripes: true}
err := json.Unmarshal(fallbackOptions(opts), &options)
return &options, err
func parseTableOptions(opts *TableOptions) *TableOptions {
if opts == nil {
return &TableOptions{ShowRowStripes: boolPtr(true)}
}
if opts.ShowRowStripes == nil {
opts.ShowRowStripes = boolPtr(true)
}
return opts
}
// AddTable provides the method to add table in a worksheet by given worksheet
// name, range reference and format set. For example, create a table of A1:D5
// on Sheet1:
//
// err := f.AddTable("Sheet1", "A1", "D5", "")
// err := f.AddTable("Sheet1", "A1:D5", nil)
//
// Create a table of F2:H6 on Sheet2 with format set:
//
// err := f.AddTable("Sheet2", "F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
// err := f.AddTable("Sheet2", "F2:H6", &excelize.TableOptions{
// Name: "table",
// StyleName: "TableStyleMedium2",
// ShowFirstColumn: true,
// ShowLastColumn: true,
// ShowRowStripes: &disable,
// ShowColumnStripes: true,
// })
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique, and must set the
// header row data of the table before calling the AddTable function. Multiple
// tables range reference that can't have an intersection.
//
// table_name: The name of the table, in the same worksheet name of the table should be unique
// Name: The name of the table, in the same worksheet name of the table should be unique
//
// table_style: The built-in table style names
// StyleName: The built-in table style names
//
// TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11
func (f *File) AddTable(sheet, hCell, vCell, opts string) error {
options, err := parseTableOptions(opts)
if err != nil {
return err
}
func (f *File) AddTable(sheet, reference string, opts *TableOptions) error {
options := parseTableOptions(opts)
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
hCol, hRow, err := CellNameToCoordinates(hCell)
coordinates, err := rangeRefToCoordinates(reference)
if err != nil {
return err
}
vCol, vRow, err := CellNameToCoordinates(vCell)
if err != nil {
return err
}
if vCol < hCol {
vCol, hCol = hCol, vCol
}
if vRow < hRow {
vRow, hRow = hRow, vRow
}
// Correct table reference range, such correct C1:B3 to B1:C3.
_ = sortCoordinates(coordinates)
tableID := f.countTables() + 1
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
@ -91,7 +80,7 @@ func (f *File) AddTable(sheet, hCell, vCell, opts string) error {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, options); err != nil {
if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil {
return err
}
return f.addContentTypePart(tableID, "table")
@ -159,7 +148,7 @@ func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn,
// addTable provides a function to add table by given worksheet name,
// range reference and format set.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tableOptions) error {
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *TableOptions) error {
// Correct the minimum number of rows, the table at least two lines.
if y1 == y2 {
y2++
@ -171,7 +160,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab
return err
}
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
name := opts.TableName
name := opts.Name
if name == "" {
name = "Table" + strconv.Itoa(i)
}
@ -189,10 +178,10 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab
TableColumn: tableColumns,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: opts.TableStyle,
Name: opts.StyleName,
ShowFirstColumn: opts.ShowFirstColumn,
ShowLastColumn: opts.ShowLastColumn,
ShowRowStripes: opts.ShowRowStripes,
ShowRowStripes: *opts.ShowRowStripes,
ShowColumnStripes: opts.ShowColumnStripes,
},
}
@ -201,36 +190,30 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab
return nil
}
// parseAutoFilterOptions provides a function to parse the settings of the auto
// filter.
func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) {
options := autoFilterOptions{}
err := json.Unmarshal([]byte(opts), &options)
return &options, err
}
// AutoFilter provides the method to add auto filter in a worksheet by given
// worksheet name, range reference and settings. An auto filter in Excel is a
// way of filtering a 2D range of data based on some simple criteria. For
// example applying an auto filter to a cell range A1:D4 in the Sheet1:
//
// err := f.AutoFilter("Sheet1", "A1", "D4", "")
// err := f.AutoFilter("Sheet1", "A1:D4", nil)
//
// Filter data in an auto filter:
//
// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`)
// err := f.AutoFilter("Sheet1", "A1:D4", &excelize.AutoFilterOptions{
// Column: "B", Expression: "x != blanks",
// })
//
// column defines the filter columns in an auto filter range based on simple
// Column defines the filter columns in an auto filter range based on simple
// criteria
//
// It isn't sufficient to just specify the filter condition. You must also
// hide any rows that don't match the filter condition. Rows are hidden using
// the SetRowVisible() method. Excelize can't filter rows automatically since
// the SetRowVisible function. Excelize can't filter rows automatically since
// this isn't part of the file format.
//
// Setting a filter criteria for a column:
//
// expression defines the conditions, the following operators are available
// Expression defines the conditions, the following operators are available
// for setting the filter criteria:
//
// ==
@ -278,28 +261,15 @@ func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) {
// x < 2000
// col < 2000
// Price < 2000
func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error {
hCol, hRow, err := CellNameToCoordinates(hCell)
func (f *File) AutoFilter(sheet, reference string, opts *AutoFilterOptions) error {
coordinates, err := rangeRefToCoordinates(reference)
if err != nil {
return err
}
vCol, vRow, err := CellNameToCoordinates(vCell)
if err != nil {
return err
}
if vCol < hCol {
vCol, hCol = hCol, vCol
}
if vRow < hRow {
vRow, hRow = hRow, vRow
}
options, _ := parseAutoFilterOptions(opts)
cellStart, _ := CoordinatesToCellName(hCol, hRow, true)
cellEnd, _ := CoordinatesToCellName(vCol, vRow, true)
ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase"
_ = sortCoordinates(coordinates)
// Correct reference range, such correct C1:B3 to B1:C3.
ref, _ := f.coordinatesToRangeRef(coordinates, true)
filterDB := "_xlnm._FilterDatabase"
wb, err := f.workbookReader()
if err != nil {
return err
@ -332,13 +302,13 @@ func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
}
}
refRange := vCol - hCol
return f.autoFilter(sheet, ref, refRange, hCol, options)
refRange := coordinates[2] - coordinates[0]
return f.autoFilter(sheet, ref, refRange, coordinates[0], opts)
}
// autoFilter provides a function to extract the tokens from the filter
// expression. The tokens are mainly non-whitespace groups.
func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *autoFilterOptions) error {
func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *AutoFilterOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
@ -351,7 +321,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *autoFilter
Ref: ref,
}
ws.AutoFilter = filter
if opts.Column == "" || opts.Expression == "" {
if opts == nil || opts.Column == "" || opts.Expression == "" {
return nil
}

View File

@ -11,22 +11,28 @@ import (
func TestAddTable(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
assert.NoError(t, f.AddTable("Sheet1", "B26", "A21", `{}`))
assert.NoError(t, f.AddTable("Sheet2", "A2", "B5", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`))
assert.NoError(t, f.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`))
assert.NoError(t, f.AddTable("Sheet1", "B26:A21", nil))
assert.NoError(t, f.AddTable("Sheet2", "A2:B5", &TableOptions{
Name: "table",
StyleName: "TableStyleMedium2",
ShowFirstColumn: true,
ShowLastColumn: true,
ShowRowStripes: boolPtr(true),
ShowColumnStripes: true,
},
))
assert.NoError(t, f.AddTable("Sheet2", "F1:F1", &TableOptions{StyleName: "TableStyleMedium8"}))
// Test add table in not exist worksheet
assert.EqualError(t, f.AddTable("SheetN", "B26", "A21", `{}`), "sheet SheetN does not exist")
// Test add table with illegal options
assert.EqualError(t, f.AddTable("Sheet1", "B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
assert.EqualError(t, f.AddTable("SheetN", "B26:A21", nil), "sheet SheetN does not exist")
// Test add table with illegal cell reference
assert.EqualError(t, f.AddTable("Sheet1", "A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddTable("Sheet1", "A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
assert.EqualError(t, f.AddTable("Sheet1", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddTable("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))
// Test add table with invalid sheet name
assert.EqualError(t, f.AddTable("Sheet:1", "B26", "A21", `{}`), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddTable("Sheet:1", "B26:A21", nil), ErrSheetNameInvalid.Error())
// Test addTable with illegal cell reference
f = NewFile()
assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell reference [0, 0]")
@ -43,73 +49,66 @@ func TestAutoFilter(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
f, err := prepareTestBook1()
assert.NoError(t, err)
formats := []string{
``,
`{"column":"B","expression":"x != blanks"}`,
`{"column":"B","expression":"x == blanks"}`,
`{"column":"B","expression":"x != nonblanks"}`,
`{"column":"B","expression":"x == nonblanks"}`,
`{"column":"B","expression":"x <= 1 and x >= 2"}`,
`{"column":"B","expression":"x == 1 or x == 2"}`,
`{"column":"B","expression":"x == 1 or x == 2*"}`,
}
for i, format := range formats {
for i, opts := range []*AutoFilterOptions{
nil,
{Column: "B", Expression: ""},
{Column: "B", Expression: "x != blanks"},
{Column: "B", Expression: "x == blanks"},
{Column: "B", Expression: "x != nonblanks"},
{Column: "B", Expression: "x == nonblanks"},
{Column: "B", Expression: "x <= 1 and x >= 2"},
{Column: "B", Expression: "x == 1 or x == 2"},
{Column: "B", Expression: "x == 1 or x == 2*"},
} {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet1", "D4", "B1", format)
assert.NoError(t, err)
assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts))
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
})
}
// Test add auto filter with invalid sheet name
assert.EqualError(t, f.AutoFilter("Sheet:1", "A1", "B1", ""), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AutoFilter("Sheet:1", "A1:B1", nil), ErrSheetNameInvalid.Error())
// Test add auto filter with illegal cell reference
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add auto filter with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.AutoFilter("Sheet1", "D4", "B1", formats[0]), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8")
}
func TestAutoFilterError(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
formats := []string{
`{"column":"B","expression":"x <= 1 and x >= blanks"}`,
`{"column":"B","expression":"x -- y or x == *2*"}`,
`{"column":"B","expression":"x != y or x ? *2"}`,
`{"column":"B","expression":"x -- y o r x == *2"}`,
`{"column":"B","expression":"x -- y"}`,
`{"column":"A","expression":"x -- y"}`,
}
for i, format := range formats {
assert.NoError(t, err)
for i, opts := range []*AutoFilterOptions{
{Column: "B", Expression: "x <= 1 and x >= blanks"},
{Column: "B", Expression: "x -- y or x == *2*"},
{Column: "B", Expression: "x != y or x ? *2"},
{Column: "B", Expression: "x -- y o r x == *2"},
{Column: "B", Expression: "x -- y"},
{Column: "A", Expression: "x -- y"},
} {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet2", "D4", "B1", format)
if assert.Error(t, err) {
if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) {
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
}
})
}
assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &autoFilterOptions{
assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &AutoFilterOptions{
Column: "A",
Expression: "",
}), "sheet SheetN does not exist")
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &autoFilterOptions{
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{
Column: "-",
Expression: "-",
}), newInvalidColumnNameError("-").Error())
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &autoFilterOptions{
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &AutoFilterOptions{
Column: "A",
Expression: "-",
}), `incorrect index of column 'A'`)
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &autoFilterOptions{
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{
Column: "A",
Expression: "-",
}), `incorrect number of tokens in criteria '-'`)

View File

@ -59,8 +59,18 @@ func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
return opts, err
}
// ProtectWorkbook provides a function to prevent other users from accidentally or
// deliberately changing, moving, or deleting data in a workbook.
// ProtectWorkbook provides a function to prevent other users from viewing
// hidden worksheets, adding, moving, deleting, or hiding worksheets, and
// renaming worksheets in a workbook. The optional field AlgorithmName
// specified hash algorithm, support XOR, MD4, MD5, SHA-1, SHA2-56, SHA-384,
// and SHA-512 currently, if no hash algorithm specified, will be using the XOR
// algorithm as default. The generated workbook only works on Microsoft Office
// 2007 and later. For example, protect workbook with protection settings:
//
// err := f.ProtectWorkbook(&excelize.WorkbookProtectionOptions{
// Password: "password",
// LockStructure: true,
// })
func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
wb, err := f.workbookReader()
if err != nil {
@ -93,8 +103,8 @@ func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
}
// UnprotectWorkbook provides a function to remove protection for workbook,
// specified the second optional password parameter to remove workbook
// protection with password verification.
// specified the optional password parameter to remove workbook protection with
// password verification.
func (f *File) UnprotectWorkbook(password ...string) error {
wb, err := f.workbookReader()
if err != nil {

View File

@ -21,11 +21,11 @@ func TestWorkbookProps(t *testing.T) {
opts, err := f.GetWorkbookProps()
assert.NoError(t, err)
assert.Equal(t, expected, opts)
// Test set workbook properties with unsupported charset workbook.
// Test set workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8")
// Test get workbook properties with unsupported charset workbook.
// Test get workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetWorkbookProps()

View File

@ -15,13 +15,13 @@ import "encoding/xml"
// AppProperties directly maps the document application properties.
type AppProperties struct {
Application string `json:"application"`
ScaleCrop bool `json:"scale_crop"`
DocSecurity int `json:"doc_security"`
Company string `json:"company"`
LinksUpToDate bool `json:"links_up_to_date"`
HyperlinksChanged bool `json:"hyperlinks_changed"`
AppVersion string `json:"app_version"`
Application string
ScaleCrop bool
DocSecurity int
Company string
LinksUpToDate bool
HyperlinksChanged bool
AppVersion string
}
// xlsxProperties specifies to an OOXML document properties such as the

View File

@ -518,136 +518,83 @@ type cPageMargins struct {
T float64 `xml:"t,attr"`
}
// chartAxisOptions directly maps the format settings of the chart axis.
type chartAxisOptions struct {
None bool `json:"none"`
Crossing string `json:"crossing"`
MajorGridlines bool `json:"major_grid_lines"`
MinorGridlines bool `json:"minor_grid_lines"`
MajorTickMark string `json:"major_tick_mark"`
MinorTickMark string `json:"minor_tick_mark"`
MinorUnitType string `json:"minor_unit_type"`
MajorUnit float64 `json:"major_unit"`
MajorUnitType string `json:"major_unit_type"`
TickLabelSkip int `json:"tick_label_skip"`
DisplayUnits string `json:"display_units"`
DisplayUnitsVisible bool `json:"display_units_visible"`
DateAxis bool `json:"date_axis"`
ReverseOrder bool `json:"reverse_order"`
Maximum *float64 `json:"maximum"`
Minimum *float64 `json:"minimum"`
NumFormat string `json:"number_format"`
Font Font `json:"font"`
LogBase float64 `json:"logbase"`
NameLayout layoutOptions `json:"name_layout"`
// ChartAxis directly maps the format settings of the chart axis.
type ChartAxis struct {
None bool
MajorGridLines bool
MinorGridLines bool
MajorUnit float64
TickLabelSkip int
ReverseOrder bool
Maximum *float64
Minimum *float64
Font Font
LogBase float64
}
// chartDimensionOptions directly maps the dimension of the chart.
type chartDimensionOptions struct {
Width int `json:"width"`
Height int `json:"height"`
// ChartDimension directly maps the dimension of the chart.
type ChartDimension struct {
Width *int
Height *int
}
// chartOptions directly maps the format settings of the chart.
type chartOptions struct {
Type string `json:"type"`
Series []chartSeriesOptions `json:"series"`
Format pictureOptions `json:"format"`
Dimension chartDimensionOptions `json:"dimension"`
Legend chartLegendOptions `json:"legend"`
Title chartTitleOptions `json:"title"`
VaryColors bool `json:"vary_colors"`
XAxis chartAxisOptions `json:"x_axis"`
YAxis chartAxisOptions `json:"y_axis"`
Chartarea struct {
Border struct {
None bool `json:"none"`
} `json:"border"`
Fill struct {
Color string `json:"color"`
} `json:"fill"`
Pattern struct {
Pattern string `json:"pattern"`
FgColor string `json:"fg_color"`
BgColor string `json:"bg_color"`
} `json:"pattern"`
} `json:"chartarea"`
Plotarea struct {
ShowBubbleSize bool `json:"show_bubble_size"`
ShowCatName bool `json:"show_cat_name"`
ShowLeaderLines bool `json:"show_leader_lines"`
ShowPercent bool `json:"show_percent"`
ShowSerName bool `json:"show_series_name"`
ShowVal bool `json:"show_val"`
Gradient struct {
Colors []string `json:"colors"`
} `json:"gradient"`
Border struct {
Color string `json:"color"`
Width int `json:"width"`
DashType string `json:"dash_type"`
} `json:"border"`
Fill struct {
Color string `json:"color"`
} `json:"fill"`
Layout layoutOptions `json:"layout"`
} `json:"plotarea"`
ShowBlanksAs string `json:"show_blanks_as"`
ShowHiddenData bool `json:"show_hidden_data"`
SetRotation int `json:"set_rotation"`
HoleSize int `json:"hole_size"`
// ChartPlotArea directly maps the format settings of the plot area.
type ChartPlotArea struct {
ShowBubbleSize bool
ShowCatName bool
ShowLeaderLines bool
ShowPercent bool
ShowSerName bool
ShowVal bool
}
// Chart directly maps the format settings of the chart.
type Chart struct {
Type string
Series []ChartSeries
Format PictureOptions
Dimension ChartDimension
Legend ChartLegend
Title ChartTitle
VaryColors *bool
XAxis ChartAxis
YAxis ChartAxis
PlotArea ChartPlotArea
ShowBlanksAs string
HoleSize int
order int
}
// chartLegendOptions directly maps the format settings of the chart legend.
type chartLegendOptions struct {
None bool `json:"none"`
DeleteSeries []int `json:"delete_series"`
Font Font `json:"font"`
Layout layoutOptions `json:"layout"`
Position string `json:"position"`
ShowLegendEntry bool `json:"show_legend_entry"`
ShowLegendKey bool `json:"show_legend_key"`
// ChartLegend directly maps the format settings of the chart legend.
type ChartLegend struct {
None bool
Position *string
ShowLegendKey bool
}
// chartSeriesOptions directly maps the format settings of the chart series.
type chartSeriesOptions struct {
Name string `json:"name"`
Categories string `json:"categories"`
Values string `json:"values"`
Line struct {
None bool `json:"none"`
Color string `json:"color"`
Smooth bool `json:"smooth"`
Width float64 `json:"width"`
} `json:"line"`
Marker struct {
Symbol string `json:"symbol"`
Size int `json:"size"`
Width float64 `json:"width"`
Border struct {
Color string `json:"color"`
None bool `json:"none"`
} `json:"border"`
Fill struct {
Color string `json:"color"`
None bool `json:"none"`
} `json:"fill"`
} `json:"marker"`
// ChartMarker directly maps the format settings of the chart marker.
type ChartMarker struct {
Symbol string
Size int
}
// chartTitleOptions directly maps the format settings of the chart title.
type chartTitleOptions struct {
None bool `json:"none"`
Name string `json:"name"`
Overlay bool `json:"overlay"`
Layout layoutOptions `json:"layout"`
// ChartLine directly maps the format settings of the chart line.
type ChartLine struct {
Color string
Smooth bool
Width float64
}
// layoutOptions directly maps the format settings of the element layout.
type layoutOptions struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Width float64 `json:"width"`
Height float64 `json:"height"`
// ChartSeries directly maps the format settings of the chart series.
type ChartSeries struct {
Name string
Categories string
Values string
Line ChartLine
Marker ChartMarker
}
// ChartTitle directly maps the format settings of the chart title.
type ChartTitle struct {
Name string
}

View File

@ -74,9 +74,9 @@ type xlsxPhoneticRun struct {
// Comment directly maps the comment information.
type Comment struct {
Author string `json:"author"`
AuthorID int `json:"author_id"`
Cell string `json:"cell"`
Text string `json:"text"`
Runs []RichTextRun `json:"runs"`
Author string
AuthorID int
Cell string
Text string
Runs []RichTextRun
}

View File

@ -122,6 +122,13 @@ const (
// Excel 2007 or in compatibility mode. Slicer can only be used with
// PivotTables created in Excel 2007 or a newer version of Excel.
pivotTableVersion = 3
defaultPictureScale = 1.0
defaultChartDimensionWidth = 480
defaultChartDimensionHeight = 290
defaultChartLegendPosition = "bottom"
defaultChartShowBlanksAs = "gap"
defaultShapeSize = 160
defaultShapeLineWidth = 1
)
// ColorMappingType is the type of color transformation.
@ -554,48 +561,48 @@ type xdrTxBody struct {
P []*aP `xml:"a:p"`
}
// pictureOptions directly maps the format settings of the picture.
type pictureOptions struct {
FPrintsWithSheet bool `json:"print_obj"`
FLocksWithSheet bool `json:"locked"`
NoChangeAspect bool `json:"lock_aspect_ratio"`
Autofit bool `json:"autofit"`
OffsetX int `json:"x_offset"`
OffsetY int `json:"y_offset"`
XScale float64 `json:"x_scale"`
YScale float64 `json:"y_scale"`
Hyperlink string `json:"hyperlink"`
HyperlinkType string `json:"hyperlink_type"`
Positioning string `json:"positioning"`
// PictureOptions directly maps the format settings of the picture.
type PictureOptions struct {
PrintObject *bool
Locked *bool
LockAspectRatio bool
AutoFit bool
OffsetX int
OffsetY int
XScale *float64
YScale *float64
Hyperlink string
HyperlinkType string
Positioning string
}
// shapeOptions directly maps the format settings of the shape.
type shapeOptions struct {
Macro string `json:"macro"`
Type string `json:"type"`
Width int `json:"width"`
Height int `json:"height"`
Format pictureOptions `json:"format"`
Color shapeColorOptions `json:"color"`
Line lineOptions `json:"line"`
Paragraph []shapeParagraphOptions `json:"paragraph"`
// Shape directly maps the format settings of the shape.
type Shape struct {
Macro string
Type string
Width *int
Height *int
Format PictureOptions
Color ShapeColor
Line ShapeLine
Paragraph []ShapeParagraph
}
// shapeParagraphOptions directly maps the format settings of the paragraph in
// ShapeParagraph directly maps the format settings of the paragraph in
// the shape.
type shapeParagraphOptions struct {
Font Font `json:"font"`
Text string `json:"text"`
type ShapeParagraph struct {
Font Font
Text string
}
// shapeColorOptions directly maps the color settings of the shape.
type shapeColorOptions struct {
Line string `json:"line"`
Fill string `json:"fill"`
Effect string `json:"effect"`
// ShapeColor directly maps the color settings of the shape.
type ShapeColor struct {
Line string
Fill string
Effect string
}
// lineOptions directly maps the line settings of the shape.
type lineOptions struct {
Width float64 `json:"width"`
// ShapeLine directly maps the line settings of the shape.
type ShapeLine struct {
Width *float64
}

View File

@ -83,6 +83,6 @@ type xlsxRPr struct {
// RichTextRun directly maps the settings of the rich text run.
type RichTextRun struct {
Font *Font `json:"font"`
Text string `json:"text"`
Font *Font
Text string
}

View File

@ -314,63 +314,63 @@ type xlsxStyleColors struct {
// Alignment directly maps the alignment settings of the cells.
type Alignment struct {
Horizontal string `json:"horizontal"`
Indent int `json:"indent"`
JustifyLastLine bool `json:"justify_last_line"`
ReadingOrder uint64 `json:"reading_order"`
RelativeIndent int `json:"relative_indent"`
ShrinkToFit bool `json:"shrink_to_fit"`
TextRotation int `json:"text_rotation"`
Vertical string `json:"vertical"`
WrapText bool `json:"wrap_text"`
Horizontal string
Indent int
JustifyLastLine bool
ReadingOrder uint64
RelativeIndent int
ShrinkToFit bool
TextRotation int
Vertical string
WrapText bool
}
// Border directly maps the border settings of the cells.
type Border struct {
Type string `json:"type"`
Color string `json:"color"`
Style int `json:"style"`
Type string
Color string
Style int
}
// Font directly maps the font settings of the fonts.
type Font struct {
Bold bool `json:"bold"`
Italic bool `json:"italic"`
Underline string `json:"underline"`
Family string `json:"family"`
Size float64 `json:"size"`
Strike bool `json:"strike"`
Color string `json:"color"`
ColorIndexed int `json:"color_indexed"`
ColorTheme *int `json:"color_theme"`
ColorTint float64 `json:"color_tint"`
VertAlign string `json:"vertAlign"`
Bold bool
Italic bool
Underline string
Family string
Size float64
Strike bool
Color string
ColorIndexed int
ColorTheme *int
ColorTint float64
VertAlign string
}
// Fill directly maps the fill settings of the cells.
type Fill struct {
Type string `json:"type"`
Pattern int `json:"pattern"`
Color []string `json:"color"`
Shading int `json:"shading"`
Type string
Pattern int
Color []string
Shading int
}
// Protection directly maps the protection settings of the cells.
type Protection struct {
Hidden bool `json:"hidden"`
Locked bool `json:"locked"`
Hidden bool
Locked bool
}
// Style directly maps the style settings of the cells.
type Style struct {
Border []Border `json:"border"`
Fill Fill `json:"fill"`
Font *Font `json:"font"`
Alignment *Alignment `json:"alignment"`
Protection *Protection `json:"protection"`
NumFmt int `json:"number_format"`
DecimalPlaces int `json:"decimal_places"`
CustomNumFmt *string `json:"custom_number_format"`
Lang string `json:"lang"`
NegRed bool `json:"negred"`
Border []Border
Fill Fill
Font *Font
Alignment *Alignment
Protection *Protection
NumFmt int
DecimalPlaces int
CustomNumFmt *string
Lang string
NegRed bool
}

View File

@ -196,22 +196,25 @@ type xlsxTableStyleInfo struct {
ShowColumnStripes bool `xml:"showColumnStripes,attr"`
}
// tableOptions directly maps the format settings of the table.
type tableOptions struct {
TableName string `json:"table_name"`
TableStyle string `json:"table_style"`
ShowFirstColumn bool `json:"show_first_column"`
ShowLastColumn bool `json:"show_last_column"`
ShowRowStripes bool `json:"show_row_stripes"`
ShowColumnStripes bool `json:"show_column_stripes"`
// TableOptions directly maps the format settings of the table.
type TableOptions struct {
Name string
StyleName string
ShowFirstColumn bool
ShowLastColumn bool
ShowRowStripes *bool
ShowColumnStripes bool
}
// autoFilterOptions directly maps the auto filter settings.
type autoFilterOptions struct {
Column string `json:"column"`
Expression string `json:"expression"`
FilterList []struct {
Column string `json:"column"`
Value []int `json:"value"`
} `json:"filter_list"`
// AutoFilterListOptions directly maps the auto filter list settings.
type AutoFilterListOptions struct {
Column string
Value []int
}
// AutoFilterOptions directly maps the auto filter settings.
type AutoFilterOptions struct {
Column string
Expression string
FilterList []AutoFilterListOptions
}

View File

@ -308,23 +308,23 @@ type xlsxCustomWorkbookView struct {
// DefinedName directly maps the name for a cell or cell range on a
// worksheet.
type DefinedName struct {
Name string `json:"name,omitempty"`
Comment string `json:"comment,omitempty"`
RefersTo string `json:"refers_to,omitempty"`
Scope string `json:"scope,omitempty"`
Name string
Comment string
RefersTo string
Scope string
}
// WorkbookPropsOptions directly maps the settings of workbook proprieties.
type WorkbookPropsOptions struct {
Date1904 *bool `json:"date_1994,omitempty"`
FilterPrivacy *bool `json:"filter_privacy,omitempty"`
CodeName *string `json:"code_name,omitempty"`
Date1904 *bool
FilterPrivacy *bool
CodeName *string
}
// WorkbookProtectionOptions directly maps the settings of workbook protection.
type WorkbookProtectionOptions struct {
AlgorithmName string `json:"algorithmName,omitempty"`
Password string `json:"password,omitempty"`
LockStructure bool `json:"lockStructure,omitempty"`
LockWindows bool `json:"lockWindows,omitempty"`
AlgorithmName string
Password string
LockStructure bool
LockWindows bool
}

View File

@ -794,141 +794,143 @@ type xlsxX14Sparkline struct {
// SparklineOptions directly maps the settings of the sparkline.
type SparklineOptions struct {
Location []string `json:"location"`
Range []string `json:"range"`
Max int `json:"max"`
CustMax int `json:"cust_max"`
Min int `json:"min"`
CustMin int `json:"cust_min"`
Type string `json:"hype"`
Weight float64 `json:"weight"`
DateAxis bool `json:"date_axis"`
Markers bool `json:"markers"`
High bool `json:"high"`
Low bool `json:"low"`
First bool `json:"first"`
Last bool `json:"last"`
Negative bool `json:"negative"`
Axis bool `json:"axis"`
Hidden bool `json:"hidden"`
Reverse bool `json:"reverse"`
Style int `json:"style"`
SeriesColor string `json:"series_color"`
NegativeColor string `json:"negative_color"`
MarkersColor string `json:"markers_color"`
FirstColor string `json:"first_color"`
LastColor string `json:"last_color"`
HightColor string `json:"hight_color"`
LowColor string `json:"low_color"`
EmptyCells string `json:"empty_cells"`
Location []string
Range []string
Max int
CustMax int
Min int
CustMin int
Type string
Weight float64
DateAxis bool
Markers bool
High bool
Low bool
First bool
Last bool
Negative bool
Axis bool
Hidden bool
Reverse bool
Style int
SeriesColor string
NegativeColor string
MarkersColor string
FirstColor string
LastColor string
HightColor string
LowColor string
EmptyCells string
}
// panesOptions directly maps the settings of the panes.
type panesOptions struct {
Freeze bool `json:"freeze"`
Split bool `json:"split"`
XSplit int `json:"x_split"`
YSplit int `json:"y_split"`
TopLeftCell string `json:"top_left_cell"`
ActivePane string `json:"active_pane"`
Panes []struct {
SQRef string `json:"sqref"`
ActiveCell string `json:"active_cell"`
Pane string `json:"pane"`
} `json:"panes"`
// PaneOptions directly maps the settings of the pane.
type PaneOptions struct {
SQRef string
ActiveCell string
Pane string
}
// conditionalOptions directly maps the conditional format settings of the cells.
type conditionalOptions struct {
Type string `json:"type"`
AboveAverage bool `json:"above_average,omitempty"`
Percent bool `json:"percent,omitempty"`
Format int `json:"format,omitempty"`
Criteria string `json:"criteria,omitempty"`
Value string `json:"value,omitempty"`
Minimum string `json:"minimum,omitempty"`
Maximum string `json:"maximum,omitempty"`
MinType string `json:"min_type,omitempty"`
MidType string `json:"mid_type,omitempty"`
MaxType string `json:"max_type,omitempty"`
MinValue string `json:"min_value,omitempty"`
MidValue string `json:"mid_value,omitempty"`
MaxValue string `json:"max_value,omitempty"`
MinColor string `json:"min_color,omitempty"`
MidColor string `json:"mid_color,omitempty"`
MaxColor string `json:"max_color,omitempty"`
MinLength string `json:"min_length,omitempty"`
MaxLength string `json:"max_length,omitempty"`
MultiRange string `json:"multi_range,omitempty"`
BarColor string `json:"bar_color,omitempty"`
// Panes directly maps the settings of the panes.
type Panes struct {
Freeze bool
Split bool
XSplit int
YSplit int
TopLeftCell string
ActivePane string
Panes []PaneOptions
}
// ConditionalFormatOptions directly maps the conditional format settings of the cells.
type ConditionalFormatOptions struct {
Type string
AboveAverage bool
Percent bool
Format int
Criteria string
Value string
Minimum string
Maximum string
MinType string
MidType string
MaxType string
MinValue string
MidValue string
MaxValue string
MinColor string
MidColor string
MaxColor string
MinLength string
MaxLength string
BarColor string
}
// SheetProtectionOptions directly maps the settings of worksheet protection.
type SheetProtectionOptions struct {
AlgorithmName string `json:"algorithm_name,omitempty"`
AutoFilter bool `json:"auto_filter,omitempty"`
DeleteColumns bool `json:"delete_columns,omitempty"`
DeleteRows bool `json:"delete_rows,omitempty"`
EditObjects bool `json:"edit_objects,omitempty"`
EditScenarios bool `json:"edit_scenarios,omitempty"`
FormatCells bool `json:"format_cells,omitempty"`
FormatColumns bool `json:"format_columns,omitempty"`
FormatRows bool `json:"format_rows,omitempty"`
InsertColumns bool `json:"insert_columns,omitempty"`
InsertHyperlinks bool `json:"insert_hyperlinks,omitempty"`
InsertRows bool `json:"insert_rows,omitempty"`
Password string `json:"password,omitempty"`
PivotTables bool `json:"pivot_tables,omitempty"`
SelectLockedCells bool `json:"select_locked_cells,omitempty"`
SelectUnlockedCells bool `json:"select_unlocked_cells,omitempty"`
Sort bool `json:"sort,omitempty"`
AlgorithmName string
AutoFilter bool
DeleteColumns bool
DeleteRows bool
EditObjects bool
EditScenarios bool
FormatCells bool
FormatColumns bool
FormatRows bool
InsertColumns bool
InsertHyperlinks bool
InsertRows bool
Password string
PivotTables bool
SelectLockedCells bool
SelectUnlockedCells bool
Sort bool
}
// HeaderFooterOptions directly maps the settings of header and footer.
type HeaderFooterOptions struct {
AlignWithMargins bool `json:"align_with_margins,omitempty"`
DifferentFirst bool `json:"different_first,omitempty"`
DifferentOddEven bool `json:"different_odd_even,omitempty"`
ScaleWithDoc bool `json:"scale_with_doc,omitempty"`
OddHeader string `json:"odd_header,omitempty"`
OddFooter string `json:"odd_footer,omitempty"`
EvenHeader string `json:"even_header,omitempty"`
EvenFooter string `json:"even_footer,omitempty"`
FirstHeader string `json:"first_header,omitempty"`
FirstFooter string `json:"first_footer,omitempty"`
AlignWithMargins bool
DifferentFirst bool
DifferentOddEven bool
ScaleWithDoc bool
OddHeader string
OddFooter string
EvenHeader string
EvenFooter string
FirstHeader string
FirstFooter string
}
// PageLayoutMarginsOptions directly maps the settings of page layout margins.
type PageLayoutMarginsOptions struct {
Bottom *float64 `json:"bottom,omitempty"`
Footer *float64 `json:"footer,omitempty"`
Header *float64 `json:"header,omitempty"`
Left *float64 `json:"left,omitempty"`
Right *float64 `json:"right,omitempty"`
Top *float64 `json:"top,omitempty"`
Horizontally *bool `json:"horizontally,omitempty"`
Vertically *bool `json:"vertically,omitempty"`
Bottom *float64
Footer *float64
Header *float64
Left *float64
Right *float64
Top *float64
Horizontally *bool
Vertically *bool
}
// PageLayoutOptions directly maps the settings of page layout.
type PageLayoutOptions struct {
// Size defines the paper size of the worksheet.
Size *int `json:"size,omitempty"`
Size *int
// Orientation defines the orientation of page layout for a worksheet.
Orientation *string `json:"orientation,omitempty"`
Orientation *string
// FirstPageNumber specified the first printed page number. If no value is
// specified, then 'automatic' is assumed.
FirstPageNumber *uint `json:"first_page_number,omitempty"`
FirstPageNumber *uint
// AdjustTo defines the print scaling. This attribute is restricted to
// value ranging from 10 (10%) to 400 (400%). This setting is overridden
// when fitToWidth and/or fitToHeight are in use.
AdjustTo *uint `json:"adjust_to,omitempty"`
AdjustTo *uint
// FitToHeight specified the number of vertical pages to fit on.
FitToHeight *int `json:"fit_to_height,omitempty"`
FitToHeight *int
// FitToWidth specified the number of horizontal pages to fit on.
FitToWidth *int `json:"fit_to_width,omitempty"`
FitToWidth *int
// BlackAndWhite specified print black and white.
BlackAndWhite *bool `json:"black_and_white,omitempty"`
BlackAndWhite *bool
}
// ViewOptions directly maps the settings of sheet view.
@ -936,37 +938,37 @@ type ViewOptions struct {
// DefaultGridColor indicating that the consuming application should use
// the default grid lines color(system dependent). Overrides any color
// specified in colorId.
DefaultGridColor *bool `json:"default_grid_color,omitempty"`
DefaultGridColor *bool
// RightToLeft indicating whether the sheet is in 'right to left' display
// mode. When in this mode, Column A is on the far right, Column B; is one
// column left of Column A, and so on. Also, information in cells is
// displayed in the Right to Left format.
RightToLeft *bool `json:"right_to_left,omitempty"`
RightToLeft *bool
// ShowFormulas indicating whether this sheet should display formulas.
ShowFormulas *bool `json:"show_formulas,omitempty"`
ShowFormulas *bool
// ShowGridLines indicating whether this sheet should display grid lines.
ShowGridLines *bool `json:"show_grid_lines,omitempty"`
ShowGridLines *bool
// ShowRowColHeaders indicating whether the sheet should display row and
// column headings.
ShowRowColHeaders *bool `json:"show_row_col_headers,omitempty"`
ShowRowColHeaders *bool
// ShowRuler indicating this sheet should display ruler.
ShowRuler *bool `json:"show_ruler,omitempty"`
ShowRuler *bool
// ShowZeros indicating whether to "show a zero in cells that have zero
// value". When using a formula to reference another cell which is empty,
// the referenced value becomes 0 when the flag is true. (Default setting
// is true.)
ShowZeros *bool `json:"show_zeros,omitempty"`
ShowZeros *bool
// TopLeftCell specifies a location of the top left visible cell Location
// of the top left visible cell in the bottom right pane (when in
// Left-to-Right mode).
TopLeftCell *string `json:"top_left_cell,omitempty"`
TopLeftCell *string
// View indicating how sheet is displayed, by default it uses empty string
// available options: normal, pageLayout, pageBreakPreview
View *string `json:"low_color,omitempty"`
View *string
// ZoomScale specifies a window zoom magnification for current view
// representing percent values. This attribute is restricted to values
// ranging from 10 to 400. Horizontal & Vertical scale together.
ZoomScale *float64 `json:"zoom_scale,omitempty"`
ZoomScale *float64
}
// SheetPropsOptions directly maps the settings of sheet view.
@ -974,55 +976,55 @@ type SheetPropsOptions struct {
// Specifies a stable name of the sheet, which should not change over time,
// and does not change from user input. This name should be used by code
// to reference a particular sheet.
CodeName *string `json:"code_name,omitempty"`
CodeName *string
// EnableFormatConditionsCalculation indicating whether the conditional
// formatting calculations shall be evaluated. If set to false, then the
// min/max values of color scales or data bars or threshold values in Top N
// rules shall not be updated. Essentially the conditional
// formatting "calc" is off.
EnableFormatConditionsCalculation *bool `json:"enable_format_conditions_calculation,omitempty"`
EnableFormatConditionsCalculation *bool
// Published indicating whether the worksheet is published.
Published *bool `json:"published,omitempty"`
Published *bool
// AutoPageBreaks indicating whether the sheet displays Automatic Page
// Breaks.
AutoPageBreaks *bool `json:"auto_page_breaks,omitempty"`
AutoPageBreaks *bool
// FitToPage indicating whether the Fit to Page print option is enabled.
FitToPage *bool `json:"fit_to_page,omitempty"`
FitToPage *bool
// TabColorIndexed represents the indexed color value.
TabColorIndexed *int `json:"tab_color_indexed,omitempty"`
TabColorIndexed *int
// TabColorRGB represents the standard Alpha Red Green Blue color value.
TabColorRGB *string `json:"tab_color_rgb,omitempty"`
TabColorRGB *string
// TabColorTheme represents the zero-based index into the collection,
// referencing a particular value expressed in the Theme part.
TabColorTheme *int `json:"tab_color_theme,omitempty"`
TabColorTheme *int
// TabColorTint specifies the tint value applied to the color.
TabColorTint *float64 `json:"tab_color_tint,omitempty"`
TabColorTint *float64
// OutlineSummaryBelow indicating whether summary rows appear below detail
// in an outline, when applying an outline.
OutlineSummaryBelow *bool `json:"outline_summary_below,omitempty"`
OutlineSummaryBelow *bool
// OutlineSummaryRight indicating whether summary columns appear to the
// right of detail in an outline, when applying an outline.
OutlineSummaryRight *bool `json:"outline_summary_right,omitempty"`
OutlineSummaryRight *bool
// BaseColWidth specifies the number of characters of the maximum digit
// width of the normal style's font. This value does not include margin
// padding or extra padding for grid lines. It is only the number of
// characters.
BaseColWidth *uint8 `json:"base_col_width,omitempty"`
BaseColWidth *uint8
// DefaultColWidth specifies the default column width measured as the
// number of characters of the maximum digit width of the normal style's
// font.
DefaultColWidth *float64 `json:"default_col_width,omitempty"`
DefaultColWidth *float64
// DefaultRowHeight specifies the default row height measured in point
// size. Optimization so we don't have to write the height on all rows.
// This can be written out if most rows have custom height, to achieve the
// optimization.
DefaultRowHeight *float64 `json:"default_row_height,omitempty"`
DefaultRowHeight *float64
// CustomHeight specifies the custom height.
CustomHeight *bool `json:"custom_height,omitempty"`
CustomHeight *bool
// ZeroHeight specifies if rows are hidden.
ZeroHeight *bool `json:"zero_height,omitempty"`
ZeroHeight *bool
// ThickTop specifies if rows have a thick top border by default.
ThickTop *bool `json:"thick_top,omitempty"`
ThickTop *bool
// ThickBottom specifies if rows have a thick bottom border by default.
ThickBottom *bool `json:"thick_bottom,omitempty"`
ThickBottom *bool
}