This closes #320, support custom chart axis font style

This commit is contained in:
xuri 2022-10-14 00:48:16 +08:00
parent 7363c1e333
commit 3d02726ad4
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
9 changed files with 78 additions and 76 deletions

View File

@ -750,16 +750,18 @@ 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.
//
@ -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

View File

@ -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"}`))

View File

@ -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

View File

@ -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
}

View File

@ -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 == "" {

View File

@ -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

View File

@ -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 {

View File

@ -537,13 +537,8 @@ type chartAxisOptions struct {
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"`
NumFormat string `json:"number_format"`
NumFont Font `json:"number_font"`
LogBase float64 `json:"logbase"`
NameLayout layoutOptions `json:"name_layout"`
}

View File

@ -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.