Creating Your Own Chart Types

Topics:

WebFOCUS BUE supports the ability to add new, custom chart types to its list of built-in charts. These custom chart types are called extensions or plug-ins. An extension is a block of code that accesses resources external to WebFOCUS BUE. This topic describes the structure of an extension and the steps necessary to create your own and add it to the chart library.

x
Introducing Chart Extensions

Chart extensions are written in JavaScript. The visual part of a visualization can be drawn with HTML, Canvas, or SVG. Extensions can include external CSS and JS libraries (such as d3), which can be used to build almost any visualization. The WebFOCUS Extension API is limited to new, complete chart types only. It is not possible to add features to existing chart types, and it is not possible to modify or extend parts of WebFOCUS BUE outside of the chart area allocated to your extension.

This topic summarizes the process of writing, configuring, and installing a chart extension. Detailed instructions can be found on the Information Builders GitHub site:

https://github.com/ibi/wf-extensions-chart

WebFOCUS BUE extensions must be placed in the extensions folder under the web_resource folder of your WebFOCUS BUE installation. By default, this is the following location:

c:\ibi\install_dir\config\web_resource\extensions

where:

install_dir

Is your WebFOCUS BUE installation directory.

Several sample chart extensions have already been installed in the extensions folder so that you can see their code, their structure, and how they are accessed in the WebFOCUS BUE tools.

Note: The user installing the extension must know how to write JavaScript code for what the chart extension needs to generate. The GitHub site documents how to make the extension conform to the WebFOCUS API and how to install the extension in the WebFOCUS BUE chart library. It does not describe how to write JavaScript code.

x
Creating a Chart Extension

Reference:

This section summarizes the build cycle for creating an extension and the structure and components of an extension.

x
Reference: Build Cycle for Writing an Extension

Creating an extension often involves cycles of writing, running, and then debugging code.

When you make changes to the properties.js file for your extension, you need to clear the WebFOCUS BUE cache in order for those changes to be recognized. Clear the cache using the Clear cache link in the Administration Console.

If you change the .js code for your extension (for example, com.ibi.simple_bar.js), you do not need to make any changes to WebFOCUS BUE. You only need to clear your own browser cache, to ensure that the new JavaScript file is downloaded. The same is true if you change any additional .js files included by your extension.

x
Reference: Extension Structure

The Simple Bar extension example demonstrates the required and optional files in an extension, and how those files are typically laid out.

You can open com.ibi.simple_bar and com.ibi.simple_bar.js in a text editor to see exactly how an extension is written.

The extension ID (ext_id) is a string in the form com.your_company.extension_name. The ext_id must be all lowercase, and can include only letters, numbers, underscores and dots. The entire extension lives in a folder named ext_id. The core of the extension lives in a file named ext_id.js. This file includes code to render the extension as a new chart type within WebFOCUS BUE.

The properties.json file configures your extension to run in WebFOCUS BUE. This file includes all the metadata needed to include your extension in the WebFOCUS BUE user interface, as well as a list of all properties you wish to expose to end users, so they can customize the behavior of your extension.

The extension folder can also include optional additional folders for external css and lib resources. If your extension uses any additional CSS or JavaScript library files, you can keep those resources organized in dedicated folders, such as css and lib, as you choose. External resources are configured and loaded inside the base ext_id.js file of your extension.

x
Using the Chart Extension API

Topics:

To see examples of everything that the chart extension API provides, look at com.ibi.simple_bar.js. It is divided into two main parts, chart rendering and extension configuration.

x
Rendering Charts

The extension API provides three entry points that you can use as needed by defining your own JavaScript callback functions. They are passed a set of properties in a config object. Some properties are available during the entire rendering process, and some are only available during render callback.

x
Reference: Chart Rendering Callback Functions

You can define the following three JavaScript callback functions. Only the renderCallback function is always required.

  • initCallback(successCallback, config) This optional function is invoked by the engine exactly once during library load time, providing a way to implement document.onload initialization code. This function is passed a successCallback, which you must invoke with true if your initialization code succeeded or false if was not successful. If you call successCallback(false), no further interaction with your extension will occur, and your extension will render as an empty page.
  • preRenderCallback(config) This optional function is invoked each time your extension is to be rendered, as the very first step in the overall rendering process. This is a good place to examine and tweak or override any internal chart properties that will affect the subsequent rendering.
  • renderCallback(config) This required function must contain all of the code that will actually draw your chart. The config object will contain the properties described in the following sections.

