How to add a barchart to a OpenXML created document
Asked Answered
K

3

5

My C# app uses OpenXML to create a MSWord doc with several tables in it already. The last part is to add a barchart. I can't find a good example of this case.

Thanks for your help!

I am creating the document from scratch. Starting with:

using (WordprocessingDocument myDoc = WordprocessingDocument.Create(documentStream, WordprocessingDocumentType.Document, autoSave: true))

Then I add new tables and paragraphs in C# code. All that is working until I get to the barchart. I found a sample project that inserts a piechart into a word document but I don't understand the differences between the chart types to convert it. Here is the piechart sample project that I found:

https://code.msdn.microsoft.com/office/How-to-create-Chart-into-a7d424f6

Thanks for your help!

Kame answered 22/2, 2017 at 21:2 Comment(3)
I'm not familiar with OpenXML, but it sounds like you might want to start by doing a Google search to see what others have tried. Have you tried reading the OpenXML documentation (msdn.microsoft.com/en-us/library/office/bb448854.aspx)? Unless you can be more specific about the problem that you're having, you're unlikely to get much help.Archivist
What have you tried so far? Are you working with creating the document from scratch or using a template to fill out? To best help you with this, we need to know more about your problem.Cagliari
Adding a chart to a docx-document is not trivial. The example you have found seems like a great starting point. It inserts a Pie3DChart. A barchart can be added using the type BarChart (both from the DocumentFormat.OpenXml.Drawing.Charts namespace).Climax
G
5

To insert a basic bar chart, I would not look at the Pie Chart code and try to guess how to convert that to a Bar Chart.

A faster way to your solution is use the Open XML Productivity Tool. It has a feature that allows you to open 2 files, determine the difference between the two Open XML structures and will generate the code you need to make the first file look like the second file.

try this approach:

  1. Install the Open XML Productivity Tool
  2. Save your Word document (a clean version before Pie Chart piece was added) and name it NoBarChart.docx
  3. Make a copy of NoBarChart.docx name it WithBarChart.docx
  4. Open WithBarChart.docx with Word and add a Bar Chart (Insert->Chart->Bar).
  5. Style the Bar Chart with colors, formats, etc to meet your ultimate solution and save and close.
  6. Run the Open XML Productivity Tool and click the Compare Files button at the top.
  7. Choose NoBarChart.docx for source and WithBarChart.docx for target
  8. Click the highlighted parts of the file that are different and click the View Part Diff to see the differences in XML. Then click View Package Code and the tool will generate code to make the source look like the target.

Inspect the code it generates for ideas on how to add a Bar Chart. If some of the code is missing, you can generate the entire Target file with the tool.

A link to a simple WPF app that generates a blank doc with Bar chart is on GitHub. Here is a picture of the document:

enter image description here

This same approach can be used to generate most Word, Excel or PowerPoint files and features they provide.

Gelation answered 2/3, 2017 at 23:14 Comment(2)
@Gelation I can't get the Open Xml Productive tool from the link you've supplied . Could you send a link ?Stclair
try this: microsoft.com/en-us/download/details.aspx?id=30425. The OpenXML Productivity Tool is not a separate download. It comes bundled with the OpenXML SDK. croll down a bit on the first screen and click the red Download button. On the following screen, select the second check box OpenXMLSDKToolV25.msi and click Next to start the download. Once the MSI is downloaded, launch the download and accept the defaults on the installation wizard screens to complete the installation.Gelation
S
2

I followed the tutorial you followed that creates a Piechart. Then I managed to change that in order to insert a bar chart to the word document. I hope the following code will help you. I'm creating a data sample in the CreateBarChart function in order to create a column in the Barchart.

