123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- var zrUtil = require("zrender/lib/core/util");
- var _model = require("../../util/model");
- var makeInner = _model.makeInner;
- var modelHelper = require("./modelHelper");
- var findPointFromSeries = require("./findPointFromSeries");
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- var each = zrUtil.each;
- var curry = zrUtil.curry;
- var inner = makeInner();
- /**
- * Basic logic: check all axis, if they do not demand show/highlight,
- * then hide/downplay them.
- *
- * @param {Object} coordSysAxesInfo
- * @param {Object} payload
- * @param {string} [payload.currTrigger] 'click' | 'mousemove' | 'leave'
- * @param {Array.<number>} [payload.x] x and y, which are mandatory, specify a point to
- * trigger axisPointer and tooltip.
- * @param {Array.<number>} [payload.y] x and y, which are mandatory, specify a point to
- * trigger axisPointer and tooltip.
- * @param {Object} [payload.seriesIndex] finder, optional, restrict target axes.
- * @param {Object} [payload.dataIndex] finder, restrict target axes.
- * @param {Object} [payload.axesInfo] finder, restrict target axes.
- * [{
- * axisDim: 'x'|'y'|'angle'|...,
- * axisIndex: ...,
- * value: ...
- * }, ...]
- * @param {Function} [payload.dispatchAction]
- * @param {Object} [payload.tooltipOption]
- * @param {Object|Array.<number>|Function} [payload.position] Tooltip position,
- * which can be specified in dispatchAction
- * @param {module:echarts/model/Global} ecModel
- * @param {module:echarts/ExtensionAPI} api
- * @return {Object} content of event obj for echarts.connect.
- */
- function _default(payload, ecModel, api) {
- var currTrigger = payload.currTrigger;
- var point = [payload.x, payload.y];
- var finder = payload;
- var dispatchAction = payload.dispatchAction || zrUtil.bind(api.dispatchAction, api);
- var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo; // Pending
- // See #6121. But we are not able to reproduce it yet.
- if (!coordSysAxesInfo) {
- return;
- }
- if (illegalPoint(point)) {
- // Used in the default behavior of `connection`: use the sample seriesIndex
- // and dataIndex. And also used in the tooltipView trigger.
- point = findPointFromSeries({
- seriesIndex: finder.seriesIndex,
- // Do not use dataIndexInside from other ec instance.
- // FIXME: auto detect it?
- dataIndex: finder.dataIndex
- }, ecModel).point;
- }
- var isIllegalPoint = illegalPoint(point); // Axis and value can be specified when calling dispatchAction({type: 'updateAxisPointer'}).
- // Notice: In this case, it is difficult to get the `point` (which is necessary to show
- // tooltip, so if point is not given, we just use the point found by sample seriesIndex
- // and dataIndex.
- var inputAxesInfo = finder.axesInfo;
- var axesInfo = coordSysAxesInfo.axesInfo;
- var shouldHide = currTrigger === 'leave' || illegalPoint(point);
- var outputFinder = {};
- var showValueMap = {};
- var dataByCoordSys = {
- list: [],
- map: {}
- };
- var updaters = {
- showPointer: curry(showPointer, showValueMap),
- showTooltip: curry(showTooltip, dataByCoordSys)
- }; // Process for triggered axes.
- each(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) {
- // If a point given, it must be contained by the coordinate system.
- var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point);
- each(coordSysAxesInfo.coordSysAxesInfo[coordSysKey], function (axisInfo, key) {
- var axis = axisInfo.axis;
- var inputAxisInfo = findInputAxisInfo(inputAxesInfo, axisInfo); // If no inputAxesInfo, no axis is restricted.
- if (!shouldHide && coordSysContainsPoint && (!inputAxesInfo || inputAxisInfo)) {
- var val = inputAxisInfo && inputAxisInfo.value;
- if (val == null && !isIllegalPoint) {
- val = axis.pointToData(point);
- }
- val != null && processOnAxis(axisInfo, val, updaters, false, outputFinder);
- }
- });
- }); // Process for linked axes.
- var linkTriggers = {};
- each(axesInfo, function (tarAxisInfo, tarKey) {
- var linkGroup = tarAxisInfo.linkGroup; // If axis has been triggered in the previous stage, it should not be triggered by link.
- if (linkGroup && !showValueMap[tarKey]) {
- each(linkGroup.axesInfo, function (srcAxisInfo, srcKey) {
- var srcValItem = showValueMap[srcKey]; // If srcValItem exist, source axis is triggered, so link to target axis.
- if (srcAxisInfo !== tarAxisInfo && srcValItem) {
- var val = srcValItem.value;
- linkGroup.mapper && (val = tarAxisInfo.axis.scale.parse(linkGroup.mapper(val, makeMapperParam(srcAxisInfo), makeMapperParam(tarAxisInfo))));
- linkTriggers[tarAxisInfo.key] = val;
- }
- });
- }
- });
- each(linkTriggers, function (val, tarKey) {
- processOnAxis(axesInfo[tarKey], val, updaters, true, outputFinder);
- });
- updateModelActually(showValueMap, axesInfo, outputFinder);
- dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction);
- dispatchHighDownActually(axesInfo, dispatchAction, api);
- return outputFinder;
- }
- function processOnAxis(axisInfo, newValue, updaters, dontSnap, outputFinder) {
- var axis = axisInfo.axis;
- if (axis.scale.isBlank() || !axis.containData(newValue)) {
- return;
- }
- if (!axisInfo.involveSeries) {
- updaters.showPointer(axisInfo, newValue);
- return;
- } // Heavy calculation. So put it after axis.containData checking.
- var payloadInfo = buildPayloadsBySeries(newValue, axisInfo);
- var payloadBatch = payloadInfo.payloadBatch;
- var snapToValue = payloadInfo.snapToValue; // Fill content of event obj for echarts.connect.
- // By default use the first involved series data as a sample to connect.
- if (payloadBatch[0] && outputFinder.seriesIndex == null) {
- zrUtil.extend(outputFinder, payloadBatch[0]);
- } // If no linkSource input, this process is for collecting link
- // target, where snap should not be accepted.
- if (!dontSnap && axisInfo.snap) {
- if (axis.containData(snapToValue) && snapToValue != null) {
- newValue = snapToValue;
- }
- }
- updaters.showPointer(axisInfo, newValue, payloadBatch, outputFinder); // Tooltip should always be snapToValue, otherwise there will be
- // incorrect "axis value ~ series value" mapping displayed in tooltip.
- updaters.showTooltip(axisInfo, payloadInfo, snapToValue);
- }
- function buildPayloadsBySeries(value, axisInfo) {
- var axis = axisInfo.axis;
- var dim = axis.dim;
- var snapToValue = value;
- var payloadBatch = [];
- var minDist = Number.MAX_VALUE;
- var minDiff = -1;
- each(axisInfo.seriesModels, function (series, idx) {
- var dataDim = series.getData().mapDimension(dim, true);
- var seriesNestestValue;
- var dataIndices;
- if (series.getAxisTooltipData) {
- var result = series.getAxisTooltipData(dataDim, value, axis);
- dataIndices = result.dataIndices;
- seriesNestestValue = result.nestestValue;
- } else {
- dataIndices = series.getData().indicesOfNearest(dataDim[0], value, // Add a threshold to avoid find the wrong dataIndex
- // when data length is not same.
- // false,
- axis.type === 'category' ? 0.5 : null);
- if (!dataIndices.length) {
- return;
- }
- seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]);
- }
- if (seriesNestestValue == null || !isFinite(seriesNestestValue)) {
- return;
- }
- var diff = value - seriesNestestValue;
- var dist = Math.abs(diff); // Consider category case
- if (dist <= minDist) {
- if (dist < minDist || diff >= 0 && minDiff < 0) {
- minDist = dist;
- minDiff = diff;
- snapToValue = seriesNestestValue;
- payloadBatch.length = 0;
- }
- each(dataIndices, function (dataIndex) {
- payloadBatch.push({
- seriesIndex: series.seriesIndex,
- dataIndexInside: dataIndex,
- dataIndex: series.getData().getRawIndex(dataIndex)
- });
- });
- }
- });
- return {
- payloadBatch: payloadBatch,
- snapToValue: snapToValue
- };
- }
- function showPointer(showValueMap, axisInfo, value, payloadBatch) {
- showValueMap[axisInfo.key] = {
- value: value,
- payloadBatch: payloadBatch
- };
- }
- function showTooltip(dataByCoordSys, axisInfo, payloadInfo, value) {
- var payloadBatch = payloadInfo.payloadBatch;
- var axis = axisInfo.axis;
- var axisModel = axis.model;
- var axisPointerModel = axisInfo.axisPointerModel; // If no data, do not create anything in dataByCoordSys,
- // whose length will be used to judge whether dispatch action.
- if (!axisInfo.triggerTooltip || !payloadBatch.length) {
- return;
- }
- var coordSysModel = axisInfo.coordSys.model;
- var coordSysKey = modelHelper.makeKey(coordSysModel);
- var coordSysItem = dataByCoordSys.map[coordSysKey];
- if (!coordSysItem) {
- coordSysItem = dataByCoordSys.map[coordSysKey] = {
- coordSysId: coordSysModel.id,
- coordSysIndex: coordSysModel.componentIndex,
- coordSysType: coordSysModel.type,
- coordSysMainType: coordSysModel.mainType,
- dataByAxis: []
- };
- dataByCoordSys.list.push(coordSysItem);
- }
- coordSysItem.dataByAxis.push({
- axisDim: axis.dim,
- axisIndex: axisModel.componentIndex,
- axisType: axisModel.type,
- axisId: axisModel.id,
- value: value,
- // Caustion: viewHelper.getValueLabel is actually on "view stage", which
- // depends that all models have been updated. So it should not be performed
- // here. Considering axisPointerModel used here is volatile, which is hard
- // to be retrieve in TooltipView, we prepare parameters here.
- valueLabelOpt: {
- precision: axisPointerModel.get('label.precision'),
- formatter: axisPointerModel.get('label.formatter')
- },
- seriesDataIndices: payloadBatch.slice()
- });
- }
- function updateModelActually(showValueMap, axesInfo, outputFinder) {
- var outputAxesInfo = outputFinder.axesInfo = []; // Basic logic: If no 'show' required, 'hide' this axisPointer.
- each(axesInfo, function (axisInfo, key) {
- var option = axisInfo.axisPointerModel.option;
- var valItem = showValueMap[key];
- if (valItem) {
- !axisInfo.useHandle && (option.status = 'show');
- option.value = valItem.value; // For label formatter param and highlight.
- option.seriesDataIndices = (valItem.payloadBatch || []).slice();
- } // When always show (e.g., handle used), remain
- // original value and status.
- else {
- // If hide, value still need to be set, consider
- // click legend to toggle axis blank.
- !axisInfo.useHandle && (option.status = 'hide');
- } // If status is 'hide', should be no info in payload.
- option.status === 'show' && outputAxesInfo.push({
- axisDim: axisInfo.axis.dim,
- axisIndex: axisInfo.axis.model.componentIndex,
- value: option.value
- });
- });
- }
- function dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction) {
- // Basic logic: If no showTip required, hideTip will be dispatched.
- if (illegalPoint(point) || !dataByCoordSys.list.length) {
- dispatchAction({
- type: 'hideTip'
- });
- return;
- } // In most case only one axis (or event one series is used). It is
- // convinient to fetch payload.seriesIndex and payload.dataIndex
- // dirtectly. So put the first seriesIndex and dataIndex of the first
- // axis on the payload.
- var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] || {}).seriesDataIndices || [])[0] || {};
- dispatchAction({
- type: 'showTip',
- escapeConnect: true,
- x: point[0],
- y: point[1],
- tooltipOption: payload.tooltipOption,
- position: payload.position,
- dataIndexInside: sampleItem.dataIndexInside,
- dataIndex: sampleItem.dataIndex,
- seriesIndex: sampleItem.seriesIndex,
- dataByCoordSys: dataByCoordSys.list
- });
- }
- function dispatchHighDownActually(axesInfo, dispatchAction, api) {
- // FIXME
- // highlight status modification shoule be a stage of main process?
- // (Consider confilct (e.g., legend and axisPointer) and setOption)
- var zr = api.getZr();
- var highDownKey = 'axisPointerLastHighlights';
- var lastHighlights = inner(zr)[highDownKey] || {};
- var newHighlights = inner(zr)[highDownKey] = {}; // Update highlight/downplay status according to axisPointer model.
- // Build hash map and remove duplicate incidentally.
- each(axesInfo, function (axisInfo, key) {
- var option = axisInfo.axisPointerModel.option;
- option.status === 'show' && each(option.seriesDataIndices, function (batchItem) {
- var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex;
- newHighlights[key] = batchItem;
- });
- }); // Diff.
- var toHighlight = [];
- var toDownplay = [];
- zrUtil.each(lastHighlights, function (batchItem, key) {
- !newHighlights[key] && toDownplay.push(batchItem);
- });
- zrUtil.each(newHighlights, function (batchItem, key) {
- !lastHighlights[key] && toHighlight.push(batchItem);
- });
- toDownplay.length && api.dispatchAction({
- type: 'downplay',
- escapeConnect: true,
- batch: toDownplay
- });
- toHighlight.length && api.dispatchAction({
- type: 'highlight',
- escapeConnect: true,
- batch: toHighlight
- });
- }
- function findInputAxisInfo(inputAxesInfo, axisInfo) {
- for (var i = 0; i < (inputAxesInfo || []).length; i++) {
- var inputAxisInfo = inputAxesInfo[i];
- if (axisInfo.axis.dim === inputAxisInfo.axisDim && axisInfo.axis.model.componentIndex === inputAxisInfo.axisIndex) {
- return inputAxisInfo;
- }
- }
- }
- function makeMapperParam(axisInfo) {
- var axisModel = axisInfo.axis.model;
- var item = {};
- var dim = item.axisDim = axisInfo.axis.dim;
- item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex;
- item.axisName = item[dim + 'AxisName'] = axisModel.name;
- item.axisId = item[dim + 'AxisId'] = axisModel.id;
- return item;
- }
- function illegalPoint(point) {
- return !point || point[0] == null || isNaN(point[0]) || point[1] == null || isNaN(point[1]);
- }
- module.exports = _default;
|