/**
|
* @license Highcharts JS v3.0.6 (2013-10-04)
|
* Prototype adapter
|
*
|
* @author Michael Nelson, Torstein Hønsi.
|
*
|
* Feel free to use and modify this script.
|
* Highcharts license: www.highcharts.com/license.
|
*/
|
|
// JSLint options:
|
/*global Effect, Class, Event, Element, $, $$, $A */
|
|
// Adapter interface between prototype and the Highcharts charting library
|
var HighchartsAdapter = (function () {
|
|
var hasEffect = typeof Effect !== 'undefined';
|
|
return {
|
|
/**
|
* Initialize the adapter. This is run once as Highcharts is first run.
|
* @param {Object} pathAnim The helper object to do animations across adapters.
|
*/
|
init: function (pathAnim) {
|
if (hasEffect) {
|
/**
|
* Animation for Highcharts SVG element wrappers only
|
* @param {Object} element
|
* @param {Object} attribute
|
* @param {Object} to
|
* @param {Object} options
|
*/
|
Effect.HighchartsTransition = Class.create(Effect.Base, {
|
initialize: function (element, attr, to, options) {
|
var from,
|
opts;
|
|
this.element = element;
|
this.key = attr;
|
from = element.attr ? element.attr(attr) : $(element).getStyle(attr);
|
|
// special treatment for paths
|
if (attr === 'd') {
|
this.paths = pathAnim.init(
|
element,
|
element.d,
|
to
|
);
|
this.toD = to;
|
|
|
// fake values in order to read relative position as a float in update
|
from = 0;
|
to = 1;
|
}
|
|
opts = Object.extend((options || {}), {
|
from: from,
|
to: to,
|
attribute: attr
|
});
|
this.start(opts);
|
},
|
setup: function () {
|
HighchartsAdapter._extend(this.element);
|
// If this is the first animation on this object, create the _highcharts_animation helper that
|
// contain pointers to the animation objects.
|
if (!this.element._highchart_animation) {
|
this.element._highchart_animation = {};
|
}
|
|
// Store a reference to this animation instance.
|
this.element._highchart_animation[this.key] = this;
|
},
|
update: function (position) {
|
var paths = this.paths,
|
element = this.element,
|
obj;
|
|
if (paths) {
|
position = pathAnim.step(paths[0], paths[1], position, this.toD);
|
}
|
|
if (element.attr) { // SVGElement
|
|
if (element.element) { // If not, it has been destroyed (#1405)
|
element.attr(this.options.attribute, position);
|
}
|
|
} else { // HTML, #409
|
obj = {};
|
obj[this.options.attribute] = position;
|
$(element).setStyle(obj);
|
}
|
|
},
|
finish: function () {
|
// Delete the property that holds this animation now that it is finished.
|
// Both canceled animations and complete ones gets a 'finish' call.
|
if (this.element && this.element._highchart_animation) { // #1405
|
delete this.element._highchart_animation[this.key];
|
}
|
}
|
});
|
}
|
},
|
|
/**
|
* Run a general method on the framework, following jQuery syntax
|
* @param {Object} el The HTML element
|
* @param {String} method Which method to run on the wrapped element
|
*/
|
adapterRun: function (el, method) {
|
|
// This currently works for getting inner width and height. If adding
|
// more methods later, we need a conditional implementation for each.
|
return parseInt($(el).getStyle(method), 10);
|
|
},
|
|
/**
|
* Downloads a script and executes a callback when done.
|
* @param {String} scriptLocation
|
* @param {Function} callback
|
*/
|
getScript: function (scriptLocation, callback) {
|
var head = $$('head')[0]; // Returns an array, so pick the first element.
|
if (head) {
|
// Append a new 'script' element, set its type and src attributes, add a 'load' handler that calls the callback
|
head.appendChild(new Element('script', { type: 'text/javascript', src: scriptLocation}).observe('load', callback));
|
}
|
},
|
|
/**
|
* Custom events in prototype needs to be namespaced. This method adds a namespace 'h:' in front of
|
* events that are not recognized as native.
|
*/
|
addNS: function (eventName) {
|
var HTMLEvents = /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
|
MouseEvents = /^(?:click|mouse(?:down|up|over|move|out))$/;
|
return (HTMLEvents.test(eventName) || MouseEvents.test(eventName)) ?
|
eventName :
|
'h:' + eventName;
|
},
|
|
// el needs an event to be attached. el is not necessarily a dom element
|
addEvent: function (el, event, fn) {
|
if (el.addEventListener || el.attachEvent) {
|
Event.observe($(el), HighchartsAdapter.addNS(event), fn);
|
|
} else {
|
HighchartsAdapter._extend(el);
|
el._highcharts_observe(event, fn);
|
}
|
},
|
|
// motion makes things pretty. use it if effects is loaded, if not... still get to the end result.
|
animate: function (el, params, options) {
|
var key,
|
fx;
|
|
// default options
|
options = options || {};
|
options.delay = 0;
|
options.duration = (options.duration || 500) / 1000;
|
options.afterFinish = options.complete;
|
|
// animate wrappers and DOM elements
|
if (hasEffect) {
|
for (key in params) {
|
// The fx variable is seemingly thrown away here, but the Effect.setup will add itself to the _highcharts_animation object
|
// on the element itself so its not really lost.
|
fx = new Effect.HighchartsTransition($(el), key, params[key], options);
|
}
|
} else {
|
if (el.attr) { // #409 without effects
|
for (key in params) {
|
el.attr(key, params[key]);
|
}
|
}
|
if (options.complete) {
|
options.complete();
|
}
|
}
|
|
if (!el.attr) { // HTML element, #409
|
$(el).setStyle(params);
|
}
|
},
|
|
// this only occurs in higcharts 2.0+
|
stop: function (el) {
|
var key;
|
if (el._highcharts_extended && el._highchart_animation) {
|
for (key in el._highchart_animation) {
|
// Cancel the animation
|
// The 'finish' function in the Effect object will remove the reference
|
el._highchart_animation[key].cancel();
|
}
|
}
|
},
|
|
// um.. each
|
each: function (arr, fn) {
|
$A(arr).each(fn);
|
},
|
|
inArray: function (item, arr, from) {
|
return arr ? arr.indexOf(item, from) : -1;
|
},
|
|
/**
|
* Get the cumulative offset relative to the top left of the page. This method, unlike its
|
* jQuery and MooTools counterpart, still suffers from issue #208 regarding the position
|
* of a chart within a fixed container.
|
*/
|
offset: function (el) {
|
return $(el).cumulativeOffset();
|
},
|
|
// fire an event based on an event name (event) and an object (el).
|
// again, el may not be a dom element
|
fireEvent: function (el, event, eventArguments, defaultFunction) {
|
if (el.fire) {
|
el.fire(HighchartsAdapter.addNS(event), eventArguments);
|
} else if (el._highcharts_extended) {
|
eventArguments = eventArguments || {};
|
el._highcharts_fire(event, eventArguments);
|
}
|
|
if (eventArguments && eventArguments.defaultPrevented) {
|
defaultFunction = null;
|
}
|
|
if (defaultFunction) {
|
defaultFunction(eventArguments);
|
}
|
},
|
|
removeEvent: function (el, event, handler) {
|
if ($(el).stopObserving) {
|
if (event) {
|
event = HighchartsAdapter.addNS(event);
|
}
|
$(el).stopObserving(event, handler);
|
} if (window === el) {
|
Event.stopObserving(el, event, handler);
|
} else {
|
HighchartsAdapter._extend(el);
|
el._highcharts_stop_observing(event, handler);
|
}
|
},
|
|
washMouseEvent: function (e) {
|
return e;
|
},
|
|
// um, grep
|
grep: function (arr, fn) {
|
return arr.findAll(fn);
|
},
|
|
// um, map
|
map: function (arr, fn) {
|
return arr.map(fn);
|
},
|
|
// extend an object to handle highchart events (highchart objects, not svg elements).
|
// this is a very simple way of handling events but whatever, it works (i think)
|
_extend: function (object) {
|
if (!object._highcharts_extended) {
|
Object.extend(object, {
|
_highchart_events: {},
|
_highchart_animation: null,
|
_highcharts_extended: true,
|
_highcharts_observe: function (name, fn) {
|
this._highchart_events[name] = [this._highchart_events[name], fn].compact().flatten();
|
},
|
_highcharts_stop_observing: function (name, fn) {
|
if (name) {
|
if (fn) {
|
this._highchart_events[name] = [this._highchart_events[name]].compact().flatten().without(fn);
|
} else {
|
delete this._highchart_events[name];
|
}
|
} else {
|
this._highchart_events = {};
|
}
|
},
|
_highcharts_fire: function (name, args) {
|
var target = this;
|
(this._highchart_events[name] || []).each(function (fn) {
|
// args is never null here
|
if (args.stopped) {
|
return; // "throw $break" wasn't working. i think because of the scope of 'this'.
|
}
|
|
// Attach a simple preventDefault function to skip default handler if called
|
args.preventDefault = function () {
|
args.defaultPrevented = true;
|
};
|
args.target = target;
|
|
// If the event handler return false, prevent the default handler from executing
|
if (fn.bind(this)(args) === false) {
|
args.preventDefault();
|
}
|
}
|
.bind(this));
|
}
|
});
|
}
|
}
|
};
|
}());
|