Skip to content

June 9, 2013

5

Highcharts: Enhancing User Interaction on Pie/Donut Charts – Dynamic Connector

by Joe Kuan

Donut chart with dynamic connectorRecently, I got a request from a Highcharts user asking me how to improve the user interaction on pie charts; when the pointer is hovered over a slice:

1. An arrow appears outside the pie section
2. A text box appears in the center of the donut charts showing the hovered section data

I think this is a good exercise and demonstration to show how flexible and powerful Highcharts can be. In this blog, I will show you how to convert a normal donut chart into the one showing here and you can try the online demo.

First, let starts with a normal donut chart. The following screenshot shows the a default pie chart. The demo code can be found in here.

Donut chart with default connector

As you can see, there are curve lines extending from each pie section to the data label. In Highcharts, these lines are called, connectors. Internally, a connector is represented as an object with properties and methods. Later we will see how flexible is Highcharts that we can manipulate these connector objects.

Pie chart connector

Our next goal is to implement an interactive connector that appears when the pointer is hovered over a pie section. That means we need to implement the mouseOver and mouseOut event callbacks and lets dig down to see what inside the point object when we call the callback.

plotOptions: {
    pie: {
        cursor: 'pointer',
        point: {
            events: {
                mouseOver: function () {
                    console.log(this);
                }
                .....

The keyword this is a Point object where the pointer is hovered over the data point. Lets launch a browser console and investigates what lies beneath this object.
Highcharts connector under Javascript console
As we can see inside the point object, there is a connector object embedded inside which contains properties and methods to generate SVG elements. Now we can use this object class to change the connector display and behavior. Lets make all the connectors hidden in the initial display and appears and disappears with mouse pointer actions. The followings are the changes made to plotOption.pie option:

plotOptions: {
   pie: {
       cursor: 'pointer',
       dataLabels: {
           connectorColor: 'white'
       },
       point: {
           events: {
               mouseOver: function () {
                   this.connector.attr('stroke', this.color);
                   this.connector.show();
               },
               mouseOut: function () {
                   this.connector.hide();
               }
           }
       }
   }
}

First, we initially set the connector color, connectorColor, to the same as the background color. Unfortunately, we cannot disable the connector by setting the connectorWidth to 0 because this will remove the connector object inside the callback handler. The mouseOver callback simply sets the SVG stroke property according to the pie section’s color and displays it. The following screenshot shows the first prototype of dynamic connector and you can click here to view the demo:

Highcharts: Prototype of Dynamic connector

Our next task is to change the shape of a connector from line to arrow. Here is the code for mouseOver callback:

mouseOver: function() {

    if (!this.connector.dynamicConnector) {
        // Size of the triangle
        var width = 16, height = 24;

        // Extract the connector start position
       var dArr = this.connector.d.split(' ');
       var startY = dArr.pop(), startX = dArr.pop();
       var start = [parseFloat(startX), parseFloat(startY)];

       // Replace the connector line into a SVG triangle
       var path = 'M ' + start[0] + ' ' + start[1] + 'L ' +
               (start[0] + height) + ' ' + (start[1] - (width / 2)) +
               ' L ' + (start[0] + height) + ' ' +
               (start[1] + (width / 2)) + ' L ' +
               start[0] + ' ' + start[1];
       this.connector.attr('d', path);

       // Convert the section angle from radian to
       // degree and apply to the trangle
       // Setup the rotation, x, y fields which are
       // required by the updateTransform method
       this.connector.rotation = (this.angle * 180) / Math.PI;
       this.connector.x = startX;
       this.connector.y = startY;
       this.connector.updateTransform();

       // Beautify the triangle with border
       this.connector.attr('stroke', this.color);
       this.connector.attr('fill',
            Highcharts.Color(this.color).brighten(0.2).get());

       this.connector.dynamicConnector = true;
    }
    this.connector.show();
},

We first extract the starting coordinate from the original connector SVG path. Then we reconstruct the SVG path, d, into a triangle using that coordinate. Next, we set the triangle’s rotation based on the pie section angle and assign x and y fields as the center of rotation. After that we call the updateTransform method to perform the rotation on the triangle. Finally, we prettify the arrow with a border color. Here is the screenshot of the final prototype and you can see the online demo here:

Dynamic connector - arrow shape

Our last task is to put the tooltip into the middle of the donut chart. This can be achieved by using tooltip.positioner option:

tooltip: {
    valueSuffix: '%',
    positioner: function () {
        return {
            // Position the tooltip into the center of
            // of the series, i.e. center of the pie chart
            // 8 is the default tooltip padding
            x: this.chart.series[0].center[0] -
               (this.label.width / 2) + 8,
            y: this.chart.series[0].center[1] -
               (this.label.height / 2) + 8
        };
    }
},

Here is the final screenshot and the demo link:

Dynamic connector with centralise tooltip

5 Comments Post a comment
  1. Andres Kane
    Jul 5 2013

    I have a problem when I use doble pie
    the pie inside over the chart outside
    example in http://jsfiddle.net/JoeKuan/6NJcM/ (I repeat the serie to prove)

    code

    $(function () {
    $(document).ready(function () {
    var chart = null;

    var colors = Highcharts.getOptions().colors,
    categories = [‘MSIE’, ‘Firefox’, ‘Chrome’, ‘Safari’, ‘Opera’],
    name = ‘Browser brands’,
    data = [{
    y: 55.11,
    color: colors[0],
    drilldown: {
    name: ‘MSIE versions’,
    categories: [‘MSIE 6.0’, ‘MSIE 7.0’, ‘MSIE 8.0’, ‘MSIE 9.0’],
    data: [10.85, 7.35, 33.06, 2.81],
    color: colors[0]
    }
    }, {
    y: 21.63,
    color: colors[1],
    drilldown: {
    name: ‘Firefox versions’,
    categories: [‘Firefox 2.0’, ‘Firefox 3.0’, ‘Firefox 3.5’, ‘Firefox 3.6’, ‘Firefox 4.0’],
    data: [0.20, 0.83, 1.58, 13.12, 5.43],
    color: colors[1]
    }
    }, {
    y: 11.94,
    color: colors[2],
    drilldown: {
    name: ‘Chrome versions’,
    categories: [‘Chrome 5.0’, ‘Chrome 6.0’, ‘Chrome 7.0’, ‘Chrome 8.0’, ‘Chrome 9.0’,
    ‘Chrome 10.0’, ‘Chrome 11.0’, ‘Chrome 12.0’],
    data: [0.12, 0.19, 0.12, 0.36, 0.32, 9.91, 0.50, 0.22],
    color: colors[2]
    }
    }, {
    y: 7.15,
    color: colors[3],
    drilldown: {
    name: ‘Safari versions’,
    categories: [‘Safari 5.0’, ‘Safari 4.0’, ‘Safari Win 5.0’, ‘Safari 4.1’, ‘Safari/Maxthon’,
    ‘Safari 3.1’, ‘Safari 4.1’],
    data: [4.55, 1.42, 0.23, 0.21, 0.20, 0.19, 0.14],
    color: colors[3]
    }
    }, {
    y: 2.14,
    color: colors[4],
    drilldown: {
    name: ‘Opera versions’,
    categories: [‘Opera 9.x’, ‘Opera 10.x’, ‘Opera 11.x’],
    data: [0.12, 0.37, 1.65],
    color: colors[4]
    }
    }];

    // Build the data array
    var browserData = [];
    for (var i = 0; i < data.length; i++) {

    // add browser data
    browserData.push({
    name: categories[i],
    y: data[i].y,
    color: data[i].color
    });

    }

    // Create the chart
    chart = new Highcharts.Chart({
    chart: {
    renderTo: 'container',
    type: 'pie'
    },
    title: {
    text: null
    },
    series: [{
    name: 'Browsers',
    data: browserData,
    innerSize: '40%'
    },{
    name: 'categories',
    data: browserData,
    size: '40%'
    }],

    tooltip: {
    valueSuffix: '%',
    positioner: function () {
    return {
    x: this.chart.series[0].center[0] – (this.label.width / 2) + 8,
    y: this.chart.series[0].center[1] – (this.label.height / 2) + 8
    };
    }
    },
    plotOptions: {
    pie: {
    cursor: 'pointer',
    dataLabels: {
    connectorColor: 'white'
    },
    point: {
    events: {
    mouseOver: function () {

    if (!this.connector.dynamicConnector) {
    var width = 16,
    height = 24;
    // Extract the connector start position
    var dArr = this.connector.d.split(' ');
    var startY = dArr.pop(),
    startX = dArr.pop();
    var start = [parseFloat(startX), parseFloat(startY)];
    // Construct the triangle
    var path = 'M ' + start[0] + ' ' + start[1] + 'L ' + (start[0] + height) + ' ' + (start[1] – (width / 2)) + ' L ' + (start[0] + height) + ' ' + (start[1] + (width / 2)) + ' L ' + start[0] + ' ' + start[1];

    // Convert the section angle from radian to degree and apply to the trangle
    // Setup rotation, x, y required by the updateTransform method
    this.connector.rotation = (this.angle * 180) / Math.PI;
    this.connector.x = startX;
    this.connector.y = startY;
    this.connector.updateTransform();

    this.connector.attr('stroke', this.color);
    this.connector.attr('fill', Highcharts.Color(this.color).brighten(0.2).get());
    this.connector.attr('d', path);

    this.connector.dynamicConnector = true;
    }
    this.connector.show();
    },
    mouseOut: function () {
    this.connector.hide();
    }
    }
    }
    }
    }
    });
    });
    });

    Reply
  2. Carlos Jose
    Aug 11 2013

    Hello joe
    I have a problem, for days, I’m trying to make a chart with columns, but I have a problem chart becomes small, and I could not fix it.

    http://social.msdn.microsoft.com/Forums/en-US/369a5f43-0fe2-4249-a034-314094dff51c/controls-highcharts-problemas-con-el-tamao-de-los-graficos

    I appreciate your help, this could save my job

    Reply
    • Joe Kuan
      Aug 20 2013

      Sorry for the very late reply. I am currently on leave until the end of this week. Please let me know whether you still need help on that issue.

      Reply
      • Carlos Jose
        Aug 21 2013

        Thanks, I managed to fix it, now I’m trying to make a 3d cake. I appreciate your help again thank you very much

  3. deepak
    Nov 17 2014

    Hi Joe Kuan,

    I checked your code here http://jsfiddle.net/JoeKuan/6NJcM/, its not working, show only one bar arrow, can you help me hot to fix this.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments

%d bloggers like this: