axisTrigger.js 14 KB

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