Skip to content

Custom code

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 visualizations, custom interactions, custom data sources, and more.

In order to use custom code in ChartFactor Studio, you need to implement the following functions:

Setup()

The setup() function can be asynchronous that returns a promise or a normal function that returns an object. The setup() function should return an Aktive object that will be used by ChartFactor Studio to interact with the widget.

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.

The example below illustrates how to implement these functions:

  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
116
117
118
119
120
async function setup(widgetId, filters) {
    let data = await getMultipleQueryData(widgetId, filters);
    let customData = buildData(data);
    // 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) {
    // merging the two datasets (etf and bc)
    let newData = data.data.etf;
    data.data.bc.forEach(i => {
        const existing = newData.find(nd => nd.group[0] === i.group[0]);
        if (existing) {
            existing.current.metrics = { ...existing.current.metrics, ...i.current.metrics };
        }
        else {
            newData.push(i);
        }
    });
    // 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;
}
function getMultipleQueryData(widgetId, filters) {
    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)
            // Second query starts
            .create("bc") // Any name to identify the query
            .provider("Elasticsearch")
            .source("oecd_bussines_confidence")
            .groupby(group2)
            .metrics(metric0)
            .processWith(eventsHandler)
            .on("execute:stop", data => {
                try {
                    // Update the visualization if it exists
                    let akt = cf.getVisualization(widgetId);
                    if (akt) {
                        akt
                            .data(buildData(data))
                            .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 this example, 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

The custom code will no be updated by ChartFactor Studio, you need to filter and field metadata updates manually.