diff --git a/chart.go b/chart.go index 0caa505..ce11b59 100644 --- a/chart.go +++ b/chart.go @@ -750,22 +750,24 @@ func parseChartOptions(opts string) (*chartOptions, error) { // reverse_order // maximum // minimum +// number_font // // The properties of y_axis that can be set are: // // none // major_grid_lines // minor_grid_lines -// major_unit +// tick_label_skip // reverse_order // maximum // minimum +// number_font // // none: Disable axes. // -// major_grid_lines: Specifies major gridlines. +// major_grid_lines: Specifies major grid lines. // -// minor_grid_lines: Specifies minor gridlines. +// minor_grid_lines: 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. // @@ -777,6 +779,17 @@ func parseChartOptions(opts string) (*chartOptions, error) { // // minimum: Specifies that the fixed minimum, 0 is auto. The minimum property is optional. The default value is auto. // +// number_font: Specifies that the font of the horizontal and vertical axis. The properties of number_font that can be set are: +// +// 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. // // combo: Specifies the create a chart that combines two or more chart types diff --git a/chart_test.go b/chart_test.go index 3c80b39..a0f7156 100644 --- a/chart_test.go +++ b/chart_test.go @@ -116,7 +116,7 @@ func TestAddChart(t *testing.T) { // 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"}`)) + 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":{"number_font":{"bold":true,"italic":true,"underline":"dbl","color":"#000000"}},"y_axis":{"number_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"}`)) diff --git a/drawing.go b/drawing.go index 2da2573..974d627 100644 --- a/drawing.go +++ b/drawing.go @@ -1017,7 +1017,7 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs { MinorTickMark: &attrValString{Val: stringPtr("none")}, TickLblPos: &attrValString{Val: stringPtr("nextTo")}, SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(), + TxPr: f.drawPlotAreaTxPr(&opts.YAxis), CrossAx: &attrValInt{Val: intPtr(753999904)}, Crosses: &attrValString{Val: stringPtr("autoZero")}, Auto: &attrValBool{Val: boolPtr(true)}, @@ -1071,7 +1071,7 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs { MinorTickMark: &attrValString{Val: stringPtr("none")}, TickLblPos: &attrValString{Val: stringPtr("nextTo")}, SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(), + TxPr: f.drawPlotAreaTxPr(&opts.XAxis), CrossAx: &attrValInt{Val: intPtr(754001152)}, Crosses: &attrValString{Val: stringPtr("autoZero")}, CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, @@ -1114,7 +1114,7 @@ func (f *File) drawPlotAreaSerAx(opts *chartOptions) []*cAxs { AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])}, TickLblPos: &attrValString{Val: stringPtr("nextTo")}, SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(), + TxPr: f.drawPlotAreaTxPr(nil), CrossAx: &attrValInt{Val: intPtr(753999904)}, }, } @@ -1140,8 +1140,8 @@ func (f *File) drawPlotAreaSpPr() *cSpPr { } // drawPlotAreaTxPr provides a function to draw the c:txPr element. -func (f *File) drawPlotAreaTxPr() *cTxPr { - return &cTxPr{ +func (f *File) drawPlotAreaTxPr(opts *chartAxisOptions) *cTxPr { + cTxPr := &cTxPr{ BodyPr: aBodyPr{ Rot: -60000000, SpcFirstLastPara: true, @@ -1176,6 +1176,18 @@ func (f *File) drawPlotAreaTxPr() *cTxPr { EndParaRPr: &aEndParaRPr{Lang: "en-US"}, }, } + if opts != nil { + cTxPr.P.PPr.DefRPr.B = opts.NumFont.Bold + cTxPr.P.PPr.DefRPr.I = opts.NumFont.Italic + if idx := inStrSlice(supportedDrawingUnderlineTypes, opts.NumFont.Underline, true); idx != -1 { + cTxPr.P.PPr.DefRPr.U = supportedDrawingUnderlineTypes[idx] + } + if opts.NumFont.Color != "" { + cTxPr.P.PPr.DefRPr.SolidFill.SchemeClr = nil + cTxPr.P.PPr.DefRPr.SolidFill.SrgbClr = &attrValString{Val: stringPtr(strings.ReplaceAll(strings.ToUpper(opts.NumFont.Color), "#", ""))} + } + } + return cTxPr } // drawingParser provides a function to parse drawingXML. In order to solve diff --git a/file.go b/file.go index c83d17e..7ce536c 100644 --- a/file.go +++ b/file.go @@ -72,7 +72,7 @@ func (f *File) SaveAs(name string, opts ...Options) error { return ErrMaxFilePathLength } f.Path = name - if _, ok := supportedContentType[filepath.Ext(f.Path)]; !ok { + if _, ok := supportedContentTypes[filepath.Ext(f.Path)]; !ok { return ErrWorkbookFileFormat } file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) @@ -112,7 +112,7 @@ func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) { f.options = &opts[i] } if len(f.Path) != 0 { - contentType, ok := supportedContentType[filepath.Ext(f.Path)] + contentType, ok := supportedContentTypes[filepath.Ext(f.Path)] if !ok { return 0, ErrWorkbookFileFormat } diff --git a/shape.go b/shape.go index eca354f..e3c6c8b 100644 --- a/shape.go +++ b/shape.go @@ -323,27 +323,6 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption colIdx := fromCol - 1 rowIdx := fromRow - 1 - textUnderlineType := map[string]bool{ - "none": true, - "words": true, - "sng": true, - "dbl": true, - "heavy": true, - "dotted": true, - "dottedHeavy": true, - "dash": true, - "dashHeavy": true, - "dashLong": true, - "dashLongHeavy": true, - "dotDash": true, - "dotDashHeavy": true, - "dotDotDash": true, - "dotDotDashHeavy": true, - "wavy": true, - "wavyHeavy": true, - "wavyDbl": true, - } - width := int(float64(opts.Width) * opts.Format.XScale) height := int(float64(opts.Height) * opts.Format.YScale) @@ -422,10 +401,9 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption } } for _, p := range opts.Paragraph { - u := p.Font.Underline - _, ok := textUnderlineType[u] - if !ok { - u = "none" + u := "none" + if idx := inStrSlice(supportedDrawingUnderlineTypes, p.Font.Underline, true); idx != -1 { + u = supportedDrawingUnderlineTypes[idx] } text := p.Text if text == "" { diff --git a/styles.go b/styles.go index 5299fbd..dc34427 100644 --- a/styles.go +++ b/styles.go @@ -2087,7 +2087,6 @@ func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (fontID int) // newFont provides a function to add font style by given cell format // settings. func (f *File) newFont(style *Style) *xlsxFont { - fontUnderlineType := map[string]string{"single": "single", "double": "double"} if style.Font.Size < MinFontSize { style.Font.Size = 11 } @@ -2112,9 +2111,8 @@ func (f *File) newFont(style *Style) *xlsxFont { if style.Font.Strike { fnt.Strike = &attrValBool{Val: &style.Font.Strike} } - val, ok := fontUnderlineType[style.Font.Underline] - if ok { - fnt.U = &attrValString{Val: stringPtr(val)} + if idx := inStrSlice(supportedUnderlineTypes, style.Font.Underline, true); idx != -1 { + fnt.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])} } return &fnt } @@ -3100,13 +3098,10 @@ func drawCondFmtCellIs(p int, ct string, format *conditionalOptions) *xlsxCfRule DxfID: &format.Format, } // "between" and "not between" criteria require 2 values. - _, ok := map[string]bool{"between": true, "notBetween": true}[ct] - if ok { - c.Formula = append(c.Formula, format.Minimum) - c.Formula = append(c.Formula, format.Maximum) + if ct == "between" || ct == "notBetween" { + c.Formula = append(c.Formula, []string{format.Minimum, format.Maximum}...) } - _, ok = map[string]bool{"equal": true, "notEqual": true, "greaterThan": true, "lessThan": true, "greaterThanOrEqual": true, "lessThanOrEqual": true, "containsText": true, "notContains": true, "beginsWith": true, "endsWith": true}[ct] - if ok { + if idx := inStrSlice([]string{"equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual", "containsText", "notContains", "beginsWith", "endsWith"}, ct, true); idx != -1 { c.Formula = append(c.Formula, format.Value) } return c @@ -3124,8 +3119,7 @@ func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule DxfID: &format.Format, Percent: format.Percent, } - rank, err := strconv.Atoi(format.Value) - if err == nil { + if rank, err := strconv.Atoi(format.Value); err == nil { c.Rank = rank } return c diff --git a/table.go b/table.go index f7cac20..112882c 100644 --- a/table.go +++ b/table.go @@ -516,7 +516,7 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str } } } - // if the string token contains an Excel match character then change the + // If the string token contains an Excel match character then change the // operator type to indicate a non "simple" equality. re, _ = regexp.Match("[*?]", []byte(token)) if operator == 2 && re { diff --git a/xmlChart.go b/xmlChart.go index 9024770..27a790e 100644 --- a/xmlChart.go +++ b/xmlChart.go @@ -521,31 +521,26 @@ type cPageMargins struct { // 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:"num_format"` - NumFont struct { - Color string `json:"color"` - Bold bool `json:"bold"` - Italic bool `json:"italic"` - Underline bool `json:"underline"` - } `json:"num_font"` - LogBase float64 `json:"logbase"` - NameLayout layoutOptions `json:"name_layout"` + 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"` + NumFont Font `json:"number_font"` + LogBase float64 `json:"logbase"` + NameLayout layoutOptions `json:"name_layout"` } // chartDimensionOptions directly maps the dimension of the chart. diff --git a/xmlDrawing.go b/xmlDrawing.go index 6a2f79d..b52e449 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -144,8 +144,8 @@ const ( // supportedImageTypes defined supported image types. var supportedImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".emf": ".emf", ".wmf": ".wmf", ".emz": ".emz", ".wmz": ".wmz"} -// supportedContentType defined supported file format types. -var supportedContentType = map[string]string{ +// supportedContentTypes defined supported file format types. +var supportedContentTypes = map[string]string{ ".xlam": ContentTypeAddinMacro, ".xlsm": ContentTypeMacro, ".xlsx": ContentTypeSheetML, @@ -153,6 +153,16 @@ var supportedContentType = map[string]string{ ".xltx": ContentTypeTemplate, } +// supportedUnderlineTypes defined supported underline types. +var supportedUnderlineTypes = []string{"none", "single", "double"} + +// supportedDrawingUnderlineTypes defined supported underline types in drawing +// markup language. +var supportedDrawingUnderlineTypes = []string{ + "none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy", + "wavyDbl", +} + // xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This // element specifies non-visual canvas properties. This allows for additional // information that does not affect the appearance of the picture to be stored.