Overview
Adding a pie/bar chart to a Word document actually creates and embeds an Excel spread-sheet into the Word document.
Also, the chart data is stored in two locations:
- In the embedded Excel spread-sheet
- In cached values in c:v elements as a separate XML file
When using the OpenXML SDK Productivity Tool on a Word document with a chart, the generated code uses binary objects for the chart - this is of little use.
One Suggested Approach
If there is no need to edit the chart values, create the chart in a temporary Excel spread-sheet and clone the graphic as an inline drawing in the Word document.
Create the Template Spread-Sheet
Create a new spread-sheet, add a bar/pie chart and format this to suit: size, colours etc.
When a chart is added to a spread-sheet, the chart title and segment labels & values are sourced from a fixed format of sheet cells in 'Sheet1' - this format must be preserved.
It is suggested to remove the chart border - after importing into a Word document, the bottom border seems to be missing.
Update the Template Spread-Sheet Code to Import Chart Data
Get the reflected code using the OpenXML SDK Productivity Tool.
Look in the code for the chart title and segment labels & values - these will need to be passed as parameters eg
..., string chartTitle, Dictionary<string, int> chartData )
Generally, these three functions need changing:
GenerateWorksheetPart1Content( worksheetPart1, chartTitle, chartData )
GenerateTableDefinitionPart1Content( tableDefinitionPart1, chartTitle, chartData )
GenerateChartPart1Content( chartPart1, chartTitle, chartData )
Be sure to preserve the cell format of the original spread-sheet.
Test this with various sets of chart data to ensure that the generated spread-sheet shows the chart title, labels & values and format that is required.
Because the reflected code is verbose, I cannot show all of this but find where the chart title, labels & values are set and change accordingly.
eg replace the repeated blocks of this:
Row row1 = new Row(){ RowIndex = (UInt32Value)1U, Spans = new ListValue<StringValue>() { InnerText = "1:2" } };
Cell cell1 = new Cell(){ CellReference = "A1", StyleIndex = (UInt32Value)1U, DataType = CellValues.SharedString };
CellValue cellValue1 = new CellValue();
cellValue1.Text = "4";
cell1.Append(cellValue1);
with this (for a pie chart):
Row row1 = new Row() { RowIndex = 1 };
row1.Append( new Cell() { CellReference = "A1", CellValue = new CellValue( "" ) , DataType = CellValues.String } );
row1.Append( new Cell() { CellReference = "B1", CellValue = new CellValue( chartTitle ), DataType = CellValues.String } );
sheetData1.Append( row1 );
for( int i = 0; i < chartData.Count; i++ )
{
var item = chartData.ElementAt( i );
uint rowNo = (uint) i + 2;
Row rowN = new Row() { RowIndex = rowNo };
rowN.Append( new Cell() { CellReference = "A" + rowNo, CellValue = new CellValue( item.Key ), DataType = CellValues.String } );
rowN.Append( new Cell() { CellReference = "B" + rowNo, CellValue = new CellValue( "" + item.Value ), DataType = CellValues.Number } );
sheetData1.Append( rowN );
}
Note that the reflected code uses shared strings - after the code changes, these are not used as the chart data is now passed as parameters.
Generate and Import the Chart Graphic into the Word Document
As part of the Word document generation, create the template spread-sheet with the populated chart, then clone the Excel chart graphic into a Word inline element.
...
Drawing drawing1 = GetChartDrawing( chartTitle, chartData, mainPart, (uint) chartId );
Run run3 = new Run();
run3.Append( drawing1 );
...
where the drawing is generated by:
public Drawing GetDrawing( string chartTitle, Dictionary<string, int> chartData, MainDocumentPart mainPart, uint indexNo )
{
Drawing drawingW = new Drawing();
Wp.Inline inlineW = new Wp.Inline() { DistanceFromTop = (UInt32Value) 0U, DistanceFromBottom = (UInt32Value) 0U, DistanceFromLeft = (UInt32Value) 0U, DistanceFromRight = (UInt32Value) 0U };
Wp.Extent extentW = new Wp.Extent() { Cx = 5486400L, Cy = 3200400L };
Wp.DocProperties docPropertiesW = new Wp.DocProperties() { Id = (UInt32Value) indexNo, Name = "Chart" + indexNo };
using( MemoryStream memoryStreamX = new MemoryStream() )
{
new ExcelChart().CreatePackage( memoryStreamX, chartTitle, chartData );
using( SpreadsheetDocument spreadsheetDocX = SpreadsheetDocument.Open( memoryStreamX, false ) )
{
WorkbookPart workbookPartX = spreadsheetDocX.WorkbookPart;
Ss.Sheet sheet1X = workbookPartX.Workbook.Descendants<Ss.Sheet>().FirstOrDefault( s => s.Name == "Sheet1" );
WorksheetPart worksheetPartX = (WorksheetPart) workbookPartX.GetPartById( sheet1X.Id );
DrawingsPart drawingPartX = worksheetPartX.DrawingsPart;
ChartPart chartPartX = (ChartPart) drawingPartX.ChartParts.FirstOrDefault();
Xdr.GraphicFrame graphicFrameX = drawingPartX.WorksheetDrawing.Descendants<DocumentFormat.OpenXml.Drawing.Spreadsheet.GraphicFrame>().First();
A.Graphic graphicW = (A.Graphic) graphicFrameX.Graphic.CloneNode( true );
ChartPart chartPartW = mainPart.AddPart<ChartPart>( chartPartX );
string relationshipId = mainPart.GetIdOfPart( chartPartW );
C.ChartReference chartReferenceW = graphicW.GraphicData.GetFirstChild<C.ChartReference>();
chartReferenceW.Id = relationshipId;
inlineW.Append( extentW );
inlineW.Append( docPropertiesW );
inlineW.Append( graphicW );
drawingW.Append( inlineW );
}
}
return drawingW;
}
Each chart should have a unique id in the docProperties
.
With this approach, the chart is shown as a graphic without the need to embed an Excel spread-sheet.