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
// Minimum
// Font
// NumFmt
// Title
//
// The properties of 'YAxis' that can be set are:
//
@ -805,6 +807,9 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// Maximum
// Minimum
// Font
// LogBase
// NumFmt
// Title
//
// None: Disable axes.
//
@ -813,14 +818,14 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// MinorGridLines: Specifies minor grid lines.
//
// 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.
//
// TickLabelSkip: Specifies how many tick labels to skip between label that is
// drawn. The 'TickLabelSkip' property is optional. The default value is auto.
//
// 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.
//
// Maximum: Specifies that the fixed maximum, 0 is auto. The 'Maximum' property
@ -841,6 +846,15 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
// Color
// 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.
// The default width is 480, and height is 290.
//

View File

@ -206,7 +206,7 @@ func TestAddChart(t *testing.T) {
sheetName, cell string
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: "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"}},

View File

@ -66,41 +66,43 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
Title: &cTitle{
Tx: cTx{
Rich: &cRich{
P: aP{
PPr: &aPPr{
DefRPr: aRPr{
Kern: 1200,
Strike: "noStrike",
U: "none",
Sz: 1400,
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{
Val: intPtr(65000),
},
LumOff: &attrValInt{
Val: intPtr(35000),
P: []aP{
{
PPr: &aPPr{
DefRPr: aRPr{
Kern: 1200,
Strike: "noStrike",
U: "none",
Sz: 1400,
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{
Val: intPtr(65000),
},
LumOff: &attrValInt{
Val: intPtr(35000),
},
},
},
},
Ea: &aEa{
Typeface: "+mn-ea",
},
Cs: &aCs{
Typeface: "+mn-cs",
},
Latin: &xlsxCTTextFont{
Typeface: "+mn-lt",
Ea: &aEa{
Typeface: "+mn-ea",
},
Cs: &aCs{
Typeface: "+mn-cs",
},
Latin: &xlsxCTTextFont{
Typeface: "+mn-lt",
},
},
},
},
R: &aR{
RPr: aRPr{
Lang: "en-US",
AltLang: "en-US",
R: &aR{
RPr: aRPr{
Lang: "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"},
MajorTickMark: &attrValString{Val: stringPtr("none")},
MinorTickMark: &attrValString{Val: stringPtr("none")},
Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""),
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
SpPr: f.drawPlotAreaSpPr(),
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
@ -1110,6 +1113,7 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
},
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])},
Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"),
NumFmt: &cNumFmt{
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.
func (f *File) drawPlotAreaSpPr() *cSpPr {
return &cSpPr{

View File

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

View File

@ -1093,7 +1093,7 @@ func TestNumFmt(t *testing.T) {
}
}
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.False(t, changeNumFmtCode)
}

View File

@ -74,7 +74,7 @@ type cTx struct {
type cRich struct {
BodyPr aBodyPr `xml:"a:bodyPr,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
@ -351,6 +351,7 @@ type cAxs struct {
AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"`
Title *cTitle `xml:"title"`
NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"`
@ -539,6 +540,7 @@ type ChartAxis struct {
Font Font
LogBase float64
NumFmt ChartNumFmt
Title []RichTextRun
}
// ChartDimension directly maps the dimension of the chart.