This closes #1553, the `AddChart` function support set primary titles

- Update unit tests and documentation
- Lint issues fixed
This commit is contained in:
xuri 2023-06-18 00:12:50 +08:00
parent 9bc3fd7e9f
commit f8aa3adf7e
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 92 additions and 43 deletions

View File

@ -794,6 +794,8 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// Maximum // Maximum
// Minimum // Minimum
// Font // Font
// NumFmt
// Title
// //
// The properties of 'YAxis' that can be set are: // The properties of 'YAxis' that can be set are:
// //
@ -805,6 +807,9 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// Maximum // Maximum
// Minimum // Minimum
// Font // Font
// LogBase
// NumFmt
// Title
// //
// None: Disable axes. // None: Disable axes.
// //
@ -813,14 +818,14 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// MinorGridLines: Specifies minor grid lines. // MinorGridLines: Specifies minor grid lines.
// //
// MajorUnit: Specifies the distance between major ticks. Shall contain a // MajorUnit: Specifies the distance between major ticks. Shall contain a
// positive floating-point number. The MajorUnit property is optional. The // positive floating-point number. The 'MajorUnit' property is optional. The
// default value is auto. // default value is auto.
// //
// TickLabelSkip: Specifies how many tick labels to skip between label that is // TickLabelSkip: Specifies how many tick labels to skip between label that is
// drawn. The 'TickLabelSkip' property is optional. The default value is auto. // drawn. The 'TickLabelSkip' property is optional. The default value is auto.
// //
// ReverseOrder: Specifies that the categories or values on reverse order // ReverseOrder: Specifies that the categories or values on reverse order
// (orientation of the chart). The ReverseOrder property is optional. The // (orientation of the chart). The 'ReverseOrder' property is optional. The
// default value is false. // default value is false.
// //
// Maximum: Specifies that the fixed maximum, 0 is auto. The 'Maximum' property // Maximum: Specifies that the fixed maximum, 0 is auto. The 'Maximum' property
@ -841,6 +846,15 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// Color // Color
// VertAlign // VertAlign
// //
// LogBase: Specifies logarithmic scale for the YAxis.
//
// NumFmt: Specifies that if linked to source and set custom number format code
// for axis. The 'NumFmt' property is optional. The default format code is
// 'General'.
//
// Title: Specifies that the primary horizontal or vertical axis title. The
// 'Title' property is optional.
//
// Set chart size by 'Dimension' property. The 'Dimension' property is optional. // Set chart size by 'Dimension' property. The 'Dimension' property is optional.
// The default width is 480, and height is 290. // The default width is 480, and height is 290.
// //

View File

@ -206,7 +206,7 @@ func TestAddChart(t *testing.T) {
sheetName, cell string sheetName, cell string
opts *Chart opts *Chart
}{ }{
{sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", 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: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}},
{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: "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: "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: "bottom", ShowLegendKey: false}, Title: ChartTitle{Name: "3D Clustered Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: Col3DClustered, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: ChartTitle{Name: "3D Clustered Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},

View File

@ -66,41 +66,43 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
Title: &cTitle{ Title: &cTitle{
Tx: cTx{ Tx: cTx{
Rich: &cRich{ Rich: &cRich{
P: aP{ P: []aP{
PPr: &aPPr{ {
DefRPr: aRPr{ PPr: &aPPr{
Kern: 1200, DefRPr: aRPr{
Strike: "noStrike", Kern: 1200,
U: "none", Strike: "noStrike",
Sz: 1400, U: "none",
SolidFill: &aSolidFill{ Sz: 1400,
SchemeClr: &aSchemeClr{ SolidFill: &aSolidFill{
Val: "tx1", SchemeClr: &aSchemeClr{
LumMod: &attrValInt{ Val: "tx1",
Val: intPtr(65000), LumMod: &attrValInt{
}, Val: intPtr(65000),
LumOff: &attrValInt{ },
Val: intPtr(35000), LumOff: &attrValInt{
Val: intPtr(35000),
},
}, },
}, },
}, Ea: &aEa{
Ea: &aEa{ Typeface: "+mn-ea",
Typeface: "+mn-ea", },
}, Cs: &aCs{
Cs: &aCs{ Typeface: "+mn-cs",
Typeface: "+mn-cs", },
}, Latin: &xlsxCTTextFont{
Latin: &xlsxCTTextFont{ Typeface: "+mn-lt",
Typeface: "+mn-lt", },
}, },
}, },
}, R: &aR{
R: &aR{ RPr: aRPr{
RPr: aRPr{ Lang: "en-US",
Lang: "en-US", AltLang: "en-US",
AltLang: "en-US", },
T: opts.Title.Name,
}, },
T: opts.Title.Name,
}, },
}, },
}, },
@ -1059,6 +1061,7 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
NumFmt: &cNumFmt{FormatCode: "General"}, NumFmt: &cNumFmt{FormatCode: "General"},
MajorTickMark: &attrValString{Val: stringPtr("none")}, MajorTickMark: &attrValString{Val: stringPtr("none")},
MinorTickMark: &attrValString{Val: stringPtr("none")}, MinorTickMark: &attrValString{Val: stringPtr("none")},
Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""),
TickLblPos: &attrValString{Val: stringPtr("nextTo")}, TickLblPos: &attrValString{Val: stringPtr("nextTo")},
SpPr: f.drawPlotAreaSpPr(), SpPr: f.drawPlotAreaSpPr(),
TxPr: f.drawPlotAreaTxPr(&opts.YAxis), TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
@ -1110,6 +1113,7 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
}, },
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)}, Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])}, AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])},
Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"),
NumFmt: &cNumFmt{ NumFmt: &cNumFmt{
FormatCode: chartValAxNumFmtFormatCode[opts.Type], FormatCode: chartValAxNumFmtFormatCode[opts.Type],
}, },
@ -1169,6 +1173,35 @@ func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
} }
} }
// drawPlotAreaTitles provides a function to draw the c:title element.
func (f *File) drawPlotAreaTitles(runs []RichTextRun, vert string) *cTitle {
if len(runs) == 0 {
return nil
}
title := &cTitle{Tx: cTx{Rich: &cRich{}}, Overlay: &attrValBool{Val: boolPtr(false)}}
for _, run := range runs {
r := &aR{T: run.Text}
if run.Font != nil {
r.RPr.B, r.RPr.I = run.Font.Bold, run.Font.Italic
if run.Font.Color != "" {
r.RPr.SolidFill = &aSolidFill{SrgbClr: &attrValString{Val: stringPtr(run.Font.Color)}}
}
if run.Font.Size > 0 {
r.RPr.Sz = run.Font.Size * 100
}
}
title.Tx.Rich.P = append(title.Tx.Rich.P, aP{
PPr: &aPPr{DefRPr: aRPr{}},
R: r,
EndParaRPr: &aEndParaRPr{Lang: "en-US", AltLang: "en-US"},
})
}
if vert == "horz" {
title.Tx.Rich.BodyPr = aBodyPr{Rot: -5400000, Vert: vert}
}
return title
}
// drawPlotAreaSpPr provides a function to draw the c:spPr element. // drawPlotAreaSpPr provides a function to draw the c:spPr element.
func (f *File) drawPlotAreaSpPr() *cSpPr { func (f *File) drawPlotAreaSpPr() *cSpPr {
return &cSpPr{ return &cSpPr{

View File

@ -1190,7 +1190,7 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
} }
for _, token := range nf.section[nf.sectionIdx].Items { for _, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeCurrencyLanguage { if token.TType == nfp.TokenTypeCurrencyLanguage {
if err, changeNumFmtCode := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode { if changeNumFmtCode, err := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode {
return nf.value return nf.value
} }
result += nf.currencyString result += nf.currencyString
@ -1326,7 +1326,7 @@ func (nf *numberFormat) dateTimeHandler() string {
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
for i, token := range nf.section[nf.sectionIdx].Items { for i, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeCurrencyLanguage { if token.TType == nfp.TokenTypeCurrencyLanguage {
if err, changeNumFmtCode := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode { if changeNumFmtCode, err := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode {
return nf.value return nf.value
} }
nf.result += nf.currencyString nf.result += nf.currencyString
@ -1397,28 +1397,28 @@ func (nf *numberFormat) positiveHandler() string {
// currencyLanguageHandler will be handling currency and language types tokens // currencyLanguageHandler will be handling currency and language types tokens
// for a number format expression. // for a number format expression.
func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (error, bool) { func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (bool, error) {
for _, part := range token.Parts { for _, part := range token.Parts {
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 { if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
return ErrUnsupportedNumberFormat, false return false, ErrUnsupportedNumberFormat
} }
if part.Token.TType == nfp.TokenSubTypeLanguageInfo { if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
if strings.EqualFold(part.Token.TValue, "F800") { // [$-x-sysdate] if strings.EqualFold(part.Token.TValue, "F800") { // [$-x-sysdate]
if nf.opts != nil && nf.opts.LongDatePattern != "" { if nf.opts != nil && nf.opts.LongDatePattern != "" {
nf.value = format(nf.value, nf.opts.LongDatePattern, nf.date1904, nf.cellType, nf.opts) nf.value = format(nf.value, nf.opts.LongDatePattern, nf.date1904, nf.cellType, nf.opts)
return nil, true return true, nil
} }
part.Token.TValue = "409" part.Token.TValue = "409"
} }
if strings.EqualFold(part.Token.TValue, "F400") { // [$-x-systime] if strings.EqualFold(part.Token.TValue, "F400") { // [$-x-systime]
if nf.opts != nil && nf.opts.LongTimePattern != "" { if nf.opts != nil && nf.opts.LongTimePattern != "" {
nf.value = format(nf.value, nf.opts.LongTimePattern, nf.date1904, nf.cellType, nf.opts) nf.value = format(nf.value, nf.opts.LongTimePattern, nf.date1904, nf.cellType, nf.opts)
return nil, true return true, nil
} }
part.Token.TValue = "409" part.Token.TValue = "409"
} }
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok { if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
return ErrUnsupportedNumberFormat, false return false, ErrUnsupportedNumberFormat
} }
nf.localCode = strings.ToUpper(part.Token.TValue) nf.localCode = strings.ToUpper(part.Token.TValue)
} }
@ -1426,7 +1426,7 @@ func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (error, bool) {
nf.currencyString = part.Token.TValue nf.currencyString = part.Token.TValue
} }
} }
return nil, false return false, nil
} }
// localAmPm return AM/PM name by supported language ID. // localAmPm return AM/PM name by supported language ID.

View File

@ -1093,7 +1093,7 @@ func TestNumFmt(t *testing.T) {
} }
} }
nf := numberFormat{} nf := numberFormat{}
err, changeNumFmtCode := nf.currencyLanguageHandler(nfp.Token{Parts: []nfp.Part{{}}}) changeNumFmtCode, err := nf.currencyLanguageHandler(nfp.Token{Parts: []nfp.Part{{}}})
assert.Equal(t, ErrUnsupportedNumberFormat, err) assert.Equal(t, ErrUnsupportedNumberFormat, err)
assert.False(t, changeNumFmtCode) assert.False(t, changeNumFmtCode)
} }

View File

@ -74,7 +74,7 @@ type cTx struct {
type cRich struct { type cRich struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"` BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
LstStyle string `xml:"a:lstStyle,omitempty"` LstStyle string `xml:"a:lstStyle,omitempty"`
P aP `xml:"a:p"` P []aP `xml:"a:p"`
} }
// aBodyPr (Body Properties) directly maps the a:bodyPr element. This element // aBodyPr (Body Properties) directly maps the a:bodyPr element. This element
@ -351,6 +351,7 @@ type cAxs struct {
AxPos *attrValString `xml:"axPos"` AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"` MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"` MinorGridlines *cChartLines `xml:"minorGridlines"`
Title *cTitle `xml:"title"`
NumFmt *cNumFmt `xml:"numFmt"` NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"` MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"` MinorTickMark *attrValString `xml:"minorTickMark"`
@ -539,6 +540,7 @@ type ChartAxis struct {
Font Font Font Font
LogBase float64 LogBase float64
NumFmt ChartNumFmt NumFmt ChartNumFmt
Title []RichTextRun
} }
// ChartDimension directly maps the dimension of the chart. // ChartDimension directly maps the dimension of the chart.