How to build Chart Layouts for Sugar 7 - Part 2

In our last post we covered the basics of adding a custom chart layout. Today's post will build on that example to cover some of the more advanced configurations that are possible using charts in Sugar.  Like the previous post, this is targeted at a beginner to intermediate skilled Sugar Developer who is interested in building custom charts.

Multiple Charts On the Same View

Previously we explored how to display a single chart on a view. Displaying more than a single chart on the view is also very easy too.

In order to add a second chart, you may be tempted to create another chart object in the initialize() method but that is not necessarily how Sugar Charts works.  The "chart" property in the view controller is a Chart factory. Chart factories will not affect how your view is rendered unless you do something with them. You can use the same factory's call() method to construct the same style of chart multiple times.

Example

We will create a custom layout and view exactly the way we did as before in the single chart example.  We will call this new example the "multi-chart-view".

Set up the Handlebars template

To display multiple charts you need to provide multiple locations for the charts to exist. These locations are represented below by the div and the enclosed svg elements below. It is convenient to allow each chart div to have a unique id.

<div id="example-chart1">

    <svg></svg></div>

<div id="example-chart2">

    <svg></svg></div>

<div id="example-chart3">

    <svg></svg></div>

Populate data for multiple views

In our JavaScript code, we will obviously need to provide data for each chart. Here we create three separate properties on the view controller:  "bluered", "purpleteal", and "yellowgray".  You'll notice that each chart uses the same format as before.

({

...

    loadData: function() {

        this.bluered = {

            data: [

                {

                    key: "Blue Stuff",

                    values: [

                        {

                            x: 1, y: 10

                        },

                        {

                            x: 2, y: 9

                        },

                        {

                            x: 3, y: 8

                        },

                        {

                            x: 4, y: 7

                        },

                        {

                            x: 5, y: 6

                        },

                    ],

                    color: "#0000ff"

                },

                {

                    key: "Red Stuff",

                    values: [

                        {

                            x: 1, y: 1

                        },

                        {

                            x: 2, y: 2

                        },

                        {

                            x: 3, y: 3

                        },

                        {

                            x: 4, y: 4

                        },

                        {

                            x: 5, y: 5

                        },

                    ],

                    color: "#ff0000"

                },

            ],

            properties: {

                title: 'First Chart Data'

            }

        };

        this.purpleteal =             {

            data: [

                {

                    key: "Purple Stuff",

                    values: [

                        {

                            x: 1, y: 10

                        },

                        {

                            x: 2, y: 9

                        },

                        {

                            x: 3, y: 8

                        },

                        {

                            x: 4, y: 7

                        },

                        {

                            x: 5, y: 6

                        },

                    ],

                    color: "#ff00ff"

                },

                {

                    key: "Teal Stuff",

                    values: [

                        {

                            x: 1, y: 1

                        },

                        {

                            x: 2, y: 2

                        },

                        {

                            x: 3, y: 3

                        },

                        {

                            x: 4, y: 4

                        },

                        {

                            x: 5, y: 5

                        },

                    ],

                    color: "#00ffff"

                },

            ],

            properties: {

                title: 'Second Chart Data'

            }

        };

        this.yellowgray = {

            data: [

                {

                    key: "Yellow Stuff",

                    values: [

                        {

                            x: 1, y: 10

                        },

                        {

                            x: 2, y: 9

                        },

                        {

                            x: 3, y: 8

                        },

                        {

                            x: 4, y: 7

                        },

                        {

                            x: 5, y: 6

                        },

                    ],

                    color: "#ffff00"

                },

                {

                    key: "Gray Stuff",

                    values: [

                        {

                            x: 1, y: 1

                        },

                        {

                            x: 2, y: 2

                        },

                        {

                            x: 3, y: 3

                        },

                        {

                            x: 4, y: 4

                        },

                        {

                            x: 5, y: 5

                        },

                    ],

                    color: "#888888"

                },

            ],

            properties: {

                title: 'Third Chart Data'

            }

        }

        this.total = 1;

    },

...

})

Call the Chart factory to display each chart

Now we make three separate calls to display each of the charts we want to display. You will notice that the unique ids we added to the div elements in the Handlebars template allow us to easily select each chart location. We also pass each a data object that we created above.

