corpus.min.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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($) {
  18. EdlpCorpus = function(){
  19. var _activated = true;
  20. var _$container = $('body');
  21. var _$canvas = $('<canvas id="corpus-map">').appendTo(_$container);
  22. var _canvas = _$canvas[0];
  23. var _ctx = _canvas.getContext('2d');
  24. var _canvas_props = {
  25. 'margin_top':100,
  26. 'margin_right':0,
  27. 'margin_bottom':100,
  28. 'margin_left':0
  29. };
  30. var _physics = new Physics();
  31. // var _stage = new createjs.Stage('corpus-map');
  32. var _nodes = [];
  33. // var _particules = [];
  34. // var _base_radius = 3; // nodes radius (real radius, not diametre)
  35. var _p_velocity_factor = 0.5;
  36. var _m_pos = {x:0, y:0};
  37. var _node_hover_id = -1;
  38. var _node_opened_id = -1;
  39. var _$entrees_block = $('#block-edlpentreesblock');
  40. var _$entrees_block_termlinks = $('a.term-link', _$entrees_block);
  41. var _node_pop_up;
  42. // Colors depend on edlp_vars loaded by edlptheme
  43. // console.log('Corpus : edlp_vars', edlp_vars);
  44. // ____ _ __
  45. // / _/___ (_) /______
  46. // / // __ \/ / __/ ___/
  47. // _/ // / / / / /_(__ )
  48. // /___/_/ /_/_/\__/____/
  49. function init(){
  50. console.log("EdlpCorpus init()");
  51. // console.log("document", document);
  52. initMap();
  53. };
  54. function initMap(){
  55. console.log("EdlpCorpus initMap()");
  56. // console.log('_physics',_physics);
  57. initCanvas();
  58. if(_activated){
  59. loadCorpus();
  60. }else{
  61. _$canvas.addClass('de-activated');
  62. }
  63. };
  64. // ______
  65. // / ____/___ _____ _ ______ ______
  66. // / / / __ `/ __ \ | / / __ `/ ___/
  67. // / /___/ /_/ / / / / |/ / /_/ (__ )
  68. // \____/\__,_/_/ /_/|___/\__,_/____/
  69. function initCanvas(){
  70. // resize the canvas to fill browser window dynamically
  71. window.addEventListener('resize', onResizeCanvas, false);
  72. onResizeCanvas();
  73. };
  74. function onResizeCanvas() {
  75. _canvas.width = window.innerWidth;
  76. _canvas.height = window.innerHeight;
  77. for (var i = 0; i < _nodes.length; i++) {
  78. _nodes[i].onResizeCanvas();
  79. }
  80. };
  81. // ___ _
  82. // / | (_)___ __ __
  83. // / /| | / / __ `/ |/_/
  84. // / ___ | / / /_/ /> <
  85. // /_/ |_|_/ /\__,_/_/|_|
  86. // /___/
  87. function loadCorpus(){
  88. // console.log('drupalSettings',drupalSettings);
  89. // console.log('window.location', window.location);
  90. var path = window.location.origin + drupalSettings.basepath + "edlp/corpus";
  91. $.getJSON(path, {})
  92. .done(function(data){
  93. onCorpusLoaded(data);
  94. })
  95. .fail(function(jqxhr, textStatus, error){
  96. onCorpusLoadError(jqxhr, textStatus, error);
  97. });
  98. };
  99. function onCorpusLoadError(jqxhr, textStatus, error){
  100. console.warn('corpus load failed', jqxhr.responseText);
  101. };
  102. function onCorpusLoaded(data){
  103. console.log('corpus loaded : data', data);
  104. // console.log('first node', data.nodes[0]);
  105. // buildParticles(data.nodes);
  106. initNodePopup();
  107. buildNodes(data.nodes);
  108. initEvents();
  109. startAnime();
  110. };
  111. // _ __ __
  112. // / | / /___ ____/ /__ _____
  113. // / |/ / __ \/ __ / _ \/ ___/
  114. // / /| / /_/ / /_/ / __(__ )
  115. // /_/ |_/\____/\__,_/\___/____/
  116. function buildNodes(nodes){
  117. console.log("buildNodes", nodes);
  118. var x,y,d;
  119. for (var i in nodes) {
  120. d = i < 1 ? true : false;
  121. _nodes.push(new Node(i,nodes[i],d));
  122. }
  123. };
  124. function Node(i,node,d){
  125. this.id = i;
  126. for(key in node)
  127. this[key] = node[key];
  128. this.debug = d;
  129. this.mass = 8;
  130. this.velocity_threshold = 0.01;
  131. // define radius regarding entries length
  132. switch(true){
  133. case this.entrees.length > 1 & this.entrees.length <= 3:
  134. this.r = 7.5;
  135. break;
  136. case this.entrees.length > 3:
  137. this.r = 10;
  138. break;
  139. default:
  140. this.r = 5;
  141. break;
  142. }
  143. this.e_color = 'e_col_'+this.entrees[Math.floor(Math.random(this.entrees.length))];
  144. this.hover = false;
  145. this.opened = false;
  146. // prototypes
  147. if (typeof Node.initialized == "undefined") {
  148. Node.prototype.init = function(){
  149. this.calcWallLimits();
  150. this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left);
  151. this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top);
  152. // physics
  153. this.p = _physics.makeParticle(this.mass, this.x, this.y);
  154. this.p.velocity = new Physics.Vector((Math.random()-0.5)*_p_velocity_factor, (Math.random()-0.5)*_p_velocity_factor);
  155. // if(this.id == '620'){
  156. // _node_pop_up.setNode(this);
  157. // }
  158. };
  159. Node.prototype.calcWallLimits = function(){
  160. this.wall_limits = {
  161. top: _canvas_props.margin_top +this.r,
  162. right: _canvas.width -_canvas_props.margin_right -this.r,
  163. bottom: _canvas.height -_canvas_props.margin_bottom -this.r,
  164. left: _canvas_props.margin_left +this.r
  165. }
  166. };
  167. Node.prototype.onResizeCanvas = function(){
  168. this.calcWallLimits();
  169. // TODO: when canvas is smaller what about nodes hors champs, they are coming back alone but it's too long
  170. };
  171. Node.prototype.onUpdate = function(){
  172. // if(this.id == 0){
  173. // console.log(_physics.playing);
  174. // }
  175. if(!this.p.resting()){
  176. this.checkVelocityThreshold();
  177. this.checkWallBouncing();
  178. this.updatePos();
  179. }
  180. this.checkMouse();
  181. // if(this.debug)
  182. // console.log("Node pos: ", {x:this.x, y:this.y});
  183. this.draw();
  184. };
  185. Node.prototype.checkVelocityThreshold = function(){
  186. if (Math.abs(this.p.velocity.x) < this.velocity_threshold
  187. && Math.abs(this.p.velocity.y) < this.velocity_threshold){
  188. this.p.velocity.multiplyScalar(0);
  189. // this.p.makeFixed();
  190. }
  191. };
  192. Node.prototype.checkWallBouncing = function(){
  193. if(
  194. (this.x < this.wall_limits.left && this.p.velocity.x < 0)
  195. || (this.x > this.wall_limits.right && this.p.velocity.x > 0)
  196. ){
  197. // console.log("bounce x");
  198. this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75);
  199. }
  200. if(
  201. (this.y < this.wall_limits.top && this.p.velocity.y < 0)
  202. || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0)
  203. ){
  204. // console.log("bounce y");
  205. this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75);
  206. }
  207. };
  208. Node.prototype.updatePos = function(){
  209. this.x = this.p.position.x;
  210. this.y = this.p.position.y;
  211. };
  212. Node.prototype.checkMouse = function(){
  213. if( _m_pos.x > this.x - this.r
  214. && _m_pos.x < this.x + this.r
  215. && _m_pos.y > this.y - this.r
  216. && _m_pos.y < this.y + this.r){
  217. if(_node_hover_id == -1 || _node_hover_id !== this.id){
  218. console.log("Node hover", this.id);
  219. this.hover = true;
  220. _node_hover_id = this.id;
  221. _node_pop_up.setNode(this);
  222. }
  223. }else{
  224. this.hover = false;
  225. if (_node_hover_id == this.id) {
  226. _node_hover_id = -1;
  227. _node_pop_up.removeNode();
  228. }
  229. }
  230. };
  231. Node.prototype.open = function(){
  232. this.opened = true;
  233. };
  234. Node.prototype.close = function(){
  235. this.opened = false;
  236. };
  237. Node.prototype.draw = function(){
  238. // carre plein
  239. // clouleur aléatoire ds les entrees
  240. // 3 tailles :
  241. // - 1 entree : petit carre 5px
  242. // - 2-3 entrees : moyen 7.5px
  243. // - >3 entrees : grand 10px
  244. // actif entouré de rouge
  245. _ctx.beginPath();
  246. _ctx.fillStyle = !this.p.resting() ? edlp_vars[this.e_color] : 'rgb(0, 0, 0)';
  247. // _ctx.fillStyle = edlp_vars[this.e_color];
  248. _ctx.fillRect(this.x - this.r,this.y - this.r,this.r*2,this.r*2);
  249. if(this.opened){
  250. _ctx.lineWidth = '1px';
  251. _ctx.strokeStyle = 'rgb(255,0,0)';
  252. _ctx.strokeRect(this.x - this.r-3,this.y - this.r-3,this.r*2+6,this.r*2+6);
  253. }
  254. _ctx.closePath();
  255. };
  256. Node.initialized = true;
  257. }
  258. this.init();
  259. };
  260. // TODO: we may convert a lot of computation into a web worker https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
  261. function checkParticulesCollisions(){
  262. // pre create vars to save memory;
  263. var d, full_rad,
  264. newVelX1, newVelY1, newVelX2, newVelY2,
  265. makeup, angle;
  266. // colisions between _particules
  267. for (var n = 0; n < _nodes.length; n++) {
  268. for (var q = n+1; q < _nodes.length; q++) {
  269. if(q===n) continue;
  270. // if(!_nodes[q].moving) continue;
  271. d = _nodes[n].p.distanceTo(_nodes[q].p);
  272. full_rad = _nodes[n].r + _nodes[q].r;
  273. // if not colliding skip following
  274. if(d > full_rad) continue;
  275. // apply new forces if colliding
  276. newVelX1 = (_nodes[n].p.velocity.x * (_nodes[n].p.mass - _nodes[q].p.mass)
  277. + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  278. newVelY1 = (_nodes[n].p.velocity.y * (_nodes[n].p.mass - _nodes[q].p.mass)
  279. + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  280. newVelX2 = (_nodes[q].p.velocity.x * (_nodes[q].p.mass - _nodes[n].p.mass)
  281. + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  282. newVelY2 = (_nodes[q].p.velocity.y * (_nodes[q].p.mass - _nodes[n].p.mass)
  283. + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass);
  284. _nodes[n].p.velocity.x = newVelX1;
  285. _nodes[n].p.velocity.y = newVelY1;
  286. _nodes[q].p.velocity.x = newVelX2;
  287. _nodes[q].p.velocity.y = newVelY2;
  288. _nodes[n].p.velocity.multiplyScalar(0.85);
  289. _nodes[q].p.velocity.multiplyScalar(0.85);
  290. // move particles if they overlap
  291. if (d < full_rad) {
  292. makeup = full_rad/2 - d/2;
  293. angle = Math.atan2(_nodes[q].p.position.y - _nodes[n].p.position.y, _nodes[q].p.position.x - _nodes[n].p.position.x);
  294. _nodes[q].p.position.x += makeup * Math.cos(angle);
  295. _nodes[q].p.position.y += makeup * Math.sin(angle);
  296. angle += Math.PI;
  297. _nodes[n].p.position.x += makeup * Math.cos(angle);
  298. _nodes[n].p.position.y += makeup * Math.sin(angle);
  299. }
  300. }
  301. }
  302. };
  303. // show opened audio node
  304. function openNode(id){
  305. closeNode();
  306. _node_opened_id = id;
  307. _nodes[id].open();
  308. }
  309. function closeNode(){
  310. if(_node_opened_id != -1){
  311. _nodes[_node_opened_id].close();
  312. _node_opened_id = -1;
  313. }
  314. }
  315. // ______ __
  316. // / ____/ _____ ____ / /______
  317. // / __/ | | / / _ \/ __ \/ __/ ___/
  318. // / /___ | |/ / __/ / / / /_(__ )
  319. // /_____/ |___/\___/_/ /_/\__/____/
  320. function initEvents(){
  321. _$canvas
  322. .on('mousemove', function(event) {
  323. event.preventDefault();
  324. // console.log("onMouseMove");
  325. _m_pos.x = event.originalEvent.clientX;
  326. _m_pos.y = event.originalEvent.clientY;
  327. // console.log("/ _ / - / _ /");
  328. // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y});
  329. // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y});
  330. })
  331. .on('click', function(event) {
  332. if(event.target.tagName != "A" && event.target.tagName != "INPUT"){
  333. // console.log("Corpus : click");
  334. event.preventDefault();
  335. if(_node_hover_id != -1){
  336. // console.log("corpus : click on node", _nodes[_node_hover_id]);
  337. var event = {
  338. 'type':'corpus-cliked-on-node',
  339. 'target_node':{
  340. 'id':_node_hover_id,
  341. 'nid':_nodes[_node_hover_id].nid,
  342. 'audio_url':_nodes[_node_hover_id].son_url
  343. },
  344. };
  345. _$canvas.trigger(event);
  346. openNode(_node_hover_id);
  347. }else{
  348. // console.log('corpus : click on map');
  349. _$canvas.trigger('corpus-cliked-on-map');
  350. }
  351. }
  352. })
  353. .on('audio-node-closed', function(e){
  354. closeNode();
  355. });
  356. _$entrees_block_termlinks.on('click', function(event) {
  357. event.preventDefault();
  358. var $li = $(this).parents('li');
  359. if(!$li.is('.opened')){
  360. _$entrees_block_termlinks.parents('li').removeClass('opened');
  361. $li.addClass('opened');
  362. }else{
  363. $li.removeClass('opened');
  364. }
  365. return false;
  366. });
  367. };
  368. // _ _ _ ___ _ _
  369. // | \| |___ __| |___| _ \___ _ __| | | |_ __
  370. // | .` / _ \/ _` / -_) _/ _ \ '_ \ |_| | '_ \
  371. // |_|\_\___/\__,_\___|_| \___/ .__/\___/| .__/
  372. // |_| |_|
  373. function initNodePopup(){
  374. _node_pop_up = new NodePopUp();
  375. };
  376. function NodePopUp(){
  377. this.visible = false;
  378. this.node;
  379. this.$dom = $('<div>')
  380. .addClass('node-popup')
  381. .attr('pos', 'top-right')
  382. .appendTo('body');
  383. this.$content = $('<div>').addClass('inner').appendTo(this.$dom);
  384. if (typeof NodePopUp.initialized == "undefined") {
  385. NodePopUp.prototype.setNode = function(n){
  386. // console.log('NodePopUp setNode()');
  387. this.node = n;
  388. // positioning NodePopUp regarding node position
  389. this.setPositioning();
  390. // update NodePopUp content
  391. this.setContent();
  392. };
  393. NodePopUp.prototype.setPositioning = function(){
  394. switch(true){
  395. case this.node.x > this.node.wall_limits.right-350 && this.node.y < this.node.wall_limits.top+200:
  396. this.$dom.attr('pos', 'bottom-left');
  397. break;
  398. case this.node.x > this.node.wall_limits.right-350:
  399. this.$dom.attr('pos', 'top-left');
  400. break;
  401. case this.node.y < this.node.wall_limits.top+200:
  402. this.$dom.attr('pos', 'bottom-right');
  403. break;
  404. default:
  405. this.$dom.attr('pos', 'top-right');
  406. }
  407. };
  408. NodePopUp.prototype.setContent = function(){
  409. // console.log(this.node);
  410. this.$content.html('');
  411. var $entrees = $('<div>').addClass('entrees');
  412. for (var i = 0; i < this.node.entrees.length; i++) {
  413. var tid = this.node.entrees[i];
  414. $entrees.append($('<span>').addClass('entree').attr('tid', tid));
  415. }
  416. this.$content
  417. .append($entrees)
  418. .append('<h2 class="title">'+this.node.title+'</h2>')
  419. .append('<section class="description">'+this.node.description+'</section>');
  420. // TODO: favoris marker
  421. };
  422. NodePopUp.prototype.removeNode = function(){
  423. // console.log('NodePopUp removeNode()');
  424. this.node = false;
  425. };
  426. NodePopUp.prototype.draw = function(){
  427. if(this.node){
  428. this.$dom.css({
  429. 'display':"block",
  430. 'left':this.node.x+"px",
  431. 'top':this.node.y+"px",
  432. });
  433. }else{
  434. this.$dom.css({
  435. 'display':"none",
  436. });
  437. }
  438. };
  439. NodePopUp.initialized = true;
  440. }
  441. }
  442. // ____ __
  443. // / __ \___ ____ ____/ /__ _____
  444. // / /_/ / _ \/ __ \/ __ / _ \/ ___/
  445. // / _, _/ __/ / / / /_/ / __/ /
  446. // /_/ |_|\___/_/ /_/\__,_/\___/_/
  447. function render(){
  448. _ctx.clearRect(0, 0, _canvas.width, _canvas.height);
  449. checkParticulesCollisions();
  450. for (var i = 0; i < _nodes.length; i++) {
  451. _nodes[i].onUpdate();
  452. }
  453. _node_pop_up.draw();
  454. if(_node_hover_id != -1){
  455. _canvas.style.cursor = 'pointer';
  456. highlightEntries();
  457. // _node_pop_up.visible = true;
  458. }else{
  459. _canvas.style.cursor = 'auto';
  460. _$entrees_block_termlinks.removeClass('highlighted');
  461. // _node_pop_up.visible = false;
  462. }
  463. };
  464. // _ _ _ _ _ _ _ _ ___ _ _
  465. // | || (_)__ _| |_ | |_(_)__ _| |_| |_| __|_ _| |_ _ _(_)___ ___
  466. // | __ | / _` | ' \| / / / _` | ' \ _| _|| ' \ _| '_| / -_|_-<
  467. // |_||_|_\__, |_||_|_\_\_\__, |_||_\__|___|_||_\__|_| |_\___/__/
  468. // |___/ |___/
  469. function highlightEntries(){
  470. _$entrees_block_termlinks.removeClass('highlighted');
  471. var entree;
  472. for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
  473. entree = _nodes[_node_hover_id].entrees[i];
  474. _$entrees_block_termlinks.filter(function(){
  475. return $(this).attr('tid') == entree;
  476. }).addClass('highlighted');
  477. }
  478. }
  479. // ___ _ _ _ _
  480. // / __| |_ __ _ _ _| |_ /_\ _ _ (_)_ __ ___
  481. // \__ \ _/ _` | '_| _|/ _ \| ' \| | ' \/ -_)
  482. // |___/\__\__,_|_| \__/_/ \_\_||_|_|_|_|_\___|
  483. function startAnime(){
  484. _physics.onUpdate(render);
  485. _physics.play()
  486. $('body').trigger('corpus-map-ready');
  487. };
  488. init();
  489. }
  490. $(document).ready(function($) {
  491. var edlpcorpus = new EdlpCorpus();
  492. });
  493. })(jQuery);