LegendView.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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. var _config = require("../../config");
  20. var __DEV__ = _config.__DEV__;
  21. var echarts = require("../../echarts");
  22. var zrUtil = require("zrender/lib/core/util");
  23. var _symbol = require("../../util/symbol");
  24. var createSymbol = _symbol.createSymbol;
  25. var graphic = require("../../util/graphic");
  26. var _listComponent = require("../helper/listComponent");
  27. var makeBackground = _listComponent.makeBackground;
  28. var layoutUtil = require("../../util/layout");
  29. /*
  30. * Licensed to the Apache Software Foundation (ASF) under one
  31. * or more contributor license agreements. See the NOTICE file
  32. * distributed with this work for additional information
  33. * regarding copyright ownership. The ASF licenses this file
  34. * to you under the Apache License, Version 2.0 (the
  35. * "License"); you may not use this file except in compliance
  36. * with the License. You may obtain a copy of the License at
  37. *
  38. * http://www.apache.org/licenses/LICENSE-2.0
  39. *
  40. * Unless required by applicable law or agreed to in writing,
  41. * software distributed under the License is distributed on an
  42. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  43. * KIND, either express or implied. See the License for the
  44. * specific language governing permissions and limitations
  45. * under the License.
  46. */
  47. var curry = zrUtil.curry;
  48. var each = zrUtil.each;
  49. var Group = graphic.Group;
  50. var _default = echarts.extendComponentView({
  51. type: 'legend.plain',
  52. newlineDisabled: false,
  53. /**
  54. * @override
  55. */
  56. init: function () {
  57. /**
  58. * @private
  59. * @type {module:zrender/container/Group}
  60. */
  61. this.group.add(this._contentGroup = new Group());
  62. /**
  63. * @private
  64. * @type {module:zrender/Element}
  65. */
  66. this._backgroundEl;
  67. /**
  68. * @private
  69. * @type {module:zrender/container/Group}
  70. */
  71. this.group.add(this._selectorGroup = new Group());
  72. /**
  73. * If first rendering, `contentGroup.position` is [0, 0], which
  74. * does not make sense and may cause unexepcted animation if adopted.
  75. * @private
  76. * @type {boolean}
  77. */
  78. this._isFirstRender = true;
  79. },
  80. /**
  81. * @protected
  82. */
  83. getContentGroup: function () {
  84. return this._contentGroup;
  85. },
  86. /**
  87. * @protected
  88. */
  89. getSelectorGroup: function () {
  90. return this._selectorGroup;
  91. },
  92. /**
  93. * @override
  94. */
  95. render: function (legendModel, ecModel, api) {
  96. var isFirstRender = this._isFirstRender;
  97. this._isFirstRender = false;
  98. this.resetInner();
  99. if (!legendModel.get('show', true)) {
  100. return;
  101. }
  102. var itemAlign = legendModel.get('align');
  103. var orient = legendModel.get('orient');
  104. if (!itemAlign || itemAlign === 'auto') {
  105. itemAlign = legendModel.get('left') === 'right' && orient === 'vertical' ? 'right' : 'left';
  106. }
  107. var selector = legendModel.get('selector', true);
  108. var selectorPosition = legendModel.get('selectorPosition', true);
  109. if (selector && (!selectorPosition || selectorPosition === 'auto')) {
  110. selectorPosition = orient === 'horizontal' ? 'end' : 'start';
  111. }
  112. this.renderInner(itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition); // Perform layout.
  113. var positionInfo = legendModel.getBoxLayoutParams();
  114. var viewportSize = {
  115. width: api.getWidth(),
  116. height: api.getHeight()
  117. };
  118. var padding = legendModel.get('padding');
  119. var maxSize = layoutUtil.getLayoutRect(positionInfo, viewportSize, padding);
  120. var mainRect = this.layoutInner(legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition); // Place mainGroup, based on the calculated `mainRect`.
  121. var layoutRect = layoutUtil.getLayoutRect(zrUtil.defaults({
  122. width: mainRect.width,
  123. height: mainRect.height
  124. }, positionInfo), viewportSize, padding);
  125. this.group.attr('position', [layoutRect.x - mainRect.x, layoutRect.y - mainRect.y]); // Render background after group is layout.
  126. this.group.add(this._backgroundEl = makeBackground(mainRect, legendModel));
  127. },
  128. /**
  129. * @protected
  130. */
  131. resetInner: function () {
  132. this.getContentGroup().removeAll();
  133. this._backgroundEl && this.group.remove(this._backgroundEl);
  134. this.getSelectorGroup().removeAll();
  135. },
  136. /**
  137. * @protected
  138. */
  139. renderInner: function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) {
  140. var contentGroup = this.getContentGroup();
  141. var legendDrawnMap = zrUtil.createHashMap();
  142. var selectMode = legendModel.get('selectedMode');
  143. var excludeSeriesId = [];
  144. ecModel.eachRawSeries(function (seriesModel) {
  145. !seriesModel.get('legendHoverLink') && excludeSeriesId.push(seriesModel.id);
  146. });
  147. each(legendModel.getData(), function (itemModel, dataIndex) {
  148. var name = itemModel.get('name'); // Use empty string or \n as a newline string
  149. if (!this.newlineDisabled && (name === '' || name === '\n')) {
  150. contentGroup.add(new Group({
  151. newline: true
  152. }));
  153. return;
  154. } // Representitive series.
  155. var seriesModel = ecModel.getSeriesByName(name)[0];
  156. if (legendDrawnMap.get(name)) {
  157. // Have been drawed
  158. return;
  159. } // Legend to control series.
  160. if (seriesModel) {
  161. var data = seriesModel.getData();
  162. var color = data.getVisual('color');
  163. var borderColor = data.getVisual('borderColor'); // If color is a callback function
  164. if (typeof color === 'function') {
  165. // Use the first data
  166. color = color(seriesModel.getDataParams(0));
  167. } // If borderColor is a callback function
  168. if (typeof borderColor === 'function') {
  169. // Use the first data
  170. borderColor = borderColor(seriesModel.getDataParams(0));
  171. } // Using rect symbol defaultly
  172. var legendSymbolType = data.getVisual('legendSymbol') || 'roundRect';
  173. var symbolType = data.getVisual('symbol');
  174. var itemGroup = this._createItem(name, dataIndex, itemModel, legendModel, legendSymbolType, symbolType, itemAlign, color, borderColor, selectMode);
  175. 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));
  176. legendDrawnMap.set(name, true);
  177. } else {
  178. // Legend to control data. In pie and funnel.
  179. ecModel.eachRawSeries(function (seriesModel) {
  180. // In case multiple series has same data name
  181. if (legendDrawnMap.get(name)) {
  182. return;
  183. }
  184. if (seriesModel.legendVisualProvider) {
  185. var provider = seriesModel.legendVisualProvider;
  186. if (!provider.containName(name)) {
  187. return;
  188. }
  189. var idx = provider.indexOfName(name);
  190. var color = provider.getItemVisual(idx, 'color');
  191. var borderColor = provider.getItemVisual(idx, 'borderColor');
  192. var legendSymbolType = 'roundRect';
  193. var itemGroup = this._createItem(name, dataIndex, itemModel, legendModel, legendSymbolType, null, itemAlign, color, borderColor, selectMode); // FIXME: consider different series has items with the same name.
  194. itemGroup.on('click', curry(dispatchSelectAction, null, name, api, excludeSeriesId)) // Should not specify the series name, consider legend controls
  195. // more than one pie series.
  196. .on('mouseover', curry(dispatchHighlightAction, null, name, api, excludeSeriesId)).on('mouseout', curry(dispatchDownplayAction, null, name, api, excludeSeriesId));
  197. legendDrawnMap.set(name, true);
  198. }
  199. }, this);
  200. }
  201. }, this);
  202. if (selector) {
  203. this._createSelector(selector, legendModel, api, orient, selectorPosition);
  204. }
  205. },
  206. _createSelector: function (selector, legendModel, api, orient, selectorPosition) {
  207. var selectorGroup = this.getSelectorGroup();
  208. each(selector, function (selectorItem) {
  209. createSelectorButton(selectorItem);
  210. });
  211. function createSelectorButton(selectorItem) {
  212. var type = selectorItem.type;
  213. var labelText = new graphic.Text({
  214. style: {
  215. x: 0,
  216. y: 0,
  217. align: 'center',
  218. verticalAlign: 'middle'
  219. },
  220. onclick: function () {
  221. api.dispatchAction({
  222. type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect'
  223. });
  224. }
  225. });
  226. selectorGroup.add(labelText);
  227. var labelModel = legendModel.getModel('selectorLabel');
  228. var emphasisLabelModel = legendModel.getModel('emphasis.selectorLabel');
  229. graphic.setLabelStyle(labelText.style, labelText.hoverStyle = {}, labelModel, emphasisLabelModel, {
  230. defaultText: selectorItem.title,
  231. isRectText: false
  232. });
  233. graphic.setHoverStyle(labelText);
  234. }
  235. },
  236. _createItem: function (name, dataIndex, itemModel, legendModel, legendSymbolType, symbolType, itemAlign, color, borderColor, selectMode) {
  237. var itemWidth = legendModel.get('itemWidth');
  238. var itemHeight = legendModel.get('itemHeight');
  239. var inactiveColor = legendModel.get('inactiveColor');
  240. var inactiveBorderColor = legendModel.get('inactiveBorderColor');
  241. var symbolKeepAspect = legendModel.get('symbolKeepAspect');
  242. var legendModelItemStyle = legendModel.getModel('itemStyle');
  243. var isSelected = legendModel.isSelected(name);
  244. var itemGroup = new Group();
  245. var textStyleModel = itemModel.getModel('textStyle');
  246. var itemIcon = itemModel.get('icon');
  247. var tooltipModel = itemModel.getModel('tooltip');
  248. var legendGlobalTooltipModel = tooltipModel.parentModel; // Use user given icon first
  249. legendSymbolType = itemIcon || legendSymbolType;
  250. var legendSymbol = createSymbol(legendSymbolType, 0, 0, itemWidth, itemHeight, isSelected ? color : inactiveColor, // symbolKeepAspect default true for legend
  251. symbolKeepAspect == null ? true : symbolKeepAspect);
  252. itemGroup.add(setSymbolStyle(legendSymbol, legendSymbolType, legendModelItemStyle, borderColor, inactiveBorderColor, isSelected)); // Compose symbols
  253. // PENDING
  254. if (!itemIcon && symbolType // At least show one symbol, can't be all none
  255. && (symbolType !== legendSymbolType || symbolType === 'none')) {
  256. var size = itemHeight * 0.8;
  257. if (symbolType === 'none') {
  258. symbolType = 'circle';
  259. }
  260. var legendSymbolCenter = createSymbol(symbolType, (itemWidth - size) / 2, (itemHeight - size) / 2, size, size, isSelected ? color : inactiveColor, // symbolKeepAspect default true for legend
  261. symbolKeepAspect == null ? true : symbolKeepAspect); // Put symbol in the center
  262. itemGroup.add(setSymbolStyle(legendSymbolCenter, symbolType, legendModelItemStyle, borderColor, inactiveBorderColor, isSelected));
  263. }
  264. var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
  265. var textAlign = itemAlign;
  266. var formatter = legendModel.get('formatter');
  267. var content = name;
  268. if (typeof formatter === 'string' && formatter) {
  269. content = formatter.replace('{name}', name != null ? name : '');
  270. } else if (typeof formatter === 'function') {
  271. content = formatter(name);
  272. }
  273. itemGroup.add(new graphic.Text({
  274. style: graphic.setTextStyle({}, textStyleModel, {
  275. text: content,
  276. x: textX,
  277. y: itemHeight / 2,
  278. textFill: isSelected ? textStyleModel.getTextColor() : inactiveColor,
  279. textAlign: textAlign,
  280. textVerticalAlign: 'middle'
  281. })
  282. })); // Add a invisible rect to increase the area of mouse hover
  283. var hitRect = new graphic.Rect({
  284. shape: itemGroup.getBoundingRect(),
  285. invisible: true,
  286. tooltip: tooltipModel.get('show') ? zrUtil.extend({
  287. content: name,
  288. // Defaul formatter
  289. formatter: legendGlobalTooltipModel.get('formatter', true) || function () {
  290. return name;
  291. },
  292. formatterParams: {
  293. componentType: 'legend',
  294. legendIndex: legendModel.componentIndex,
  295. name: name,
  296. $vars: ['name']
  297. }
  298. }, tooltipModel.option) : null
  299. });
  300. itemGroup.add(hitRect);
  301. itemGroup.eachChild(function (child) {
  302. child.silent = true;
  303. });
  304. hitRect.silent = !selectMode;
  305. this.getContentGroup().add(itemGroup);
  306. graphic.setHoverStyle(itemGroup);
  307. itemGroup.__legendDataIndex = dataIndex;
  308. return itemGroup;
  309. },
  310. /**
  311. * @protected
  312. */
  313. layoutInner: function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) {
  314. var contentGroup = this.getContentGroup();
  315. var selectorGroup = this.getSelectorGroup(); // Place items in contentGroup.
  316. layoutUtil.box(legendModel.get('orient'), contentGroup, legendModel.get('itemGap'), maxSize.width, maxSize.height);
  317. var contentRect = contentGroup.getBoundingRect();
  318. var contentPos = [-contentRect.x, -contentRect.y];
  319. if (selector) {
  320. // Place buttons in selectorGroup
  321. layoutUtil.box( // Buttons in selectorGroup always layout horizontally
  322. 'horizontal', selectorGroup, legendModel.get('selectorItemGap', true));
  323. var selectorRect = selectorGroup.getBoundingRect();
  324. var selectorPos = [-selectorRect.x, -selectorRect.y];
  325. var selectorButtonGap = legendModel.get('selectorButtonGap', true);
  326. var orientIdx = legendModel.getOrient().index;
  327. var wh = orientIdx === 0 ? 'width' : 'height';
  328. var hw = orientIdx === 0 ? 'height' : 'width';
  329. var yx = orientIdx === 0 ? 'y' : 'x';
  330. if (selectorPosition === 'end') {
  331. selectorPos[orientIdx] += contentRect[wh] + selectorButtonGap;
  332. } else {
  333. contentPos[orientIdx] += selectorRect[wh] + selectorButtonGap;
  334. } //Always align selector to content as 'middle'
  335. selectorPos[1 - orientIdx] += contentRect[hw] / 2 - selectorRect[hw] / 2;
  336. selectorGroup.attr('position', selectorPos);
  337. contentGroup.attr('position', contentPos);
  338. var mainRect = {
  339. x: 0,
  340. y: 0
  341. };
  342. mainRect[wh] = contentRect[wh] + selectorButtonGap + selectorRect[wh];
  343. mainRect[hw] = Math.max(contentRect[hw], selectorRect[hw]);
  344. mainRect[yx] = Math.min(0, selectorRect[yx] + selectorPos[1 - orientIdx]);
  345. return mainRect;
  346. } else {
  347. contentGroup.attr('position', contentPos);
  348. return this.group.getBoundingRect();
  349. }
  350. },
  351. /**
  352. * @protected
  353. */
  354. remove: function () {
  355. this.getContentGroup().removeAll();
  356. this._isFirstRender = true;
  357. }
  358. });
  359. function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, inactiveBorderColor, isSelected) {
  360. var itemStyle;
  361. if (symbolType !== 'line' && symbolType.indexOf('empty') < 0) {
  362. itemStyle = legendModelItemStyle.getItemStyle();
  363. symbol.style.stroke = borderColor;
  364. if (!isSelected) {
  365. itemStyle.stroke = inactiveBorderColor;
  366. }
  367. } else {
  368. itemStyle = legendModelItemStyle.getItemStyle(['borderWidth', 'borderColor']);
  369. }
  370. return symbol.setStyle(itemStyle);
  371. }
  372. function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
  373. // downplay before unselect
  374. dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId);
  375. api.dispatchAction({
  376. type: 'legendToggleSelect',
  377. name: seriesName != null ? seriesName : dataName
  378. }); // highlight after select
  379. dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId);
  380. }
  381. function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
  382. // If element hover will move to a hoverLayer.
  383. var el = api.getZr().storage.getDisplayList()[0];
  384. if (!(el && el.useHoverLayer)) {
  385. api.dispatchAction({
  386. type: 'highlight',
  387. seriesName: seriesName,
  388. name: dataName,
  389. excludeSeriesId: excludeSeriesId
  390. });
  391. }
  392. }
  393. function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) {
  394. // If element hover will move to a hoverLayer.
  395. var el = api.getZr().storage.getDisplayList()[0];
  396. if (!(el && el.useHoverLayer)) {
  397. api.dispatchAction({
  398. type: 'downplay',
  399. seriesName: seriesName,
  400. name: dataName,
  401. excludeSeriesId: excludeSeriesId
  402. });
  403. }
  404. }
  405. module.exports = _default;