chart.js 18 KB

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