public static WordprocessingDocument CreateBarChart(WordprocessingDocument document)//List<ChartSubArea> chartList,
    {
        string title = "New Chart";

        Dictionary<string, int> data = new Dictionary<string, int>();
        data.Add("abc", 1);

        // Get MainDocumentPart of Document
        MainDocumentPart mainPart = document.AddMainDocumentPart();
        mainPart.Document = new Document(new Body());

        // Create ChartPart object in Word Document
        ChartPart chartPart = mainPart.AddNewPart<ChartPart>("rId110");

        // the root element of chartPart 
        dc.ChartSpace chartSpace = new dc.ChartSpace();
        chartSpace.Append(new dc.EditingLanguage() { Val = "en-us" });

        // Create Chart 
        dc.Chart chart = new dc.Chart();
        chart.Append(new dc.AutoTitleDeleted() { Val = true });

        // Define the 3D view
        dc.View3D view3D = new dc.View3D();
        view3D.Append(new dc.RotateX() { Val = 30 });
        view3D.Append(new dc.RotateY() { Val = 0 });

        // Intiliazes a new instance of the PlotArea class
        dc.PlotArea plotArea = new dc.PlotArea();
        BarChart barChart = plotArea.AppendChild<BarChart>(new BarChart(new BarDirection()
        { Val = new EnumValue<BarDirectionValues>(BarDirectionValues.Column) },
           new BarGrouping() { Val = new EnumValue<BarGroupingValues>(BarGroupingValues.Clustered) }));

        plotArea.Append(new dc.Layout());


        dc.ChartShapeProperties chartShapePros = new dc.ChartShapeProperties();

        uint i = 0;
        // Iterate through each key in the Dictionary collection and add the key to the chart Series
        // and add the corresponding value to the chart Values.
        foreach (string key in data.Keys)
        {
            BarChartSeries barChartSeries = barChart.AppendChild<BarChartSeries>(new BarChartSeries(new Index()
            {
                Val =
                new UInt32Value(i)
            },
                new Order() { Val = new UInt32Value(i) },
                new SeriesText(new NumericValue() { Text = key })));

            StringLiteral strLit = barChartSeries.AppendChild<CategoryAxisData>(new CategoryAxisData()).AppendChild<StringLiteral>(new StringLiteral());
            strLit.Append(new PointCount() { Val = new UInt32Value(1U) });
            strLit.AppendChild<StringPoint>(new StringPoint() { Index = new UInt32Value(0U) }).Append(new NumericValue(title));

            NumberLiteral numLit = barChartSeries.AppendChild<DocumentFormat.OpenXml.Drawing.Charts.Values>(
                new DocumentFormat.OpenXml.Drawing.Charts.Values()).AppendChild<NumberLiteral>(new NumberLiteral());
            numLit.Append(new FormatCode("General"));
            numLit.Append(new PointCount() { Val = new UInt32Value(1U) });
            numLit.AppendChild<NumericPoint>(new NumericPoint() { Index = new UInt32Value(0u) }).Append
            (new NumericValue(data[key].ToString()));

            i++;
        }

        barChart.Append(new AxisId() { Val = new UInt32Value(48650112u) });
        barChart.Append(new AxisId() { Val = new UInt32Value(48672768u) });

        // Add the Category Axis.
        CategoryAxis catAx = plotArea.AppendChild<CategoryAxis>(new CategoryAxis(new AxisId()
        { Val = new UInt32Value(48650112u) }, new Scaling(new Orientation()
        {
            Val = new EnumValue<DocumentFormat.
            OpenXml.Drawing.Charts.OrientationValues>(DocumentFormat.OpenXml.Drawing.Charts.OrientationValues.MinMax)
        }),
           new AxisPosition() { Val = new EnumValue<AxisPositionValues>(AxisPositionValues.Bottom) },
           new TickLabelPosition() { Val = new EnumValue<TickLabelPositionValues>(TickLabelPositionValues.NextTo) },
           new CrossingAxis() { Val = new UInt32Value(48672768U) },
           new Crosses() { Val = new EnumValue<CrossesValues>(CrossesValues.AutoZero) },
           new AutoLabeled() { Val = new BooleanValue(true) },
           new LabelAlignment() { Val = new EnumValue<LabelAlignmentValues>(LabelAlignmentValues.Center) },
           new LabelOffset() { Val = new UInt16Value((ushort)100) }));

        // Add the Value Axis.
        ValueAxis valAx = plotArea.AppendChild<ValueAxis>(new ValueAxis(new AxisId() { Val = new UInt32Value(48672768u) },
        new Scaling(new Orientation()
        {
            Val = new EnumValue<DocumentFormat.OpenXml.Drawing.Charts.OrientationValues>(
            DocumentFormat.OpenXml.Drawing.Charts.OrientationValues.MinMax)
        }),
        new AxisPosition() { Val = new EnumValue<AxisPositionValues>(AxisPositionValues.Left) },
        new MajorGridlines(),
        new DocumentFormat.OpenXml.Drawing.Charts.NumberingFormat()
        {
            FormatCode = new StringValue("General"),
            SourceLinked = new BooleanValue(true)
        }, new TickLabelPosition()
        {
            Val = new EnumValue<TickLabelPositionValues>
            (TickLabelPositionValues.NextTo)
        }, new CrossingAxis() { Val = new UInt32Value(48650112U) },
        new Crosses() { Val = new EnumValue<CrossesValues>(CrossesValues.AutoZero) },
        new CrossBetween() { Val = new EnumValue<CrossBetweenValues>(CrossBetweenValues.Between) }));

        // create child elements of the c:legend element
        dc.Legend legend = new dc.Legend();
        legend.Append(new dc.LegendPosition() { Val = LegendPositionValues.Right });
        dc.Overlay overlay = new dc.Overlay() { Val = false };
        legend.Append(overlay);

        dc.TextProperties textPros = new DocumentFormat.OpenXml.Drawing.Charts.TextProperties();
        textPros.Append(new d.BodyProperties());
        textPros.Append(new d.ListStyle());

        d.Paragraph paragraph = new d.Paragraph();
        d.ParagraphProperties paraPros = new d.ParagraphProperties();
        d.DefaultParagraphProperties defaultParaPros = new d.DefaultParagraphProperties();
        defaultParaPros.Append(new d.LatinFont() { Typeface = "Arial", PitchFamily = 34, CharacterSet = 0 });
        defaultParaPros.Append(new d.ComplexScriptFont() { Typeface = "Arial", PitchFamily = 34, CharacterSet = 0 });
        paraPros.Append(defaultParaPros);
        paragraph.Append(paraPros);
        paragraph.Append(new d.EndParagraphRunProperties() { Language = "en-Us" });

        textPros.Append(paragraph);
        legend.Append(textPros);

        // Append c:view3D, c:plotArea and c:legend elements to the end of c:chart element
        chart.Append(view3D);
        chart.Append(plotArea);
        chart.Append(legend);

        // Append the c:chart element to the end of c:chartSpace element
        chartSpace.Append(chart);

        // Create c:spPr Elements and fill the child elements of it
        chartShapePros = new dc.ChartShapeProperties();
        d.Outline outline = new d.Outline();
        outline.Append(new d.NoFill());
        chartShapePros.Append(outline);

        // Append c:spPr element to the end of c:chartSpace element
        chartSpace.Append(chartShapePros);

        chartPart.ChartSpace = chartSpace;

        // Generate content of the MainDocumentPart
        GeneratePartContent(mainPart);

        return document;

    }
