linear_time.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with this
  4. * work for additional information regarding copyright ownership. The ASF
  5. * licenses this file to you under the Apache License, Version 2.0 (the
  6. * "License"); you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations under
  15. * the License.
  16. */
  17. var App = require('app');
  18. var string_utils = require('utils/string_utils');
  19. /**
  20. * @class
  21. *
  22. * This is a view which GETs data from a URL and shows it as a time based line
  23. * graph. Time is shown on the X axis with data series shown on Y axis. It
  24. * optionally also has the ability to auto refresh itself over a given time
  25. * interval.
  26. *
  27. * This is an abstract class which is meant to be extended.
  28. *
  29. * Extending classes should override the following:
  30. * <ul>
  31. * <li>url - from where the data can be retrieved
  32. * <li>title - Title to be displayed when showing the chart
  33. * <li>id - which uniquely identifies this chart in any page
  34. * <li>#transformToSeries(jsonData) - function to map server data into graph
  35. * series
  36. * </ul>
  37. *
  38. * Extending classes could optionally override the following:
  39. * <ul>
  40. * <li>#colorForSeries(series) - function to get custom colors per series
  41. * </ul>
  42. *
  43. * @extends Ember.Object
  44. * @extends Ember.View
  45. */
  46. App.ChartLinearTimeView = Ember.View.extend({
  47. templateName: require('templates/main/charts/linear_time'),
  48. /**
  49. * The URL from which data can be retrieved.
  50. *
  51. * This property must be provided for the graph to show properly.
  52. *
  53. * @type String
  54. * @default null
  55. */
  56. url: null,
  57. /**
  58. * A unique ID for this chart.
  59. *
  60. * @type String
  61. * @default null
  62. */
  63. id: null,
  64. /**
  65. * Title to be shown under the chart.
  66. *
  67. * @type String
  68. * @default null
  69. */
  70. title: null,
  71. /**
  72. * @private
  73. *
  74. * @type Rickshaw.Graph
  75. * @default null
  76. */
  77. _graph: null,
  78. /**
  79. * Array of classnames for each series (in widget)
  80. * @type Rickshaw.Graph
  81. */
  82. _popupGraph: null,
  83. /**
  84. * Array of classnames for each series
  85. * @type Array
  86. */
  87. _seriesProperties: null,
  88. /**
  89. * Array of classnames for each series (in widget)
  90. * @type Array
  91. */
  92. _seriesPropertiesWidget: null,
  93. /**
  94. * Renderer type
  95. * See <code>Rickshaw.Graph.Renderer</code> for more info
  96. * @type String
  97. */
  98. renderer: 'area',
  99. /**
  100. * Suffix used in DOM-elements selectors
  101. * @type String
  102. */
  103. popupSuffix: '-popup',
  104. /**
  105. * Is popup for current graph open
  106. * @type Boolean
  107. */
  108. isPopup: false,
  109. /**
  110. * Is graph ready
  111. * @type Boolean
  112. */
  113. isReady: false,
  114. /**
  115. * Is popup-graph ready
  116. * @type Boolean
  117. */
  118. isPopupReady: false,
  119. /**
  120. * Is data for graph available
  121. * @type Boolean
  122. */
  123. hasData: true,
  124. /**
  125. * chart height
  126. * @type {number}
  127. * @default 150
  128. */
  129. height: 150,
  130. didInsertElement: function () {
  131. this.loadData();
  132. this.registerGraph();
  133. },
  134. registerGraph: function() {
  135. var graph = {
  136. name: this.get('title'),
  137. id: this.get('elementId'),
  138. popupId: this.get('id')
  139. };
  140. App.router.get('updateController.graphs').push(graph);
  141. },
  142. loadData: function() {
  143. App.ajax.send({
  144. name: this.get('ajaxIndex'),
  145. sender: this,
  146. data: this.getDataForAjaxRequest(),
  147. success: '_refreshGraph',
  148. error: 'loadDataErrorCallback'
  149. });
  150. },
  151. getDataForAjaxRequest: function() {
  152. var toSeconds = Math.round(App.dateTime() / 1000);
  153. var hostName = (this.get('content')) ? this.get('content.hostName') : "";
  154. var HDFSService = App.HDFSService.find().objectAt(0);
  155. var nameNodeName = "";
  156. var YARNService = App.YARNService.find().objectAt(0);
  157. var resourceManager = YARNService ? YARNService.get('resourceManager.hostName') : "";
  158. var timeUnit = this.get('timeUnitSeconds');
  159. if (HDFSService) {
  160. nameNodeName = (HDFSService.get('activeNameNode')) ? HDFSService.get('activeNameNode.hostName') : HDFSService.get('nameNode.hostName');
  161. }
  162. return {
  163. toSeconds: toSeconds,
  164. fromSeconds: toSeconds - timeUnit,
  165. stepSeconds: 15,
  166. hostName: hostName,
  167. nameNodeName: nameNodeName,
  168. resourceManager: resourceManager
  169. };
  170. },
  171. loadDataErrorCallback: function(xhr, textStatus, errorThrown) {
  172. this.set('isReady', true);
  173. if (xhr.readyState == 4 && xhr.status) {
  174. textStatus = xhr.status + " " + textStatus;
  175. }
  176. this._showMessage('warn', this.t('graphs.error.title'), this.t('graphs.error.message').format(textStatus, errorThrown));
  177. this.set('isPopup', false);
  178. this.set('hasData', false);
  179. },
  180. /**
  181. * Shows a yellow warning message in place of the chart.
  182. *
  183. * @param type Can be any of 'warn', 'error', 'info', 'success'
  184. * @param title Bolded title for the message
  185. * @param message String representing the message
  186. * @type: Function
  187. */
  188. _showMessage: function(type, title, message) {
  189. var chartOverlay = '#' + this.get('id');
  190. var chartOverlayId = chartOverlay + '-chart';
  191. var chartOverlayY = chartOverlay + '-yaxis';
  192. var chartOverlayX = chartOverlay + '-xaxis';
  193. var chartOverlayLegend = chartOverlay + '-legend';
  194. var chartOverlayTimeline = chartOverlay + '-timeline';
  195. if (this.get('isPopup')) {
  196. chartOverlayId += this.get('popupSuffix');
  197. chartOverlayY += this.get('popupSuffix');
  198. chartOverlayX += this.get('popupSuffix');
  199. chartOverlayLegend += this.get('popupSuffix');
  200. chartOverlayTimeline += this.get('popupSuffix');
  201. }
  202. var typeClass;
  203. switch (type) {
  204. case 'error':
  205. typeClass = 'alert-error';
  206. break;
  207. case 'success':
  208. typeClass = 'alert-success';
  209. break;
  210. case 'info':
  211. typeClass = 'alert-info';
  212. break;
  213. default:
  214. typeClass = '';
  215. break;
  216. }
  217. $(chartOverlayId+', '+chartOverlayY+', '+chartOverlayX+', '+chartOverlayLegend+', '+chartOverlayTimeline).html('');
  218. $(chartOverlayId).append('<div class=\"alert '+typeClass+'\"><strong>'+title+'</strong> '+message+'</div>');
  219. },
  220. /**
  221. * Transforms the JSON data retrieved from the server into the series
  222. * format that Rickshaw.Graph understands.
  223. *
  224. * The series object is generally in the following format: [ { name :
  225. * "Series 1", data : [ { x : 0, y : 0 }, { x : 1, y : 1 } ] } ]
  226. *
  227. * Extending classes should override this method.
  228. *
  229. * @param seriesData
  230. * Data retrieved from the server
  231. * @param displayName
  232. * Graph title
  233. * @type: Function
  234. *
  235. */
  236. transformData: function (seriesData, displayName) {
  237. var seriesArray = [];
  238. if (seriesData != null) {
  239. // Is it a string?
  240. if ("string" == typeof seriesData) {
  241. seriesData = JSON.parse(seriesData);
  242. }
  243. // Is it a number?
  244. if ("number" == typeof seriesData) {
  245. // Same number applies to all time.
  246. var number = seriesData;
  247. seriesData = [];
  248. seriesData.push([number, App.dateTime()-(60*60)]);
  249. seriesData.push([number, App.dateTime()]);
  250. }
  251. // We have valid data
  252. var series = {};
  253. series.name = displayName;
  254. series.data = [];
  255. for ( var index = 0; index < seriesData.length; index++) {
  256. series.data.push({
  257. x: seriesData[index][1],
  258. y: seriesData[index][0]
  259. });
  260. }
  261. return series;
  262. }
  263. return null;
  264. },
  265. /**
  266. * Provides the formatter to use in displaying Y axis.
  267. *
  268. * Uses the App.ChartLinearTimeView.DefaultFormatter which shows 10K,
  269. * 300M etc.
  270. *
  271. * @type Function
  272. */
  273. yAxisFormatter: function(y) {
  274. return App.ChartLinearTimeView.DefaultFormatter(y);
  275. },
  276. /**
  277. * Provides the color (in any HTML color format) to use for a particular
  278. * series.
  279. * May be redefined in child views
  280. *
  281. * @param series
  282. * Series for which color is being requested
  283. * @return color String. Returning null allows this chart to pick a color
  284. * from palette.
  285. * @default null
  286. * @type Function
  287. */
  288. colorForSeries: function (series) {
  289. return null;
  290. },
  291. /**
  292. * Check whether seriesData is correct data for chart drawing
  293. * @param {Array} seriesData
  294. * @return {Boolean}
  295. */
  296. checkSeries : function(seriesData) {
  297. if(!seriesData || !seriesData.length) {
  298. return false;
  299. }
  300. var result = true;
  301. seriesData.forEach(function(item) {
  302. if(!item.data || !item.data.length || !item.data[0] || typeof item.data[0].x === 'undefined') {
  303. result = false;
  304. }
  305. });
  306. return result;
  307. },
  308. /**
  309. * @private
  310. *
  311. * Refreshes the graph with the latest JSON data.
  312. *
  313. * @type Function
  314. */
  315. _refreshGraph: function (jsonData) {
  316. if(this.get('isDestroyed')){
  317. return;
  318. }
  319. var seriesData = this.transformToSeries(jsonData);
  320. //if graph opened as modal popup
  321. var popup_path = $("#" + this.get('id') + "-container" + this.get('popupSuffix'));
  322. var graph_container = $("#" + this.get('id') + "-container");
  323. if(popup_path.length) {
  324. popup_path.children().each(function () {
  325. $(this).children().remove();
  326. });
  327. this.set('isPopup', true);
  328. }
  329. else {
  330. graph_container.children().each(function (index, value) {
  331. $(value).children().remove();
  332. });
  333. }
  334. if (this.checkSeries(seriesData)) {
  335. // Check container exists (may be not, if we go to another page and wait while graphs loading)
  336. if (graph_container.length) {
  337. this.draw(seriesData);
  338. this.set('hasData', true);
  339. //move yAxis value lower to make them fully visible
  340. $("#" + this.get('id') + "-container").find('.y_axis text').attr('y',8);
  341. }
  342. }
  343. else {
  344. this.set('isReady', true);
  345. //if Axis X time interval is default(60 minutes)
  346. if(this.get('timeUnitSeconds') === 3600){
  347. this._showMessage('info', this.t('graphs.noData.title'), this.t('graphs.noData.message'));
  348. this.set('hasData', false);
  349. }
  350. else {
  351. this._showMessage('info', this.t('graphs.noData.title'), this.t('graphs.noDataAtTime.message'));
  352. }
  353. this.set('isPopup', false);
  354. }
  355. },
  356. /**
  357. * Returns a custom time unit, that depends on X axis interval length, for the graph's X axis.
  358. * This is needed as Rickshaw's default time X axis uses UTC time, which can be confusing
  359. * for users expecting locale specific time.
  360. *
  361. * If <code>null</code> is returned, Rickshaw's default time unit is used.
  362. *
  363. * @type Function
  364. * @return Rickshaw.Fixtures.Time
  365. */
  366. localeTimeUnit: function(timeUnitSeconds) {
  367. var timeUnit = new Rickshaw.Fixtures.Time();
  368. switch (timeUnitSeconds){
  369. case 604800:
  370. timeUnit = timeUnit.unit('day');
  371. break;
  372. case 2592000:
  373. timeUnit = timeUnit.unit('week');
  374. break;
  375. case 31104000:
  376. timeUnit = timeUnit.unit('month');
  377. break;
  378. default:
  379. timeUnit = {
  380. name: timeUnitSeconds / 240 + ' minute',
  381. seconds: timeUnitSeconds / 4,
  382. formatter: function (d) {
  383. // format locale specific time
  384. var minutes = d.getMinutes() > 9 ? "" + d.getMinutes() : "0" + d.getMinutes();
  385. var hours = d.getHours() > 9 ? "" + d.getHours() : "0" + d.getHours();
  386. return hours + ":" + minutes;
  387. }
  388. };
  389. }
  390. return timeUnit;
  391. },
  392. /**
  393. * temporary fix for incoming data for graph
  394. * to shift data time to correct time point
  395. * @param {Array} data
  396. */
  397. dataShiftFix: function(data) {
  398. var nowTime = Math.round(App.dateTime() / 1000);
  399. data.forEach(function(series){
  400. var l = series.data.length;
  401. var shiftDiff = nowTime - series.data[l - 1].x;
  402. if(shiftDiff > 3600){
  403. for(var i = 0;i < l;i++){
  404. series.data[i].x = series.data[i].x + shiftDiff;
  405. }
  406. series.data.unshift({
  407. x: nowTime - this.get('timeUnitSeconds'),
  408. y: 0
  409. });
  410. }
  411. }, this);
  412. },
  413. /**
  414. * calculate statistic data for popup legend and set proper colors for series
  415. * @param {Array} data
  416. */
  417. dataPreProcess: function(data) {
  418. var self = this;
  419. var palette = new Rickshaw.Color.Palette({ scheme: 'munin'});
  420. // Format series for display
  421. var series_min_length = 100000000;
  422. data.forEach(function (series) {
  423. var seriesColor = self.colorForSeries(series);
  424. if (Em.isNone(seriesColor)) {
  425. seriesColor = palette.color();
  426. }
  427. series.color = seriesColor;
  428. series.stroke = 'rgba(0,0,0,0.3)';
  429. if (self.get('isPopup')) {
  430. // calculate statistic data for popup legend
  431. var avg = 0;
  432. var min = Number.MAX_VALUE;
  433. var max = Number.MIN_VALUE;
  434. for (var i = 0; i < series.data.length; i++) {
  435. avg += series.data[i]['y'];
  436. if (!Em.isNone(series.data[i]['y'])) {
  437. if (series.data[i]['y'] < min) {
  438. min = series.data[i]['y'];
  439. }
  440. }
  441. if (series.data[i]['y'] > max) {
  442. max = series.data[i]['y'];
  443. }
  444. }
  445. series.name = string_utils.pad(series.name.length > 36 ? series.name.substr(0, 36) + '...' : series.name, 40, '&nbsp;', 2) + '|&nbsp;' +
  446. string_utils.pad('min', 5, '&nbsp;', 3) +
  447. string_utils.pad(self.get('yAxisFormatter')(min), 12, '&nbsp;', 3) +
  448. string_utils.pad('avg', 5, '&nbsp;', 3) +
  449. string_utils.pad(self.get('yAxisFormatter')(avg/series.data.compact().length), 12, '&nbsp;', 3) +
  450. string_utils.pad('max', 12, '&nbsp;', 3) +
  451. string_utils.pad(self.get('yAxisFormatter')(max), 5, '&nbsp;', 3);
  452. }
  453. if (series.data.length < series_min_length) {
  454. series_min_length = series.data.length;
  455. }
  456. });
  457. // All series should have equal length
  458. data.forEach(function(series) {
  459. if (series.data.length > series_min_length) {
  460. series.data.length = series_min_length;
  461. }
  462. });
  463. },
  464. draw: function(seriesData) {
  465. var self = this;
  466. var isPopup = this.get('isPopup');
  467. var p = isPopup ? this.get('popupSuffix') : '';
  468. this.dataShiftFix(seriesData);
  469. this.dataPreProcess(seriesData);
  470. var chartId = "#" + this.get('id') + "-chart" + p;
  471. var chartOverlayId = "#" + this.get('id') + "-container" + p;
  472. var xaxisElementId = "#" + this.get('id') + "-xaxis" + p;
  473. var yaxisElementId = "#" + this.get('id') + "-yaxis" + p;
  474. var legendElementId = "#" + this.get('id') + "-legend" + p;
  475. var chartElement = document.querySelector(chartId);
  476. var overlayElement = document.querySelector(chartOverlayId);
  477. var xaxisElement = document.querySelector(xaxisElementId);
  478. var yaxisElement = document.querySelector(yaxisElementId);
  479. var legendElement = document.querySelector(legendElementId);
  480. var height = this.get('height');
  481. var width = 400;
  482. var diff = 32;
  483. if(this.get('inWidget')) {
  484. height = 105; // for widgets view
  485. diff = 22;
  486. }
  487. if (isPopup) {
  488. height = 180;
  489. width = 670;
  490. }
  491. else {
  492. // If not in popup, the width could vary.
  493. // We determine width based on div's size.
  494. var thisElement = this.get('element');
  495. if (thisElement!=null) {
  496. var calculatedWidth = $(thisElement).width();
  497. if (calculatedWidth > diff) {
  498. width = calculatedWidth - diff;
  499. }
  500. }
  501. }
  502. var _graph = new Rickshaw.GraphReopened({
  503. height: height,
  504. width: width,
  505. element: chartElement,
  506. series: seriesData,
  507. interpolation: 'step-after',
  508. stroke: true,
  509. renderer: this.get('renderer'),
  510. strokeWidth: (this.get('renderer') != 'area' ? 2 : 1)
  511. });
  512. if (this.get('renderer') === 'area') {
  513. _graph.renderer.unstack = false;
  514. }
  515. new Rickshaw.Graph.Axis.Time({
  516. graph: _graph,
  517. timeUnit: this.localeTimeUnit(this.get('timeUnitSeconds'))
  518. });
  519. new Rickshaw.Graph.Axis.Y({
  520. tickFormat: this.yAxisFormatter,
  521. pixelsPerTick: (isPopup ? 75 : 40),
  522. element: yaxisElement,
  523. orientation: (isPopup ? 'left' : 'right'),
  524. graph: _graph
  525. });
  526. var legend = new Rickshaw.Graph.Legend({
  527. graph: _graph,
  528. element: legendElement
  529. });
  530. new Rickshaw.Graph.Behavior.Series.Toggle({
  531. graph: _graph,
  532. legend: legend
  533. });
  534. new Rickshaw.Graph.Behavior.Series.Order({
  535. graph: _graph,
  536. legend: legend
  537. });
  538. if (!isPopup) {
  539. overlayElement.addEventListener('mousemove', function () {
  540. $(xaxisElement).removeClass('hide');
  541. $(legendElement).removeClass('hide');
  542. $(chartElement).children("div").removeClass('hide');
  543. });
  544. overlayElement.addEventListener('mouseout', function () {
  545. $(legendElement).addClass('hide');
  546. });
  547. _graph.onUpdate(function () {
  548. $(legendElement).addClass('hide');
  549. });
  550. }
  551. //show the graph when it's loaded
  552. _graph.onUpdate(function() {
  553. self.set('isReady', true);
  554. });
  555. _graph.render();
  556. if (isPopup) {
  557. new Rickshaw.Graph.HoverDetail({
  558. graph: _graph,
  559. yFormatter:function (y) {
  560. return self.yAxisFormatter(y);
  561. },
  562. xFormatter:function (x) {
  563. return (new Date(x * 1000)).toLocaleTimeString();
  564. },
  565. formatter:function (series, x, y, formattedX, formattedY, d) {
  566. return formattedY + '<br />' + formattedX;
  567. }
  568. });
  569. }
  570. _graph = this.updateSeriesInGraph(_graph);
  571. if (isPopup) {
  572. //show the graph when it's loaded
  573. _graph.onUpdate(function() {
  574. self.set('isPopupReady', true);
  575. });
  576. _graph.update();
  577. var selector = '#'+this.get('id')+'-container'+this.get('popupSuffix');
  578. $(selector + ' li.line').click(function() {
  579. var series = [];
  580. $(selector + ' a.action').each(function(index, v) {
  581. series[index] = v.parentNode.classList;
  582. });
  583. self.set('_seriesProperties', series);
  584. });
  585. this.set('_popupGraph', _graph);
  586. }
  587. else {
  588. _graph.update();
  589. var selector = '#'+this.get('id')+'-container';
  590. $(selector + ' li.line').click(function() {
  591. var series = [];
  592. $(selector + ' a.action').each(function(index, v) {
  593. series[index] = v.parentNode.classList;
  594. });
  595. self.set('_seriesPropertiesWidget', series);
  596. });
  597. this.set('_graph', _graph);
  598. }
  599. },
  600. /**
  601. *
  602. * @param {Rickshaw.Graph} graph
  603. * @returns {Rickshaw.Graph}
  604. */
  605. updateSeriesInGraph: function(graph) {
  606. var id = this.get('id');
  607. var isPopup = this.get('isPopup');
  608. var popupSuffix = this.get('popupSuffix');
  609. var _series = isPopup ? this.get('_seriesProperties') : this.get('_seriesPropertiesWidget');
  610. graph.series.forEach(function(series, index) {
  611. if (_series !== null && _series[index] !== null && _series[index] !== undefined ) {
  612. if(_series[_series.length - index - 1].length > 1) {
  613. var s = '#' + id + '-container' + (isPopup ? popupSuffix : '') + ' a.action:eq(' + (_series.length - index - 1) + ')';
  614. $(s).parent('li').addClass('disabled');
  615. series.disable();
  616. }
  617. }
  618. });
  619. return graph;
  620. },
  621. showGraphInPopup: function() {
  622. if(!this.get('hasData')) {
  623. return;
  624. }
  625. this.set('isPopup', true);
  626. var self = this;
  627. App.ModalPopup.show({
  628. bodyClass: Em.View.extend({
  629. containerId: null,
  630. containerClass: null,
  631. yAxisId: null,
  632. yAxisClass: null,
  633. xAxisId: null,
  634. xAxisClass: null,
  635. legendId: null,
  636. legendClass: null,
  637. chartId: null,
  638. chartClass: null,
  639. titleId: null,
  640. titleClass: null,
  641. isReady: function() {
  642. return this.get('parentView.graph.isPopupReady');
  643. }.property('parentView.graph.isPopupReady'),
  644. didInsertElement: function() {
  645. $('#modal').addClass('modal-graph-line');
  646. var popupSuffix = this.get('parentView.graph.popupSuffix');
  647. var id = this.get('parentView.graph.id');
  648. var idTemplate = id + '-{element}' + popupSuffix;
  649. this.set('containerId', idTemplate.replace('{element}', 'container'));
  650. this.set('containerClass', 'chart-container' + popupSuffix);
  651. this.set('yAxisId', idTemplate.replace('{element}', 'yaxis'));
  652. this.set('yAxisClass', this.get('yAxisId').replace(popupSuffix, ''));
  653. this.set('xAxisId', idTemplate.replace('{element}', 'xaxis'));
  654. this.set('xAxisClass', this.get('xAxisId').replace(popupSuffix, ''));
  655. this.set('legendId', idTemplate.replace('{element}', 'legend'));
  656. this.set('legendClass', this.get('legendId').replace(popupSuffix, ''));
  657. this.set('chartId', idTemplate.replace('{element}', 'chart'));
  658. this.set('chartClass', this.get('chartId').replace(popupSuffix, ''));
  659. this.set('titleId', idTemplate.replace('{element}', 'title'));
  660. this.set('titleClass', this.get('titleId').replace(popupSuffix, ''));
  661. },
  662. templateName: require('templates/common/chart/linear_time'),
  663. /**
  664. * check is time paging feature is enable for graph
  665. */
  666. isTimePagingEnable: function() {
  667. return !self.get('isTimePagingDisable');
  668. }.property(),
  669. rightArrowVisible: function() {
  670. return (this.get('isReady') && (this.get('parentView.currentTimeIndex') != 0));
  671. }.property('isReady', 'parentView.currentTimeIndex'),
  672. leftArrowVisible: function() {
  673. return (this.get('isReady') && (this.get('parentView.currentTimeIndex') != 7));
  674. }.property('isReady', 'parentView.currentTimeIndex')
  675. }),
  676. header: this.get('title'),
  677. /**
  678. * App.ChartLinearTimeView
  679. */
  680. graph: self,
  681. secondary: null,
  682. onPrimary: function() {
  683. this.hide();
  684. self.set('isPopup', false);
  685. },
  686. onClose: function() {
  687. this.onPrimary();
  688. },
  689. /**
  690. * move graph back by time
  691. * @param event
  692. */
  693. switchTimeBack: function(event) {
  694. var index = this.get('currentTimeIndex');
  695. // 7 - number of last time state
  696. if(index < 7) {
  697. this.reloadGraphByTime(++index);
  698. }
  699. },
  700. /**
  701. * move graph forward by time
  702. * @param event
  703. */
  704. switchTimeForward: function(event) {
  705. var index = this.get('currentTimeIndex');
  706. if(index > 0) {
  707. this.reloadGraphByTime(--index);
  708. }
  709. },
  710. /**
  711. * reload graph depending on the time
  712. * @param index
  713. */
  714. reloadGraphByTime: function(index) {
  715. this.set('currentTimeIndex', index);
  716. self.set('currentTimeIndex', index);
  717. self.loadData();
  718. },
  719. currentTimeIndex: self.get('currentTimeIndex'),
  720. currentTimeState: function() {
  721. return self.get('timeStates').objectAt(this.get('currentTimeIndex'));
  722. }.property('currentTimeIndex')
  723. });
  724. Ember.run.next(function() {
  725. self.loadData();
  726. self.set('isPopupReady', false);
  727. });
  728. },
  729. timeStates: [
  730. {name: Em.I18n.t('graphs.timeRange.hour'), seconds: 3600},
  731. {name: Em.I18n.t('graphs.timeRange.twoHours'), seconds: 7200},
  732. {name: Em.I18n.t('graphs.timeRange.fourHours'), seconds: 14400},
  733. {name: Em.I18n.t('graphs.timeRange.twelveHours'), seconds: 43200},
  734. {name: Em.I18n.t('graphs.timeRange.day'), seconds: 86400},
  735. {name: Em.I18n.t('graphs.timeRange.week'), seconds: 604800},
  736. {name: Em.I18n.t('graphs.timeRange.month'), seconds: 2592000},
  737. {name: Em.I18n.t('graphs.timeRange.year'), seconds: 31104000}
  738. ],
  739. // should be set by time range control dropdown list when create current graph
  740. currentTimeIndex: 0,
  741. timeUnitSeconds: function() {
  742. return this.get('timeStates').objectAt(this.get('currentTimeIndex')).seconds;
  743. }.property('currentTimeIndex')
  744. });
  745. /**
  746. * A formatter which will turn a number into computer storage sizes of the
  747. * format '23 GB' etc.
  748. *
  749. * @type {Function}
  750. */
  751. App.ChartLinearTimeView.BytesFormatter = function (y) {
  752. if (y == 0) return '0 B';
  753. var value = Rickshaw.Fixtures.Number.formatBase1024KMGTP(y);
  754. if (!y || y.length < 1) {
  755. value = '0 B';
  756. }
  757. else {
  758. if ("number" == typeof value) {
  759. value = String(value);
  760. }
  761. if ("string" == typeof value) {
  762. value = value.replace(/\.\d(\d+)/, function($0, $1){ // Remove only 1-digit after decimal part
  763. return $0.replace($1, '');
  764. });
  765. // Either it ends with digit or ends with character
  766. value = value.replace(/(\d$)/, '$1 '); // Ends with digit like '120'
  767. value = value.replace(/([a-zA-Z]$)/, ' $1'); // Ends with character like
  768. // '120M'
  769. value = value + 'B'; // Append B to make B, MB, GB etc.
  770. }
  771. }
  772. return value;
  773. };
  774. /**
  775. * A formatter which will turn a number into percentage display like '42%'
  776. *
  777. * @type {Function}
  778. */
  779. App.ChartLinearTimeView.PercentageFormatter = function (percentage) {
  780. var value = percentage;
  781. if (!value || value.length < 1) {
  782. value = '0 %';
  783. } else {
  784. value = value.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + '%';
  785. }
  786. return value;
  787. };
  788. /**
  789. * A formatter which will turn elapsed time into display time like '50 ms',
  790. * '5s', '10 m', '3 hr' etc. Time is expected to be provided in milliseconds.
  791. *
  792. * @type {Function}
  793. */
  794. App.ChartLinearTimeView.TimeElapsedFormatter = function (millis) {
  795. var value = millis;
  796. if (!value || value.length < 1) {
  797. value = '0 ms';
  798. } else if ("number" == typeof millis) {
  799. var seconds = millis > 1000 ? Math.round(millis / 1000) : 0;
  800. var minutes = seconds > 60 ? Math.round(seconds / 60) : 0;
  801. var hours = minutes > 60 ? Math.round(minutes / 60) : 0;
  802. var days = hours > 24 ? Math.round(hours / 24) : 0;
  803. if (days > 0) {
  804. value = days + ' d';
  805. } else if (hours > 0) {
  806. value = hours + ' hr';
  807. } else if (minutes > 0) {
  808. value = minutes + ' m';
  809. } else if (seconds > 0) {
  810. value = seconds + ' s';
  811. } else if (millis > 0) {
  812. value = millis.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + ' ms';
  813. } else {
  814. value = millis.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + ' ms';
  815. }
  816. }
  817. return value;
  818. };
  819. /**
  820. * The default formatter which uses Rickshaw.Fixtures.Number.formatKMBT
  821. * which shows 10K, 300M etc.
  822. *
  823. * @type {Function}
  824. */
  825. App.ChartLinearTimeView.DefaultFormatter = function(y) {
  826. if(isNaN(y)){
  827. return 0;
  828. }
  829. var value = Rickshaw.Fixtures.Number.formatKMBT(y);
  830. if (value == '') return '0';
  831. value = String(value);
  832. var c = value[value.length - 1];
  833. if (!isNaN(parseInt(c))) {
  834. // c is digit
  835. value = parseFloat(value).toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
  836. }
  837. else {
  838. // c in not digit
  839. value = parseFloat(value.substr(0, value.length - 1)).toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + c;
  840. }
  841. return value;
  842. };
  843. /**
  844. * Creates and returns a formatter that can convert a 'value'
  845. * to 'value units/s'.
  846. *
  847. * @param unitsPrefix Prefix which will be used in 'unitsPrefix/s'
  848. * @param valueFormatter Value itself will need further processing
  849. * via provided formatter. Ex: '10M requests/s'. Generally
  850. * should be App.ChartLinearTimeView.DefaultFormatter.
  851. * @return {Function}
  852. */
  853. App.ChartLinearTimeView.CreateRateFormatter = function (unitsPrefix, valueFormatter) {
  854. var suffix = " "+unitsPrefix+"/s";
  855. return function (value) {
  856. value = valueFormatter(value) + suffix;
  857. return value;
  858. };
  859. };
  860. Rickshaw.GraphReopened = function(a){
  861. Rickshaw.Graph.call(this, a);
  862. };
  863. //reopened in order to exclude "null" value from validation
  864. Rickshaw.GraphReopened.prototype = Object.create(Rickshaw.Graph.prototype, {
  865. validateSeries : {
  866. value: function(series) {
  867. if (!(series instanceof Array) && !(series instanceof Rickshaw.Series)) {
  868. var seriesSignature = Object.prototype.toString.apply(series);
  869. throw "series is not an array: " + seriesSignature;
  870. }
  871. var pointsCount;
  872. series.forEach( function(s) {
  873. if (!(s instanceof Object)) {
  874. throw "series element is not an object: " + s;
  875. }
  876. if (!(s.data)) {
  877. throw "series has no data: " + JSON.stringify(s);
  878. }
  879. if (!(s.data instanceof Array)) {
  880. throw "series data is not an array: " + JSON.stringify(s.data);
  881. }
  882. pointsCount = pointsCount || s.data.length;
  883. if (pointsCount && s.data.length != pointsCount) {
  884. throw "series cannot have differing numbers of points: " +
  885. pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.zeroFill()";
  886. }
  887. })
  888. },
  889. enumerable: true,
  890. configurable: false,
  891. writable: false
  892. }
  893. });
  894. //show no line if point is "null"
  895. Rickshaw.Graph.Renderer.Line.prototype.seriesPathFactory = function() {
  896. var graph = this.graph;
  897. return d3.svg.line()
  898. .x( function(d) { return graph.x(d.x) } )
  899. .y( function(d) { return graph.y(d.y) } )
  900. .defined(function(d) { return d.y!=null; })
  901. .interpolate(this.graph.interpolation).tension(this.tension);
  902. };
  903. //show no area if point is null
  904. Rickshaw.Graph.Renderer.Stack.prototype.seriesPathFactory = function() {
  905. var graph = this.graph;
  906. return d3.svg.area()
  907. .x( function(d) { return graph.x(d.x) } )
  908. .y0( function(d) { return graph.y(d.y0) } )
  909. .y1( function(d) { return graph.y(d.y + d.y0) } )
  910. .defined(function(d) { return d.y!=null; })
  911. .interpolate(this.graph.interpolation).tension(this.tension);
  912. };