1444 lines
43 KiB
Go
1444 lines
43 KiB
Go
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
|
|
// this source code is governed by a BSD-style license that can be found in
|
|
// the LICENSE file.
|
|
//
|
|
// Package excelize providing a set of functions that allow you to write to and
|
|
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
|
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
|
// Supports complex components by high compatibility, and provided streaming
|
|
// API for generating or reading data from a worksheet with huge amounts of
|
|
// data. This library needs Go version 1.16 or later.
|
|
|
|
package excelize
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// prepareDrawing provides a function to prepare drawing ID and XML by given
|
|
// drawingID, worksheet name and default drawingXML.
|
|
func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
|
|
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
|
if ws.Drawing != nil {
|
|
// The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
|
|
sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
|
drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
|
|
drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
|
|
} else {
|
|
// Add first picture for given sheet.
|
|
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
|
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
|
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
|
|
f.addSheetDrawing(sheet, rID)
|
|
}
|
|
return drawingID, drawingXML
|
|
}
|
|
|
|
// prepareChartSheetDrawing provides a function to prepare drawing ID and XML
|
|
// by given drawingID, worksheet name and default drawingXML.
|
|
func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) {
|
|
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
|
// Only allow one chart in a chartsheet.
|
|
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
|
sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/chartsheets/") + ".rels"
|
|
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
|
|
f.addSheetNameSpace(sheet, SourceRelationship)
|
|
cs.Drawing = &xlsxDrawing{
|
|
RID: "rId" + strconv.Itoa(rID),
|
|
}
|
|
}
|
|
|
|
// addChart provides a function to create chart as xl/charts/chart%d.xml by
|
|
// given format sets.
|
|
func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|
count := f.countCharts()
|
|
xlsxChartSpace := xlsxChartSpace{
|
|
XMLNSa: NameSpaceDrawingML.Value,
|
|
Date1904: &attrValBool{Val: boolPtr(false)},
|
|
Lang: &attrValString{Val: stringPtr("en-US")},
|
|
RoundedCorners: &attrValBool{Val: boolPtr(false)},
|
|
Chart: cChart{
|
|
Title: f.drawPlotAreaTitles(opts.Title, ""),
|
|
View3D: &cView3D{
|
|
RotX: &attrValInt{Val: intPtr(chartView3DRotX[opts.Type])},
|
|
RotY: &attrValInt{Val: intPtr(chartView3DRotY[opts.Type])},
|
|
Perspective: &attrValInt{Val: intPtr(chartView3DPerspective[opts.Type])},
|
|
RAngAx: &attrValInt{Val: intPtr(chartView3DRAngAx[opts.Type])},
|
|
},
|
|
Floor: &cThicknessSpPr{
|
|
Thickness: &attrValInt{Val: intPtr(0)},
|
|
},
|
|
SideWall: &cThicknessSpPr{
|
|
Thickness: &attrValInt{Val: intPtr(0)},
|
|
},
|
|
BackWall: &cThicknessSpPr{
|
|
Thickness: &attrValInt{Val: intPtr(0)},
|
|
},
|
|
PlotArea: &cPlotArea{},
|
|
Legend: &cLegend{
|
|
LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])},
|
|
Overlay: &attrValBool{Val: boolPtr(false)},
|
|
},
|
|
|
|
PlotVisOnly: &attrValBool{Val: boolPtr(false)},
|
|
DispBlanksAs: &attrValString{Val: stringPtr(opts.ShowBlanksAs)},
|
|
ShowDLblsOverMax: &attrValBool{Val: boolPtr(false)},
|
|
},
|
|
SpPr: &cSpPr{
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{Val: "bg1"},
|
|
},
|
|
Ln: &aLn{
|
|
W: 9525,
|
|
Cap: "flat",
|
|
Cmpd: "sng",
|
|
Algn: "ctr",
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{
|
|
Val: "tx1",
|
|
LumMod: &attrValInt{
|
|
Val: intPtr(15000),
|
|
},
|
|
LumOff: &attrValInt{
|
|
Val: intPtr(85000),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
PrintSettings: &cPrintSettings{
|
|
PageMargins: &cPageMargins{
|
|
B: 0.75,
|
|
L: 0.7,
|
|
R: 0.7,
|
|
T: 0.7,
|
|
Header: 0.3,
|
|
Footer: 0.3,
|
|
},
|
|
},
|
|
}
|
|
plotAreaFunc := map[ChartType]func(*Chart) *cPlotArea{
|
|
Area: f.drawBaseChart,
|
|
AreaStacked: f.drawBaseChart,
|
|
AreaPercentStacked: f.drawBaseChart,
|
|
Area3D: f.drawBaseChart,
|
|
Area3DStacked: f.drawBaseChart,
|
|
Area3DPercentStacked: f.drawBaseChart,
|
|
Bar: f.drawBaseChart,
|
|
BarStacked: f.drawBaseChart,
|
|
BarPercentStacked: f.drawBaseChart,
|
|
Bar3DClustered: f.drawBaseChart,
|
|
Bar3DStacked: f.drawBaseChart,
|
|
Bar3DPercentStacked: f.drawBaseChart,
|
|
Bar3DConeClustered: f.drawBaseChart,
|
|
Bar3DConeStacked: f.drawBaseChart,
|
|
Bar3DConePercentStacked: f.drawBaseChart,
|
|
Bar3DPyramidClustered: f.drawBaseChart,
|
|
Bar3DPyramidStacked: f.drawBaseChart,
|
|
Bar3DPyramidPercentStacked: f.drawBaseChart,
|
|
Bar3DCylinderClustered: f.drawBaseChart,
|
|
Bar3DCylinderStacked: f.drawBaseChart,
|
|
Bar3DCylinderPercentStacked: f.drawBaseChart,
|
|
Col: f.drawBaseChart,
|
|
ColStacked: f.drawBaseChart,
|
|
ColPercentStacked: f.drawBaseChart,
|
|
Col3D: f.drawBaseChart,
|
|
Col3DClustered: f.drawBaseChart,
|
|
Col3DStacked: f.drawBaseChart,
|
|
Col3DPercentStacked: f.drawBaseChart,
|
|
Col3DCone: f.drawBaseChart,
|
|
Col3DConeClustered: f.drawBaseChart,
|
|
Col3DConeStacked: f.drawBaseChart,
|
|
Col3DConePercentStacked: f.drawBaseChart,
|
|
Col3DPyramid: f.drawBaseChart,
|
|
Col3DPyramidClustered: f.drawBaseChart,
|
|
Col3DPyramidStacked: f.drawBaseChart,
|
|
Col3DPyramidPercentStacked: f.drawBaseChart,
|
|
Col3DCylinder: f.drawBaseChart,
|
|
Col3DCylinderClustered: f.drawBaseChart,
|
|
Col3DCylinderStacked: f.drawBaseChart,
|
|
Col3DCylinderPercentStacked: f.drawBaseChart,
|
|
Doughnut: f.drawDoughnutChart,
|
|
Line: f.drawLineChart,
|
|
Line3D: f.drawLine3DChart,
|
|
Pie: f.drawPieChart,
|
|
Pie3D: f.drawPie3DChart,
|
|
PieOfPie: f.drawPieOfPieChart,
|
|
BarOfPie: f.drawBarOfPieChart,
|
|
Radar: f.drawRadarChart,
|
|
Scatter: f.drawScatterChart,
|
|
Surface3D: f.drawSurface3DChart,
|
|
WireframeSurface3D: f.drawSurface3DChart,
|
|
Contour: f.drawSurfaceChart,
|
|
WireframeContour: f.drawSurfaceChart,
|
|
Bubble: f.drawBubbleChart,
|
|
Bubble3D: f.drawBubbleChart,
|
|
}
|
|
if opts.Legend.Position == "none" {
|
|
xlsxChartSpace.Chart.Legend = nil
|
|
}
|
|
addChart := func(c, p *cPlotArea) {
|
|
immutable, mutable := reflect.ValueOf(c).Elem(), reflect.ValueOf(p).Elem()
|
|
for i := 0; i < mutable.NumField(); i++ {
|
|
field := mutable.Field(i)
|
|
if field.IsNil() {
|
|
continue
|
|
}
|
|
immutable.FieldByName(mutable.Type().Field(i).Name).Set(field)
|
|
}
|
|
}
|
|
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](opts))
|
|
order := len(opts.Series)
|
|
for idx := range comboCharts {
|
|
comboCharts[idx].order = order
|
|
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](comboCharts[idx]))
|
|
order += len(comboCharts[idx].Series)
|
|
}
|
|
chart, _ := xml.Marshal(xlsxChartSpace)
|
|
media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml"
|
|
f.saveFileList(media, 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 {
|
|
c := cCharts{
|
|
BarDir: &attrValString{
|
|
Val: stringPtr("col"),
|
|
},
|
|
Grouping: &attrValString{
|
|
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
Shape: f.drawChartShape(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
Overlap: &attrValInt{Val: intPtr(100)},
|
|
}
|
|
var ok bool
|
|
if *c.BarDir.Val, ok = plotAreaChartBarDir[opts.Type]; !ok {
|
|
c.BarDir = nil
|
|
}
|
|
if *c.Overlap.Val, ok = plotAreaChartOverlap[opts.Type]; !ok {
|
|
c.Overlap = nil
|
|
}
|
|
catAx := f.drawPlotAreaCatAx(opts)
|
|
valAx := f.drawPlotAreaValAx(opts)
|
|
charts := map[ChartType]*cPlotArea{
|
|
Area: {
|
|
AreaChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
AreaStacked: {
|
|
AreaChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
AreaPercentStacked: {
|
|
AreaChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Area3D: {
|
|
Area3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Area3DStacked: {
|
|
Area3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Area3DPercentStacked: {
|
|
Area3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
BarStacked: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
BarPercentStacked: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DConeClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DConeStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DConePercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DPyramidClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DPyramidStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DPyramidPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DCylinderClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DCylinderStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bar3DCylinderPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
ColStacked: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
ColPercentStacked: {
|
|
BarChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3D: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DCone: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DConeClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DConeStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DConePercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DPyramid: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DPyramidClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DPyramidStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DPyramidPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DCylinder: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DCylinderClustered: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DCylinderStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Col3DCylinderPercentStacked: {
|
|
Bar3DChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bubble: {
|
|
BubbleChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
Bubble3D: {
|
|
BubbleChart: &c,
|
|
CatAx: catAx,
|
|
ValAx: valAx,
|
|
},
|
|
}
|
|
return charts[opts.Type]
|
|
}
|
|
|
|
// drawDoughnutChart provides a function to draw the c:plotArea element for
|
|
// doughnut chart by given format sets.
|
|
func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea {
|
|
holeSize := 75
|
|
if opts.HoleSize > 0 && opts.HoleSize <= 90 {
|
|
holeSize = opts.HoleSize
|
|
}
|
|
|
|
return &cPlotArea{
|
|
DoughnutChart: &cCharts{
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
HoleSize: &attrValInt{Val: intPtr(holeSize)},
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawLineChart provides a function to draw the c:plotArea element for line
|
|
// chart by given format sets.
|
|
func (f *File) drawLineChart(opts *Chart) *cPlotArea {
|
|
return &cPlotArea{
|
|
LineChart: &cCharts{
|
|
Grouping: &attrValString{
|
|
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: boolPtr(false),
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(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 {
|
|
return &cPlotArea{
|
|
Line3DChart: &cCharts{
|
|
Grouping: &attrValString{
|
|
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: boolPtr(false),
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(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 {
|
|
return &cPlotArea{
|
|
PieChart: &cCharts{
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return &cPlotArea{
|
|
Pie3DChart: &cCharts{
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawPieOfPieChart provides a function to draw the c:plotArea element for
|
|
// pie chart by given format sets.
|
|
func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea {
|
|
var splitPos *attrValInt
|
|
if opts.PlotArea.SecondPlotValues > 0 {
|
|
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
|
|
}
|
|
return &cPlotArea{
|
|
OfPieChart: &cCharts{
|
|
OfPieType: &attrValString{
|
|
Val: stringPtr("pie"),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
SplitPos: splitPos,
|
|
SerLines: &attrValString{},
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawBarOfPieChart provides a function to draw the c:plotArea element for
|
|
// pie chart by given format sets.
|
|
func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea {
|
|
var splitPos *attrValInt
|
|
if opts.PlotArea.SecondPlotValues > 0 {
|
|
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
|
|
}
|
|
return &cPlotArea{
|
|
OfPieChart: &cCharts{
|
|
OfPieType: &attrValString{
|
|
Val: stringPtr("bar"),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
SplitPos: splitPos,
|
|
Ser: f.drawChartSeries(opts),
|
|
SerLines: &attrValString{},
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawRadarChart provides a function to draw the c:plotArea element for radar
|
|
// chart by given format sets.
|
|
func (f *File) drawRadarChart(opts *Chart) *cPlotArea {
|
|
return &cPlotArea{
|
|
RadarChart: &cCharts{
|
|
RadarStyle: &attrValString{
|
|
Val: stringPtr("marker"),
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: boolPtr(false),
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(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 {
|
|
return &cPlotArea{
|
|
ScatterChart: &cCharts{
|
|
ScatterStyle: &attrValString{
|
|
Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker
|
|
},
|
|
VaryColors: &attrValBool{
|
|
Val: boolPtr(false),
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(opts),
|
|
}
|
|
}
|
|
|
|
// drawSurface3DChart provides a function to draw the c:surface3DChart element by
|
|
// given format sets.
|
|
func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea {
|
|
plotArea := &cPlotArea{
|
|
Surface3DChart: &cCharts{
|
|
Ser: f.drawChartSeries(opts),
|
|
AxID: []*attrValInt{
|
|
{Val: intPtr(100000000)},
|
|
{Val: intPtr(100000001)},
|
|
{Val: intPtr(100000005)},
|
|
},
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(opts),
|
|
SerAx: f.drawPlotAreaSerAx(opts),
|
|
}
|
|
if opts.Type == WireframeSurface3D {
|
|
plotArea.Surface3DChart.Wireframe = &attrValBool{Val: boolPtr(true)}
|
|
}
|
|
return plotArea
|
|
}
|
|
|
|
// drawSurfaceChart provides a function to draw the c:surfaceChart element by
|
|
// given format sets.
|
|
func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea {
|
|
plotArea := &cPlotArea{
|
|
SurfaceChart: &cCharts{
|
|
Ser: f.drawChartSeries(opts),
|
|
AxID: []*attrValInt{
|
|
{Val: intPtr(100000000)},
|
|
{Val: intPtr(100000001)},
|
|
{Val: intPtr(100000005)},
|
|
},
|
|
},
|
|
CatAx: f.drawPlotAreaCatAx(opts),
|
|
ValAx: f.drawPlotAreaValAx(opts),
|
|
SerAx: f.drawPlotAreaSerAx(opts),
|
|
}
|
|
if opts.Type == WireframeContour {
|
|
plotArea.SurfaceChart.Wireframe = &attrValBool{Val: boolPtr(true)}
|
|
}
|
|
return plotArea
|
|
}
|
|
|
|
// drawBubbleChart provides a function to draw the c:bubbleChart element by
|
|
// given format sets.
|
|
func (f *File) drawBubbleChart(opts *Chart) *cPlotArea {
|
|
plotArea := &cPlotArea{
|
|
BubbleChart: &cCharts{
|
|
VaryColors: &attrValBool{
|
|
Val: opts.VaryColors,
|
|
},
|
|
Ser: f.drawChartSeries(opts),
|
|
DLbls: f.drawChartDLbls(opts),
|
|
AxID: f.genAxID(opts),
|
|
},
|
|
ValAx: []*cAxs{f.drawPlotAreaCatAx(opts)[0], f.drawPlotAreaValAx(opts)[0]},
|
|
}
|
|
return plotArea
|
|
}
|
|
|
|
// drawChartShape provides a function to draw the c:shape element by given
|
|
// format sets.
|
|
func (f *File) drawChartShape(opts *Chart) *attrValString {
|
|
shapes := map[ChartType]string{
|
|
Bar3DConeClustered: "cone",
|
|
Bar3DConeStacked: "cone",
|
|
Bar3DConePercentStacked: "cone",
|
|
Bar3DPyramidClustered: "pyramid",
|
|
Bar3DPyramidStacked: "pyramid",
|
|
Bar3DPyramidPercentStacked: "pyramid",
|
|
Bar3DCylinderClustered: "cylinder",
|
|
Bar3DCylinderStacked: "cylinder",
|
|
Bar3DCylinderPercentStacked: "cylinder",
|
|
Col3DCone: "cone",
|
|
Col3DConeClustered: "cone",
|
|
Col3DConeStacked: "cone",
|
|
Col3DConePercentStacked: "cone",
|
|
Col3DPyramid: "pyramid",
|
|
Col3DPyramidClustered: "pyramid",
|
|
Col3DPyramidStacked: "pyramid",
|
|
Col3DPyramidPercentStacked: "pyramid",
|
|
Col3DCylinder: "cylinder",
|
|
Col3DCylinderClustered: "cylinder",
|
|
Col3DCylinderStacked: "cylinder",
|
|
Col3DCylinderPercentStacked: "cylinder",
|
|
}
|
|
if shape, ok := shapes[opts.Type]; ok {
|
|
return &attrValString{Val: stringPtr(shape)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// drawChartSeries provides a function to draw the c:ser element by given
|
|
// format sets.
|
|
func (f *File) drawChartSeries(opts *Chart) *[]cSer {
|
|
var ser []cSer
|
|
for k := range opts.Series {
|
|
ser = append(ser, cSer{
|
|
IDx: &attrValInt{Val: intPtr(k + opts.order)},
|
|
Order: &attrValInt{Val: intPtr(k + opts.order)},
|
|
Tx: &cTx{
|
|
StrRef: &cStrRef{
|
|
F: opts.Series[k].Name,
|
|
},
|
|
},
|
|
SpPr: f.drawChartSeriesSpPr(k, opts),
|
|
Marker: f.drawChartSeriesMarker(k, opts),
|
|
DPt: f.drawChartSeriesDPt(k, opts),
|
|
DLbls: f.drawChartSeriesDLbls(opts),
|
|
InvertIfNegative: &attrValBool{Val: boolPtr(false)},
|
|
Cat: f.drawChartSeriesCat(opts.Series[k], opts),
|
|
Smooth: &attrValBool{Val: boolPtr(opts.Series[k].Line.Smooth)},
|
|
Val: f.drawChartSeriesVal(opts.Series[k], opts),
|
|
XVal: f.drawChartSeriesXVal(opts.Series[k], opts),
|
|
YVal: f.drawChartSeriesYVal(opts.Series[k], opts),
|
|
BubbleSize: f.drawCharSeriesBubbleSize(opts.Series[k], opts),
|
|
Bubble3D: f.drawCharSeriesBubble3D(opts),
|
|
})
|
|
}
|
|
return &ser
|
|
}
|
|
|
|
// drawChartSeriesSpPr provides a function to draw the c:spPr element by given
|
|
// format sets.
|
|
func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr {
|
|
var srgbClr *attrValString
|
|
var schemeClr *aSchemeClr
|
|
|
|
if color := opts.Series[i].Fill.Color; len(color) == 1 {
|
|
srgbClr = &attrValString{Val: stringPtr(strings.TrimPrefix(color[0], "#"))}
|
|
} else {
|
|
schemeClr = &aSchemeClr{Val: "accent" + strconv.Itoa((opts.order+i)%6+1)}
|
|
}
|
|
|
|
spPr := &cSpPr{SolidFill: &aSolidFill{SchemeClr: schemeClr, SrgbClr: srgbClr}}
|
|
spPrScatter := &cSpPr{
|
|
Ln: &aLn{
|
|
W: 25400,
|
|
NoFill: " ",
|
|
},
|
|
}
|
|
spPrLine := &cSpPr{
|
|
Ln: &aLn{
|
|
W: f.ptToEMUs(opts.Series[i].Line.Width),
|
|
Cap: "rnd", // rnd, sq, flat
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: schemeClr,
|
|
SrgbClr: srgbClr,
|
|
},
|
|
},
|
|
}
|
|
if chartSeriesSpPr, ok := map[ChartType]*cSpPr{
|
|
Line: spPrLine, Scatter: spPrScatter,
|
|
}[opts.Type]; ok {
|
|
return chartSeriesSpPr
|
|
}
|
|
if srgbClr != nil {
|
|
return spPr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// drawChartSeriesDPt provides a function to draw the c:dPt element by given
|
|
// data index and format sets.
|
|
func (f *File) drawChartSeriesDPt(i int, opts *Chart) []*cDPt {
|
|
dpt := []*cDPt{{
|
|
IDx: &attrValInt{Val: intPtr(i)},
|
|
Bubble3D: &attrValBool{Val: boolPtr(false)},
|
|
SpPr: &cSpPr{
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)},
|
|
},
|
|
Ln: &aLn{
|
|
W: 25400,
|
|
Cap: "rnd",
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)},
|
|
},
|
|
},
|
|
Sp3D: &aSp3D{
|
|
ContourW: 25400,
|
|
ContourClr: &aContourClr{
|
|
SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)},
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
chartSeriesDPt := map[ChartType][]*cDPt{Pie: dpt, Pie3D: dpt}
|
|
return chartSeriesDPt[opts.Type]
|
|
}
|
|
|
|
// drawChartSeriesCat provides a function to draw the c:cat element by given
|
|
// chart series and format sets.
|
|
func (f *File) drawChartSeriesCat(v ChartSeries, opts *Chart) *cCat {
|
|
cat := &cCat{
|
|
StrRef: &cStrRef{
|
|
F: v.Categories,
|
|
},
|
|
}
|
|
chartSeriesCat := map[ChartType]*cCat{Scatter: nil, Bubble: nil, Bubble3D: nil}
|
|
if _, ok := chartSeriesCat[opts.Type]; ok || v.Categories == "" {
|
|
return nil
|
|
}
|
|
return cat
|
|
}
|
|
|
|
// drawChartSeriesVal provides a function to draw the c:val element by given
|
|
// chart series and format sets.
|
|
func (f *File) drawChartSeriesVal(v ChartSeries, opts *Chart) *cVal {
|
|
val := &cVal{
|
|
NumRef: &cNumRef{
|
|
F: v.Values,
|
|
},
|
|
}
|
|
chartSeriesVal := map[ChartType]*cVal{Scatter: nil, Bubble: nil, Bubble3D: nil}
|
|
if _, ok := chartSeriesVal[opts.Type]; ok {
|
|
return nil
|
|
}
|
|
return val
|
|
}
|
|
|
|
// drawChartSeriesMarker provides a function to draw the c:marker element by
|
|
// given data index and format sets.
|
|
func (f *File) drawChartSeriesMarker(i int, opts *Chart) *cMarker {
|
|
defaultSymbol := map[ChartType]*attrValString{Scatter: {Val: stringPtr("circle")}}
|
|
marker := &cMarker{
|
|
Symbol: defaultSymbol[opts.Type],
|
|
Size: &attrValInt{Val: intPtr(5)},
|
|
}
|
|
if symbol := stringPtr(opts.Series[i].Marker.Symbol); *symbol != "" {
|
|
marker.Symbol = &attrValString{Val: symbol}
|
|
}
|
|
if size := intPtr(opts.Series[i].Marker.Size); *size != 0 {
|
|
marker.Size = &attrValInt{Val: size}
|
|
}
|
|
if i < 6 {
|
|
marker.SpPr = &cSpPr{
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{
|
|
Val: "accent" + strconv.Itoa(i+1),
|
|
},
|
|
},
|
|
Ln: &aLn{
|
|
W: 9252,
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{
|
|
Val: "accent" + strconv.Itoa(i+1),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
chartSeriesMarker := map[ChartType]*cMarker{Scatter: marker, Line: marker}
|
|
return chartSeriesMarker[opts.Type]
|
|
}
|
|
|
|
// drawChartSeriesXVal provides a function to draw the c:xVal element by given
|
|
// chart series and format sets.
|
|
func (f *File) drawChartSeriesXVal(v ChartSeries, opts *Chart) *cCat {
|
|
cat := &cCat{
|
|
StrRef: &cStrRef{
|
|
F: v.Categories,
|
|
},
|
|
}
|
|
chartSeriesXVal := map[ChartType]*cCat{Scatter: cat, Bubble: cat, Bubble3D: cat}
|
|
return chartSeriesXVal[opts.Type]
|
|
}
|
|
|
|
// drawChartSeriesYVal provides a function to draw the c:yVal element by given
|
|
// chart series and format sets.
|
|
func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal {
|
|
val := &cVal{
|
|
NumRef: &cNumRef{
|
|
F: v.Values,
|
|
},
|
|
}
|
|
chartSeriesYVal := map[ChartType]*cVal{Scatter: val, Bubble: val, Bubble3D: val}
|
|
return chartSeriesYVal[opts.Type]
|
|
}
|
|
|
|
// 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 == "" {
|
|
return nil
|
|
}
|
|
return &cVal{
|
|
NumRef: &cNumRef{
|
|
F: v.Sizes,
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element
|
|
// by given format sets.
|
|
func (f *File) drawCharSeriesBubble3D(opts *Chart) *attrValBool {
|
|
if _, ok := map[ChartType]bool{Bubble3D: true}[opts.Type]; !ok {
|
|
return nil
|
|
}
|
|
return &attrValBool{Val: boolPtr(true)}
|
|
}
|
|
|
|
// drawChartNumFmt provides a function to draw the c:numFmt element by given
|
|
// data labels format sets.
|
|
func (f *File) drawChartNumFmt(labels ChartNumFmt) *cNumFmt {
|
|
var numFmt *cNumFmt
|
|
if labels.CustomNumFmt != "" || labels.SourceLinked {
|
|
numFmt = &cNumFmt{
|
|
FormatCode: labels.CustomNumFmt,
|
|
SourceLinked: labels.SourceLinked,
|
|
}
|
|
}
|
|
return numFmt
|
|
}
|
|
|
|
// drawChartDLbls provides a function to draw the c:dLbls element by given
|
|
// format sets.
|
|
func (f *File) drawChartDLbls(opts *Chart) *cDLbls {
|
|
return &cDLbls{
|
|
NumFmt: f.drawChartNumFmt(opts.PlotArea.NumFmt),
|
|
ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)},
|
|
ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)},
|
|
ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)},
|
|
ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)},
|
|
ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)},
|
|
ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)},
|
|
ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)},
|
|
}
|
|
}
|
|
|
|
// drawChartSeriesDLbls provides a function to draw the c:dLbls element by
|
|
// given format sets.
|
|
func (f *File) drawChartSeriesDLbls(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,
|
|
}
|
|
if _, ok := chartSeriesDLbls[opts.Type]; ok {
|
|
return nil
|
|
}
|
|
return dLbls
|
|
}
|
|
|
|
// drawPlotAreaCatAx provides a function to draw the c:catAx element.
|
|
func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
|
|
max := &attrValFloat{Val: opts.XAxis.Maximum}
|
|
min := &attrValFloat{Val: opts.XAxis.Minimum}
|
|
if opts.XAxis.Maximum == nil {
|
|
max = nil
|
|
}
|
|
if opts.XAxis.Minimum == nil {
|
|
min = nil
|
|
}
|
|
axs := []*cAxs{
|
|
{
|
|
AxID: &attrValInt{Val: intPtr(100000000)},
|
|
Scaling: &cScaling{
|
|
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
|
|
Max: max,
|
|
Min: min,
|
|
},
|
|
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)},
|
|
},
|
|
}
|
|
if numFmt := f.drawChartNumFmt(opts.XAxis.NumFmt); numFmt != nil {
|
|
axs[0].NumFmt = numFmt
|
|
}
|
|
if opts.XAxis.MajorGridLines {
|
|
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
|
}
|
|
if opts.XAxis.MinorGridLines {
|
|
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
|
}
|
|
if opts.XAxis.TickLabelSkip != 0 {
|
|
axs[0].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: max,
|
|
Min: min,
|
|
},
|
|
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)},
|
|
})
|
|
}
|
|
return axs
|
|
}
|
|
|
|
// drawPlotAreaValAx provides a function to draw the c:valAx element.
|
|
func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
|
|
max := &attrValFloat{Val: opts.YAxis.Maximum}
|
|
min := &attrValFloat{Val: opts.YAxis.Minimum}
|
|
if opts.YAxis.Maximum == nil {
|
|
max = nil
|
|
}
|
|
if opts.YAxis.Minimum == nil {
|
|
min = nil
|
|
}
|
|
var logBase *attrValFloat
|
|
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: max,
|
|
Min: min,
|
|
},
|
|
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])},
|
|
},
|
|
}
|
|
if numFmt := f.drawChartNumFmt(opts.YAxis.NumFmt); numFmt != nil {
|
|
axs[0].NumFmt = numFmt
|
|
}
|
|
if opts.YAxis.MajorGridLines {
|
|
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
|
}
|
|
if opts.YAxis.MinorGridLines {
|
|
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
|
}
|
|
if pos, ok := valTickLblPos[opts.Type]; ok {
|
|
axs[0].TickLblPos.Val = stringPtr(pos)
|
|
}
|
|
if opts.YAxis.MajorUnit != 0 {
|
|
axs[0].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: max,
|
|
Min: min,
|
|
},
|
|
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])},
|
|
})
|
|
}
|
|
return axs
|
|
}
|
|
|
|
// drawPlotAreaSerAx provides a function to draw the c:serAx element.
|
|
func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
|
|
max := &attrValFloat{Val: opts.YAxis.Maximum}
|
|
min := &attrValFloat{Val: opts.YAxis.Minimum}
|
|
if opts.YAxis.Maximum == nil {
|
|
max = nil
|
|
}
|
|
if opts.YAxis.Minimum == nil {
|
|
min = nil
|
|
}
|
|
return []*cAxs{
|
|
{
|
|
AxID: &attrValInt{Val: intPtr(100000005)},
|
|
Scaling: &cScaling{
|
|
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
|
|
Max: max,
|
|
Min: min,
|
|
},
|
|
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
|
|
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
|
|
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
|
SpPr: f.drawPlotAreaSpPr(),
|
|
TxPr: f.drawPlotAreaTxPr(nil),
|
|
CrossAx: &attrValInt{Val: intPtr(100000001)},
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawPlotAreaTitles provides a function to draw the c:title element.
|
|
func (f *File) drawPlotAreaTitles(runs []RichTextRun, vert string) *cTitle {
|
|
if len(runs) == 0 {
|
|
return nil
|
|
}
|
|
title := &cTitle{Tx: cTx{Rich: &cRich{}}, Overlay: &attrValBool{Val: boolPtr(false)}}
|
|
for _, run := range runs {
|
|
r := &aR{T: run.Text}
|
|
if run.Font != nil {
|
|
r.RPr.B, r.RPr.I = run.Font.Bold, run.Font.Italic
|
|
if run.Font.Color != "" {
|
|
r.RPr.SolidFill = &aSolidFill{SrgbClr: &attrValString{Val: stringPtr(run.Font.Color)}}
|
|
}
|
|
if run.Font.Size > 0 {
|
|
r.RPr.Sz = run.Font.Size * 100
|
|
}
|
|
}
|
|
title.Tx.Rich.P = append(title.Tx.Rich.P, aP{
|
|
PPr: &aPPr{DefRPr: aRPr{}},
|
|
R: r,
|
|
EndParaRPr: &aEndParaRPr{Lang: "en-US", AltLang: "en-US"},
|
|
})
|
|
}
|
|
if vert == "horz" {
|
|
title.Tx.Rich.BodyPr = aBodyPr{Rot: -5400000, Vert: vert}
|
|
}
|
|
return title
|
|
}
|
|
|
|
// drawPlotAreaSpPr provides a function to draw the c:spPr element.
|
|
func (f *File) drawPlotAreaSpPr() *cSpPr {
|
|
return &cSpPr{
|
|
Ln: &aLn{
|
|
W: 9525,
|
|
Cap: "flat",
|
|
Cmpd: "sng",
|
|
Algn: "ctr",
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{
|
|
Val: "tx1",
|
|
LumMod: &attrValInt{Val: intPtr(15000)},
|
|
LumOff: &attrValInt{Val: intPtr(85000)},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// drawPlotAreaTxPr provides a function to draw the c:txPr element.
|
|
func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr {
|
|
cTxPr := &cTxPr{
|
|
BodyPr: aBodyPr{
|
|
Rot: -60000000,
|
|
SpcFirstLastPara: true,
|
|
VertOverflow: "ellipsis",
|
|
Vert: "horz",
|
|
Wrap: "square",
|
|
Anchor: "ctr",
|
|
AnchorCtr: true,
|
|
},
|
|
P: aP{
|
|
PPr: &aPPr{
|
|
DefRPr: aRPr{
|
|
Sz: 900,
|
|
B: false,
|
|
I: false,
|
|
U: "none",
|
|
Strike: "noStrike",
|
|
Kern: 1200,
|
|
Baseline: 0,
|
|
SolidFill: &aSolidFill{
|
|
SchemeClr: &aSchemeClr{
|
|
Val: "tx1",
|
|
LumMod: &attrValInt{Val: intPtr(15000)},
|
|
LumOff: &attrValInt{Val: intPtr(85000)},
|
|
},
|
|
},
|
|
Latin: &xlsxCTTextFont{Typeface: "+mn-lt"},
|
|
Ea: &aEa{Typeface: "+mn-ea"},
|
|
Cs: &aCs{Typeface: "+mn-cs"},
|
|
},
|
|
},
|
|
EndParaRPr: &aEndParaRPr{Lang: "en-US"},
|
|
},
|
|
}
|
|
if opts != nil {
|
|
cTxPr.P.PPr.DefRPr.B = opts.Font.Bold
|
|
cTxPr.P.PPr.DefRPr.I = opts.Font.Italic
|
|
if idx := inStrSlice(supportedDrawingUnderlineTypes, opts.Font.Underline, true); idx != -1 {
|
|
cTxPr.P.PPr.DefRPr.U = supportedDrawingUnderlineTypes[idx]
|
|
}
|
|
if opts.Font.Color != "" {
|
|
cTxPr.P.PPr.DefRPr.SolidFill.SchemeClr = nil
|
|
cTxPr.P.PPr.DefRPr.SolidFill.SrgbClr = &attrValString{Val: stringPtr(strings.ReplaceAll(strings.ToUpper(opts.Font.Color), "#", ""))}
|
|
}
|
|
}
|
|
return cTxPr
|
|
}
|
|
|
|
// drawingParser provides a function to parse drawingXML. In order to solve
|
|
// the problem that the label structure is changed after serialization and
|
|
// deserialization, two different structures: decodeWsDr and encodeWsDr are
|
|
// defined.
|
|
func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) {
|
|
var (
|
|
err error
|
|
ok bool
|
|
)
|
|
_, ok = f.Drawings.Load(path)
|
|
if !ok {
|
|
content := xlsxWsDr{}
|
|
content.A = NameSpaceDrawingML.Value
|
|
content.Xdr = NameSpaceDrawingMLSpreadSheet.Value
|
|
if _, ok = f.Pkg.Load(path); ok { // Append Model
|
|
decodeWsDr := decodeWsDr{}
|
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
|
|
Decode(&decodeWsDr); err != nil && err != io.EOF {
|
|
return nil, 0, err
|
|
}
|
|
content.R = decodeWsDr.R
|
|
for _, v := range decodeWsDr.AlternateContent {
|
|
content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{
|
|
Content: v.Content,
|
|
XMLNSMC: SourceRelationshipCompatibility.Value,
|
|
})
|
|
}
|
|
for _, v := range decodeWsDr.OneCellAnchor {
|
|
content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
|
|
EditAs: v.EditAs,
|
|
GraphicFrame: v.Content,
|
|
})
|
|
}
|
|
for _, v := range decodeWsDr.TwoCellAnchor {
|
|
content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{
|
|
EditAs: v.EditAs,
|
|
GraphicFrame: v.Content,
|
|
})
|
|
}
|
|
}
|
|
f.Drawings.Store(path, &content)
|
|
}
|
|
var wsDr *xlsxWsDr
|
|
if drawing, ok := f.Drawings.Load(path); ok && drawing != nil {
|
|
wsDr = drawing.(*xlsxWsDr)
|
|
}
|
|
wsDr.mu.Lock()
|
|
defer wsDr.mu.Unlock()
|
|
return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2, nil
|
|
}
|
|
|
|
// addDrawingChart provides a function to add chart graphic frame by given
|
|
// sheet, drawingXML, cell, width, height, relationship index and format sets.
|
|
func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *GraphicOptions) error {
|
|
col, row, err := CellNameToCoordinates(cell)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
width = int(float64(width) * opts.ScaleX)
|
|
height = int(float64(height) * opts.ScaleY)
|
|
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height)
|
|
content, cNvPrID, err := f.drawingParser(drawingXML)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
twoCellAnchor := xdrCellAnchor{}
|
|
twoCellAnchor.EditAs = opts.Positioning
|
|
from := xlsxFrom{}
|
|
from.Col = colStart
|
|
from.ColOff = opts.OffsetX * EMU
|
|
from.Row = rowStart
|
|
from.RowOff = opts.OffsetY * EMU
|
|
to := xlsxTo{}
|
|
to.Col = colEnd
|
|
to.ColOff = x2 * EMU
|
|
to.Row = rowEnd
|
|
to.RowOff = y2 * EMU
|
|
twoCellAnchor.From = &from
|
|
twoCellAnchor.To = &to
|
|
|
|
graphicFrame := xlsxGraphicFrame{
|
|
NvGraphicFramePr: xlsxNvGraphicFramePr{
|
|
CNvPr: &xlsxCNvPr{
|
|
ID: cNvPrID,
|
|
Name: "Chart " + strconv.Itoa(cNvPrID),
|
|
},
|
|
},
|
|
Graphic: &xlsxGraphic{
|
|
GraphicData: &xlsxGraphicData{
|
|
URI: NameSpaceDrawingMLChart.Value,
|
|
Chart: &xlsxChart{
|
|
C: NameSpaceDrawingMLChart.Value,
|
|
R: SourceRelationship.Value,
|
|
RID: "rId" + strconv.Itoa(rID),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
graphic, _ := xml.Marshal(graphicFrame)
|
|
twoCellAnchor.GraphicFrame = string(graphic)
|
|
twoCellAnchor.ClientData = &xdrClientData{
|
|
FLocksWithSheet: *opts.Locked,
|
|
FPrintsWithSheet: *opts.PrintObject,
|
|
}
|
|
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
|
|
f.Drawings.Store(drawingXML, content)
|
|
return err
|
|
}
|
|
|
|
// addSheetDrawingChart provides a function to add chart graphic frame for
|
|
// chartsheet by given sheet, drawingXML, width, height, relationship index
|
|
// and format sets.
|
|
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *GraphicOptions) error {
|
|
content, cNvPrID, err := f.drawingParser(drawingXML)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
absoluteAnchor := xdrCellAnchor{
|
|
EditAs: opts.Positioning,
|
|
Pos: &xlsxPoint2D{},
|
|
Ext: &xlsxExt{},
|
|
}
|
|
|
|
graphicFrame := xlsxGraphicFrame{
|
|
NvGraphicFramePr: xlsxNvGraphicFramePr{
|
|
CNvPr: &xlsxCNvPr{
|
|
ID: cNvPrID,
|
|
Name: "Chart " + strconv.Itoa(cNvPrID),
|
|
},
|
|
},
|
|
Graphic: &xlsxGraphic{
|
|
GraphicData: &xlsxGraphicData{
|
|
URI: NameSpaceDrawingMLChart.Value,
|
|
Chart: &xlsxChart{
|
|
C: NameSpaceDrawingMLChart.Value,
|
|
R: SourceRelationship.Value,
|
|
RID: "rId" + strconv.Itoa(rID),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
graphic, _ := xml.Marshal(graphicFrame)
|
|
absoluteAnchor.GraphicFrame = string(graphic)
|
|
absoluteAnchor.ClientData = &xdrClientData{
|
|
FLocksWithSheet: *opts.Locked,
|
|
FPrintsWithSheet: *opts.PrintObject,
|
|
}
|
|
content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
|
|
f.Drawings.Store(drawingXML, content)
|
|
return err
|
|
}
|
|
|
|
// deleteDrawing provides a function to delete chart graphic frame by given by
|
|
// given coordinates and graphic type.
|
|
func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error {
|
|
var (
|
|
err error
|
|
wsDr *xlsxWsDr
|
|
deTwoCellAnchor *decodeCellAnchor
|
|
)
|
|
xdrCellAnchorFuncs := map[string]func(anchor *xdrCellAnchor) bool{
|
|
"Chart": func(anchor *xdrCellAnchor) bool { return anchor.Pic == nil },
|
|
"Pic": func(anchor *xdrCellAnchor) bool { return anchor.Pic != nil },
|
|
}
|
|
decodeCellAnchorFuncs := map[string]func(anchor *decodeCellAnchor) bool{
|
|
"Chart": func(anchor *decodeCellAnchor) bool { return anchor.Pic == nil },
|
|
"Pic": func(anchor *decodeCellAnchor) bool { return anchor.Pic != nil },
|
|
}
|
|
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
|
|
return err
|
|
}
|
|
for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ {
|
|
if err = nil; wsDr.TwoCellAnchor[idx].From != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) {
|
|
if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row {
|
|
wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...)
|
|
idx--
|
|
}
|
|
}
|
|
}
|
|
for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ {
|
|
deTwoCellAnchor = new(decodeCellAnchor)
|
|
if err = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeCellAnchor>")).
|
|
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
if err = nil; deTwoCellAnchor.From != nil && decodeCellAnchorFuncs[drawingType](deTwoCellAnchor) {
|
|
if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
|
|
wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...)
|
|
idx--
|
|
}
|
|
}
|
|
}
|
|
f.Drawings.Store(drawingXML, wsDr)
|
|
return err
|
|
}
|
|
|
|
// genAxID provides a function to generate ID for primary and secondary
|
|
// horizontal or vertical axis.
|
|
func (f *File) genAxID(opts *Chart) []*attrValInt {
|
|
opts.XAxis.axID, opts.YAxis.axID = 100000000, 100000001
|
|
if opts.order > 0 && opts.YAxis.Secondary {
|
|
opts.XAxis.axID, opts.YAxis.axID = 100000003, 100000004
|
|
}
|
|
return []*attrValInt{{Val: intPtr(opts.XAxis.axID)}, {Val: intPtr(opts.YAxis.axID)}}
|
|
}
|