TooltipView.js 33 KB

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