Skip to content

Commit 8879f84

Browse files
authored
This closes qax-os#518, support creating chart with a secondary series axis
1 parent d783452 commit 8879f84

File tree

4 files changed

+82
-41
lines changed

4 files changed

+82
-41
lines changed

chart.go

+5
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
803803
// MajorGridLines
804804
// MinorGridLines
805805
// MajorUnit
806+
// Secondary
806807
// ReverseOrder
807808
// Maximum
808809
// Minimum
@@ -821,6 +822,10 @@ func parseChartOptions(opts *Chart) (*Chart, error) {
821822
// positive floating-point number. The 'MajorUnit' property is optional. The
822823
// default value is auto.
823824
//
825+
// Secondary: Specifies the current series vertical axis as the secondary axis,
826+
// this only works for the second and later chart in the combo chart. The
827+
// default value is false.
828+
//
824829
// TickLabelSkip: Specifies how many tick labels to skip between label that is
825830
// drawn. The 'TickLabelSkip' property is optional. The default value is auto.
826831
//

chart_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func TestAddChart(t *testing.T) {
238238
{sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: BarPercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked 100% Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
239239
{sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: Bar3DClustered, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Clustered Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
240240
{sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: Bar3DStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{Maximum: &maximum, Minimum: &minimum}}},
241-
{sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: Bar3DPercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}},
241+
{sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: Bar3DPercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Secondary: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}},
242242
// area series chart
243243
{sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: Area, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
244244
{sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: AreaStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
@@ -280,7 +280,7 @@ func TestAddChart(t *testing.T) {
280280
{"I1", Doughnut, "Clustered Column - Doughnut Chart"},
281281
}
282282
for _, props := range clusteredColumnCombo {
283-
assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[2].(string)}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}))
283+
assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[2].(string)}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}, YAxis: ChartAxis{Secondary: true}}))
284284
}
285285
stackedAreaCombo := map[string][]interface{}{
286286
"A16": {Line, "Stacked Area - Line Chart"},

drawing.go

+73-39
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,10 @@ func (f *File) drawBaseChart(opts *Chart) *cPlotArea {
277277
VaryColors: &attrValBool{
278278
Val: opts.VaryColors,
279279
},
280-
Ser: f.drawChartSeries(opts),
281-
Shape: f.drawChartShape(opts),
282-
DLbls: f.drawChartDLbls(opts),
283-
AxID: []*attrValInt{
284-
{Val: intPtr(754001152)},
285-
{Val: intPtr(753999904)},
286-
},
280+
Ser: f.drawChartSeries(opts),
281+
Shape: f.drawChartShape(opts),
282+
DLbls: f.drawChartDLbls(opts),
283+
AxID: f.genAxID(opts),
287284
Overlap: &attrValInt{Val: intPtr(100)},
288285
}
289286
var ok bool
@@ -542,10 +539,7 @@ func (f *File) drawLineChart(opts *Chart) *cPlotArea {
542539
},
543540
Ser: f.drawChartSeries(opts),
544541
DLbls: f.drawChartDLbls(opts),
545-
AxID: []*attrValInt{
546-
{Val: intPtr(754001152)},
547-
{Val: intPtr(753999904)},
548-
},
542+
AxID: f.genAxID(opts),
549543
},
550544
CatAx: f.drawPlotAreaCatAx(opts),
551545
ValAx: f.drawPlotAreaValAx(opts),
@@ -565,10 +559,7 @@ func (f *File) drawLine3DChart(opts *Chart) *cPlotArea {
565559
},
566560
Ser: f.drawChartSeries(opts),
567561
DLbls: f.drawChartDLbls(opts),
568-
AxID: []*attrValInt{
569-
{Val: intPtr(754001152)},
570-
{Val: intPtr(753999904)},
571-
},
562+
AxID: f.genAxID(opts),
572563
},
573564
CatAx: f.drawPlotAreaCatAx(opts),
574565
ValAx: f.drawPlotAreaValAx(opts),
@@ -658,10 +649,7 @@ func (f *File) drawRadarChart(opts *Chart) *cPlotArea {
658649
},
659650
Ser: f.drawChartSeries(opts),
660651
DLbls: f.drawChartDLbls(opts),
661-
AxID: []*attrValInt{
662-
{Val: intPtr(754001152)},
663-
{Val: intPtr(753999904)},
664-
},
652+
AxID: f.genAxID(opts),
665653
},
666654
CatAx: f.drawPlotAreaCatAx(opts),
667655
ValAx: f.drawPlotAreaValAx(opts),
@@ -681,10 +669,7 @@ func (f *File) drawScatterChart(opts *Chart) *cPlotArea {
681669
},
682670
Ser: f.drawChartSeries(opts),
683671
DLbls: f.drawChartDLbls(opts),
684-
AxID: []*attrValInt{
685-
{Val: intPtr(754001152)},
686-
{Val: intPtr(753999904)},
687-
},
672+
AxID: f.genAxID(opts),
688673
},
689674
CatAx: f.drawPlotAreaCatAx(opts),
690675
ValAx: f.drawPlotAreaValAx(opts),
@@ -698,9 +683,9 @@ func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea {
698683
Surface3DChart: &cCharts{
699684
Ser: f.drawChartSeries(opts),
700685
AxID: []*attrValInt{
701-
{Val: intPtr(754001152)},
702-
{Val: intPtr(753999904)},
703-
{Val: intPtr(832256642)},
686+
{Val: intPtr(100000000)},
687+
{Val: intPtr(100000001)},
688+
{Val: intPtr(100000005)},
704689
},
705690
},
706691
CatAx: f.drawPlotAreaCatAx(opts),
@@ -720,9 +705,9 @@ func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea {
720705
SurfaceChart: &cCharts{
721706
Ser: f.drawChartSeries(opts),
722707
AxID: []*attrValInt{
723-
{Val: intPtr(754001152)},
724-
{Val: intPtr(753999904)},
725-
{Val: intPtr(832256642)},
708+
{Val: intPtr(100000000)},
709+
{Val: intPtr(100000001)},
710+
{Val: intPtr(100000005)},
726711
},
727712
},
728713
CatAx: f.drawPlotAreaCatAx(opts),
@@ -745,10 +730,7 @@ func (f *File) drawBubbleChart(opts *Chart) *cPlotArea {
745730
},
746731
Ser: f.drawChartSeries(opts),
747732
DLbls: f.drawChartDLbls(opts),
748-
AxID: []*attrValInt{
749-
{Val: intPtr(754001152)},
750-
{Val: intPtr(753999904)},
751-
},
733+
AxID: f.genAxID(opts),
752734
},
753735
ValAx: []*cAxs{f.drawPlotAreaCatAx(opts)[0], f.drawPlotAreaValAx(opts)[0]},
754736
}
@@ -1050,7 +1032,7 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
10501032
}
10511033
axs := []*cAxs{
10521034
{
1053-
AxID: &attrValInt{Val: intPtr(754001152)},
1035+
AxID: &attrValInt{Val: intPtr(100000000)},
10541036
Scaling: &cScaling{
10551037
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
10561038
Max: max,
@@ -1065,7 +1047,7 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
10651047
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
10661048
SpPr: f.drawPlotAreaSpPr(),
10671049
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
1068-
CrossAx: &attrValInt{Val: intPtr(753999904)},
1050+
CrossAx: &attrValInt{Val: intPtr(100000001)},
10691051
Crosses: &attrValString{Val: stringPtr("autoZero")},
10701052
Auto: &attrValBool{Val: boolPtr(true)},
10711053
LblAlgn: &attrValString{Val: stringPtr("ctr")},
@@ -1085,6 +1067,28 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
10851067
if opts.XAxis.TickLabelSkip != 0 {
10861068
axs[0].TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)}
10871069
}
1070+
if opts.order > 0 && opts.YAxis.Secondary {
1071+
axs = append(axs, &cAxs{
1072+
AxID: &attrValInt{Val: intPtr(opts.XAxis.axID)},
1073+
Scaling: &cScaling{
1074+
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
1075+
Max: max,
1076+
Min: min,
1077+
},
1078+
Delete: &attrValBool{Val: boolPtr(true)},
1079+
AxPos: &attrValString{Val: stringPtr("b")},
1080+
MajorTickMark: &attrValString{Val: stringPtr("none")},
1081+
MinorTickMark: &attrValString{Val: stringPtr("none")},
1082+
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
1083+
SpPr: f.drawPlotAreaSpPr(),
1084+
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
1085+
CrossAx: &attrValInt{Val: intPtr(opts.YAxis.axID)},
1086+
Auto: &attrValBool{Val: boolPtr(true)},
1087+
LblAlgn: &attrValString{Val: stringPtr("ctr")},
1088+
LblOffset: &attrValInt{Val: intPtr(100)},
1089+
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
1090+
})
1091+
}
10881092
return axs
10891093
}
10901094

