Embed Charts in PowerPoint

Enhancing Reporting Consistency And Usability Using Tools in J2EE-based Web Applications

Business users often request functionality in their web applications that connects them to the applications they know best – the Microsoft Office suite.  In particular, a significant portion of today’s workforce live and breathe PowerPoint and Excel daily. 

However, silos of data embedded in Excel spreadsheets scattered across the company present real challenges for today’s CIOs and IT executives in charge of maintaining consistent and accurate reporting.  In this post, I am going to describe a solution for providing a consistent and easy to use method for enhancing reporting consistency across the enterprise by providing business users the ability to upload their presentation tool of choice – a PowerPoint presentation – and return the same presentation to them containing accurate and up-to-date data in the form of customizable charts and tables.

Using ASPOSE Slides and JFreeChart to export charts to PowerPoint from a Java servlet

The tools we will be using in this process are ASPOSE Slides and JFreeChart.  ASPOSE Slides is a component which is available for trial download (http://www.aspose.com/community/files/51/file-format-components/aspose.slides/entry137220.aspx).  ASPOSE is a commercial product, but all components are available for trial downloads.  JFreeChart is an open source, pure Java charting package that is licensed under the LGPL, and available for download at http://www.jfree.org/jfreechart/download.html.  You will also need JAI, the Java Advanced Imaging library, available from java.net (https://jai.dev.java.net/).  This project can of course be built in any J2EE environment, but I will be using Eclipse for screenshots, etc.

bullet A key component of making this tutorial into a complete solution is the ability to upload an arbitrary PowerPoint template and have the chosen chart generated and inserted.  Bob has covered file uploading previously for Nexaweb-based Java applications.  Please read How to upload a file to learn how to incorporate this functionality. 

In this particular example, we are simply demonstrating the servlet functionality, so we will assume that the file has already been uploaded.

To get started, create a J2EE web application, and add the Java ASPOSE.Slides component (“aspose.slides.jar”) to your WEB-INF/lib directory.  Next, create a servlet that will output your updated PowerPoint.  In WEB-INF/web.xml:

<servlet>
     <servlet-name>javappt</servlet-name>
     <servlet-class>test.PowerPointServlet</servlet-class>
</servlet>
<servlet-mapping>
     <servlet-name>javappt</servlet-name>
     <url-pattern>/javappt</url-pattern>
</servlet-mapping>

And in test/PowerPointServlet.java:

public class PowerPointServlet extends HttpServlet { }

For the purposes of this demo, we will use a simple doGet to respond to GET requests only from the browser.
public class PowerPointServlet extends HttpServlet {
    //Process the HTTP Get request
    public void doGet(HttpServletRequest request, HttpServletResponse response) 		 
        throws ServletException, IOException {}
}

Next, we need to create a presentation object based on the ASPOSE.Slides component. For the purposes of this article, we will assume that the user has uploaded a template called “template.ppt”. [Download template] This also assumes the uploaded file is in the root of your J2EEE web application context. Since we want to create a PowerPoint presentation based on this template, we will first read in the template file:

ServletContext context = request.getSession().getServletContext(); 
String path = context.getRealPath(request.getContextPath()); 
int pos = path.lastIndexOf("\\"); 
path = path.substring(0, pos); 
String templatePath = path + "\\" + "template.ppt"; 
FileInputStream templateFileIS = new FileInputStream(templatePath);

 

Once the file has been read into a FileInputStream object, we can create an ASPOSE Presentation object based on that object. Note that the constructor throws a PptException, so we will wrap the rest of the code dealing with the presentation object in a try…catch block. Add the following import statement:

import com.aspose.slides.*; 

Now, create the presentation object using the FileInputStream:

try {  Presentation pres = new Presentation(templateFileIS); 
     //&hellip; &ldquo;pres&rdquo; object processing code 
} catch (PptException e) {  
    response.setContentType("text/html"); 	 
    PrintWriter out = response.getWriter(); 	
    //Output some error HTML here 
}

Since this is a servlet, we should print out a message to the user in HTML if we encounter some exception when creating the presentation.

Let’s also create a basic JFreeChart object that we will insert into the PowerPoint template.  For more information on using JFreeChart, please go to http://www.jfreechart.org

//Create JFreeChart and save as an array of bytes
 
DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
categoryDataset.setValue(new Integer(35), "2006", "First Quarter");
categoryDataset.setValue(new Integer(45), "2007", "First Quarter");
categoryDataset.setValue(new Integer(40), "2006", "Second Quarter");
categoryDataset.setValue(new Integer(60), "2007", "Second Quarter");
 
JFreeChart chart = ChartFactory.createBarChart3D
    ("Year Over Year Sales Analysis", // Title
     "Quarters",  // X-Axis label
     "Sales", // Y-Axis label
     categoryDataset,  // Dataset
     PlotOrientation.VERTICAL,  // Vertical bar chart
     true,  // Show legend
     false,  // Show tooltips
     false  // Configure chart to generate URLs
     );

This will generate a chart like this:

Nexaweb

Next, let’s determine where to put our chart.  For the sake of simplicity, our template will have two slides only – a title slide and a chart slide.  To enable the business users to customize your template, you should publish a guide to the available keywords and what each one does.  In this particular demonstration, we have four keywords:

  • Title
  • Subtitle1
  • Subtitle2
  • InsertChartHere

We take Title1, Subtitle1 and Subtitle2 as parameters into the servlet, for example: "http://localhost/YourProjectName/
javappt?title1=Your%20Title&subtitle1=Your%20First%20Subtitle&subtitle2=Your%20Second%20Subtitle" 

 

Helpful Tip

If you get a NullPointerException the first time you test this program, make sure you included all 3 parameters.

 

In order to update your presentation with text and images based on keywords, we will need to iterate through each slide.  To do this, we will use the following loop:

Slides slides = pres.getSlides();
for (int i = 0; i < slides.size(); i++) 
{
     //Do some stuff
}

Now, one potential pitfall to make a note of as you work with the ASPOSE API is that there are going to be two different classes of objects that might contain your keyword inside your template PowerPoint file, depending on how the textbox containing your keyword was created.

If the textbox containing your keyword was created based on a Slide Layout, ASPOSE will use the TextHolder class, an extension of the Placeholder class, to instantiate the object representing the textbox.  If, however, you created the textbox yourself, ASPOSE will use a Shape containing a TextFrame as the class.  To ensure that we are thorough in our search for keywords, we will loop through both the Shape and Placeholder collections in each slide:

Slides slides = pres.getSlides();
for (int i = 0; i < slides.size(); i++) {
 
    //Check placeholders for keywords
    Placeholders places = slides.get(i).getPlaceholders();
    for (int j = 0; j < places.size(); j++) 
    {
        //Check that this placeholder is a TextHolder
        if(places.get(j) instanceof TextHolder)
            this.replaceKeyword( 
(ITextBox) places.get( j ), chart, request )
    }
 
    //Check shapes for keywords
    Shapes shapes = slides.get(i).getShapes();
 
    for (int j = 0; j < shapes.size(); j++) 
    {
        //Check that this shape contains a TextFrame
        if(shapes.get(j)!=null && shapes.get(j).getTextFrame() != null)            
           this.replaceKeyword( 
               (ITextBox) shapes.get(j).getTextFrame(), 
                   chart, request);    
    }
}

In this block, we have also introduced a new method – replaceKeyword.  replaceKeyword takes an ITextBox, the chart we would like to insert into the presentation, and the request object that we use to get the text from the GET request to insert into the presentation.  This method examines the text box to determine if it contains any text, and if so, gets the text of the first “portion” – that is, the first line – and assumes that to be the keyword.  Choose your keywords carefully, so that text is not inadvertently replaced that the user did not intend to be replaced.  The method then matches the keyword of the text box against the keywords you have programmed to catch.  When a match is found, the appropriate replacement is performed.

private void replaceKeyword(ITextBox textBox, 
    JFreeChart chart, 
    HttpServletRequest request) 
        throws PptReadException, PptEditException, 
       IOException, PptImageException
{
    if(textBox.getParagraphs().size() > 0)
    {
        String keyword = 
                 textBox.getParagraphs().get(0).getText();
        Presentation pres = textBox.getSlide().getParent();
 
        if (keyword.equalsIgnoreCase("Title1"))                            
            textBox.getParagraphs().
                get(0).getPortions().get(0).setText( 
                request.getParameter("title1")
            );
        else if (keyword.equalsIgnoreCase("Subtitle1"))
        {
            //The template comes with one paragraph 
            //and one portion already defined 
            textBox.getParagraphs().get(0).getPortions().
            get(0).setText(request.getParameter("subtitle1"));
        }
        else if (keyword.equalsIgnoreCase("Subtitle2"))
        {    
            //Here I am just illustrating how to create //a new paragraph
            textBox.getParagraphs().get(0).getPortions().
                get(0).setText(" ");
            //To create a new line, we must add a paragraph
            textBox.getParagraphs().add(new Paragraph());
            //Then add a portion to the paragraph 
            textBox.getParagraphs().get(1).getPortions().add(
                new Portion(
                    request.getParameter("subtitle2")
                ));
        }
        else if(keyword.equalsIgnoreCase("InsertChartHere"))
        {
            int width;
            int height;
            if(textBox instanceof IShape)
            {
                IShape chartShape = (IShape)textBox;
                width = chartShape.getWidth();
                height = chartShape.getHeight();
                System.out.println(width+ " x " +height);

Helpful Tip

Create an image from the chart based on the size of the text holder.  To ensure no skewing, but also to ensure that the image is not too small, divide the textholder dimensions by the same factor.  I recommend testing with factors between 4 and 8 to see what is best for the chart being created.  4 will create a higher resolution chart with smaller fonts, 8 will create a lower resolution chart with easier to read fonts.  Further research into JFreeChart APIs would likely enable bigger fonts along with higher resolution images.

BufferedImage chartImage = 
    chart.createBufferedImage(width /6 , height/6);
 
    //Using the ImageIO library, we will write the //buffered image to a PNG format 
    ByteArrayOutputStream chartImageOS = 
          new ByteArrayOutputStream();
    ImageIO.write(chartImage, "png", chartImageOS);
    ByteArrayInputStream chartImageIS = 
        new ByteArrayInputStream(chartImageOS.toByteArray());
    BufferedInputStream chartImageBuffIS = 
        new BufferedInputStream(chartImageIS);
 
    //Creating a picture object that will be used to fill //the shape
    Picture chartPic = new Picture(pres, chartImageBuffIS);
 
 //After adding the picture object to pictures collection,
    //the picture is given a unique id
    int chartPicId = pres.getPictures().add( chartPic);
 
    //Needed to clear the potential "Click here to add text" //message that appears if the textBox is a TextHolder 
    textBox.getParagraphs().get(0).setText(" ");
 
    //If the object is already a shape, i.e. a TextFrame,
    //we can work with the object directly
    if(chartShape instanceof TextFrame)
    {               
       ((TextFrame) chartShape).getFillFormat().setType(
            FillType.PICTURE);
       ((TextFrame) chartShape).getFillFormat().setPictureId(
            chartPicId);
    }
 
    //If the object is a placeholder, i.e. a TextHolder,
    //we need to get a reference to the shape object first
    //and then work with that
    else if(chartShape instanceof TextHolder)
    {
      ((TextHolder) chartShape.getShapeRef().
         getFillFormat().setType(FillType.PICTURE);
      ((TextHolder) chartShape).getShapeRef().
         getFillFormat().setPictureId(chartPicId);
     }
    }
   }        
  }
} 

 

Finally, once we have looped through all the shapes and placeholders and performed our replacements, we are ready to package up the presentation and ship it back to the browser.  This is a fairly straightforward procedure, since the presentation object comes with a write(OutputStream os) method.  We will simply pass the servlet response object’s output stream to this write method, set the content type appropriately, and then flush the response to the user.

ServletOutputStream out = response.getOutputStream();
 
response.setContentType("application/vnd.ms-powerpoint");
response.addHeader("Content-Disposition", 
    "attachment; filename=template_with_charts.ppt");
 
pres.write(out);
templateFileIS.close();
response.flushBuffer();

And we’re done! View the complete solution.

By Joel Barciauskas