axisTrigger.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { makeInner } from '../../util/model.js';
  41. import * as modelHelper from './modelHelper.js';
  42. import findPointFromSeries from './findPointFromSeries.js';
  43. import { each, curry, bind, extend } from 'zrender/lib/core/util.js';
  44. import { isNullableNumberFinite } from '../../util/number.js';
  45. var inner = makeInner();
  46. /**
  47. * Basic logic: check all axis, if they do not demand show/highlight,
  48. * then hide/downplay them.
  49. *
  50. * @return content of event obj for echarts.connect.
  51. */
  52. export default function axisTrigger(payload, ecModel, api) {
  53. var currTrigger = payload.currTrigger;
  54. var point = [payload.x, payload.y];
  55. var finder = payload;
  56. var dispatchAction = payload.dispatchAction || bind(api.dispatchAction, api);
  57. var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;
  58. // Pending
  59. // See #6121. But we are not able to reproduce it yet.
  60. if (!coordSysAxesInfo) {
  61. return;
  62. }
  63. if (illegalPoint(point)) {
  64. // Used in the default behavior of `connection`: use the sample seriesIndex
  65. // and dataIndex. And also used in the tooltipView trigger.
  66. point = findPointFromSeries({
  67. seriesIndex: finder.seriesIndex,
  68. // Do not use dataIndexInside from other ec instance.
  69. // FIXME: auto detect it?
  70. dataIndex: finder.dataIndex
  71. }, ecModel).point;
  72. }
  73. var isIllegalPoint = illegalPoint(point);
  74. // Axis and value can be specified when calling dispatchAction({type: 'updateAxisPointer'}).
  75. // Notice: In this case, it is difficult to get the `point` (which is necessary to show
  76. // tooltip, so if point is not given, we just use the point found by sample seriesIndex
  77. // and dataIndex.
  78. var inputAxesInfo = finder.axesInfo;
  79. var axesInfo = coordSysAxesInfo.axesInfo;
  80. var shouldHide = currTrigger === 'leave' || illegalPoint(point);
  81. var outputPayload = {};
  82. var showValueMap = {};
  83. var dataByCoordSys = {
  84. list: [],
  85. map: {}
  86. };
  87. var updaters = {
  88. showPointer: curry(showPointer, showValueMap),
  89. showTooltip: curry(showTooltip, dataByCoordSys)
  90. };
  91. // Process for triggered axes.
  92. each(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) {
  93. // If a point given, it must be contained by the coordinate system.
  94. var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point);
  95. each(coordSysAxesInfo.coordSysAxesInfo[coordSysKey], function (axisInfo, key) {
  96. var axis = axisInfo.axis;
  97. var inputAxisInfo = findInputAxisInfo(inputAxesInfo, axisInfo);
  98. // If no inputAxesInfo, no axis is restricted.
  99. if (!shouldHide && coordSysContainsPoint && (!inputAxesInfo || inputAxisInfo)) {
  100. var val = inputAxisInfo && inputAxisInfo.value;
  101. if (val == null && !isIllegalPoint) {
  102. val = axis.pointToData(point);
  103. }
  104. val != null && processOnAxis(axisInfo, val, updaters, false, outputPayload);
  105. }
  106. });
  107. });
  108. // Process for linked axes.
  109. var linkTriggers = {};
  110. each(axesInfo, function (tarAxisInfo, tarKey) {
  111. var linkGroup = tarAxisInfo.linkGroup;
  112. // If axis has been triggered in the previous stage, it should not be triggered by link.
  113. if (linkGroup && !showValueMap[tarKey]) {
  114. each(linkGroup.axesInfo, function (srcAxisInfo, srcKey) {
  115. var srcValItem = showValueMap[srcKey];
  116. // If srcValItem exist, source axis is triggered, so link to target axis.
  117. if (srcAxisInfo !== tarAxisInfo && srcValItem) {
  118. var val = srcValItem.value;
  119. linkGroup.mapper && (val = tarAxisInfo.axis.scale.parse(linkGroup.mapper(val, makeMapperParam(srcAxisInfo), makeMapperParam(tarAxisInfo))));
  120. linkTriggers[tarAxisInfo.key] = val;
  121. }
  122. });
  123. }
  124. });
  125. each(linkTriggers, function (val, tarKey) {
  126. processOnAxis(axesInfo[tarKey], val, updaters, true, outputPayload);
  127. });
  128. updateModelActually(showValueMap, axesInfo, outputPayload);
  129. dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction);
  130. dispatchHighDownActually(axesInfo, dispatchAction, api);
  131. return outputPayload;
  132. }
  133. function processOnAxis(axisInfo, newValue, updaters, noSnap, outputFinder) {
  134. var axis = axisInfo.axis;
  135. if (axis.scale.isBlank() || !axis.containData(newValue)) {
  136. return;
  137. }
  138. if (!axisInfo.involveSeries) {
  139. updaters.showPointer(axisInfo, newValue);
  140. return;
  141. }
  142. // Heavy calculation. So put it after axis.containData checking.
  143. var payloadInfo = buildPayloadsBySeries(newValue, axisInfo);
  144. var payloadBatch = payloadInfo.payloadBatch;
  145. var snapToValue = payloadInfo.snapToValue;
  146. // Fill content of event obj for echarts.connect.
  147. // By default use the first involved series data as a sample to connect.
  148. if (payloadBatch[0] && outputFinder.seriesIndex == null) {
  149. extend(outputFinder, payloadBatch[0]);
  150. }
  151. // If no linkSource input, this process is for collecting link
  152. // target, where snap should not be accepted.
  153. if (!noSnap && axisInfo.snap) {
  154. if (axis.containData(snapToValue) && snapToValue != null) {
  155. newValue = snapToValue;
  156. }
  157. }
  158. updaters.showPointer(axisInfo, newValue, payloadBatch);
  159. // Tooltip should always be snapToValue, otherwise there will be
  160. // incorrect "axis value ~ series value" mapping displayed in tooltip.
  161. updaters.showTooltip(axisInfo, payloadInfo, snapToValue);
  162. }
  163. function buildPayloadsBySeries(value, axisInfo) {
  164. var axis = axisInfo.axis;
  165. var dim = axis.dim;
  166. var snapToValue = value;
  167. var payloadBatch = [];
  168. var minDist = Number.MAX_VALUE;
  169. var minDiff = -1;
  170. each(axisInfo.seriesModels, function (series, idx) {
  171. var dataDim = series.getData().mapDimensionsAll(dim);
  172. var seriesNestestValue;
  173. var dataIndices;
  174. if (series.getAxisTooltipData) {
  175. var result = series.getAxisTooltipData(dataDim, value, axis);
  176. dataIndices = result.dataIndices;
  177. seriesNestestValue = result.nestestValue;
  178. } else {
  179. dataIndices = series.indicesOfNearest(dim, dataDim[0], value,
  180. // Add a threshold to avoid find the wrong dataIndex
  181. // when data length is not same.
  182. // false,
  183. axis.type === 'category' ? 0.5 : null);
  184. if (!dataIndices.length) {
  185. return;
  186. }
  187. seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]);
  188. }
  189. if (!isNullableNumberFinite(seriesNestestValue)) {
  190. return;
  191. }
  192. var diff = value - seriesNestestValue;
  193. var dist = Math.abs(diff);
  194. // Consider category case
  195. if (dist <= minDist) {
  196. if (dist < minDist || diff >= 0 && minDiff < 0) {
  197. minDist = dist;
  198. minDiff = diff;
  199. snapToValue = seriesNestestValue;
  200. payloadBatch.length = 0;
  201. }
  202. each(dataIndices, function (dataIndex) {
  203. payloadBatch.push({
  204. seriesIndex: series.seriesIndex,
  205. dataIndexInside: dataIndex,
  206. dataIndex: series.getData().getRawIndex(dataIndex)
  207. });
  208. });
  209. }
  210. });
  211. return {
  212. payloadBatch: payloadBatch,
  213. snapToValue: snapToValue
  214. };
  215. }
  216. function showPointer(showValueMap, axisInfo, value, payloadBatch) {
  217. showValueMap[axisInfo.key] = {
  218. value: value,
  219. payloadBatch: payloadBatch
  220. };
  221. }
  222. function showTooltip(dataByCoordSys, axisInfo, payloadInfo, value) {
  223. var payloadBatch = payloadInfo.payloadBatch;
  224. var axis = axisInfo.axis;
  225. var axisModel = axis.model;
  226. var axisPointerModel = axisInfo.axisPointerModel;
  227. // If no data, do not create anything in dataByCoordSys,
  228. // whose length will be used to judge whether dispatch action.
  229. if (!axisInfo.triggerTooltip || !payloadBatch.length) {
  230. return;
  231. }
  232. var coordSysModel = axisInfo.coordSys.model;
  233. var coordSysKey = modelHelper.makeKey(coordSysModel);
  234. var coordSysItem = dataByCoordSys.map[coordSysKey];
  235. if (!coordSysItem) {
  236. coordSysItem = dataByCoordSys.map[coordSysKey] = {
  237. coordSysId: coordSysModel.id,
  238. coordSysIndex: coordSysModel.componentIndex,
  239. coordSysType: coordSysModel.type,
  240. coordSysMainType: coordSysModel.mainType,
  241. dataByAxis: []
  242. };
  243. dataByCoordSys.list.push(coordSysItem);
  244. }
  245. coordSysItem.dataByAxis.push({
  246. axisDim: axis.dim,
  247. axisIndex: axisModel.componentIndex,
  248. axisType: axisModel.type,
  249. axisId: axisModel.id,
  250. value: value,
  251. // Caution: viewHelper.getValueLabel is actually on "view stage", which
  252. // depends that all models have been updated. So it should not be performed
  253. // here. Considering axisPointerModel used here is volatile, which is hard
  254. // to be retrieve in TooltipView, we prepare parameters here.
  255. valueLabelOpt: {
  256. precision: axisPointerModel.get(['label', 'precision']),
  257. formatter: axisPointerModel.get(['label', 'formatter'])
  258. },
  259. seriesDataIndices: payloadBatch.slice()
  260. });
  261. }
  262. function updateModelActually(showValueMap, axesInfo, outputPayload) {
  263. var outputAxesInfo = outputPayload.axesInfo = [];
  264. // Basic logic: If no 'show' required, 'hide' this axisPointer.
  265. each(axesInfo, function (axisInfo, key) {
  266. var option = axisInfo.axisPointerModel.option;
  267. var valItem = showValueMap[key];
  268. if (valItem) {
  269. !axisInfo.useHandle && (option.status = 'show');
  270. option.value = valItem.value;
  271. // For label formatter param and highlight.
  272. option.seriesDataIndices = (valItem.payloadBatch || []).slice();
  273. }
  274. // When always show (e.g., handle used), remain
  275. // original value and status.
  276. else {
  277. // If hide, value still need to be set, consider
  278. // click legend to toggle axis blank.
  279. !axisInfo.useHandle && (option.status = 'hide');
  280. }
  281. // If status is 'hide', should be no info in payload.
  282. option.status === 'show' && outputAxesInfo.push({
  283. axisDim: axisInfo.axis.dim,
  284. axisIndex: axisInfo.axis.model.componentIndex,
  285. value: option.value
  286. });
  287. });
  288. }
  289. function dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction) {
  290. // Basic logic: If no showTip required, hideTip will be dispatched.
  291. if (illegalPoint(point) || !dataByCoordSys.list.length) {
  292. dispatchAction({
  293. type: 'hideTip'
  294. });
  295. return;
  296. }
  297. // In most case only one axis (or event one series is used). It is
  298. // convenient to fetch payload.seriesIndex and payload.dataIndex
  299. // directly. So put the first seriesIndex and dataIndex of the first
  300. // axis on the payload.
  301. var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] || {}).seriesDataIndices || [])[0] || {};
  302. dispatchAction({
  303. type: 'showTip',
  304. escapeConnect: true,
  305. x: point[0],
  306. y: point[1],
  307. tooltipOption: payload.tooltipOption,
  308. position: payload.position,
  309. dataIndexInside: sampleItem.dataIndexInside,
  310. dataIndex: sampleItem.dataIndex,
  311. seriesIndex: sampleItem.seriesIndex,
  312. dataByCoordSys: dataByCoordSys.list
  313. });
  314. }
  315. function dispatchHighDownActually(axesInfo, dispatchAction, api) {
  316. // FIXME
  317. // highlight status modification should be a stage of main process?
  318. // (Consider conflict (e.g., legend and axisPointer) and setOption)
  319. var zr = api.getZr();
  320. var highDownKey = 'axisPointerLastHighlights';
  321. var lastHighlights = inner(zr)[highDownKey] || {};
  322. var newHighlights = inner(zr)[highDownKey] = {};
  323. // Update highlight/downplay status according to axisPointer model.
  324. // Build hash map and remove duplicate incidentally.
  325. each(axesInfo, function (axisInfo, key) {
  326. var option = axisInfo.axisPointerModel.option;
  327. option.status === 'show' && axisInfo.triggerEmphasis && each(option.seriesDataIndices, function (batchItem) {
  328. newHighlights[batchItem.seriesIndex + '|' + batchItem.dataIndex] = batchItem;
  329. });
  330. });
  331. // Diff.
  332. var toHighlight = [];
  333. var toDownplay = [];
  334. function makeHighDownItem(batchItem) {
  335. // `dataIndexInside` should be removed, since the last recorded `dataIndexInside` may have
  336. // been changed if `dataZoomInside` changed the view. Only `dataIndex` will suffice.
  337. return {
  338. seriesIndex: batchItem.seriesIndex,
  339. dataIndex: batchItem.dataIndex
  340. };
  341. }
  342. each(lastHighlights, function (batchItem, key) {
  343. !newHighlights[key] && toDownplay.push(makeHighDownItem(batchItem));
  344. });
  345. each(newHighlights, function (batchItem, key) {
  346. !lastHighlights[key] && toHighlight.push(makeHighDownItem(batchItem));
  347. });
  348. toDownplay.length && api.dispatchAction({
  349. type: 'downplay',
  350. escapeConnect: true,
  351. // Not blur others when highlight in axisPointer.
  352. notBlur: true,
  353. batch: toDownplay
  354. });
  355. toHighlight.length && api.dispatchAction({
  356. type: 'highlight',
  357. escapeConnect: true,
  358. // Not blur others when highlight in axisPointer.
  359. notBlur: true,
  360. batch: toHighlight
  361. });
  362. }
  363. function findInputAxisInfo(inputAxesInfo, axisInfo) {
  364. for (var i = 0; i < (inputAxesInfo || []).length; i++) {
  365. var inputAxisInfo = inputAxesInfo[i];
  366. if (axisInfo.axis.dim === inputAxisInfo.axisDim && axisInfo.axis.model.componentIndex === inputAxisInfo.axisIndex) {
  367. return inputAxisInfo;
  368. }
  369. }
  370. }
  371. function makeMapperParam(axisInfo) {
  372. var axisModel = axisInfo.axis.model;
  373. var item = {};
  374. var dim = item.axisDim = axisInfo.axis.dim;
  375. item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex;
  376. item.axisName = item[dim + 'AxisName'] = axisModel.name;
  377. item.axisId = item[dim + 'AxisId'] = axisModel.id;
  378. return item;
  379. }
  380. function illegalPoint(point) {
  381. return !point || point[0] == null || isNaN(point[0]) || point[1] == null || isNaN(point[1]);
  382. }