You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

393 lines
12 KiB

(function (Highcharts, HighchartsAdapter) {
var UNDEFINED,
ALIGN_FACTOR,
ALLOWED_SHAPES,
Chart = Highcharts.Chart,
extend = Highcharts.extend,
each = Highcharts.each;
ALLOWED_SHAPES = ["path", "rect", "circle"];
ALIGN_FACTOR = {
top: 0,
left: 0,
center: 0.5,
middle: 0.5,
bottom: 1,
right: 1
};
// Highcharts helper methods
var inArray = HighchartsAdapter.inArray,
merge = Highcharts.merge;
function defaultOptions(shapeType) {
var shapeOptions,
options;
options = {
xAxis: 0,
yAxis: 0,
title: {
style: {},
text: "",
x: 0,
y: 0
},
shape: {
params: {
stroke: "#000000",
fill: "transparent",
strokeWidth: 2
}
}
};
shapeOptions = {
circle: {
params: {
x: 0,
y: 0
}
}
};
if (shapeOptions[shapeType]) {
options.shape = merge(options.shape, shapeOptions[shapeType]);
}
return options;
}
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
function isNumber(n) {
return typeof n === 'number';
}
function defined(obj) {
return obj !== UNDEFINED && obj !== null;
}
function translatePath(d, xAxis, yAxis, xOffset, yOffset) {
var len = d.length,
i = 0;
while (i < len) {
if (typeof d[i] === 'number' && typeof d[i + 1] === 'number') {
d[i] = xAxis.toPixels(d[i]) - xOffset;
d[i + 1] = yAxis.toPixels(d[i + 1]) - yOffset;
i += 2;
} else {
i += 1;
}
}
return d;
}
// Define annotation prototype
var Annotation = function () {
this.init.apply(this, arguments);
};
Annotation.prototype = {
/*
* Initialize the annotation
*/
init: function (chart, options) {
var shapeType = options.shape && options.shape.type;
this.chart = chart;
this.options = merge({}, defaultOptions(shapeType), options);
},
/*
* Render the annotation
*/
render: function (redraw) {
var annotation = this,
chart = this.chart,
renderer = annotation.chart.renderer,
group = annotation.group,
title = annotation.title,
shape = annotation.shape,
options = annotation.options,
titleOptions = options.title,
shapeOptions = options.shape;
if (!group) {
group = annotation.group = renderer.g();
}
if (!shape && shapeOptions && inArray(shapeOptions.type, ALLOWED_SHAPES) !== -1) {
shape = annotation.shape = renderer[options.shape.type](shapeOptions.params);
shape.add(group);
}
if (!title && titleOptions) {
title = annotation.title = renderer.label(titleOptions);
title.add(group);
}
group.add(chart.annotations.group);
// link annotations to point or series
annotation.linkObjects();
if (redraw !== false) {
annotation.redraw();
}
},
/*
* Redraw the annotation title or shape after options update
*/
redraw: function () {
var options = this.options,
chart = this.chart,
group = this.group,
title = this.title,
shape = this.shape,
linkedTo = this.linkedObject,
xAxis = chart.xAxis[options.xAxis],
yAxis = chart.yAxis[options.yAxis],
width = options.width,
height = options.height,
anchorY = ALIGN_FACTOR[options.anchorY],
anchorX = ALIGN_FACTOR[options.anchorX],
resetBBox = false,
shapeParams,
linkType,
series,
param,
bbox,
x,
y;
if (linkedTo) {
linkType = (linkedTo instanceof Highcharts.Point) ? 'point' :
(linkedTo instanceof Highcharts.Series) ? 'series' : null;
if (linkType === 'point') {
options.xValue = linkedTo.x;
options.yValue = linkedTo.y;
series = linkedTo.series;
} else if (linkType === 'series') {
series = linkedTo;
}
if (group.visibility !== series.group.visibility) {
group.attr({
visibility: series.group.visibility
});
}
}
// Based on given options find annotation pixel position
x = (defined(options.xValue) ? xAxis.toPixels(options.xValue + xAxis.minPointOffset) - xAxis.minPixelPadding : options.x);
y = defined(options.yValue) ? yAxis.toPixels(options.yValue) : options.y;
if (isNaN(x) || isNaN(y) || !isNumber(x) || !isNumber(y)) {
return;
}
if (title) {
title.attr(options.title);
title.css(options.title.style);
resetBBox = true;
}
if (shape) {
shapeParams = extend({}, options.shape.params);
if (options.units === 'values') {
for (param in shapeParams) {
if (inArray(param, ['width', 'x']) > -1) {
shapeParams[param] = xAxis.translate(shapeParams[param]);
} else if (inArray(param, ['height', 'y']) > -1) {
shapeParams[param] = yAxis.translate(shapeParams[param]);
}
}
if (shapeParams.width) {
shapeParams.width -= xAxis.toPixels(0) - xAxis.left;
}
if (shapeParams.x) {
shapeParams.x += xAxis.minPixelPadding;
}
if (options.shape.type === 'path') {
translatePath(shapeParams.d, xAxis, yAxis, x, y);
}
}
// move the center of the circle to shape x/y
if (options.shape.type === 'circle') {
shapeParams.x += shapeParams.r;
shapeParams.y += shapeParams.r;
}
resetBBox = true;
shape.attr(shapeParams);
}
group.bBox = null;
// If annotation width or height is not defined in options use bounding box size
if (!isNumber(width)) {
bbox = group.getBBox();
width = bbox.width;
}
if (!isNumber(height)) {
// get bbox only if it wasn't set before
if (!bbox) {
bbox = group.getBBox();
}
height = bbox.height;
}
// Calculate anchor point
if (!isNumber(anchorX)) {
anchorX = ALIGN_FACTOR.center;
}
if (!isNumber(anchorY)) {
anchorY = ALIGN_FACTOR.center;
}
// Translate group according to its dimension and anchor point
x = x - width * anchorX;
y = y - height * anchorY;
if (chart.animation && defined(group.translateX) && defined(group.translateY)) {
group.animate({
translateX: x,
translateY: y
});
} else {
group.translate(x, y);
}
},
/*
* Destroy the annotation
*/
destroy: function () {
var annotation = this,
chart = this.chart,
allItems = chart.annotations.allItems,
index = allItems.indexOf(annotation);
if (index > -1) {
allItems.splice(index, 1);
}
each(['title', 'shape', 'group'], function (element) {
if (annotation[element]) {
annotation[element].destroy();
annotation[element] = null;
}
});
annotation.group = annotation.title = annotation.shape = annotation.chart = annotation.options = null;
},
/*
* Update the annotation with a given options
*/
update: function (options, redraw) {
extend(this.options, options);
// update link to point or series
this.linkObjects();
this.render(redraw);
},
linkObjects: function () {
var annotation = this,
chart = annotation.chart,
linkedTo = annotation.linkedObject,
linkedId = linkedTo && (linkedTo.id || linkedTo.options.id),
options = annotation.options,
id = options.linkedTo;
if (!defined(id)) {
annotation.linkedObject = null;
} else if (!defined(linkedTo) || id !== linkedId) {
annotation.linkedObject = chart.get(id);
}
}
};
// Add annotations methods to chart prototype
extend(Chart.prototype, {
annotations: {
/*
* Unified method for adding annotations to the chart
*/
add: function (options, redraw) {
var annotations = this.allItems,
chart = this.chart,
item,
len;
if (!isArray(options)) {
options = [options];
}
len = options.length;
while (len--) {
item = new Annotation(chart, options[len]);
annotations.push(item);
item.render(redraw);
}
},
/**
* Redraw all annotations, method used in chart events
*/
redraw: function () {
each(this.allItems, function (annotation) {
annotation.redraw();
});
}
}
});
// Initialize on chart load
Chart.prototype.callbacks.push(function (chart) {
var options = chart.options.annotations,
group;
group = chart.renderer.g("annotations");
group.attr({
zIndex: 7
});
group.add();
// initialize empty array for annotations
chart.annotations.allItems = [];
// link chart object to annotations
chart.annotations.chart = chart;
// link annotations group element to the chart
chart.annotations.group = group;
if (isArray(options) && options.length > 0) {
chart.annotations.add(chart.options.annotations);
}
// update annotations after chart redraw
Highcharts.addEvent(chart, 'redraw', function () {
chart.annotations.redraw();
});
});
}(Highcharts, HighchartsAdapter));