Вход Регистрация
Файл: protected/extensions/widgets/highcharts/assets/highcharts-more.src.js
Строк: 2528
<?php
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS

/**
 * @license Highcharts JS v3.0.4 (2013-08-02)
 *
 * (c) 2009-2013 Torstein Hønsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */

(function (Highcharts, UNDEFINED) {
var 
arrayMin = Highcharts.arrayMin,
    
arrayMax = Highcharts.arrayMax,
    
each = Highcharts.each,
    
extend = Highcharts.extend,
    
merge = Highcharts.merge,
    
map = Highcharts.map,
    
pick = Highcharts.pick,
    
pInt = Highcharts.pInt,
    
defaultPlotOptions = Highcharts.getOptions().plotOptions,
    
seriesTypes = Highcharts.seriesTypes,
    
extendClass = Highcharts.extendClass,
    
splat = Highcharts.splat,
    
wrap = Highcharts.wrap,
    
Axis = Highcharts.Axis,
    
Tick = Highcharts.Tick,
    
Series = Highcharts.Series,
    
colProto = seriesTypes.column.prototype,
    
math = Math,
    
mathRound = math.round,
    
mathFloor = math.floor,
    
mathMax = math.max,
    
noop = function () {};/**
 * The Pane object allows options that are common to a set of X and Y axes.
 * 
 * In the future, this can be extended to basic Highcharts and Highstock.
 */
function Pane(options, chart, firstAxis) {
    
this.init.call(this, options, chart, firstAxis);
}

// Extend the Pane prototype
extend(Pane.prototype, {
    
    
/**
     * Initiate the Pane object
     */
    
init: function (options, chart, firstAxis) {
        var 
pane = this,
            
backgroundOption,
            
defaultOptions = pane.defaultOptions;
        
        
pane.chart = chart;
        
        
// Set options
        
if (chart.angular) { // gauges
            
defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions
        
}
        
pane.options = options = merge(defaultOptions, options);
        
        
backgroundOption = options.background;
        
        
// To avoid having weighty logic to place, update and remove the backgrounds,
        // push them to the first axis' plot bands and borrow the existing logic there.
        
if (backgroundOption) {
            
each([].concat(splat(backgroundOption)).reverse(), function (config) {
                var 
backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)
                
config = merge(pane.defaultBackgroundOptions, config);
                if (
backgroundColor) {
                    
config.backgroundColor = backgroundColor;
                }
                
config.color = config.backgroundColor; // due to naming in plotBands
                
firstAxis.options.plotBands.unshift(config);
            });
        }
    },
    
    
/**
     * The default options object
     */
    
defaultOptions: {
        
// background: {conditional},
        
center: ['50%', '50%'],
        
size: '85%',
        
startAngle: 0
        
//endAngle: startAngle + 360
    
},    
    
    
/**
     * The default background options
     */
    
defaultBackgroundOptions: {
        
shape: 'circle',
        
borderWidth: 1,
        
borderColor: 'silver',
        
backgroundColor: {
            
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
            
stops: [
                [
0, '#FFF'],
                [
1, '#DDD']
            ]
        },
        
from: Number.MIN_VALUE, // corrected to axis min
        
innerRadius: 0,
        
to: Number.MAX_VALUE, // corrected to axis max
        
outerRadius: '105%'
    
}
    
});
var 
axisProto = Axis.prototype,
    
tickProto = Tick.prototype;
    
/**
 * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
 */
var hiddenAxisMixin = {
    
getOffset: noop,
    
redraw: function () {
        
this.isDirty = false; // prevent setting Y axis dirty
    
},
    
render: function () {
        
this.isDirty = false; // prevent setting Y axis dirty
    
},
    
setScale: noop,
    
setCategories: noop,
    
setTitle: noop
};

/**
 * Augmented methods for the value axis
 */
/*jslint unparam: true*/
var radialAxisMixin = {
    
isRadial: true,
    
    
/**
     * The default options extend defaultYAxisOptions
     */
    
defaultRadialGaugeOptions: {
        
labels: {
            
align: 'center',
            
x: 0,
            
y: null // auto
        
},
        
minorGridLineWidth: 0,
        
minorTickInterval: 'auto',
        
minorTickLength: 10,
        
minorTickPosition: 'inside',
        
minorTickWidth: 1,
        
plotBands: [],
        
tickLength: 10,
        
tickPosition: 'inside',
        
tickWidth: 2,
        
title: {
            
rotation: 0
        
},
        
zIndex: 2 // behind dials, points in the series group
    
},
    
    
// Circular axis around the perimeter of a polar chart
    
defaultRadialXOptions: {
        
gridLineWidth: 1, // spokes
        
labels: {
            
align: null, // auto
            
distance: 15,
            
x: 0,
            
y: null // auto
        
},
        
maxPadding: 0,
        
minPadding: 0,
        
plotBands: [],
        
showLastLabel: false, 
        
tickLength: 0
    
},
    
    
// Radial axis, like a spoke in a polar chart
    
defaultRadialYOptions: {
        
gridLineInterpolation: 'circle',
        
labels: {
            
align: 'right',
            
x: -3,
            
y: -2
        
},
        
plotBands: [],
        
showLastLabel: false,
        
title: {
            
x: 4,
            
text: null,
            
rotation: 90
        
}
    },
    
    
/**
     * Merge and set options
     */
    
setOptions: function (userOptions) {
        
        
this.options = merge(
            
this.defaultOptions,
            
this.defaultRadialOptions,
            
userOptions
        
);
        
    },
    
    
/**
     * Wrap the getOffset method to return zero offset for title or labels in a radial 
     * axis
     */
    
getOffset: function () {
        
// Call the Axis prototype method (the method we're in now is on the instance)
        
axisProto.getOffset.call(this);
        
        
// Title or label offsets are not counted
        
this.chart.axisOffset[this.side] = 0;
        
        
// Set the center array
        
this.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);
    },


    
/**
     * Get the path for the axis line. This method is also referenced in the getPlotLinePath
     * method.
     */
    
getLinePath: function (lineWidth, radius) {
        var 
center = this.center;
        
radius = pick(radius, center[2] / 2 - this.offset);
        
        return 
this.chart.renderer.symbols.arc(
            
this.left + center[0],
            
this.top + center[1],
            
radius,
            
radius, 
            {
                
start: this.startAngleRad,
                
end: this.endAngleRad,
                
open: true,
                
innerR: 0
            
}
        );
    },

    
/**
     * Override setAxisTranslation by setting the translation to the difference
     * in rotation. This allows the translate method to return angle for 
     * any given value.
     */
    
setAxisTranslation: function () {
        
        
// Call uber method        
        
axisProto.setAxisTranslation.call(this);
            
        
// Set transA and minPixelPadding
        
if (this.center) { // it's not defined the first time
            
if (this.isCircular) {
                
                
this.transA = (this.endAngleRad - this.startAngleRad) / 
                    ((
this.max - this.min) || 1);
                    
                
            } else { 
                
this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
            }
            
            if (
this.isXAxis) {
                
this.minPixelPadding = this.transA * this.minPointOffset +
                    (
this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???
            
}
        }
    },
    
    
/**
     * In case of auto connect, add one closestPointRange to the max value right before
     * tickPositions are computed, so that ticks will extend passed the real max.
     */
    
beforeSetTickPositions: function () {
        if (
this.autoConnect) {
            
this.max += (this.categories && 1) || this.pointRange || this.closestPointRange; // #1197
        
}
    },
    
    
/**
     * Override the setAxisSize method to use the arc's circumference as length. This
     * allows tickPixelInterval to apply to pixel lengths along the perimeter
     */
    
setAxisSize: function () {
        
        
axisProto.setAxisSize.call(this);
        
        if (
this.center) { // it's not defined the first time
            
this.len = this.width = this.height = this.isCircular ?
                
this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :
                
this.center[2] / 2;
        }
    },
    
    
/**
     * Returns the x, y coordinate of a point given by a value and a pixel distance
     * from center
     */
    
getPosition: function (value, length) {
        if (!
this.isCircular) {
            
length = this.translate(value);
            
value = this.min;    
        }
        
        return 
this.postTranslate(
            
this.translate(value),
            
pick(length, this.center[2] / 2) - this.offset
        
);        
    },
    
    
/**
     * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. 
     */
    
postTranslate: function (angle, radius) {
        
        var 
chart = this.chart,
            
center = this.center;
            
        
angle = this.startAngleRad + angle;
        
        return {
            
x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
            
y: chart.plotTop + center[1] + Math.sin(angle) * radius
        
}; 
        
    },
    
    
/**
     * Find the path for plot bands along the radial axis
     */
    
getPlotBandPath: function (from, to, options) {
        var 
center = this.center,
            
startAngleRad = this.startAngleRad,
            
fullRadius = center[2] / 2,
            
radii = [
                
pick(options.outerRadius, '100%'),
                
options.innerRadius,
                
pick(options.thickness, 10)
            ],
            
percentRegex = /%$/,
            
start,
            
end,
            
open,
            
isCircular = this.isCircular, // X axis in a polar chart
            
ret;
            
        
// Polygonal plot bands
        
if (this.options.gridLineInterpolation === 'polygon') {
            
ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
        
        
// Circular grid bands
        
} else {
            
            
// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
            
if (!isCircular) {
                
radii[0] = this.translate(from);
                
radii[1] = this.translate(to);
            }
            
            
// Convert percentages to pixel values
            
radii = map(radii, function (radius) {
                if (
percentRegex.test(radius)) {
                    
radius = (pInt(radius, 10) * fullRadius) / 100;
                }
                return 
radius;
            });
            
            
// Handle full circle
            
if (options.shape === 'circle' || !isCircular) {
                
start = -Math.PI / 2;
                
end = Math.PI * 1.5;
                
open = true;
            } else {
                
start = startAngleRad + this.translate(from);
                
end = startAngleRad + this.translate(to);
            }
        
        
            
ret = this.chart.renderer.symbols.arc(
                
this.left + center[0],
                
this.top + center[1],
                
radii[0],
                
radii[0],
                {
                    
start: start,
                    
end: end,
                    
innerR: pick(radii[1], radii[0] - radii[2]),
                    
open: open
                
}
            );
        }
         
        return 
ret;
    },
    
    
/**
     * Find the path for plot lines perpendicular to the radial axis.
     */
    
getPlotLinePath: function (value, reverse) {
        var 
axis = this,
            
center = axis.center,
            
chart = axis.chart,
            
end = axis.getPosition(value),
            
xAxis,
            
xy,
            
tickPositions,
            
ret;
        
        
// Spokes
        
if (axis.isCircular) {
            
ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
        
        
// Concentric circles            
        
} else if (axis.options.gridLineInterpolation === 'circle') {
            
value = axis.translate(value);
            if (
value) { // a value of 0 is in the center
                
ret = axis.getLinePath(0, value);
            }
        
// Concentric polygons 
        
} else {
            
xAxis = chart.xAxis[0];
            
ret = [];
            
value = axis.translate(value);
            
tickPositions = xAxis.tickPositions;
            if (
xAxis.autoConnect) {
                
tickPositions = tickPositions.concat([tickPositions[0]]);
            }
            
// Reverse the positions for concatenation of polygonal plot bands
            
if (reverse) {
                
tickPositions = [].concat(tickPositions).reverse();
            }
                
            
each(tickPositions, function (pos, i) {
                
xy = xAxis.getPosition(pos, value);
                
ret.push(i ? 'L' : 'M', xy.x, xy.y);
            });
            
        }
        return 
ret;
    },
    
    
/**
     * Find the position for the axis title, by default inside the gauge
     */
    
getTitlePosition: function () {
        var 
center = this.center,
            
chart = this.chart,
            
titleOptions = this.options.title;
        
        return { 
            
x: chart.plotLeft + center[0] + (titleOptions.x || 0), 
            
y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * 
                
center[2]) + (titleOptions.y || 0)  
        };
    }
    
};
/*jslint unparam: false*/

