blob: fef36c3fe7ff8ccc5ee9ebb6d345084be60d642f [file] [log] [blame]
/**
* Box selection tool.
*
* @module echarts/component/helper/BrushController
*/
define(function (require) {
var Eventful = require('zrender/mixin/Eventful');
var zrUtil = require('zrender/core/util');
var BoundingRect = require('zrender/core/BoundingRect');
var graphic = require('../../util/graphic');
var interactionMutex = require('./interactionMutex');
var DataDiffer = require('../../data/DataDiffer');
var curry = zrUtil.curry;
var each = zrUtil.each;
var map = zrUtil.map;
var mathMin = Math.min;
var mathMax = Math.max;
var mathPow = Math.pow;
var COVER_Z = 10000;
var UNSELECT_THRESHOLD = 6;
var MIN_RESIZE_LINE_WIDTH = 6;
var MUTEX_RESOURCE_KEY = 'globalPan';
var DIRECTION_MAP = {
w: [0, 0],
e: [0, 1],
n: [1, 0],
s: [1, 1]
};
var CURSOR_MAP = {
w: 'ew',
e: 'ew',
n: 'ns',
s: 'ns',
ne: 'nesw',
sw: 'nesw',
nw: 'nwse',
se: 'nwse'
};
var DEFAULT_BRUSH_OPT = {
brushStyle: {
lineWidth: 2,
stroke: 'rgba(0,0,0,0.3)',
fill: 'rgba(0,0,0,0.1)'
},
transformable: true,
brushMode: 'single',
removeOnClick: false
};
var baseUID = 0;
/**
* @alias module:echarts/component/helper/BrushController
* @constructor
* @mixin {module:zrender/mixin/Eventful}
* @event module:echarts/component/helper/BrushController#brush
* params:
* areas: Array.<Array>, coord relates to container group,
* If no container specified, to global.
* opt {
* isEnd: boolean,
* removeOnClick: boolean
* }
*
* @param {module:zrender/zrender~ZRender} zr
*/
function BrushController(zr) {
if (__DEV__) {
zrUtil.assert(zr);
}
Eventful.call(this);
/**
* @type {module:zrender/zrender~ZRender}
* @private
*/
this._zr = zr;
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = new graphic.Group();
/**
* Only for drawing (after enabledBrush).
* @private
* @type {string}
*/
this._brushType;
/**
* Only for drawing (after enabledBrush).
* @private
* @type {Object}
*/
this._brushOption;
/**
* @private
* @type {Object}
*/
this._panels;
/**
* @private
* @type {Array.<nubmer>}
*/
this._track = [];
/**
* @private
* @type {boolean}
*/
this._dragging;
/**
* @private
* @type {Array}
*/
this._covers = [];
/**
* @private
* @type {moudule:zrender/container/Group}
*/
this._creatingCover;
/**
* true means global panel
* @private
* @type {module:zrender/container/Group|boolean}
*/
this._creatingPanel;
/**
* @private
* @type {boolean}
*/
this._enableGlobalPan;
/**
* @private
* @type {boolean}
*/
if (__DEV__) {
this._mounted;
}
/**
* @private
* @type {string}
*/
this._uid = 'brushController_' + baseUID++;
/**
* @private
* @type {Object}
*/
this._handlers = {};
each(mouseHandlers, function (handler, eventName) {
this._handlers[eventName] = zrUtil.bind(handler, this);
}, this);
}
BrushController.prototype = {
constructor: BrushController,
/**
* If set to null/undefined/false, select disabled.
* @param {Object} brushOption
* @param {string|boolean} brushOption.brushType 'line', 'rect', 'polygon' or false
* If pass false/null/undefined, disable brush.
* @param {number} [brushOption.brushMode='single'] 'single' or 'multiple'
* @param {boolean} [brushOption.transformable=true]
* @param {boolean} [brushOption.removeOnClick=false]
* @param {Object} [brushOption.brushStyle]
* @param {number} [brushOption.brushStyle.width]
* @param {number} [brushOption.brushStyle.lineWidth]
* @param {string} [brushOption.brushStyle.stroke]
* @param {string} [brushOption.brushStyle.fill]
*/
enableBrush: function (brushOption) {
if (__DEV__) {
zrUtil.assert(this._mounted);
}
this._brushType && doDisableBrush(this);
brushOption.brushType && doEnableBrush(this, brushOption);
return this;
},
/**
* @param {Array.<Object>} panelOpts If not pass, it is global brush.
* Each items: {panelId, rect}
*/
setPanels: function (panelOpts) {
var oldPanels = this._panels || {};
var newPanels = this._panels = panelOpts && panelOpts.length && {};
var thisGroup = this.group;
newPanels && each(panelOpts, function (panelOpt) {
var panelId = panelOpt.panelId;
var panel = oldPanels[panelId];
if (!panel) {
panel = new graphic.Rect({
silent: true,
invisible: true
});
thisGroup.add(panel);
}
var rect = panelOpt.rect;
// Using BoundingRect to normalize negative width/height.
if (!(rect instanceof BoundingRect)) {
rect = BoundingRect.create(rect);
}
panel.attr('shape', rect.plain());
panel.__brushPanelId = panelId;
newPanels[panelId] = panel;
oldPanels[panelId] = null;
});
each(oldPanels, function (panel) {
panel && thisGroup.remove(panel);
});
return this;
},
/**
* @param {Object} [opt]
* @return {boolean} [opt.enableGlobalPan=false]
* @return {boolean} [opt.position=[0, 0]]
* @return {boolean} [opt.rotation=0]
* @return {boolean} [opt.scale=[1, 1]]
*/
mount: function (opt) {
opt = opt || {};
if (__DEV__) {
this._mounted = true; // should be at first.
}
this._enableGlobalPan = opt.enableGlobalPan;
var thisGroup = this.group;
this._zr.add(thisGroup);
thisGroup.attr({
position: opt.position || [0, 0],
rotation: opt.rotation || 0,
scale: opt.scale || [1, 1]
});
return this;
},
eachCover: function (cb, context) {
each(this._covers, cb, context);
},
/**
* Update covers.
* @param {Array.<Object>} brushOptionList Like:
* [
* {id: 'xx', brushType: 'line', range: [23, 44], brushStyle, transformable},
* {id: 'yy', brushType: 'rect', range: [[23, 44], [23, 54]]},
* ...
* ]
* `brushType` is required in each cover info.
* `id` is not mandatory.
* `brushStyle`, `transformable` is not mandatory, use DEFAULT_BRUSH_OPT by default.
* If brushOptionList is null/undefined, all covers removed.
*/
updateCovers: function (brushOptionList) {
if (__DEV__) {
zrUtil.assert(this._mounted);
}
brushOptionList = zrUtil.map(brushOptionList, function (brushOption) {
return zrUtil.merge(zrUtil.clone(DEFAULT_BRUSH_OPT), brushOption, true);
});
var tmpIdPrefix = '\0-brush-index-';
var oldCovers = this._covers;
var newCovers = this._covers = [];
var controller = this;
var creatingCover = this._creatingCover;
(new DataDiffer(oldCovers, brushOptionList, oldGetKey, getKey))
.add(addOrUpdate)
.update(addOrUpdate)
.remove(remove)
.execute();
return this;
function getKey(brushOption, index) {
return (brushOption.id != null ? brushOption.id : tmpIdPrefix + index)
+ '-' + brushOption.brushType;
}
function oldGetKey(cover, index) {
return getKey(cover.__brushOption, index);
}
function addOrUpdate(newIndex, oldIndex) {
var newBrushOption = brushOptionList[newIndex];
// Consider setOption in event listener of brushSelect,
// where updating cover when creating should be forbiden.
if (oldIndex != null && oldCovers[oldIndex] === creatingCover) {
newCovers[newIndex] = oldCovers[oldIndex];
}
else {
var cover = newCovers[newIndex] = oldIndex != null
? (
oldCovers[oldIndex].__brushOption = newBrushOption,
oldCovers[oldIndex]
)
: endCreating(controller, createCover(controller, newBrushOption));
updateCoverAfterCreation(controller, cover);
}
}
function remove(oldIndex) {
if (oldCovers[oldIndex] !== creatingCover) {
controller.group.remove(oldCovers[oldIndex]);
}
}
},
unmount: function () {
this.enableBrush(false);
// container may 'removeAll' outside.
clearCovers(this);
this._zr.remove(this.group);
if (__DEV__) {
this._mounted = false; // should be at last.
}
return this;
},
dispose: function () {
this.unmount();
this.off();
}
};
zrUtil.mixin(BrushController, Eventful);
function doEnableBrush(controller, brushOption) {
var zr = controller._zr;
// Consider roam, which takes globalPan too.
if (!controller._enableGlobalPan) {
interactionMutex.take(zr, MUTEX_RESOURCE_KEY, controller._uid);
}
each(controller._handlers, function (handler, eventName) {
zr.on(eventName, handler);
});
controller._brushType = brushOption.brushType;
controller._brushOption = zrUtil.merge(zrUtil.clone(DEFAULT_BRUSH_OPT), brushOption, true);
}
function doDisableBrush(controller) {
var zr = controller._zr;
interactionMutex.release(zr, MUTEX_RESOURCE_KEY, controller._uid);
each(controller._handlers, function (handler, eventName) {
zr.off(eventName, handler);
});
controller._brushType = controller._brushOption = null;
}
function createCover(controller, brushOption) {
var cover = coverRenderers[brushOption.brushType].createCover(controller, brushOption);
updateZ(cover);
cover.__brushOption = brushOption;
controller.group.add(cover);
return cover;
}
function endCreating(controller, creatingCover) {
var coverRenderer = getCoverRenderer(creatingCover);
if (coverRenderer.endCreating) {
coverRenderer.endCreating(controller, creatingCover);
updateZ(creatingCover);
}
return creatingCover;
}
function updateCoverShape(controller, cover) {
var brushOption = cover.__brushOption;
getCoverRenderer(cover).updateCoverShape(
controller, cover, brushOption.range, brushOption
);
}
function updateZ(group) {
group.traverse(function (el) {
el.z = COVER_Z;
el.z2 = COVER_Z; // Consider in given container.
});
}
function updateCoverAfterCreation(controller, cover) {
getCoverRenderer(cover).updateCommon(controller, cover);
updateCoverShape(controller, cover);
}
function getCoverRenderer(cover) {
return coverRenderers[cover.__brushOption.brushType];
}
function getPanelByPoint(controller, x, y) {
var panels = controller._panels;
if (!panels) {
return true; // Global panel
}
var panel;
each(panels, function (pn) {
pn.contain(x, y) && (panel = pn);
});
return panel;
}
function getPanelByCover(controller, cover) {
var panels = controller._panels;
if (!panels) {
return true; // Global panel
}
var panelId = cover.__brushOption.panelId;
// User may give cover without coord sys info,
// which is then treated as global panel.
return panelId != null ? panels[panelId] : true;
}
function clearCovers(controller) {
var covers = controller._covers;
var originalLength = covers.length;
each(covers, function (cover) {
controller.group.remove(cover);
}, controller);
covers.length = 0;
return !!originalLength;
}
function trigger(controller, opt) {
var areas = map(controller._covers, function (cover) {
var brushOption = cover.__brushOption;
var range = zrUtil.clone(brushOption.range);
return {
brushType: brushOption.brushType,
panelId: brushOption.panelId,
range: range
};
});
controller.trigger('brush', areas, {
isEnd: !!opt.isEnd,
removeOnClick: !!opt.removeOnClick
});
}
function shouldShowCover(controller) {
var track = controller._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 getTrackEnds(track) {
var tail = track.length - 1;
tail < 0 && (tail = 0);
return [track[0], track[tail]];
}
function createBaseRectCover(doDrift, controller, brushOption, edgeNames) {
var cover = new graphic.Group();
cover.add(new graphic.Rect({
name: 'main',
style: makeStyle(brushOption),
silent: true,
draggable: true,
cursor: 'move',
drift: curry(doDrift, controller, cover, 'nswe'),
ondragend: curry(trigger, controller, {isEnd: true})
}));
each(
edgeNames,
function (name) {
cover.add(new graphic.Rect({
name: name,
style: {opacity: 0},
draggable: true,
silent: true,
invisible: true,
drift: curry(doDrift, controller, cover, name),
ondragend: curry(trigger, controller, {isEnd: true})
}));
}
);
return cover;
}
function updateBaseRect(controller, cover, localRange, brushOption) {
var lineWidth = brushOption.brushStyle.lineWidth || 0;
var handleSize = mathMax(lineWidth, MIN_RESIZE_LINE_WIDTH);
var x = localRange[0][0];
var y = localRange[1][0];
var xa = x - lineWidth / 2;
var ya = y - lineWidth / 2;
var x2 = localRange[0][1];
var y2 = localRange[1][1];
var x2a = x2 - handleSize + lineWidth / 2;
var y2a = y2 - handleSize + lineWidth / 2;
var width = x2 - x;
var height = y2 - y;
var widtha = width + lineWidth;
var heighta = height + lineWidth;
updateRectShape(controller, cover, 'main', x, y, width, height);
if (brushOption.transformable) {
updateRectShape(controller, cover, 'w', xa, ya, handleSize, heighta);
updateRectShape(controller, cover, 'e', x2a, ya, handleSize, heighta);
updateRectShape(controller, cover, 'n', xa, ya, widtha, handleSize);
updateRectShape(controller, cover, 's', xa, y2a, widtha, handleSize);
updateRectShape(controller, cover, 'nw', xa, ya, handleSize, handleSize);
updateRectShape(controller, cover, 'ne', x2a, ya, handleSize, handleSize);
updateRectShape(controller, cover, 'sw', xa, y2a, handleSize, handleSize);
updateRectShape(controller, cover, 'se', x2a, y2a, handleSize, handleSize);
}
}
function updateCommon(controller, cover) {
var brushOption = cover.__brushOption;
var transformable = brushOption.transformable;
var mainEl = cover.childAt(0);
mainEl.useStyle(makeStyle(brushOption));
mainEl.attr({
silent: !transformable,
cursor: transformable ? 'move' : 'default'
});
each(
['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw'],
function (name) {
var el = cover.childOfName(name);
var globalDir = getGlobalDirection(controller, name);
el && el.attr({
silent: !transformable,
invisible: !transformable,
cursor: transformable ? CURSOR_MAP[globalDir] + '-resize' : null
});
}
);
}
function updateRectShape(controller, cover, name, x, y, w, h) {
var el = cover.childOfName(name);
el && el.setShape(pointsToRect(
clipByPanel(controller, cover, [[x, y], [x + w, y + h]])
));
}
function makeStyle(brushOption) {
return zrUtil.defaults({strokeNoScale: true}, brushOption.brushStyle);
}
function formatRectRange(x, y, x2, y2) {
var min = [mathMin(x, x2), mathMin(y, y2)];
var max = [mathMax(x, x2), mathMax(y, y2)];
return [
[min[0], max[0]], // x range
[min[1], max[1]] // y range
];
}
function getTransform(controller) {
return graphic.getTransform(controller.group);
}
function getGlobalDirection(controller, localDirection) {
if (localDirection.length > 1) {
localDirection = localDirection.split('');
var globalDir = [
getGlobalDirection(controller, localDirection[0]),
getGlobalDirection(controller, localDirection[1])
];
(globalDir[0] === 'e' || globalDir[0] === 'w') && globalDir.reverse();
return globalDir.join('');
}
else {
var map = {w: 'left', e: 'right', n: 'top', s: 'bottom'};
var inverseMap = {left: 'w', right: 'e', top: 'n', bottom: 's'};
var globalDir = graphic.transformDirection(
map[localDirection], getTransform(controller)
);
return inverseMap[globalDir];
}
}
function driftRect(toRectRange, fromRectRange, controller, cover, name, dx, dy, e) {
var brushOption = cover.__brushOption;
var rectRange = toRectRange(brushOption.range);
var localDelta = toLocalDelta(controller, dx, dy);
each(name.split(''), function (namePart) {
var ind = DIRECTION_MAP[namePart];
rectRange[ind[0]][ind[1]] += localDelta[ind[0]];
});
brushOption.range = fromRectRange(formatRectRange(
rectRange[0][0], rectRange[1][0], rectRange[0][1], rectRange[1][1]
));
updateCoverAfterCreation(controller, cover);
trigger(controller, {isEnd: false});
}
function driftPolygon(controller, cover, dx, dy, e) {
var range = cover.__brushOption.range;
var localDelta = toLocalDelta(controller, dx, dy);
each(range, function (point) {
point[0] += localDelta[0];
point[1] += localDelta[1];
});
updateCoverAfterCreation(controller, cover);
trigger(controller, {isEnd: false});
}
function toLocalDelta(controller, dx, dy) {
var thisGroup = controller.group;
var localD = thisGroup.transformCoordToLocal(dx, dy);
var localZero = thisGroup.transformCoordToLocal(0, 0);
return [localD[0] - localZero[0], localD[1] - localZero[1]];
}
function clipByPanel(controller, cover, data) {
var panel = getPanelByCover(controller, cover);
if (panel === true) { // Global panel
return zrUtil.clone(data);
}
var panelRect = panel.getBoundingRect();
return zrUtil.map(data, function (point) {
var x = point[0];
x = mathMax(x, panelRect.x);
x = mathMin(x, panelRect.x + panelRect.width);
var y = point[1];
y = mathMax(y, panelRect.y);
y = mathMin(y, panelRect.y + panelRect.height);
return [x, y];
});
}
function pointsToRect(points) {
var xmin = mathMin(points[0][0], points[1][0]);
var ymin = mathMin(points[0][1], points[1][1]);
var xmax = mathMax(points[0][0], points[1][0]);
var ymax = mathMax(points[0][1], points[1][1]);
return {
x: xmin,
y: ymin,
width: xmax - xmin,
height: ymax - ymin
};
}
function resetCursor(controller, e) {
var x = e.offsetX;
var y = e.offsetY;
var zr = controller._zr;
if (controller._brushType) { // If active
var panels = controller._panels;
var covers = controller._covers;
var inCover;
for (var i = 0; i < covers.length; i++) {
if (coverRenderers[covers[i].__brushOption.brushType].contain(covers[i], x, y)) {
inCover = true;
break;
}
}
if (!inCover) {
if (panels) { // Brush on panels
each(panels, function (panel) {
panel.contain(x, y) && zr.setCursorStyle('crosshair');
});
}
else { // Global brush
zr.setCursorStyle('crosshair');
}
}
}
}
function preventDefault(e) {
var rawE = e.event;
rawE.preventDefault && rawE.preventDefault();
}
function mainShapeContain(cover, x, y) {
return cover.childOfName('main').contain(x, y);
}
function updateCoverByMouse(controller, e, isEnd) {
var x = e.offsetX;
var y = e.offsetY;
var creatingCover = controller._creatingCover;
var panel = controller._creatingPanel;
var thisBrushOption = controller._brushOption;
var eventParams;
controller._track.push(controller.group.transformCoordToLocal(x, y));
if (shouldShowCover(controller) || creatingCover) {
if (panel && !creatingCover) {
thisBrushOption.brushMode === 'single' && clearCovers(controller);
var brushOption = zrUtil.clone(thisBrushOption);
brushOption.panelId = panel === true ? null : panel.__brushPanelId;
creatingCover = controller._creatingCover = createCover(controller, brushOption);
controller._covers.push(creatingCover);
}
if (creatingCover) {
var coverRenderer = coverRenderers[controller._brushType];
var coverBrushOption = creatingCover.__brushOption;
coverBrushOption.range = coverRenderer.getCreatingRange(
clipByPanel(controller, creatingCover, controller._track)
);
if (isEnd) {
endCreating(controller, creatingCover);
coverRenderer.updateCommon(controller, creatingCover);
}
updateCoverShape(controller, creatingCover);
eventParams = {isEnd: isEnd};
}
}
else if (
isEnd
&& thisBrushOption.brushMode === 'single'
&& thisBrushOption.removeOnClick
) {
// Help user to remove covers easily, only by a tiny drag, in 'single' mode.
// But a single click do not clear covers, because user may have casual
// clicks (for example, click on other component and do not expect covers
// disappear).
// Only some cover removed, trigger action, but not every click trigger action.
if (getPanelByPoint(controller, x, y) && clearCovers(controller)) {
eventParams = {isEnd: isEnd, removeOnClick: true};
}
}
return eventParams;
}
var mouseHandlers = {
mousedown: function (e) {
if (this._dragging) {
// In case some browser do not support globalOut,
// and release mose out side the browser.
handleDragEnd.call(this, e);
}
else if (!e.target || !e.target.draggable) {
preventDefault(e);
var x = e.offsetX;
var y = e.offsetY;
this._creatingCover = null;
var panel = this._creatingPanel = getPanelByPoint(this, x, y);
if (panel) {
this._dragging = true;
this._track = [this.group.transformCoordToLocal(x, y)];
}
}
},
mousemove: function (e) {
// set Cursor
resetCursor(this, e);
if (this._dragging) {
preventDefault(e);
var eventParams = updateCoverByMouse(this, e, false);
eventParams && trigger(this, eventParams);
}
},
mouseup: handleDragEnd //,
// FIXME
// in tooltip, globalout should not be triggered.
// globalout: handleDragEnd
};
function handleDragEnd(e) {
if (this._dragging) {
preventDefault(e);
var eventParams = updateCoverByMouse(this, e, true);
this._dragging = false;
this._track = [];
this._creatingCover = null;
// trigger event shoule be at final, after procedure will be nested.
eventParams && trigger(this, eventParams);
}
}
/**
* key: brushType
* @type {Object}
*/
var coverRenderers = {
lineX: getLineRenderer(0),
lineY: getLineRenderer(1),
rect: {
createCover: function (controller, brushOption) {
return createBaseRectCover(
curry(
driftRect,
function (range) {
return range;
},
function (range) {
return range;
}
),
controller,
brushOption,
['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw']
);
},
getCreatingRange: function (localTrack) {
var ends = getTrackEnds(localTrack);
return formatRectRange(ends[1][0], ends[1][1], ends[0][0], ends[0][1]);
},
updateCoverShape: function (controller, cover, localRange, brushOption) {
updateBaseRect(controller, cover, localRange, brushOption);
},
updateCommon: updateCommon,
contain: mainShapeContain
},
polygon: {
createCover: function (controller, brushOption) {
var cover = new graphic.Group();
// Do not use graphic.Polygon because graphic.Polyline do not close the
// border of the shape when drawing, which is a better experience for user.
cover.add(new graphic.Polyline({
name: 'main',
style: makeStyle(brushOption),
silent: true
}));
return cover;
},
getCreatingRange: function (localTrack) {
return localTrack;
},
endCreating: function (controller, cover) {
cover.remove(cover.childAt(0));
// Use graphic.Polygon close the shape.
cover.add(new graphic.Polygon({
name: 'main',
draggable: true,
drift: curry(driftPolygon, controller, cover),
ondragend: curry(trigger, controller, {isEnd: true})
}));
},
updateCoverShape: function (controller, cover, localRange, brushOption) {
cover.childAt(0).setShape({
points: clipByPanel(controller, cover, localRange)
});
},
updateCommon: updateCommon,
contain: mainShapeContain
}
};
function getLineRenderer(xyIndex) {
return {
createCover: function (controller, brushOption) {
return createBaseRectCover(
curry(
driftRect,
function (range) {
var rectRange = [range, [0, 100]];
xyIndex && rectRange.reverse();
return rectRange;
},
function (rectRange) {
return rectRange[xyIndex];
}
),
controller,
brushOption,
[['w', 'e'], ['n', 's']][xyIndex]
);
},
getCreatingRange: function (localTrack) {
var ends = getTrackEnds(localTrack);
var min = mathMin(ends[0][xyIndex], ends[1][xyIndex]);
var max = mathMax(ends[0][xyIndex], ends[1][xyIndex]);
return [min, max];
},
updateCoverShape: function (controller, cover, localRange, brushOption) {
var brushWidth = brushOption.brushStyle.width;
var otherExtent;
// If brushWidth not specified, fit the panel.
if (brushWidth == null) {
var panel = getPanelByCover(controller, cover);
var base = 0;
if (panel !== true) {
var rect = panel.getBoundingRect();
brushWidth = xyIndex ? rect.width : rect.height;
base = xyIndex ? rect.x : rect.y;
}
// FIXME
// do not support global panel yet.
otherExtent = [base, base + (brushWidth || 0)];
}
else {
otherExtent = [-brushWidth / 2, brushWidth / 2];
}
var rectRange = [localRange, otherExtent];
xyIndex && rectRange.reverse();
updateBaseRect(controller, cover, rectRange, brushOption);
},
updateCommon: updateCommon,
contain: mainShapeContain
};
}
return BrushController;
});