({

...

    renderChart: function () {

        if (!this.isChartReady()) {

            return;

        }

        d3.select(this.el).select('#example-chart1 svg')

            .datum(this.bluered)

            .transition().duration(500)

            .call(this.chart);

        d3.select(this.el).select('#example-chart2 svg')

            .datum(this.purpleteal)

            .transition().duration(500)

            .call(this.chart);

        d3.select(this.el).select('#example-chart3 svg')

            .datum(this.yellowgray)

            .transition().duration(500)

            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);

    }

})

With this code in place, you can run a Quick Repair & Rebuild and navigate to the following URL.

http://{your sugar server}/#Accounts/layout/multi-chart-layout

You should then see each of your charts in the layout:

Any Number of Charts from an API Call

While we have shown how to get basic charts working, what we have done so far does not reflect the real world implementations very well.  You do not always know how many charts you need to display and you pretty much never are working with static data. Typically you will make a HTTP call to some REST API to provide data to display. Depending on the nature of that data you may want to display a variable number of charts.

In this example, we provide an example that makes a call to an API which returns a variable amount of data, therefore requiring a variable number of charts, so we can explore how to handle this.

Set up an API to call

For our example we will create a toy API for us to call.  It is simplistic in that it returns data in the exact format the LineChart expects.  In the real world you might have to transform the data in JavaScript a bit first. However, this will illustrate the important principles involved.

This API will be invoked using a simple HTTP GET to

http://{your sugar server}/rest/v10/Accounts/get_line_chart_data

which will return a randomly generated set of data for multiple charts with multiple lines. You can play with the numbers at the top of the method to give you different results.custom/modules/Accounts/clients/base/api/chartInfoAPI.php

<?php if (!defined('sugarEntry') || !sugarEntry) {

die('Not A Valid Entry Point'); }

require_once 'clients/base/api/ModuleApi.php';

class chartInfoAPI extends ModuleApi {

public function registerApiRest() {

return array(

'webhook' => array(

                'reqType' => 'GET',

                'path' => array('Accounts', 'get_line_chart_data'),

                'pathVars' => array('module', 'action'),

                'method' => 'getLineChartInfo',

                'shortHelp' => 'This endpoint retrieves line chart information in the proper format',

                'longHelp' => '',

            ),

        );

    }

    /**

     * This method generates data for a line chart example and returns it in the following LineChart format:

     * array(

     *     array(

     *         'data' => array(

     *             array(

     *                 'key' => "Data set the first", // Title of this data set

     *                 'values' => array(

     *                     array(

     *                         'x' => {some number},

     *                         'y' => {some other number},

     *                     ),

     *                     ...

     *                 ),

     *                 'color' => "#ff0000", // Any color you want, of course

     *             ),

     *             ...

     *         ),

     *         'properties' => array(

     *             'title' => "Title of this chart",

     *         ),

     *     ),

     *     ...

     * )

     */

    public function getLineChartInfo($api, $args) {

        $out_data_arr = array();

        $num_charts = 3; // Total number of charts to display

        $num_lines = 2; // Number of lines per chart to display

        $num_data_points = 5; // Number of data points per line to display

        $color_map = array(

            'silver',

            'gray',

            'black',

            'red',

            'maroon',

            'yellow',

            'olive',

            'lime',

            'green',

            'aqua',

            'teal',

            'blue',

            'navy',

            'fuchsia',

            'purple',

        );

        for ($i = 0; $i < $num_charts; $i++) {             $tmp_chart_arr = array(                 'data' => array(),

                'properties' => array(

                    'title' => "Chart #" . ($i + 1),

                ),

            );

            for ($j = 0; $j < $num_lines; $j++) {                 // Pick a color for the line                 $line_color = $color_map[rand(0, count($color_map) - 1)];                 $tmp_line_arr = array(                     'key' => ucfirst($line_color) . " Data",

                    'values' => array(),

                    'color' => $line_color,

                );

                for ($k = 1; $k <= $num_data_points; $k++) {                     $tmp_data_point = array(                         'x' => $k,

                        'y' => rand(0, 10),

                    );

                    $tmp_line_arr['values'][] = $tmp_data_point;

                }

                $tmp_chart_arr['data'][] = $tmp_line_arr;

            }

            $out_data_arr[] = $tmp_chart_arr;

        }

