You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
378 lines
14 KiB
378 lines
14 KiB
4 weeks ago
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
import { makeInner } from '../../util/model.js';
|
||
|
import * as modelHelper from './modelHelper.js';
|
||
|
import findPointFromSeries from './findPointFromSeries.js';
|
||
|
import { each, curry, bind, extend } from 'zrender/lib/core/util.js';
|
||
|
var inner = makeInner();
|
||
|
/**
|
||
|
* Basic logic: check all axis, if they do not demand show/highlight,
|
||
|
* then hide/downplay them.
|
||
|
*
|
||
|
* @return content of event obj for echarts.connect.
|
||
|
*/
|
||
|
export default function axisTrigger(payload, ecModel, api) {
|
||
|
var currTrigger = payload.currTrigger;
|
||
|
var point = [payload.x, payload.y];
|
||
|
var finder = payload;
|
||
|
var dispatchAction = payload.dispatchAction || 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 outputPayload = {};
|
||
|
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, outputPayload);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
// 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, outputPayload);
|
||
|
});
|
||
|
updateModelActually(showValueMap, axesInfo, outputPayload);
|
||
|
dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction);
|
||
|
dispatchHighDownActually(axesInfo, dispatchAction, api);
|
||
|
return outputPayload;
|
||
|
}
|
||
|
function processOnAxis(axisInfo, newValue, updaters, noSnap, 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) {
|
||
|
extend(outputFinder, payloadBatch[0]);
|
||
|
}
|
||
|
// If no linkSource input, this process is for collecting link
|
||
|
// target, where snap should not be accepted.
|
||
|
if (!noSnap && axisInfo.snap) {
|
||
|
if (axis.containData(snapToValue) && snapToValue != null) {
|
||
|
newValue = snapToValue;
|
||
|
}
|
||
|
}
|
||
|
updaters.showPointer(axisInfo, newValue, payloadBatch);
|
||
|
// 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().mapDimensionsAll(dim);
|
||
|
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, outputPayload) {
|
||
|
var outputAxesInfo = outputPayload.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
|
||
|
// convenient to fetch payload.seriesIndex and payload.dataIndex
|
||
|
// directly. 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 should 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' && axisInfo.triggerEmphasis && each(option.seriesDataIndices, function (batchItem) {
|
||
|
var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex;
|
||
|
newHighlights[key] = batchItem;
|
||
|
});
|
||
|
});
|
||
|
// Diff.
|
||
|
var toHighlight = [];
|
||
|
var toDownplay = [];
|
||
|
each(lastHighlights, function (batchItem, key) {
|
||
|
!newHighlights[key] && toDownplay.push(batchItem);
|
||
|
});
|
||
|
each(newHighlights, function (batchItem, key) {
|
||
|
!lastHighlights[key] && toHighlight.push(batchItem);
|
||
|
});
|
||
|
toDownplay.length && api.dispatchAction({
|
||
|
type: 'downplay',
|
||
|
escapeConnect: true,
|
||
|
// Not blur others when highlight in axisPointer.
|
||
|
notBlur: true,
|
||
|
batch: toDownplay
|
||
|
});
|
||
|
toHighlight.length && api.dispatchAction({
|
||
|
type: 'highlight',
|
||
|
escapeConnect: true,
|
||
|
// Not blur others when highlight in axisPointer.
|
||
|
notBlur: 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]);
|
||
|
}
|