/**
* @license Highcharts JS v6.0.4 (2017-12-15)
* X-range series
*
* (c) 2010-2017 Torstein Honsi, Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*/
'use strict';
(function(factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else {
factory(Highcharts);
}
}(function(Highcharts) {
(function(H) {
/**
* X-range series module
*
* (c) 2010-2017 Torstein Honsi, Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*/
/**
* @todo
* - JSDoc
* - New series checklist
*/
var defined = H.defined,
color = H.Color,
columnType = H.seriesTypes.column,
each = H.each,
isNumber = H.isNumber,
isObject = H.isObject,
merge = H.merge,
pick = H.pick,
seriesType = H.seriesType,
seriesTypes = H.seriesTypes,
wrap = H.wrap,
Axis = H.Axis,
Point = H.Point,
Series = H.Series;
/**
* The X-range series displays ranges on the X axis, typically time intervals
* with a start and end date.
*
* @extends {plotOptions.column}
* @excluding boostThreshold,crisp,cropThreshold,depth,edgeColor,edgeWidth,
* findNearestPointBy,getExtremesFromAll,grouping,groupPadding,
* negativeColor,pointInterval,pointIntervalUnit,pointPlacement,
* pointRange,pointStart,softThreshold,stacking,threshold,data
* @product highcharts
* @sample {highcharts} highcharts/demo/x-range/
* X-range
* @sample {highcharts} highcharts/css/x-range/
* Styled mode X-range
* @sample {highcharts} highcharts/chart/inverted-xrange/
* Inverted X-range
* @since 6.0.0
* @optionparent plotOptions.xrange
*/
seriesType('xrange', 'column', {
/**
* In an X-range series, this option makes all points of the same Y-axis
* category the same color.
*/
colorByPoint: true,
dataLabels: {
verticalAlign: 'middle',
inside: true,
/**
* The default formatter for X-range data labels displays the percentage
* of the partial fill amount.
*/
formatter: function() {
var point = this.point,
amount = point.partialFill;
if (isObject(amount)) {
amount = amount.amount;
}
if (!defined(amount)) {
amount = 0;
}
return (amount * 100) + '%';
}
},
tooltip: {
headerFormat: '{point.x} - {point.x2}
',
pointFormat: '\u25CF {series.name}: {point.yCategory}
'
},
borderRadius: 3,
pointRange: 0
/**
* A partial fill for each point, typically used to visualize how much of
* a task is performed. The partial fill object can be set either on series
* or point level.
*
* @sample {highcharts} highcharts/demo/x-range
* X-range with partial fill
* @type {Object}
* @product highcharts
* @apioption plotOptions.xrange.partialFill
*/
/**
* The fill color to be used for partial fills. Defaults to a darker shade
* of the point color.
*
* @type {Color}
* @product highcharts
* @apioption plotOptions.xrange.partialFill.fill
*/
}, {
type: 'xrange',
parallelArrays: ['x', 'x2', 'y'],
requireSorting: false,
animate: seriesTypes.line.prototype.animate,
cropShoulder: 1,
getExtremesFromAll: true,
/**
* Borrow the column series metrics, but with swapped axes. This gives free
* access to features like groupPadding, grouping, pointWidth etc.
*/
getColumnMetrics: function() {
var metrics,
chart = this.chart;
function swapAxes() {
each(chart.series, function(s) {
var xAxis = s.xAxis;
s.xAxis = s.yAxis;
s.yAxis = xAxis;
});
}
swapAxes();
metrics = columnType.prototype.getColumnMetrics.call(this);
swapAxes();
return metrics;
},
/**
* Override cropData to show a point where x or x2 is outside visible range,
* but one of them is inside.
*/
cropData: function(xData, yData, min, max) {
// Replace xData with x2Data to find the appropriate cropStart
var cropData = Series.prototype.cropData,
crop = cropData.call(this, this.x2Data, yData, min, max);
// Re-insert the cropped xData
crop.xData = xData.slice(crop.start, crop.end);
return crop;
},
translatePoint: function(point) {
var series = this,
xAxis = series.xAxis,
metrics = series.columnMetrics,
minPointLength = series.options.minPointLength || 0,
plotX = point.plotX,
posX = pick(point.x2, point.x + (point.len || 0)),
plotX2 = xAxis.translate(posX, 0, 0, 0, 1),
length = plotX2 - plotX,
widthDifference,
shapeArgs,
partialFill,
inverted = this.chart.inverted,
borderWidth = pick(series.options.borderWidth, 1),
crisper = borderWidth % 2 / 2,
dlLeft,
dlRight,
dlWidth;
if (minPointLength) {
widthDifference = minPointLength - length;
if (widthDifference < 0) {
widthDifference = 0;
}
plotX -= widthDifference / 2;
plotX2 += widthDifference / 2;
}
plotX = Math.max(plotX, -10);
plotX2 = Math.min(Math.max(plotX2, -10), xAxis.len + 10);
point.shapeArgs = {
x: Math.floor(Math.min(plotX, plotX2)) + crisper,
y: Math.floor(point.plotY + metrics.offset) + crisper,
width: Math.round(Math.abs(plotX2 - plotX)),
height: Math.round(metrics.width),
r: series.options.borderRadius
};
// Align data labels inside the shape and inside the plot area
dlLeft = point.shapeArgs.x;
dlRight = dlLeft + point.shapeArgs.width;
if (dlLeft < 0 || dlRight > xAxis.len) {
dlLeft = Math.min(xAxis.len, Math.max(0, dlLeft));
dlRight = Math.max(0, Math.min(dlRight, xAxis.len));
dlWidth = dlRight - dlLeft;
point.dlBox = merge(point.shapeArgs, {
x: dlLeft,
width: dlRight - dlLeft,
centerX: dlWidth ? dlWidth / 2 : null
});
} else {
point.dlBox = null;
}
// Tooltip position
point.tooltipPos[0] += inverted ? 0 : length / 2;
point.tooltipPos[1] -= inverted ? length / 2 : metrics.width / 2;
// Add a partShapeArgs to the point, based on the shapeArgs property
partialFill = point.partialFill;
if (partialFill) {
// Get the partial fill amount
if (isObject(partialFill)) {
partialFill = partialFill.amount;
}
// If it was not a number, assume 0
if (!isNumber(partialFill)) {
partialFill = 0;
}
shapeArgs = point.shapeArgs;
point.partShapeArgs = {
x: shapeArgs.x,
y: shapeArgs.y,
width: shapeArgs.width,
height: shapeArgs.height,
r: series.options.borderRadius
};
point.clipRectArgs = {
x: shapeArgs.x,
y: shapeArgs.y,
width: Math.round(shapeArgs.width * partialFill),
height: shapeArgs.height
};
}
},
translate: function() {
columnType.prototype.translate.apply(this, arguments);
each(this.points, function(point) {
this.translatePoint(point);
}, this);
},
/**
* Draws a single point in the series. Needed for partial fill.
*
* This override turns point.graphic into a group containing the original
* graphic and an overlay displaying the partial fill.
*
* @param {Object} point an instance of Point in the series
* @param {string} verb 'animate' (animates changes) or 'attr' (sets
* options)
* @returns {void}
*/
drawPoint: function(point, verb) {
var series = this,
seriesOpts = series.options,
renderer = series.chart.renderer,
graphic = point.graphic,
type = point.shapeType,
shapeArgs = point.shapeArgs,
partShapeArgs = point.partShapeArgs,
clipRectArgs = point.clipRectArgs,
pfOptions = point.partialFill,
fill,
state = point.selected && 'select',
cutOff = seriesOpts.stacking && !seriesOpts.borderRadius;
if (!point.isNull) {
// Original graphic
if (graphic) { // update
point.graphicOriginal[verb](
merge(shapeArgs)
);
} else {
point.graphic = graphic = renderer.g('point')
.addClass(point.getClassName())
.add(point.group || series.group);
point.graphicOriginal = renderer[type](shapeArgs)
.addClass(point.getClassName())
.addClass('highcharts-partfill-original')
.add(graphic);
}
// Partial fill graphic
if (partShapeArgs) {
if (point.graphicOverlay) {
point.graphicOverlay[verb](
merge(partShapeArgs)
);
point.clipRect.animate(
merge(clipRectArgs)
);
} else {
point.clipRect = renderer.clipRect(
clipRectArgs.x,
clipRectArgs.y,
clipRectArgs.width,
clipRectArgs.height
);
point.graphicOverlay = renderer[type](partShapeArgs)
.addClass('highcharts-partfill-overlay')
.add(graphic)
.clip(point.clipRect);
}
}
// Presentational
point.graphicOriginal
.attr(series.pointAttribs(point, state))
.shadow(seriesOpts.shadow, null, cutOff);
if (partShapeArgs) {
// Ensure pfOptions is an object
if (!isObject(pfOptions)) {
pfOptions = {};
}
if (isObject(seriesOpts.partialFill)) {
pfOptions = merge(pfOptions, seriesOpts.partialFill);
}
fill = (
pfOptions.fill ||
color(point.color || series.color).brighten(-0.3).get()
);
point.graphicOverlay
.attr(series.pointAttribs(point, state))
.attr({
'fill': fill
})
.shadow(seriesOpts.shadow, null, cutOff);
}
} else if (graphic) {
point.graphic = graphic.destroy(); // #1269
}
},
drawPoints: function() {
var series = this,
chart = this.chart,
options = series.options,
animationLimit = options.animationLimit || 250,
verb = chart.pointCount < animationLimit ? 'animate' : 'attr';
// draw the columns
each(series.points, function(point) {
series.drawPoint(point, verb);
});
}
/**
* Override to remove stroke from points.
* For partial fill.
* /
pointAttribs: function () {
var series = this,
retVal = columnType.prototype.pointAttribs.apply(series, arguments);
//retVal['stroke-width'] = 0;
return retVal;
}
*/
// Point class properties
}, {
/**
* Extend init so that `colorByPoint` for x-range means that one color is
* applied per Y axis category.
*/
init: function() {
Point.prototype.init.apply(this, arguments);
var colors,
series = this.series,
colorCount = series.chart.options.chart.colorCount;
if (!this.y) {
this.y = 0;
}
if (series.options.colorByPoint) {
colors = series.options.colors || series.chart.options.colors;
colorCount = colors.length;
if (!this.options.color && colors[this.y % colorCount]) {
this.color = colors[this.y % colorCount];
}
}
this.colorIndex = this.y % colorCount;
return this;
},
// Add x2 and yCategory to the available properties for tooltip formats
getLabelConfig: function() {
var point = this,
cfg = Point.prototype.getLabelConfig.call(point),
yCats = point.series.yAxis.categories;
cfg.x2 = point.x2;
cfg.yCategory = point.yCategory = yCats && yCats[point.y];
return cfg;
},
tooltipDateKeys: ['x', 'x2'],
isValid: function() {
return typeof this.x === 'number' &&
typeof this.x2 === 'number';
}
});
/**
* Max x2 should be considered in xAxis extremes
*/
wrap(Axis.prototype, 'getSeriesExtremes', function(proceed) {
var axis = this,
axisSeries = axis.series,
dataMax,
modMax;
proceed.call(axis);
if (axis.isXAxis) {
dataMax = pick(axis.dataMax, -Number.MAX_VALUE);
each(axisSeries, function(series) {
if (series.x2Data) {
each(series.x2Data, function(val) {
if (val > dataMax) {
dataMax = val;
modMax = true;
}
});
}
});
if (modMax) {
axis.dataMax = dataMax;
}
}
});
/**
* An `xrange` series. If the [type](#series.xrange.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
*
* For options that apply to multiple series, it is recommended to add
* them to the [plotOptions.series](#plotOptions.series) options structure.
* To apply to all series of this specific type, apply it to [plotOptions.
* xrange](#plotOptions.xrange).
*
* @type {Object}
* @extends series,plotOptions.xrange
* @excluding boostThreshold,crisp,cropThreshold,depth,edgeColor,edgeWidth,
* findNearestPointBy,getExtremesFromAll,grouping,groupPadding,
* negativeColor,pointInterval,pointIntervalUnit,pointPlacement,
* pointRange,pointStart,softThreshold,stacking,threshold,data
* @product highcharts highstock
* @apioption series.xrange
*/
/**
* An array of data points for the series. For the `xrange` series type,
* points can be given in the following ways:
*
* 1. An array of objects with named values. The objects are point
* configuration objects as seen below.
*
* ```js
* data: [{
* x: Date.UTC(2017, 0, 1),
* x2: Date.UTC(2017, 0, 3),
* name: "Test",
* y: 0,
* color: "#00FF00"
* }, {
* x: Date.UTC(2017, 0, 4),
* x2: Date.UTC(2017, 0, 5),
* name: "Deploy",
* y: 1,
* color: "#FF0000"
* }]
* ```
*
* @type {Array