Highcharts Tips – Accessing chart object from container id

Given the following container for Highcharts, how do you access the appropriate chart object from the container’s id?

<div id="containerId"></div>

Let’s see how we can get a handle of the chart object to display the title

Highcharts 3.0.1+

Highcharts 3.0.1 has made it fairly straightforward like most jQuery plugins

var chart=$("#containerId").highcharts();

Highcharts 2.3.4+

For Highcharts 2.3.4+ the array Highcharts.charts could be used in conjunction with the data-highcharts-chart attribute to get the position of the chart in the array

var index=$("#containerId").data('highchartsChart');
var chart=Highcharts.charts[index];

Highcharts.charts    

An array containing the current chart objects in the page. A chart’s position in the array is preserved throughout the page’s lifetime. When a chart is destroyed, the array item becomes undefined.

If you have only a single chart on your page, you can smartly predict the index to be 0 and skip the first step

var chart=Highcharts.charts[0];

All Versions

For versions before 2.3.4 you would need to track/manage the object yourself. We could use a map/object to store the charts by Id

var window.charts={}; 
function foo(){
  new Highcharts.Chart({...},function(chart){  
      window.charts[chart.options.chart.renderTo] = chart;
  });
}

function bar(){
  var chart=window.charts["containerId"];
}

If you have only a single chart on the page, you could simplify to just a window.chart  object

function foo(){
   window.chart=new Highcharts.Chart({...});
}

function bar(){
   var myChart=window.chart;
}

References

Understanding Highcharts – Regular/Discrete Series Data

This is a part of the Understanding Highcharts – Series Data blog series

Let us first define what we mean by discrete or regular to set the context on this blog

  1. The x-axis (independent axis) is numerical
  2. The x-axis can be extended infinitely on either side, but sometimes can be bound by a mix & max
  3. Discrete: The x value of a points can’t be any number, but a certain (yet infinitely large) subset of the number system
  4. Regular: The difference between the x-values of any two given points is always an integral multiple of a fixed interval

Mathematically, all the x values can be formulated as follows

x = reference_x + N * fixed_interval

Where

reference_x

is the x value of a pre-defined reference point in the series, this can be the first point, last point or any other point in the series. N is an integer, can be positive or negative.

fixed_interval

is a fixed float. The values of

reference_x

&

fixed_interval

are static across all the points and only the N value varies from point to point. Integers & Natural numbers are classic examples of such series with

fixed_interval=1

Theoretically, for any series if we were to reduce the

fixed_interval

to a very small number, we would always be able fit all data points into the above formula. But in most cases we would not have y-values for all corresponding

N

values for such small

fixed_interval

Let’s say we are driving a car and wish to track the fuel consumption & time taken along our journey. For this experiment, we may choose a fixed interval after which we take a reading, let’s take a reading after every 50 kilometers. Initially, my odometer reads 10000 km and fuel tank is 90%. At the end of the journey I have the following readings

Odometer (km) Fuel Consumed (ltr.) Time Taken (mins.)
10050 7.2 110
10100 7.76 120
10150 8.96 115
10200 3.92 89
10250 7.76 122.6
10300 8.4 117

It can be easily inferred that

fixed_interval = 50 & reference_x = 10000

In fact the

reference_x

could have been any x value in the series, just that the value of

N

for points before it would be negative if we were to choose any point other than the start point. More importantly, Highcharts makes life easy if we choose the first point as reference, we will see how to do it next. Generally, although not always, there would be not more than one y value corresponding to each value of x.

Regular/Discrete Series Data in Highcharts

It is easy to notice the pattern of the x values in such series. Given the first point and the interval, the Nth x-value can be calculated & Highcharts offers doing this calculation for you! The series options of Highcharts has following two special properties
plotOptions.series.pointStart

If no x values are given for the points in a series, pointStart defines on what value to start. On a datetime X axis, the number will be given as milliseconds since 1970-01-01, for exampleDate.UTC(2011, 0, 1). Defaults to 0.

plotOptions.series.pointInterval

If no x values are given for the points in a series, pointInterval defines the interval of the x values in milliseconds. For example, if a series contains one value each day, set pointInterval to 24 * 3600 * 1000. Defaults to 1

If the above two properties are set in the series options, we can simply skip the x-values in the data and just provide the y values and let Highcharts take care of the rest.
series.data

1) A list of numerical values. In this case, the numerical values will be interpreted as y values, and x values will be automatically calculated, either starting at 0 and incrementing by 1, or from pointStart and pointInterval given in the plotOptions. This option is not available for series types with more than one value per point, like area range or OHLC.

Example:
data: [0, 5, 3, 5]

Let us see how to handle the above example in Highcharts

