resource.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. define(function(require, exports, module) {
  2. var kity = require('../core/kity');
  3. var utils = require('../core/utils');
  4. var Minder = require('../core/minder');
  5. var MinderNode = require('../core/node');
  6. var Command = require('../core/command');
  7. var Module = require('../core/module');
  8. var Renderer = require('../core/render');
  9. Module.register('Resource', function() {
  10. // String Hash
  11. // https://github.com/drostie/sha3-js/edit/master/blake32.min.js
  12. var blake32=(function(){var k,g,r,l,m,o,p,q,t,w,x;x=4*(1<<30);k=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];m=[0x243F6A88,0x85A308D3,0x13198A2E,0x03707344,0xA4093822,0x299F31D0,0x082EFA98,0xEC4E6C89,0x452821E6,0x38D01377,0xBE5466CF,0x34E90C6C,0xC0AC29B7,0xC97C50DD,0x3F84D5B5,0xB5470917];w=function(i){if(i<0){i+=x}return("00000000"+i.toString(16)).slice(-8)};o=[[16,50,84,118,152,186,220,254],[174,132,249,109,193,32,123,53],[139,12,37,223,234,99,23,73],[151,19,205,235,98,165,4,143],[9,117,66,250,30,203,134,211],[194,166,176,56,212,87,239,145],[92,241,222,164,112,54,41,184],[189,231,28,147,5,79,104,162],[246,158,59,128,44,125,65,90],[42,72,103,81,191,233,195,13]];p=function(a,b,n){var s=q[a]^q[b];q[a]=(s>>>n)|(s<<(32-n))};g=function(i,a,b,c,d){var u=l+o[r][i]%16,v=l+(o[r][i]>>4);a%=4;b=4+b%4;c=8+c%4;d=12+d%4;q[a]+=q[b]+(t[u]^m[v%16]);p(d,a,16);q[c]+=q[d];p(b,c,12);q[a]+=q[b]+(t[v]^m[u%16]);p(d,a,8);q[c]+=q[d];p(b,c,7)};return function(a,b){if(!(b instanceof Array&&b.length===4)){b=[0,0,0,0]}var c,d,e,L,f,h,j,i;d=k.slice(0);c=m.slice(0,8);for(r=0;r<4;r+=1){c[r]^=b[r]}e=a.length*16;f=(e%512>446||e%512===0)?0:e;if(e%512===432){a+="\u8001"}else{a+="\u8000";while(a.length%32!==27){a+="\u0000"}a+="\u0001"}t=[];for(i=0;i<a.length;i+=2){t.push(a.charCodeAt(i)*65536+a.charCodeAt(i+1))}t.push(0);t.push(e);h=t.length-16;j=0;for(l=0;l<t.length;l+=16){j+=512;L=(l===h)?f:Math.min(e,j);q=d.concat(c);q[12]^=L;q[13]^=L;for(r=0;r<10;r+=1){for(i=0;i<8;i+=1){if(i<4){g(i,i,i,i,i)}else{g(i,i,i+1,i+2,i+3)}}}for(i=0;i<8;i+=1){d[i]^=b[i%4]^q[i]^q[i+8]}}return d.map(w).join("")}}());
  13. /**
  14. * 自动使用的颜色序列
  15. */
  16. var RESOURCE_COLOR_SERIES = [51, 303, 75, 200, 157, 0, 26, 254].map(function(h) {
  17. return kity.Color.createHSL(h, 100, 85);
  18. });
  19. /**
  20. * 在 Minder 上拓展一些关于资源的支持接口
  21. */
  22. kity.extendClass(Minder, {
  23. /**
  24. * 获取字符串的哈希值
  25. *
  26. * @param {String} str
  27. * @return {Number} hashCode
  28. */
  29. getHashCode: function(str) {
  30. str = blake32(str);
  31. var hash = 1315423911, i, ch;
  32. for (i = str.length - 1; i >= 0; i--) {
  33. ch = str.charCodeAt(i);
  34. hash ^= ((hash << 5) + ch + (hash >> 2));
  35. }
  36. return (hash & 0x7FFFFFFF);
  37. },
  38. /**
  39. * 获取脑图中某个资源对应的颜色
  40. *
  41. * 如果存在同名资源,则返回已经分配给该资源的颜色,否则分配给该资源一个颜色,并且返回
  42. *
  43. * 如果资源数超过颜色序列数量,返回哈希颜色
  44. *
  45. * @param {String} resource 资源名称
  46. * @return {Color}
  47. */
  48. getResourceColor: function(resource) {
  49. var colorMapping = this._getResourceColorIndexMapping();
  50. var nextIndex;
  51. if (!Object.prototype.hasOwnProperty.call(colorMapping, resource)) {
  52. // 找不到找下个可用索引
  53. nextIndex = this._getNextResourceColorIndex();
  54. colorMapping[resource] = nextIndex;
  55. }
  56. // 资源过多,找不到可用索引颜色,统一返回哈希函数得到的颜色
  57. return RESOURCE_COLOR_SERIES[colorMapping[resource]] || kity.Color.createHSL(Math.floor(this.getHashCode(resource) / 0x7FFFFFFF * 359), 100, 85);
  58. },
  59. /**
  60. * 获得已使用的资源的列表
  61. *
  62. * @return {Array}
  63. */
  64. getUsedResource: function() {
  65. var mapping = this._getResourceColorIndexMapping();
  66. var used = [],
  67. resource;
  68. for (resource in mapping) {
  69. if (Object.prototype.hasOwnProperty.call(mapping, resource)) {
  70. used.push(resource);
  71. }
  72. }
  73. return used;
  74. },
  75. /**
  76. * 获取脑图下一个可用的资源颜色索引
  77. *
  78. * @return {int}
  79. */
  80. _getNextResourceColorIndex: function() {
  81. // 获取现有颜色映射
  82. // resource => color_index
  83. var colorMapping = this._getResourceColorIndexMapping();
  84. var resource, used, i;
  85. used = [];
  86. // 抽取已经使用的值到 used 数组
  87. for (resource in colorMapping) {
  88. if (Object.prototype.hasOwnProperty.call(colorMapping, resource)) {
  89. used.push(colorMapping[resource]);
  90. }
  91. }
  92. // 枚举所有的可用值,如果还没被使用,返回
  93. for (i = 0; i < RESOURCE_COLOR_SERIES.length; i++) {
  94. if (!~used.indexOf(i)) return i;
  95. }
  96. // 没有可用的颜色了
  97. return -1;
  98. },
  99. // 获取现有颜色映射
  100. // resource => color_index
  101. _getResourceColorIndexMapping: function() {
  102. return this._resourceColorMapping || (this._resourceColorMapping = {});
  103. }
  104. });
  105. /**
  106. * @class 设置资源的命令
  107. *
  108. * @example
  109. *
  110. * // 设置选中节点资源为 "张三"
  111. * minder.execCommand('resource', ['张三']);
  112. *
  113. * // 添加资源 "李四" 到选中节点
  114. * var resource = minder.queryCommandValue();
  115. * resource.push('李四');
  116. * minder.execCommand('resource', resource);
  117. *
  118. * // 清除选中节点的资源
  119. * minder.execCommand('resource', null);
  120. */
  121. var ResourceCommand = kity.createClass('ResourceCommand', {
  122. base: Command,
  123. execute: function(minder, resource) {
  124. var nodes = minder.getSelectedNodes();
  125. if (typeof(resource) == 'string') {
  126. resource = [resource];
  127. }
  128. nodes.forEach(function(node) {
  129. node.setData('resource', resource).render();
  130. });
  131. minder.layout(200);
  132. },
  133. queryValue: function(minder) {
  134. var nodes = minder.getSelectedNodes();
  135. var resource = [];
  136. nodes.forEach(function(node) {
  137. var nodeResource = node.getData('resource');
  138. if (!nodeResource) return;
  139. nodeResource.forEach(function(name) {
  140. if (!~resource.indexOf(name)) {
  141. resource.push(name);
  142. }
  143. });
  144. });
  145. return resource;
  146. },
  147. queryState: function(km) {
  148. return km.getSelectedNode() ? 0 : -1;
  149. }
  150. });
  151. /**
  152. * @class 资源的覆盖图形
  153. *
  154. * 该类为一个资源以指定的颜色渲染一个动态的覆盖图形
  155. */
  156. var ResourceOverlay = kity.createClass('ResourceOverlay', {
  157. base: kity.Group,
  158. constructor: function() {
  159. this.callBase();
  160. var text, rect;
  161. rect = this.rect = new kity.Rect().setRadius(4);
  162. text = this.text = new kity.Text()
  163. .setFontSize(12)
  164. .setVerticalAlign('middle');
  165. this.addShapes([rect, text]);
  166. },
  167. setValue: function(resourceName, color) {
  168. var paddingX = 8,
  169. paddingY = 4,
  170. borderRadius = 4;
  171. var text, box, rect;
  172. text = this.text;
  173. if (resourceName == this.lastResourceName) {
  174. box = this.lastBox;
  175. } else {
  176. text.setContent(resourceName);
  177. box = text.getBoundaryBox();
  178. this.lastResourceName = resourceName;
  179. this.lastBox = box;
  180. }
  181. text.setX(paddingX).fill(color.dec('l', 70));
  182. rect = this.rect;
  183. rect.setPosition(0, box.y - paddingY);
  184. this.width = Math.round(box.width + paddingX * 2);
  185. this.height = Math.round(box.height + paddingY * 2);
  186. rect.setSize(this.width, this.height);
  187. rect.fill(color);
  188. }
  189. });
  190. /**
  191. * @class 资源渲染器
  192. */
  193. var ResourceRenderer = kity.createClass('ResourceRenderer', {
  194. base: Renderer,
  195. create: function(node) {
  196. this.overlays = [];
  197. return new kity.Group();
  198. },
  199. shouldRender: function(node) {
  200. return node.getData('resource') && node.getData('resource').length;
  201. },
  202. update: function(container, node, box) {
  203. var spaceRight = node.getStyle('space-right');
  204. var overlays = this.overlays;
  205. /* 修复 resource 数组中出现 null 的 bug
  206. * @Author zhangbobell
  207. * @date 2016-01-15
  208. */
  209. var resource = node.getData("resource").filter(function(ele) {
  210. return ele !== null;
  211. });
  212. if (resource.length === 0) {
  213. return;
  214. }
  215. var minder = node.getMinder();
  216. var i, overlay, x;
  217. x = 0;
  218. for (i = 0; i < resource.length; i++) {
  219. x += spaceRight;
  220. overlay = overlays[i];
  221. if (!overlay) {
  222. overlay = new ResourceOverlay();
  223. overlays.push(overlay);
  224. container.addShape(overlay);
  225. }
  226. overlay.setVisible(true);
  227. overlay.setValue(resource[i], minder.getResourceColor(resource[i]));
  228. overlay.setTranslate(x, -1);
  229. x += overlay.width;
  230. }
  231. while ((overlay = overlays[i++])) overlay.setVisible(false);
  232. container.setTranslate(box.right, 0);
  233. return new kity.Box({
  234. x: box.right,
  235. y: Math.round(-overlays[0].height / 2),
  236. width: x,
  237. height: overlays[0].height
  238. });
  239. }
  240. });
  241. return {
  242. commands: {
  243. 'resource': ResourceCommand
  244. },
  245. renderers: {
  246. right: ResourceRenderer
  247. }
  248. };
  249. });
  250. });