/**
 * Override axisProto.init to mix in special axis instance functions and function overrides
 */
wrap(axisProto, 'init', function (proceed, chart, userOptions) {
    var 
axis = this,
        
angular = chart.angular,
        
polar = chart.polar,
        
isX = userOptions.isX,
        
isHidden = angular && isX,
        
isCircular,
        
startAngleRad,
        
endAngleRad,
        
options,
        
chartOptions = chart.options,
        
paneIndex = userOptions.pane || 0,
        
pane,
        
paneOptions;
        
    
// Before prototype.init
    
if (angular) {
        
extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
        
isCircular =  !isX;
        if (
isCircular) {
            
this.defaultRadialOptions = this.defaultRadialGaugeOptions;
        }
        
    } else if (
polar) {
        
//extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);
        
extend(this, radialAxisMixin);
        
isCircular = isX;
        
this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
        
    }
    
    
// Run prototype.init
    
proceed.call(this, chart, userOptions);
    
    if (!
isHidden && (angular || polar)) {
        
options = this.options;
        
        
// Create the pane and set the pane options.
        
if (!chart.panes) {
            
chart.panes = [];
        }
        
this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(
            
splat(chartOptions.pane)[paneIndex],
            
chart,
            
axis
        
);
        
paneOptions = pane.options;
        
            
        
// Disable certain features on angular and polar axes
        
chart.inverted = false;
        
chartOptions.chart.zoomType = null;
        
        
// Start and end angle options are
        // given in degrees relative to top, while internal computations are
        // in radians relative to right (like SVG).
        
this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
        
this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360)  - 90) * Math.PI / 180;
        
this.offset = options.offset || 0;
        
        
this.isCircular = isCircular;
        
        
// Automatically connect grid lines?
        
if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {
            
this.autoConnect = true;
        }
    }
    
});

/**
 * Add special cases within the Tick class' methods for radial axes.
 */    
wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {
    var 
axis = this.axis;
    
    return 
axis.getPosition ? 
        
axis.getPosition(pos) :
        
proceed.call(this, horiz, pos, tickmarkOffset, old);    
});

/**
 * Wrap the getLabelPosition function to find the center position of the label
 * based on the distance option
 */    
wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
    var 
axis = this.axis,
        
optionsY = labelOptions.y,
        
ret,
        
align = labelOptions.align,
        
angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
    
    if (
axis.isRadial) {
        
ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));
        
        
// Automatically rotated
        
if (labelOptions.rotation === 'auto') {
            
label.attr({ 
                
rotation: angle
            
});
        
        
// Vertically centered
        
} else if (optionsY === null) {
            
optionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
        
        }
        
        
// Automatic alignment
        
if (align === null) {
            if (
axis.isCircular) {
                if (
angle > 20 && angle < 160) {
                    
align = 'left'; // right hemisphere
                
} else if (angle > 200 && angle < 340) {
                    
align = 'right'; // left hemisphere
                
} else {
                    
align = 'center'; // top or bottom
                
}
            } else {
                
align = 'center';
            }
            
label.attr({
                
align: align
            
});
        }
        
        
ret.x += labelOptions.x;
        
ret.y += optionsY;
        
    } else {
        
ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
    }
    return 
ret;
});

/**
 * Wrap the getMarkPath function to return the path of the radial marker
 */
wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
    var 
axis = this.axis,
        
endPoint,
        
ret;
        
    if (
axis.isRadial) {
        
endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
        
ret = [
            
'M',
            
x,
            
y,
            
'L',
            
endPoint.x,
            
endPoint.y
        
];
    } else {
        
ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
    }
    return 
ret;
});
/* 
 * The AreaRangeSeries class
 * 
 */

/**
 * Extend the default options with map options
 */
defaultPlotOptions.arearange = merge(defaultPlotOptions.area, {
    
lineWidth: 1,
    
marker: null,
    
threshold: null,
    
tooltip: {
        
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' 
    
},
    
trackByArea: true,
    
dataLabels: {
        
verticalAlign: null,
        
xLow: 0,
        
xHigh: 0,
        
yLow: 0,
        
yHigh: 0    
    
}
});

/**
 * Add the series type
 */
seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {
    
type: 'arearange',
    
pointArrayMap: ['low', 'high'],
    
toYData: function (point) {
        return [
point.low, point.high];
    },
    
pointValKey: 'low',
    
    
/**
     * Extend getSegments to force null points if the higher value is null. #1703.
     */
    
getSegments: function () {
        var 
series = this;

        
each(series.points, function (point) {
            if (!
series.options.connectNulls && (point.low === null || point.high === null)) {
                
point.y = null;
            } else if (
point.low === null && point.high !== null) {
                
point.y = point.high;
            }
        });
        
Series.prototype.getSegments.call(this);
    },
    
    
/**
     * Translate data points from raw values x and y to plotX and plotY
     */
    
translate: function () {
        var 
series = this,
            
yAxis = series.yAxis;

        
seriesTypes.area.prototype.translate.apply(series);

        
// Set plotLow and plotHigh
        
each(series.points, function (point) {

            var 
low = point.low,
                
high = point.high,
                
plotY = point.plotY;

            if (
high === null && low === null) {
                
point.y = null;
            } else if (
low === null) {
                
point.plotLow = point.plotY = null;
                
point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
            } else if (
high === null) {
                
point.plotLow = plotY;
                
point.plotHigh = null;
            } else {
                
point.plotLow = plotY;
                
point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
            }
        });
    },
    
    
/**
     * Extend the line series' getSegmentPath method by applying the segment
     * path to both lower and higher values of the range
     */
    
getSegmentPath: function (segment) {
        
        var 
lowSegment,
            
highSegment = [],
            
i = segment.length,
            
baseGetSegmentPath = Series.prototype.getSegmentPath,
            
point,
            
linePath,
            
lowerPath,
            
options = this.options,
            
step = options.step,
            
higherPath;
            
        
// Remove nulls from low segment
        
lowSegment = HighchartsAdapter.grep(segment, function (point) {
            return 
point.plotLow !== null;
        });
        
        
// Make a segment with plotX and plotY for the top values
        
while (i--) {
            
point = segment[i];
            if (
point.plotHigh !== null) {
                
highSegment.push({
                    
plotX: point.plotX,
                    
plotY: point.plotHigh
                
});
            }
        }
        
        
// Get the paths
        
lowerPath = baseGetSegmentPath.call(this, lowSegment);
        if (
step) {
            if (
step === true) {
                
step = 'left';
            }
            
options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath
        
}
        
higherPath = baseGetSegmentPath.call(this, highSegment);
        
options.step = step;
        
        
// Create a line on both top and bottom of the range
        
linePath = [].concat(lowerPath, higherPath);
        
        
// For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
        
higherPath[0] = 'L'; // this probably doesn't work for spline            
        
this.areaPath = this.areaPath.concat(lowerPath, higherPath);
        
        return 
linePath;
    },
    
    
/**
     * Extend the basic drawDataLabels method by running it for both lower and higher
     * values.
     */
    
drawDataLabels: function () {
        
        var 
data = this.data,
            
length = data.length,
            
i,
            
originalDataLabels = [],
            
seriesProto = Series.prototype,
            
dataLabelOptions = this.options.dataLabels,
            
point,
            
inverted = this.chart.inverted;
            
        if (
dataLabelOptions.enabled || this._hasPointLabels) {
            
            
// Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
            
i = length;
            while (
i--) {
                
point = data[i];
                
                
// Set preliminary values
                
point.y = point.high;
                
point.plotY = point.plotHigh;
                
                
// Store original data labels and set preliminary label objects to be picked up 
                // in the uber method
                
originalDataLabels[i] = point.dataLabel;
                
point.dataLabel = point.dataLabelUpper;
                
                
// Set the default offset
                
point.below = false;
                if (
inverted) {
                    
dataLabelOptions.align = 'left';
                    
dataLabelOptions.x = dataLabelOptions.xHigh;                                
                } else {
                    
dataLabelOptions.y = dataLabelOptions.yHigh;
                }
            }
            
seriesProto.drawDataLabels.apply(this, arguments); // #1209
            
            // Step 2: reorganize and handle data labels for the lower values
            
i = length;
            while (
i--) {
                
point = data[i];
                
                
// Move the generated labels from step 1, and reassign the original data labels
                
point.dataLabelUpper = point.dataLabel;
                
point.dataLabel = originalDataLabels[i];
                
                
// Reset values
                
point.y = point.low;
                
point.plotY = point.plotLow;
                
                
// Set the default offset
                
point.below = true;
                if (
inverted) {
                    
dataLabelOptions.align = 'right';
                    
dataLabelOptions.x = dataLabelOptions.xLow;
                } else {
                    
dataLabelOptions.y = dataLabelOptions.yLow;
                }
            }
            
seriesProto.drawDataLabels.apply(this, arguments);
        }
    
    },
    
    
alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
    
    
getSymbol: seriesTypes.column.prototype.getSymbol,
    
    
drawPoints: noop
});/**
 * The AreaSplineRangeSeries class
 */

defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);

/**
 * AreaSplineRangeSeries object
 */
seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
    
type: 'areasplinerange',
    
getPointSpline: seriesTypes.spline.prototype.getPointSpline
});/**
 * The ColumnRangeSeries class
 */
defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
    
lineWidth: 1,
    
pointRange: null
});

/**
 * ColumnRangeSeries object
 */
seriesTypes.columnrange = extendClass(seriesTypes.arearange, {
    
type: 'columnrange',
    
/**
     * Translate data points from raw values x and y to plotX and plotY
     */
    
translate: function () {
        var 
series = this,
            
yAxis = series.yAxis,
            
plotHigh;

        
colProto.translate.apply(series);

        
// Set plotLow and plotHigh
        
each(series.points, function (point) {
            var 
shapeArgs = point.shapeArgs,
                
minPointLength = series.options.minPointLength,
                
heightDifference,
                
height,
                
y;

            
point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
            
point.plotLow = point.plotY;

            
// adjust shape
            
y = plotHigh;
            
height = point.plotY - plotHigh;

            if (
height < minPointLength) {
                
heightDifference = (minPointLength - height);
                
height += heightDifference;
                
y -= heightDifference / 2;
            }
            
shapeArgs.height = height;
            
shapeArgs.y = y;
        });
    },
    
trackerGroups: ['group', 'dataLabels'],
    
drawGraph: noop,
    
pointAttrToOptions: colProto.pointAttrToOptions,
    
drawPoints: colProto.drawPoints,
    
drawTracker: colProto.drawTracker,
    
animate: colProto.animate,
    
getColumnMetrics: colProto.getColumnMetrics
});
/* 
 * The GaugeSeries class
 */



/**
 * Extend the default options
 */
