Translated using Weblate (German)
[phpmyadmin.git] / js / src / chart.js
blobadfabeb934f21c6e2c1ea0aade364711bf3a385b
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] !== null ? data[j][0].toString() : null);
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             // See https://github.com/phpmyadmin/phpmyadmin/issues/14395 for the block
466             if (d !== null && typeof d === 'object') {
467                 retRow.push([d.getTime(), row[j]]);
468             } else if (typeof d === 'string') {
469                 d = new Date(d);
470                 retRow.push([d.getTime(), row[j]]);
471             }
472         }
473     }
474     return retData;
478  * JQPlot area chart
480  * @param elementId
481  *            id of the div element the chart is drawn in
482  */
483 var JQPlotAreaChart = function (elementId) {
484     JQPlotLineChart.call(this, elementId);
486 JQPlotAreaChart.prototype = new JQPlotLineChart();
487 JQPlotAreaChart.prototype.constructor = JQPlotAreaChart;
489 JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) {
490     var optional = {
491         seriesDefaults : {
492             fillToZero : true
493         }
494     };
495     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
496         options);
497     var compulsory = {
498         seriesDefaults : {
499             fill : true
500         }
501     };
502     $.extend(true, optional, opt, compulsory);
503     return optional;
507  * JQPlot column chart
509  * @param elementId
510  *            id of the div element the chart is drawn in
511  */
512 var JQPlotColumnChart = function (elementId) {
513     JQPlotLineChart.call(this, elementId);
515 JQPlotColumnChart.prototype = new JQPlotLineChart();
516 JQPlotColumnChart.prototype.constructor = JQPlotColumnChart;
518 JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) {
519     var optional = {
520         seriesDefaults : {
521             fillToZero : true
522         }
523     };
524     var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
525         options);
526     var compulsory = {
527         seriesDefaults : {
528             renderer : $.jqplot.BarRenderer
529         }
530     };
531     $.extend(true, optional, opt, compulsory);
532     return optional;
536  * JQPlot bar chart
538  * @param elementId
539  *            id of the div element the chart is drawn in
540  */
541 var JQPlotBarChart = function (elementId) {
542     JQPlotLineChart.call(this, elementId);
544 JQPlotBarChart.prototype = new JQPlotLineChart();
545 JQPlotBarChart.prototype.constructor = JQPlotBarChart;
547 JQPlotBarChart.prototype.populateOptions = function (dataTable, options) {
548     var columns = dataTable.getColumns();
549     var optional = {
550         axes : {
551             yaxis : {
552                 label : columns[0].name,
553                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer,
554                 renderer : $.jqplot.CategoryAxisRenderer,
555                 ticks : []
556             },
557             xaxis : {
558                 label : (columns.length === 2 ? columns[1].name : 'Values'),
559                 labelRenderer : $.jqplot.CanvasAxisLabelRenderer
560             }
561         },
562         highlighter: {
563             show: true,
564             tooltipAxes: 'x',
565             formatString:'%d'
566         },
567         series : [],
568         seriesDefaults : {
569             fillToZero : true
570         }
571     };
572     var compulsory = {
573         seriesDefaults : {
574             renderer : $.jqplot.BarRenderer,
575             rendererOptions : {
576                 barDirection : 'horizontal'
577             }
578         }
579     };
580     $.extend(true, optional, options, compulsory);
582     if (optional.axes.yaxis.ticks.length === 0) {
583         var data = dataTable.getData();
584         for (var i = 0; i < data.length; i++) {
585             optional.axes.yaxis.ticks.push(data[i][0].toString());
586         }
587     }
588     if (optional.series.length === 0) {
589         for (var j = 1; j < columns.length; j++) {
590             optional.series.push({
591                 label : columns[j].name.toString()
592             });
593         }
594     }
595     return optional;
599  * JQPlot pie chart
601  * @param elementId
602  *            id of the div element the chart is drawn in
603  */
604 var JQPlotPieChart = function (elementId) {
605     JQPlotChart.call(this, elementId);
606     this.validator = PieChart.prototype;
608 JQPlotPieChart.prototype = new JQPlotChart();
609 JQPlotPieChart.prototype.constructor = JQPlotPieChart;
611 JQPlotPieChart.prototype.populateOptions = function (dataTable, options) {
612     var optional = {
613         highlighter: {
614             show: true,
615             tooltipAxes: 'xy',
616             formatString:'%s, %d',
617             useAxesFormatters: false
618         },
619         legend: {
620             renderer: $.jqplot.EnhancedPieLegendRenderer,
621         },
622     };
623     var compulsory = {
624         seriesDefaults : {
625             shadow: false,
626             renderer : $.jqplot.PieRenderer,
627             rendererOptions: { sliceMargin: 1, showDataLabels: true }
628         }
629     };
630     $.extend(true, optional, options, compulsory);
631     return optional;
634 JQPlotPieChart.prototype.prepareData = function (dataTable) {
635     var data = dataTable.getData();
636     var row;
637     var retData = [];
638     for (var i = 0; i < data.length; i++) {
639         row = data[i];
640         retData.push([row[0], row[1]]);
641     }
642     return [retData];
646  * Chart factory that returns JQPlotCharts
647  */
648 var JQPlotChartFactory = function () {
650 JQPlotChartFactory.prototype = new ChartFactory();
651 JQPlotChartFactory.prototype.createChart = function (type, elementId) {
652     var chart = null;
653     switch (type) {
654     case ChartType.LINE:
655         chart = new JQPlotLineChart(elementId);
656         break;
657     case ChartType.SPLINE:
658         chart = new JQPlotSplineChart(elementId);
659         break;
660     case ChartType.TIMELINE:
661         chart = new JQPlotTimelineChart(elementId);
662         break;
663     case ChartType.AREA:
664         chart = new JQPlotAreaChart(elementId);
665         break;
666     case ChartType.BAR:
667         chart = new JQPlotBarChart(elementId);
668         break;
669     case ChartType.COLUMN:
670         chart = new JQPlotColumnChart(elementId);
671         break;
672     case ChartType.PIE:
673         chart = new JQPlotPieChart(elementId);
674         break;
675     case ChartType.SCATTER:
676         chart = new JQPlotScatterChart(elementId);
677         break;
678     }
680     return chart;