diff --git a/chart_test.go b/chart_test.go index b35cb98..6ee5f7c 100644 --- a/chart_test.go +++ b/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() + } + } + } +} diff --git a/drawing.go b/drawing.go index ea75e82..3ce1282 100644 --- a/drawing.go +++ b/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, diff --git a/xmlChart.go b/xmlChart.go index 6f800d7..fae5426 100644 --- a/xmlChart.go +++ b/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"` }