Highcharts can be extended through plugins that allow us to add functionality without disturbing the core layer of code and are easy to share. There is a library of plugins available online contributed by Highcharts staff and other users at http://www.highcharts.com/plugin-registry. One distinct advantage of developing features out of plugins is that we can pick and choose the plugin features and build a compressed JavaScript library from them. In fact, we can already do something similar with the Highcharts library on the download page.
In this section, we will take a tour of a couple of plugins that you may find handy.
When we create a scatter plot with lots of data points, it is often worthwhile to overlay them with a regression line. Of course, we can always achieve this by adding a line series manually. However, we still need to write the code for regression analysis. It is much more convenient to include a plugin. The Highcharts regression plugin created by Ignacio Vazquez does the job nicely. First, we include the plugin:
<script src="http://rawgithub.com/phpepe/highcharts-regression/master/highcharts-regression.js"> </script>
Then, we create our scatter chart as usual. Since we include the regression plugin, it provides additional regression options:
series: [{ regression: true , regressionSettings: { type: 'linear', color: 'rgba(223, 83, 83, .9)' }, name: 'Female', color: 'rgba(223, 83, 83, .5)', data: [ [161.2, 51.6], [167.5, 59.0], ....
Here is the chart from the demo (http://www.highcharts.com/plugin-registry/single/22/Highcharts%20regression):
Here is another remarkable plugin by Torstein that enables chart viewers to drag any series data points. We import the plugin with the following line:
<script src="http://rawgithub.com/highslide-software/draggable-points/master/draggable-points.js"></script>
This plugin brings two new point events, drag
and drop
, which we can define the handlers for via the plotOptions.series.point.events
option (or the events
option in a data point object). Here is the example code from the demo:
events: { drag: function(e) { // Update new data value in 'drag' div box $('#drag').html( 'Dragging <b>' + this.series.name + '</b>, <b>' + this.category + '</b> to <b>' + Highcharts.numberFormat(e.newY, 2) + '</b>' ); }, drop: function() { $('#drop').html( 'In <b>' + this.series.name + '</b>, <b>' + this.category + '</b> was set to <b>' + Highcharts.numberFormat(this.y, 2) + '</b>' ); } }
When we select a data point and move the mouse, a drag event is triggered and the demo code will update the textbox below the chart, as seen in the following screenshot. The plugin provides several new options to control how we can drag and drop the data points. The following is a usage example:
series: [{ data: [0, 71.5, 106.4, .... ], draggableY: true, dragMinY: 0, type: 'column', minPointLength: 2 }, { data: [0, 71.5, 106.4, ....], draggableY: true, dragMinY: 0, type: 'column', minPointLength: 2 }, {
The Boolean option draggableX/Y
notifies which direction the data points can be dragged in. Furthermore, the drag range can be limited by the dragMinX/Y
and dragMaxX/Y
options. The following screenshot shows a column being dragged:
So far, we have seen the effect of two individual plugins. It's time for us to create a new user experience by loading these two plugins and combining their effects. The idea is to create a regression chart with movable data points, so that the regression line automatically adjusts in real time as we drag a data point. When doing so, we need to slightly modify the regression plugin code. Here is part of the original code:
(function (H) { H.wrap(H.Chart.prototype, 'init', function (proceed) { .... for (i = 0 ; i < series.length ; i++){ var s = series[i]; if ( s.regression && !s.rendered ) { // Create regression series option var extraSerie = { .... }; // Compute regression based on the type if (regressionType == "linear") { regression = _linear(s.data) ; extraSerie.type = "line"; } } } // Append to series configuration array .... }); function _linear(data) { .... } .... })(Highcharts);
Basically, before the chart is created and rendered, the plugin scans the series data, computes the regression result, and formats the result into a line series option. To do that, the regression implementation is included as part of the init
method for the Chart
class, which is called when a Chart
object is created. To extend an existing function in Highcharts, we call the wrap
function on a method inside the object's prototype. In other words, when a Chart
object is created, it will call the init
function, which executes each function stacked internally (closure). We will further investigate this subject later.
For the purpose of updating the regression line at runtime, we need the ability to call _linear
from outside the plugin. Here is a pseudo code of the new modification to add a new method, updateRegression
:
(function (H) { H.wrap(H.Chart.prototype, 'init', function (proceed) { .... }); H.Chart.prototype.updateRegression = function(point) { // Get the series from the dragged data point var series = point.series; var chart = series.chart; // Get the regression series associated // with this data series var regressSeries = chart.series[series.regressIdx]; // Recompute based on the regression type and // update the series if (series.regressionType == "linear") { regression = _linear(series.data) ; } regressSeries.update(regression, true); }; function _linear(data) { .... } .... })(Highcharts);
Now we have a regression plugin with an accessible method, updateRegression
, to call the inner scope function _linear
. With this new plugin function, we can link the functionality with the drag
event exported by the draggable plugin:
plotOptions: { series: { point: { events: { drag: function(e) { // Pass the dragged data point to the // regression plugin and update the // regression line Highcharts.charts[0] .updateRegression(e.target); } } } } }
In order to observe the new effect more clearly, we use a smaller set of scatter plots. Here is the series configuration with both plugin options:
series: [{ regression: true , regressionSettings: { type: 'linear', color: 'rgba(223, 83, 83, .9)' }, draggableX: true, draggableY: true, name: 'Female', color: 'rgba(223, 83, 83, .5)', data: [ [161.2, 51.6], [167.5, 59.0], [159.5, 49.2], [157.0, 63.0], [155.8, 53.6], [170.0, 59.0], [159.1, 47.6], [166.0, 69.8], [176.2, 66.8], ....
In the configuration, we have the scatter points draggable in both x and y directions and the regression type is linear. Let's load our new improved chart. The following is the initial screen:
Let's hypothetically assume an overactive, unapproved slim-fast drug, "mouse down", has slipped onto the market, which has some unreported side effects. The unfortunate ones will shoot up and the really unfortunate ones have their heights gravitate. Here is the outcome of the new result:
The regression line responds in real time as well as updating the top-left legend box as we mouse down those weights at the far-right data points.
Some users create a plugin because certain tasks cannot be fulfilled by the API and the task is generic enough that it will be beneficial to other chart users. However, there is no standard API to create a plugin; developers have to be hands-on with their knowledge of Highcharts code. Nonetheless, there are a few guidelines that we can generalize from existing plugins.
Always implement the plugin within a self-invoking anonymous function with Highcharts as the parameter. A self-invoking anonymous function is a pretty common technique in JavaScript. All the Highcharts plugins are implemented in this style. The following code shows an example:
(function (Highcharts) { .... // what happens in anonymous function, // stays in anonymous function function hangoverIn(place) { if (place === 'home') { return 'phew'; } else if (place === 'hospital') { return 'ouch'; } else if (place === 'vegas') { return 'aaahhhhh!!'; } } })(Highcharts);
None of the named functions and variables declared in the plugin are accessible externally because they are declared within the scope of a self-invoking anonymous function (closure and module pattern). Hence, the implementation is private to the outside world unless we assign properties in the Highcharts namespace.
Depending on the plugin task, some plugins need to extend an existing function. For instance, the regression plugin calls H.wrap
to extend the init
function, which is called from the Chart
constructor. See the following code:
(function (H) { H.wrap(H.Chart.prototype, 'init', function (proceed) { // Plugin specific code for processing // chart configuration .... // Must include this instruction for wrap // method to work proceed.apply(this, Array.prototype.slice.call(arguments, 1)); } ); })(Highcharts);
Highcharts.wrap
is a commonly used function within plugins. The way wrap
functions work is to overwrite the init
function with a new function body that includes the previous implementation proceed
. When we extend the method with our new plugin code in an anonymous function, we have to accept the proceed
argument, which represents the parent function body. Before or after our plugin code, we must call proceed.apply
on the same arguments in order to complete the chain of executions.
For reference, we can always extend methods for a particular series, for example, Highcharts.seriesTypes.column.prototype
, where seriesTypes
is an object containing all the series classes. Alternatively, if the plugin needs to be set up for all the series, we can invoke the wrap
method on Highcharts.Series.prototype
instead (all series classes are extended from Highcharts.Series
).
Sometimes we may need to export specific methods for a plugin. To do so, we should always define new methods inside the prototype
property, such as:
(function (H) { H.Chart.prototype.pluginMethod = function(x, y) { .... }; })(Highcharts);
This is because any code declared within the anonymous function is not accessible from the outside. Therefore, the only way to create a callable method is bound to the object passed to the anonymous function, which is the top level Highcharts object in this case. The prototype
property is the standard way in JavaScript to inherit properties and methods from objects. The reason for attaching the method within the prototype
property is because we don't know how developers will use the plugin. For instance, they may decide to create a new Chart
object and call the plugin method. In such cases, the plugin code will still work.
Another type of action for plugins is to define new events, as we saw in the draggable plugin. Here is the problem: we need access to a chart element to bind the event handler, that is, after the chart is rendered. However, the class init
method is executed prior to the chart being rendered. This is where the Highcharts.Chart.prototype.callbacks
array comes in. It is a place designed to store external functions that require access to elements. For example, the exporting module uses this array to insert buttons into the chart. Here is some pseudo code to set up events:
(function (H) { Highcharts.Chart.prototype.callbacks.push(function (chart) { // A DOM element, e.g. chart, container, document, var element = chart.container; // DOM event type: mousedown, touchstart, etc var eventName = 'mousedown'; // We can call fireEvent to fire a new event // defined in this plugin fireEvent(element, newEvent, evtArgs, function(args) { .... }); // Plugin event function handler(e) { // Plugin code ... } // Bind specific event to the defined handler Highcharts.addEvent(element, eventName, handler); }); })(Highcharts);
Highcharts has two event-related methods: addEvent
and fireEvent
. The former method is to bind an event to a chart element with a handler, whereas fireEvent
triggers an event on an element.
The preceding code basically creates an anonymous function that organizes all the event(s) setup, such as defining the handler(s) and binding them to elements. The function must accept chart
as the only parameter. Finally, we append the function into the callbacks
array. The function will be automatically executed once the chart is fully rendered.
3.128.205.21