Element.js 11 KB

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