diff --git a/core/frontend/graph.js b/core/frontend/graph.js index 370fb55..4ae0733 100644 --- a/core/frontend/graph.js +++ b/core/frontend/graph.js @@ -85,12 +85,25 @@ hotkeys('space', (e) => { const elts = {}; const imageFileValidExtnames = new Set(['jpg', 'jpeg', 'png']); +// Precompute bidirectional edges +const edgeSet = new Set(data.edges.map((e) => `${e.source.key}--${e.target.key}`)); +const bidirectionalEdges = new Set( + data.edges + .filter((e) => edgeSet.has(`${e.target.key}--${e.source.key}`)) + .map((e) => e.key) +); + simulation.on('tick', () => { elts.links - .attr('x1', (d) => d.source.x) - .attr('y1', (d) => d.source.y) - .attr('x2', (d) => d.target.x) - .attr('y2', (d) => d.target.y); + .attr('d', (d) => { + const dx = d.target.x - d.source.x; + const dy = d.target.y - d.source.y; + const dr = Math.sqrt(dx * dx + dy * dy); + if (bidirectionalEdges.has(d.key)) { + return `M ${d.source.x},${d.source.y} A ${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`; + } + return `M ${d.source.x},${d.source.y} L ${d.target.x},${d.target.y}`; + }); elts.nodes.attr('transform', (d) => 'translate(' + d.x + ',' + d.y + ')'); @@ -105,10 +118,11 @@ simulation.on('tick', () => { /** @type {d3.Selection} */ elts.links = svgSub .append('g') - .selectAll('line') + .selectAll('path') .data(data.edges) .enter() - .append('line') + .append('path') + .attr('fill', 'none') .attr('stroke', (d) => `var(--l_${d.attributes.type})`) .attr('title', (d) => d.attributes.title) .attr('data-link', (d) => d.key) @@ -128,9 +142,14 @@ elts.links = svgSub }); if (graphProperties.graph_arrows === true) { - elts.links.attr('marker-end', 'url(#arrow)'); + if (graphProperties.graph_arrows_reversed === true) { + elts.links.attr('marker-start', 'url(#arrow-reversed)'); + } else { + elts.links.attr('marker-end', 'url(#arrow)'); + } } + const strokeWidth = 2; /** @type {d3.Selection} */ @@ -484,9 +503,8 @@ function highlightNodes(nodeIds) { nodeIds .filter((nodeId) => graph.hasNode(nodeId)) .forEach((nodeId) => { - const { links, node } = getNodeNetwork(nodeId); + const { node } = getNodeNetwork(nodeId); node.node().classList.add('highlight'); - links.nodes().forEach((elt) => elt.classList.add('highlight')); }); highlightedNodes = highlightedNodes.concat(nodeIds); @@ -501,14 +519,7 @@ function unlightNodes() { return; } - highlightedNodes - .filter((nodeId) => graph.hasNode(nodeId)) - .forEach((nodeId) => { - const { links, node } = getNodeNetwork(nodeId); - node.node().classList.remove('highlight'); - links.nodes().forEach((elt) => elt.classList.remove('highlight')); - }); - + elts.nodes.nodes().forEach((elt) => elt.classList.remove('highlight')); highlightedNodes = []; } diff --git a/core/frontend/styles.css b/core/frontend/styles.css index 70d5e07..f0e3f31 100644 --- a/core/frontend/styles.css +++ b/core/frontend/styles.css @@ -714,8 +714,8 @@ text { /* arrows */ marker { /* set into config.yml file by 'undefined' link type */ - stroke: var(--l_undefined); - fill: var(--l_undefined); + stroke: context-stroke; + fill: context-stroke; } /* --- diff --git a/core/i18n.yml b/core/i18n.yml index 762c93c..f78256f 100644 --- a/core/i18n.yml +++ b/core/i18n.yml @@ -397,6 +397,9 @@ config: graph_arrows: fr: La commande de fléchage des liens est invalide en: Invalid link arrow command + graph_arrows_reversed: + fr: La commande d'inversion des flèches est invalide + en: Invalid reverse arrow command devtools: fr: La commande d'activation des outils de développement est invalide en: Invalid devtools activation command diff --git a/core/models/config.js b/core/models/config.js index f513387..9c169c1 100644 --- a/core/models/config.js +++ b/core/models/config.js @@ -57,6 +57,7 @@ import Joi from 'joi'; * @property {boolean} graph_highlight_on_hover * @property {number} graph_text_size * @property {boolean} graph_arrows + * @property {boolean} graph_arrows_reversed * @property {'unique'|'degree'} node_size_method * @property {number} node_size * @property {number} [node_size_max] @@ -183,6 +184,7 @@ const optionsSchema = Joi.object({ graph_highlight_on_hover: Joi.boolean(), graph_text_size: Joi.number(), graph_arrows: Joi.boolean(), + graph_arrows_reversed: Joi.boolean(), node_size_method: Joi.string().valid('unique', 'degree'), node_size: Joi.number().integer().min(0), node_size_max: Joi.number().integer().min(0).optional(), @@ -254,6 +256,7 @@ class Config { graph_highlight_on_hover: true, graph_text_size: 10, graph_arrows: true, + graph_arrows_reversed: false, node_size_method: 'degree', node_size: 10, node_size_max: 20, diff --git a/static/template/cosmoscope.njk b/static/template/cosmoscope.njk index e0e414f..f2be964 100644 --- a/static/template/cosmoscope.njk +++ b/static/template/cosmoscope.njk @@ -269,8 +269,11 @@ - - + + + + + @@ -612,4 +615,4 @@ - \ No newline at end of file +