legend.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import _ from 'lodash';
  2. import $ from 'jquery';
  3. import baron from 'baron';
  4. import coreModule from 'app/core/core_module';
  5. coreModule.directive('graphLegend', (popoverSrv, $timeout) => {
  6. return {
  7. link: (scope, elem) => {
  8. let firstRender = true;
  9. const ctrl = scope.ctrl;
  10. const panel = ctrl.panel;
  11. let data;
  12. let seriesList;
  13. let i;
  14. let legendScrollbar;
  15. const legendRightDefaultWidth = 10;
  16. const legendElem = elem.parent();
  17. scope.$on('$destroy', () => {
  18. destroyScrollbar();
  19. });
  20. ctrl.events.on('render-legend', () => {
  21. data = ctrl.seriesList;
  22. if (data) {
  23. render();
  24. }
  25. ctrl.events.emit('legend-rendering-complete');
  26. });
  27. function getSeriesIndexForElement(el) {
  28. return el.parents('[data-series-index]').data('series-index');
  29. }
  30. function openColorSelector(e) {
  31. // if we clicked inside poup container ignore click
  32. if ($(e.target).parents('.popover').length) {
  33. return;
  34. }
  35. const el = $(e.currentTarget).find('.fa-minus');
  36. const index = getSeriesIndexForElement(el);
  37. const series = seriesList[index];
  38. $timeout(() => {
  39. popoverSrv.show({
  40. element: el[0],
  41. position: 'bottom left',
  42. targetAttachment: 'top left',
  43. template:
  44. '<series-color-picker series="series" onToggleAxis="toggleAxis" onColorChange="colorSelected">' +
  45. '</series-color-picker>',
  46. openOn: 'hover',
  47. model: {
  48. series: series,
  49. toggleAxis: () => {
  50. ctrl.toggleAxis(series);
  51. },
  52. colorSelected: color => {
  53. ctrl.changeSeriesColor(series, color);
  54. },
  55. },
  56. });
  57. });
  58. }
  59. function toggleSeries(e) {
  60. const el = $(e.currentTarget);
  61. const index = getSeriesIndexForElement(el);
  62. const seriesInfo = seriesList[index];
  63. const scrollPosition = legendScrollbar.scroller.scrollTop;
  64. ctrl.toggleSeries(seriesInfo, e);
  65. legendScrollbar.scroller.scrollTop = scrollPosition;
  66. }
  67. function sortLegend(e) {
  68. const el = $(e.currentTarget);
  69. const stat = el.data('stat');
  70. if (stat !== panel.legend.sort) {
  71. panel.legend.sortDesc = null;
  72. }
  73. // if already sort ascending, disable sorting
  74. if (panel.legend.sortDesc === false) {
  75. panel.legend.sort = null;
  76. panel.legend.sortDesc = null;
  77. ctrl.render();
  78. return;
  79. }
  80. panel.legend.sortDesc = !panel.legend.sortDesc;
  81. panel.legend.sort = stat;
  82. ctrl.render();
  83. }
  84. function getTableHeaderHtml(statName) {
  85. if (!panel.legend[statName]) {
  86. return '';
  87. }
  88. let html = '<th class="pointer" data-stat="' + statName + '">' + statName;
  89. if (panel.legend.sort === statName) {
  90. const cssClass = panel.legend.sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up';
  91. html += ' <span class="' + cssClass + '"></span>';
  92. }
  93. return html + '</th>';
  94. }
  95. function render() {
  96. const legendWidth = legendElem.width();
  97. if (!ctrl.panel.legend.show) {
  98. elem.empty();
  99. firstRender = true;
  100. return;
  101. }
  102. if (firstRender) {
  103. elem.on('click', '.graph-legend-icon', openColorSelector);
  104. elem.on('click', '.graph-legend-alias', toggleSeries);
  105. elem.on('click', 'th', sortLegend);
  106. firstRender = false;
  107. }
  108. seriesList = data;
  109. elem.empty();
  110. // Set min-width if side style and there is a value, otherwise remove the CSS property
  111. // Set width so it works with IE11
  112. const width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : '';
  113. const ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : '';
  114. legendElem.css('min-width', width);
  115. legendElem.css('width', ieWidth);
  116. elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
  117. let tableHeaderElem;
  118. if (panel.legend.alignAsTable) {
  119. let header = '<tr>';
  120. header += '<th colspan="2" style="text-align:left"></th>';
  121. if (panel.legend.values) {
  122. header += getTableHeaderHtml('min');
  123. header += getTableHeaderHtml('max');
  124. header += getTableHeaderHtml('avg');
  125. header += getTableHeaderHtml('current');
  126. header += getTableHeaderHtml('total');
  127. }
  128. header += '</tr>';
  129. tableHeaderElem = $(header);
  130. }
  131. if (panel.legend.sort) {
  132. seriesList = _.sortBy(seriesList, series => {
  133. let sort = series.stats[panel.legend.sort];
  134. if (sort === null) {
  135. sort = -Infinity;
  136. }
  137. return sort;
  138. });
  139. if (panel.legend.sortDesc) {
  140. seriesList = seriesList.reverse();
  141. }
  142. }
  143. // render first time for getting proper legend height
  144. if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
  145. renderLegendElement(tableHeaderElem);
  146. elem.empty();
  147. }
  148. renderLegendElement(tableHeaderElem);
  149. }
  150. function renderSeriesLegendElements() {
  151. const seriesElements = [];
  152. for (i = 0; i < seriesList.length; i++) {
  153. const series = seriesList[i];
  154. if (series.hideFromLegend(panel.legend)) {
  155. continue;
  156. }
  157. let html = '<div class="graph-legend-series';
  158. if (series.yaxis === 2) {
  159. html += ' graph-legend-series--right-y';
  160. }
  161. if (ctrl.hiddenSeries[series.alias]) {
  162. html += ' graph-legend-series-hidden';
  163. }
  164. html += '" data-series-index="' + i + '">';
  165. html += '<div class="graph-legend-icon">';
  166. html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
  167. html += '</div>';
  168. html +=
  169. '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
  170. if (panel.legend.values) {
  171. const avg = series.formatValue(series.stats.avg);
  172. const current = series.formatValue(series.stats.current);
  173. const min = series.formatValue(series.stats.min);
  174. const max = series.formatValue(series.stats.max);
  175. const total = series.formatValue(series.stats.total);
  176. if (panel.legend.min) {
  177. html += '<div class="graph-legend-value min">' + min + '</div>';
  178. }
  179. if (panel.legend.max) {
  180. html += '<div class="graph-legend-value max">' + max + '</div>';
  181. }
  182. if (panel.legend.avg) {
  183. html += '<div class="graph-legend-value avg">' + avg + '</div>';
  184. }
  185. if (panel.legend.current) {
  186. html += '<div class="graph-legend-value current">' + current + '</div>';
  187. }
  188. if (panel.legend.total) {
  189. html += '<div class="graph-legend-value total">' + total + '</div>';
  190. }
  191. }
  192. html += '</div>';
  193. seriesElements.push($(html));
  194. }
  195. return seriesElements;
  196. }
  197. function renderLegendElement(tableHeaderElem) {
  198. const legendWidth = elem.width();
  199. const seriesElements = renderSeriesLegendElements();
  200. if (panel.legend.alignAsTable) {
  201. const tbodyElem = $('<tbody></tbody>');
  202. tbodyElem.append(tableHeaderElem);
  203. tbodyElem.append(seriesElements);
  204. elem.append(tbodyElem);
  205. tbodyElem.wrap('<div class="graph-legend-scroll"></div>');
  206. } else {
  207. elem.append('<div class="graph-legend-scroll"></div>');
  208. elem.find('.graph-legend-scroll').append(seriesElements);
  209. }
  210. if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
  211. addScrollbar();
  212. } else {
  213. destroyScrollbar();
  214. }
  215. }
  216. function addScrollbar() {
  217. const scrollRootClass = 'baron baron__root';
  218. const scrollerClass = 'baron__scroller';
  219. const scrollBarHTML = `
  220. <div class="baron__track">
  221. <div class="baron__bar"></div>
  222. </div>
  223. `;
  224. const scrollRoot = elem;
  225. const scroller = elem.find('.graph-legend-scroll');
  226. // clear existing scroll bar track to prevent duplication
  227. scrollRoot.find('.baron__track').remove();
  228. scrollRoot.addClass(scrollRootClass);
  229. $(scrollBarHTML).appendTo(scrollRoot);
  230. scroller.addClass(scrollerClass);
  231. const scrollbarParams = {
  232. root: scrollRoot[0],
  233. scroller: scroller[0],
  234. bar: '.baron__bar',
  235. track: '.baron__track',
  236. barOnCls: '_scrollbar',
  237. scrollingCls: '_scrolling',
  238. };
  239. if (!legendScrollbar) {
  240. legendScrollbar = baron(scrollbarParams);
  241. } else {
  242. destroyScrollbar();
  243. legendScrollbar = baron(scrollbarParams);
  244. }
  245. // #11830 - compensates for Firefox scrollbar calculation error in the baron framework
  246. scroller[0].style.marginRight = '-' + (scroller[0].offsetWidth - scroller[0].clientWidth) + 'px';
  247. legendScrollbar.scroll();
  248. }
  249. function destroyScrollbar() {
  250. if (legendScrollbar) {
  251. legendScrollbar.dispose();
  252. legendScrollbar = undefined;
  253. }
  254. }
  255. },
  256. };
  257. });