series: [{
    name: 'Fuel Consumed',
    pointStart: 10000,
    pointInterval: 100,
    tooltip: {
        valueSuffix: ' ltrs.'
    },
    data: [0, 7.2, 7.76, 8.96, 3.92, 7.76, 8.4]
}, {
    name: 'Time Taken',
    pointStart: 10000,
    pointInterval: 100,
    data: [0, 110, 120, 115, 89, 122.6, 117],
    tooltip: {
        valueSuffix: ' mins.'
    },
    yAxis: 1
}]

Limitations

In this approach we are forced to put one and only one value for each possible x value. Since Highcharts calculates the x values by itself, we have very limited control over it.

Say, we did not have valid data for one of the x values? Or one of the x-values is itself not valid? Had we specified the x-values ourselves, we could easily skip the particular data point. We do have an option to mark the y-value as

null

for missing points, but this will break the chart and may be undesired. If we just wish to skip the point but not break the chart, we would need to use the [x,y] approach.

data: [0, 7.2, 7.76, null, 3.92, 7.76, 8.4]

If we wanted to something more special, e.g. have multiple Y values for a given X, we may have to choose to treat the data as irregular and provide explicit x values for all points.

Read More @ Understanding Highcharts – Series Data blog series

Reference Links

Understanding Highcharts – Series Data

Getting back to basics, let us have a look at the the most fundamental aspect of a chart, the data. Diving deeper into the series.data option of Highcharts.

This is a series of posts dealing with the following topics

Some of the topics are tentative, leave feedback if you want something more covered

Understanding Highcharts – Categorized Series Data

This is a part of the Understanding Highcharts – Series Data blog series

Categories are a bunch of strings that sit on your independent (generally x) axis. Say, for plotting average temperature by country, the country names on the x-axis are categories. This is the simplest form of data, the points & x-axis labels are equally spaced horizontally.

Highcharts supports defining this kind of data in the following two ways

Method I – Using xAxis.categories

Defining the categories at once while defining the x-axis using the xAxis.categories option, and defining only the y values for each series

xAxis: {
	categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
},
series: [{
	name: 'Mumbai',
	data: [24.2, 24.6, 26.7, 28.6, 30.1, 29.0, 27.5, 27.2, 27.4, 28.2, 27.4, 25.6]
}, {
	name: 'New Delhi',
	data: [14.1, 16.9, 22.4, 28.6, 32.8, 33.8, 31.0, 29.8, 29.2, 26.0, 20.3, 15.4]
}]

Method II – Using {name,y} point format

If you are like me, you may not find the above approach very natural. The approach asked us to define all x-values (categories) first and then the y-values as another array, I prefer defining my points as (x,y) or something similar. Well, now we can do just that with Highcharts 3.0, which supports defining categories at the time of defining your data. The series.data now also takes an array of objects, with two properties viz. name and y. The name acts as the category. Additionally we need to instruct Highcharts to use the xAxis as a category axis as follows.

xAxis: {
	type: 'category'
},series: [{
	name: 'Mumbai',
	data: 	[{name:'Jan',y: 24.2},
		 {name:'Feb',y: 24.6},
		 {name:'Mar',y: 26.7},
		 {name:'Apr',y: 28.6},
		 {name:'May',y: 30.1},
		 {name:'Jun',y: 29.0},
		 {name:'Jul',y: 27.5},
		 {name:'Aug',y: 27.2},
		 {name:'Sep',y: 27.4},
		 {name:'Oct',y: 28.2},
		 {name:'Nov',y: 27.4},
		 {name:'Dec',y: 25.6}]
}]

Basic observation tells that the first method allows defining categories only once, hence if there are multiple series having same set of categories the first comes handy and prevents repetition & duplication. I would also use the first form if the set of categories were to be static, this would allow me to easily elevate the categories in a default option object. The second approach is more suited, in my opinion, for situations where the data and the categories are generated programmatically.

Read more @ Understanding Highcharts – Series Data blog series

Reference Links

Customizing Highcharts – Tooltip Visibility

Let’s play around with the Highcharts tooltip some more. Last time we saw how to customize the position of the Highcharts tooltip, today we shall look at how to work around the visibility of the tooltip.

The outcome of this exercise would be

The default behavior of the tooltip is to show up when the user hovers over a point, the tooltip then stays visible as long as the user is still interacting with the points on the chart. The tooltip fades out with a delay after the user moves outside the chart area. Let’s see how to prevent this fading out and always have the tooltip persist on the chart area even after the mouse has moved outside the chart area.

In the process, we shall also learn about Extending Highcharts. Let’s extend the Highcharts.Tooltip class using the very convenient Highcharts.wrap method.

JavaScript with its dynamic nature is extremely powerful when it comes to altering the behaviour of scripts on the fly. In Highcharts we created a utility called wrap, which wraps an existing prototype function (“method”) and allows you to add your own code before or after it.