Each of the three entry point callbacks is passed a config object, which contains a set of useful properties.



Example: Sample renderCallback Function

The following sample renderCallback code renders the Simple Bar extension.

function renderCallback(renderConfig) {
  var chart = renderConfig.moonbeamInstance;
  var props = renderConfig.properties;
  var container = d3.select(renderConfig.container)
   .attr('class', 'com_ibi_chart');
  var data = renderConfig.data;
  if (renderConfig.dataBuckets.depth === 1) {
   data = [data];
  }
   
  var seriesCount = data[0].length;
  var seriesLabels = data[0].map(function(el){return el.labels;});
  data = d3.transpose(data).map(function(el, idx) {
   el = el[0];
   var v = Array.isArray(el.value) ? el.value : [el.value];
   var y0 = 0;
   return v.map(function(d, s) {
    return chart.mergeObjects(d, {y0: y0, y1: y0 += d, seriesID: s, value: d, labels: seriesLabels[idx]});
   });
  });
  
  var w = renderConfig.width;
  var h = renderConfig.height;
  var x = d3.scale.ordinal().domain(pv.range(seriesCount)).rangeRoundBands([0, w], 0.2);
  var ymax = d3.max([].concat.apply([], data), function(d){return d.y1;});
  var y = d3.scale.linear().domain([0, ymax]).range([25, h]);
  var svg = container.selectAll("g")
   .data(data)
   .enter().append('g')
   .attr('transform', function(d, i){return 'translate(' + x(i) + ', 0)';});
   
  svg.selectAll("rect")
   .data(function(d){return d;})
   .enter().append('rect')
   .attr("width", x.rangeBand())
   .attr("y", function(d) {return h - y(d.y1);})
   .attr("height", function(d){return y(d.y1) - y(d.y0);})
   .attr('tdgtitle', function(d, s, g) {
    // To support tooltips, each chart object that should draw a tooltip must 
    // set its 'tdgtitle' attribute to the tooltip's content string.
    
    // Retrieve the chart engine's user-defined tooltip content with getToolTipContent():
    // 's' and 'g' are the series and group IDs for the riser in question.
    // 'd' is this riser's individual datum, and seriesData is the array of data for this riser's series.
    var seriesData = chart.data[s];
    var tooltip = renderConfig.modules.tooltip.getToolTipContent(s, g, d, seriesData);
    // getToolTipContent() return values:
    //  - undefined: do not add any content to this riser's tooltip
    //  - the string 'auto': you must define some 'nice' automatic tooltip content for this riser
    //  - anything else: use this directly as the tooltip content
    if (tooltip === 'auto') {
     if (d.hasOwnProperty('color')) {
      return 'Bar Size: ' + d.value + '<br />Bar Color: ' + d.color;
     }
     return 'Bar Size: ' + d.value;
    }
    return tooltip;
   })
   .attr('class', function(d, s, g) {
    // To support data selection and tooltips, each riser must include a class name with the appropriate seriesID and groupID
    // Use chart.buildClassName to create an appropriate class name.
    // 1st argument must be 'riser', 2nd is seriesID, 3rd is groupID, 4th is an optional extra string which can be used to identify the risers in your extension.
    return chart.buildClassName('riser', s, g, 'bar');
   })
   .attr('fill', function(d) {
    // getSeriesAndGroupProperty(seriesID, groupID, property) is a handy function
    // to easily look up any series dependent property.  'property' can be in
    // dot notation (eg: 'marker.border.width').
    return chart.getSeriesAndGroupProperty(d.seriesID, null, 'color');
   });
   
  svg.append('text')
   .attr('transform', function(d) {return 'translate(' + (x.rangeBand() / 2) + ',' + (h - 5) + ')';})
   .text(function(d, i){return seriesLabels[i];})
   
  renderConfig.modules.tooltip.updateToolTips();  // Tell the chart engine your chart is ready for tooltips to be added
  renderConfig.modules.dataSelection.activateSelection();  // Tell the chart engine your chart is ready for data selection to be enabled
 }
x
Reference: Properties That Are Always Available

The following properties are always available.

Property Name

Description

moonbeamInstance

