module.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  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.histogram', []);
  32. app.useModule(module);
  33. module.controller('histogram', function($scope, querySrv, dashboard, filterSrv) {
  34. $scope.panelMeta = {
  35. modals : [
  36. {
  37. description: "Inspect",
  38. icon: "icon-info-sign",
  39. partial: "app/partials/inspector.html",
  40. show: $scope.panel.spyable
  41. }
  42. ],
  43. editorTabs : [
  44. {
  45. title:'Style',
  46. src:'app/panels/histogram/styleEditor.html'
  47. },
  48. {
  49. title:'Queries',
  50. src:'app/panels/histogram/queriesEditor.html'
  51. },
  52. ],
  53. status : "Stable",
  54. description : "A bucketed time series chart of the current query or queries. Uses the "+
  55. "Elasticsearch date_histogram facet. If using time stamped indices this panel will query"+
  56. " them sequentially to attempt to apply the lighest possible load to your Elasticsearch cluster"
  57. };
  58. // Set and populate defaults
  59. var _d = {
  60. /** @scratch /panels/histogram/3
  61. * === Parameters
  62. * ==== Axis options
  63. * mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
  64. * defined. Possible values: count, mean, max, min, total.
  65. */
  66. mode : 'count',
  67. /** @scratch /panels/histogram/3
  68. * time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
  69. */
  70. time_field : '@timestamp',
  71. /** @scratch /panels/histogram/3
  72. * value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
  73. */
  74. value_field : null,
  75. /** @scratch /panels/histogram/3
  76. * x-axis:: Show the x-axis
  77. */
  78. 'x-axis' : true,
  79. /** @scratch /panels/histogram/3
  80. * y-axis:: Show the y-axis
  81. */
  82. 'y-axis' : true,
  83. /** @scratch /panels/histogram/3
  84. * scale:: Scale the y-axis by this factor
  85. */
  86. scale : 1,
  87. /** @scratch /panels/histogram/3
  88. * y_format:: 'none','bytes','short '
  89. */
  90. y_format : 'none',
  91. /** @scratch /panels/histogram/5
  92. * grid object:: Min and max y-axis values
  93. * grid.min::: Minimum y-axis value
  94. * grid.max::: Maximum y-axis value
  95. */
  96. grid : {
  97. max: null,
  98. min: 0
  99. },
  100. /** @scratch /panels/histogram/5
  101. * ==== Queries
  102. * queries object:: This object describes the queries to use on this panel.
  103. * queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
  104. * queries.ids::: In +selected+ mode, which query ids are selected.
  105. */
  106. queries : {
  107. mode : 'all',
  108. ids : []
  109. },
  110. /** @scratch /panels/histogram/3
  111. * ==== Annotations
  112. * annotate object:: A query can be specified, the results of which will be displayed as markers on
  113. * the chart. For example, for noting code deploys.
  114. * annotate.enable::: Should annotations, aka markers, be shown?
  115. * annotate.query::: Lucene query_string syntax query to use for markers.
  116. * annotate.size::: Max number of markers to show
  117. * annotate.field::: Field from documents to show
  118. * annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
  119. */
  120. annotate : {
  121. enable : false,
  122. query : "*",
  123. size : 20,
  124. field : '_type',
  125. sort : ['_score','desc']
  126. },
  127. /** @scratch /panels/histogram/3
  128. * ==== Interval options
  129. * auto_int:: Automatically scale intervals?
  130. */
  131. auto_int : true,
  132. /** @scratch /panels/histogram/3
  133. * resolution:: If auto_int is true, shoot for this many bars.
  134. */
  135. resolution : 100,
  136. /** @scratch /panels/histogram/3
  137. * interval:: If auto_int is set to false, use this as the interval.
  138. */
  139. interval : '5m',
  140. /** @scratch /panels/histogram/3
  141. * interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
  142. */
  143. intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
  144. /** @scratch /panels/histogram/3
  145. * ==== Drawing options
  146. * lines:: Show line chart
  147. */
  148. lines : false,
  149. /** @scratch /panels/histogram/3
  150. * fill:: Area fill factor for line charts, 1-10
  151. */
  152. fill : 0,
  153. /** @scratch /panels/histogram/3
  154. * linewidth:: Weight of lines in pixels
  155. */
  156. linewidth : 3,
  157. /** @scratch /panels/histogram/3
  158. * points:: Show points on chart
  159. */
  160. points : false,
  161. /** @scratch /panels/histogram/3
  162. * pointradius:: Size of points in pixels
  163. */
  164. pointradius : 5,
  165. /** @scratch /panels/histogram/3
  166. * bars:: Show bars on chart
  167. */
  168. bars : true,
  169. /** @scratch /panels/histogram/3
  170. * stack:: Stack multiple series
  171. */
  172. stack : true,
  173. /** @scratch /panels/histogram/3
  174. * spyable:: Show inspect icon
  175. */
  176. spyable : true,
  177. /** @scratch /panels/histogram/3
  178. * zoomlinks:: Show `Zoom Out' link
  179. */
  180. zoomlinks : true,
  181. /** @scratch /panels/histogram/3
  182. * options:: Show quick view options section
  183. */
  184. options : true,
  185. /** @scratch /panels/histogram/3
  186. * legend:: Display the legond
  187. */
  188. legend : true,
  189. /** @scratch /panels/histogram/3
  190. * show_query:: If no alias is set, should the query be displayed?
  191. */
  192. show_query : true,
  193. /** @scratch /panels/histogram/3
  194. * interactive:: Enable click-and-drag to zoom functionality
  195. */
  196. interactive : true,
  197. /** @scratch /panels/histogram/3
  198. * legend_counts:: Show counts in legend
  199. */
  200. legend_counts : true,
  201. /** @scratch /panels/histogram/3
  202. * ==== Transformations
  203. * timezone:: Correct for browser timezone?. Valid values: browser, utc
  204. */
  205. timezone : 'browser', // browser or utc
  206. /** @scratch /panels/histogram/3
  207. * percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
  208. * queries
  209. */
  210. percentage : false,
  211. /** @scratch /panels/histogram/3
  212. * zerofill:: Improves the accuracy of line charts at a small performance cost.
  213. */
  214. zerofill : true,
  215. /** @scratch /panels/histogram/3
  216. * derivative:: Show each point on the x-axis as the change from the previous point
  217. */
  218. derivative : false,
  219. /** @scratch /panels/histogram/3
  220. * tooltip object::
  221. * tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
  222. * tooltip.query_as_alias::: If no alias is set, should the query be displayed?
  223. */
  224. tooltip : {
  225. value_type: 'cumulative',
  226. query_as_alias: true
  227. }
  228. };
  229. _.defaults($scope.panel,_d);
  230. _.defaults($scope.panel.tooltip,_d.tooltip);
  231. _.defaults($scope.panel.annotate,_d.annotate);
  232. _.defaults($scope.panel.grid,_d.grid);
  233. $scope.init = function() {
  234. // Hide view options by default
  235. $scope.options = false;
  236. // Always show the query if an alias isn't set. Users can set an alias if the query is too
  237. // long
  238. $scope.panel.tooltip.query_as_alias = true;
  239. $scope.get_data();
  240. };
  241. $scope.set_interval = function(interval) {
  242. if(interval !== 'auto') {
  243. $scope.panel.auto_int = false;
  244. $scope.panel.interval = interval;
  245. } else {
  246. $scope.panel.auto_int = true;
  247. }
  248. };
  249. $scope.interval_label = function(interval) {
  250. return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
  251. };
  252. /**
  253. * The time range effecting the panel
  254. * @return {[type]} [description]
  255. */
  256. $scope.get_time_range = function () {
  257. var range = $scope.range = filterSrv.timeRange('last');
  258. return range;
  259. };
  260. $scope.get_interval = function () {
  261. var interval = $scope.panel.interval,
  262. range;
  263. if ($scope.panel.auto_int) {
  264. range = $scope.get_time_range();
  265. if (range) {
  266. interval = kbn.secondsToHms(
  267. kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
  268. );
  269. }
  270. }
  271. $scope.panel.interval = interval || '10m';
  272. return $scope.panel.interval;
  273. };
  274. /**
  275. * Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
  276. * need to be consulted (like timestamped logstash indicies)
  277. *
  278. * The results of this function are stored on the scope's data property. This property will be an
  279. * array of objects with the properties info, time_series, and hits. These objects are used in the
  280. * render_panel function to create the historgram.
  281. *
  282. * @param {number} segment The segment count, (0 based)
  283. * @param {number} query_id The id of the query, generated on the first run and passed back when
  284. * this call is made recursively for more segments
  285. */
  286. $scope.get_data = function(data, segment, query_id) {
  287. var
  288. _range,
  289. _interval,
  290. request,
  291. queries,
  292. results;
  293. if (_.isUndefined(segment)) {
  294. segment = 0;
  295. }
  296. delete $scope.panel.error;
  297. // Make sure we have everything for the request to complete
  298. if(dashboard.indices.length === 0) {
  299. return;
  300. }
  301. _range = $scope.get_time_range();
  302. _interval = $scope.get_interval(_range);
  303. if ($scope.panel.auto_int) {
  304. $scope.panel.interval = kbn.secondsToHms(
  305. kbn.calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000);
  306. }
  307. $scope.panelMeta.loading = true;
  308. request = $scope.ejs.Request().indices(dashboard.indices[segment]);
  309. $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
  310. queries = querySrv.getQueryObjs($scope.panel.queries.ids);
  311. // Build the query
  312. _.each(queries, function(q) {
  313. var query = $scope.ejs.FilteredQuery(
  314. querySrv.toEjsObj(q),
  315. filterSrv.getBoolFilter(filterSrv.ids)
  316. );
  317. var facet = $scope.ejs.DateHistogramFacet(q.id);
  318. if($scope.panel.mode === 'count') {
  319. facet = facet.field($scope.panel.time_field).global(true);
  320. } else {
  321. if(_.isNull($scope.panel.value_field)) {
  322. $scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
  323. return;
  324. }
  325. facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field).global(true);
  326. }
  327. facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
  328. request = request.facet(facet)
  329. .size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
  330. });
  331. if($scope.panel.annotate.enable) {
  332. var query = $scope.ejs.FilteredQuery(
  333. $scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
  334. filterSrv.getBoolFilter(filterSrv.idsByType('time'))
  335. );
  336. request = request.query(query);
  337. // This is a hack proposed by @boaz to work around the fact that we can't get
  338. // to field data values directly, and we need timestamps as normalized longs
  339. request = request.sort([
  340. $scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
  341. $scope.ejs.Sort($scope.panel.time_field).desc()
  342. ]);
  343. }
  344. // Populate the inspector panel
  345. $scope.populate_modal(request);
  346. // Then run it
  347. results = request.doSearch();
  348. // Populate scope when we have results
  349. return results.then(function(results) {
  350. $scope.panelMeta.loading = false;
  351. if(segment === 0) {
  352. $scope.legend = [];
  353. $scope.hits = 0;
  354. data = [];
  355. $scope.annotations = [];
  356. query_id = $scope.query_id = new Date().getTime();
  357. }
  358. // Check for error and abort if found
  359. if(!(_.isUndefined(results.error))) {
  360. $scope.panel.error = $scope.parse_error(results.error);
  361. return;
  362. }
  363. // Make sure we're still on the same query/queries
  364. if($scope.query_id === query_id) {
  365. var i = 0,
  366. time_series,
  367. hits;
  368. _.each(queries, function(q) {
  369. var query_results = results.facets[q.id];
  370. // we need to initialize the data variable on the first run,
  371. // and when we are working on the first segment of the data.
  372. if(_.isUndefined(data[i]) || segment === 0) {
  373. var tsOpts = {
  374. interval: _interval,
  375. start_date: _range && _range.from,
  376. end_date: _range && _range.to,
  377. fill_style: $scope.panel.derivative ? 'null' : 'minimal'
  378. };
  379. time_series = new timeSeries.ZeroFilled(tsOpts);
  380. hits = 0;
  381. } else {
  382. time_series = data[i].time_series;
  383. hits = data[i].hits;
  384. }
  385. // push each entry into the time series, while incrementing counters
  386. _.each(query_results.entries, function(entry) {
  387. time_series.addValue(entry.time, entry[$scope.panel.mode]);
  388. hits += entry.count; // The series level hits counter
  389. $scope.hits += entry.count; // Entire dataset level hits counter
  390. });
  391. $scope.legend[i] = {query:q,hits:hits};
  392. data[i] = {
  393. info: q,
  394. time_series: time_series,
  395. hits: hits
  396. };
  397. console.log("elastic time_series:", time_series);
  398. i++;
  399. });
  400. if($scope.panel.annotate.enable) {
  401. $scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
  402. var _p = _.omit(hit,'_source','sort','_score');
  403. var _h = _.extend(kbn.flatten_json(hit._source),_p);
  404. return {
  405. min: hit.sort[1],
  406. max: hit.sort[1],
  407. eventType: "annotation",
  408. title: null,
  409. description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
  410. _h[$scope.panel.annotate.field]+"</small><br>"+
  411. moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
  412. score: hit.sort[0]
  413. };
  414. }));
  415. // Sort the data
  416. $scope.annotations = _.sortBy($scope.annotations, function(v){
  417. // Sort in reverse
  418. return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
  419. });
  420. // And slice to the right size
  421. $scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
  422. }
  423. // Tell the histogram directive to render.
  424. $scope.$emit('render', data);
  425. // If we still have segments left, get them
  426. if(segment < dashboard.indices.length-1) {
  427. $scope.get_data(data,segment+1,query_id);
  428. }
  429. }
  430. });
  431. };
  432. // function $scope.zoom
  433. // factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
  434. $scope.zoom = function(factor) {
  435. var _range = filterSrv.timeRange('last');
  436. var _timespan = (_range.to.valueOf() - _range.from.valueOf());
  437. var _center = _range.to.valueOf() - _timespan/2;
  438. var _to = (_center + (_timespan*factor)/2);
  439. var _from = (_center - (_timespan*factor)/2);
  440. // If we're not already looking into the future, don't.
  441. if(_to > Date.now() && _range.to < Date.now()) {
  442. var _offset = _to - Date.now();
  443. _from = _from - _offset;
  444. _to = Date.now();
  445. }
  446. if(factor > 1) {
  447. filterSrv.removeByType('time');
  448. }
  449. filterSrv.set({
  450. type:'time',
  451. from:moment.utc(_from).toDate(),
  452. to:moment.utc(_to).toDate(),
  453. field:$scope.panel.time_field
  454. });
  455. };
  456. // I really don't like this function, too much dom manip. Break out into directive?
  457. $scope.populate_modal = function(request) {
  458. $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
  459. };
  460. $scope.set_refresh = function (state) {
  461. $scope.refresh = state;
  462. };
  463. $scope.close_edit = function() {
  464. if($scope.refresh) {
  465. $scope.get_data();
  466. }
  467. $scope.refresh = false;
  468. $scope.$emit('render');
  469. };
  470. $scope.render = function() {
  471. $scope.$emit('render');
  472. };
  473. });
  474. module.directive('histogramChart', function(dashboard, filterSrv) {
  475. return {
  476. restrict: 'A',
  477. template: '<div></div>',
  478. link: function(scope, elem) {
  479. var data, plot;
  480. scope.$on('refresh',function(){
  481. scope.get_data();
  482. });
  483. // Receive render events
  484. scope.$on('render',function(event,d){
  485. data = d || data;
  486. render_panel(data);
  487. });
  488. // Re-render if the window is resized
  489. angular.element(window).bind('resize', function(){
  490. render_panel(data);
  491. });
  492. var scale = function(series,factor) {
  493. return _.map(series,function(p) {
  494. return [p[0],p[1]*factor];
  495. });
  496. };
  497. var scaleSeconds = function(series,interval) {
  498. return _.map(series,function(p) {
  499. return [p[0],p[1]/kbn.interval_to_seconds(interval)];
  500. });
  501. };
  502. var derivative = function(series) {
  503. return _.map(series, function(p,i) {
  504. var _v;
  505. if(i === 0 || p[1] === null) {
  506. _v = [p[0],null];
  507. } else {
  508. _v = series[i-1][1] === null ? [p[0],null] : [p[0],p[1]-(series[i-1][1])];
  509. }
  510. return _v;
  511. });
  512. };
  513. // Function for rendering panel
  514. function render_panel(data) {
  515. // IE doesn't work without this
  516. elem.css({height:scope.panel.height || scope.row.height});
  517. // Populate from the query service
  518. try {
  519. _.each(data, function(series) {
  520. series.label = series.info.alias;
  521. series.color = series.info.color;
  522. });
  523. } catch(e) {return;}
  524. // Set barwidth based on specified interval
  525. var barwidth = kbn.interval_to_ms(scope.panel.interval);
  526. var stack = scope.panel.stack ? true : null;
  527. // Populate element
  528. try {
  529. var options = {
  530. legend: { show: false },
  531. series: {
  532. stackpercent: scope.panel.stack ? scope.panel.percentage : false,
  533. stack: scope.panel.percentage ? null : stack,
  534. lines: {
  535. show: scope.panel.lines,
  536. // Silly, but fixes bug in stacked percentages
  537. fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
  538. lineWidth: scope.panel.linewidth,
  539. steps: false
  540. },
  541. bars: {
  542. show: scope.panel.bars,
  543. fill: 1,
  544. barWidth: barwidth/1.5,
  545. zero: false,
  546. lineWidth: 0
  547. },
  548. points: {
  549. show: scope.panel.points,
  550. fill: 1,
  551. fillColor: false,
  552. radius: scope.panel.pointradius
  553. },
  554. shadowSize: 1
  555. },
  556. yaxis: {
  557. show: scope.panel['y-axis'],
  558. min: scope.panel.grid.min,
  559. max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
  560. },
  561. xaxis: {
  562. timezone: scope.panel.timezone,
  563. show: scope.panel['x-axis'],
  564. mode: "time",
  565. min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
  566. max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
  567. timeformat: time_format(scope.panel.interval),
  568. label: "Datetime",
  569. ticks: elem.width()/100
  570. },
  571. grid: {
  572. backgroundColor: null,
  573. borderWidth: 0,
  574. hoverable: true,
  575. color: '#c8c8c8'
  576. }
  577. };
  578. if(scope.panel.y_format === 'bytes') {
  579. options.yaxis.mode = "byte";
  580. }
  581. if(scope.panel.y_format === 'short') {
  582. options.yaxis.tickFormatter = function(val) {
  583. return kbn.shortFormat(val,0);
  584. };
  585. }
  586. if(scope.panel.annotate.enable) {
  587. options.events = {
  588. levels: 1,
  589. data: scope.annotations,
  590. types: {
  591. 'annotation': {
  592. level: 1,
  593. icon: {
  594. icon: "icon-tag icon-flip-vertical",
  595. size: 20,
  596. color: "#222",
  597. outline: "#bbb"
  598. }
  599. }
  600. }
  601. //xaxis: int // the x axis to attach events to
  602. };
  603. }
  604. if(scope.panel.interactive) {
  605. options.selection = { mode: "x", color: '#666' };
  606. }
  607. // when rendering stacked bars, we need to ensure each point that has data is zero-filled
  608. // so that the stacking happens in the proper order
  609. var required_times = [];
  610. if (data.length > 1) {
  611. required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
  612. return query.time_series.getOrderedTimes();
  613. }));
  614. required_times = _.uniq(required_times.sort(function (a, b) {
  615. // decending numeric sort
  616. return a-b;
  617. }), true);
  618. }
  619. for (var i = 0; i < data.length; i++) {
  620. var _d = data[i].time_series.getFlotPairs(required_times);
  621. if(scope.panel.derivative) {
  622. _d = derivative(_d);
  623. }
  624. if(scope.panel.scale !== 1) {
  625. _d = scale(_d,scope.panel.scale);
  626. }
  627. if(scope.panel.scaleSeconds) {
  628. _d = scaleSeconds(_d,scope.panel.interval);
  629. }
  630. data[i].data = _d;
  631. }
  632. plot = $.plot(elem, data, options);
  633. } catch(e) {
  634. // Nothing to do here
  635. }
  636. }
  637. function time_format(interval) {
  638. var _int = kbn.interval_to_seconds(interval);
  639. if(_int >= 2628000) {
  640. return "%Y-%m";
  641. }
  642. if(_int >= 86400) {
  643. return "%Y-%m-%d";
  644. }
  645. if(_int >= 60) {
  646. return "%H:%M<br>%m-%d";
  647. }
  648. return "%H:%M:%S";
  649. }
  650. var $tooltip = $('<div>');
  651. elem.bind("plothover", function (event, pos, item) {
  652. var group, value, timestamp;
  653. if (item) {
  654. if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
  655. group = '<small style="font-size:0.9em;">' +
  656. '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
  657. (item.series.info.alias || item.series.info.query)+
  658. '</small><br>';
  659. } else {
  660. group = kbn.query_color_dot(item.series.color, 15) + ' ';
  661. }
  662. value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
  663. item.datapoint[1] - item.datapoint[2] :
  664. item.datapoint[1];
  665. if(scope.panel.y_format === 'bytes') {
  666. value = kbn.byteFormat(value,2);
  667. }
  668. if(scope.panel.y_format === 'short') {
  669. value = kbn.shortFormat(value,2);
  670. }
  671. timestamp = scope.panel.timezone === 'browser' ?
  672. moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
  673. moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
  674. $tooltip
  675. .html(
  676. group + value + " @ " + timestamp
  677. )
  678. .place_tt(pos.pageX, pos.pageY);
  679. } else {
  680. $tooltip.detach();
  681. }
  682. });
  683. elem.bind("plotselected", function (event, ranges) {
  684. filterSrv.set({
  685. type : 'time',
  686. from : moment.utc(ranges.xaxis.from).toDate(),
  687. to : moment.utc(ranges.xaxis.to).toDate(),
  688. field : scope.panel.time_field
  689. });
  690. });
  691. }
  692. };
  693. });
  694. });