/** * @Author: Bachir Soussi Chiadmi * @Date: 18-12-2017 * @Email: bachir@figureslibres.io * @Filename: corpus.js * @Last modified by: bach * @Last modified time: 20-12-2017 * @License: GPL-V3 */ /** * depends on : * * physics.js : http://jonobr1.com/Physics/# * EaselJS : https://createjs.com/docs/easeljs/modules/EaselJS.html * */ (function($) { EdlpCorpus = function(){ var _activated = true; var _$container = $('body'); var _$canvas = $('').appendTo(_$container); var _canvas = _$canvas[0]; var _ctx = _canvas.getContext('2d'); var _canvas_props = { 'margin_top':100, 'margin_right':0, 'margin_bottom':100, 'margin_left':0 }; var _physics = new Physics(); // var _stage = new createjs.Stage('corpus-map'); var _nodes = []; // var _particules = []; // var _base_radius = 3; // nodes radius (real radius, not diametre) var _p_velocity_factor = 0.5; var _m_pos = {x:0, y:0}; var _node_hover_id = -1; var _node_opened_id = -1; var _$entrees_block = $('#block-edlpentreesblock'); var _$entrees_block_termlinks = $('a.term-link', _$entrees_block); var _node_pop_up; // Colors depend on edlp_vars loaded by edlptheme // console.log('Corpus : edlp_vars', edlp_vars); // ____ _ __ // / _/___ (_) /______ // / // __ \/ / __/ ___/ // _/ // / / / / /_(__ ) // /___/_/ /_/_/\__/____/ function init(){ console.log("EdlpCorpus init()"); // console.log("document", document); initMap(); }; function initMap(){ console.log("EdlpCorpus initMap()"); // console.log('_physics',_physics); initCanvas(); if(_activated){ loadCorpus(); }else{ _$canvas.addClass('de-activated'); } }; // ______ // / ____/___ _____ _ ______ ______ // / / / __ `/ __ \ | / / __ `/ ___/ // / /___/ /_/ / / / / |/ / /_/ (__ ) // \____/\__,_/_/ /_/|___/\__,_/____/ function initCanvas(){ // resize the canvas to fill browser window dynamically window.addEventListener('resize', onResizeCanvas, false); onResizeCanvas(); }; function onResizeCanvas() { _canvas.width = window.innerWidth; _canvas.height = window.innerHeight; for (var i = 0; i < _nodes.length; i++) { _nodes[i].onResizeCanvas(); } }; // ___ _ // / | (_)___ __ __ // / /| | / / __ `/ |/_/ // / ___ | / / /_/ /> < // /_/ |_|_/ /\__,_/_/|_| // /___/ function loadCorpus(){ // console.log('drupalSettings',drupalSettings); // console.log('window.location', window.location); var path = window.location.origin + drupalSettings.basepath + "edlp/corpus"; $.getJSON(path, {}) .done(function(data){ onCorpusLoaded(data); }) .fail(function(jqxhr, textStatus, error){ onCorpusLoadError(jqxhr, textStatus, error); }); }; function onCorpusLoadError(jqxhr, textStatus, error){ console.warn('corpus load failed', jqxhr.responseText); }; function onCorpusLoaded(data){ console.log('corpus loaded : data', data); // console.log('first node', data.nodes[0]); // buildParticles(data.nodes); initNodePopup(); buildNodes(data.nodes); initEvents(); startAnime(); }; // _ __ __ // / | / /___ ____/ /__ _____ // / |/ / __ \/ __ / _ \/ ___/ // / /| / /_/ / /_/ / __(__ ) // /_/ |_/\____/\__,_/\___/____/ function buildNodes(nodes){ console.log("buildNodes", nodes); var x,y,d; for (var i in nodes) { d = i < 1 ? true : false; _nodes.push(new Node(i,nodes[i],d)); } }; function Node(i,node,d){ this.id = i; for(key in node) this[key] = node[key]; this.debug = d; this.mass = 8; this.velocity_threshold = 0.01; // define radius regarding entries length switch(true){ case this.entrees.length > 1 & this.entrees.length <= 3: this.r = 7.5; break; case this.entrees.length > 3: this.r = 10; break; default: this.r = 5; break; } this.e_color = 'e_col_'+this.entrees[Math.floor(Math.random(this.entrees.length))]; this.hover = false; this.opened = false; // prototypes if (typeof Node.initialized == "undefined") { Node.prototype.init = function(){ this.calcWallLimits(); this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left); this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top); // physics this.p = _physics.makeParticle(this.mass, this.x, this.y); this.p.velocity = new Physics.Vector((Math.random()-0.5)*_p_velocity_factor, (Math.random()-0.5)*_p_velocity_factor); // if(this.id == '620'){ // _node_pop_up.setNode(this); // } }; Node.prototype.calcWallLimits = function(){ this.wall_limits = { top: _canvas_props.margin_top +this.r, right: _canvas.width -_canvas_props.margin_right -this.r, bottom: _canvas.height -_canvas_props.margin_bottom -this.r, left: _canvas_props.margin_left +this.r } }; Node.prototype.onResizeCanvas = function(){ this.calcWallLimits(); // TODO: when canvas is smaller what about nodes hors champs, they are coming back alone but it's too long }; Node.prototype.onUpdate = function(){ // if(this.id == 0){ // console.log(_physics.playing); // } if(!this.p.resting()){ this.checkVelocityThreshold(); this.checkWallBouncing(); this.updatePos(); } this.checkMouse(); // if(this.debug) // console.log("Node pos: ", {x:this.x, y:this.y}); this.draw(); }; Node.prototype.checkVelocityThreshold = function(){ if (Math.abs(this.p.velocity.x) < this.velocity_threshold && Math.abs(this.p.velocity.y) < this.velocity_threshold){ this.p.velocity.multiplyScalar(0); // this.p.makeFixed(); } }; Node.prototype.checkWallBouncing = function(){ if( (this.x < this.wall_limits.left && this.p.velocity.x < 0) || (this.x > this.wall_limits.right && this.p.velocity.x > 0) ){ // console.log("bounce x"); this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75); } if( (this.y < this.wall_limits.top && this.p.velocity.y < 0) || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0) ){ // console.log("bounce y"); this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75); } }; Node.prototype.updatePos = function(){ this.x = this.p.position.x; this.y = this.p.position.y; }; Node.prototype.checkMouse = function(){ if( _m_pos.x > this.x - this.r && _m_pos.x < this.x + this.r && _m_pos.y > this.y - this.r && _m_pos.y < this.y + this.r){ if(_node_hover_id == -1 || _node_hover_id !== this.id){ console.log("Node hover", this.id); this.hover = true; _node_hover_id = this.id; _node_pop_up.setNode(this); } }else{ this.hover = false; if (_node_hover_id == this.id) { _node_hover_id = -1; _node_pop_up.removeNode(); } } }; Node.prototype.open = function(){ this.opened = true; }; Node.prototype.close = function(){ this.opened = false; }; Node.prototype.draw = function(){ // carre plein // clouleur aléatoire ds les entrees // 3 tailles : // - 1 entree : petit carre 5px // - 2-3 entrees : moyen 7.5px // - >3 entrees : grand 10px // actif entouré de rouge _ctx.beginPath(); _ctx.fillStyle = !this.p.resting() ? edlp_vars[this.e_color] : 'rgb(0, 0, 0)'; // _ctx.fillStyle = edlp_vars[this.e_color]; _ctx.fillRect(this.x - this.r,this.y - this.r,this.r*2,this.r*2); if(this.opened){ _ctx.lineWidth = '1px'; _ctx.strokeStyle = 'rgb(255,0,0)'; _ctx.strokeRect(this.x - this.r-3,this.y - this.r-3,this.r*2+6,this.r*2+6); } _ctx.closePath(); }; Node.initialized = true; } this.init(); }; // 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 function checkParticulesCollisions(){ // pre create vars to save memory; var d, full_rad, newVelX1, newVelY1, newVelX2, newVelY2, makeup, angle; // colisions between _particules for (var n = 0; n < _nodes.length; n++) { for (var q = n+1; q < _nodes.length; q++) { if(q===n) continue; // if(!_nodes[q].moving) continue; d = _nodes[n].p.distanceTo(_nodes[q].p); full_rad = _nodes[n].r + _nodes[q].r; // if not colliding skip following if(d > full_rad) continue; // apply new forces if colliding newVelX1 = (_nodes[n].p.velocity.x * (_nodes[n].p.mass - _nodes[q].p.mass) + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass); newVelY1 = (_nodes[n].p.velocity.y * (_nodes[n].p.mass - _nodes[q].p.mass) + (2 * _nodes[q].p.mass * _nodes[q].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass); newVelX2 = (_nodes[q].p.velocity.x * (_nodes[q].p.mass - _nodes[n].p.mass) + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.x)) / (_nodes[n].p.mass + _nodes[q].p.mass); newVelY2 = (_nodes[q].p.velocity.y * (_nodes[q].p.mass - _nodes[n].p.mass) + (2 * _nodes[n].p.mass * _nodes[n].p.velocity.y)) / (_nodes[n].p.mass + _nodes[q].p.mass); _nodes[n].p.velocity.x = newVelX1; _nodes[n].p.velocity.y = newVelY1; _nodes[q].p.velocity.x = newVelX2; _nodes[q].p.velocity.y = newVelY2; _nodes[n].p.velocity.multiplyScalar(0.85); _nodes[q].p.velocity.multiplyScalar(0.85); // move particles if they overlap if (d < full_rad) { makeup = full_rad/2 - d/2; angle = Math.atan2(_nodes[q].p.position.y - _nodes[n].p.position.y, _nodes[q].p.position.x - _nodes[n].p.position.x); _nodes[q].p.position.x += makeup * Math.cos(angle); _nodes[q].p.position.y += makeup * Math.sin(angle); angle += Math.PI; _nodes[n].p.position.x += makeup * Math.cos(angle); _nodes[n].p.position.y += makeup * Math.sin(angle); } } } }; // show opened audio node function openNode(id){ closeNode(); _node_opened_id = id; _nodes[id].open(); } function closeNode(){ if(_node_opened_id != -1){ _nodes[_node_opened_id].close(); _node_opened_id = -1; } } // ______ __ // / ____/ _____ ____ / /______ // / __/ | | / / _ \/ __ \/ __/ ___/ // / /___ | |/ / __/ / / / /_(__ ) // /_____/ |___/\___/_/ /_/\__/____/ function initEvents(){ _$canvas .on('mousemove', function(event) { event.preventDefault(); // console.log("onMouseMove"); _m_pos.x = event.originalEvent.clientX; _m_pos.y = event.originalEvent.clientY; // console.log("/ _ / - / _ /"); // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); }) .on('click', function(event) { if(event.target.tagName != "A" && event.target.tagName != "INPUT"){ // console.log("Corpus : click"); event.preventDefault(); if(_node_hover_id != -1){ // console.log("corpus : click on node", _nodes[_node_hover_id]); var event = { 'type':'corpus-cliked-on-node', 'target_node':{ 'id':_node_hover_id, 'nid':_nodes[_node_hover_id].nid, 'audio_url':_nodes[_node_hover_id].son_url }, }; _$canvas.trigger(event); openNode(_node_hover_id); }else{ // console.log('corpus : click on map'); _$canvas.trigger('corpus-cliked-on-map'); } } }) .on('audio-node-closed', function(e){ closeNode(); }); _$entrees_block_termlinks.on('click', function(event) { event.preventDefault(); var $li = $(this).parents('li'); if(!$li.is('.opened')){ _$entrees_block_termlinks.parents('li').removeClass('opened'); $li.addClass('opened'); }else{ $li.removeClass('opened'); } return false; }); }; // _ _ _ ___ _ _ // | \| |___ __| |___| _ \___ _ __| | | |_ __ // | .` / _ \/ _` / -_) _/ _ \ '_ \ |_| | '_ \ // |_|\_\___/\__,_\___|_| \___/ .__/\___/| .__/ // |_| |_| function initNodePopup(){ _node_pop_up = new NodePopUp(); }; function NodePopUp(){ this.visible = false; this.node; this.$dom = $('
') .addClass('node-popup') .attr('pos', 'top-right') .appendTo('body'); this.$content = $('
').addClass('inner').appendTo(this.$dom); if (typeof NodePopUp.initialized == "undefined") { NodePopUp.prototype.setNode = function(n){ // console.log('NodePopUp setNode()'); this.node = n; // positioning NodePopUp regarding node position this.setPositioning(); // update NodePopUp content this.setContent(); }; NodePopUp.prototype.setPositioning = function(){ switch(true){ case this.node.x > this.node.wall_limits.right-350 && this.node.y < this.node.wall_limits.top+200: this.$dom.attr('pos', 'bottom-left'); break; case this.node.x > this.node.wall_limits.right-350: this.$dom.attr('pos', 'top-left'); break; case this.node.y < this.node.wall_limits.top+200: this.$dom.attr('pos', 'bottom-right'); break; default: this.$dom.attr('pos', 'top-right'); } }; NodePopUp.prototype.setContent = function(){ // console.log(this.node); this.$content.html(''); var $entrees = $('
').addClass('entrees'); for (var i = 0; i < this.node.entrees.length; i++) { var tid = this.node.entrees[i]; $entrees.append($('').addClass('entree').attr('tid', tid)); } this.$content .append($entrees) .append('

'+this.node.title+'

') .append('
'+this.node.description+'
'); // TODO: favoris marker }; NodePopUp.prototype.removeNode = function(){ // console.log('NodePopUp removeNode()'); this.node = false; }; NodePopUp.prototype.draw = function(){ if(this.node){ this.$dom.css({ 'display':"block", 'left':this.node.x+"px", 'top':this.node.y+"px", }); }else{ this.$dom.css({ 'display':"none", }); } }; NodePopUp.initialized = true; } } // ____ __ // / __ \___ ____ ____/ /__ _____ // / /_/ / _ \/ __ \/ __ / _ \/ ___/ // / _, _/ __/ / / / /_/ / __/ / // /_/ |_|\___/_/ /_/\__,_/\___/_/ function render(){ _ctx.clearRect(0, 0, _canvas.width, _canvas.height); checkParticulesCollisions(); for (var i = 0; i < _nodes.length; i++) { _nodes[i].onUpdate(); } _node_pop_up.draw(); if(_node_hover_id != -1){ _canvas.style.cursor = 'pointer'; highlightEntries(); // _node_pop_up.visible = true; }else{ _canvas.style.cursor = 'auto'; _$entrees_block_termlinks.removeClass('highlighted'); // _node_pop_up.visible = false; } }; // _ _ _ _ _ _ _ _ ___ _ _ // | || (_)__ _| |_ | |_(_)__ _| |_| |_| __|_ _| |_ _ _(_)___ ___ // | __ | / _` | ' \| / / / _` | ' \ _| _|| ' \ _| '_| / -_|_-< // |_||_|_\__, |_||_|_\_\_\__, |_||_\__|___|_||_\__|_| |_\___/__/ // |___/ |___/ function highlightEntries(){ _$entrees_block_termlinks.removeClass('highlighted'); var entree; for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) { entree = _nodes[_node_hover_id].entrees[i]; _$entrees_block_termlinks.filter(function(){ return $(this).attr('tid') == entree; }).addClass('highlighted'); } } // ___ _ _ _ _ // / __| |_ __ _ _ _| |_ /_\ _ _ (_)_ __ ___ // \__ \ _/ _` | '_| _|/ _ \| ' \| | ' \/ -_) // |___/\__\__,_|_| \__/_/ \_\_||_|_|_|_|_\___| function startAnime(){ _physics.onUpdate(render); _physics.play() $('body').trigger('corpus-map-ready'); }; init(); } $(document).ready(function($) { var edlpcorpus = new EdlpCorpus(); }); })(jQuery);