defaultPlotOptions.gauge = merge(defaultPlotOptions.line, {
    
dataLabels: {
        
enabled: true,
        
y: 15,
        
borderWidth: 1,
        
borderColor: 'silver',
        
borderRadius: 3,
        
style: {
            
fontWeight: 'bold'
        
},
        
verticalAlign: 'top',
        
zIndex: 2
    
},
    
dial: {
        
// radius: '80%',
        // backgroundColor: 'black',
        // borderColor: 'silver',
        // borderWidth: 0,
        // baseWidth: 3,
        // topWidth: 1,
        // baseLength: '70%' // of radius
        // rearLength: '10%'
    
},
    
pivot: {
        
//radius: 5,
        //borderWidth: 0
        //borderColor: 'silver',
        //backgroundColor: 'black'
    
},
    
tooltip: {
        
headerFormat: ''
    
},
    
showInLegend: false
});

/**
 * Extend the point object
 */
var GaugePoint = Highcharts.extendClass(Highcharts.Point, {
    
/**
     * Don't do any hover colors or anything
     */
    
setState: function (state) {
        
this.state = state;
    }
});


/**
 * Add the series type
 */
var GaugeSeries = {
    
type: 'gauge',
    
pointClass: GaugePoint,
    
    
// chart.angular will be set to true when a gauge series is present, and this will
    // be used on the axes
    
angular: true, 
    
drawGraph: noop,
    
trackerGroups: ['group', 'dataLabels'],
    
    
/**
     * Calculate paths etc
     */
    
translate: function () {
        
        var 
series = this,
            
yAxis = series.yAxis,
            
options = series.options,
            
center = yAxis.center;
            
        
series.generatePoints();
        
        
each(series.points, function (point) {
            
            var 
dialOptions = merge(options.dial, point.dial),
                
radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
                
baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
                
rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
                
baseWidth = dialOptions.baseWidth || 3,
                
topWidth = dialOptions.topWidth || 1,
                
rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);

            
// Handle the wrap option
            
if (options.wrap === false) {
                
rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
            }
            
rotation = rotation * 180 / Math.PI;
                
            
point.shapeType = 'path';
            
point.shapeArgs = {
                
d: dialOptions.path || [
                    
'M', 
                    -
rearLength, -baseWidth / 2, 
                    
'L', 
                    
baseLength, -baseWidth / 2,
                    
radius, -topWidth / 2,
                    
radius, topWidth / 2,
                    
baseLength, baseWidth / 2,
                    -
rearLength, baseWidth / 2,
                    
'z'
                
],
                
translateX: center[0],
                
translateY: center[1],
                
rotation: rotation
            
};
            
            
// Positions for data label
            
point.plotX = center[0];
            
point.plotY = center[1];
        });
    },
    
    
/**
     * Draw the points where each point is one needle
     */
    
drawPoints: function () {
        
        var 
series = this,
            
center = series.yAxis.center,
            
pivot = series.pivot,
            
options = series.options,
            
pivotOptions = options.pivot,
            
renderer = series.chart.renderer;
        
        
each(series.points, function (point) {
            
            var 
graphic = point.graphic,
                
shapeArgs = point.shapeArgs,
                
d = shapeArgs.d,
                
dialOptions = merge(options.dial, point.dial); // #1233
            
            
if (graphic) {
                
graphic.animate(shapeArgs);
                
shapeArgs.d = d; // animate alters it
            
} else {
                
point.graphic = renderer[point.shapeType](shapeArgs)
                    .
attr({
                        
stroke: dialOptions.borderColor || 'none',
                        
'stroke-width': dialOptions.borderWidth || 0,
                        
fill: dialOptions.backgroundColor || 'black',
                        
rotation: shapeArgs.rotation // required by VML when animation is false
                    
})
                    .
add(series.group);
            }
        });
        
        
// Add or move the pivot
        
if (pivot) {
            
pivot.animate({ // #1235
                
translateX: center[0],
                
translateY: center[1]
            });
        } else {
            
series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
                .
attr({
                    
'stroke-width': pivotOptions.borderWidth || 0,
                    
stroke: pivotOptions.borderColor || 'silver',
                    
fill: pivotOptions.backgroundColor || 'black'
                
})
                .
translate(center[0], center[1])
                .
add(series.group);
        }
    },
    
    
/**
     * Animate the arrow up from startAngle
     */
    
animate: function (init) {
        var 
series = this;

        if (!
init) {
            
each(series.points, function (point) {
                var 
graphic = point.graphic;

                if (
graphic) {
                    
// start value
                    
graphic.attr({
                        
rotation: series.yAxis.startAngleRad * 180 / Math.PI
                    
});

                    
// animate
                    
graphic.animate({
                        
rotation: point.shapeArgs.rotation
                    
}, series.options.animation);
                }
            });

            
// delete this function to allow it only once
            
series.animate = null;
        }
    },
    
    
render: function () {
        
this.group = this.plotGroup(
            
'group', 
            
'series', 
            
this.visible ? 'visible' : 'hidden', 
            
this.options.zIndex, 
            
this.chart.seriesGroup
        
);
        
seriesTypes.pie.prototype.render.call(this);
        
this.group.clip(this.chart.clipRect);
    },
    
    
setData: seriesTypes.pie.prototype.setData,
    
drawTracker: seriesTypes.column.prototype.drawTracker
};
seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************
 * Start Box plot series code                                                  *
 *****************************************************************************/

// Set default options
defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {
    
fillColor: '#FFFFFF',
    
lineWidth: 1,
    
//medianColor: null,
    
medianWidth: 2,
    
states: {
        
hover: {
            
brightness: -0.3
        
}
    },
    
//stemColor: null,
    //stemDashStyle: 'solid'
    //stemWidth: null,
    
threshold: null,
    
tooltip: {
        
pointFormat: '<span style="color:{series.color};font-weight:bold">{series.name}</span><br/>' +
            
'Maximum: {point.high}<br/>' +
            
'Upper quartile: {point.q3}<br/>' +
            
'Median: {point.median}<br/>' +
            
'Lower quartile: {point.q1}<br/>' +
            
'Minimum: {point.low}<br/>'
            
    
},
    
//whiskerColor: null,
    
whiskerLength: '50%',
    
whiskerWidth: 2
});

