From 1a99dd4a233ce170e91cdccb3fedc11e1925b038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=81=94=E7=9B=9F=E5=B0=91=E4=BE=A0?= Date: Mon, 17 Jun 2024 21:54:15 +0800 Subject: [PATCH] This closes #1921, fix set axis format doesn't work in combo chart (#1924) - Fix incorrect primary axis titles position --- drawing.go | 229 +++++++++++++++++++++++------------------------------ 1 file changed, 99 insertions(+), 130 deletions(-) diff --git a/drawing.go b/drawing.go index 3c2da06..49b7fcb 100644 --- a/drawing.go +++ b/drawing.go @@ -108,7 +108,7 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) { }, } xlsxChartSpace.SpPr = f.drawShapeFill(opts.Fill, xlsxChartSpace.SpPr) - plotAreaFunc := map[ChartType]func(*Chart) *cPlotArea{ + plotAreaFunc := map[ChartType]func(pa *cPlotArea, opts *Chart) *cPlotArea{ Area: f.drawBaseChart, AreaStacked: f.drawBaseChart, AreaPercentStacked: f.drawBaseChart, @@ -179,11 +179,11 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) { immutable.FieldByName(mutable.Type().Field(i).Name).Set(field) } } - addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](opts)) + addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](xlsxChartSpace.Chart.PlotArea, opts)) order := len(opts.Series) for idx := range comboCharts { comboCharts[idx].order = order - addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](comboCharts[idx])) + addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](xlsxChartSpace.Chart.PlotArea, comboCharts[idx])) order += len(comboCharts[idx].Series) } chart, _ := xml.Marshal(xlsxChartSpace) @@ -193,7 +193,7 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) { // drawBaseChart provides a function to draw the c:plotArea element for bar, // and column series charts by given format sets. -func (f *File) drawBaseChart(opts *Chart) *cPlotArea { +func (f *File) drawBaseChart(pa *cPlotArea, opts *Chart) *cPlotArea { c := cCharts{ BarDir: &attrValString{ Val: stringPtr("col"), @@ -217,8 +217,8 @@ func (f *File) drawBaseChart(opts *Chart) *cPlotArea { if *c.Overlap.Val, ok = plotAreaChartOverlap[opts.Type]; !ok { c.Overlap = nil } - catAx := f.drawPlotAreaCatAx(opts) - valAx := f.drawPlotAreaValAx(opts) + catAx := f.drawPlotAreaCatAx(pa, opts) + valAx := f.drawPlotAreaValAx(pa, opts) charts := map[ChartType]*cPlotArea{ Area: { AreaChart: &c, @@ -436,7 +436,7 @@ func (f *File) drawBaseChart(opts *Chart) *cPlotArea { // drawDoughnutChart provides a function to draw the c:plotArea element for // doughnut chart by given format sets. -func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea { +func (f *File) drawDoughnutChart(pa *cPlotArea, opts *Chart) *cPlotArea { holeSize := 75 if opts.HoleSize > 0 && opts.HoleSize <= 90 { holeSize = opts.HoleSize @@ -455,7 +455,7 @@ func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea { // drawLineChart provides a function to draw the c:plotArea element for line // chart by given format sets. -func (f *File) drawLineChart(opts *Chart) *cPlotArea { +func (f *File) drawLineChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ LineChart: &cCharts{ Grouping: &attrValString{ @@ -468,14 +468,14 @@ func (f *File) drawLineChart(opts *Chart) *cPlotArea { DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), } } // drawLine3DChart provides a function to draw the c:plotArea element for line // chart by given format sets. -func (f *File) drawLine3DChart(opts *Chart) *cPlotArea { +func (f *File) drawLine3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ Line3DChart: &cCharts{ Grouping: &attrValString{ @@ -488,14 +488,14 @@ func (f *File) drawLine3DChart(opts *Chart) *cPlotArea { DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), } } // drawPieChart provides a function to draw the c:plotArea element for pie // chart by given format sets. -func (f *File) drawPieChart(opts *Chart) *cPlotArea { +func (f *File) drawPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ PieChart: &cCharts{ VaryColors: &attrValBool{ @@ -508,7 +508,7 @@ func (f *File) drawPieChart(opts *Chart) *cPlotArea { // drawPie3DChart provides a function to draw the c:plotArea element for 3D // pie chart by given format sets. -func (f *File) drawPie3DChart(opts *Chart) *cPlotArea { +func (f *File) drawPie3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ Pie3DChart: &cCharts{ VaryColors: &attrValBool{ @@ -521,7 +521,7 @@ func (f *File) drawPie3DChart(opts *Chart) *cPlotArea { // drawPieOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. -func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea { +func (f *File) drawPieOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { var splitPos *attrValInt if opts.PlotArea.SecondPlotValues > 0 { splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)} @@ -543,7 +543,7 @@ func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea { // drawBarOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. -func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea { +func (f *File) drawBarOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea { var splitPos *attrValInt if opts.PlotArea.SecondPlotValues > 0 { splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)} @@ -565,7 +565,7 @@ func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea { // drawRadarChart provides a function to draw the c:plotArea element for radar // chart by given format sets. -func (f *File) drawRadarChart(opts *Chart) *cPlotArea { +func (f *File) drawRadarChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ RadarChart: &cCharts{ RadarStyle: &attrValString{ @@ -578,14 +578,14 @@ func (f *File) drawRadarChart(opts *Chart) *cPlotArea { DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), } } // drawScatterChart provides a function to draw the c:plotArea element for // scatter chart by given format sets. -func (f *File) drawScatterChart(opts *Chart) *cPlotArea { +func (f *File) drawScatterChart(pa *cPlotArea, opts *Chart) *cPlotArea { return &cPlotArea{ ScatterChart: &cCharts{ ScatterStyle: &attrValString{ @@ -598,14 +598,14 @@ func (f *File) drawScatterChart(opts *Chart) *cPlotArea { DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), } } // drawSurface3DChart provides a function to draw the c:surface3DChart element by // given format sets. -func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea { +func (f *File) drawSurface3DChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ Surface3DChart: &cCharts{ Ser: f.drawChartSeries(opts), @@ -615,8 +615,8 @@ func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea { {Val: intPtr(100000005)}, }, }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), SerAx: f.drawPlotAreaSerAx(opts), } if opts.Type == WireframeSurface3D { @@ -627,7 +627,7 @@ func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea { // drawSurfaceChart provides a function to draw the c:surfaceChart element by // given format sets. -func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea { +func (f *File) drawSurfaceChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ SurfaceChart: &cCharts{ Ser: f.drawChartSeries(opts), @@ -637,8 +637,8 @@ func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea { {Val: intPtr(100000005)}, }, }, - CatAx: f.drawPlotAreaCatAx(opts), - ValAx: f.drawPlotAreaValAx(opts), + CatAx: f.drawPlotAreaCatAx(pa, opts), + ValAx: f.drawPlotAreaValAx(pa, opts), SerAx: f.drawPlotAreaSerAx(opts), } if opts.Type == WireframeContour { @@ -649,7 +649,7 @@ func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea { // drawBubbleChart provides a function to draw the c:bubbleChart element by // given format sets. -func (f *File) drawBubbleChart(opts *Chart) *cPlotArea { +func (f *File) drawBubbleChart(pa *cPlotArea, opts *Chart) *cPlotArea { plotArea := &cPlotArea{ BubbleChart: &cCharts{ VaryColors: &attrValBool{ @@ -659,7 +659,7 @@ func (f *File) drawBubbleChart(opts *Chart) *cPlotArea { DLbls: f.drawChartDLbls(opts), AxID: f.genAxID(opts), }, - ValAx: []*cAxs{f.drawPlotAreaCatAx(opts)[0], f.drawPlotAreaValAx(opts)[0]}, + ValAx: []*cAxs{f.drawPlotAreaCatAx(pa, opts)[0], f.drawPlotAreaValAx(pa, opts)[0]}, } if opts.BubbleSize > 0 && opts.BubbleSize <= 300 { plotArea.BubbleChart.BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))} @@ -979,7 +979,7 @@ func (f *File) drawChartSeriesDLbls(i int, opts *Chart) *cDLbls { } // drawPlotAreaCatAx provides a function to draw the c:catAx element. -func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs { +func (f *File) drawPlotAreaCatAx(pa *cPlotArea, opts *Chart) []*cAxs { maxVal := &attrValFloat{Val: opts.XAxis.Maximum} minVal := &attrValFloat{Val: opts.XAxis.Minimum} if opts.XAxis.Maximum == nil { @@ -988,70 +988,53 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs { if opts.XAxis.Minimum == nil { minVal = nil } - axs := []*cAxs{ - { - AxID: &attrValInt{Val: intPtr(100000000)}, - Scaling: &cScaling{ - Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])}, - Max: maxVal, - Min: minVal, - }, - Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)}, - AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])}, - 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), - CrossAx: &attrValInt{Val: intPtr(100000001)}, - Crosses: &attrValString{Val: stringPtr("autoZero")}, - Auto: &attrValBool{Val: boolPtr(true)}, - LblAlgn: &attrValString{Val: stringPtr("ctr")}, - LblOffset: &attrValInt{Val: intPtr(100)}, - NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, + ax := &cAxs{ + AxID: &attrValInt{Val: intPtr(100000000)}, + Scaling: &cScaling{ + Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])}, + Max: maxVal, + Min: minVal, }, + Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)}, + AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])}, + 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.XAxis), + CrossAx: &attrValInt{Val: intPtr(100000001)}, + Crosses: &attrValString{Val: stringPtr("autoZero")}, + Auto: &attrValBool{Val: boolPtr(true)}, + LblAlgn: &attrValString{Val: stringPtr("ctr")}, + LblOffset: &attrValInt{Val: intPtr(100)}, + NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, } if numFmt := f.drawChartNumFmt(opts.XAxis.NumFmt); numFmt != nil { - axs[0].NumFmt = numFmt + ax.NumFmt = numFmt } if opts.XAxis.MajorGridLines { - axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.XAxis.MinorGridLines { - axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.XAxis.TickLabelSkip != 0 { - axs[0].TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)} + ax.TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)} } - if opts.order > 0 && opts.YAxis.Secondary { - axs = append(axs, &cAxs{ - AxID: &attrValInt{Val: intPtr(opts.XAxis.axID)}, - Scaling: &cScaling{ - Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])}, - Max: maxVal, - Min: minVal, - }, - Delete: &attrValBool{Val: boolPtr(true)}, - AxPos: &attrValString{Val: stringPtr("b")}, - MajorTickMark: &attrValString{Val: stringPtr("none")}, - MinorTickMark: &attrValString{Val: stringPtr("none")}, - TickLblPos: &attrValString{Val: stringPtr("nextTo")}, - SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(&opts.YAxis), - CrossAx: &attrValInt{Val: intPtr(opts.YAxis.axID)}, - Auto: &attrValBool{Val: boolPtr(true)}, - LblAlgn: &attrValString{Val: stringPtr("ctr")}, - LblOffset: &attrValInt{Val: intPtr(100)}, - NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, - }) + if opts.order > 0 && opts.YAxis.Secondary && pa.CatAx != nil { + ax.AxID = &attrValInt{Val: intPtr(opts.XAxis.axID)} + ax.Delete = &attrValBool{Val: boolPtr(true)} + ax.Crosses = nil + ax.CrossAx = &attrValInt{Val: intPtr(opts.YAxis.axID)} + return []*cAxs{pa.CatAx[0], ax} } - return axs + return []*cAxs{ax} } // drawPlotAreaValAx provides a function to draw the c:valAx element. -func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs { +func (f *File) drawPlotAreaValAx(pa *cPlotArea, opts *Chart) []*cAxs { maxVal := &attrValFloat{Val: opts.YAxis.Maximum} minVal := &attrValFloat{Val: opts.YAxis.Minimum} if opts.YAxis.Maximum == nil { @@ -1064,67 +1047,53 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs { if opts.YAxis.LogBase >= 2 && opts.YAxis.LogBase <= 1000 { logBase = &attrValFloat{Val: float64Ptr(opts.YAxis.LogBase)} } - axs := []*cAxs{ - { - AxID: &attrValInt{Val: intPtr(100000001)}, - Scaling: &cScaling{ - LogBase: logBase, - Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])}, - Max: maxVal, - Min: minVal, - }, - 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], - }, - MajorTickMark: &attrValString{Val: stringPtr("none")}, - MinorTickMark: &attrValString{Val: stringPtr("none")}, - TickLblPos: &attrValString{Val: stringPtr("nextTo")}, - SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(&opts.XAxis), - CrossAx: &attrValInt{Val: intPtr(100000000)}, - Crosses: &attrValString{Val: stringPtr("autoZero")}, - CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, + ax := &cAxs{ + AxID: &attrValInt{Val: intPtr(100000001)}, + Scaling: &cScaling{ + LogBase: logBase, + Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])}, + Max: maxVal, + Min: minVal, }, + 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], + }, + MajorTickMark: &attrValString{Val: stringPtr("none")}, + MinorTickMark: &attrValString{Val: stringPtr("none")}, + TickLblPos: &attrValString{Val: stringPtr("nextTo")}, + SpPr: f.drawPlotAreaSpPr(), + TxPr: f.drawPlotAreaTxPr(&opts.YAxis), + CrossAx: &attrValInt{Val: intPtr(100000000)}, + Crosses: &attrValString{Val: stringPtr("autoZero")}, + CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, } if numFmt := f.drawChartNumFmt(opts.YAxis.NumFmt); numFmt != nil { - axs[0].NumFmt = numFmt + ax.NumFmt = numFmt } if opts.YAxis.MajorGridLines { - axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.YAxis.MinorGridLines { - axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if pos, ok := valTickLblPos[opts.Type]; ok { - axs[0].TickLblPos.Val = stringPtr(pos) + ax.TickLblPos.Val = stringPtr(pos) } if opts.YAxis.MajorUnit != 0 { - axs[0].MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)} + ax.MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)} } - if opts.order > 0 && opts.YAxis.Secondary { - axs = append(axs, &cAxs{ - AxID: &attrValInt{Val: intPtr(opts.YAxis.axID)}, - Scaling: &cScaling{ - Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])}, - Max: maxVal, - Min: minVal, - }, - Delete: &attrValBool{Val: boolPtr(false)}, - AxPos: &attrValString{Val: stringPtr("r")}, - MajorTickMark: &attrValString{Val: stringPtr("none")}, - MinorTickMark: &attrValString{Val: stringPtr("none")}, - TickLblPos: &attrValString{Val: stringPtr("nextTo")}, - SpPr: f.drawPlotAreaSpPr(), - TxPr: f.drawPlotAreaTxPr(&opts.XAxis), - CrossAx: &attrValInt{Val: intPtr(opts.XAxis.axID)}, - Crosses: &attrValString{Val: stringPtr("max")}, - CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, - }) + if opts.order > 0 && opts.YAxis.Secondary && pa.ValAx != nil { + ax.AxID = &attrValInt{Val: intPtr(opts.YAxis.axID)} + ax.AxPos = &attrValString{Val: stringPtr("r")} + ax.Title = nil + ax.Crosses = &attrValString{Val: stringPtr("max")} + ax.CrossAx = &attrValInt{Val: intPtr(opts.XAxis.axID)} + return []*cAxs{pa.ValAx[0], ax} } - return axs + return []*cAxs{ax} } // drawPlotAreaSerAx provides a function to draw the c:serAx element.