The wrap function accepts the parent object as the first argument, the name of the function to wrap as the second, and a callback replacement function as the third. The original function is passed as the first argument to the replacement function, and original arguments follow after that.

Let’s get wrapping. We will create a quick Highcharts plugin that overrides the Highcharts.Tooltip.prototype.hide behavior. We want the tooltip to persist, in other words we don’t want the tooltip to hide. Let’s override the hide method with a no-op method.

(function (H) {
    H.wrap(H.Tooltip.prototype, 'hide', function (defaultCallback) {
        /*
            ░░░░░▄▄▄▄▀▀▀▀▀▀▀▀▄▄▄▄▄▄░░░░░░░
            ░░░░░█░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░▀▀▄░░░░
            ░░░░█░░░▒▒▒▒▒▒░░░░░░░░▒▒▒░░█░░░
            ░░░█░░░░░░▄██▀▄▄░░░░░▄▄▄░░░░█░░
            ░▄▀▒▄▄▄▒░█▀▀▀▀▄▄█░░░██▄▄█░░░░█░
            █░▒█▒▄░▀▄▄▄▀░░░░░░░░█░░░▒▒▒▒▒░█
            █░▒█░█▀▄▄░░░░░█▀░░░░▀▄░░▄▀▀▀▄▒█
            ░█░▀▄░█▄░█▀▄▄░▀░▀▀░▄▄▀░░░░█░░█░
            ░░█░░░▀▄▀█▄▄░█▀▀▀▄▄▄▄▀▀█▀██░█░░
            ░░░█░░░░██░░▀█▄▄▄█▄▄█▄████░█░░░
            ░░░░█░░░░▀▀▄░█░░░█░█▀██████░█░░
            ░░░░░▀▄░░░░░▀▀▄▄▄█▄█▄█▄█▄▀░░█░░
            ░░░░░░░▀▄▄░▒▒▒▒░░░░░░░░░░▒░░░█░
            ░░░░░░░░░░▀▀▄▄░▒▒▒▒▒▒▒▒▒▒░░░░█░
            ░░░░░░░░░░░░░░▀▄▄▄▄▄░░░░░░░░█░░
            */
    });
}(Highcharts));

For the minimalists, the following code also does the exact same thing

(function (H) {
    H.wrap(H.Tooltip.prototype, 'hide', function () {});
}(Highcharts));

Although, we have completely removed the hide functionality in the above example, we sometimes may want this to happen conditionally. The wrap method provides the original method as the first parameter too and we could use it for pre-processing, post-processing, conditional processing, etc.

(function (H) {
        H.wrap(H.Tooltip.prototype, 'hide', function (defaultCallback) {
            // Pre Processing
            if (/* .. */) {
                //Do Nothing (Do Not Hide)
            } else { // Call the default Hide Callback
                defaultCallback.apply(this);
            }
            // Post Processing
        });
    }(Highcharts));

If you noticed the demo carefully, you see the tooltip does persist but the tooltip does not come up till the user hovers over the chart once. We may want to force the showing of tooltip on load, let us see how to accomplish that.

The tooltip object has a refresh method on it, this method takes the points on which the tooltip shall show up. In case of a shared tooltip this argument would be an array, otherwise the method takes a single point as the argument. Following code would bring up the tooltip on the 3rd point on the 2nd series.

chart.tooltip.refresh(chart.series[1].points[2]);
// If the tooltip.shared=true, the parameter is an array of points
chart.tooltip.refresh([chart.series[1].points[2]]);

Invoking the above immediately after the chart instantiation shall show the tooltip on load, and clubbing with the previous plugin we shall have an ever persisting tooltip.

Here is the example we began with and see what can be accomplished by combining the techniques together

Reference Links

Customizing Highcharts – Tooltip Positioning

The Highcharts API Reference for tooltip.positioner reads

A callback function to place the tooltip in a default position. The callback receives three parameters: labelWidth, labelHeight and point, where point contains values for plotX and plotY telling where the reference point is in the plot area. Add chart.plotLeft and chart.plotTop to get the full coordinates. The return should be an object containing x and y values, for example { x: 100, y: 100 }.

However, positioner is much more than just the default position of the tooltip. It takes the following syntax

tooltip:{
    positioner:function(labelWidth, labelHeight, point){
        var tooltipX,tooltipY;
        // ... Calculations go here ... //
        return { x : tooltipX, y : tooltipY };
    }
}

