Tutorial

PPT reports

Table of contents
  1. cplace Report Generation Basics
  2. Writing simple substitutions
  3. Slide based reporting
  4. Report Components
  5. Working with Tables
    1. Table Basics
    2. Tables with Dynamic Length
    3. Table Headers
    4. Multiple Template Rows
    5. Styling Table Cells
    6. Images in Table Cells
  6. Cloning Shapes
  7. Ordering (Layering) of Components
    1. Implicit Ordering of Shapes
    2. Explicit Ordering of Shapes
  8. Numbering Slides
  9. Creating charts
    1. Unsupported chart types
    2. Chart example

cplace Report Generation Basics

Generating a PowerPoint report in cplace requires two ingredients:

  • a PowerPoint file containing template slide(s). These slides contain shapes (like text boxes, tables, images) which are marked with a placeholder
  • a cplaceJS script which takes care of defining the actual values for these placeholder shapes. cplace provides an API which allows the script to specify
    • which slides from the template should be put into the final report
    • text values/image data to insert into the template
    • data for chart shapes in the template
    • rows of data for building tables

Report generation will first execute the script to collect the data which will then - after script execution has finished - be used to replace the placeholders in the PowerPoint template file. In order for that to work as intended, you have to make sure that the placeholders specified in the template actually match the placeholders used in the script.

Some notes about placeholders:

  • in the PowerPoint template the placeholders must be enclosed by curly braces so that cplace will recognize them, i.e. they must be written as {placeholder}
    • in some cases (like text shapes or table cells), the placeholder can directly be written as text in the shape/table cell
    • Note: In order to apply styling to a component this string must be connected to the shape. This is done in the description of the alternative text for the shape (Format shape -> Alternative Text -> Description)
  • In the script, the placeholders are referenced without the curly braces, i.e. the code will contain calls like report.put('placeholder', ...);

This tutorial will begin with simple examples and gradually explain how to create more complex reports.

Writing Simple Substitutions

The simplest way of placing data in a report template is to just put it there globally. For this use the report.put() method with where you want to put what data. Lets say we have an opening slide with just two text fields {title} and {subtitle} where we want to put the report title and some descriptive subtitle (obviously). The first lines of the script then will probably look like this:

report.put('title', 'Office reporting tutorial');
report.put('subtitle', 'The definitive guide to mastering reports');

Using put on the (global) report level will search the whole template for the given placeholder and replace it. This will replace the placeholder on all slides and will keep the structure of the template as is, i.e. every slide will be contained in the result, no matter if data has been put or not.

Slide Based Reporting

If global replacement does not fit your requirements you can also use slide based reporting. By calling report.addSlide(n) you will get a copy of slide number n in the template which will then be added to the report. You can then put data to that slide as you can do with the report object.

Let's keep the previous example but just solve the situation by using the slide based reporting:

var slide = report.addSlide(1);
slide.put('title', 'Office reporting tutorial');
slide.put('subtitle', 'The definitive guide to mastering reports');

This will add the first slide of the template to the generated report and put the data as defined. Note that you will have to stick to that method if you use it. The generated report will only consist of those slides which where added like this, no matter how many slides the template has. This way you can also reorder and repeat slides as you need, just add them using the report.addSlide(<slide nr>); method.

Also, please note that the slide number is not zero-based as arrays are for example - 1 will get you slide 1, 2 slide 2 and so forth. You can also repeat slides for each page you process.

So a more complex report with conditional reordering and slide repetition could look like this:

var titleSlide = report.addSlide(1);
titleSlide.put(...);

cplace.each(pages, function(page) {
  if (page.get('condition')){
      var slide = report.addSlide(2);
      slide.put(...);
  } else {
      var altSlide = report.addSlide(4);
      altSlide.put(...);
  }
  var pageSlide = report.addSlide(3);
  pageSlide.put('pageName', page.getName());
  // ...
});

Report Components

