diff --git a/index.html b/index.html
index aea8fbe..d5fc023 100644
--- a/index.html
+++ b/index.html
@@ -1,13 +1,23 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/individus.ts b/src/individus.ts
index ff4e432..e26bef8 100644
--- a/src/individus.ts
+++ b/src/individus.ts
@@ -5,7 +5,7 @@ import ChartDataLabels from "chartjs-plugin-datalabels";
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
import { jStat } from "jstat";
-const individus = generate(1000);
+export const individus = generate(150);
const individualMale = individus.filter(i => i.sexe === "M");
const individualFemale = individus.filter(i => i.sexe === "F");
diff --git a/src/main.ts b/src/main.ts
index 869ef9b..82348f5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,134 +1,27 @@
-import './style.css';
-import Graph from "graphology";
-import Sigma from "sigma";
-import forceAtlas2 from "graphology-layout-forceatlas2";
+import { individus } from "./individus.ts";
-// --- Génération de données de base ---
-const graph = new Graph();
-
-const N = 30;
-const colors = ["#ec635e", "#61afef", "#2c3029ff", "#e5c07b"];
-
-for (let i = 0; i < N; i++) {
- const sex = Math.random() < 0.5 ? "F" : "M";
- const education = Math.floor(Math.random() * 4);
- const color = colors[education];
-
- graph.addNode(`n${i}`, {
- x: Math.random(), y: Math.random(),
- label: `${sex} ${i}`,
- sex,
- education,
- size: 6 + education,
- color,
- });
+function download(filename: string, content:any, type:string) {
+ const blob = new Blob([content], { type });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
}
-let total = N * 1.5
-
-
-// --- Calcul du layout ForceAtlas2 ---
-/*
-const positions = forceAtlas2(graph, { iterations: 50 });
-
-// --- Application des positions calculées ---
-for (const [node, pos] of Object.entries(positions)) {
- graph.setNodeAttribute(node, "x", pos.x);
- graph.setNodeAttribute(node, "y", pos.y);
-}
-*/
-// --- Rendu Sigma ---
-const container = document.getElementById("app");
-const renderer = new Sigma(graph, container, { renderLabels: true });
-
-// --- Fonctions dynamiques ---
-function addLink() {
- const a = `n${Math.floor(Math.random() * N)}`;
- const b = `n${Math.floor(Math.random() * N)}`;
- if (a !== b && !graph.hasEdge(a, b)) {
- graph.addEdge(a, b, { color: "#da1515ff", size: 1 });
- }
- if (total-- > 0) setTimeout(() => addLink(), 1000); else running = false;
-}
-
-function removeLink(source, target) {
- if (graph.hasEdge(source, target)) {
- graph.dropEdge(source, target);
- renderer.refresh();
- }
-}
-
-// --- Exemple d’évolution dynamique ---
-setTimeout(() => addLink(), 1000);
-setTimeout(() => removeLink("n1", "n2"), 8000);
-
-// --- Animation du layout ---
-// On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame.
-let running = true;
-
-function stepLayout() {
- if (!running) return;
-
- // Effectue une itération de ForceAtlas2 (ne recrée pas tout)
- forceAtlas2.assign(graph, { iterations: 1, settings: { gravity: 0.1, scalingRatio: 10 } });
-
- // Sigma détecte les changements automatiquement → inutile de refresh manuellement
- requestAnimationFrame(stepLayout);
-}
-
-// Lancement
-stepLayout();
-
-
-/*
-const layout = new ForceAtlas2Layout(graph, {
- settings: {
- gravity: 0.1,
- slowDown: 10,
- linLogMode: false,
- outboundAttractionDistribution: false,
- adjustSizes: true,
- },
+document.getElementById("download-json")!.addEventListener("click", () => {
+ console.log(individus);
+ const json = JSON.stringify(individus, null, 2);
+ download("individus.json", json, "application/json");
});
-// --- Animation : on démarre le layout ---
-layout.start();
+document.getElementById("download-csv")!.addEventListener("click", () => {
+ const headers = Object.keys(individus[0]);
+ const csv = [
+ headers.join(";"),
+ ...individus.map(i => headers.map(h => i[h]).join(";"))
+ ].join("\n");
-// --- Optionnel : arrêt automatique après quelques secondes ---
-setTimeout(() => {
- layout.stop();
- console.log("Layout stabilisé");
-}, 5000);
-
-// --- Animation continue du rendu ---
-function animate() {
- // On redessine continuellement le graphe tant que le layout tourne
- renderer.refresh();
- requestAnimationFrame(animate);
-}
-animate();
-*/
-//import typescriptLogo from './typescript.svg'
-//import viteLogo from '/vite.svg'
-//import { setupCounter } from './counter.ts'
-/*
-document.querySelector
('#app')!.innerHTML = `
-
-
-
-
-
-
-
-
Vite + TypeScript
-
-
-
-
- Click on the Vite and TypeScript logos to learn more
-
-
-`
-
-setupCounter(document.querySelector('#counter')!)
-*/
+ download("individus.csv", csv, "text/csv");
+});
diff --git a/src/network.ts b/src/network.ts
new file mode 100644
index 0000000..dc495c0
--- /dev/null
+++ b/src/network.ts
@@ -0,0 +1,154 @@
+import Graph from "graphology";
+import Sigma from "sigma";
+import forceAtlas2 from "graphology-layout-forceatlas2";
+
+let individus: any[] = [];
+let graph: Graph;
+let sigma: Sigma;
+let running = false;
+
+document.getElementById("fileInput")!.addEventListener("change", async (e) => {
+ const file = (e.target as HTMLInputElement).files?.[0];
+ if (!file) return;
+ const text = await file.text();
+ individus = JSON.parse(text);
+});
+
+document.getElementById("generateGraph")!.addEventListener("click", () => {
+ if (!individus.length) {
+ alert("Veuillez charger un fichier JSON d’individus d’abord !");
+ return;
+ }
+
+ graph = new Graph();
+ running = true;
+ document.getElementById("stopSim")!.removeAttribute("disabled");
+
+ const container = document.getElementById("sigma-container")!;
+
+ const width = container.clientWidth || 800;
+ const height = container.clientHeight || 600;
+ const cx = 0; //width / 2;
+ const cy = 0; //height / 2;
+
+ // rayon maximum (on garde une marge)
+ const maxRadius = Math.min(width, height) * 0.45;
+
+ // la spirale progresse doucement de l'intérieur vers l'extérieur
+ const turns = 5; // nombre de tours de spirale
+ const angleStep = (2 * Math.PI * turns) / individus.length;
+
+ // ajouter les nœuds avec positions initiales sur un cercle
+ individus.forEach((ind, i) => {
+ const t = i / individus.length; // 0 → 1
+ const angle = i * angleStep;
+ const radius = t * maxRadius;
+
+ // petite variation aléatoire pour éviter une grille parfaite (optionnel)
+ const jitter = 0.02 * radius;
+
+ // coordonnees
+ const x = cx + Math.cos(angle) * (radius + (Math.random() - 0.5) * jitter);
+ const y = cy + Math.sin(angle) * (radius + (Math.random() - 0.5) * jitter);
+
+ graph.addNode(String(i), {
+ label: `${ind.prenom} (${ind.age} ans)`,
+ x, // position initiale X
+ y, // position initiale Y
+ size: 2,
+ color: ind.sexe === "F" ? "#ff99aa" : "#6699ff",
+ age: ind.age,
+ sexe: ind.sexe,
+ richesse: ind.richesse,
+ etudes: ind.etudes,
+ lecture: ind.lecture,
+ musique: ind.musique,
+ sport: ind.sport,
+ });
+ });
+
+ // maintenant on peut créer Sigma en lui passant le conteneur
+ sigma = new Sigma(graph, container, { renderLabels: true });
+ // rafraîchir pour que Sigma prenne en compte les positions initiales
+ sigma.refresh();
+
+ // Lancer l’animation
+ animateLinks();
+});
+
+document.getElementById("stopSim")!.addEventListener("click", () => {
+ running = false;
+ (document.getElementById("stopSim") as HTMLButtonElement).disabled = true;
+});
+
+function updateSizes() {
+ graph.forEachNode((node, attrs) => {
+ const degree = graph.degree(node);
+ //const size = 2 + Math.sqrt(degree) * 2;
+ const size = 5 * (1 - Math.exp(-degree / 8));
+ graph.setNodeAttribute(node, "size", size);
+ });
+ sigma.refresh();
+}
+
+async function animateLinks() {
+ const pairs: [number, number][] = [];
+
+ // Liste de toutes les paires (i,j)
+ for (let i = 0; i < individus.length; i++) {
+ for (let j = i + 1; j < individus.length; j++) {
+ pairs.push([i, j]);
+ }
+ }
+
+ // Mélanger un peu les paires pour éviter les patterns trop linéaires
+ pairs.sort(() => Math.random() - 0.5);
+
+ for (const [i, j] of pairs) {
+ if (!running) break;
+
+ const a = individus[i];
+ const b = individus[j];
+
+ // Homophilie
+ const diffAge = Math.abs(a.age - b.age) / 60;
+ const diffLecture = Math.abs(a.lecture - b.lecture);
+ const diffMusique = Math.abs(a.musique - b.musique);
+ const diffSport = Math.abs(a.sport - b.sport);
+ const similitude = 1 - (diffAge * 2 + diffLecture + diffMusique + diffSport) / 5;
+
+ // Attachement préférentiel
+ const degreeA = graph.degree(String(i)) + 1;
+ const degreeB = graph.degree(String(j)) + 1;
+ const pref = (degreeA + degreeB) / (2 * individus.length);
+
+ // Fermeture triadique
+ const neighborsA = new Set(graph.neighbors(String(i)));
+ const neighborsB = new Set(graph.neighbors(String(j)));
+ const common = [...neighborsA].filter((n) => neighborsB.has(n)).length;
+ const triadic = Math.min(common / 3, 0.5);
+
+ // Probabilité globale
+ const p = 0.15 * similitude + 0.45 * pref + 0.4 * triadic;
+
+ if (Math.random() < p) {
+ graph.addEdge(String(i), String(j));
+ updateSizes();
+ }
+
+ // ForceAtlas2 s’exécute par étapes
+ if (graph.order % 10 === 0) {
+ forceAtlas2.assign(graph, { iterations: 20, settings: { gravity: 0.1 } });
+ sigma.refresh();
+ await delay(40); // petit délai entre lots
+ }
+ }
+
+ // Dernière stabilisation
+ forceAtlas2.assign(graph, { iterations: 150, settings: { gravity: 0.1 } });
+ sigma.refresh();
+}
+
+function delay(ms: number) {
+ return new Promise((res) => setTimeout(res, ms));
+}
diff --git a/src/network0.ts b/src/network0.ts
new file mode 100644
index 0000000..5906f37
--- /dev/null
+++ b/src/network0.ts
@@ -0,0 +1,134 @@
+import './style.css';
+import Graph from "graphology";
+import Sigma from "sigma";
+import forceAtlas2 from "graphology-layout-forceatlas2";
+
+// --- Génération de données de base ---
+const graph = new Graph();
+
+const N = 30;
+const colors = ["#ec635e", "#61afef", "#2c3029ff", "#e5c07b"];
+
+for (let i = 0; i < N; i++) {
+ const sex = Math.random() < 0.5 ? "F" : "M";
+ const education = Math.floor(Math.random() * 4);
+ const color = colors[education];
+
+ graph.addNode(`n${i}`, {
+ x: Math.random(), y: Math.random(),
+ label: `${sex} ${i}`,
+ sex,
+ education,
+ size: 6 + education,
+ color,
+ });
+}
+
+let total = N * 1.5
+
+
+// --- Calcul du layout ForceAtlas2 ---
+/*
+const positions = forceAtlas2(graph, { iterations: 50 });
+
+// --- Application des positions calculées ---
+for (const [node, pos] of Object.entries(positions)) {
+ graph.setNodeAttribute(node, "x", pos.x);
+ graph.setNodeAttribute(node, "y", pos.y);
+}
+*/
+// --- Rendu Sigma ---
+const container = document.getElementById("app");
+const renderer = new Sigma(graph, container, { renderLabels: true });
+
+// --- Fonctions dynamiques ---
+function addLink() {
+ const a = `n${Math.floor(Math.random() * N)}`;
+ const b = `n${Math.floor(Math.random() * N)}`;
+ if (a !== b && !graph.hasEdge(a, b)) {
+ graph.addEdge(a, b, { color: "#da1515ff", size: 1 });
+ }
+ if (total-- > 0) setTimeout(() => addLink(), 1000); else running = false;
+}
+
+function removeLink(source, target) {
+ if (graph.hasEdge(source, target)) {
+ graph.dropEdge(source, target);
+ renderer.refresh();
+ }
+}
+
+// --- Exemple d’évolution dynamique ---
+setTimeout(() => addLink(), 250);
+setTimeout(() => removeLink("n1", "n2"), 8000);
+
+// --- Animation du layout ---
+// On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame.
+let running = true;
+
+function stepLayout() {
+ if (!running) return;
+
+ // Effectue une itération de ForceAtlas2 (ne recrée pas tout)
+ forceAtlas2.assign(graph, { iterations: 1, settings: { gravity: 0.1, scalingRatio: 10 } });
+
+ // Sigma détecte les changements automatiquement → inutile de refresh manuellement
+ requestAnimationFrame(stepLayout);
+}
+
+// Lancement
+stepLayout();
+
+
+/*
+const layout = new ForceAtlas2Layout(graph, {
+ settings: {
+ gravity: 0.1,
+ slowDown: 10,
+ linLogMode: false,
+ outboundAttractionDistribution: false,
+ adjustSizes: true,
+ },
+});
+
+// --- Animation : on démarre le layout ---
+layout.start();
+
+// --- Optionnel : arrêt automatique après quelques secondes ---
+setTimeout(() => {
+ layout.stop();
+ console.log("Layout stabilisé");
+}, 5000);
+
+// --- Animation continue du rendu ---
+function animate() {
+ // On redessine continuellement le graphe tant que le layout tourne
+ renderer.refresh();
+ requestAnimationFrame(animate);
+}
+animate();
+*/
+//import typescriptLogo from './typescript.svg'
+//import viteLogo from '/vite.svg'
+//import { setupCounter } from './counter.ts'
+/*
+document.querySelector('#app')!.innerHTML = `
+
+
+
+
+
+
+
+
Vite + TypeScript
+
+
+
+
+ Click on the Vite and TypeScript logos to learn more
+
+
+`
+
+setupCounter(document.querySelector('#counter')!)
+*/