TooltipView.js 33 KB

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