So far, this tutorial has only explained the most basic replacement mechanism, namely putting in text replacements by simply providing the replacement string:

report.put('placeholder', 'This will show up in the textbox marked with {placeholder} in the template');

However, in real life reports, we will also want to work with other shapes like images, tables or charts, which require additional configuration. To allow this, the cplaceJS reporting API provides the concept of different types of "Components" - each component type provides the necessary functions to configure the data needed for rendering the associated PowerPoint shape(s) in the report.

In the cplaceJS API for reporting, there is a central class Components which provides the necessary functions for creating these component objects.

Here is a quick overview of the reporting component types cplace provides:

Component type Method to create it Associated PowerPoint shapes
TextComponent Components.createText() (any shape having a) Textbox
BooleanComponent Components.createBoolean() (any shape having a) Textbox
DateComponent Components.createDate() (any shape having a) Textbox
NumberComponent Components.createNumber() (any shape having a) Textbox
ImageComponent Components.createImage() Images
ChartComponent Components.createChart() Charts
TableComponent Components.createTable() Tables

In addition to these components, the Components class also provides the function Components.createCellFormat() which can be used to apply specific styles to cells within a table.

In general, you will use report.put('placeholder', myComponent); for replacing a shape in the PowerPoint template. The simple report.put('placeholder', 'Text'); example we showed before is in reality just a convenient shortcut for the much more verbose

var textComponent = Components.createText().withText('Text');
report.put('placeholder', textComponent);

Working with Tables

A common use case handled in PowerPoint reports is to gather a set of pages (like projects or milestones) and display their attributes (e.g. name, responsible persons, status, dates) in tables.

Working with tables in cplaceJS reporting is accomplished by creating a TableComponent in the report script and adding the desired number of data rows (TableRows) to this component. Each TableRow in turn can be filled with text and/or image replacements containing the specific data for that row. To add a new row of data to a table component, simply call the method addRow() on it. This will return a new TableRow which you can fill with data specific to this row.
This collection of data then is applied to a table shape in the PowerPoint template file, taking the template table and copying the template data row(s) before filling them with the data your script provides.

Table Basics

This chapter explains how to create the most basic tables in a report. We will develop a report script which performs a search for specific pages and iterates over the resulting pages to fill a table.

Step 1: Defining the Report Template

For this example, we need a PowerPoint file with one slide. This slide contains a table shape with the description (Format shape -> Alternative Text -> Description) set to {basic_table}. The table should be constructed with the following layout:

{project_name} {project_description} {project_status}
 
 
 

So the template table has 3 columns and 4 rows - only the first row contains any data. This first row is taken as template for creating the rows filled with the data your script provides. In our example, one row of data will contain three replacements: The project name, a project description and a project status.

Step 2: Creating a Test Data Type and Test Pages

To actually play around with this and create a report we should create a test type which contains the data we want to put into the report. For the time being, just create a test type with internal name cf.cplace.tutorial.project which contains the two mandatory attributes cf.cplace.tutorial.project.description (text) and cf.cplace.tutorial.project.status (number). The latter attribute will be used to store a percentage value of project completion - so if you feel like it you might want to add a cplaceJS validator to make sure only values between 0 and 100 are actually entered. Then create two or three test pages of that type, so that the report script will actually find some data.

Step 3: Writing the Report Script

The script to find all pages (of our desired type within the space) and put their data into the table rows could look like this:

// Report with tables - version 1 
var slide = report.addSlide(1);   // Take the one slide from the template and add it to the report

// Search for all pages of our test type 
var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filtes.type('cf.cplace.tutorial.project'));
var projectPages = search.findAllPages();

// Create the table component
var table = Components.createTable();

// For each found page: Add a row of data to the table component and put the replacements 
// for the placeholders into that table row. 
cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  tableRow.put('project_status', projectPage.get('cf.cplace.tutorial.project.status'));
});

// Finally put the finished table component into the slide, using the correct placeholder of the table 
slide.put('basic_table', table);

