chart.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  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. };
  13. /**
  14. * Abstract chart factory which defines the contract for chart factories
  15. */
  16. var ChartFactory = function() {
  17. };
  18. ChartFactory.prototype = {
  19. createChart : function(type, options) {
  20. throw new Error("createChart must be implemented by a subclass");
  21. }
  22. };
  23. /**
  24. * Abstract chart which defines the contract for charts
  25. *
  26. * @param elementId
  27. * id of the div element the chart is drawn in
  28. */
  29. var Chart = function(elementId) {
  30. this.elementId = elementId;
  31. };
  32. Chart.prototype = {
  33. draw : function(data, options) {
  34. throw new Error("draw must be implemented by a subclass");
  35. },
  36. redraw : function(options) {
  37. throw new Error("redraw must be implemented by a subclass");
  38. },
  39. destroy : function() {
  40. throw new Error("destroy must be implemented by a subclass");
  41. }
  42. };
  43. /**
  44. * Abstract representation of charts that operates on DataTable where,<br />
  45. * <ul>
  46. * <li>First column provides index to the data.</li>
  47. * <li>Each subsequent columns are of type
  48. * <code>ColumnType.NUMBER<code> and represents a data series.</li>
  49. * </ul>
  50. * Line chart, area chart, bar chart, column chart are typical examples.
  51. *
  52. * @param elementId
  53. * id of the div element the chart is drawn in
  54. */
  55. var BaseChart = function(elementId) {
  56. Chart.call(this, elementId);
  57. };
  58. BaseChart.prototype = new Chart();
  59. BaseChart.prototype.constructor = BaseChart;
  60. BaseChart.prototype.validateColumns = function(dataTable) {
  61. var columns = dataTable.getColumns();
  62. if (columns.length < 2) {
  63. throw new Error("Minimum of two columns are required for this chart");
  64. }
  65. for ( var i = 1; i < columns.length; i++) {
  66. if (columns[i].type != ColumnType.NUMBER) {
  67. throw new Error("Column " + (i + 1) + " should be of type 'Number'");
  68. }
  69. }
  70. return true;
  71. };
  72. /**
  73. * Abstract pie chart
  74. *
  75. * @param elementId
  76. * id of the div element the chart is drawn in
  77. */
  78. var PieChart = function(elementId) {
  79. BaseChart.call(this, elementId);
  80. };
  81. PieChart.prototype = new BaseChart();
  82. PieChart.prototype.constructor = PieChart;
  83. PieChart.prototype.validateColumns = function(dataTable) {
  84. var columns = dataTable.getColumns();
  85. if (columns.length > 2) {
  86. throw new Error("Pie charts can draw only one series");
  87. }
  88. return BaseChart.prototype.validateColumns.call(this, dataTable);
  89. };
  90. /**
  91. * Abstract timeline chart
  92. *
  93. * @param elementId
  94. * id of the div element the chart is drawn in
  95. */
  96. var TimelineChart = function(elementId) {
  97. BaseChart.call(this, elementId);
  98. };
  99. TimelineChart.prototype = new BaseChart();
  100. TimelineChart.prototype.constructor = TimelineChart;
  101. TimelineChart.prototype.validateColumns = function(dataTable) {
  102. var result = BaseChart.prototype.validateColumns.call(this, dataTable);
  103. if (result) {
  104. var columns = dataTable.getColumns();
  105. if (columns[0].type != ColumnType.DATE) {
  106. throw new Error("First column of timeline chart need to be a date column");
  107. }
  108. }
  109. return result;
  110. };
  111. /**
  112. * The data table contains column information and data for the chart.
  113. */
  114. var DataTable = function() {
  115. var columns = [];
  116. var data;
  117. this.addColumn = function(type, name) {
  118. columns.push({
  119. 'type' : type,
  120. 'name' : name
  121. });
  122. };
  123. this.getColumns = function() {
  124. return columns;
  125. };
  126. this.setData = function(rows) {
  127. data = rows;
  128. fillMissingValues();
  129. };
  130. this.getData = function() {
  131. return data;
  132. };
  133. var fillMissingValues = function() {
  134. if (columns.length == 0) {
  135. throw new Error("Set columns first");
  136. }
  137. var row, column;
  138. for ( var i = 0; i < data.length; i++) {
  139. row = data[i];
  140. if (row.length > columns.length) {
  141. row.splice(columns.length - 1, row.length - columns.length);
  142. } else if (row.length < columns.length) {
  143. for ( var j = row.length; j < columns.length; j++) {
  144. row.push(null);
  145. }
  146. }
  147. }
  148. };
  149. };
  150. /**
  151. * Column type enumeration
  152. */
  153. var ColumnType = {
  154. STRING : 'string',
  155. NUMBER : 'number',
  156. BOOLEAN : 'boolean',
  157. DATE : 'date'
  158. };
  159. /*******************************************************************************
  160. * JQPlot specifc code
  161. ******************************************************************************/
  162. /**
  163. * Chart factory that returns JQPlotCharts
  164. */
  165. var JQPlotChartFactory = function() {
  166. };
  167. JQPlotChartFactory.prototype = new ChartFactory();
  168. JQPlotChartFactory.prototype.createChart = function(type, elementId) {
  169. var chart;
  170. switch (type) {
  171. case ChartType.LINE:
  172. chart = new JQPlotLineChart(elementId);
  173. break;
  174. case ChartType.SPLINE:
  175. chart = new JQPlotSplineChart(elementId);
  176. break;
  177. case ChartType.TIMELINE:
  178. chart = new JQPlotTimelineChart(elementId);
  179. break;
  180. case ChartType.AREA:
  181. chart = new JQPlotAreaChart(elementId);
  182. break;
  183. case ChartType.BAR:
  184. chart = new JQPlotBarChart(elementId);
  185. break;
  186. case ChartType.COLUMN:
  187. chart = new JQPlotColumnChart(elementId);
  188. break;
  189. case ChartType.PIE:
  190. chart = new JQPlotPieChart(elementId);
  191. break;
  192. }
  193. return chart;
  194. };
  195. /**
  196. * Abstract JQplot chart
  197. *
  198. * @param elementId
  199. * id of the div element the chart is drawn in
  200. */
  201. var JQPlotChart = function(elementId) {
  202. Chart.call(this, elementId);
  203. this.plot;
  204. this.validator;
  205. };
  206. JQPlotChart.prototype = new Chart();
  207. JQPlotChart.prototype.constructor = JQPlotChart;
  208. JQPlotChart.prototype.draw = function(data, options) {
  209. if (this.validator.validateColumns(data)) {
  210. this.plot = $.jqplot(this.elementId, this.prepareData(data), this
  211. .populateOptions(data, options));
  212. }
  213. };
  214. JQPlotChart.prototype.destroy = function() {
  215. if (this.plot != null) {
  216. this.plot.destroy();
  217. }
  218. };
  219. JQPlotChart.prototype.redraw = function(options) {
  220. if (this.plot != null) {
  221. this.plot.replot(options);
  222. }
  223. };
  224. JQPlotChart.prototype.populateOptions = function(dataTable, options) {
  225. throw new Error("populateOptions must be implemented by a subclass");
  226. };
  227. JQPlotChart.prototype.prepareData = function(dataTable) {
  228. throw new Error("prepareData must be implemented by a subclass");
  229. };
  230. /**
  231. * JQPlot line chart
  232. *
  233. * @param elementId
  234. * id of the div element the chart is drawn in
  235. */
  236. var JQPlotLineChart = function(elementId) {
  237. JQPlotChart.call(this, elementId);
  238. this.validator = BaseChart.prototype;
  239. };
  240. JQPlotLineChart.prototype = new JQPlotChart();
  241. JQPlotLineChart.prototype.constructor = JQPlotLineChart;
  242. JQPlotLineChart.prototype.populateOptions = function(dataTable, options) {
  243. var columns = dataTable.getColumns();
  244. var optional = {
  245. axes : {
  246. xaxis : {
  247. label : columns[0].name,
  248. renderer : $.jqplot.CategoryAxisRenderer,
  249. ticks : []
  250. },
  251. yaxis : {
  252. label : (columns.length == 2 ? columns[1].name : 'Values'),
  253. labelRenderer : $.jqplot.CanvasAxisLabelRenderer
  254. }
  255. },
  256. series : []
  257. };
  258. $.extend(true, optional, options);
  259. if (optional.series.length == 0) {
  260. for ( var i = 1; i < columns.length; i++) {
  261. optional.series.push({
  262. label : columns[i].name.toString()
  263. });
  264. }
  265. }
  266. if (optional.axes.xaxis.ticks.length == 0) {
  267. var data = dataTable.getData();
  268. for ( var i = 0; i < data.length; i++) {
  269. optional.axes.xaxis.ticks.push(data[i][0].toString());
  270. }
  271. }
  272. return optional;
  273. };
  274. JQPlotLineChart.prototype.prepareData = function(dataTable) {
  275. var data = dataTable.getData(), row;
  276. var retData = [], retRow;
  277. for ( var i = 0; i < data.length; i++) {
  278. row = data[i];
  279. for ( var j = 1; j < row.length; j++) {
  280. retRow = retData[j - 1];
  281. if (retRow == null) {
  282. retRow = [];
  283. retData[j - 1] = retRow;
  284. }
  285. retRow.push(row[j]);
  286. }
  287. }
  288. return retData;
  289. };
  290. /**
  291. * JQPlot spline chart
  292. *
  293. * @param elementId
  294. * id of the div element the chart is drawn in
  295. */
  296. var JQPlotSplineChart = function(elementId) {
  297. JQPlotLineChart.call(this, elementId);
  298. };
  299. JQPlotSplineChart.prototype = new JQPlotLineChart();
  300. JQPlotSplineChart.prototype.constructor = JQPlotSplineChart;
  301. JQPlotSplineChart.prototype.populateOptions = function(dataTable, options) {
  302. var optional = {};
  303. var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
  304. options);
  305. var compulsory = {
  306. seriesDefaults : {
  307. rendererOptions : {
  308. smooth : true
  309. }
  310. }
  311. };
  312. $.extend(true, optional, opt, compulsory);
  313. return optional;
  314. };
  315. /**
  316. * JQPlot timeline chart
  317. *
  318. * @param elementId
  319. * id of the div element the chart is drawn in
  320. */
  321. var JQPlotTimelineChart = function(elementId) {
  322. JQPlotLineChart.call(this, elementId);
  323. this.validator = TimelineChart.prototype;
  324. };
  325. JQPlotTimelineChart.prototype = new JQPlotLineChart();
  326. JQPlotTimelineChart.prototype.constructor = JQPlotAreaChart;
  327. JQPlotTimelineChart.prototype.populateOptions = function(dataTable, options) {
  328. var optional = {
  329. axes : {
  330. xaxis : {
  331. tickOptions : {
  332. formatString:'%b %#d, %y'
  333. }
  334. }
  335. }
  336. };
  337. var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options);
  338. var compulsory = {
  339. axes : {
  340. xaxis : {
  341. renderer : $.jqplot.DateAxisRenderer
  342. }
  343. }
  344. };
  345. $.extend(true, optional, opt, compulsory);
  346. return optional;
  347. };
  348. JQPlotTimelineChart.prototype.prepareData = function(dataTable) {
  349. var data = dataTable.getData(), row, d;
  350. var retData = [], retRow;
  351. for ( var i = 0; i < data.length; i++) {
  352. row = data[i];
  353. d = row[0];
  354. for ( var j = 1; j < row.length; j++) {
  355. retRow = retData[j - 1];
  356. if (retRow == null) {
  357. retRow = [];
  358. retData[j - 1] = retRow;
  359. }
  360. if (d != null) {
  361. retRow.push([d.getTime(), row[j]]);
  362. }
  363. }
  364. }
  365. return retData;
  366. };
  367. /**
  368. * JQPlot area chart
  369. *
  370. * @param elementId
  371. * id of the div element the chart is drawn in
  372. */
  373. var JQPlotAreaChart = function(elementId) {
  374. JQPlotLineChart.call(this, elementId);
  375. };
  376. JQPlotAreaChart.prototype = new JQPlotLineChart();
  377. JQPlotAreaChart.prototype.constructor = JQPlotAreaChart;
  378. JQPlotAreaChart.prototype.populateOptions = function(dataTable, options) {
  379. var optional = {
  380. seriesDefaults : {
  381. fillToZero : true
  382. }
  383. };
  384. var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
  385. options);
  386. var compulsory = {
  387. seriesDefaults : {
  388. fill : true
  389. }
  390. };
  391. $.extend(true, optional, opt, compulsory);
  392. return optional;
  393. };
  394. /**
  395. * JQPlot column chart
  396. *
  397. * @param elementId
  398. * id of the div element the chart is drawn in
  399. */
  400. var JQPlotColumnChart = function(elementId) {
  401. JQPlotLineChart.call(this, elementId);
  402. };
  403. JQPlotColumnChart.prototype = new JQPlotLineChart();
  404. JQPlotColumnChart.prototype.constructor = JQPlotColumnChart;
  405. JQPlotColumnChart.prototype.populateOptions = function(dataTable, options) {
  406. var optional = {
  407. seriesDefaults : {
  408. fillToZero : true
  409. }
  410. };
  411. var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
  412. options);
  413. var compulsory = {
  414. seriesDefaults : {
  415. renderer : $.jqplot.BarRenderer
  416. }
  417. };
  418. $.extend(true, optional, opt, compulsory);
  419. return optional;
  420. };
  421. /**
  422. * JQPlot bar chart
  423. *
  424. * @param elementId
  425. * id of the div element the chart is drawn in
  426. */
  427. var JQPlotBarChart = function(elementId) {
  428. JQPlotLineChart.call(this, elementId);
  429. };
  430. JQPlotBarChart.prototype = new JQPlotLineChart();
  431. JQPlotBarChart.prototype.constructor = JQPlotBarChart;
  432. JQPlotBarChart.prototype.populateOptions = function(dataTable, options) {
  433. var columns = dataTable.getColumns();
  434. var optional = {
  435. axes : {
  436. yaxis : {
  437. label : columns[0].name,
  438. labelRenderer : $.jqplot.CanvasAxisLabelRenderer,
  439. renderer : $.jqplot.CategoryAxisRenderer,
  440. ticks : []
  441. },
  442. xaxis : {
  443. label : (columns.length == 2 ? columns[1].name : 'Values'),
  444. labelRenderer : $.jqplot.CanvasAxisLabelRenderer
  445. }
  446. },
  447. series : [],
  448. seriesDefaults : {
  449. fillToZero : true
  450. }
  451. };
  452. var compulsory = {
  453. seriesDefaults : {
  454. renderer : $.jqplot.BarRenderer,
  455. rendererOptions : {
  456. barDirection : 'horizontal'
  457. }
  458. }
  459. };
  460. $.extend(true, optional, options, compulsory);
  461. if (optional.axes.yaxis.ticks.length == 0) {
  462. var data = dataTable.getData();
  463. for ( var i = 0; i < data.length; i++) {
  464. optional.axes.yaxis.ticks.push(data[i][0].toString());
  465. }
  466. }
  467. if (optional.series.length == 0) {
  468. for ( var i = 1; i < columns.length; i++) {
  469. optional.series.push({
  470. label : columns[i].name.toString()
  471. });
  472. }
  473. }
  474. return optional;
  475. };
  476. /**
  477. * JQPlot pie chart
  478. *
  479. * @param elementId
  480. * id of the div element the chart is drawn in
  481. */
  482. var JQPlotPieChart = function(elementId) {
  483. JQPlotChart.call(this, elementId);
  484. this.validator = PieChart.prototype;
  485. };
  486. JQPlotPieChart.prototype = new JQPlotChart();
  487. JQPlotPieChart.prototype.constructor = JQPlotPieChart;
  488. JQPlotPieChart.prototype.populateOptions = function(dataTable, options) {
  489. var optional = {};
  490. var compulsory = {
  491. seriesDefaults : {
  492. renderer : $.jqplot.PieRenderer
  493. }
  494. };
  495. $.extend(true, optional, options, compulsory);
  496. return optional;
  497. };
  498. JQPlotPieChart.prototype.prepareData = function(dataTable) {
  499. var data = dataTable.getData(), row;
  500. var retData = [];
  501. for ( var i = 0; i < data.length; i++) {
  502. row = data[i];
  503. retData.push([ row[0], row[1] ]);
  504. }
  505. return [ retData ];
  506. };