From 1e66fab2737a4791356606f206a07f49be2f5e89 Mon Sep 17 00:00:00 2001
From: Eskimue
Date: Wed, 29 Apr 2026 11:31:16 +0200
Subject: [PATCH] Export der Sternkarten eingebaut
---
public/monatsvorhersage.php | 602 +++++++++++++++++++++++++++---------
1 file changed, 460 insertions(+), 142 deletions(-)
diff --git a/public/monatsvorhersage.php b/public/monatsvorhersage.php
index f26f87d..5f35993 100644
--- a/public/monatsvorhersage.php
+++ b/public/monatsvorhersage.php
@@ -2502,6 +2502,11 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
= htmlspecialchars($chartLocationName, ENT_QUOTES, 'UTF-8') ?>
+
+
+
@@ -2522,6 +2527,11 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
= htmlspecialchars($chartLocationName, ENT_QUOTES, 'UTF-8') ?>
+
+
+
@@ -2875,9 +2885,11 @@ const MONTH_PHASE_EVENTS = = $moonPhaseEventsJson ?: '[]' ?>;
const MONTH_CHART_ERROR = = $starDataErrorJson ?: 'null' ?>;
const OBSERVATION_TIP_ICAL = = $observationTipCalendarIcsJson ?: '""' ?>;
const OBSERVATION_TIP_ICAL_FILENAME = = $observationTipCalendarFilenameJson ?: '"astrotools-monatsereignisse.ics"' ?>;
+const MONTH_CHART_EXPORT_LOGO_SRC = = json_encode($publicBasePath . 'images/logoAM_ws.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
const MONTH_CHART_COMET_K = 0.01720209895;
const MONTH_CHART_COMET_OBLIQ = 23.439279444444445 * Math.PI / 180;
+const MONTH_CHART_EXPORT_WIDTH = 2000;
function isoWeekCount(year) {
const dec28 = new Date(Date.UTC(year, 11, 28));
@@ -2895,6 +2907,168 @@ function isoWeekMonday(year, week) {
return monday;
}
+function downloadCanvasAsPng(sourceCanvas, filename, outputWidth) {
+ if (!sourceCanvas || !sourceCanvas.width || !sourceCanvas.height) {
+ return false;
+ }
+
+ const exportCanvas = document.createElement('canvas');
+ const aspectRatio = sourceCanvas.height / sourceCanvas.width;
+ exportCanvas.width = outputWidth;
+ exportCanvas.height = Math.max(1, Math.round(outputWidth * aspectRatio));
+
+ const exportCtx = exportCanvas.getContext('2d');
+ if (!exportCtx) {
+ return false;
+ }
+
+ exportCtx.fillStyle = '#000000';
+ exportCtx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
+ exportCtx.drawImage(sourceCanvas, 0, 0, exportCanvas.width, exportCanvas.height);
+
+ const link = document.createElement('a');
+ link.href = exportCanvas.toDataURL('image/png');
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ return true;
+}
+
+let monthChartExportLogoPromise = null;
+
+function loadImage(src) {
+ return new Promise(function (resolve, reject) {
+ const image = new Image();
+ image.onload = function () {
+ resolve(image);
+ };
+ image.onerror = function () {
+ reject(new Error('Bild konnte nicht geladen werden: ' + src));
+ };
+ image.src = src;
+ });
+}
+
+function getMonthChartExportLogo() {
+ if (!MONTH_CHART_EXPORT_LOGO_SRC) {
+ return Promise.resolve(null);
+ }
+
+ if (!monthChartExportLogoPromise) {
+ monthChartExportLogoPromise = loadImage(MONTH_CHART_EXPORT_LOGO_SRC).catch(function () {
+ return null;
+ });
+ }
+
+ return monthChartExportLogoPromise;
+}
+
+async function composeExportCanvas(sourceCanvas, logoPosition) {
+ const exportCanvas = document.createElement('canvas');
+ exportCanvas.width = sourceCanvas.width;
+ exportCanvas.height = sourceCanvas.height;
+
+ const exportCtx = exportCanvas.getContext('2d');
+ if (!exportCtx) {
+ return null;
+ }
+
+ exportCtx.fillStyle = '#000000';
+ exportCtx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
+ exportCtx.drawImage(sourceCanvas, 0, 0);
+
+ const logo = await getMonthChartExportLogo();
+ if (logo) {
+ const padding = Math.round(exportCanvas.width * 0.024);
+ const targetWidth = Math.max(180, Math.min(320, Math.round(exportCanvas.width * 0.14)));
+ const targetHeight = Math.round(targetWidth * (logo.naturalHeight / Math.max(1, logo.naturalWidth)));
+ const drawX = padding;
+ const drawY = logoPosition === 'bottom-left'
+ ? exportCanvas.height - padding - targetHeight
+ : padding;
+ exportCtx.drawImage(logo, drawX, drawY, targetWidth, targetHeight);
+ }
+
+ const caption = monthChartExportCaption();
+ if (caption !== '') {
+ const padding = Math.round(exportCanvas.width * 0.024);
+ const fontSize = Math.max(26, Math.round(exportCanvas.width * 0.022));
+ exportCtx.save();
+ exportCtx.font = '600 ' + fontSize + 'px Rajdhani, sans-serif';
+ exportCtx.textAlign = 'right';
+ exportCtx.textBaseline = 'bottom';
+
+ const metrics = exportCtx.measureText(caption);
+ const textWidth = metrics.width;
+ const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
+ const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
+ const boxPadX = Math.round(fontSize * 0.45);
+ const boxPadY = Math.round(fontSize * 0.3);
+ const boxWidth = textWidth + boxPadX * 2;
+ const boxHeight = ascent + descent + boxPadY * 2;
+ const boxX = exportCanvas.width - padding - boxWidth;
+ const boxY = exportCanvas.height - padding - boxHeight;
+ const radius = Math.round(fontSize * 0.28);
+
+ exportCtx.fillStyle = 'rgba(3, 7, 14, 0.7)';
+ exportCtx.beginPath();
+ exportCtx.moveTo(boxX + radius, boxY);
+ exportCtx.lineTo(boxX + boxWidth - radius, boxY);
+ exportCtx.quadraticCurveTo(boxX + boxWidth, boxY, boxX + boxWidth, boxY + radius);
+ exportCtx.lineTo(boxX + boxWidth, boxY + boxHeight - radius);
+ exportCtx.quadraticCurveTo(boxX + boxWidth, boxY + boxHeight, boxX + boxWidth - radius, boxY + boxHeight);
+ exportCtx.lineTo(boxX + radius, boxY + boxHeight);
+ exportCtx.quadraticCurveTo(boxX, boxY + boxHeight, boxX, boxY + boxHeight - radius);
+ exportCtx.lineTo(boxX, boxY + radius);
+ exportCtx.quadraticCurveTo(boxX, boxY, boxX + radius, boxY);
+ exportCtx.closePath();
+ exportCtx.fill();
+
+ exportCtx.fillStyle = 'rgba(235, 242, 255, 0.96)';
+ exportCtx.fillText(caption, exportCanvas.width - padding - boxPadX, exportCanvas.height - padding - boxPadY);
+ exportCtx.restore();
+ }
+
+ return exportCanvas;
+}
+
+function triggerCanvasDownload(sourceCanvas, filename) {
+ const link = document.createElement('a');
+ link.href = sourceCanvas.toDataURL('image/png');
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+}
+
+function monthChartExportFilename(viewMode) {
+ if (!MONTH_CHART_CONFIG) {
+ return 'Himmelsanblick_' + viewMode + '.png';
+ }
+
+ const month = String(MONTH_CHART_CONFIG.monthNumber || MONTH_CHART_CONFIG.month || '').padStart(2, '0');
+ const year = String(MONTH_CHART_CONFIG.year || '');
+ const hour = String(Number(MONTH_CHART_CONFIG.monthSkyHour ?? 0)).padStart(2, '0');
+ const minute = String(Number(MONTH_CHART_CONFIG.monthSkyMinute ?? 0)).padStart(2, '0');
+ const safeMode = viewMode === 'sued' ? 'sued' : 'polar';
+ return 'Himmelsanblick_' + safeMode + '_' + year + '_' + month + '_' + hour + '-' + minute + '.png';
+}
+
+function monthChartExportCaption() {
+ if (!MONTH_CHART_CONFIG) {
+ return '';
+ }
+
+ const hour = Number(MONTH_CHART_CONFIG.monthSkyHour ?? NaN);
+ const minute = Number(MONTH_CHART_CONFIG.monthSkyMinute ?? NaN);
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) {
+ return '';
+ }
+
+ return 'Sternhimmel fuer ' + String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0') + ' Uhr';
+}
+
function formatGermanDate(date) {
const day = String(date.getUTCDate()).padStart(2, '0');
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
@@ -3213,7 +3387,7 @@ function monthChartCometRaDec(comet, date) {
ctx.restore();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.16)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.stroke();
@@ -3301,6 +3475,7 @@ function monthChartCometRaDec(comet, date) {
})();
(function () {
+ const downloadButton = document.getElementById('downloadMonatskartePng');
const canvas = document.getElementById('monatskarteCanvas');
const statusEl = document.getElementById('monatskarteStatus');
@@ -3335,9 +3510,25 @@ function monthChartCometRaDec(comet, date) {
const displayStarMap = new Map();
let labelPlacer = null;
let projectionLayout = null;
+ let viewportWidth = 900;
+ let viewportHeight = 900;
+ let renderScale = 1;
+ let exportRenderMode = false;
const projCenterAzDeg = 180;
const projCenterAltDeg = 45;
+ function uiScale() {
+ return (viewportWidth / 900) * 1.22;
+ }
+
+ function scalePx(value) {
+ return value * uiScale();
+ }
+
+ function scaleFont(sizePx, weight = '600', family = 'Rajdhani, sans-serif') {
+ return weight + ' ' + scalePx(sizePx) + 'px ' + family;
+ }
+
for (const star of MONTH_CHART_LINE_STARS) {
if (Number.isFinite(Number(star.hip))) {
starByHip.set(Number(star.hip), star);
@@ -3395,8 +3586,8 @@ function monthChartCometRaDec(comet, date) {
function insideStage(box) {
return box.x >= 0 &&
box.y >= 0 &&
- box.x + box.width <= canvas.clientWidth &&
- box.y + box.height <= canvas.clientHeight;
+ box.x + box.width <= viewportWidth &&
+ box.y + box.height <= viewportHeight;
}
return {
@@ -3408,11 +3599,11 @@ function monthChartCometRaDec(comet, date) {
text,
x,
y,
- font = '12px Georgia, serif',
+ font = scaleFont(12, '400', 'Georgia, serif'),
color = 'rgba(232, 239, 255, 0.82)',
align = 'left',
baseline = 'middle',
- padding = 2,
+ padding = scalePx(2),
} = options;
if (!text) {
@@ -3465,25 +3656,25 @@ function monthChartCometRaDec(comet, date) {
function placePlanetLabel(text, x, y, radius) {
const attempts = [
- { x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
- { x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
- { x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
- { x, y: y + radius + 7, align: 'center', baseline: 'top' },
- { x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
- { x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
- { x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
- { x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - scalePx(1), align: 'left', baseline: 'middle' },
+ { x: x - radius - scalePx(6), y: y - scalePx(1), align: 'right', baseline: 'middle' },
+ { x, y: y - radius - scalePx(7), align: 'center', baseline: 'bottom' },
+ { x, y: y + radius + scalePx(7), align: 'center', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - radius - scalePx(5), align: 'left', baseline: 'bottom' },
+ { x: x - radius - scalePx(6), y: y - radius - scalePx(5), align: 'right', baseline: 'bottom' },
+ { x: x + radius + scalePx(6), y: y + radius + scalePx(5), align: 'left', baseline: 'top' },
+ { x: x - radius - scalePx(6), y: y + radius + scalePx(5), align: 'right', baseline: 'top' },
];
function drawLabelBackdrop(drawX, drawY, align, baseline) {
ctx.save();
- ctx.font = '600 11px Rajdhani, sans-serif';
+ ctx.font = scaleFont(11);
const metrics = ctx.measureText(text);
const textWidth = metrics.width;
const ascent = metrics.actualBoundingBoxAscent || 8;
const descent = metrics.actualBoundingBoxDescent || 4;
- const padX = 5;
- const padY = 3;
+ const padX = scalePx(5);
+ const padY = scalePx(3);
let boxX = drawX;
if (align === 'center') boxX = drawX - textWidth / 2;
@@ -3497,7 +3688,7 @@ function monthChartCometRaDec(comet, date) {
const rectY = boxY - padY;
const rectW = textWidth + padX * 2;
const rectH = ascent + descent + padY * 2;
- const radiusPx = 6;
+ const radiusPx = scalePx(6);
ctx.fillStyle = 'rgba(3, 6, 12, 0.52)';
ctx.beginPath();
@@ -3520,15 +3711,15 @@ function monthChartCometRaDec(comet, date) {
text,
x: attempt.x,
y: attempt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(246, 235, 214, 0.84)',
align: attempt.align,
baseline: attempt.baseline,
- padding: 2,
+ padding: scalePx(2),
})) {
drawLabelBackdrop(attempt.x, attempt.y, attempt.align, attempt.baseline);
ctx.save();
- ctx.font = '600 11px Rajdhani, sans-serif';
+ ctx.font = scaleFont(11);
ctx.textAlign = attempt.align;
ctx.textBaseline = attempt.baseline;
ctx.fillStyle = 'rgba(246, 235, 214, 0.84)';
@@ -3543,25 +3734,25 @@ function monthChartCometRaDec(comet, date) {
function placePlanetLabel(text, x, y, radius) {
const attempts = [
- { x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
- { x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
- { x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
- { x, y: y + radius + 7, align: 'center', baseline: 'top' },
- { x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
- { x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
- { x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
- { x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - scalePx(1), align: 'left', baseline: 'middle' },
+ { x: x - radius - scalePx(6), y: y - scalePx(1), align: 'right', baseline: 'middle' },
+ { x, y: y - radius - scalePx(7), align: 'center', baseline: 'bottom' },
+ { x, y: y + radius + scalePx(7), align: 'center', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - radius - scalePx(5), align: 'left', baseline: 'bottom' },
+ { x: x - radius - scalePx(6), y: y - radius - scalePx(5), align: 'right', baseline: 'bottom' },
+ { x: x + radius + scalePx(6), y: y + radius + scalePx(5), align: 'left', baseline: 'top' },
+ { x: x - radius - scalePx(6), y: y + radius + scalePx(5), align: 'right', baseline: 'top' },
];
function drawLabelBackdrop(drawX, drawY, align, baseline) {
ctx.save();
- ctx.font = '600 11px Rajdhani, sans-serif';
+ ctx.font = scaleFont(11);
const metrics = ctx.measureText(text);
const textWidth = metrics.width;
const ascent = metrics.actualBoundingBoxAscent || 8;
const descent = metrics.actualBoundingBoxDescent || 4;
- const padX = 5;
- const padY = 3;
+ const padX = scalePx(5);
+ const padY = scalePx(3);
let boxX = drawX;
if (align === 'center') boxX = drawX - textWidth / 2;
@@ -3575,7 +3766,7 @@ function monthChartCometRaDec(comet, date) {
const rectY = boxY - padY;
const rectW = textWidth + padX * 2;
const rectH = ascent + descent + padY * 2;
- const radiusPx = 6;
+ const radiusPx = scalePx(6);
ctx.fillStyle = 'rgba(3, 6, 12, 0.52)';
ctx.beginPath();
@@ -3598,15 +3789,15 @@ function monthChartCometRaDec(comet, date) {
text,
x: attempt.x,
y: attempt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(246, 235, 214, 0.84)',
align: attempt.align,
baseline: attempt.baseline,
- padding: 2,
+ padding: scalePx(2),
})) {
drawLabelBackdrop(attempt.x, attempt.y, attempt.align, attempt.baseline);
ctx.save();
- ctx.font = '600 11px Rajdhani, sans-serif';
+ ctx.font = scaleFont(11);
ctx.textAlign = attempt.align;
ctx.textBaseline = attempt.baseline;
ctx.fillStyle = 'rgba(246, 235, 214, 0.84)';
@@ -3622,12 +3813,15 @@ function monthChartCometRaDec(comet, date) {
function resizeCanvas() {
const cssSize = Math.max(320, Math.min(canvas.parentElement.clientWidth, 900));
const ratio = Math.min(window.devicePixelRatio || 1, 2);
- canvas.width = Math.round(cssSize * ratio);
- canvas.height = Math.round(cssSize * ratio);
+ viewportWidth = cssSize;
+ viewportHeight = cssSize;
+ renderScale = ratio;
+ canvas.width = Math.round(viewportWidth * renderScale);
+ canvas.height = Math.round(viewportHeight * renderScale);
canvas.style.width = cssSize + 'px';
canvas.style.height = cssSize + 'px';
ctx.setTransform(1, 0, 0, 1, 0, 0);
- ctx.scale(ratio, ratio);
+ ctx.scale(renderScale, renderScale);
labelPlacer = createLabelPlacer(ctx);
draw();
}
@@ -3709,13 +3903,13 @@ function monthChartCometRaDec(comet, date) {
horizonGlow.addColorStop(0.9, 'rgba(118, 170, 255, 0.09)');
horizonGlow.addColorStop(1, 'rgba(168, 208, 255, 0.14)');
ctx.strokeStyle = horizonGlow;
- ctx.lineWidth = Math.max(8, radius * 0.035);
+ ctx.lineWidth = Math.max(scalePx(8), radius * 0.035);
ctx.beginPath();
ctx.arc(cx, cy, radius * 0.97, 0, Math.PI * 2);
ctx.stroke();
ctx.strokeStyle = 'rgba(183, 205, 255, 0.28)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.stroke();
@@ -3723,7 +3917,7 @@ function monthChartCometRaDec(comet, date) {
function drawGuides(cx, cy, radius) {
ctx.strokeStyle = 'rgba(140, 165, 220, 0.18)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
for (const frac of [0.33, 0.66]) {
ctx.beginPath();
@@ -3760,20 +3954,20 @@ function monthChartCometRaDec(comet, date) {
ctx.restore();
}
- drawDirectionLabel(0, 'Norden', 18, '600 13px Rajdhani, sans-serif', 0.78);
- drawDirectionLabel(90, 'Westen', 22, '600 13px Rajdhani, sans-serif', 0.78);
- drawDirectionLabel(180, 'Süden', 18, '600 13px Rajdhani, sans-serif', 0.78);
- drawDirectionLabel(270, 'Osten', 22, '600 13px Rajdhani, sans-serif', 0.78);
+ drawDirectionLabel(0, 'Norden', scalePx(18), scaleFont(13), 0.78);
+ drawDirectionLabel(90, 'Westen', scalePx(22), scaleFont(13), 0.78);
+ drawDirectionLabel(180, 'Süden', scalePx(18), scaleFont(13), 0.78);
+ drawDirectionLabel(270, 'Osten', scalePx(22), scaleFont(13), 0.78);
- drawDirectionLabel(45, 'Nordosten', 18, '600 11px Rajdhani, sans-serif', 0.64);
- drawDirectionLabel(315, 'Nordwesten', 18, '600 11px Rajdhani, sans-serif', 0.64);
- drawDirectionLabel(135, 'Südosten', 18, '600 11px Rajdhani, sans-serif', 0.64);
- drawDirectionLabel(225, 'Südwesten', 18, '600 11px Rajdhani, sans-serif', 0.64);
+ drawDirectionLabel(45, 'Nordosten', scalePx(18), scaleFont(11), 0.64);
+ drawDirectionLabel(315, 'Nordwesten', scalePx(18), scaleFont(11), 0.64);
+ drawDirectionLabel(135, 'Südosten', scalePx(18), scaleFont(11), 0.64);
+ drawDirectionLabel(225, 'Südwesten', scalePx(18), scaleFont(11), 0.64);
ctx.fillStyle = 'rgba(214, 226, 255, 0.62)';
- ctx.font = '600 11px Rajdhani, sans-serif';
- ctx.fillText('60°', cx, cy - radius * 0.66 - 6);
- ctx.fillText('30°', cx, cy - radius * 0.33 - 6);
+ ctx.font = scaleFont(11);
+ ctx.fillText('60°', cx, cy - radius * 0.66 - scalePx(6));
+ ctx.fillText('30°', cx, cy - radius * 0.33 - scalePx(6));
ctx.fillText('Zenit', cx, cy);
}
@@ -3817,7 +4011,7 @@ function monthChartCometRaDec(comet, date) {
ctx.lineJoin = 'round';
ctx.strokeStyle = 'rgba(210, 214, 224, 0.16)';
- ctx.lineWidth = 2.1;
+ ctx.lineWidth = scalePx(2.1);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
@@ -3826,12 +4020,12 @@ function monthChartCometRaDec(comet, date) {
ctx.stroke();
ctx.strokeStyle = 'rgba(36, 40, 48, 0.34)';
- ctx.lineWidth = 0.95;
+ ctx.lineWidth = scalePx(0.95);
ctx.beginPath();
- ctx.moveTo(points[0].x + 0.18, points[0].y - 0.12);
+ ctx.moveTo(points[0].x + scalePx(0.18), points[0].y - scalePx(0.12));
for (let i = 1; i < points.length; i++) {
- const jitterX = (i % 2 === 0 ? 0.22 : -0.12);
- const jitterY = (i % 2 === 0 ? -0.14 : 0.2);
+ const jitterX = (i % 2 === 0 ? scalePx(0.22) : -scalePx(0.12));
+ const jitterY = (i % 2 === 0 ? -scalePx(0.14) : scalePx(0.2));
ctx.lineTo(points[i].x + jitterX, points[i].y + jitterY);
}
ctx.stroke();
@@ -3870,11 +4064,11 @@ function monthChartCometRaDec(comet, date) {
text: label,
x: pt.x,
y: pt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(198, 214, 255, 0.54)',
align: 'center',
baseline: 'middle',
- padding: 3,
+ padding: scalePx(3),
});
if (placed) {
@@ -3955,7 +4149,7 @@ function monthChartCometRaDec(comet, date) {
const phaseFraction = clamp01(Number(MONTH_CHART_MOON.phaseFraction ?? 0.5));
const waxing = Boolean(MONTH_CHART_MOON.waxing);
- const moonRadius = 10.5;
+ const moonRadius = scalePx(10.5);
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, moonRadius * 2.1);
glow.addColorStop(0, 'rgba(220, 226, 238, 0.28)');
glow.addColorStop(0.5, 'rgba(220, 226, 238, 0.08)');
@@ -3990,7 +4184,7 @@ function monthChartCometRaDec(comet, date) {
ctx.restore();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(pt.x, pt.y, moonRadius, 0, Math.PI * 2);
ctx.stroke();
@@ -4012,8 +4206,8 @@ function monthChartCometRaDec(comet, date) {
const style = getPlanetStyle(planet);
const mag = Number(planet.mag);
const baseRadius = Number.isFinite(mag)
- ? Math.max(3.8, Math.min(8.6, 6.7 - mag * 0.72))
- : 5.4;
+ ? Math.max(scalePx(3.8), Math.min(scalePx(8.6), scalePx(6.7 - mag * 0.72)))
+ : scalePx(5.4);
const glowRadius = baseRadius * 2.2;
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, glowRadius);
@@ -4031,7 +4225,7 @@ function monthChartCometRaDec(comet, date) {
ctx.fill();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.26)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
ctx.stroke();
@@ -4040,11 +4234,11 @@ function monthChartCometRaDec(comet, date) {
ctx.save();
ctx.translate(pt.x, pt.y);
ctx.beginPath();
- ctx.arc(0, 0, baseRadius - 0.3, 0, Math.PI * 2);
+ ctx.arc(0, 0, baseRadius - scalePx(0.3), 0, Math.PI * 2);
ctx.clip();
ctx.strokeStyle = 'rgba(116, 84, 54, 0.78)';
- ctx.lineWidth = Math.max(1, baseRadius * 0.34);
+ ctx.lineWidth = Math.max(scalePx(1), baseRadius * 0.34);
ctx.beginPath();
ctx.moveTo(-baseRadius * 0.95, -baseRadius * 0.34);
ctx.lineTo(baseRadius * 0.95, -baseRadius * 0.34);
@@ -4064,7 +4258,7 @@ function monthChartCometRaDec(comet, date) {
ctx.translate(pt.x, pt.y);
ctx.rotate(-0.35);
ctx.strokeStyle = 'rgba(232, 221, 170, 0.72)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.ellipse(0, 0, baseRadius * 1.7, baseRadius * 0.72, 0, 0, Math.PI * 2);
ctx.stroke();
@@ -4115,7 +4309,7 @@ function monthChartCometRaDec(comet, date) {
tailAngle = Math.atan2(pt.y - sunPt.y, pt.x - sunPt.x);
}
- const tailLen = pos.rH > 0 ? Math.max(8, Math.min(28, 18 / pos.rH)) : 12;
+ const tailLen = pos.rH > 0 ? Math.max(scalePx(8), Math.min(scalePx(28), scalePx(18) / pos.rH)) : scalePx(12);
const tx = pt.x + Math.cos(tailAngle) * tailLen;
const ty = pt.y + Math.sin(tailAngle) * tailLen;
@@ -4127,11 +4321,11 @@ function monthChartCometRaDec(comet, date) {
ctx.moveTo(pt.x, pt.y);
ctx.lineTo(tx, ty);
ctx.strokeStyle = grd;
- ctx.lineWidth = 2.2;
+ ctx.lineWidth = scalePx(2.2);
ctx.lineCap = 'round';
ctx.stroke();
- const comaR = 4.2;
+ const comaR = scalePx(4.2);
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, comaR * 2.6);
glow.addColorStop(0, 'rgba(255, 245, 200, 0.94)');
glow.addColorStop(0.45, 'rgba(201, 168, 76, 0.54)');
@@ -4170,10 +4364,10 @@ function monthChartCometRaDec(comet, date) {
}
const style = getMessierStyle(String(obj.type || 'As'));
- const symbolRadius = Math.max(3.4, Math.min(6.8, 3.2 + Math.max(0, 6.5 - Number(obj.mag)) * 0.5));
+ const symbolRadius = Math.max(scalePx(3.4), Math.min(scalePx(6.8), scalePx(3.2 + Math.max(0, 6.5 - Number(obj.mag)) * 0.5)));
ctx.strokeStyle = style.stroke;
ctx.fillStyle = style.fill;
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
if (obj.type === 'Gc') {
ctx.beginPath();
@@ -4187,7 +4381,7 @@ function monthChartCometRaDec(comet, date) {
ctx.lineTo(pt.x, pt.y + symbolRadius * 0.8);
ctx.stroke();
} else if (obj.type === 'Oc') {
- ctx.setLineDash([2, 2]);
+ ctx.setLineDash([scalePx(2), scalePx(2)]);
ctx.beginPath();
ctx.arc(pt.x, pt.y, symbolRadius, 0, Math.PI * 2);
ctx.stroke();
@@ -4222,13 +4416,13 @@ function monthChartCometRaDec(comet, date) {
if (label !== '') {
labelPlacer.drawText({
text: label,
- x: pt.x + symbolRadius + 5,
+ x: pt.x + symbolRadius + scalePx(5),
y: pt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(210, 224, 255, 0.78)',
align: 'left',
baseline: 'middle',
- padding: 2,
+ padding: scalePx(2),
});
}
}
@@ -4258,7 +4452,7 @@ function monthChartCometRaDec(comet, date) {
? Math.min(Number(star.mag), Number(MONTH_CHART_CONFIG.starLimit) + 0.35)
: Number(star.mag);
const brightness = clamp01((Number(MONTH_CHART_CONFIG.starLimit) - effectiveMagnitude + 1.6) / magRange);
- const starRadius = (isConstellationStar ? 0.85 : 0.65) + Math.pow(brightness, 1.9) * 5.2;
+ const starRadius = ((isConstellationStar ? 0.85 : 0.65) + Math.pow(brightness, 1.9) * 5.2) * (exportRenderMode ? 2.0 : 1);
const glowRadius = starRadius * (effectiveMagnitude <= 1 ? 2.8 : 1.9);
const color = starColor(star);
@@ -4278,7 +4472,7 @@ function monthChartCometRaDec(comet, date) {
if (star.mag <= 0.8) {
ctx.strokeStyle = color + '44';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.moveTo(pt.x - starRadius * 1.35, pt.y);
ctx.lineTo(pt.x + starRadius * 1.35, pt.y);
@@ -4292,13 +4486,13 @@ function monthChartCometRaDec(comet, date) {
if (label !== '') {
labelPlacer.drawText({
text: label,
- x: pt.x + starRadius + 5,
- y: pt.y - 1,
- font: '600 11px Rajdhani, sans-serif',
+ x: pt.x + starRadius + scalePx(5),
+ y: pt.y - scalePx(1),
+ font: scaleFont(11),
color: 'rgba(232, 239, 255, 0.82)',
align: 'left',
baseline: 'middle',
- padding: 2,
+ padding: scalePx(2),
});
}
}
@@ -4306,8 +4500,8 @@ function monthChartCometRaDec(comet, date) {
}
function draw() {
- const width = canvas.clientWidth;
- const height = canvas.clientHeight;
+ const width = viewportWidth;
+ const height = viewportHeight;
const cx = width / 2;
const cy = height / 2;
const radius = Math.min(width, height) * 0.44;
@@ -4327,11 +4521,64 @@ function monthChartCometRaDec(comet, date) {
statusEl.textContent = 'Sternkarte für ' + MONTH_CHART_CONFIG.localTimeLabel;
}
+ if (downloadButton) {
+ downloadButton.addEventListener('click', async function () {
+ const previous = {
+ width: canvas.width,
+ height: canvas.height,
+ styleWidth: canvas.style.width,
+ styleHeight: canvas.style.height,
+ viewportWidth,
+ viewportHeight,
+ renderScale,
+ };
+
+ try {
+ statusEl.textContent = 'PNG wird in 2000 px neu gerendert...';
+ downloadButton.disabled = true;
+
+ exportRenderMode = true;
+ viewportWidth = MONTH_CHART_EXPORT_WIDTH;
+ viewportHeight = MONTH_CHART_EXPORT_WIDTH;
+ renderScale = 1;
+ canvas.width = viewportWidth;
+ canvas.height = viewportHeight;
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ labelPlacer = createLabelPlacer(ctx);
+ draw();
+
+ const exportCanvas = await composeExportCanvas(canvas, 'bottom-left');
+ if (!exportCanvas) {
+ throw new Error('Export-Canvas konnte nicht erzeugt werden.');
+ }
+
+ triggerCanvasDownload(exportCanvas, monthChartExportFilename('polar'));
+ } catch (error) {
+ statusEl.textContent = 'PNG-Download konnte nicht erstellt werden.';
+ } finally {
+ exportRenderMode = false;
+ viewportWidth = previous.viewportWidth;
+ viewportHeight = previous.viewportHeight;
+ renderScale = previous.renderScale;
+ canvas.width = previous.width;
+ canvas.height = previous.height;
+ canvas.style.width = previous.styleWidth;
+ canvas.style.height = previous.styleHeight;
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.scale(renderScale, renderScale);
+ labelPlacer = createLabelPlacer(ctx);
+ draw();
+ downloadButton.disabled = false;
+ }
+ });
+ }
+
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
})();
(function () {
+ const downloadButton = document.getElementById('downloadMonatskarteSouthPng');
const canvas = document.getElementById('monatskarteSouthCanvas');
const statusEl = document.getElementById('monatskarteSouthStatus');
@@ -4351,9 +4598,25 @@ function monthChartCometRaDec(comet, date) {
const displayStarMap = new Map();
let labelPlacer = null;
let projectionLayout = null;
+ let viewportWidth = 1200;
+ let viewportHeight = 720;
+ let renderScale = 1;
+ let exportRenderMode = false;
const projCenterAzDeg = 180;
const projCenterAltDeg = 45;
+ function uiScale() {
+ return (viewportWidth / 1100) * 1.22;
+ }
+
+ function scalePx(value) {
+ return value * uiScale();
+ }
+
+ function scaleFont(sizePx, weight = '600', family = 'Rajdhani, sans-serif') {
+ return weight + ' ' + scalePx(sizePx) + 'px ' + family;
+ }
+
for (const star of MONTH_CHART_LINE_STARS) {
if (Number.isFinite(Number(star.hip))) {
starByHip.set(Number(star.hip), star);
@@ -4409,8 +4672,8 @@ function monthChartCometRaDec(comet, date) {
function insideStage(box) {
return box.x >= 0 &&
box.y >= 0 &&
- box.x + box.width <= canvas.clientWidth &&
- box.y + box.height <= canvas.clientHeight;
+ box.x + box.width <= viewportWidth &&
+ box.y + box.height <= viewportHeight;
}
return {
@@ -4422,11 +4685,11 @@ function monthChartCometRaDec(comet, date) {
text,
x,
y,
- font = '600 11px Rajdhani, sans-serif',
+ font = scaleFont(11),
color = 'rgba(232, 239, 255, 0.82)',
align = 'left',
baseline = 'middle',
- padding = 2,
+ padding = scalePx(2),
} = options;
if (!text) {
@@ -4473,14 +4736,14 @@ function monthChartCometRaDec(comet, date) {
function placePlanetLabel(text, x, y, radius) {
const attempts = [
- { x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
- { x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
- { x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
- { x, y: y + radius + 7, align: 'center', baseline: 'top' },
- { x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
- { x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
- { x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
- { x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - scalePx(1), align: 'left', baseline: 'middle' },
+ { x: x - radius - scalePx(6), y: y - scalePx(1), align: 'right', baseline: 'middle' },
+ { x, y: y - radius - scalePx(7), align: 'center', baseline: 'bottom' },
+ { x, y: y + radius + scalePx(7), align: 'center', baseline: 'top' },
+ { x: x + radius + scalePx(6), y: y - radius - scalePx(5), align: 'left', baseline: 'bottom' },
+ { x: x - radius - scalePx(6), y: y - radius - scalePx(5), align: 'right', baseline: 'bottom' },
+ { x: x + radius + scalePx(6), y: y + radius + scalePx(5), align: 'left', baseline: 'top' },
+ { x: x - radius - scalePx(6), y: y + radius + scalePx(5), align: 'right', baseline: 'top' },
];
for (const attempt of attempts) {
@@ -4488,11 +4751,11 @@ function monthChartCometRaDec(comet, date) {
text,
x: attempt.x,
y: attempt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(246, 235, 214, 0.84)',
align: attempt.align,
baseline: attempt.baseline,
- padding: 2,
+ padding: scalePx(2),
})) {
return true;
}
@@ -4505,12 +4768,15 @@ function monthChartCometRaDec(comet, date) {
const cssWidth = Math.max(420, Math.min(canvas.parentElement.clientWidth, 1100));
const cssHeight = Math.round(cssWidth * 0.6);
const ratio = Math.min(window.devicePixelRatio || 1, 2);
- canvas.width = Math.round(cssWidth * ratio);
- canvas.height = Math.round(cssHeight * ratio);
+ viewportWidth = cssWidth;
+ viewportHeight = cssHeight;
+ renderScale = ratio;
+ canvas.width = Math.round(viewportWidth * renderScale);
+ canvas.height = Math.round(viewportHeight * renderScale);
canvas.style.width = cssWidth + 'px';
canvas.style.height = cssHeight + 'px';
ctx.setTransform(1, 0, 0, 1, 0, 0);
- ctx.scale(ratio, ratio);
+ ctx.scale(renderScale, renderScale);
labelPlacer = createLabelPlacer(ctx);
draw();
}
@@ -4700,7 +4966,7 @@ function monthChartCometRaDec(comet, date) {
const phaseFraction = clamp01(Number(MONTH_CHART_MOON.phaseFraction ?? 0.5));
const waxing = Boolean(MONTH_CHART_MOON.waxing);
- const moonRadius = 10.5;
+ const moonRadius = scalePx(10.5);
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, moonRadius * 2.1);
glow.addColorStop(0, 'rgba(220, 226, 238, 0.28)');
glow.addColorStop(0.5, 'rgba(220, 226, 238, 0.08)');
@@ -4735,7 +5001,7 @@ function monthChartCometRaDec(comet, date) {
ctx.restore();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(pt.x, pt.y, moonRadius, 0, Math.PI * 2);
ctx.stroke();
@@ -4753,8 +5019,8 @@ function monthChartCometRaDec(comet, date) {
const style = getPlanetStyle(planet);
const mag = Number(planet.mag);
const baseRadius = Number.isFinite(mag)
- ? Math.max(3.8, Math.min(8.6, 6.7 - mag * 0.72))
- : 5.4;
+ ? Math.max(scalePx(3.8), Math.min(scalePx(8.6), scalePx(6.7 - mag * 0.72)))
+ : scalePx(5.4);
const glowRadius = baseRadius * 2.2;
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, glowRadius);
@@ -4772,7 +5038,7 @@ function monthChartCometRaDec(comet, date) {
ctx.fill();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.26)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
ctx.stroke();
@@ -4781,11 +5047,11 @@ function monthChartCometRaDec(comet, date) {
ctx.save();
ctx.translate(pt.x, pt.y);
ctx.beginPath();
- ctx.arc(0, 0, baseRadius - 0.3, 0, Math.PI * 2);
+ ctx.arc(0, 0, baseRadius - scalePx(0.3), 0, Math.PI * 2);
ctx.clip();
ctx.strokeStyle = 'rgba(116, 84, 54, 0.78)';
- ctx.lineWidth = Math.max(1, baseRadius * 0.34);
+ ctx.lineWidth = Math.max(scalePx(1), baseRadius * 0.34);
ctx.beginPath();
ctx.moveTo(-baseRadius * 0.95, -baseRadius * 0.34);
ctx.lineTo(baseRadius * 0.95, -baseRadius * 0.34);
@@ -4805,7 +5071,7 @@ function monthChartCometRaDec(comet, date) {
ctx.translate(pt.x, pt.y);
ctx.rotate(-0.35);
ctx.strokeStyle = 'rgba(232, 221, 170, 0.72)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.ellipse(0, 0, baseRadius * 1.7, baseRadius * 0.72, 0, 0, Math.PI * 2);
ctx.stroke();
@@ -4856,7 +5122,7 @@ function monthChartCometRaDec(comet, date) {
tailAngle = Math.atan2(pt.y - sunPt.y, pt.x - sunPt.x);
}
- const tailLen = pos.rH > 0 ? Math.max(8, Math.min(28, 18 / pos.rH)) : 12;
+ const tailLen = pos.rH > 0 ? Math.max(scalePx(8), Math.min(scalePx(28), scalePx(18) / pos.rH)) : scalePx(12);
const tx = pt.x + Math.cos(tailAngle) * tailLen;
const ty = pt.y + Math.sin(tailAngle) * tailLen;
@@ -4868,11 +5134,11 @@ function monthChartCometRaDec(comet, date) {
ctx.moveTo(pt.x, pt.y);
ctx.lineTo(tx, ty);
ctx.strokeStyle = grd;
- ctx.lineWidth = 2.2;
+ ctx.lineWidth = scalePx(2.2);
ctx.lineCap = 'round';
ctx.stroke();
- const comaR = 4.2;
+ const comaR = scalePx(4.2);
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, comaR * 2.6);
glow.addColorStop(0, 'rgba(255, 245, 200, 0.94)');
glow.addColorStop(0.45, 'rgba(201, 168, 76, 0.54)');
@@ -4953,14 +5219,14 @@ function monthChartCometRaDec(comet, date) {
function drawGuides(width, height) {
ctx.strokeStyle = 'rgba(140, 165, 220, 0.14)';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
for (const alt of [0, 30, 60]) {
const samples = [];
for (let az = 90; az <= 270; az += 2) {
samples.push({ alt, az });
}
- strokeProjectedCurve(samples, alt === 0 ? 'rgba(183, 205, 255, 0.18)' : 'rgba(140, 165, 220, 0.14)', 1);
+ strokeProjectedCurve(samples, alt === 0 ? 'rgba(183, 205, 255, 0.18)' : 'rgba(140, 165, 220, 0.14)', scalePx(1));
}
for (const az of [90, 135, 180, 225, 270]) {
@@ -4968,7 +5234,7 @@ function monthChartCometRaDec(comet, date) {
for (let alt = 0; alt <= 90; alt += 2) {
samples.push({ alt, az });
}
- strokeProjectedCurve(samples, 'rgba(140, 165, 220, 0.12)', 1);
+ strokeProjectedCurve(samples, 'rgba(140, 165, 220, 0.12)', scalePx(1));
}
function drawDirectionLabelOutside(azDeg, text, offset = 18) {
@@ -4995,7 +5261,7 @@ function monthChartCometRaDec(comet, date) {
ctx.translate(horizonPt.x + nx * offset, horizonPt.y + ny * offset);
ctx.rotate(angle);
ctx.fillStyle = 'rgba(214, 226, 255, 0.72)';
- ctx.font = '600 12px Rajdhani, sans-serif';
+ ctx.font = scaleFont(12);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, 0, 0);
@@ -5011,16 +5277,16 @@ function monthChartCometRaDec(comet, date) {
];
for (const label of directionLabels) {
- drawDirectionLabelOutside(label.az, label.text, label.az === 180 ? 22 : 18);
+ drawDirectionLabelOutside(label.az, label.text, label.az === 180 ? scalePx(22) : scalePx(18));
}
const zenitPt = southProject(88, 180);
if (zenitPt) {
ctx.fillStyle = 'rgba(214, 226, 255, 0.72)';
- ctx.font = '600 12px Rajdhani, sans-serif';
+ ctx.font = scaleFont(12);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
- ctx.fillText('Zenit', zenitPt.x, zenitPt.y - 8);
+ ctx.fillText('Zenit', zenitPt.x, zenitPt.y - scalePx(8));
}
for (const alt of [30, 60]) {
@@ -5029,10 +5295,10 @@ function monthChartCometRaDec(comet, date) {
continue;
}
ctx.fillStyle = 'rgba(214, 226, 255, 0.58)';
- ctx.font = '600 11px Rajdhani, sans-serif';
+ ctx.font = scaleFont(11);
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
- ctx.fillText(`${alt}°`, pt.x + 8, pt.y);
+ ctx.fillText(`${alt}°`, pt.x + scalePx(8), pt.y);
}
}
@@ -5055,19 +5321,19 @@ function monthChartCometRaDec(comet, date) {
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = 'rgba(210, 214, 224, 0.16)';
- ctx.lineWidth = 2.1;
+ ctx.lineWidth = scalePx(2.1);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
ctx.strokeStyle = 'rgba(36, 40, 48, 0.34)';
- ctx.lineWidth = 0.95;
+ ctx.lineWidth = scalePx(0.95);
ctx.beginPath();
- ctx.moveTo(points[0].x + 0.18, points[0].y - 0.12);
+ ctx.moveTo(points[0].x + scalePx(0.18), points[0].y - scalePx(0.12));
for (let i = 1; i < points.length; i++) {
- const jitterX = (i % 2 === 0 ? 0.22 : -0.12);
- const jitterY = (i % 2 === 0 ? -0.14 : 0.2);
+ const jitterX = (i % 2 === 0 ? scalePx(0.22) : -scalePx(0.12));
+ const jitterY = (i % 2 === 0 ? -scalePx(0.14) : scalePx(0.2));
ctx.lineTo(points[i].x + jitterX, points[i].y + jitterY);
}
ctx.stroke();
@@ -5092,11 +5358,11 @@ function monthChartCometRaDec(comet, date) {
text: label,
x: pt.x,
y: pt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(198, 214, 255, 0.54)',
align: 'center',
baseline: 'middle',
- padding: 3,
+ padding: scalePx(3),
})) break;
}
}
@@ -5109,10 +5375,10 @@ function monthChartCometRaDec(comet, date) {
const pt = southProject(hor.altitude, hor.azimuth);
if (!pt) continue;
const style = getMessierStyle(String(obj.type || 'As'));
- const symbolRadius = Math.max(3.4, Math.min(6.8, 3.2 + Math.max(0, 6.5 - Number(obj.mag)) * 0.5));
+ const symbolRadius = Math.max(scalePx(3.4), Math.min(scalePx(6.8), scalePx(3.2 + Math.max(0, 6.5 - Number(obj.mag)) * 0.5)));
ctx.strokeStyle = style.stroke;
ctx.fillStyle = style.fill;
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
if (obj.type === 'Gc') {
ctx.beginPath(); ctx.arc(pt.x, pt.y, symbolRadius, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
@@ -5121,7 +5387,7 @@ function monthChartCometRaDec(comet, date) {
ctx.moveTo(pt.x, pt.y - symbolRadius * 0.8); ctx.lineTo(pt.x, pt.y + symbolRadius * 0.8);
ctx.stroke();
} else if (obj.type === 'Oc') {
- ctx.setLineDash([2, 2]); ctx.beginPath(); ctx.arc(pt.x, pt.y, symbolRadius, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]);
+ ctx.setLineDash([scalePx(2), scalePx(2)]); ctx.beginPath(); ctx.arc(pt.x, pt.y, symbolRadius, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]);
} else if (obj.type === 'Gx') {
ctx.save(); ctx.translate(pt.x, pt.y); ctx.rotate(Math.PI / 5); ctx.beginPath(); ctx.ellipse(0, 0, symbolRadius, symbolRadius * 0.58, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.restore();
} else if (obj.type === 'Nb' || obj.type === 'Sc') {
@@ -5136,13 +5402,13 @@ function monthChartCometRaDec(comet, date) {
if (label) {
labelPlacer.drawText({
text: label,
- x: pt.x + symbolRadius + 5,
+ x: pt.x + symbolRadius + scalePx(5),
y: pt.y,
- font: '600 11px Rajdhani, sans-serif',
+ font: scaleFont(11),
color: 'rgba(210, 224, 255, 0.78)',
align: 'left',
baseline: 'middle',
- padding: 2,
+ padding: scalePx(2),
});
}
}
@@ -5162,7 +5428,7 @@ function monthChartCometRaDec(comet, date) {
? Math.min(Number(star.mag), Number(MONTH_CHART_CONFIG.starLimit) + 0.35)
: Number(star.mag);
const brightness = clamp01((Number(MONTH_CHART_CONFIG.starLimit) - effectiveMagnitude + 1.6) / magRange);
- const starRadius = (isConstellationStar ? 0.85 : 0.65) + Math.pow(brightness, 1.9) * 5.2;
+ const starRadius = ((isConstellationStar ? 0.85 : 0.65) + Math.pow(brightness, 1.9) * 5.2) * (exportRenderMode ? 2.0 : 1);
const glowRadius = starRadius * (effectiveMagnitude <= 1 ? 2.8 : 1.9);
const color = starColor(star);
@@ -5178,7 +5444,7 @@ function monthChartCometRaDec(comet, date) {
if (star.mag <= 0.8) {
ctx.strokeStyle = color + '44';
- ctx.lineWidth = 1;
+ ctx.lineWidth = scalePx(1);
ctx.beginPath();
ctx.moveTo(pt.x - starRadius * 1.35, pt.y); ctx.lineTo(pt.x + starRadius * 1.35, pt.y);
ctx.moveTo(pt.x, pt.y - starRadius * 1.35); ctx.lineTo(pt.x, pt.y + starRadius * 1.35);
@@ -5190,13 +5456,13 @@ function monthChartCometRaDec(comet, date) {
if (label) {
labelPlacer.drawText({
text: label,
- x: pt.x + starRadius + 5,
- y: pt.y - 1,
- font: '600 11px Rajdhani, sans-serif',
+ x: pt.x + starRadius + scalePx(5),
+ y: pt.y - scalePx(1),
+ font: scaleFont(11),
color: 'rgba(232, 239, 255, 0.82)',
align: 'left',
baseline: 'middle',
- padding: 2,
+ padding: scalePx(2),
});
}
}
@@ -5204,8 +5470,8 @@ function monthChartCometRaDec(comet, date) {
}
function draw() {
- const width = canvas.clientWidth;
- const height = canvas.clientHeight;
+ const width = viewportWidth;
+ const height = viewportHeight;
projectionLayout = computeProjectionLayout(width, height);
ctx.clearRect(0, 0, width, height);
labelPlacer.reset();
@@ -5221,6 +5487,58 @@ function monthChartCometRaDec(comet, date) {
statusEl.textContent = 'Südhimmel für ' + MONTH_CHART_CONFIG.localTimeLabel;
}
+ if (downloadButton) {
+ downloadButton.addEventListener('click', async function () {
+ const previous = {
+ width: canvas.width,
+ height: canvas.height,
+ styleWidth: canvas.style.width,
+ styleHeight: canvas.style.height,
+ viewportWidth,
+ viewportHeight,
+ renderScale,
+ };
+
+ try {
+ statusEl.textContent = 'PNG wird in 2000 px neu gerendert...';
+ downloadButton.disabled = true;
+
+ exportRenderMode = true;
+ viewportWidth = MONTH_CHART_EXPORT_WIDTH;
+ viewportHeight = Math.round(MONTH_CHART_EXPORT_WIDTH * 0.6);
+ renderScale = 1;
+ canvas.width = viewportWidth;
+ canvas.height = viewportHeight;
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ labelPlacer = createLabelPlacer(ctx);
+ draw();
+
+ const exportCanvas = await composeExportCanvas(canvas, 'bottom-left');
+ if (!exportCanvas) {
+ throw new Error('Export-Canvas konnte nicht erzeugt werden.');
+ }
+
+ triggerCanvasDownload(exportCanvas, monthChartExportFilename('sued'));
+ } catch (error) {
+ statusEl.textContent = 'PNG-Download konnte nicht erstellt werden.';
+ } finally {
+ exportRenderMode = false;
+ viewportWidth = previous.viewportWidth;
+ viewportHeight = previous.viewportHeight;
+ renderScale = previous.renderScale;
+ canvas.width = previous.width;
+ canvas.height = previous.height;
+ canvas.style.width = previous.styleWidth;
+ canvas.style.height = previous.styleHeight;
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.scale(renderScale, renderScale);
+ labelPlacer = createLabelPlacer(ctx);
+ draw();
+ downloadButton.disabled = false;
+ }
+ });
+ }
+
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
})();