import { __extends } from "tslib"; import { deepMix, each, find, get, head, isBoolean, last } from '@antv/util'; import { COMPONENT_MAX_VIEW_PERCENTAGE, COMPONENT_TYPE, DIRECTION, LAYER } from '../../constant'; import { CategoryLegend, ContinuousLegend } from '../../dependents'; import { DEFAULT_ANIMATE_CFG } from '../../animate'; import { BBox } from '../../util/bbox'; import { directionToPosition } from '../../util/direction'; import { omit } from '../../util/helper'; import { getCustomLegendItems, getLegendItems, getLegendLayout } from '../../util/legend'; import { getName } from '../../util/scale'; import { Controller } from './base'; /** * 从配置中获取单个字段的 legend 配置 * @param legends * @param field * @returns the option of one legend field */ function getLegendOption(legends, field) { if (isBoolean(legends)) { return legends === false ? false : {}; } return get(legends, [field], legends); } function getDirection(legendOption) { return get(legendOption, 'position', DIRECTION.BOTTOM); } /** * @ignore * legend Controller */ var Legend = /** @class */ (function (_super) { __extends(Legend, _super); function Legend(view) { var _this = _super.call(this, view) || this; _this.container = _this.view.getLayer(LAYER.FORE).addGroup(); return _this; } Object.defineProperty(Legend.prototype, "name", { get: function () { return 'legend'; }, enumerable: false, configurable: true }); Legend.prototype.init = function () { }; /** * render the legend component by legend options */ Legend.prototype.render = function () { var _this = this; this.option = this.view.getOptions().legends; var doEachLegend = function (geometry, attr, scale) { var legend = _this.createFieldLegend(geometry, attr, scale); if (legend) { legend.component.init(); _this.components.push(legend); } }; // 全局自定义图例 if (get(this.option, 'custom')) { var component = this.createCustomLegend(undefined, undefined, undefined, this.option); if (component) { component.init(); var layer = LAYER.FORE; var direction = getDirection(this.option); this.components.push({ id: 'global-custom', component: component, layer: layer, direction: direction, type: COMPONENT_TYPE.LEGEND, extra: undefined, }); } } else { // 遍历处理每一个创建逻辑 this.loopLegends(doEachLegend); } }; /** * layout legend * 计算出 legend 的 direction 位置 x, y */ Legend.prototype.layout = function () { var _this = this; this.layoutBBox = this.view.viewBBox; each(this.components, function (co) { var component = co.component, direction = co.direction; var layout = getLegendLayout(direction); var maxSize = _this.getCategoryLegendSizeCfg(layout); var maxWidth = component.get('maxWidth'); var maxHeight = component.get('maxHeight'); // 先更新 maxSize,更新 layoutBBox,以便计算正确的 x y component.update({ maxWidth: Math.min(maxSize.maxWidth, maxWidth || 0), maxHeight: Math.min(maxSize.maxHeight, maxHeight || 0), }); var bboxObject = component.getLayoutBBox(); // 这里只需要他的 width、height 信息做位置调整 var bbox = new BBox(bboxObject.x, bboxObject.y, bboxObject.width, bboxObject.height); var _a = directionToPosition(_this.view.coordinateBBox, bbox, direction), x1 = _a[0], y1 = _a[1]; var _b = directionToPosition(_this.layoutBBox, bbox, direction), x2 = _b[0], y2 = _b[1]; var x = 0; var y = 0; // 因为 legend x y 要和 coordinateBBox 对齐,所以要做一个简单的判断 if (direction.startsWith('top') || direction.startsWith('bottom')) { x = x1; y = y2; } else { x = x2; y = y1; } // 更新位置 component.update({ x: x, y: y, }); _this.layoutBBox = _this.layoutBBox.cut(bbox, direction); }); }; /** * legend 的更新逻辑 */ Legend.prototype.update = function () { var _this = this; this.option = this.view.getOptions().legends; // 已经处理过的 legend var updated = {}; var eachLegend = function (geometry, attr, scale) { var id = _this.getId(scale.field); var existCo = _this.getComponentById(id); // 存在则 update if (existCo) { var cfg = void 0; var legendOption = getLegendOption(_this.option, scale.field); // if the legend option is not false, means legend should be created. if (legendOption !== false) { if (get(legendOption, 'custom')) { cfg = _this.getCategoryCfg(geometry, attr, scale, legendOption, true); } else { if (scale.isLinear) { // linear field, create continuous legend cfg = _this.getContinuousCfg(geometry, attr, scale, legendOption); } else if (scale.isCategory) { // category field, create category legend cfg = _this.getCategoryCfg(geometry, attr, scale, legendOption); } } } // 如果 cfg 为空,则不在 updated 标记,那么会在后面逻辑中删除 if (cfg) { // omit 掉一些属性,比如 container 等 omit(cfg, ['container']); existCo.direction = getDirection(legendOption); existCo.component.update(cfg); // 标记为新的 updated[id] = true; } } else { // 不存在则 create var legend = _this.createFieldLegend(geometry, attr, scale); if (legend) { legend.component.init(); _this.components.push(legend); // 标记为新的 updated[id] = true; } } }; // 全局自定义图例 if (get(this.option, 'custom')) { var id = 'global-custom'; var existCo = this.getComponentById(id); if (existCo) { var customCfg = this.getCategoryCfg(undefined, undefined, undefined, this.option, true); omit(customCfg, ['container']); existCo.component.update(customCfg); updated[id] = true; } else { var component = this.createCustomLegend(undefined, undefined, undefined, this.option); if (component) { component.init(); var layer = LAYER.FORE; var direction = getDirection(this.option); this.components.push({ id: id, component: component, layer: layer, direction: direction, type: COMPONENT_TYPE.LEGEND, extra: undefined, }); // 标记为更新 updated[id] = true; } } } else { // 遍历处理每一个创建逻辑 this.loopLegends(eachLegend); } // 处理完成之后,销毁删除的 // 不在处理中的 var components = []; each(this.getComponents(), function (co) { if (updated[co.id]) { components.push(co); } else { co.component.destroy(); } }); // 更新当前已有的 components this.components = components; }; Legend.prototype.clear = function () { _super.prototype.clear.call(this); this.container.clear(); }; Legend.prototype.destroy = function () { _super.prototype.destroy.call(this); this.container.remove(true); }; /** * 递归获取所有的 Geometry */ Legend.prototype.getGeometries = function (view) { var _this = this; var geometries = view.geometries; each(view.views, function (v) { geometries = geometries.concat(_this.getGeometries(v)); }); return geometries; }; /** * 遍历 Geometry,处理 legend 逻辑 * @param doEach 每个 loop 中的处理方法 */ Legend.prototype.loopLegends = function (doEach) { var isRootView = this.view.getRootView() === this.view; // 非根 view,不处理 legend if (!isRootView) { return; } // 递归 view 中所有的 Geometry,进行创建 legend var geometries = this.getGeometries(this.view); var looped = {}; // 防止一个字段创建两个 legend each(geometries, function (geometry) { var attributes = geometry.getGroupAttributes(); each(attributes, function (attr) { var scale = attr.getScale(attr.type); // 如果在视觉通道上映射常量值,如 size(2) shape('circle') 不创建 legend if (!scale || scale.type === 'identity' || looped[scale.field]) { return; } doEach(geometry, attr, scale); looped[scale.field] = true; }); }); }; /** * 创建一个 legend * @param geometry * @param attr * @param scale */ Legend.prototype.createFieldLegend = function (geometry, attr, scale) { var component; var legendOption = getLegendOption(this.option, scale.field); var layer = LAYER.FORE; var direction = getDirection(legendOption); // if the legend option is not false, means legend should be created. if (legendOption !== false) { if (get(legendOption, 'custom')) { component = this.createCustomLegend(geometry, attr, scale, legendOption); } else { if (scale.isLinear) { // linear field, create continuous legend component = this.createContinuousLegend(geometry, attr, scale, legendOption); } else if (scale.isCategory) { // category field, create category legend component = this.createCategoryLegend(geometry, attr, scale, legendOption); } } } if (component) { component.set('field', scale.field); return { id: this.getId(scale.field), component: component, layer: layer, direction: direction, type: COMPONENT_TYPE.LEGEND, extra: { scale: scale }, }; } }; /** * 自定义图例使用 category 图例去渲染 * @param geometry * @param attr * @param scale * @param legendOption */ Legend.prototype.createCustomLegend = function (geometry, attr, scale, legendOption) { // 直接使用 分类图例渲染 var cfg = this.getCategoryCfg(geometry, attr, scale, legendOption, true); return new CategoryLegend(cfg); }; /** * 创建连续图例 * @param geometry * @param attr * @param scale * @param legendOption */ Legend.prototype.createContinuousLegend = function (geometry, attr, scale, legendOption) { var cfg = this.getContinuousCfg(geometry, attr, scale, legendOption); return new ContinuousLegend(cfg); }; /** * 创建分类图例 * @param geometry * @param attr * @param scale * @param legendOption */ Legend.prototype.createCategoryLegend = function (geometry, attr, scale, legendOption) { var cfg = this.getCategoryCfg(geometry, attr, scale, legendOption); return new CategoryLegend(cfg); }; /** * 获得连续图例的配置 * @param geometry * @param attr * @param scale * @param legendOption */ Legend.prototype.getContinuousCfg = function (geometry, attr, scale, legendOption) { var ticks = scale.getTicks(); var containMin = find(ticks, function (tick) { return tick.value === 0; }); var containMax = find(ticks, function (tick) { return tick.value === 1; }); var items = ticks.map(function (tick) { var value = tick.value, tickValue = tick.tickValue; var attrValue = attr.mapping(scale.invert(value)).join(''); return { value: tickValue, attrValue: attrValue, color: attrValue, scaleValue: value, }; }); if (!containMin) { items.push({ value: scale.min, attrValue: attr.mapping(scale.invert(0)).join(''), color: attr.mapping(scale.invert(0)).join(''), scaleValue: 0, }); } if (!containMax) { items.push({ value: scale.max, attrValue: attr.mapping(scale.invert(1)).join(''), color: attr.mapping(scale.invert(1)).join(''), scaleValue: 1, }); } // 排序 items.sort(function (a, b) { return a.value - b.value; }); // 跟 attr 相关的配置 // size color 区别的配置 var attrLegendCfg = { min: head(items).value, max: last(items).value, colors: [], rail: { type: attr.type, }, track: {}, }; if (attr.type === 'size') { attrLegendCfg.track = { style: { // size 的选中前景色,对于 color,则直接使用 color 标识 // @ts-ignore fill: attr.type === 'size' ? this.view.getTheme().defaultColor : undefined, }, }; } if (attr.type === 'color') { attrLegendCfg.colors = items.map(function (item) { return item.attrValue; }); } var container = this.container; // if position is not set, use top as default var direction = getDirection(legendOption); var layout = getLegendLayout(direction); var title = get(legendOption, 'title'); if (title) { title = deepMix({ text: getName(scale), }, title); } // 基础配置,从当前数据中读到的配置 attrLegendCfg.container = container; attrLegendCfg.layout = layout; attrLegendCfg.title = title; attrLegendCfg.animateOption = DEFAULT_ANIMATE_CFG; // @ts-ignore return this.mergeLegendCfg(attrLegendCfg, legendOption, 'continuous'); }; /** * 获取分类图例的配置项 * @param geometry * @param attr * @param scale * @param custom * @param legendOption */ Legend.prototype.getCategoryCfg = function (geometry, attr, scale, legendOption, custom) { var container = this.container; // if position is not set, use top as default var direction = get(legendOption, 'position', DIRECTION.BOTTOM); // the default marker style var themeMarker = get(this.view.getTheme(), ['components', 'legend', direction, 'marker']); var userMarker = get(legendOption, 'marker'); var layout = getLegendLayout(direction); var items = custom ? getCustomLegendItems(themeMarker, userMarker, legendOption.items) : getLegendItems(this.view, geometry, attr, themeMarker, userMarker); var title = get(legendOption, 'title'); if (title) { title = deepMix({ text: scale ? getName(scale) : '', }, title); } var baseCfg = this.getCategoryLegendSizeCfg(layout); baseCfg.container = container; baseCfg.layout = layout; baseCfg.items = items; baseCfg.title = title; baseCfg.animateOption = DEFAULT_ANIMATE_CFG; var categoryCfg = this.mergeLegendCfg(baseCfg, legendOption, direction); if (categoryCfg.reversed) { // 图例项需要逆序 categoryCfg.items.reverse(); } return categoryCfg; }; /** * get legend config, use option > suggestion > theme * @param baseCfg * @param legendOption * @param direction */ Legend.prototype.mergeLegendCfg = function (baseCfg, legendOption, direction) { var themeObject = get(this.view.getTheme(), ['components', 'legend', direction], {}); return deepMix({}, themeObject, baseCfg, legendOption); }; /** * 生成 id * @param key */ Legend.prototype.getId = function (key) { return this.name + "-" + key; }; /** * 根据 id 来获取组件 * @param id */ Legend.prototype.getComponentById = function (id) { return find(this.components, function (co) { return co.id === id; }); }; Legend.prototype.getCategoryLegendSizeCfg = function (layout) { var _a = this.view.viewBBox, vw = _a.width, vh = _a.height; var _b = this.view.coordinateBBox, cw = _b.width, ch = _b.height; return layout === 'vertical' ? { maxWidth: vw * COMPONENT_MAX_VIEW_PERCENTAGE, maxHeight: ch, } : { maxWidth: cw, maxHeight: vh * COMPONENT_MAX_VIEW_PERCENTAGE, }; }; return Legend; }(Controller)); export default Legend; //# sourceMappingURL=legend.js.map