How to build Chart layouts for Sugar 7

This blog will be the first in a two part series on building Charts components for Sugar 7. This post is targeted at beginner to intermediate Sugar Developers who want to learn how to build their first Chart component.

This post assumes some basic knowledge of Sugar 7 development, Sugar 7 administration, JavaScript, and PHP.  This information should be useful to anyone who has an interest in Sugar 7 development.

The examples in this post were created on an out-of-the-box installation of Sugar Professional 7.8.0.0.  But this technique should work on any on-premise Sugar 7 instance.

Introduction

You may have noticed that a number of out of the box dashlets and views contain various fancy charts and visualizations.  This is possible because Sugar has a charting component build into it.  You can make use of this to display charts within your own custom dashlets, views or layouts.

In this post, we will focus on the "LineChart" type. There are other chart types that use different data formats and chart options but the general techniques covered here will work for all chart types.  These examples were implemented in a basic custom view but they will also work within dashlets.

NVD3 and Sugar's Charts

To provide the ability to display charts easily, Sugar uses a highly customized version of NVD3. The basic structure of our version is the same as NVD3 but the details are different so take some time to explore the chart examples in the source code at /sugarcrm/styleguide/content/charts/. Sugar plans to fully migrate to Sucrose Charts which is SugarCRM's official fork of the NVD3 libraries in an upcoming release. It is worth taking some time to explore Sucrose Charts documentation as well. It includes a number of detailed examples of what charts are possible as well as the data models used to produce them.

Basic Requirements for a Sugar Chart

Within a Sugar view, all charts have the same general requirements. The examples here use the LineChart chart type but these steps apply to any chart you may want to create.

  1. Create the container for your Chart in your Handlebars template
    • Create a DIV element with a child SVG element inside your Handlebars template
  2. Implement the Chart plug-in within your JavaScript controller
    • Include the "Chart" Sidecar plug-in
    • Instantiate the chart model for your view in the initialize() method
    • Collect data in the right format for your chart type
    • Invoke the chart model via the chart call() method

The example below will make this clear.

A Basic Line Chart Example

We can create a custom Sidecar layout under the Accounts module called single-chart-layout which, in turn, includes a custom Sidecar view for Accounts module called single-chart-view. While we are using Accounts module here, this example could be adapted to run on any module.

First, we will create our custom layout.custom/modules/Accounts/clients/base/layouts/single-chart-layout/single-chart-layout.php

<?php

$viewdefs['Accounts']['base']['layout']['single-chart-layout'] =

array( 'type' => 'simple',

    'components' => array(

        array(

            'view' => 'single-chart-view',

        ),

    ),

);

As you can see from the layout, we reference "single-chart-view" so we should create this next.custom/modules/Accounts/clients/base/views/single-chart-view/single-chart-view.php

<?php

$viewdefs['Accounts']['base']['view']['single-chart-view'] =

array( 'title' => 'Chart example',

    'config' => "",

);

Now we can set up our chart within the single-chart-view component.

Set up your Handlebars template

Next, we create our Handlebars template.single-chart-view.hbs

<div class="single-chart-view">

<svg></svg></div>

Include the "Chart" plugin

When creating single-chart-view.js the first thing to do is to include the "Chart" plugin:

