1476 lines
52 KiB
Java
1476 lines
52 KiB
Java
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 大小相对 coordinateBBox,changeSize 的时候需要重新计算
|
||
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 |