// Create the series object
seriesTypes.boxplot = extendClass(seriesTypes.column, {
    
type: 'boxplot',
    
pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
    
toYData: function (point) { // return a plain array for speedy calculation
        
return [point.low, point.q1, point.median, point.q3, point.high];
    },
    
pointValKey: 'high', // defines the top of the tracker
    
    /**
     * One-to-one mapping from options to SVG attributes
     */
    
pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
        
fill: 'fillColor',
        
stroke: 'color',
        
'stroke-width': 'lineWidth'
    
},
    
    
/**
     * Disable data labels for box plot
     */
    
drawDataLabels: noop,

    
/**
     * Translate data points from raw values x and y to plotX and plotY
     */
    
translate: function () {
        var 
series = this,
            
yAxis = series.yAxis,
            
pointArrayMap = series.pointArrayMap;

        
seriesTypes.column.prototype.translate.apply(series);

        
// do the translation on each point dimension
        
each(series.points, function (point) {
            
each(pointArrayMap, function (key) {
                if (
point[key] !== null) {
                    
point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
                }
            });
        });
    },

    
/**
     * Draw the data points
     */
    
drawPoints: function () {
        var 
series = this,  //state = series.state,
            
points = series.points,
            
options = series.options,
            
chart = series.chart,
            
renderer = chart.renderer,
            
pointAttr,
            
q1Plot,
            
q3Plot,
            
highPlot,
            
lowPlot,
            
medianPlot,
            
crispCorr,
            
crispX,
            
graphic,
            
stemPath,
            
stemAttr,
            
boxPath,
            
whiskersPath,
            
whiskersAttr,
            
medianPath,
            
medianAttr,
            
width,
            
left,
            
right,
            
halfWidth,
            
shapeArgs,
            
color,
            
doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
            
whiskerLength = parseInt(series.options.whiskerLength, 10) / 100;


        
each(points, function (point) {

            
graphic = point.graphic;
            
shapeArgs = point.shapeArgs; // the box
            
stemAttr = {};
            
whiskersAttr = {};
            
medianAttr = {};
            
color = point.color || series.color;
            
            if (
point.plotY !== UNDEFINED) {

                
pointAttr = point.pointAttr[point.selected ? 'selected' : ''];

                
// crisp vector coordinates
                
width = shapeArgs.width;
                
left = mathFloor(shapeArgs.x);
                
right = left + width;
                
halfWidth = mathRound(width / 2);
                
//crispX = mathRound(left + halfWidth) + crispCorr;
                
q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;
                
q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;
                
highPlot = mathFloor(point.highPlot);// + crispCorr;
                
lowPlot = mathFloor(point.lowPlot);// + crispCorr;
                
                // Stem attributes
                
stemAttr.stroke = point.stemColor || options.stemColor || color;
                
stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
                
stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
                
                
// Whiskers attributes
                
whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
                
whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
                
                
// Median attributes
                
medianAttr.stroke = point.medianColor || options.medianColor || color;
                
medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
                
                
                
// The stem
                
crispCorr = (stemAttr['stroke-width'] % 2) / 2;
                
crispX = left + halfWidth + crispCorr;                
                
stemPath = [
                    
// stem up
                    
'M',
                    
crispX, q3Plot,
                    
'L',
                    
crispX, highPlot,
                    
                    
// stem down
                    
'M',
                    
crispX, q1Plot,
                    
'L',
                    
crispX, lowPlot,
                    
'z'
                
];
                
                
// The box
                
if (doQuartiles) {
                    
crispCorr = (pointAttr['stroke-width'] % 2) / 2;
                    
crispX = mathFloor(crispX) + crispCorr;
                    
q1Plot = mathFloor(q1Plot) + crispCorr;
                    
q3Plot = mathFloor(q3Plot) + crispCorr;
                    
left += crispCorr;
                    
right += crispCorr;
                    
boxPath = [
                        
'M',
                        
left, q3Plot,
                        
'L',
                        
left, q1Plot,
                        
'L',
                        
right, q1Plot,
                        
'L',
                        
right, q3Plot,
                        
'L',
                        
left, q3Plot,
                        
'z'
                    
];
                }
                
                
// The whiskers
                
if (whiskerLength) {
                    
crispCorr = (whiskersAttr['stroke-width'] % 2) / 2;
                    
highPlot = highPlot + crispCorr;
                    
lowPlot = lowPlot + crispCorr;
                    
whiskersPath = [
                        
// High whisker
                        
'M',
                        
crispX - halfWidth * whiskerLength, 
                        
highPlot,
                        
'L',
                        
crispX + halfWidth * whiskerLength, 
                        
highPlot,
                        
                        
// Low whisker
                        
'M',
                        
crispX - halfWidth * whiskerLength, 
                        
lowPlot,
                        
'L',
                        
crispX + halfWidth * whiskerLength, 
                        
lowPlot
                    
];
                }
                
                
// The median
                
crispCorr = (medianAttr['stroke-width'] % 2) / 2;                
                
medianPlot = mathRound(point.medianPlot) + crispCorr;
                
medianPath = [
                    
'M',
                    
left, 
                    
medianPlot,
                    
'L',
                    
right, 
                    
medianPlot,
                    
'z'
                
];
                
                
// Create or update the graphics
                
if (graphic) { // update
                    
                    
point.stem.animate({ d: stemPath });
                    if (
whiskerLength) {
                        
point.whiskers.animate({ d: whiskersPath });
                    }
                    if (
doQuartiles) {
                        
point.box.animate({ d: boxPath });
                    }
                    
point.medianShape.animate({ d: medianPath });
                    
                } else { 
// create new
                    
point.graphic = graphic = renderer.g()
                        .
add(series.group);
                    
                    
point.stem = renderer.path(stemPath)
                        .
attr(stemAttr)
                        .
add(graphic);
                        
                    if (
whiskerLength) {
                        
point.whiskers = renderer.path(whiskersPath) 
                            .
attr(whiskersAttr)
                            .
add(graphic);
                    }
                    if (
doQuartiles) {
                        
point.box = renderer.path(boxPath)
                            .
attr(pointAttr)
                            .
add(graphic);
                    }    
                    
point.medianShape = renderer.path(medianPath)
                        .
attr(medianAttr)
                        .
add(graphic);
                }
            }
        });

    }


});

/* ****************************************************************************
 * End Box plot series code                                                *
 *****************************************************************************/
/* ****************************************************************************
 * Start error bar series code                                                *
 *****************************************************************************/

// 1 - set default options
defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {
    
color: '#000000',
    
grouping: false,
    
linkedTo: ':previous',
    
tooltip: {
        
pointFormat: defaultPlotOptions.arearange.tooltip.pointFormat
    
},
    
whiskerWidth: null
});

// 2 - Create the series object
seriesTypes.errorbar = extendClass(seriesTypes.boxplot, {
    
type: 'errorbar',
    
pointArrayMap: ['low', 'high'], // array point configs are mapped to this
    
toYData: function (point) { // return a plain array for speedy calculation
        
return [point.low, point.high];
    },
    
pointValKey: 'high', // defines the top of the tracker
    
doQuartiles: false,

    
/**
     * Get the width and X offset, either on top of the linked series column
     * or standalone
     */
    
getColumnMetrics: function () {
        return (
this.linkedParent && this.linkedParent.columnMetrics) || 
            
seriesTypes.column.prototype.getColumnMetrics.call(this);
    }
});

/* ****************************************************************************
 * End error bar series code                                                  *
 *****************************************************************************/
/* ****************************************************************************
 * Start Waterfall series code                                                *
 *****************************************************************************/

// 1 - set default options
defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {
    
lineWidth: 1,
    
lineColor: '#333',
    
dashStyle: 'dot',
    
borderColor: '#333'
});