The chart instance currently being rendered.

data

The data set being rendered.

properties

The block of properties for your extension, as set by the user.

x
Reference: Properties Available Only During Render Callback

The following properties are available only during render callback, and are used by your chart rendering code (renderCallback).

Property Name

Description

width

Width of the container your extension renders into, in pixels.

height

Height of the container your extension renders into, in pixels.

containerIDPrefix

The ID of the DOM container your extension renders into. Prepend this to all IDs your extension generates, to ensure multiple copies of your extension work on one page.

container

DOM node for your extension to render into, either an HTML DIV element or an SVG G element, depending on your chosen containerType extension configuration

rootContainer

DOM node containing the specific chart engine instance being rendered.

x
Configuring Your Chart Extension

Extension configuration consists of two parts.

  • Chart Engine Configuration configures the extension to interact with the chart engine and chart canvas in WebFOCUS BUE. This part of the extension configuration is defined in the config object that is passed to the chart renderer functions.
  • Chart Interface Configuration interacts with the chart type picker in the user interface and the chart attribute categories. This part of the extension configuration is defined in the properties.json file.
x
Creating a config Object for Chart Engine Configuration

To configure your extension, create a config object with all the information unique to your extension, then register your extension with the extension API.

x
Reference: Creating a config Object for Your Extension

Required and optional properties in your config object are described in the following table.

Property Name

Description

id

Is the extension ID described in Extension Structure.

name

Is the name for the chart type to be displayed in the user interface.

description

Is a description for the chart type to be displayed in the user interface.

containerType

Is either 'html' or 'svg' (the default).

initCallback

Optional. References your initCallback function, described in Rendering Charts.

preRenderCallback

Optional. References your preRenderCallback function, described in Rendering Charts.

renderCallback

Required. References your renderCallback function, described in Rendering Charts.

resources

Optional. Are additional external resources (CSS and JS) required by this extension.



Example: Sample config Object

The following code is a sample of the config object used with the Simple Bar extension.

var config = {
    id: 'com.ibi.simple_bar',     // string that uniquely identifies this extension
    containerType: 'svg',  // either 'html' or 'svg' (default)
    initCallback: initCallback,  // Refers to your init callback fn (optional)
    preRenderCallback: preRenderCallback,  // Refers to your preRender callback fn (optional)
    renderCallback: renderCallback,  // Refers to your render fn (required)
    resources:  {  // Additional external resources (CSS & JS) required by this extension (optional)
        script: ['lib/d3.min.js'],
        css: ['css/extension.css']
    },
   }
x
Reference: Registering Your Extension

To register your extension with the WebFOCUS extension API, call:

tdgchart.extensionManager.register(config);
x
Reference: Tips for Building Your Extension

The easiest way to build your own extension is to clone the Simple Bar example, then tweak it. Assume the ID of the new extension is com.foo.bar:

  1. Rename root folder to com.foo.bar. Rename com.ibi.simple_bar.js to com.foo.bar.js.
  2. In com.foo.bar.js, delete the inner content of the three callback functions.
  3. In com.foo.bar.js, change the entries for each property in config to match the requirements of your extension.
  4. Add any external resources you need to lib and css, and load them by setting config.resources in com.foo.bar.js.
  5. Implement renderCallback in com.foo.bar.js to draw your extension.
x
Configuring the Chart Interface

Each extension must include a properties.json file, which defines the information needed by WebFOCUS BUE when drawing its user interface.

The properties.json file consists of the following blocks.

  • info. This block defines several general purpose configuration options.
  • properties. This block defines any properties of your extension that the end user may want to change. The user can change these properties in the GRAPH_JS blocks in a WebFOCUS BUE chart procedure.
  • propertyAnnotations. This block validates the content of the properties block. Everything in properties must appear in propertyAnnotations. The possible types of any non-object (leaf) property in properties must be notated as one of "str", "bool", or "number".
  • dataBuckets. This block defines the set of chart attribute categories that appear in the Query pane in the WebFOCUS BUE user interface when creating a chart. Each member in the dataBuckets collection is a bucket.

    There are two types of buckets, built-in and custom. Built-in buckets provide an easy way to reuse the existing WebFOCUS BUE data bucket logic. There are currently two built-in buckets, tooltip, and series_break. Use any of these buckets by setting the associated dataBuckets property to true.

  • bucket. Each bucket block defines one custom chart attribute category. Each custom bucket requires the following properties:
    • id. This property corresponds exactly to the dataArrayMap and data properties that will be received by the render function for your chart.
    • type. This property defines the type of data field this bucket accepts, "measure", "dimension", or "both".
    • count. Consists of count.min and count.max, which define the minimum and maximum number of fields this bucket can accept. A minimum of 0 means this bucket is optional.
  • translations. Defines translations in different languages for every label to be drawn in the WebFOCUS BUE interface. The translation object has one property for each language the extension supports, keyed by ISO-639 two letter locale strings.


