analytics.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /*
  2. * This code is executed from document ready function
  3. * Also note that some variable are set from C++ code
  4. */
  5. var x1 = $.url("?x1");
  6. var x2 = $.url("?x2");
  7. var y1 = $.url("?y1");
  8. var y2 = $.url("?y2");
  9. var gantt = $.url("?gantt");
  10. var out = $.url("?out");
  11. var linesfill = $.url("?linesfill") == "y";
  12. var linessteps = $.url("?linessteps") == "y";
  13. var linesshow = $.url("?linesshow") != "n";
  14. var pointsshow = $.url("?pointsshow") != "n";
  15. var autoscale = $.url("?autoscale") == "y";
  16. var legendshow = $.url("?legendshow") != "n";
  17. var cutts = $.url("?cutts") == "y";
  18. var fullscreen = $.url("?fullscreen") == "y";
  19. var realnames = $.url("?realnames") == "y";
  20. var xzoomoff = $.url("?xzoomoff") == "y";
  21. var yzoomoff = $.url("?yzoomoff") == "y";
  22. function inIframe() { try { return window.self !== window.top; } catch (e) { return true; } }
  23. function inDashboard() { return fullscreen && inIframe(); }
  24. // URL management
  25. function makeUrl(url, queryFunc, hashFunc) {
  26. var query = $.url("?", url);
  27. var hash = $.url("#", url);
  28. if (paused) {
  29. query.paused = "y";
  30. }
  31. if (queryFunc) { queryFunc(query); }
  32. if (hashFunc) { hashFunc(hash); }
  33. var queryStr = "";
  34. var first = true;
  35. for (var k in query) {
  36. queryStr += (first? "?": "&") + k + "=" + encodeURIComponent(query[k]);
  37. first = false;
  38. }
  39. var hashStr = "";
  40. first = true;
  41. for (k in hash) {
  42. hashStr += (first? "#": "&") + k + "=" + encodeURIComponent(hash[k]);
  43. first = false;
  44. }
  45. return queryStr + hashStr;
  46. }
  47. function dataUrl() {
  48. return makeUrl(dataurl, function(query) {
  49. query.error = "text";
  50. });
  51. }
  52. // Error message popup
  53. $("<div id='errmsg'></div>").css({
  54. position: "absolute",
  55. display: "none",
  56. border: "1px solid #faa",
  57. padding: "2px",
  58. "background-color": "#fcc",
  59. opacity: 0.80
  60. }).appendTo("body");
  61. if (out == "gantt") {
  62. $("#gantt-apply").click(function() {
  63. try {
  64. let val = $("#textareaGantt").val().replace(/\s/g, "");
  65. JSON.parse(val);
  66. window.location.replace(makeUrl($.url(), function(query) {
  67. query.gantt = val;
  68. }));
  69. } catch(e) {
  70. $("#errmsg").text("Not valid JSON: " + e)
  71. .css({bottom: "5px", left: "25%", width: "50%"})
  72. .fadeIn(200);
  73. }
  74. });
  75. if (gantt) {
  76. // Data fetching and auto-refreshing
  77. var fetchCounter = 0;
  78. function fetchData() {
  79. function onDataReceived(json, textStatus, xhr) {
  80. $("#errmsg").hide();
  81. logs = json;
  82. chart(config, logs, true);
  83. }
  84. function onDataError(xhr, error) {
  85. console.log(arguments);
  86. $("#errmsg").text("Fetch data error: " + error + (xhr.status == 200? xhr.responseText: ""))
  87. .css({bottom: "5px", left: "25%", width: "50%"})
  88. .fadeIn(200);
  89. }
  90. if (dataurl) {
  91. $.ajax({
  92. url: dataUrl(),
  93. type: "GET",
  94. dataType: "json",
  95. success: function (json, textStatus, xhr) { onDataReceived(json, textStatus, xhr); },
  96. error: onDataError
  97. });
  98. } else {
  99. onDataReceived(datajson, "textStatus", "xhr");
  100. }
  101. // if (fetchPeriod > 0) {
  102. // if (fetchCounter == 0) {
  103. // fetchCounter++;
  104. // setTimeout(function() {
  105. // fetchCounter--;
  106. // if (!paused) {
  107. // fetchData();
  108. // }
  109. // }, $("#errmsg").is(":visible")? errorPeriod: fetchPeriod);
  110. // }
  111. // }
  112. }
  113. try {
  114. var config = JSON.parse(gantt);
  115. $("#textareaGantt").val(JSON.stringify(config, "\n", " "));
  116. let formHeight = 220;
  117. var chart = d3.gantt()
  118. .height(window.innerHeight - $("#placeholder")[0].getBoundingClientRect().y - window.scrollY - formHeight)
  119. .selector("#placeholder");
  120. var logs = null;
  121. fetchData();
  122. } catch(e) {
  123. $("#textareaGantt").val(gantt);
  124. alert("Not valid JSON: " + e);
  125. }
  126. }
  127. } else { // flot
  128. // Special options adjustment for fullscreen charts in iframe (solomon dashboards)
  129. if (fullscreen) {
  130. navigate = false; // Disable navigation to avoid scrolling problems
  131. legendshow = false; // Show legend only on hover
  132. }
  133. // Adjust zooming options
  134. var xZoomRange = null;
  135. var yZoomRange = null;
  136. if (xzoomoff) {
  137. xZoomRange = false;
  138. }
  139. if (yzoomoff) {
  140. yZoomRange = false;
  141. }
  142. var placeholder = $("#placeholder");
  143. var playback = $("#playback");
  144. var data = null;
  145. var loaded_data = [];
  146. var imported_data = [];
  147. var fetchPeriod = refreshPeriod;
  148. var errorPeriod = 5000;
  149. var playbackPeriod = 500;
  150. var abstimestep = 1.0;
  151. var paused = $.url("?paused") == "y";
  152. var timestep = abstimestep;
  153. var seriesDescription = {
  154. _time: "time",
  155. _thread: "thread"
  156. }
  157. function seriesDesc(name) {
  158. if (seriesDescription.hasOwnProperty(name)) {
  159. return (realnames? "[" + name + "] ": "") + seriesDescription[name];
  160. } else {
  161. return name;
  162. }
  163. }
  164. playback.show();
  165. var options = {
  166. series: {
  167. lines: { show: linesshow, fill: linesfill, steps: linessteps},
  168. points: { show: pointsshow },
  169. shadowSize: 0
  170. },
  171. xaxis: { zoomRange: xZoomRange },
  172. yaxis: { zoomRange: yZoomRange },
  173. grid: { clickable: true, hoverable: true },
  174. zoom: { interactive: navigate },
  175. pan: { interactive: navigate },
  176. legend: {
  177. show: legendshow,
  178. labelFormatter: function(label, series) { return seriesDesc(label) + '(' + seriesDesc(xn) + ')'; }
  179. }
  180. };
  181. if (fullscreen) {
  182. $("body").attr("class","body-fullscreen");
  183. $("#container").attr("class","container-fullscreen");
  184. $("#toolbar").attr("class","toolbar-fullscreen");
  185. $("#selectors-container").attr("class","toolbar-fullscreen");
  186. options.grid.margin = 0;
  187. }
  188. if (x1) { options.xaxis.min = x1; }
  189. if (x2) { options.xaxis.max = x2; }
  190. if (y1) { options.yaxis.min = y1; }
  191. if (y2) { options.yaxis.max = y2; }
  192. $("<div id='tooltip'></div>").css({
  193. position: "absolute",
  194. display: "none",
  195. border: "1px solid #fdd",
  196. padding: "2px",
  197. "background-color": "#fee",
  198. opacity: 0.80
  199. }).appendTo("body");
  200. // Helper to hide tooltip
  201. var lastShow = new Date();
  202. var hideCounter = 0;
  203. function hideTooltip() {
  204. if (hideCounter == 0) {
  205. hideCounter++;
  206. setTimeout(function() {
  207. hideCounter--;
  208. if (new Date().getTime() - lastShow.getTime() > 1000) {
  209. $("#tooltip").fadeOut(200);
  210. } else if ($("#tooltip").is(":visible")) {
  211. hideTooltip();
  212. }
  213. }, 200);
  214. }
  215. }
  216. // Helper to hide legend
  217. var legendLastShow = new Date();
  218. var legendHideCounter = 0;
  219. function hideLegend() {
  220. if (legendHideCounter == 0) {
  221. legendHideCounter++;
  222. setTimeout(function() {
  223. legendHideCounter--;
  224. if (new Date().getTime() - legendLastShow.getTime() > 1000) {
  225. options.legend.show = false;
  226. $.plot(placeholder, data, options);
  227. } else {
  228. hideLegend();
  229. }
  230. }, 200);
  231. }
  232. }
  233. function onPlotClick(event, pos, item) {
  234. // Leave fullscreen on click
  235. var nonFullscreenUrl = makeUrl($.url(), function(query) {
  236. delete query.fullscreen;
  237. });
  238. if (inDashboard()) {
  239. window.open(nonFullscreenUrl, "_blank");
  240. } else if (fullscreen) {
  241. window.location.href = nonFullscreenUrl;
  242. }
  243. }
  244. function onPlotHover(event, pos, item) {
  245. var redraw = false;
  246. // Show legend on hover
  247. if (fullscreen) {
  248. legendLastShow = new Date();
  249. if (!options.legend.show) {
  250. redraw = true;
  251. }
  252. options.legend.show = true;
  253. hideLegend();
  254. }
  255. // Show names on hover
  256. var left = placeholder.position().left;
  257. var top = placeholder.position().top;
  258. var ttmargin = 10;
  259. var ttwidth = $("#tooltip").width();
  260. var ttheight = $("#tooltip").height();
  261. if (linessteps) {
  262. var pts = data[0].data;
  263. var idx = 0;
  264. for (var i = 0; i < pts.length - 1; i++) {
  265. var x1 = pts[i][0];
  266. var x2 = pts[i+1][0];
  267. if (pos.x >= x1 && (x2 == null || pos.x < x2)) {
  268. idx = i;
  269. }
  270. }
  271. var n = pts[idx][2];
  272. var html = (n? "<u><b><center>" + n + "</center></b></u>": "") + "<table><tr><td align=\"right\">" + seriesDesc(xn) + "</td><td>: </td><td>" + pts[idx][3] + "</td></tr>";
  273. for (var sid = 0; sid < data.length; sid++) {
  274. var series = data[sid];
  275. var y = series.data[idx][4];
  276. html += "<tr><td align=\"right\">" + seriesDesc(series.label) + "</td><td>: </td><td>" + y + "</td></tr>";
  277. }
  278. html += "</table>";
  279. lastShow = new Date();
  280. if (pos.pageX < left + placeholder.width() && pos.pageX > left &&
  281. pos.pageY < top + placeholder.height() && pos.pageY > top) {
  282. $("#tooltip").html(html)
  283. .css({top: Math.max(top + ttmargin + 10, pos.pageY - ttheight - 20),
  284. left: Math.max(left + ttmargin + 10, pos.pageX - ttwidth - 20)})
  285. .fadeIn(200, "swing", hideTooltip());
  286. }
  287. } else {
  288. if (item) {
  289. var idx = item.dataIndex;
  290. var n = item.series.data[idx][2];
  291. var x = item.series.data[idx][3]; //item.datapoint[0];
  292. var y = item.series.data[idx][4]; //item.datapoint[1];
  293. $("#tooltip").html((n? n + ": ": "") + seriesDesc(item.series.label) + " = " + y + ", " + seriesDesc(xn) + " = " + x)
  294. .css({top: Math.max(top + ttmargin + 10, item.pageY - ttheight - 20),
  295. left: Math.max(left + ttmargin + 10, item.pageX - ttwidth - 20)})
  296. .fadeIn(200);
  297. } else {
  298. $("#tooltip").hide();
  299. }
  300. }
  301. // Redraw if required
  302. if (redraw) {
  303. $.plot(placeholder, data, options);
  304. }
  305. }
  306. // Add some more interactivity
  307. function onZoomOrPan(event, plot) {
  308. var axes = plot.getAxes();
  309. options.xaxis.min = axes.xaxis.min;
  310. options.xaxis.max = axes.xaxis.max;
  311. options.yaxis.min = axes.yaxis.min;
  312. options.yaxis.max = axes.yaxis.max;
  313. }
  314. // Bind to events
  315. placeholder.bind("plotclick", onPlotClick);
  316. placeholder.bind("plothover", onPlotHover);
  317. placeholder.bind("plotpan", onZoomOrPan);
  318. placeholder.bind("plotzoom", onZoomOrPan);
  319. // Data fetching and auto-refreshing
  320. var fetchCounter = 0;
  321. function fetchData() {
  322. function onDataReceived(json, textStatus, xhr) {
  323. $("#errmsg").hide();
  324. if (paused && logs) {
  325. return;
  326. }
  327. // Plot fetched data
  328. loaded_data = json;
  329. data = loaded_data.concat(imported_data);
  330. if (autoscale) {
  331. var xmin = null;
  332. var xmax = null;
  333. var ymin = null;
  334. var ymax = null;
  335. for (var sid = 0; sid < data.length; sid++) {
  336. var pts = data[sid].data;
  337. for (var i = 0; i < pts.length; i++) {
  338. var x = pts[i][0];
  339. var y = pts[i][1];
  340. if (x != null && y != null) {
  341. if (xmin == null || x < xmin) { xmin = x; }
  342. if (xmax == null || x > xmax) { xmax = x; }
  343. if (ymin == null || y < ymin) { ymin = y; }
  344. if (ymax == null || y > ymax) { ymax = y; }
  345. }
  346. }
  347. }
  348. if (xmin != null) { options.xaxis.min = xmin; }
  349. if (xmax != null) { options.xaxis.max = xmax; }
  350. if (ymin != null) { options.yaxis.min = ymin; }
  351. if (ymax != null) { options.yaxis.max = ymax; }
  352. }
  353. $.plot(placeholder, data, options);
  354. }
  355. function onDataError(xhr, error) {
  356. console.log(arguments);
  357. $("#errmsg").text("Fetch data error: " + error + (xhr.status == 200? xhr.responseText: ""))
  358. .css({bottom: "5px", left: "25%", width: "50%"})
  359. .fadeIn(200);
  360. }
  361. if (dataurl) {
  362. $.ajax({
  363. url: dataUrl(),
  364. type: "GET",
  365. dataType: "json",
  366. success: function (json, textStatus, xhr) { onDataReceived(json, textStatus, xhr); },
  367. error: onDataError
  368. });
  369. } else {
  370. onDataReceived(datajson, "textStatus", "xhr");
  371. }
  372. if (fetchPeriod > 0) {
  373. if (fetchCounter == 0) {
  374. fetchCounter++;
  375. setTimeout(function() {
  376. fetchCounter--;
  377. if (!paused) {
  378. fetchData();
  379. }
  380. }, $("#errmsg").is(":visible")? errorPeriod: fetchPeriod);
  381. }
  382. }
  383. }
  384. // Playback control
  385. var pb_pause = $("#pb-pause");
  386. function setActive(btn, active) {
  387. if (active) {
  388. btn.addClass("btn-primary");
  389. btn.removeClass("btn-default");
  390. } else {
  391. btn.addClass("btn-default");
  392. btn.removeClass("btn-primary");
  393. }
  394. }
  395. function onPlaybackControl() {
  396. setActive(pb_pause, paused);
  397. fetchPeriod = refreshPeriod;
  398. fetchData();
  399. }
  400. function clickPause(val) {
  401. paused = val;
  402. onPlaybackControl();
  403. }
  404. pb_pause.click(function() { clickPause(!paused); });
  405. function addOptionButton(opt, btn, defOn) {
  406. setActive(btn, defOn ? $.url("?" + opt) != "n" : $.url("?" + opt) == "y");
  407. btn.click(function() {
  408. window.location.replace(makeUrl($.url(), function(query) {
  409. if (defOn) {
  410. if ($.url("?" + opt) != "n") {
  411. query[opt] = "n";
  412. } else {
  413. delete query[opt];
  414. }
  415. } else {
  416. if ($.url("?" + opt) == "y") {
  417. delete query[opt];
  418. } else {
  419. query[opt] = "y";
  420. }
  421. }
  422. }));
  423. });
  424. }
  425. // Options
  426. addOptionButton("linesfill", $("#pb-linesfill"), false);
  427. addOptionButton("linessteps", $("#pb-linessteps"), false);
  428. addOptionButton("linesshow", $("#pb-linesshow"), true);
  429. addOptionButton("pointsshow", $("#pb-pointsshow"), true);
  430. addOptionButton("legendshow", $("#pb-legendshow"), true);
  431. addOptionButton("cutts", $("#pb-cutts"), false);
  432. addOptionButton("autoscale", $("#pb-autoscale"), false);
  433. var pb_fullscreen = $("#pb-fullscreen");
  434. setActive(pb_fullscreen, fullscreen);
  435. pb_fullscreen.click(function(){
  436. window.location.href = makeUrl($.url(), function(query) {
  437. if (fullscreen) {
  438. delete query.fullscreen;
  439. } else {
  440. query.fullscreen = "y";
  441. }
  442. });
  443. });
  444. // Embeded mode (in case there is other stuff on page)
  445. function embededMode() {
  446. $("#pb-fullscreen").hide();
  447. $("#pb-autoscale").hide();
  448. $("#pb-legendshow").hide();
  449. $("#pb-pause").hide();
  450. }
  451. function enableSelection() {
  452. // Redraw chart with selection enabled
  453. options.selection = {mode: "x"};
  454. var plot = $.plot(placeholder, data, options);
  455. if ($.url("?sel_x1") && $.url("?sel_x2")) {
  456. plot.setSelection({
  457. xaxis: {
  458. from: $.url("?sel_x1") ,
  459. to: $.url("?sel_x2")
  460. }
  461. });
  462. }
  463. // Create selection hooks
  464. placeholder.bind("plotselected", function (event, ranges) {
  465. window.location.replace(makeUrl($.url(), function(query) {
  466. query.sel_x1 = ranges.xaxis.from;
  467. query.sel_x2 = ranges.xaxis.to;
  468. }));
  469. });
  470. placeholder.bind("plotunselected", function (event) {
  471. window.location.replace(makeUrl($.url(), function(query) {
  472. delete query.sel_x1;
  473. delete query.sel_x2;
  474. }));
  475. });
  476. // Init and show unselect button
  477. $("#pb-unselect").click(function () {
  478. plot.clearSelection();
  479. });
  480. $("#pb-unselect").removeClass("hidden");
  481. }
  482. // Export data
  483. var pb_export = $("#pb-export");
  484. pb_export.click(function(){
  485. var blob = new Blob([JSON.stringify(data)], {type: "application/json;charset=utf-8"});
  486. saveAs(blob, "exported.json");
  487. });
  488. // Import data
  489. $("#btnDoImport").click(function(){
  490. var files = document.getElementById('importFiles').files;
  491. if (files.length <= 0) {
  492. return false;
  493. }
  494. var fr = new FileReader();
  495. fr.onload = function(e) {
  496. imported_data = imported_data.concat(JSON.parse(e.target.result));
  497. data = loaded_data.concat(imported_data);
  498. $.plot(placeholder, data, options);
  499. $('#importModal').modal('hide');
  500. }
  501. fr.readAsText(files.item(0));
  502. });
  503. // Initialize stuff and fetch data
  504. onPlaybackControl();
  505. }