BaseAxisPointer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var zrUtil = require("zrender/lib/core/util");
  20. var clazzUtil = require("../../util/clazz");
  21. var graphic = require("../../util/graphic");
  22. var axisPointerModelHelper = require("./modelHelper");
  23. var eventTool = require("zrender/lib/core/event");
  24. var throttleUtil = require("../../util/throttle");
  25. var _model = require("../../util/model");
  26. var makeInner = _model.makeInner;
  27. /*
  28. * Licensed to the Apache Software Foundation (ASF) under one
  29. * or more contributor license agreements. See the NOTICE file
  30. * distributed with this work for additional information
  31. * regarding copyright ownership. The ASF licenses this file
  32. * to you under the Apache License, Version 2.0 (the
  33. * "License"); you may not use this file except in compliance
  34. * with the License. You may obtain a copy of the License at
  35. *
  36. * http://www.apache.org/licenses/LICENSE-2.0
  37. *
  38. * Unless required by applicable law or agreed to in writing,
  39. * software distributed under the License is distributed on an
  40. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  41. * KIND, either express or implied. See the License for the
  42. * specific language governing permissions and limitations
  43. * under the License.
  44. */
  45. var inner = makeInner();
  46. var clone = zrUtil.clone;
  47. var bind = zrUtil.bind;
  48. /**
  49. * Base axis pointer class in 2D.
  50. * Implemenents {module:echarts/component/axis/IAxisPointer}.
  51. */
  52. function BaseAxisPointer() {}
  53. BaseAxisPointer.prototype = {
  54. /**
  55. * @private
  56. */
  57. _group: null,
  58. /**
  59. * @private
  60. */
  61. _lastGraphicKey: null,
  62. /**
  63. * @private
  64. */
  65. _handle: null,
  66. /**
  67. * @private
  68. */
  69. _dragging: false,
  70. /**
  71. * @private
  72. */
  73. _lastValue: null,
  74. /**
  75. * @private
  76. */
  77. _lastStatus: null,
  78. /**
  79. * @private
  80. */
  81. _payloadInfo: null,
  82. /**
  83. * In px, arbitrary value. Do not set too small,
  84. * no animation is ok for most cases.
  85. * @protected
  86. */
  87. animationThreshold: 15,
  88. /**
  89. * @implement
  90. */
  91. render: function (axisModel, axisPointerModel, api, forceRender) {
  92. var value = axisPointerModel.get('value');
  93. var status = axisPointerModel.get('status'); // Bind them to `this`, not in closure, otherwise they will not
  94. // be replaced when user calling setOption in not merge mode.
  95. this._axisModel = axisModel;
  96. this._axisPointerModel = axisPointerModel;
  97. this._api = api; // Optimize: `render` will be called repeatly during mouse move.
  98. // So it is power consuming if performing `render` each time,
  99. // especially on mobile device.
  100. if (!forceRender && this._lastValue === value && this._lastStatus === status) {
  101. return;
  102. }
  103. this._lastValue = value;
  104. this._lastStatus = status;
  105. var group = this._group;
  106. var handle = this._handle;
  107. if (!status || status === 'hide') {
  108. // Do not clear here, for animation better.
  109. group && group.hide();
  110. handle && handle.hide();
  111. return;
  112. }
  113. group && group.show();
  114. handle && handle.show(); // Otherwise status is 'show'
  115. var elOption = {};
  116. this.makeElOption(elOption, value, axisModel, axisPointerModel, api); // Enable change axis pointer type.
  117. var graphicKey = elOption.graphicKey;
  118. if (graphicKey !== this._lastGraphicKey) {
  119. this.clear(api);
  120. }
  121. this._lastGraphicKey = graphicKey;
  122. var moveAnimation = this._moveAnimation = this.determineAnimation(axisModel, axisPointerModel);
  123. if (!group) {
  124. group = this._group = new graphic.Group();
  125. this.createPointerEl(group, elOption, axisModel, axisPointerModel);
  126. this.createLabelEl(group, elOption, axisModel, axisPointerModel);
  127. api.getZr().add(group);
  128. } else {
  129. var doUpdateProps = zrUtil.curry(updateProps, axisPointerModel, moveAnimation);
  130. this.updatePointerEl(group, elOption, doUpdateProps, axisPointerModel);
  131. this.updateLabelEl(group, elOption, doUpdateProps, axisPointerModel);
  132. }
  133. updateMandatoryProps(group, axisPointerModel, true);
  134. this._renderHandle(value);
  135. },
  136. /**
  137. * @implement
  138. */
  139. remove: function (api) {
  140. this.clear(api);
  141. },
  142. /**
  143. * @implement
  144. */
  145. dispose: function (api) {
  146. this.clear(api);
  147. },
  148. /**
  149. * @protected
  150. */
  151. determineAnimation: function (axisModel, axisPointerModel) {
  152. var animation = axisPointerModel.get('animation');
  153. var axis = axisModel.axis;
  154. var isCategoryAxis = axis.type === 'category';
  155. var useSnap = axisPointerModel.get('snap'); // Value axis without snap always do not snap.
  156. if (!useSnap && !isCategoryAxis) {
  157. return false;
  158. }
  159. if (animation === 'auto' || animation == null) {
  160. var animationThreshold = this.animationThreshold;
  161. if (isCategoryAxis && axis.getBandWidth() > animationThreshold) {
  162. return true;
  163. } // It is important to auto animation when snap used. Consider if there is
  164. // a dataZoom, animation will be disabled when too many points exist, while
  165. // it will be enabled for better visual effect when little points exist.
  166. if (useSnap) {
  167. var seriesDataCount = axisPointerModelHelper.getAxisInfo(axisModel).seriesDataCount;
  168. var axisExtent = axis.getExtent(); // Approximate band width
  169. return Math.abs(axisExtent[0] - axisExtent[1]) / seriesDataCount > animationThreshold;
  170. }
  171. return false;
  172. }
  173. return animation === true;
  174. },
  175. /**
  176. * add {pointer, label, graphicKey} to elOption
  177. * @protected
  178. */
  179. makeElOption: function (elOption, value, axisModel, axisPointerModel, api) {// Shoule be implemenented by sub-class.
  180. },
  181. /**
  182. * @protected
  183. */
  184. createPointerEl: function (group, elOption, axisModel, axisPointerModel) {
  185. var pointerOption = elOption.pointer;
  186. if (pointerOption) {
  187. var pointerEl = inner(group).pointerEl = new graphic[pointerOption.type](clone(elOption.pointer));
  188. group.add(pointerEl);
  189. }
  190. },
  191. /**
  192. * @protected
  193. */
  194. createLabelEl: function (group, elOption, axisModel, axisPointerModel) {
  195. if (elOption.label) {
  196. var labelEl = inner(group).labelEl = new graphic.Rect(clone(elOption.label));
  197. group.add(labelEl);
  198. updateLabelShowHide(labelEl, axisPointerModel);
  199. }
  200. },
  201. /**
  202. * @protected
  203. */
  204. updatePointerEl: function (group, elOption, updateProps) {
  205. var pointerEl = inner(group).pointerEl;
  206. if (pointerEl && elOption.pointer) {
  207. pointerEl.setStyle(elOption.pointer.style);
  208. updateProps(pointerEl, {
  209. shape: elOption.pointer.shape
  210. });
  211. }
  212. },
  213. /**
  214. * @protected
  215. */
  216. updateLabelEl: function (group, elOption, updateProps, axisPointerModel) {
  217. var labelEl = inner(group).labelEl;
  218. if (labelEl) {
  219. labelEl.setStyle(elOption.label.style);
  220. updateProps(labelEl, {
  221. // Consider text length change in vertical axis, animation should
  222. // be used on shape, otherwise the effect will be weird.
  223. shape: elOption.label.shape,
  224. position: elOption.label.position
  225. });
  226. updateLabelShowHide(labelEl, axisPointerModel);
  227. }
  228. },
  229. /**
  230. * @private
  231. */
  232. _renderHandle: function (value) {
  233. if (this._dragging || !this.updateHandleTransform) {
  234. return;
  235. }
  236. var axisPointerModel = this._axisPointerModel;
  237. var zr = this._api.getZr();
  238. var handle = this._handle;
  239. var handleModel = axisPointerModel.getModel('handle');
  240. var status = axisPointerModel.get('status');
  241. if (!handleModel.get('show') || !status || status === 'hide') {
  242. handle && zr.remove(handle);
  243. this._handle = null;
  244. return;
  245. }
  246. var isInit;
  247. if (!this._handle) {
  248. isInit = true;
  249. handle = this._handle = graphic.createIcon(handleModel.get('icon'), {
  250. cursor: 'move',
  251. draggable: true,
  252. onmousemove: function (e) {
  253. // Fot mobile devicem, prevent screen slider on the button.
  254. eventTool.stop(e.event);
  255. },
  256. onmousedown: bind(this._onHandleDragMove, this, 0, 0),
  257. drift: bind(this._onHandleDragMove, this),
  258. ondragend: bind(this._onHandleDragEnd, this)
  259. });
  260. zr.add(handle);
  261. }
  262. updateMandatoryProps(handle, axisPointerModel, false); // update style
  263. var includeStyles = ['color', 'borderColor', 'borderWidth', 'opacity', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
  264. handle.setStyle(handleModel.getItemStyle(null, includeStyles)); // update position
  265. var handleSize = handleModel.get('size');
  266. if (!zrUtil.isArray(handleSize)) {
  267. handleSize = [handleSize, handleSize];
  268. }
  269. handle.attr('scale', [handleSize[0] / 2, handleSize[1] / 2]);
  270. throttleUtil.createOrUpdate(this, '_doDispatchAxisPointer', handleModel.get('throttle') || 0, 'fixRate');
  271. this._moveHandleToValue(value, isInit);
  272. },
  273. /**
  274. * @private
  275. */
  276. _moveHandleToValue: function (value, isInit) {
  277. updateProps(this._axisPointerModel, !isInit && this._moveAnimation, this._handle, getHandleTransProps(this.getHandleTransform(value, this._axisModel, this._axisPointerModel)));
  278. },
  279. /**
  280. * @private
  281. */
  282. _onHandleDragMove: function (dx, dy) {
  283. var handle = this._handle;
  284. if (!handle) {
  285. return;
  286. }
  287. this._dragging = true; // Persistent for throttle.
  288. var trans = this.updateHandleTransform(getHandleTransProps(handle), [dx, dy], this._axisModel, this._axisPointerModel);
  289. this._payloadInfo = trans;
  290. handle.stopAnimation();
  291. handle.attr(getHandleTransProps(trans));
  292. inner(handle).lastProp = null;
  293. this._doDispatchAxisPointer();
  294. },
  295. /**
  296. * Throttled method.
  297. * @private
  298. */
  299. _doDispatchAxisPointer: function () {
  300. var handle = this._handle;
  301. if (!handle) {
  302. return;
  303. }
  304. var payloadInfo = this._payloadInfo;
  305. var axisModel = this._axisModel;
  306. this._api.dispatchAction({
  307. type: 'updateAxisPointer',
  308. x: payloadInfo.cursorPoint[0],
  309. y: payloadInfo.cursorPoint[1],
  310. tooltipOption: payloadInfo.tooltipOption,
  311. axesInfo: [{
  312. axisDim: axisModel.axis.dim,
  313. axisIndex: axisModel.componentIndex
  314. }]
  315. });
  316. },
  317. /**
  318. * @private
  319. */
  320. _onHandleDragEnd: function (moveAnimation) {
  321. this._dragging = false;
  322. var handle = this._handle;
  323. if (!handle) {
  324. return;
  325. }
  326. var value = this._axisPointerModel.get('value'); // Consider snap or categroy axis, handle may be not consistent with
  327. // axisPointer. So move handle to align the exact value position when
  328. // drag ended.
  329. this._moveHandleToValue(value); // For the effect: tooltip will be shown when finger holding on handle
  330. // button, and will be hidden after finger left handle button.
  331. this._api.dispatchAction({
  332. type: 'hideTip'
  333. });
  334. },
  335. /**
  336. * Should be implemenented by sub-class if support `handle`.
  337. * @protected
  338. * @param {number} value
  339. * @param {module:echarts/model/Model} axisModel
  340. * @param {module:echarts/model/Model} axisPointerModel
  341. * @return {Object} {position: [x, y], rotation: 0}
  342. */
  343. getHandleTransform: null,
  344. /**
  345. * * Should be implemenented by sub-class if support `handle`.
  346. * @protected
  347. * @param {Object} transform {position, rotation}
  348. * @param {Array.<number>} delta [dx, dy]
  349. * @param {module:echarts/model/Model} axisModel
  350. * @param {module:echarts/model/Model} axisPointerModel
  351. * @return {Object} {position: [x, y], rotation: 0, cursorPoint: [x, y]}
  352. */
  353. updateHandleTransform: null,
  354. /**
  355. * @private
  356. */
  357. clear: function (api) {
  358. this._lastValue = null;
  359. this._lastStatus = null;
  360. var zr = api.getZr();
  361. var group = this._group;
  362. var handle = this._handle;
  363. if (zr && group) {
  364. this._lastGraphicKey = null;
  365. group && zr.remove(group);
  366. handle && zr.remove(handle);
  367. this._group = null;
  368. this._handle = null;
  369. this._payloadInfo = null;
  370. }
  371. },
  372. /**
  373. * @protected
  374. */
  375. doClear: function () {// Implemented by sub-class if necessary.
  376. },
  377. /**
  378. * @protected
  379. * @param {Array.<number>} xy
  380. * @param {Array.<number>} wh
  381. * @param {number} [xDimIndex=0] or 1
  382. */
  383. buildLabel: function (xy, wh, xDimIndex) {
  384. xDimIndex = xDimIndex || 0;
  385. return {
  386. x: xy[xDimIndex],
  387. y: xy[1 - xDimIndex],
  388. width: wh[xDimIndex],
  389. height: wh[1 - xDimIndex]
  390. };
  391. }
  392. };
  393. BaseAxisPointer.prototype.constructor = BaseAxisPointer;
  394. function updateProps(animationModel, moveAnimation, el, props) {
  395. // Animation optimize.
  396. if (!propsEqual(inner(el).lastProp, props)) {
  397. inner(el).lastProp = props;
  398. moveAnimation ? graphic.updateProps(el, props, animationModel) : (el.stopAnimation(), el.attr(props));
  399. }
  400. }
  401. function propsEqual(lastProps, newProps) {
  402. if (zrUtil.isObject(lastProps) && zrUtil.isObject(newProps)) {
  403. var equals = true;
  404. zrUtil.each(newProps, function (item, key) {
  405. equals = equals && propsEqual(lastProps[key], item);
  406. });
  407. return !!equals;
  408. } else {
  409. return lastProps === newProps;
  410. }
  411. }
  412. function updateLabelShowHide(labelEl, axisPointerModel) {
  413. labelEl[axisPointerModel.get('label.show') ? 'show' : 'hide']();
  414. }
  415. function getHandleTransProps(trans) {
  416. return {
  417. position: trans.position.slice(),
  418. rotation: trans.rotation || 0
  419. };
  420. }
  421. function updateMandatoryProps(group, axisPointerModel, silent) {
  422. var z = axisPointerModel.get('z');
  423. var zlevel = axisPointerModel.get('zlevel');
  424. group && group.traverse(function (el) {
  425. if (el.type !== 'group') {
  426. z != null && (el.z = z);
  427. zlevel != null && (el.zlevel = zlevel);
  428. el.silent = silent;
  429. }
  430. });
  431. }
  432. clazzUtil.enableClassExtend(BaseAxisPointer);
  433. var _default = BaseAxisPointer;
  434. module.exports = _default;