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.
If you implement the extension API version 2, you can retrieve the field name and number format associated with a data bucket entry. To use API 2 and get a bucket entry field name or number format, an extension must declare that it implements extension API version 2 using the 'implements_api_version' entry in the info block of properties.json:
{ "info": { "implements_api_version": "2.0" } }
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. |
dataBuckets |
Optional custom data buckets. For information, see dataBuckets Block. |
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 Chart Rendering. |
preRenderCallback |
Optional. References your preRenderCallback function, described in Chart Rendering. |
renderCallback |
Required. References your renderCallback function, described in Chart Rendering. |
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:
Reference: |
Each extension must include a properties.json file, which defines the information needed by WebFOCUS when drawing its user interface.
The properties.json file consists of the following blocks.
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": "2.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" } } }
This block defines several general purpose configuration options, such as files containing chart icons.
icons
This property defines the files to be used as icons that represent the extension inside WebFOCUS tools. An extension can include multiple bitmap icons of different sizes, or a single scalable SVG icon. An SVG icon should not include hardcoded 'width' or 'height' attributes. The syntax for the info block with an icons object is:
{ "info": { "icons": { "size": "icons/filename" } } }
where:
Is the size for each icon file. Each icon file name is listed as an entry under 'icons' according to its size, which is one of:
Is the name of the file containing the icon.
For example:
{ "info": { "icons": { "medium": "icons/my_svg_icon.svg" } } }
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 chart procedure.
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".
This block defines the set of chart attribute categories that appear in the Query pane in the WebFOCUS user interface when creating a chart.
If you are using API version 2.0, dataBuckets.buckets is an array instead of an object. Each entry in this array represents the content of one data bucket. The id property identifies which bucket this is, and the fields array specifies how many entries are in this bucket and the unique information for each (titles, field names, number formats).
The dataBuckets object includes a method named getBucket(). Pass it the name of a bucket and it returns the content of that bucket.
There are two types of buckets, built-in and custom. Built-in buckets provide an easy way to reuse the existing WebFOCUS 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 dataBuckets block includes the following objects for API Version 1.0.
bucket. Each bucket block defines one custom chart attribute category. Each custom bucket requires the following properties:
The dataBuckets block includes the following objects for API Version 2.0.
{"title": "fieldtitle", "fieldName": "fieldname", "numberFormat": "format"}
With API Version 2.0, the content of renderConfig.dataBuckets that is passed to each render callback in an extension provides field title and format information. In the following example, the there are two buckets. The labels bucket has one field, CAR.ORIGIN.COUNTRY, whose title is COUNTRY. The value bucket has two fields, CAR.SALES, title SALES, and CAR.BODY.DEALER_COST, title DEALER_COST.
"dataBuckets": { "getBucket(bucketName)", "depth": 2, "buckets": [ { "id": "labels", "fields": [ {"title": "COUNTRY", "fieldName": "CAR.ORIGIN.COUNTRY"} ] }, { "id": "value", "fields": [ {"title": "SALES", "fieldName": "CAR.SALES", "numberFormat": "#,###.00"}, {"title": "DEALER_COST", "fieldName": "CAR.BODY.DEALER_COST", "numberFormat": "#,###"} ] } ] }
This block defines translations in different languages for every label to be drawn in the WebFOCUS interface. The translation object has one property for each language the extension supports, keyed by ISO-639 two letter locale strings.
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 WebFOCUS. 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 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, WebFOCUS also generates meaningful tooltip content for each series. This tooltip content is the same content used for all of the built in WebFOCUS 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); }
WebFOCUS | |
Feedback |