提交 b9c7e9e5 编写于 作者: 1 100pah

feature: [tooltip]

(1) Make component tooltip inherit cascade correct: itemOption.tooltip -> componentOption.tooltip -> globalOption.tooltip
(previous incorrect cascade: itemOption.tooltip -> globalOption.tooltip
(2) Enable trigger component tooltip by chart.dispatchAction({ type: 'showTip', legendIndex: 0, name: 'some' });

To make (2) happen, this commit migrate `ECElement['tooltip']` to ECData['tooltipConfig']['option'],
and add other info in ECData['tooltipConfig']:
{
      componentMainType
      componentIndex
      name
}
to locate a tooltipable component element by a payload.
上级 13f1f032
......@@ -422,8 +422,6 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', AxisElementsBu
opt.nameTruncateMaxWidth, truncateOpt.maxWidth, axisNameAvailableWidth
);
const tooltipOpt = axisModel.get('tooltip', true);
const mainType = axisModel.mainType;
const formatterParams: LabelFormatterParams = {
componentType: mainType,
......@@ -452,15 +450,16 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', AxisElementsBu
}),
z2: 1
}) as AxisLabelText;
textEl.tooltip = (tooltipOpt && tooltipOpt.show)
? extend({
getECData(textEl).tooltipConfig = {
componentMainType: axisModel.mainType,
componentIndex: axisModel.componentIndex,
name: name,
option: {
content: name,
formatter() {
return name;
},
formatter: () => name,
formatterParams: formatterParams
}, tooltipOpt)
: null;
}
};
textEl.__fullText = name;
// Id for animation
textEl.anid = 'name';
......
......@@ -63,6 +63,11 @@ export interface BrushAreaParam extends ModelFinderObject {
// coord ranges, used in multiple cartesian in one grid.
// Only for output to users.
coordRanges?: BrushAreaRange[];
__rangeOffset?: {
offset: BrushDimensionMinMax[] | BrushDimensionMinMax,
xyMinMax: BrushDimensionMinMax[]
}
}
/**
......
......@@ -33,7 +33,6 @@ import {
ZRColor,
ItemStyleOption,
ZRRectLike,
ECElement,
CommonTooltipOption,
ColorString
} from '../../util/types';
......@@ -42,6 +41,7 @@ import Displayable, { DisplayableState } from 'zrender/src/graphic/Displayable';
import { PathStyleProps } from 'zrender/src/graphic/Path';
import { parse, stringify } from 'zrender/src/tool/color';
import {PatternObject} from 'zrender/src/graphic/Pattern';
import { getECData } from '../../util/innerStore';
const curry = zrUtil.curry;
const each = zrUtil.each;
......@@ -353,8 +353,6 @@ class LegendView extends ComponentView {
const itemIcon = itemModel.get('icon');
const tooltipModel = itemModel.getModel('tooltip') as Model<CommonTooltipOption<LegendTooltipFormatterParams>>;
const legendGlobalTooltipModel = tooltipModel.parentModel;
// Use user given icon first
legendSymbolType = itemIcon || legendSymbolType;
......@@ -432,22 +430,25 @@ class LegendView extends ComponentView {
shape: itemGroup.getBoundingRect(),
invisible: true
});
const tooltipModel = itemModel.getModel('tooltip') as Model<CommonTooltipOption<LegendTooltipFormatterParams>>;
if (tooltipModel.get('show')) {
const componentIndex = legendModel.componentIndex;
const formatterParams: LegendTooltipFormatterParams = {
componentType: 'legend',
legendIndex: legendModel.componentIndex,
legendIndex: componentIndex,
name: name,
$vars: ['name']
};
(hitRect as ECElement).tooltip = zrUtil.extend({
content: name,
// Defaul formatter
formatter: legendGlobalTooltipModel.get('formatter', true)
|| function (params: LegendTooltipFormatterParams) {
return params.name;
},
formatterParams: formatterParams
}, tooltipModel.option);
getECData(hitRect).tooltipConfig = {
componentMainType: legendModel.mainType,
componentIndex: componentIndex,
name: name,
option: zrUtil.defaults({
content: name,
formatterParams: formatterParams
}, tooltipModel.option)
};
}
itemGroup.add(hitRect);
......
......@@ -148,7 +148,8 @@ class ToolboxModel extends ComponentModel<ToolboxOption> {
// feature
tooltip: {
show: false
show: false,
position: 'bottom'
}
};
}
......
......@@ -28,7 +28,7 @@ import ComponentView from '../../view/Component';
import ToolboxModel from './ToolboxModel';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import { DisplayState, Dictionary, ECElement, Payload } from '../../util/types';
import { DisplayState, Dictionary, Payload } from '../../util/types';
import {
ToolboxFeature,
getFeature,
......@@ -39,6 +39,7 @@ import {
import { getUID } from '../../util/component';
import Displayable from 'zrender/src/graphic/Displayable';
import ZRText from 'zrender/src/graphic/Text';
import { getECData } from '../../util/innerStore';
type IconPath = ToolboxFeatureModel['iconPaths'][string];
......@@ -224,23 +225,20 @@ class ToolboxView extends ComponentView {
});
path.setTextContent(textContent);
const tooltipModel = toolboxModel.getModel('tooltip');
if (tooltipModel && tooltipModel.get('show')) {
(path as ECElement).tooltip = zrUtil.extend({
getECData(path).tooltipConfig = {
componentMainType: toolboxModel.mainType,
componentIndex: toolboxModel.componentIndex,
name: iconName,
option: {
content: titlesMap[iconName],
formatter: tooltipModel.get('formatter', true)
|| function () {
return titlesMap[iconName];
},
formatterParams: {
componentType: 'toolbox',
name: iconName,
title: titlesMap[iconName],
$vars: ['name', 'title']
},
position: tooltipModel.get('position', true) || 'bottom'
}, tooltipModel.option);
}
}
}
};
// graphic.enableHoverEmphasis(path);
......
......@@ -29,7 +29,7 @@ import Model from '../../model/Model';
import * as globalListener from '../axisPointer/globalListener';
import * as axisHelper from '../../coord/axisHelper';
import * as axisPointerViewHelper from '../axisPointer/viewHelper';
import { getTooltipRenderMode } from '../../util/model';
import { getTooltipRenderMode, preParseFinder, queryReferringComponents } from '../../util/model';
import ComponentView from '../../view/Component';
import { format as timeFormat } from '../../util/time';
import {
......@@ -41,7 +41,9 @@ import {
TooltipRenderMode,
ECElement,
CommonTooltipOption,
ZRColor
ZRColor,
ComponentMainType,
ComponentItemTooltipOption
} from '../../util/types';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
......@@ -49,12 +51,13 @@ import TooltipModel, {TooltipOption} from './TooltipModel';
import Element from 'zrender/src/Element';
import { AxisBaseModel } from '../../coord/AxisBaseModel';
// import { isDimensionStacked } from '../../data/helper/dataStackHelper';
import { getECData } from '../../util/innerStore';
import { ECData, getECData } from '../../util/innerStore';
import { shouldTooltipConfine } from './helper';
import { DataByCoordSys, DataByAxis } from '../axisPointer/axisTrigger';
import { normalizeTooltipFormatResult } from '../../model/mixin/dataFormat';
import { createTooltipMarkup, buildTooltipMarkup, TooltipMarkupStyleCreator } from './tooltipMarkup';
import { findEventDispatcher } from '../../util/event';
import ComponentModel from '../../model/Component';
const bind = zrUtil.bind;
const each = zrUtil.each;
......@@ -76,7 +79,7 @@ interface ShowTipPayload {
from?: string
// Type 1
tooltip?: ECElement['tooltip']
tooltip?: ECData['tooltipConfig']['option']
// Type 2
dataByCoordSys?: DataByCoordSys[]
......@@ -86,6 +89,11 @@ interface ShowTipPayload {
seriesIndex?: number
dataIndex?: number
// Type 4
name?: string // target item name that enable tooltip.
// legendIndex: 0,
// toolboxId: 'some_id',
// geoName: 'some_name',
x?: number
y?: number
......@@ -112,7 +120,7 @@ interface TryShowParams {
*/
dataByCoordSys?: DataByCoordSys[]
tooltipOption?: CommonTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]>
tooltipOption?: ComponentItemTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]>
position?: TooltipOption['position']
}
......@@ -287,12 +295,29 @@ class TooltipView extends ComponentView {
// When triggered from axisPointer.
const dataByCoordSys = payload.dataByCoordSys;
if (payload.tooltip && payload.x != null && payload.y != null) {
const cmptRef = findComponentReference(payload, ecModel, api);
if (cmptRef) {
const rect = cmptRef.el.getBoundingRect().clone();
rect.applyTransform(cmptRef.el.transform);
this._tryShow({
offsetX: rect.x + rect.width / 2,
offsetY: rect.y + rect.height / 2,
target: cmptRef.el,
position: payload.position || 'bottom'
}, dispatchAction);
}
else if (payload.tooltip && payload.x != null && payload.y != null) {
const el = proxyRect as unknown as ECElement;
el.x = payload.x;
el.y = payload.y;
el.update();
el.tooltip = payload.tooltip;
getECData(el).tooltipConfig = {
componentMainType: null,
componentIndex: null,
name: null,
option: payload.tooltip
};
// Manually show tooltip while view is not using zrender elements.
this._tryShow({
offsetX: payload.x,
......@@ -428,15 +453,33 @@ class TooltipView extends ComponentView {
if (dataByCoordSys && dataByCoordSys.length) {
this._showAxisTooltip(dataByCoordSys, e);
}
// Always show item tooltip if mouse is on the element with dataIndex
else if (el && findEventDispatcher(el, (target) => getECData(target).dataIndex != null, true)) {
this._lastDataByCoordSys = null;
this._showSeriesItemTooltip(e, el, dispatchAction);
}
// Tooltip provided directly. Like legend.
else if (el && el.tooltip) {
else if (el) {
this._lastDataByCoordSys = null;
this._showComponentItemTooltip(e, el, dispatchAction);
let seriesDispatcher: Element;
let cmptDispatcher: Element;
findEventDispatcher(el, (target) => {
// Always show item tooltip if mouse is on the element with dataIndex
if (getECData(target).dataIndex != null) {
seriesDispatcher = target;
return true;
}
// Tooltip provided directly. Like legend.
if (getECData(target).tooltipConfig != null) {
cmptDispatcher = target;
return true;
}
}, true);
if (seriesDispatcher) {
this._showSeriesItemTooltip(e, seriesDispatcher, dispatchAction);
}
else if (cmptDispatcher) {
this._showComponentItemTooltip(e, cmptDispatcher, dispatchAction);
}
else {
this._hide(dispatchAction);
}
}
else {
this._lastDataByCoordSys = null;
......@@ -573,10 +616,9 @@ class TooltipView extends ComponentView {
private _showSeriesItemTooltip(
e: TryShowParams,
el: ECElement,
dispatcher: ECElement,
dispatchAction: ExtensionAPI['dispatchAction']
) {
const dispatcher = findEventDispatcher(el, (target) => getECData(target).dataIndex != null, true);
const ecModel = this._ecModel;
const ecData = getECData(dispatcher);
// Use dataModel in element if possible
......@@ -653,7 +695,8 @@ class TooltipView extends ComponentView {
el: ECElement,
dispatchAction: ExtensionAPI['dispatchAction']
) {
let tooltipOpt = el.tooltip;
const tooltipConfig = getECData(el).tooltipConfig;
let tooltipOpt = tooltipConfig.option;
if (zrUtil.isString(tooltipOpt)) {
const content = tooltipOpt;
tooltipOpt = {
......@@ -662,7 +705,14 @@ class TooltipView extends ComponentView {
formatter: content
};
}
const subTooltipModel = new Model(tooltipOpt, this._tooltipModel, this._ecModel);
const tooltipModelCascade = [tooltipOpt] as Parameters<typeof buildTooltipModel>[0];
const cmpt = this._ecModel.getComponent(tooltipConfig.componentMainType, tooltipConfig.componentIndex);
cmpt && tooltipModelCascade.push(cmpt);
tooltipModelCascade.push(this._tooltipModel);
const subTooltipModel = buildTooltipModel(tooltipModelCascade) as Model<ComponentItemTooltipOption<unknown>>;
const defaultHtml = subTooltipModel.get('content');
const asyncTicket = Math.random() + '';
// PENDING: this case do not support richText style yet.
......@@ -908,13 +958,13 @@ class TooltipView extends ComponentView {
}
type TooltipableOption = {
tooltip?: Omit<TooltipOption, 'mainType'> | string
tooltip?: CommonTooltipOption<unknown>;
};
/**
* From top to bottom. (the last one should be globalTooltipModel);
*/
function buildTooltipModel(modelCascade: (
TooltipModel | Model<TooltipableOption> | Omit<TooltipOption, 'mainType'> | string
TooltipModel | Model<TooltipableOption> | CommonTooltipOption<unknown> | ComponentModel | string
)[]) {
// Last is always tooltip model.
let resultModel = modelCascade.pop() as Model<TooltipOption>;
......@@ -1036,4 +1086,60 @@ function isCenterAlign(align: HorizontalAlign | VerticalAlign) {
return align === 'center' || align === 'middle';
}
/**
* Find target component by payload like:
* ```js
* { legendId: 'some_id', name: 'xxx' }
* { toolboxIndex: 1, name: 'xxx' }
* { geoName: 'some_name', name: 'xxx' }
* ```
* PENDING: at present only
*
* If not found, return null/undefined.
*/
function findComponentReference(
payload: ShowTipPayload,
ecModel: GlobalModel,
api: ExtensionAPI
): {
componentMainType: ComponentMainType;
componentIndex: number;
el: ECElement;
} {
const { queryOptionMap } = preParseFinder(payload);
const componentMainType = queryOptionMap.keys()[0];
if (!componentMainType) {
return;
}
const queryResult = queryReferringComponents(
ecModel,
componentMainType,
queryOptionMap.get(componentMainType),
{ useDefault: false, enableAll: false, enableNone: false }
);
const model = queryResult.models[0];
if (!model) {
return;
}
const view = api.getViewOfComponentModel(model);
let el: ECElement;
view.group.traverse((subEl: ECElement) => {
const tooltipConfig = getECData(subEl).tooltipConfig;
if (tooltipConfig && tooltipConfig.name === payload.name) {
el = subEl;
return true; // stop
}
});
if (el) {
return {
componentMainType,
componentIndex: model.componentIndex,
el
};
}
}
export default TooltipView;
......@@ -18,7 +18,10 @@
*/
import Element from 'zrender/src/Element';
import { DataModel, ECEventData, BlurScope, InnerFocus, SeriesDataType } from './types';
import {
DataModel, ECEventData, BlurScope, InnerFocus, SeriesDataType,
ComponentMainType, ComponentItemTooltipOption
} from './types';
import { makeInner } from './model';
/**
* ECData stored on graphic element
......@@ -31,5 +34,16 @@ export interface ECData {
dataType?: SeriesDataType;
focus?: InnerFocus;
blurScope?: BlurScope;
tooltipConfig?: {
// Used to find component tooltip option, which is used as
// the parent of tooltipConfig.option for cascading.
// If not provided, do not use component as its parent.
// (Set manatary to make developers not to forget them).
componentMainType: ComponentMainType;
componentIndex: number;
// Target item name to locate tooltip.
name: string;
option: ComponentItemTooltipOption<unknown>;
};
}
export const getECData = makeInner<ECData, Element>();
......@@ -774,8 +774,8 @@ export type ModelFinderObject = {
xAxisIndex?: ModelFinderIndexQuery, xAxisId?: ModelFinderIdQuery, xAxisName?: ModelFinderNameQuery
yAxisIndex?: ModelFinderIndexQuery, yAxisId?: ModelFinderIdQuery, yAxisName?: ModelFinderNameQuery
gridIndex?: ModelFinderIndexQuery, gridId?: ModelFinderIdQuery, gridName?: ModelFinderNameQuery
// ... (can be extended)
[key: string]: unknown
dataIndex?: number, dataIndexInside?: number
// ... (can be extended)
};
/**
* {
......@@ -819,6 +819,43 @@ export function parseFinder(
enableNone?: boolean;
}
): ParsedModelFinder {
const { mainTypeSpecified, queryOptionMap, others } = preParseFinder(finderInput, opt);
const result = others as ParsedModelFinderKnown;
const defaultMainType = opt ? opt.defaultMainType : null;
if (!mainTypeSpecified && defaultMainType) {
queryOptionMap.set(defaultMainType, {});
}
queryOptionMap.each(function (queryOption, mainType) {
const queryResult = queryReferringComponents(
ecModel,
mainType,
queryOption,
{
useDefault: defaultMainType === mainType,
enableAll: (opt && opt.enableAll != null) ? opt.enableAll : true,
enableNone: (opt && opt.enableNone != null) ? opt.enableNone : true
}
);
result[mainType + 'Models'] = queryResult.models;
result[mainType + 'Model'] = queryResult.models[0];
});
return result;
}
export function preParseFinder(
finderInput: ModelFinder,
opt?: {
// If pervided, types out of this list will be ignored.
includeMainTypes?: ComponentMainType[];
}
): {
mainTypeSpecified: boolean;
queryOptionMap: HashMap<QueryReferringUserOption, ComponentMainType>;
others: Partial<Pick<ParsedModelFinderKnown, 'dataIndex' | 'dataIndexInside'>>
} {
let finder: ModelFinderObject;
if (isString(finderInput)) {
const obj = {};
......@@ -830,13 +867,13 @@ export function parseFinder(
}
const queryOptionMap = createHashMap<QueryReferringUserOption, ComponentMainType>();
const result = {} as ParsedModelFinderKnown;
const others = {} as Partial<Pick<ParsedModelFinderKnown, 'dataIndex' | 'dataIndexInside'>>;
let mainTypeSpecified = false;
each(finder, function (value, key) {
// Exclude 'dataIndex' and other illgal keys.
if (key === 'dataIndex' || key === 'dataIndexInside') {
result[key] = value as number;
others[key] = value as number;
return;
}
......@@ -858,29 +895,10 @@ export function parseFinder(
queryOption[queryType] = value as any;
});
const defaultMainType = opt ? opt.defaultMainType : null;
if (!mainTypeSpecified && defaultMainType) {
queryOptionMap.set(defaultMainType, {});
}
queryOptionMap.each(function (queryOption, mainType) {
const queryResult = queryReferringComponents(
ecModel,
mainType,
queryOption,
{
useDefault: defaultMainType === mainType,
enableAll: (opt && opt.enableAll != null) ? opt.enableAll : true,
enableNone: (opt && opt.enableNone != null) ? opt.enableNone : true
}
);
result[mainType + 'Models'] = queryResult.models;
result[mainType + 'Model'] = queryResult.models[0];
});
return result;
return { mainTypeSpecified, queryOptionMap, others };
}
export type QueryReferringUserOption = {
index?: ModelFinderIndexQuery,
id?: ModelFinderIdQuery,
......
......@@ -46,6 +46,7 @@ import { PathStyleProps } from 'zrender/src/graphic/Path';
import { ImageStyleProps } from 'zrender/src/graphic/Image';
import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text';
import { Source } from '../data/Source';
import Model from '../model/Model';
......@@ -101,10 +102,6 @@ export interface ComponentTypeInfo {
}
export interface ECElement extends Element {
tooltip?: CommonTooltipOption<unknown> & {
content?: string;
formatterParams?: unknown;
};
highDownSilentOnTouch?: boolean;
onHoverStateChange?: (toState: DisplayState) => void;
......@@ -130,7 +127,7 @@ export interface DataHost {
getData(dataType?: SeriesDataType): List;
}
export interface DataModel extends DataHost, DataFormatMixin {}
export interface DataModel extends Model<unknown>, DataHost, DataFormatMixin {}
// Pick<DataHost, 'getData'>,
// Pick<DataFormatMixin, 'getDataParams' | 'formatTooltip'> {}
......@@ -1348,6 +1345,12 @@ export interface CommonTooltipOption<FormatterParams> {
}
}
export type ComponentItemTooltipOption<T> = CommonTooltipOption<T> & {
// Default content HTML.
content?: string;
formatterParams?: unknown;
};
/**
* Tooltip option configured on each series
*/
......@@ -1355,6 +1358,9 @@ export type SeriesTooltipOption = CommonTooltipOption<CallbackDataParams> & {
trigger?: 'item' | 'axis' | boolean | 'none'
};
type LabelFormatterParams = {
value: ScaleDataValue
axisDimension: string
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册