409 lines
11 KiB
JavaScript
409 lines
11 KiB
JavaScript
![]() |
'use strict';
|
||
|
import H from './../../parts/Globals.js';
|
||
|
import './../../parts/Utilities.js';
|
||
|
import './../../parts/Tooltip.js';
|
||
|
import ControlPoint from './../ControlPoint.js';
|
||
|
import MockPoint from './../MockPoint.js';
|
||
|
|
||
|
/**
|
||
|
* It provides methods for handling points, control points
|
||
|
* and points transformations.
|
||
|
*
|
||
|
* @mixin
|
||
|
* @memberOf Annotation
|
||
|
*/
|
||
|
var controllableMixin = {
|
||
|
/**
|
||
|
* Init the controllable
|
||
|
*
|
||
|
* @param {Annotation} annotation - an annotation instance
|
||
|
* @param {Object} options - options specific for controllable
|
||
|
**/
|
||
|
init: function (annotation, options) {
|
||
|
this.annotation = annotation;
|
||
|
this.chart = annotation.chart;
|
||
|
this.options = options;
|
||
|
this.points = [];
|
||
|
this.controlPoints = [];
|
||
|
|
||
|
this.linkPoints();
|
||
|
this.addControlPoints();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Redirect attr usage on the controllable graphic element.
|
||
|
**/
|
||
|
attr: function () {
|
||
|
this.graphic.attr.apply(this.graphic, arguments);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Get the controllable's points options.
|
||
|
*
|
||
|
* @return {Array<PointLikeOptions>} - an array of points' options.
|
||
|
*
|
||
|
*/
|
||
|
getPointsOptions: function () {
|
||
|
var options = this.options;
|
||
|
|
||
|
return options.points || (options.point && H.splat(options.point));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Utility function for mapping item's options
|
||
|
* to element's attribute
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @return {Object} mapped options
|
||
|
**/
|
||
|
attrsFromOptions: function (options) {
|
||
|
var map = this.constructor.attrsMap,
|
||
|
attrs = {},
|
||
|
key,
|
||
|
mappedKey,
|
||
|
styledMode = this.chart.styledMode;
|
||
|
|
||
|
for (key in options) {
|
||
|
mappedKey = map[key];
|
||
|
|
||
|
if (
|
||
|
mappedKey &&
|
||
|
(
|
||
|
!styledMode ||
|
||
|
['fill', 'stroke', 'stroke-width']
|
||
|
.indexOf(mappedKey) === -1
|
||
|
)
|
||
|
) {
|
||
|
attrs[mappedKey] = options[key];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return attrs;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @typedef {Object} Annotation.controllableMixin.Position
|
||
|
* @property {number} x
|
||
|
* @property {number} y
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* An object which denotes an anchor position
|
||
|
*
|
||
|
* @typedef Annotation.controllableMixin.AnchorPosition
|
||
|
* Annotation.controllableMixin.Position
|
||
|
* @property {number} height
|
||
|
* @property {number} width
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* An object which denots a controllable's anchor positions
|
||
|
* - relative and absolute.
|
||
|
*
|
||
|
* @typedef {Object} Annotation.controllableMixin.Anchor
|
||
|
* @property {Annotation.controllableMixin.AnchorPosition} relativePosition
|
||
|
* @property {Annotation.controllableMixin.AnchorPosition} absolutePosition
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Returns object which denotes anchor position - relative and absolute.
|
||
|
*
|
||
|
* @param {Annotation.PointLike} point a point like object
|
||
|
* @return {Annotation.controllableMixin.Anchor} a controllable anchor
|
||
|
*/
|
||
|
anchor: function (point) {
|
||
|
var plotBox = point.series.getPlotBox(),
|
||
|
|
||
|
box = point.mock ?
|
||
|
point.toAnchor() :
|
||
|
H.Tooltip.prototype.getAnchor.call({
|
||
|
chart: point.series.chart
|
||
|
}, point),
|
||
|
|
||
|
anchor = {
|
||
|
x: box[0] + (this.options.x || 0),
|
||
|
y: box[1] + (this.options.y || 0),
|
||
|
height: box[2] || 0,
|
||
|
width: box[3] || 0
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
relativePosition: anchor,
|
||
|
absolutePosition: H.merge(anchor, {
|
||
|
x: anchor.x + plotBox.translateX,
|
||
|
y: anchor.y + plotBox.translateY
|
||
|
})
|
||
|
};
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Map point's options to a point-like object.
|
||
|
*
|
||
|
* @param {Annotation.MockPoint.Options} pointOptions point's options
|
||
|
* @param {Annotation.PointLike} point a point like instance
|
||
|
* @return {Annotation.PointLike|null} if the point is
|
||
|
* found/set returns this point, otherwise null
|
||
|
*/
|
||
|
point: function (pointOptions, point) {
|
||
|
if (pointOptions && pointOptions.series) {
|
||
|
return pointOptions;
|
||
|
}
|
||
|
|
||
|
if (!point || point.series === null) {
|
||
|
if (H.isObject(pointOptions)) {
|
||
|
point = new MockPoint(
|
||
|
this.chart,
|
||
|
this,
|
||
|
pointOptions
|
||
|
);
|
||
|
} else if (H.isString(pointOptions)) {
|
||
|
point = this.chart.get(pointOptions) || null;
|
||
|
} else if (typeof pointOptions === 'function') {
|
||
|
var pointConfig = pointOptions.call(point, this);
|
||
|
|
||
|
point = pointConfig.series ?
|
||
|
pointConfig :
|
||
|
new MockPoint(
|
||
|
this.chart,
|
||
|
this,
|
||
|
pointOptions
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return point;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Find point-like objects based on points options.
|
||
|
*
|
||
|
* @return {Array<Annotation.PointLike>} an array of point-like objects
|
||
|
*/
|
||
|
linkPoints: function () {
|
||
|
var pointsOptions = this.getPointsOptions(),
|
||
|
points = this.points,
|
||
|
len = (pointsOptions && pointsOptions.length) || 0,
|
||
|
i,
|
||
|
point;
|
||
|
|
||
|
for (i = 0; i < len; i++) {
|
||
|
point = this.point(pointsOptions[i], points[i]);
|
||
|
|
||
|
if (!point) {
|
||
|
points.length = 0;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (point.mock) {
|
||
|
point.refresh();
|
||
|
}
|
||
|
|
||
|
points[i] = point;
|
||
|
}
|
||
|
|
||
|
return points;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Add control points to a controllable.
|
||
|
*/
|
||
|
addControlPoints: function () {
|
||
|
var controlPointsOptions = this.options.controlPoints;
|
||
|
|
||
|
(controlPointsOptions || []).forEach(
|
||
|
function (controlPointOptions, i) {
|
||
|
var options = H.merge(
|
||
|
this.options.controlPointOptions,
|
||
|
controlPointOptions
|
||
|
);
|
||
|
|
||
|
if (!options.index) {
|
||
|
options.index = i;
|
||
|
}
|
||
|
|
||
|
controlPointsOptions[i] = options;
|
||
|
|
||
|
this.controlPoints.push(
|
||
|
new ControlPoint(this.chart, this, options)
|
||
|
);
|
||
|
},
|
||
|
this
|
||
|
);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Check if a controllable should be rendered/redrawn.
|
||
|
*
|
||
|
* @return {boolean} whether a controllable should be drawn.
|
||
|
*/
|
||
|
shouldBeDrawn: function () {
|
||
|
return Boolean(this.points.length);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Render a controllable.
|
||
|
**/
|
||
|
render: function () {
|
||
|
this.controlPoints.forEach(function (controlPoint) {
|
||
|
controlPoint.render();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Redraw a controllable.
|
||
|
*
|
||
|
* @param {boolean} animation
|
||
|
**/
|
||
|
redraw: function (animation) {
|
||
|
this.controlPoints.forEach(function (controlPoint) {
|
||
|
controlPoint.redraw(animation);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Transform a controllable with a specific transformation.
|
||
|
*
|
||
|
* @param {string} transformation a transformation name
|
||
|
* @param {number} cx origin x transformation
|
||
|
* @param {number} cy origin y transformation
|
||
|
* @param {number} p1 param for the transformation
|
||
|
* @param {number} p2 param for the transformation
|
||
|
**/
|
||
|
transform: function (transformation, cx, cy, p1, p2) {
|
||
|
if (this.chart.inverted) {
|
||
|
var temp = cx;
|
||
|
cx = cy;
|
||
|
cy = temp;
|
||
|
}
|
||
|
|
||
|
this.points.forEach(function (point, i) {
|
||
|
this.transformPoint(transformation, cx, cy, p1, p2, i);
|
||
|
}, this);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Transform a point with a specific transformation
|
||
|
* If a transformed point is a real point it is replaced with
|
||
|
* the mock point.
|
||
|
*
|
||
|
* @param {string} transformation a transformation name
|
||
|
* @param {number} cx origin x transformation
|
||
|
* @param {number} cy origin y transformation
|
||
|
* @param {number} p1 param for the transformation
|
||
|
* @param {number} p2 param for the transformation
|
||
|
* @param {number} i index of the point
|
||
|
*
|
||
|
**/
|
||
|
transformPoint: function (transformation, cx, cy, p1, p2, i) {
|
||
|
var point = this.points[i];
|
||
|
|
||
|
if (!point.mock) {
|
||
|
point = this.points[i] = MockPoint.fromPoint(point);
|
||
|
}
|
||
|
|
||
|
point[transformation](cx, cy, p1, p2);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Translate a controllable.
|
||
|
*
|
||
|
* @param {number} dx translation for x coordinate
|
||
|
* @param {number} dy translation for y coordinate
|
||
|
**/
|
||
|
translate: function (dx, dy) {
|
||
|
this.transform('translate', null, null, dx, dy);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Translate a specific point within a controllable.
|
||
|
*
|
||
|
* @param {number} dx translation for x coordinate
|
||
|
* @param {number} dy translation for y coordinate
|
||
|
* @param {number} i index of the point
|
||
|
**/
|
||
|
translatePoint: function (dx, dy, i) {
|
||
|
this.transformPoint('translate', null, null, dx, dy, i);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Rotate a controllable.
|
||
|
*
|
||
|
* @param {number} cx origin x rotation
|
||
|
* @param {number} cy origin y rotation
|
||
|
* @param {number} radians
|
||
|
**/
|
||
|
rotate: function (cx, cy, radians) {
|
||
|
this.transform('rotate', cx, cy, radians);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Scale a controllable.
|
||
|
*
|
||
|
* @param {number} cx origin x rotation
|
||
|
* @param {number} cy origin y rotation
|
||
|
* @param {number} sx scale factor x
|
||
|
* @param {number} sy scale factor y
|
||
|
*/
|
||
|
scale: function (cx, cy, sx, sy) {
|
||
|
this.transform('scale', cx, cy, sx, sy);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set control points' visibility.
|
||
|
*
|
||
|
* @param {boolean} [visible]
|
||
|
*/
|
||
|
setControlPointsVisibility: function (visible) {
|
||
|
this.controlPoints.forEach(function (controlPoint) {
|
||
|
controlPoint.setVisibility(visible);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Destroy a controllable.
|
||
|
*/
|
||
|
destroy: function () {
|
||
|
if (this.graphic) {
|
||
|
this.graphic = this.graphic.destroy();
|
||
|
}
|
||
|
|
||
|
if (this.tracker) {
|
||
|
this.tracker = this.tracker.destroy();
|
||
|
}
|
||
|
|
||
|
this.controlPoints.forEach(function (controlPoint) {
|
||
|
controlPoint.destroy();
|
||
|
});
|
||
|
|
||
|
this.chart = null;
|
||
|
this.points = null;
|
||
|
this.controlPoints = null;
|
||
|
this.options = null;
|
||
|
|
||
|
if (this.annotation) {
|
||
|
this.annotation = null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Update a controllable.
|
||
|
*
|
||
|
* @param {Object} newOptions
|
||
|
*/
|
||
|
update: function (newOptions) {
|
||
|
var annotation = this.annotation,
|
||
|
options = H.merge(true, this.options, newOptions),
|
||
|
parentGroup = this.graphic.parentGroup;
|
||
|
|
||
|
this.destroy();
|
||
|
this.constructor(annotation, options);
|
||
|
this.render(parentGroup);
|
||
|
this.redraw();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
export default controllableMixin;
|