Passed
Push — master ( 771cdd...c0fb5f )
by Maurício
07:44
created

js/tbl_chart.js (1 issue)

Labels
Severity
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
It seems like values was already defined.
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