Wizualizacja liczb pierwszych
Odkryj piękno matematyki w nowy sposób!
Moja aplikacja pozwala zobaczyć liczby pierwsze i złożone w fascynujących układach graficznych: od klasycznej siatki N×N, przez słynną Spiralę Ulama, aż po hipnotyzującą Spiralę Sacksa.
Dzięki interaktywnym ustawieniom możesz:
- zmieniać rozmiar wizualizacji,
- wybierać kolory punktów,
- regulować wielkość i gęstość siatki,
- podglądać zarówno liczby pierwsze, jak i złożone na przezroczystym tle.
To idealne narzędzie dla pasjonatów matematyki, nauczycieli, studentów i wszystkich, którzy chcą zobaczyć jak z abstrakcyjnych liczb rodzą się niezwykłe wzory.
Uruchom aplikację i przekonaj się, że liczby to nie tylko rachunki — to także sztuka ukryta w matematyce.
Wizualizacje liczb pierwszych
Siatka N×N • Spirala Ulama • Spirala Sacksa
Kod po stronie przeglądarki
<style>
body { min-height: 100vh;}
canvas { width: 100%; height: auto; image-rendering: pixelated; border-radius: 1rem; background: transparent; }
.card { border-radius: 1rem; }
.legend-box { width: 14px; height: 14px; display:inline-block; border-radius: 3px; margin-right: .35rem; vertical-align: middle; }
</style>
<div id="app">
<div class="container py-4">
<div class="row g-4">
<div class="col-12">
<h2 class="h3 mb-1">Wizualizacje liczb pierwszych</h2>
<p class="text-muted mb-0">Siatka N×N • Spirala Ulama • Spirala Sacksa</p>
</div>
<!-- Panel sterowania -->
<div class="col-12 col-lg-4">
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-3">
<span class="badge bg-body-alt text-body">Interaktywne</span>
<button id="downloadBtn" class="btn btn-sm btn-outline-auto-darklight" type="button">
Pobierz PNG
</button>
</div>
<div class="mb-3">
<label class="form-label" for="mode">Tryb wizualizacji</label>
<select id="mode" class="form-select">
<option value="grid">Siatka N×N</option>
<option value="ulam">Spirala Ulama</option>
<option value="sacks">Spirala Sacksa</option>
</select>
</div>
<div class="mb-3" id="n-control">
<label for="nInput" class="form-label">N (rozmiar siatki, N×N)</label>
<input type="range" class="form-range" id="nInput" min="25" max="501" step="2" value="201">
<div class="d-flex justify-content-between">
<small class="text-muted">25</small>
<span><span id="nValue" class="fw-semibold">201</span></span>
<small class="text-muted">501</small>
</div>
<div class="form-text">W trybie spiral N oznacza „zasięg” (liczby 1…N²).</div>
</div>
<div class="row g-3">
<div class="col-6">
<label for="primeColor" class="form-label">Kolor pierwszych</label>
<input type="color" id="primeColor" class="form-control form-control-color" value="#dc3545">
</div>
<div class="col-6">
<label for="compositeColor" class="form-label">Kolor złożonych</label>
<input type="color" id="compositeColor" class="form-control form-control-color" value="#0d6efd">
</div>
</div>
<div class="mt-3">
<label class="form-label" for="pointSize">Wielkość punktu/komórki</label>
<input type="range" class="form-range" id="pointSize" min="1" max="12" value="4">
</div>
<hr class="my-4">
<div>
<div class="mb-2">
<span class="legend-box" id="legendPrime"></span> liczby pierwsze
</div>
<div>
<span class="legend-box" id="legendComposite"></span> liczby złożone
</div>
</div>
</div>
</div>
</div>
<!-- Płótno -->
<div class="col-12 col-lg-8">
<div class="card shadow-sm">
<div class="card-body">
<div id="canvas-wrap">
<canvas id="primeCanvas" width="1200" height="1200" aria-label="Wizualizacja liczb pierwszych"></canvas>
</div>
<div class="pt-3 text-muted small">
Wskazówka: dla wyższych wartości N rysowanie może chwilę potrwać (generujemy sito do N²).
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function sieve(max) {
const isPrime = new Uint8Array(max + 1);
if (max >= 2) isPrime.fill(1, 2);
for (let p = 2; p * p <= max; p++)
if (isPrime[p])
for (let m = p * p; m <= max; m += p) isPrime[m] = 0;
return isPrime;
}
function ulamCoords(limit) {
const coords = new Array(limit + 1);
let x = 0,
y = 0,
n = 1;
coords[1] = [0, 0];
let step = 1;
const dirs = [
[1, 0],
[0, -1],
[-1, 0],
[0, 1]
];
while (n < limit) {
for (let d = 0; d < 4 && n < limit; d++) {
for (let k = 0; k < step && n < limit; k++) {
x += dirs[d][0];
y += dirs[d][1];
n++;
coords[n] = [x, y];
}
if (d % 2 === 1) step++;
}
}
return coords;
}
function drawGrid(ctx, N, isPrime, opt) {
const {
w,
h,
primeColor,
compColor,
pointSize
} = opt;
ctx.clearRect(0, 0, w, h);
const cell = Math.floor(Math.min(w, h) / N);
const offX = (w - cell * N) / 2,
offY = (h - cell * N) / 2;
let n = 1;
for (let r = 0; r < N; r++) {
for (let c = 0; c < N; c++) {
if (n < isPrime.length) {
ctx.fillStyle = isPrime[n] ? primeColor : compColor;
ctx.globalAlpha = isPrime[n] ? 1 : 0.3;
ctx.fillRect(offX + c * cell, offY + r * cell, pointSize, pointSize);
}
n++;
}
}
ctx.globalAlpha = 1;
}
function drawUlam(ctx, N, isPrime, opt) {
const {
w,
h,
primeColor,
compColor,
pointSize
} = opt;
ctx.clearRect(0, 0, w, h);
const coords = ulamCoords(N * N);
let minX = 0,
maxX = 0,
minY = 0,
maxY = 0;
for (let n = 1; n <= N * N; n++) {
const [x, y] = coords[n];
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
const spanX = maxX - minX + 1,
spanY = maxY - minY + 1;
const cell = Math.floor(Math.min(w / spanX, h / spanY));
const offX = (w - cell * spanX) / 2 - minX * cell,
offY = (h - cell * spanY) / 2 - minY * cell;
for (let n = 1; n <= N * N; n++) {
const [gx, gy] = coords[n];
const x = offX + gx * cell,
y = offY + gy * cell;
ctx.fillStyle = isPrime[n] ? primeColor : compColor;
ctx.globalAlpha = isPrime[n] ? 1 : 0.3;
ctx.fillRect(x, y, pointSize, pointSize);
}
ctx.globalAlpha = 1;
}
function drawSacks(ctx, N, isPrime, opt) {
const {
w,
h,
primeColor,
compColor,
pointSize
} = opt;
ctx.clearRect(0, 0, w, h);
const cx = w / 2,
cy = h / 2;
const scale = (Math.min(w, h) / 2 - 20) / N;
for (let n = 1; n <= N * N; n++) {
const r = Math.sqrt(n) * scale;
const th = n;
const x = cx + r * Math.cos(th),
y = cy + r * Math.sin(th);
ctx.fillStyle = isPrime[n] ? primeColor : compColor;
ctx.globalAlpha = isPrime[n] ? 1 : 0.3;
ctx.fillRect(x, y, pointSize, pointSize);
}
ctx.globalAlpha = 1;
}
const canvas = document.getElementById('primeCanvas'),
ctx = canvas.getContext('2d');
const nInput = document.getElementById('nInput'),
nValue = document.getElementById('nValue');
const mode = document.getElementById('mode');
const primeColorEl = document.getElementById('primeColor'),
compColorEl = document.getElementById('compositeColor');
const pointSizeEl = document.getElementById('pointSize');
const legendPrime = document.getElementById('legendPrime');
const legendComposite = document.getElementById('legendComposite');
const downloadBtn = document.getElementById('downloadBtn');
function updateLegends() {
legendPrime.style.background = primeColorEl.value;
legendComposite.style.background = compColorEl.value;
}
function getOpt() {
return {
w: canvas.width,
h: canvas.height,
primeColor: primeColorEl.value,
compColor: compColorEl.value,
pointSize: parseInt(pointSizeEl.value)
};
}
function debounce(func, delay) {
let timer;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => func.apply(context, args), delay);
};
}
function draw() {
const N = parseInt(nInput.value);
nValue.textContent = N;
const isPrime = sieve(N * N);
const opt = getOpt();
updateLegends();
if (mode.value === 'grid') drawGrid(ctx, N, isPrime, opt);
else if (mode.value === 'ulam') drawUlam(ctx, N, isPrime, opt);
else drawSacks(ctx, N, isPrime, opt);
}
[nInput, mode, primeColorEl, compColorEl, pointSizeEl].forEach(e => e.addEventListener('input', debounce(draw, 200)));
document.addEventListener('DOMContentLoaded', function() {
draw();
});
// Pobieranie PNG
downloadBtn.addEventListener('click', () => {
const link = document.createElement('a');
const modeName = ({
grid: 'siatka',
ulam: 'ulam',
sacks: 'sacks'
})[mode.value] || 'tryb';
link.download = `primes_${modeName}_N${nInput.value}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
});
</script>
Kod po stronie serwera
Informacja
Aplikacja nie korzysta z kodu po stronie serwera.