Now you can create a report configuration with the given template file and script and generate the report. The result should be a PowerPoint file containing one slide with a table. The table should contain the page names as well as the values of description and status attributes of the test pages you created.

Note: If there is no data in the table or missing values, please check if you have any typos in the type or attribute names or in the placeholder names. Make sure that you have really created some test pages of type cf.cplace.tutorial.project.

Tables with Dynamic Length

With the previous script you were able to create a report containing some data in a table.

However, when you take a closer look at the generated report, you will notice that the table in your report will always contain the exact same number of rows as the table in your template. No matter how many test pages you created, there will always be 4 rows (if you defined the template as described above). This means that there will be either empty rows (when you have fewer than 4 test pages) or some test page data will not appear in the report (when you have more than 4 test pages).

This, of course, is not ideal - you want to have all data found in your search to be also visible in the report. So how can you achieve this?

Of course you could go ahead and try to change your script, so that it simply adds a new copy of the template slide for every 4 rows of data. While this would accomplish the goal of having all data in the report, it has a couple of downsides: First of all, it really complicates the script code. Second, it does not solve the problem of having empty rows on the last slide (when the number of data rows is not divisible by 4). And finally, it makes changes in your layout unnecessarily hard to implement: What if you later on decide that you want to have 6 table rows per slide instead of 4? You would have to change both the table in the template and the script, so that you add 6 data rows per slide.

So this clearly is not the solution you want.

Instead, cplaceJS reporting API provides the feature of growing tables. A growing table in this context means, that your report script can add an arbitrary number of data rows to the table component. During report generation, cplace will then automatically split the table onto multiple slides when the number of data rows is too large for one slide. Cplace will also take care of getting rid of empty rows when there are fewer data rows than template rows for the last slide of the table.

There are a couple of cplaceJS API methods you can use for controlling the behavior of growing tables. They are all part of the TableComponent object.

Method Description
allowGrowth() Allows table to overflow to another slide. Use this method if you want a simple growing table with default growing behavior (see below)
withMaximumHeightPerSlide(number height) This method implicitly activates table growth, but in addition sets the maximum height (in pts) of the resulting table. A new slide will automatically be created when adding the data row would exceed the maximum table height.
withMaximumDataRowsPerSlide(number rows) This method implicitly activates table growth. In contrast to the method above you do not specify a specific maximum height for the table, but you specify how many data rows you want to display on each slide (at least 1). So table.withMaximumDataRowsPerSlide(3) will configure the table component so that a new slide will be started after each third data row.

In the table above, the default growing behavior was mentioned - the behavior that is used, when you simply call allowGrowth() on the table component. This simply calculates the height of the table from the template slide and uses it as input for withMaximumHeightPerSlide(height). So the table will grow to a new slide as soon as the new data row would cause the table to exceed the height of the original template table.

So when should you use which method?

Method Use case
allowGrowth() Use this when you want to control the height of the table visually, i.e. by changing the table in the template.
withMaximumHeightPerSlide(number height) Use this when you want to make sure the table does not exceed a specific - programmatically controlled - height. I.e. the actual height is more important than the number of rows per slide.
withMaximumDataRowsPerSlide(number rows) Use this when you want to be sure to have a consistent number of data rows on each slide, even if (e.g. by linebreaks) the actual height of the table will not be the same on each slide.

To finish off this part about growing tables, here is an improved version of our report script which will handle an arbitrary number of data rows by always starting a new slide after 5 rows - there is just one changed line when creating the table component:

// Report with tables - version 2: Growing table
var slide = report.addSlide(1);   // Take the one slide from the template and add it to the report

// Search for all pages of our test type 
var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filtes.type('cf.cplace.tutorial.project'));
var projectPages = search.findAllPages();

// Create the table component
var table = Components.createTable()
  .withMaximumDataRowsPerSlide(5);   // Growing table with max. 5 rows per slide

