Skip navigation
All Places > Developer > Blog > 2017 > March
2017
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.

To be honest, I do not get to sling code all that often anymore. But I do spend a lot of time thinking about software even when I have no real hand (or keyboard) in creating it. I also talk to people about software a lot - software design, solution architecture, teams, and development processes. This means my growth today as a software professional comes primarily from the wisdom of others. And this was probably always true.

 

I have started to read more books about software and most recently that meant reading The Pragmatic Programmer. For those who do not know it, it is a classic software engineering book that was released in 1999. In a fast moving industry, what could you possible learn from a 17+ year old technology book? Well if you read it (again) then you may be surprised. The Pragmatic Programmer is not about programming - it is about programming processes, practices, and code craftsmanship. While you may not agree with everything you find in it, some parts feel out of date, it remains overall a very worthy book. I recommend it.

 

Something that books do for me is to put names on some concepts that I acquired from first-hand experience. It has also fully explained others I have known from having heard them tossed over cubicle walls or conference room tables over the years. Terms like Reversibility, Orthogonality, and many others represent concepts that should guide any Sugar Developer who sits down to her daily work of customizing, integrating, and extending the Sugar platform.

 

So in the spirit of The Pragmatic Programmer, here is my take on what some of the lessons taught in the Pragmatic Programmer mean to a Sugar Developer.

For a "cliff notes" version of the Pragmatic Programmer, you can find a quick reference guides and other summaries online.

 

The Pragmatic Sugar Developer

 

This blog post will help you become a better Sugar Developer. Apologies to Andrew Hunt and David Thomas who are the authors of The Pragmatic Programmer.

 

Orthogonality of Sugar customizations

 

The Pragmatic Sugar Developer designs Sugar customizations to be as orthogonal to Sugar core code as possible. Keeping customizations independent and loosely coupled from the Sugar platform means that Sugar version upgrades will rarely introduce side effects or breakages. The reverse is also true, which means the Developer can make changes to her customizations that will not cause side effects or breakages in the Sugar platform either.

 

For example, overriding core Sugar code (like SugarBean.php) using the custom directory to implement a customization is decidedly non-orthogonal with respect to the Sugar platform. Any change in the core file will impact the override. However, customizations implemented using an Extension such as a Logic Hook, a custom Sidecar Dashlet, or using the REST API would be orthogonal to the Sugar platform. As Sugar platform evolves, these orthogonal customizations will be better protected from side effects.

 

Sugar core code and the DRY Principle

 

DRY stands for Don't Repeat Yourself. Every piece of knowledge should have an single, unambiguous, and authoritative representation in the system.

 

This means that a Pragmatic Sugar Developer will not blindly copy and paste core code into the custom directory. Developers should be calling APIs and not copying them. Core code should never be repeated under the custom directory where it will not be updated during upgrades.

 

Reversibility

 

The Pragmatic Sugar Developer knows that customers/stakeholders/management will often change their mind. This means that the Developer will ensure that design decisions are easily reversible in the future. The long term success of her project will likely depend on it.

 

Today, a customer is deploying on MySQL but later they might want to use Oracle instead. That custom chart was in a Sidecar Dashlet today may need to be moved into a custom layout tomorrow. Pragmatic Sugar Developers ensure that the right set of abstractions are in place to minimize the amount of rework associated with these change requests. Any required irreversible (final) decisions are postponed or delayed as long as possible.

 

Deconstruct user workflows to leverage concurrency

 

A Pragmatic Sugar Developer understands how to analyze user workflows and business processes to identify tasks that can be run independently and therefore concurrently. Implementing a giant workflow in one place can easily result in a solution that is a mess. Complex single threaded processes take a long time to run and lock up resources in the mean time like the user's UI, memory, etc.

 

Performance can be improved by scheduling these tasks to be run asynchronously using the Sugar Job Queue or on different web servers for horizontal scaling. Additionally, breaking down a large workflow into independent tasks allows for each of them to be implemented more simply and maintained more easily.

 

Use Sidecar to separate views from models

 

The Pragmatic Sugar Developer understands the difference between the views and models when building Sidecar user interfaces. They create Sidecar Views that focus on representing models in the UI and keeping them synchronized. Sidecar allows Developers to build more flexible and adaptable user interfaces.

 

A Pragmatic Sugar Developer works with the Sidecar framework and not around it in order to drop in foreign frameworks (ex. to incorporate a flavor of the week JavaScript framework) or techniques (ex. fetching Sugar data using jqXHRs instead of using Beans or BeanCollections). Using the native Sidecar framework allows you to minimize complexity and maintain cohesion with the rest of Sugar platform.

 

Design to Test using Sugar Test Tools

 

The Pragmatic Sugar Developer designs her tests before writing any code and includes that additional QA scope in project plans. The Developer also designs her code with the intention to make it easy to test.

 

Pragmatic Sugar Developers benefit from using Sugar Test Tools instead of needing to build new test frameworks from scratch.

 

Dig for requirements for customer success (and your own)

 

Developers know that sometimes customers and stakeholders can provide unreliable or incomplete information. So Pragmatic Sugar Developers do not gather requirements, they dig for them.

 

An experienced Developer knows Sugar better and has done more CRM projects than her customers. She uses her experience to ask lots of questions to test assumptions and uncover new requirements that need to be addressed. Very importantly, customers are often focused on explaining their current systems and processes - Developers should challenge their customers to improve processes and not implement sub-optimal or broken processes on a brand new system.

 

For example, has her customer thought about mobile use cases? Will they want e-mail notifications for certain actions and, if so, what should the e-mail templates look like? Is there customer data outside the CRM today that could be integrated so that users do not need to switch between systems? Is the right data captured in the CRM to support the customer's reporting requirements?