diff --git a/src/main/angular/config.less b/src/main/angular/config.less new file mode 100644 index 0000000..30e7e29 --- /dev/null +++ b/src/main/angular/config.less @@ -0,0 +1,27 @@ +/* bright theme */ + +@foreground: gray; +@background: white; + +@consumption: orange; +@purchase: orangered; +@production: dodgerblue; +@self: forestgreen; +@delivery: magenta; + +@consumptionBack: @consumption; +@purchaseBack: @purchase; +@productionBack: @production; +@selfBack: @self; +@deliveryBack: @delivery; + +/* dark theme */ + +@foreground: gray; +@background: #11171b; + +@consumptionBack: #856938; +@purchaseBack: #71361d; +@productionBack: #2d4255; +@selfBack: #2b4e2b; +@deliveryBack: #753475; diff --git a/src/main/angular/src/app/app.component.html b/src/main/angular/src/app/app.component.html index c1e9121..db697e7 100644 --- a/src/main/angular/src/app/app.component.html +++ b/src/main/angular/src/app/app.component.html @@ -6,7 +6,7 @@
-
Verbrauch
+
Bedarf
 
{{ powerConsumption?.formatted }}
@@ -15,13 +15,13 @@
{{ powerPurchasePercent?.formatted }}
{{ powerPurchase?.formatted }}
-
+
Produktion
{{ powerProducedPercent?.formatted }}
-
{{ powerProduced?.formatted }}
+
{{ powerProduction?.formatted }}
-
Eigenverbrauch
+
Eigenbedarf
{{ powerSelfPercent?.formatted }}
{{ powerSelf?.formatted }}
@@ -32,14 +32,14 @@
+ +
Energie - Live - Archiv
@@ -56,7 +56,7 @@
-
Verbrauch
+
Bedarf
 
