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
|
||||
// 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.
|
||||
//
|
||||
|
|
|
@ -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"}},
|
||||
|
|
35
drawing.go
35
drawing.go
|
@ -66,7 +66,8 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|||
Title: &cTitle{
|
||||
Tx: cTx{
|
||||
Rich: &cRich{
|
||||
P: aP{
|
||||
P: []aP{
|
||||
{
|
||||
PPr: &aPPr{
|
||||
DefRPr: aRPr{
|
||||
Kern: 1200,
|
||||
|
@ -105,6 +106,7 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TxPr: cTxPr{
|
||||
P: aP{
|
||||
PPr: &aPPr{
|
||||
|
@ -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{
|
||||
|
|
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 {
|
||||
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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue