@pgaubatz/npm-registry-couchapp
Version:
The CouchApp implementation of an npm registry
2,144 lines (1,890 loc) • 258 kB
JavaScript
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
* @license Highcharts JS v2.1.2 (2011-01-12)
*
* (c) 2009-2010 Torstein Hønsi
*
* License: www.highcharts.com/license
*/
// JSLint options:
/*jslint forin: true */
/*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
(function() {
// encapsulated variables
var doc = document,
win = window,
math = Math,
mathRound = math.round,
mathFloor = math.floor,
mathCeil = math.ceil,
mathMax = math.max,
mathMin = math.min,
mathAbs = math.abs,
mathCos = math.cos,
mathSin = math.sin,
mathPI = math.PI,
deg2rad = mathPI * 2 / 360,
// some variables
userAgent = navigator.userAgent,
isIE = /msie/i.test(userAgent) && !win.opera,
docMode8 = doc.documentMode == 8,
isWebKit = /AppleWebKit/.test(userAgent),
hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
SVG_NS = 'http://www.w3.org/2000/svg',
hasTouch = 'ontouchstart' in doc.documentElement,
colorCounter,
symbolCounter,
symbolSizes = {},
idCounter = 0,
timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
garbageBin,
defaultOptions,
dateFormat, // function
globalAnimation,
pathAnim,
// some constants for frequently used strings
UNDEFINED,
DIV = 'div',
ABSOLUTE = 'absolute',
RELATIVE = 'relative',
HIDDEN = 'hidden',
PREFIX = 'highcharts-',
VISIBLE = 'visible',
PX = 'px',
NONE = 'none',
M = 'M',
L = 'L',
/*
* Empirical lowest possible opacities for TRACKER_FILL
* IE6: 0.002
* IE7: 0.002
* IE8: 0.002
* IE9: 0.00000000001 (unlimited)
* FF: 0.00000000001 (unlimited)
* Chrome: 0.000001
* Safari: 0.000001
* Opera: 0.00000000001 (unlimited)
*/
TRACKER_FILL = 'rgba(192,192,192,'+ (hasSVG ? 0.000001 : 0.002) +')', // invisible but clickable
NORMAL_STATE = '',
HOVER_STATE = 'hover',
SELECT_STATE = 'select',
// 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,
removeEvent = adapter.removeEvent,
fireEvent = adapter.fireEvent,
animate = adapter.animate,
stop = adapter.stop,
// lookup over the types and the associated classes
seriesTypes = {},
hoverChart;
/**
* 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;
}
/**
* Shortcut for parseInt
* @param {Object} s
*/
function pInt(s, mag) {
return parseInt(s, mag || 10);
}
/**
* Check for string
* @param {Object} s
*/
function isString(s) {
return typeof s == 'string';
}
/**
* Check for object
* @param {Object} obj
*/
function isObject(obj) {
return typeof obj == 'object';
}
/**
* Check for number
* @param {Object} n
*/
function isNumber(n) {
return typeof n == 'number';
}
/**
* Remove last occurence of an item from an array
* @param {Array} arr
* @param {Mixed} item
*/
function erase(arr, item) {
var i = arr.length;
while (i--) {
if (arr[i] == item) {
arr.splice(i, 1);
break;
}
}
//return arr;
}
/**
* Returns true if the object is not null or undefined. Like MooTools' $.defined.
* @param {Object} obj
*/
function defined (obj) {
return obj !== UNDEFINED && obj !== null;
}
/**
* Set or get an attribute or an object of attributes. Can't use jQuery attr because
* it attempts to set expando properties on the SVG element, which is not allowed.
*
* @param {Object} elem The DOM element to receive the attribute(s)
* @param {String|Object} prop The property or an abject of key-value pairs
* @param {String} value The value if a single property is set
*/
function attr(elem, prop, value) {
var key,
setAttribute = 'setAttribute',
ret;
// if the prop is a string
if (isString(prop)) {
// set the value
if (defined(value)) {
elem[setAttribute](prop, value);
// get the value
} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
ret = elem.getAttribute(prop);
}
// else if prop is defined, it is a hash of key/value pairs
} else if (defined(prop) && isObject(prop)) {
for (key in prop) {
elem[setAttribute](key, prop[key]);
}
}
return ret;
}
/**
* 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;
}
/**
* Return the first value that is defined. Like MooTools' $.pick.
*/
function pick() {
var args = arguments,
i,
arg,
length = args.length;
for (i = 0; i < length; i++) {
arg = args[i];
if (typeof arg !== 'undefined' && arg !== null) {
return arg;
}
}
}
/**
* Make a style string from a JS object
* @param {Object} style
*/
function serializeCSS(style) {
var s = '',
key;
// serialize the declaration
for (key in style) {
s += hyphenate(key) +':'+ style[key] + ';';
}
return s;
}
/**
* Set CSS on a give element
* @param {Object} el
* @param {Object} styles
*/
function css (el, styles) {
if (isIE) {
if (styles && styles.opacity !== UNDEFINED) {
styles.filter = 'alpha(opacity='+ (styles.opacity * 100) +')';
}
}
extend(el.style, styles);
}
/**
* Utility function to create element with attributes and styles
* @param {Object} tag
* @param {Object} attribs
* @param {Object} styles
* @param {Object} parent
* @param {Object} nopad
*/
function createElement (tag, attribs, styles, parent, nopad) {
var el = doc.createElement(tag);
if (attribs) {
extend(el, attribs);
}
if (nopad) {
css(el, {padding: 0, border: NONE, margin: 0});
}
if (styles) {
css(el, styles);
}
if (parent) {
parent.appendChild(el);
}
return el;
}
/**
* Set the global animation to either a given value, or fall back to the
* given chart's animation option
* @param {Object} animation
* @param {Object} chart
*/
function setAnimation(animation, chart) {
globalAnimation = pick(animation, chart.animation);
}
/*
* Define the adapter for frameworks. If an external adapter is not defined,
* Highcharts reverts to the built-in jQuery adapter.
*/
if (globalAdapter && globalAdapter.init) {
globalAdapter.init();
}
if (!globalAdapter && win.jQuery) {
var jQ = jQuery;
/**
* Utility for iterating over an array. Parameters are reversed compared to jQuery.
* @param {Array} arr
* @param {Function} fn
*/
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;
}
}
};
/**
* Filter an array
*/
grep = jQ.grep;
/**
* Map an array
* @param {Array} arr
* @param {Function} fn
*/
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;
};
/**
* Deep merge two objects and return a third object
*/
merge = function(){
var args = arguments;
return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
};
/**
* Convert a camelCase string to a hyphenated string
* @param {String} str
*/
hyphenate = function (str) {
return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase(); });
};
/**
* Add an event listener
* @param {Object} el A HTML element or custom object
* @param {String} event The event type
* @param {Function} fn The event handler
*/
addEvent = function (el, event, fn){
jQ(el).bind(event, fn);
};
/**
* Remove event added with addEvent
* @param {Object} el The object
* @param {String} eventType The event type. Leave blank to remove all events.
* @param {Function} handler The function to remove
*/
removeEvent = function(el, eventType, handler) {
// workaround for jQuery issue with unbinding custom events:
// http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
if (doc[func] && !el[func]) {
el[func] = function() {};
}
jQ(el).unbind(eventType, handler);
};
/**
* Fire an event on a custom object
* @param {Object} el
* @param {String} type
* @param {Object} eventArguments
* @param {Function} defaultFunction
*/
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 a HTML element or SVG element wrapper
* @param {Object} el
* @param {Object} params
* @param {Object} options jQuery-like animation options: duration, easing, callback
*/
animate = function (el, params, options) {
var $el = jQ(el);
if (params.d) {
el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
params.d = 1; // because in jQuery, animating to an array has a different meaning
}
$el.stop();
$el.animate(params, options);
};
/**
* Stop running animation
*/
stop = function (el) {
jQ(el).stop();
};
// extend jQuery
jQ.extend( jQ.easing, {
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
});
// extend the animate function to allow SVG animations
var oldStepDefault = jQuery.fx.step._default,
oldCur = jQuery.fx.prototype.cur;
// do the step
jQ.fx.step._default = function(fx){
var elem = fx.elem;
if (elem.attr) { // is SVG element wrapper
elem.attr(fx.prop, fx.now);
} else {
oldStepDefault.apply(this, arguments);
}
};
// animate paths
jQ.fx.step.d = function(fx) {
var elem = fx.elem;
// Normally start and end should be set in state == 0, but sometimes,
// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
// in these cases
if (!fx.started) {
var ends = pathAnim.init(elem, elem.d, elem.toD);
fx.start = ends[0];
fx.end = ends[1];
fx.started = true;
}
// interpolate each value of the path
elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
};
// get the current value
jQ.fx.prototype.cur = function() {
var elem = this.elem,
r;
if (elem.attr) { // is SVG element wrapper
r = elem.attr(this.prop);
} else {
r = oldCur.apply(this, arguments);
}
return r;
};
}
/**
* Add a global listener for mousemove events
*/
/*addEvent(doc, 'mousemove', function(e) {
if (globalMouseMove) {
globalMouseMove(e);
}
});*/
/**
* Path interpolation algorithm used across adapters
*/
pathAnim = {
/**
* Prepare start and end values so that the path can be animated one to one
*/
init: function(elem, fromD, toD) {
fromD = fromD || '';
var shift = elem.shift,
bezier = fromD.indexOf('C') > -1,
numParams = bezier ? 7 : 3,
endLength,
slice,
i,
start = fromD.split(' '),
end = [].concat(toD), // copy
startBaseLine,
endBaseLine,
sixify = function(arr) { // in splines make move points have six parameters like bezier curves
i = arr.length;
while (i--) {
if (arr[i] == M) {
arr.splice(i + 1, 0, arr[i+1], arr[i+2], arr[i+1], arr[i+2]);
}
}
};
if (bezier) {
sixify(start);
sixify(end);
}
// pull out the base lines before padding
if (elem.isArea) {
startBaseLine = start.splice(start.length - 6, 6);
endBaseLine = end.splice(end.length - 6, 6);
}
// if shifting points, prepend a dummy point to the end path
if (shift) {
end = [].concat(end).splice(0, numParams).concat(end);
elem.shift = false; // reset for following animations
}
// copy and append last point until the length matches the end length
if (start.length) {
endLength = end.length;
while (start.length < endLength) {
//bezier && sixify(start);
slice = [].concat(start).splice(start.length - numParams, numParams);
if (bezier) { // disable first control point
slice[numParams - 6] = slice[numParams - 2];
slice[numParams - 5] = slice[numParams - 1];
}
start = start.concat(slice);
}
}
if (startBaseLine) { // append the base lines for areas
start = start.concat(startBaseLine);
end = end.concat(endBaseLine);
}
return [start, end];
},
/**
* Interpolate each value of the path and return the array
*/
step: function(start, end, pos, complete) {
var ret = [],
i = start.length,
startVal;
if (pos == 1) { // land on the final path without adjustment points appended in the ends
ret = complete;
} else if (i == end.length && pos < 1) {
while (i--) {
startVal = parseFloat(start[i]);
ret[i] =
isNaN(startVal) ? // a letter instruction like M or L
start[i] :
pos * (parseFloat(end[i] - startVal)) + startVal;
}
} else { // if animation is finished or length not matching, land on right value
ret = end;
}
return ret;
}
};
/**
* 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';
}
/**
* 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;
}
/**
* Get the updated default options. Merely exposing defaultOptions for outside modules
* isn't enough because the setOptions method creates a new object.
*/
function getOptions() {
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 = '';
}
/* ****************************************************************************
* Handle the options *
*****************************************************************************/
var
defaultLabelOptions = {
enabled: true,
// rotation: 0,
align: 'center',
x: 0,
y: 15,
/*formatter: function() {
return this.value;
},*/
style: {
color: '#666',
fontSize: '11px',
lineHeight: '14px'
}
};
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: {
//animation: true,
//alignTicks: false,
//reflow: true,
//className: null,
//events: { load, selection },
//margin: [null],
//marginTop: null,
//marginRight: null,
//marginBottom: null,
//marginLeft: null,
borderColor: '#4572A7',
//borderWidth: 0,
borderRadius: 5,
defaultSeriesType: 'line',
ignoreHiddenSeries: true,
//inverted: false,
//shadow: false,
spacingTop: 10,
spacingRight: 10,
spacingBottom: 15,
spacingLeft: 10,
style: {
fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
fontSize: '12px'
},
backgroundColor: '#FFFFFF',
//plotBackgroundColor: null,
plotBorderColor: '#C0C0C0'
//plotBorderWidth: 0,
//plotShadow: false,
//zoomType: ''
},
title: {
text: 'Chart title',
align: 'center',
// floating: false,
// margin: 15,
// x: 0,
// verticalAlign: 'top',
y: 15, // docs
style: {
color: '#3E576F',
fontSize: '16px'
}
},
subtitle: {
text: '',
align: 'center',
// floating: false
// x: 0,
// verticalAlign: 'top',
y: 30, // docs
style: {
color: '#6D869F'
}
},
plotOptions: {
line: { // base series options
allowPointSelect: false,
showCheckbox: false,
animation: {
duration: 1000
},
//cursor: 'default',
//dashStyle: null,
//enableMouseTracking: true,
events: {},
lineWidth: 2,
shadow: true,
// stacking: null,
marker: {
enabled: true,
//symbol: null,
lineWidth: 0,
radius: 4,
lineColor: '#FFFFFF',
//fillColor: null,
states: { // states for a single point
hover: {
//radius: base + 2
},
select: {
fillColor: '#FFFFFF',
lineColor: '#000000',
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: base + 1,
marker: {
// lineWidth: base + 1,
// radius: base + 1
}
},
select: {
marker: {}
}
},
stickyTracking: true
//zIndex: null
}
},
labels: {
//items: [],
style: {
//font: defaultFont,
position: ABSOLUTE,
color: '#3E576F'
}
},
legend: {
enabled: true,
align: 'center',
//floating: false,
layout: 'horizontal',
labelFormatter: function() {
return this.name;
},
// lineHeight: 16, // docs: deprecated
borderWidth: 1,
borderColor: '#909090',
borderRadius: 5,
// margin: 10,
// reversed: false,
shadow: false,
// backgroundColor: null,
style: {
padding: '5px'
},
itemStyle: {
cursor: 'pointer',
color: '#3E576F'
},
itemHoverStyle: {
cursor: 'pointer',
color: '#000000'
},
itemHiddenStyle: {
color: '#C0C0C0'
},
itemCheckboxStyle: {
position: ABSOLUTE,
width: '13px', // for IE precision
height: '13px'
},
// itemWidth: undefined,
symbolWidth: 16,
symbolPadding: 5,
verticalAlign: 'bottom',
// width: undefined,
x: 0, // docs
y: 0 // docs
},
loading: {
hideDuration: 100,
labelStyle: {
fontWeight: 'bold',
position: RELATIVE,
top: '1em'
},
showDuration: 100,
style: {
position: ABSOLUTE,
backgroundColor: 'white',
opacity: 0.5,
textAlign: 'center'
}
},
tooltip: {
enabled: true,
//crosshairs: null,
backgroundColor: 'rgba(255, 255, 255, .85)',
borderWidth: 2,
borderRadius: 5,
//formatter: defaultFormatter,
shadow: true,
//shared: false,
snap: hasTouch ? 25 : 10,
style: {
color: '#333333',
fontSize: '12px',
padding: '5px',
whiteSpace: 'nowrap'
}
},
toolbar: {
itemStyle: {
color: '#4572A7',
cursor: 'pointer'
}
},
credits: {
enabled: true,
text: 'Highcharts.com',
href: 'http://www.highcharts.com',
position: {
align: 'right',
x: -10,
verticalAlign: 'bottom',
y: -5
},
style: {
cursor: 'pointer',
color: '#909090',
fontSize: '10px'
}
}
};
// Axis defaults
var defaultXAxisOptions = {
// allowDecimals: null,
// 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',
// gridLineDashStyle: 'solid', // docs
// gridLineWidth: 0,
// reversed: false,
labels: defaultLabelOptions,
// { step: null },
lineColor: '#C0D0E0',
lineWidth: 1,
//linkedTo: null,
max: null,
min: null,
minPadding: 0.01,
maxPadding: 0.01,
//maxZoom: null,
minorGridLineColor: '#E0E0E0',
// minorGridLineDashStyle: null,
minorGridLineWidth: 1,
minorTickColor: '#A0A0A0',
//minorTickInterval: null,
minorTickLength: 2,
minorTickPosition: 'outside', // inside or outside
//minorTickWidth: 0,
//opposite: false,
//offset: 0,
//plotBands: [{
// events: {},
// zIndex: 1,
// labels: { align, x, verticalAlign, y, style, rotation, textAlign }
//}],
//plotLines: [{
// events: {}
// dashStyle: {}
// zIndex:
// labels: { align, x, verticalAlign, y, style, rotation, textAlign }
//}],
//reversed: false,
// showFirstLabel: true,
// showLastLabel: false,
startOfWeek: 1,
startOnTick: false,
tickColor: '#C0D0E0',
//tickInterval: null,
tickLength: 5,
tickmarkPlacement: 'between', // on or between
tickPixelInterval: 100,
tickPosition: 'outside',
tickWidth: 1,
title: {
//text: null,
align: 'middle', // low, middle or high
//margin: 0 for horizontal, 10 for vertical axes,
//rotation: 0,
//side: 'outside',
style: {
color: '#6D869F',
//font: defaultFont.replace('normal', 'bold')
fontWeight: 'bold'
}
//x: 0,
//y: 0
},
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: {
rotation: 270,
text: 'Y-values'
}
}),
defaultLeftAxisOptions = {
labels: {
align: 'right',
x: -8,
y: null // docs
},
title: {
rotation: 270
}
},
defaultRightAxisOptions = {
labels: {
align: 'left',
x: 8,
y: null // docs
},
title: {
rotation: 90
}
},
defaultBottomAxisOptions = { // horizontal axis
labels: {
align: 'center',
x: 0,
y: 14
// staggerLines: null
},
title: {
rotation: 0
}
},
defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
labels: {
y: -5
// staggerLines: null
}
});
// Series defaults
var defaultPlotOptions = defaultOptions.plotOptions,
defaultSeriesOptions = defaultPlotOptions.line;
//defaultPlotOptions.line = merge(defaultSeriesOptions);
defaultPlotOptions.spline = merge(defaultSeriesOptions);
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
lineWidth: 0,
states: {
hover: {
lineWidth: 0
}
}
});
defaultPlotOptions.area = merge(defaultSeriesOptions, {
// threshold: 0,
// lineColor: null, // overrides color, but lets fillColor be unaltered
// fillOpacity: 0.75,
// fillColor: null
});
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
defaultPlotOptions.column = merge(defaultSeriesOptions, {
borderColor: '#FFFFFF',
borderWidth: 1,
borderRadius: 0,
//colorByPoint: undefined,
groupPadding: 0.2,
marker: null, // point options are specified in the base options
pointPadding: 0.1,
//pointWidth: null,
minPointLength: 0,
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%'],
colorByPoint: true, // always true for pies
dataLabels: {
// align: null,
// connectorWidth: 1,
// connectorColor: '#606060',
// connectorPadding: 5,
distance: 30,
enabled: true,
formatter: function() {
return this.point.name;
},
y: 5
},
//innerSize: 0,
legendType: 'point',
marker: null, // point options are specified in the base options
size: '75%',
showInLegend: false,
slicedOffset: 10,
states: {
hover: {
brightness: 0.1,
shadow: false
}
}
});
// set the default time methods
setTimeMethods();
/**
* Extend a prototyped class by new members
* @param {Object} parent
* @param {Object} members
*/
function extendClass(parent, members) {
var object = function(){};
object.prototype = new parent();
extend(object.prototype, members);
return object;
}
/**
* Handle color operations. The object methods are chainable.
* @param {String} input The input color in either rbga or hex format
*/
var Color = function(input) {
// declare variables
var rgba = [], result;
/**
* Parse the input color to rgba array
* @param {String} input
*/
function init(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 = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
}
// hex
else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input))) {
rgba = [pInt(result[1],16), pInt(result[2],16), pInt(result[3],16), 1];
}
}
/**
* Return the color a specified format
* @param {String} format
*/
function get(format) {
var ret;
// it's NaN if gradient colors on a column chart
if (rgba && !isNaN(rgba[0])) {
if (format == 'rgb') {
ret = 'rgb('+ rgba[0] +','+ rgba[1] +','+ rgba[2] +')';
} else if (format == 'a') {
ret = rgba[3];
} else {
ret = 'rgba('+ rgba.join(',') +')';
}
} else {
ret = input;
}
return ret;
}
/**
* Brighten the color
* @param {Number} alpha
*/
function brighten(alpha) {
if (isNumber(alpha) && alpha !== 0) {
var i;
for (i = 0; i < 3; i++) {
rgba[i] += pInt(alpha * 255);
if (rgba[i] < 0) {
rgba[i] = 0;
}
if (rgba[i] > 255) {
rgba[i] = 255;
}
}
}
return this;
}
/**
* Set the color's opacity to a given alpha value
* @param {Number} alpha
*/
function setOpacity(alpha) {
rgba[3] = alpha;
return this;
}
// initialize: parse the input
init(input);
// public methods
return {
get: get,
brighten: brighten,
setOpacity: setOpacity
};
};
/**
* 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 = pInt(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
*/
dateFormat = function (format, timestamp, capitalize) {
function pad (number) {
return number.toString().replace(/^([0-9])$/, '0$1');
}
if (!defined(timestamp) || isNaN(timestamp)) {
return 'Invalid date';
}
format = pick(format, '%Y-%m-%d %H:%M:%S');
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;
};
/**
* Loop up the node tree and add offsetWidth and offsetHeight to get the
* total page offset for a given element. Used by Opera and iOS on hover and
* all browsers on point click.
*
* @param {Object} el
*
*/
function getPosition (el) {
var p = { left: el.offsetLeft, top: el.offsetTop };
while ((el = el.offsetParent)) {
p.left += el.offsetLeft;
p.top += el.offsetTop;
if (el != doc.body && el != doc.documentElement) {
p.left -= el.scrollLeft;
p.top -= el.scrollTop;
}
}
return p;
}
/**
* A wrapper object for SVG elements
*/
function SVGElement () {}
SVGElement.prototype = {
/**
* Initialize the SVG renderer
* @param {Object} renderer
* @param {String} nodeName
*/
init: function(renderer, nodeName) {
this.element = doc.createElementNS(SVG_NS, nodeName);
this.renderer = renderer;
},
/**
* Animate a given attribute
* @param {Object} params
* @param {Number} options The same options as in jQuery animation
* @param {Function} complete Function to perform at the end of animation
*/
animate: function(params, options, complete) {
var animOptions = pick(options, globalAnimation, true);
if (animOptions) {
animOptions = merge(animOptions);
if (complete) { // allows using a callback with the global animation without overwriting it
animOptions.complete = complete;
}
animate(this, params, animOptions);
} else {
this.attr(params);
if (complete) {
complete();
}
}
},
/**
* Set or get a given attribute
* @param {Object|String} hash
* @param {Mixed|Undefined} val
*/
attr: function(hash, val) {
var key,
value,
i,
child,
element = this.element,
nodeName = element.nodeName,
renderer = this.renderer,
skipAttr,
shadows = this.shadows,
hasSetSymbolSize,
ret = this;
// single key-value pair
if (isString(hash) && defined(val)) {
key = hash;
hash = {};
hash[key] = val;
}
// used as a getter: first argument is a string, second is undefined
if (isString(hash)) {
key = hash;
if (nodeName == 'circle') {
key = { x: 'cx', y: 'cy' }[key] || key;
} else if (key == 'strokeWidth') {
key = 'stroke-width';
}
ret = attr(element, key) || this[key] || 0;
if (key != 'd' && key != 'visibility') { // 'd' is string in animation step
ret = parseFloat(ret);
}
// setter
} else {
for (key in hash) {
skipAttr = false; // reset
value = hash[key];
// paths
if (key == 'd') {
if (value && value.join) { // join path
value = value.join(' ');
}
if (/(NaN| {2}|^$)/.test(value)) {
value = 'M 0 0';
}
this.d = value; // shortcut for animations
// update child tspans x values
} else if (key == 'x' && nodeName == 'text') {
for (i = 0; i < element.childNodes.length; i++ ) {
child = element.childNodes[i];
// if the x values are equal, the tspan represents a linebreak
if (attr(child, 'x') == attr(element, 'x')) {
//child.setAttribute('x', value);
attr(child, 'x', value);
}
}
if (this.rotation) {
attr(element, 'transform', 'rotate('+ this.rotation +' '+ value +' '+
pInt(hash.y || attr(element, 'y')) +')');
}
// apply gradients
} else if (key == 'fill') {
value = renderer.color(value, element, key);
// circle x and y
} else if (nodeName == 'circle' && (key == 'x' || key == 'y')) {
key = { x: 'cx', y: 'cy' }[key] || key;
// translation and text rotation
} else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'verticalAlign') {
this[key] = value;
this.updateTransform();
skipAttr = true;
// apply opacity as subnode (required by legacy WebKit and Batik)
} else if (key == 'stroke') {
value = renderer.color(value, element, key);
// emulate VML's dashstyle implementation
} else if (key == 'dashstyle') {
key = 'stroke-dasharray';
if (value) {
value = value.toLowerCase()
.replace('shortdashdotdot', '3,1,1,1,1,1,')
.replace('shortdashdot', '3,1,1,1')
.replace('shortdot', '1,1,')
.replace('shortdash', '3,1,')
.replace('longdash', '8,3,')
.replace(/dot/g, '1,3,')
.replace('dash', '4,3,')
.replace(/,$/, '')
.split(','); // ending comma
i = value.length;
while (i--) {
value[i] = pInt(value[i]) * hash['stroke-width'];
}
value = value.join(',');
}
// special
} else if (key == 'isTracker') {
this[key] = value;
// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
// is unable to cast them. Test again with final IE9.
} else if (key == 'width') {
value = pInt(value);
// Text alignment
} else if (key == 'align') {
key = 'text-anchor';
value = { left: 'start', center: 'middle', right: 'end' }[value];
}
// jQuery animate changes case
if (key == 'strokeWidth') {
key = 'stroke-width';
}
// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
if (isWebKit && key == 'stroke-width' && value === 0) {
value = 0.000001;
}
// symbols
if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
if (!hasSetSymbolSize) {
this.symbolAttr(hash);
hasSetSymbolSize = true;
}
skipAttr = true;
}
// let the shadow follow the main element
if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
i = shadows.length;
while (i--) {
attr(shadows[i], key, value);
}
}
/* trows errors in Chrome
if ((key == 'width' || key == 'height') && nodeName == 'rect' && value < 0) {
console.log(element);
}
*/
if (key == 'text') {
// only one node allowed
this.textStr = value;
renderer.buildText(this);
} else if (!skipAttr) {
//element.setAttribute(key, value);
attr(element, key, value);
}
}
}
return ret;
},
/**
* If one of the symbol size affecting parameters are changed,
* check all the others only once for each call to an element's
* .attr() method
* @param {Object} hash
*/
symbolAttr: function(hash) {
var wrapper = this;
wrapper.x = pick(hash.x, wrapper.x);
wrapper.y = pick(hash.y, wrapper.y); // mootools animation bug needs parseFloat
wrapper.r = pick(hash.r, wrapper.r);
wrapper.start = pick(hash.start, wrapper.start);
wrapper.end = pick(hash.end, wrapper.end);
wrapper.width = pick(hash.width, wrapper.width);
wrapper.height = pick(hash.height, wrapper.height);
wrapper.innerR = pick(hash.innerR, wrapper.innerR);
wrapper.attr({
d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.r, {
start: wrapper.start,
end: wrapper.end,
width: wrapper.width,
height: wrapper.height,
innerR: wrapper.innerR
})
});
},
/**
* Apply a clipping path to this object
* @param {String} id
*/
clip: function(clipRect) {
return this.attr('clip-path', 'url('+ this.renderer.url +'#'+ clipRect.id +')');
},
/**
* Set styles for the element
* @param {Object} styles
*/
css: function(styles) {
var elemWrapper = this,
elem = elemWrapper.element;
// convert legacy
if (styles && styles.color) {
styles.fill = styles.color;
}
// save the styles in an object
styles = extend(
elemWrapper.styles,
styles
);
// serialize and set style attribute
if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
css(elemWrapper.element, styles);
} else {
elemWrapper.attr({
style: serializeCSS(styles)
});
}
// store object
elemWrapper.styles = styles;
// re-build text
if (styles.width && elem.nodeName == 'text' && elemWrapper.added) {
elemWrapper.renderer.buildText(elemWrapper);
}
return elemWrapper;
},
/**
* Add an event listener
* @param {String} eventType
* @param {Function} handler
*/
on: function(eventType, handler) {
var fn = handler;
// touch
if (hasTouch && eventType == 'click') {
eventType = 'touchstart';
fn = function(e) {
e.preventDefault();
handler();
}
}
// simplest possible event model for internal use
this.element['on'+ eventType] = fn;
return this;
},
/**
* Move an object and its children by x and y values
* @param {Number} x
* @param {Number} y
*/
translate: function(x, y) {
return this.attr({
translateX: x,
translateY: y
});
},
/**
* Invert a group, rotate and flip
*/
invert: function() {
var wrapper = this;
wrapper.inverted = true;
wrapper.updateTransform();
return wrapper;
},
/**
* Private method to update the transform attribute based on internal
* properties
*/
updateTransform: function() {
var wrapper = this,
translateX = wrapper.translateX || 0,
translateY = wrapper.translateY || 0,
inverted = wrapper.inverted,
rotation = wrapper.rotation,
transform = [];
// flipping affects translate as adjustment for flipping around the group's axis
if (inverted) {
translateX += wrapper.attr('width');
translateY += wrapper.attr('height');
}
// apply translate
if (translateX || translateY) {
transform.push('translate('+ translateX +','+ translateY +')');
}
// apply rotation
if (inverted) {
transform.push('rotate(90) scale(-1,1)');
} else if (rotation) { // text rotation
transform.push('rotate('+ rotation +' '+ wrapper.x +' '+ wrapper.y +')');
}
if (transform.length) {
attr(wrapper.element, 'transform', transform.join(' '));
}
},
/**
* Bring the element to the front
*/
toFront: function() {
var element = this.element;
element.parentNode.appendChild(element);
return this;
},
/**
* Break down alignment options like align, verticalAlign, x and y
* to x and y relative to the chart.
*
* @param {Object} alignOptions
* @param {Boolean} alignByTranslate
* @param {Object} box The box to align to, needs a width and height
*
*/
align: function(alignOptions, alignByTranslate, box) {
if (!alignOptions) { // called on resize
alignOptions = this.alignOptions;
alignByTranslate = this.alignByTranslate;
} else { // first call on instanciate
this.alignOptions = alignOptions;
this.alignByTranslate = alignByTranslate;
if (!box) { // boxes other than renderer handle this internally
this.renderer.alignedObjects.push(this);
}
}
box = pick(box, this.renderer);
var align = alignOptions.align,
vAlign = alignOptions.verticalAlign,
x = (box.x || 0) + (alignOptions.x || 0), // default: left align
y = (box.y || 0) + (alignOptions.y || 0), // default: top align
attribs = {};
// align
if (/^(right|center)$/.test(align)) {
x += (box.width - (alignOptions.width || 0) ) /
{ right: 1, center: 2 }[align];
}
attribs[alignByTranslate ? 'translateX' : 'x'] = x;
// vertical align
if (/^(bottom|middle)$/.test(vAlign)) {
y += (box.height - (alignOptions.height || 0)) /
({ bottom: 1, middle: 2 }[vAlign] || 1);
}
attribs[alignByTranslate ? 'translateY' : 'y'] = y;
// animate only if already placed
this[this.placed ? 'animate' : 'attr'](attribs);
this.placed = true;
return this;
},
/**
* Get the bounding box (width, height, x and y) for the element
*/
getBBox: function() {
var bBox,
width,
height,
rotation = this.rotation,
rad = rotation * deg2rad;
try { // fails in Firefox if the container has display: none
// use extend because IE9 is not allowed to change width and height in case
// of rotation (below)
bBox = extend({}, this.element.getBBox());
} catch(e) {
bBox = { width: 0, height: 0 };
}
width = bBox.width;
height = bBox.height;
// adjust for rotated text
if (rotation) {
bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
}
return bBox;
},
/* *
* Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
* @param {Object} bBox
* @param {number} rotation
* /
rotateBBox: function(bBox, rotation) {
var rad = rotation * math.PI * 2 / 360, // radians
width = bBox.width,
height = bBox.height;
},*/
/**
* Show the element
*/
show: function() {
return this.attr({ visibility: VISIBLE });
},
/**
* Hide the element
*/
hide: function() {
return this.attr({ visibility: HIDDEN });
},
/**
* Add the element
* @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
* to append the element to the renderer.box.
*/
add: function(parent) {
var renderer = this.renderer,
parentWrapper = parent || renderer,
parentNode = parentWrapper.element || renderer.box,
childNodes = parentNode.childNodes,
element = this.element,
zIndex = attr(element, 'zIndex'),
textStr = this.textStr,
otherElement,
otherZIndex,
i;
// mark as inverted
this.parentInverted = parent && parent.inverted;
// mark the container as having z indexed children
if (zIndex) {
parentWrapper.handleZ = true;
zIndex = pInt(zIndex);
}
// insert according to this and other elements' zIndex
if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
for (i = 0; i < childNodes.length; i++) {
otherElement = childNodes[i];
otherZIndex = attr(otherElement, 'zIndex');
if (otherElement != element && (
// insert before the first element with a higher zIndex
pInt(otherZIndex) > zIndex ||
// if no zIndex given, insert before the first element with a zIndex
(!defined(zIndex) && defined(otherZIndex))
)) {
parentNode.insertBefore(element, otherElement);
return this;
}
}
}
// operations before adding
if (textStr !== undefined) {
renderer.buildText(this);
this.added = true;
}
// default: append at the end
parentNode.appendChild(element);
return this;
},
/**
* Destroy the element and element wrapper
*/
destroy: function() {
var wrapper = this,
element = wrapper.element || {},
shadows = wrapper.shadows,
parentNode = element.parentNode,
key;
// remove events
element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
stop(wrapper); // stop running animations
// remove element
if (parentNode) {
parentNode.removeChild(element);
}
// destroy shadows
if (shadows) {
each(shadows, function(shadow) {
parentNode = shadow.parentNode;
if (parentNode) { // the entire chart HTML can be overwritten
parentNode.removeChild(shadow);
}
});
}
// remove from alignObjects
erase(wrapper.renderer.alignedObjects, wrapper);
for (key in wrapper) {
delete wrapper[key];
}
return null;
},
/**
* Empty a group element
*/
empty: function() {
var element = this.element,
childNodes = element.childNodes,
i = childNodes.length;
while (i--) {
element.removeChild(childNodes[i]);
}
},
/**
* Add a shadow to the element. Must be done after the element is added to the DOM
* @param {Boolean} apply
*/
shadow: function(apply) {
var shadows = [],
i,
shadow,
element = this.element,
// compensate for inverted plot area
transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
if (apply) {
for (i = 1; i <= 3; i++) {
shadow = element.cloneNode(0);
attr(shadow, {
'isShadow': 'true',
'stroke': 'rgb(0, 0, 0)',
'stroke-opacity': 0.05 * i,
'stroke-width': 7 - 2 * i,
'transform': 'translate'+ transform,
'fill': NONE
});
element.parentNode.insertBefore(shadow, element);
shadows.push(shadow);
}
this.shadows = shadows;
}
return this;
}
};
/**
* The default SVG renderer
*/
var SVGRenderer = function() {
this.init.apply(this, arguments);
};
SVGRenderer.prototype = {
/**
* Initialize the SVGRenderer
* @param {Object} container
* @param {Number} width
* @param {Number} height
*/
init: function(container, width, height) {
var renderer = this,
loc = location,
boxWrapper;
renderer.Element = SVGElement;
boxWrapper = renderer.createElement('svg')
.attr({
xmlns: SVG_NS,
version: '1.1'
});
container.appendChild(boxWrapper.element);
// object properties
renderer.box = boxWrapper.element;
renderer.boxWrapper = boxWrapper;
renderer.alignedObjects = [];
renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
renderer.defs = this.createElement('defs').add();
renderer.setSize(width, height, false);
},
/**
* Create a wrapper for an SVG element
* @param {Object} nodeName
*/
createElement: function(nodeName) {
var wrapper = new this.Element();
wrapper.init(this, nodeName);
return wrapper;
},
/**
* Parse a simple HTML string into SVG tspans
*
* @param {Object} textNode The parent text SVG node
*/
buildText: function(wrapper) {
var textNode = wrapper.element,
lines = pick(wrapper.textStr, '').toString()
.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
.replace(/<(i|em)>/g, '<span style="font-style:italic">')
.replace(/<a/g, '<span')
.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
.split(/<br[^>]?>/g),
childNodes = textNode.childNodes,
styleRegex = /style="([^"]+)"/,
hrefRegex = /href="([^"]+)"/,
parentX = attr(textNode, 'x'),
textStyles = wrapper.styles,
width = textStyles && pInt(textStyles.width),
textLineHeight = textStyles && textStyles.lineHeight,
lastLine,
i = childNodes.length;
// remove old text
while (i--) {
textNode.removeChild(childNodes[i]);
}
if (width) {
this.box.appendChild(textNode); // attach it to the DOM to read offset width
}
each(lines, function(line, lineNo) {
var spans, spanNo = 0, lineHeight;
line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
spans = line.split('|||');
each(spans, function (span) {
if (span !== '' || spans.length == 1) {
var attributes = {},
tspan = doc.createElementNS(SVG_NS, 'tspan');
if (styleRegex.test(span)) {
attr(
tspan,
'style',
span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
);
}
if (hrefRegex.test(span)) {
attr(tspan, 'onclick', 'location.href=\"'+ span.match(hrefRegex)[1] +'\"');
css(tspan, { cursor: 'pointer' });
}
span = span.replace(/<(.|\n)*?>/g, '') || ' ';
tspan.appendChild(doc.createTextNode(span)); // WebKit needs a string
//console