diff --git a/chart.go b/chart.go index 5b7a5a2..ffc0456 100644 --- a/chart.go +++ b/chart.go @@ -688,11 +688,11 @@ func (opts *Chart) parseTitle() { // // Name // Categories -// Sizes // Values // Fill // Line // Marker +// DataLabelPosition // // Name: Set the name for the series. The name is displayed in the chart legend // and in the formula bar. The 'Name' property is optional and if it isn't @@ -703,8 +703,6 @@ func (opts *Chart) parseTitle() { // the same as the X axis. In most chart types the 'Categories' property is // optional and the chart will just assume a sequential series from 1..n. // -// Sizes: This sets the bubble size in a data series. -// // Values: This is the most important property of a series and is the only // mandatory option for every chart object. This option links the chart with // the worksheet data that it displays. @@ -733,6 +731,8 @@ func (opts *Chart) parseTitle() { // x // auto // +// DataLabelPosition: This sets the position of the chart series data label. +// // Set properties of the chart legend. The options that can be set are: // // Position @@ -776,11 +776,11 @@ func (opts *Chart) parseTitle() { // Specifies that each data marker in the series has a different color by // 'VaryColors'. The default value is true. // -// Set chart offset, scale, aspect ratio setting and print settings by format, +// Set chart offset, scale, aspect ratio setting and print settings by 'Format', // same as function 'AddPicture'. // -// Set the position of the chart plot area by PlotArea. The properties that can -// be set are: +// Set the position of the chart plot area by 'PlotArea'. The properties that +// can be set are: // // SecondPlotValues // ShowBubbleSize @@ -891,6 +891,15 @@ func (opts *Chart) parseTitle() { // Set chart size by 'Dimension' property. The 'Dimension' property is optional. // The default width is 480, and height is 260. // +// Set the bubble size in all data series for the bubble chart or 3D bubble +// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional. +// The default width is 100, and the value should be great than 0 and less or +// equal than 300. +// +// Set the doughnut hole size in all data series for the doughnut chart by +// 'HoleSize' property. The 'HoleSize' property is optional. The default width +// is 75, and the value should be great than 0 and less or equal than 90. +// // combo: Specifies the create a chart that combines two or more chart types in // a single chart. For example, create a clustered column - line chart with // data Sheet1!$E$1:$L$15: diff --git a/chart_test.go b/chart_test.go index a988d3f..350852e 100644 --- a/chart_test.go +++ b/chart_test.go @@ -176,14 +176,14 @@ func TestAddChart(t *testing.T) { } series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}} series4 := []ChartSeries{ - {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Sizes: "Sheet1!$B$30:$D$30"}, - {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31", Sizes: "Sheet1!$B$31:$D$31"}, - {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32", Sizes: "Sheet1!$B$32:$D$32"}, - {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33", Sizes: "Sheet1!$B$33:$D$33"}, - {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34", Sizes: "Sheet1!$B$34:$D$34"}, - {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35", Sizes: "Sheet1!$B$35:$D$35"}, - {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36", Sizes: "Sheet1!$B$36:$D$36"}, - {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Sizes: "Sheet1!$B$37:$D$37"}, + {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", DataLabelPosition: ChartDataLabelsPositionAbove}, + {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31", DataLabelPosition: ChartDataLabelsPositionLeft}, + {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32", DataLabelPosition: ChartDataLabelsPositionBestFit}, + {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33", DataLabelPosition: ChartDataLabelsPositionCenter}, + {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34", DataLabelPosition: ChartDataLabelsPositionInsideBase}, + {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35", DataLabelPosition: ChartDataLabelsPositionInsideEnd}, + {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36", DataLabelPosition: ChartDataLabelsPositionOutsideEnd}, + {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", DataLabelPosition: ChartDataLabelsPositionRight}, } format := GraphicOptions{ ScaleX: defaultDrawingScale, @@ -265,7 +265,7 @@ func TestAddChart(t *testing.T) { {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: Contour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, {sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: WireframeContour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Wireframe Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // bubble chart - {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series4, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series4, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", BubbleSize: 75}}, {sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: Bubble3D, Series: series4, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, // pie of pie chart {sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: PieOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Pie of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, diff --git a/drawing.go b/drawing.go index a65c885..43e3312 100644 --- a/drawing.go +++ b/drawing.go @@ -659,6 +659,9 @@ func (f *File) drawBubbleChart(opts *Chart) *cPlotArea { }, ValAx: []*cAxs{f.drawPlotAreaCatAx(opts)[0], f.drawPlotAreaValAx(opts)[0]}, } + if opts.BubbleSize > 0 && opts.BubbleSize <= 300 { + plotArea.BubbleChart.BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))} + } return plotArea } @@ -710,7 +713,7 @@ func (f *File) drawChartSeries(opts *Chart) *[]cSer { SpPr: f.drawChartSeriesSpPr(k, opts), Marker: f.drawChartSeriesMarker(k, opts), DPt: f.drawChartSeriesDPt(k, opts), - DLbls: f.drawChartSeriesDLbls(opts), + DLbls: f.drawChartSeriesDLbls(k, opts), InvertIfNegative: &attrValBool{Val: boolPtr(false)}, Cat: f.drawChartSeriesCat(opts.Series[k], opts), Smooth: &attrValBool{Val: boolPtr(opts.Series[k].Line.Smooth)}, @@ -885,12 +888,12 @@ func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal { // drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize // element by given chart series and format sets. func (f *File) drawCharSeriesBubbleSize(v ChartSeries, opts *Chart) *cVal { - if _, ok := map[ChartType]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok || v.Sizes == "" { + if _, ok := map[ChartType]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok { return nil } return &cVal{ NumRef: &cNumRef{ - F: v.Sizes, + F: v.Values, }, } } @@ -932,16 +935,33 @@ func (f *File) drawChartDLbls(opts *Chart) *cDLbls { } } +// inSupportedChartDataLabelsPositionType provides a method to check if an +// element is present in an array, and return the index of its location, +// otherwise return -1. +func inSupportedChartDataLabelsPositionType(a []ChartDataLabelPositionType, x ChartDataLabelPositionType) int { + for idx, n := range a { + if x == n { + return idx + } + } + return -1 +} + // drawChartSeriesDLbls provides a function to draw the c:dLbls element by // given format sets. -func (f *File) drawChartSeriesDLbls(opts *Chart) *cDLbls { +func (f *File) drawChartSeriesDLbls(i int, opts *Chart) *cDLbls { dLbls := f.drawChartDLbls(opts) chartSeriesDLbls := map[ChartType]*cDLbls{ - Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil, + Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, } if _, ok := chartSeriesDLbls[opts.Type]; ok { return nil } + if types, ok := supportedChartDataLabelsPosition[opts.Type]; ok && opts.Series[i].DataLabelPosition != ChartDataLabelsPositionUnset { + if inSupportedChartDataLabelsPositionType(types, opts.Series[i].DataLabelPosition) != -1 { + dLbls.DLblPos = &attrValString{Val: stringPtr(chartDataLabelsPositionTypes[opts.Series[i].DataLabelPosition])} + } + } return dLbls } diff --git a/templates.go b/templates.go index 41a107c..3861f6b 100644 --- a/templates.go +++ b/templates.go @@ -217,6 +217,54 @@ const ( ColorMappingTypeUnset int = -1 ) +// ChartDataLabelPositionType is the type of chart data labels position. +type ChartDataLabelPositionType byte + +// Chart data labels positions types enumeration. +const ( + ChartDataLabelsPositionUnset ChartDataLabelPositionType = iota + ChartDataLabelsPositionBestFit + ChartDataLabelsPositionBelow + ChartDataLabelsPositionCenter + ChartDataLabelsPositionInsideBase + ChartDataLabelsPositionInsideEnd + ChartDataLabelsPositionLeft + ChartDataLabelsPositionOutsideEnd + ChartDataLabelsPositionRight + ChartDataLabelsPositionAbove +) + +// chartDataLabelsPositionTypes defined supported chart data labels position +// types. +var chartDataLabelsPositionTypes = map[ChartDataLabelPositionType]string{ + ChartDataLabelsPositionBestFit: "bestFit", + ChartDataLabelsPositionBelow: "b", + ChartDataLabelsPositionCenter: "ctr", + ChartDataLabelsPositionInsideBase: "inBase", + ChartDataLabelsPositionInsideEnd: "inEnd", + ChartDataLabelsPositionLeft: "l", + ChartDataLabelsPositionOutsideEnd: "outEnd", + ChartDataLabelsPositionRight: "r", + ChartDataLabelsPositionAbove: "t", +} + +// supportedChartDataLabelsPosition defined supported chart data labels position +// types for each type of chart. +var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionType{ + Bar: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, + BarStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, + BarPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, + Col: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, + ColStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, + ColPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd}, + Line: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, + Pie: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, + Pie3D: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd}, + Scatter: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, + Bubble: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, + Bubble3D: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove}, +} + const ( defaultTempFileSST = "sharedStrings" defaultXMLPathCalcChain = "xl/calcChain.xml" diff --git a/xmlChart.go b/xmlChart.go index 93dd857..5e20930 100644 --- a/xmlChart.go +++ b/xmlChart.go @@ -482,14 +482,15 @@ type cNumCache struct { // entire series or the entire chart. It contains child elements that specify // the specific formatting and positioning settings. type cDLbls struct { - NumFmt *cNumFmt `xml:"numFmt"` - ShowLegendKey *attrValBool `xml:"showLegendKey"` - ShowVal *attrValBool `xml:"showVal"` - ShowCatName *attrValBool `xml:"showCatName"` - ShowSerName *attrValBool `xml:"showSerName"` - ShowPercent *attrValBool `xml:"showPercent"` - ShowBubbleSize *attrValBool `xml:"showBubbleSize"` - ShowLeaderLines *attrValBool `xml:"showLeaderLines"` + NumFmt *cNumFmt `xml:"numFmt"` + DLblPos *attrValString `xml:"dLblPos"` + ShowLegendKey *attrValBool `xml:"showLegendKey"` + ShowVal *attrValBool `xml:"showVal"` + ShowCatName *attrValBool `xml:"showCatName"` + ShowSerName *attrValBool `xml:"showSerName"` + ShowPercent *attrValBool `xml:"showPercent"` + ShowBubbleSize *attrValBool `xml:"showBubbleSize"` + ShowLeaderLines *attrValBool `xml:"showLeaderLines"` } // cLegend (Legend) directly maps the legend element. This element specifies @@ -577,6 +578,7 @@ type Chart struct { PlotArea ChartPlotArea Border ChartLine ShowBlanksAs string + BubbleSize int HoleSize int order int } @@ -602,11 +604,11 @@ type ChartLine struct { // ChartSeries directly maps the format settings of the chart series. type ChartSeries struct { - Name string - Categories string - Sizes string - Values string - Fill Fill - Line ChartLine - Marker ChartMarker + Name string + Categories string + Values string + Fill Fill + Line ChartLine + Marker ChartMarker + DataLabelPosition ChartDataLabelPositionType }