Merge pull request #15404 from jfcherng/fix-php74-curly-braces
[phpmyadmin.git] / js / chart.js
blob8145dac252cb6d8ad3fe32fae36c213f51bc1a98
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 (type, options) {
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 (data, options) {
47         throw new Error('draw must be implemented by a subclass');
48     },
49     redraw : function (options) {
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 var DataTable = function () {
157     var columns = [];
158     var data = null;
160     this.addColumn = function (type, name) {
161         columns.push({
162             'type' : type,
163             'name' : name
164         });
165     };
167     this.getColumns = function () {
168         return columns;
169     };
171     this.setData = function (rows) {
172         data = rows;
173         fillMissingValues();
174     };
176     this.getData = function () {
177         return data;
178     };
180     var fillMissingValues = function () {
181         if (columns.length === 0) {
182             throw new Error('Set columns first');
183         }
184         var row;
185         for (var i = 0; i < data.length; i++) {
186             row = data[i];
187             if (row.length > columns.length) {
188                 row.splice(columns.length - 1, row.length - columns.length);
189             } else if (row.length < columns.length) {
190                 for (var j = row.length; j < columns.length; j++) {
191                     row.push(null);
192                 }
193             }
194         }
195     };
198 /** *****************************************************************************
199  * JQPlot specific code
200  ******************************************************************************/
203  * Abstract JQplot chart
205  * @param elementId
206  *            id of the div element the chart is drawn in
207  */
208 var JQPlotChart = function (elementId) {
209     Chart.call(this, elementId);
210     this.plot = null;
211     this.validator = null;
213 JQPlotChart.prototype = new Chart();
214 JQPlotChart.prototype.constructor = JQPlotChart;
215 JQPlotChart.prototype.draw = function (data, options) {
216     if (this.validator.validateColumns(data)) {
217         this.plot = $.jqplot(this.elementId, this.prepareData(data), this
218             .populateOptions(data, options));
219     }
221 JQPlotChart.prototype.destroy = function () {
222     if (this.plot !== null) {
223         this.plot.destroy();
224     }
226 JQPlotChart.prototype.redraw = function (options) {
227     if (this.plot !== null) {
228         this.plot.replot(options);
229     }
231 JQPlotChart.prototype.toImageString = function (options) {
232     if (this.plot !== null) {
233         return $('#' + this.elementId).jqplotToImageStr({});
234     }
236 JQPlotChart.prototype.populateOptions = function (dataTable, options) {
237     throw new Error('populateOptions must be implemented by a subclass');
239 JQPlotChart.prototype.prepareData = function (dataTable) {
240     throw new Error('prepareData must be implemented by a subclass');
244  * JQPlot line chart
246  * @param elementId
247  *            id of the div element the chart is drawn in
248  */
249 var JQPlotLineChart = function (elementId) {
250     JQPlotChart.call(this, elementId);
251     this.validator = BaseChart.prototype;
253 JQPlotLineChart.prototype = new JQPlotChart();
254 JQPlotLineChart.prototype.constructor = JQPlotLineChart;
256 JQPlotLineChart.prototype.populateOptions = function (dataTable, options) {
257     var columns = dataTable.getColumns();
258     var optional = {
259         axes : {
260             xaxis : {
261                 label : columns[0].name,
262                 renderer : $.jqplot.CategoryAxisRenderer,
263                 ticks : []
264             },
265             yaxis : {
266                 label : (columns.length === 2 ? columns[1].name : 'Values'),
267                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
268             }
269         },
270         highlighter: {
271             show: true,
272             tooltipAxes: 'y',
273             formatString:'%d'
274         },
275         series : []
276     };
277     $.extend(true, optional, options);
279     if (optional.series.length === 0) {
280         for (var i = 1; i < columns.length; i++) {
281             optional.series.push({
282                 label : columns[i].name.toString()
283             });
284         }
285     }
286     if (optional.axes.xaxis.ticks.length === 0) {
287         var data = dataTable.getData();
288         for (var j = 0; j < data.length; j++) {
289             optional.axes.xaxis.ticks.push(data[j][0].toString());
290         }
291     }
292     return optional;
295 JQPlotLineChart.prototype.prepareData = function (dataTable) {
296     var data = dataTable.getData();
297     var row;
298     var retData = [];
299     var retRow;
300     for (var i = 0; i < data.length; i++) {
301         row = data[i];
302         for (var j = 1; j < row.length; j++) {
303             retRow = retData[j - 1];
304             if (retRow === undefined) {
305                 retRow = [];
306                 retData[j - 1] = retRow;
307             }
308             retRow.push(row[j]);
309         }
310     }
311     return retData;
315  * JQPlot spline chart
317  * @param elementId
318  *            id of the div element the chart is drawn in
319  */
320 var JQPlotSplineChart = function (elementId) {
321     JQPlotLineChart.call(this, elementId);
323 JQPlotSplineChart.prototype = new JQPlotLineChart();
324 JQPlotSplineChart.prototype.constructor = JQPlotSplineChart;
326 JQPlotSplineChart.prototype.populateOptions = function (dataTable, options) {
327     var optional = {};
328     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
329         options);
330     var compulsory = {
331         seriesDefaults : {
332             rendererOptions : {
333                 smooth : true
334             }
335         }
336     };
337     $.extend(true, optional, opt, compulsory);
338     return optional;
342  * JQPlot scatter chart
344  * @param elementId
345  *            id of the div element the chart is drawn in
346  */
347 var JQPlotScatterChart = function (elementId) {
348     JQPlotChart.call(this, elementId);
349     this.validator = ScatterChart.prototype;
351 JQPlotScatterChart.prototype = new JQPlotChart();
352 JQPlotScatterChart.prototype.constructor = JQPlotScatterChart;
354 JQPlotScatterChart.prototype.populateOptions = function (dataTable, options) {
355     var columns = dataTable.getColumns();
356     var optional = {
357         axes : {
358             xaxis : {
359                 label : columns[0].name
360             },
361             yaxis : {
362                 label : (columns.length === 2 ? columns[1].name : 'Values'),
363                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
364             }
365         },
366         highlighter: {
367             show: true,
368             tooltipAxes: 'xy',
369             formatString:'%d, %d'
370         },
371         series : []
372     };
373     for (var i = 1; i < columns.length; i++) {
374         optional.series.push({
375             label : columns[i].name.toString()
376         });
377     }
379     var compulsory = {
380         seriesDefaults : {
381             showLine: false,
382             markerOptions: {
383                 size: 7,
384                 style: 'x'
385             }
386         }
387     };
389     $.extend(true, optional, options, compulsory);
390     return optional;
393 JQPlotScatterChart.prototype.prepareData = function (dataTable) {
394     var data = dataTable.getData();
395     var row;
396     var retData = [];
397     var retRow;
398     for (var i = 0; i < data.length; i++) {
399         row = data[i];
400         if (row[0]) {
401             for (var j = 1; j < row.length; j++) {
402                 retRow = retData[j - 1];
403                 if (retRow === undefined) {
404                     retRow = [];
405                     retData[j - 1] = retRow;
406                 }
407                 retRow.push([row[0], row[j]]);
408             }
409         }
410     }
411     return retData;
415  * JQPlot timeline chart
417  * @param elementId
418  *            id of the div element the chart is drawn in
419  */
420 var JQPlotTimelineChart = function (elementId) {
421     JQPlotLineChart.call(this, elementId);
422     this.validator = TimelineChart.prototype;
424 JQPlotTimelineChart.prototype = new JQPlotLineChart();
425 JQPlotTimelineChart.prototype.constructor = JQPlotTimelineChart;
427 JQPlotTimelineChart.prototype.populateOptions = function (dataTable, options) {
428     var optional = {
429         axes : {
430             xaxis : {
431                 tickOptions : {
432                     formatString: '%b %#d, %y'
433                 }
434             }
435         }
436     };
437     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options);
438     var compulsory = {
439         axes : {
440             xaxis : {
441                 renderer : $.jqplot.DateAxisRenderer
442             }
443         }
444     };
445     $.extend(true, optional, opt, compulsory);
446     return optional;
449 JQPlotTimelineChart.prototype.prepareData = function (dataTable) {
450     var data = dataTable.getData();
451     var row;
452     var d;
453     var retData = [];
454     var retRow;
455     for (var i = 0; i < data.length; i++) {
456         row = data[i];
457         d = row[0];
458         for (var j = 1; j < row.length; j++) {
459             retRow = retData[j - 1];
460             if (retRow === undefined) {
461                 retRow = [];
462                 retData[j - 1] = retRow;
463             }
464             if (d !== null) {
465                 retRow.push([d.getTime(), row[j]]);
466             }
467         }
468     }
469     return retData;
473  * JQPlot area chart
475  * @param elementId
476  *            id of the div element the chart is drawn in
477  */
478 var JQPlotAreaChart = function (elementId) {
479     JQPlotLineChart.call(this, elementId);
481 JQPlotAreaChart.prototype = new JQPlotLineChart();
482 JQPlotAreaChart.prototype.constructor = JQPlotAreaChart;
484 JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) {
485     var optional = {
486         seriesDefaults : {
487             fillToZero : true
488         }
489     };
490     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
491         options);
492     var compulsory = {
493         seriesDefaults : {
494             fill : true
495         }
496     };
497     $.extend(true, optional, opt, compulsory);
498     return optional;
502  * JQPlot column chart
504  * @param elementId
505  *            id of the div element the chart is drawn in
506  */
507 var JQPlotColumnChart = function (elementId) {
508     JQPlotLineChart.call(this, elementId);
510 JQPlotColumnChart.prototype = new JQPlotLineChart();
511 JQPlotColumnChart.prototype.constructor = JQPlotColumnChart;
513 JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) {
514     var optional = {
515         seriesDefaults : {
516             fillToZero : true
517         }
518     };
519     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
520         options);
521     var compulsory = {
522         seriesDefaults : {
523             renderer : $.jqplot.BarRenderer
524         }
525     };
526     $.extend(true, optional, opt, compulsory);
527     return optional;
531  * JQPlot bar chart
533  * @param elementId
534  *            id of the div element the chart is drawn in
535  */
536 var JQPlotBarChart = function (elementId) {
537     JQPlotLineChart.call(this, elementId);
539 JQPlotBarChart.prototype = new JQPlotLineChart();
540 JQPlotBarChart.prototype.constructor = JQPlotBarChart;
542 JQPlotBarChart.prototype.populateOptions = function (dataTable, options) {
543     var columns = dataTable.getColumns();
544     var optional = {
545         axes : {
546             yaxis : {
547                 label : columns[0].name,
548                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer,
549                 renderer : $.jqplot.CategoryAxisRenderer,
550                 ticks : []
551             },
552             xaxis : {
553                 label : (columns.length === 2 ? columns[1].name : 'Values'),
554                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
555             }
556         },
557         highlighter: {
558             show: true,
559             tooltipAxes: 'x',
560             formatString:'%d'
561         },
562         series : [],
563         seriesDefaults : {
564             fillToZero : true
565         }
566     };
567     var compulsory = {
568         seriesDefaults : {
569             renderer : $.jqplot.BarRenderer,
570             rendererOptions : {
571                 barDirection : 'horizontal'
572             }
573         }
574     };
575     $.extend(true, optional, options, compulsory);
577     if (optional.axes.yaxis.ticks.length === 0) {
578         var data = dataTable.getData();
579         for (var i = 0; i < data.length; i++) {
580             optional.axes.yaxis.ticks.push(data[i][0].toString());
581         }
582     }
583     if (optional.series.length === 0) {
584         for (var j = 1; j < columns.length; j++) {
585             optional.series.push({
586                 label : columns[j].name.toString()
587             });
588         }
589     }
590     return optional;
594  * JQPlot pie chart
596  * @param elementId
597  *            id of the div element the chart is drawn in
598  */
599 var JQPlotPieChart = function (elementId) {
600     JQPlotChart.call(this, elementId);
601     this.validator = PieChart.prototype;
603 JQPlotPieChart.prototype = new JQPlotChart();
604 JQPlotPieChart.prototype.constructor = JQPlotPieChart;
606 JQPlotPieChart.prototype.populateOptions = function (dataTable, options) {
607     var optional = {
608         highlighter: {
609             show: true,
610             tooltipAxes: 'xy',
611             formatString:'%s, %d',
612             useAxesFormatters: false
613         },
614         legend: {
615             renderer: $.jqplot.EnhancedPieLegendRenderer,
616         },
617     };
618     var compulsory = {
619         seriesDefaults : {
620             shadow: false,
621             renderer : $.jqplot.PieRenderer,
622             rendererOptions: { sliceMargin: 1, showDataLabels: true }
623         }
624     };
625     $.extend(true, optional, options, compulsory);
626     return optional;
629 JQPlotPieChart.prototype.prepareData = function (dataTable) {
630     var data = dataTable.getData();
631     var row;
632     var retData = [];
633     for (var i = 0; i < data.length; i++) {
634         row = data[i];
635         retData.push([row[0], row[1]]);
636     }
637     return [retData];
641  * Chart factory that returns JQPlotCharts
642  */
643 var JQPlotChartFactory = function () {
645 JQPlotChartFactory.prototype = new ChartFactory();
646 JQPlotChartFactory.prototype.createChart = function (type, elementId) {
647     var chart = null;
648     switch (type) {
649     case ChartType.LINE:
650         chart = new JQPlotLineChart(elementId);
651         break;
652     case ChartType.SPLINE:
653         chart = new JQPlotSplineChart(elementId);
654         break;
655     case ChartType.TIMELINE:
656         chart = new JQPlotTimelineChart(elementId);
657         break;
658     case ChartType.AREA:
659         chart = new JQPlotAreaChart(elementId);
660         break;
661     case ChartType.BAR:
662         chart = new JQPlotBarChart(elementId);
663         break;
664     case ChartType.COLUMN:
665         chart = new JQPlotColumnChart(elementId);
666         break;
667     case ChartType.PIE:
668         chart = new JQPlotPieChart(elementId);
669         break;
670     case ChartType.SCATTER:
671         chart = new JQPlotScatterChart(elementId);
672         break;
673     }
675     return chart;