Note the three arguments labelWidth, labelHeight & point at your disposal, these seem to be sufficient for most of the use cases to calculate a desired tooltip position. labelWidth and labelHeight are the width and height that your tooltip requires, hence you can use them for edge cases to adjust your tooltip and prevent it from spilling out of the chart or even worse getting clipped. Let us see an example of positioning the tooltip to the right of the point.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    tooltipX = point.plotX + chart.plotLeft + 20;
    tooltipY = point.plotY + chart.plotTop - 20;
    return {
        x: tooltipX,
        y: tooltipY
    };
}

Keen eyes would have noticed a problem while hovering over the December points, the tooltip goes outside the chart area. Let’s fix this using the labelWidth, this tells us how much space would the tooltip need. Using the labelWidth along side pointX we can find the far right of the tooltip and compare that with the plot’s width, if it goes outta bounds, we position the tooltip to the left instead. Try fiddling with the rightmost points on the series.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    if (point.plotX + labelWidth > chart.plotWidth) {
        tooltipX = point.plotX + chart.plotLeft - labelWidth - 20;
    } else {
        tooltipX = point.plotX + chart.plotLeft + 20;
    }
    tooltipY = point.plotY + chart.plotTop - 20;
    return {
        x: tooltipX,
        y: tooltipY
    };
}

Sometimes, all we may want is a static location for the tooltip, like one of the corners of the chart. At the same time, we might not want the tooltip to eclipse any datapoints. We could simply assign the bottom left corner (chart.plotLeft,chart.plotTop + chart.plotHeight – labelHeight) as the tooltip’s position, and make use of the all the three parameters to determine if the current point falls behind the tooltip’s default location, if so, move the tooltip to the top right of the point. Hover over any point and observe the tooltip stays at the corner, now hover over the point at the bottom left and see the tooltip give way to the point.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    if (point.plotX + chart.plotLeft < labelWidth && point.plotY + labelHeight > chart.plotHeight) {
        tooltipX = chart.plotLeft;
        tooltipY = chart.plotTop + chart.plotHeight - 2 * labelHeight - 10;
    } else {
        tooltipX = chart.plotLeft;
        tooltipY = chart.plotTop + chart.plotHeight - labelHeight;
    }
    return {
        x: tooltipX,
        y: tooltipY
    };
}

For further reading, the default tooltip positioner that comes with highcharts is interesting. One should be able to adapt this to the appropriate requirement (Source).

/**
 * Place the tooltip in a chart without spilling over
 * and not covering the point it self.
 */
getPosition: function (boxWidth, boxHeight, point) {

    var chart = this.chart,
        distance = this.distance,
        ret = {},
        swapped,
        first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop],
        second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft],
        // The far side is right or bottom
        preferFarSide = point.ttBelow || (chart.inverted && !point.negative) || (!chart.inverted && point.negative),
        /**
         * Handle the preferred dimension. When the preferred dimension is tooltip
         * on top or bottom of the point, it will look for space there.
         */
        firstDimension = function (dim, outerSize, innerSize, point) {
            var roomLeft = innerSize < point - distance,
                roomRight = point + distance + innerSize < outerSize,
                alignedLeft = point - distance - innerSize,
                alignedRight = point + distance;

            if (preferFarSide && roomRight) {
                ret[dim] = alignedRight;
            } else if (!preferFarSide && roomLeft) {
                ret[dim] = alignedLeft;
            } else if (roomLeft) {
                ret[dim] = alignedLeft;
            } else if (roomRight) {
                ret[dim] = alignedRight;
            } else {
                return false;
            }
        },
        /**
         * Handle the secondary dimension. If the preferred dimension is tooltip
         * on top or bottom of the point, the second dimension is to align the tooltip
         * above the point, trying to align center but allowing left or right
         * align within the chart box.
         */
        secondDimension = function (dim, outerSize, innerSize, point) {
            // Too close to the edge, return false and swap dimensions
            if (point < distance || point > outerSize - distance) {
                return false;

                // Align left/top
            } else if (point < innerSize / 2) {                 ret[dim] = 1;                 // Align right/bottom             } else if (point > outerSize - innerSize / 2) {
                ret[dim] = outerSize - innerSize - 2;
                // Align center
            } else {
                ret[dim] = point - innerSize / 2;
            }
        },
        /**
         * Swap the dimensions 
         */
        swap = function (count) {
            var temp = first;
            first = second;
            second = temp;
            swapped = count;
        },
        run = function () {
            if (firstDimension.apply(0, first) !== false) {
                if (secondDimension.apply(0, second) === false && !swapped) {
                    swap(true);
                    run();
                }
            } else if (!swapped) {
                swap(true);
                run();
            } else {
                ret.x = ret.y = 0;
            }
        };

    // Under these conditions, prefer the tooltip on the side of the point
    if (chart.inverted || this.len > 1) {
        swap();
    }
    run();

    return ret;

}

Read further about customizing the visibility of the tooltip @ Customizing Highcharts – Tooltip Visibility

Reference Links