Value.NONE not nullable + offset change

This commit is contained in:
Patrick Haßel 2025-10-30 16:33:44 +01:00
parent 49a87bb154
commit 0dff76f598
11 changed files with 112 additions and 45 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--suppress HtmlDeprecatedAttribute -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<circle fill="#4a97b8" cx="256" cy="256" r="256"/>
<path fill="#57b153" d="M412,280l-60-64l-60,127.252L172,280L52,400v10.62C98.752,472.204,172.72,512,256,512c141.384,0,256-114.616,256-256c0-44.752-11.512-86.8-31.696-123.404L412,280z"/>
<g stroke="black" stroke-width="10px">
<path d="M52,408c-2.048,0-4.092-0.78-5.656-2.344c-3.124-3.124-3.124-8.188,0-11.312l120-120c2.48-2.48,6.28-3.064,9.388-1.42l112.6,59.36l56.432-119.688c1.124-2.4,3.368-4.08,5.984-4.492c2.624-0.424,5.268,0.496,7.084,2.428l51.892,55.356L480.74,112.64c1.86-4.012,6.612-5.748,10.62-3.896c4.008,1.856,5.752,6.612,3.896,10.62l-76,164c-1.116,2.416-3.36,4.112-5.984,4.54c-2.616,0.408-5.288-0.492-7.104-2.424l-51.948-55.424L299.24,346.66c-0.924,1.976-2.628,3.484-4.7,4.176c-2.064,0.696-4.332,0.508-6.264-0.508l-114.78-60.504L57.664,405.652C56.096,407.22,54.048,408,52,408z"/>
</g>
<g fill="#F5F5F5">
<path d="M172,264c8.84,0,16,7.16,16,16s-7.16,16-16,16s-16-7.16-16-16S163.16,264,172,264 M172,256c-13.236,0-24,10.764-24,24s10.764,24,24,24s24-10.764,24-24S185.236,256,172,256L172,256z"/>
<path d="M46.964,388c8.84,0,16,7.16,16,16s-7.16,16-16,16s-16-7.16-16-16S38.124,388,46.964,388 M46.964,380c-13.236,0-24,10.764-24,24s10.764,24,24,24s24-10.764,24-24S60.2,380,46.964,380L46.964,380z"/>
<path d="M486.96,100c8.844,0,16,7.16,16,16s-7.156,16-16,16c-8.836,0-16-7.16-16-16S478.124,100,486.96,100M486.96,92c-13.236,0-24,10.764-24,24s10.764,24,24,24c13.24,0,24-10.764,24-24S500.2,92,486.96,92L486.96,92z"/>
<path d="M352,200c8.84,0,16,7.16,16,16s-7.16,16-16,16s-16-7.16-16-16S343.16,200,352,200 M352,192c-13.236,0-24,10.764-24,24s10.764,24,24,24s24-10.764,24-24S365.236,192,352,192L352,192z"/>
</g>
<g fill="black">
<circle cx="172" cy="280" r="16"/>
<circle cx="46.964" cy="404" r="16"/>
<circle cx="486.96" cy="116" r="16"/>
<circle cx="352" cy="216" r="16"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -2,6 +2,7 @@
<div class="SectionHeading">
<div class="SectionHeadingText">
{{ heading }}
<ng-content #SeriesHistoryHeading></ng-content>
</div>
</div>
<div class="SectionBody">
@ -11,7 +12,7 @@
Bezug
</div>
<div class="SectionBody purchase">
{{ purchase?.toValueString(true, interval ? null : now) }}
{{ purchase.toValueString(true, interval ? null : now) }}
</div>
</div>
@ -20,7 +21,7 @@
Solar
</div>
<div class="SectionBody produce">
{{ produce?.toValueString(true, interval ? null : now) }}
{{ produce.toValueString(true, interval ? null : now) }}
</div>
</div>
@ -29,7 +30,7 @@
Verbrauch
</div>
<div class="SectionBody consume">
{{ consume?.toValueString(true, interval ? null : now) }}
{{ consume.toValueString(true, interval ? null : now) }}
</div>
</div>
@ -38,7 +39,7 @@
Einspeisung
</div>
<div class="SectionBody deliver">
{{ deliver?.toValueString(true, interval ? null : now) }}
{{ deliver.toValueString(true, interval ? null : now) }}
</div>
</div>

View File

@ -20,19 +20,28 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy {
private readonly subs: Subscription[] = [];
protected purchase: Value | null = null;
protected purchase: Value = Value.NONE;
protected deliver: Value | null = null;
protected deliver: Value = Value.NONE;
protected produce: Value | null = null;
protected produce: Value = Value.NONE;
protected consume: Value | null = null;
protected consume: Value = Value.NONE;
@Input()
heading!: string;
heading: string = "";
private _o_: number = 0;
@Input()
offset: number = 0;
set offset(value: number) {
this._o_ = value;
this.ngAfterViewInit();
}
get offset(): number {
return this._o_;
}
@Input()
interval: Interval | null = null;
@ -85,11 +94,11 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy {
}
};
private history(fresh: Series | null | undefined, series: Series | null | undefined, next: Next<Value | null>) {
const n = (value: Value | null) => {
private history(fresh: Series | null | undefined, series: Series | null | undefined, next: Next<Value>): void {
const callNextAndUpdateConsume = (value: Value) => {
next(value);
this.consume = this.purchase?.plus(this.produce)?.minus(this.deliver) || null;
}
this.consume = this.purchase.plus(this.produce).minus(this.deliver);
};
if (fresh !== null && fresh !== undefined) {
if (fresh.id !== series?.id) {
return;
@ -97,18 +106,14 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy {
series = fresh;
}
if (!series) {
n(null);
callNextAndUpdateConsume(Value.NONE);
return
}
if (this.interval) {
this.pointService.relative([series], this.interval, this.offset, 1, response => n(Value.ofPoint(response, 0, 0, 1)));
this.pointService.relative([series], this.interval, this.offset, 1, response => callNextAndUpdateConsume(Value.ofPoint(response, 0, 0, 1)));
} else {
n(series.value);
callNextAndUpdateConsume(series.value);
}
}
protected nullOrZero(value: Value | null | undefined): boolean {
return value === null || value === undefined || value.value === 0;
}
}

View File

@ -4,7 +4,18 @@
<app-series-history [now]="now" [location]="location" [interval]="Interval.DAY" heading="Heute"></app-series-history>
<app-series-history [now]="now" [location]="location" [interval]="Interval.DAY" [offset]="1" heading="Gestern"></app-series-history>
<app-series-history [now]="now" [location]="location" [interval]="Interval.DAY" [offset]="offset">
<ng-content #SeriesHistoryHeading>
<div style="display: flex; width: 100%">
&nbsp;
<div (click)="offset += 1">&larr;</div>
&nbsp;
<div (click)="offset = Math.max(1, offset -1)">&rarr;</div>
&nbsp;
<div style="flex: 1">{{ offsetDayTitle() }}</div>
</div>
</ng-content>
</app-series-history>
<div class="Section">
<div class="SectionHeading">

View File

@ -1,4 +1,4 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core';
import {LocationService} from '../location-service';
import {ActivatedRoute} from '@angular/router';
import {Location} from '../Location';
@ -12,6 +12,7 @@ import {Subscription, timer} from 'rxjs';
import {SeriesHistory} from './history/series-history';
import {Interval} from '../../series/Interval';
import {MenuService} from '../../menu-service';
import {DatePipe} from '@angular/common';
function yesterday(now: any) {
const yesterday = new Date(now.getTime());
@ -34,6 +35,8 @@ export class LocationDetail implements OnInit, OnDestroy {
protected readonly Interval = Interval;
protected readonly Math = Math;
protected location: Location | null = null;
private readonly subs: Subscription [] = [];
@ -44,13 +47,18 @@ export class LocationDetail implements OnInit, OnDestroy {
protected yesterday: Date = yesterday(this.now);
protected offset: number = 1;
private readonly datePipe: DatePipe;
constructor(
readonly locationService: LocationService,
readonly seriesService: SeriesService,
readonly activatedRoute: ActivatedRoute,
readonly menuService: MenuService,
@Inject(LOCALE_ID) readonly locale: string,
) {
//
this.datePipe = new DatePipe(locale);
}
ngOnInit(): void {
@ -96,4 +104,19 @@ export class LocationDetail implements OnInit, OnDestroy {
return this.series.filter(series => series.type === SeriesType.VARYING && series.unit === 'W');
};
protected offsetDayTitle(): string {
if (this.offset === 1) {
return 'Gestern';
} else if (this.offset === 2) {
return 'Vorgestern';
} else {
if (this.offset < 7) {
const d = new Date(this.now);
d.setDate(d.getDate() - this.offset);
return this.datePipe.transform(d, 'EEEE') || '';
}
return `Vor ${this.offset} Tagen`;
}
}
}

View File

@ -5,7 +5,7 @@ import {Value} from './Value';
export class Series {
readonly value: Value | null = null;
readonly value: Value;
constructor(
readonly id: number,

View File

@ -4,8 +4,10 @@ import {Series} from './Series';
export class Value {
constructor(
readonly value: number,
static readonly NONE: Value = new Value(null, 0, 0, "", new Date());
private constructor(
readonly value: number | null,
readonly precision: number,
readonly seconds: number,
readonly unit: string,
@ -15,8 +17,8 @@ export class Value {
}
toValueString(zeroToDash: boolean, now_ageCheckToDash: Date | null): string {
if (this.value === null || this.value === undefined) {
return "[???]";
if (this.value === null) {
return "-";
}
if (this.value === 0) {
return zeroToDash ? "-" : `0 ${this.unit}`;
@ -39,20 +41,20 @@ export class Value {
return formatNumber(this.value, "de-DE", `0.${-rest}-${-rest}`) + ' ' + this.unit;
}
plus(other: Value | null | undefined): Value | null {
plus(other: Value): Value {
return this.operateSameUnit("plus", other, (a, b) => a + b);
}
minus(other: Value | null | undefined): Value | null {
minus(other: Value): Value {
return this.operateSameUnit("minus", other, (a, b) => a - b);
}
operateSameUnit(operationName: string, other: Value | null | undefined, operation: (a: number, b: number) => number): Value | null {
if (!other) {
return null;
operateSameUnit(name: string, other: Value, operation: (a: number, b: number) => number): Value {
if (this.value === null || other.value === null) {
return Value.NONE;
}
if (this.unit !== other.unit) {
throw new Error(`Operation '${operationName} needs units to be the same: this=${this}, other=${other}`);
throw new Error(`Operation '${name} needs units to be the same: this=${this}, other=${other}`);
}
const decimals = Math.max(this.precision, other.precision);
const seconds = Math.max(this.seconds, other.seconds);
@ -60,25 +62,27 @@ export class Value {
return new Value(operation(this.value, other.value), decimals, seconds, this.unit, date);
}
static of(series: Series, value: number | null | undefined, date: Date | null | undefined): Value | null {
if (value === null || value === undefined) {
return null;
static of(series: Series, value: number | null | undefined, date: Date | null | undefined): Value {
value = value === undefined ? null : value;
date = date === undefined ? null : date;
if (value === null) {
return this.NONE;
}
if (date === null || date === undefined) {
if (date === null) {
throw new Error("When 'value' is set, 'last' must be set too, but isn't!")
}
return new Value(value, series.precision, series.seconds, series.unit, date);
}
static ofPoint(response: PointResponse, seriesIndex: number, pointIndex: number, valueIndex: number): Value | null {
static ofPoint(response: PointResponse, seriesIndex: number, pointIndex: number, valueIndex: number): Value {
const series = response.series[seriesIndex];
if (!series) {
return null;
return this.NONE;
}
const point = series.points[pointIndex];
if (!point) {
return null;
return this.NONE;
}
const date = new Date(point[0] * 1000);

View File

@ -8,7 +8,7 @@
<option [ngValue]="null">-</option>
@for (series of series; track series.id) {
<option [ngValue]="series.id">
{{ series.name }}: {{ series.value?.toValueString(false, now) }}
{{ series.name }}: {{ series.value.toValueString(false, now) }}
</option>
}
</select>

View File

@ -2,11 +2,11 @@
<html lang="de">
<head>
<meta charset="utf-8">
<title>Angular</title>
<title>Data2025</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--suppress HtmlUnknownTarget -->
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="icon" type="image/svg" href="favicon.svg">
</head>
<body>
<app-root></app-root>

View File

@ -4,6 +4,7 @@ body {
margin: 0;
font-family: sans-serif;
font-size: 4vw;
user-select: none;
}
div {
@ -46,6 +47,7 @@ div {
> .SectionHeading {
color: dimgray;
> .SectionHeadingText {
font-size: 70%;
font-style: italic;