WeatherDiagram: Temperature, Weekday-names, Legend

This commit is contained in:
Patrick Haßel 2025-03-25 09:33:25 +01:00
parent d81034e6c4
commit 5358f1b9f6
7 changed files with 166 additions and 19 deletions

View File

@ -4,7 +4,7 @@ import {map, Subscription} from 'rxjs';
import {StompService} from '@stomp/ng2-stompjs'; import {StompService} from '@stomp/ng2-stompjs';
import {FromJson, Next} from './types'; import {FromJson, Next} from './types';
const DEV_TO_PROD = true; const DEV_TO_PROD = false;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'

View File

@ -53,18 +53,44 @@ export class Value {
return new Value(-this.value, this.unit, this.decimals, this.locale); return new Value(-this.value, this.unit, this.decimals, this.locale);
} }
plus(other: Value | undefined): Value | undefined { plus(other: Value | number | undefined | null): Value | undefined {
if (!other) { const v = this.extractValue(other);
return undefined; return v === undefined ? undefined : new Value(this.value + v, this.unit, this.decimals, this.locale);
}
return new Value(this.value + other.value, this.unit, this.decimals, this.locale);
} }
minus(other: Value | undefined): Value | undefined { minus(other: Value | number | undefined | null): Value | undefined {
if (!other) { const v = this.extractValue(other);
return v === undefined ? undefined : new Value(this.value - v, this.unit, this.decimals, this.locale);
}
gte(other: Value | number | undefined | null): boolean | undefined {
const v = this.extractValue(other);
return v === undefined ? undefined : this.value >= v;
}
gt(other: Value | number | undefined | null): boolean | undefined {
const v = this.extractValue(other);
return v === undefined ? undefined : this.value > v;
}
lte(other: Value | number | undefined | null): boolean | undefined {
const v = this.extractValue(other);
return v === undefined ? undefined : this.value <= v;
}
lt(other: Value | number | undefined | null): boolean | undefined {
const v = this.extractValue(other);
return v === undefined ? undefined : this.value < v;
}
extractValue(other: Value | number | undefined | null): number | undefined {
if (other === undefined || other === null) {
return undefined; return undefined;
} }
return new Value(this.value - other.value, this.unit, this.decimals, this.locale); if (other instanceof Value && other.unit !== this.unit) {
throw new Error(`Unit mismatch: this=${JSON.stringify(this)}, other=${JSON.stringify(other)}`);
}
return typeof other === "number" ? other : other.value;
} }
notNegative(): Value { notNegative(): Value {

View File

@ -8,6 +8,7 @@ export class WeatherHour {
readonly clouds: Value, readonly clouds: Value,
readonly irradiation: Value, readonly irradiation: Value,
readonly precipitation: Value, readonly precipitation: Value,
readonly temperature: Value,
) { ) {
// //
} }
@ -18,6 +19,7 @@ export class WeatherHour {
Value.fromJson2(json['clouds'], locale), Value.fromJson2(json['clouds'], locale),
Value.fromJson2(json['irradiation'], locale), Value.fromJson2(json['irradiation'], locale),
Value.fromJson2(json['precipitation'], locale), Value.fromJson2(json['precipitation'], locale),
Value.fromJson2(json['temperature'], locale),
); );
} }

View File

@ -4,9 +4,22 @@
</div> </div>
<div class="day"> <div class="day">
<div class="hour" *ngFor="let hour of hours"> <div class="hour" *ngFor="let hour of hours">
<div class="bar clouds" [style.height]="clouds(hour)"></div> <div class="bar weekdayHolder" *ngIf="hour.date.getHours() === 8">
<div class="bar precipitation" [style.height]="precipitation(hour)"></div> {{ dateFormat(hour.date | date:'E') }}
<div class="bar irradiation" [style.height]="irradiation(hour)"></div>
</div> </div>
<div class="bar clouds" [style.height]="clouds(hour)"></div>
<div class="bar irradiation" [style.height]="irradiation(hour)"></div>
<div class="bar precipitation" [style.height]="precipitation(hour)"></div>
<div class="bar temperature" [style.height]="temperature(hour)" [ngClass]="temperatureClasses(hour)"></div>
</div>
</div>
<div class="legend">
<span class="line temperatureGTE30">&ge;30°C</span>
<span class="line temperatureGTE20">&ge;20°C</span>
<span class="line temperatureGTE10">&ge;10°C</span>
<span class="line temperatureGT0">&gt;0°C</span>
<span class="line temperatureNegative">&le;0°C</span>
<span class="line">&nbsp;</span>
<span class="line">Niederschlag 100% = {{ PRECIPITATION_MAX_MM }}mm</span>
</div> </div>
</div> </div>

View File

