527 lines
14 KiB
JavaScript
527 lines
14 KiB
JavaScript
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
var zrUtil = require("zrender/lib/core/util");
|
||
|
|
||
|
var clazzUtil = require("../../util/clazz");
|
||
|
|
||
|
var graphic = require("../../util/graphic");
|
||
|
|
||
|
var axisPointerModelHelper = require("./modelHelper");
|
||
|
|
||
|
var eventTool = require("zrender/lib/core/event");
|
||
|
|
||
|
var throttleUtil = require("../../util/throttle");
|
||
|
|
||
|
var _model = require("../../util/model");
|
||
|
|
||
|
var makeInner = _model.makeInner;
|
||
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
var inner = makeInner();
|
||
|
var clone = zrUtil.clone;
|
||
|
var bind = zrUtil.bind;
|
||
|
/**
|
||
|
* Base axis pointer class in 2D.
|
||
|
* Implemenents {module:echarts/component/axis/IAxisPointer}.
|
||
|
*/
|
||
|
|
||
|
function BaseAxisPointer() {}
|
||
|
|
||
|
BaseAxisPointer.prototype = {
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_group: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_lastGraphicKey: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_handle: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_dragging: false,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_lastValue: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_lastStatus: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_payloadInfo: null,
|
||
|
|
||
|
/**
|
||
|
* In px, arbitrary value. Do not set too small,
|
||
|
* no animation is ok for most cases.
|
||
|
* @protected
|
||
|
*/
|
||
|
animationThreshold: 15,
|
||
|
|
||
|
/**
|
||
|
* @implement
|
||
|
*/
|
||
|
render: function (axisModel, axisPointerModel, api, forceRender) {
|
||
|
var value = axisPointerModel.get('value');
|
||
|
var status = axisPointerModel.get('status'); // Bind them to `this`, not in closure, otherwise they will not
|
||
|
// be replaced when user calling setOption in not merge mode.
|
||
|
|
||
|
this._axisModel = axisModel;
|
||
|
this._axisPointerModel = axisPointerModel;
|
||
|
this._api = api; // Optimize: `render` will be called repeatly during mouse move.
|
||
|
// So it is power consuming if performing `render` each time,
|
||
|
// especially on mobile device.
|
||
|
|
||
|
if (!forceRender && this._lastValue === value && this._lastStatus === status) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this._lastValue = value;
|
||
|
this._lastStatus = status;
|
||
|
var group = this._group;
|
||
|
var handle = this._handle;
|
||
|
|
||
|
if (!status || status === 'hide') {
|
||
|
// Do not clear here, for animation better.
|
||
|
group && group.hide();
|
||
|
handle && handle.hide();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
group && group.show();
|
||
|
handle && handle.show(); // Otherwise status is 'show'
|
||
|
|
||
|
var elOption = {};
|
||
|
this.makeElOption(elOption, value, axisModel, axisPointerModel, api); // Enable change axis pointer type.
|
||
|
|
||
|
var graphicKey = elOption.graphicKey;
|
||
|
|
||
|
if (graphicKey !== this._lastGraphicKey) {
|
||
|
this.clear(api);
|
||
|
}
|
||
|
|
||
|
this._lastGraphicKey = graphicKey;
|
||
|
var moveAnimation = this._moveAnimation = this.determineAnimation(axisModel, axisPointerModel);
|
||
|
|
||
|
if (!group) {
|
||
|
group = this._group = new graphic.Group();
|
||
|
this.createPointerEl(group, elOption, axisModel, axisPointerModel);
|
||
|
this.createLabelEl(group, elOption, axisModel, axisPointerModel);
|
||
|
api.getZr().add(group);
|
||
|
} else {
|
||
|
var doUpdateProps = zrUtil.curry(updateProps, axisPointerModel, moveAnimation);
|
||
|
this.updatePointerEl(group, elOption, doUpdateProps, axisPointerModel);
|
||
|
this.updateLabelEl(group, elOption, doUpdateProps, axisPointerModel);
|
||
|
}
|
||
|
|
||
|
updateMandatoryProps(group, axisPointerModel, true);
|
||
|
|
||
|
this._renderHandle(value);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @implement
|
||
|
*/
|
||
|
remove: function (api) {
|
||
|
this.clear(api);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @implement
|
||
|
*/
|
||
|
dispose: function (api) {
|
||
|
this.clear(api);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
determineAnimation: function (axisModel, axisPointerModel) {
|
||
|
var animation = axisPointerModel.get('animation');
|
||
|
var axis = axisModel.axis;
|
||
|
var isCategoryAxis = axis.type === 'category';
|
||
|
var useSnap = axisPointerModel.get('snap'); // Value axis without snap always do not snap.
|
||
|
|
||
|
if (!useSnap && !isCategoryAxis) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (animation === 'auto' || animation == null) {
|
||
|
var animationThreshold = this.animationThreshold;
|
||
|
|
||
|
if (isCategoryAxis && axis.getBandWidth() > animationThreshold) {
|
||
|
return true;
|
||
|
} // It is important to auto animation when snap used. Consider if there is
|
||
|
// a dataZoom, animation will be disabled when too many points exist, while
|
||
|
// it will be enabled for better visual effect when little points exist.
|
||
|
|
||
|
|
||
|
if (useSnap) {
|
||
|
var seriesDataCount = axisPointerModelHelper.getAxisInfo(axisModel).seriesDataCount;
|
||
|
var axisExtent = axis.getExtent(); // Approximate band width
|
||
|
|
||
|
return Math.abs(axisExtent[0] - axisExtent[1]) / seriesDataCount > animationThreshold;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return animation === true;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* add {pointer, label, graphicKey} to elOption
|
||
|
* @protected
|
||
|
*/
|
||
|
makeElOption: function (elOption, value, axisModel, axisPointerModel, api) {// Shoule be implemenented by sub-class.
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
createPointerEl: function (group, elOption, axisModel, axisPointerModel) {
|
||
|
var pointerOption = elOption.pointer;
|
||
|
|
||
|
if (pointerOption) {
|
||
|
var pointerEl = inner(group).pointerEl = new graphic[pointerOption.type](clone(elOption.pointer));
|
||
|
group.add(pointerEl);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
createLabelEl: function (group, elOption, axisModel, axisPointerModel) {
|
||
|
if (elOption.label) {
|
||
|
var labelEl = inner(group).labelEl = new graphic.Rect(clone(elOption.label));
|
||
|
group.add(labelEl);
|
||
|
updateLabelShowHide(labelEl, axisPointerModel);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
updatePointerEl: function (group, elOption, updateProps) {
|
||
|
var pointerEl = inner(group).pointerEl;
|
||
|
|
||
|
if (pointerEl && elOption.pointer) {
|
||
|
pointerEl.setStyle(elOption.pointer.style);
|
||
|
updateProps(pointerEl, {
|
||
|
shape: elOption.pointer.shape
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
updateLabelEl: function (group, elOption, updateProps, axisPointerModel) {
|
||
|
var labelEl = inner(group).labelEl;
|
||
|
|
||
|
if (labelEl) {
|
||
|
labelEl.setStyle(elOption.label.style);
|
||
|
updateProps(labelEl, {
|
||
|
// Consider text length change in vertical axis, animation should
|
||
|
// be used on shape, otherwise the effect will be weird.
|
||
|
shape: elOption.label.shape,
|
||
|
position: elOption.label.position
|
||
|
});
|
||
|
updateLabelShowHide(labelEl, axisPointerModel);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_renderHandle: function (value) {
|
||
|
if (this._dragging || !this.updateHandleTransform) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var axisPointerModel = this._axisPointerModel;
|
||
|
|
||
|
var zr = this._api.getZr();
|
||
|
|
||
|
var handle = this._handle;
|
||
|
var handleModel = axisPointerModel.getModel('handle');
|
||
|
var status = axisPointerModel.get('status');
|
||
|
|
||
|
if (!handleModel.get('show') || !status || status === 'hide') {
|
||
|
handle && zr.remove(handle);
|
||
|
this._handle = null;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var isInit;
|
||
|
|
||
|
if (!this._handle) {
|
||
|
isInit = true;
|
||
|
handle = this._handle = graphic.createIcon(handleModel.get('icon'), {
|
||
|
cursor: 'move',
|
||
|
draggable: true,
|
||
|
onmousemove: function (e) {
|
||
|
// Fot mobile devicem, prevent screen slider on the button.
|
||
|
eventTool.stop(e.event);
|
||
|
},
|
||
|
onmousedown: bind(this._onHandleDragMove, this, 0, 0),
|
||
|
drift: bind(this._onHandleDragMove, this),
|
||
|
ondragend: bind(this._onHandleDragEnd, this)
|
||
|
});
|
||
|
zr.add(handle);
|
||
|
}
|
||
|
|
||
|
updateMandatoryProps(handle, axisPointerModel, false); // update style
|
||
|
|
||
|
var includeStyles = ['color', 'borderColor', 'borderWidth', 'opacity', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
|
||
|
handle.setStyle(handleModel.getItemStyle(null, includeStyles)); // update position
|
||
|
|
||
|
var handleSize = handleModel.get('size');
|
||
|
|
||
|
if (!zrUtil.isArray(handleSize)) {
|
||
|
handleSize = [handleSize, handleSize];
|
||
|
}
|
||
|
|
||
|
handle.attr('scale', [handleSize[0] / 2, handleSize[1] / 2]);
|
||
|
throttleUtil.createOrUpdate(this, '_doDispatchAxisPointer', handleModel.get('throttle') || 0, 'fixRate');
|
||
|
|
||
|
this._moveHandleToValue(value, isInit);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_moveHandleToValue: function (value, isInit) {
|
||
|
updateProps(this._axisPointerModel, !isInit && this._moveAnimation, this._handle, getHandleTransProps(this.getHandleTransform(value, this._axisModel, this._axisPointerModel)));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_onHandleDragMove: function (dx, dy) {
|
||
|
var handle = this._handle;
|
||
|
|
||
|
if (!handle) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this._dragging = true; // Persistent for throttle.
|
||
|
|
||
|
var trans = this.updateHandleTransform(getHandleTransProps(handle), [dx, dy], this._axisModel, this._axisPointerModel);
|
||
|
this._payloadInfo = trans;
|
||
|
handle.stopAnimation();
|
||
|
handle.attr(getHandleTransProps(trans));
|
||
|
inner(handle).lastProp = null;
|
||
|
|
||
|
this._doDispatchAxisPointer();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Throttled method.
|
||
|
* @private
|
||
|
*/
|
||
|
_doDispatchAxisPointer: function () {
|
||
|
var handle = this._handle;
|
||
|
|
||
|
if (!handle) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var payloadInfo = this._payloadInfo;
|
||
|
var axisModel = this._axisModel;
|
||
|
|
||
|
this._api.dispatchAction({
|
||
|
type: 'updateAxisPointer',
|
||
|
x: payloadInfo.cursorPoint[0],
|
||
|
y: payloadInfo.cursorPoint[1],
|
||
|
tooltipOption: payloadInfo.tooltipOption,
|
||
|
axesInfo: [{
|
||
|
axisDim: axisModel.axis.dim,
|
||
|
axisIndex: axisModel.componentIndex
|
||
|
}]
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
_onHandleDragEnd: function (moveAnimation) {
|
||
|
this._dragging = false;
|
||
|
var handle = this._handle;
|
||
|
|
||
|
if (!handle) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var value = this._axisPointerModel.get('value'); // Consider snap or categroy axis, handle may be not consistent with
|
||
|
// axisPointer. So move handle to align the exact value position when
|
||
|
// drag ended.
|
||
|
|
||
|
|
||
|
this._moveHandleToValue(value); // For the effect: tooltip will be shown when finger holding on handle
|
||
|
// button, and will be hidden after finger left handle button.
|
||
|
|
||
|
|
||
|
this._api.dispatchAction({
|
||
|
type: 'hideTip'
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Should be implemenented by sub-class if support `handle`.
|
||
|
* @protected
|
||
|
* @param {number} value
|
||
|
* @param {module:echarts/model/Model} axisModel
|
||
|
* @param {module:echarts/model/Model} axisPointerModel
|
||
|
* @return {Object} {position: [x, y], rotation: 0}
|
||
|
*/
|
||
|
getHandleTransform: null,
|
||
|
|
||
|
/**
|
||
|
* * Should be implemenented by sub-class if support `handle`.
|
||
|
* @protected
|
||
|
* @param {Object} transform {position, rotation}
|
||
|
* @param {Array.<number>} delta [dx, dy]
|
||
|
* @param {module:echarts/model/Model} axisModel
|
||
|
* @param {module:echarts/model/Model} axisPointerModel
|
||
|
* @return {Object} {position: [x, y], rotation: 0, cursorPoint: [x, y]}
|
||
|
*/
|
||
|
updateHandleTransform: null,
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
clear: function (api) {
|
||
|
this._lastValue = null;
|
||
|
this._lastStatus = null;
|
||
|
var zr = api.getZr();
|
||
|
var group = this._group;
|
||
|
var handle = this._handle;
|
||
|
|
||
|
if (zr && group) {
|
||
|
this._lastGraphicKey = null;
|
||
|
group && zr.remove(group);
|
||
|
handle && zr.remove(handle);
|
||
|
this._group = null;
|
||
|
this._handle = null;
|
||
|
this._payloadInfo = null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
*/
|
||
|
doClear: function () {// Implemented by sub-class if necessary.
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @protected
|
||
|
* @param {Array.<number>} xy
|
||
|
* @param {Array.<number>} wh
|
||
|
* @param {number} [xDimIndex=0] or 1
|
||
|
*/
|
||
|
buildLabel: function (xy, wh, xDimIndex) {
|
||
|
xDimIndex = xDimIndex || 0;
|
||
|
return {
|
||
|
x: xy[xDimIndex],
|
||
|
y: xy[1 - xDimIndex],
|
||
|
width: wh[xDimIndex],
|
||
|
height: wh[1 - xDimIndex]
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
BaseAxisPointer.prototype.constructor = BaseAxisPointer;
|
||
|
|
||
|
function updateProps(animationModel, moveAnimation, el, props) {
|
||
|
// Animation optimize.
|
||
|
if (!propsEqual(inner(el).lastProp, props)) {
|
||
|
inner(el).lastProp = props;
|
||
|
moveAnimation ? graphic.updateProps(el, props, animationModel) : (el.stopAnimation(), el.attr(props));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function propsEqual(lastProps, newProps) {
|
||
|
if (zrUtil.isObject(lastProps) && zrUtil.isObject(newProps)) {
|
||
|
var equals = true;
|
||
|
zrUtil.each(newProps, function (item, key) {
|
||
|
equals = equals && propsEqual(lastProps[key], item);
|
||
|
});
|
||
|
return !!equals;
|
||
|
} else {
|
||
|
return lastProps === newProps;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function updateLabelShowHide(labelEl, axisPointerModel) {
|
||
|
labelEl[axisPointerModel.get('label.show') ? 'show' : 'hide']();
|
||
|
}
|
||
|
|
||
|
function getHandleTransProps(trans) {
|
||
|
return {
|
||
|
position: trans.position.slice(),
|
||
|
rotation: trans.rotation || 0
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function updateMandatoryProps(group, axisPointerModel, silent) {
|
||
|
var z = axisPointerModel.get('z');
|
||
|
var zlevel = axisPointerModel.get('zlevel');
|
||
|
group && group.traverse(function (el) {
|
||
|
if (el.type !== 'group') {
|
||
|
z != null && (el.z = z);
|
||
|
zlevel != null && (el.zlevel = zlevel);
|
||
|
el.silent = silent;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
clazzUtil.enableClassExtend(BaseAxisPointer);
|
||
|
var _default = BaseAxisPointer;
|
||
|
module.exports = _default;
|