Topics: |
Business User Edition 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 Business User Edition. This topic describes the structure of an extension and the steps necessary to create your own and add it to the chart library.
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 Business User Edition 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
Business User Edition extensions must be placed in the extensions folder under the web_resource folder of your Business User Edition installation. By default, this is the following location:
c:\ibi\install_dir\config\web_resource\extensions
where:
Is your Business User Edition 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 Business User Edition.
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 Business User Edition chart library. It does not describe how to write JavaScript code.
Reference: |
This section summarizes the build cycle for creating an extension and the structure and components of 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 Business User Edition 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 Business User Edition. 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.
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 Business User Edition.
The properties.json file configures your extension to run in Business User Edition. This file includes all the metadata needed to include your extension in Business User Edition, 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.
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.
Reference: |
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.
You can define the following three JavaScript callback functions. Only the renderCallback function is always required.
Each of the three entry point callbacks is passed a config object, which contains a set of useful properties.
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 }
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. |
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. |
Topics: |
Extension configuration consists of two parts.
Reference: |
To configure your extension, create a config object with all the information unique to your extension, then register your extension with the extension API.
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. |
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'] }, }
To register your extension with the WebFOCUS extension API, call:
tdgchart.extensionManager.register(config);
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:
Each extension must include a properties.json file, which defines the information needed by Business User Edition when drawing its user interface.
The properties.json file consists of the following blocks.
There are two types of buckets, built-in and custom. Built-in buckets provide an easy way to reuse the existing Business User Edition 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.
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" } } }
Topics: |
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.
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 Business User Edition. 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 bucket is either a standard bucket or a break bucket.
Types of Break Buckets
Break buckets can be of two types:
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, Business User Edition also generates meaningful tooltip content for each series. This tooltip content is the same content used for all of the built-in Business User Edition chart types. Using the tooltip bucket means the extension does not have to figure out what ought to go into each tooltip.
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:
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"}
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.
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); }
Reference: |
C:\ibi\install_dir\config\web_resource\extensions
where:
Is your Business User Edition 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...
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 Business User Edition extensions folder, using the same naming conventions for the folder and the extension ID as described for the sample extensions.
"com.ibi.abc": {"enabled": true},
where:
Is the name of the extension.
Following is a sample html5chart_extensions.json.
{ "com.ibi.simple_bar": {enabled: true}, "com.ibi.liquid_gauge": {enabled: false}, "com.ibi.sankey": {enabled: true} }
Note: The Administration Console provides a user interface for installing chart extensions. For information, see How to Install HTML5 Chart Extensions From the IBI GitHub Page.
If you reinstall the Business User Edition, 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 Business User Edition and copying them back to the extensions folder after reinstalling the Business User Edition.
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 Business User Edition.
Note: The extensions that are delivered as part of Business User Edition 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 Business User Edition.
If you have installed and configured your extension as described, your extension will be available for use in Business User Edition 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:
*GRAPH_JS chartType: "com.ibi.simple_bar", }
TYPE=DATA, COLUMN=N1, BUCKET= >labels, $ TYPE=DATA, COLUMN=N2, BUCKET= >value, $ TYPE=DATA, COLUMN=N3, BUCKET= >value, $ TYPE=DATA, COLUMN=N4, BUCKET= >value, $ TYPE=DATA, COLUMN=N5, BUCKET= >value, $
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 |