chart.js 18 KB

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