Fix some Selenium tests
[phpmyadmin.git] / js / chart.js
blob5344f0f7fba5c38d2e1a329d75a4e4ced425e9f1
1 /**
2  * Chart type enumerations
3  */
4 var ChartType = {
5     LINE : 'line',
6     SPLINE : 'spline',
7     AREA : 'area',
8     BAR : 'bar',
9     COLUMN : 'column',
10     PIE : 'pie',
11     TIMELINE: 'timeline',
12     SCATTER: 'scatter'
15 /**
16  * Column type enumeration
17  */
18 var ColumnType = {
19     STRING : 'string',
20     NUMBER : 'number',
21     BOOLEAN : 'boolean',
22     DATE : 'date'
25 /**
26  * Abstract chart factory which defines the contract for chart factories
27  */
28 var ChartFactory = function () {
30 ChartFactory.prototype = {
31     createChart : function () {
32         throw new Error('createChart must be implemented by a subclass');
33     }
36 /**
37  * Abstract chart which defines the contract for charts
38  *
39  * @param elementId
40  *            id of the div element the chart is drawn in
41  */
42 var Chart = function (elementId) {
43     this.elementId = elementId;
45 Chart.prototype = {
46     draw : function () {
47         throw new Error('draw must be implemented by a subclass');
48     },
49     redraw : function () {
50         throw new Error('redraw must be implemented by a subclass');
51     },
52     destroy : function () {
53         throw new Error('destroy must be implemented by a subclass');
54     },
55     toImageString : function () {
56         throw new Error('toImageString must be implemented by a subclass');
57     }
60 /**
61  * Abstract representation of charts that operates on DataTable where,<br>
62  * <ul>
63  * <li>First column provides index to the data.</li>
64  * <li>Each subsequent columns are of type
65  * <code>ColumnType.NUMBER<code> and represents a data series.</li>
66  * </ul>
67  * Line chart, area chart, bar chart, column chart are typical examples.
68  *
69  * @param elementId
70  *            id of the div element the chart is drawn in
71  */
72 var BaseChart = function (elementId) {
73     Chart.call(this, elementId);
75 BaseChart.prototype = new Chart();
76 BaseChart.prototype.constructor = BaseChart;
77 BaseChart.prototype.validateColumns = function (dataTable) {
78     var columns = dataTable.getColumns();
79     if (columns.length < 2) {
80         throw new Error('Minimum of two columns are required for this chart');
81     }
82     for (var i = 1; i < columns.length; i++) {
83         if (columns[i].type !== ColumnType.NUMBER) {
84             throw new Error('Column ' + (i + 1) + ' should be of type \'Number\'');
85         }
86     }
87     return true;
90 /**
91  * Abstract pie chart
92  *
93  * @param elementId
94  *            id of the div element the chart is drawn in
95  */
96 var PieChart = function (elementId) {
97     BaseChart.call(this, elementId);
99 PieChart.prototype = new BaseChart();
100 PieChart.prototype.constructor = PieChart;
101 PieChart.prototype.validateColumns = function (dataTable) {
102     var columns = dataTable.getColumns();
103     if (columns.length > 2) {
104         throw new Error('Pie charts can draw only one series');
105     }
106     return BaseChart.prototype.validateColumns.call(this, dataTable);
110  * Abstract timeline chart
112  * @param elementId
113  *            id of the div element the chart is drawn in
114  */
115 var TimelineChart = function (elementId) {
116     BaseChart.call(this, elementId);
118 TimelineChart.prototype = new BaseChart();
119 TimelineChart.prototype.constructor = TimelineChart;
120 TimelineChart.prototype.validateColumns = function (dataTable) {
121     var result = BaseChart.prototype.validateColumns.call(this, dataTable);
122     if (result) {
123         var columns = dataTable.getColumns();
124         if (columns[0].type !== ColumnType.DATE) {
125             throw new Error('First column of timeline chart need to be a date column');
126         }
127     }
128     return result;
132  * Abstract scatter chart
134  * @param elementId
135  *            id of the div element the chart is drawn in
136  */
137 var ScatterChart = function (elementId) {
138     BaseChart.call(this, elementId);
140 ScatterChart.prototype = new BaseChart();
141 ScatterChart.prototype.constructor = ScatterChart;
142 ScatterChart.prototype.validateColumns = function (dataTable) {
143     var result = BaseChart.prototype.validateColumns.call(this, dataTable);
144     if (result) {
145         var columns = dataTable.getColumns();
146         if (columns[0].type !== ColumnType.NUMBER) {
147             throw new Error('First column of scatter chart need to be a numeric column');
148         }
149     }
150     return result;
154  * The data table contains column information and data for the chart.
155  */
156 // eslint-disable-next-line no-unused-vars
157 var DataTable = function () {
158     var columns = [];
159     var data = null;
161     this.addColumn = function (type, name) {
162         columns.push({
163             'type' : type,
164             'name' : name
165         });
166     };
168     this.getColumns = function () {
169         return columns;
170     };
172     this.setData = function (rows) {
173         data = rows;
174         fillMissingValues();
175     };
177     this.getData = function () {
178         return data;
179     };
181     var fillMissingValues = function () {
182         if (columns.length === 0) {
183             throw new Error('Set columns first');
184         }
185         var row;
186         for (var i = 0; i < data.length; i++) {
187             row = data[i];
188             if (row.length > columns.length) {
189                 row.splice(columns.length - 1, row.length - columns.length);
190             } else if (row.length < columns.length) {
191                 for (var j = row.length; j < columns.length; j++) {
192                     row.push(null);
193                 }
194             }
195         }
196     };
199 /** *****************************************************************************
200  * JQPlot specific code
201  ******************************************************************************/
204  * Abstract JQplot chart
206  * @param elementId
207  *            id of the div element the chart is drawn in
208  */
209 var JQPlotChart = function (elementId) {
210     Chart.call(this, elementId);
211     this.plot = null;
212     this.validator = null;
214 JQPlotChart.prototype = new Chart();
215 JQPlotChart.prototype.constructor = JQPlotChart;
216 JQPlotChart.prototype.draw = function (data, options) {
217     if (this.validator.validateColumns(data)) {
218         this.plot = $.jqplot(this.elementId, this.prepareData(data), this
219             .populateOptions(data, options));
220     }
222 JQPlotChart.prototype.destroy = function () {
223     if (this.plot !== null) {
224         this.plot.destroy();
225     }
227 JQPlotChart.prototype.redraw = function (options) {
228     if (this.plot !== null) {
229         this.plot.replot(options);
230     }
232 JQPlotChart.prototype.toImageString = function () {
233     if (this.plot !== null) {
234         return $('#' + this.elementId).jqplotToImageStr({});
235     }
237 JQPlotChart.prototype.populateOptions = function () {
238     throw new Error('populateOptions must be implemented by a subclass');
240 JQPlotChart.prototype.prepareData = function () {
241     throw new Error('prepareData must be implemented by a subclass');
245  * JQPlot line chart
247  * @param elementId
248  *            id of the div element the chart is drawn in
249  */
250 var JQPlotLineChart = function (elementId) {
251     JQPlotChart.call(this, elementId);
252     this.validator = BaseChart.prototype;
254 JQPlotLineChart.prototype = new JQPlotChart();
255 JQPlotLineChart.prototype.constructor = JQPlotLineChart;
257 JQPlotLineChart.prototype.populateOptions = function (dataTable, options) {
258     var columns = dataTable.getColumns();
259     var optional = {
260         axes : {
261             xaxis : {
262                 label : columns[0].name,
263                 renderer : $.jqplot.CategoryAxisRenderer,
264                 ticks : []
265             },
266             yaxis : {
267                 label : (columns.length === 2 ? columns[1].name : 'Values'),
268                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
269             }
270         },
271         highlighter: {
272             show: true,
273             tooltipAxes: 'y',
274             formatString:'%d'
275         },
276         series : []
277     };
278     $.extend(true, optional, options);
280     if (optional.series.length === 0) {
281         for (var i = 1; i < columns.length; i++) {
282             optional.series.push({
283                 label : columns[i].name.toString()
284             });
285         }
286     }
287     if (optional.axes.xaxis.ticks.length === 0) {
288         var data = dataTable.getData();
289         for (var j = 0; j < data.length; j++) {
290             optional.axes.xaxis.ticks.push(data[j][0].toString());
291         }
292     }
293     return optional;
296 JQPlotLineChart.prototype.prepareData = function (dataTable) {
297     var data = dataTable.getData();
298     var row;
299     var retData = [];
300     var retRow;
301     for (var i = 0; i < data.length; i++) {
302         row = data[i];
303         for (var j = 1; j < row.length; j++) {
304             retRow = retData[j - 1];
305             if (retRow === undefined) {
306                 retRow = [];
307                 retData[j - 1] = retRow;
308             }
309             retRow.push(row[j]);
310         }
311     }
312     return retData;
316  * JQPlot spline chart
318  * @param elementId
319  *            id of the div element the chart is drawn in
320  */
321 var JQPlotSplineChart = function (elementId) {
322     JQPlotLineChart.call(this, elementId);
324 JQPlotSplineChart.prototype = new JQPlotLineChart();
325 JQPlotSplineChart.prototype.constructor = JQPlotSplineChart;
327 JQPlotSplineChart.prototype.populateOptions = function (dataTable, options) {
328     var optional = {};
329     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
330         options);
331     var compulsory = {
332         seriesDefaults : {
333             rendererOptions : {
334                 smooth : true
335             }
336         }
337     };
338     $.extend(true, optional, opt, compulsory);
339     return optional;
343  * JQPlot scatter chart
345  * @param elementId
346  *            id of the div element the chart is drawn in
347  */
348 var JQPlotScatterChart = function (elementId) {
349     JQPlotChart.call(this, elementId);
350     this.validator = ScatterChart.prototype;
352 JQPlotScatterChart.prototype = new JQPlotChart();
353 JQPlotScatterChart.prototype.constructor = JQPlotScatterChart;
355 JQPlotScatterChart.prototype.populateOptions = function (dataTable, options) {
356     var columns = dataTable.getColumns();
357     var optional = {
358         axes : {
359             xaxis : {
360                 label : columns[0].name
361             },
362             yaxis : {
363                 label : (columns.length === 2 ? columns[1].name : 'Values'),
364                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
365             }
366         },
367         highlighter: {
368             show: true,
369             tooltipAxes: 'xy',
370             formatString:'%d, %d'
371         },
372         series : []
373     };
374     for (var i = 1; i < columns.length; i++) {
375         optional.series.push({
376             label : columns[i].name.toString()
377         });
378     }
380     var compulsory = {
381         seriesDefaults : {
382             showLine: false,
383             markerOptions: {
384                 size: 7,
385                 style: 'x'
386             }
387         }
388     };
390     $.extend(true, optional, options, compulsory);
391     return optional;
394 JQPlotScatterChart.prototype.prepareData = function (dataTable) {
395     var data = dataTable.getData();
396     var row;
397     var retData = [];
398     var retRow;
399     for (var i = 0; i < data.length; i++) {
400         row = data[i];
401         if (row[0]) {
402             for (var j = 1; j < row.length; j++) {
403                 retRow = retData[j - 1];
404                 if (retRow === undefined) {
405                     retRow = [];
406                     retData[j - 1] = retRow;
407                 }
408                 retRow.push([row[0], row[j]]);
409             }
410         }
411     }
412     return retData;
416  * JQPlot timeline chart
418  * @param elementId
419  *            id of the div element the chart is drawn in
420  */
421 var JQPlotTimelineChart = function (elementId) {
422     JQPlotLineChart.call(this, elementId);
423     this.validator = TimelineChart.prototype;
425 JQPlotTimelineChart.prototype = new JQPlotLineChart();
426 JQPlotTimelineChart.prototype.constructor = JQPlotTimelineChart;
428 JQPlotTimelineChart.prototype.populateOptions = function (dataTable, options) {
429     var optional = {
430         axes : {
431             xaxis : {
432                 tickOptions : {
433                     formatString: '%b %#d, %y'
434                 }
435             }
436         }
437     };
438     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options);
439     var compulsory = {
440         axes : {
441             xaxis : {
442                 renderer : $.jqplot.DateAxisRenderer
443             }
444         }
445     };
446     $.extend(true, optional, opt, compulsory);
447     return optional;
450 JQPlotTimelineChart.prototype.prepareData = function (dataTable) {
451     var data = dataTable.getData();
452     var row;
453     var d;
454     var retData = [];
455     var retRow;
456     for (var i = 0; i < data.length; i++) {
457         row = data[i];
458         d = row[0];
459         for (var j = 1; j < row.length; j++) {
460             retRow = retData[j - 1];
461             if (retRow === undefined) {
462                 retRow = [];
463                 retData[j - 1] = retRow;
464             }
465             if (d !== null) {
466                 retRow.push([d.getTime(), row[j]]);
467             }
468         }
469     }
470     return retData;
474  * JQPlot area chart
476  * @param elementId
477  *            id of the div element the chart is drawn in
478  */
479 var JQPlotAreaChart = function (elementId) {
480     JQPlotLineChart.call(this, elementId);
482 JQPlotAreaChart.prototype = new JQPlotLineChart();
483 JQPlotAreaChart.prototype.constructor = JQPlotAreaChart;
485 JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) {
486     var optional = {
487         seriesDefaults : {
488             fillToZero : true
489         }
490     };
491     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
492         options);
493     var compulsory = {
494         seriesDefaults : {
495             fill : true
496         }
497     };
498     $.extend(true, optional, opt, compulsory);
499     return optional;
503  * JQPlot column chart
505  * @param elementId
506  *            id of the div element the chart is drawn in
507  */
508 var JQPlotColumnChart = function (elementId) {
509     JQPlotLineChart.call(this, elementId);
511 JQPlotColumnChart.prototype = new JQPlotLineChart();
512 JQPlotColumnChart.prototype.constructor = JQPlotColumnChart;
514 JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) {
515     var optional = {
516         seriesDefaults : {
517             fillToZero : true
518         }
519     };
520     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
521         options);
522     var compulsory = {
523         seriesDefaults : {
524             renderer : $.jqplot.BarRenderer
525         }
526     };
527     $.extend(true, optional, opt, compulsory);
528     return optional;
532  * JQPlot bar chart
534  * @param elementId
535  *            id of the div element the chart is drawn in
536  */
537 var JQPlotBarChart = function (elementId) {
538     JQPlotLineChart.call(this, elementId);
540 JQPlotBarChart.prototype = new JQPlotLineChart();
541 JQPlotBarChart.prototype.constructor = JQPlotBarChart;
543 JQPlotBarChart.prototype.populateOptions = function (dataTable, options) {
544     var columns = dataTable.getColumns();
545     var optional = {
546         axes : {
547             yaxis : {
548                 label : columns[0].name,
549                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer,
550                 renderer : $.jqplot.CategoryAxisRenderer,
551                 ticks : []
552             },
553             xaxis : {
554                 label : (columns.length === 2 ? columns[1].name : 'Values'),
555                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
556             }
557         },
558         highlighter: {
559             show: true,
560             tooltipAxes: 'x',
561             formatString:'%d'
562         },
563         series : [],
564         seriesDefaults : {
565             fillToZero : true
566         }
567     };
568     var compulsory = {
569         seriesDefaults : {
570             renderer : $.jqplot.BarRenderer,
571             rendererOptions : {
572                 barDirection : 'horizontal'
573             }
574         }
575     };
576     $.extend(true, optional, options, compulsory);
578     if (optional.axes.yaxis.ticks.length === 0) {
579         var data = dataTable.getData();
580         for (var i = 0; i < data.length; i++) {
581             optional.axes.yaxis.ticks.push(data[i][0].toString());
582         }
583     }
584     if (optional.series.length === 0) {
585         for (var j = 1; j < columns.length; j++) {
586             optional.series.push({
587                 label : columns[j].name.toString()
588             });
589         }
590     }
591     return optional;
595  * JQPlot pie chart
597  * @param elementId
598  *            id of the div element the chart is drawn in
599  */
600 var JQPlotPieChart = function (elementId) {
601     JQPlotChart.call(this, elementId);
602     this.validator = PieChart.prototype;
604 JQPlotPieChart.prototype = new JQPlotChart();
605 JQPlotPieChart.prototype.constructor = JQPlotPieChart;
607 JQPlotPieChart.prototype.populateOptions = function (dataTable, options) {
608     var optional = {
609         highlighter: {
610             show: true,
611             tooltipAxes: 'xy',
612             formatString:'%s, %d',
613             useAxesFormatters: false
614         },
615         legend: {
616             renderer: $.jqplot.EnhancedPieLegendRenderer,
617         },
618     };
619     var compulsory = {
620         seriesDefaults : {
621             shadow: false,
622             renderer : $.jqplot.PieRenderer,
623             rendererOptions: { sliceMargin: 1, showDataLabels: true }
624         }
625     };
626     $.extend(true, optional, options, compulsory);
627     return optional;
630 JQPlotPieChart.prototype.prepareData = function (dataTable) {
631     var data = dataTable.getData();
632     var row;
633     var retData = [];
634     for (var i = 0; i < data.length; i++) {
635         row = data[i];
636         retData.push([row[0], row[1]]);
637     }
638     return [retData];
642  * Chart factory that returns JQPlotCharts
643  */
644 var JQPlotChartFactory = function () {
646 JQPlotChartFactory.prototype = new ChartFactory();
647 JQPlotChartFactory.prototype.createChart = function (type, elementId) {
648     var chart = null;
649     switch (type) {
650     case ChartType.LINE:
651         chart = new JQPlotLineChart(elementId);
652         break;
653     case ChartType.SPLINE:
654         chart = new JQPlotSplineChart(elementId);
655         break;
656     case ChartType.TIMELINE:
657         chart = new JQPlotTimelineChart(elementId);
658         break;
659     case ChartType.AREA:
660         chart = new JQPlotAreaChart(elementId);
661         break;
662     case ChartType.BAR:
663         chart = new JQPlotBarChart(elementId);
664         break;
665     case ChartType.COLUMN:
666         chart = new JQPlotColumnChart(elementId);
667         break;
668     case ChartType.PIE:
669         chart = new JQPlotPieChart(elementId);
670         break;
671     case ChartType.SCATTER:
672         chart = new JQPlotScatterChart(elementId);
673         break;
674     }
676     return chart;