chart.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. "use strict";
  2. /* global ColumnType, DataTable, JQPlotChartFactory */
  3. // js/chart.js
  4. /* global codeMirrorEditor */
  5. // js/functions.js
  6. var chartData = {};
  7. var tempChartTitle;
  8. var currentChart = null;
  9. var currentSettings = null;
  10. var dateTimeCols = [];
  11. var numericCols = [];
  12. function extractDate(dateString) {
  13. var matches;
  14. var match;
  15. var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/;
  16. var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/;
  17. matches = dateTimeRegExp.exec(dateString);
  18. if (matches !== null && matches.length > 0) {
  19. match = matches[0];
  20. return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2));
  21. } else {
  22. matches = dateRegExp.exec(dateString);
  23. if (matches !== null && matches.length > 0) {
  24. match = matches[0];
  25. return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2));
  26. }
  27. }
  28. return null;
  29. }
  30. function queryChart(data, columnNames, settings) {
  31. if ($('#querychart').length === 0) {
  32. return;
  33. }
  34. var plotSettings = {
  35. title: {
  36. text: settings.title,
  37. escapeHtml: true
  38. },
  39. grid: {
  40. drawBorder: false,
  41. shadow: false,
  42. background: 'rgba(0,0,0,0)'
  43. },
  44. legend: {
  45. show: true,
  46. placement: 'outsideGrid',
  47. location: 'e',
  48. rendererOptions: {
  49. numberColumns: 2
  50. }
  51. },
  52. axes: {
  53. xaxis: {
  54. label: Functions.escapeHtml(settings.xaxisLabel)
  55. },
  56. yaxis: {
  57. label: settings.yaxisLabel
  58. }
  59. },
  60. stackSeries: settings.stackSeries
  61. }; // create the chart
  62. var factory = new JQPlotChartFactory();
  63. var chart = factory.createChart(settings.type, 'querychart'); // create the data table and add columns
  64. var dataTable = new DataTable();
  65. if (settings.type === 'timeline') {
  66. dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]);
  67. } else if (settings.type === 'scatter') {
  68. dataTable.addColumn(ColumnType.NUMBER, columnNames[settings.mainAxis]);
  69. } else {
  70. dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]);
  71. }
  72. var i;
  73. var values = [];
  74. if (settings.seriesColumn === null) {
  75. $.each(settings.selectedSeries, function (index, element) {
  76. dataTable.addColumn(ColumnType.NUMBER, columnNames[element]);
  77. }); // set data to the data table
  78. var columnsToExtract = [settings.mainAxis];
  79. $.each(settings.selectedSeries, function (index, element) {
  80. columnsToExtract.push(element);
  81. });
  82. var newRow;
  83. var row;
  84. var col;
  85. for (i = 0; i < data.length; i++) {
  86. row = data[i];
  87. newRow = [];
  88. for (var j = 0; j < columnsToExtract.length; j++) {
  89. col = columnNames[columnsToExtract[j]];
  90. if (j === 0) {
  91. if (settings.type === 'timeline') {
  92. // first column is date type
  93. newRow.push(extractDate(row[col]));
  94. } else if (settings.type === 'scatter') {
  95. newRow.push(parseFloat(row[col]));
  96. } else {
  97. // first column is string type
  98. newRow.push(row[col]);
  99. }
  100. } else {
  101. // subsequent columns are of type, number
  102. newRow.push(parseFloat(row[col]));
  103. }
  104. }
  105. values.push(newRow);
  106. }
  107. dataTable.setData(values);
  108. } else {
  109. var seriesNames = {};
  110. var seriesNumber = 1;
  111. var seriesColumnName = columnNames[settings.seriesColumn];
  112. for (i = 0; i < data.length; i++) {
  113. if (!seriesNames[data[i][seriesColumnName]]) {
  114. seriesNames[data[i][seriesColumnName]] = seriesNumber;
  115. seriesNumber++;
  116. }
  117. }
  118. $.each(seriesNames, function (seriesName) {
  119. dataTable.addColumn(ColumnType.NUMBER, seriesName);
  120. });
  121. var valueMap = {};
  122. var xValue;
  123. var value;
  124. var mainAxisName = columnNames[settings.mainAxis];
  125. var valueColumnName = columnNames[settings.valueColumn];
  126. for (i = 0; i < data.length; i++) {
  127. xValue = data[i][mainAxisName];
  128. value = valueMap[xValue];
  129. if (!value) {
  130. value = [xValue];
  131. valueMap[xValue] = value;
  132. }
  133. seriesNumber = seriesNames[data[i][seriesColumnName]];
  134. value[seriesNumber] = parseFloat(data[i][valueColumnName]);
  135. }
  136. $.each(valueMap, function (index, value) {
  137. values.push(value);
  138. });
  139. dataTable.setData(values);
  140. } // draw the chart and return the chart object
  141. chart.draw(dataTable, plotSettings);
  142. return chart;
  143. }
  144. function drawChart() {
  145. currentSettings.width = $('#resizer').width() - 20;
  146. currentSettings.height = $('#resizer').height() - 20; // TODO: a better way using .redraw() ?
  147. if (currentChart !== null) {
  148. currentChart.destroy();
  149. }
  150. var columnNames = [];
  151. $('select[name="chartXAxis"] option').each(function () {
  152. columnNames.push(Functions.escapeHtml($(this).text()));
  153. });
  154. try {
  155. currentChart = queryChart(chartData, columnNames, currentSettings);
  156. if (currentChart !== null) {
  157. $('#saveChart').attr('href', currentChart.toImageString());
  158. }
  159. } catch (err) {
  160. Functions.ajaxShowMessage(err.message, false);
  161. }
  162. }
  163. function getSelectedSeries() {
  164. var val = $('select[name="chartSeries"]').val() || [];
  165. var ret = [];
  166. $.each(val, function (i, v) {
  167. ret.push(parseInt(v, 10));
  168. });
  169. return ret;
  170. }
  171. function onXAxisChange() {
  172. var $xAxisSelect = $('select[name="chartXAxis"]');
  173. currentSettings.mainAxis = parseInt($xAxisSelect.val(), 10);
  174. if (dateTimeCols.indexOf(currentSettings.mainAxis) !== -1) {
  175. $('span.span_timeline').show();
  176. } else {
  177. $('span.span_timeline').hide();
  178. if (currentSettings.type === 'timeline') {
  179. $('input#radio_line').prop('checked', true);
  180. currentSettings.type = 'line';
  181. }
  182. }
  183. if (numericCols.indexOf(currentSettings.mainAxis) !== -1) {
  184. $('span.span_scatter').show();
  185. } else {
  186. $('span.span_scatter').hide();
  187. if (currentSettings.type === 'scatter') {
  188. $('input#radio_line').prop('checked', true);
  189. currentSettings.type = 'line';
  190. }
  191. }
  192. var xAxisTitle = $xAxisSelect.children('option:selected').text();
  193. $('input[name="xaxis_label"]').val(xAxisTitle);
  194. currentSettings.xaxisLabel = xAxisTitle;
  195. }
  196. function onDataSeriesChange() {
  197. var $seriesSelect = $('select[name="chartSeries"]');
  198. currentSettings.selectedSeries = getSelectedSeries();
  199. var yAxisTitle;
  200. if (currentSettings.selectedSeries.length === 1) {
  201. $('span.span_pie').show();
  202. yAxisTitle = $seriesSelect.children('option:selected').text();
  203. } else {
  204. $('span.span_pie').hide();
  205. if (currentSettings.type === 'pie') {
  206. $('input#radio_line').prop('checked', true);
  207. currentSettings.type = 'line';
  208. }
  209. yAxisTitle = Messages.strYValues;
  210. }
  211. $('input[name="yaxis_label"]').val(yAxisTitle);
  212. currentSettings.yaxisLabel = yAxisTitle;
  213. }
  214. /**
  215. * Unbind all event handlers before tearing down a page
  216. */
  217. AJAX.registerTeardown('table/chart.js', function () {
  218. $('input[name="chartType"]').off('click');
  219. $('input[name="barStacked"]').off('click');
  220. $('input[name="chkAlternative"]').off('click');
  221. $('input[name="chartTitle"]').off('focus').off('keyup').off('blur');
  222. $('select[name="chartXAxis"]').off('change');
  223. $('select[name="chartSeries"]').off('change');
  224. $('select[name="chartSeriesColumn"]').off('change');
  225. $('select[name="chartValueColumn"]').off('change');
  226. $('input[name="xaxis_label"]').off('keyup');
  227. $('input[name="yaxis_label"]').off('keyup');
  228. $('#resizer').off('resizestop');
  229. $('#tblchartform').off('submit');
  230. });
  231. AJAX.registerOnload('table/chart.js', function () {
  232. // handle manual resize
  233. $('#resizer').on('resizestop', function () {
  234. // make room so that the handle will still appear
  235. $('#querychart').height($('#resizer').height() * 0.96);
  236. $('#querychart').width($('#resizer').width() * 0.96);
  237. if (currentChart !== null) {
  238. currentChart.redraw({
  239. resetAxes: true
  240. });
  241. }
  242. }); // handle chart type changes
  243. $('input[name="chartType"]').on('click', function () {
  244. var type = currentSettings.type = $(this).val();
  245. if (type === 'bar' || type === 'column' || type === 'area') {
  246. $('span.barStacked').show();
  247. } else {
  248. $('input[name="barStacked"]').prop('checked', false);
  249. $.extend(true, currentSettings, {
  250. stackSeries: false
  251. });
  252. $('span.barStacked').hide();
  253. }
  254. drawChart();
  255. }); // handle chosing alternative data format
  256. $('input[name="chkAlternative"]').on('click', function () {
  257. var $seriesColumn = $('select[name="chartSeriesColumn"]');
  258. var $valueColumn = $('select[name="chartValueColumn"]');
  259. var $chartSeries = $('select[name="chartSeries"]');
  260. if ($(this).is(':checked')) {
  261. $seriesColumn.prop('disabled', false);
  262. $valueColumn.prop('disabled', false);
  263. $chartSeries.prop('disabled', true);
  264. currentSettings.seriesColumn = parseInt($seriesColumn.val(), 10);
  265. currentSettings.valueColumn = parseInt($valueColumn.val(), 10);
  266. } else {
  267. $seriesColumn.prop('disabled', true);
  268. $valueColumn.prop('disabled', true);
  269. $chartSeries.prop('disabled', false);
  270. currentSettings.seriesColumn = null;
  271. currentSettings.valueColumn = null;
  272. }
  273. drawChart();
  274. }); // handle stacking for bar, column and area charts
  275. $('input[name="barStacked"]').on('click', function () {
  276. if ($(this).is(':checked')) {
  277. $.extend(true, currentSettings, {
  278. stackSeries: true
  279. });
  280. } else {
  281. $.extend(true, currentSettings, {
  282. stackSeries: false
  283. });
  284. }
  285. drawChart();
  286. }); // handle changes in chart title
  287. $('input[name="chartTitle"]').on('focus', function () {
  288. tempChartTitle = $(this).val();
  289. }).on('keyup', function () {
  290. currentSettings.title = $('input[name="chartTitle"]').val();
  291. drawChart();
  292. }).on('blur', function () {
  293. if ($(this).val() !== tempChartTitle) {
  294. drawChart();
  295. }
  296. }); // handle changing the x-axis
  297. $('select[name="chartXAxis"]').on('change', function () {
  298. onXAxisChange();
  299. drawChart();
  300. }); // handle changing the selected data series
  301. $('select[name="chartSeries"]').on('change', function () {
  302. onDataSeriesChange();
  303. drawChart();
  304. }); // handle changing the series column
  305. $('select[name="chartSeriesColumn"]').on('change', function () {
  306. currentSettings.seriesColumn = parseInt($(this).val(), 10);
  307. drawChart();
  308. }); // handle changing the value column
  309. $('select[name="chartValueColumn"]').on('change', function () {
  310. currentSettings.valueColumn = parseInt($(this).val(), 10);
  311. drawChart();
  312. }); // handle manual changes to the chart x-axis labels
  313. $('input[name="xaxis_label"]').on('keyup', function () {
  314. currentSettings.xaxisLabel = $(this).val();
  315. drawChart();
  316. }); // handle manual changes to the chart y-axis labels
  317. $('input[name="yaxis_label"]').on('keyup', function () {
  318. currentSettings.yaxisLabel = $(this).val();
  319. drawChart();
  320. }); // handler for ajax form submission
  321. $('#tblchartform').on('submit', function () {
  322. var $form = $(this);
  323. if (codeMirrorEditor) {
  324. $form[0].elements.sql_query.value = codeMirrorEditor.getValue();
  325. }
  326. if (!Functions.checkSqlQuery($form[0])) {
  327. return false;
  328. }
  329. var $msgbox = Functions.ajaxShowMessage();
  330. Functions.prepareForAjaxRequest($form);
  331. $.post($form.attr('action'), $form.serialize(), function (data) {
  332. if (typeof data !== 'undefined' && data.success === true && typeof data.chartData !== 'undefined') {
  333. chartData = JSON.parse(data.chartData);
  334. drawChart();
  335. Functions.ajaxRemoveMessage($msgbox);
  336. } else {
  337. Functions.ajaxShowMessage(data.error, false);
  338. }
  339. }, 'json'); // end $.post()
  340. return false;
  341. }); // from jQuery UI
  342. $('#resizer').resizable({
  343. minHeight: 240,
  344. minWidth: 300
  345. }).width($('#div_view_options').width() - 50).trigger('resizestop');
  346. currentSettings = {
  347. type: 'line',
  348. width: $('#resizer').width() - 20,
  349. height: $('#resizer').height() - 20,
  350. xaxisLabel: $('input[name="xaxis_label"]').val(),
  351. yaxisLabel: $('input[name="yaxis_label"]').val(),
  352. title: $('input[name="chartTitle"]').val(),
  353. stackSeries: false,
  354. mainAxis: parseInt($('select[name="chartXAxis"]').val(), 10),
  355. selectedSeries: getSelectedSeries(),
  356. seriesColumn: null
  357. };
  358. var vals = $('input[name="dateTimeCols"]').val().split(' ');
  359. $.each(vals, function (i, v) {
  360. dateTimeCols.push(parseInt(v, 10));
  361. });
  362. vals = $('input[name="numericCols"]').val().split(' ');
  363. $.each(vals, function (i, v) {
  364. numericCols.push(parseInt(v, 10));
  365. });
  366. onXAxisChange();
  367. onDataSeriesChange();
  368. $('#tblchartform').trigger('submit');
  369. });