LegendView.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { __extends } from "tslib";
  41. import * as zrUtil from 'zrender/lib/core/util';
  42. import { parse, stringify } from 'zrender/lib/tool/color';
  43. import * as graphic from '../../util/graphic';
  44. import { enableHoverEmphasis } from '../../util/states';
  45. import { setLabelStyle, createTextStyle } from '../../label/labelStyle';
  46. import { makeBackground } from '../helper/listComponent';
  47. import * as layoutUtil from '../../util/layout';
  48. import ComponentView from '../../view/Component';
  49. import { createSymbol } from '../../util/symbol';
  50. var curry = zrUtil.curry;
  51. var each = zrUtil.each;
  52. var Group = graphic.Group;
  53. var LegendView =
  54. /** @class */
  55. function (_super) {
  56. __extends(LegendView, _super);
  57. function LegendView() {
  58. var _this = _super !== null && _super.apply(this, arguments) || this;
  59. _this.type = LegendView.type;
  60. _this.newlineDisabled = false;
  61. return _this;
  62. }
  63. LegendView.prototype.init = function () {
  64. this.group.add(this._contentGroup = new Group());
  65. this.group.add(this._selectorGroup = new Group());
  66. this._isFirstRender = true;
  67. };
  68. /**
  69. * @protected
  70. */
  71. LegendView.prototype.getContentGroup = function () {
  72. return this._contentGroup;
  73. };
  74. /**
  75. * @protected
  76. */
  77. LegendView.prototype.getSelectorGroup = function () {
  78. return this._selectorGroup;
  79. };
  80. /**
  81. * @override
  82. */
  83. LegendView.prototype.render = function (legendModel, ecModel, api) {
  84. var isFirstRender = this._isFirstRender;
  85. this._isFirstRender = false;
  86. this.resetInner();
  87. if (!legendModel.get('show', true)) {
  88. return;
  89. }
  90. var itemAlign = legendModel.get('align');
  91. var orient = legendModel.get('orient');
  92. if (!itemAlign || itemAlign === 'auto') {
  93. itemAlign = legendModel.get('left') === 'right' && orient === 'vertical' ? 'right' : 'left';
  94. } // selector has been normalized to an array in model
  95. var selector = legendModel.get('selector', true);
  96. var selectorPosition = legendModel.get('selectorPosition', true);
  97. if (selector && (!selectorPosition || selectorPosition === 'auto')) {
  98. selectorPosition = orient === 'horizontal' ? 'end' : 'start';
  99. }
  100. this.renderInner(itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition); // Perform layout.
  101. var positionInfo = legendModel.getBoxLayoutParams();
  102. var viewportSize = {
  103. width: api.getWidth(),
  104. height: api.getHeight()
  105. };
  106. var padding = legendModel.get('padding');
  107. var maxSize = layoutUtil.getLayoutRect(positionInfo, viewportSize, padding);
  108. var mainRect = this.layoutInner(legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition); // Place mainGroup, based on the calculated `mainRect`.
  109. var layoutRect = layoutUtil.getLayoutRect(zrUtil.defaults({
  110. width: mainRect.width,
  111. height: mainRect.height
  112. }, positionInfo), viewportSize, padding);
  113. this.group.x = layoutRect.x - mainRect.x;
  114. this.group.y = layoutRect.y - mainRect.y;
  115. this.group.markRedraw(); // Render background after group is layout.
  116. this.group.add(this._backgroundEl = makeBackground(mainRect, legendModel));
  117. };
  118. LegendView.prototype.resetInner = function () {
  119. this.getContentGroup().removeAll();
  120. this._backgroundEl && this.group.remove(this._backgroundEl);
  121. this.getSelectorGroup().removeAll();
  122. };
  123. LegendView.prototype.renderInner = function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) {
  124. var contentGroup = this.getContentGroup();
  125. var legendDrawnMap = zrUtil.createHashMap();
  126. var selectMode = legendModel.get('selectedMode');
  127. var excludeSeriesId = [];
  128. ecModel.eachRawSeries(function (seriesModel) {
  129. !seriesModel.get('legendHoverLink') && excludeSeriesId.push(seriesModel.id);
  130. });
  131. each(legendModel.getData(), function (legendItemModel, dataIndex) {
  132. var name = legendItemModel.get('name'); // Use empty string or \n as a newline string
  133. if (!this.newlineDisabled && (name === '' || name === '\n')) {
  134. var g = new Group(); // @ts-ignore
  135. g.newline = true;
  136. contentGroup.add(g);
  137. return;
  138. } // Representitive series.
  139. var seriesModel = ecModel.getSeriesByName(name)[0];
  140. if (legendDrawnMap.get(name)) {
  141. // Have been drawed
  142. return;
  143. } // Legend to control series.
  144. if (seriesModel) {
  145. var data = seriesModel.getData();
  146. var lineVisualStyle = data.getVisual('legendLineStyle') || {};
  147. var legendIcon = data.getVisual('legendIcon');
  148. /**
  149. * `data.getVisual('style')` may be the color from the register
  150. * in series. For example, for line series,
  151. */
  152. var style = data.getVisual('style');
  153. var itemGroup = this._createItem(seriesModel, name, dataIndex, legendItemModel, legendModel, itemAlign, lineVisualStyle, style, legendIcon, selectMode);
  154. itemGroup.on('click', curry(dispatchSelectAction, name, null, api, excludeSeriesId)).on('mouseover', curry(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId)).on('mouseout', curry(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId));
  155. legendDrawnMap.set(name, true);
  156. } else {
  157. // Legend to control data. In pie and funnel.
  158. ecModel.eachRawSeries(function (seriesModel) {
  159. // In case multiple series has same data name
  160. if (legendDrawnMap.get(name)) {
  161. return;
  162. }
  163. if (seriesModel.legendVisualProvider) {
  164. var provider = seriesModel.legendVisualProvider;
  165. if (!provider.containName(name)) {
  166. return;
  167. }
  168. var idx = provider.indexOfName(name);
  169. var style = provider.getItemVisual(idx, 'style');
  170. var legendIcon = provider.getItemVisual(idx, 'legendIcon');
  171. var colorArr = parse(style.fill); // Color may be set to transparent in visualMap when data is out of range.
  172. // Do not show nothing.
  173. if (colorArr && colorArr[3] === 0) {
  174. colorArr[3] = 0.2; // TODO color is set to 0, 0, 0, 0. Should show correct RGBA
  175. style.fill = stringify(colorArr, 'rgba');
  176. }
  177. var itemGroup = this._createItem(seriesModel, name, dataIndex, legendItemModel, legendModel, itemAlign, {}, style, legendIcon, selectMode); // FIXME: consider different series has items with the same name.
  178. itemGroup.on('click', curry(dispatchSelectAction, null, name, api, excludeSeriesId)) // Should not specify the series name, consider legend controls
  179. // more than one pie series.
  180. .on('mouseover', curry(dispatchHighlightAction, null, name, api, excludeSeriesId)).on('mouseout', curry(dispatchDownplayAction, null, name, api, excludeSeriesId));
  181. legendDrawnMap.set(name, true);
  182. }
  183. }, this);
  184. }
  185. if (process.env.NODE_ENV !== 'production') {
  186. if (!legendDrawnMap.get(name)) {
  187. console.warn(name + ' series not exists. Legend data should be same with series name or data name.');
  188. }
  189. }
  190. }, this);
  191. if (selector) {
  192. this._createSelector(selector, legendModel, api, orient, selectorPosition);
  193. }
  194. };
  195. LegendView.prototype._createSelector = function (selector, legendModel, api, orient, selectorPosition) {
  196. var selectorGroup = this.getSelectorGroup();
  197. each(selector, function createSelectorButton(selectorItem) {
  198. var type = selectorItem.type;
  199. var labelText = new graphic.Text({
  200. style: {
  201. x: 0,
  202. y: 0,
  203. align: 'center',
  204. verticalAlign: 'middle'
  205. },
  206. onclick: function () {
  207. api.dispatchAction({
  208. type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect'
  209. });
  210. }
  211. });
  212. selectorGroup.add(labelText);
  213. var labelModel = legendModel.getModel('selectorLabel');
  214. var emphasisLabelModel = legendModel.getModel(['emphasis', 'selectorLabel']);
  215. setLabelStyle(labelText, {
  216. normal: labelModel,
  217. emphasis: emphasisLabelModel
  218. }, {
  219. defaultText: selectorItem.title
  220. });
  221. enableHoverEmphasis(labelText);
  222. });
  223. };
  224. LegendView.prototype._createItem = function (seriesModel, name, dataIndex, legendItemModel, legendModel, itemAlign, lineVisualStyle, itemVisualStyle, legendIcon, selectMode) {
  225. var drawType = seriesModel.visualDrawType;
  226. var itemWidth = legendModel.get('itemWidth');
  227. var itemHeight = legendModel.get('itemHeight');
  228. var isSelected = legendModel.isSelected(name);
  229. var iconRotate = legendItemModel.get('symbolRotate');
  230. var symbolKeepAspect = legendItemModel.get('symbolKeepAspect');
  231. var legendIconType = legendItemModel.get('icon');
  232. legendIcon = legendIconType || legendIcon || 'roundRect';
  233. var style = getLegendStyle(legendIcon, legendItemModel, lineVisualStyle, itemVisualStyle, drawType, isSelected);
  234. var itemGroup = new Group();
  235. var textStyleModel = legendItemModel.getModel('textStyle');
  236. if (typeof seriesModel.getLegendIcon === 'function' && (!legendIconType || legendIconType === 'inherit')) {
  237. // Series has specific way to define legend icon
  238. itemGroup.add(seriesModel.getLegendIcon({
  239. itemWidth: itemWidth,
  240. itemHeight: itemHeight,
  241. icon: legendIcon,
  242. iconRotate: iconRotate,
  243. itemStyle: style.itemStyle,
  244. lineStyle: style.lineStyle,
  245. symbolKeepAspect: symbolKeepAspect
  246. }));
  247. } else {
  248. // Use default legend icon policy for most series
  249. var rotate = legendIconType === 'inherit' && seriesModel.getData().getVisual('symbol') ? iconRotate === 'inherit' ? seriesModel.getData().getVisual('symbolRotate') : iconRotate : 0; // No rotation for no icon
  250. itemGroup.add(getDefaultLegendIcon({
  251. itemWidth: itemWidth,
  252. itemHeight: itemHeight,
  253. icon: legendIcon,
  254. iconRotate: rotate,
  255. itemStyle: style.itemStyle,
  256. lineStyle: style.lineStyle,
  257. symbolKeepAspect: symbolKeepAspect
  258. }));
  259. }
  260. var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
  261. var textAlign = itemAlign;
  262. var formatter = legendModel.get('formatter');
  263. var content = name;
  264. if (typeof formatter === 'string' && formatter) {
  265. content = formatter.replace('{name}', name != null ? name : '');
  266. } else if (typeof formatter === 'function') {
  267. content = formatter(name);
  268. }
  269. var inactiveColor = legendItemModel.get('inactiveColor');
  270. itemGroup.add(new graphic.Text({
  271. style: createTextStyle(textStyleModel, {
  272. text: content,
  273. x: textX,
  274. y: itemHeight / 2,
  275. fill: isSelected ? textStyleModel.getTextColor() : inactiveColor,
  276. align: textAlign,
  277. verticalAlign: 'middle'
  278. })
  279. })); // Add a invisible rect to increase the area of mouse hover
  280. var hitRect = new graphic.Rect({
  281. shape: itemGroup.getBoundingRect(),
  282. invisible: true
  283. });
  284. var tooltipModel = legendItemModel.getModel('tooltip');
  285. if (tooltipModel.get('show')) {
  286. graphic.setTooltipConfig({
  287. el: hitRect,
  288. componentModel: legendModel,
  289. itemName: name,
  290. itemTooltipOption: tooltipModel.option
  291. });
  292. }
  293. itemGroup.add(hitRect);
  294. itemGroup.eachChild(function (child) {
  295. child.silent = true;
  296. });
  297. hitRect.silent = !selectMode;
  298. this.getContentGroup().add(itemGroup);
  299. enableHoverEmphasis(itemGroup); // @ts-ignore
  300. itemGroup.__legendDataIndex = dataIndex;
  301. return itemGroup;
  302. };
  303. LegendView.prototype.layoutInner = function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) {
  304. var contentGroup = this.getContentGroup();
  305. var selectorGroup = this.getSelectorGroup(); // Place items in contentGroup.
  306. layoutUtil.box(legendModel.get('orient'), contentGroup, legendModel.get('itemGap'), maxSize.width, maxSize.height);
  307. var contentRect = contentGroup.getBoundingRect();
  308. var contentPos = [-contentRect.x, -contentRect.y];
  309. selectorGroup.markRedraw();
  310. contentGroup.markRedraw();
  311. if (selector) {
  312. // Place buttons in selectorGroup
  313. layoutUtil.box( // Buttons in selectorGroup always layout horizontally
  314. 'horizontal', selectorGroup, legendModel.get('selectorItemGap', true));
  315. var selectorRect = selectorGroup.getBoundingRect();
  316. var selectorPos = [-selectorRect.x, -selectorRect.y];
  317. var selectorButtonGap = legendModel.get('selectorButtonGap', true);
  318. var orientIdx = legendModel.getOrient().index;
  319. var wh = orientIdx === 0 ? 'width' : 'height';
  320. var hw = orientIdx === 0 ? 'height' : 'width';
  321. var yx = orientIdx === 0 ? 'y' : 'x';
  322. if (selectorPosition === 'end') {
  323. selectorPos[orientIdx] += contentRect[wh] + selectorButtonGap;
  324. } else {
  325. contentPos[orientIdx] += selectorRect[wh] + selectorButtonGap;
  326. } //Always align selector to content as 'middle'
  327. selectorPos[1 - orientIdx] += contentRect[hw] / 2 - selectorRect[hw] / 2;
  328. selectorGroup.x = selectorPos[0];
  329. selectorGroup.y = selectorPos[1];
  330. contentGroup.x = contentPos[0];
  331. contentGroup.y = contentPos[1];
  332. var mainRect = {
  333. x: 0,
  334. y: 0
  335. };
  336. mainRect[wh] = contentRect[wh] + selectorButtonGap + selectorRect[wh];
  337. mainRect[hw] = Math.max(contentRect[hw], selectorRect[hw]);
  338. mainRect[yx] = Math.min(0, selectorRect[yx] + selectorPos[1 - orientIdx]);
  339. return mainRect;
  340. } else {
  341. contentGroup.x = contentPos[0];
  342. contentGroup.y = contentPos[1];
  343. return this.group.getBoundingRect();
  344. }
  345. };
  346. /**
  347. * @protected
  348. */
  349. LegendView.prototype.remove = function () {
  350. this.getContentGroup().removeAll();
  351. this._isFirstRender = true;
  352. };
  353. LegendView.type = 'legend.plain';
  354. return LegendView;
  355. }(ComponentView);
  356. function getLegendStyle(iconType, legendModel, lineVisualStyle, itemVisualStyle, drawType, isSelected) {
  357. /**
  358. * Use series style if is inherit;
  359. * elsewise, use legend style
  360. */
  361. function handleCommonProps(style, visualStyle) {
  362. // If lineStyle.width is 'auto', it is set to be 2 if series has border
  363. if (style.lineWidth === 'auto') {
  364. style.lineWidth = visualStyle.lineWidth > 0 ? 2 : 0;
  365. }
  366. each(style, function (propVal, propName) {
  367. style[propName] === 'inherit' && (style[propName] = visualStyle[propName]);
  368. });
  369. } // itemStyle
  370. var legendItemModel = legendModel.getModel('itemStyle');
  371. var itemStyle = legendItemModel.getItemStyle();
  372. var iconBrushType = iconType.lastIndexOf('empty', 0) === 0 ? 'fill' : 'stroke';
  373. itemStyle.decal = itemVisualStyle.decal;
  374. if (itemStyle.fill === 'inherit') {
  375. /**
  376. * Series with visualDrawType as 'stroke' should have
  377. * series stroke as legend fill
  378. */
  379. itemStyle.fill = itemVisualStyle[drawType];
  380. }
  381. if (itemStyle.stroke === 'inherit') {
  382. /**
  383. * icon type with "emptyXXX" should use fill color
  384. * in visual style
  385. */
  386. itemStyle.stroke = itemVisualStyle[iconBrushType];
  387. }
  388. if (itemStyle.opacity === 'inherit') {
  389. /**
  390. * Use lineStyle.opacity if drawType is stroke
  391. */
  392. itemStyle.opacity = (drawType === 'fill' ? itemVisualStyle : lineVisualStyle).opacity;
  393. }
  394. handleCommonProps(itemStyle, itemVisualStyle); // lineStyle
  395. var legendLineModel = legendModel.getModel('lineStyle');
  396. var lineStyle = legendLineModel.getLineStyle();
  397. handleCommonProps(lineStyle, lineVisualStyle); // Fix auto color to real color
  398. itemStyle.fill === 'auto' && (itemStyle.fill = itemVisualStyle.fill);
  399. itemStyle.stroke === 'auto' && (itemStyle.stroke = itemVisualStyle.fill);
  400. lineStyle.stroke === 'auto' && (lineStyle.stroke = itemVisualStyle.fill);
  401. if (!isSelected) {
  402. var borderWidth = legendModel.get('inactiveBorderWidth');
  403. /**
  404. * Since stroke is set to be inactiveBorderColor, it may occur that
  405. * there is no border in series but border in legend, so we need to
  406. * use border only when series has border if is set to be auto
  407. */
  408. var visualHasBorder = itemStyle[iconBrushType];
  409. itemStyle.lineWidth = borderWidth === 'auto' ? itemVisualStyle.lineWidth > 0 && visualHasBorder ? 2 : 0 : itemStyle.lineWidth;
  410. itemStyle.fill = legendModel.get('inactiveColor');
  411. itemStyle.stroke = legendModel.get('inactiveBorderColor');
  412. lineStyle.stroke = legendLineModel.get('inactiveColor');
  413. lineStyle.lineWidth = legendLineModel.get('inactiveWidth');
  414. }
  415. return {
  416. itemStyle: itemStyle,
  417. lineStyle: lineStyle
  418. };
  419. }
  420. function getDefaultLegendIcon(opt) {
  421. var symboType = opt.icon || 'roundRect';
  422. var icon = createSymbol(symboType, 0, 0, opt.itemWidth, opt.itemHeight, opt.itemStyle.fill, opt.symbolKeepAspect);
  423. icon.setStyle(opt.itemStyle);
  424. icon.rotation = (opt.iconRotate || 0) * Math.PI / 180;
  425. icon.setOrigin([opt.itemWidth / 2, opt.itemHeight / 2]);
  426. if (symboType.indexOf('empty') > -1) {
  427. icon.style.stroke = icon.style.fill;
  428. icon.style.fill = '#fff';
  429. icon.style.lineWidth = 2;
  430. }
  431. return icon;
  432. }
  433. function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
  434. // downplay before unselect
  435. dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId);
  436. api.dispatchAction({
  437. type: 'legendToggleSelect',
  438. name: seriesName != null ? seriesName : dataName
  439. }); // highlight after select
  440. // TODO higlight immediately may cause animation loss.
  441. dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId);
  442. }
  443. function isUseHoverLayer(api) {
  444. var list = api.getZr().storage.getDisplayList();
  445. var emphasisState;
  446. var i = 0;
  447. var len = list.length;
  448. while (i < len && !(emphasisState = list[i].states.emphasis)) {
  449. i++;
  450. }
  451. return emphasisState && emphasisState.hoverLayer;
  452. }
  453. function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
  454. // If element hover will move to a hoverLayer.
  455. if (!isUseHoverLayer(api)) {
  456. api.dispatchAction({
  457. type: 'highlight',
  458. seriesName: seriesName,
  459. name: dataName,
  460. excludeSeriesId: excludeSeriesId
  461. });
  462. }
  463. }
  464. function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) {
  465. // If element hover will move to a hoverLayer.
  466. if (!isUseHoverLayer(api)) {
  467. api.dispatchAction({
  468. type: 'downplay',
  469. seriesName: seriesName,
  470. name: dataName,
  471. excludeSeriesId: excludeSeriesId
  472. });
  473. }
  474. }
  475. export default LegendView;