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.
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); //… “pres” 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:
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