diff --git a/index.html b/index.html
index 7a69a90..aea8fbe 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Graph MesAmis
+ Graphe Mes Amis
diff --git a/individus.html b/individus.html
new file mode 100644
index 0000000..ba5d664
--- /dev/null
+++ b/individus.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Distribution des âges
+
+
+ Distribution des âges selon le genre
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 3b1650b..368d230 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"chart.js": "^4.5.1",
"graphology": "^0.26.0",
"graphology-layout-forceatlas2": "^0.10.1",
+ "jstat": "^1.9.6",
"sigma": "^3.0.2"
},
"devDependencies": {
@@ -461,6 +462,11 @@
"graphology-types": ">=0.23.0"
}
},
+ "node_modules/jstat": {
+ "version": "1.9.6",
+ "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.6.tgz",
+ "integrity": "sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug=="
+ },
"node_modules/lightningcss": {
"version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
diff --git a/package.json b/package.json
index 108d5ff..7aa6de9 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"chart.js": "^4.5.1",
"graphology": "^0.26.0",
"graphology-layout-forceatlas2": "^0.10.1",
+ "jstat": "^1.9.6",
"sigma": "^3.0.2"
}
}
diff --git a/src/individual.ts b/src/individual.ts
new file mode 100644
index 0000000..e222dbd
--- /dev/null
+++ b/src/individual.ts
@@ -0,0 +1,160 @@
+import { jStat } from "jstat";
+
+function randomChoice(ens:Array) {
+ return ens[Math.floor(Math.random() * ens.length)];
+}
+
+function choiceAge() {
+ const r = Math.random();
+
+ if (r < 0.35) {
+ const age = jStat.normal.sample(25, 4);
+ return Math.min(30, Math.max(18, Math.round(age)));
+ }
+
+ if (r < 0.75) {
+ const age = jStat.normal.sample(40, 6);
+ return Math.min(60, Math.max(31, Math.round(age)));
+ }
+
+ const age = jStat.normal.sample(66, 5);
+ return Math.min(78, Math.max(61, Math.round(age)));
+}
+function randomFirstName(sex:string, age:number) {
+ const pool = [
+ [
+ [ "Lucas","Léo","Nathan","Gabriel","Maël","Hugo","Ethan","Noah","Arthur","Gabin",
+ "Tom","Medhi","Evan","Sacha","Rayan","Mathis","Enzo","Théo","Isaac","Liam"
+ ],
+ [ "Julien","Maxime","Alexandre","Pierre","Antoine","Romain","Jérémy","Kevin","Nicolas","Benjamin",
+ "Florian","Vincent","Michaël","Samuel","Baptiste","Yann","Cédric","Quentin","Thomas","Adrien"
+ ],
+ [ "Jean","Michel","Christian","Philippe","Daniel","Patrick","Bernard","Alain","Jacques","Guy",
+ "Louis","André","Roger","Maurice","Robert","Henri","Marc","Gérard","Serge","Raymond"
+ ]
+ ],
+ [
+ [ "Emma","Léa","Manon","Chloé","Jade","Lina","Lola","Anna","Zoé","Mila",
+ "Camille","Nina","Darya","Léna","Louise","Inès","Julia","Samia","Clara","Maya"
+ ],
+ [ "Marine","Laura","Céline","Charlotte","Elodie","Marion","Sophie","Julie","Amélie","Amandine",
+ "Valérie","Aurélie","Isabelle","Caroline","Sonia","Laurence","Cécile","Stéphanie","Sandrine","Emilie"
+ ],
+ [ "Marie","Monique","Françoise","Denise","Nicole","Pierrette","Madeleine","Colette","Agnès","Simone",
+ "Geneviève","Jacqueline","Jeanne","Yvonne","Raymonde","Thérèse","Lucienne","Gisèle","Marguerite","Suzanne"
+ ]
+ ]
+ ];
+
+ let a = 2;
+
+ if (age <= 30)
+ a = 0;
+ else if (age <= 50)
+ a = 1;
+
+ return randomChoice(pool[sex == 'M' ? 0 : 1][a]);
+}
+
+function randomSex(age:number) {
+ let c = 0.55;
+
+ if (age < 30)
+ c = 0.51;
+ else if (age < 55)
+ c = 0.45;
+
+ return Math.random() < c ? "F" : "M";
+}
+
+function randomSport(age:number) {
+ // Moyenne de sport qui diminue avec l’âge
+ const meanSport = Math.min(Math.max(0.65 - 0.006 * (age - 18), 0.05), 0.9);
+
+ // Paramètres de la distribution bêta
+ const a = Math.max(meanSport * 6, 0.5);
+ const b = Math.max((1 - meanSport) * 6, 0.5);
+
+ // Tirage aléatoire selon Beta(a, b)
+ const sport = jStat.beta.sample(a, b);
+
+ return sport;
+}
+
+function randomEducation(age:number) {
+ // --- Niveau d'études influencé par âge ---
+ const base = [0.25, 0.35, 0.25, 0.15];
+ const ageEffect = (30 - age) / 60;
+ const adjust = [-0.05, 0.00, 0.03, 0.02].map(v => v * (1 + ageEffect));
+
+ // Calcul des probabilités corrigées
+ let probs = base.map((b, i) => Math.min(0.9, Math.max(0.01, b + adjust[i])));
+
+ // Normalisation
+ const total = jStat.sum(probs);
+ probs = probs.map(p => p / total);
+
+ // Sélection pondérée : équivalent à np.random.choice
+ const values = [0, 1, 2, 3];
+ const cumProbs = jStat.cumsum(probs);
+ const r = Math.random();
+ const idx = cumProbs.findIndex(p => r <= p);
+ return values[idx >= 0 ? idx : values.length - 1];
+}
+
+function randomWealth(education:number) {
+ // Ajoute un bruit gaussien (moyenne 0, écart-type 0.9)
+ const wealthCont = education + jStat.normal.sample(0, 0.9);
+
+ // Classification en 4 niveaux selon la valeur continue
+ let wealth;
+ if (wealthCont < 0.5) {
+ wealth = 0;
+ } else if (wealthCont < 1.5) {
+ wealth = 1;
+ } else if (wealthCont < 2.5) {
+ wealth = 2;
+ } else {
+ wealth = 3;
+ }
+
+ return wealth;
+}
+
+function randomLecture(education:number, age:number) {
+ let meanRead = 0.15 + 0.18 * education + 0.002 * (age - 18);
+ meanRead = Math.min(Math.max(meanRead, 0.02), 0.98); // équivalent de np.clip
+
+ // Paramètres de la distribution bêta
+ const a = Math.max(meanRead * 7, 0.5);
+ const b = Math.max((1 - meanRead) * 7, 0.5);
+
+ // Tirage aléatoire
+ const reading = jStat.beta.sample(a, b);
+
+ return reading;
+}
+
+function randomMusique() {
+ return jStat.beta.sample(2, 2);
+}
+
+export function generate(n: number) {
+
+ const individus = [];
+ for (let i = 0; i < n; i++) {
+ const age = choiceAge();
+ const sexe = randomSex(age);
+ const prenom = randomFirstName(sexe, age);
+
+ const etudes = randomEducation(age);
+ const richesse = randomWealth(etudes);
+ const sport = randomSport(age);
+ const lecture = randomLecture(etudes, age);
+ const musique = randomMusique();
+
+ individus.push({ id: i, prenom, sexe, age, etudes, richesse, sport, lecture, musique });
+ }
+
+ return individus;
+}
diff --git a/src/individus.ts b/src/individus.ts
new file mode 100644
index 0000000..bb5fb32
--- /dev/null
+++ b/src/individus.ts
@@ -0,0 +1,50 @@
+// src/main.ts
+import { generate } from "./individual";
+import Chart from "chart.js/auto";
+
+const individus = generate(1000);
+console.log(individus);
+
+// Séparer les âges par sexe
+const agesH = individus.filter(i => i.sexe === "M").map(i => i.age);
+const agesF = individus.filter(i => i.sexe === "F").map(i => i.age);
+
+// Construire des classes d'âge
+const ageClasses = Array.from({ length: 13 }, (_, i) => i * 5 + 18); // [10,20,30,...,70]
+
+function histogramData(ages: number[]) {
+ return ageClasses.map((a, idx) =>
+ ages.filter(age => age >= a && age < (ageClasses[idx + 1] ?? 80)).length
+ );
+}
+
+const ctx = document.getElementById("ageChart") as HTMLCanvasElement;
+
+new Chart(ctx, {
+ type: "bar",
+ data: {
+ labels: ageClasses.map(a => `${a}-${a+9}`),
+ datasets: [
+ {
+ label: "Hommes",
+ data: histogramData(agesH),
+ backgroundColor: "rgba(54, 162, 235, 0.6)",
+ },
+ {
+ label: "Femmes",
+ data: histogramData(agesF),
+ backgroundColor: "rgba(255, 99, 132, 0.6)",
+ }
+ ],
+ },
+ options: {
+ responsive: true,
+ plugins: {
+ title: { display: true, text: "Distribution des âges par sexe" },
+ },
+ scales: {
+ x: { stacked: false },
+ y: { beginAtZero: true, max: 100, title: { display: true, text: "Nombre d'individus" } },
+ },
+ },
+});
diff --git a/src/main.ts b/src/main.ts
index 6ca176a..e118df3 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,7 +1,7 @@
import './style.css'
-import Graph from "https://esm.sh/graphology";
-import Sigma from "https://esm.sh/sigma";
-import forceAtlas2 from "graphology-layout-forceatlas2";
+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();
@@ -24,14 +24,8 @@ for (let i = 0; i < N; i++) {
});
}
-// --- Ajout de quelques liens initiaux ---
-for (let i = 0; i < N * 1.5; i++) {
- 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 });
- }
-}
+let total = N * 1.5
+
// --- Calcul du layout ForceAtlas2 ---
/*
@@ -48,11 +42,13 @@ const container = document.getElementById("app");
const renderer = new Sigma(graph, container, { renderLabels: true });
// --- Fonctions dynamiques ---
-function addLink(source, target) {
- if (!graph.hasEdge(source, target)) {
- graph.addEdge(source, target, { color: "#666" });
- renderer.refresh();
+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) {
@@ -63,11 +59,8 @@ function removeLink(source, target) {
}
// --- Exemple d’évolution dynamique ---
-//setTimeout(() => addLink("n0", "n5"), 3000);
-//setTimeout(() => removeLink("n1", "n2"), 6000);
-
-//console.log("Graph loaded:", graph.order, "nodes,", graph.size, "edges");
-
+setTimeout(() => addLink(), 1000);
+setTimeout(() => removeLink("n1", "n2"), 8000);
// --- Animation du layout ---
// On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame.
@@ -86,11 +79,6 @@ function stepLayout() {
// Lancement
stepLayout();
-// Arrêt automatique après 5 secondes
-setTimeout(() => {
- running = false;
- console.log("Layout stabilisé");
-}, 5000);
/*
const layout = new ForceAtlas2Layout(graph, {