import { __assign, __extends } from "tslib"; import { contains, deepMix, each, get, isArray, isFunction, isNil, isString, keys, upperFirst } from '@antv/util'; import { Annotation as AnnotationComponent } from '../../dependents'; import { DEFAULT_ANIMATE_CFG } from '../../animate/'; import { COMPONENT_TYPE, DIRECTION, LAYER, VIEW_LIFE_CIRCLE } from '../../constant'; import { getAngleByPoint, getDistanceToCenter } from '../../util/coordinate'; import { omit } from '../../util/helper'; import { Controller } from './base'; /** * Annotation controller, 主要作用: * 1. 创建 Annotation: line、text、arc ... * 2. 生命周期: init、layout、render、clear、destroy */ var Annotation = /** @class */ (function (_super) { __extends(Annotation, _super); function Annotation(view) { var _this = _super.call(this, view) || this; /* 组件更新的 cache,组件配置 object : 组件 */ _this.cache = new Map(); _this.foregroundContainer = _this.view.getLayer(LAYER.FORE).addGroup(); _this.backgroundContainer = _this.view.getLayer(LAYER.BG).addGroup(); _this.option = []; return _this; } Object.defineProperty(Annotation.prototype, "name", { get: function () { return 'annotation'; }, enumerable: false, configurable: true }); Annotation.prototype.init = function () { }; Annotation.prototype.layout = function () { var _this = this; var components = this.getComponents(); var updateComponentFn = function (co) { var component = co.component, extra = co.extra; var type = extra.type; var theme = _this.getAnnotationTheme(type); component.update(_this.getAnnotationCfg(type, extra, theme)); }; var createComponentFn = function (option) { var co = _this.createAnnotation(option); if (co) { co.component.init(); // Note:regionFilter 特殊处理,regionFilter需要取到 Geometry 中的 Shape,需要在 view render 之后处理 // 其他组件使用外层的统一 render 逻辑 if (option.type === 'regionFilter') { co.component.render(); } // 缓存起来 _this.cache.set(option, co); } }; if (components.length) { each(components, function (co) { var component = co.component; if (component.get('type') === 'regionFilter') { // regionFilter 依赖绘制后的 Geometry Shapes _this.view.once(VIEW_LIFE_CIRCLE.AFTER_RENDER, function () { updateComponentFn(co); }); } else { updateComponentFn(co); } }); } else { each(this.option, function (option) { if (option.type === 'regionFilter') { _this.view.once(VIEW_LIFE_CIRCLE.AFTER_RENDER, function () { // regionFilter 依赖绘制后的 Geometry Shapes createComponentFn(option); }); } else { createComponentFn(option); } }); } }; Annotation.prototype.render = function () { // 因为 Annotation 不参与布局,但是渲染的位置依赖于坐标系,所以可以将绘制阶段延迟到 layout() 进行 }; /** * 更新 */ Annotation.prototype.update = function () { var _this = this; // 已经处理过的 legend var updated = new WeakMap(); var updateComponentFn = function (option) { var type = option.type; var theme = _this.getAnnotationTheme(type); var cfg = _this.getAnnotationCfg(type, option, theme); var existCo = _this.cache.get(option); // 存在,则更新 if (existCo) { // 忽略掉一些配置 omit(cfg, ['container']); existCo.component.update(cfg); updated.set(option, true); } else { // 不存在,则创建 var co = _this.createAnnotation(option); if (co) { co.component.init(); // Note:regionFilter 特殊处理,regionFilter需要取到 Geometry 中的 Shape,需要在 view render 之后处理 // 其他组件使用外层的统一 render 逻辑 if (option.type === 'regionFilter') { co.component.render(); } // 缓存起来 _this.cache.set(option, co); updated.set(option, true); } } }; this.view.once(VIEW_LIFE_CIRCLE.AFTER_RENDER, function () { // 先看是否有 regionFilter 要更新 each(_this.option, function (option) { if (option.type === 'regionFilter') { updateComponentFn(option); } }); // 处理完成之后,销毁删除的 // 不在处理中的 var newCache = new Map(); _this.cache.forEach(function (value, key) { if (updated.has(key)) { newCache.set(key, value); } else { // 不存在,则是所有需要被销毁的组件 value.component.destroy(); } }); // 更新缓存 _this.cache = newCache; }); each(this.option, function (option) { if (option.type !== 'regionFilter') { updateComponentFn(option); } }); }; /** * 清空 * @param includeOption 是否清空 option 配置项 */ Annotation.prototype.clear = function (includeOption) { if (includeOption === void 0) { includeOption = false; } _super.prototype.clear.call(this); this.cache.clear(); this.foregroundContainer.clear(); this.backgroundContainer.clear(); // clear all option if (includeOption) { this.option = []; } }; Annotation.prototype.destroy = function () { this.clear(true); this.foregroundContainer.remove(true); this.backgroundContainer.remove(true); }; /** * 复写基类的方法 */ Annotation.prototype.getComponents = function () { var co = []; this.cache.forEach(function (value) { co.push(value); }); return co; }; Annotation.prototype.createAnnotation = function (option) { var type = option.type; var Ctor = AnnotationComponent[upperFirst(type)]; if (Ctor) { var theme = this.getAnnotationTheme(type); var cfg = this.getAnnotationCfg(type, option, theme); var annotation = new Ctor(cfg); return { component: annotation, layer: this.isTop(cfg) ? LAYER.FORE : LAYER.BG, direction: DIRECTION.NONE, type: COMPONENT_TYPE.ANNOTATION, extra: option, }; } }; // APIs for creating annotation component Annotation.prototype.annotation = function (option) { this.option.push(option); }; /** * 创建 Arc * @param option * @returns AnnotationController */ Annotation.prototype.arc = function (option) { this.annotation(__assign({ type: 'arc' }, option)); return this; }; /** * 创建 image * @param option * @returns AnnotationController */ Annotation.prototype.image = function (option) { this.annotation(__assign({ type: 'image' }, option)); return this; }; /** * 创建 Line * @param option * @returns AnnotationController */ Annotation.prototype.line = function (option) { this.annotation(__assign({ type: 'line' }, option)); return this; }; /** * 创建 Region * @param option * @returns AnnotationController */ Annotation.prototype.region = function (option) { this.annotation(__assign({ type: 'region' }, option)); return this; }; /** * 创建 Text * @param option * @returns AnnotationController */ Annotation.prototype.text = function (option) { this.annotation(__assign({ type: 'text' }, option)); return this; }; /** * 创建 DataMarker * @param option * @returns AnnotationController */ Annotation.prototype.dataMarker = function (option) { this.annotation(__assign({ type: 'dataMarker' }, option)); return this; }; /** * 创建 DataRegion * @param option * @returns AnnotationController */ Annotation.prototype.dataRegion = function (option) { this.annotation(__assign({ type: 'dataRegion' }, option)); }; /** * 创建 RegionFilter * @param option * @returns AnnotationController */ Annotation.prototype.regionFilter = function (option) { this.annotation(__assign({ type: 'regionFilter' }, option)); }; // end API /** * parse the point position to [x, y] * @param p Position * @returns { x, y } */ Annotation.prototype.parsePosition = function (p) { var xScale = this.view.getXScale(); // 转成 object var yScales = this.view.getScalesByDim('y'); var position = isFunction(p) ? p.call(null, xScale, yScales) : p; var x = 0; var y = 0; // 入参是 [24, 24] 这类时 if (isArray(position)) { var xPos = position[0], yPos = position[1]; // 如果数据格式是 ['50%', '50%'] 的格式 // fix: 原始数据中可能会包含 'xxx5%xxx' 这样的数据,需要判断下 https://github.com/antvis/f2/issues/590 // @ts-ignore if (isString(xPos) && xPos.indexOf('%') !== -1 && !isNaN(xPos.slice(0, -1))) { return this.parsePercentPosition(position); } x = this.getNormalizedValue(xPos, xScale); y = this.getNormalizedValue(yPos, Object.values(yScales)[0]); } else if (!isNil(position)) { // 入参是 object 结构,数据点 for (var _i = 0, _a = keys(position); _i < _a.length; _i++) { var key = _a[_i]; var value = position[key]; if (key === xScale.field) { x = this.getNormalizedValue(value, xScale); } if (yScales[key]) { y = this.getNormalizedValue(value, yScales[key]); } } } return this.view.getCoordinate().convert({ x: x, y: y }); }; /** * parse all the points between start and end * @param start * @param end * @return Point[] */ Annotation.prototype.getRegionPoints = function (start, end) { var _this = this; var xScale = this.view.getXScale(); var yScales = this.view.getScalesByDim('y'); var yScale = Object.values(yScales)[0]; var xField = xScale.field; var viewData = this.view.getData(); var startXValue = isArray(start) ? start[0] : start[xField]; var endXValue = isArray(end) ? end[0] : end[xField]; var arr = []; var startIndex; each(viewData, function (item, idx) { if (item[xField] === startXValue) { startIndex = idx; } if (idx >= startIndex) { var point = _this.parsePosition([item[xField], item[yScale.field]]); if (point) { arr.push(point); } } if (item[xField] === endXValue) { return false; } }); return arr; }; /** * parse the value position * @param val * @param scale */ Annotation.prototype.getNormalizedValue = function (val, scale) { var result; var scaled; switch (val) { case 'start': result = 0; break; case 'end': result = 1; break; case 'median': { scaled = scale.isCategory ? (scale.values.length - 1) / 2 : (scale.min + scale.max) / 2; result = scale.scale(scaled); break; } case 'min': case 'max': if (scale.isCategory) { scaled = val === 'min' ? 0 : scale.values.length - 1; } else { scaled = scale[val]; } result = scale.scale(scaled); break; default: result = scale.scale(val); } return result; }; /** * parse percent position * @param position */ Annotation.prototype.parsePercentPosition = function (position) { var xPercent = parseFloat(position[0]) / 100; var yPercent = parseFloat(position[1]) / 100; var coordinate = this.view.getCoordinate(); var start = coordinate.start, end = coordinate.end; var topLeft = { x: Math.min(start.x, end.x), y: Math.min(start.y, end.y), }; var x = coordinate.getWidth() * xPercent + topLeft.x; var y = coordinate.getHeight() * yPercent + topLeft.y; return { x: x, y: y }; }; /** * get coordinate bbox */ Annotation.prototype.getCoordinateBBox = function () { var coordinate = this.view.getCoordinate(); var start = coordinate.start, end = coordinate.end; var width = coordinate.getWidth(); var height = coordinate.getHeight(); var topLeft = { x: Math.min(start.x, end.x), y: Math.min(start.y, end.y), }; return { x: topLeft.x, y: topLeft.y, minX: topLeft.x, minY: topLeft.y, maxX: topLeft.x + width, maxY: topLeft.y + height, width: width, height: height, }; }; /** * get annotation component config by different type * @param type * @param option 用户的配置 * @param theme */ Annotation.prototype.getAnnotationCfg = function (type, option, theme) { var coordinate = this.view.getCoordinate(); var o = {}; if (isNil(option)) { return null; } if (type === 'arc') { var _a = option, start = _a.start, end = _a.end; var sp = this.parsePosition(start); var ep = this.parsePosition(end); var startAngle = getAngleByPoint(coordinate, sp); var endAngle = getAngleByPoint(coordinate, ep); if (startAngle > endAngle) { endAngle = Math.PI * 2 + endAngle; } o = { center: coordinate.getCenter(), radius: getDistanceToCenter(coordinate, sp), startAngle: startAngle, endAngle: endAngle, }; } else if (type === 'image') { var _b = option, start = _b.start, end = _b.end; o = { start: this.parsePosition(start), end: this.parsePosition(end), src: option.src, }; } else if (type === 'line') { var _c = option, start = _c.start, end = _c.end; o = { start: this.parsePosition(start), end: this.parsePosition(end), text: get(option, 'text', null), }; } else if (type === 'region') { var _d = option, start = _d.start, end = _d.end; o = { start: this.parsePosition(start), end: this.parsePosition(end), }; } else if (type === 'text') { var _e = option, position = _e.position, rotate = _e.rotate; o = __assign(__assign({}, this.parsePosition(position)), { content: option.content, rotate: rotate }); } else if (type === 'dataMarker') { var _f = option, position = _f.position, point = _f.point, line = _f.line, text = _f.text, autoAdjust = _f.autoAdjust, direction = _f.direction; o = __assign(__assign({}, this.parsePosition(position)), { coordinateBBox: this.getCoordinateBBox(), point: point, line: line, text: text, autoAdjust: autoAdjust, direction: direction }); } else if (type === 'dataRegion') { var _g = option, start = _g.start, end = _g.end, region = _g.region, text = _g.text, lineLength = _g.lineLength; o = { points: this.getRegionPoints(start, end), region: region, text: text, lineLength: lineLength, }; } else if (type === 'regionFilter') { var _h = option, start = _h.start, end = _h.end, apply_1 = _h.apply, color = _h.color; var geometries = this.view.geometries; var shapes_1 = []; var addShapes_1 = function (item) { if (!item) { return; } if (item.isGroup()) { item.getChildren().forEach(function (child) { return addShapes_1(child); }); } else { shapes_1.push(item); } }; each(geometries, function (geom) { if (apply_1) { if (contains(apply_1, geom.type)) { each(geom.elements, function (elem) { addShapes_1(elem.shape); }); } } else { each(geom.elements, function (elem) { addShapes_1(elem.shape); }); } }); o = { color: color, shapes: shapes_1, start: this.parsePosition(start), end: this.parsePosition(end), }; } // 合并主题,用户配置优先级高于默认主题 var cfg = deepMix({}, theme, __assign(__assign({}, o), { top: option.top, style: option.style, offsetX: option.offsetX, offsetY: option.offsetY })); cfg.container = this.getComponentContainer(cfg); cfg.animate = this.view.getOptions().animate && cfg.animate && get(option, 'animate', cfg.animate); // 如果 view 关闭动画,则不执行 cfg.animateOption = deepMix({}, DEFAULT_ANIMATE_CFG, cfg.animateOption, option.animateOption); return cfg; }; /** * is annotation render on top * @param option * @return whethe on top */ Annotation.prototype.isTop = function (option) { return get(option, 'top', true); }; /** * get the container by option.top * default is on top * @param option * @returns the container */ Annotation.prototype.getComponentContainer = function (option) { return this.isTop(option) ? this.foregroundContainer : this.backgroundContainer; }; Annotation.prototype.getAnnotationTheme = function (type) { return get(this.view.getTheme(), ['components', 'annotation', type], {}); }; return Annotation; }(Controller)); export default Annotation; //# sourceMappingURL=annotation.js.map