Przejdź do głównej treści

Wizualizacja liczb pierwszych

Obraz losowe liczby pierwsze

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

Interaktywne
25 201 501
W trybie spiral N oznacza „zasięg” (liczby 1…N²).

liczby pierwsze
liczby złożone
Wskazówka: dla wyższych wartości N rysowanie może chwilę potrwać (generujemy sito do N²).

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.

Tagi

JavaScript

Dziękujemy!
()

Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.