@@ -1104,7 +1108,7 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
11041108
}
11051109
axs := []*cAxs{
11061110
{
1107-
AxID: &attrValInt{Val: intPtr(753999904)},
1111+
AxID: &attrValInt{Val: intPtr(100000001)},
11081112
Scaling: &cScaling{
11091113
LogBase: logBase,
11101114
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
@@ -1122,7 +1126,7 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
11221126
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
11231127
SpPr: f.drawPlotAreaSpPr(),
11241128
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
1125-
CrossAx: &attrValInt{Val: intPtr(754001152)},
1129+
CrossAx: &attrValInt{Val: intPtr(100000000)},
11261130
Crosses: &attrValString{Val: stringPtr("autoZero")},
11271131
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
11281132
},
@@ -1142,6 +1146,26 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
11421146
if opts.YAxis.MajorUnit != 0 {
11431147
axs[0].MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)}
11441148
}
1149+
if opts.order > 0 && opts.YAxis.Secondary {
1150+
axs = append(axs, &cAxs{
1151+
AxID: &attrValInt{Val: intPtr(opts.YAxis.axID)},
1152+
Scaling: &cScaling{
1153+
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
1154+
Max: max,
1155+
Min: min,
1156+
},
1157+
Delete: &attrValBool{Val: boolPtr(false)},
1158+
AxPos: &attrValString{Val: stringPtr("r")},
1159+
MajorTickMark: &attrValString{Val: stringPtr("none")},
1160+
MinorTickMark: &attrValString{Val: stringPtr("none")},
1161+
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
1162+
SpPr: f.drawPlotAreaSpPr(),
1163+
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
1164+
CrossAx: &attrValInt{Val: intPtr(opts.XAxis.axID)},
1165+
Crosses: &attrValString{Val: stringPtr("max")},
1166+
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
1167+
})
1168+
}
11451169
return axs
11461170
}
11471171

