blob: 687beedcf679a2712d804c14f4a9ee63cb020796 [file]
/*
* 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 * as graphic from '../../util/graphic';
import SymbolClz from './Symbol';
import { isObject } from 'zrender/src/core/util';
import SeriesData from '../../data/SeriesData';
import type Displayable from 'zrender/src/graphic/Displayable';
import {
StageHandlerProgressParams,
LabelOption,
ZRStyleProps,
BlurScope,
DisplayState,
DefaultEmphasisFocus,
NullUndefined
} from '../../util/types';
import Model from '../../model/Model';
import { ScatterSeriesOption } from '../scatter/ScatterSeries';
import { getLabelStatesModels } from '../../label/labelStyle';
import Element from 'zrender/src/Element';
import SeriesModel from '../../model/Series';
import { ISymbolDraw, ListForSymbolDraw, SymbolDrawItemModelOption, SymbolDrawUpdateOpt } from './baseDraw';
interface SymbolLike extends graphic.Group {
updateData(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: SymbolDrawUpdateOpt): void
fadeOut?(cb: () => void, seriesModel: SeriesModel): void
}
interface SymbolLikeCtor {
new(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: SymbolDrawUpdateOpt): SymbolLike
}
function symbolNeedsDraw(data: SeriesData, point: number[], idx: number, opt: SymbolDrawUpdateOpt | NullUndefined) {
return point && !isNaN(point[0]) && !isNaN(point[1])
&& !(opt && opt.isIgnore && opt.isIgnore(idx))
// We do not set clipShape on group, because it will cut part of
// the symbol element shape. We use the same clip shape here as
// the line clip.
&& !(opt && opt.clipShape && !opt.clipShape.contain(point[0], point[1]))
&& data.getItemVisual(idx, 'symbol') !== 'none';
}
function normalizeUpdateOpt(opt: SymbolDrawUpdateOpt) {
if (opt != null && !isObject(opt)) {
opt = {isIgnore: opt};
}
return opt || {};
}
export interface SymbolDrawSeriesScope {
emphasisItemStyle?: ZRStyleProps
blurItemStyle?: ZRStyleProps
selectItemStyle?: ZRStyleProps
focus?: DefaultEmphasisFocus
blurScope?: BlurScope
emphasisDisabled?: boolean
labelStatesModels: Record<DisplayState, Model<LabelOption>>
itemModel?: Model<SymbolDrawItemModelOption>
hoverScale?: boolean | number
cursorStyle?: string
fadeIn?: boolean
}
function makeSeriesScope(data: SeriesData): SymbolDrawSeriesScope {
const seriesModel = data.hostModel as Model<ScatterSeriesOption>;
const emphasisModel = seriesModel.getModel('emphasis');
return {
emphasisItemStyle: emphasisModel.getModel('itemStyle').getItemStyle(),
blurItemStyle: seriesModel.getModel(['blur', 'itemStyle']).getItemStyle(),
selectItemStyle: seriesModel.getModel(['select', 'itemStyle']).getItemStyle(),
focus: emphasisModel.get('focus'),
blurScope: emphasisModel.get('blurScope'),
emphasisDisabled: emphasisModel.get('disabled'),
hoverScale: emphasisModel.get('scale'),
labelStatesModels: getLabelStatesModels(seriesModel),
cursorStyle: seriesModel.get('cursor')
};
}
function createEl(
SymbolCtor: SymbolLikeCtor,
data: SeriesData,
newIdx: number,
seriesScope: SymbolDrawSeriesScope,
symbolUpdateOpt: SymbolDrawUpdateOpt,
point: number[],
group: graphic.Group
): SymbolLike {
const symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt);
symbolEl.setPosition(point);
data.setItemGraphicEl(newIdx, symbolEl);
group.add(symbolEl);
return symbolEl;
}
class SymbolDraw implements ISymbolDraw {
group = new graphic.Group();
private _data: ListForSymbolDraw;
private _SymbolCtor: SymbolLikeCtor;
private _seriesScope: SymbolDrawSeriesScope;
private _getSymbolPoint: SymbolDrawUpdateOpt['getSymbolPoint'];
private _progressiveEls: SymbolLike[];
constructor(SymbolCtor?: SymbolLikeCtor) {
this._SymbolCtor = SymbolCtor || SymbolClz as SymbolLikeCtor;
}
/**
* Update symbols draw by new data
*/
updateData(data: ListForSymbolDraw, opt?: SymbolDrawUpdateOpt) {
// Remove progressive els.
this._progressiveEls = null;
opt = normalizeUpdateOpt(opt);
const group = this.group;
const seriesModel = data.hostModel;
const oldData = this._data;
const SymbolCtor = this._SymbolCtor;
const disableAnimation = opt.disableAnimation;
const seriesScope = this._seriesScope = makeSeriesScope(data);
const symbolUpdateOpt = { disableAnimation };
const getSymbolPoint = opt.getSymbolPoint || function (idx: number) {
return data.getItemLayout(idx);
};
// There is no oldLineData only when first rendering or switching from
// stream mode to normal mode, where previous elements should be removed.
if (!oldData) {
group.removeAll();
}
data.diff(oldData)
.add(function (newIdx) {
const point = getSymbolPoint(newIdx);
if (symbolNeedsDraw(data, point, newIdx, opt)) {
createEl(SymbolCtor, data, newIdx, seriesScope, symbolUpdateOpt, point, group);
}
})
.update(function (newIdx, oldIdx) {
let symbolEl = oldData.getItemGraphicEl(oldIdx) as SymbolLike;
const point = getSymbolPoint(newIdx) as number[];
if (!symbolNeedsDraw(data, point, newIdx, opt)) {
group.remove(symbolEl);
return;
}
const newSymbolType = data.getItemVisual(newIdx, 'symbol') || 'circle';
const oldSymbolType = symbolEl
&& (symbolEl as SymbolClz).getSymbolType
&& (symbolEl as SymbolClz).getSymbolType();
if (!symbolEl
// Create a new if symbol type changed.
|| (oldSymbolType && oldSymbolType !== newSymbolType)
) {
group.remove(symbolEl);
symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt);
symbolEl.setPosition(point);
}
else {
symbolEl.updateData(data, newIdx, seriesScope, symbolUpdateOpt);
const target = {
x: point[0],
y: point[1]
};
disableAnimation
? symbolEl.attr(target)
: graphic.updateProps(symbolEl, target, seriesModel);
}
// Add back
group.add(symbolEl);
data.setItemGraphicEl(newIdx, symbolEl);
})
.remove(function (oldIdx) {
const el = oldData.getItemGraphicEl(oldIdx) as SymbolLike;
el && el.fadeOut(function () {
group.remove(el);
}, seriesModel as SeriesModel);
})
.execute();
this._getSymbolPoint = getSymbolPoint;
this._data = data;
};
updateLayout(opt?: SymbolDrawUpdateOpt) {
const data = this._data;
if (!data) {
return;
}
const symbolDraw = this;
const store = data.getStore();
for (let idx = 0, len = store.count(); idx < len; idx++) {
let el = data.getItemGraphicEl(idx);
const point = symbolDraw._getSymbolPoint(idx);
// FIXME: [FIXME_SYMBOL_CLIP_CONSIDERING_COORD_SYS_ALIGNMENT_DURING_ANIMATION]
// See VIEW_COORD_SYS animation (by center/zoom change) and FIXME_VIEW_COORD_SYS_SYNC_BACK.
// If we create symbols when roaming them into the clip area, they can not be laid out
// based on the intermediate state but can only based on the final state of VIEW_COORD_SYS,
// and introduce visual artifacts.
// Consider clip, need to handle create or remove.
if (symbolNeedsDraw(data, point, idx, opt)) {
el = el || createEl(
symbolDraw._SymbolCtor,
data,
idx,
symbolDraw._seriesScope,
{disableAnimation: true},
point,
symbolDraw.group
);
el.stopAnimation();
el.setPosition(point);
el.markRedraw();
}
else if (el) {
symbolDraw.group.remove(el);
data.setItemGraphicEl(idx, null);
}
}
};
incrementalPrepareUpdate(data: ListForSymbolDraw) {
this._seriesScope = makeSeriesScope(data);
this._data = null;
this.group.removeAll();
};
incrementalUpdate(
taskParams: StageHandlerProgressParams,
data: ListForSymbolDraw,
incrementalId: Displayable['incremental'],
opt?: SymbolDrawUpdateOpt
) {
// Clear
this._progressiveEls = [];
opt = normalizeUpdateOpt(opt);
function updateIncrementalAndHover(el: Displayable) {
if (!el.isGroup) {
el.incremental = incrementalId;
el.ensureState('emphasis').hoverLayer = graphic.HOVER_LAYER_FOR_INCREMENTAL;
}
}
for (let idx = taskParams.start; idx < taskParams.end; idx++) {
const point = data.getItemLayout(idx) as number[];
if (symbolNeedsDraw(data, point, idx, opt)) {
const el = new this._SymbolCtor(data, idx, this._seriesScope);
el.traverse(updateIncrementalAndHover);
el.setPosition(point);
this.group.add(el);
data.setItemGraphicEl(idx, el);
this._progressiveEls.push(el);
}
}
};
eachRendered(cb: (el: Element) => boolean | void) {
graphic.traverseElements(this._progressiveEls || this.group, cb);
}
remove(enableAnimation?: boolean) {
const group = this.group;
const data = this._data;
// Incremental model do not have this._data.
if (data && enableAnimation) {
data.eachItemGraphicEl(function (el: SymbolLike) {
el.fadeOut(function () {
group.remove(el);
}, data.hostModel as SeriesModel);
});
}
else {
group.removeAll();
}
};
}
export default SymbolDraw;