373 lines
9.2 KiB
Plaintext
373 lines
9.2 KiB
Plaintext
/**
|
|
* Box selection tool.
|
|
*
|
|
* @module echarts/component/helper/SelectController
|
|
*/
|
|
|
|
define(function (require) {
|
|
|
|
var Eventful = require('zrender/mixin/Eventful');
|
|
var zrUtil = require('zrender/core/util');
|
|
var graphic = require('../../util/graphic');
|
|
var bind = zrUtil.bind;
|
|
var each = zrUtil.each;
|
|
var mathMin = Math.min;
|
|
var mathMax = Math.max;
|
|
var mathPow = Math.pow;
|
|
|
|
var COVER_Z = 10000;
|
|
var UNSELECT_THRESHOLD = 2;
|
|
var EVENTS = ['mousedown', 'mousemove', 'mouseup'];
|
|
|
|
/**
|
|
* @alias module:echarts/component/helper/SelectController
|
|
* @constructor
|
|
* @mixin {module:zrender/mixin/Eventful}
|
|
*
|
|
* @param {string} type 'line', 'rect'
|
|
* @param {module:zrender/zrender~ZRender} zr
|
|
* @param {Object} [opt]
|
|
* @param {number} [opt.width]
|
|
* @param {number} [opt.lineWidth]
|
|
* @param {string} [opt.stroke]
|
|
* @param {string} [opt.fill]
|
|
*/
|
|
function SelectController(type, zr, opt) {
|
|
|
|
Eventful.call(this);
|
|
|
|
/**
|
|
* @type {string}
|
|
* @readOnly
|
|
*/
|
|
this.type = type;
|
|
|
|
/**
|
|
* @type {module:zrender/zrender~ZRender}
|
|
*/
|
|
this.zr = zr;
|
|
|
|
/**
|
|
* @type {Object}
|
|
* @readOnly
|
|
*/
|
|
this.opt = zrUtil.clone(opt);
|
|
|
|
/**
|
|
* @type {module:zrender/container/Group}
|
|
* @readOnly
|
|
*/
|
|
this.group = new graphic.Group();
|
|
|
|
/**
|
|
* @type {module:zrender/core/BoundingRect}
|
|
*/
|
|
this._containerRect = null;
|
|
|
|
/**
|
|
* @type {Array.<nubmer>}
|
|
* @private
|
|
*/
|
|
this._track = [];
|
|
|
|
/**
|
|
* @type {boolean}
|
|
*/
|
|
this._dragging;
|
|
|
|
/**
|
|
* @type {module:zrender/Element}
|
|
* @private
|
|
*/
|
|
this._cover;
|
|
|
|
/**
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._disabled = true;
|
|
|
|
/**
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._handlers = {
|
|
mousedown: bind(mousedown, this),
|
|
mousemove: bind(mousemove, this),
|
|
mouseup: bind(mouseup, this)
|
|
};
|
|
|
|
each(EVENTS, function (eventName) {
|
|
this.zr.on(eventName, this._handlers[eventName]);
|
|
}, this);
|
|
}
|
|
|
|
SelectController.prototype = {
|
|
|
|
constructor: SelectController,
|
|
|
|
/**
|
|
* @param {module:zrender/mixin/Transformable} container
|
|
* @param {module:zrender/core/BoundingRect|boolean} [rect] If not specified,
|
|
* use container.getBoundingRect().
|
|
* If false, do not use containerRect.
|
|
*/
|
|
enable: function (container, rect) {
|
|
|
|
this._disabled = false;
|
|
|
|
// Remove from old container.
|
|
removeGroup.call(this);
|
|
|
|
// boundingRect will change when dragging, so we have
|
|
// to keep initial boundingRect.
|
|
this._containerRect = rect !== false
|
|
? (rect || container.getBoundingRect()) : null;
|
|
|
|
// Add to new container.
|
|
container.add(this.group);
|
|
},
|
|
|
|
/**
|
|
* Update cover location.
|
|
* @param {Array.<number>|Object} ranges If null/undefined, remove cover.
|
|
*/
|
|
update: function (ranges) {
|
|
// TODO
|
|
// Only support one interval yet.
|
|
renderCover.call(this, ranges && zrUtil.clone(ranges));
|
|
},
|
|
|
|
disable: function () {
|
|
this._disabled = true;
|
|
|
|
removeGroup.call(this);
|
|
},
|
|
|
|
dispose: function () {
|
|
this.disable();
|
|
|
|
each(EVENTS, function (eventName) {
|
|
this.zr.off(eventName, this._handlers[eventName]);
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
|
|
zrUtil.mixin(SelectController, Eventful);
|
|
|
|
function updateZ(group) {
|
|
group.traverse(function (el) {
|
|
el.z = COVER_Z;
|
|
});
|
|
}
|
|
|
|
function isInContainer(x, y) {
|
|
var localPos = this.group.transformCoordToLocal(x, y);
|
|
return !this._containerRect
|
|
|| this._containerRect.contain(localPos[0], localPos[1]);
|
|
}
|
|
|
|
function preventDefault(e) {
|
|
var rawE = e.event;
|
|
rawE.preventDefault && rawE.preventDefault();
|
|
}
|
|
|
|
function mousedown(e) {
|
|
if (this._disabled || (e.target && e.target.draggable)) {
|
|
return;
|
|
}
|
|
|
|
preventDefault(e);
|
|
|
|
var x = e.offsetX;
|
|
var y = e.offsetY;
|
|
|
|
if (isInContainer.call(this, x, y)) {
|
|
this._dragging = true;
|
|
this._track = [[x, y]];
|
|
}
|
|
}
|
|
|
|
function mousemove(e) {
|
|
if (!this._dragging || this._disabled) {
|
|
return;
|
|
}
|
|
|
|
preventDefault(e);
|
|
|
|
updateViewByCursor.call(this, e);
|
|
}
|
|
|
|
function mouseup(e) {
|
|
if (!this._dragging || this._disabled) {
|
|
return;
|
|
}
|
|
|
|
preventDefault(e);
|
|
|
|
updateViewByCursor.call(this, e, true);
|
|
|
|
this._dragging = false;
|
|
this._track = [];
|
|
}
|
|
|
|
function updateViewByCursor(e, isEnd) {
|
|
var x = e.offsetX;
|
|
var y = e.offsetY;
|
|
|
|
if (isInContainer.call(this, x, y)) {
|
|
this._track.push([x, y]);
|
|
|
|
// Create or update cover.
|
|
var ranges = shouldShowCover.call(this)
|
|
? coverRenderers[this.type].getRanges.call(this)
|
|
// Remove cover.
|
|
: [];
|
|
|
|
renderCover.call(this, ranges);
|
|
|
|
this.trigger('selected', zrUtil.clone(ranges));
|
|
|
|
if (isEnd) {
|
|
this.trigger('selectEnd', zrUtil.clone(ranges));
|
|
}
|
|
}
|
|
}
|
|
|
|
function shouldShowCover() {
|
|
var track = this._track;
|
|
|
|
if (!track.length) {
|
|
return false;
|
|
}
|
|
|
|
var p2 = track[track.length - 1];
|
|
var p1 = track[0];
|
|
var dx = p2[0] - p1[0];
|
|
var dy = p2[1] - p1[1];
|
|
var dist = mathPow(dx * dx + dy * dy, 0.5);
|
|
|
|
return dist > UNSELECT_THRESHOLD;
|
|
}
|
|
|
|
function renderCover(ranges) {
|
|
var coverRenderer = coverRenderers[this.type];
|
|
|
|
if (ranges && ranges.length) {
|
|
if (!this._cover) {
|
|
this._cover = coverRenderer.create.call(this);
|
|
this.group.add(this._cover);
|
|
}
|
|
coverRenderer.update.call(this, ranges);
|
|
}
|
|
else {
|
|
this.group.remove(this._cover);
|
|
this._cover = null;
|
|
}
|
|
|
|
updateZ(this.group);
|
|
}
|
|
|
|
function removeGroup() {
|
|
// container may 'removeAll' outside.
|
|
var group = this.group;
|
|
var container = group.parent;
|
|
if (container) {
|
|
container.remove(group);
|
|
}
|
|
}
|
|
|
|
function createRectCover() {
|
|
var opt = this.opt;
|
|
return new graphic.Rect({
|
|
// FIXME
|
|
// customize style.
|
|
style: {
|
|
stroke: opt.stroke,
|
|
fill: opt.fill,
|
|
lineWidth: opt.lineWidth,
|
|
opacity: opt.opacity
|
|
}
|
|
});
|
|
}
|
|
|
|
function getLocalTrack() {
|
|
return zrUtil.map(this._track, function (point) {
|
|
return this.group.transformCoordToLocal(point[0], point[1]);
|
|
}, this);
|
|
}
|
|
|
|
function getLocalTrackEnds() {
|
|
var localTrack = getLocalTrack.call(this);
|
|
var tail = localTrack.length - 1;
|
|
tail < 0 && (tail = 0);
|
|
return [localTrack[0], localTrack[tail]];
|
|
}
|
|
|
|
/**
|
|
* key: this.type
|
|
* @type {Object}
|
|
*/
|
|
var coverRenderers = {
|
|
|
|
line: {
|
|
|
|
create: createRectCover,
|
|
|
|
getRanges: function () {
|
|
var ends = getLocalTrackEnds.call(this);
|
|
var min = mathMin(ends[0][0], ends[1][0]);
|
|
var max = mathMax(ends[0][0], ends[1][0]);
|
|
|
|
return [[min, max]];
|
|
},
|
|
|
|
update: function (ranges) {
|
|
var range = ranges[0];
|
|
var width = this.opt.width;
|
|
this._cover.setShape({
|
|
x: range[0],
|
|
y: -width / 2,
|
|
width: range[1] - range[0],
|
|
height: width
|
|
});
|
|
}
|
|
},
|
|
|
|
rect: {
|
|
|
|
create: createRectCover,
|
|
|
|
getRanges: function () {
|
|
var ends = getLocalTrackEnds.call(this);
|
|
|
|
var min = [
|
|
mathMin(ends[1][0], ends[0][0]),
|
|
mathMin(ends[1][1], ends[0][1])
|
|
];
|
|
var max = [
|
|
mathMax(ends[1][0], ends[0][0]),
|
|
mathMax(ends[1][1], ends[0][1])
|
|
];
|
|
|
|
return [[
|
|
[min[0], max[0]], // x range
|
|
[min[1], max[1]] // y range
|
|
]];
|
|
},
|
|
|
|
update: function (ranges) {
|
|
var range = ranges[0];
|
|
this._cover.setShape({
|
|
x: range[0][0],
|
|
y: range[1][0],
|
|
width: range[0][1] - range[0][0],
|
|
height: range[1][1] - range[1][0]
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
return SelectController;
|
|
}); |