// 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: &cTitle{
				Tx: cTx{
					Rich: &cRich{
						P: aP{
							PPr: &aPPr{
								DefRPr: aRPr{
									Kern:   1200,
									Strike: "noStrike",
									U:      "none",
									Sz:     1400,
									SolidFill: &aSolidFill{
										SchemeClr: &aSchemeClr{
											Val: "tx1",
											LumMod: &attrValInt{
												Val: intPtr(65000),
											},
											LumOff: &attrValInt{
												Val: intPtr(35000),
											},
										},
									},
									Ea: &aEa{
										Typeface: "+mn-ea",
									},
									Cs: &aCs{
										Typeface: "+mn-cs",
									},
									Latin: &xlsxCTTextFont{
										Typeface: "+mn-lt",
									},
								},
							},
							R: &aR{
								RPr: aRPr{
									Lang:    "en-US",
									AltLang: "en-US",
								},
								T: opts.Title.Name,
							},
						},
					},
				},
				TxPr: cTxPr{
					P: aP{
						PPr: &aPPr{
							DefRPr: aRPr{
								Kern:   1200,
								U:      "none",
								Sz:     14000,
								Strike: "noStrike",
							},
						},
						EndParaRPr: &aEndParaRPr{
							Lang: "en-US",
						},
					},
				},
				Overlay: &attrValBool{Val: boolPtr(false)},
			},
			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: []*attrValInt{
			{Val: intPtr(754001152)},
			{Val: intPtr(753999904)},
		},
		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: []*attrValInt{
				{Val: intPtr(754001152)},
				{Val: intPtr(753999904)},
			},
		},
		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: []*attrValInt{
				{Val: intPtr(754001152)},
				{Val: intPtr(753999904)},
			},
		},
		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: []*attrValInt{
				{Val: intPtr(754001152)},
				{Val: intPtr(753999904)},
			},
		},
		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: []*attrValInt{
				{Val: intPtr(754001152)},
				{Val: intPtr(753999904)},
			},
		},
		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(754001152)},
				{Val: intPtr(753999904)},
				{Val: intPtr(832256642)},
			},
		},
		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(754001152)},
				{Val: intPtr(753999904)},
				{Val: intPtr(832256642)},
			},
		},
		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: []*attrValInt{
				{Val: intPtr(754001152)},
				{Val: intPtr(753999904)},
			},
		},
		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(754001152)},
			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")},
			TickLblPos:    &attrValString{Val: stringPtr("nextTo")},
			SpPr:          f.drawPlotAreaSpPr(),
			TxPr:          f.drawPlotAreaTxPr(&opts.YAxis),
			CrossAx:       &attrValInt{Val: intPtr(753999904)},
			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)}
	}
	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(753999904)},
			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])},
			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(754001152)},
			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)}
	}
	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(832256642)},
			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(753999904)},
		},
	}
}

// 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.Lock()
	defer wsDr.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
	}
	colIdx := col - 1
	rowIdx := row - 1

	width = int(float64(width) * opts.ScaleX)
	height = int(float64(height) * opts.ScaleY)
	colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, 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 *decodeTwoCellAnchor
	)
	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 },
	}
	decodeTwoCellAnchorFuncs := map[string]func(anchor *decodeTwoCellAnchor) bool{
		"Chart": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic == nil },
		"Pic":   func(anchor *decodeTwoCellAnchor) 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(decodeTwoCellAnchor)
		if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeTwoCellAnchor>")).
			Decode(deTwoCellAnchor); err != nil && err != io.EOF {
			return err
		}
		if err = nil; deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[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
}
