corpus.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. /**
  2. * @Author: Bachir Soussi Chiadmi <bach>
  3. * @Date: 18-12-2017
  4. * @Email: bachir@figureslibres.io
  5. * @Filename: corpus.js
  6. * @Last modified by: bach
  7. * @Last modified time: 20-12-2017
  8. * @License: GPL-V3
  9. */
  10. /**
  11. * depends on :
  12. *
  13. * physics.js : http://jonobr1.com/Physics/#
  14. * EaselJS : https://createjs.com/docs/easeljs/modules/EaselJS.html
  15. *
  16. */
  17. (function($, Drupal, drupalSettings) {
  18. EdlpCorpus = function(){
  19. var _activated = true;
  20. var _$body = $('body');
  21. var _$container = _$body;
  22. var _$canvas = $('<canvas id="corpus-map">').appendTo(_$container);
  23. var _canvas = _$canvas[0];
  24. var _ctx = _canvas.getContext('2d');
  25. var _canvas_props = {
  26. 'margin_top':100,
  27. 'margin_right':0,
  28. 'margin_bottom':100,
  29. 'margin_left':0
  30. };
  31. var _physics = new Physics();
  32. // var _stage = new createjs.Stage('corpus-map');
  33. var _nodes = [];
  34. var _articles_nodes = [];
  35. var _no_articles_nodes = [];
  36. var _nodes_by_entries = {};
  37. var _nodes_by_nid = {};
  38. var _nodes_centered = [];
  39. var _nodes_centered_attractions = [];
  40. var _playlist = [];
  41. // var _particules = [];
  42. // var _base_radius = 3; // nodes radius (real radius, not diametre)
  43. var _p_velocity_factor = 0.5;
  44. var _mouse_in = true;
  45. var _m_pos = {x:0, y:0};
  46. var _node_hover_id = -1;
  47. var _node_opened_id = -1;
  48. var _$entrees_block = $('#block-edlpentreesblock');
  49. var _$entrees_block_termlinks = $('a.term-link', _$entrees_block);
  50. var _$articles_link;
  51. var _node_pop_up;
  52. // Colors depend on drupalSettings loaded by edlp_corpus.module
  53. var _ecolors = drupalSettings.edlp_corpus.colors;
  54. console.log('Corpus : _ecolors', _ecolors);
  55. // Physics
  56. var _attracter,
  57. _repulser_top,
  58. _repulser_center,
  59. _repulser_bottom,
  60. _scrambler_TL,
  61. _scrambler_TR,
  62. _scrambler_BR,
  63. _scrambler_BL,
  64. _scrambler_CL,
  65. _scrambler_CR;
  66. // ____ _ __
  67. // / _/___ (_) /______
  68. // / // __ \/ / __/ ___/
  69. // _/ // / / / / /_(__ )
  70. // /___/_/ /_/_/\__/____/
  71. function init(){
  72. initCanvas();
  73. if(_activated){
  74. loadCorpus();
  75. }else{
  76. _$canvas.addClass('de-activated');
  77. }
  78. };
  79. // ___
  80. // / __|__ _ _ ___ ____ _ ___
  81. // | (__/ _` | ' \ V / _` (_-<
  82. // \___\__,_|_||_\_/\__,_/__/
  83. function initCanvas(){
  84. // resize the canvas to fill browser window dynamically
  85. window.addEventListener('resize', onResizeCanvas, false);
  86. onResizeCanvas();
  87. };
  88. function onResizeCanvas() {
  89. _canvas.width = window.innerWidth;
  90. _canvas.height = window.innerHeight;
  91. for (var i = 0; i < _nodes.length; i++) {
  92. _nodes[i].onResizeCanvas();
  93. // move _attracter and _repulser to the center again
  94. resizePhysics();
  95. }
  96. };
  97. // ___
  98. // / __|___ _ _ _ __ _ _ ___
  99. // | (__/ _ \ '_| '_ \ || (_-<
  100. // \___\___/_| | .__/\_,_/__/
  101. // |_|
  102. function loadCorpus(){
  103. // console.log('drupalSettings',drupalSettings);
  104. // console.log('window.location', window.location);
  105. var path = window.location.origin + drupalSettings.basepath + "edlp/corpus";
  106. $.getJSON(path, {})
  107. .done(function(data){
  108. onCorpusLoaded(data);
  109. })
  110. .fail(function(jqxhr, textStatus, error){
  111. onCorpusLoadError(jqxhr, textStatus, error);
  112. });
  113. };
  114. function onCorpusLoadError(jqxhr, textStatus, error){
  115. console.warn('corpus load failed', jqxhr.responseText);
  116. };
  117. function onCorpusLoaded(data){
  118. console.log('corpus loaded : data', data);
  119. // console.log('first node', data.nodes[0]);
  120. // buildParticles(data.nodes);
  121. initPhysics();
  122. buildNodes(data.nodes);
  123. initArtilesLink();
  124. initNodePopup();
  125. initEvents();
  126. startAnime();
  127. };
  128. // ___ _ _
  129. // | _ \ |_ _ _ __(_)__ ___
  130. // | _/ ' \ || (_-< / _(_-<
  131. // |_| |_||_\_, /__/_\__/__/
  132. // |__/
  133. function initPhysics(){
  134. _attracter = _physics.makeParticle(1000);
  135. _attracter.fixed = true;
  136. _repulser_top = _physics.makeParticle(100);
  137. _repulser_top.fixed = true;
  138. _repulser_center = _physics.makeParticle(100);
  139. _repulser_center.fixed = true;
  140. _repulser_bottom = _physics.makeParticle(100);
  141. _repulser_bottom.fixed = true;
  142. _scrambler_TL = _physics.makeParticle(100);
  143. _scrambler_TL.fixed = true;
  144. _scrambler_TR = _physics.makeParticle(100);
  145. _scrambler_TR.fixed = true;
  146. _scrambler_BR = _physics.makeParticle(100);
  147. _scrambler_BR.fixed = true;
  148. _scrambler_BL = _physics.makeParticle(100);
  149. _scrambler_BL.fixed = true;
  150. _scrambler_CR = _physics.makeParticle(100);
  151. _scrambler_CR.fixed = true;
  152. _scrambler_CL = _physics.makeParticle(100);
  153. _scrambler_CL.fixed = true;
  154. // move _attracter and _repulser on window resize
  155. resizePhysics();
  156. };
  157. function resizePhysics(){
  158. // attracters
  159. _attracter.position = {x:_canvas.width/2, y:_canvas.height/2};
  160. // repulsers
  161. _repulser_top.position = {x:_canvas.width/2, y:0};
  162. _repulser_center.position = {x:_canvas.width/2, y:_canvas.height/2};
  163. _repulser_bottom.position = {x:_canvas.width/2, y:_canvas.height};
  164. // scramblers
  165. _scrambler_TL.position = {x:-200, y:_canvas.height/40};
  166. _scrambler_BL.position = {x:-200, y:_canvas.height-_canvas.height/40};
  167. // _scrambler_CL.position = {x:-200, y:_canvas.height/2};
  168. _scrambler_TR.position = {x:_canvas.width+200, y:_canvas.height/40};
  169. _scrambler_BR.position = {x:_canvas.width+200, y:_canvas.height-_canvas.height/40};
  170. // _scrambler_CR.position = {x:_canvas.width+200, y:_canvas.height/2};
  171. };
  172. // _ _ _
  173. // | \| |___ __| |___ ___
  174. // | .` / _ \/ _` / -_|_-<
  175. // |_|\_\___/\__,_\___/__/
  176. function buildNodes(nodes){
  177. console.log("buildNodes", nodes);
  178. var d;
  179. for (var i in nodes) {
  180. d = i < 1 ? true : false;
  181. // _nodes.push(new Node(i,nodes[i],d));
  182. new Node(i,nodes[i],d);
  183. _playlist.push({
  184. 'id':i,
  185. 'nid':nodes[i].nid,
  186. 'audio_url':nodes[i].audio_url
  187. });
  188. }
  189. console.log('_nodes',_nodes);
  190. console.log('_articles_nodes',_articles_nodes);
  191. console.log('_no_articles_nodes',_no_articles_nodes);
  192. console.log('_nodes_by_entries', _nodes_by_entries);
  193. console.log('_physics.attractions.length', _physics.attractions.length);
  194. };
  195. function Node(i,node,d){
  196. this.id = i;
  197. for(key in node)
  198. this[key] = node[key];
  199. // record the node in different lists
  200. _nodes.push(this);
  201. _nodes_by_nid[this.nid] = this;
  202. if(this.has_article == 1){
  203. _articles_nodes.push(this);
  204. }else{
  205. _no_articles_nodes.push(this);
  206. }
  207. for (var j = 0; j < this.entrees.length; j++) {
  208. if(typeof _nodes_by_entries[this.entrees[j]] == 'undefined')
  209. _nodes_by_entries[this.entrees[j]] = [];
  210. _nodes_by_entries[this.entrees[j]].push(this);
  211. }
  212. this.debug = d;
  213. this.mass = Math.max(1, this.entrees.length); //1;
  214. this.velocity_threshold = 0.01;
  215. // define radius regarding entries length
  216. switch(true){
  217. case this.entrees.length > 3:
  218. this.r = 10;
  219. break;
  220. case this.entrees.length > 1 && this.entrees.length <= 3:
  221. this.r = 7;
  222. break;
  223. default:
  224. this.r = 4;
  225. break;
  226. }
  227. this.e_color = 'e_col_'+this.entrees[Math.floor(Math.random(this.entrees.length))];
  228. this.hover = false;
  229. this.opened = false;
  230. this.faded = false;
  231. this.center = false;
  232. this.aside = false;
  233. this.scrambling = false;
  234. this.n_repulses = {};
  235. // prototypes
  236. if (typeof Node.initialized == "undefined") {
  237. Node.prototype.init = function(){
  238. this.calcWallLimits();
  239. this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left);
  240. this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top);
  241. this.initPhysics();
  242. // if(this.id == '620'){
  243. // _node_pop_up.setNode(this);
  244. // }
  245. };
  246. Node.prototype.initPhysics = function(){
  247. // particule
  248. this.p = _physics.makeParticle(this.mass, this.x, this.y);
  249. this.p.velocity = new Physics.Vector((Math.random()-0.5)*_p_velocity_factor, (Math.random()-0.5)*_p_velocity_factor);
  250. // attracter
  251. this.attract = _physics.makeAttraction(_attracter, this.p, 1000, _canvas.width*2);
  252. this.attract.on = false;
  253. // repulsers
  254. this.repulse_top = _physics.makeAttraction(_repulser_top, this.p, -60, _canvas.height/6);
  255. this.repulse_center = _physics.makeAttraction(_repulser_center, this.p, -50, _canvas.height/6);
  256. this.repulse_bottom = _physics.makeAttraction(_repulser_bottom, this.p, -60, _canvas.height/6);
  257. this.repulse_top.on = false;
  258. this.repulse_center.on = false;
  259. this.repulse_bottom.on = false;
  260. // Scramblers
  261. this.scramble_TL = _physics.makeAttraction(_scrambler_TL, this.p, -100, _canvas.height/2.5);
  262. this.scramble_TR = _physics.makeAttraction(_scrambler_TR, this.p, -100, _canvas.height/2.5);
  263. this.scramble_BR = _physics.makeAttraction(_scrambler_BR, this.p, -100, _canvas.height/2.5);
  264. this.scramble_BL = _physics.makeAttraction(_scrambler_BL, this.p, -100, _canvas.height/2.5);
  265. // this.scramble_CR = _physics.makeAttraction(_scrambler_CR, this.p, -50, _canvas.height/4);
  266. // this.scramble_CL = _physics.makeAttraction(_scrambler_CL, this.p, -50, _canvas.height/4);
  267. this.scramble_TL.on = false;
  268. this.scramble_TR.on = false;
  269. this.scramble_BR.on = false;
  270. this.scramble_BL.on = false;
  271. // this.scramble_CR.on = false;
  272. // this.scramble_CL.on = false;
  273. };
  274. Node.prototype.calcWallLimits = function(){
  275. this.wall_limits = {
  276. top: _canvas_props.margin_top +this.r,
  277. right: _canvas.width -_canvas_props.margin_right -this.r,
  278. bottom: _canvas.height -_canvas_props.margin_bottom -this.r,
  279. left: _canvas_props.margin_left +this.r
  280. }
  281. };
  282. Node.prototype.onResizeCanvas = function(){
  283. this.calcWallLimits();
  284. // TODO: when canvas is smaller what about nodes hors champs, they are coming back alone but it's too long
  285. };
  286. Node.prototype.onUpdate = function(){
  287. // this.p.velocity.multiplyScalar(0.99);
  288. if(this.center)
  289. this.slowDownNearCenter();
  290. if(!this.p.resting()){
  291. // this.checkVelocityThreshold();
  292. this.checkWallBouncing();
  293. this.updatePos();
  294. }
  295. if(_mouse_in && !this.aside)
  296. this.checkMouse();
  297. // if(this.debug)
  298. // console.log("Node pos: ", {x:this.x, y:this.y});
  299. this.draw();
  300. };
  301. Node.prototype.checkVelocityThreshold = function(){
  302. if(!this.centered || !this.aside){
  303. if (Math.abs(this.p.velocity.x) < this.velocity_threshold
  304. && Math.abs(this.p.velocity.y) < this.velocity_threshold){
  305. this.p.velocity.multiplyScalar(0);
  306. // this.p.makeFixed();
  307. }
  308. }
  309. };
  310. Node.prototype.checkWallBouncing = function(){
  311. switch(true){
  312. case this.x < this.wall_limits.left && this.p.velocity.x < 0:
  313. this.p.position.x = this.wall_limits.left;
  314. this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75);
  315. break;
  316. case this.x > this.wall_limits.right && this.p.velocity.x > 0:
  317. this.p.position.x = this.wall_limits.right;
  318. this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75);
  319. break;
  320. case this.y < this.wall_limits.top && this.p.velocity.y < 0 :
  321. this.p.position.y = this.wall_limits.top;
  322. this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75);
  323. break;
  324. case this.y > this.wall_limits.bottom && this.p.velocity.y > 0 :
  325. this.p.position.y = this.wall_limits.bottom;
  326. this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75);
  327. break;
  328. }
  329. };
  330. Node.prototype.updatePos = function(){
  331. this.x = this.p.position.x;
  332. this.y = this.p.position.y;
  333. };
  334. Node.prototype.checkMouse = function(){
  335. if( _m_pos.x > this.x - this.r
  336. && _m_pos.x < this.x + this.r
  337. && _m_pos.y > this.y - this.r
  338. && _m_pos.y < this.y + this.r){
  339. if(_node_hover_id == -1 || _node_hover_id !== this.id){
  340. // console.log("Node hover", this.id);
  341. this.hover = true;
  342. _node_hover_id = this.id;
  343. _node_pop_up.setNode(this);
  344. }
  345. }else{
  346. this.hover = false;
  347. if (_node_hover_id == this.id) {
  348. _node_hover_id = -1;
  349. _node_pop_up.removeNode();
  350. }
  351. }
  352. };
  353. Node.prototype.open = function(){
  354. this.opened = true;
  355. };
  356. Node.prototype.close = function(){
  357. this.opened = false;
  358. };
  359. Node.prototype.fade = function(){
  360. this.faded = true;
  361. };
  362. Node.prototype.unFade = function(){
  363. this.faded = false;
  364. };
  365. Node.prototype.setCenteredOnEntree = function(tid){
  366. this.e_color = 'e_col_'+tid;
  367. this.setCentered();
  368. };
  369. Node.prototype.setCentered = function(){
  370. this.center = true;
  371. this.stopScrambling();
  372. this.unsetAside();
  373. this.attract.on = true;
  374. }
  375. Node.prototype.unsetCentered = function(){
  376. this.center = false;
  377. this.attract.on = false;
  378. }
  379. Node.prototype.slowDownNearCenter = function(){
  380. // smouthly slow down node near center
  381. // if(this.id == 1)
  382. // console.log('this.p.velocity', this.p.velocity);
  383. this.dist_to_attracter = this.p.distanceTo(_attracter);
  384. if( this.dist_to_attracter < _canvas.width/4){
  385. if( this.p.velocity.length() > 0.4 ){
  386. this.p.velocity.multiplyScalar(0.995);
  387. }
  388. // if(Math.abs(this.p.velocity.x) > 1){
  389. // this.p.velocity.x *= 0.99;
  390. // }
  391. // if(Math.abs(this.p.velocity.y) > 1){
  392. // this.p.velocity.y *= 0.99;
  393. // }
  394. }
  395. }
  396. Node.prototype.setAside = function(){
  397. this.aside = true;
  398. // this.fade();
  399. this.stopScrambling();
  400. this.unsetCentered();
  401. this.repulse_top.on = true;
  402. this.repulse_center.on = true;
  403. this.repulse_bottom.on = true;
  404. }
  405. Node.prototype.unsetAside = function(){
  406. this.aside = false;
  407. // this.unFade();
  408. this.repulse_top.on = false;
  409. this.repulse_center.on = false;
  410. this.repulse_bottom.on = false;
  411. }
  412. Node.prototype.scramble = function(){
  413. this.scrambling = true;
  414. this.unsetCentered();
  415. this.unsetAside();
  416. this.scramble_TL.on = true;
  417. this.scramble_TR.on = true;
  418. this.scramble_BR.on = true;
  419. this.scramble_BL.on = true;
  420. // this.scramble_CR.on = true;
  421. // this.scramble_CL.on = true;
  422. }
  423. Node.prototype.stopScrambling = function(){
  424. this.scrambling = false;
  425. this.scramble_TL.on = false;
  426. this.scramble_TR.on = false;
  427. this.scramble_BR.on = false;
  428. this.scramble_BL.on = false;
  429. // this.scramble_CR.on = false;
  430. // this.scramble_CL.on = false;
  431. }
  432. Node.prototype.draw = function(){
  433. // carre plein
  434. // clouleur aléatoire ds les entrees
  435. // 3 tailles :
  436. // - 1 entree : petit carre 5px
  437. // - 2-3 entrees : moyen 7.5px
  438. // - >3 entrees : grand 10px
  439. // actif entouré de rouge
  440. _ctx.beginPath();
  441. _ctx.globalAlpha = this.faded ? 0.1 : 1;
  442. // _ctx.fillStyle = !this.p.resting() ? _ecolors[this.e_color] : 'rgb(0, 0, 0)';
  443. _ctx.fillStyle = _ecolors[this.e_color];
  444. _ctx.fillRect(this.x - this.r,this.y - this.r,this.r*2,this.r*2);
  445. if(this.opened){
  446. _ctx.lineWidth = '1px';
  447. _ctx.strokeStyle = 'rgb(255,0,0)';
  448. _ctx.strokeRect(this.x - this.r-3,this.y - this.r-3,this.r*2+6,this.r*2+6);
  449. }
  450. _ctx.globalAlpha = 1;
  451. _ctx.closePath();
  452. };
  453. Node.initialized = true;
  454. }
  455. this.init();
  456. };
  457. // TODO: we may convert a lot of computation into a web worker
  458. // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
  459. function checkParticulesCollisions(){
  460. // pre create vars to save memory;
  461. var d, full_rad, margin,
  462. newVelX1, newVelY1, newVelX2, newVelY2,
  463. makeup, angle;
  464. // colisions between _particules
  465. for (var n = 0; n < _nodes.length; n++) {
  466. // avoid colliding for centered nodes
  467. // if(_nodes[n].center) continue;
  468. // avoid colliding for scrambling nodes
  469. if(_nodes[n].scrambling) continue;
  470. for (var q = n+1; q < _nodes.length; q++) {
  471. if(q===n) continue;
  472. // avoid impact between center and aside particules
  473. if((_nodes[n].center && _nodes[q].aside) || (_nodes[n].aside && _nodes[q].center))
  474. continue;
  475. // TODO: what if centered nodes are repulsing each other ??
  476. margin = _nodes[n].center ? 1 : 1;
  477. // avoid impact between two centered particulses that comes to the center
  478. if(_nodes[n].center && _nodes[q].center){
  479. if(Math.min(_nodes[n].p.distanceTo(_attracter), _nodes[q].p.distanceTo(_attracter)) > 300){
  480. if( Math.random()>0.3 ) continue;
  481. }
  482. }
  483. d = _nodes[n].p.distanceTo(_nodes[q].p);
  484. full_rad = _nodes[n].r*margin + _nodes[q].r*margin;
  485. // if not colliding skip following
  486. if(d > full_rad) continue;
  487. // apply new forces if colliding
  488. newVelX1 = (_nodes[n].p.velocity.x * (_nodes[n].p.mass - _nodes[q].p.mass)
  489. + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  490. newVelY1 = (_nodes[n].p.velocity.y * (_nodes[n].p.mass - _nodes[q].p.mass)
  491. + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  492. newVelX2 = (_nodes[q].p.velocity.x * (_nodes[q].p.mass - _nodes[n].p.mass)
  493. + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  494. newVelY2 = (_nodes[q].p.velocity.y * (_nodes[q].p.mass - _nodes[n].p.mass)
  495. + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  496. _nodes[n].p.velocity.x = newVelX1;
  497. _nodes[n].p.velocity.y = newVelY1;
  498. _nodes[q].p.velocity.x = newVelX2;
  499. _nodes[q].p.velocity.y = newVelY2;
  500. // slow down particule on impact
  501. _nodes[n].p.velocity.multiplyScalar(_nodes[n].center && _nodes[n].p.velocity.length() < 1 ? 1.1 : 0.90);
  502. _nodes[q].p.velocity.multiplyScalar(_nodes[q].center && _nodes[q].p.velocity.length() < 1 ? 1.1 : 0.90);
  503. // move particles if they overlap
  504. if (d < full_rad) {
  505. makeup = (full_rad/2 - d/2)*1.2;
  506. angle = Math.atan2(_nodes[q].p.position.y - _nodes[n].p.position.y, _nodes[q].p.position.x - _nodes[n].p.position.x);
  507. _nodes[q].p.position.x += makeup * Math.cos(angle);
  508. _nodes[q].p.position.y += makeup * Math.sin(angle);
  509. angle += Math.PI;
  510. _nodes[n].p.position.x += makeup * Math.cos(angle);
  511. _nodes[n].p.position.y += makeup * Math.sin(angle);
  512. }
  513. }
  514. }
  515. };
  516. // show opened audio node
  517. function openNode(id){
  518. closeNode();
  519. _node_opened_id = id;
  520. _nodes[id].open();
  521. }
  522. function closeNode(){
  523. if(_node_opened_id != -1){
  524. _nodes[_node_opened_id].close();
  525. _node_opened_id = -1;
  526. }
  527. }
  528. function createNodesRepulsions(){
  529. // how to delete all these attractions before creates new once
  530. console.log('_nodes_centered', _nodes_centered);
  531. purgeNodesRepulsions();
  532. for (var n = 0; n < _nodes_centered.length; n++) {
  533. for (var q = n+1; q < _nodes_centered.length; q++) {
  534. _physics.makeAttraction(_nodes_centered[n].p, _nodes_centered[q].p, -0.4, 10);
  535. }
  536. }
  537. };
  538. function purgeNodesRepulsions(){
  539. // console.log('_physics.attractions.length', _physics.attractions.length);
  540. for (var a = _physics.attractions.length-1; a >= 0; a--) {
  541. // assuming that only nodes repulsions have constant < 1
  542. // and are all at the end of array
  543. if(Math.abs(_physics.attractions[a].constant) < 1){
  544. _physics.attractions.splice(a,1);
  545. }else{
  546. break;
  547. }
  548. }
  549. // console.log('_physics.attractions.length', _physics.attractions.length);
  550. };
  551. // ___ _
  552. // | __|_ _| |_ _ _ ___ ___ ___
  553. // | _|| ' \ _| '_/ -_) -_|_-<
  554. // |___|_||_\__|_| \___\___/__/
  555. function filterEntree(t){
  556. // shuydown articles if active
  557. if(_$articles_link.is('.is-active'))
  558. _$articles_link.trigger('click');
  559. _nodes_centered = [];
  560. for (var n = 0; n < _nodes.length; n++) {
  561. if(_nodes[n].entrees.indexOf(t) == -1){
  562. _nodes[n].setAside();
  563. }else{
  564. _nodes[n].setCenteredOnEntree(t);
  565. // record centered nodes for inter node repulsions
  566. _nodes_centered.push(_nodes[n]);
  567. }
  568. }
  569. createNodesRepulsions();
  570. };
  571. function highlightEntries(){
  572. _$entrees_block_termlinks.removeClass('highlighted');
  573. var entree;
  574. for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
  575. entree = _nodes[_node_hover_id].entrees[i];
  576. _$entrees_block_termlinks.filter(function(){
  577. return $(this).attr('tid') == entree;
  578. }).addClass('highlighted');
  579. }
  580. };
  581. // ___ _ _ _
  582. // / __| __ _ _ __ _ _ __ | |__| (_)_ _ __ _
  583. // \__ \/ _| '_/ _` | ' \| '_ \ | | ' \/ _` |
  584. // |___/\__|_| \__,_|_|_|_|_.__/_|_|_||_\__, |
  585. // |___/
  586. function scrambleCollection(){
  587. for (var i = 0; i < _nodes.length; i++) {
  588. _nodes[i].scramble();
  589. }
  590. // setTimeout(stopScrambling, 2000);
  591. stopScrambling();
  592. };
  593. function stopScrambling(){
  594. for (var i = 0; i < _nodes.length; i++) {
  595. setTimeout(function(n){
  596. n.stopScrambling();
  597. }.bind(this, _nodes[i]), 1000 + Math.random()*4000);
  598. }
  599. };
  600. function closeAllEntries(){
  601. _$entrees_block_termlinks.each(function(index, el) {
  602. if($(this).parents('li').is('.opened')){
  603. $(this).trigger('click');
  604. return false;
  605. }
  606. });
  607. };
  608. // _ _ _ _
  609. // /_\ _ _| |_(_)__| |___ ___
  610. // / _ \| '_| _| / _| / -_|_-<
  611. // /_/ \_\_| \__|_\__|_\___/__/
  612. function initArtilesLink(){
  613. // add "articles link to blockentrees"
  614. _$articles_link = $('<a>')
  615. .html('Articles')
  616. .attr("href", "#articles")
  617. .addClass('articles-link')
  618. .on('click', onCLickedOnArticles);
  619. $('.item-list ul',_$entrees_block).append(
  620. $('<li>').append(
  621. $('<span class="oblique-wrapper">').append(_$articles_link)
  622. )
  623. );
  624. };
  625. function onCLickedOnArticles(e){
  626. e.preventDefault();
  627. $(this).toggleClass('is-active');
  628. if($(this).is('.is-active')){
  629. filterArticles();
  630. closeAllEntries();
  631. }else{
  632. resetArticlesFilter();
  633. }
  634. // _$canvas.trigger({
  635. // 'type':'corpus-cliqued-on-articles'
  636. // });
  637. return false;
  638. };
  639. function filterArticles(){
  640. console.log('filterArticles');
  641. for (var i = 0; i < _no_articles_nodes.length; i++) {
  642. _no_articles_nodes[i].fade();
  643. }
  644. };
  645. function resetArticlesFilter(){
  646. console.log('resetArticlesFilter');
  647. for (var i = 0; i < _no_articles_nodes.length; i++) {
  648. _no_articles_nodes[i].unFade();
  649. }
  650. };
  651. // ___ _
  652. // | __|_ _____ _ _| |_ ___
  653. // | _|\ V / -_) ' \ _(_-<
  654. // |___|\_/\___|_||_\__/__/
  655. function initEvents(){
  656. _$canvas
  657. .on('mousemove', function(event) {
  658. event.preventDefault();
  659. _m_pos.x = event.originalEvent.clientX;
  660. _m_pos.y = event.originalEvent.clientY;
  661. })
  662. .on('mouseenter', function(event){
  663. // console.log('onMouseIN');
  664. _mouse_in = true;
  665. })
  666. .on('mouseout', function(event){
  667. // console.log('onMouseOUT');
  668. _mouse_in = false;
  669. _node_pop_up.removeNode();
  670. })
  671. .on('click', function(event) {
  672. if(event.target.tagName != "A" && event.target.tagName != "INPUT"){
  673. // console.log("Corpus : click");
  674. event.preventDefault();
  675. if(_node_hover_id != -1){
  676. // console.log("corpus : click on node", _nodes[_node_hover_id]);
  677. var event = {
  678. 'type':'corpus-cliked-on-node',
  679. 'target_node':{
  680. 'id':_node_hover_id,
  681. 'nid':_nodes[_node_hover_id].nid,
  682. 'audio_url':_nodes[_node_hover_id].audio_url
  683. },
  684. };
  685. _$canvas.trigger(event);
  686. // instead of directly opening the doc, create an event listener (e.g. : audio played from random)
  687. // openNode(_node_hover_id);
  688. }else{
  689. // console.log('corpus : click on map');
  690. _$canvas.trigger('corpus-cliked-on-map');
  691. }
  692. }
  693. })
  694. .on('audio-node-opened', function(e){
  695. openNode(e.id);
  696. })
  697. .on('audio-node-closed', function(e){
  698. closeNode();
  699. });
  700. _$entrees_block_termlinks.on('click', function(event) {
  701. event.preventDefault();
  702. var tid = $(this).attr('tid');
  703. var $li = $(this).parents('li');
  704. if(!$li.is('.opened')){
  705. _$entrees_block_termlinks.parents('li').removeClass('opened');
  706. $li.addClass('opened');
  707. filterEntree(tid);
  708. _$body.trigger({'type':'open_entree', 'tid':tid});
  709. }else{
  710. $li.removeClass('opened');
  711. scrambleCollection();
  712. _$body.trigger({'type':'close_entree', 'tid':tid});
  713. }
  714. return false;
  715. });
  716. // $('body').trigger({
  717. // 'type':'chutier-action-done',
  718. // 'action_done':data.action_done,
  719. // 'target_id':$link.attr('target_id'),
  720. // });
  721. _$body.on('chutier-action-done', function(e) {
  722. _nodes_by_nid[e.target_id].chutier_action = e.new_action;
  723. });
  724. };
  725. // _ _ _ ___ _ _
  726. // | \| |___ __| |___| _ \___ _ __| | | |_ __
  727. // | .` / _ \/ _` / -_) _/ _ \ '_ \ |_| | '_ \
  728. // |_|\_\___/\__,_\___|_| \___/ .__/\___/| .__/
  729. // |_| |_|
  730. function initNodePopup(){
  731. _node_pop_up = new NodePopUp();
  732. };
  733. function NodePopUp(){
  734. this.visible = false;
  735. this.node;
  736. this.$dom = $('<div>')
  737. .addClass('node-popup')
  738. .attr('pos', 'top-right')
  739. .appendTo('body');
  740. this.$content = $('<div>').addClass('inner').appendTo(this.$dom);
  741. if (typeof NodePopUp.initialized == "undefined") {
  742. NodePopUp.prototype.setNode = function(n){
  743. // console.log('NodePopUp setNode()');
  744. this.node = n;
  745. // positioning NodePopUp regarding node position
  746. this.setPositioning();
  747. // update NodePopUp content
  748. this.setContent();
  749. };
  750. NodePopUp.prototype.setPositioning = function(){
  751. switch(true){
  752. case this.node.x > this.node.wall_limits.right-350 && this.node.y < this.node.wall_limits.top+200:
  753. this.$dom.attr('pos', 'bottom-left');
  754. break;
  755. case this.node.x > this.node.wall_limits.right-350:
  756. this.$dom.attr('pos', 'top-left');
  757. break;
  758. case this.node.y < this.node.wall_limits.top+200:
  759. this.$dom.attr('pos', 'bottom-right');
  760. break;
  761. default:
  762. this.$dom.attr('pos', 'top-right');
  763. }
  764. };
  765. NodePopUp.prototype.setContent = function(){
  766. // console.log(this.node);
  767. this.$content.html('');
  768. var $entrees = $('<div>').addClass('entrees');
  769. for (var i = 0; i < this.node.entrees.length; i++) {
  770. var tid = this.node.entrees[i];
  771. $entrees.append($('<span>').addClass('entree').attr('tid', tid));
  772. }
  773. var $chutier_action = $('<span>').addClass('chutier-icon').attr('action', this.node.chutier_action);
  774. this.$content
  775. .append($entrees)
  776. .append('<h2 class="title">'+this.node.title+'</h2>')
  777. .append('<section class="description">'+this.node.description+'</section>')
  778. .append($chutier_action);
  779. // TODO: favoris marker
  780. };
  781. NodePopUp.prototype.removeNode = function(){
  782. // console.log('NodePopUp removeNode()');
  783. this.node = false;
  784. };
  785. NodePopUp.prototype.draw = function(){
  786. if(this.node){
  787. this.$dom.css({
  788. 'display':"block",
  789. 'left':this.node.x+"px",
  790. 'top':this.node.y+"px",
  791. });
  792. }else{
  793. this.$dom.css({
  794. 'display':"none",
  795. });
  796. }
  797. };
  798. NodePopUp.initialized = true;
  799. }
  800. }
  801. // ___ _
  802. // | _ \___ _ _ __| |___ _ _
  803. // | / -_) ' \/ _` / -_) '_|
  804. // |_|_\___|_||_\__,_\___|_|
  805. function render(){
  806. _ctx.clearRect(0, 0, _canvas.width, _canvas.height);
  807. checkParticulesCollisions();
  808. for (var i = 0; i < _nodes.length; i++) {
  809. _nodes[i].onUpdate();
  810. }
  811. _node_pop_up.draw();
  812. if(_node_hover_id != -1){
  813. _canvas.style.cursor = 'pointer';
  814. highlightEntries();
  815. // _node_pop_up.visible = true;
  816. }else{
  817. _canvas.style.cursor = 'auto';
  818. _$entrees_block_termlinks.removeClass('highlighted');
  819. // _node_pop_up.visible = false;
  820. }
  821. };
  822. // ___ _ _ _ _
  823. // / __| |_ __ _ _ _| |_ /_\ _ _ (_)_ __ ___
  824. // \__ \ _/ _` | '_| _|/ _ \| ' \| | ' \/ -_)
  825. // |___/\__\__,_|_| \__/_/ \_\_||_|_|_|_|_\___|
  826. function startAnime(){
  827. _physics.onUpdate(render);
  828. _physics.play()
  829. $('body').trigger({
  830. 'type':'corpus-map-ready',
  831. 'playlist':_playlist
  832. });
  833. };
  834. init();
  835. }
  836. $(document).ready(function($) {
  837. var edlpcorpus = new EdlpCorpus();
  838. });
  839. })(jQuery, Drupal, drupalSettings);