UNPKG

19.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var tslib_1 = require("tslib");
4var util_1 = require("@antv/util");
5var constant_1 = require("../../constant");
6var dependents_1 = require("../../dependents");
7var animate_1 = require("../../animate");
8var bbox_1 = require("../../util/bbox");
9var direction_1 = require("../../util/direction");
10var helper_1 = require("../../util/helper");
11var legend_1 = require("../../util/legend");
12var scale_1 = require("../../util/scale");
13var base_1 = require("./base");
14/**
15 * 从配置中获取单个字段的 legend 配置
16 * @param legends
17 * @param field
18 * @returns the option of one legend field
19 */
20function getLegendOption(legends, field) {
21 if ((0, util_1.isBoolean)(legends)) {
22 return legends === false ? false : {};
23 }
24 return (0, util_1.get)(legends, [field], legends);
25}
26function getDirection(legendOption) {
27 return (0, util_1.get)(legendOption, 'position', constant_1.DIRECTION.BOTTOM);
28}
29/**
30 * @ignore
31 * legend Controller
32 */
33var Legend = /** @class */ (function (_super) {
34 tslib_1.__extends(Legend, _super);
35 function Legend(view) {
36 var _this = _super.call(this, view) || this;
37 _this.container = _this.view.getLayer(constant_1.LAYER.FORE).addGroup();
38 return _this;
39 }
40 Object.defineProperty(Legend.prototype, "name", {
41 get: function () {
42 return 'legend';
43 },
44 enumerable: false,
45 configurable: true
46 });
47 Legend.prototype.init = function () { };
48 /**
49 * render the legend component by legend options
50 */
51 Legend.prototype.render = function () {
52 // 和 update 逻辑保持一致
53 this.update();
54 };
55 /**
56 * layout legend
57 * 计算出 legend 的 direction 位置 x, y
58 */
59 Legend.prototype.layout = function () {
60 var _this = this;
61 this.layoutBBox = this.view.viewBBox;
62 (0, util_1.each)(this.components, function (co) {
63 var component = co.component, direction = co.direction;
64 var layout = (0, legend_1.getLegendLayout)(direction);
65 var maxWidthRatio = component.get('maxWidthRatio');
66 var maxHeightRatio = component.get('maxHeightRatio');
67 var maxSize = _this.getCategoryLegendSizeCfg(layout, maxWidthRatio, maxHeightRatio);
68 var maxWidth = component.get('maxWidth');
69 var maxHeight = component.get('maxHeight');
70 // 先更新 maxSize,更新 layoutBBox,以便计算正确的 x y
71 component.update({
72 maxWidth: Math.min(maxSize.maxWidth, maxWidth || 0),
73 maxHeight: Math.min(maxSize.maxHeight, maxHeight || 0),
74 });
75 var padding = component.get('padding');
76 var bboxObject = component.getLayoutBBox(); // 这里只需要他的 width、height 信息做位置调整
77 var bbox = new bbox_1.BBox(bboxObject.x, bboxObject.y, bboxObject.width, bboxObject.height).expand(padding);
78 var _a = tslib_1.__read((0, direction_1.directionToPosition)(_this.view.viewBBox, bbox, direction), 2), x1 = _a[0], y1 = _a[1];
79 var _b = tslib_1.__read((0, direction_1.directionToPosition)(_this.layoutBBox, bbox, direction), 2), x2 = _b[0], y2 = _b[1];
80 var x = 0;
81 var y = 0;
82 // 因为 legend x y 要和 coordinateBBox 对齐,所以要做一个简单的判断
83 if (direction.startsWith('top') || direction.startsWith('bottom')) {
84 x = x1;
85 y = y2;
86 }
87 else {
88 x = x2;
89 y = y1;
90 }
91 // 更新位置
92 component.setLocation({ x: x + padding[3], y: y + padding[0] });
93 _this.layoutBBox = _this.layoutBBox.cut(bbox, direction);
94 });
95 };
96 /**
97 * legend 的更新逻辑
98 */
99 Legend.prototype.update = function () {
100 var _this = this;
101 this.option = this.view.getOptions().legends;
102 // 已经处理过的 legend
103 var updated = {};
104 var eachLegend = function (geometry, attr, scale) {
105 var id = _this.getId(scale.field);
106 var existCo = _this.getComponentById(id);
107 // 存在则 update
108 if (existCo) {
109 var cfg = void 0;
110 var legendOption = getLegendOption(_this.option, scale.field);
111 // if the legend option is not false, means legend should be created.
112 if (legendOption !== false) {
113 if ((0, util_1.get)(legendOption, 'custom')) {
114 cfg = _this.getCategoryCfg(geometry, attr, scale, legendOption, true);
115 }
116 else {
117 if (scale.isLinear) {
118 // linear field, create continuous legend
119 cfg = _this.getContinuousCfg(geometry, attr, scale, legendOption);
120 }
121 else if (scale.isCategory) {
122 // category field, create category legend
123 cfg = _this.getCategoryCfg(geometry, attr, scale, legendOption);
124 }
125 }
126 }
127 // 如果 cfg 为空,则不在 updated 标记,那么会在后面逻辑中删除
128 if (cfg) {
129 // omit 掉一些属性,比如 container 等
130 (0, helper_1.omit)(cfg, ['container']);
131 existCo.direction = getDirection(legendOption);
132 existCo.component.update(cfg);
133 // 标记为新的
134 updated[id] = true;
135 }
136 }
137 else {
138 // 不存在则 create
139 var legend = _this.createFieldLegend(geometry, attr, scale);
140 if (legend) {
141 legend.component.init();
142 _this.components.push(legend);
143 // 标记为新的
144 updated[id] = true;
145 }
146 }
147 };
148 // 全局自定义图例
149 if ((0, util_1.get)(this.option, 'custom')) {
150 var id = 'global-custom';
151 var existCo = this.getComponentById(id);
152 if (existCo) {
153 var customCfg = this.getCategoryCfg(undefined, undefined, undefined, this.option, true);
154 (0, helper_1.omit)(customCfg, ['container']);
155 existCo.component.update(customCfg);
156 updated[id] = true;
157 }
158 else {
159 var component = this.createCustomLegend(undefined, undefined, undefined, this.option);
160 if (component) {
161 component.init();
162 var layer = constant_1.LAYER.FORE;
163 var direction = getDirection(this.option);
164 this.components.push({
165 id: id,
166 component: component,
167 layer: layer,
168 direction: direction,
169 type: constant_1.COMPONENT_TYPE.LEGEND,
170 extra: undefined,
171 });
172 // 标记为更新
173 updated[id] = true;
174 }
175 }
176 }
177 else {
178 // 遍历处理每一个创建逻辑
179 this.loopLegends(eachLegend);
180 }
181 // 处理完成之后,销毁删除的
182 // 不在处理中的
183 var components = [];
184 (0, util_1.each)(this.getComponents(), function (co) {
185 if (updated[co.id]) {
186 components.push(co);
187 }
188 else {
189 co.component.destroy();
190 }
191 });
192 // 更新当前已有的 components
193 this.components = components;
194 };
195 Legend.prototype.clear = function () {
196 _super.prototype.clear.call(this);
197 this.container.clear();
198 };
199 Legend.prototype.destroy = function () {
200 _super.prototype.destroy.call(this);
201 this.container.remove(true);
202 };
203 /**
204 * 递归获取所有的 Geometry
205 */
206 Legend.prototype.getGeometries = function (view) {
207 var _this = this;
208 var geometries = view.geometries;
209 (0, util_1.each)(view.views, function (v) {
210 geometries = geometries.concat(_this.getGeometries(v));
211 });
212 return geometries;
213 };
214 /**
215 * 遍历 Geometry,处理 legend 逻辑
216 * @param doEach 每个 loop 中的处理方法
217 */
218 Legend.prototype.loopLegends = function (doEach) {
219 var isRootView = this.view.getRootView() === this.view;
220 // 非根 view,不处理 legend
221 if (!isRootView) {
222 return;
223 }
224 // 递归 view 中所有的 Geometry,进行创建 legend
225 var geometries = this.getGeometries(this.view);
226 var looped = {}; // 防止一个字段创建两个 legend
227 (0, util_1.each)(geometries, function (geometry) {
228 var attributes = geometry.getGroupAttributes();
229 (0, util_1.each)(attributes, function (attr) {
230 var scale = attr.getScale(attr.type);
231 // 如果在视觉通道上映射常量值,如 size(2) shape('circle') 不创建 legend
232 if (!scale || scale.type === 'identity' || looped[scale.field]) {
233 return;
234 }
235 doEach(geometry, attr, scale);
236 looped[scale.field] = true;
237 });
238 });
239 };
240 /**
241 * 创建一个 legend
242 * @param geometry
243 * @param attr
244 * @param scale
245 */
246 Legend.prototype.createFieldLegend = function (geometry, attr, scale) {
247 var component;
248 var legendOption = getLegendOption(this.option, scale.field);
249 var layer = constant_1.LAYER.FORE;
250 var direction = getDirection(legendOption);
251 // if the legend option is not false, means legend should be created.
252 if (legendOption !== false) {
253 if ((0, util_1.get)(legendOption, 'custom')) {
254 component = this.createCustomLegend(geometry, attr, scale, legendOption);
255 }
256 else {
257 if (scale.isLinear) {
258 // linear field, create continuous legend
259 component = this.createContinuousLegend(geometry, attr, scale, legendOption);
260 }
261 else if (scale.isCategory) {
262 // category field, create category legend
263 component = this.createCategoryLegend(geometry, attr, scale, legendOption);
264 }
265 }
266 }
267 if (component) {
268 component.set('field', scale.field);
269 return {
270 id: this.getId(scale.field),
271 component: component,
272 layer: layer,
273 direction: direction,
274 type: constant_1.COMPONENT_TYPE.LEGEND,
275 extra: { scale: scale },
276 };
277 }
278 };
279 /**
280 * 自定义图例使用 category 图例去渲染
281 * @param geometry
282 * @param attr
283 * @param scale
284 * @param legendOption
285 */
286 Legend.prototype.createCustomLegend = function (geometry, attr, scale, legendOption) {
287 // 直接使用 分类图例渲染
288 var cfg = this.getCategoryCfg(geometry, attr, scale, legendOption, true);
289 return new dependents_1.CategoryLegend(cfg);
290 };
291 /**
292 * 创建连续图例
293 * @param geometry
294 * @param attr
295 * @param scale
296 * @param legendOption
297 */
298 Legend.prototype.createContinuousLegend = function (geometry, attr, scale, legendOption) {
299 var cfg = this.getContinuousCfg(geometry, attr, scale, (0, helper_1.omit)(legendOption, ['value']));
300 return new dependents_1.ContinuousLegend(cfg);
301 };
302 /**
303 * 创建分类图例
304 * @param geometry
305 * @param attr
306 * @param scale
307 * @param legendOption
308 */
309 Legend.prototype.createCategoryLegend = function (geometry, attr, scale, legendOption) {
310 var cfg = this.getCategoryCfg(geometry, attr, scale, legendOption);
311 return new dependents_1.CategoryLegend(cfg);
312 };
313 /**
314 * 获得连续图例的配置
315 * @param geometry
316 * @param attr
317 * @param scale
318 * @param legendOption
319 */
320 Legend.prototype.getContinuousCfg = function (geometry, attr, scale, legendOption) {
321 var ticks = scale.getTicks();
322 var containMin = (0, util_1.find)(ticks, function (tick) { return tick.value === 0; });
323 var containMax = (0, util_1.find)(ticks, function (tick) { return tick.value === 1; });
324 var items = ticks.map(function (tick) {
325 var value = tick.value, tickValue = tick.tickValue;
326 var attrValue = attr.mapping(scale.invert(value)).join('');
327 return {
328 value: tickValue,
329 attrValue: attrValue,
330 color: attrValue,
331 scaleValue: value,
332 };
333 });
334 if (!containMin) {
335 items.push({
336 value: scale.min,
337 attrValue: attr.mapping(scale.invert(0)).join(''),
338 color: attr.mapping(scale.invert(0)).join(''),
339 scaleValue: 0,
340 });
341 }
342 if (!containMax) {
343 items.push({
344 value: scale.max,
345 attrValue: attr.mapping(scale.invert(1)).join(''),
346 color: attr.mapping(scale.invert(1)).join(''),
347 scaleValue: 1,
348 });
349 }
350 // 排序
351 items.sort(function (a, b) { return a.value - b.value; });
352 // 跟 attr 相关的配置
353 // size color 区别的配置
354 var attrLegendCfg = {
355 min: (0, util_1.head)(items).value,
356 max: (0, util_1.last)(items).value,
357 colors: [],
358 rail: {
359 type: attr.type,
360 },
361 track: {},
362 };
363 if (attr.type === 'size') {
364 attrLegendCfg.track = {
365 style: {
366 // size 的选中前景色,对于 color,则直接使用 color 标识
367 // @ts-ignore
368 fill: attr.type === 'size' ? this.view.getTheme().defaultColor : undefined,
369 },
370 };
371 }
372 if (attr.type === 'color') {
373 attrLegendCfg.colors = items.map(function (item) { return item.attrValue; });
374 }
375 var container = this.container;
376 // if position is not set, use top as default
377 var direction = getDirection(legendOption);
378 var layout = (0, legend_1.getLegendLayout)(direction);
379 var title = (0, util_1.get)(legendOption, 'title');
380 if (title) {
381 title = (0, util_1.deepMix)({
382 text: (0, scale_1.getName)(scale),
383 }, title);
384 }
385 // 基础配置,从当前数据中读到的配置
386 attrLegendCfg.container = container;
387 attrLegendCfg.layout = layout;
388 attrLegendCfg.title = title;
389 attrLegendCfg.animateOption = animate_1.DEFAULT_ANIMATE_CFG;
390 // @ts-ignore
391 return this.mergeLegendCfg(attrLegendCfg, legendOption, 'continuous');
392 };
393 /**
394 * 获取分类图例的配置项
395 * @param geometry
396 * @param attr
397 * @param scale
398 * @param custom
399 * @param legendOption
400 */
401 Legend.prototype.getCategoryCfg = function (geometry, attr, scale, legendOption, custom) {
402 var container = this.container;
403 // if position is not set, use top as default
404 var direction = (0, util_1.get)(legendOption, 'position', constant_1.DIRECTION.BOTTOM);
405 var legendTheme = (0, legend_1.getLegendThemeCfg)(this.view.getTheme(), direction);
406 // the default marker style
407 var themeMarker = (0, util_1.get)(legendTheme, ['marker']);
408 var userMarker = (0, util_1.get)(legendOption, 'marker');
409 var layout = (0, legend_1.getLegendLayout)(direction);
410 var themePageNavigator = (0, util_1.get)(legendTheme, ['pageNavigator']);
411 var userPageNavigator = (0, util_1.get)(legendOption, 'pageNavigator');
412 var items = custom
413 ? (0, legend_1.getCustomLegendItems)(themeMarker, userMarker, legendOption.items)
414 : (0, legend_1.getLegendItems)(this.view, geometry, attr, themeMarker, userMarker);
415 var title = (0, util_1.get)(legendOption, 'title');
416 if (title) {
417 title = (0, util_1.deepMix)({
418 text: scale ? (0, scale_1.getName)(scale) : '',
419 }, title);
420 }
421 var maxWidthRatio = (0, util_1.get)(legendOption, 'maxWidthRatio');
422 var maxHeightRatio = (0, util_1.get)(legendOption, 'maxHeightRatio');
423 var baseCfg = this.getCategoryLegendSizeCfg(layout, maxWidthRatio, maxHeightRatio);
424 baseCfg.container = container;
425 baseCfg.layout = layout;
426 baseCfg.items = items;
427 baseCfg.title = title;
428 baseCfg.animateOption = animate_1.DEFAULT_ANIMATE_CFG;
429 baseCfg.pageNavigator = (0, util_1.deepMix)({}, themePageNavigator, userPageNavigator);
430 var categoryCfg = this.mergeLegendCfg(baseCfg, legendOption, direction);
431 if (categoryCfg.reversed) {
432 // 图例项需要逆序
433 categoryCfg.items.reverse();
434 }
435 var maxItemWidth = (0, util_1.get)(categoryCfg, 'maxItemWidth');
436 if (maxItemWidth && maxItemWidth <= 1) {
437 // 转换成像素值
438 categoryCfg.maxItemWidth = this.view.viewBBox.width * maxItemWidth;
439 }
440 return categoryCfg;
441 };
442 /**
443 * get legend config, use option > suggestion > theme
444 * @param baseCfg
445 * @param legendOption
446 * @param direction
447 */
448 Legend.prototype.mergeLegendCfg = function (baseCfg, legendOption, direction) {
449 var position = direction.split('-')[0];
450 var themeObject = (0, legend_1.getLegendThemeCfg)(this.view.getTheme(), position);
451 return (0, util_1.deepMix)({}, themeObject, baseCfg, legendOption);
452 };
453 /**
454 * 生成 id
455 * @param key
456 */
457 Legend.prototype.getId = function (key) {
458 return "".concat(this.name, "-").concat(key);
459 };
460 /**
461 * 根据 id 来获取组件
462 * @param id
463 */
464 Legend.prototype.getComponentById = function (id) {
465 return (0, util_1.find)(this.components, function (co) { return co.id === id; });
466 };
467 Legend.prototype.getCategoryLegendSizeCfg = function (layout, maxWidthRatio, maxHeightRatio) {
468 if (maxWidthRatio === void 0) { maxWidthRatio = constant_1.COMPONENT_MAX_VIEW_PERCENTAGE; }
469 if (maxHeightRatio === void 0) { maxHeightRatio = constant_1.COMPONENT_MAX_VIEW_PERCENTAGE; }
470 var _a = this.view.viewBBox, vw = _a.width, vh = _a.height;
471 // 目前 legend 的布局是以 viewBBox 为参照
472 // const { width: cw, height: ch } = this.view.coordinateBBox;
473 return layout === 'vertical'
474 ? {
475 maxWidth: vw * maxWidthRatio,
476 maxHeight: vh,
477 }
478 : {
479 maxWidth: vw,
480 maxHeight: vh * maxHeightRatio,
481 };
482 };
483 return Legend;
484}(base_1.Controller));
485exports.default = Legend;
486//# sourceMappingURL=legend.js.map
\No newline at end of file