Example: Sample properties.json File

The following properties.json file is from the Simple Bar extension.

{
    // Define some general extension configuration options
    "info": {
        "version": "1.0",  // version number of your extension.
        "implements_api_version": "1.0",  // version number of the WebFocus API used by your extension.
        "author": "Information Builders",
        "copyright": "Information Builders Inc.",
        "url": "https://github.com/ibi/wf-extensions-chart/tree/master/simple_bar%20example",
        "icons": {
            "medium": "icons/medium.png"  // Reference to an image in the extension, used in the WF chart picker
        }
    },
    // Define any properties of your extension that end user may want to change.  
    "properties": {
        "exampleProperty": 50
    },
    // Define the possible values for each property in 'properties'.
    "propertyAnnotations": {
        "exampleProperty": "number"
    },
    // Define the available data buckets drawn in WF's 'Query' data bucket tree.  
    "dataBuckets":  {
        // Choose whether or not to reuse existing WF data buckets.  All optional.
        "tooltip": false,
        "series_break": true,
        // Define your own custom data buckets.  Optional
        "buckets": [
            {
                "id": "value",
                "type": "measure",
                "count": {"min": 1, "max": 5}
            },
            {
                "id": "labels",
                "type": "dimension",
                "count": {"min": 1, "max": 5}
            }
        ]
    },
    // Define the set of labels used in the WF interface for buckets and chart type picker.
    "translations": {
        "en": {
            "name": "My Simple Bar Chart",
            "description": "This chart is just a simple bar chart, nothing to see here.",
            "icon_tooltip": "This extension does ...", 
            "value_name": "Value Bucket", 
            "value_tooltip": "Drop a measure here", 
            "labels_name": "Label Bucket", 
            "labels_tooltip": "Drop a dimension here"
        },
        "fr": {
            "name": "Un Bar Chart tres simple",
            "description": "C'est un Bar Chart vraiment simple",
            "icon_tooltip": "This extension does ...", 
            "value_name": "Value Bucket", 
            "value_tooltip": "Drop a measure here", 
            "labels_name": "Label Bucket", 
            "labels_tooltip": "Drop a dimension here"
        }
    }
}
x
Accessing Data for Your Extension

Each time an extension is rendered, the render callback for the extension is passed the current data set using the renderConfig.data argument. The overall structure of the data set is defined by the set of buckets listed in the properties.json file, while the specific content of the data is defined by the data fields the user has added to each bucket.

x
Defining and Using Buckets in an Extension

The data set is passed into an extension using the data property of the first argument of the render callback, typically named renderConfig. Additional information about the current set of fields in each bucket is in renderConfig.dataBuckets.

A data set is represented in JavaScript as arrays of objects. If an extension defines only custom buckets, the data set will be a flat array of objects. If an extension uses some built-in buckets, the data set may contain deeply nested arrays of arrays. The renderConfig.dataBuckets.depth property will be set to the number of array dimensions in the current data set.

Custom Buckets

Each innermost object within the arrays of data (called a datum) will have one property for each data bucket that contains a field. Each property will be the id of a custom bucket, as defined in the dataBuckets.buckets section of properties.json. The type of values of these properties depend on the bucket type. Dimension buckets have string values, while measure buckets have numeric values. If a bucket contains more than one field, the associated property for each innermost object will be an array of string or number values.

Built-in Buckets

An extension can use buckets that are built-in and predefined by WebFOCUS BUE. These buckets will affect more than just the data set. Each bucket will also set specific chart engine properties, to pass in additional information related to that bucket.

