/**
* @ignore
* ================================================================================================
* fetch.js =======================================================================================
* ================================================================================================
* Extract data from JSON files for activate diplays (graph, board, search engine)
*/
/**
* Graph attributs and methods
* @namespace Graph
*/
const graph = {
/**
* All nodes attributes
* @type array
* @memberof Graph
*/
nodes: [],
/**
* All links attributes
* @type array
* @memberof Graph
*/
links: [],
/**
* If the nodes must contain an image
* @type bool
* @memberof Graph
*/
nodeContainImage: true,
/**
* Graph SVG position as the user view parameters
* @type obj
* @default
* @memberof Graph
*/
pos: {
zoom: 1,
x: 0,
y: 0
},
elts: {},
defs: {},
/**
* Id of the current selected node
* @type obj
* @default
* @memberof Graph
*/
selectedNodeId: undefined,
/**
* Selected SVG as a d3 object
* @type obj
* @memberof Graph
*/
svg: d3.select("#graph")
}
Promise.all([
fetch('data/entites.json'), // = data[0]
fetch('data/liens.json') // = data[1]
]).then(function(data) {
// get data
const entites = data[0]
const liens = data[1]
Promise.all([
entites.json(),
liens.json()
]).then(function(data) {
// get JSON from data
const entites = data[0]
const liens = data[1]
/**
* Fetch the entites from 'entites.json'
* @namespace Fetch_entites
*/
graph.nodes = entites
.filter(entite => entite.id)
.map(function(entite) {
return {
id: entite.id,
label: entite.label,
title: entite.titre,
group: entite.relation_otlet,
image: './assets/images/' + entite.photo,
genre: entite.genre,
annee_naissance: entite.annee_naissance,
annee_mort: ((!entite.annee_mort) ? undefined : ' - ' + entite.annee_mort),
pays: entite.pays,
domaine: entite.domaine,
description: entite.description,
lien_wikipedia: entite.lien_wikipedia,
// translated metas
Fr: {
title: entite.titre,
pays: entite.pays,
domaine: entite.domaine,
description: entite.description
},
En: {
title: entite.titre_en,
pays: entite.pays_en,
domaine: entite.domaine_en,
description: entite.description_en
},
sortName: entite.nom || entite.label,
hidden: false
};
});
/**
* Fetch the entites from 'entites.json'
* @namespace Fetch_links
*/
graph.links = liens
.filter(lien => lien.id && lien.from && lien.to)
.map(function(lien) {
return {
id: lien.id,
source: lien.from,
target: lien.to,
title: lien.label,
Fr: {
title: lien.label
},
En: {
title: lien.label_en
},
}
});
graph.init();
});
});
/**
* @ignore
* ================================================================================================
* network.js =====================================================================================
* ================================================================================================
* Display & lauch events of the graph
* Distribute the data extracted from fetch.js
*/
/**
* Parameters for graph : node gravity, appearance
* @type obj
* @default
* @memberof Graph
*/
graph.params = {
nodeSize: 12,
nodeStrokeSize: 2,
force: 800,
distanceMax: 400,
highlightColor: 'red'
};
/**
* Graph svg with
* @type number
* @memberof Graph
*/
graph.width =+ graph.svg.node().getBoundingClientRect().width;
/**
* Graph svg height
* @type number
* @memberof Graph
*/
graph.height =+ graph.svg.node().getBoundingClientRect().height;
/**
* Graph initialisation
* @memberof Graph
*/
graph.init = function() {
graph.params.imgSize = graph.params.nodeSize + 10;
d3.select(window).on("resize", function () {
graph.width =+ graph.svg.node().getBoundingClientRect().width;
graph.height =+ graph.svg.node().getBoundingClientRect().height;
toPosition();
});
graph.simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(graph.width / 2, graph.height / 2));
/**
* Infotip (on hover links)
* @memberof Graph
*/
graph.elts.tip = undefined;
/**
* Graph links (d3) elements
* @memberof Graph
*/
graph.elts.links = graph.svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("class", (d) => 'l_' + d.type)
.attr("title", (d) => d.title)
.attr("data-source", (d) => d.source)
.attr("data-target", (d) => d.target)
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y)
.on("mouseenter", function (d) {
if (d.description === '') { return; }
const coordinates = d3.mouse(this)
, x = coordinates[0] + 10
, y = coordinates[1] + 10;
graph.elts.tip = graph.svg.append("g")
.attr("transform", `translate(200,200)`);
let rect = graph.elts.tip.append("rect")
.style("fill", "white")
.style("stroke", "black")
.attr("rx", 2)
.attr("ry", 2);
graph.elts.tip.append("text")
.text(function() {
return d.title;
})
.style("fill", "black")
.attr('font-size', 17 - graph.pos.zoom * 3)
.attr("dy", "1em")
.attr("x", 7)
.attr("class", "tip_description");
const bbox = graph.elts.tip.node().getBBox();
rect.attr("width", bbox.width + 20)
.attr("height", bbox.height);
graph.elts.tip.attr("transform", "translate(" + x + "," + y + ")")
})
.on("mouseout", function (d) {
graph.elts.tip.remove();
})
/**
* Graph nodes (d3) elements
* @memberof Graph
*/
graph.elts.nodes = graph.svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g")
.attr("data-node", (d) => d.id)
.on('click', function(d) {
// openRecord(nodeMetas.id);
switchNode(d.id);
historique.actualiser(d.id);
});
/**
* Graph circle (into nodes) (d3) elements
* @memberof Graph
*/
graph.elts.circles = graph.elts.nodes.append("circle")
.attr("r", (d) => graph.params.nodeSize)
.style("stroke", (d) => chooseColor(d.group))
.attr("stroke-width", graph.params.nodeStrokeSize)
.on('mouseenter', hoverNode)
.on('mouseout', hoverNodeRemove);
if (graph.nodeContainImage === true) {
/**
* Graph images (into nodes) (d3) elements
* @memberof Graph
*/
graph.elts.images = graph.elts.nodes.append("svg:image")
.attr("xlink:href", function(d) { return d.image;})
.attr("clip-path", "url(#image-clip-path)")
.attr('transform', 'translate(-' + graph.params.imgSize / 2 + ', -' + graph.params.imgSize / 2 + ')')
.attr("height", graph.params.imgSize)
.attr("width", graph.params.imgSize)
.call(d3.drag()
.on("start", function(d) {
if (!d3.event.active) graph.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y; })
.on("drag", function(d) {
d.fx = d3.event.x;
d.fy = d3.event.y; })
.on("end", function(d) {
if (!d3.event.active) graph.simulation.alphaTarget(0.0001);
d.fx = null;
d.fy = null; })
)
.on('mouseenter', hoverNode)
.on('mouseout', hoverNodeRemove);
}
/**
* Graph texts (into nodes) (d3) elements
* @memberof Graph
*/
graph.elts.labels = graph.elts.nodes.append("text")
.each(function(d) {
const words = d.label.split(' ')
, max = 25
, text = d3.select(this);
let label = '';
for (let i = 0; i < words.length; i++) {
// combine words and seperate them by a space caracter into label
label += words[i] + ' ';
// if label (words combination) is longer than max & not the single iteration
if (label.length < max && i !== words.length - 1) { continue; }
text.append("tspan")
.attr('x', 0)
.attr('dy', '1.2em')
.text(label.slice(0, -1)); // remove last space caracter
label = '';
}
})
.attr('font-size', 10)
.attr('x', 0)
.attr('y', (d) => graph.params.nodeSize)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
graph.elts.defs = graph.svg.append("defs")
graph.defs.imageClipPath = graph.elts.defs.append("clipPath")
.attr("id", "image-clip-path")
.append("circle")
.attr("cx", graph.params.imgSize / 2)
.attr("cy", graph.params.imgSize / 2)
.attr("r", graph.params.imgSize / 2);
graph.simulation
.nodes(graph.nodes)
.on("tick", function() {
graph.elts.links
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
const marge = 20;
graph.elts.nodes
.attr("transform", function(d) {
d.x = Math.max(graph.params.nodeSize + marge, Math.min(graph.width - graph.params.nodeSize - marge, d.x));
d.y = Math.max(graph.params.nodeSize + marge, Math.min(graph.height - graph.params.nodeSize - marge, d.y));
return "translate(" + d.x + "," + d.y + ")";
});
});
graph.simulation
.force("link")
.links(graph.links);
graph.simulation
.force("center", d3.forceCenter())
.force("charge", d3.forceManyBody());
graph.simulation.force("charge")
.strength(-Math.abs(graph.params.force)) // turn force value to negative number
.distanceMax(graph.params.distanceMax);
function toPosition() {
graph.simulation.force("center")
.x(graph.width * 0.5)
.y(graph.height * 0.5);
graph.simulation
.alpha(1).restart();
}
toPosition();
board.init(); // activate the alphabetical list display
search.input.addEventListener('focus', search.init); // activate the search engine
filter.init(); // activate filters
// If there is entity id one URL : activate
const urlPathnameArray = window.location.pathname.split('/');
let nodeId = urlPathnameArray[urlPathnameArray.length -1];
nodeId = Number(nodeId);
if (switchNode(nodeId, false)) {
historique.init(nodeId);
}
function hoverNode(d) {
graph.elts.nodes.classed('translucent', true);
graph.elts.links.classed('translucent', true);
const ntwOfHoveredNode = getNodeNetwork(d.id)
, nodeHovered = ntwOfHoveredNode.node
, linksHovered = ntwOfHoveredNode.links
, connectedNodesHovered = ntwOfHoveredNode.connectedNodes;
nodeHovered.classed('translucent', false);
linksHovered.classed('translucent', false);
connectedNodesHovered.classed('translucent', false);
if (graph.selectedNodeId) {
const ntwOfSelectedNode = getNodeNetwork(graph.selectedNodeId)
, nodeSelected = ntwOfSelectedNode.node
, linksSelected = ntwOfSelectedNode.links
, connectedNodesSelected = ntwOfSelectedNode.connectedNodes;
nodeSelected.classed('translucent', false);
linksSelected.classed('translucent', false);
connectedNodesSelected.classed('translucent', false);
}
}
function hoverNodeRemove(d) {
graph.elts.nodes.classed('translucent', false);
graph.elts.links.classed('translucent', false);
}
}
/**
* Get d3 elts objects : the node, its links and its linked nodes
* @param {number} nodeId
* @returns {object} - {node, links, connectedNodes}
*/
function getNodeNetwork(nodeId) {
const ntw = {
node: graph.elts.nodes.filter(node => node.id === nodeId),
connectedNodes: []
}
ntw.links = graph.elts.links.filter(function(link) {
if (link.source.id === nodeId && link.target.hidden === false) {
ntw.connectedNodes.push(link.target.id);
return true;
}
if (link.target.id === nodeId && link.source.hidden === false) {
ntw.connectedNodes.push(link.source.id);
return true;
}
});
ntw.connectedNodes = graph.elts.nodes.filter(node => ntw.connectedNodes.includes(node.id));
return ntw;
}
/**
* Highlight a node with the highlight color
* @param {number} nodeId
*/
function highlightNodeNetwork(nodeId) {
const ntw = getNodeNetwork(nodeId)
, node = ntw.node
, links = ntw.links;
node.select('circle').style("stroke", graph.params.highlightColor);
links.classed('highlight', true);
}
/**
* Unlight the selected node (by his id)
*/
function unlightNodeNetwork() {
const ntw = getNodeNetwork(graph.selectedNodeId)
, node = ntw.node
, links = ntw.links;
node.select('circle').style("stroke", (d) => chooseColor(d.group));
links.classed('highlight', false);
}
/**
* Return the RGB color linked to the group name
* @param {string} name - Group name
* @returns {string} - RGB formated color
*/
function chooseColor(name) {
let color;
switch (name) {
case 'collegue':
color = '154, 60, 154'; break;
case 'collaborateur':
color = '97, 172, 97'; break;
case 'opposant':
color = '250, 128, 114'; break;
case 'famille':
color = '102, 179, 222'; break;
case 'otlet':
color = '244, 164, 96'; break;
case 'non-catégorisé':
color = '128,128,128'; break;
case 'institution':
color = '128,128,128'; break;
case 'œuvre':
color = '128,128,128'; break;
case 'évènement':
color = '128,128,128'; break;
default:
color = '169, 169, 169'; break;
}
return ['rgb(', color, ')'].join('');
}
/**
* Return the metadatas from a node (d3) element
* @param {number} nodeId - Entity id
* @returns {mixed} metadatas of false if malfunction
*/
function getNodeMetas(nodeId) {
const nodeMetas = graph.elts.nodes.filter(node => node.id === nodeId).data()[0];
if (!nodeMetas) { return false; }
return nodeMetas;
}
/**
* Return metas from connected nodes
* @param {number} nodeId - Entity id
* @returns {array} objects array contains metadatas
*/
function findConnectedNodes(nodeId) {
return graph.links
.filter(link => link.source.id === nodeId || link.target.id === nodeId)
.map(function(link) {
if (link.source.id === nodeId) {
return link.target;
} else {
return link.source;
}
})
.map(function(link) {
return {
id: link.id,
label: link.label,
relation: link.group,
title: link.title,
hidden: link.hidden
};
});
}
/**
* Change view (graph focus & description bar content) about a node
* @param {number} nodeId - Entity id
* @param {boolean} mustZoom - if true : zoom on node
* @returns {boolean} if it works
*/
function switchNode(nodeId, mustZoom = true) {
var nodeMetas = getNodeMetas(nodeId);
if (nodeMetas === false) { return false; }
if (graph.selectedNodeId) { unlightNodeNetwork(); }
highlightNodeNetwork(nodeId);
graph.selectedNodeId = Number(nodeId);
// rename webpage
document.title = nodeMetas.label + ' - Otetosphère';
if (mustZoom) { zoomToNode(nodeId); }
fiche.fill();
fiche.open();
return true;
}
/**
* @ignore
* ================================================================================================
* board.js =======================================================================================
* ================================================================================================
* Display of the alphabetical list of entities in the form of cards
*/
/**
* Records list attributs & methods
* @namespace Board
*/
var board = {
content: document.querySelector('#board-content'),
wrapper: document.querySelector('#board-wrapper'),
engine: new Board,
/**
* Initialize the records list display
* @memberof Board
*/
init: function() {
this.engine.empty();
this.engine.cards = graph.elts.nodes.filter(node => node.hidden !== true)
.data()
.sort(function (a, b) { return a.sortName.localeCompare(b.sortName); })
.map(function(d) {
let card = new Card;
card.id = d.id;
card.label = d.label;
card.labelFirstLetter = d.sortName.charAt(0);
card.title = (d.title || '');
card.img = d.image;
return card;
});
this.engine.init();
}
}
/**
* Init instance of Card.
* @constructs Card
* @param {number} id - Entity id
* @param {string} label - Entity label
* @param {string} labelFirstLetter - Letter for alphabetical diplaying
* @param {string} title - Entity title
* @param {HTMLElement} domElt - <article> who contain card HTML
*/
function Card() {
this.id = null;
this.label = 'No name';
this.labelFirstLetter = undefined;
this.title = '';
this.domElt = document.createElement('article');
}
/**
* Make card HTML & appendChild in its container
* @param {HTMLElement} container - Container for cards linked by the same first letter
*/
Card.prototype.inscribe = function(container) {
this.domElt.classList.add('card');
this.domElt.innerHTML =
`<header>
<img src="${this.img}" alt="${this.label}">
<div class="card-identite">
<h3 class="card__label">${this.label}</h3>
</div>
</header>
<h4 class="card__titre">${this.title}</h4>`;
container.appendChild(this.domElt);
this.domElt.addEventListener('click', () => {
switchNode(this.id);
historique.actualiser(this.id);
});
}
/**
* Init instance of the Board.
* @constructs Board
* @param {array} cards - Cards objects
* @param {array} letterList - First letters list, feed by Board.fill()
* @param {array} alphaSpace - For store same first letter cards in groups, feed by Board.bundle()
*/
function Board() {
this.domElt = document.querySelector('#board-content');
this.domLetterList = document.querySelector('#board-alphabetic');
this.cards = [];
this.letterList = [];
this.alphaSpace = [];
}
/**
* For each card, verif the first letter. If it change between
* two card put the second and others in a new array (group) while waiting
* for another change
*/
Board.prototype.bundle = function() {
var letter = this.cards[0].labelFirstLetter; // current letter
var letterBundle = []; // same first letter cards array
this.cards.forEach(card => {
if (card.labelFirstLetter != letter) {
// first letter has change
this.alphaSpace.push(letterBundle);
letterBundle = [];
letter = card.labelFirstLetter;
}
letterBundle.push(card);
});
this.alphaSpace.push(letterBundle);
}
/**
* For each cards groups, appendCild the Board elements
* and the Cards prototypes
*/
Board.prototype.fill = function() {
this.alphaSpace.forEach(letterStack => {
var letter = letterStack[0].labelFirstLetter;
this.letterList.push(letter);
var cardStack = document.createElement('div');
cardStack.classList.add('section');
this.domElt.appendChild(cardStack);
var divider = document.createElement('div');
divider.id = 'letter-' + letter;
divider.classList.add('title');
divider.textContent = letter;
cardStack.appendChild(divider);
letterStack.forEach(card => {
card.inscribe(cardStack);
});
});
}
/**
* Generate the nav bar with all context letters
*/
Board.prototype.listLetters = function() {
this.letterList.forEach(letter => {
var listElt = document.createElement('li');
listElt.textContent = letter;
this.domLetterList.appendChild(listElt);
listElt.addEventListener('click', () => {
board.wrapper.scrollTop = 0;
board.wrapper.scrollTo({
top: document.querySelector('#letter-' + letter).getBoundingClientRect().y - 100,
behavior: 'smooth'
});
})
});
}
Board.prototype.init = function() {
this.bundle();
this.fill();
this.listLetters();
}
/**
* Delete all Board elements & data
*/
Board.prototype.empty = function() {
this.domElt.innerHTML = '';
this.domLetterList.innerHTML = '';
this.cards = [];
this.alphaSpace = [];
this.letterList = [];
}
/**
* @ignore
* ================================================================================================
* filter.js =======================================================================================
* ================================================================================================
* Activate filters buttons & hide/show entitied from the graph
*/
/**
* Filters attributs & methods
* @namespace Filter
*/
var filter = {
btnsGroups: document.querySelectorAll('.btn-group'),
volet: {
body: document.querySelector('#filter-volet'),
btnOpen: document.querySelector('#filtre-open'),
btnClose: document.querySelector('#filtre-close')
},
/**
* Initialize the filters click events
* @memberof Filter
*/
init: function() {
this.btnsGroups.forEach(btn => {
var meta = btn.dataset.meta;
var type = btn.dataset.type;
btn.style.backgroundColor = chooseColor(meta);
let isActiveGroup = true;
btn.addEventListener('click', () => {
if (isActiveGroup) {
graph.elts.nodes.filter(node => node[type] == meta)
.each(function(d) {
d.hidden = true;
const ntw = getNodeNetwork(d.id)
, node = ntw.node
, links = ntw.links;
node.style('display', 'none');
links.style('display', 'none');
});
// activation visuelle boutons filtre de entête et volet
document.querySelectorAll('[data-meta="' + btn.dataset.meta + '"]').forEach(btn => {
btn.classList.add('active'); });
isActiveGroup = false;
} else {
graph.elts.nodes.filter(node => node[type] == meta)
.each(function(d) {
d.hidden = false;
const ntw = getNodeNetwork(d.id)
, node = ntw.node
, links = ntw.links;
node.style('display', null);
links.style('display', null);
});
// deactivation visuelle boutons filtre de entête et volet
document.querySelectorAll('[data-meta="' + btn.dataset.meta + '"]').forEach(btn => {
btn.classList.remove('active'); });
isActiveGroup = true;
}
search.reset();
board.init();
});
});
}
}
filter.volet.btnOpen.addEventListener('click', () => {
filter.volet.body.classList.add('lateral--active'); });
filter.volet.btnClose.addEventListener('click', () => {
filter.volet.body.classList.remove('lateral--active'); });
/**
* @ignore
* ================================================================================================
* fiche.js ========================================================================================
* ================================================================================================
* Display the description bar & its fields
*/
/**
* Description bar attributs & methods
* @namespace Fiche
*/
var fiche = {
body: document.querySelector('#fiche'),
content: document.querySelector('#fiche-content'),
entete: document.querySelector('#fiche-entete'),
toggle: document.querySelector('#fiche-toggle-btn'), // arrow button
isOpen: false,
fields: {
title: document.querySelector('#fiche-title'),
wikiLink: document.querySelector('#fiche-wiki-link'),
img: document.querySelector('#fiche-meta-img'),
connexion: document.querySelector('#fiche-connexion'),
permalien: document.querySelector('#fiche-permalien')
},
domFields: document.querySelectorAll('#fiche [data-meta]'),
/**
* position: fixed; the description bar
* @memberof Filter
*/
fixer: function(bool) {
if (bool) { fiche.body.classList.add('lateral--fixed'); }
else { fiche.body.classList.remove('lateral--fixed'); }
},
/**
* Open the description bar
* @memberof Filter
*/
open: function() {
this.toggle.classList.add('active');
fiche.body.classList.add('lateral--active');
this.isOpen = true;
},
/**
* Close the description bar
* @memberof Filter
*/
close: function() {
if (movement.currentSection === 'fiches') { return; }
this.toggle.classList.remove('active');
fiche.body.classList.remove('lateral--active');
this.isOpen = false;
},
/**
* True : description bar can be closed, False : description bar can not be closed
* @param {bool} bool
* @memberof Filter
*/
canClose: function(bool) {
if (bool) { this.toggle.classList.remove('d-none'); }
else { this.toggle.classList.add('d-none'); }
},
/**
* Show entity image on description bar
* @param {string} photoPath - photo path
* @param {string} entiteLabel - Entity name for alt img attribute
* @memberof Filter
*/
setImage: function(photoPath, entiteLabel) {
if (!this.fields.img) { return ; }
if (!photoPath) { return this.fields.img.style.display = 'none'; }
this.fields.img.setAttribute('src', photoPath);
this.fields.img.setAttribute('alt', 'photo de ' + entiteLabel);
},
/**
* Set the entity link to Wikipedia on description bar
* @param {string} wikiLink - URL adress
* @memberof Filter
*/
setWikiLink: function(wikiLink) {
if (!this.fields.wikiLink) { return ; }
if (!wikiLink) {
this.fields.wikiLink.style.display = 'none';
this.fields.wikiLink.setAttribute('href', '')
} else {
this.fields.wikiLink.style.display = 'flex';
this.fields.wikiLink.setAttribute('href', wikiLink)
}
},
/**
* Set an entity meta on a field from description bar
* @param {string} meta - meta text
* @param {HTMLElement} content - HTML element from description bar
* @memberof Filter
*/
setMeta: function(meta, content) {
if (!content) { return ; }
if (!meta) {
content.innerHTML = ''; }
else {
content.innerHTML = meta; }
},
/**
* Set the permanent link button from description bar
* onlick : save link on clipboard
* @memberof Filter
*/
setPermaLink: function() {
this.fields.permalien.addEventListener('click', () => {
const tempInput = document.createElement('input');
document.body.appendChild(tempInput);
tempInput.value = window.location.protocol + '//' + window.location.host + window.location.pathname;
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
this.fields.permalien.classList.add('active'); // CSS animation
this.fields.permalien.textContent = '✓';
this.fields.permalien.addEventListener('animationend', () => {
this.fields.permalien.textContent = 'Permalink' ;
this.fields.permalien.classList.remove('active')
});
});
},
/**
* Generate the connected nodes <ul> list, its description frame
* @param {array} nodeConnectedList - Connected nodes array
* @memberof Filter
*/
setConnexion: function(nodeConnectedList) {
this.fields.connexion.innerHTML = ''; // reset
if (nodeConnectedList === null) { return; }
for (const connectedNode of nodeConnectedList) {
if (connectedNode.hidden == true) { continue; }
var listElt = document.createElement('li');
listElt.textContent = connectedNode.label;
listElt.setAttribute('title', connectedNode.title);
this.fields.connexion.appendChild(listElt);
var puceColored = document.createElement('span');
puceColored.style.backgroundColor = chooseColor(connectedNode.relation);
listElt.prepend(puceColored);
listElt.addEventListener('click', () => {
switchNode(connectedNode.id);
historique.actualiser(connectedNode.id);
});
}
},
/**
* Feed all fields from the description bar about the selected entity
* @memberof Filter
*/
fill: function() {
const nodeMetas = getNodeMetas(graph.selectedNodeId)
if (nodeMetas === false) { return ; }
const nodeConnectedList = findConnectedNodes(graph.selectedNodeId);
// show description bar fields
this.content.classList.add('visible');
// feed all element marked by [data-meta] into description bar
this.domFields.forEach(elt => {
const metaName = elt.dataset.meta;
this.setMeta(nodeMetas[metaName], elt);
});
this.setImage(nodeMetas.image, nodeMetas.label);
this.setWikiLink(nodeMetas.lien_wikipedia);
this.setPermaLink(graph.selectedNodeId);
this.setConnexion(nodeConnectedList);
}
}
fiche.toggle.addEventListener('click', () => {
if (fiche.isOpen) { fiche.close(); }
else { fiche.open(); }
});
fiche.fields.title.addEventListener('click', () => {
switchNode(graph.selectedNodeId); });
/**
* @ignore
* ================================================================================================
* search.js ======================================================================================
* ================================================================================================
* Set search engine parameters & data
*/
/**
* Search bar attributs & methods
* @namespace Search
*/
var search = {
input: document.querySelector('#search'),
resultContent: document.querySelector('#search-result'),
options: {
includeScore: true,
keys: ['label']
},
/**
* Display results from a search request into 'resultContent' elt
* @param {object} resultObj - array of objects (request results)
* @memberof Search
*/
showResult: function(resultObj) {
var nodeId = resultObj.item.id;
var nodeLabel = resultObj.item.label;
var resultElement = document.createElement('li');
resultElement.classList.add('search__result');
resultElement.textContent = nodeLabel;
search.resultContent.appendChild(resultElement);
resultElement.addEventListener('click', () => {
if (graph.selectedNodeId !== undefined && graph.selectedNodeId == nodeId) {
// si cette id correpond à celle du nœeud selectionné
return;
}
search.input.value = nodeLabel;
this.cleanResultContent();
switchNode(nodeId);
historique.actualiser(nodeId);
});
},
/**
* Empty search bar and results
* @memberof Search
*/
reset: function() {
search.input.value = ''; // form value
this.cleanResultContent();
},
/**
* Empty search results
* @memberof Search
*/
cleanResultContent: function() {
search.resultContent.innerHTML = ''; // results
},
/**
* Launch search events on search input and on input
* @memberof Search
*/
init: function() {
const noHiddenNodes = graph.elts.nodes.filter(node => node.hidden !== true)
.data()
.map(function(d) {
return {
id: d.id,
label: d.label
}
});
const fuse = new Fuse(noHiddenNodes, search.options);
search.input.addEventListener('input', () => {
search.resultContent.innerHTML = '';
if (search.input.value == '') { return; }
const resultList = fuse.search(search.input.value);
if (resultList.length > 5) {
// si plus de 5 résultats, limiter à 5
var nbResult = 5;
} else {
// sinon garder l nombre de résultats
var nbResult = resultList.length;
}
for (let i = 0; i < nbResult; i++) {
search.showResult(resultList[i]); }
});
}
}
search.reset();
/**
* @ignore
* ================================================================================================
* translate.js ===================================================================================
* ================================================================================================
* Activate translate buttons & translate website elements on click
*/
(function() {
const activFlag = document.querySelector('.lang-flag[data-active="true"]');
if (!activFlag) { return; }
var langage = {
flags: document.querySelectorAll('.lang-flag'),
actual: activFlag.dataset.lang,
translateAll: function() {
document.querySelectorAll('[data-lang-' + langage.actual.toLowerCase() + ']').forEach(elt => {
eval('elt.innerHTML = elt.dataset.lang' + langage.actual);
});
}
}
langage.flags.forEach(flag => {
flag.addEventListener('click', (e) => {
var flagCliked = e.target;
if (flagCliked.dataset.lang == langage.actual) {
// si le bouton flag cliqué active la langue déjà active
return;
}
// désactiver la surbrillance du flag de la précédante langue
document.querySelector('[data-lang="' + langage.actual + '"]')
.dataset.active = 'false';
// activer la surbrillance du flag de l'actuelle langue
flagCliked.dataset.active = 'true';
langage.actual = flagCliked.dataset.lang;
// translate website interface
langage.translateAll();
// translate graph & entities metas
graph.elts.nodes.each(function(d) {
if (!d[langage.actual]) { return; }
d.title = d[langage.actual].title,
d.description = d[langage.actual].description,
d.domaine = d[langage.actual].domaine,
d.pays = d[langage.actual].pays
});
graph.elts.links.each(function(d) {
if (!d[langage.actual]) { return; }
d.title = d[langage.actual].title
});
fiche.fill();
board.init();
});
});
})()
/**
* @ignore
* ================================================================================================
* zoom.js ========================================================================================
* ================================================================================================
* Manage the point of view of the user on the graph, on nodes
*/
/**
* Graph zoom parameters
* @default
* @memberof Graph
*/
graph.zoomParams = {
zoomInterval: 0.3, // interval between two (de)zoom
zoomMax: 3,
zoomMin: 1
}
var zoom = {
btnPlus: document.querySelector('#zoom-plus'),
btnMoins: document.querySelector('#zoom-moins'),
btnReinitialiser: document.querySelector('#zoom-general'),
interval: 0.1
}
graph.svg.call(d3.zoom().on('zoom', function () {
// for each move one the SVG
if (d3.event.sourceEvent === null) {
zoomMore();
return;
}
switch (d3.event.sourceEvent.type) {
case 'wheel':
// by mouse wheel
if (d3.event.sourceEvent.deltaY >= 0) {
zoomLess();
} else {
zoomMore();
}
break;
case 'mousemove':
// by drag and move with mouse
graph.pos.x += d3.event.sourceEvent.movementX;
graph.pos.y += d3.event.sourceEvent.movementY;
translate();
break;
}
}));
/**
* Zoom in from graph SVG
*/
function zoomMore() {
graph.pos.zoom += graph.zoomParams.zoomInterval;
if (graph.pos.zoom >= graph.zoomParams.zoomMax) {
graph.pos.zoom = graph.zoomParams.zoomMax; }
translate();
}
/**
* Zoom out from graph SVG
*/
function zoomLess() {
graph.pos.zoom -= graph.zoomParams.zoomInterval;
if (graph.pos.zoom <= graph.zoomParams.zoomMin) {
graph.pos.zoom = graph.zoomParams.zoomMin; }
translate();
}
/**
* Set default parameter to 'graph.zoomParams' then apply them
*/
function zoomReset() {
graph.pos.zoom = 1;
graph.pos.x = 0;
graph.pos.y = 0;
translate();
}
/**
* Apply zoom paramters from 'graph.zoomParams'
*/
function translate() {
graph.svg.attr('style', `transform:translate(${graph.pos.x}px, ${graph.pos.y}px) scale(${graph.pos.zoom});`);
}
/**
* Zoom to a node from its coordinates
* @param {number} nodeId
*/
function zoomToNode(nodeId) {
const nodeToZoomMetas = graph.elts.nodes.filter(node => node.id === nodeId).datum()
, x = nodeToZoomMetas.x
, y = nodeToZoomMetas.y
, zoom = 2;
graph.pos = {
zoom: zoom,
x: graph.width - zoom * x,
y: graph.height - zoom * y
};
translate();
}
/**
* @ignore
* ================================================================================================
* navigation.js ==================================================================================
* ================================================================================================
* Display and coordinate three website sections : graph, board & about menu
*/
MicroModal.init();
document.querySelector('#about-btn').addEventListener('click', () => {
MicroModal.show('modal-about');
})
var navigation = {
links: document.querySelectorAll('[data-section]'),
activLink: function(section) {
if (movement.currentSection !== undefined) {
// désactiver la surbrillance du lien vers la précédante section
document.querySelector('[data-section="' + movement.currentSection + '"]')
.classList.remove('active');
}
// activer la surbrillance du lien vers la nouvelle section
document.querySelector('[data-section="' + section + '"]')
.classList.add('active');
}
}
navigation.links.forEach(link => {
link.addEventListener('click', (e) => {
movement.goTo(e.target.dataset.section);
})
});
const headerHeight = 140;
/**
* Navigation into the website views
* @namespace Movement
*/
var movement = {
currentSection: undefined,
offset: {
graph: 0,
board: window.innerHeight + headerHeight
},
/**
* Get the position of the section to scroll and apply displaying options
* @param {string} section - 'reseau' or 'fiches'
* @memberof Movement
*/
goTo: function(section) {
navigation.activLink(section);
this.currentSection = section;
switch (section) {
case 'reseau':
this.scroll(this.offset.graph);
fiche.fixer(true);
fiche.canClose(true);
break;
case 'fiches':
this.scroll(this.offset.board);
fiche.fixer(true);
fiche.canClose(false);
fiche.open();
break;
}
},
/**
* Scroll to the position
* @param {number} offset
* @memberof Movement
*/
scroll: function(offset) {
window.scrollTo({
top: offset,
behavior: 'smooth'
});
}
}
movement.goTo('reseau'); // default
window.onresize = function() {
movement.goTo(movement.currentSection);
}
/**
* @ignore
* ================================================================================================
* history.js =====================================================================================
* ================================================================================================
* Manage the navigation history by sync with the web browser historical functions
*/
/**
* History of the navigation
* @namespace Historique
*/
var historique = {
/**
* Register the node id into the navigation history
* @param {number} nodeId
* @memberof Historique
*/
actualiser: function(nodeId) {
if (history.state == null) { this.init(nodeId); }
else {
var timeline = history.state.hist;
timeline.push(nodeId);
history.pushState({hist : timeline}, 'entite ' + nodeId, nodeId);
}
},
/**
* Initialise the navigation history, for the first
* @param {number} nodeId
* @memberof Historique
*/
init: function(nodeId) {
history.pushState({hist : [nodeId]}, 'entite ' + nodeId, nodeId);
}
}
window.onpopstate = function(e) {
if (e.state === null) { return; }
var timeline = e.state.hist;
var nodeId = timeline[timeline.length - 1];
switchNode(nodeId);
};