({

    plugins: ['Chart'],

    className: 'single-chart-view',

    chartData: {},

    total: 0,

    initialize: function (options) {

    ...

})

Instantiate the chart model for your View in initialize() method

The Chart plug-in will rely on the view's "chart" property.  We need to set up that property to point to a chart object.  In our example, we will tell it to point to a Line Chart.  To do that we can call the nv.models.lineChart() method which returns a line chart object.  For the options available for the Line Chart and others, you will need to consult NVD3 and Sucrose Charts documentation listed above.

For our first example, we will create a very simple line chart that displays a title.  You will notice that we attach "x" and "y" callbacks to the new Line Chart object.  These methods tell the chart object where to get the X and Y axis data for the line chart.  In our case the "x" callback retrieves from the "widget_points" property and the "y" callback retrieves from the "num_widgets" property. There is nothing magical about those names, of course. I made them deliberately arbitrary to illustrate that they can be anything you want.

({

    ...

    initialize: function (options) {

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

        this.chart = nv.models.lineChart()

            .x(function (d) {

                return d.widget_points;  // We get the X data points from 'widget_points'

            })

            .y(function (d) {

                return d.num_widgets;  // We get the Y data points from 'num_widgets'

            })

            .showTitle(true)

            .tooltips(false);

    },

    ...

})

Populate your data

If a loadData() method exists, then it will be executed when the View is rendered. This is when you retrieve data to be used in a chart. If you attach the data object to the View controller then it will be available whenever needed. Here we set it to a property called "chartData".

The format of the object is crucial. Use this format to working with Line Charts.

({

  ...

    loadData: function() {

        this.chartData = {

                data: [

                    {

                        key: "Blue Stuff",

                        values: [

                            {

                                widget_points: 1, num_widgets: 10

                            },

                            {

                                widget_points: 2, num_widgets: 9

                            },

                            {

                                widget_points: 3, num_widgets: 8

                            },

                            {

                                widget_points: 4, num_widgets: 7

                            },

                            {

                                widget_points: 5, num_widgets: 6

                            },

                        ],

                        color: "#0000ff"

                    },

                    {

                        key: "Red Stuff",

                        values: [

                            {

                                widget_points: 1, num_widgets: 1

                            },

                            {

                                widget_points: 2, num_widgets: 2

                            },

                            {

                                widget_points: 3, num_widgets: 3

                            },

                            {

                                widget_points: 4, num_widgets: 4

                            },

                            {

                                widget_points: 5, num_widgets: 5

                            },

                        ],

                        color: "#ff0000"

                    },

                ],

                properties: {

                    title: 'Example Chart Data'

                }

            };

        this.total = 1;

    }

...

})

You should also notice that at the end of the method is the line:

this.total = 1;

When rendering the view, this value is tested to see if data is available prior to rendering chart. With a hard coded example this does not matter much but when you are making an AJAX call for data then it matters since the response returns asynchronously. Other chart types use "total" differently but line charts simply evaluate if it is set to a non-zero value.

Invoke the chart model via the chart call() method

When rendering, the loadData() method is called and then later the renderChart() method is called, if it is defined. This method is used to draw the chart on the screen. We do this by using the built-in d3 call() method.

({

  ...

      renderChart: function () {

        if (!this.isChartReady()) {

            return;

        }

        d3.select(this.el).select('svg')

            .datum(this.chartData)

            .transition().duration(500)

            .call(this.chart);

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

    }

  ...

})

This method first calls isChartReady() to see whether the chart is ready to display or not. Recall the this.total value we set in loadData()?  That's what this method is examining, among other things, to figure out whether it is appropriate to now draw the chart.

At the end of this method we set this.chart_loaded, indicating that the chart has indeed been drawn.

There are several parts to that long call() method chain that are important to understand. First, the string argument to select() is a CSS selector which points to the SVG element in our HTML where the chart will be drawn.

Second, the argument to the datum() method is the object we populated in the loadData() method. It is looking for the appropriately formatted data object.

Third, we attach whatever features of the d3 object we want. In this case, we set transitions to half a second (500 milliseconds). You can experiment with different values there to see what happens.

Finally, we pass the chart property of the view to the call() method. At that point the chart will be drawn.

To put it all together, here is the complete JavaScript controller:

({

    plugins: ['Chart'],

    className: 'single-chart-view',

    chartData: {},

    total: 0,

    initialize: function (options) {

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

        this.chart = nv.models.lineChart()

            .x(function (d) {

                return d.widget_points;

            })

            .y(function (d) {

                return d.num_widgets;

            })

            .showTitle(true)

            .tooltips(false);

    },

    loadData: function() {

        this.chartData = {

                data: [

                    {

                        key: "Blue Stuff",

                        values: [

                            {

                                widget_points: 1, num_widgets: 10

                            },

                            {

                                widget_points: 2, num_widgets: 9

                            },

                            {

                                widget_points: 3, num_widgets: 8

                            },

                            {

                                widget_points: 4, num_widgets: 7

                            },

                            {

                                widget_points: 5, num_widgets: 6

                            },

                        ],

                        color: "#0000ff"

                    },

                    {

                        key: "Red Stuff",

                        values: [

                            {

                                widget_points: 1, num_widgets: 1

                            },

                            {

                                widget_points: 2, num_widgets: 2

                            },

                            {

                                widget_points: 3, num_widgets: 3

                            },

                            {

                                widget_points: 4, num_widgets: 4

                            },

                            {

                                widget_points: 5, num_widgets: 5

                            },

                        ],

                        color: "#ff0000"

                    },

                ],

                properties: {

                    title: 'Example Chart Data'

                }

            };

        this.total = 1;

    },

    renderChart: function () {

        if (!this.isChartReady()) {

            return;

        }

        d3.select(this.el).select('svg')

            .datum(this.chartData)

            .transition().duration(500)

            .call(this.chart);

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

    }

})

Run a Quick Repair & Rebuild when finished. If you put the view in the layout described above, then you can see it by navigating to the following URL.

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

and something like the following will appear:

Look at that beautiful chart!

You, of course, are not limited in number of lines, colors that are used, or even to line charts alone. Users love charts, so I recommend spending some time to experiment with Sugar's chart engine to see what they can do for you.