Each built in WebFOCUS BUE bucket is either a standard bucket or a break bucket.

  • Standard buckets behave exactly like custom buckets. The data set remains a single array, and each datum object will include an additional property named after the bucket.
  • Break buckets divide the data set into additional arrays of data. For each break bucket used, each datum object will be transformed into a full array of datum objects. The number of datum objects in each array will remain unchanged, but the number of arrays or datum arrays will correspond to the number of entries in the break field.

Types of Break Buckets

Break buckets can be of two types:

  • A series-break bucket breaks the data set into one array for each entry in the series break field chosen by the user. A series-break bucket uses series-dependent properties defined in the chart engine, and the data names are now listed in those series-dependent properties. Each entry in the series-break field will generate a corresponding series property object in the chart engine, retrievable with renderConfig.moonbeamInstance.getSeries(x), where x is an integer for the series to be retrieved. getSeries returns an object with properties such as color and label, which are unique to the chosen series.
  • A matrix-break bucket is used for the sort fields that define the columns and rows in a matrix chart. A matrix-break bucket also adds more array dimensions to the data set. A matrix-break bucket is broken into column and row sub-buckets. If either the row or column bucket contains any fields, the data set will contain two additional dimensions of data, even if one of the matrix buckets is empty. That is, the data set will either contain neither row nor column data, or both row and column data, never just one or the other. bucket.depth will always be at least three.

The Tooltip Bucket

The tooltip bucket is not a break bucket, and does not add any additional array dimensions to the data set. Instead, tooltip behaves like a custom bucket. Each inner datum object will contain a property named tooltip, with a value of type string for dimensions, number for measures, and an array of values for multiple fields in the bucket.

The usefulness of this bucket is that in addition to including tooltip-specific data in the data set, WebFOCUS BUE also generates meaningful tooltip content for each series. This tooltip content is the same content used for all of the built in WebFOCUS BUE chart types. Using the tooltip bucket means the extension does not have to figure out what ought to go into each tooltip.



Example: Sample Series-Break Bucket Definition

This example uses the following sample data.

Car

Country

Seats

BMW

Germany

5

Audi

Germany

4

Peugeot

France

5

Alfa Romeo

Italy

4

Maserati

Italy

2

Toyota

Japan

4

The following code defines a series-break bucket.

dataBuckets:
        series_break: true,
        buckets: [
            {id: "label", type: "dimension"},
            {id: "value", type: "measure"}
        ]

Consider the following fields assigned to each of the buckets:

  • "Country" assigned to the "series_break" bucket.
  • "Car" assigned to the "label" bucket.
  • "Seats" assigned to the "value" bucket.