// For each found page: Add a row of data to the table component and put the replacements 
// for the placeholders into that table row. 
cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  tableRow.put('project_status', projectPage.get('cf.cplace.tutorial.project.status'));
});

// Finally put the finished table component into the slide, using the correct placeholder of the table 
slide.put('basic_table', table);

Note: Please remember that adding long text replacements may lead to increasing cell height due to automatic line breaks. This might cause unintended slide and table layout and even cause the table to be longer than the whole slide. So when replacing placeholders with texts of unknown length, please make sure to either truncate it or adjust the cell's font size so that this will not happen.

Table Headers

By now we can handle tables with either fixed number of rows (default) or arbitrary number of rows (growing tables, see previous chapter). However, somehow our tables seem to be missing something important to actually make sense of the data which they contain: a nice table header.

Static Header Row

Step 1 in adding a header to our table is to actually define the header in the template table. So change the table in the PowerPoint template to look like this:

Project name Description Status
{project_name} {project_description} {project_status}
 
 
 

Now your template table contains one additional row for the column headers, making the meaning of these columns clearer.

However, we also need to change our script a little, as by default cplaceJS reporting will consider the first row of the template table the template row which will be copied for each data row added to the table component. So without changing the sript to configure our table component correctly, we would end up with a table containing just multiple copies of the header row.

The method to use for telling the table component that there is a header is withHeader(). This will configure the table component in a way that it will take the 1st row of the template table and leave it unchanged. For each data row added to the table component, it will copy the 2nd row (instead of the 1st) and fill in the replacements. For growing tables, the header row is repeated on each slide.

Our updated example script from the previous chapter now looks like this:

// Report with tables - version 3: Adding a header 
var slide = report.addSlide(1);

var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filters.customAttributeNonempty('cf.cplace.tutorial.project.status'));
search.add(Filters.type('cf.cplace.tutorial.project'));

var projectPages = search.findAllPages();

var table = Components.createTable()
    .withHeader()                      // Template table has header row
    .withMaximumDataRowsPerSlide(5);   // Growing table with max. 5 rows per slide

cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  tableRow.put('project_status', projectPage.get('cf.cplace.tutorial.project.status'));
});

slide.put('basic_table', table);

Note: The withMaximumDataRowsPerSlide(5) always refers to the number of rows added by addRow(), the header row does not feature in this count.

Header Row with Placeholders

So the table now has a header row, but the values in that header row are static. They will always say Project name, Description and Status as they do in the template. This will be sufficient for many use cases, but certainly not for all. There might be a requirement to have e.g. localized reports, e.g. the option to generate the same report both in
in English and in German.

Instead of having to duplicate and translate the template (and adapt both versions of the template as the report evolves over time), it will be easier to also have placeholders in the header row - similar to the the template row.

Adjust our template file so that the table looks like this:

{header_col1} {header_col2} {header_col3}
{project_name} {project_description} {project_status}
 
 
 

The header row now contains placeholders, but how can we fill them?

For this, cplaceJS reporting API provides the withHeaderRow() method on the table component. Use this instead of withHeader() when you want to substitute placeholders in the header row. The return value of this method is a TableRow, same as in addRow(). You can use this TableRowobject to specify the replacement values for the placeholdes in the header row.

Note: You can call withHeaderRow() multiple times on the same table component, but it will always the same TableRow object.

So in our example report, we will simply remove the call to withHeader() and instead add a small section which fills the header row (currently hardcoded with the same values we had in the previous static header, but one can imagine to substitute different values here based on some condition):

// Report with tables - version 4: Dynamic header 
var slide = report.addSlide(1);

var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filters.customAttributeNonempty('cf.cplace.tutorial.project.status'));
search.add(Filters.type('cf.cplace.tutorial.project'));

var projectPages = search.findAllPages();

var table = Components.createTable()
    .withMaximumDataRowsPerSlide(5);    // Growing table with max. 5 rows per slide
    
table.withHeaderRow()
    .put('header_col1', 'Project name')
    .put('header_col2', 'Description')
    .put('header_col3', 'Status');

cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  tableRow.put('project_status', projectPage.get('cf.cplace.tutorial.project.status'));
});

slide.put('basic_table', table);

Multiple Template Rows

By default a table has one template row (either the first row of the template, when there is no table header, or the second row, when there is a header). This template row is copied and filled for each data row added via addRow(). For many use cases this will be sufficient, however, sometimes the data you want to display in the table will be more visually appealing when spread over multiple rows. So it should be possible to design the template table and configure the table component so that multiple template rows will be copied for each data row.

To facilitate this, it is possible to configure the number of template rows to be used.

In our example report, we will change the structure of our table as follows, changing our 3-column layout to 2 columns:

{header_col1} {header_col2} / {header_col3}
{project_name} {project_description}
  {project_status}
 
 

Note: As you can see, we now have two placeholders in the header of column 2, separated by hardcoded text. It is perfectly valid to use multiple placeholders in one table cell, each will be replaced by the values you provide in the script.

To tell the table component, that there is more than one template row, cplaceJS API provides the method withNumberOfTemplateRows(number rows) on the table component. In our case we have two template rows, so will call table.withNumberOfTemplateRows(2);. As you can see, in the script below, this call is the only change in the script.

// Report with tables - version 5: Two template rows 
var slide = report.addSlide(1);

var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filters.customAttributeNonempty('cf.cplace.tutorial.project.status'));
search.add(Filters.type('cf.cplace.tutorial.project'));

var projectPages = search.findAllPages();

var table = Components.createTable()
    .withMaximumDataRowsPerSlide(5)     // Growing table with max. 5 rows per slide
    .withNumberOfTemplateRows(2);       // Two template rows per added data row

table.withHeaderRow()
    .put('header_col1', 'Project name')
    .put('header_col2', 'Description')
    .put('header_col3', 'Status')

cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  tableRow.put('project_status', projectPage.get('cf.cplace.tutorial.project.status'));
});

slide.put('basic_table', table);