@@ -1157,7 +1181,7 @@ func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
11571181
}
11581182
return []*cAxs{
11591183
{
1160-
AxID: &attrValInt{Val: intPtr(832256642)},
1184+
AxID: &attrValInt{Val: intPtr(100000005)},
11611185
Scaling: &cScaling{
11621186
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
11631187
Max: max,
@@ -1168,7 +1192,7 @@ func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
11681192
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
11691193
SpPr: f.drawPlotAreaSpPr(),
11701194
TxPr: f.drawPlotAreaTxPr(nil),
1171-
CrossAx: &attrValInt{Val: intPtr(753999904)},
1195+
CrossAx: &attrValInt{Val: intPtr(100000001)},
11721196
},
11731197
}
11741198
}
@@ -1467,3 +1491,13 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error
14671491
f.Drawings.Store(drawingXML, wsDr)
14681492
return err
14691493
}
1494+
1495+
// genAxID provides a function to generate ID for primary and secondary
1496+
// horizontal or vertical axis.
1497+
func (f *File) genAxID(opts *Chart) []*attrValInt {
1498+
opts.XAxis.axID, opts.YAxis.axID = 100000000, 100000001
1499+
if opts.order > 0 && opts.YAxis.Secondary {
1500+
opts.XAxis.axID, opts.YAxis.axID = 100000003, 100000004
1501+
}
1502+
return []*attrValInt{{Val: intPtr(opts.XAxis.axID)}, {Val: intPtr(opts.YAxis.axID)}}
1503+
}

xmlChart.go

+2
Original file line numberDiff line numberDiff line change
@@ -535,12 +535,14 @@ type ChartAxis struct {
535535
MajorUnit float64
536536
TickLabelSkip int
537537
ReverseOrder bool
538+
Secondary bool
538539
Maximum *float64
539540
Minimum *float64
540541
Font Font
541542
LogBase float64
542543
NumFmt ChartNumFmt
543544
Title []RichTextRun
545+
axID int
544546
}
545547

546548
// ChartDimension directly maps the dimension of the chart.

0 commit comments

Comments
 (0)