NuclearDispersionSystem/ant-design-vue-jeecg/node_modules/@antv/g2/esm/chart/view.js
2023-09-14 14:47:11 +08:00

1476 lines
52 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { __assign, __extends, __spreadArrays } from "tslib";
import { clone, deepMix, each, filter, find, flatten, get, isBoolean, isFunction, isNil, isObject, isString, isUndefined, mix, remove, set, size, uniqueId, isEqual, } from '@antv/util';
import { GROUP_Z_INDEX, LAYER, PLOT_EVENTS, VIEW_LIFE_CIRCLE } from '../constant';
import Base from '../base';
import { getFacet } from '../facet';
import { createInteraction } from '../interaction';
import { getTheme } from '../theme';
import { BBox } from '../util/bbox';
import { getCoordinateClipCfg, isFullCircle, isPointInCoordinate } from '../util/coordinate';
import { uniq } from '../util/helper';
import { findDataByPoint } from '../util/tooltip';
import { getComponentController, getComponentControllerNames } from './controller';
import CoordinateController from './controller/coordinate';
import Event from './event';
import defaultLayout from './layout';
import { ScalePool } from './util/scale-pool';
/**
* G2 视图 View 类
*/
var View = /** @class */ (function (_super) {
__extends(View, _super);
function View(props) {
var _this = _super.call(this, { visible: props.visible }) || this;
/** view id全局唯一。 */
_this.id = uniqueId('view');
/** 所有的子 view。 */
_this.views = [];
/** 所有的 geometry 实例。 */
_this.geometries = [];
/** 所有的组件 controllers。 */
_this.controllers = [];
/** 所有的 Interaction 实例。 */
_this.interactions = {};
/** 是否对超出坐标系范围的 Geometry 进行剪切 */
_this.limitInPlot = false;
// 配置信息存储
_this.options = {
data: [],
animate: true,
}; // 初始化为空
/** 配置开启的组件插件,默认为全局配置的组件。 */
_this.usedControllers = getComponentControllerNames();
/** 所有的 scales */
_this.scalePool = new ScalePool();
/** 布局函数 */
_this.layoutFunc = defaultLayout;
/** 当前鼠标是否在 plot 内CoordinateBBox */
_this.isPreMouseInPlot = false;
/** 默认标识位,用于判定数据是否更新 */
_this.isDataChanged = false;
/** 用于判断坐标系范围是否发生变化的标志位 */
_this.isCoordinateChanged = false;
/** 从当前这个 view 创建的 scale key */
_this.createdScaleKeys = new Map();
_this.onCanvasEvent = function (evt) {
var name = evt.name;
if (!name.includes(':')) { // 非委托事件
var e = _this.createViewEvent(evt);
// 处理 plot 事件
_this.doPlotEvent(e);
_this.emit(name, e);
}
};
/**
* 触发事件之后
* @param evt
*/
_this.onDelegateEvents = function (evt) {
// 阻止继续冒泡,防止重复事件触发
// evt.preventDefault();
var name = evt.name;
if (!name.includes(':')) {
return;
}
// 事件在 view 嵌套中冒泡(暂不提供阻止冒泡的机制)
var e = _this.createViewEvent(evt);
// 包含有基本事件、组合事件
_this.emit(name, e);
// const currentTarget = evt.currentTarget as IShape;
// const inheritNames = currentTarget.get('inheritNames');
// if (evt.delegateObject || inheritNames) {
// const events = this.getEvents();
// each(inheritNames, (subName) => {
// const eventName = `${subName}:${type}`;
// if (events[eventName]) {
// this.emit(eventName, e);
// }
// });
// }
};
var parent = props.parent, canvas = props.canvas, backgroundGroup = props.backgroundGroup, middleGroup = props.middleGroup, foregroundGroup = props.foregroundGroup, _a = props.region, region = _a === void 0 ? { start: { x: 0, y: 0 }, end: { x: 1, y: 1 } } : _a, padding = props.padding, theme = props.theme, options = props.options, limitInPlot = props.limitInPlot;
_this.parent = parent;
_this.canvas = canvas;
_this.backgroundGroup = backgroundGroup;
_this.middleGroup = middleGroup;
_this.foregroundGroup = foregroundGroup;
_this.region = region;
_this.padding = padding;
// 接受父 view 传入的参数
_this.options = __assign(__assign({}, _this.options), options);
_this.limitInPlot = limitInPlot;
// 初始化 theme
_this.themeObject = isObject(theme) ? deepMix({}, getTheme('default'), theme) : getTheme(theme);
_this.init();
return _this;
}
/**
* 设置 layout 布局函数
* @param layout 布局函数
* @returns void
*/
View.prototype.setLayout = function (layout) {
this.layoutFunc = layout;
};
/**
* 生命周期:初始化
* @returns voids
*/
View.prototype.init = function () {
// 计算画布的 viewBBox
this.calculateViewBBox();
// 事件委托机制
this.initEvents();
// 初始化组件 controller
this.initComponentController();
// 创建 coordinate controller
this.coordinateController = new CoordinateController(this.options.coordinate);
this.initOptions();
// 递归初始化子 view
var views = this.views;
for (var i = 0; i < views.length; i++) {
views[i].init();
}
};
/**
* 生命周期:渲染流程,渲染过程需要处理数据更新的情况。
* render 函数仅仅会处理 view 和子 view。
* @param isUpdate 是否触发更新流程。
*/
View.prototype.render = function (isUpdate) {
if (isUpdate === void 0) { isUpdate = false; }
this.emit(VIEW_LIFE_CIRCLE.BEFORE_RENDER);
// 递归渲染
this.paint(isUpdate);
this.emit(VIEW_LIFE_CIRCLE.AFTER_RENDER);
if (this.visible === false) {
// 用户在初始化的时候声明 visible: false
this.changeVisible(false);
}
};
/**
* 生命周期清空图表上所有的绘制内容但是不销毁图表chart 仍可使用。
* @returns void
*/
View.prototype.clear = function () {
var _this = this;
this.emit(VIEW_LIFE_CIRCLE.BEFORE_CLEAR);
// 1. 清空缓存和计算数据
this.filteredData = [];
this.coordinateInstance = undefined;
this.isDataChanged = false; // 复位
this.isCoordinateChanged = false; // 复位
// 2. 清空 geometries
var geometries = this.geometries;
for (var i = 0; i < geometries.length; i++) {
geometries[i].clear();
}
this.geometries = [];
// 3. 清空 controllers
var controllers = this.controllers;
for (var i = 0; i < controllers.length; i++) {
controllers[i].clear();
}
// 4. 删除 scale 缓存
this.createdScaleKeys.forEach(function (v, k) {
_this.getRootView().scalePool.deleteScale(k);
});
this.createdScaleKeys.clear();
// 递归处理子 view
var views = this.views;
for (var i = 0; i < views.length; i++) {
views[i].clear();
}
this.emit(VIEW_LIFE_CIRCLE.AFTER_CLEAR);
};
/**
* 生命周期:销毁,完全无法使用。
* @returns void
*/
View.prototype.destroy = function () {
// 销毁前事件,销毁之后已经没有意义了,所以不抛出事件
this.emit(VIEW_LIFE_CIRCLE.BEFORE_DESTROY);
var interactions = this.interactions;
// 销毁 interactions
each(interactions, function (interaction) {
if (interaction) {
// 有可能已经销毁,设置了 undefined
interaction.destroy();
}
});
this.clear();
// 销毁 controller 中的组件
var controllers = this.controllers;
for (var i = 0, len = controllers.length; i < len; i++) {
var controller = controllers[i];
controller.destroy();
}
this.backgroundGroup.remove(true);
this.middleGroup.remove(true);
this.foregroundGroup.remove(true);
_super.prototype.destroy.call(this);
};
/* end 生命周期函数 */
/**
* 显示或者隐藏整个 view。
* @param visible 是否可见
* @returns View
*/
View.prototype.changeVisible = function (visible) {
_super.prototype.changeVisible.call(this, visible);
var geometries = this.geometries;
for (var i = 0, len = geometries.length; i < len; i++) {
var geometry = geometries[i];
geometry.changeVisible(visible);
}
var controllers = this.controllers;
for (var i = 0, len = controllers.length; i < len; i++) {
var controller = controllers[i];
controller.changeVisible(visible);
}
this.foregroundGroup.set('visible', visible);
this.middleGroup.set('visible', visible);
this.backgroundGroup.set('visible', visible);
// group.set('visible', visible) 不会触发自动刷新
this.getCanvas().draw();
return this;
};
/**
* 装载数据源。
*
* ```ts
* view.data([{ city: '杭州', sale: 100 }, { city: '上海', sale: 110 } ]);
* ```
*
* @param data 数据源json 数组。
* @returns View
*/
View.prototype.data = function (data) {
set(this.options, 'data', data);
this.isDataChanged = true;
return this;
};
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #data(data)}
*/
View.prototype.source = function (data) {
console.warn('This method will be removed at G2 V4.1. Please use chart.data() instead.');
return this.data(data);
};
/**
* 设置数据筛选规则。
*
* ```ts
* view.filter('city', (value: any, datum: Datum) => value !== '杭州');
*
* // 删除 'city' 字段对应的筛选规则。
* view.filter('city', null);
* ```
*
* @param field 数据字段
* @param condition 筛选规则
* @returns View
*/
View.prototype.filter = function (field, condition) {
if (isFunction(condition)) {
set(this.options, ['filters', field], condition);
return this;
}
// condition 为空,则表示删除过滤条件
if (!condition && get(this.options, ['filters', field])) {
delete this.options.filters[field];
}
return this;
};
View.prototype.axis = function (field, axisOption) {
if (isBoolean(field)) {
set(this.options, ['axes'], field);
}
else {
set(this.options, ['axes', field], axisOption);
}
return this;
};
View.prototype.legend = function (field, legendOption) {
if (isBoolean(field)) {
set(this.options, ['legends'], field);
}
else if (isString(field)) {
set(this.options, ['legends', field], legendOption);
}
else {
// 设置全局的 legend 配置
set(this.options, ['legends'], field);
}
return this;
};
View.prototype.scale = function (field, scaleOption) {
var _this = this;
if (isString(field)) {
set(this.options, ['scales', field], scaleOption);
}
else if (isObject(field)) {
each(field, function (v, k) {
set(_this.options, ['scales', k], v);
});
}
return this;
};
/**
* tooltip 提示信息配置。
*
* ```ts
* view.tooltip(false); // 关闭 tooltip
*
* view.tooltip({
* shared: true
* });
* ```
*
* @param cfg Tooltip 配置更详细的配置项参考https://github.com/antvis/component#tooltip
* @returns View
*/
View.prototype.tooltip = function (cfg) {
set(this.options, 'tooltip', cfg);
return this;
};
/**
* 辅助标记配置。
*
* ```ts
* view.annotation().line({
* start: ['min', 85],
* end: ['max', 85],
* style: {
* stroke: '#595959',
* lineWidth: 1,
* lineDash: [3, 3],
* },
* });
* ```
* 更详细的配置项https://github.com/antvis/component#annotation
* @returns [[Annotation]]
*/
View.prototype.annotation = function () {
return this.getController('annotation');
};
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #guide()}
*/
View.prototype.guide = function () {
console.warn('This method will be removed at G2 V4.1. Please use chart.annotation() instead.');
return this.annotation();
};
View.prototype.coordinate = function (type, coordinateCfg) {
// 提供语法糖,使用更简单
if (isString(type)) {
set(this.options, 'coordinate', { type: type, cfg: coordinateCfg });
}
else {
set(this.options, 'coordinate', type);
}
// 更新 coordinate 配置
this.coordinateController.update(this.options.coordinate);
return this.coordinateController;
};
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #coordinate()}
*/
View.prototype.coord = function (type, coordinateCfg) {
console.warn('This method will be removed at G2 V4.1. Please use chart.coordinate() instead.');
// @ts-ignore
return this.coordinate(type, coordinateCfg);
};
/**
* view 分面绘制。
*
* ```ts
* view.facet('rect', {
* rowField: 'province',
* columnField: 'category',
* eachView: (innerView: View, facet?: FacetData) => {
* innerView.line().position('city*sale');
* },
* });
* ```
*
* @param type 分面类型
* @param cfg 分面配置, [[FacetCfgMap]]
* @returns View
*/
View.prototype.facet = function (type, cfg) {
// 先销毁掉之前的分面
if (this.facetInstance) {
this.facetInstance.destroy();
}
// 创建新的分面
var Ctor = getFacet(type);
if (!Ctor) {
throw new Error("facet '" + type + "' is not exist!");
}
this.facetInstance = new Ctor(this, __assign(__assign({}, cfg), { type: type }));
return this;
};
/*
* 开启或者关闭动画。
*
* ```ts
* view.animate(false);
* ```
*
* @param status 动画状态true 表示开始false 表示关闭
* @returns View
*/
View.prototype.animate = function (status) {
set(this.options, 'animate', status);
return this;
};
/**
* 更新配置项,用于配置项式声明。
* @param options 配置项
*/
View.prototype.updateOptions = function (options) {
this.clear(); // 清空
mix(this.options, options);
this.initOptions();
return this;
};
/**
* 往 `view.options` 属性中存储配置项。
* @param name 属性名称
* @param opt 属性值
* @returns view
*/
View.prototype.option = function (name, opt) {
// 对于内置的 option避免覆盖。
// name 在原型上,说明可能是内置 API存在 option 被覆盖的风险,不处理
if (View.prototype[name]) {
throw new Error("Can't use built in variable name \"" + name + "\", please change another one.");
}
// 存入到 option 中
set(this.options, name, opt);
return this;
};
/**
* 设置主题。
*
* ```ts
* view.theme('dark'); // 'dark' 需要事先通过 `registerTheme()` 接口注册完成
*
* view.theme({ defaultColor: 'red' });
* ```
*
* @param theme 主题名或者主题配置
* @returns View
*/
View.prototype.theme = function (theme) {
this.themeObject = isObject(theme) ? deepMix({}, this.themeObject, theme) : getTheme(theme);
return this;
};
/* end 一系列传入配置的 API */
/**
* Call the interaction based on the interaction name
*
* ```ts
* view.interaction('my-interaction', { extra: 'hello world' });
* ```
* 详细文档可以参考https://g2.antv.vision/zh/docs/manual/tutorial/interaction
* @param name interaction name
* @param cfg interaction config
* @returns
*/
View.prototype.interaction = function (name, cfg) {
var existInteraction = this.interactions[name];
// 存在则先销毁已有的
if (existInteraction) {
existInteraction.destroy();
}
// 新建交互实例
var interaction = createInteraction(name, this, cfg);
if (interaction) {
interaction.init();
this.interactions[name] = interaction;
}
return this;
};
/**
* 移除当前 View 的 interaction
* ```ts
* view.removeInteraction('my-interaction');
* ```
* @param name interaction name
*/
View.prototype.removeInteraction = function (name) {
var existInteraction = this.interactions[name];
// 存在则先销毁已有的
if (existInteraction) {
existInteraction.destroy();
this.interactions[name] = undefined;
}
};
/**
* 修改数据,数据更新逻辑,数据更新仅仅影响当前这一层的 view
*
* ```ts
* view.changeData([{ city: '北京', sale: '200' }]);
* ```
*
* @param data
* @returns void
*/
View.prototype.changeData = function (data) {
this.isDataChanged = true;
this.emit(VIEW_LIFE_CIRCLE.BEFORE_CHANGE_DATA);
// 1. 保存数据
this.data(data);
// 2. 渲染
this.paint(true);
// 3. 遍历子 view 进行 change data
var views = this.views;
for (var i = 0, len = views.length; i < len; i++) {
var view = views[i];
// FIXME 子 view 有自己的数据的情况,该如何处理?
view.changeData(data);
}
this.emit(VIEW_LIFE_CIRCLE.AFTER_CHANGE_DATA);
};
/* View 管理相关的 API */
/**
* 创建子 view
*
* ```ts
* const innerView = view.createView({
* start: { x: 0, y: 0 },
* end: { x: 0.5, y: 0.5 },
* padding: 8,
* });
* ```
*
* @param cfg
* @returns View
*/
View.prototype.createView = function (cfg) {
// 子 view 共享 options 配置数据
var sharedOptions = {
data: this.options.data,
scales: clone(this.options.scales),
axes: clone(this.options.axes),
coordinate: clone(this.coordinateController.getOption()),
tooltip: clone(this.options.tooltip),
legends: clone(this.options.legends),
animate: this.options.animate,
visible: this.visible,
};
var v = new View(__assign(__assign({ parent: this, canvas: this.canvas,
// 子 view 共用三层 group
backgroundGroup: this.backgroundGroup.addGroup({ zIndex: GROUP_Z_INDEX.BG }), middleGroup: this.middleGroup.addGroup({ zIndex: GROUP_Z_INDEX.MID }), foregroundGroup: this.foregroundGroup.addGroup({ zIndex: GROUP_Z_INDEX.FORE }), theme: this.themeObject, padding: this.padding }, cfg), { options: __assign(__assign({}, sharedOptions), get(cfg, 'options', {})) }));
this.views.push(v);
return v;
};
/**
* @deprecated
* This method will be removed at G2 V4.1. Replaced by {@link #createView()}
*/
View.prototype.view = function (cfg) {
console.warn('This method will be removed at G2 V4.1. Please use chart.createView() instead.');
return this.createView(cfg);
};
/**
* 删除一个子 view
* @param view
* @return removedView
*/
View.prototype.removeView = function (view) {
var removedView = remove(this.views, function (v) { return v === view; })[0];
if (removedView) {
removedView.destroy();
}
return removedView;
};
/* end View 管理相关的 API */
// 一些 get 方法
/**
* 获取当前坐标系实例。
* @returns [[Coordinate]]
*/
View.prototype.getCoordinate = function () {
return this.coordinateInstance;
};
/**
* 获取当前 view 的主题配置。
* @returns themeObject
*/
View.prototype.getTheme = function () {
return this.themeObject;
};
/**
* 获得 x 轴字段的 scale 实例。
* @returns view 中 Geometry 对于的 x scale
*/
View.prototype.getXScale = function () {
// 拿第一个 Geometry 的 X scale
// 隐藏逻辑:一个 view 中的 Geometry 必须 x 字段一致
var g = this.geometries[0];
return g ? g.getXScale() : null;
};
/**
* 获取 y 轴字段的 scales 实例。
* @returns view 中 Geometry 对于的 y scale 数组
*/
View.prototype.getYScales = function () {
// 拿到所有的 Geometry 的 Y scale然后去重
var tmpMap = {};
return this.geometries.map(function (g) {
var yScale = g.getYScale();
var field = yScale.field;
if (!tmpMap[field]) {
tmpMap[field] = true;
return yScale;
}
});
};
/**
* 获取 x 轴或者 y 轴对应的所有 scale 实例。
* @param dimType x | y
* @returns x 轴或者 y 轴对应的所有 scale 实例。
*/
View.prototype.getScalesByDim = function (dimType) {
var geometries = this.geometries;
var scales = {};
for (var i = 0, len = geometries.length; i < len; i++) {
var geometry = geometries[i];
var scale = dimType === 'x' ? geometry.getXScale() : geometry.getYScale();
if (scale && !scales[scale.field]) {
scales[scale.field] = scale;
}
}
return scales;
};
/**
* 根据字段名去获取 scale 实例。
* @param field 数据字段名称
* @param key id
*/
View.prototype.getScaleByField = function (field, key) {
var defaultKey = key ? key : this.getScaleKey(field);
// 调用根节点 view 的方法获取
return this.getRootView().scalePool.getScale(defaultKey);
};
/**
* 返回所有配置信息。
* @returns 所有的 view API 配置。
*/
View.prototype.getOptions = function () {
return this.options;
};
/**
* 获取 view 的数据(过滤后的数据)。
* @returns 处理过滤器之后的数据。
*/
View.prototype.getData = function () {
return this.filteredData;
};
/**
* 获得绘制的层级 group。
* @param layer 层级名称。
* @returns 对应层级的 Group。
*/
View.prototype.getLayer = function (layer) {
return layer === LAYER.BG
? this.backgroundGroup
: layer === LAYER.MID
? this.middleGroup
: layer === LAYER.FORE
? this.foregroundGroup
: this.foregroundGroup;
};
/**
* 对外暴露方法,判断一个点是否在绘图区域(即坐标系范围)内部。
* @param point 坐标点
*/
View.prototype.isPointInPlot = function (point) {
return isPointInCoordinate(this.getCoordinate(), point);
};
/**
* 获得所有的 legend 对应的 attribute 实例。
* @returns 维度字段的 Attribute 数组
*/
View.prototype.getLegendAttributes = function () {
return flatten(this.geometries.map(function (g) { return g.getGroupAttributes(); }));
};
/**
* 获取所有的分组字段的 scale 实例。
* @returns 获得分组字段的 scale 实例数组。
*/
View.prototype.getGroupScales = function () {
// 拿到所有的 Geometry 的 分组字段 scale然后打平去重
var scales = this.geometries.map(function (g) { return g.getGroupScales(); });
return uniq(flatten(scales));
};
/**
* 获取 G.Canvas 实例。
* @returns G.Canvas 画布实例。
*/
View.prototype.getCanvas = function () {
return this.getRootView().canvas;
};
/**
* 获得根节点 view。
*/
View.prototype.getRootView = function () {
var v = this;
while (true) {
if (v.parent) {
v = v.parent;
continue;
}
break;
}
return v;
};
/**
* 获取该数据在可视化后,对应的画布坐标点。
* @param data 原始数据记录
* @returns 对应的画布坐标点
*/
View.prototype.getXY = function (data) {
var coordinate = this.getCoordinate();
var xScales = this.getScalesByDim('x');
var yScales = this.getScalesByDim('y');
var x;
var y;
each(data, function (value, key) {
if (xScales[key]) {
x = xScales[key].scale(value);
}
if (yScales[key]) {
y = yScales[key].scale(value);
}
});
if (!isNil(x) && !isNil(y)) {
return coordinate.convert({ x: x, y: y });
}
};
/**
* 获取 name 对应的 controller 实例
* @param name
*/
View.prototype.getController = function (name) {
return find(this.controllers, function (c) { return c.name === name; });
};
/**
* 显示 point 坐标点对应的 tooltip。
* @param point 画布坐标点
* @returns View
*/
View.prototype.showTooltip = function (point) {
var tooltip = this.getController('tooltip');
if (tooltip) {
tooltip.showTooltip(point);
}
return this;
};
/**
* 隐藏 tooltip。
* @returns View
*/
View.prototype.hideTooltip = function () {
var tooltip = this.getController('tooltip');
if (tooltip) {
tooltip.hideTooltip();
}
return this;
};
/**
* 将 tooltip 锁定到当前位置不能移动。
* @returns View
*/
View.prototype.lockTooltip = function () {
var tooltip = this.getController('tooltip');
if (tooltip) {
tooltip.lockTooltip();
}
return this;
};
/**
* 将 tooltip 锁定解除。
* @returns View
*/
View.prototype.unlockTooltip = function () {
var tooltip = this.getController('tooltip');
if (tooltip) {
tooltip.unlockTooltip();
}
return this;
};
/**
* 是否锁定 tooltip。
* @returns 是否锁定
*/
View.prototype.isTooltipLocked = function () {
var tooltip = this.getController('tooltip');
return tooltip && tooltip.isTooltipLocked();
};
/**
* 获取当前 point 对应的 tooltip 数据项。
* @param point 坐标点
* @returns tooltip 数据项
*/
View.prototype.getTooltipItems = function (point) {
var tooltip = this.getController('tooltip');
return tooltip ? tooltip.getTooltipItems(point) : [];
};
/**
* 获取逼近的点的数据集合
* @param point 当前坐标点
* @returns 数据
*/
View.prototype.getSnapRecords = function (point) {
var geometries = this.geometries;
var rst = [];
for (var i = 0, len = geometries.length; i < len; i++) {
var geom = geometries[i];
var dataArray = geom.dataArray;
geom.sort(dataArray); // 先进行排序,便于 tooltip 查找
var record = void 0;
for (var j = 0, dataLen = dataArray.length; j < dataLen; j++) {
var data = dataArray[j];
record = findDataByPoint(point, data, geom);
if (record) {
rst.push(record);
}
}
}
// 同样递归处理子 views
var views = this.views;
for (var i = 0, len = views.length; i < len; i++) {
var view = views[i];
var snapRecords = view.getSnapRecords(point);
rst = rst.concat(snapRecords);
}
return rst;
};
/**
* 获取所有的 pure component 组件,用于布局。
*/
View.prototype.getComponents = function () {
var components = [];
var controllers = this.controllers;
for (var i = 0, len = controllers.length; i < len; i++) {
var controller = controllers[i];
components = components.concat(controller.getComponents());
}
return components;
};
/**
* 将 data 数据进行过滤。
* @param data
* @returns 过滤之后的数据
*/
View.prototype.filterData = function (data) {
var filters = this.options.filters;
// 不存在 filters则不需要进行数据过滤
if (size(filters) === 0) {
return data;
}
// 存在过滤器,则逐个执行过滤,过滤器之间是 与 的关系
return filter(data, function (datum, idx) {
// 所有的 filter 字段
var fields = Object.keys(filters);
// 所有的条件都通过,才算通过
return fields.every(function (field) {
var condition = filters[field];
// condition 返回 true则保留
return condition(datum[field], datum, idx);
});
});
};
/**
* 对某一个字段进行过滤
* @param field
* @param data
*/
View.prototype.filterFieldData = function (field, data) {
var filters = this.options.filters;
var condition = get(filters, field);
if (isUndefined(condition)) {
return data;
}
return data.filter(function (datum, idx) { return condition(datum[field], datum, idx); });
};
/**
* 调整 coordinate 的坐标范围。
*/
View.prototype.adjustCoordinate = function () {
var _a = this.getCoordinate(), curStart = _a.start, curEnd = _a.end;
var start = this.coordinateBBox.bl;
var end = this.coordinateBBox.tr;
// 在 defaultLayoutFn 中只会在 coordinateBBox 发生变化的时候会调用 adjustCoorinate(),所以不用担心被置位
if (isEqual(curStart, start) && isEqual(curEnd, end)) {
this.isCoordinateChanged = false;
// 如果大小没有变化则不更新
return;
}
this.isCoordinateChanged = true;
this.coordinateInstance = this.coordinateController.adjust(start, end);
};
View.prototype.paint = function (isUpdate) {
this.renderDataRecursive(isUpdate);
// 处理 sync scale 的逻辑
this.syncScale();
this.emit(VIEW_LIFE_CIRCLE.BEFORE_PAINT);
this.renderLayoutRecursive(isUpdate);
this.renderPaintRecursive(isUpdate);
this.emit(VIEW_LIFE_CIRCLE.AFTER_PAINT);
this.isDataChanged = false; // 渲染完毕复位
};
/**
* 替换处理 view 的布局,最终是计算各个 view 的 coordinateBBox 和 coordinateInstance
* @param isUpdate
*/
View.prototype.renderLayoutRecursive = function (isUpdate) {
// 1. 子 view 大小相对 coordinateBBoxchangeSize 的时候需要重新计算
this.calculateViewBBox();
// 2. 更新 coordinate
this.adjustCoordinate();
// 3. 初始化组件 component
this.initComponents(isUpdate);
// 4. 进行布局,计算 coordinateBBox进行组件布局update 位置
this.doLayout();
// 5. 更新并存储最终的 padding 值
var viewBBox = this.viewBBox;
var coordinateBBox = this.coordinateBBox;
if (!this.padding) {
// 用户未设置 padding 时,将自动计算的 padding 保存至 autoPadding 属性中
this.autoPadding = [
coordinateBBox.tl.y - viewBBox.tl.y,
viewBBox.tr.x - coordinateBBox.tr.x,
viewBBox.bl.y - coordinateBBox.bl.y,
coordinateBBox.tl.x - viewBBox.tl.x
];
}
// 同样递归处理子 views
var views = this.views;
for (var i = 0, len = views.length; i < len; i++) {
var view = views[i];
view.renderLayoutRecursive(isUpdate);
}
};
/**
* 最终递归绘制组件和图形
* @param isUpdate
*/
View.prototype.renderPaintRecursive = function (isUpdate) {
if (this.limitInPlot) {
var middleGroup = this.middleGroup;
var _a = getCoordinateClipCfg(this.coordinateInstance), type = _a.type, attrs = _a.attrs;
middleGroup.setClip({
type: type,
attrs: attrs,
});
}
// 1. 渲染几何标记
this.paintGeometries(isUpdate);
// 2. 绘制组件
this.renderComponents(isUpdate);
// 同样递归处理子 views
var views = this.views;
for (var i = 0, len = views.length; i < len; i++) {
var view = views[i];
view.renderPaintRecursive(isUpdate);
}
};
// end Get 方法
/**
* 创建 scale递归到顶层 view 去创建和缓存 scale
* @param field
* @param data
* @param scaleDef
* @param key
*/
View.prototype.createScale = function (field, data, scaleDef, key) {
// 1. 合并 field 对应的 scaleDef合并原则是底层覆盖顶层就近原则
var currentScaleDef = get(this.options.scales, [field]);
var mergedScaleDef = __assign(__assign({}, currentScaleDef), scaleDef);
// 2. 是否存在父 view在则递归否则创建
if (this.parent) {
return this.parent.createScale(field, data, mergedScaleDef, key);
}
// 3. 在根节点 view 通过 scalePool 创建
return this.scalePool.createScale(field, data, mergedScaleDef, key);
};
/**
* 递归渲染中的数据处理
* @param isUpdate
*/
View.prototype.renderDataRecursive = function (isUpdate) {
// 1. 处理数据
this.doFilterData();
// 2. 创建实例
this.createCoordinate();
// 3. 初始化 Geometry
this.initGeometries(isUpdate);
// 4. 处理分面逻辑,最终都是生成子 view 和 geometry
this.renderFacet(isUpdate);
// 同样递归处理子 views
var views = this.views;
for (var i = 0, len = views.length; i < len; i++) {
var view = views[i];
view.renderDataRecursive(isUpdate);
}
};
/**
* 计算 region计算实际的像素范围坐标
* @private
*/
View.prototype.calculateViewBBox = function () {
var x;
var y;
var width;
var height;
if (this.parent) {
var bbox = this.parent.coordinateBBox;
// 存在 parent 那么就是通过父容器大小计算
x = bbox.x;
y = bbox.y;
width = bbox.width;
height = bbox.height;
}
else {
// 顶层容器,从 canvas 中取值 宽高
x = 0;
y = 0;
width = this.canvas.get('width');
height = this.canvas.get('height');
}
var _a = this.region, start = _a.start, end = _a.end;
// 根据 region 计算当前 view 的 bbox 大小。
var viewBBox = new BBox(x + width * start.x, y + height * start.y, width * (end.x - start.x), height * (end.y - start.y));
if (!this.viewBBox || !this.viewBBox.isEqual(viewBBox)) {
// viewBBox 发生变化的时候进行更新
this.viewBBox = new BBox(x + width * start.x, y + height * start.y, width * (end.x - start.x), height * (end.y - start.y));
// 初始的 coordinate bbox 大小
this.coordinateBBox = this.viewBBox;
}
};
/**
* 初始化事件机制G 4.0 底层内置支持 name:event 的机制,那么只要所有组件都有自己的 name 即可。
*
* G2 的事件只是获取事件委托,然后在 view 嵌套结构中,形成事件冒泡机制。
* 当前 view 只委托自己 view 中的 Component 和 Geometry 事件,并向上冒泡
* @private
*/
View.prototype.initEvents = function () {
// 三层 group 中的 shape 事件都会通过 G 冒泡上来的
this.foregroundGroup.on('*', this.onDelegateEvents);
this.middleGroup.on('*', this.onDelegateEvents);
this.backgroundGroup.on('*', this.onDelegateEvents);
this.canvas.on('*', this.onCanvasEvent);
};
/**
* 初始化插件
*/
View.prototype.initComponentController = function () {
var usedControllers = this.usedControllers;
for (var i = 0, len = usedControllers.length; i < len; i++) {
var controllerName = usedControllers[i];
var Ctor = getComponentController(controllerName);
if (Ctor) {
this.controllers.push(new Ctor(this));
}
}
};
View.prototype.createViewEvent = function (evt) {
var shape = evt.shape, name = evt.name;
var data = shape ? shape.get('origin') : null;
// 事件在 view 嵌套中冒泡(暂不提供阻止冒泡的机制)
var e = new Event(this, evt, data);
e.type = name;
return e;
};
/**
* 处理 PLOT_EVENTS
* plot event 需要处理所有的基础事件,并判断是否在画布中,然后再决定是否要 emit。
* 对于 mouseenter、mouseleave 比较特殊,需要做一下数学比较。
* @param e
*/
View.prototype.doPlotEvent = function (e) {
var type = e.type, x = e.x, y = e.y;
var point = { x: x, y: y };
var ALL_EVENTS = [
'mousedown',
'mouseup',
'mousemove',
'mouseleave',
'mousewheel',
'touchstart',
'touchmove',
'touchend',
'touchcancel',
'click',
'dblclick',
'contextmenu',
];
if (ALL_EVENTS.includes(type)) {
var currentInPlot = this.isPointInPlot(point);
if (currentInPlot) {
var TYPE = "plot:" + type; // 组合 plot 事件
e.type = TYPE;
this.emit(TYPE, e);
if (type === 'mouseleave' || type === 'touchend') { // 在plot 内部却离开画布
this.isPreMouseInPlot = false;
}
}
// 对于 mouseenter, mouseleave 的计算处理
if (type === 'mousemove' || type === 'touchmove') {
if (this.isPreMouseInPlot && !currentInPlot) {
if (type === 'mousemove') {
e.type = PLOT_EVENTS.MOUSE_LEAVE;
this.emit(PLOT_EVENTS.MOUSE_LEAVE, e);
}
e.type = PLOT_EVENTS.LEAVE;
this.emit(PLOT_EVENTS.LEAVE, e);
}
else if (!this.isPreMouseInPlot && currentInPlot) {
if (type === 'mousemove') {
e.type = PLOT_EVENTS.MOUSE_ENTER;
this.emit(PLOT_EVENTS.MOUSE_ENTER, e);
}
e.type = PLOT_EVENTS.ENTER;
this.emit(PLOT_EVENTS.ENTER, e);
}
// 赋新的状态值
this.isPreMouseInPlot = currentInPlot;
}
else if (type === 'mouseleave' || type === 'touchend') { // 可能不在 currentInPlot 中
if (this.isPreMouseInPlot) {
if (type === 'mouseleave') {
e.type = PLOT_EVENTS.MOUSE_LEAVE;
this.emit(PLOT_EVENTS.MOUSE_LEAVE, e);
}
e.type = PLOT_EVENTS.LEAVE;
this.emit(PLOT_EVENTS.LEAVE, e);
this.isPreMouseInPlot = false;
}
}
}
};
// view 生命周期 —— 渲染流程
/**
* 处理筛选器,筛选数据
* @private
*/
View.prototype.doFilterData = function () {
var data = this.options.data;
this.filteredData = this.filterData(data);
};
/**
* 初始化 Geometries
* @private
*/
View.prototype.initGeometries = function (isUpdate) {
// 初始化图形的之前,先创建 / 更新 scales
this.createOrUpdateScales();
// 实例化 Geometry然后 view 将所有的 scale 管理起来
var coordinate = this.getCoordinate();
var scaleDefs = get(this.options, 'scales', {});
var geometries = this.geometries;
for (var i = 0, len = geometries.length; i < len; i++) {
var geometry = geometries[i];
// 保持 scales 引用不要变化
geometry.scales = this.getGeometryScales();
var cfg = {
coordinate: coordinate,
scaleDefs: scaleDefs,
data: this.filteredData,
theme: this.themeObject,
isDataChanged: this.isDataChanged,
isCoordinateChanged: this.isCoordinateChanged,
};
if (isUpdate) {
// 数据发生更新
geometry.update(cfg);
}
else {
geometry.init(cfg);
}
}
// Geometry 初始化之后,生成了 scale然后进行调整 scale 配置
this.adjustScales();
};
/**
* 根据 Geometry 的所有字段创建 scales
* 如果存在,则更新,不存在则创建
*/
View.prototype.createOrUpdateScales = function () {
var fields = this.getScaleFields();
var groupedFields = this.getGroupedFields();
var _a = this.getOptions(), data = _a.data, _b = _a.scales, scales = _b === void 0 ? {} : _b;
var filteredData = this.filteredData;
for (var i = 0, len = fields.length; i < len; i++) {
var field = fields[i];
var scaleDef = scales[field];
// 调用方法,递归去创建
var key = this.getScaleKey(field);
this.createScale(field,
// 分组字段的 scale 使用未过滤的数据创建
groupedFields.includes(field) ? data : filteredData, scaleDef, key);
// 缓存从当前 view 创建的 scale key
this.createdScaleKeys.set(key, true);
}
};
/**
* 处理 scale 同步逻辑
*/
View.prototype.syncScale = function () {
// 最终调用 root view 的
this.getRootView().scalePool.sync();
};
/**
* 获得 Geometry 中的 scale 对象
*/
View.prototype.getGeometryScales = function () {
var fields = this.getScaleFields();
var scales = {};
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
scales[field] = this.getScaleByField(field);
}
return scales;
};
View.prototype.getScaleFields = function () {
var fields = [];
var tmpMap = {};
var geometries = this.geometries;
for (var i = 0; i < geometries.length; i++) {
var geometry = geometries[i];
var geometryScales = geometry.getScaleFields();
uniq(geometryScales, fields, tmpMap);
}
return fields;
};
View.prototype.getGroupedFields = function () {
var fields = [];
var tmpMap = {};
var geometries = this.geometries;
for (var i = 0; i < geometries.length; i++) {
var geometry = geometries[i];
var groupFields = geometry.getGroupFields();
uniq(groupFields, fields, tmpMap);
}
return fields;
};
/**
* 调整 scale 配置
* @private
*/
View.prototype.adjustScales = function () {
// 调整目前包括:
// 分类 scale调整 range 范围
this.adjustCategoryScaleRange();
};
/**
* 调整分类 scale 的 range防止超出坐标系外面
* @private
*/
View.prototype.adjustCategoryScaleRange = function () {
var _this = this;
var xyScales = __spreadArrays([this.getXScale()], this.getYScales()).filter(function (e) { return !!e; });
var coordinate = this.getCoordinate();
var scaleOptions = this.options.scales;
each(xyScales, function (scale) {
var field = scale.field, values = scale.values, isCategory = scale.isCategory, isIdentity = scale.isIdentity;
// 分类或者 identity 的 scale 才进行处理
if (isCategory || isIdentity) {
// 存在 value 值,且用户没有配置 range 配置
if (values && !get(scaleOptions, [field, 'range'])) {
var count = values.length;
var range = void 0;
if (count === 1) {
range = [0.5, 1]; // 只有一个分类时,防止计算出现 [0.5,0.5] 的状态
}
else {
var widthRatio = 1;
var offset = 0;
if (isFullCircle(coordinate)) {
if (!coordinate.isTransposed) {
range = [0, 1 - 1 / count];
}
else {
widthRatio = get(_this.theme, 'widthRatio.multiplePie', 1 / 1.3);
offset = (1 / count) * widthRatio;
range = [offset / 2, 1 - offset / 2];
}
}
else {
offset = 1 / count / 2; // 两边留下分类空间的一半
range = [offset, 1 - offset]; // 坐标轴最前面和最后面留下空白防止绘制柱状图时
}
}
// 更新 range
scale.range = range;
}
}
});
};
/**
* 根据 options 配置、Geometry 字段配置,自动生成 components
* @param isUpdate 是否是更新
* @private
*/
View.prototype.initComponents = function (isUpdate) {
// 先全部清空,然后 render
var controllers = this.controllers;
for (var i = 0; i < controllers.length; i++) {
var controller = controllers[i];
// 更新则走更新逻辑;否则清空载重绘
if (isUpdate) {
controller.update();
}
else {
controller.clear();
controller.render();
}
}
;
};
View.prototype.doLayout = function () {
this.layoutFunc(this);
};
/**
* 创建坐标系
* @private
*/
View.prototype.createCoordinate = function () {
var start = this.coordinateBBox.bl;
var end = this.coordinateBBox.tr;
this.coordinateInstance = this.coordinateController.create(start, end);
};
/**
* 根据 options 配置自动渲染 geometry
* @private
*/
View.prototype.paintGeometries = function (isUpdate) {
var doAnimation = this.options.animate;
// geometry 的 paint 阶段
var coordinate = this.getCoordinate();
var canvasRegion = {
x: this.viewBBox.x,
y: this.viewBBox.y,
minX: this.viewBBox.minX,
minY: this.viewBBox.minY,
maxX: this.viewBBox.maxX,
maxY: this.viewBBox.maxY,
width: this.viewBBox.width,
height: this.viewBBox.height,
};
var geometries = this.geometries;
for (var i = 0; i < geometries.length; i++) {
var geometry = geometries[i];
geometry.coordinate = coordinate;
geometry.canvasRegion = canvasRegion;
if (!doAnimation) {
// 如果 view 不执行动画,那么 view 下所有的 geometry 都不执行动画
geometry.animate(false);
}
geometry.paint(isUpdate);
}
};
/**
* 最后的绘制组件
* @param isUpdate
*/
View.prototype.renderComponents = function (isUpdate) {
// 先全部清空,然后 render
for (var i = 0; i < this.getComponents().length; i++) {
var co = this.getComponents()[i];
co.component.render();
}
};
/**
* 渲染分面,会在其中进行数据分面,然后进行子 view 创建
* @param isUpdate
*/
View.prototype.renderFacet = function (isUpdate) {
if (this.facetInstance) {
if (isUpdate) {
this.facetInstance.update();
}
else {
this.facetInstance.clear();
// 计算分面数据
this.facetInstance.init();
// 渲染组件和 views
this.facetInstance.render();
}
}
};
View.prototype.initOptions = function () {
var _a = this.options, _b = _a.geometries, geometries = _b === void 0 ? [] : _b, _c = _a.interactions, interactions = _c === void 0 ? [] : _c, _d = _a.views, views = _d === void 0 ? [] : _d, _e = _a.annotations, annotations = _e === void 0 ? [] : _e;
// 创建 geometry 实例
for (var i = 0; i < geometries.length; i++) {
var geometryOption = geometries[i];
this.createGeometry(geometryOption);
}
// 创建 interactions 实例
for (var j = 0; j < interactions.length; j++) {
var interactionOption = interactions[j];
var type = interactionOption.type, cfg = interactionOption.cfg;
this.interaction(type, cfg);
}
// 创建 view 实例
for (var k = 0; k < views.length; k++) {
var viewOption = views[k];
this.createView(viewOption);
}
// 设置 annotation
var annotationComponent = this.getController('annotation');
for (var l = 0; l < annotations.length; l++) {
var annotationOption = annotations[l];
annotationComponent.annotation(annotationOption);
}
};
View.prototype.createGeometry = function (geometryOption) {
var type = geometryOption.type, _a = geometryOption.cfg, cfg = _a === void 0 ? {} : _a;
if (this[type]) {
var geometry_1 = this[type](cfg);
each(geometryOption, function (v, k) {
if (isFunction(geometry_1[k])) {
geometry_1[k](v);
}
});
}
};
/**
* scale key 的创建方式
* @param field
*/
View.prototype.getScaleKey = function (field) {
return this.id + "-" + field;
};
return View;
}(Base));
export { View };
/**
* 注册 geometry 组件
* @param name
* @param Ctor
* @returns Geometry
*/
export function registerGeometry(name, Ctor) {
// 语法糖,在 view API 上增加原型方法
View.prototype[name.toLowerCase()] = function (cfg) {
if (cfg === void 0) { cfg = {}; }
var props = __assign({
/** 图形容器 */
container: this.middleGroup.addGroup(), labelsContainer: this.foregroundGroup.addGroup() }, cfg);
var geometry = new Ctor(props);
this.geometries.push(geometry);
return geometry;
};
}
export default View;
//# sourceMappingURL=view.js.map