// 2 - Create the series object
seriesTypes.waterfall = extendClass(seriesTypes.column, {
    
type: 'waterfall',

    
upColorProp: 'fill',

    
pointArrayMap: ['low', 'y'],

    
pointValKey: 'y',

    
/**
     * Init waterfall series, force stacking
     */
    
init: function (chart, options) {
        
// force stacking
        
options.stacking = true;

        
seriesTypes.column.prototype.init.call(this, chart, options);
    },


    
/**
     * Translate data points from raw values
     */
    
translate: function () {
        var 
series = this,
            
options = series.options,
            
axis = series.yAxis,
            
len,
            
i,
            
points,
            
point,
            
shapeArgs,
            
stack,
            
y,
            
previousY,
            
stackPoint,
            
threshold = options.threshold,
            
crispCorr = (options.borderWidth % 2) / 2;

        
// run column series translate
        
seriesTypes.column.prototype.translate.apply(this);

        
previousY = threshold;
        
points = series.points;

        for (
i = 0, len = points.length; i < len; i++) {
            
// cache current point object
            
point = points[i];
            
shapeArgs = point.shapeArgs;

            
// get current stack
            
stack = series.getStack(i);
            
stackPoint = stack.points[series.index];

            
// override point value for sums
            
if (isNaN(point.y)) {
                
point.y = series.yData[i];
            }

            
// up points
            
y = mathMax(previousY, previousY + point.y) + stackPoint[0];
            
shapeArgs.y = axis.translate(y, 0, 1);


            
// sum points
            
if (point.isSum || point.isIntermediateSum) {
                
shapeArgs.y = axis.translate(stackPoint[1], 0, 1);
                
shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;

            
// if it's not the sum point, update previous stack end position
            
} else {
                
previousY += stack.total;
            }

            
// negative points
            
if (shapeArgs.height < 0) {
                
shapeArgs.y += shapeArgs.height;
                
shapeArgs.height *= -1;
            }

            
point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;
            
shapeArgs.height = mathRound(shapeArgs.height);
            
point.yBottom = shapeArgs.y + shapeArgs.height;
        }
    },

    
/**
     * Call default processData then override yData to reflect waterfall's extremes on yAxis
     */
    
processData: function (force) {
        var 
series = this,
            
options = series.options,
            
yData = series.yData,
            
points = series.points,
            
point,
            
dataLength = yData.length,
            
threshold = options.threshold || 0,
            
subSum,
            
sum,
            
dataMin,
            
dataMax,
            
y,
            
i;

        
sum = subSum = dataMin = dataMax = threshold;

        for (
i = 0; i < dataLength; i++) {
            
y = yData[i];
            
point = points ? points[i] : {};

            if (
y === "sum" || point.isSum) {
                
yData[i] = sum;
            } else if (
y === "intermediateSum" || point.isIntermediateSum) {
                
yData[i] = subSum;
                
subSum = threshold;
            } else {
                
sum += y;
                
subSum += y;
            }
            
dataMin = Math.min(sum, dataMin);
            
dataMax = Math.max(sum, dataMax);
        }

        
Series.prototype.processData.call(this, force);

        
// Record extremes
        
series.dataMin = dataMin;
        
series.dataMax = dataMax;
    },

    
/**
     * Return y value or string if point is sum
     */
    
toYData: function (pt) {
        if (
pt.isSum) {
            return 
"sum";
        } else if (
pt.isIntermediateSum) {
            return 
"intermediateSum";
        }

        return 
pt.y;
    },

    
/**
     * Postprocess mapping between options and SVG attributes
     */
    
getAttribs: function () {
        
seriesTypes.column.prototype.getAttribs.apply(this, arguments);

        var 
series = this,
            
options = series.options,
            
stateOptions = options.states,
            
upColor = options.upColor || series.color,
            
hoverColor = Highcharts.Color(upColor).brighten(0.1).get(),
            
seriesDownPointAttr = merge(series.pointAttr),
            
upColorProp = series.upColorProp;

        
seriesDownPointAttr[''][upColorProp] = upColor;
        
seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;
        
seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;

        
each(series.points, function (point) {
            if (
point.y > 0 && !point.color) {
                
point.pointAttr = seriesDownPointAttr;
                
point.color = upColor;
            }
        });
    },

    
/**
     * Draw columns' connector lines
     */
    
getGraphPath: function () {

        var 
data = this.data,
            
length = data.length,
            
lineWidth = this.options.lineWidth + this.options.borderWidth,
            
normalizer = mathRound(lineWidth) % 2 / 2,
            
path = [],
            
M = 'M',
            
L = 'L',
            
prevArgs,
            
pointArgs,
            
i,
            
d;

        for (
i = 1; i < length; i++) {
            
pointArgs = data[i].shapeArgs;
            
prevArgs = data[i - 1].shapeArgs;

            
d = [
                
M,
                
prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
                
L,
                
pointArgs.x, prevArgs.y + normalizer
            
];

            if (
data[i - 1].y < 0) {
                
d[2] += prevArgs.height;
                
d[5] += prevArgs.height;
            }

            
path = path.concat(d);
        }

        return 
path;
    },

    
/**
     * Extremes are recorded in processData
     */
    
getExtremes: noop,

    
/**
     * Return stack for given index
     */
    
getStack: function (i) {
        var 
axis = this.yAxis,
            
stacks = axis.stacks,
            
key = this.stackKey;

        if (
this.processedYData[i] < this.options.threshold) {
            
key = '-' + key;
        }

        return 
stacks[key][i];
    },

    
drawGraph: Series.prototype.drawGraph
});

/* ****************************************************************************
 * End Waterfall series code                                                  *
 *****************************************************************************/
/* ****************************************************************************
 * Start Bubble series code                                                      *
 *****************************************************************************/

// 1 - set default options
defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {
    
dataLabels: {
        
inside: true,
        
style: {
            
color: 'white',
            
textShadow: '0px 0px 3px black'
        
},
        
verticalAlign: 'middle'
    
},
    
// displayNegative: true,
    
marker: {
        
// fillOpacity: 0.5,
        
lineColor: null, // inherit from series.color
        
lineWidth: 1
    
},
    
minSize: 8,
    
maxSize: '20%',
    
// negativeColor: null,
    
tooltip: {
        
pointFormat: '({point.x}, {point.y}), Size: {point.z}'
    
},
    
turboThreshold: 0,
    
zThreshold: 0
});

// 2 - Create the series object
seriesTypes.bubble = extendClass(seriesTypes.scatter, {
    
type: 'bubble',
    
pointArrayMap: ['y', 'z'],
    
trackerGroups: ['group', 'dataLabelsGroup'],
    
    
/**
     * Mapping between SVG attributes and the corresponding options
     */
    
pointAttrToOptions: { 
        
stroke: 'lineColor',
        
'stroke-width': 'lineWidth',
        
fill: 'fillColor'
    
},
    
    
/**
     * Apply the fillOpacity to all fill positions
     */
    
applyOpacity: function (fill) {
        var 
markerOptions = this.options.marker,
            
fillOpacity = pick(markerOptions.fillOpacity, 0.5);
        
        
// When called from Legend.colorizeItem, the fill isn't predefined
        
fill = fill || markerOptions.fillColor || this.color; 
        
        if (
fillOpacity !== 1) {
            
fill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');
        }
        return 
fill;
    },
    
    
/**
     * Extend the convertAttribs method by applying opacity to the fill
     */
    
convertAttribs: function () {
        var 
obj = Series.prototype.convertAttribs.apply(this, arguments);
        
        
obj.fill = this.applyOpacity(obj.fill);
        
        return 
obj;
    },

    
/**
     * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
     * must be done prior to Series.translate because the axis needs to add padding in 
     * accordance with the point sizes.
     */
    
getRadii: function (zMin, zMax, minSize, maxSize) {
        var 
len,
            
i,
            
pos,
            
zData = this.zData,
            
radii = [],
            
zRange;
        
        
// Set the shape type and arguments to be picked up in drawPoints
        
for (i = 0, len = zData.length; i < len; i++) {
            
zRange = zMax - zMin;
            
pos = zRange > 0 ? // relative size, a number between 0 and 1
                
(zData[i] - zMin) / (zMax - zMin) : 
                
0.5;
            
radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);
        }
        
this.radii = radii;
    },
    
    
/**
     * Perform animation on the bubbles
     */
    
animate: function (init) {
        var 
animation = this.options.animation;
        
        if (!
init) { // run the animation
            
each(this.points, function (point) {
                var 
graphic = point.graphic,
                    
shapeArgs = point.shapeArgs;

                if (
graphic && shapeArgs) {
                    
// start values
                    
graphic.attr('r', 1);

                    
// animate
                    
graphic.animate({
                        
r: shapeArgs.r
                    
}, animation);
                }
            });

            
// delete this function to allow it only once
            
this.animate = null;
        }
    },
    
    
/**
     * Extend the base translate method to handle bubble size
     */
    
translate: function () {
        
        var 
i,
            
data = this.data,
            
point,
            
radius,
            
radii = this.radii;
        
        
// Run the parent method
        
seriesTypes.scatter.prototype.translate.call(this);
        
        
// Set the shape type and arguments to be picked up in drawPoints
        
i = data.length;
        
        while (
i--) {
            
point = data[i];
            
radius = radii ? radii[i] : 0; // #1737

            // Flag for negativeColor to be applied in Series.js
            
point.negative = point.z < (this.options.zThreshold || 0);
            
            if (
radius >= this.minPxSize / 2) {
                
// Shape arguments
                
point.shapeType = 'circle';
                
point.shapeArgs = {
                    
x: point.plotX,
                    
y: point.plotY,
                    
r: radius
                
};
                
                
// Alignment box for the data label
                
point.dlBox = {
                    
x: point.plotX - radius,
                    
y: point.plotY - radius,
                    
width: 2 * radius,
                    
height: 2 * radius
                
};
            } else { 
// below zThreshold
                
point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691
            
}
        }
    },
    
    
/**
     * Get the series' symbol in the legend
     * 
     * @param {Object} legend The legend object
     * @param {Object} item The series (this) or point
     */
    
drawLegendSymbol: function (legend, item) {
        var 
radius = pInt(legend.itemStyle.fontSize) / 2;
        
        
item.legendSymbol = this.chart.renderer.circle(
            
radius,
            
legend.baseline - radius,
            
radius
        
).attr({
            
zIndex: 3
        
}).add(item.legendGroup);
        
item.legendSymbol.isMarker = true;    
        
    },
    
    
drawPoints: seriesTypes.column.prototype.drawPoints,
    
alignDataLabel: seriesTypes.column.prototype.alignDataLabel
});

