Skip to content

Custom widgets

ChartFactor Studio allows users to easily use custom javascript code to extend the functionality of the platform. This code can be used to create custom widgets with custom visualizations, custom interactions, custom data sources, and more.

In order to create a custom widget in ChartFactor Studio, you need to implement the following functions:

Setup(widgetId, utils)

The setup(widgetId, utils) function is invoked by ChartFactor Studio to set up your widget. It should return an Aktive object that will be used by ChartFactor Studio to interact with the widget. This function can be asynchronous so that it returns a promise or it can be a normal function that returns an object.

Parameters:

  • widgetId: The id of this widget
  • utils: An object with Studio utility functions that are made available to the custom code. The utils object includes the following functions:
    • getSourceFilters(provider, source): This function is used to get the filters for a specific source. The provider parameter is the name of the provider and the source parameter is the name of the source. This function returns a promise that resolves to an array of filters.
    • saveVar(name, value): This function is used to save a variable in the widget context. The name parameter is the name of the variable and the value parameter is the value of the variable. This function returns a promise that resolves when the variable is saved.
    • getVar(name): This function is used to get a variable from the widget context. The name parameter is the name of the variable. This function returns a promise that resolves to the value of the variable.
    • removeVar(name): This function is used to remove a variable from the widget context. The name parameter is the name of the variable. This function returns a promise that resolves when the variable is removed.
    • mergeData(...data): This function is used to merge data from multiple queries using the group in each data object. Therefore, it requires that each data object has only one group. The data parameter allows you to pass multiple data objects, eg. mergeData(data1, data2, data3, data4). This function returns a promise that resolves to a single data object. The Custom Widget Example section shows how this function can be used.

loadProviders()

The loadProviders() function is used to load the providers that will be used in the custom code. This function should return an array with the names of the providers that will be used in the custom code.

onDestroy()

The onDestroy() function should perform any clean up needed when users delete the widget from the dashboard.

Custom Widget Example

The example below shows how to implement a custom widget that renders trends for ETF (Exchange Traded Funds) volume and consumer confidence. The data resides in two separate sources and this widget queries those data sources separately and then merges the trend results.

image

The code of this custom widget is below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
async function setup(widgetId, utils) {
    const source1Filters = await utils.getSourceFilters("Elasticsearch", "global_economy_indicators");
    const source2Filters = await utils.getSourceFilters("Elasticsearch", "fedfunds");
    let data = await getMultipleQueryData(widgetId, source1Filters, source2Filters, utils);
    let customData = buildData(data, utils);
    // Define Grid
    let grid = cf.Grid()
        .top(40)
        .right(56)
        .bottom(15)
        .left(65);
    let myChart = cf.create()
        .graph("Multimetric Trend")
        .data(customData)
        .set("dataZoom", "dragFilter")
        .set("grid", grid)
        .set("legend", "top")
        .set("yAxis", [{ "show": true }, { "show": true, min: 90, max: 105 }]);
    return myChart;
}
function loadProviders() {
    return [
        "Elasticsearch",
    ];
}
function onDestroy(wId) {
    cf.remove(wId + "-dummy");
}
function buildData(data, utils) {
    // merging the two datasets (etf and bc)
    let newData = utils.mergeData(data.data.etf, data.data.bc);
    // defining custom data object with data and metadata
    const customData = {
        groups: [
            {
                name: "Time",
                label: "Time",
                func: "MONTH",
                type: "TIME"
            },
        ],
        metrics: [
            {
                name: "volume",
                label: "ETF volume",
                type: "Number",
                displayFormat: "$ 0[.]0",
            },
            {
                name: "OECD - Total",
                label: "Consumer confidence index",
                func: "avg",
                hideFunction: true,
                type: "Number",
                displayFormat: "$ 0[.]0",
                position: "right"
            }
        ],
        data: newData
    };
    return customData;
}
async function getMultipleQueryData(widgetId, source1Filters, source2Filters, utils) {
    return new Promise((resolve, reject) => {
        let m1 = cf.Metric("volume", "sum");
        let group1 = cf.Attribute("price_date")
            .func("MONTH")
            .limit(1000)
            .sort("asc", "price_date");
        let group2 = cf.Attribute("Location")
            .limit(1000).func("MONTH")
            .sort("asc", "Location");
        let metric0 = cf.Metric("OECD - Total", "avg");
        var eventsHandler = function(result) {
            result.keep = true;
            return result;
        };
        cf
            // First query starts
            .create("etf") // Any name to identify the query
            .provider("Elasticsearch")
            .source("etf_prices")
            .groupby(group1)
            .metrics(m1)
            .filters(...source1Filters)
            // Second query starts
            .create("bc") // Any name to identify the query
            .provider("Elasticsearch")
            .source("oecd_bussines_confidence")
            .groupby(group2)
            .metrics(metric0)
            .filters(...source2Filters)
            .processWith(eventsHandler)
            .on("execute:stop", data => {
                try {
                    // Update the visualization if it exists
                    let akt = cf.getVisualization(widgetId);
                    if (akt) {
                        akt
                            .data(buildData(data, utils))
                            .execute();
                    }
                    resolve(data);
                }
                catch (e) {
                    reject(e);
                }
            })
            // This element is a non existing DOM element
            // and the purpouse is only to be able to get
            // the query using cf.getVisualization('dummy')
            .element(widgetId + "-dummy")
            .execute();
    });
}

In the example above, we are using the loadProviders() function to load the providers that will be used in the custom code. The getMultipleQueryData() function is an asynchronous function that returns a promise and is used to get data from multiple queries. The setup() function is an asynchronous function that returns a promise and is used to setup the custom code.

Note

You need to provide your own logic to persist filters and field metadata updates (e.g. field labels) manually. The utils parameter passed to the setup() function includes helper functions that you can use to implement this functionality if needed.