In the renderConfig function, the renderConfig.data object will be similar to the following, in which the Country values are no longer part of the data array. However, a new array starts for each change in the Country value:

 [{labels: "PEUGEOT", value: 5}],
        [{labels: "ALFA ROMEO", value: 4}, {labels: "MASERATI", value: 2],
        [{labels: "TOYOTA", value: 4}],
        [{labels: "AUDI" ,value: 4}, {labels: "BMW", value: 5}]

The renderConfig.dataBuckets object will be defined as follows:

renderConfig.dataBuckets = {
        depth: 2,
        series_break: {title: "Country"},
        buckets: {
            label: {title: "Car"},
            value: {title: "Seats"}
x
Handling Partial and Null Data in an Extension

In many cases, the end user working with an extension cannot populate all of the extension buckets immediately. An extension must correctly handle these partial data cases, and cannot crash if one or more buckets are empty. It is important to check renderConfig.dataBuckets to see which buckets have been populated, and act accordingly.

In addition, data sets are often incomplete, missing some values for a given combination of dimensions and measures. These missing values may show up in the data set as null entries within an array (instead of datum objects), or they may show up as entirely empty arrays. It is important to detect and handle these missing data cases, and render a visualization appropriate for such missing data.

Most extensions require some minimum number of populated buckets before anything can be rendered. Use the count.min properties of each dataBuckets.bucket entry in properties.json to define these minimum requirements. If the fields in all buckets do not meet the minimum counts, then the renderCallback for the extension will not be called. Instead, the noDataPreRenderCallback for the extension is called. This allows the extension to render in a special no data mode. In this mode, the extension should render in grey scale, using renderCallback.baseColor as the main color. This should be a very simplified, sample rendering of the extension.



Example: Sample noDataPreRenderCallback Function

The following noDataPreRenderCallback function is from the Simple Bar sample extension.

function noDataRenderCallback(renderConfig) {
  var grey = renderConfig.baseColor;
  renderConfig.data = [{value: [3, 3]}, {value: [4, 4]}, {value: [5, 5]}, {value: [6, 6]}, {value: [7, 7]}];
  renderConfig.moonbeamInstance.getSeries(0).color = grey;
  renderConfig.moonbeamInstance.getSeries(1).color = pv.color(grey).lighter(0.18).color;
  renderCallback(renderConfig);
 }
x
Installing a Chart Extension

Reference:

  1. Find the extensions folder for your local WebFOCUS BUE installation. This is typically the following folder.
    C:\ibi\install_dir\config\web_resource\extensions

    where:

    install_dir

    Is your WebFOCUS BUE installation directory.

    Note: The WebFOCUS Extension section of the Information Builders GitHub page maintains a list of publicly available and supported extensions. To install one of those, click the extension you want to install, then right click the zip file for that extension, for example com.ibi.xyz.zip, and choose Save link as...

  2. Unzip the downloaded zip file into the WebFOCUS BUE extensions folder. For example, for the com.ibi.xyz.zip zip file, this should create the following folder.
    C:\ibi\install_dir\config\web_resource\extensions\com.ibi.xyz

    If you are installing your own extension from your own environment, copy or download it to the WebFOCUS BUE extensions folder, using the same naming conventions for the folder and the extension ID as described for the sample extensions.

  3. Edit C:\ibi\install_dir\config\web_resource\extensions\html5chart_extensions.json. Create a new line for the new extension in the form:
    "com.ibi.abc": {"enabled": true},

    where:

    abc

    Is the name of the extension.

  4. In the Administration Console, click Clear cache. This will force WebFOCUS to reload all extensions.

Following is a sample html5chart_extensions.json.

{
        "com.ibi.simple_bar": {enabled: true},
        "com.ibi.liquid_gauge": {enabled: false},
        "com.ibi.sankey": {enabled: true}
    }
x
Reference: Preserving Custom Chart Types When Reinstalling the WebFOCUS Client

If you reinstall the WebFOCUS Client, your extensions folder will be overwritten. Therefore, if you have installed any custom chart extensions, you should preserve them by copying them to another location prior to reinstalling the WebFOCUS Client and copying them back to the extensions folder after reinstalling the WebFOCUS Client.

You will also have to copy the entries for your custom extensions into the new html5chart_extensions.json file installed with the new version of the WebFOCUS Client.

Note: The extensions that are delivered as part of WebFOCUS BUE will be reinstalled automatically, so you should not preserve those extensions. In that way, if any enhancements have been made to those extensions, you will automatically have access to the enhanced versions when you reinstall the WebFOCUS Client.

x
Using Your Extension in a WebFOCUS Request

If you have installed and configured your extension as described, your extension will be available for use in the WebFOCUS BUE tools as a chart type in the Other format category under HTML5 Extension, as shown in the following image.

The attribute categories you defined in the dataBuckets object of your extension are available in the query pane.

In the FOCEXEC:

The following is a sample request using the Simple Bar extension.

GRAPH FILE WF_RETAIL_LITE
SUM COGS_US
GROSS_PROFIT_US
REVENUE_US
DISCOUNT_US
BY PRODUCT_CATEGORY
ON GRAPH PCHOLD FORMAT JSCHART
ON GRAPH SET LOOKGRAPH EXTENSION
ON GRAPH SET AUTOFIT ON
ON GRAPH SET STYLE *
INCLUDE=IBFS:/FILE/IBI_HTML_DIR/javaassist/intl/EN/combine_templates/ENWarm.sty,$
TYPE=DATA, COLUMN=PRODUCT_CATEGORY, BUCKET= >labels, $
TYPE=DATA, COLUMN=COGS_US, BUCKET= >value, $
TYPE=DATA, COLUMN=GROSS_PROFIT_US, BUCKET= >value, $
TYPE=DATA, COLUMN=REVENUE_US, BUCKET= >value, $
TYPE=DATA, COLUMN=DISCOUNT_US, BUCKET= >value, $
*GRAPH_JS
chartType: "com.ibi.simple_bar",
*END
ENDSTYLE
END

Run the chart. The output is shown in the following image.


WebFOCUS

Feedback