dashboard
Version:
Create dashboards with gadgets on node.js
2,027 lines (1,779 loc) • 170 kB
JavaScript
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
* @license Highcharts JS v1.2.5 (2010-04-13)
*
* (c) 2010 Torstein Hønsi
*
* License: www.highcharts.com/license
*/
/*
* Roadmap
*
* 2.0 (summer 2010)
* - Rewritten rendering engine to SVG/VML
* - Save as image
* - Print chart - open in a popup and call window.print.
* - Improvements to the toolbar object to allow the two above
*
* 2.1 (summer/autumn 2010)
* - Logarithmic axis
* - Built-in table parser
* - Base value for column, bar and area series
* - Automatic margin size based on axis label size and legend. Anti-collision logic for labels.
* - Floating columns and bars
* - Candlestick charts
* - Stock charts?
* - Radar charts?
*
* 2.2 (autumn 2010)
* - Improve pies: shadow, better dataLabels, 3D view.
*
*/
(function() {
// encapsulated variables
var
// abstracts to make compiled code smaller
undefined,
doc = document,
win = window,
math = Math,
mathRound = math.round,
mathFloor = math.floor,
mathMax = math.max,
mathAbs = math.abs,
mathCos = math.cos,
mathSin = math.sin,
// some variables
userAgent = navigator.userAgent,
isIE = /msie/i.test(userAgent) && !win.opera,
isWebKit = /AppleWebKit/.test(userAgent),
styleTag,
canvasCounter = 0,
colorCounter,
symbolCounter,
symbolSizes = {},
idCounter = 0,
timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
garbageBin,
// some constants for frequently used strings
DIV = 'div',
ABSOLUTE = 'absolute',
RELATIVE = 'relative',
HIDDEN = 'hidden',
HIGHCHARTS_HIDDEN = 'highcharts-' + HIDDEN,
VISIBLE = 'visible',
PX = 'px',
// time methods, changed based on whether or not UTC is used
makeTime,
getMinutes,
getHours,
getDay,
getDate,
getMonth,
getFullYear,
setMinutes,
setHours,
setDate,
setMonth,
setFullYear,
// check for a custom HighchartsAdapter defined prior to this file
globalAdapter = win.HighchartsAdapter,
adapter = globalAdapter || {},
// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
// and all the utility functions will be null. In that case they are populated by the
// default adapters below.
each = adapter.each,
grep = adapter.grep,
map = adapter.map,
merge = adapter.merge,
hyphenate = adapter.hyphenate,
addEvent = adapter.addEvent,
fireEvent = adapter.fireEvent,
animate = adapter.animate,
getAjax = adapter.getAjax,
// lookup over the types and the associated classes
seriesTypes = {};
// the jQuery adapter
if (!globalAdapter && win.jQuery) {
var jQ = jQuery;
each = function(arr, fn) {
for (var i = 0, len = arr.length; i < len; i++) {
if (fn.call(arr[i], arr[i], i, arr) === false) {
return i;
}
}
};
grep = jQ.grep;
map = function(arr, fn){
//return jQuery.map(arr, fn);
var results = [];
for (var i = 0, len = arr.length; i < len; i++) {
results[i] = fn.call(arr[i], arr[i], i, arr);
}
return results;
}
merge = function(){
var args = arguments;
return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
}
hyphenate = function (str){
return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase() });
}
addEvent = function (el, event, fn){
jQ(el).bind(event, fn);
}
fireEvent = function(el, type, eventArguments, defaultFunction) {
var event = jQ.Event(type),
detachedType = 'detached'+ type;
extend(event, eventArguments);
// Prevent jQuery from triggering the object method that is named the
// same as the event. For example, if the event is 'select', jQuery
// attempts calling el.select and it goes into a loop.
if (el[type]) {
el[detachedType] = el[type];
el[type] = null;
}
// trigger it
jQ(el).trigger(event);
// attach the method
if (el[detachedType]) {
el[type] = el[detachedType];
el[detachedType] = null;
}
if (defaultFunction && !event.isDefaultPrevented()) {
defaultFunction(event);
}
}
animate = function (el, params, options) {
jQ(el).animate(params, options);
}
getAjax = function (url, callback) {
jQ.get(url, null, callback);
}
jQ.extend( jQ.easing, {
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
});
// the MooTools adapter
} else if (!globalAdapter && win.MooTools) {
each = $each;
map = function (arr, fn){
return arr.map(fn);
}
grep = function(arr, fn) {
return arr.filter(fn)
}
merge = $merge;
hyphenate = function (str){
return str.hyphenate();
}
addEvent = function (el, type, fn) {
// if the addEvent method is not defined, el is a custom Highcharts object
// like series or point
if (!el.addEvent) {
if (el.nodeName) el = $(el); // a dynamically generated node
else extend(el, new Events()); // a custom object
}
el.addEvent(type, fn);
}
fireEvent = function(el, event, eventArguments, defaultFunction) {
// create an event object that keeps all functions
event = new Event({
type: event,
target: el
});
event = extend (event, eventArguments);
// override the preventDefault function to be able to use
// this for custom events
event.preventDefault = function() {
defaultFunction = null;
}
// if fireEvent is not available on the object, there hasn't been added
// any events to it above
if (el.fireEvent) el.fireEvent(event.type, event);
// fire the default if it is passed and it is not prevented above
if (defaultFunction) defaultFunction(event);
}
animate = function (el, params, options){
var myEffect = new Fx.Morph($(el), extend(options, {
transition: Fx.Transitions.Quad.easeInOut
}));
myEffect.start(params);
}
getAjax = function (url, callback) {
(new Request({
url: url,
method: 'get',
onSuccess: callback
})).send();
}
}
/**
* Check if an element is an array, and if not, make it into an array. Like
* MooTools' $.splat.
*/
function splat(obj) {
if (!obj || obj.constructor != Array) obj = [obj];
return obj;
}
/**
* Returns true if the object is not null or undefined. Like MooTools' $.defined.
* @param {Object} obj
*/
function defined (obj) {
return obj !== undefined && obj !== null;
}
/**
* Return the first value that is defined. Like MooTools' $.pick.
*/
function pick() {
var args = arguments,
i,
arg;
for (i = 0; i < args.length; i++) {
arg = args[i];
if (defined(arg)) return arg;
};
}
/**
* Dynamically add a CSS rule to the page
* @param {String} selector
* @param {Object} declaration
* @param {Boolean} print Whether to add the styles only to print media. IE only.
*/
function addCSSRule(selector, declaration, print) {
var key,
serialized = '',
styleSheets,
last,
media = print ? 'print' : '',
createStyleTag = function(print) {
return createElement(
'style', {
type: 'text/css',
media: print ? 'print' : ''
},
null,
doc.getElementsByTagName('HEAD')[0]
);
};
// add the style tag the first time
if (!styleTag) styleTag = createStyleTag();
// serialize the declaration
for (key in declaration)
serialized += hyphenate(key) +':'+ declaration[key] + ';';
if (!isIE) { // create a text node in the style tag
styleTag.appendChild(
doc.createTextNode(
selector + " {" + serialized + "}\n"
)
);
} else { // get the last stylesheet and add rules
var styleSheets = doc.styleSheets,
index,
styleSheet;
if (print) { // only in IE for now
createStyleTag(true);
}
index = styleSheets.length - 1;
while (index >= 0 && styleSheets[index].media != media) index--;
styleSheet = styleSheets[index];
styleSheet.addRule(selector, serialized);
}
}
/**
* Extend an object with the members of another
* @param {Object} a The object to be extended
* @param {Object} b The object to add to the first one
*/
function extend(a, b) {
if (!a) a = {};
for (var n in b) a[n] = b[n];
return a;
}
/**
* Merge the default options with custom options and return the new options structure
* @param {Object} options The new custom options
*/
function setOptions(options) {
defaultOptions = merge(defaultOptions, options);
// apply UTC
setTimeMethods();
return defaultOptions;
}
/**
* Discard an element by moving it to the bin and delete
* @param {Object} The HTML node to discard
*/
function discardElement(element) {
// create a garbage bin element, not part of the DOM
if (!garbageBin) garbageBin = createElement(DIV);
// move the node and empty bin
if (element) garbageBin.appendChild(element);
garbageBin.innerHTML = '';
}
var defaultFont = 'normal 12px "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif',
defaultLabelOptions = {
enabled: true,
// rotation: 0,
align: 'center',
x: 0,
y: 15,
/*formatter: function() {
return this.value;
},*/
style: {
color: '#666',
font: defaultFont.replace('12px', '11px')
//'10px bold "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif'
}
},
defaultOptions = {
colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
'#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
lang: {
loading: 'Loading...',
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'],
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
decimalPoint: '.',
resetZoom: 'Reset zoom',
resetZoomTitle: 'Reset zoom level 1:1',
thousandsSep: ','
},
global: {
useUTC: true
},
chart: {
//alignTicks: false,
//className: null,
/*events: {
* load,
* selection
* },
*/
margin: [50, 50, 60, 80],
borderColor: '#4572A7',
//borderWidth: 0,
borderRadius: 5,
defaultSeriesType: 'line',
ignoreHiddenSeries: true,
//inverted: false,
//shadow: false,
//style: {},
//backgroundColor: null,
//plotBackgroundColor: null,
plotBorderColor: '#C0C0C0'
//plotBorderWidth: 0,
//plotShadow: false,
//zoomType: ''
},
title: {
text: 'Chart title',
style: {
textAlign: 'center',
color: '#3E576F',
font: defaultFont.replace('12px', '16px'),
margin: '10px 0 0 0'
}
},
subtitle: {
text: '',
style: {
textAlign: 'center',
color: '#6D869F',
font: defaultFont,
margin: 0
}
},
plotOptions: {
line: { // base series options
allowPointSelect: false,
//allowDrag: false, // point dragging - not yet implemented
//dragType: 'y',
showCheckbox: false,
animation: true,
//cursor: 'default',
//enableMouseTracking: true,
events: {},
lineWidth: 2,
shadow: true,
// stacking: null,
marker: {
enabled: true,
symbol: 'auto',
lineWidth: 0,
radius: 4,
lineColor: '#FFFFFF',
fillColor: 'auto',
states: { // states for a single point
hover: {
//radius: base + 2
},
select: {
fillColor: '#FFFFFF',
lineColor: 'auto',
lineWidth: 2
}
}
},
point: {
events: {}
},
dataLabels: merge(defaultLabelOptions, {
enabled: false,
y: -6,
formatter: function() {
return this.y;
}
}),
//pointStart: 0,
//pointInterval: 1,
showInLegend: true,
states: { // states for the entire series
hover: {
//enabled: false,
lineWidth: 3,
marker: {
// lineWidth: base + 1,
// radius: base + 1
}
},
select: {
marker: {}
}
}
}
},
labels: {
//items: [],
style: {
position: ABSOLUTE,
color: '#3E576F',
font: defaultFont
}
},
legend: {
enabled: true,
layout: 'horizontal',
labelFormatter: function() {
return this.name
},
//borderWidth: 0,
borderColor: '#909090',
borderRadius: 5,
shadow: true,
//backgroundColor: null,
style: {
bottom: '10px',
left: '80px',
padding: '5px'
},
itemStyle: {
listStyle: 'none',
margin: 0,
padding: '0 2em 0 0', // make room for the checkbox
font: defaultFont,
cursor: 'pointer',
color: '#3E576F',
position: RELATIVE // to allow absolute placement of the checkboxes
},
itemHoverStyle: {
color: '#000'
},
itemHiddenStyle: {
color: '#CCC'
},
itemCheckboxStyle: {
position: ABSOLUTE,
right: 0
},
//reversed: false, // docs
symbolWidth: 16,
symbolPadding: 5
},
loading: {
hideDuration: 100,
labelStyle: {
font: defaultFont.replace('normal', 'bold'),
position: RELATIVE,
top: '1em'
},
showDuration: 100,
style: {
position: ABSOLUTE,
backgroundColor: 'white',
opacity: 0.5,
textAlign: 'center'
}
},
tooltip: {
enabled: true,
formatter: function() {
var pThis = this,
series = pThis.series,
xAxis = series.xAxis,
x = pThis.x;
return '<b>'+ (pThis.point.name || series.name) +'</b><br/>'+
(defined(x) ?
'X value: '+ (xAxis && xAxis.options.type == 'datetime' ?
dateFormat('%Y-%m-%d %H:%M:%S', x) : x) +'<br/>':
'')+
'Y value: '+ pThis.y;
},
backgroundColor: 'rgba(255, 255, 255, .85)',
borderWidth: 2,
borderRadius: 5,
shadow: true,
snap: 10,
style: {
color: '#333333',
font: defaultFont,
fontSize: '9pt',
padding: '5px',
whiteSpace: 'nowrap'
}
},
toolbar: {
itemStyle: {
color: '#4572A7',
cursor: 'pointer',
margin: '20px',
font: defaultFont
}
},
credits: {
enabled: true,
text: 'Highcharts.com',
href: 'http://www.highcharts.com',
style: {
position: ABSOLUTE,
right: '10px',
bottom: '5px',
color: '#999',
textDecoration: 'none',
font: defaultFont.replace('12px', '10px')
},
target: '_self'
}
};
// Axis defaults
//defaultOptions.xAxis = merge(defaultOptions.axis);
var defaultXAxisOptions = {
// alternateGridColor: null,
// categories: [],
dateTimeLabelFormats: {
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H:%M',
day: '%e. %b',
week: '%e. %b',
month: '%b \'%y',
year: '%Y'
},
endOnTick: false,
gridLineColor: '#C0C0C0',
// gridLineWidth: 0,
// reversed: false,
labels: defaultLabelOptions,
lineColor: '#C0D0E0',
lineWidth: 1,
max: null,
min: null,
maxZoom: null,
minorGridLineColor: '#E0E0E0',
minorGridLineWidth: 1,
minorTickColor: '#A0A0A0',
//minorTickInterval: null,
minorTickLength: 2,
minorTickPosition: 'outside', // inside or outside
minorTickWidth: 1,
//plotBands: [],
//plotLines: [],
//reversed: false,
showFirstLabel: true,
showLastLabel: false,
startOfWeek: 1,
startOnTick: false,
tickColor: '#C0D0E0',
tickInterval: 'auto',
tickLength: 5,
tickmarkPlacement: 'between', // on or between
tickPixelInterval: 100,
tickPosition: 'outside',
tickWidth: 1,
title: {
enabled: false,
text: 'X-values',
align: 'middle', // low, middle or high
margin: 35,
//rotation: 0,
//side: 'outside',
style: {
color: '#6D869F',
font: defaultFont.replace('normal', 'bold')
}
},
type: 'linear' // linear or datetime
},
defaultYAxisOptions = merge(defaultXAxisOptions, {
endOnTick: true,
gridLineWidth: 1,
tickPixelInterval: 72,
showLastLabel: true,
labels: {
align: 'right',
x: -8,
y: 3
},
lineWidth: 0,
maxPadding: 0.05,
minPadding: 0.05,
startOnTick: true,
tickWidth: 0,
title: {
enabled: true,
margin: 40,
rotation: 270,
text: 'Y-values'
}
}),
defaultLeftAxisOptions = {
labels: {
align: 'right',
x: -8,
y: 3
},
title: {
rotation: 270
}
},
defaultRightAxisOptions = {
labels: {
align: 'left',
x: 8,
y: 3
},
title: {
rotation: 90
}
},
defaultBottomAxisOptions = { // horizontal axis
labels: {
align: 'center',
x: 0,
y: 14
},
title: {
rotation: 0
}
},
defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
labels: {
y: -5
}
});
// Series defaults
var defaultPlotOptions = defaultOptions.plotOptions,
defaultSeriesOptions = defaultPlotOptions.line;
//defaultPlotOptions.line = merge(defaultSeriesOptions);
defaultPlotOptions.spline = merge(defaultSeriesOptions);
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
//dragType: 'xy', // n/a
lineWidth: 0,
states: {
hover: {
lineWidth: 0
}
}
});
defaultPlotOptions.area = merge(defaultSeriesOptions, {
// lineColor: null, // overrides color, but lets fillColor be unaltered
// fillOpacity: .75,
fillColor: 'auto'
});
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
defaultPlotOptions.column = merge(defaultSeriesOptions, {
borderColor: '#FFFFFF',
borderWidth: 1,
borderRadius: 0,
groupPadding: 0.2,
pointPadding: 0.1,
states: {
hover: {
brightness: 0.1,
shadow: false
},
select: {
color: '#C0C0C0',
borderColor: '#000000',
shadow: false
}
}
});
defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
dataLabels: {
align: 'left',
x: 5,
y: 0
}
});
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
//dragType: '', // n/a
borderColor: '#FFFFFF',
borderWidth: 1,
center: ['50%', '50%'],
legendType: 'point',
size: '90%',
slicedOffset: 10,
states: {
hover: {
brightness: 0.1,
shadow: false
}
}
});
// set the default time methods
setTimeMethods();
// class-like inheritance
function extendClass(parent, members) {
var object = function(){};
object.prototype = new parent();
extend(object.prototype, members);
return object;
}
/*
function reverseArray(arr) {
var reversed = [];
for (var i = arr.length - 1; i >= 0; i--)
reversed.push( arr[i]);
return reversed;
}
*/
// return a deep value without throwing an error
/*function deepStructure(obj, path) {
// split the path into an array
path = path.split('.'), i = 0;
// recursively set obj to the path
while (path[i] && obj) obj = obj[path[i++]];
if (i == path.length) return obj;
}*/
/**
* Create a color from a string or configuration object
* @param {Object} val
*/
function setColor(val, ctx) {
if (typeof val == 'string') {
return val;
} else if (val.linearGradient) {
var gradient = ctx.createLinearGradient.apply(ctx, val.linearGradient);
each (val.stops, function(stop) {
gradient.addColorStop(stop[0], stop[1]);
});
return gradient;
}
}
var Color = function(input) {
var rgba = [], result;
function parse(input) {
// rgba
if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(input)))
rgba = [parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4])];
// hex
else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input)))
rgba = [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16), 1];
}
function get() {
// it's NaN if gradient colors on a column chart
if (rgba && !isNaN(rgba[0])) return 'rgba('+ rgba.join(',') +')';
else return input;
}
function brighten(alpha) {
if (typeof alpha == 'number' && alpha != 0) {
for (var i = 0; i < 3; i++) {
rgba[i] += parseInt(alpha * 255);
if (rgba[i] < 0) rgba[i] = 0;
if (rgba[i] > 255) rgba[i] = 255;
}
}
return this;
}
function setOpacity(alpha) {
rgba[3] = alpha;
return this;
}
parse(input);
// public methods
return {
get: get,
brighten: brighten,
setOpacity: setOpacity
};
};
//defaultMarkers = ['circle'];
function createElement (tag, attribs, styles, parent, nopad) {
var el = doc.createElement(tag);
if (attribs) extend(el, attribs);
if (nopad) setStyles(el, {padding: 0, border: 'none', margin: 0});
if (styles) setStyles(el, styles);
if (parent) parent.appendChild(el);
return el;
};
function setStyles (el, styles) {
//for (var x in styles) el.style[x] = styles[x];
if (isIE) {
if (styles.opacity !== undefined)
styles.filter = 'alpha(opacity='+ (styles.opacity * 100) +')';
}
extend(el.style, styles);
};
/**
* Format a number and return a string based on input settings
* @param {Number} number The input number to format
* @param {Number} decimals The amount of decimals
* @param {String} decPoint The decimal point, defaults to the one given in the lang options
* @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
*/
function numberFormat (number, decimals, decPoint, thousandsSep) {
var lang = defaultOptions.lang,
// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
d = decPoint === undefined ? lang.decimalPoint : decPoint,
t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
i = parseInt(n = mathAbs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
(c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
};
/**
* Based on http://www.php.net/manual/en/function.strftime.php
* @param {String} format
* @param {Number} timestamp
* @param {Boolean} capitalize
*/
function dateFormat(format, timestamp, capitalize) {
function pad (number) {
return number.toString().replace(/^([0-9])$/, '0$1');
}
if (!defined(timestamp)) return 'Invalid date';
var date = new Date(timestamp * timeFactor),
// get the basic time values
hours = date[getHours](),
day = date[getDay](),
dayOfMonth = date[getDate](),
month = date[getMonth](),
fullYear = date[getFullYear](),
lang = defaultOptions.lang,
langWeekdays = lang.weekdays,
langMonths = lang.months,
// list all format keys
replacements = {
// Day
'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
'A': langWeekdays[day], // Long weekday, like 'Monday'
'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
'e': dayOfMonth, // Day of the month, 1 through 31
// Week (none implemented)
// Month
'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
'B': langMonths[month], // Long month, like 'January'
'm': pad(month + 1), // Two digit month number, 01 through 12
// Year
'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
'Y': fullYear, // Four digits year, like 2009
// Time
'H': pad(hours), // Two digits hours in 24h format, 00 through 23
'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
'S': pad(date.getSeconds()) // Two digits seconds, 00 through 59
};
// do the replaces
for (var key in replacements) format = format.replace('%'+ key, replacements[key]);
// Optionally capitalize the string and return
return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
};
/**
* Set the time methods globally based on the useUTC option. Time method can be either
* local time or UTC (default).
*/
function setTimeMethods() {
var useUTC = defaultOptions.global.useUTC;
makeTime = useUTC ? Date.UTC : function(year, month, date, hours, minutes, seconds) {
return new Date(
year,
month,
pick(date, 1),
pick(hours, 0),
pick(minutes, 0),
pick(seconds, 0)
).getTime();
};
getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
getHours = useUTC ? 'getUTCHours' : 'getHours';
getDay = useUTC ? 'getUTCDay' : 'getDay';
getDate = useUTC ? 'getUTCDate' : 'getDate';
getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
setHours = useUTC ? 'setUTCHours' : 'setHours';
setDate = useUTC ? 'setUTCDate' : 'setDate';
setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';
};
/**
* Return the absolute page position of an element
* @param {Object} el
*/
function getPosition (el) {
var p = { x: el.offsetLeft, y: el.offsetTop };
while (el.offsetParent) {
el = el.offsetParent;
p.x += el.offsetLeft;
p.y += el.offsetTop;
if (el != doc.body && el != doc.documentElement) {
p.x -= el.scrollLeft;
p.y -= el.scrollTop;
}
}
return p;
}
var Layer = function (name, appendTo, props, styles) {
var layer = this,
div,
appendToStyle = appendTo.style;
props = extend({
className: 'highcharts-'+ name
}, props);
styles = extend({
width: appendToStyle.width, //appendTo.offsetWidth + PX,
height: appendToStyle.height, //appendTo.offsetHeight + PX,
position: ABSOLUTE,
top: 0,
left: 0,
margin: 0,
padding: 0,
border: 'none'
}, styles);
div = createElement(DIV, props, styles, appendTo);
extend(layer, {
div: div,
width: parseInt(styles.width),
height: parseInt(styles.height)
});
// initial SVG string
layer.svg = isIE ? '' : '<?xml version="1.0" encoding="utf-8"?>'+
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" '+
'xmlns:xlink="http://www.w3.org/1999/xlink" width="'+ layer.width
+'px" height="'+ layer.height +'">';
// save it for later
layer.basicSvg = layer.svg;
}
Layer.prototype = {
getCtx: function() {
if (!this.ctx) {
var cvs = createElement('canvas', {
id: 'highcharts-canvas-' + idCounter++,
width: this.width,
height: this.height
}, {
position: ABSOLUTE
}, this.div);
if (isIE) {
G_vmlCanvasManager.initElement(cvs);
cvs = doc.getElementById(cvs.id);
}
this.ctx = cvs.getContext('2d');
}
return this.ctx;
},
getSvg: function() {
if (!this.svgObject) {
var layer = this,
div = layer.div,
width = layer.width,
height = layer.height;
if (isIE) {
// create xmlns if excanvas hasn't done it
if (!doc.namespaces["g_vml_"]) {
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
// setup default css
doc.createStyleSheet().cssText = "g_vml_\\:*{behavior:url(#default#VML)}";
}
this.svgObject = createElement(DIV, null, {
width: width + PX,
height: height + PX,
position: ABSOLUTE
}, div);
} else {
// create an object and inject SVG into it
this.svgObject = createElement('object', {
width: width,
height: height,
type: 'image/svg+xml'
}, {
position : ABSOLUTE,
left: 0,
top: 0
}, div);
}
}
return this.svgObject;
},
drawLine: function(x1, y1, x2, y2, color, width) {
var ctx = this.getCtx(), xBefore = x1;
// normalize to a crisp line
if (x1 == x2) x1 = x2 = mathRound(x1) + (width % 2 / 2);
if (y1 == y2) y1 = y2 = mathRound(y1) + (width % 2 / 2);
// draw path
ctx.lineWidth = width;
ctx.lineCap = 'round'; /* If this is not set, plotBands appear
like 'square' in Firefox/Win. Reason unknown. */
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.strokeStyle = color;
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
},
drawPolyLine: function(points, color, width, shadow, fillColor) {
var ctx = this.getCtx(),
shadowLine = [];
// the shadow
if (shadow && width) {
each (points, function(point) { // add 1px offset
shadowLine.push(point === undefined ? point : point + 1);
});
for (var i = 1; i <= 3; i++) // three lines of differing thickness and opacity
this.drawPolyLine(shadowLine, 'rgba(0, 0, 0, '+ (0.05 * i) +')', 6 - 2 * i);
}
// the line path
ctx.beginPath();
for (i = 0; i < points.length; i += 2)
ctx[i == 0 ? 'moveTo' : 'lineTo'](points[i], points[i + 1]);
// common properties
extend(ctx, {
lineWidth: width,
lineJoin: 'round'
});
// stroke
if (color && width) {
ctx.strokeStyle = setColor(color, ctx);
ctx.stroke();
}
// fill
if (fillColor) {
ctx.fillStyle = setColor(fillColor, ctx);
ctx.fill();
}
},
drawRect: function(x, y, w, h, color, width, radius, fill, shadow, image) {
// must (?) be done twice to apply both stroke and fill in excanvas
var drawPath = function() {
var ret;
if (w > 0 && h > 0) { // zero or negative dimensions break Opera 10
ctx.beginPath();
if (!radius) {
ctx.rect(x, y, w, h);
} else {
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + h - radius);
ctx.quadraticCurveTo(x, y + h, x + radius, y + h); // change: use bezier
ctx.lineTo(x + w - radius, y + h);
ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - radius);
ctx.lineTo(x + w, y + radius);
ctx.quadraticCurveTo(x + w, y , x + w - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x , y, x, y + radius);
}
ctx.closePath();
ret = true;
}
return ret;
};
var ctx = this.getCtx(), normalizer = (width || 0) % 2 / 2;
// normalize for sharp edges
x = mathRound(x) + normalizer;
y = mathRound(y) + normalizer;
w = mathRound(w - 2 * normalizer);
h = mathRound(h - 2 * normalizer);
// apply the drop shadow
if (shadow) for (var i = 1; i <= 3; i++) {
this.drawRect(x + 1, y + 1, w, h, 'rgba(0, 0, 0, '+ (0.05 * i) +')',
6 - 2 * i, radius);
}
// apply the background image behind everything
if (image) ctx.drawImage(image, x, y, w, h);
if (drawPath()) {
if (fill) {
ctx.fillStyle = setColor(fill, ctx);
ctx.fill();
// draw path again
if (win.G_vmlCanvasManager) drawPath();
}
if (width) {
ctx.strokeStyle = setColor(color, ctx);
ctx.lineWidth = width;
ctx.stroke();
}
}
},
drawSymbol: function(symbol, x, y, radius, lineWidth, lineColor, fillColor) {
var ctx = this.getCtx(),
imageRegex = /^url\((.*?)\)$/;
ctx.beginPath();
if (symbol == 'square') {
var len = 0.707 * radius;
ctx.moveTo(x - len, y - len);
ctx.lineTo(x + len, y - len);
ctx.lineTo(x + len, y + len);
ctx.lineTo(x - len, y + len);
ctx.lineTo(x - len, y - len);
} else if (symbol == 'triangle') {
y++;
ctx.moveTo(x, y - 1.33 * radius);
ctx.lineTo(x + radius, y + 0.67 * radius);
ctx.lineTo(x - radius, y + 0.67 * radius);
ctx.lineTo(x, y - 1.33 * radius);
} else if (symbol == 'triangle-down') {
y--;
ctx.moveTo(x, y + 1.33 * radius);
ctx.lineTo(x - radius, y - 0.67 * radius);
ctx.lineTo(x + radius, y - 0.67 * radius);
ctx.lineTo(x, y + 1.33 * radius);
} else if (symbol == 'diamond') {
ctx.moveTo(x, y - radius);
ctx.lineTo(x + radius, y);
ctx.lineTo(x, y + radius);
ctx.lineTo(x - radius, y);
ctx.lineTo(x, y - radius);
} else if (imageRegex.test(symbol)) {
createElement('img', {
onload: function() {
var img = this,
size = symbolSizes[img.src] || [img.width, img.height];
setStyles(img, {
left: mathRound(x - size[0] / 2) + PX,
top: mathRound(y - size[1] / 2) + PX,
visibility: VISIBLE
})
// Bug workaround: Opera (10.01) fails to get size the second time
symbolSizes[img.src] = size;
},
src: symbol.match(imageRegex)[1]
}, {
position: ABSOLUTE,
visibility: isIE ? VISIBLE : HIDDEN // hide until left and top are set in Gecko
}, this.div);
} else { // default: circle
ctx.arc(x, y, radius, 0, 2*math.PI, true);
}
if (fillColor) {
ctx.fillStyle = fillColor;
ctx.fill();
// draw path again
//if (isIE) ctx.arc(x, y, radius, 0, 2*Math.PI, true);
}
if (lineColor && lineWidth) {
ctx.strokeStyle = lineColor || "rgb(100, 100, 255)";
ctx.lineWidth = lineWidth || 2;
ctx.stroke();
}
},
drawHtml: function(html, attributes, styles) {
createElement(
DIV,
extend(attributes, { innerHTML: html }),
extend(styles, { position: ABSOLUTE}),
this.div
);
},
/**
* Add text and draw it. For those browsers adding the text to an SVG object,
* it is better for performance to add all strings before the object
* is created. This function takes the same arguments as addText.
*
* @param {string} str
* @param {number} x
* @param {number} y
* @param {object} style
* @param {number} rotation
* @param {string} align
*/
drawText: function() {
this.addText.apply(this, arguments);
this.strokeText();
},
addText: function(str, x, y, style, rotation, align) {
if (str || str === 0) {
// declare variables
var layer = this,
hasObject,
div = layer.div,
CSStransform,
css = '',
style = style || {},
fill = style.color || '#000000',
align = align || 'left',
fontSize = parseInt(style.fontSize || style.font.replace(/^[a-z ]+/, '')),
span,
spanWidth,
transformOriginX;
// prepare style
for (var key in style) css += hyphenate(key) +':'+ style[key] +';';
// what transform property is supported?
each (['MozTransform', 'WebkitTransform', 'transform'], function(str) {
if (str in div.style) CSStransform = str;
});
// if the text is not rotated, or if the browser supports CSS transform,
// write a simple span
if (!rotation || CSStransform) {
span = createElement('span', {
innerHTML: str
}, extend(style, {
position: ABSOLUTE,
left: x + PX,
whiteSpace: 'nowrap',
bottom: mathRound(layer.height - y - fontSize * 0.25) + PX,
color: fill
}), div);
// fix the position according to align and rotation
spanWidth = span.offsetWidth;
if (align == 'right') setStyles(span, {
left: (x - spanWidth) + PX
});
else if (align == 'center') setStyles(span, {
left: mathRound(x - spanWidth / 2) + PX
});
if (rotation) { // ... and CSStransform
transformOriginX = { left: 0, center: 50, right: 100 }[align]
span.style[CSStransform] = 'rotate('+ rotation +'deg)';
span.style[CSStransform +'Origin'] = transformOriginX +'% 100%';
}
} else if (isIE) {
// to achieve rotated text, the ie text is drawn on a vector line that
// is extrapolated to the left or right or both depending on the
// alignment of the text
hasObject = true;
var radians = (rotation || 0) * math.PI * 2 / 360, // deg to rad
costheta = mathCos(radians),
sintheta = mathSin(radians),
length = layer.width, // the text is not likely to be longer than this
baselineCorrection = fontSize / 3 || 3,
left = align == 'left',
right = align == 'right',
x1 = left ? x : x - length * costheta,
x2 = right ? x : x + length * costheta,
y1 = left ? y : y - length * sintheta,
y2 = right ? y : y + length * sintheta;
// IE seems to always draw the text with v-text-align middle, so we need
// to correct for that by moving the path
x1 += baselineCorrection * sintheta;
x2 += baselineCorrection * sintheta;
y1 -= baselineCorrection * costheta;
y2 -= baselineCorrection * costheta;
if (mathAbs(x1 - x2) < 0.1) x1 += 0.1; // strange IE painting bug
if (mathAbs(y1 - y2) < 0.1) y1 += 0.1; // strange IE painting bug
layer.svg +=
'<g_vml_:line from="'+ x1 +', '+ y1 +'" to="'+ x2 +', '+ y2 +'" stroked="false">'+
'<g_vml_:fill on="true" color="'+ fill +'"/>'+
'<g_vml_:path textpathok="true"/>'+
'<g_vml_:textpath on="true" string="'+ str +'" '+
'style="v-text-align:'+ align +
';'+ css +'"/>'+
'</g_vml_:line>';
// svg browsers
} else {
hasObject = true;
layer.svg +=
'<g>'+
'<text transform="translate('+ x +','+ y +
') rotate('+ (rotation || 0) +')" '+
'style="fill:'+ fill +';text-anchor:'+
{ left: 'start', center: 'middle', right: 'end' }[align] +
';'+ css.replace(/"/g, "'") +'">'+ str+
'</text>'+
'</g>';
}
layer.hasObject = hasObject;
}
},
/*
Experimental text rendering using canvas text. Speed and possibly weight are the advantages.
Excanvas trunk supports canvas text, but not current version (2009-11-03). Older Gecko and Webkit
browsers and Opera needs SVG approach, so all in all there is not much weight spared by this one.
Furthermore, CanvasText looks crappy in Firefox, but on the other hand, SVG object make the tooltip
animation slow.
2009-11-07: Preliminary conclusion:
- IE: Use VML on a textpath. The con is that IE8 renders all text as bold italic. Possibly
experiment with CSS text rotation for whole 90 degrees if that's supported by IE8.
- Firefox >= 3.5 (Gecko 1.9.1 was it?) and Webkit > ?: Use CSS transforms to rotate the text.
Canvas and textFill would be a better and faster alternative, but the text is very badly
drawn in Firefox. When that's fixed in future versions, go for Canvas and textFill instead.
- Opera, older Gecko and older Webkit: Use SVG. It is slow, and all the text has to be added
before written to the SVG object. Hence the addText and strokeText functions. When Opera
starts supporting textFill or text rotate, use that instead.
_addText: function(str, x, y, style, rotation, anchor) {
if (str || str === 0) {
// declare variables
var css = '',
style = style || {},
fill = style.color || '#000000',
anchor = anchor || 'start',
ctx,
span,
font = (style.font || '') +' '+ (style.fontSize || '') +' '+
(style.fontWeight || '') +' '+ (style.fontFamily || ''),
align = { start: 'left', middle: 'center', end: 'right' }[anchor],
rotation = (rotation || 0) * math.PI * 2 / 360, // deg to rad
fontSize = parseInt(style.fontSize || font);
// prepare style
for (var key in style) css += hyphenate(key) +':'+ style[key] +';';
if (rotation) {
var ctx = this.getCtx();
ctx.font = font;
ctx.fillStyle = fill;
ctx.textAlign = align;
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillText(str, 0, 0);
ctx.rotate(-rotation);
ctx.translate(-x, -y);
} else {
}
}
},*/
strokeText: function() {
if (this.hasObject) {
var svgObject = this.getSvg(),
svg = this.svg;
if (isIE) {
svgObject.innerHTML = svg;
} else {
svgObject.data =
'data:image/svg+xml,'+ svg +'</svg>';
// append it again for Chrome to update
if (isWebKit) this.div.appendChild(svgObject);
}
}
},
clear: function() {
var layer = this,
div = this.div,
childNodes = div.childNodes,
node;
if (layer.ctx) layer.ctx.clearRect(0, 0, layer.width, layer.height);
if (layer.svgObject) {
discardElement(layer.svgObject);
layer.svgObject = null;
layer.svg = layer.basicSvg;
}
// remove all spans
for (var i = childNodes.length - 1; i >= 0; i--) {
node = childNodes[i];
if (/(SPAN|IMG)/.test(node.tagName)) discardElement(node);
}
},
hide: function() {
setStyles (this.div, {
display: 'none'
})
//jQuery(this.div).fadeOut(250);
// A possible way to fade out in IE would be to get all the shapes
// in the layer and change the opacities of their FillColor and StrokeColor
/*var shapes = this.div.getElementsByTagName('shape');
each (shapes, function(shape) {
//shape.style.filter = 'alpha(opacity=59)';
})
var layer = this;
setTimeout(function() {
layer.div.style.visibility = HIDDEN
}, 2000);*/
},
show: function() {
setStyles (this.div, {
display: ''
})
//jQuery(this.div).fadeIn(50);
},
/**
* Discard layer DOM elements and null the reference
*/
destroy: function() {
discardElement(this.div);
return null;
}
};
function Chart (options) {
/**
* Add a series dynamically after time
*
* @param {Object} options The config options
* @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
*
* @return {Object} series The newly created series object
*/
function addSeries(options, redraw) {
var series;
redraw = pick(redraw, true); // defaults to true
fireEvent(chart, 'addSeries', { options: options }, function() {
series = initSeries(options);
series.isDirty = true;
chart.isDirty = true; // the series array is out of sync with the display
if (redraw) chart.redraw();
});
return series;
};
/**
* Redraw legend, axes or series based on updated data
*/
function redraw() {
var redrawLegend = chart.isDirty;
// handle updated data in the series
each (series, function(serie) {
if (serie.isDirty) { // prepare the data so axis can read it
serie.cleanData();
serie.getSegments();
if (serie.options.legendType == 'point') redrawLegend = true;
}
});
// reset maxTicks
maxTicks = null;
if (hasCartesianSeries) {
// set axes scales
each (axes, function(axis) {
axis.setScale();
})
adjustTickAmounts();
// redraw axes
each (axes, function(axis) {
if (axis.isDirty) axis.redraw();
})
}
// redraw affected series
each (series, function(serie) {
if (serie.isDirty && serie.visible) serie.redraw();
});
// handle added or removed series
if (redrawLegend) { // series or pie points are added or removed
// draw legend graphics
if (legend && legend.renderHTML) {
legend.renderHTML(true);
legend.drawGraphics(true);
}
chart.isDirty = false;
}
// hide tooltip and hover states
if (tracker && tracker.resetTracker) tracker.resetTracker();
// fire the event
fireEvent(chart, 'redraw');
}
/**
* Initialize an individual series, called internally before render time
*/
function initSeries(options) {
var type = options.type || optionsChart.defaultSeriesType,
typeClass = seriesTypes[type],
serie,
hasRendered = chart.hasRendered;
// an inverted chart can't take a column series and vice versa
if (hasRendered) {
if (inverted && type == 'column') typeClass = BarSeries;
else if (!inverted && type == 'bar') typeClass = ColumnSeries;
}
serie = new typeClass();
serie.init(chart, options);
// set internal chart properties
if (!hasRendered && serie.inverted) inverted = true;
if (serie.isCartesian) hasCartesianSeries = serie.isCartesian;
series.push(serie);
return serie;
}
/**
* Dim the chart and show a loading text or symbol
*/
function showLoading() {
var loadingOptions = options.loading;
// create the layer at the first call
if (!loadingLayer) {
loadingLayer = createElement(DIV, {
className: 'highcharts-loading'
}, extend(loadingOptions.style, {
left: marginLeft + PX,
top: marginTop + PX,
width: plotWidth + PX,
height: plotHeight + PX,
zIndex: 10,
display: 'none'
}), container);
createElement('span', {
innerHTML: options.lang.loading
}, loadingOptions.labelStyle, loadingLayer);
}
// show it
setStyles(loadingLayer, { display: '' });
animate(loadingLayer, {
opacity: loadingOptions.style.opacity
}, {
duration: loadingOptions.showDuration
});
}
/**
* Hide the loading layer
*/
function hideLoading() {
animate(loadingLayer, {
opacity: 0
}, {
duration: options.loading.hideDuration,
complete: function() {
setStyles(loadingLayer, { display: 'none' });
}
});
}
/**
* Get an axis, series or point object by id.
* @param id {String} The id as given in the configuration options
*/
function get(id) {
var i,
j,
match,
data;
// search axes
for (i = 0; i < axes.length; i++) {
if (axes[i].options.id == id) return axes[i];
}
// search series
for (i = 0; i < series.length; i++) {
if (series[i].options.id == id) return series[i];
}
// search points
for (i = 0; i < series.length; i++) {
data = series[i].data;
for (j = 0; j < data.length; j++) {
if (data[j].id == id) return data[j];
}
}
return null;
}
/**
* Update the chart's position after it has been moved, to match
* the mouse positions with the chart
*/
function updatePosition() {
var container = doc.getElementById(containerId);
if (container) {
position = getPosition(container);
}
}
/**
* Create the Axis instances based on the config options
*/
function getAxes() {
var xAxisOptions = options.xAxis || {},
yAxisOptions = options.yAxis || {},
axis;
// make sure the options are arrays and add some members
xAxisOptions = splat(xAxisOptions);
each(xAxisOptions, function(axis, i) {
axis.index = i;
axis.isX = true;
});
yAxisOptions = splat(yAxisOptions);
each(yAxisOptions, function(axis, i) {
axis.index = i;
});
// concatenate all axis options into one array
axes = xAxisOptions.concat(yAxisOptions);
// loop the options and construct axis objects
chart.xAxis = [];
chart.yAxis = [];
axes = map (axes, function(axisOptions) {
axis = new Axis(chart, axisOptions);
chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);
return axis;
});
adjustTickAmounts();
};
/**
* Adjust all axes tick amounts
*/
function adjustTickAmounts() {
if (optionsChart.alignTicks !== false) each (axes, function(axis) {
axis.adjustTickAmount();
});
}
/**
* Get the currently selected points from all series
*/
function getSelectedPoints() {
var points = [];
each(series, function(serie) {
points = points.concat( grep( serie.data, function(point) {
return point.selected;
}));
});
return points;
};
/**
* Get the currently selected series
*/
function getSelectedSeries() {
return grep (series, function (serie) {
return serie.selected;
});
}
/**
* Zoom into a given portion of the chart given by axis coordinates
* @param {Object} event
*/
function zoom(event) {
var lang = defaultOptions.lang;
// add button to reset selection
chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, function() {
//zoom(false);
fireEvent(chart, 'selection', { resetSelection: true }, zoom);
chart.toolbar.remove('zoom');
});
// if zoom is called with no arguments, reset the axes
if (!event || event.resetSelection) each(axes, function(axis) {
axis.setExtremes(null, null, false);
});
// else, zoom in on all axes
else {
each (event.xAxis.concat(event.yAxis), function(axisData) {
var axis = axisData.axis;
// don't zoom more than maxZoom
if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY'])
axis.setExtremes(axisData.min, axisData.max, false);
});
}
// redraw chart
redraw();
}
/**
* Function: (private) showTitle
*
* Show the title and subtitle of the chart
*/
function showTitle () {
var title = options.title,
subtitle = options.subtitle;
if (!chart.titleLayer) {
var titleLayer = new Layer('title-layer', container, null, {
zIndex: 2
});
// title
if (title && title.text) createElement('h2', {
className: 'highcharts-title',
innerHTML: title.text
}, title.style, titleLayer.div);
// subtitle
if (subtitle && subtitle.text) createElement('h3', {
className: 'highcharts-subtitle',
innerHTML: subtitle.text
}, subtitle.style, titleLayer.div);
chart.titleLayer = titleLayer;
}
}
/**
* Load graphics and data required to draw the chart
*/
function checkResources() {
var allLoaded = true;
for (var n in chart.resources) {
if (!chart.resources[n]) allLoaded = false;
}
if (allLoaded) resourcesLoaded();
};
/**
* Prepare for first rendering after all data are loaded
*/
function resourcesLoaded() {
getAxes();
// Prepare for the axis sizes
each(series, function(serie) {
serie.translate();
serie.setTooltipPoints();
/*if (options.tooltip.enabled) */ serie.createArea();
});
chart.render = render;
setTimeout(function() { // IE(7) needs timeout
render();
fireEvent(chart, 'load');
}, 0);
}
/**
* Get the containing element, determine the size and create the inner container
* div to hold the chart
*/
function getContainer() {
renderTo = optionsChart.renderTo;
containerId = 'highcharts-'+ idCounter++;
if (typeof renderTo == 'string') {
renderTo = doc.getElementById(renderTo);
}
// remove previous chart
renderTo.innerHTML = '';
// If the container doesn't have an offsetWidth, it has or is a child of a node
// that has display:none. We need to temporarily move it out to a visible
// state to determine the size, else the legend and tooltips won't render
// properly
if (!renderTo.offsetWidth) {
renderToClone = renderTo.cloneNode(0);
setStyles(renderToClone, {
position: ABSOLUTE,
top: '-9999px',
display: ''
});
doc.body.appendChild(renderToClone);
}
// get the width and height
var renderToOffsetHeight = (renderToClone || renderTo).offsetHeight;
chartWidth = optionsChart.width || (renderToClone || renderTo).offsetWidth || 600;
chartHeight = optionsChart.height ||
// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
(renderToOffsetHeight > marginTop + marginBottom ? renderToOffsetHeight : 0) ||
400;
// create the inner container
container = createElement(DIV, {
className: 'highcharts-container' +
(optionsChart.className ? ' '+ optionsChart.className : ''),
id: containerId
}, extend({
position: RELATIVE,
overflow: HIDDEN,
width: chartWidth + PX,
height: chartHeight + PX,
textAlign: 'left'
}, optionsChart.style),
renderToClone || renderTo
);
}
/**
* Render all graphics for the chart
*/
function render () {
var mgn,
div,
i,
labels = options.labels,
credits = options.credits;
// Chart area
mgn = 2 * (optionsChart.borderWidth || 0) + (optionsChart.shadow ? 8 : 0);
backgroundLayer.drawRect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
optionsChart.borderColor, optionsChart.borderWidth, optionsChart.borderRadius,
optionsChart.backgroundColor, optionsChart.shadow);
// Plot background
backgroundLayer.drawRect(
marginLeft,
marginTop,
plotWidth,
plotHeight,
null,
null,