ScatterRenderer.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. Ext.require('Ext.chart.*');
  2. Ext.require(['Ext.Window', 'Ext.fx.target.Sprite', 'Ext.layout.container.Fit', 'Ext.window.MessageBox']);
  3. var Renderers = {};
  4. (function() {
  5. var ColorManager = {
  6. rgbToHsv: function(rgb) {
  7. var r = rgb[0] / 255,
  8. g = rgb[1] / 255,
  9. b = rgb[2] / 255,
  10. rd = Math.round,
  11. minVal = Math.min(r, g, b),
  12. maxVal = Math.max(r, g, b),
  13. delta = maxVal - minVal,
  14. h = 0, s = 0, v = 0,
  15. deltaRgb;
  16. v = maxVal;
  17. if (delta == 0) {
  18. return [0, 0, v];
  19. } else {
  20. s = delta / maxVal;
  21. deltaRgb = {
  22. r: (((maxVal - r) / 6) + (delta / 2)) / delta,
  23. g: (((maxVal - g) / 6) + (delta / 2)) / delta,
  24. b: (((maxVal - b) / 6) + (delta / 2)) / delta
  25. };
  26. if (r == maxVal) {
  27. h = deltaRgb.b - deltaRgb.g;
  28. } else if (g == maxVal) {
  29. h = (1 / 3) + deltaRgb.r - deltaRgb.b;
  30. } else if (b == maxVal) {
  31. h = (2 / 3) + deltaRgb.g - deltaRgb.r;
  32. }
  33. //handle edge cases for hue
  34. if (h < 0) {
  35. h += 1;
  36. }
  37. if (h > 1) {
  38. h -= 1;
  39. }
  40. }
  41. h = rd(h * 360);
  42. s = rd(s * 100);
  43. v = rd(v * 100);
  44. return [h, s, v];
  45. },
  46. hsvToRgb : function(hsv) {
  47. var h = hsv[0] / 360,
  48. s = hsv[1] / 100,
  49. v = hsv[2] / 100,
  50. r, g, b, rd = Math.round;
  51. if (s == 0) {
  52. v *= 255;
  53. return [v, v, v];
  54. } else {
  55. var vh = h * 6,
  56. vi = vh >> 0,
  57. v1 = v * (1 - s),
  58. v2 = v * (1 - s * (vh - vi)),
  59. v3 = v * (1 - s * (1 - (vh - vi)));
  60. switch(vi) {
  61. case 0:
  62. r = v; g = v3; b = v1;
  63. break;
  64. case 1:
  65. r = v2; g = v; b = v1;
  66. break;
  67. case 2:
  68. r = v1; g = v; b = v3;
  69. break;
  70. case 3:
  71. r = v1; g = v2; b = v;
  72. break;
  73. case 4:
  74. r = v3; g = v1; b = v;
  75. break;
  76. default:
  77. r = v; g = v1; b = v2;
  78. }
  79. return [rd(r * 255),
  80. rd(g * 255),
  81. rd(b * 255)];
  82. }
  83. }
  84. };
  85. //Generic number interpolator
  86. var delta = function(x, y, a, b, theta) {
  87. return a + (b - a) * (y - theta) / (y - x);
  88. };
  89. //Add renderer methods.
  90. Ext.apply(Renderers, {
  91. color: function(fieldName, minColor, maxColor, minValue, maxValue) {
  92. var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
  93. minColorMatch = minColor.match(re),
  94. maxColorMatch = maxColor.match(re),
  95. interpolate = function(theta) {
  96. return [ delta(minValue, maxValue, minColor[0], maxColor[0], theta),
  97. delta(minValue, maxValue, minColor[1], maxColor[1], theta),
  98. delta(minValue, maxValue, minColor[2], maxColor[2], theta) ];
  99. };
  100. minColor = ColorManager.rgbToHsv([ +minColorMatch[1], +minColorMatch[2], +minColorMatch[3] ]);
  101. maxColor = ColorManager.rgbToHsv([ +maxColorMatch[1], +maxColorMatch[2], +maxColorMatch[3] ]);
  102. //Return the renderer
  103. return function(sprite, record, attr, index, store) {
  104. var value = +record.get(fieldName),
  105. rgb = ColorManager.hsvToRgb(interpolate(value)),
  106. rgbString = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
  107. return Ext.apply(attr, {
  108. fill: rgbString
  109. });
  110. };
  111. },
  112. grayscale: function(fieldName, minColor, maxColor, minValue, maxValue) {
  113. var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
  114. minColorMatch = minColor.match(re),
  115. maxColorMatch = maxColor.match(re),
  116. interpolate = function(theta) {
  117. var ans = delta(minValue, maxValue, +minColorMatch[1], +maxColorMatch[1], theta) >> 0;
  118. return [ ans, ans, ans ];
  119. };
  120. //Return the renderer
  121. return function(sprite, record, attr, index, store) {
  122. var value = +record.get(fieldName),
  123. rgb = interpolate(value),
  124. rgbString = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
  125. return Ext.apply(attr, {
  126. fill: rgbString,
  127. strokeFill: 'rgb(0, 0, 0)'
  128. });
  129. };
  130. },
  131. radius: function(fieldName, minRadius, maxRadius, minValue, maxValue) {
  132. var interpolate = function(theta) {
  133. return delta(minValue, maxValue, minRadius, maxRadius, theta);
  134. };
  135. //Return the renderer
  136. return function(sprite, record, attr, index, store) {
  137. var value = +record.get(fieldName),
  138. radius = interpolate(value);
  139. return Ext.apply(attr, {
  140. radius: radius,
  141. size: radius
  142. });
  143. };
  144. }
  145. });
  146. })();
  147. Ext.onReady(function () {
  148. //current renderer configuration
  149. var rendererConfiguration = {
  150. xField: 'data1',
  151. yField: 'data2',
  152. color: false,
  153. colorFrom: 'rgb(250, 20, 20)',
  154. colorTo: 'rgb(127, 0, 240)',
  155. scale: false,
  156. scaleFrom: 'rgb(20, 20, 20)',
  157. scaleTo: 'rgb(220, 220, 220)',
  158. radius: false,
  159. radiusSize: 50
  160. };
  161. //update the visualization with the new renderer configuration
  162. function refresh() {
  163. var chart = Ext.getCmp('chartCmp'),
  164. series = chart.series.items,
  165. len = series.length,
  166. rc = rendererConfiguration,
  167. color, grayscale, radius, s;
  168. for(var i = 0; i < len; i++) {
  169. s = series[i];
  170. s.xField = rc.xField;
  171. s.yField = rc.yField;
  172. color = rc.color? Renderers.color(rc.color, rc.colorFrom, rc.colorTo, 0, 100) : function(a, b, attr) { return attr; };
  173. grayscale = rc.grayscale? Renderers.grayscale(rc.grayscale, rc.scaleFrom, rc.scaleTo, 0, 100) : function(a, b, attr) { return attr; };
  174. radius = rc.radius? Renderers.radius(rc.radius, 10, rc.radiusSize, 0, 100) : function(a, b, attr) { return attr; };
  175. s.renderer = function(sprite, record, attr, index, store) {
  176. return radius(sprite, record, grayscale(sprite, record, color(sprite, record, attr, index, store), index, store), index, store);
  177. };
  178. }
  179. chart.redraw();
  180. }
  181. //form selection callbacks/handlers.
  182. var xAxisHandler = function(elem) {
  183. var xField = elem.text;
  184. rendererConfiguration.xField = xField;
  185. refresh();
  186. };
  187. var yAxisHandler = function(elem) {
  188. var yField = elem.text;
  189. rendererConfiguration.yField = yField;
  190. refresh();
  191. };
  192. var colorVariableHandler = function(elem) {
  193. var color = elem.text;
  194. rendererConfiguration.color = color;
  195. rendererConfiguration.grayscale = false;
  196. refresh();
  197. };
  198. var grayscaleVariableHandler = function(elem) {
  199. var color = elem.text;
  200. rendererConfiguration.grayscale = color;
  201. rendererConfiguration.color = false;
  202. refresh();
  203. };
  204. var scaleFromHandler = function(elem) {
  205. var from = elem.text;
  206. rendererConfiguration.scaleFrom = from;
  207. refresh();
  208. };
  209. var scaleToHandler = function(elem) {
  210. var to = elem.text;
  211. rendererConfiguration.scaleTo = to;
  212. refresh();
  213. };
  214. var colorFromHandler = function(elem) {
  215. var from = elem.text;
  216. rendererConfiguration.colorFrom = from;
  217. refresh();
  218. };
  219. var colorToHandler = function(elem) {
  220. var to = elem.text;
  221. rendererConfiguration.colorTo = to;
  222. refresh();
  223. };
  224. var radiusHandler = function(elem) {
  225. var radius = elem.text;
  226. rendererConfiguration.radius = radius;
  227. refresh();
  228. };
  229. var radiusSizeHandler = function(elem) {
  230. var radius = elem.text;
  231. rendererConfiguration.radiusSize = parseInt(radius, 10);
  232. refresh();
  233. };
  234. var xAxisMenu = Ext.create('Ext.menu.Menu', {
  235. id: 'xAxisMenu',
  236. items: [ {
  237. text: 'data1',
  238. handler: xAxisHandler,
  239. checked: true,
  240. group: 'x'
  241. },
  242. {
  243. text: 'data2',
  244. handler: xAxisHandler,
  245. checked: false,
  246. group: 'x'
  247. },
  248. {
  249. text: 'data3',
  250. handler: xAxisHandler,
  251. checked: false,
  252. group: 'x'
  253. } ]
  254. });
  255. var yAxisMenu = Ext.create('Ext.menu.Menu', {
  256. id: 'yAxisMenu',
  257. items: [ {
  258. text: 'data1',
  259. handler: yAxisHandler,
  260. checked: false,
  261. group: 'y'
  262. },
  263. {
  264. text: 'data2',
  265. handler: yAxisHandler,
  266. checked: true,
  267. group: 'y'
  268. },
  269. {
  270. text: 'data3',
  271. handler: yAxisHandler,
  272. checked: false,
  273. group: 'y'
  274. } ]
  275. });
  276. var colorMenu = Ext.create('Ext.menu.Menu', {
  277. id: 'colorMenu',
  278. items: [ { text: 'data1', handler: colorVariableHandler, checked: false, group: 'color' },
  279. { text: 'data2', handler: colorVariableHandler, checked: false, group: 'color' },
  280. { text: 'data3', handler: colorVariableHandler, checked: false, group: 'color' },
  281. { text: 'Color From',
  282. menu: {
  283. items: [{ text: 'rgb(250, 20, 20)', handler: colorToHandler, checked: true, group: 'colorrange' },
  284. { text: 'rgb(20, 250, 20)', handler: colorToHandler, checked: false, group: 'colorrange' },
  285. { text: 'rgb(20, 20, 250)', handler: colorToHandler, checked: false, group: 'colorrange' },
  286. { text: 'rgb(127, 0, 240)', handler: colorFromHandler, checked: false, group: 'colorrange' },
  287. { text: 'rgb(213, 70, 121)', handler: colorToHandler, checked: false, group: 'colorrange' },
  288. { text: 'rgb(44, 153, 201)', handler: colorFromHandler, checked: false, group: 'colorrange' },
  289. { text: 'rgb(146, 6, 157)', handler: colorFromHandler, checked: false, group: 'colorrange' },
  290. { text: 'rgb(49, 149, 0)', handler: colorFromHandler, checked: false, group: 'colorrange' },
  291. { text: 'rgb(249, 153, 0)', handler: colorFromHandler, checked: false, group: 'colorrange' }]
  292. }
  293. },
  294. { text: 'Color To',
  295. menu: {
  296. items: [{ text: 'rgb(250, 20, 20)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  297. { text: 'rgb(20, 250, 20)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  298. { text: 'rgb(20, 20, 250)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  299. { text: 'rgb(127, 0, 220)', handler: colorFromHandler, checked: true, group: 'tocolorrange' },
  300. { text: 'rgb(213, 70, 121)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  301. { text: 'rgb(44, 153, 201)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  302. { text: 'rgb(146, 6, 157)' , handler: colorToHandler, checked: false, group: 'tocolorrange' },
  303. { text: 'rgb(49, 149, 0)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
  304. { text: 'rgb(249, 153, 0)', handler: colorToHandler, checked: false, group: 'tocolorrange' }]
  305. }
  306. }
  307. ]
  308. });
  309. var grayscaleMenu = Ext.create('Ext.menu.Menu', {
  310. id: 'grayscaleMenu',
  311. items: [ { text: 'data1', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
  312. { text: 'data2', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
  313. { text: 'data3', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
  314. { text: 'Scale From',
  315. menu: {
  316. items: [{ text: 'rgb(20, 20, 20)', handler: scaleFromHandler, checked: true, group: 'gsrange' },
  317. { text: 'rgb(80, 80, 80)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
  318. { text: 'rgb(120, 120, 120)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
  319. { text: 'rgb(180, 180, 180)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
  320. { text: 'rgb(220, 220, 220)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
  321. { text: 'rgb(250, 250, 250)', handler: scaleFromHandler, checked: false, group: 'gsrange' }]
  322. }
  323. },
  324. { text: 'Scale To',
  325. menu: {
  326. items: [{ text: 'rgb(20, 20, 20)', handler: scaleToHandler, checked: false, group: 'togsrange' },
  327. { text: 'rgb(80, 80, 80)', handler: scaleToHandler, checked: false, group: 'togsrange' },
  328. { text: 'rgb(120, 120, 120)', handler: scaleToHandler, checked: false, group: 'togsrange' },
  329. { text: 'rgb(180, 180, 180)', handler: scaleToHandler, checked: false, group: 'togsrange' },
  330. { text: 'rgb(220, 220, 220)', handler: scaleToHandler, checked: true, group: 'togsrange' },
  331. { text: 'rgb(250, 250, 250)', handler: scaleToHandler, checked: false, group: 'togsrange' }]
  332. }
  333. }
  334. ]
  335. });
  336. var radiusMenu = Ext.create('Ext.menu.Menu', {
  337. id: 'radiusMenu',
  338. style: {
  339. overflow: 'visible' // For the Combo popup
  340. },
  341. items: [ { text: 'data1', handler: radiusHandler, checked: false, group: 'radius' },
  342. { text: 'data2', handler: radiusHandler, checked: false, group: 'radius' },
  343. { text: 'data3', handler: radiusHandler, checked: false, group: 'radius' },
  344. { text: 'Max Radius',
  345. menu: {
  346. items: [{ text: '20', handler: radiusSizeHandler, checked: false, group: 'sradius' },
  347. { text: '30', handler: radiusSizeHandler, checked: false, group: 'sradius' },
  348. { text: '40', handler: radiusSizeHandler, checked: false, group: 'sradius' },
  349. { text: '50', handler: radiusSizeHandler, checked: true, group: 'sradius' },
  350. { text: '60', handler: radiusSizeHandler, checked: false, group: 'sradius' }]
  351. }
  352. }
  353. ]
  354. });
  355. var chart = Ext.create('Ext.chart.Chart', {
  356. id: 'chartCmp',
  357. xtype: 'chart',
  358. style: 'background:#fff',
  359. animate: true,
  360. store: store1,
  361. axes: false,
  362. insetPadding: 50,
  363. series: [{
  364. type: 'scatter',
  365. axis: false,
  366. xField: 'data1',
  367. yField: 'data2',
  368. color: '#ccc',
  369. markerConfig: {
  370. type: 'circle',
  371. radius: 20,
  372. size: 20
  373. }
  374. }]
  375. });
  376. var win = Ext.create('Ext.Window', {
  377. width: 800,
  378. height: 600,
  379. minHeight: 400,
  380. minWidth: 650,
  381. hidden: false,
  382. maximizable: true,
  383. title: 'Scatter Chart Renderer',
  384. renderTo: Ext.getBody(),
  385. layout: 'fit',
  386. tbar: [{
  387. text: 'Save Chart',
  388. handler: function() {
  389. Ext.MessageBox.confirm('Confirm Download', 'Would you like to download the chart as an image?', function(choice){
  390. if(choice == 'yes'){
  391. chart.save({
  392. type: 'image/png'
  393. });
  394. }
  395. });
  396. }
  397. },
  398. {
  399. text: 'Reload Data',
  400. handler: function() {
  401. store1.loadData(generateData());
  402. }
  403. },
  404. {
  405. text: 'Select X Axis',
  406. menu: xAxisMenu
  407. },
  408. {
  409. text: 'Select Y Axis',
  410. menu: yAxisMenu
  411. },
  412. {
  413. text: 'Select Color',
  414. menu: colorMenu
  415. },
  416. {
  417. text: 'Select Grayscale',
  418. menu: grayscaleMenu
  419. },
  420. {
  421. text: 'Select Radius',
  422. menu: radiusMenu
  423. }
  424. ],
  425. items: chart
  426. });
  427. });