/**
 * Add logic to pad each axis with the amount of pixels
 * necessary to avoid the bubbles to overflow.
 */
Axis.prototype.beforePadding = function () {
    var 
axis = this,
        
axisLength = this.len,
        
chart = this.chart,
        
pxMin = 0, 
        
pxMax = axisLength,
        
isXAxis = this.isXAxis,
        
dataKey = isXAxis ? 'xData' : 'yData',
        
min = this.min,
        
extremes = {},
        
smallestSize = math.min(chart.plotWidth, chart.plotHeight),
        
zMin = Number.MAX_VALUE,
        
zMax = -Number.MAX_VALUE,
        
range = this.max - min,
        
transA = axisLength / range,
        
activeSeries = [];

    
// Handle padding on the second pass, or on redraw
    
if (this.tickPositions) {
        
each(this.series, function (series) {

            var 
seriesOptions = series.options,
                
zData;

            if (
series.type === 'bubble' && series.visible) {

                
// Correction for #1673
                
axis.allowZoomOutside = true;

                
// Cache it
                
activeSeries.push(series);

                if (
isXAxis) { // because X axis is evaluated first
                
                    // For each series, translate the size extremes to pixel values
                    
each(['minSize', 'maxSize'], function (prop) {
                        var 
length = seriesOptions[prop],
                            
isPercent = /%$/.test(length);
                        
                        
length = pInt(length);
                        
extremes[prop] = isPercent ?
                            
smallestSize * length / 100 :
                            
length;
                        
                    });
                    
series.minPxSize = extremes.minSize;
                    
                    
// Find the min and max Z
                    
zData = series.zData;
                    if (
zData.length) { // #1735
                        
zMin = math.min(
                            
zMin,
                            
math.max(
                                
arrayMin(zData), 
                                
seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
                            
)
                        );
                        
zMax = math.max(zMax, arrayMax(zData));
                    }
                }
            }
        });

        
each(activeSeries, function (series) {

            var 
data = series[dataKey],
                
i = data.length,
                
radius;

            if (
isXAxis) {
                
series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);
            }
            
            if (
range > 0) {
                while (
i--) {
                    
radius = series.radii[i];
                    
pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
                    
pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
                }
            }
        });
        
        if (
range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {
            
pxMax -= axisLength;
            
transA *= (axisLength + pxMin - pxMax) / axisLength;
            
this.min += pxMin / transA;
            
this.max += pxMax / transA;
        }
    }
};

/* ****************************************************************************
 * End Bubble series code                                                     *
 *****************************************************************************/
/**
 * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
 * gathered in RadialAxes.js.
 * 
 */

var seriesProto = Series.prototype,
    
pointerProto = Highcharts.Pointer.prototype;



/**
 * Translate a point's plotX and plotY from the internal angle and radius measures to 
 * true plotX, plotY coordinates
 */
seriesProto.toXY = function (point) {
    var 
xy,
        
chart = this.chart,
        
plotX = point.plotX,
        
plotY = point.plotY;
    
    
// Save rectangular plotX, plotY for later computation
    
point.rectPlotX = plotX;
    
point.rectPlotY = plotY;
    
    
// Record the angle in degrees for use in tooltip
    
point.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
    
    
// Find the polar plotX and plotY
    
xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
    
point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
    
point.plotY = point.polarPlotY = xy.y - chart.plotTop;
};

/** 
 * Order the tooltip points to get the mouse capture ranges correct. #1915. 
 */
seriesProto.orderTooltipPoints = function (points) {
    if (
this.chart.polar) {
        
points.sort(function (a, b) {
            return 
a.clientX - b.clientX;
        });

        
// Wrap mouse tracking around to capture movement on the segment to the left
        // of the north point (#1469, #2093).
        
if (points[0]) {
            
points[0].wrappedClientX = points[0].clientX + 360;
            
points.push(points[0]);
        }
    }
};


/**
 * Add some special init logic to areas and areasplines
 */
function initArea(proceed, chart, options) {
    
proceed.call(this, chart, options);
    if (
this.chart.polar) {
        
        
/**
         * Overridden method to close a segment path. While in a cartesian plane the area 
         * goes down to the threshold, in the polar chart it goes to the center.
         */
        
this.closeSegment = function (path) {
            var 
center = this.xAxis.center;
            
path.push(
                
'L',
                
center[0],
                
center[1]
            );            
        };
        
        
// Instead of complicated logic to draw an area around the inner area in a stack,
        // just draw it behind
        
this.closedStacks = true;
    }
}
wrap(seriesTypes.area.prototype, 'init', initArea);
wrap(seriesTypes.areaspline.prototype, 'init', initArea);
        

/**
 * Overridden method for calculating a spline from one point to the next
 */
wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {
    
    var 
ret,
        
smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
        
denom = smoothing + 1,
        
plotX, 
        
plotY,
        
lastPoint,
        
nextPoint,
        
lastX,
        
lastY,
        
nextX,
        
nextY,
        
leftContX,
        
leftContY,
        
rightContX,
        
rightContY,
        
distanceLeftControlPoint,
        
distanceRightControlPoint,
        
leftContAngle,
        
rightContAngle,
        
jointAngle;
        
        
    if (
this.chart.polar) {
        
        
plotX = point.plotX;
        
plotY = point.plotY;
        
lastPoint = segment[i - 1];
        
nextPoint = segment[i + 1];
            
        
// Connect ends
        
if (this.connectEnds) {
            if (!
lastPoint) {
                
lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
            
}
            if (!
nextPoint) {
                
nextPoint = segment[1];
            }    
        }

        
// find control points
        
if (lastPoint && nextPoint) {
        
            
lastX = lastPoint.plotX;
            
lastY = lastPoint.plotY;
            
nextX = nextPoint.plotX;
            
nextY = nextPoint.plotY;
            
leftContX = (smoothing * plotX + lastX) / denom;
            
leftContY = (smoothing * plotY + lastY) / denom;
            
rightContX = (smoothing * plotX + nextX) / denom;
            
rightContY = (smoothing * plotY + nextY) / denom;
            
distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
            
distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
            
leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
            
rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
            
jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
                
                
            
// Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
            
if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
                
jointAngle -= Math.PI;
            }
            
            
// Find the corrected control points for a spline straight through the point
            
leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
            
leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
            
rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
            
rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
            
            
// Record for drawing in next point
            
point.rightContX = rightContX;
            
point.rightContY = rightContY;

        }
        
        
        
// moveTo or lineTo
        
if (!i) {
            
ret = ['M', plotX, plotY];
        } else { 
// curve from last point to this
            
ret = [
                
'C',
                
lastPoint.rightContX || lastPoint.plotX,
                
lastPoint.rightContY || lastPoint.plotY,
                
leftContX || plotX,
                
leftContY || plotY,
                
plotX,
                
plotY
            
];
            
lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
        
}
        
        
    } else {
        
ret = proceed.call(this, segment, point, i);
    }
    return 
