Element.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.charting.Element"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.charting.Element"] = true;
  8. dojo.provide("dojox.charting.Element");
  9. dojo.require("dojox.gfx");
  10. dojo.declare("dojox.charting.Element", null, {
  11. // summary:
  12. // A base class that is used to build other elements of a chart, such as
  13. // a series.
  14. // chart: dojox.charting.Chart2D
  15. // The parent chart for this element.
  16. // group: dojox.gfx.Group
  17. // The visual GFX group representing this element.
  18. // htmlElement: Array
  19. // Any DOMNodes used as a part of this element (such as HTML-based labels).
  20. // dirty: Boolean
  21. // A flag indicating whether or not this element needs to be rendered.
  22. chart: null,
  23. group: null,
  24. htmlElements: null,
  25. dirty: true,
  26. constructor: function(chart){
  27. // summary:
  28. // Creates a new charting element.
  29. // chart: dojox.charting.Chart2D
  30. // The chart that this element belongs to.
  31. this.chart = chart;
  32. this.group = null;
  33. this.htmlElements = [];
  34. this.dirty = true;
  35. this.trailingSymbol = "...";
  36. this._events = [];
  37. },
  38. createGroup: function(creator){
  39. // summary:
  40. // Convenience function to create a new dojox.gfx.Group.
  41. // creator: dojox.gfx.Surface?
  42. // An optional surface in which to create this group.
  43. // returns: dojox.charting.Element
  44. // A reference to this object for functional chaining.
  45. if(!creator){ creator = this.chart.surface; }
  46. if(!this.group){
  47. this.group = creator.createGroup();
  48. }
  49. return this; // dojox.charting.Element
  50. },
  51. purgeGroup: function(){
  52. // summary:
  53. // Clear any elements out of our group, and destroy the group.
  54. // returns: dojox.charting.Element
  55. // A reference to this object for functional chaining.
  56. this.destroyHtmlElements();
  57. if(this.group){
  58. this.group.clear();
  59. this.group.removeShape();
  60. this.group = null;
  61. }
  62. this.dirty = true;
  63. if(this._events.length){
  64. dojo.forEach(this._events, function(item){
  65. item.shape.disconnect(item.handle);
  66. });
  67. this._events = [];
  68. }
  69. return this; // dojox.charting.Element
  70. },
  71. cleanGroup: function(creator){
  72. // summary:
  73. // Clean any elements (HTML or GFX-based) out of our group, and create a new one.
  74. // creator: dojox.gfx.Surface?
  75. // An optional surface to work with.
  76. // returns: dojox.charting.Element
  77. // A reference to this object for functional chaining.
  78. this.destroyHtmlElements();
  79. if(!creator){ creator = this.chart.surface; }
  80. if(this.group){
  81. this.group.clear();
  82. }else{
  83. this.group = creator.createGroup();
  84. }
  85. this.dirty = true;
  86. return this; // dojox.charting.Element
  87. },
  88. destroyHtmlElements: function(){
  89. // summary:
  90. // Destroy any DOMNodes that may have been created as a part of this element.
  91. if(this.htmlElements.length){
  92. dojo.forEach(this.htmlElements, dojo.destroy);
  93. this.htmlElements = [];
  94. }
  95. },
  96. destroy: function(){
  97. // summary:
  98. // API addition to conform to the rest of the Dojo Toolkit's standard.
  99. this.purgeGroup();
  100. },
  101. //text utilities
  102. getTextWidth: function(s, font){
  103. return dojox.gfx._base._getTextBox(s, {font: font}).w || 0;
  104. },
  105. getTextWithLimitLength: function(s, font, limitWidth, truncated){
  106. // summary:
  107. // Get the truncated string based on the limited width in px(dichotomy algorithm)
  108. // s: String?
  109. // candidate text.
  110. // font: String?
  111. // text's font style.
  112. // limitWidth: Number?
  113. // text limited width in px.
  114. // truncated: Boolean?
  115. // whether the input text(s) has already been truncated.
  116. // returns: Object
  117. // {
  118. // text: processed text, maybe truncated or not
  119. // truncated: whether text has been truncated
  120. // }
  121. if (!s || s.length <= 0) {
  122. return {
  123. text: "",
  124. truncated: truncated || false
  125. };
  126. }
  127. if(!limitWidth || limitWidth <= 0){
  128. return {
  129. text: s,
  130. truncated: truncated || false
  131. };
  132. }
  133. var delta = 2,
  134. //golden section for dichotomy algorithm
  135. trucPercentage = 0.618,
  136. minStr = s.substring(0,1) + this.trailingSymbol,
  137. minWidth = this.getTextWidth(minStr, font);
  138. if (limitWidth <= minWidth) {
  139. return {
  140. text: minStr,
  141. truncated: true
  142. };
  143. }
  144. var width = this.getTextWidth(s, font);
  145. if(width <= limitWidth){
  146. return {
  147. text: s,
  148. truncated: truncated || false
  149. };
  150. }else{
  151. var begin = 0,
  152. end = s.length;
  153. while(begin < end){
  154. if(end - begin <= delta ){
  155. while (this.getTextWidth(s.substring(0, begin) + this.trailingSymbol, font) > limitWidth) {
  156. begin -= 1;
  157. }
  158. return {
  159. text: (s.substring(0,begin) + this.trailingSymbol),
  160. truncated: true
  161. };
  162. }
  163. var index = begin + Math.round((end - begin) * trucPercentage),
  164. widthIntercepted = this.getTextWidth(s.substring(0, index), font);
  165. if(widthIntercepted < limitWidth){
  166. begin = index;
  167. end = end;
  168. }else{
  169. begin = begin;
  170. end = index;
  171. }
  172. }
  173. }
  174. },
  175. getTextWithLimitCharCount: function(s, font, wcLimit, truncated){
  176. // summary:
  177. // Get the truncated string based on the limited character count(dichotomy algorithm)
  178. // s: String?
  179. // candidate text.
  180. // font: String?
  181. // text's font style.
  182. // wcLimit: Number?
  183. // text limited character count.
  184. // truncated: Boolean?
  185. // whether the input text(s) has already been truncated.
  186. // returns: Object
  187. // {
  188. // text: processed text, maybe truncated or not
  189. // truncated: whether text has been truncated
  190. // }
  191. if (!s || s.length <= 0) {
  192. return {
  193. text: "",
  194. truncated: truncated || false
  195. };
  196. }
  197. if(!wcLimit || wcLimit <= 0 || s.length <= wcLimit){
  198. return {
  199. text: s,
  200. truncated: truncated || false
  201. };
  202. }
  203. return {
  204. text: s.substring(0, wcLimit) + this.trailingSymbol,
  205. truncated: true
  206. };
  207. },
  208. // fill utilities
  209. _plotFill: function(fill, dim, offsets){
  210. // process a plot-wide fill
  211. if(!fill || !fill.type || !fill.space){
  212. return fill;
  213. }
  214. var space = fill.space;
  215. switch(fill.type){
  216. case "linear":
  217. if(space === "plot" || space === "shapeX" || space === "shapeY"){
  218. // clone a fill so we can modify properly directly
  219. fill = dojox.gfx.makeParameters(dojox.gfx.defaultLinearGradient, fill);
  220. fill.space = space;
  221. // process dimensions
  222. if(space === "plot" || space === "shapeX"){
  223. // process Y
  224. var span = dim.height - offsets.t - offsets.b;
  225. fill.y1 = offsets.t + span * fill.y1 / 100;
  226. fill.y2 = offsets.t + span * fill.y2 / 100;
  227. }
  228. if(space === "plot" || space === "shapeY"){
  229. // process X
  230. var span = dim.width - offsets.l - offsets.r;
  231. fill.x1 = offsets.l + span * fill.x1 / 100;
  232. fill.x2 = offsets.l + span * fill.x2 / 100;
  233. }
  234. }
  235. break;
  236. case "radial":
  237. if(space === "plot"){
  238. // this one is used exclusively for scatter charts
  239. // clone a fill so we can modify properly directly
  240. fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
  241. fill.space = space;
  242. // process both dimensions
  243. var spanX = dim.width - offsets.l - offsets.r,
  244. spanY = dim.height - offsets.t - offsets.b;
  245. fill.cx = offsets.l + spanX * fill.cx / 100;
  246. fill.cy = offsets.t + spanY * fill.cy / 100;
  247. fill.r = fill.r * Math.sqrt(spanX * spanX + spanY * spanY) / 200;
  248. }
  249. break;
  250. case "pattern":
  251. if(space === "plot" || space === "shapeX" || space === "shapeY"){
  252. // clone a fill so we can modify properly directly
  253. fill = dojox.gfx.makeParameters(dojox.gfx.defaultPattern, fill);
  254. fill.space = space;
  255. // process dimensions
  256. if(space === "plot" || space === "shapeX"){
  257. // process Y
  258. var span = dim.height - offsets.t - offsets.b;
  259. fill.y = offsets.t + span * fill.y / 100;
  260. fill.height = span * fill.height / 100;
  261. }
  262. if(space === "plot" || space === "shapeY"){
  263. // process X
  264. var span = dim.width - offsets.l - offsets.r;
  265. fill.x = offsets.l + span * fill.x / 100;
  266. fill.width = span * fill.width / 100;
  267. }
  268. }
  269. break;
  270. }
  271. return fill;
  272. },
  273. _shapeFill: function(fill, bbox){
  274. // process shape-specific fill
  275. if(!fill || !fill.space){
  276. return fill;
  277. }
  278. var space = fill.space;
  279. switch(fill.type){
  280. case "linear":
  281. if(space === "shape" || space === "shapeX" || space === "shapeY"){
  282. // clone a fill so we can modify properly directly
  283. fill = dojox.gfx.makeParameters(dojox.gfx.defaultLinearGradient, fill);
  284. fill.space = space;
  285. // process dimensions
  286. if(space === "shape" || space === "shapeX"){
  287. // process X
  288. var span = bbox.width;
  289. fill.x1 = bbox.x + span * fill.x1 / 100;
  290. fill.x2 = bbox.x + span * fill.x2 / 100;
  291. }
  292. if(space === "shape" || space === "shapeY"){
  293. // process Y
  294. var span = bbox.height;
  295. fill.y1 = bbox.y + span * fill.y1 / 100;
  296. fill.y2 = bbox.y + span * fill.y2 / 100;
  297. }
  298. }
  299. break;
  300. case "radial":
  301. if(space === "shape"){
  302. // this one is used exclusively for bubble charts and pie charts
  303. // clone a fill so we can modify properly directly
  304. fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
  305. fill.space = space;
  306. // process both dimensions
  307. fill.cx = bbox.x + bbox.width / 2;
  308. fill.cy = bbox.y + bbox.height / 2;
  309. fill.r = fill.r * bbox.width / 200;
  310. }
  311. break;
  312. case "pattern":
  313. if(space === "shape" || space === "shapeX" || space === "shapeY"){
  314. // clone a fill so we can modify properly directly
  315. fill = dojox.gfx.makeParameters(dojox.gfx.defaultPattern, fill);
  316. fill.space = space;
  317. // process dimensions
  318. if(space === "shape" || space === "shapeX"){
  319. // process X
  320. var span = bbox.width;
  321. fill.x = bbox.x + span * fill.x / 100;
  322. fill.width = span * fill.width / 100;
  323. }
  324. if(space === "shape" || space === "shapeY"){
  325. // process Y
  326. var span = bbox.height;
  327. fill.y = bbox.y + span * fill.y / 100;
  328. fill.height = span * fill.height / 100;
  329. }
  330. }
  331. break;
  332. }
  333. return fill;
  334. },
  335. _pseudoRadialFill: function(fill, center, radius, start, end){
  336. // process pseudo-radial fills
  337. if(!fill || fill.type !== "radial" || fill.space !== "shape"){
  338. return fill;
  339. }
  340. // clone and normalize fill
  341. var space = fill.space;
  342. fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
  343. fill.space = space;
  344. if(arguments.length < 4){
  345. // process both dimensions
  346. fill.cx = center.x;
  347. fill.cy = center.y;
  348. fill.r = fill.r * radius / 100;
  349. return fill;
  350. }
  351. // convert to a linear gradient
  352. var angle = arguments.length < 5 ? start : (end + start) / 2;
  353. return {
  354. type: "linear",
  355. x1: center.x,
  356. y1: center.y,
  357. x2: center.x + fill.r * radius * Math.cos(angle) / 100,
  358. y2: center.y + fill.r * radius * Math.sin(angle) / 100,
  359. colors: fill.colors
  360. };
  361. return fill;
  362. }
  363. });
  364. }