Browse Source

Resolve #661 Add Logarithmic scale option support on Y axis (#662)

* Resolve #661 Add Logarithmic scale option support on Y axis

Example usage:
Add the following option into the format string when using AddChart:

"y_axis":{"scaling":{"logbase":"10"}}

* Change type of LogBase from attrValString to attrVarFloat

* Add test case for testing Logarithmic Option in Y axis of charts

* Move field `LogBase` in the format string up one level (remove `Scaling`) as suggested the owner

Test cases are updated accordingly.
Huy Bui (Kevin) 5 years ago
parent
commit
42b1c81488
3 changed files with 122 additions and 0 deletions
  1. 113 0
      chart_test.go
  2. 7 0
      drawing.go
  3. 2 0
      xmlChart.go

+ 113 - 0
chart_test.go

@@ -253,3 +253,116 @@ func TestDeleteChart(t *testing.T) {
 	// Test delete chart on no chart worksheet.
 	assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1"))
 }
+
+func TestChartWithLogarithmicBase(t *testing.T) {
+	// Create test XLSX file with data
+	xlsx := NewFile()
+	sheet1 := xlsx.GetSheetName(0)
+	categories := map[string]float64{
+		"A1":  1,
+		"A2":  2,
+		"A3":  3,
+		"A4":  4,
+		"A5":  5,
+		"A6":  6,
+		"A7":  7,
+		"A8":  8,
+		"A9":  9,
+		"A10": 10,
+		"B1":  0.1,
+		"B2":  1,
+		"B3":  2,
+		"B4":  3,
+		"B5":  20,
+		"B6":  30,
+		"B7":  100,
+		"B8":  500,
+		"B9":  700,
+		"B10": 5000,
+	}
+	for cell, v := range categories {
+		assert.NoError(t, xlsx.SetCellValue(sheet1, cell, v))
+	}
+
+	// Add two chart, one without and one with log scaling
+	assert.NoError(t, xlsx.AddChart(sheet1, "C1",
+		`{"type":"line","dimension":{"width":640, "height":480},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"title":{"name":"Line chart without log scaling"}}`))
+	assert.NoError(t, xlsx.AddChart(sheet1, "M1",
+		`{"type":"line","dimension":{"width":640, "height":480},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"y_axis":{"logbase":10.5},`+
+			`"title":{"name":"Line chart with log 10 scaling"}}`))
+	assert.NoError(t, xlsx.AddChart(sheet1, "A25",
+		`{"type":"line","dimension":{"width":320, "height":240},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"y_axis":{"logbase":1.9},`+
+			`"title":{"name":"Line chart with log 1.9 scaling"}}`))
+	assert.NoError(t, xlsx.AddChart(sheet1, "F25",
+		`{"type":"line","dimension":{"width":320, "height":240},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"y_axis":{"logbase":2},`+
+			`"title":{"name":"Line chart with log 2 scaling"}}`))
+	assert.NoError(t, xlsx.AddChart(sheet1, "K25",
+		`{"type":"line","dimension":{"width":320, "height":240},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"y_axis":{"logbase":1000.1},`+
+			`"title":{"name":"Line chart with log 1000.1 scaling"}}`))
+	assert.NoError(t, xlsx.AddChart(sheet1, "P25",
+		`{"type":"line","dimension":{"width":320, "height":240},`+
+			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
+			`"y_axis":{"logbase":1000},`+
+			`"title":{"name":"Line chart with log 1000 scaling"}}`))
+
+	// Export XLSX file for human confirmation
+	assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))
+
+	// Write the XLSX file to a buffer
+	var buffer bytes.Buffer
+	assert.NoError(t, xlsx.Write(&buffer))
+
+	// Read back the XLSX file from the buffer
+	newFile, err := OpenReader(&buffer)
+	assert.NoError(t, err)
+
+	// Check the number of charts
+	expectedChartsCount := 6
+	chartsNum := newFile.countCharts()
+	if !assert.Equal(t, expectedChartsCount, chartsNum,
+		"Expected %d charts, actual %d", expectedChartsCount, chartsNum) {
+		t.FailNow()
+	}
+
+	chartSpaces := make([]xlsxChartSpace, expectedChartsCount)
+	type xmlChartContent []byte
+	xmlCharts := make([]xmlChartContent, expectedChartsCount)
+	expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000}
+	var ok bool
+
+	for i := 0; i < expectedChartsCount; i++ {
+		chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
+		xmlCharts[i], ok = newFile.XLSX[chartPath]
+		assert.True(t, ok, "Can't open the %s", chartPath)
+
+		err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i])
+		if !assert.NoError(t, err) {
+			t.FailNow()
+		}
+
+		chartLogBasePtr := chartSpaces[i].Chart.PlotArea.ValAx[0].Scaling.LogBase
+		if expectedChartsLogBase[i] == 0 {
+			if !assert.Nil(t, chartLogBasePtr, "LogBase is not nil") {
+				t.FailNow()
+			}
+		} else {
+			if !assert.NotNil(t, chartLogBasePtr, "LogBase is nil") {
+				t.FailNow()
+			}
+			if !assert.Equal(t, expectedChartsLogBase[i], *(chartLogBasePtr.Val),
+				"Expected log base to %f, actual %f", expectedChartsLogBase[i], *(chartLogBasePtr.Val)) {
+				t.FailNow()
+			}
+		}
+	}
+}

+ 7 - 0
drawing.go

@@ -1000,10 +1000,17 @@ func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs {
 	if formatSet.YAxis.Maximum == 0 {
 		max = nil
 	}
+	var logBase *attrValFloat
+	// Follow OOXML requirements on
+	// [https://github.com/sc34wg4/OOXMLSchemas/blob/2b074ca2c5df38b18ac118646b329b508b5bdecc/Part1/OfficeOpenXML-XMLSchema-Strict/dml-chart.xsd#L1142-L1147]
+	if formatSet.YAxis.LogBase >= 2 && formatSet.YAxis.LogBase <= 1000 {
+		logBase = &attrValFloat{Val: float64Ptr(formatSet.YAxis.LogBase)}
+	}
 	axs := []*cAxs{
 		{
 			AxID: &attrValInt{Val: intPtr(753999904)},
 			Scaling: &cScaling{
+				LogBase:     logBase,
 				Orientation: &attrValString{Val: stringPtr(orientation[formatSet.YAxis.ReverseOrder])},
 				Max:         max,
 				Min:         min,

+ 2 - 0
xmlChart.go

@@ -380,6 +380,7 @@ type cChartLines struct {
 // cScaling directly maps the scaling element. This element contains
 // additional axis settings.
 type cScaling struct {
+	LogBase     *attrValFloat  `xml:"logBase"`
 	Orientation *attrValString `xml:"orientation"`
 	Max         *attrValFloat  `xml:"max"`
 	Min         *attrValFloat  `xml:"min"`
@@ -544,6 +545,7 @@ type formatChartAxis struct {
 		Italic    bool   `json:"italic"`
 		Underline bool   `json:"underline"`
 	} `json:"num_font"`
+	LogBase    float64      `json:"logbase"`
 	NameLayout formatLayout `json:"name_layout"`
 }