public static void GeneratePartContent(MainDocumentPart mainPart)
    {
        w.Paragraph paragraph = new w.Paragraph() { RsidParagraphAddition = "00C75AEB", RsidRunAdditionDefault = "000F3EFF" };

        // Create a new run that has an inline drawing object
        w.Run run = new w.Run();
        w.Drawing drawing = new w.Drawing();

        dw.Inline inline = new dw.Inline();
        inline.Append(new dw.Extent() { Cx = 5274310L, Cy = 3076575L });
        dw.DocProperties docPros = new dw.DocProperties() { Id = (UInt32Value)1U, Name = "Chart 1" };
        inline.Append(docPros);

        d.Graphic g = new d.Graphic();
        d.GraphicData graphicData = new d.GraphicData() { Uri = "http://schemas.openxmlformats.org/drawingml/2006/chart" };
        dc.ChartReference chartReference = new ChartReference() { Id = "rId110" };
        graphicData.Append(chartReference);
        g.Append(graphicData);
        inline.Append(g);
        drawing.Append(inline);
        run.Append(drawing);
        paragraph.Append(run);

        mainPart.Document.Body.Append(paragraph);
    }

This is the result I'm seeing . Hope this will help. enter image description here

Stclair answered 23/8, 2019 at 5:22 Comment(1)
The result looks good, the code is not really clean, it's missing the using statemant for eg. dc. using dc = DocumentFormat.OpenXml.Drawing.Charts; But then BarChart is not using the prefix dc, so my guess is that you have using DocumentFormat.OpenXml.Drawing.Charts; too.Footrope
P
0

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:

  1. In the embedded Excel spread-sheet
  2. 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.

Primer answered 14/2, 2023 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.