Graphe 2e version
This commit is contained in:
24
index.html
24
index.html
@@ -1,13 +1,23 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<title>Simulation de graphe d'amis</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<script type="module" src="src/network.ts"></script>
|
||||||
<title>Graphe Mes Amis</title>
|
<style>
|
||||||
|
body { font-family: sans-serif; }
|
||||||
|
#sigma-container { width: 100%; height: 400px; border: 1px solid #ccc; margin-top: 1rem; }
|
||||||
|
#graph-container canvas {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto; /* si vous voulez le centrer sans perturber Sigma */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" style="width: 100vw; height: 100vh; background: white"></div>
|
<h1>Simulation de réseau d’amitiés</h1>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<input type="file" id="fileInput" accept="application/json" />
|
||||||
|
<button id="generateGraph">Générer le graphe</button>
|
||||||
|
<button id="stopSim" disabled>⏸️ Stop</button>
|
||||||
|
<div id="sigma-container"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Distribution des âges</title>
|
<title>Individus</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="max-width: 700px; margin: 2rem auto; font-family: sans-serif;">
|
|
||||||
|
<body>
|
||||||
|
<div style="max-width: 700px; margin: 2rem auto; font-family: sans-serif;">
|
||||||
<h2>Distribution des âges</h2>
|
<h2>Distribution des âges</h2>
|
||||||
<section id="stats" style="margin-bottom: 2rem;">
|
<section id="stats" style="margin-bottom: 2rem;">
|
||||||
<h3>Indicateurs clés</h3>
|
<h3>Indicateurs clés</h3>
|
||||||
@@ -33,6 +36,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div><canvas id="heatmapChart" width="700" height="200"></canvas>
|
<div><canvas id="heatmapChart" width="700" height="200"></canvas>
|
||||||
<div><canvas id="radarChart" width="200" height="100"></canvas>
|
<div><canvas id="radarChart" width="200" height="100"></canvas>
|
||||||
<script type="module" src="/src/individus.ts"></script>
|
<div style="text-align:center; margin-top:1rem;">
|
||||||
|
<button id="download-json" style="margin-right:1rem; padding:0.5rem 1rem;">
|
||||||
|
Télécharger JSON
|
||||||
|
</button>
|
||||||
|
<button id="download-csv" style="padding:0.5rem 1rem;">
|
||||||
|
Télécharger CSV
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ChartDataLabels from "chartjs-plugin-datalabels";
|
|||||||
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
|
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
|
||||||
import { jStat } from "jstat";
|
import { jStat } from "jstat";
|
||||||
|
|
||||||
const individus = generate(1000);
|
export const individus = generate(150);
|
||||||
const individualMale = individus.filter(i => i.sexe === "M");
|
const individualMale = individus.filter(i => i.sexe === "M");
|
||||||
const individualFemale = individus.filter(i => i.sexe === "F");
|
const individualFemale = individus.filter(i => i.sexe === "F");
|
||||||
|
|
||||||
|
|||||||
149
src/main.ts
149
src/main.ts
@@ -1,134 +1,27 @@
|
|||||||
import './style.css';
|
import { individus } from "./individus.ts";
|
||||||
import Graph from "graphology";
|
|
||||||
import Sigma from "sigma";
|
|
||||||
import forceAtlas2 from "graphology-layout-forceatlas2";
|
|
||||||
|
|
||||||
// --- Génération de données de base ---
|
function download(filename: string, content:any, type:string) {
|
||||||
const graph = new Graph();
|
const blob = new Blob([content], { type });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
const N = 30;
|
const a = document.createElement("a");
|
||||||
const colors = ["#ec635e", "#61afef", "#2c3029ff", "#e5c07b"];
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
for (let i = 0; i < N; i++) {
|
a.click();
|
||||||
const sex = Math.random() < 0.5 ? "F" : "M";
|
URL.revokeObjectURL(url);
|
||||||
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
|
document.getElementById("download-json")!.addEventListener("click", () => {
|
||||||
|
console.log(individus);
|
||||||
|
const json = JSON.stringify(individus, null, 2);
|
||||||
// --- Calcul du layout ForceAtlas2 ---
|
download("individus.json", json, "application/json");
|
||||||
/*
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Animation : on démarre le layout ---
|
document.getElementById("download-csv")!.addEventListener("click", () => {
|
||||||
layout.start();
|
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 ---
|
download("individus.csv", csv, "text/csv");
|
||||||
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<HTMLDivElement>('#app')!.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<a href="https://vite.dev" target="_blank">
|
|
||||||
<img src="${viteLogo}" class="logo" alt="Vite logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://www.typescriptlang.org/" target="_blank">
|
|
||||||
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
|
|
||||||
</a>
|
|
||||||
<h1>Vite + TypeScript</h1>
|
|
||||||
<div class="card">
|
|
||||||
<button id="counter" type="button"></button>
|
|
||||||
</div>
|
|
||||||
<p class="read-the-docs">
|
|
||||||
Click on the Vite and TypeScript logos to learn more
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)
|
|
||||||
*/
|
|
||||||
|
|||||||
154
src/network.ts
Normal file
154
src/network.ts
Normal file
@@ -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));
|
||||||
|
}
|
||||||
134
src/network0.ts
Normal file
134
src/network0.ts
Normal file
@@ -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<HTMLDivElement>('#app')!.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<a href="https://vite.dev" target="_blank">
|
||||||
|
<img src="${viteLogo}" class="logo" alt="Vite logo" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.typescriptlang.org/" target="_blank">
|
||||||
|
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
|
||||||
|
</a>
|
||||||
|
<h1>Vite + TypeScript</h1>
|
||||||
|
<div class="card">
|
||||||
|
<button id="counter" type="button"></button>
|
||||||
|
</div>
|
||||||
|
<p class="read-the-docs">
|
||||||
|
Click on the Vite and TypeScript logos to learn more
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user