app: Implémentation initiale du graphe
This commit is contained in:
parent
0faad66aa2
commit
70b3f00a54
12
README.md
12
README.md
|
@ -1,15 +1,7 @@
|
||||||
Liste des signes, symptômes et maladies :
|
Liste des signes, symptômes et maladies :
|
||||||
|
|
||||||
https://wikimedi.ca/wiki/Concept:Signes_et_sympt%C3%B4mes
|
<https://wikimedi.ca/wiki/Concept:Signes_et_sympt%C3%B4mes>
|
||||||
|
|
||||||
Export RDF :
|
Export RDF :
|
||||||
|
|
||||||
https://wikimedi.ca/wiki/Sp%C3%A9cial:Export_RDF/<NOM_DE_LA_PAGE>
|
<https://wikimedi.ca/wiki/Sp%C3%A9cial:Export_RDF/NOM_DE_LA_PAGE>
|
||||||
|
|
||||||
Visu :
|
|
||||||
|
|
||||||
* https://js.cytoscape.org/
|
|
||||||
* https://www.npmjs.com/package/react-graph-vis
|
|
||||||
* http://sigmajs.org/
|
|
||||||
* http://getspringy.com/
|
|
||||||
* https://github.com/danielcaldas/react-d3-graph
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import TermInput from './TermInput.js';
|
||||||
|
import ResultsGraph from './ResultsGraph.js';
|
||||||
|
|
||||||
const App = () => <div>Je suis rechargé !</div>;
|
const App = () => (
|
||||||
|
<div className="App">
|
||||||
|
<TermInput />
|
||||||
|
<ResultsGraph />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
import React, {useState, useRef, useEffect} from 'react';
|
||||||
|
import Springy from 'springy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un identifiant unique pour l’arête identifiée par ses deux extrémités.
|
||||||
|
*
|
||||||
|
* @param from Premier nœud de l’arête.
|
||||||
|
* @param to Second nœud de l’arête.
|
||||||
|
* @return Identifiant unique pour les deux directions de l’arête.
|
||||||
|
*/
|
||||||
|
const getEdgeId = (from, to) =>
|
||||||
|
{
|
||||||
|
if (to < from)
|
||||||
|
{
|
||||||
|
return getEdgeId(to, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${from}-${to}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si une liste d’arêtes contient une arête donnée, dans un sens ou
|
||||||
|
* dans l’autre.
|
||||||
|
*
|
||||||
|
* @param list Liste d’arêtes.
|
||||||
|
* @param from Premier nœud de l’arête.
|
||||||
|
* @param to Second nœud de l’arête.
|
||||||
|
* @return Vrai si et seulement si l’arête existe.
|
||||||
|
*/
|
||||||
|
const includesEdge = (list, from, to) =>
|
||||||
|
list.some(([source, target]) => (
|
||||||
|
(source === from && target === to)
|
||||||
|
|| (source === to && target === from)
|
||||||
|
));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche un graphe.
|
||||||
|
*
|
||||||
|
* @prop nodes Liste des identifiants de nœuds du graphe. Chaque nœud doit
|
||||||
|
* avoir un identifiant unique ne contenant pas de tiret ('-').
|
||||||
|
* @prop edges Couples d’identifiants de nœuds formant les arêtes du graphe.
|
||||||
|
* @prop render Fonction de rendu prenant en paramètre l’identifiant d’un nœud
|
||||||
|
* du graphe et renvoyant un élément à afficher pour le représenter.
|
||||||
|
*/
|
||||||
|
const Graph = ({nodes, edges, render}) =>
|
||||||
|
{
|
||||||
|
const [graph,] = useState(new Springy.Graph());
|
||||||
|
const [layout,] = useState(new Springy.Layout.ForceDirected(
|
||||||
|
graph,
|
||||||
|
/* rigidité = */ 400,
|
||||||
|
/* répulsion = */ 400,
|
||||||
|
/* amortissement = */ 0.5
|
||||||
|
));
|
||||||
|
|
||||||
|
// N’arrête jamais l’animation
|
||||||
|
layout.minEnergyThreshold = 0;
|
||||||
|
|
||||||
|
const graphParent = useRef(null);
|
||||||
|
|
||||||
|
// Ajout des nouveaux nœuds
|
||||||
|
for (let node of nodes)
|
||||||
|
{
|
||||||
|
if (!(node in graph.nodeSet))
|
||||||
|
{
|
||||||
|
console.info(`Ajout du nouveau nœud ${node}`);
|
||||||
|
graph.addNode(new Springy.Node(node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrait des anciens nœuds et de leurs arêtes adjacentes
|
||||||
|
graph.filterNodes(node =>
|
||||||
|
{
|
||||||
|
if (!nodes.includes(node.id))
|
||||||
|
{
|
||||||
|
console.info(`Retrait de l’ancien nœud ${node.id}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajout des nouvelles arêtes
|
||||||
|
for (let [from, to] of edges)
|
||||||
|
{
|
||||||
|
const edgeId = getEdgeId(from, to);
|
||||||
|
|
||||||
|
if (graph.edges.every(edge => edge.id !== edgeId))
|
||||||
|
{
|
||||||
|
console.info(`Ajout de la nouvelle arête ${edgeId}`);
|
||||||
|
graph.addEdge(new Springy.Edge(edgeId, {id: from}, {id: to}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrait des anciennes arêtes
|
||||||
|
graph.filterEdges(({source: {id: from}, target: {id: to}}) =>
|
||||||
|
{
|
||||||
|
if (!includesEdge(edges, from, to))
|
||||||
|
{
|
||||||
|
console.info(`Retrait de l’ancienne arête ${from}-${to}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const center = new Springy.Vector(
|
||||||
|
window.innerWidth / 2,
|
||||||
|
window.innerHeight / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
const scale = 50;
|
||||||
|
const coordsToScreen = vec => vec.multiply(scale).add(center);
|
||||||
|
const screenToCoords = vec => vec.subtract(center).divide(scale);
|
||||||
|
|
||||||
|
layout.start(() =>
|
||||||
|
{
|
||||||
|
layout.eachNode(({id}, {p}) =>
|
||||||
|
{
|
||||||
|
const element = graphParent.current.querySelector(
|
||||||
|
`[data-node-id="${id.replace('"', '\\"')}"]`
|
||||||
|
);
|
||||||
|
|
||||||
|
const {x, y} = coordsToScreen(p);
|
||||||
|
|
||||||
|
element.style.transform = `translate(
|
||||||
|
calc(${x}px - 50%),
|
||||||
|
calc(${y}px - 50%)
|
||||||
|
)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
layout.eachEdge(({id}, {point1: {p: p1}, point2: {p: p2}}) =>
|
||||||
|
{
|
||||||
|
const element = graphParent.current.querySelector(
|
||||||
|
`[data-edge-id="${id}"]`
|
||||||
|
);
|
||||||
|
|
||||||
|
const {x: x1, y: y1} = coordsToScreen(p1);
|
||||||
|
const {x: x2, y: y2} = coordsToScreen(p2);
|
||||||
|
|
||||||
|
element.setAttribute('x1', x1);
|
||||||
|
element.setAttribute('y1', y1);
|
||||||
|
element.setAttribute('x2', x2);
|
||||||
|
element.setAttribute('y2', y2);
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
console.info(`Fin du rendu du graphe`);
|
||||||
|
}, () => {
|
||||||
|
console.info(`Démarrage du rendu du graphe`);
|
||||||
|
});
|
||||||
|
|
||||||
|
let dragging = null;
|
||||||
|
|
||||||
|
const mouseDown = ev =>
|
||||||
|
{
|
||||||
|
const {clientX: x, clientY: y} = ev;
|
||||||
|
const screen = new Springy.Vector(x, y);
|
||||||
|
const position = screenToCoords(screen);
|
||||||
|
const nearest = layout.nearest(position);
|
||||||
|
|
||||||
|
if (nearest.distance <= scale / 50)
|
||||||
|
{
|
||||||
|
dragging = nearest;
|
||||||
|
dragging.point.m = 1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseMove = ev =>
|
||||||
|
{
|
||||||
|
if (dragging !== null)
|
||||||
|
{
|
||||||
|
const {clientX: x, clientY: y} = ev;
|
||||||
|
const screen = new Springy.Vector(x, y);
|
||||||
|
dragging.point.p = screenToCoords(screen);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseUp = ev =>
|
||||||
|
{
|
||||||
|
if (dragging !== null)
|
||||||
|
{
|
||||||
|
dragging.point.m = 1;
|
||||||
|
dragging = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
graphParent.current.addEventListener('mousedown', mouseDown);
|
||||||
|
document.body.addEventListener('mousemove', mouseMove);
|
||||||
|
document.body.addEventListener('mouseup', mouseUp);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
layout.stop();
|
||||||
|
graphParent.current.removeEventListener('mousedown', mouseDown);
|
||||||
|
document.body.removeEventListener('mousemove', mouseMove);
|
||||||
|
document.body.removeEventListener('mouseup', mouseUp);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={graphParent} className="Graph">
|
||||||
|
<svg className="Graph_edgesContainer">
|
||||||
|
{edges.map(edge => (
|
||||||
|
<line data-edge-id={getEdgeId(...edge)} />
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{nodes.map(id => (
|
||||||
|
<span
|
||||||
|
className="Graph_node"
|
||||||
|
data-node-id={id}
|
||||||
|
>{render(id)}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Graph;
|
|
@ -0,0 +1,178 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Graph from './Graph.js';
|
||||||
|
|
||||||
|
// const config = {
|
||||||
|
// automaticRearrangeAfterDropNode: true,
|
||||||
|
// node: {
|
||||||
|
// color: 'lightgreen',
|
||||||
|
// size: 240,
|
||||||
|
// fontSize: 14,
|
||||||
|
// highlightStrokeColor: 'blue',
|
||||||
|
// labelProperty: 'name'
|
||||||
|
// },
|
||||||
|
// link: {
|
||||||
|
// highlightColor: 'lightblue'
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const nodes = {
|
||||||
|
Q2840: {
|
||||||
|
id: 'Q2840',
|
||||||
|
name: 'Grippe',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000035
|
||||||
|
},
|
||||||
|
Q154882: {
|
||||||
|
id: 'Q154882',
|
||||||
|
name: 'Légionellose',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000015
|
||||||
|
},
|
||||||
|
Q155098: {
|
||||||
|
id: 'Q155098',
|
||||||
|
name: 'Leptospirose',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.00001
|
||||||
|
},
|
||||||
|
Q326663: {
|
||||||
|
id: 'Q326663',
|
||||||
|
name: 'Encéphalite à tiques',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000001
|
||||||
|
},
|
||||||
|
Q133780: {
|
||||||
|
id: 'Q133780',
|
||||||
|
name: 'Peste',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000032
|
||||||
|
},
|
||||||
|
Q38933: {
|
||||||
|
id: 'Q38933',
|
||||||
|
name: 'Fièvre',
|
||||||
|
types: ['Symptôme']
|
||||||
|
},
|
||||||
|
Q474959: {
|
||||||
|
id: 'Q474959',
|
||||||
|
name: 'Myalgie',
|
||||||
|
types: ['Symptôme']
|
||||||
|
},
|
||||||
|
Q86: {
|
||||||
|
id: 'Q86',
|
||||||
|
name: 'Céphalée',
|
||||||
|
types: ['Signe']
|
||||||
|
},
|
||||||
|
Q1115038: {
|
||||||
|
id: 'Q1115038',
|
||||||
|
name: 'Rhinorrhée',
|
||||||
|
types: ['Symptôme']
|
||||||
|
},
|
||||||
|
Q9690: {
|
||||||
|
id: 'Q9690',
|
||||||
|
name: 'Fatigue',
|
||||||
|
types: ['Symptôme']
|
||||||
|
},
|
||||||
|
Q127076: {
|
||||||
|
id: 'Q127076',
|
||||||
|
name: 'Vomissement',
|
||||||
|
types: ['Symptôme', 'Signe']
|
||||||
|
},
|
||||||
|
Q178061: {
|
||||||
|
id: 'Q178061',
|
||||||
|
name: 'Choc circulatoire',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000038
|
||||||
|
},
|
||||||
|
Q35805: {
|
||||||
|
id: 'Q35805',
|
||||||
|
name: 'Toux',
|
||||||
|
types: ['Symptôme', 'Signe']
|
||||||
|
},
|
||||||
|
Q647099: {
|
||||||
|
id: 'Q647099',
|
||||||
|
name: 'Hémoptysie',
|
||||||
|
types: ['Symptôme']
|
||||||
|
},
|
||||||
|
Q653197: {
|
||||||
|
id: 'Q653197',
|
||||||
|
name: 'Rash',
|
||||||
|
types: ['Symptôme', 'Signe']
|
||||||
|
},
|
||||||
|
Q160796: {
|
||||||
|
id: 'Q160796',
|
||||||
|
name: 'Syndrome confusionnel',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.000004
|
||||||
|
},
|
||||||
|
Q186235: {
|
||||||
|
id: 'Q186235',
|
||||||
|
name: 'Myocardite',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.0000075
|
||||||
|
},
|
||||||
|
Q476921: {
|
||||||
|
id: 'Q476921',
|
||||||
|
name: 'Insuffisance rénale',
|
||||||
|
types: ['Maladie'],
|
||||||
|
weight: 0.0000046
|
||||||
|
},
|
||||||
|
Q281289: {
|
||||||
|
id: 'Q281289',
|
||||||
|
name: 'Photophobie',
|
||||||
|
types: ['Signe']
|
||||||
|
},
|
||||||
|
Q159557: {
|
||||||
|
id: 'Q159557',
|
||||||
|
name: 'Coma',
|
||||||
|
types: ['Signe']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const edges = [
|
||||||
|
['Q2840', 'Q38933'],
|
||||||
|
['Q2840', 'Q1115038'],
|
||||||
|
['Q2840', 'Q474959'],
|
||||||
|
['Q2840', 'Q86'],
|
||||||
|
['Q2840', 'Q9690'],
|
||||||
|
|
||||||
|
['Q154882', 'Q86'],
|
||||||
|
['Q154882', 'Q38933'],
|
||||||
|
['Q154882', 'Q35805'],
|
||||||
|
['Q154882', 'Q474959'],
|
||||||
|
|
||||||
|
['Q155098', 'Q38933'],
|
||||||
|
['Q155098', 'Q474959'],
|
||||||
|
['Q155098', 'Q86'],
|
||||||
|
['Q155098', 'Q476921'],
|
||||||
|
['Q155098', 'Q186235'],
|
||||||
|
['Q155098', 'Q653197'],
|
||||||
|
|
||||||
|
['Q326663', 'Q86'],
|
||||||
|
['Q326663', 'Q474959'],
|
||||||
|
['Q326663', 'Q38933'],
|
||||||
|
['Q326663', 'Q9690'],
|
||||||
|
['Q326663', 'Q281289'],
|
||||||
|
['Q326663', 'Q159557'],
|
||||||
|
['Q326663', 'Q127076'],
|
||||||
|
|
||||||
|
['Q133780', 'Q38933'],
|
||||||
|
['Q133780', 'Q86'],
|
||||||
|
['Q133780', 'Q127076'],
|
||||||
|
['Q133780', 'Q474959'],
|
||||||
|
['Q133780', 'Q178061'],
|
||||||
|
['Q133780', 'Q35805'],
|
||||||
|
['Q133780', 'Q647099'],
|
||||||
|
['Q133780', 'Q653197'],
|
||||||
|
['Q133780', 'Q160796']
|
||||||
|
];
|
||||||
|
|
||||||
|
const ResultsGraph = () => (
|
||||||
|
<div className="ResultsGraph">
|
||||||
|
<Graph
|
||||||
|
nodes={Object.keys(nodes)}
|
||||||
|
edges={edges}
|
||||||
|
render={id => nodes[id].name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ResultsGraph;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class TermInput extends React.Component
|
||||||
|
{
|
||||||
|
constructor(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
this.state = { input: '' };
|
||||||
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange(ev)
|
||||||
|
{
|
||||||
|
this.setState({
|
||||||
|
input: ev.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<div className="TermInput">
|
||||||
|
<input type="text"
|
||||||
|
className="TermInput_input"
|
||||||
|
value={this.state.input}
|
||||||
|
onChange={this.handleInputChange} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TermInput;
|
|
@ -2,7 +2,10 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Bonjour</title>
|
<title>Recherche de maladies par symptômes</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style/font/SourceSansPro.css">
|
||||||
|
<link rel="stylesheet" href="style/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,27 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'),
|
||||||
|
url('SourceSansPro-Semibold.woff2') format('woff2'),
|
||||||
|
url('SourceSansPro-Semibold.woff') format('woff');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'),
|
||||||
|
url('SourceSansPro-It.woff2') format('woff2'),
|
||||||
|
url('SourceSansPro-It.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
src: local('Source Sans Pro'), local('SourceSansPro-Regular'),
|
||||||
|
url('SourceSansPro-Regular.woff2') format('woff2'),
|
||||||
|
url('SourceSansPro-Regular.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
:root
|
||||||
|
{
|
||||||
|
--font-family: 'Source Sans Pro';
|
||||||
|
--font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html
|
||||||
|
{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
outline: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html, #root
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input
|
||||||
|
{
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TermInput
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
top: 32px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
border: 2px solid #333333;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TermInput:focus-within
|
||||||
|
{
|
||||||
|
outline: 2px solid #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TermInput_input
|
||||||
|
{
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: none;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ResultsGraph, .ResultsGraph div, .ResultsGraph svg
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Graph
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Graph_edgesContainer
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Graph_edgesContainer line
|
||||||
|
{
|
||||||
|
stroke: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Graph_node
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
background: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
|
@ -6561,6 +6561,11 @@
|
||||||
"extend-shallow": "^3.0.0"
|
"extend-shallow": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"springy": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/springy/-/springy-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-PXtwxjww53H/8c+ng3zxMJwNRN/KPv6DKA8ITHi6lW57gfzty0wV/N8ZeTmgfO0ElfQip8W/1iVZk1d5ne4L+g=="
|
||||||
|
},
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0"
|
"react-dom": "^16.12.0",
|
||||||
|
"springy": "^2.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue