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

Brak kodu serwera

Ta aplikacja działa wyłącznie w przeglądarce i nie korzysta z kodu po stronie serwera.

Licencja

## BSD-3-Clause License Agreement
BSD-3-Clause
Сopyright (c) 2026 Dariusz Rorat

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 sierpnia 2025 2

Kategorie

Technologie

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.