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 {
|
||||
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[] {
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
<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>
|
||||
<div style="display: flex; width: 100%">
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div style="flex: 1">{{ offsetDayTitle() }}</div>
|
||||
</div>
|
||||
</ng-content>
|
||||
</app-series-history>
|
||||
</app-location-energy>
|
||||
|
||||
<div class="Section">
|
||||
<div class="SectionHeading">
|
||||
|
||||
@ -6,7 +6,7 @@ import {Text} from '../../shared/text/text';
|
||||
import {Number} from '../../shared/number/number';
|
||||
import {SeriesSelect} from '../../series/select/series-select';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {LocationEnergy} from '../electricity/location-energy';
|
||||
import {LocationEnergy} from '../energy/location-energy';
|
||||
import {Interval} from '../../series/Interval';
|
||||
import {MenuService} from '../../menu-service';
|
||||
import {DatePipe} from '@angular/common';
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Bezug
|
||||
</div>
|
||||
<div class="SectionBody purchase">
|
||||
<div class="SectionBody COLOR_FONT_PURCHASE">
|
||||
{{ purchase.toValueString(interval ? null : now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Solar
|
||||
</div>
|
||||
<div class="SectionBody produce">
|
||||
<div class="SectionBody COLOR_FONT_PRODUCE">
|
||||
{{ produce.toValueString(interval ? null : now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -29,7 +29,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Verbrauch
|
||||
</div>
|
||||
<div class="SectionBody consume">
|
||||
<div class="SectionBody COLOR_FONT_CONSUME">
|
||||
{{ consume.toValueString(interval ? null : now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -38,15 +38,13 @@
|
||||
<div class="SectionHeadingText">
|
||||
Einspeisung
|
||||
</div>
|
||||
<div class="SectionBody deliver">
|
||||
<div class="SectionBody COLOR_FONT_DELIVER">
|
||||
{{ deliver.toValueString(interval ? null : now) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@if (interval) {
|
||||
<!-- <app-series-history-graph></app-series-history-graph>-->
|
||||
}
|
||||
<app-energy-plot [location]="location" [interval]="interval" [offset]="offset"></app-energy-plot>
|
||||
|
||||
</div>
|
||||
@ -7,10 +7,13 @@ import {PointService} from '../../point/point-service';
|
||||
import {SeriesService} from '../../series/series-service';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Value} from '../../series/Value';
|
||||
import {EnergyPlot} from './plot/energy-plot';
|
||||
|
||||
@Component({
|
||||
selector: 'app-series-history',
|
||||
imports: [],
|
||||
selector: 'app-location-energy',
|
||||
imports: [
|
||||
EnergyPlot
|
||||
],
|
||||
templateUrl: './location-energy.html',
|
||||
styleUrl: './location-energy.less',
|
||||
})
|
||||
@ -98,7 +101,7 @@ export class LocationEnergy implements OnInit, AfterViewInit, OnDestroy {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
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">
|
||||
Bezug
|
||||
</div>
|
||||
<div class="SectionBody purchase">
|
||||
<div class="SectionBody COLOR_FONT_PURCHASE">
|
||||
{{ location.powerPurchase?.value?.toValueString(dateService.now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -19,7 +19,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Solar
|
||||
</div>
|
||||
<div class="SectionBody produce">
|
||||
<div class="SectionBody COLOR_FONT_PRODUCE">
|
||||
{{ location.powerProduce?.value?.toValueString(dateService.now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -28,7 +28,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Verbrauch
|
||||
</div>
|
||||
<div class="SectionBody consume">
|
||||
<div class="SectionBody COLOR_FONT_CONSUME">
|
||||
{{ location.powerConsume?.toValueString(dateService.now) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -37,7 +37,7 @@
|
||||
<div class="SectionHeadingText">
|
||||
Einspeisung
|
||||
</div>
|
||||
<div class="SectionBody deliver">
|
||||
<div class="SectionBody COLOR_FONT_DELIVER">
|
||||
{{ location.powerDeliver?.value?.toValueString(dateService.now) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {validateDate, validateList} from "../common";
|
||||
import {validateDate, validateList, validateNumber} from "../common";
|
||||
|
||||
import {PointSeries} from './PointSeries';
|
||||
|
||||
@ -8,6 +8,7 @@ export class PointResponse {
|
||||
readonly begin: Date,
|
||||
readonly end: Date,
|
||||
readonly series: PointSeries[],
|
||||
readonly expectedCount: number,
|
||||
) {
|
||||
//
|
||||
}
|
||||
@ -17,6 +18,7 @@ export class PointResponse {
|
||||
validateDate(json.begin),
|
||||
validateDate(json.end),
|
||||
validateList(json.series, PointSeries.fromJson),
|
||||
validateNumber(json.expectedCount),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -16,12 +16,13 @@ export class PointService extends CrudService<PointResponse> {
|
||||
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 = {
|
||||
ids: series.map(s => s.id),
|
||||
interval: interval,
|
||||
outerInterval: outer.name,
|
||||
offset: offset,
|
||||
count: count,
|
||||
interval: interval.name,
|
||||
};
|
||||
this.postSingle(['relative'], request, next);
|
||||
}
|
||||
|
||||
@ -1,8 +1,22 @@
|
||||
export enum Interval {
|
||||
FIVE = 'FIVE',
|
||||
HOUR = 'HOUR',
|
||||
DAY = 'DAY',
|
||||
WEEK = 'WEEK',
|
||||
MONTH = 'MONTH',
|
||||
YEAR = 'YEAR',
|
||||
export class Interval {
|
||||
|
||||
static readonly FIVE = new Interval('FIVE');
|
||||
|
||||
static readonly HOUR = new Interval('HOUR', this.FIVE);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
onlyPositive(): Value {
|
||||
if (this.value < 0) {
|
||||
return Value.ZERO;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class BiValue extends Value {
|
||||
|
||||
@ -1,23 +1,60 @@
|
||||
@empty: gray;
|
||||
@purchase: red;
|
||||
@deliver: magenta;
|
||||
@produce: #0095ff;
|
||||
@consume: #ff8800;
|
||||
|
||||
.purchase {
|
||||
color: @purchase;
|
||||
@COLOR_FONT_PURCHASE: red;
|
||||
@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: @deliver;
|
||||
.COLOR_FONT_DELIVER {
|
||||
color: @COLOR_FONT_DELIVER;
|
||||
}
|
||||
|
||||
.produce {
|
||||
color: @produce;
|
||||
.COLOR_FONT_PRODUCE {
|
||||
color: @COLOR_FONT_PRODUCE;
|
||||
}
|
||||
|
||||
.consume {
|
||||
color: @consume;
|
||||
.COLOR_FONT_SELF {
|
||||
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 {
|
||||
|
||||
@ -51,7 +51,7 @@ public class Plot {
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private long duration = 288;
|
||||
private long duration = 1;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
|
||||
@ -15,13 +15,15 @@ public interface IPointRequest {
|
||||
@NonNull
|
||||
List<Long> getIds();
|
||||
|
||||
@NonNull
|
||||
Interval getInterval();
|
||||
|
||||
@NonNull
|
||||
ZonedDateTime getBegin();
|
||||
|
||||
@NonNull
|
||||
ZonedDateTime getEnd();
|
||||
|
||||
@NonNull
|
||||
Interval getInterval();
|
||||
|
||||
long getExpectedCount();
|
||||
|
||||
}
|
||||
|
||||
@ -5,34 +5,40 @@ import lombok.Data;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Data
|
||||
public class PointRequestRelative implements IPointRequest {
|
||||
|
||||
public final List<Long> ids;
|
||||
|
||||
public final Interval interval;
|
||||
public final Interval outerInterval;
|
||||
|
||||
public final long offset;
|
||||
|
||||
public final long count;
|
||||
|
||||
public final Interval interval;
|
||||
|
||||
public final ZonedDateTime begin;
|
||||
|
||||
public final ZonedDateTime end;
|
||||
|
||||
public PointRequestRelative(
|
||||
final List<Long> ids,
|
||||
final Interval interval,
|
||||
final long offset,
|
||||
final long count
|
||||
) {
|
||||
public final long expectedCount;
|
||||
|
||||
public PointRequestRelative(final List<Long> ids, final Interval outerInterval, final long offset, final long count, final Interval interval) {
|
||||
this.ids = ids;
|
||||
this.interval = interval;
|
||||
this.outerInterval = outerInterval;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
this.end = interval.align.apply(ZonedDateTime.now()).minus(interval.amount * (offset - 1), interval.unit);
|
||||
this.begin = this.end.minus(interval.amount * count, interval.unit);
|
||||
this.interval = interval;
|
||||
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 long expectedCount;
|
||||
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public class PointService {
|
||||
@NonNull
|
||||
public PointResponse points(@NonNull final IPointRequest request) {
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user