TooltipView.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  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. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. import { __extends } from "tslib";
  23. /*
  24. * Licensed to the Apache Software Foundation (ASF) under one
  25. * or more contributor license agreements. See the NOTICE file
  26. * distributed with this work for additional information
  27. * regarding copyright ownership. The ASF licenses this file
  28. * to you under the Apache License, Version 2.0 (the
  29. * "License"); you may not use this file except in compliance
  30. * with the License. You may obtain a copy of the License at
  31. *
  32. * http://www.apache.org/licenses/LICENSE-2.0
  33. *
  34. * Unless required by applicable law or agreed to in writing,
  35. * software distributed under the License is distributed on an
  36. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  37. * KIND, either express or implied. See the License for the
  38. * specific language governing permissions and limitations
  39. * under the License.
  40. */
  41. import * as zrUtil from 'zrender/lib/core/util';
  42. import env from 'zrender/lib/core/env';
  43. import TooltipHTMLContent from './TooltipHTMLContent';
  44. import TooltipRichContent from './TooltipRichContent';
  45. import * as formatUtil from '../../util/format';
  46. import * as numberUtil from '../../util/number';
  47. import * as graphic from '../../util/graphic';
  48. import findPointFromSeries from '../axisPointer/findPointFromSeries';
  49. import * as layoutUtil from '../../util/layout';
  50. import Model from '../../model/Model';
  51. import * as globalListener from '../axisPointer/globalListener';
  52. import * as axisHelper from '../../coord/axisHelper';
  53. import * as axisPointerViewHelper from '../axisPointer/viewHelper';
  54. import { getTooltipRenderMode, preParseFinder, queryReferringComponents } from '../../util/model';
  55. import ComponentView from '../../view/Component';
  56. import { format as timeFormat } from '../../util/time'; // import { isDimensionStacked } from '../../data/helper/dataStackHelper';
  57. import { getECData } from '../../util/innerStore';
  58. import { shouldTooltipConfine } from './helper';
  59. import { normalizeTooltipFormatResult } from '../../model/mixin/dataFormat';
  60. import { createTooltipMarkup, buildTooltipMarkup, TooltipMarkupStyleCreator } from './tooltipMarkup';
  61. import { findEventDispatcher } from '../../util/event';
  62. import { throttle } from '../../util/throttle';
  63. var bind = zrUtil.bind;
  64. var each = zrUtil.each;
  65. var parsePercent = numberUtil.parsePercent;
  66. var proxyRect = new graphic.Rect({
  67. shape: {
  68. x: -1,
  69. y: -1,
  70. width: 2,
  71. height: 2
  72. }
  73. });
  74. var TooltipView =
  75. /** @class */
  76. function (_super) {
  77. __extends(TooltipView, _super);
  78. function TooltipView() {
  79. var _this = _super !== null && _super.apply(this, arguments) || this;
  80. _this.type = TooltipView.type;
  81. return _this;
  82. }
  83. TooltipView.prototype.init = function (ecModel, api) {
  84. if (env.node) {
  85. return;
  86. }
  87. var tooltipModel = ecModel.getComponent('tooltip');
  88. var renderMode = tooltipModel.get('renderMode');
  89. this._renderMode = getTooltipRenderMode(renderMode);
  90. this._tooltipContent = this._renderMode === 'richText' ? new TooltipRichContent(api) : new TooltipHTMLContent(api.getDom(), api, {
  91. appendToBody: tooltipModel.get('appendToBody', true)
  92. });
  93. };
  94. TooltipView.prototype.render = function (tooltipModel, ecModel, api) {
  95. if (env.node) {
  96. return;
  97. } // Reset
  98. this.group.removeAll();
  99. this._tooltipModel = tooltipModel;
  100. this._ecModel = ecModel;
  101. this._api = api;
  102. /**
  103. * @private
  104. * @type {boolean}
  105. */
  106. this._alwaysShowContent = tooltipModel.get('alwaysShowContent');
  107. var tooltipContent = this._tooltipContent;
  108. tooltipContent.update(tooltipModel);
  109. tooltipContent.setEnterable(tooltipModel.get('enterable'));
  110. this._initGlobalListener();
  111. this._keepShow(); // PENDING
  112. // `mousemove` event will be triggered very frequently when the mouse moves fast,
  113. // which causes that the updatePosition was also called very frequently.
  114. // In Chrome with devtools open and Firefox, tooltip looks lagged and shaked around. See #14695.
  115. // To avoid the frequent triggering,
  116. // consider throttling it in 50ms. (the tested result may need to validate)
  117. this._updatePosition = this._renderMode === 'html' ? throttle(bind(this._doUpdatePosition, this), 50) : this._doUpdatePosition;
  118. };
  119. TooltipView.prototype._initGlobalListener = function () {
  120. var tooltipModel = this._tooltipModel;
  121. var triggerOn = tooltipModel.get('triggerOn');
  122. globalListener.register('itemTooltip', this._api, bind(function (currTrigger, e, dispatchAction) {
  123. // If 'none', it is not controlled by mouse totally.
  124. if (triggerOn !== 'none') {
  125. if (triggerOn.indexOf(currTrigger) >= 0) {
  126. this._tryShow(e, dispatchAction);
  127. } else if (currTrigger === 'leave') {
  128. this._hide(dispatchAction);
  129. }
  130. }
  131. }, this));
  132. };
  133. TooltipView.prototype._keepShow = function () {
  134. var tooltipModel = this._tooltipModel;
  135. var ecModel = this._ecModel;
  136. var api = this._api; // Try to keep the tooltip show when refreshing
  137. if (this._lastX != null && this._lastY != null // When user is willing to control tooltip totally using API,
  138. // self.manuallyShowTip({x, y}) might cause tooltip hide,
  139. // which is not expected.
  140. && tooltipModel.get('triggerOn') !== 'none') {
  141. var self_1 = this;
  142. clearTimeout(this._refreshUpdateTimeout);
  143. this._refreshUpdateTimeout = setTimeout(function () {
  144. // Show tip next tick after other charts are rendered
  145. // In case highlight action has wrong result
  146. // FIXME
  147. !api.isDisposed() && self_1.manuallyShowTip(tooltipModel, ecModel, api, {
  148. x: self_1._lastX,
  149. y: self_1._lastY,
  150. dataByCoordSys: self_1._lastDataByCoordSys
  151. });
  152. });
  153. }
  154. };
  155. /**
  156. * Show tip manually by
  157. * dispatchAction({
  158. * type: 'showTip',
  159. * x: 10,
  160. * y: 10
  161. * });
  162. * Or
  163. * dispatchAction({
  164. * type: 'showTip',
  165. * seriesIndex: 0,
  166. * dataIndex or dataIndexInside or name
  167. * });
  168. *
  169. * TODO Batch
  170. */
  171. TooltipView.prototype.manuallyShowTip = function (tooltipModel, ecModel, api, payload) {
  172. if (payload.from === this.uid || env.node) {
  173. return;
  174. }
  175. var dispatchAction = makeDispatchAction(payload, api); // Reset ticket
  176. this._ticket = ''; // When triggered from axisPointer.
  177. var dataByCoordSys = payload.dataByCoordSys;
  178. var cmptRef = findComponentReference(payload, ecModel, api);
  179. if (cmptRef) {
  180. var rect = cmptRef.el.getBoundingRect().clone();
  181. rect.applyTransform(cmptRef.el.transform);
  182. this._tryShow({
  183. offsetX: rect.x + rect.width / 2,
  184. offsetY: rect.y + rect.height / 2,
  185. target: cmptRef.el,
  186. position: payload.position,
  187. // When manully trigger, the mouse is not on the el, so we'd better to
  188. // position tooltip on the bottom of the el and display arrow is possible.
  189. positionDefault: 'bottom'
  190. }, dispatchAction);
  191. } else if (payload.tooltip && payload.x != null && payload.y != null) {
  192. var el = proxyRect;
  193. el.x = payload.x;
  194. el.y = payload.y;
  195. el.update();
  196. getECData(el).tooltipConfig = {
  197. name: null,
  198. option: payload.tooltip
  199. }; // Manually show tooltip while view is not using zrender elements.
  200. this._tryShow({
  201. offsetX: payload.x,
  202. offsetY: payload.y,
  203. target: el
  204. }, dispatchAction);
  205. } else if (dataByCoordSys) {
  206. this._tryShow({
  207. offsetX: payload.x,
  208. offsetY: payload.y,
  209. position: payload.position,
  210. dataByCoordSys: dataByCoordSys,
  211. tooltipOption: payload.tooltipOption
  212. }, dispatchAction);
  213. } else if (payload.seriesIndex != null) {
  214. if (this._manuallyAxisShowTip(tooltipModel, ecModel, api, payload)) {
  215. return;
  216. }
  217. var pointInfo = findPointFromSeries(payload, ecModel);
  218. var cx = pointInfo.point[0];
  219. var cy = pointInfo.point[1];
  220. if (cx != null && cy != null) {
  221. this._tryShow({
  222. offsetX: cx,
  223. offsetY: cy,
  224. target: pointInfo.el,
  225. position: payload.position,
  226. // When manully trigger, the mouse is not on the el, so we'd better to
  227. // position tooltip on the bottom of the el and display arrow is possible.
  228. positionDefault: 'bottom'
  229. }, dispatchAction);
  230. }
  231. } else if (payload.x != null && payload.y != null) {
  232. // FIXME
  233. // should wrap dispatchAction like `axisPointer/globalListener` ?
  234. api.dispatchAction({
  235. type: 'updateAxisPointer',
  236. x: payload.x,
  237. y: payload.y
  238. });
  239. this._tryShow({
  240. offsetX: payload.x,
  241. offsetY: payload.y,
  242. position: payload.position,
  243. target: api.getZr().findHover(payload.x, payload.y).target
  244. }, dispatchAction);
  245. }
  246. };
  247. TooltipView.prototype.manuallyHideTip = function (tooltipModel, ecModel, api, payload) {
  248. var tooltipContent = this._tooltipContent;
  249. if (!this._alwaysShowContent && this._tooltipModel) {
  250. tooltipContent.hideLater(this._tooltipModel.get('hideDelay'));
  251. }
  252. this._lastX = this._lastY = this._lastDataByCoordSys = null;
  253. if (payload.from !== this.uid) {
  254. this._hide(makeDispatchAction(payload, api));
  255. }
  256. }; // Be compatible with previous design, that is, when tooltip.type is 'axis' and
  257. // dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis pointer
  258. // and tooltip.
  259. TooltipView.prototype._manuallyAxisShowTip = function (tooltipModel, ecModel, api, payload) {
  260. var seriesIndex = payload.seriesIndex;
  261. var dataIndex = payload.dataIndex; // @ts-ignore
  262. var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;
  263. if (seriesIndex == null || dataIndex == null || coordSysAxesInfo == null) {
  264. return;
  265. }
  266. var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
  267. if (!seriesModel) {
  268. return;
  269. }
  270. var data = seriesModel.getData();
  271. var tooltipCascadedModel = buildTooltipModel([data.getItemModel(dataIndex), seriesModel, (seriesModel.coordinateSystem || {}).model], this._tooltipModel);
  272. if (tooltipCascadedModel.get('trigger') !== 'axis') {
  273. return;
  274. }
  275. api.dispatchAction({
  276. type: 'updateAxisPointer',
  277. seriesIndex: seriesIndex,
  278. dataIndex: dataIndex,
  279. position: payload.position
  280. });
  281. return true;
  282. };
  283. TooltipView.prototype._tryShow = function (e, dispatchAction) {
  284. var el = e.target;
  285. var tooltipModel = this._tooltipModel;
  286. if (!tooltipModel) {
  287. return;
  288. } // Save mouse x, mouse y. So we can try to keep showing the tip if chart is refreshed
  289. this._lastX = e.offsetX;
  290. this._lastY = e.offsetY;
  291. var dataByCoordSys = e.dataByCoordSys;
  292. if (dataByCoordSys && dataByCoordSys.length) {
  293. this._showAxisTooltip(dataByCoordSys, e);
  294. } else if (el) {
  295. this._lastDataByCoordSys = null;
  296. var seriesDispatcher_1;
  297. var cmptDispatcher_1;
  298. findEventDispatcher(el, function (target) {
  299. // Always show item tooltip if mouse is on the element with dataIndex
  300. if (getECData(target).dataIndex != null) {
  301. seriesDispatcher_1 = target;
  302. return true;
  303. } // Tooltip provided directly. Like legend.
  304. if (getECData(target).tooltipConfig != null) {
  305. cmptDispatcher_1 = target;
  306. return true;
  307. }
  308. }, true);
  309. if (seriesDispatcher_1) {
  310. this._showSeriesItemTooltip(e, seriesDispatcher_1, dispatchAction);
  311. } else if (cmptDispatcher_1) {
  312. this._showComponentItemTooltip(e, cmptDispatcher_1, dispatchAction);
  313. } else {
  314. this._hide(dispatchAction);
  315. }
  316. } else {
  317. this._lastDataByCoordSys = null;
  318. this._hide(dispatchAction);
  319. }
  320. };
  321. TooltipView.prototype._showOrMove = function (tooltipModel, cb) {
  322. // showDelay is used in this case: tooltip.enterable is set
  323. // as true. User intent to move mouse into tooltip and click
  324. // something. `showDelay` makes it easier to enter the content
  325. // but tooltip do not move immediately.
  326. var delay = tooltipModel.get('showDelay');
  327. cb = zrUtil.bind(cb, this);
  328. clearTimeout(this._showTimout);
  329. delay > 0 ? this._showTimout = setTimeout(cb, delay) : cb();
  330. };
  331. TooltipView.prototype._showAxisTooltip = function (dataByCoordSys, e) {
  332. var ecModel = this._ecModel;
  333. var globalTooltipModel = this._tooltipModel;
  334. var point = [e.offsetX, e.offsetY];
  335. var singleTooltipModel = buildTooltipModel([e.tooltipOption], globalTooltipModel);
  336. var renderMode = this._renderMode;
  337. var cbParamsList = [];
  338. var articleMarkup = createTooltipMarkup('section', {
  339. blocks: [],
  340. noHeader: true
  341. }); // Only for legacy: `Serise['formatTooltip']` returns a string.
  342. var markupTextArrLegacy = [];
  343. var markupStyleCreator = new TooltipMarkupStyleCreator();
  344. each(dataByCoordSys, function (itemCoordSys) {
  345. each(itemCoordSys.dataByAxis, function (axisItem) {
  346. var axisModel = ecModel.getComponent(axisItem.axisDim + 'Axis', axisItem.axisIndex);
  347. var axisValue = axisItem.value;
  348. if (!axisModel || axisValue == null) {
  349. return;
  350. }
  351. var axisValueLabel = axisPointerViewHelper.getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt);
  352. var axisSectionMarkup = createTooltipMarkup('section', {
  353. header: axisValueLabel,
  354. noHeader: !zrUtil.trim(axisValueLabel),
  355. sortBlocks: true,
  356. blocks: []
  357. });
  358. articleMarkup.blocks.push(axisSectionMarkup);
  359. zrUtil.each(axisItem.seriesDataIndices, function (idxItem) {
  360. var series = ecModel.getSeriesByIndex(idxItem.seriesIndex);
  361. var dataIndex = idxItem.dataIndexInside;
  362. var cbParams = series.getDataParams(dataIndex); // Can't find data.
  363. if (cbParams.dataIndex < 0) {
  364. return;
  365. }
  366. cbParams.axisDim = axisItem.axisDim;
  367. cbParams.axisIndex = axisItem.axisIndex;
  368. cbParams.axisType = axisItem.axisType;
  369. cbParams.axisId = axisItem.axisId;
  370. cbParams.axisValue = axisHelper.getAxisRawValue(axisModel.axis, {
  371. value: axisValue
  372. });
  373. cbParams.axisValueLabel = axisValueLabel; // Pre-create marker style for makers. Users can assemble richText
  374. // text in `formatter` callback and use those markers style.
  375. cbParams.marker = markupStyleCreator.makeTooltipMarker('item', formatUtil.convertToColorString(cbParams.color), renderMode);
  376. var seriesTooltipResult = normalizeTooltipFormatResult(series.formatTooltip(dataIndex, true, null));
  377. if (seriesTooltipResult.markupFragment) {
  378. axisSectionMarkup.blocks.push(seriesTooltipResult.markupFragment);
  379. }
  380. if (seriesTooltipResult.markupText) {
  381. markupTextArrLegacy.push(seriesTooltipResult.markupText);
  382. }
  383. cbParamsList.push(cbParams);
  384. });
  385. });
  386. }); // In most cases, the second axis is displays upper on the first one.
  387. // So we reverse it to look better.
  388. articleMarkup.blocks.reverse();
  389. markupTextArrLegacy.reverse();
  390. var positionExpr = e.position;
  391. var orderMode = singleTooltipModel.get('order');
  392. var builtMarkupText = buildTooltipMarkup(articleMarkup, markupStyleCreator, renderMode, orderMode, ecModel.get('useUTC'), singleTooltipModel.get('textStyle'));
  393. builtMarkupText && markupTextArrLegacy.unshift(builtMarkupText);
  394. var blockBreak = renderMode === 'richText' ? '\n\n' : '<br/>';
  395. var allMarkupText = markupTextArrLegacy.join(blockBreak);
  396. this._showOrMove(singleTooltipModel, function () {
  397. if (this._updateContentNotChangedOnAxis(dataByCoordSys, cbParamsList)) {
  398. this._updatePosition(singleTooltipModel, positionExpr, point[0], point[1], this._tooltipContent, cbParamsList);
  399. } else {
  400. this._showTooltipContent(singleTooltipModel, allMarkupText, cbParamsList, Math.random() + '', point[0], point[1], positionExpr, null, markupStyleCreator);
  401. }
  402. }); // Do not trigger events here, because this branch only be entered
  403. // from dispatchAction.
  404. };
  405. TooltipView.prototype._showSeriesItemTooltip = function (e, dispatcher, dispatchAction) {
  406. var ecModel = this._ecModel;
  407. var ecData = getECData(dispatcher); // Use dataModel in element if possible
  408. // Used when mouseover on a element like markPoint or edge
  409. // In which case, the data is not main data in series.
  410. var seriesIndex = ecData.seriesIndex;
  411. var seriesModel = ecModel.getSeriesByIndex(seriesIndex); // For example, graph link.
  412. var dataModel = ecData.dataModel || seriesModel;
  413. var dataIndex = ecData.dataIndex;
  414. var dataType = ecData.dataType;
  415. var data = dataModel.getData(dataType);
  416. var renderMode = this._renderMode;
  417. var positionDefault = e.positionDefault;
  418. var tooltipModel = buildTooltipModel([data.getItemModel(dataIndex), dataModel, seriesModel && (seriesModel.coordinateSystem || {}).model], this._tooltipModel, positionDefault ? {
  419. position: positionDefault
  420. } : null);
  421. var tooltipTrigger = tooltipModel.get('trigger');
  422. if (tooltipTrigger != null && tooltipTrigger !== 'item') {
  423. return;
  424. }
  425. var params = dataModel.getDataParams(dataIndex, dataType);
  426. var markupStyleCreator = new TooltipMarkupStyleCreator(); // Pre-create marker style for makers. Users can assemble richText
  427. // text in `formatter` callback and use those markers style.
  428. params.marker = markupStyleCreator.makeTooltipMarker('item', formatUtil.convertToColorString(params.color), renderMode);
  429. var seriesTooltipResult = normalizeTooltipFormatResult(dataModel.formatTooltip(dataIndex, false, dataType));
  430. var orderMode = tooltipModel.get('order');
  431. var markupText = seriesTooltipResult.markupFragment ? buildTooltipMarkup(seriesTooltipResult.markupFragment, markupStyleCreator, renderMode, orderMode, ecModel.get('useUTC'), tooltipModel.get('textStyle')) : seriesTooltipResult.markupText;
  432. var asyncTicket = 'item_' + dataModel.name + '_' + dataIndex;
  433. this._showOrMove(tooltipModel, function () {
  434. this._showTooltipContent(tooltipModel, markupText, params, asyncTicket, e.offsetX, e.offsetY, e.position, e.target, markupStyleCreator);
  435. }); // FIXME
  436. // duplicated showtip if manuallyShowTip is called from dispatchAction.
  437. dispatchAction({
  438. type: 'showTip',
  439. dataIndexInside: dataIndex,
  440. dataIndex: data.getRawIndex(dataIndex),
  441. seriesIndex: seriesIndex,
  442. from: this.uid
  443. });
  444. };
  445. TooltipView.prototype._showComponentItemTooltip = function (e, el, dispatchAction) {
  446. var ecData = getECData(el);
  447. var tooltipConfig = ecData.tooltipConfig;
  448. var tooltipOpt = tooltipConfig.option || {};
  449. if (zrUtil.isString(tooltipOpt)) {
  450. var content = tooltipOpt;
  451. tooltipOpt = {
  452. content: content,
  453. // Fixed formatter
  454. formatter: content
  455. };
  456. }
  457. var tooltipModelCascade = [tooltipOpt];
  458. var cmpt = this._ecModel.getComponent(ecData.componentMainType, ecData.componentIndex);
  459. if (cmpt) {
  460. tooltipModelCascade.push(cmpt);
  461. } // In most cases, component tooltip formatter has different params with series tooltip formatter,
  462. // so that they can not share the same formatter. Since the global tooltip formatter is used for series
  463. // by convension, we do not use it as the default formatter for component.
  464. tooltipModelCascade.push({
  465. formatter: tooltipOpt.content
  466. });
  467. var positionDefault = e.positionDefault;
  468. var subTooltipModel = buildTooltipModel(tooltipModelCascade, this._tooltipModel, positionDefault ? {
  469. position: positionDefault
  470. } : null);
  471. var defaultHtml = subTooltipModel.get('content');
  472. var asyncTicket = Math.random() + ''; // PENDING: this case do not support richText style yet.
  473. var markupStyleCreator = new TooltipMarkupStyleCreator(); // Do not check whether `trigger` is 'none' here, because `trigger`
  474. // only works on coordinate system. In fact, we have not found case
  475. // that requires setting `trigger` nothing on component yet.
  476. this._showOrMove(subTooltipModel, function () {
  477. // Use formatterParams from element defined in component
  478. // Avoid users modify it.
  479. var formatterParams = zrUtil.clone(subTooltipModel.get('formatterParams') || {});
  480. this._showTooltipContent(subTooltipModel, defaultHtml, formatterParams, asyncTicket, e.offsetX, e.offsetY, e.position, el, markupStyleCreator);
  481. }); // If not dispatch showTip, tip may be hide triggered by axis.
  482. dispatchAction({
  483. type: 'showTip',
  484. from: this.uid
  485. });
  486. };
  487. TooltipView.prototype._showTooltipContent = function ( // Use Model<TooltipOption> insteadof TooltipModel because this model may be from series or other options.
  488. // Instead of top level tooltip.
  489. tooltipModel, defaultHtml, params, asyncTicket, x, y, positionExpr, el, markupStyleCreator) {
  490. // Reset ticket
  491. this._ticket = '';
  492. if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) {
  493. return;
  494. }
  495. var tooltipContent = this._tooltipContent;
  496. var formatter = tooltipModel.get('formatter');
  497. positionExpr = positionExpr || tooltipModel.get('position');
  498. var html = defaultHtml;
  499. var nearPoint = this._getNearestPoint([x, y], params, tooltipModel.get('trigger'), tooltipModel.get('borderColor'));
  500. var nearPointColor = nearPoint.color;
  501. if (formatter) {
  502. if (zrUtil.isString(formatter)) {
  503. var useUTC = tooltipModel.ecModel.get('useUTC');
  504. var params0 = zrUtil.isArray(params) ? params[0] : params;
  505. var isTimeAxis = params0 && params0.axisType && params0.axisType.indexOf('time') >= 0;
  506. html = formatter;
  507. if (isTimeAxis) {
  508. html = timeFormat(params0.axisValue, html, useUTC);
  509. }
  510. html = formatUtil.formatTpl(html, params, true);
  511. } else if (zrUtil.isFunction(formatter)) {
  512. var callback = bind(function (cbTicket, html) {
  513. if (cbTicket === this._ticket) {
  514. tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr);
  515. this._updatePosition(tooltipModel, positionExpr, x, y, tooltipContent, params, el);
  516. }
  517. }, this);
  518. this._ticket = asyncTicket;
  519. html = formatter(params, asyncTicket, callback);
  520. } else {
  521. html = formatter;
  522. }
  523. }
  524. tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr);
  525. tooltipContent.show(tooltipModel, nearPointColor);
  526. this._updatePosition(tooltipModel, positionExpr, x, y, tooltipContent, params, el);
  527. };
  528. TooltipView.prototype._getNearestPoint = function (point, tooltipDataParams, trigger, borderColor) {
  529. if (trigger === 'axis' || zrUtil.isArray(tooltipDataParams)) {
  530. return {
  531. color: borderColor || (this._renderMode === 'html' ? '#fff' : 'none')
  532. };
  533. }
  534. if (!zrUtil.isArray(tooltipDataParams)) {
  535. return {
  536. color: borderColor || tooltipDataParams.color || tooltipDataParams.borderColor
  537. };
  538. }
  539. };
  540. TooltipView.prototype._doUpdatePosition = function (tooltipModel, positionExpr, x, // Mouse x
  541. y, // Mouse y
  542. content, params, el) {
  543. var viewWidth = this._api.getWidth();
  544. var viewHeight = this._api.getHeight();
  545. positionExpr = positionExpr || tooltipModel.get('position');
  546. var contentSize = content.getSize();
  547. var align = tooltipModel.get('align');
  548. var vAlign = tooltipModel.get('verticalAlign');
  549. var rect = el && el.getBoundingRect().clone();
  550. el && rect.applyTransform(el.transform);
  551. if (zrUtil.isFunction(positionExpr)) {
  552. // Callback of position can be an array or a string specify the position
  553. positionExpr = positionExpr([x, y], params, content.el, rect, {
  554. viewSize: [viewWidth, viewHeight],
  555. contentSize: contentSize.slice()
  556. });
  557. }
  558. if (zrUtil.isArray(positionExpr)) {
  559. x = parsePercent(positionExpr[0], viewWidth);
  560. y = parsePercent(positionExpr[1], viewHeight);
  561. } else if (zrUtil.isObject(positionExpr)) {
  562. var boxLayoutPosition = positionExpr;
  563. boxLayoutPosition.width = contentSize[0];
  564. boxLayoutPosition.height = contentSize[1];
  565. var layoutRect = layoutUtil.getLayoutRect(boxLayoutPosition, {
  566. width: viewWidth,
  567. height: viewHeight
  568. });
  569. x = layoutRect.x;
  570. y = layoutRect.y;
  571. align = null; // When positionExpr is left/top/right/bottom,
  572. // align and verticalAlign will not work.
  573. vAlign = null;
  574. } // Specify tooltip position by string 'top' 'bottom' 'left' 'right' around graphic element
  575. else if (zrUtil.isString(positionExpr) && el) {
  576. var pos = calcTooltipPosition(positionExpr, rect, contentSize, tooltipModel.get('borderWidth'));
  577. x = pos[0];
  578. y = pos[1];
  579. } else {
  580. var pos = refixTooltipPosition(x, y, content, viewWidth, viewHeight, align ? null : 20, vAlign ? null : 20);
  581. x = pos[0];
  582. y = pos[1];
  583. }
  584. align && (x -= isCenterAlign(align) ? contentSize[0] / 2 : align === 'right' ? contentSize[0] : 0);
  585. vAlign && (y -= isCenterAlign(vAlign) ? contentSize[1] / 2 : vAlign === 'bottom' ? contentSize[1] : 0);
  586. if (shouldTooltipConfine(tooltipModel)) {
  587. var pos = confineTooltipPosition(x, y, content, viewWidth, viewHeight);
  588. x = pos[0];
  589. y = pos[1];
  590. }
  591. content.moveTo(x, y);
  592. }; // FIXME
  593. // Should we remove this but leave this to user?
  594. TooltipView.prototype._updateContentNotChangedOnAxis = function (dataByCoordSys, cbParamsList) {
  595. var lastCoordSys = this._lastDataByCoordSys;
  596. var lastCbParamsList = this._cbParamsList;
  597. var contentNotChanged = !!lastCoordSys && lastCoordSys.length === dataByCoordSys.length;
  598. contentNotChanged && each(lastCoordSys, function (lastItemCoordSys, indexCoordSys) {
  599. var lastDataByAxis = lastItemCoordSys.dataByAxis || [];
  600. var thisItemCoordSys = dataByCoordSys[indexCoordSys] || {};
  601. var thisDataByAxis = thisItemCoordSys.dataByAxis || [];
  602. contentNotChanged = contentNotChanged && lastDataByAxis.length === thisDataByAxis.length;
  603. contentNotChanged && each(lastDataByAxis, function (lastItem, indexAxis) {
  604. var thisItem = thisDataByAxis[indexAxis] || {};
  605. var lastIndices = lastItem.seriesDataIndices || [];
  606. var newIndices = thisItem.seriesDataIndices || [];
  607. contentNotChanged = contentNotChanged && lastItem.value === thisItem.value && lastItem.axisType === thisItem.axisType && lastItem.axisId === thisItem.axisId && lastIndices.length === newIndices.length;
  608. contentNotChanged && each(lastIndices, function (lastIdxItem, j) {
  609. var newIdxItem = newIndices[j];
  610. contentNotChanged = contentNotChanged && lastIdxItem.seriesIndex === newIdxItem.seriesIndex && lastIdxItem.dataIndex === newIdxItem.dataIndex;
  611. }); // check is cbParams data value changed
  612. lastCbParamsList && zrUtil.each(lastItem.seriesDataIndices, function (idxItem) {
  613. var seriesIdx = idxItem.seriesIndex;
  614. var cbParams = cbParamsList[seriesIdx];
  615. var lastCbParams = lastCbParamsList[seriesIdx];
  616. if (cbParams && lastCbParams && lastCbParams.data !== cbParams.data) {
  617. contentNotChanged = false;
  618. }
  619. });
  620. });
  621. });
  622. this._lastDataByCoordSys = dataByCoordSys;
  623. this._cbParamsList = cbParamsList;
  624. return !!contentNotChanged;
  625. };
  626. TooltipView.prototype._hide = function (dispatchAction) {
  627. // Do not directly hideLater here, because this behavior may be prevented
  628. // in dispatchAction when showTip is dispatched.
  629. // FIXME
  630. // duplicated hideTip if manuallyHideTip is called from dispatchAction.
  631. this._lastDataByCoordSys = null;
  632. dispatchAction({
  633. type: 'hideTip',
  634. from: this.uid
  635. });
  636. };
  637. TooltipView.prototype.dispose = function (ecModel, api) {
  638. if (env.node) {
  639. return;
  640. }
  641. this._tooltipContent.dispose();
  642. globalListener.unregister('itemTooltip', api);
  643. };
  644. TooltipView.type = 'tooltip';
  645. return TooltipView;
  646. }(ComponentView);
  647. /**
  648. * From top to bottom. (the last one should be globalTooltipModel);
  649. */
  650. function buildTooltipModel(modelCascade, globalTooltipModel, defaultTooltipOption) {
  651. // Last is always tooltip model.
  652. var ecModel = globalTooltipModel.ecModel;
  653. var resultModel;
  654. if (defaultTooltipOption) {
  655. resultModel = new Model(defaultTooltipOption, ecModel, ecModel);
  656. resultModel = new Model(globalTooltipModel.option, resultModel, ecModel);
  657. } else {
  658. resultModel = globalTooltipModel;
  659. }
  660. for (var i = modelCascade.length - 1; i >= 0; i--) {
  661. var tooltipOpt = modelCascade[i];
  662. if (tooltipOpt) {
  663. if (tooltipOpt instanceof Model) {
  664. tooltipOpt = tooltipOpt.get('tooltip', true);
  665. } // In each data item tooltip can be simply write:
  666. // {
  667. // value: 10,
  668. // tooltip: 'Something you need to know'
  669. // }
  670. if (zrUtil.isString(tooltipOpt)) {
  671. tooltipOpt = {
  672. formatter: tooltipOpt
  673. };
  674. }
  675. if (tooltipOpt) {
  676. resultModel = new Model(tooltipOpt, resultModel, ecModel);
  677. }
  678. }
  679. }
  680. return resultModel;
  681. }
  682. function makeDispatchAction(payload, api) {
  683. return payload.dispatchAction || zrUtil.bind(api.dispatchAction, api);
  684. }
  685. function refixTooltipPosition(x, y, content, viewWidth, viewHeight, gapH, gapV) {
  686. var size = content.getSize();
  687. var width = size[0];
  688. var height = size[1];
  689. if (gapH != null) {
  690. // Add extra 2 pixels for this case:
  691. // At present the "values" in defaut tooltip are using CSS `float: right`.
  692. // When the right edge of the tooltip box is on the right side of the
  693. // viewport, the `float` layout might push the "values" to the second line.
  694. if (x + width + gapH + 2 > viewWidth) {
  695. x -= width + gapH;
  696. } else {
  697. x += gapH;
  698. }
  699. }
  700. if (gapV != null) {
  701. if (y + height + gapV > viewHeight) {
  702. y -= height + gapV;
  703. } else {
  704. y += gapV;
  705. }
  706. }
  707. return [x, y];
  708. }
  709. function confineTooltipPosition(x, y, content, viewWidth, viewHeight) {
  710. var size = content.getSize();
  711. var width = size[0];
  712. var height = size[1];
  713. x = Math.min(x + width, viewWidth) - width;
  714. y = Math.min(y + height, viewHeight) - height;
  715. x = Math.max(x, 0);
  716. y = Math.max(y, 0);
  717. return [x, y];
  718. }
  719. function calcTooltipPosition(position, rect, contentSize, borderWidth) {
  720. var domWidth = contentSize[0];
  721. var domHeight = contentSize[1];
  722. var offset = Math.ceil(Math.SQRT2 * borderWidth) + 8;
  723. var x = 0;
  724. var y = 0;
  725. var rectWidth = rect.width;
  726. var rectHeight = rect.height;
  727. switch (position) {
  728. case 'inside':
  729. x = rect.x + rectWidth / 2 - domWidth / 2;
  730. y = rect.y + rectHeight / 2 - domHeight / 2;
  731. break;
  732. case 'top':
  733. x = rect.x + rectWidth / 2 - domWidth / 2;
  734. y = rect.y - domHeight - offset;
  735. break;
  736. case 'bottom':
  737. x = rect.x + rectWidth / 2 - domWidth / 2;
  738. y = rect.y + rectHeight + offset;
  739. break;
  740. case 'left':
  741. x = rect.x - domWidth - offset;
  742. y = rect.y + rectHeight / 2 - domHeight / 2;
  743. break;
  744. case 'right':
  745. x = rect.x + rectWidth + offset;
  746. y = rect.y + rectHeight / 2 - domHeight / 2;
  747. }
  748. return [x, y];
  749. }
  750. function isCenterAlign(align) {
  751. return align === 'center' || align === 'middle';
  752. }
  753. /**
  754. * Find target component by payload like:
  755. * ```js
  756. * { legendId: 'some_id', name: 'xxx' }
  757. * { toolboxIndex: 1, name: 'xxx' }
  758. * { geoName: 'some_name', name: 'xxx' }
  759. * ```
  760. * PENDING: at present only
  761. *
  762. * If not found, return null/undefined.
  763. */
  764. function findComponentReference(payload, ecModel, api) {
  765. var queryOptionMap = preParseFinder(payload).queryOptionMap;
  766. var componentMainType = queryOptionMap.keys()[0];
  767. if (!componentMainType || componentMainType === 'series') {
  768. return;
  769. }
  770. var queryResult = queryReferringComponents(ecModel, componentMainType, queryOptionMap.get(componentMainType), {
  771. useDefault: false,
  772. enableAll: false,
  773. enableNone: false
  774. });
  775. var model = queryResult.models[0];
  776. if (!model) {
  777. return;
  778. }
  779. var view = api.getViewOfComponentModel(model);
  780. var el;
  781. view.group.traverse(function (subEl) {
  782. var tooltipConfig = getECData(subEl).tooltipConfig;
  783. if (tooltipConfig && tooltipConfig.name === payload.name) {
  784. el = subEl;
  785. return true; // stop
  786. }
  787. });
  788. if (el) {
  789. return {
  790. componentMainType: componentMainType,
  791. componentIndex: model.componentIndex,
  792. el: el
  793. };
  794. }
  795. }
  796. export default TooltipView;