@ -8,6 +8,7 @@
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
width: 100%; // any width will do (flex cares about real with) width: 100%; // any width will do (flex cares about real with)
overflow: visible;
.bar { .bar {
position: absolute; position: absolute;
@ -25,8 +26,73 @@
.precipitation { .precipitation {
background-color: blue; background-color: blue;
opacity: 0.75;
}
.temperature {
border-top: 0.06em solid black;
opacity: 0.75;
}
.temperatureGTE30 {
border-top-color: red;
}
.temperatureGTE20 {
border-top-color: orange;
}
.temperatureGTE10 {
border-top-color: yellow;
}
.temperatureGT0 {
border-top-color: blue;
}
.temperatureNegative {
border-top-color: white;
}
.weekdayHolder {
overflow: visible;
opacity: 1;
height: 100%;
} }
} }
} }
.legend {
display: flex;
width: 100%;
flex-direction: row;
justify-content: center;
.line {
padding: 0 0.25em;
font-size: 50%;
}
.temperatureGTE30 {
color: red;
}
.temperatureGTE20 {
color: orange;
}
.temperatureGTE10 {
color: yellow;
}
.temperatureGT0 {
color: blue;
}
.temperatureNegative {
color: white;
}
}

View File

@ -1,5 +1,5 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {NgForOf} from '@angular/common'; import {DatePipe, NgClass, NgForOf, NgIf} from '@angular/common';
import {WeatherHour} from './WeatherHour'; import {WeatherHour} from './WeatherHour';
import {WeatherService} from './weather.service'; import {WeatherService} from './weather.service';
import {WeatherDay} from './WeatherDay'; import {WeatherDay} from './WeatherDay';
@ -11,13 +11,18 @@ const DAY_COUNT = 7;
@Component({ @Component({
selector: 'app-weather-diagram', selector: 'app-weather-diagram',
imports: [ imports: [
NgForOf NgForOf,
NgIf,
DatePipe,
NgClass
], ],
templateUrl: './weather-diagram.component.html', templateUrl: './weather-diagram.component.html',
styleUrl: './weather-diagram.component.less' styleUrl: './weather-diagram.component.less'
}) })
export class WeatherDiagramComponent implements OnInit { export class WeatherDiagramComponent implements OnInit {
protected readonly PRECIPITATION_MAX_MM = 15;
protected days: WeatherDay[] = []; protected days: WeatherDay[] = [];
protected hours: WeatherHour[] = []; protected hours: WeatherHour[] = [];
@ -44,7 +49,11 @@ export class WeatherDiagramComponent implements OnInit {
} }
precipitation(hour: WeatherHour) { precipitation(hour: WeatherHour) {
return (hour.precipitation.percent(15)?.value || 0) + '%'; return (hour.precipitation.percent(this.PRECIPITATION_MAX_MM)?.value || 0) + '%';
}
temperature(hour: WeatherHour) {
return (hour.temperature.plus(10)?.percent(50)?.value || 0) + '%';
} }
private updateHours() { private updateHours() {
@ -74,4 +83,26 @@ export class WeatherDiagramComponent implements OnInit {
} }
} }
dateFormat(date: string | null) {
if (!date) {
return "";
}
return date.substring(0, 2);
}
temperatureClasses(hour: WeatherHour): {} {
const temperatureGTE30 = hour.temperature.gte(30);
const temperatureGTE20 = hour.temperature.gte(20);
const temperatureGTE10 = hour.temperature.gte(10);
const temperatureGT0 = hour.temperature.gt(0);
const temperatureNegative = hour.temperature.lte(0);
return {
"temperatureGTE30": temperatureGTE30,
"temperatureGTE20": temperatureGTE20 && !temperatureGTE30,
"temperatureGTE10": temperatureGTE10 && !temperatureGTE20,
"temperatureGT0": temperatureGT0 && !temperatureGTE10,
"temperatureNegative": temperatureNegative
};
}
} }

View File

@ -1,13 +1,18 @@
package de.ph87.data.weather; package de.ph87.data.weather;
import de.ph87.data.value.Unit;
import de.ph87.data.value.Value; import de.ph87.data.value.Value;
import de.ph87.data.value.*; import lombok.Data;
import lombok.*; import lombok.NonNull;
import lombok.ToString;
import java.io.*; import java.io.IOException;
import java.time.*; import java.time.LocalDate;
import java.util.*; import java.time.LocalTime;
import java.util.stream.*; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Data @Data
@ToString(includeFieldNames = false) @ToString(includeFieldNames = false)
@ -67,11 +72,15 @@ public class WeatherDay {
@NonNull @NonNull
public final Value precipitation; public final Value precipitation;
@NonNull
public final Value temperature;
public Hour(@NonNull final BrightSkyDto.Weather dto) { public Hour(@NonNull final BrightSkyDto.Weather dto) {
date = dto.getTimestamp(); date = dto.getTimestamp();
clouds = new Value(dto.getCloud_cover(), Unit.CLOUD_COVER_PERCENT); clouds = new Value(dto.getCloud_cover(), Unit.CLOUD_COVER_PERCENT);
irradiation = new Value(dto.getSolar() * 1000, Unit.IRRADIATION_WH_M2); irradiation = new Value(dto.getSolar() * 1000, Unit.IRRADIATION_WH_M2);
precipitation = new Value(dto.getPrecipitation(), Unit.PRECIPITATION_MM); precipitation = new Value(dto.getPrecipitation(), Unit.PRECIPITATION_MM);
temperature = new Value((dto.getTemperature()), Unit.TEMPERATURE_C);
} }
} }