legend.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. "use strict";
  2. var Util = require('../util/common');
  3. var List = require('../component/list');
  4. var Global = require('../global');
  5. var LEGEND_GAP = 12;
  6. var MARKER_SIZE = 3;
  7. var DEFAULT_CFG = {
  8. itemMarginBottom: 12,
  9. itemGap: 10,
  10. showTitle: false,
  11. titleStyle: {
  12. fontSize: 12,
  13. fill: '#808080',
  14. textAlign: 'start',
  15. textBaseline: 'top'
  16. },
  17. nameStyle: {
  18. fill: '#808080',
  19. fontSize: 12,
  20. textAlign: 'start',
  21. textBaseline: 'middle'
  22. },
  23. valueStyle: {
  24. fill: '#000000',
  25. fontSize: 12,
  26. textAlign: 'start',
  27. textBaseline: 'middle'
  28. },
  29. unCheckStyle: {
  30. fill: '#bfbfbf'
  31. },
  32. itemWidth: 'auto',
  33. wordSpace: 6,
  34. selectedMode: 'multiple' // 'multiple' or 'single'
  35. }; // Register the default configuration for Legend
  36. Global.legend = Util.deepMix({
  37. common: DEFAULT_CFG,
  38. // common legend configuration
  39. right: Util.mix({
  40. position: 'right',
  41. layout: 'vertical'
  42. }, DEFAULT_CFG),
  43. left: Util.mix({
  44. position: 'left',
  45. layout: 'vertical'
  46. }, DEFAULT_CFG),
  47. top: Util.mix({
  48. position: 'top',
  49. layout: 'horizontal'
  50. }, DEFAULT_CFG),
  51. bottom: Util.mix({
  52. position: 'bottom',
  53. layout: 'horizontal'
  54. }, DEFAULT_CFG)
  55. }, Global.legend || {});
  56. function getPaddingByPos(pos, appendPadding) {
  57. var padding = 0;
  58. appendPadding = Util.parsePadding(appendPadding);
  59. switch (pos) {
  60. case 'top':
  61. padding = appendPadding[0];
  62. break;
  63. case 'right':
  64. padding = appendPadding[1];
  65. break;
  66. case 'bottom':
  67. padding = appendPadding[2];
  68. break;
  69. case 'left':
  70. padding = appendPadding[3];
  71. break;
  72. default:
  73. break;
  74. }
  75. return padding;
  76. }
  77. var LegendController = /*#__PURE__*/function () {
  78. function LegendController(cfg) {
  79. var _this = this;
  80. this.handleEvent = function (ev) {
  81. var self = _this;
  82. function findItem(x, y) {
  83. var result = null;
  84. var legends = self.legends;
  85. Util.each(legends, function (legendItems) {
  86. Util.each(legendItems, function (legend) {
  87. var itemsGroup = legend.itemsGroup,
  88. legendHitBoxes = legend.legendHitBoxes;
  89. var children = itemsGroup.get('children');
  90. if (children.length) {
  91. var legendPosX = legend.x;
  92. var legendPosY = legend.y;
  93. Util.each(legendHitBoxes, function (box, index) {
  94. if (x >= box.x + legendPosX && x <= box.x + box.width + legendPosX && y >= box.y + legendPosY && y <= box.height + box.y + legendPosY) {
  95. // inbox
  96. result = {
  97. clickedItem: children[index],
  98. clickedLegend: legend
  99. };
  100. return false;
  101. }
  102. });
  103. }
  104. });
  105. });
  106. return result;
  107. }
  108. var chart = self.chart;
  109. var _Util$createEvent = Util.createEvent(ev, chart),
  110. x = _Util$createEvent.x,
  111. y = _Util$createEvent.y;
  112. var clicked = findItem(x, y);
  113. if (clicked && clicked.clickedLegend.clickable !== false) {
  114. var clickedItem = clicked.clickedItem,
  115. clickedLegend = clicked.clickedLegend;
  116. if (clickedLegend.onClick) {
  117. ev.clickedItem = clickedItem;
  118. clickedLegend.onClick(ev);
  119. } else if (!clickedLegend.custom) {
  120. var checked = clickedItem.get('checked');
  121. var value = clickedItem.get('dataValue');
  122. var filteredVals = clickedLegend.filteredVals,
  123. field = clickedLegend.field,
  124. selectedMode = clickedLegend.selectedMode;
  125. var isSingeSelected = selectedMode === 'single';
  126. if (isSingeSelected) {
  127. chart.filter(field, function (val) {
  128. return val === value;
  129. });
  130. } else {
  131. if (checked) {
  132. filteredVals.push(value);
  133. } else {
  134. Util.Array.remove(filteredVals, value);
  135. }
  136. chart.filter(field, function (val) {
  137. return filteredVals.indexOf(val) === -1;
  138. });
  139. }
  140. chart.repaint();
  141. }
  142. }
  143. };
  144. this.legendCfg = {};
  145. this.enable = true;
  146. this.position = 'top';
  147. Util.mix(this, cfg);
  148. var _chart = this.chart;
  149. this.canvasDom = _chart.get('canvas').get('el');
  150. this.clear();
  151. }
  152. var _proto = LegendController.prototype;
  153. _proto.addLegend = function addLegend(scale, items, filteredVals) {
  154. var self = this;
  155. var legendCfg = self.legendCfg;
  156. var field = scale.field;
  157. var fieldCfg = legendCfg[field];
  158. if (fieldCfg === false) {
  159. return null;
  160. }
  161. if (fieldCfg && fieldCfg.custom) {
  162. self.addCustomLegend(field);
  163. } else {
  164. var position = legendCfg.position || self.position;
  165. if (fieldCfg && fieldCfg.position) {
  166. position = fieldCfg.position;
  167. }
  168. if (scale.isCategory) {
  169. self._addCategoryLegend(scale, items, position, filteredVals);
  170. }
  171. }
  172. };
  173. _proto.addCustomLegend = function addCustomLegend(field) {
  174. var self = this;
  175. var legendCfg = self.legendCfg;
  176. if (field && legendCfg[field]) {
  177. legendCfg = legendCfg[field];
  178. }
  179. var position = legendCfg.position || self.position;
  180. var legends = self.legends;
  181. legends[position] = legends[position] || [];
  182. var items = legendCfg.items;
  183. if (!items) {
  184. return null;
  185. }
  186. var container = self.container;
  187. Util.each(items, function (item) {
  188. if (!Util.isPlainObject(item.marker)) {
  189. item.marker = {
  190. symbol: item.marker || 'circle',
  191. fill: item.fill,
  192. radius: MARKER_SIZE
  193. };
  194. } else {
  195. item.marker.radius = item.marker.radius || MARKER_SIZE;
  196. }
  197. item.checked = Util.isNil(item.checked) ? true : item.checked;
  198. item.name = item.name || item.value;
  199. });
  200. var legend = new List(Util.deepMix({}, Global.legend[position], legendCfg, {
  201. maxLength: self._getMaxLength(position),
  202. items: items,
  203. parent: container
  204. }));
  205. legends[position].push(legend);
  206. };
  207. _proto.clear = function clear() {
  208. var legends = this.legends;
  209. Util.each(legends, function (legendItems) {
  210. Util.each(legendItems, function (legend) {
  211. legend.clear();
  212. });
  213. });
  214. this.legends = {};
  215. this.unBindEvents();
  216. };
  217. _proto._isFiltered = function _isFiltered(scale, values, value) {
  218. var rst = false;
  219. Util.each(values, function (val) {
  220. rst = rst || scale.getText(val) === scale.getText(value);
  221. if (rst) {
  222. return false;
  223. }
  224. });
  225. return rst;
  226. };
  227. _proto._getMaxLength = function _getMaxLength(position) {
  228. var chart = this.chart;
  229. var appendPadding = Util.parsePadding(chart.get('appendPadding'));
  230. return position === 'right' || position === 'left' ? chart.get('height') - (appendPadding[0] + appendPadding[2]) : chart.get('width') - (appendPadding[1] + appendPadding[3]);
  231. };
  232. _proto._addCategoryLegend = function _addCategoryLegend(scale, items, position, filteredVals) {
  233. var self = this;
  234. var legendCfg = self.legendCfg,
  235. legends = self.legends,
  236. container = self.container,
  237. chart = self.chart;
  238. var field = scale.field;
  239. legends[position] = legends[position] || [];
  240. var symbol = 'circle';
  241. if (legendCfg[field] && legendCfg[field].marker) {
  242. symbol = legendCfg[field].marker;
  243. } else if (legendCfg.marker) {
  244. symbol = legendCfg.marker;
  245. }
  246. Util.each(items, function (item) {
  247. if (Util.isPlainObject(symbol)) {
  248. Util.mix(item.marker, symbol);
  249. } else {
  250. item.marker.symbol = symbol;
  251. }
  252. if (filteredVals) {
  253. item.checked = !self._isFiltered(scale, filteredVals, item.dataValue);
  254. }
  255. });
  256. var legendItems = chart.get('legendItems');
  257. legendItems[field] = items;
  258. var lastCfg = Util.deepMix({}, Global.legend[position], legendCfg[field] || legendCfg, {
  259. maxLength: self._getMaxLength(position),
  260. items: items,
  261. field: field,
  262. filteredVals: filteredVals,
  263. parent: container
  264. });
  265. if (lastCfg.showTitle) {
  266. Util.deepMix(lastCfg, {
  267. title: scale.alias || scale.field
  268. });
  269. }
  270. var legend = new List(lastCfg);
  271. legends[position].push(legend);
  272. return legend;
  273. };
  274. _proto._alignLegend = function _alignLegend(legend, pre, position) {
  275. var self = this;
  276. var _self$plotRange = self.plotRange,
  277. tl = _self$plotRange.tl,
  278. bl = _self$plotRange.bl;
  279. var chart = self.chart;
  280. var offsetX = legend.offsetX || 0;
  281. var offsetY = legend.offsetY || 0;
  282. var chartWidth = chart.get('width');
  283. var chartHeight = chart.get('height');
  284. var appendPadding = Util.parsePadding(chart.get('appendPadding'));
  285. var legendHeight = legend.getHeight();
  286. var legendWidth = legend.getWidth();
  287. var x = 0;
  288. var y = 0;
  289. if (position === 'left' || position === 'right') {
  290. var verticalAlign = legend.verticalAlign || 'middle';
  291. var height = Math.abs(tl.y - bl.y);
  292. x = position === 'left' ? appendPadding[3] : chartWidth - legendWidth - appendPadding[1];
  293. y = (height - legendHeight) / 2 + tl.y;
  294. if (verticalAlign === 'top') {
  295. y = tl.y;
  296. } else if (verticalAlign === 'bottom') {
  297. y = bl.y - legendHeight;
  298. }
  299. if (pre) {
  300. y = pre.get('y') - legendHeight - LEGEND_GAP;
  301. }
  302. } else {
  303. var align = legend.align || 'left';
  304. x = appendPadding[3];
  305. if (align === 'center') {
  306. x = chartWidth / 2 - legendWidth / 2;
  307. } else if (align === 'right') {
  308. x = chartWidth - (legendWidth + appendPadding[1]);
  309. }
  310. y = position === 'top' ? appendPadding[0] + Math.abs(legend.container.getBBox().minY) : chartHeight - legendHeight;
  311. if (pre) {
  312. var preWidth = pre.getWidth();
  313. x = pre.x + preWidth + LEGEND_GAP;
  314. }
  315. }
  316. if (position === 'bottom' && offsetY > 0) {
  317. offsetY = 0;
  318. }
  319. if (position === 'right' && offsetX > 0) {
  320. offsetX = 0;
  321. }
  322. legend.moveTo(x + offsetX, y + offsetY);
  323. };
  324. _proto.alignLegends = function alignLegends() {
  325. var self = this;
  326. var legends = self.legends;
  327. Util.each(legends, function (legendItems, position) {
  328. Util.each(legendItems, function (legend, index) {
  329. var pre = legendItems[index - 1];
  330. self._alignLegend(legend, pre, position);
  331. });
  332. });
  333. return self;
  334. };
  335. _proto.bindEvents = function bindEvents() {
  336. var legendCfg = this.legendCfg;
  337. var triggerOn = legendCfg.triggerOn || 'touchstart';
  338. Util.addEventListener(this.canvasDom, triggerOn, this.handleEvent);
  339. };
  340. _proto.unBindEvents = function unBindEvents() {
  341. var legendCfg = this.legendCfg;
  342. var triggerOn = legendCfg.triggerOn || 'touchstart';
  343. Util.removeEventListener(this.canvasDom, triggerOn, this.handleEvent);
  344. };
  345. return LegendController;
  346. }();
  347. module.exports = {
  348. init: function init(chart) {
  349. var legendController = new LegendController({
  350. container: chart.get('backPlot'),
  351. plotRange: chart.get('plotRange'),
  352. chart: chart
  353. });
  354. chart.set('legendController', legendController);
  355. chart.legend = function (field, cfg) {
  356. var legendCfg = legendController.legendCfg;
  357. legendController.enable = true;
  358. if (Util.isBoolean(field)) {
  359. legendController.enable = field;
  360. legendCfg = cfg || {};
  361. } else if (Util.isObject(field)) {
  362. legendCfg = field;
  363. } else {
  364. legendCfg[field] = cfg;
  365. }
  366. legendController.legendCfg = legendCfg;
  367. return this;
  368. };
  369. },
  370. beforeGeomDraw: function beforeGeomDraw(chart) {
  371. var legendController = chart.get('legendController');
  372. if (!legendController.enable) return null; // legend is not displayed
  373. var legendCfg = legendController.legendCfg;
  374. if (legendCfg && legendCfg.custom) {
  375. legendController.addCustomLegend();
  376. } else {
  377. var legendItems = chart.getLegendItems();
  378. var scales = chart.get('scales');
  379. var filters = chart.get('filters');
  380. Util.each(legendItems, function (items, field) {
  381. var scale = scales[field];
  382. var values = scale.values;
  383. var filteredVals;
  384. if (filters && filters[field]) {
  385. filteredVals = values.filter(function (v) {
  386. return !filters[field](v);
  387. });
  388. } else {
  389. filteredVals = [];
  390. }
  391. legendController.addLegend(scale, items, filteredVals);
  392. });
  393. }
  394. if (legendCfg && legendCfg.clickable !== false) {
  395. legendController.bindEvents();
  396. }
  397. var legends = legendController.legends;
  398. var legendRange = {
  399. top: 0,
  400. right: 0,
  401. bottom: 0,
  402. left: 0
  403. };
  404. Util.each(legends, function (legendItems, position) {
  405. var padding = 0;
  406. Util.each(legendItems, function (legend) {
  407. var width = legend.getWidth();
  408. var height = legend.getHeight();
  409. if (position === 'top' || position === 'bottom') {
  410. padding = Math.max(padding, height);
  411. if (legend.offsetY > 0) {
  412. padding += legend.offsetY;
  413. }
  414. } else {
  415. padding = Math.max(padding, width);
  416. if (legend.offsetX > 0) {
  417. padding += legend.offsetX;
  418. }
  419. }
  420. });
  421. legendRange[position] = padding + getPaddingByPos(position, chart.get('appendPadding'));
  422. });
  423. chart.set('legendRange', legendRange);
  424. },
  425. afterGeomDraw: function afterGeomDraw(chart) {
  426. var legendController = chart.get('legendController');
  427. legendController.alignLegends();
  428. },
  429. clearInner: function clearInner(chart) {
  430. var legendController = chart.get('legendController');
  431. legendController.clear();
  432. chart.set('legendRange', null);
  433. }
  434. };