module.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. /** @scratch /panels/5
  2. * include::panels/histogram.asciidoc[]
  3. */
  4. /** @scratch /panels/histogram/0
  5. * == Histogram
  6. * Status: *Stable*
  7. *
  8. * The histogram panel allow for the display of time charts. It includes several modes and tranformations
  9. * to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
  10. * fields.
  11. *
  12. */
  13. define([
  14. 'angular',
  15. 'app',
  16. 'jquery',
  17. 'underscore',
  18. 'kbn',
  19. 'moment',
  20. './timeSeries',
  21. 'jquery.flot',
  22. 'jquery.flot.events',
  23. 'jquery.flot.selection',
  24. 'jquery.flot.time',
  25. 'jquery.flot.byte',
  26. 'jquery.flot.stack',
  27. 'jquery.flot.stackpercent'
  28. ],
  29. function (angular, app, $, _, kbn, moment, timeSeries) {
  30. 'use strict';
  31. var module = angular.module('kibana.panels.graphite', []);
  32. app.useModule(module);
  33. module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout) {
  34. $scope.panelMeta = {
  35. modals : [],
  36. editorTabs: [],
  37. fullEditorTabs : [
  38. {
  39. title:'Targets',
  40. src:'app/panels/graphite/editor.html'
  41. },
  42. {
  43. title:'Axis labels',
  44. src:'app/panels/graphite/axisEditor.html'
  45. },
  46. {
  47. title:'Style',
  48. src:'app/panels/graphite/styleEditor.html'
  49. }
  50. ],
  51. menuItems: [
  52. { text: 'View fullscreen', action: function() { $scope.toggleFullscreen(); }},
  53. { text: 'Edit', action: function() { $scope.openConfigureModal(); }},
  54. { text: 'Duplicate', action: function() { $scope.duplicate(); }},
  55. { text: 'Remove', action: function() { $scope.remove_panel_from_row($scope.row, $scope.panel); }}
  56. ],
  57. status : "Unstable",
  58. description : "Graphite graphing panel <br /><br />"
  59. };
  60. // Set and populate defaults
  61. var _d = {
  62. /** @scratch /panels/histogram/3
  63. * x-axis:: Show the x-axis
  64. */
  65. 'x-axis' : true,
  66. /** @scratch /panels/histogram/3
  67. * y-axis:: Show the y-axis
  68. */
  69. 'y-axis' : true,
  70. /** @scratch /panels/histogram/3
  71. * scale:: Scale the y-axis by this factor
  72. */
  73. scale : 1,
  74. /** @scratch /panels/histogram/3
  75. * y_format:: 'none','bytes','short '
  76. */
  77. y_format : 'none',
  78. /** @scratch /panels/histogram/5
  79. * grid object:: Min and max y-axis values
  80. * grid.min::: Minimum y-axis value
  81. * grid.max::: Maximum y-axis value
  82. */
  83. grid : {
  84. max: null,
  85. min: 0
  86. },
  87. /** @scratch /panels/histogram/3
  88. * ==== Annotations
  89. * annotate object:: A query can be specified, the results of which will be displayed as markers on
  90. * the chart. For example, for noting code deploys.
  91. * annotate.enable::: Should annotations, aka markers, be shown?
  92. * annotate.query::: Lucene query_string syntax query to use for markers.
  93. * annotate.size::: Max number of markers to show
  94. * annotate.field::: Field from documents to show
  95. * annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
  96. */
  97. annotate : {
  98. enable : false,
  99. query : "*",
  100. size : 20,
  101. field : '_type',
  102. sort : ['_score','desc']
  103. },
  104. /** @scratch /panels/histogram/3
  105. * ==== Interval options
  106. * auto_int:: Automatically scale intervals?
  107. */
  108. auto_int : true,
  109. /** @scratch /panels/histogram/3
  110. * resolution:: If auto_int is true, shoot for this many bars.
  111. */
  112. resolution : 100,
  113. /** @scratch /panels/histogram/3
  114. * interval:: If auto_int is set to false, use this as the interval.
  115. */
  116. interval : '5m',
  117. /** @scratch /panels/histogram/3
  118. * interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
  119. */
  120. intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
  121. /** @scratch /panels/histogram/3
  122. * ==== Drawing options
  123. * lines:: Show line chart
  124. */
  125. lines : true,
  126. /** @scratch /panels/histogram/3
  127. * fill:: Area fill factor for line charts, 1-10
  128. */
  129. fill : 0,
  130. /** @scratch /panels/histogram/3
  131. * linewidth:: Weight of lines in pixels
  132. */
  133. linewidth : 1,
  134. /** @scratch /panels/histogram/3
  135. * points:: Show points on chart
  136. */
  137. points : false,
  138. /** @scratch /panels/histogram/3
  139. * pointradius:: Size of points in pixels
  140. */
  141. pointradius : 5,
  142. /** @scratch /panels/histogram/3
  143. * bars:: Show bars on chart
  144. */
  145. bars : false,
  146. /** @scratch /panels/histogram/3
  147. * stack:: Stack multiple series
  148. */
  149. stack : true,
  150. /** @scratch /panels/histogram/3
  151. * spyable:: Show inspect icon
  152. */
  153. spyable : true,
  154. /** @scratch /panels/histogram/3
  155. * zoomlinks:: Show `Zoom Out' link
  156. */
  157. zoomlinks : false,
  158. /** @scratch /panels/histogram/3
  159. * options:: Show quick view options section
  160. */
  161. options : false,
  162. /** @scratch /panels/histogram/3
  163. * legend:: Display the legond
  164. */
  165. legend : true,
  166. /** @scratch /panels/histogram/3
  167. * interactive:: Enable click-and-drag to zoom functionality
  168. */
  169. interactive : true,
  170. /** @scratch /panels/histogram/3
  171. * legend_counts:: Show counts in legend
  172. */
  173. legend_counts : true,
  174. /** @scratch /panels/histogram/3
  175. * ==== Transformations
  176. * timezone:: Correct for browser timezone?. Valid values: browser, utc
  177. */
  178. timezone : 'browser', // browser or utc
  179. /** @scratch /panels/histogram/3
  180. * percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
  181. * queries
  182. */
  183. percentage : false,
  184. /** @scratch /panels/histogram/3
  185. * zerofill:: Improves the accuracy of line charts at a small performance cost.
  186. */
  187. zerofill : true,
  188. tooltip : {
  189. value_type: 'cumulative',
  190. query_as_alias: true
  191. },
  192. targets: [],
  193. aliasColors: {}
  194. };
  195. _.defaults($scope.panel,_d);
  196. _.defaults($scope.panel.tooltip,_d.tooltip);
  197. _.defaults($scope.panel.annotate,_d.annotate);
  198. _.defaults($scope.panel.grid,_d.grid);
  199. $scope.init = function() {
  200. // Hide view options by default
  201. $scope.options = false;
  202. $scope.editor = {index: 1};
  203. $scope.editorTabs = _.union(['General'],_.pluck($scope.panelMeta.fullEditorTabs,'title'));
  204. $scope.hiddenSeries = {};
  205. // Always show the query if an alias isn't set. Users can set an alias if the query is too
  206. // long
  207. $scope.panel.tooltip.query_as_alias = true;
  208. $scope.get_data();
  209. };
  210. $scope.set_interval = function(interval) {
  211. if(interval !== 'auto') {
  212. $scope.panel.auto_int = false;
  213. $scope.panel.interval = interval;
  214. } else {
  215. $scope.panel.auto_int = true;
  216. }
  217. };
  218. $scope.typeAheadSource = function () {
  219. return ["test", "asd", "testing2"];
  220. };
  221. $scope.remove_panel_from_row = function(row, panel) {
  222. if ($scope.fullscreen) {
  223. $rootScope.$emit('panel-fullscreen-exit');
  224. }
  225. else {
  226. $scope.$parent.remove_panel_from_row(row, panel);
  227. }
  228. };
  229. $scope.removeTarget = function (target) {
  230. $scope.panel.targets = _.without($scope.panel.targets, target);
  231. $scope.get_data();
  232. };
  233. $scope.interval_label = function(interval) {
  234. return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
  235. };
  236. /**
  237. * The time range effecting the panel
  238. * @return {[type]} [description]
  239. */
  240. $scope.get_time_range = function () {
  241. var range = $scope.range = filterSrv.timeRange();
  242. return range;
  243. };
  244. $scope.get_interval = function () {
  245. var interval = $scope.panel.interval;
  246. var range;
  247. if ($scope.panel.auto_int) {
  248. range = $scope.get_time_range();
  249. if (range) {
  250. interval = kbn.secondsToHms(
  251. kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
  252. );
  253. }
  254. }
  255. $scope.panel.interval = interval || '10m';
  256. return $scope.panel.interval;
  257. };
  258. $scope.colors = [
  259. "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
  260. "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
  261. "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
  262. "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
  263. "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
  264. "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
  265. "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
  266. ];
  267. /**
  268. * Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
  269. * need to be consulted (like timestamped logstash indicies)
  270. *
  271. * The results of this function are stored on the scope's data property. This property will be an
  272. * array of objects with the properties info, time_series, and hits. These objects are used in the
  273. * render_panel function to create the historgram.
  274. *
  275. */
  276. $scope.get_data = function() {
  277. delete $scope.panel.error;
  278. $scope.panelMeta.loading = true;
  279. var range = $scope.get_time_range();
  280. var interval = $scope.get_interval(range);
  281. var graphiteQuery = {
  282. range: range,
  283. targets: $scope.panel.targets,
  284. maxDataPoints: $scope.panel.span * 50
  285. };
  286. return graphiteSrv.query(graphiteQuery)
  287. .then(function(results) {
  288. $scope.panelMeta.loading = false;
  289. var data = $scope.receiveGraphiteData(results, range, interval);
  290. $scope.$emit('render', data);
  291. })
  292. .then(null, function(err) {
  293. $scope.panel.error = err.message || "Graphite HTTP Request Error";
  294. });
  295. };
  296. $scope.receiveGraphiteData = function(results, range, interval) {
  297. results = results.data;
  298. $scope.legend = [];
  299. var data = [];
  300. if(results.length === 0 ) {
  301. return [];
  302. }
  303. var tsOpts = {
  304. interval: interval,
  305. start_date: range && range.from,
  306. end_date: range && range.to,
  307. fill_style: 'no'
  308. };
  309. _.each(results, function(targetData) {
  310. var time_series = new timeSeries.ZeroFilled(tsOpts);
  311. _.each(targetData.datapoints, function(valueArray) {
  312. if (valueArray[0]) {
  313. time_series.addValue(valueArray[1] * 1000, valueArray[0]);
  314. }
  315. });
  316. var target = graphiteSrv.match($scope.panel.targets, targetData.target);
  317. var alias = targetData.target;
  318. var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
  319. var seriesInfo = {
  320. alias: alias,
  321. color: color,
  322. enable: true,
  323. yaxis: target.yaxis || 1
  324. };
  325. $scope.legend.push(seriesInfo);
  326. data.push({
  327. info: seriesInfo,
  328. time_series: time_series,
  329. yaxis: target.yaxis || 1
  330. });
  331. });
  332. return data;
  333. };
  334. $scope.add_target = function() {
  335. $scope.panel.targets.push({target: ''});
  336. };
  337. $scope.enterFullscreenMode = function(options) {
  338. var oldHeight = $scope.row.height;
  339. var docHeight = $(window).height();
  340. $scope.row.height = options.edit ? 200 : Math.floor(docHeight * 0.7);
  341. var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
  342. $scope.inEditMode = false;
  343. $scope.fullscreen = false;
  344. $scope.row.height = oldHeight;
  345. closeEditMode();
  346. $timeout(function() {
  347. $scope.$emit('render');
  348. });
  349. });
  350. $(window).scrollTop(0);
  351. $scope.inEditMode = options.edit;
  352. $scope.fullscreen = true;
  353. $rootScope.$emit('panel-fullscreen-enter');
  354. $timeout(function() {
  355. $scope.$emit('render');
  356. });
  357. };
  358. $scope.openConfigureModal = function() {
  359. if ($scope.fullscreen) {
  360. $rootScope.$emit('panel-fullscreen-exit');
  361. return;
  362. }
  363. $scope.enterFullscreenMode({edit: true});
  364. };
  365. $scope.set_refresh = function (state) {
  366. $scope.refresh = state;
  367. };
  368. $scope.close_edit = function() {
  369. if($scope.refresh) {
  370. $scope.get_data();
  371. }
  372. $scope.refresh = false;
  373. $scope.$emit('render');
  374. };
  375. $scope.render = function() {
  376. $scope.$emit('render');
  377. };
  378. $scope.changeSeriesColor = function(series, color) {
  379. series.color = color;
  380. $scope.panel.aliasColors[series.alias] = series.color;
  381. $scope.render();
  382. };
  383. $scope.duplicate = function(addToRow) {
  384. addToRow = addToRow || $scope.row;
  385. var currentRowSpan = $scope.rowSpan(addToRow);
  386. if (currentRowSpan <= 8) {
  387. addToRow.panels.push(angular.copy($scope.panel));
  388. }
  389. else {
  390. var rowsList = $scope.dashboard.current.rows;
  391. var rowIndex = _.indexOf(rowsList, addToRow);
  392. if (rowIndex === rowsList.length - 1) {
  393. var newRow = angular.copy($scope.row);
  394. newRow.panels = [];
  395. $scope.dashboard.current.rows.push(newRow);
  396. $scope.duplicate(newRow);
  397. }
  398. else {
  399. $scope.duplicate(rowsList[rowIndex+1]);
  400. }
  401. }
  402. };
  403. $scope.toggleFullscreen = function(evt) {
  404. if ($scope.fullscreen) {
  405. $rootScope.$emit('panel-fullscreen-exit');
  406. return;
  407. }
  408. if (evt) {
  409. var elem = $(evt.target);
  410. if (!elem.hasClass('panel-extra') ||
  411. elem.attr('ng-click')) {
  412. return;
  413. }
  414. }
  415. $scope.enterFullscreenMode({edit: false});
  416. };
  417. $scope.toggleSeries = function(info) {
  418. if ($scope.hiddenSeries[info.alias]) {
  419. delete $scope.hiddenSeries[info.alias];
  420. }
  421. else {
  422. $scope.hiddenSeries[info.alias] = true;
  423. }
  424. $scope.$emit('toggleLegend', info.alias);
  425. };
  426. });
  427. module.directive('histogramChart', function(filterSrv) {
  428. return {
  429. restrict: 'A',
  430. template: '<div> </div>',
  431. link: function(scope, elem) {
  432. var data, plot;
  433. var hiddenData = {};
  434. scope.$on('refresh',function() {
  435. scope.get_data();
  436. });
  437. scope.$on('toggleLegend', function(e, alias) {
  438. if (hiddenData[alias]) {
  439. data.push(hiddenData[alias]);
  440. delete hiddenData[alias];
  441. }
  442. render_panel();
  443. });
  444. // Receive render events
  445. scope.$on('render',function(event, d) {
  446. data = d || data;
  447. render_panel();
  448. });
  449. // Re-render if the window is resized
  450. angular.element(window).bind('resize', function() {
  451. render_panel();
  452. });
  453. // Function for rendering panel
  454. function render_panel() {
  455. if (!data) {
  456. return;
  457. }
  458. // IE doesn't work without this
  459. elem.css({height:scope.panel.height || scope.row.height});
  460. _.each(data, function(series) {
  461. series.label = series.info.alias;
  462. series.color = series.info.color;
  463. });
  464. _.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
  465. var dataSeries = _.find(data, function(series) {
  466. return series.info.alias === seriesAlias;
  467. });
  468. if (dataSeries) {
  469. hiddenData[dataSeries.info.alias] = dataSeries;
  470. data = _.without(data, dataSeries);
  471. }
  472. });
  473. // Set barwidth based on specified interval
  474. var barwidth = kbn.interval_to_ms(scope.panel.interval);
  475. var stack = scope.panel.stack ? true : null;
  476. // Populate element
  477. var options = {
  478. legend: { show: false },
  479. series: {
  480. stackpercent: scope.panel.stack ? scope.panel.percentage : false,
  481. stack: scope.panel.percentage ? null : stack,
  482. lines: {
  483. show: scope.panel.lines,
  484. // Silly, but fixes bug in stacked percentages
  485. fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
  486. lineWidth: scope.panel.linewidth,
  487. steps: false
  488. },
  489. bars: {
  490. show: scope.panel.bars,
  491. fill: 1,
  492. barWidth: barwidth/1.5,
  493. zero: false,
  494. lineWidth: 0
  495. },
  496. points: {
  497. show: scope.panel.points,
  498. fill: 1,
  499. fillColor: false,
  500. radius: scope.panel.pointradius
  501. },
  502. shadowSize: 1
  503. },
  504. yaxes: [
  505. {
  506. position: 'left',
  507. show: scope.panel['y-axis'],
  508. min: scope.panel.grid.min,
  509. max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
  510. }
  511. ],
  512. xaxis: {
  513. timezone: scope.panel.timezone,
  514. show: scope.panel['x-axis'],
  515. mode: "time",
  516. min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
  517. max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
  518. timeformat: time_format(scope.panel.interval),
  519. label: "Datetime",
  520. ticks: elem.width()/100
  521. },
  522. grid: {
  523. backgroundColor: null,
  524. borderWidth: 0,
  525. hoverable: true,
  526. color: '#c8c8c8'
  527. }
  528. };
  529. if(scope.panel.y_format === 'bytes') {
  530. options.yaxes[0].mode = "byte";
  531. }
  532. if(scope.panel.y_format === 'short') {
  533. options.yaxes[0].tickFormatter = function(val) {
  534. return kbn.shortFormat(val,0);
  535. };
  536. }
  537. if(scope.panel.annotate.enable) {
  538. options.events = {
  539. levels: 1,
  540. data: scope.annotations,
  541. types: {
  542. 'annotation': {
  543. level: 1,
  544. icon: {
  545. icon: "icon-tag icon-flip-vertical",
  546. size: 20,
  547. color: "#222",
  548. outline: "#bbb"
  549. }
  550. }
  551. }
  552. //xaxis: int // the x axis to attach events to
  553. };
  554. }
  555. if(scope.panel.interactive) {
  556. options.selection = { mode: "x", color: '#666' };
  557. }
  558. // when rendering stacked bars, we need to ensure each point that has data is zero-filled
  559. // so that the stacking happens in the proper order
  560. var required_times = [];
  561. if (data.length > 1) {
  562. required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
  563. return query.time_series.getOrderedTimes();
  564. }));
  565. required_times = _.uniq(required_times.sort(function (a, b) {
  566. // decending numeric sort
  567. return a-b;
  568. }), true);
  569. }
  570. for (var i = 0; i < data.length; i++) {
  571. var _d = data[i].time_series.getFlotPairs(required_times);
  572. data[i].data = _d;
  573. }
  574. var hasSecondY = _.findWhere(data, { yaxis: 2});
  575. if (hasSecondY) {
  576. options.yaxes.push({
  577. position: 'right',
  578. show: scope.panel['y-axis'],
  579. min: scope.panel.grid.min,
  580. max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
  581. });
  582. }
  583. plot = $.plot(elem, data, options);
  584. if (scope.panel.leftYAxisLabel) {
  585. elem.css('margin-left', '10px');
  586. var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
  587. .text(scope.panel.leftYAxisLabel)
  588. .appendTo(elem);
  589. yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
  590. } else if (elem.css('margin-left')) {
  591. elem.css('margin-left', '');
  592. }
  593. }
  594. function time_format(interval) {
  595. var _int = kbn.interval_to_seconds(interval);
  596. if(_int >= 2628000) {
  597. return "%Y-%m";
  598. }
  599. if(_int >= 10000) {
  600. return "%Y-%m-%d";
  601. }
  602. if(_int >= 60) {
  603. return "%H:%M<br>%m-%d";
  604. }
  605. return "%H:%M:%S";
  606. }
  607. var $tooltip = $('<div>');
  608. elem.bind("plothover", function (event, pos, item) {
  609. var group, value, timestamp;
  610. if (item) {
  611. if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
  612. group = '<small style="font-size:0.9em;">' +
  613. '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
  614. (item.series.info.alias || item.series.info.query)+
  615. '</small><br>';
  616. } else {
  617. group = kbn.query_color_dot(item.series.color, 15) + ' ';
  618. }
  619. value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
  620. item.datapoint[1] - item.datapoint[2] :
  621. item.datapoint[1];
  622. if(scope.panel.y_format === 'bytes') {
  623. value = kbn.byteFormat(value,2);
  624. }
  625. if(scope.panel.y_format === 'short') {
  626. value = kbn.shortFormat(value,2);
  627. }
  628. timestamp = scope.panel.timezone === 'browser' ?
  629. moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
  630. moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
  631. $tooltip
  632. .html(
  633. group + value + " @ " + timestamp
  634. )
  635. .place_tt(pos.pageX, pos.pageY);
  636. } else {
  637. $tooltip.detach();
  638. }
  639. });
  640. elem.bind("plotselected", function (event, ranges) {
  641. filterSrv.setTime({
  642. from : moment.utc(ranges.xaxis.from).toDate(),
  643. to : moment.utc(ranges.xaxis.to).toDate(),
  644. });
  645. });
  646. }
  647. };
  648. });
  649. });