energy-plot
This commit is contained in:
parent
5bc68ca0c3
commit
eebb917a6d
@ -31,7 +31,8 @@ export function validateString(value: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateDate(value: any): Date {
|
export function validateDate(value: any): Date {
|
||||||
return new Date(Date.parse(validateString(value)));
|
const parsed = Date.parse(validateString(value));
|
||||||
|
return new Date(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateList<T>(value: any, fromJson: FromJson<T>): T[] {
|
export function validateList<T>(value: any, fromJson: FromJson<T>): T[] {
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
<app-location-power [location]="location"></app-location-power>
|
<app-location-power [location]="location"></app-location-power>
|
||||||
|
|
||||||
<app-series-history [location]="location" [interval]="Interval.DAY" heading="Heute"></app-series-history>
|
<app-location-energy [location]="location" [interval]="Interval.DAY" heading="Heute"></app-location-energy>
|
||||||
|
|
||||||
<app-series-history [location]="location" [interval]="Interval.DAY" [offset]="offset">
|
<app-location-energy [location]="location" [interval]="Interval.DAY" [offset]="offset">
|
||||||
<ng-content #SeriesHistoryHeading>
|
<ng-content #SeriesHistoryHeading>
|
||||||
<div style="display: flex; width: 100%">
|
<div style="display: flex; width: 100%">
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<div style="flex: 1">{{ offsetDayTitle() }}</div>
|
<div style="flex: 1">{{ offsetDayTitle() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-content>
|
</ng-content>
|
||||||
</app-series-history>
|
</app-location-energy>
|
||||||
|
|
||||||
<div class="Section">
|
<div class="Section">
|
||||||
<div class="SectionHeading">
|
<div class="SectionHeading">
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {Text} from '../../shared/text/text';
|
|||||||
import {Number} from '../../shared/number/number';
|
import {Number} from '../../shared/number/number';
|
||||||
import {SeriesSelect} from '../../series/select/series-select';
|
import {SeriesSelect} from '../../series/select/series-select';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {LocationEnergy} from '../electricity/location-energy';
|
import {LocationEnergy} from '../energy/location-energy';
|
||||||
import {Interval} from '../../series/Interval';
|
import {Interval} from '../../series/Interval';
|
||||||
import {MenuService} from '../../menu-service';
|
import {MenuService} from '../../menu-service';
|
||||||
import {DatePipe} from '@angular/common';
|
import {DatePipe} from '@angular/common';
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Bezug
|
Bezug
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody purchase">
|
<div class="SectionBody COLOR_FONT_PURCHASE">
|
||||||
{{ purchase.toValueString(interval ? null : now) }}
|
{{ purchase.toValueString(interval ? null : now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Solar
|
Solar
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody produce">
|
<div class="SectionBody COLOR_FONT_PRODUCE">
|
||||||
{{ produce.toValueString(interval ? null : now) }}
|
{{ produce.toValueString(interval ? null : now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Verbrauch
|
Verbrauch
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody consume">
|
<div class="SectionBody COLOR_FONT_CONSUME">
|
||||||
{{ consume.toValueString(interval ? null : now) }}
|
{{ consume.toValueString(interval ? null : now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,15 +38,13 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Einspeisung
|
Einspeisung
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody deliver">
|
<div class="SectionBody COLOR_FONT_DELIVER">
|
||||||
{{ deliver.toValueString(interval ? null : now) }}
|
{{ deliver.toValueString(interval ? null : now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (interval) {
|
<app-energy-plot [location]="location" [interval]="interval" [offset]="offset"></app-energy-plot>
|
||||||
<!-- <app-series-history-graph></app-series-history-graph>-->
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -7,10 +7,13 @@ import {PointService} from '../../point/point-service';
|
|||||||
import {SeriesService} from '../../series/series-service';
|
import {SeriesService} from '../../series/series-service';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {Value} from '../../series/Value';
|
import {Value} from '../../series/Value';
|
||||||
|
import {EnergyPlot} from './plot/energy-plot';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-series-history',
|
selector: 'app-location-energy',
|
||||||
imports: [],
|
imports: [
|
||||||
|
EnergyPlot
|
||||||
|
],
|
||||||
templateUrl: './location-energy.html',
|
templateUrl: './location-energy.html',
|
||||||
styleUrl: './location-energy.less',
|
styleUrl: './location-energy.less',
|
||||||
})
|
})
|
||||||
@ -98,7 +101,7 @@ export class LocationEnergy implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.interval) {
|
if (this.interval) {
|
||||||
this.pointService.relative([series], this.interval, this.offset, 1, response => callNextAndUpdateConsume(Value.ofPoint(response, 0, 0, 1)));
|
this.pointService.relative([series], this.interval, this.offset, 1, this.interval, response => callNextAndUpdateConsume(Value.ofPoint(response, 0, 0, 1)));
|
||||||
} else {
|
} else {
|
||||||
callNextAndUpdateConsume(series.value);
|
callNextAndUpdateConsume(series.value);
|
||||||
}
|
}
|
||||||
98
src/main/angular/src/app/location/energy/plot/EnergyPoint.ts
Normal file
98
src/main/angular/src/app/location/energy/plot/EnergyPoint.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
export class EnergyPoint {
|
||||||
|
|
||||||
|
readonly epochSeconds: number;
|
||||||
|
|
||||||
|
private _purchase: number | null = null;
|
||||||
|
|
||||||
|
private _produce: number | null = null;
|
||||||
|
|
||||||
|
private _deliver: number | null = null;
|
||||||
|
|
||||||
|
private _self: number | null = null;
|
||||||
|
|
||||||
|
private _consume: number | null = null;
|
||||||
|
|
||||||
|
private _purchaseY: number | null = null;
|
||||||
|
|
||||||
|
private _selfY: number | null = null;
|
||||||
|
|
||||||
|
private _deliverY: number | null = null;
|
||||||
|
|
||||||
|
getPurchaseY(yFactor: number): number {
|
||||||
|
return this.getPurchaseH(yFactor) + this.getSelfH(yFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchaseH(yFactor: number): number {
|
||||||
|
if (this._purchaseY === null) {
|
||||||
|
this._purchaseY = (this.purchase || 0) * yFactor;
|
||||||
|
}
|
||||||
|
return this._purchaseY;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelfY(yFactor: number): number {
|
||||||
|
return this.getSelfH(yFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelfH(yFactor: number): number {
|
||||||
|
if (this._selfY === null) {
|
||||||
|
this._selfY = (this.self || 0) * yFactor;
|
||||||
|
}
|
||||||
|
return this._selfY;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeliverH(yFactor: number): number {
|
||||||
|
if (this._deliverY === null) {
|
||||||
|
this._deliverY = (this.deliver || 0) * yFactor;
|
||||||
|
}
|
||||||
|
return this._deliverY;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
point: number[],
|
||||||
|
) {
|
||||||
|
this.epochSeconds = point[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
set deliver(value: number | null) {
|
||||||
|
this._deliver = value;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
set produce(value: number | null) {
|
||||||
|
this._produce = value;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
set purchase(value: number | null) {
|
||||||
|
this._purchase = value;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
if (this._purchase !== null && this._produce !== null && this._deliver !== null) {
|
||||||
|
this._self = Math.max(0, this._produce - this._deliver);
|
||||||
|
this._consume = Math.max(0, this._purchase + this._self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get consume(): number | null {
|
||||||
|
return this._consume;
|
||||||
|
}
|
||||||
|
|
||||||
|
get self(): number | null {
|
||||||
|
return this._self;
|
||||||
|
}
|
||||||
|
|
||||||
|
get deliver(): number | null {
|
||||||
|
return this._deliver;
|
||||||
|
}
|
||||||
|
|
||||||
|
get produce(): number | null {
|
||||||
|
return this._produce;
|
||||||
|
}
|
||||||
|
|
||||||
|
get purchase(): number | null {
|
||||||
|
return this._purchase;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<svg [attr.viewBox]="`0 0 ${widthPx} ${heightPx}`" [style.background-color]="'#eee'">
|
||||||
|
@for (point of points; track point.epochSeconds) {
|
||||||
|
<rect
|
||||||
|
[attr.x]="(point.epochSeconds - xMin) * xFactor"
|
||||||
|
[attr.y]="heightPx - 1 + yMinPx - point.getPurchaseY(yFactor)"
|
||||||
|
[attr.width]="xWidthPx"
|
||||||
|
[attr.height]="point.getPurchaseH(yFactor)"
|
||||||
|
class="COLOR_BACK_PURCHASE"
|
||||||
|
></rect>
|
||||||
|
<rect
|
||||||
|
[attr.x]="(point.epochSeconds - xMin) * xFactor"
|
||||||
|
[attr.y]="heightPx - 1 + yMinPx - point.getSelfY(yFactor)"
|
||||||
|
[attr.width]="xWidthPx"
|
||||||
|
[attr.height]="point.getSelfH(yFactor)"
|
||||||
|
class="COLOR_BACK_SELF"
|
||||||
|
></rect>
|
||||||
|
<rect
|
||||||
|
[attr.x]="(point.epochSeconds - xMin) * xFactor"
|
||||||
|
[attr.y]="heightPx + yMinPx"
|
||||||
|
[attr.width]="xWidthPx"
|
||||||
|
[attr.height]="point.getDeliverH(yFactor)"
|
||||||
|
class="COLOR_BACK_DELIVER"
|
||||||
|
></rect>
|
||||||
|
<line
|
||||||
|
x1="0"
|
||||||
|
[attr.y1]="heightPx - 1 + yMinPx"
|
||||||
|
[attr.x2]="widthPx"
|
||||||
|
[attr.y2]="heightPx - 1 + yMinPx"
|
||||||
|
stroke="#aaaaaa"
|
||||||
|
stroke-width="1"
|
||||||
|
></line>
|
||||||
|
}
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1 @@
|
|||||||
|
@import "../../../../colors";
|
||||||
135
src/main/angular/src/app/location/energy/plot/energy-plot.ts
Normal file
135
src/main/angular/src/app/location/energy/plot/energy-plot.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import {AfterViewInit, Component, Input} from '@angular/core';
|
||||||
|
import {Location} from '../../Location';
|
||||||
|
import {Interval} from '../../../series/Interval';
|
||||||
|
import {PointService} from '../../../point/point-service';
|
||||||
|
import {PointResponse} from '../../../point/PointResponse';
|
||||||
|
import {PointSeries} from '../../../point/PointSeries';
|
||||||
|
import {EnergyPoint} from './EnergyPoint';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-energy-plot',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './energy-plot.html',
|
||||||
|
styleUrl: './energy-plot.less',
|
||||||
|
})
|
||||||
|
export class EnergyPlot implements AfterViewInit {
|
||||||
|
|
||||||
|
readonly widthPx = 800;
|
||||||
|
|
||||||
|
readonly heightPx = 100;
|
||||||
|
|
||||||
|
private _location!: Location;
|
||||||
|
|
||||||
|
private _interval: Interval = Interval.FIVE;
|
||||||
|
|
||||||
|
private _offset: number = 0;
|
||||||
|
|
||||||
|
private _count: number = 1;
|
||||||
|
|
||||||
|
protected points: EnergyPoint[] = [];
|
||||||
|
|
||||||
|
protected yMin: number = 0;
|
||||||
|
|
||||||
|
protected yMinPx: number = 0;
|
||||||
|
|
||||||
|
protected yMax: number = 0;
|
||||||
|
|
||||||
|
protected yFactor: number = 0;
|
||||||
|
|
||||||
|
protected xMin: number = 0;
|
||||||
|
|
||||||
|
protected xMax: number = 0;
|
||||||
|
|
||||||
|
protected xFactor: number = 0;
|
||||||
|
|
||||||
|
protected xWidthPx: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly pointService: PointService,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (!this._location.energyPurchase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._location.energyProduce) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._location.energyDeliver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const series = [this._location.energyPurchase, this._location.energyProduce, this._location.energyDeliver];
|
||||||
|
this.pointService.relative(series, this._interval, this._offset, this._count, this._interval.inner, this.update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly update = (response: PointResponse): void => {
|
||||||
|
this.points.length = 0;
|
||||||
|
this.add(response.series[0], (p, v) => p.purchase = v);
|
||||||
|
this.add(response.series[1], (p, v) => p.produce = v);
|
||||||
|
this.add(response.series[2], (p, v) => p.deliver = v);
|
||||||
|
this.yMax = -Infinity;
|
||||||
|
this.yMin = Infinity;
|
||||||
|
for (let point of this.points) {
|
||||||
|
this.yMax = Math.max(this.yMax, point.consume || 0);
|
||||||
|
this.yMin = Math.min(this.yMin, -(point.deliver || 0));
|
||||||
|
}
|
||||||
|
this.yMinPx = this.yMin * this.yFactor;
|
||||||
|
this.yFactor = this.heightPx / (this.yMax - this.yMin);
|
||||||
|
this.xMin = response.begin.getTime() / 1000;
|
||||||
|
this.xMax = response.end.getTime() / 1000;
|
||||||
|
this.xFactor = this.widthPx / (this.xMax - this.xMin);
|
||||||
|
this.xWidthPx = this.widthPx / response.expectedCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
private add(series: PointSeries, setter: (p: EnergyPoint, v: number) => void): void {
|
||||||
|
for (const point of series.points) {
|
||||||
|
const index = this.insert(point, setter);
|
||||||
|
if (index >= 0) {
|
||||||
|
const fresh = new EnergyPoint(point);
|
||||||
|
setter(fresh, point[1])
|
||||||
|
this.points.splice(index, 0, fresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private insert(point: number[], setter: (p: EnergyPoint, v: number) => any): number {
|
||||||
|
let index = 0;
|
||||||
|
for (let old of this.points) {
|
||||||
|
const age = old.epochSeconds - point[0];
|
||||||
|
if (age === 0) {
|
||||||
|
setter(old, point[1])
|
||||||
|
return -1;
|
||||||
|
} else if (age < 0) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set location(value: Location) {
|
||||||
|
this._location = value;
|
||||||
|
this.ngAfterViewInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set interval(value: Interval) {
|
||||||
|
this._interval = value;
|
||||||
|
this.ngAfterViewInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set offset(value: number) {
|
||||||
|
this._offset = value;
|
||||||
|
this.ngAfterViewInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set count(value: number) {
|
||||||
|
this._count = value;
|
||||||
|
this.ngAfterViewInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<div class="segments">
|
|
||||||
@for (segment of segments; track segment) {
|
|
||||||
<div class="segment">
|
|
||||||
<!-- @for (graph of graphs; track graph) {-->
|
|
||||||
<!-- <div class="" [style.background-color]="graph[0]" [style.height.%]="graph[1][segment] / total(segment) * 100"></div>-->
|
|
||||||
<!-- }-->
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
.segments {
|
|
||||||
display: flex;
|
|
||||||
height: 4em;
|
|
||||||
|
|
||||||
.segment {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import {AfterViewInit, Component, Input} from '@angular/core';
|
|
||||||
import {Location} from '../Location';
|
|
||||||
import {Interval} from '../../series/Interval';
|
|
||||||
import {PointService} from '../../point/point-service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-series-history-graph',
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './simple-plot.html',
|
|
||||||
styleUrl: './simple-plot.less',
|
|
||||||
})
|
|
||||||
export class SeriesHistoryGraph implements AfterViewInit {
|
|
||||||
|
|
||||||
protected segments = Array.from(Array(288).keys());
|
|
||||||
|
|
||||||
protected totals: number[] = [];
|
|
||||||
|
|
||||||
protected historyEnergyPurchase: number[] | null = null;
|
|
||||||
|
|
||||||
protected historyEnergyDeliver: number[] | null = null;
|
|
||||||
|
|
||||||
protected historyEnergyProduce: number[] | null = null;
|
|
||||||
|
|
||||||
protected readonly Interval = Interval;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
heading!: string;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
date!: Date;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
interval!: Interval;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
location!: Location;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly pointService: PointService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
// this.history(this.location?.energyPurchase, history => this.historyEnergyPurchase = history);
|
|
||||||
// this.history(this.location?.energyDeliver, history => this.historyEnergyDeliver = history);
|
|
||||||
// this.history(this.location?.energyProduce, history => this.historyEnergyProduce = history);
|
|
||||||
}
|
|
||||||
|
|
||||||
// public readonly updateSeries = (fresh: Series): void => {
|
|
||||||
// if (fresh.id === this.location?.energyPurchase?.id) {
|
|
||||||
// this.history(this.location?.energyPurchase, history => this.historyEnergyPurchase = history);
|
|
||||||
// }
|
|
||||||
// if (fresh.id === this.location?.energyDeliver?.id) {
|
|
||||||
// this.history(this.location?.energyDeliver, history => this.historyEnergyDeliver = history);
|
|
||||||
// }
|
|
||||||
// if (fresh.id === this.location?.energyProduce?.id) {
|
|
||||||
// this.history(this.location?.energyProduce, history => this.historyEnergyProduce = history);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// private history(series: Series | null | undefined, next: Next<number[] | null>) {
|
|
||||||
// if (!series || !this.interval) {
|
|
||||||
// next(null);
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// this.pointService.points(series, this.date, this.interval, next);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Bezug
|
Bezug
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody purchase">
|
<div class="SectionBody COLOR_FONT_PURCHASE">
|
||||||
{{ location.powerPurchase?.value?.toValueString(dateService.now) }}
|
{{ location.powerPurchase?.value?.toValueString(dateService.now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Solar
|
Solar
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody produce">
|
<div class="SectionBody COLOR_FONT_PRODUCE">
|
||||||
{{ location.powerProduce?.value?.toValueString(dateService.now) }}
|
{{ location.powerProduce?.value?.toValueString(dateService.now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Verbrauch
|
Verbrauch
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody consume">
|
<div class="SectionBody COLOR_FONT_CONSUME">
|
||||||
{{ location.powerConsume?.toValueString(dateService.now) }}
|
{{ location.powerConsume?.toValueString(dateService.now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
Einspeisung
|
Einspeisung
|
||||||
</div>
|
</div>
|
||||||
<div class="SectionBody deliver">
|
<div class="SectionBody COLOR_FONT_DELIVER">
|
||||||
{{ location.powerDeliver?.value?.toValueString(dateService.now) }}
|
{{ location.powerDeliver?.value?.toValueString(dateService.now) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {validateDate, validateList} from "../common";
|
import {validateDate, validateList, validateNumber} from "../common";
|
||||||
|
|
||||||
import {PointSeries} from './PointSeries';
|
import {PointSeries} from './PointSeries';
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ export class PointResponse {
|
|||||||
readonly begin: Date,
|
readonly begin: Date,
|
||||||
readonly end: Date,
|
readonly end: Date,
|
||||||
readonly series: PointSeries[],
|
readonly series: PointSeries[],
|
||||||
|
readonly expectedCount: number,
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -17,6 +18,7 @@ export class PointResponse {
|
|||||||
validateDate(json.begin),
|
validateDate(json.begin),
|
||||||
validateDate(json.end),
|
validateDate(json.end),
|
||||||
validateList(json.series, PointSeries.fromJson),
|
validateList(json.series, PointSeries.fromJson),
|
||||||
|
validateNumber(json.expectedCount),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,12 +16,13 @@ export class PointService extends CrudService<PointResponse> {
|
|||||||
super(api, ws, ['Point'], PointResponse.fromJson);
|
super(api, ws, ['Point'], PointResponse.fromJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
relative(series: Series[], interval: Interval, offset: number, count: number, next: Next<PointResponse>): void {
|
relative(series: Series[], outer: Interval, offset: number, count: number, interval: Interval, next: Next<PointResponse>): void {
|
||||||
const request = {
|
const request = {
|
||||||
ids: series.map(s => s.id),
|
ids: series.map(s => s.id),
|
||||||
interval: interval,
|
outerInterval: outer.name,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
count: count,
|
count: count,
|
||||||
|
interval: interval.name,
|
||||||
};
|
};
|
||||||
this.postSingle(['relative'], request, next);
|
this.postSingle(['relative'], request, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,22 @@
|
|||||||
export enum Interval {
|
export class Interval {
|
||||||
FIVE = 'FIVE',
|
|
||||||
HOUR = 'HOUR',
|
static readonly FIVE = new Interval('FIVE');
|
||||||
DAY = 'DAY',
|
|
||||||
WEEK = 'WEEK',
|
static readonly HOUR = new Interval('HOUR', this.FIVE);
|
||||||
MONTH = 'MONTH',
|
|
||||||
YEAR = 'YEAR',
|
static readonly DAY = new Interval('DAY', this.FIVE);
|
||||||
|
|
||||||
|
static readonly WEEK = new Interval('WEEK', this.HOUR);
|
||||||
|
|
||||||
|
static readonly MONTH = new Interval('MONTH', this.HOUR);
|
||||||
|
|
||||||
|
static readonly YEAR = new Interval('YEAR', this.DAY);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly name: string,
|
||||||
|
readonly inner: Interval = this,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,6 +83,13 @@ export class Value {
|
|||||||
return ageSeconds > this.seconds * 2.1;
|
return ageSeconds > this.seconds * 2.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onlyPositive(): Value {
|
||||||
|
if (this.value < 0) {
|
||||||
|
return Value.ZERO;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BiValue extends Value {
|
export class BiValue extends Value {
|
||||||
|
|||||||
@ -1,23 +1,60 @@
|
|||||||
@empty: gray;
|
@empty: gray;
|
||||||
@purchase: red;
|
|
||||||
@deliver: magenta;
|
|
||||||
@produce: #0095ff;
|
|
||||||
@consume: #ff8800;
|
|
||||||
|
|
||||||
.purchase {
|
@COLOR_FONT_PURCHASE: red;
|
||||||
color: @purchase;
|
@COLOR_FONT_DELIVER: magenta;
|
||||||
|
@COLOR_FONT_PRODUCE: #0095ff;
|
||||||
|
@COLOR_FONT_SELF: #0095ff;
|
||||||
|
@COLOR_FONT_CONSUME: #ff8800;
|
||||||
|
|
||||||
|
@COLOR_BACK_PURCHASE: #ffa7a7;
|
||||||
|
@COLOR_BACK_DELIVER: #ff00ff;
|
||||||
|
@COLOR_BACK_PRODUCE: #5cbcff;
|
||||||
|
@COLOR_BACK_SELF: #00ff69;
|
||||||
|
@COLOR_BACK_CONSUME: #ffc07a;
|
||||||
|
|
||||||
|
.COLOR_FONT_PURCHASE {
|
||||||
|
color: @COLOR_FONT_PURCHASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deliver {
|
.COLOR_FONT_DELIVER {
|
||||||
color: @deliver;
|
color: @COLOR_FONT_DELIVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
.produce {
|
.COLOR_FONT_PRODUCE {
|
||||||
color: @produce;
|
color: @COLOR_FONT_PRODUCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.consume {
|
.COLOR_FONT_SELF {
|
||||||
color: @consume;
|
color: @COLOR_FONT_SELF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_FONT_CONSUME {
|
||||||
|
color: @COLOR_FONT_CONSUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_BACK_PURCHASE {
|
||||||
|
color: @COLOR_BACK_PURCHASE;
|
||||||
|
fill: @COLOR_BACK_PURCHASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_BACK_DELIVER {
|
||||||
|
color: @COLOR_BACK_DELIVER;
|
||||||
|
fill: @COLOR_BACK_DELIVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_BACK_PRODUCE {
|
||||||
|
color: @COLOR_BACK_PRODUCE;
|
||||||
|
fill: @COLOR_BACK_PRODUCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_BACK_SELF {
|
||||||
|
color: @COLOR_BACK_SELF;
|
||||||
|
fill: @COLOR_BACK_SELF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.COLOR_BACK_CONSUME {
|
||||||
|
color: @COLOR_BACK_CONSUME;
|
||||||
|
fill: @COLOR_BACK_CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ public class Plot {
|
|||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private long duration = 288;
|
private long duration = 1;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
|
|||||||
@ -15,13 +15,15 @@ public interface IPointRequest {
|
|||||||
@NonNull
|
@NonNull
|
||||||
List<Long> getIds();
|
List<Long> getIds();
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Interval getInterval();
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
ZonedDateTime getBegin();
|
ZonedDateTime getBegin();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
ZonedDateTime getEnd();
|
ZonedDateTime getEnd();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Interval getInterval();
|
||||||
|
|
||||||
|
long getExpectedCount();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,34 +5,40 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class PointRequestRelative implements IPointRequest {
|
public class PointRequestRelative implements IPointRequest {
|
||||||
|
|
||||||
public final List<Long> ids;
|
public final List<Long> ids;
|
||||||
|
|
||||||
public final Interval interval;
|
public final Interval outerInterval;
|
||||||
|
|
||||||
public final long offset;
|
public final long offset;
|
||||||
|
|
||||||
public final long count;
|
public final long count;
|
||||||
|
|
||||||
|
public final Interval interval;
|
||||||
|
|
||||||
public final ZonedDateTime begin;
|
public final ZonedDateTime begin;
|
||||||
|
|
||||||
public final ZonedDateTime end;
|
public final ZonedDateTime end;
|
||||||
|
|
||||||
public PointRequestRelative(
|
public final long expectedCount;
|
||||||
final List<Long> ids,
|
|
||||||
final Interval interval,
|
public PointRequestRelative(final List<Long> ids, final Interval outerInterval, final long offset, final long count, final Interval interval) {
|
||||||
final long offset,
|
|
||||||
final long count
|
|
||||||
) {
|
|
||||||
this.ids = ids;
|
this.ids = ids;
|
||||||
this.interval = interval;
|
this.outerInterval = outerInterval;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.end = interval.align.apply(ZonedDateTime.now()).minus(interval.amount * (offset - 1), interval.unit);
|
this.interval = interval;
|
||||||
this.begin = this.end.minus(interval.amount * count, interval.unit);
|
this.end = outerInterval.align.apply(ZonedDateTime.now()).minus(outerInterval.amount * (offset - 1), outerInterval.unit);
|
||||||
|
this.begin = this.end.minus(outerInterval.amount * count, outerInterval.unit);
|
||||||
|
this.expectedCount = calculateExpectedCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calculateExpectedCount() {
|
||||||
|
return Stream.iterate(begin, d -> d.isBefore(end), d -> d.plus(interval.amount, interval.unit)).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,4 +14,6 @@ public class PointResponse {
|
|||||||
|
|
||||||
public final List<PointSeries> series;
|
public final List<PointSeries> series;
|
||||||
|
|
||||||
|
public final long expectedCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public class PointService {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public PointResponse points(@NonNull final IPointRequest request) {
|
public PointResponse points(@NonNull final IPointRequest request) {
|
||||||
final List<PointSeries> series = request.getIds().stream().map(s -> points(s, request)).toList();
|
final List<PointSeries> series = request.getIds().stream().map(s -> points(s, request)).toList();
|
||||||
return new PointResponse(request.getBegin(), request.getEnd(), series);
|
return new PointResponse(request.getBegin(), request.getEnd(), series, request.getExpectedCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user