Note: It is possible to use multiple template rows, however, the number of header rows is fixed to one (or zero if you don't want any header).

Styling Table Cells

By default, cplace will not change any style settings (like font family, size, color, background color) of the copied table cells, i.e. these settings will be taken directly from the template table. In case you want to change some of these styles dynamically in your script, cplaceJS API provides the functionality for this as well:

When adding components to a table row via row.put(<placeholder>,<component>) you can optionally provide a third parameter, a CellFormatComponent. This CellFormatComponent can contain formatting information for the cell which overrides the template cell's settings. We will not discuss the various possible settings in detail in this tutorial - please refer to the API documentation of CellFormatComponent and CellStyle for that. Instead this tutorial will describe a few ways to define style information for a table.

Defining Styles: Javascript Notation vs. Fluent API

There are two ways to define table cell styles: You can either use a fluent API to set specific style aspects or you can define the style settings as Javascript object. This Javascript object can then either be passed to the CellFormatComponent.withStyle(<jsStyle>) method (if you want to tweak some more aspects via fluent API) or you can use is directly for setting the cell style. The following example script will define two (identical) styles using these different approaches:

var table = ...   // Define table as seen in previous examples
// Method 1: define JS object and pass it to CellFormatComponent.withStyle() 
var jsStyle = {
   "color": [0, 255, 0],
   "font": {
               "family": "whatever",
               "size": 10,
               "color": "#ff0000",
               "style": "italic, underline, strikeThrough"
           }
};
// Optional: create a CellFormatComponent from that jsStyle and further tweak it
//  jsStyle = Components.createCellFormat().withStyle(jsStyle);
//  jsStyle.withFillType...

// Method 2: Fluent API
var fluentApiStyle = Components.createCellFormat()
    .withBackgroundColor("#00ff00")
    .withFont('Arial', 10, '#ff0000', false, true, true, true);

// Use either of these to style a cell
table.addRow().put('myCellPlaceholder', 'Text in this cell will be 10pt Arial in red on green background', jsStyle); 

Important Note: When multiple placeholders exist in the same table cell, and different cell styles are specified for those placeholders, it is not defined which of those cell styles is used in the resulting cell. So please make sure to either always provide the same style for all placeholders in one cell or (better) only provide the style for one of these placeholders. This only refers to the style attributes which affect the entire cell (like background color, font etc.), style attributes which only affect one component (like image component scaling or positioning) can and should be used on each component individually.

Default Row Style

In case you want to style all cells of a row in a certain way via script you can achieve this by calling withStyle(<style>) method directly on the table row. This method again takes either a Javascript object or a CellFormatComponent. Of course it is still possible to override this row default style for specific cells by just providing a different style in the TableRow.put(...) method.

var table = ...  // Define table as seen in previous examples

// Default style for all cells in a row
var defaultRowStyle = {
   "color": [0, 255, 0]
};

var cellSpecificStyle = {
   "color": [128, 128, 128]
};

// Create row with default cell style
var row = table.addRow().withStyle(defaultRowStyle);
// Put your components into the row
row.put('cell1', 'Cell1 uses default row style');
row.put('cell2', 'Cell2 uses default row style');
row.put('cell3', 'Cell3 uses a specific cell style override', cellSpecificStyle);

Images in Table Cells

Until now our example only used simple strings for text replacement. However, it is also possible to substitute the placeholders in table cells with images. To test this, please attach two simple images to your report configuration page (green.png (a green small square) and red.png (a green red square)), we will display the red image for all projects with a status value which is less than 30, the green image for all other projects.

The code for creating the image components from image files attached to the page looks like this:

var imageComponent = Components.createImage().withImage(config.getImage(<file name>));
...
row.put(<placeholder>, imageComponent, <optional style>);

Please refer to the API documentation of ImageComponent for other methods of configuring image components (like using enum icons etc.).

Unless specified otherwise, the image will be placed centered in the table cell and resized so that it will touch either the vertical or horizontal cell borders (aspect ratio will be maintained). This behavior can be overridden by defining a cell style with image scaling and/or alignment options, e.g.:

var imageComponent = Components.createImage().withImage(config.getImage(<file name>));
var imageStyle = Components.createCellFormat()
       .withImageScaling(0.5, 1)
       .withImageAlignment("Left", "Center");
...
row.put(<placeholder>, imageComponent, imageStyle);

The above script will scale the image to take up half the cell's width and the full cell height and it will be aligned to the left side of the cell. Please refer to the API documentation of CellFormatComponent for the complete image alignment and scaling API.

Here is the complete example script which will display a green or red bar stretched to half the height of the cell with its horizontal scaling dependent on the value of the cf.cplace.tutorial.project.status custom attribute - a value 100(%) means the bar fills the entire cell horizontally:

// Report with tables - version 6: Image in status cell
var greenImage = Components.createImage().withImage(config.getImage('green.png'));
var redImage = Components.createImage().withImage(config.getImage('red.png'));

var slide = report.addSlide(1);

var search = new Search();
search.add(Filters.embeddingSpace());
search.add(Filters.customAttributeNonempty('cf.cplace.tutorial.project.status'));
search.add(Filters.type('cf.cplace.tutorial.project'));

var projectPages = search.findAllPages();

var table = Components.createTable()
    .withMaximumDataRowsPerSlide(5)     // Growing table with max. 5 rows per slide
    .withNumberOfTemplateRows(2);       // Two template rows per added data row

table.withHeaderRow()
    .put('header_col1', 'Project name')
    .put('header_col2', 'Description')
    .put('header_col3', 'Status');

cplace.each(projectPages, function(projectPage) {
  var tableRow = table.addRow();
  tableRow.put('project_name', projectPage.getName());
  tableRow.put('project_description', projectPage.get('cf.cplace.tutorial.project.description'));
  var status = projectPage.get('cf.cplace.tutorial.project.status');
  status = status > 100 ? 100 : status;  // Make sure the value is between 0 and 100
  status = status < 0 ? 0 : status;
  var imageStyle = Components.createCellFormat()
       .withImageScaling((status / 100.0), 0.5)
       .withImageAlignment("Left", "Center");

  if (status < 30) {
    tableRow.put('project_status', redImage, imageStyle);
  } else {
    tableRow.put('project_status', greenImage, imageStyle);
  }
});

slide.put('basic_table', table);

Cloning Shapes

Until now our report scripts just performed 1 to 1 replacements, i.e. we replaced each shape of the template by creating one cplaceJS report component and connecting that component to the original shape via its placeholder string.

Sometimes, however, it is useful to be able to add an arbitrary number of shapes. For example if you have a list of project milestones with associated dates and want to display one icon for each milestone in a timeline to visualize project progress. For these use cases, the feature of shape cloning was implemented in cplace reporting API.

Shape cloning means that you take an existing shape of the template (along with its properties like position, color, image etc.) and put a copy (clone) of it (with adapted position and possible adapted color, size, image etc.) into the report.

In our example use case (one icon per project milestone) you could define your template to have one image shape for the first milestone on the left side of the timeline (this assumes that each project has at least one milestone). Your report script would then iterate over all milestones of your project, calculate a useful position (e.g. based on milestone date) and put a clone of that original shape in this position into the report slide.

The basic code logic for achieving this looks like this:

// Calculate offset for current date
function calculateOffset(firstDate, lastDate, maxOffset, currentDate) {
  var maxInterval = lastDate.getTime() - firstDate.getTime();
  var interval = currentDate.getTime() - firstDate.getTime();
  var offset = (1.0*interval / maxInterval) * maxOffset;
  return offset;  
}

// Main report script
var slide = report.addSlide(1);
var placeholderName = 'milestoneShape';  // Placeholder name of the original shape in template
var milestoneDates = [...];   // Get array of milestone Date objects from somewhere

var milestone1 = milestoneDates[0]
var lastMilestone = milestoneDates[milestoneDates.length - 1]
var maxOffset = 600; // Spread milestones over max. 600pts

for (int index = 1; index < milestoneDates.length; index++) {
  // For each milestone date: Calculate horizontal offset... 
  var xOffset = calculateOffset(milestone1, lastMilestone, maxOffset, milestoneDates[index]);
  // ...and create s clone of the original shape, moved horizontally by that offset 
  var clone = Components.createShape()
      .moveX(xOffset);
  slide.putClone(placeholderName, clone);
}

The slide.putClone(...) method will take the placeholder name of the original shape and a report component (in our example a simple shape which is moved horizontally) by the calculated offset. The result of this will be a clone of the original shape at the new location.

If you want to have e.g. different images for each milestone (instead of exact clone of template shape), you can achieve this by creating an image shape with your desired image instead of creating just a generic shape, e.g.:

...
var clone = Components.createImage()
      .withImage(config.getImage('myMilestone.png'))
      .moveX(xOffset);
slide.putClone(placeholderName, clone);
...

Ordering (Layering) of Components

When you add overlapping shapes (e.g. multiple images) it is important that we can define which image should be displayed in front and which in the back. Layering (z-Ordering) of shapes can be controlled in two ways:

Implicit Ordering of Shapes

Components (text, images, cloned shapes) are automatically z-ordered in the order in which they are added to the report (or to the slide). While adding the component, it is automatically assigned a generated z-Index value which is used in later processing to reorder the generated shapes of the presentation.

The following rules apply for shape ordering:

  • Global substitutions (i.e. report.put(...) are always in front of slide-specific substitutions (slide.put(...))
  • In each of these two groups (global vs. slide specific substitutions): Components which are added later in the script are positioned in front of the components added earlier in the script
  • Images in tables are always in front of the table and all other components

This implicit ordering of shapes is the preferred way of layering shapes as it behaves kind of naturally without much effort on your part. Simply imagine your script as pasting one shape after the other onto your presentation.

Explicit Ordering of Shapes

If for some reason you want to explicitely set z-index values for single components, you can do so by calling .withZIndex(int). This method exists in the following components: image, shape, text, table.

Example (putting cloned shapes behind original and behind each previous clone):

var slide2 = ...

// Add 4 clones of "image_shape", each one slightly to right and 
// down of the previous, but behind it!

for (var i = 1; i <= 4; i++) {

    var moveBy = 30;
    
    shape = Components.createShape()
        .moveX( i * moveBy )
        .moveY( i * moveBy )
        .withZIndex(-i);     // Increasingly negative z-index to put behind previous

    slide2.putClone("image_shape", shape);
}

However, for most use cases, the implicit ordering will be preferrable as it has less code to maintain and works quite intuitively.

Numbering Slides

For more complex report use cases you will often not know beforehand how many slides the resulting report will contain. This is especially true when you make use of growing tables which will span multiple slides.

To allow having slide numbers on each slide of the report, cplace provides two special placeholders:

  • #CURRENT_SLIDE_NR#: Will be replaced with the number of the current slide
  • #TOTAL_SLIDE_NR#: Will be replaced with the total number of slides in the report

Simply place the above string(s) anywhere in a textbox on your template slides (note: no curly braces this time, just the strings). There is no need to add anything to your cplaceJS report script, this replacement will occur automatically.

Example:

Put a textbox on each template slide which contains this text: Slide #CURRENT_SLIDE_NR# of #TOTAL_SLIDE_NR#. This will result in a report where each slide contains text like Slide 1 of 5, Slide 2 of 5, etc.

Creating charts

In PPT reporting it is also possible to populate charts with data using the ChartComponent. Chart data consists of two major parts which are firmly interconnected and dependant on each other's structure:

  • Categories represent data points that hold information about a cross section of data of the series. Categories appear in the order they are added. At least one category is mandatory.
  • Series hold data for each category. They are mandatory and their length has to match the number of categories added to the component. Also the number of series must not deviate from that declared in the template.

Unsupported chart types

There are also some restrictions to what kind of chart can be used. As there are a lot of different chart types this is best tested for each use case. The engine will throw an error if a chart cannot be supported. Some examples for this are pie chart, mixed chart (having e.g. a line, bars and an area) and bubble chart. If your report doesn't require one of these or another, specialized chart your chances of success are reasonably high.

Chart example

Let's say we want to visualize and compare costs of different component parts of products for a short example:
We could use a stacked bar chart for this which accumulates the costs of all the components of a product so we can compare the overall costs to the component's costs and to other products.

As we want to stack the different cost groups, we create a series for each of those groups. For simplicity lets say we have baseParts and additionalParts. Now we want to create a separate category for each product, so they are rendered next to each other and are somewhat comparable.

A script could look like this:

var chart = Components.createChart();
// series [base parts, additional parts, specialized parts]
var products = [ /* array of product pages */];
// structure used to gather series data
var series = {
    baseParts: [],
    additionalParts: []
};

for(var i = 0; i < products.length; i++) {
    var product = products[i];
    // create a category for each product
    chart.addCategory(product.getName());
    // add product data to different series
    series.baseParts.push(product.get('baseCost'));
    series.additionalParts.push(product.get('additionalCost'));
}
// add series to the chart using the built helper object
chart.addSeries('baseParts', series.baseParts);
chart.addSeries('additionalParts', series.additionalParts);

report.put('costChart', chart);

The resulting data structure will be associated like this

category:          'P01' 'P02' 'P03' ...
                     ↓     ↓     ↓
baseParts:       [  b01,  b02,  b03, ...]
                     ↓     ↓     ↓
additionalParts: [  a01,  a02,  a03, ...]

so the category created at first will be linked to the first entry of each series, the second category to the second entry and so forth. In a stacked bar chart this will create a bar for each category consisting of the corresponding series values.