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