This closes # 1704, support set the data labels position for the chart (#1755)

- Breaking change: remove the Sizes field in the ChartSeries data type
- Add new field DataLabelPosition in the ChartSeries data type, support to sets the position of the chart series data label
- Add new field BubbleSize in the Chart data type, support set the bubble size in all data series for the bubble chart or 3D bubble chart
- Add new exported ChartDataLabelPositionType data type
- Update docs and unit test for the AddChart function
- Fix a v2.7.1 regression bug, the bubble is hidden in the bubble or 3D bubble chart, commit ID: c2d6707a85
This commit is contained in:
yuegu520 2023-12-14 00:03:53 +08:00 committed by GitHub
parent 284345e471
commit dfaf418f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 35 deletions

View File

@ -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:

View File

@ -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}}},

View File

@ -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
}

View File

@ -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"

View File

@ -483,6 +483,7 @@ type cNumCache struct {
// the specific formatting and positioning settings.
type cDLbls struct {
NumFmt *cNumFmt `xml:"numFmt"`
DLblPos *attrValString `xml:"dLblPos"`
ShowLegendKey *attrValBool `xml:"showLegendKey"`
ShowVal *attrValBool `xml:"showVal"`
ShowCatName *attrValBool `xml:"showCatName"`
@ -577,6 +578,7 @@ type Chart struct {
PlotArea ChartPlotArea
Border ChartLine
ShowBlanksAs string
BubbleSize int
HoleSize int
order int
}
@ -604,9 +606,9 @@ type ChartLine struct {
type ChartSeries struct {
Name string
Categories string
Sizes string
Values string
Fill Fill
Line ChartLine
Marker ChartMarker
DataLabelPosition ChartDataLabelPositionType
}