Füge Seite für Schwarzkörperstrahlung hinzu

This commit is contained in:
2026-06-20 20:32:26 +02:00
parent c7e7d0438a
commit 3f456c0e13
3 changed files with 615 additions and 0 deletions
+5
View File
@@ -56,6 +56,10 @@
"label": "Astronomical Conversions",
"menu_group": "Himmel & Planeten"
},
"blackbody_radiation.php": {
"label": "Schwarzkörperstrahlung",
"menu_group": "Himmel & Planeten"
},
"tcrb_lightcurve.php": {
"label": "T CrB Lichtkurve",
"menu_group": "Himmel & Planeten"
@@ -131,6 +135,7 @@
"ephemeriden.php",
"comets.php",
"astronomical_conversions.php",
"blackbody_radiation.php",
"tcrb_lightcurve.php",
"solarsystem.php",
"jupitersystem.php",
+609
View File
@@ -0,0 +1,609 @@
<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
$pageTitle = 'Schwarzkörperstrahlung';
$headerIntroPre = 'Astronomie-Werkzeuge';
$headerIntroTitle = 'Schwarzkörperstrahlung';
$headerIntroSub = 'Planck-Kurve für eine frei wählbare Temperatur als interaktives Diagramm.';
require __DIR__ . '/header.php';
?>
<style>
.bb-layout {
display: grid;
grid-template-columns: minmax(18rem, 24rem) minmax(0, 1fr);
gap: 1.5rem;
align-items: start;
}
.bb-panel,
.bb-chart-card {
display: grid;
gap: 1rem;
}
.bb-field-grid {
display: grid;
gap: 1rem;
}
.bb-field {
display: grid;
gap: 0.45rem;
}
.bb-field label {
color: var(--text-dim);
font-size: 0.92rem;
}
.bb-number-row {
display: flex;
align-items: center;
gap: 0.6rem;
}
.bb-number-row input[type="number"] {
width: 100%;
padding: 0.72rem 0.8rem;
border-radius: var(--radius);
border: 1px solid var(--border);
background: rgba(255,255,255,0.04);
color: var(--text);
font-size: 0.96rem;
font-family: inherit;
}
.bb-number-row input[type="number"]:focus,
.bb-field input[type="range"]:focus {
outline: none;
border-color: rgba(201, 168, 76, 0.5);
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.1);
}
.bb-unit {
color: var(--text-dim);
font-size: 0.88rem;
white-space: nowrap;
}
.bb-field input[type="range"] {
width: 100%;
accent-color: var(--gold);
cursor: pointer;
}
.bb-hint {
color: var(--text-dim);
font-size: 0.8rem;
line-height: 1.5;
}
.bb-presets {
display: flex;
flex-wrap: wrap;
gap: 0.55rem;
}
.bb-toggle {
display: inline-flex;
align-items: center;
gap: 0.55rem;
color: var(--text-dim);
font-size: 0.86rem;
}
.bb-toggle input[type="checkbox"] {
accent-color: var(--gold);
cursor: pointer;
}
.bb-field.is-disabled {
opacity: 0.55;
}
.bb-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.85rem;
}
.bb-stat {
display: grid;
gap: 0.2rem;
padding: 0.9rem 1rem;
border-radius: var(--radius);
border: 1px solid var(--border);
background: var(--bg-card2);
}
.bb-stat-label {
color: var(--text-dim);
font-size: 0.78rem;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.bb-stat-value {
color: var(--gold-pale);
font-size: 1.18rem;
font-weight: 600;
}
.bb-stat-sub {
color: var(--text-dim);
font-size: 0.8rem;
}
.bb-curve-wrap {
border-radius: var(--radius);
border: 1px solid var(--border);
background:
radial-gradient(circle at top, rgba(201, 168, 76, 0.12), transparent 40%),
linear-gradient(180deg, rgba(10, 14, 28, 0.96), rgba(5, 7, 14, 1));
padding: 0.75rem;
}
#bb-canvas {
display: block;
width: 100%;
height: 28rem;
border-radius: calc(var(--radius) - 0.2rem);
}
.bb-note-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
}
.bb-note {
color: var(--text-dim);
font-size: 0.9rem;
line-height: 1.6;
}
.bb-note strong {
color: var(--text);
}
@media (max-width: 980px) {
.bb-layout {
grid-template-columns: 1fr;
}
.bb-stats,
.bb-note-grid {
grid-template-columns: 1fr;
}
#bb-canvas {
height: 22rem;
}
}
</style>
<div class="bb-layout">
<section class="card bb-panel">
<div>
<h2>Parameter</h2>
<p class="bb-hint">Die Kurve zeigt die spektrale Strahlungsdichte eines idealen Schwarzkörpers nach dem planckschen Strahlungsgesetz.</p>
</div>
<div class="bb-field-grid">
<div class="bb-field">
<label for="bbTemperature">Temperatur</label>
<div class="bb-number-row">
<input id="bbTemperature" type="number" min="100" max="40000" step="10" value="5778">
<span class="bb-unit">K</span>
</div>
<input id="bbTemperatureRange" type="range" min="100" max="40000" step="10" value="5778">
<div class="bb-hint">Typische Bereiche: Glühlampe ~ 2.700 K, Sonne ~ 5.778 K, heißer Stern > 10.000 K.</div>
</div>
<div class="bb-field">
<label for="bbMaxWavelength">Diagrammbereich</label>
<div class="bb-number-row">
<input id="bbMaxWavelength" type="number" min="1000" max="10000" step="100" value="3000">
<span class="bb-unit">nm</span>
</div>
<input id="bbMaxWavelengthRange" type="range" min="1000" max="10000" step="100" value="3000">
<div class="bb-hint">Das Diagramm beginnt bei 100 nm und endet beim hier gewählten Maximalwert.</div>
</div>
<div class="bb-field is-disabled" id="bbYAxisField">
<label for="bbYAxisMax">Y-Achse</label>
<label class="bb-toggle" for="bbYAxisAuto">
<input id="bbYAxisAuto" type="checkbox" checked>
<span>Automatisch skalieren</span>
</label>
<div class="bb-number-row">
<input id="bbYAxisMax" type="number" min="1000000" max="400000000000000000" step="1000000" value="10000000000000" disabled>
<span class="bb-unit">W·sr⁻¹·m⁻³</span>
</div>
<input id="bbYAxisMaxRange" type="range" min="1000000" max="400000000000000000" step="1000000" value="10000000000000" disabled>
<div class="bb-hint">Wenn Auto aus ist, legst du hier den Maximalwert der y-Achse selbst fest.</div>
</div>
</div>
<div>
<div class="bb-hint" style="margin-bottom:0.5rem;">Beispiele</div>
<div class="bb-presets">
<button type="button" class="btn btn-secondary btn-sm" data-temp="300">Erde ~ 300 K</button>
<button type="button" class="btn btn-secondary btn-sm" data-temp="2700">Glühlampe</button>
<button type="button" class="btn btn-secondary btn-sm" data-temp="5778">Sonne</button>
<button type="button" class="btn btn-secondary btn-sm" data-temp="10000">Heißer Stern</button>
</div>
</div>
</section>
<section class="bb-chart-card">
<div class="bb-stats">
<div class="bb-stat">
<span class="bb-stat-label">Wellenlängenmaximum</span>
<span class="bb-stat-value" id="bbPeakNm">–</span>
<span class="bb-stat-sub" id="bbPeakRegion">–</span>
</div>
<div class="bb-stat">
<span class="bb-stat-label">Stefan-Boltzmann</span>
<span class="bb-stat-value" id="bbFlux">–</span>
<span class="bb-stat-sub">Gesamtleistung pro Fläche</span>
</div>
<div class="bb-stat">
<span class="bb-stat-label">Relative Farbwirkung</span>
<span class="bb-stat-value" id="bbColorLabel">–</span>
<span class="bb-stat-sub">grob aus dem Peak-Bereich</span>
</div>
</div>
<div class="card">
<h2>Planck-Kurve</h2>
<div class="bb-curve-wrap">
<canvas id="bb-canvas" aria-label="Diagramm der Schwarzkörperstrahlung"></canvas>
</div>
</div>
<div class="bb-note-grid">
<div class="card bb-note">
<strong>Was zeigt die Kurve?</strong><br>
Auf der x-Achse steht die Wellenlänge in Nanometern, auf der y-Achse die spektrale Strahlungsdichte. Höhere Temperaturen verschieben das Maximum zu kleineren Wellenlängen.
</div>
<div class="card bb-note">
<strong>Wichtige Gesetze</strong><br>
Das Wiensche Verschiebungsgesetz liefert die Lage des Maximums, das Stefan-Boltzmann-Gesetz die gesamte abgestrahlte Leistung über alle Wellenlängen.
</div>
</div>
</section>
</div>
<script>
(function () {
const PLANCK_H = 6.62607015e-34;
const LIGHT_C = 2.99792458e8;
const BOLTZMANN_K = 1.380649e-23;
const STEFAN_BOLTZMANN = 5.670374419e-8;
const WIEN_DISPLACEMENT_NM_K = 2.897771955e6;
const MIN_WAVELENGTH_NM = 100;
const temperatureInput = document.getElementById('bbTemperature');
const temperatureRange = document.getElementById('bbTemperatureRange');
const maxWavelengthInput = document.getElementById('bbMaxWavelength');
const maxWavelengthRange = document.getElementById('bbMaxWavelengthRange');
const yAxisField = document.getElementById('bbYAxisField');
const yAxisAutoInput = document.getElementById('bbYAxisAuto');
const yAxisMaxInput = document.getElementById('bbYAxisMax');
const yAxisMaxRange = document.getElementById('bbYAxisMaxRange');
const peakNmOutput = document.getElementById('bbPeakNm');
const peakRegionOutput = document.getElementById('bbPeakRegion');
const fluxOutput = document.getElementById('bbFlux');
const colorLabelOutput = document.getElementById('bbColorLabel');
const canvas = document.getElementById('bb-canvas');
const ctx = canvas.getContext('2d');
function syncPair(primary, secondary, onChange) {
primary.addEventListener('input', function () {
secondary.value = primary.value;
onChange();
});
secondary.addEventListener('input', function () {
primary.value = secondary.value;
onChange();
});
}
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function updateYAxisControls() {
const isAuto = yAxisAutoInput.checked;
yAxisMaxInput.disabled = isAuto;
yAxisMaxRange.disabled = isAuto;
yAxisField.classList.toggle('is-disabled', isAuto);
}
function planckSpectralRadiance(lambdaMeters, temperatureKelvin) {
const exponent = (PLANCK_H * LIGHT_C) / (lambdaMeters * BOLTZMANN_K * temperatureKelvin);
if (!Number.isFinite(exponent) || exponent > 700) {
return 0;
}
const numerator = 2 * PLANCK_H * Math.pow(LIGHT_C, 2);
const denominator = Math.pow(lambdaMeters, 5) * (Math.exp(exponent) - 1);
return numerator / denominator;
}
function formatGermanNumber(value, fractionDigits) {
return new Intl.NumberFormat('de-DE', {
minimumFractionDigits: fractionDigits,
maximumFractionDigits: fractionDigits
}).format(value);
}
function formatScientific(value) {
if (!Number.isFinite(value) || value <= 0) {
return '0';
}
const exponent = Math.floor(Math.log10(value));
const mantissa = value / Math.pow(10, exponent);
return formatGermanNumber(mantissa, 2) + ' × 10^' + exponent;
}
function describePeakRegion(peakNm) {
if (peakNm < 380) {
return 'Maximum im Ultraviolett';
}
if (peakNm < 450) {
return 'Maximum im violett-blauen Bereich';
}
if (peakNm < 495) {
return 'Maximum im blauen Bereich';
}
if (peakNm < 570) {
return 'Maximum im grün-gelben Bereich';
}
if (peakNm < 590) {
return 'Maximum im gelben Bereich';
}
if (peakNm < 620) {
return 'Maximum im orangefarbenen Bereich';
}
if (peakNm < 750) {
return 'Maximum im roten Bereich';
}
return 'Maximum im Infrarot';
}
function describeColor(peakNm) {
if (peakNm < 380) {
return 'bläulich bis UV-dominiert';
}
if (peakNm < 500) {
return 'bläulich';
}
if (peakNm < 570) {
return 'weißlich';
}
if (peakNm < 620) {
return 'gelblich';
}
if (peakNm < 750) {
return 'orange bis rötlich';
}
return 'rot bis infrarotdominiert';
}
function drawVisibleBand(plotLeft, plotTop, plotWidth, plotHeight, xMin, xMax) {
const visibleStart = Math.max(380, xMin);
const visibleEnd = Math.min(750, xMax);
if (visibleEnd <= visibleStart) {
return;
}
const startX = plotLeft + ((visibleStart - xMin) / (xMax - xMin)) * plotWidth;
const endX = plotLeft + ((visibleEnd - xMin) / (xMax - xMin)) * plotWidth;
const gradient = ctx.createLinearGradient(startX, 0, endX, 0);
gradient.addColorStop(0.00, 'rgba(102, 0, 255, 0.30)');
gradient.addColorStop(0.17, 'rgba(0, 90, 255, 0.30)');
gradient.addColorStop(0.34, 'rgba(0, 200, 255, 0.30)');
gradient.addColorStop(0.50, 'rgba(0, 255, 120, 0.25)');
gradient.addColorStop(0.67, 'rgba(255, 230, 0, 0.25)');
gradient.addColorStop(0.83, 'rgba(255, 140, 0, 0.28)');
gradient.addColorStop(1.00, 'rgba(255, 40, 0, 0.28)');
ctx.fillStyle = gradient;
ctx.fillRect(startX, plotTop, endX - startX, plotHeight);
}
function renderChart() {
const temperature = clamp(Number(temperatureInput.value) || 0, 100, 40000);
const maxWavelengthNm = clamp(Number(maxWavelengthInput.value) || 0, 1000, 10000);
temperatureInput.value = String(temperature);
temperatureRange.value = String(temperature);
maxWavelengthInput.value = String(maxWavelengthNm);
maxWavelengthRange.value = String(maxWavelengthNm);
const peakNm = WIEN_DISPLACEMENT_NM_K / temperature;
const totalFlux = STEFAN_BOLTZMANN * Math.pow(temperature, 4);
peakNmOutput.textContent = formatGermanNumber(peakNm, peakNm < 100 ? 1 : 0) + ' nm';
peakRegionOutput.textContent = describePeakRegion(peakNm);
fluxOutput.textContent = formatScientific(totalFlux) + ' W/m²';
colorLabelOutput.textContent = describeColor(peakNm);
const ratio = Math.min(window.devicePixelRatio || 1, 2);
const width = canvas.clientWidth;
const height = canvas.clientHeight;
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.clearRect(0, 0, width, height);
const padding = { top: 20, right: 22, bottom: 48, left: 78 };
const plotLeft = padding.left;
const plotTop = padding.top;
const plotWidth = width - padding.left - padding.right;
const plotHeight = height - padding.top - padding.bottom;
const xMin = MIN_WAVELENGTH_NM;
const xMax = maxWavelengthNm;
const samples = Math.max(320, Math.floor(plotWidth));
const points = [];
let dataMaxRadiance = 0;
for (let index = 0; index <= samples; index += 1) {
const wavelengthNm = xMin + ((xMax - xMin) * index / samples);
const radiance = planckSpectralRadiance(wavelengthNm * 1e-9, temperature);
if (radiance > dataMaxRadiance) {
dataMaxRadiance = radiance;
}
points.push({ wavelengthNm, radiance });
}
let maxRadiance = dataMaxRadiance;
if (!yAxisAutoInput.checked) {
const manualMaxRadiance = Number(yAxisMaxInput.value) || 0;
if (Number.isFinite(manualMaxRadiance) && manualMaxRadiance > 0) {
maxRadiance = manualMaxRadiance;
}
}
maxRadiance = Math.max(maxRadiance, dataMaxRadiance * 0.05, 1);
drawVisibleBand(plotLeft, plotTop, plotWidth, plotHeight, xMin, xMax);
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
ctx.lineWidth = 1;
ctx.font = '11px Georgia, serif';
ctx.fillStyle = 'rgba(220, 226, 244, 0.72)';
const xTicks = [100, 250, 500, 750, 1000, 1500, 2000, 3000, 5000, 8000, 10000].filter(function (tick) {
return tick >= xMin && tick <= xMax;
});
xTicks.forEach(function (tick) {
const x = plotLeft + ((tick - xMin) / (xMax - xMin)) * plotWidth;
ctx.beginPath();
ctx.moveTo(x, plotTop);
ctx.lineTo(x, plotTop + plotHeight);
ctx.stroke();
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(String(tick), x, plotTop + plotHeight + 8);
});
const yTicks = 5;
for (let tick = 0; tick <= yTicks; tick += 1) {
const value = maxRadiance * tick / yTicks;
const y = plotTop + plotHeight - (tick / yTicks) * plotHeight;
ctx.beginPath();
ctx.moveTo(plotLeft, y);
ctx.lineTo(plotLeft + plotWidth, y);
ctx.stroke();
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.fillText(formatScientific(value), plotLeft - 10, y);
}
ctx.strokeStyle = 'rgba(255,255,255,0.22)';
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.moveTo(plotLeft, plotTop);
ctx.lineTo(plotLeft, plotTop + plotHeight);
ctx.lineTo(plotLeft + plotWidth, plotTop + plotHeight);
ctx.stroke();
const areaGradient = ctx.createLinearGradient(0, plotTop, 0, plotTop + plotHeight);
areaGradient.addColorStop(0, 'rgba(201, 168, 76, 0.38)');
areaGradient.addColorStop(1, 'rgba(201, 168, 76, 0.03)');
ctx.beginPath();
points.forEach(function (point, index) {
const x = plotLeft + ((point.wavelengthNm - xMin) / (xMax - xMin)) * plotWidth;
const y = plotTop + plotHeight - ((point.radiance / maxRadiance) * plotHeight);
if (index === 0) {
ctx.moveTo(x, y);
return;
}
ctx.lineTo(x, y);
});
ctx.lineTo(plotLeft + plotWidth, plotTop + plotHeight);
ctx.lineTo(plotLeft, plotTop + plotHeight);
ctx.closePath();
ctx.fillStyle = areaGradient;
ctx.fill();
ctx.beginPath();
points.forEach(function (point, index) {
const x = plotLeft + ((point.wavelengthNm - xMin) / (xMax - xMin)) * plotWidth;
const y = plotTop + plotHeight - ((point.radiance / maxRadiance) * plotHeight);
if (index === 0) {
ctx.moveTo(x, y);
return;
}
ctx.lineTo(x, y);
});
ctx.strokeStyle = '#f0d47b';
ctx.lineWidth = 2.4;
ctx.stroke();
if (peakNm >= xMin && peakNm <= xMax) {
const peakX = plotLeft + ((peakNm - xMin) / (xMax - xMin)) * plotWidth;
ctx.beginPath();
ctx.moveTo(peakX, plotTop);
ctx.lineTo(peakX, plotTop + plotHeight);
ctx.strokeStyle = 'rgba(79, 195, 216, 0.95)';
ctx.lineWidth = 1.4;
ctx.setLineDash([6, 5]);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#4fc3d8';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('λmax', Math.min(peakX + 6, plotLeft + plotWidth - 42), plotTop + 6);
}
ctx.fillStyle = 'rgba(240, 244, 255, 0.85)';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText('Wellenlänge in nm', plotLeft + plotWidth / 2, height - 10);
ctx.save();
ctx.translate(18, plotTop + plotHeight / 2);
ctx.rotate(-Math.PI / 2);
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('Spektrale Strahlungsdichte Bλ', 0, 0);
ctx.restore();
}
syncPair(temperatureInput, temperatureRange, renderChart);
syncPair(maxWavelengthInput, maxWavelengthRange, renderChart);
syncPair(yAxisMaxInput, yAxisMaxRange, renderChart);
yAxisAutoInput.addEventListener('change', function () {
updateYAxisControls();
renderChart();
});
document.querySelectorAll('[data-temp]').forEach(function (button) {
button.addEventListener('click', function () {
const value = button.getAttribute('data-temp') || '5778';
temperatureInput.value = value;
temperatureRange.value = value;
renderChart();
});
});
window.addEventListener('resize', renderChart);
updateYAxisControls();
renderChart();
})();
</script>
<?php require __DIR__ . '/footer.php'; ?>
+1
View File
@@ -176,6 +176,7 @@ $menuGroups = [
'links' => [
['href' => 'tcrb_lightcurve.php', 'label' => 'T CrB Lichtkurve'],
['href' => 'astronomical_conversions.php', 'label' => 'Astronomical Conversions'],
['href' => 'blackbody_radiation.php', 'label' => 'Schwarzkörperstrahlung'],
['href' => 'geocron/index.php', 'label' => 'Geocron'],
['href' => 'geocron3d.php', 'label' => 'Geocron 3D'],
['href' => 'geocron3d_C.php', 'label' => 'Geocron 3D Cesium'],