ret;
});

/**
 * Extend translate. The plotX and plotY values are computed as if the polar chart were a
 * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
 * center. 
 */
wrap(seriesProto, 'translate', function (proceed) {
        
    
// Run uber method
    
proceed.call(this);
    
    
// Postprocess plot coordinates
    
if (this.chart.polar && !this.preventPostTranslate) {
        var 
points = this.points,
            
i = points.length;
        while (
i--) {
            
// Translate plotX, plotY from angle and radius to true plot coordinates
            
this.toXY(points[i]);
        }
    }
});

/** 
 * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in 
 * line-like series.
 */
wrap(seriesProto, 'getSegmentPath', function (proceed, segment) {
        
    var 
points = this.points;
    
    
// Connect the path
    
if (this.chart.polar && this.options.connectEnds !== false && 
            
segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {
        
this.connectEnds = true; // re-used in splines
        
segment = [].concat(segment, [points[0]]);
    }
    
    
// Run uber method
    
return proceed.call(this, segment);
    
});


function 
polarAnimate(proceed, init) {
    var 
chart = this.chart,
        
animation = this.options.animation,
        
group = this.group,
        
markerGroup = this.markerGroup,
        
center = this.xAxis.center,
        
plotLeft = chart.plotLeft,
        
plotTop = chart.plotTop,
        
attribs;

    
// Specific animation for polar charts
    
if (chart.polar) {
        
        
// Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
        // would be so slow it would't matter.
        
if (chart.renderer.isSVG) {

            if (
animation === true) {
                
animation = {};
            }
    
            
// Initialize the animation
            
if (init) {
                
                
// Scale down the group and place it in the center
                
attribs = {
                    
translateX: center[0] + plotLeft,
                    
translateY: center[1] + plotTop,
                    
scaleX: 0.001, // #1499
                    
scaleY: 0.001
                
};
                    
                
group.attr(attribs);
                if (
markerGroup) {
                    
markerGroup.attrSetters = group.attrSetters;
                    
markerGroup.attr(attribs);
                }
                
            
// Run the animation
            
} else {
                
attribs = {
                    
translateX: plotLeft,
                    
translateY: plotTop,
                    
scaleX: 1,
                    
scaleY: 1
                
};
                
group.animate(attribs, animation);
                if (
markerGroup) {
                    
markerGroup.animate(attribs, animation);
                }
                
                
// Delete this function to allow it only once
                
this.animate = null;
            }
        }
    
    
// For non-polar charts, revert to the basic animation
    
} else {
        
proceed.call(this, init);
    } 
}

// Define the animate method for both regular series and column series and their derivatives
wrap(seriesProto, 'animate', polarAnimate);
wrap(colProto, 'animate', polarAnimate);


/**
 * Throw in a couple of properties to let setTooltipPoints know we're indexing the points
 * in degrees (0-360), not plot pixel width.
 */
wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {
        
    if (
this.chart.polar) {
        
extend(this.xAxis, {
            
tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array
        
});    
    }
    
    
// Run uber method
    
return proceed.call(this, renew);
});


/**
 * Extend the column prototype's translate method
 */
wrap(colProto, 'translate', function (proceed) {
        
    var 
xAxis = this.xAxis,
        
len = this.yAxis.len,
        
center = xAxis.center,
        
startAngleRad = xAxis.startAngleRad,
        
renderer = this.chart.renderer,
        
start,
        
points,
        
point,
        
i;
    
    
this.preventPostTranslate = true;
    
    
// Run uber method
    
proceed.call(this);
    
    
// Postprocess plot coordinates
    
if (xAxis.isRadial) {
        
points = this.points;
        
i = points.length;
        while (
i--) {
            
point = points[i];
            
start = point.barX + startAngleRad;
            
point.shapeType = 'path';
            
point.shapeArgs = {
                
d: renderer.symbols.arc(
                    
center[0],
                    
center[1],
                    
len - point.plotY,
                    
null, 
                    {
                        
start: start,
                        
end: start + point.pointWidth,
                        
innerR: len - pick(point.yBottom, len)
                    }
                )
            };
            
this.toXY(point); // provide correct plotX, plotY for tooltip
        
}
    }
});


/**
 * Align column data labels outside the columns. #1199.
 */
wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {
    
    if (
this.chart.polar) {
        var 
angle = point.rectPlotX / Math.PI * 180,
            
align,
            
verticalAlign;
        
        
// Align nicely outside the perimeter of the columns
        
if (options.align === null) {
            if (
angle > 20 && angle < 160) {
                
align = 'left'; // right hemisphere
            
} else if (angle > 200 && angle < 340) {
                
align = 'right'; // left hemisphere
            
} else {
                
align = 'center'; // top or bottom
            
}
            
options.align = align;
        }
        if (
options.verticalAlign === null) {
            if (
angle < 45 || angle > 315) {
                
verticalAlign = 'bottom'; // top part
            
} else if (angle > 135 && angle < 225) {
                
verticalAlign = 'top'; // bottom part
            
} else {
                
verticalAlign = 'middle'; // left or right
            
}
            
options.verticalAlign = verticalAlign;
        }
        
        
seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
    } else {
        
proceed.call(this, point, dataLabel, options, alignTo, isNew);
    }
    
});

/**
 * Extend the mouse tracker to return the tooltip position index in terms of
 * degrees rather than pixels
 */
wrap(pointerProto, 'getIndex', function (proceed, e) {
    var 
ret,
        
chart = this.chart,
        
center,
        
x,
        
y;
    
    if (
chart.polar) {
        
center = chart.xAxis[0].center;
        
x = e.chartX - center[0] - chart.plotLeft;
        
y = e.chartY - center[1] - chart.plotTop;
        
        
ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);
    
    } else {
    
        
// Run uber method
        
ret = proceed.call(this, e);
    }
    return 
ret;
});

/**
 * Extend getCoordinates to prepare for polar axis values
 */
wrap(pointerProto, 'getCoordinates', function (proceed, e) {
    var 
chart = this.chart,
        
ret = {
            
xAxis: [],
            
yAxis: []
        };
    
    if (
chart.polar) {    

        
each(chart.axes, function (axis) {
            var 
isXAxis = axis.isXAxis,
                
center = axis.center,
                
x = e.chartX - center[0] - chart.plotLeft,
                
y = e.chartY - center[1] - chart.plotTop;
            
            
ret[isXAxis ? 'xAxis' : 'yAxis'].push({
                
axis: axis,
                
value: axis.translate(
                    
isXAxis ?
                        
Math.PI - Math.atan2(x, y) : // angle 
                        
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
                    
true
                
)
            });
        });
        
    } else {
        
ret = proceed.call(this, e);
    }
    
    return 
ret;
});
}(
Highcharts));
?>
Онлайн: 0
Реклама