{{ aggregations.energyConsumed?.formatted }}
@@ -71,17 +71,19 @@
{{ aggregations.energyProduced?.delta?.formatted }}
-
Eigenverbrauch
+
Eigenbedarf
{{ aggregations.energySelfPercent?.formatted }}
{{ aggregations.energySelf?.formatted }}
-
Eingespeist
+
Einspeisung
{{ aggregations.energyDeliveredPercent?.formatted }}
{{ aggregations.energyDelivered?.delta?.formatted }}
+ +
diff --git a/src/main/angular/src/app/app.component.less b/src/main/angular/src/app/app.component.less index adf0d31..5df8cd7 100644 --- a/src/main/angular/src/app/app.component.less +++ b/src/main/angular/src/app/app.component.less @@ -1,5 +1,7 @@ +@import "../../config.less"; + .section { - margin-bottom: 1em; + margin-bottom: 2em; .back { float: left; @@ -12,7 +14,6 @@ .title { clear: left; font-weight: bold; - font-size: 120%; text-align: center; } @@ -28,7 +29,6 @@ .name { float: left; - font-weight: bold; } .value { @@ -39,7 +39,7 @@ float: right; padding-top: 0.5em; font-size: 60%; - width: 3.5em; + width: 4.5em; text-align: right; } @@ -62,25 +62,25 @@ } .consumption { - color: orange; + color: @consumption; } .purchase { - color: orangered; + color: @purchase; } .production { - color: dodgerblue; + color: @production; } .self { - color: forestgreen; + color: @self; } .delivery { - color: magenta; + color: @delivery; } .zero { - filter: opacity(30%); + filter: opacity(20%); } diff --git a/src/main/angular/src/app/app.component.ts b/src/main/angular/src/app/app.component.ts index 553dbe7..5dcd749 100644 --- a/src/main/angular/src/app/app.component.ts +++ b/src/main/angular/src/app/app.component.ts @@ -5,11 +5,11 @@ import {AggregationWrapperDto} from './series/AggregationWrapperDto'; import {SeriesService} from './series/series.service'; import {Subscription} from 'rxjs'; import {Value} from './value/Value'; -import {NgIf} from '@angular/common'; +import {PercentBarComponent} from './percent-bar/percent-bar.component'; @Component({ selector: 'app-root', - imports: [RouterOutlet, NgIf], + imports: [RouterOutlet, PercentBarComponent], templateUrl: './app.component.html', styleUrl: './app.component.less' }) @@ -25,7 +25,7 @@ export class AppComponent implements OnInit, OnDestroy { private readonly subs: Subscription[] = []; - get powerProduced(): Value | undefined { + get powerProduction(): Value | undefined { return this.seriesService.powerProduced.series?.lastValue; } @@ -42,19 +42,19 @@ export class AppComponent implements OnInit, OnDestroy { } get powerSelf(): Value | undefined { - return this.powerProduced?.minus(this.powerDelivery); + return this.powerProduction?.minus(this.powerDelivery); } get powerConsumption(): Value | undefined { - return this.powerBalance?.plus(this.powerProduced); + return this.powerPurchase?.plus(this.powerProduction); } get powerProducedPercent(): Value | undefined { - return this.powerProduced?.percent(this.powerConsumption); + return this.powerProduction?.percent(this.powerConsumption); } get powerSelfPercent(): Value | undefined { - return this.powerSelf?.percent(this.powerProduced); + return this.powerSelf?.percent(this.powerProduction); } get powerPurchasePercent(): Value | undefined { @@ -62,7 +62,7 @@ export class AppComponent implements OnInit, OnDestroy { } get powerDeliveryPercent(): Value | undefined { - return this.powerDelivery?.percent(this.powerProduced); + return this.powerDelivery?.percent(this.powerProduction); } constructor( diff --git a/src/main/angular/src/app/percent-bar/percent-bar.component.html b/src/main/angular/src/app/percent-bar/percent-bar.component.html new file mode 100644 index 0000000..216986a --- /dev/null +++ b/src/main/angular/src/app/percent-bar/percent-bar.component.html @@ -0,0 +1,20 @@ +
+
+
+ {{ _purchase?.formatted }}
+ {{ purchasePercent?.formatted }} +
+
+
+
+ {{ _self?.formatted }}
+ {{ selfPercent?.formatted }} +
+
+
+
+ {{ _delivery?.formatted }}
+ {{ deliveryPercent?.formatted }} +
+
+
diff --git a/src/main/angular/src/app/percent-bar/percent-bar.component.less b/src/main/angular/src/app/percent-bar/percent-bar.component.less new file mode 100644 index 0000000..3733aee --- /dev/null +++ b/src/main/angular/src/app/percent-bar/percent-bar.component.less @@ -0,0 +1,30 @@ +@import "../../../config.less"; + +.bar { + position: relative; + color: white; + + .part { + float: left; + white-space: nowrap; + font-size: 40%; + + .text { + padding-left: 0.25em; + } + border-radius: 0.5em; + } + + .self { + background-color: @selfBack; + } + + .purchase { + background-color: @purchaseBack; + } + + .delivery { + background-color: @deliveryBack; + } + +} diff --git a/src/main/angular/src/app/percent-bar/percent-bar.component.ts b/src/main/angular/src/app/percent-bar/percent-bar.component.ts new file mode 100644 index 0000000..4897f38 --- /dev/null +++ b/src/main/angular/src/app/percent-bar/percent-bar.component.ts @@ -0,0 +1,78 @@ +import {Component, Input} from '@angular/core'; +import {Value} from '../value/Value'; +import {NgIf} from '@angular/common'; + +@Component({ + selector: 'app-percent-bar', + imports: [ + NgIf + ], + templateUrl: './percent-bar.component.html', + styleUrl: './percent-bar.component.less' +}) +export class PercentBarComponent { + + protected _produktion: Value | undefined; + + @Input() + set produktion(produktion: Value | undefined) { + this._produktion = produktion; + this.update(); + } + + protected _self: Value | undefined; + + @Input() + set self(self: Value | undefined) { + this._self = self; + this.update(); + } + + protected _purchase: Value | undefined; + + @Input() + set purchase(purchase: Value | undefined) { + this._purchase = purchase; + this.update(); + } + + protected _delivery: Value | undefined; + + @Input() + set delivery(delivery: Value | undefined) { + this._delivery = delivery; + this.update(); + } + + @Input() + percent: boolean = false; + + protected consumption: Value | undefined; + + protected barSum: Value | undefined; + + protected selfPercent: Value | undefined; + + protected purchasePercent: Value | undefined; + + protected deliveryPercent: Value | undefined; + + protected barSelfPercent: Value | undefined; + + protected barPurchasePercent: Value | undefined; + + protected barDeliveryPercent: Value | undefined; + + private update() { + this.consumption = this._self?.plus(this._purchase); + this.selfPercent = this._self?.percent(this.consumption); + this.purchasePercent = this._purchase?.percent(this.consumption); + this.deliveryPercent = this._delivery?.percent(this._produktion); + + this.barSum = this.consumption?.plus(this._delivery); + this.barSelfPercent = this._self?.percent(this.barSum); + this.barPurchasePercent = this._purchase?.percent(this.barSum); + this.barDeliveryPercent = this._delivery?.percent(this.barSum); + } + +} diff --git a/src/main/angular/src/app/series/AggregationWrapperDto.ts b/src/main/angular/src/app/series/AggregationWrapperDto.ts index 7f3e6ac..d51b119 100644 --- a/src/main/angular/src/app/series/AggregationWrapperDto.ts +++ b/src/main/angular/src/app/series/AggregationWrapperDto.ts @@ -48,11 +48,11 @@ export class AggregationWrapperDto { this.energySelf = this.energyProduced?.delta.minus(this.energyDelivered?.delta); } - static fromJson(json: any): AggregationWrapperDto { + static fromJson(json: any, locale: string): AggregationWrapperDto { return new AggregationWrapperDto( json['alignment'] as Alignment, new Date(json['date']), - (json['aggregations'] as any[]).map(a => a.hasOwnProperty('delta') ? MeterAggregate.fromJson(a) : VaryingAggregate.fromJson(a)), + (json['aggregations'] as any[]).map(a => a.hasOwnProperty('delta') ? MeterAggregate.fromJson(a, locale) : VaryingAggregate.fromJson(a, locale)), ); } diff --git a/src/main/angular/src/app/series/Series.ts b/src/main/angular/src/app/series/Series.ts index 6f9937d..0ca700e 100644 --- a/src/main/angular/src/app/series/Series.ts +++ b/src/main/angular/src/app/series/Series.ts @@ -15,7 +15,7 @@ export class Series { // } - static fromJson(json: any): Series { + static fromJson(json: any, locale: string): Series { const decimals = validateNumber(json['decimals']); const unit = Unit.fromJson(json['unit']); return new Series( @@ -24,7 +24,7 @@ export class Series { validateString(json['title']), decimals, unit, - Value.fromJson(json['lastValue'], json['unit'], unit, decimals), + Value.fromJson(json['lastValue'], unit, decimals, locale), ); } diff --git a/src/main/angular/src/app/series/meter/MeterAggregate.ts b/src/main/angular/src/app/series/meter/MeterAggregate.ts index 84f9ff8..1c3d096 100644 --- a/src/main/angular/src/app/series/meter/MeterAggregate.ts +++ b/src/main/angular/src/app/series/meter/MeterAggregate.ts @@ -11,11 +11,11 @@ export class MeterAggregate extends Aggregate { super(series); } - static fromJson(json: any): MeterAggregate { - const series = Series.fromJson(json['series']); + static fromJson(json: any, locale: string): MeterAggregate { + const series = Series.fromJson(json['series'], locale); return new MeterAggregate( series, - Value.fromJson(json['delta'], series, series.unit, series.decimals), + Value.fromJson(json['delta'], series.unit, series.decimals, locale), ); } diff --git a/src/main/angular/src/app/series/series.service.ts b/src/main/angular/src/app/series/series.service.ts index fb0b555..b6bb422 100644 --- a/src/main/angular/src/app/series/series.service.ts +++ b/src/main/angular/src/app/series/series.service.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {Inject, Injectable, LOCALE_ID} from '@angular/core'; import {ApiService} from '../core/api.service'; import {Alignment} from './Alignment'; import {AggregationWrapperDto} from './AggregationWrapperDto'; @@ -34,6 +34,7 @@ export class SeriesService { ] constructor( + @Inject(LOCALE_ID) readonly locale: string, protected readonly api: ApiService, ) { // @@ -57,7 +58,7 @@ export class SeriesService { if (this.subs.length !== 0) { return; } - this.subs.push(this.api.subscribe(['Series'], Series.fromJson, series => this.update(series))); + this.subs.push(this.api.subscribe(['Series'], j => Series.fromJson(j, this.locale), series => this.update(series))); this.subs.push(this.api.subscribeConnection(connected => { if (connected) { this.all(); @@ -97,7 +98,7 @@ export class SeriesService { } all(next?: Next) { - this.api.getList(['Series', 'all'], Series.fromJson, list => { + this.api.getList(['Series', 'all'], j => Series.fromJson(j, this.locale), list => { list.forEach(item => this.update(item)); if (next) { next(list); @@ -106,7 +107,7 @@ export class SeriesService { } aggregations(alignment: Alignment, offset: number, next: Next) { - this.api.getSingle(['Series', 'agg', 'all', alignment.name, 'offset', offset], AggregationWrapperDto.fromJson, next); + this.api.getSingle(['Series', 'agg', 'all', alignment.name, 'offset', offset], j => AggregationWrapperDto.fromJson(j, this.locale), next); } } diff --git a/src/main/angular/src/app/series/varying/VaryingAggregate.ts b/src/main/angular/src/app/series/varying/VaryingAggregate.ts index c00164b..e5c7050 100644 --- a/src/main/angular/src/app/series/varying/VaryingAggregate.ts +++ b/src/main/angular/src/app/series/varying/VaryingAggregate.ts @@ -12,9 +12,9 @@ export class VaryingAggregate extends Aggregate { super(series); } - static fromJson(json: any): VaryingAggregate { + static fromJson(json: any, locale: string): VaryingAggregate { return new VaryingAggregate( - Series.fromJson(json['series']), + Series.fromJson(json['series'], locale), json['min'] as number, json['avg'] as number, json['max'] as number, diff --git a/src/main/angular/src/app/value/Value.ts b/src/main/angular/src/app/value/Value.ts index c90a189..93a527e 100644 --- a/src/main/angular/src/app/value/Value.ts +++ b/src/main/angular/src/app/value/Value.ts @@ -1,25 +1,18 @@ import {Unit} from "./Unit"; import {validateNumber} from "../core/validators"; -import {Series} from "../series/Series"; export class Value { - static readonly EMPTY: Value = new Value(0, Unit._UNKNOWN_, 1); - constructor( readonly value: number, readonly unit: Unit, readonly decimals: number, - ) { + readonly locale: string) { // } - static fromJson(value: any, series: Series, unit: Unit, decimals: number): Value { - return new Value( - validateNumber(value), - unit, - decimals, - ); + static fromJson(value: any, unit: Unit, decimals: number, locale: string): Value { + return new Value(validateNumber(value), unit, decimals, locale); } get zero(): boolean { @@ -27,25 +20,25 @@ export class Value { } get formatted(): string { - return `${this.value.toFixed(this.decimals)} ${this.unit.unit}`; + return `${this.value.toLocaleString(this.locale, {maximumFractionDigits: this.decimals, minimumFractionDigits: this.decimals})} ${this.unit.unit}`; } negate() { - return new Value(-this.value, this.unit, this.decimals); + return new Value(-this.value, this.unit, this.decimals, this.locale); } plus(other: Value | undefined): Value | undefined { if (!other) { return undefined; } - return new Value(this.value + other.value, this.unit, this.decimals); + return new Value(this.value + other.value, this.unit, this.decimals, this.locale); } minus(other: Value | undefined): Value | undefined { if (!other) { return undefined; } - return new Value(this.value - other.value, this.unit, this.decimals); + return new Value(this.value - other.value, this.unit, this.decimals, this.locale); } notNegative(): Value { @@ -59,14 +52,14 @@ export class Value { if (this.value === 0) { return this; } - return new Value(0, this.unit, this.decimals); + return new Value(0, this.unit, this.decimals, this.locale); } percent(other: Value | undefined): Value | undefined { if (!other || other.value === 0) { return undefined; } - return new Value(this.value / other.value * 100, Unit.PERCENT, 0); + return new Value(this.value / other.value * 100, Unit.PERCENT, 0, this.locale); } } diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index ed16151..20bd6c3 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -1,11 +1,22 @@ +@import "../config.less"; + body { font-family: sans-serif; font-size: 6vw; user-select: none; + background-color: @background; + color: @foreground; + margin: 0; } -button, input, select { +button { + all: unset; font-size: inherit; + padding: 0 0.25em; + background-color: #002433; + color: #008fca; + border: unset; + border-radius: 10%; } div {