Value.NONE not nullable + offset change
This commit is contained in:
parent
49a87bb154
commit
0dff76f598
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
21
src/main/angular/public/favicon.svg
Normal file
21
src/main/angular/public/favicon.svg
Normal 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 |
@ -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>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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%">
|
||||
|
||||
<div (click)="offset += 1">←</div>
|
||||
|
||||
<div (click)="offset = Math.max(1, offset -1)">→</div>
|
||||
|
||||
<div style="flex: 1">{{ offsetDayTitle() }}</div>
|
||||
</div>
|
||||
</ng-content>
|
||||
</app-series-history>
|
||||
|
||||
<div class="Section">
|
||||
<div class="SectionHeading">
|
||||
|
||||
@ -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`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import {Value} from './Value';
|
||||
|
||||
export class Series {
|
||||
|
||||
readonly value: Value | null = null;
|
||||
readonly value: Value;
|
||||
|
||||
constructor(
|
||||
readonly id: number,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user