        return $out_data_arr;

    }

}

Implement the Handlebars template

Since we have no idea how many charts we are going to need to display we cannot just set up div and svg elements ahead of time like we did for the previous example. Instead we will just have a generic div into which we'll  add further div and svg elements dynamically within the Javascript below.

<div id="chart-section"></div>

Implement the loadData() method

Now we need to actually call our custom API. Here we call the API via Sugar's built in app.api.call() method which takes care of access token management automatically. We take the results from the API call and assign them to the chartDataArr array for use during render. Take note of the assignment to self.total. Here it matters where this happens because it should not be set until the data is available and charts can be rendered.

({

    ...

    loadData: function() {

        var self = this;

        var url = app.api.buildURL('Accounts/get_line_chart_data');

        app.api.call('read', url, null, {

            success: function (response) {

                _.each(response, function (data, key) {

                    self.chartDataArr.push(data);

                });

                if (self.chartDataArr.length > 0) {

                    self.total = 1;

                } else {

                    self.errorMsg = "There is no chart information available";

                }

                self.render();

            },

            error: _.bind(function () {

                this.errorMsg = "Error encountered retrieving chart information";

            }, this)

        });

    },

    ...

})

Implement the renderChart() method

The renderChart() method gets a little more complex here. We iterate through the set of arrays we stored in chartDataArr in the loadData() method, each of which represents a chart to display. First we add a new div and svg element, with a unique identifier, in which to display the chart. Then we make the call to the call() method to actually display the chart to the new elements.

({

...

      renderChart: function () {

        if (!this.isChartReady()) {

            return;

        }

        var self = this;

        var id_val = "";

        var selector_str = "";

        _.each(this.chartDataArr, function (data, key) {

            id_val = 'example_chart' + key;

            selector_str = '#' + id_val + ' svg';

            $("#chart-section").append('

<div id="' + id_val + '"><svg></svg></div>

');

            d3.select(self.el).select(selector_str)

                .datum(data)

                .transition().duration(500)

                .call(self.chart);

        });

        this.chart_loaded = _.isFunction(this.chart.update);

    }

})

Here's the JavaScript when you put it all together

({

    plugins: ['Chart'],

    className: 'api-chart-view',

    chartDataArr: [],

    total: 0,

    initialize: function (options) {

        this._super('initialize', [options]);

        this.chart = nv.models.lineChart()

            .x(function (d) {

                return d.x;

            })

            .y(function (d) {

                return d.y;

            })

            .showTitle(true)

            .tooltips(false);

    },

    loadData: function() {

        var self = this;

        var url = app.api.buildURL('Accounts/get_line_chart_data');

        app.api.call('read', url, null, {

            success: function (response) {

                _.each(response, function (data, key) {

                    self.chartDataArr.push(data);

                });

                if (self.chartDataArr.length > 0) {

                    self.total = 1;

                } else {

                    self.errorMsg = "There is no chart information available";

                }

                self.render();

            },

            error: _.bind(function () {

                this.errorMsg = "Error encountered retrieving chart information";

            }, this)

        });

    },

    renderChart: function () {

        if (!this.isChartReady()) {

            return;

        }

        var self = this;

        var id_val = "";

        var selector_str = "";

        _.each(this.chartDataArr, function (data, key) {

            id_val = 'example_chart' + key;

            selector_str = '#' + id_val + ' svg';

            $("#chart-section").append('

<div id="' + id_val + '"><svg></svg></div>

');

            d3.select(self.el).select(selector_str)

                .datum(data)

                .transition().duration(500)

                .call(self.chart);

        });

        this.chart_loaded = _.isFunction(this.chart.update);

    }

})

When everything is in place, do a Quick Repair & Rebuild and go to

http://{your sugar server}/#Accounts/layout/api-chart-layout

That should show you something like this

Final Thoughts

The examples here are very simple. There are many chart types to choose from and many options for each chart type. Hopefully using these tutorials, the resources mentioned above, and some trial and error, you will then be able to build custom charts in your Sugar customizations.