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