forked from p30928647/excelize
This closes #1553, the `AddChart` function support set primary titles
- Update unit tests and documentation - Lint issues fixed
This commit is contained in:
parent
9bc3fd7e9f
commit
f8aa3adf7e
18
chart.go
18
chart.go
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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"}},
|
||||||
|
|
93
drawing.go
93
drawing.go
|
@ -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{
|
||||||
|
|
16
numfmt.go
16
numfmt.go
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue