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.
401 lines
8.6 KiB
401 lines
8.6 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));
|
|
|