chart.js 17 KB

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