<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Metody Statyczne</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f9f9f9;
color: #333;
padding: 20px;
}
</style>
</head>
<body>
<canvas id="gauge" width="320" height="160" style="margin: 0 auto;"></canvas>
<canvas id="gauge2" width="320" height="160" style="margin: 0 auto;"></canvas>
<script>
class SemicircleGauge {
/**
* @param {HTMLCanvasElement} canvas
* @param {{
* min?: number,
* max?: number,
* value?: number,
* segments?: number, // 1..4
* segmentColors?: string[],
* trackColor?: string,
* thickness?: number,
* labelFormatter?: (v:number)=>string,
* animationDuration?: number,
* fontFamily?: string,
* showTicks?: boolean,
* tickColor?: string,
* separatorWidth?: number,
* showRangeLabels?: boolean // <<< NOWA OPCJA
* }} opts
*/
constructor(canvas, opts = {}) {
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error("SemicircleGauge: canvas is required");
}
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.min = opts.min ?? 0;
this.max = opts.max ?? 100;
this._value = this._clamp(opts.value ?? this.min);
this.segments = Math.min(4, Math.max(1, opts.segments ?? 3));
this.segmentColors = (opts.segmentColors && opts.segmentColors.length)
? opts.segmentColors.slice(0, this.segments)
: this._defaultSegmentColors(this.segments);
this.trackColor = opts.trackColor ?? "#e6e6e9";
this.thickness = opts.thickness ?? 18;
this.fontFamily = opts.fontFamily ?? "system-ui, -apple-system, Segoe UI, Roboto, Arial";
this.labelFormatter = typeof opts.labelFormatter === "function"
? opts.labelFormatter
: (v) => `${Math.round(v)}`;
this.animationDuration = Math.max(0, opts.animationDuration ?? 600);
this.showTicks = opts.showTicks ?? false;
this.tickColor = opts.tickColor ?? "rgba(0,0,0,.25)";
this.separatorWidthDeg = opts.separatorWidth ?? 2.5;
this.showRangeLabels = opts.showRangeLabels ?? true; // <<< NOWA OPCJA
this.startAngle = Math.PI;
this.endAngle = 2 * Math.PI;
this._resizeForDPR();
this._onResize = () => {
this._resizeForDPR();
this.draw();
};
window.addEventListener("resize", this._onResize, { passive: true });
this.draw();
}
destroy() {
window.removeEventListener("resize", this._onResize);
cancelAnimationFrame(this._rafId);
}
setValue(newValue, { animate = true } = {}) {
const target = this._clamp(newValue);
if (!animate || this.animationDuration === 0) {
this._value = target;
this.draw();
return;
}
this._animateTo(target);
}
setSegments(count, colors) {
this.segments = Math.min(4, Math.max(1, count|0));
this.segmentColors = (colors && colors.length)
? colors.slice(0, this.segments)
: this._defaultSegmentColors(this.segments);
this.draw();
}
draw() {
const ctx = this.ctx;
const { width, height } = this.canvas;
ctx.clearRect(0, 0, width, height);
const cx = width / 2;
const cy = height;
const radius = Math.min(width / 2, height) - this._px(this.thickness/2 + 6);
this._drawSegmentsBackground(ctx, cx, cy, radius);
this._drawProgress(ctx, cx, cy, radius);
if (this.showTicks) {
this._drawTicks(ctx, cx, cy, radius);
}
this._drawLabel(ctx, cx, cy, radius);
if (this.showRangeLabels) {
this._drawRangeLabels(ctx, cx, cy, radius); // <<< NOWE
}
}
_drawSegmentsBackground(ctx, cx, cy, r) {
const segCount = this.segments;
const totalAngle = this.endAngle - this.startAngle;
const sepRad = this._deg2rad(this.separatorWidthDeg);
const usableAngle = totalAngle - sepRad * (segCount - 1);
const segAngle = usableAngle / segCount;
let a = this.startAngle;
for (let i = 0; i < segCount; i++) {
const start = a;
const end = start + segAngle;
ctx.beginPath();
ctx.arc(cx, cy, r, start, end);
ctx.lineWidth = this._px(this.thickness);
ctx.strokeStyle = this.trackColor;
ctx.lineCap = "round";
ctx.stroke();
a = end + (i < segCount - 1 ? sepRad : 0);
}
}
_drawProgress(ctx, cx, cy, r) {
const norm = (this._value - this.min) / (this.max - this.min || 1);
const clampNorm = Math.max(0, Math.min(1, norm));
const angle = this.startAngle + clampNorm * (this.endAngle - this.startAngle);
const segIndex = this._segmentIndexForNorm(clampNorm);
const color = this.segmentColors[segIndex];
ctx.beginPath();
ctx.arc(cx, cy, r, this.startAngle, angle);
ctx.lineWidth = this._px(this.thickness);
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.stroke();
}
_drawTicks(ctx, cx, cy, r) {
const steps = 5;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const a = this.startAngle + t * (this.endAngle - this.startAngle);
const inner = r - this._px(this.thickness/2) - this._px(6);
const outer = inner - this._px(i % steps === 0 ? 10 : 6);
const x1 = cx + inner * Math.cos(a);
const y1 = cy + inner * Math.sin(a);
const x2 = cx + outer * Math.cos(a);
const y2 = cy + outer * Math.sin(a);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = this._px(2);
ctx.strokeStyle = this.tickColor;
ctx.stroke();
}
}
_drawLabel(ctx, cx, cy, r) {
const label = this.labelFormatter(this._value);
ctx.save();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.shadowColor = "rgba(0,0,0,0.08)";
ctx.shadowBlur = this._px(6);
const fontSize = Math.max(14, Math.floor(this._cssPxToDevice(22)));
ctx.font = `600 ${fontSize}px ${this.fontFamily}`;
ctx.fillStyle = "#111";
const y = cy - r / 2.2;
ctx.fillText(label, cx, y);
ctx.shadowBlur = 0;
const subSize = Math.max(10, Math.floor(this._cssPxToDevice(12)));
ctx.font = `400 ${subSize}px ${this.fontFamily}`;
ctx.fillStyle = "rgba(0,0,0,.55)";
ctx.fillText(`${this.min} — ${this.max}`, cx, y + this._px(20));
ctx.restore();
}
_drawRangeLabels(ctx, cx, cy, r) {
ctx.save();
ctx.fillStyle = "#333";
ctx.font = `400 ${this._cssPxToDevice(12)}px ${this.fontFamily}`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const offset = r + this._px(this.thickness/2) + this._px(12);
// min (lewy koniec)
const xMin = cx + offset * Math.cos(this.startAngle);
const yMin = cy + offset * Math.sin(this.startAngle);
ctx.fillText(this.min, xMin, yMin);
// max (prawy koniec)
const xMax = cx + offset * Math.cos(this.endAngle);
const yMax = cy + offset * Math.sin(this.endAngle);
ctx.fillText(this.max, xMax, yMax);
ctx.restore();
}
_segmentIndexForNorm(norm) {
if (this.segments === 1) return 0;
const segSize = 1 / this.segments;
let idx = Math.floor(norm / segSize);
if (idx >= this.segments) idx = this.segments - 1;
return idx;
}
_defaultSegmentColors(n) {
const presets = {
1: ["#2ecc71"],
2: ["#e74c3c", "#2ecc71"],
3: ["#e74c3c", "#f1c40f", "#2ecc71"],
4: ["#e74c3c", "#e67e22", "#f1c40f", "#2ecc71"],
};
return presets[n] ?? presets[3];
}
_deg2rad(d) { return d * Math.PI / 180; }
_clamp(v) { return Math.min(this.max, Math.max(this.min, v)); }
_resizeForDPR() {
const dpr = window.devicePixelRatio || 1;
const cssWidth = this.canvas.clientWidth || this.canvas.width;
const cssHeight = this.canvas.clientHeight || this.canvas.height;
this.canvas.width = Math.round(cssWidth * dpr);
this.canvas.height = Math.round(cssHeight * dpr);
this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
_px(cssPx) { return cssPx; }
_cssPxToDevice(cssPx) { return cssPx; }
_animateTo(target) {
cancelAnimationFrame(this._rafId);
const start = performance.now();
const from = this._value;
const delta = target - from;
const dur = this.animationDuration;
const ease = (t) => t < 0.5 ? 2*t*t : -1 + (4 - 2*t)*t;
const step = (now) => {
const t = Math.min(1, (now - start) / dur);
this._value = from + delta * ease(t);
this.draw();
if (t < 1) {
this._rafId = requestAnimationFrame(step);
}
};
this._rafId = requestAnimationFrame(step);
}
}
// --- PRZYKŁAD UŻYCIA ---
const canvas = document.getElementById("gauge");
const canvas2 = document.getElementById("gauge2");
const gauge = new SemicircleGauge(canvas, {
min: -20,
max: 70,
value: 35,
segments: 3,
segmentColors: ["#e74c3c","#f1c40f","#2ecc71"],
thickness: 30,
animationDuration: 500,
showTicks: true,
showRangeLabels: true, // <<< OPCJA
labelFormatter: (v) => `${Math.round(v)}°C`
});
const gauge2 = new SemicircleGauge(canvas2, {
min: 0,
max: 100,
value: 35,
segments: 3,
segmentColors: ["#e74c3c","#f1c40f","#2ecc71"],
thickness: 30,
animationDuration: 500,
showTicks: true,
showRangeLabels: true, // <<< OPCJA
labelFormatter: (v) => `${Math.round(v)}%`
});
// Aktualizacja wartości (bez przeładowania strony)
// setInterval(() => gauge.setValue(Math.random() * 70 - 20), 6000);
// setInterval(() => gauge2.setValue(Math.random() * 100), 6000);
// --- Funkcja do pobrania aktualnej pogody ---
async function getActualWeather() {
try {
const response = await fetch("https://api.open-meteo.com/v1/forecast?latitude=52.237049&longitude=21.017532&hourly=rain,weather_code,wind_speed_10m,surface_pressure,apparent_temperature,relative_humidity_2m&forecast_days=1");
const data = await response.json();
console.log('Data:', data);
// Pobieram biezaca date i godzine
const now = new Date();
// Sprawdzam ktora jest godzina
let currentHour = now.getHours();
// Sprawdzam, ile jest minut, jesli powyzej 30 to dodaje 1 godzine
const minutes = now.getMinutes();
if (minutes > 30) {
currentHour++;
}
//Sprawdzam, czy danych z api sa potrzebne informacje, jesli nie ma to pojawia sie error i zatrzymujemy dzialanie programu
if (!data.hourly.apparent_temperature || //brak temperatury
!data.hourly.relative_humidity_2m )//brak wilgotnosci
{
console.error("Brak danych pogodowych dla godziny:", currentHour);
return;
}
// Pobieram temperature i wilgotnosc
const temperature = data.hourly.apparent_temperature[currentHour];
const humidity = data.hourly.relative_humidity_2m[currentHour];
//Walidacja temperatury i wilgotnosci
if (temperature == null || isNaN(temperature)) {
console.error("Nieprawidlowa temperatura:", temperature);
return;
}
if (humidity == null || isNaN(humidity)) {
console.error("Nieprawidlowa wilgotność:", humidity);
return;
}
// jesli przejdzie walidacje to wyswuetli poprawne dane w konsoli
console.log(`Godzina: ${currentHour}:00 | Temperatura: ${temperature} °C | Wilgotność: ${humidity} %`);
// przypisanie wartosci temperatury i wilgotnosci do wskaznikow
gauge.setValue(temperature);
gauge2.setValue(humidity);
} catch (error) {
console.error("Błąd pobierania danych pogodowych:", error);
}
}
getActualWeather();
</script>
</body>
</html>