AbstractChart.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. topSuite("Ext.chart.AbstractChart", ['Ext.chart.*', 'Ext.data.ArrayStore'], function() {
  2. var chart, store;
  3. var Model = Ext.define(null, {
  4. extend: 'Ext.data.Model',
  5. fields: ['label', 'value']
  6. });
  7. function makeStore(rows) {
  8. var data = [],
  9. i;
  10. for (i = 1; i <= rows; ++i) {
  11. data.push({
  12. label: 'Item' + i,
  13. value: i
  14. });
  15. }
  16. store = new Ext.data.Store({
  17. model: Model,
  18. data: data
  19. });
  20. }
  21. beforeEach(function() {
  22. // Tons of warnings regarding Sencha download server in the console
  23. spyOn(Ext.log, 'warn');
  24. });
  25. afterEach(function() {
  26. store = chart = Ext.destroy(chart, store);
  27. // cleanup gradients
  28. Ext.draw.gradient.GradientDefinition.gradients = {};
  29. });
  30. it('is defined', function() {
  31. expect(Ext.chart.AbstractChart).toBeDefined();
  32. });
  33. describe("stores", function() {
  34. function makeChart(storeOnSeries, chartCfg, seriesCfg) {
  35. var cfg = Ext.apply({
  36. xtype: 'cartesian',
  37. axes: [{
  38. type: 'numeric',
  39. position: 'left'
  40. }, {
  41. type: 'category',
  42. position: 'bottom'
  43. }],
  44. animation: false,
  45. series: Ext.apply({
  46. type: 'bar',
  47. xField: 'label',
  48. yField: 'value'
  49. }, seriesCfg)
  50. }, chartCfg);
  51. if (storeOnSeries) {
  52. if (!cfg.series.store) {
  53. cfg.series.store = makeStore(3);
  54. }
  55. } else {
  56. if (!cfg.store) {
  57. cfg.store = makeStore(3);
  58. }
  59. }
  60. chart = new Ext.chart.CartesianChart(cfg);
  61. }
  62. function extractHasListeners(o) {
  63. var ret = {},
  64. key;
  65. for (key in o) {
  66. ret[key] = o[key];
  67. }
  68. delete ret._decr_;
  69. delete ret._incr_;
  70. return ret;
  71. }
  72. describe("store on the chart", function() {
  73. function makeStoreChart(chartCfg, seriesCfg) {
  74. makeChart(false, chartCfg, seriesCfg);
  75. }
  76. describe("configuration", function() {
  77. it("should accept a store id", function() {
  78. store = new Ext.data.Store({
  79. model: Model,
  80. storeId: 'foo'
  81. });
  82. makeStoreChart({
  83. store: 'foo'
  84. });
  85. expect(chart.getStore()).toBe(store);
  86. });
  87. it("should accept a store config", function() {
  88. makeStoreChart({
  89. store: {
  90. model: Model,
  91. data: [{}]
  92. }
  93. });
  94. expect(chart.getStore().getCount()).toBe(1);
  95. expect(chart.getStore().getModel()).toBe(Model);
  96. });
  97. it("should accept a store instance", function() {
  98. makeStore(10);
  99. makeStoreChart({
  100. store: store
  101. });
  102. expect(chart.getStore()).toBe(store);
  103. });
  104. });
  105. describe("destruction", function() {
  106. it("should remove all listeners", function() {
  107. makeStore(3);
  108. var listeners = extractHasListeners(store.hasListeners);
  109. makeStoreChart({
  110. store: store
  111. });
  112. chart.destroy();
  113. expect(extractHasListeners(store.hasListeners)).toEqual(listeners);
  114. });
  115. it("should not destroy the store by default", function() {
  116. makeStore(3);
  117. makeStoreChart({
  118. store: store
  119. });
  120. chart.destroy();
  121. expect(store.destroyed).toBe(false);
  122. });
  123. it("should destroy the store when the store has autoDestroy: true", function() {
  124. makeStore(3);
  125. store.setAutoDestroy(true);
  126. makeStoreChart({
  127. store: store
  128. });
  129. chart.destroy();
  130. expect(store.destroyed).toBe(true);
  131. });
  132. });
  133. describe("change", function () {
  134. it("should fire 'storechange' event", function () {
  135. var isFired = false,
  136. store1 = new Ext.data.Store({
  137. model: Model
  138. }),
  139. store2 = new Ext.data.Store({
  140. model: Model
  141. }),
  142. param1, param2, param3;
  143. makeStoreChart({
  144. store: store1
  145. });
  146. chart.on('storechange', function (chart, newStore, oldStore) {
  147. isFired = true;
  148. param1 = chart;
  149. param2 = newStore;
  150. param3 = oldStore;
  151. });
  152. chart.setStore(store2);
  153. expect(isFired).toEqual(true);
  154. expect(param1).toEqual(chart);
  155. expect(param2).toEqual(store2);
  156. expect(param3).toEqual(store1);
  157. });
  158. });
  159. });
  160. describe("store on the series", function() {
  161. function makeSeriesChart(chartCfg, seriesCfg) {
  162. makeChart(true, chartCfg, seriesCfg);
  163. }
  164. describe("configuration", function() {
  165. it("should accept a store id", function() {
  166. store = new Ext.data.Store({
  167. model: Model,
  168. storeId: 'foo'
  169. });
  170. makeSeriesChart(null, {
  171. store: 'foo'
  172. });
  173. expect(chart.getStore().isEmptyStore).toBe(true);
  174. expect(chart.getSeries()[0].getStore()).toBe(store);
  175. });
  176. it("should accept a store config", function() {
  177. makeSeriesChart(null, {
  178. store: {
  179. model: Model,
  180. data: [{}]
  181. }
  182. });
  183. expect(chart.getStore().isEmptyStore).toBe(true);
  184. expect(chart.getSeries()[0].getStore().getCount()).toBe(1);
  185. expect(chart.getSeries()[0].getStore().getModel()).toBe(Model);
  186. });
  187. it("should accept a store instance", function() {
  188. makeStore(10);
  189. makeSeriesChart(null, {
  190. store: store
  191. });
  192. expect(chart.getStore().isEmptyStore).toBe(true);
  193. expect(chart.getSeries()[0].getStore()).toBe(store);
  194. });
  195. });
  196. describe("destruction", function() {
  197. it("should remove all listeners", function() {
  198. makeStore(3);
  199. var listeners = extractHasListeners(store.hasListeners);
  200. makeSeriesChart(null, {
  201. store: store
  202. });
  203. chart.destroy();
  204. expect(extractHasListeners(store.hasListeners)).toEqual(listeners);
  205. });
  206. it("should not destroy the store by default", function() {
  207. makeStore(3);
  208. makeSeriesChart(null, {
  209. store: store
  210. });
  211. chart.destroy();
  212. expect(store.destroyed).toBe(false);
  213. });
  214. it("should destroy the store when the store has autoDestroy: true", function() {
  215. makeStore(3);
  216. store.setAutoDestroy(true);
  217. makeSeriesChart(null, {
  218. store: store
  219. });
  220. chart.destroy();
  221. expect(store.destroyed).toBe(true);
  222. });
  223. it("should not destroy the store when destroying the series by default", function() {
  224. makeStore(3);
  225. makeSeriesChart(null, {
  226. store: store
  227. });
  228. chart.setSeries([{
  229. type: 'bar',
  230. xField: 'label',
  231. yField: 'value'
  232. }]);
  233. expect(store.destroyed).toBe(false);
  234. });
  235. it("should destroy the store when destroying the series when the store has autoDestroy: true", function() {
  236. makeStore(3);
  237. store.setAutoDestroy(true);
  238. makeSeriesChart(null, {
  239. store: store
  240. });
  241. chart.setSeries([{
  242. type: 'bar',
  243. xField: 'label',
  244. yField: 'value'
  245. }]);
  246. expect(store.destroyed).toBe(true);
  247. });
  248. });
  249. describe("change", function () {
  250. it("should fire 'storechange' event", function () {
  251. var isFired = false,
  252. store1 = new Ext.data.Store({
  253. model: Model
  254. }),
  255. store2 = new Ext.data.Store({
  256. model: Model
  257. }),
  258. series, param1, param2, param3;
  259. makeSeriesChart(null, {
  260. store: store1
  261. });
  262. series = chart.getSeries()[0];
  263. series.on('storechange', function (series, newStore, oldStore) {
  264. isFired = true;
  265. param1 = series;
  266. param2 = newStore;
  267. param3 = oldStore;
  268. });
  269. series.setStore(store2);
  270. expect(isFired).toEqual(true);
  271. expect(param1).toEqual(series);
  272. expect(param2).toEqual(store2);
  273. expect(param3).toEqual(store1);
  274. });
  275. });
  276. });
  277. });
  278. describe('adding and removing series', function() {
  279. var layoutDone;
  280. beforeEach(function() {
  281. store = new Ext.data.Store({
  282. fields: ['x', 'y', 'z'],
  283. data: [
  284. {x: 0, y: 0, z: 0},
  285. {x: 1, y: 1, z: 1}
  286. ]
  287. });
  288. chart = new Ext.chart.CartesianChart({
  289. renderTo: Ext.getBody(),
  290. width: 400,
  291. height: 400,
  292. store: store,
  293. axes: [{
  294. position: 'left',
  295. type: 'numeric'
  296. }, {
  297. position: 'bottom',
  298. type: 'numeric'
  299. }],
  300. listeners: {
  301. layout: function () {
  302. layoutDone = true;
  303. }
  304. }
  305. });
  306. });
  307. afterEach(function () {
  308. layoutDone = false;
  309. });
  310. it('should start with no series', function() {
  311. expect(chart.getSeries().length).toBe(0);
  312. });
  313. it('should add and remove series using setSeries', function() {
  314. var series;
  315. waitsFor(function () {
  316. return layoutDone;
  317. });
  318. runs(function () {
  319. layoutDone = false;
  320. chart.setSeries([{
  321. type: 'line',
  322. xField: 'x',
  323. yField: 'y',
  324. id: 'xySeries'
  325. }]);
  326. });
  327. waitsFor(function () {
  328. return layoutDone;
  329. });
  330. runs(function () {
  331. layoutDone = false;
  332. series = chart.getSeries();
  333. expect(series.length).toBe(1);
  334. expect(series[0].getId()).toBe('xySeries');
  335. chart.setSeries([{
  336. type: 'line',
  337. xField: 'x',
  338. yField: 'z',
  339. id: 'xzSeries'
  340. }]);
  341. });
  342. waitsFor(function () {
  343. return layoutDone;
  344. });
  345. runs(function () {
  346. layoutDone = false;
  347. series = chart.getSeries();
  348. expect(series.length).toBe(1);
  349. expect(series[0].getId()).toBe('xzSeries');
  350. });
  351. });
  352. it('should add series using addSeries', function() {
  353. var series;
  354. waitsFor(function () {
  355. return layoutDone;
  356. });
  357. runs(function () {
  358. layoutDone = false;
  359. chart.addSeries([{
  360. type: 'line',
  361. xField: 'x',
  362. yField: 'y',
  363. id: 'xySeries'
  364. }]);
  365. });
  366. waitsFor(function () {
  367. return layoutDone;
  368. });
  369. runs(function () {
  370. layoutDone = false;
  371. series = chart.getSeries();
  372. expect(series.length).toBe(1);
  373. expect(series[0].getId()).toBe('xySeries');
  374. chart.addSeries({
  375. type: 'line',
  376. xField: 'x',
  377. yField: 'z',
  378. id: 'xzSeries'
  379. });
  380. });
  381. waitsFor(function () {
  382. return layoutDone;
  383. });
  384. runs(function () {
  385. layoutDone = false;
  386. series = chart.getSeries();
  387. expect(series.length).toBe(2);
  388. expect(series[0].getId()).toBe('xySeries');
  389. expect(series[1].getId()).toBe('xzSeries');
  390. });
  391. });
  392. it('should remove series using removeSeries', function() {
  393. var series;
  394. waitsFor(function () {
  395. return layoutDone;
  396. });
  397. runs(function () {
  398. layoutDone = false;
  399. chart.addSeries([{
  400. type: 'line',
  401. xField: 'x',
  402. yField: 'y',
  403. id: 'xySeries'
  404. }, {
  405. type: 'line',
  406. xField: 'x',
  407. yField: 'z',
  408. id: 'xzSeries'
  409. }]);
  410. });
  411. waitsFor(function () {
  412. return layoutDone;
  413. });
  414. runs(function () {
  415. layoutDone = false;
  416. series = chart.getSeries();
  417. expect(series.length).toBe(2);
  418. expect(series[0].getId()).toBe('xySeries');
  419. expect(series[1].getId()).toBe('xzSeries');
  420. // Remove Series id "xySeries", should leave only "xzSeries"
  421. chart.removeSeries('xySeries');
  422. });
  423. waitsFor(function () {
  424. return layoutDone;
  425. });
  426. runs(function () {
  427. layoutDone = false;
  428. series = chart.getSeries();
  429. expect(series.length).toBe(1);
  430. expect(series[0].getId()).toBe('xzSeries');
  431. // Remove a Series by specifying the instance should leav no Series
  432. chart.removeSeries(series[0]);
  433. });
  434. waitsFor(function () {
  435. return layoutDone;
  436. });
  437. runs(function () {
  438. layoutDone = false;
  439. expect(chart.getSeries().length).toBe(0);
  440. });
  441. });
  442. });
  443. describe('getInteraction', function () {
  444. it("should return a correct interaction based on its type", function () {
  445. makeStore(3);
  446. chart = new Ext.chart.CartesianChart({
  447. store: store,
  448. interactions: [
  449. {
  450. type: 'itemhighlight'
  451. },
  452. {
  453. type: 'itemedit'
  454. },
  455. {
  456. type: 'crosszoom'
  457. }
  458. ],
  459. axes: [{
  460. type: 'numeric',
  461. position: 'left'
  462. }, {
  463. type: 'category',
  464. position: 'bottom'
  465. }],
  466. series: {
  467. type: 'bar',
  468. xField: 'label',
  469. yField: 'value'
  470. }
  471. });
  472. var itemhighlight = chart.getInteraction('itemhighlight'),
  473. crosszoom = chart.getInteraction('crosszoom'),
  474. itemedit = chart.getInteraction('itemedit');
  475. expect(itemhighlight.isItemHighlight).toBe(true);
  476. expect(crosszoom.isCrossZoom).toBe(true);
  477. expect(itemedit.isItemEdit).toBe(true);
  478. });
  479. });
  480. describe('processData', function () {
  481. it('should refresh legend store', function () {
  482. var layoutEnd, processDataSpy;
  483. runs(function () {
  484. chart = new Ext.chart.PolarChart({
  485. animation: false,
  486. renderTo: document.body,
  487. width: 400,
  488. height: 400,
  489. legend: {
  490. docked: 'right'
  491. },
  492. store: {
  493. data: [{
  494. "name": "A",
  495. "data1": 1
  496. }, {
  497. "name": "B",
  498. "data1": 2
  499. }]
  500. },
  501. series: {
  502. type: 'pie3d',
  503. angleField: 'data1',
  504. label: {
  505. field: 'name'
  506. }
  507. },
  508. listeners: {
  509. layout: function () {
  510. layoutEnd = true;
  511. }
  512. }
  513. });
  514. });
  515. waitsFor(function () {
  516. return layoutEnd;
  517. });
  518. runs(function () {
  519. layoutEnd = false;
  520. expect(chart.getLegend().getStore().getAt(0).get('name')).toBe('A');
  521. processDataSpy = spyOn(chart, 'processData').andCallThrough();
  522. chart.getStore().loadData([{
  523. name: 'X',
  524. data1: 24
  525. }, {
  526. name: 'Y',
  527. data1: 25
  528. }]);
  529. expect(processDataSpy).toHaveBeenCalled();
  530. expect(chart.getLegend().getStore().getAt(0).get('name')).toBe('X');
  531. });
  532. });
  533. });
  534. describe("update gradients", function () {
  535. beforeEach(function () {
  536. makeStore(3);
  537. chart = new Ext.chart.CartesianChart({
  538. store: store,
  539. axes: [{
  540. type: 'numeric',
  541. position: 'left'
  542. }, {
  543. type: 'category',
  544. position: 'bottom'
  545. }],
  546. series: {
  547. type: 'bar',
  548. xField: 'label',
  549. yField: 'value',
  550. style: {
  551. fillStyle: 'url(#foo)'
  552. }
  553. },
  554. gradients: [{
  555. id: 'foo',
  556. type: 'linear',
  557. degrees: 270,
  558. stops: [{
  559. offset: 0,
  560. color: '#78C5D6'
  561. }, {
  562. offset: 0.56,
  563. color: '#F5D63D'
  564. }, {
  565. offset: 1,
  566. color: '#BF62A6'
  567. }]
  568. }]
  569. });
  570. });
  571. it("should create sprites with the correct gradient applied", function () {
  572. var seriesSprite = chart.getSeries()[0].sprites[0],
  573. fillStyle = seriesSprite.attr.fillStyle;
  574. expect(typeof fillStyle).toBe('object');
  575. expect(fillStyle.isGradient).toBe(true);
  576. });
  577. it("should update sprites when gradient has been updated", function () {
  578. var seriesSprite = chart.getSeries()[0].sprites[0],
  579. fillStyle = seriesSprite.attr.fillStyle,
  580. newGradient = [{
  581. id: 'foo',
  582. type: 'linear',
  583. degrees: 270,
  584. stops: [{
  585. offset: 0,
  586. color: '#000000'
  587. }, {
  588. offset: 0.56,
  589. color: '#F5D63D'
  590. }, {
  591. offset: 1,
  592. color: '#000000'
  593. }]
  594. }];
  595. // first, make sure correct color is applied on initial construction
  596. expect(fillStyle.getStops()[0].color).toBe('#78c5d6');
  597. // now let's update the draw container with a slightly different gradient with the same id
  598. chart.setGradients(newGradient);
  599. // theme should get updated; check sprite again
  600. seriesSprite = chart.getSeries()[0].sprites[0];
  601. fillStyle = seriesSprite.attr.fillStyle;
  602. // should have different colors for updated gradient
  603. expect(fillStyle.getStops()[0].color).toBe('#000000');
  604. });
  605. });
  606. describe('legend render checking if legend is getting rendered along with Chart', function () {
  607. afterEach(function() {
  608. chart = Ext.destroy(chart);
  609. });
  610. it("legend should be rendered", function () {
  611. runs(function () {
  612. chart = Ext.create({
  613. xtype: 'cartesian',
  614. renderTo: Ext.getBody(),
  615. width: 400,
  616. height: 400,
  617. innerPadding: 10,
  618. store: {
  619. fields: ['name', 'data1'],
  620. data: [[{
  621. age: '0',
  622. value: 400
  623. }, {
  624. age: '2',
  625. value: 150
  626. }, {
  627. age: '4',
  628. value: 120
  629. }, {
  630. age: '6',
  631. value: 100
  632. }, {
  633. age: '8',
  634. value: 1500
  635. }]]
  636. },
  637. legend: {
  638. type: 'dom',
  639. docked: 'right'
  640. },
  641. series: {
  642. type: 'pie',
  643. highlight: true,
  644. angleField: 'data1',
  645. donut: 30
  646. }
  647. });
  648. });
  649. runs(function () {
  650. var legend = chart.getLegend();
  651. expect(legend.rendered).toBe(true);
  652. });
  653. });
  654. });
  655. });