GraphOperation
This commit is contained in:
parent
ab7f3de9a2
commit
d65628a7d6
@ -53,6 +53,14 @@ export function validateBoolean(json: any): boolean {
|
||||
return json;
|
||||
}
|
||||
|
||||
export function validateEnum<T extends Record<string, string>>(value: any, enumType: T): T[keyof T] {
|
||||
const str = validateString(value);
|
||||
if (Object.values(enumType).includes(str)) {
|
||||
return str as T[keyof T];
|
||||
}
|
||||
throw new Error(`Invalid enum value: ${str}`);
|
||||
}
|
||||
|
||||
export function validateList<T>(json: any, fromJson: FromJson<T>): T[] {
|
||||
return json.map(fromJson);
|
||||
}
|
||||
|
||||
@ -1,10 +1,20 @@
|
||||
import {Series} from "../../../series/Series";
|
||||
import {Group} from "../../Group";
|
||||
import {validateBoolean, validateNumber, validateString} from "../../../COMMON";
|
||||
import {mapNotNull, validateBoolean, validateEnum, validateNumber, validateString} from "../../../COMMON";
|
||||
import {Axis} from '../Axis';
|
||||
import {SeriesType} from '../../../series/SeriesType';
|
||||
import {GraphType} from './GraphType';
|
||||
|
||||
export enum GraphOperation {
|
||||
MINUS = 'MINUS',
|
||||
PLUS = 'PLUS',
|
||||
DIVIDE = 'DIVIDE',
|
||||
}
|
||||
|
||||
export function listGraphOperation() {
|
||||
return Object.keys(GraphOperation);
|
||||
}
|
||||
|
||||
export class Graph {
|
||||
|
||||
readonly showMin: boolean;
|
||||
@ -18,12 +28,15 @@ export class Graph {
|
||||
readonly id: number,
|
||||
readonly version: number,
|
||||
readonly series: Series,
|
||||
readonly factor: number,
|
||||
readonly operation: GraphOperation,
|
||||
readonly series2: Series | null,
|
||||
readonly factor2: number,
|
||||
readonly name: string,
|
||||
readonly visible: boolean,
|
||||
readonly type: GraphType,
|
||||
readonly fill: string,
|
||||
readonly color: string,
|
||||
readonly factor: number,
|
||||
readonly group: Group,
|
||||
readonly stack: string,
|
||||
readonly min: boolean,
|
||||
@ -41,12 +54,15 @@ export class Graph {
|
||||
validateNumber(json.id),
|
||||
validateNumber(json.version),
|
||||
Series.fromJson(json.series),
|
||||
validateNumber(json.factor),
|
||||
validateEnum(json.operation, GraphOperation),
|
||||
mapNotNull(json.series2, Series.fromJson),
|
||||
validateNumber(json.factor2),
|
||||
validateString(json.name),
|
||||
validateBoolean(json.visible),
|
||||
GraphType.fromJson(json.type),
|
||||
validateString(json.fill),
|
||||
validateString(json.color),
|
||||
validateNumber(json.factor),
|
||||
validateString(json.group) as Group,
|
||||
validateString(json.stack),
|
||||
validateBoolean(json.min),
|
||||
|
||||
@ -136,9 +136,9 @@
|
||||
</th>
|
||||
<th>Name</th>
|
||||
<th>Serie</th>
|
||||
<th>Faktor</th>
|
||||
<th>Typ</th>
|
||||
<th>Farbe</th>
|
||||
<th>Faktor</th>
|
||||
<th>Aggregat</th>
|
||||
<th>Stack</th>
|
||||
<th class="vertical">min</th>
|
||||
@ -163,6 +163,27 @@
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<app-number-nn [initial]="graph.factor" (onChange)="plotService.graphFactor(graph, $event)"></app-number-nn>
|
||||
</td>
|
||||
<td>
|
||||
<select [ngModel]="graph.operation" (ngModelChange)="plotService.graphOperation(graph, $event)">
|
||||
@for (operation of listGraphOperation(); track operation) {
|
||||
<option [ngValue]="operation">{{ operation }}</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select [ngModel]="graph.series2?.id" (ngModelChange)="plotService.graphSeries2(graph, $event)">
|
||||
<option [ngValue]="null">-</option>
|
||||
@for (s of seriesService.list; track s.name) {
|
||||
<option [ngValue]="s.id">{{ s.name }} [{{ s.valueString }}]</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<app-number-nn [initial]="graph.factor2" (onChange)="plotService.graphFactor2(graph, $event)"></app-number-nn>
|
||||
</td>
|
||||
<td>
|
||||
<select [ngModel]="graph.type" (ngModelChange)="plotService.graphType(graph, $event)">
|
||||
@for (type of GraphType.values; track type.jsonName) {
|
||||
@ -173,9 +194,6 @@
|
||||
<td>
|
||||
<app-text [initial]="graph.color" (onChange)="plotService.graphColor(graph, $event)"></app-text>
|
||||
</td>
|
||||
<td>
|
||||
<app-number-nn [initial]="graph.factor" (onChange)="plotService.graphFactor(graph, $event)"></app-number-nn>
|
||||
</td>
|
||||
<td>
|
||||
<select [ngModel]="graph.group" (ngModelChange)="plotService.graphGroup(graph, $event)">
|
||||
@for (group of groups(); track group) {
|
||||
|
||||
@ -19,6 +19,7 @@ import {Location} from '@angular/common';
|
||||
import {PlotComponent} from '../plot/plot.component';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {GraphType} from '../axis/graph/GraphType';
|
||||
import {GraphOperation, listGraphOperation} from '../axis/graph/Graph';
|
||||
|
||||
Chart.register(
|
||||
CategoryScale,
|
||||
@ -35,13 +36,6 @@ Chart.register(
|
||||
Filler,
|
||||
);
|
||||
|
||||
export function unitInBrackets(unit: string): string {
|
||||
if (!unit) {
|
||||
return '';
|
||||
}
|
||||
return ` [${unit}]`;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-plot-editor',
|
||||
imports: [
|
||||
@ -58,6 +52,12 @@ export function unitInBrackets(unit: string): string {
|
||||
})
|
||||
export class PlotEditor implements OnInit, OnDestroy {
|
||||
|
||||
protected readonly GraphType = GraphType;
|
||||
|
||||
protected readonly GraphOperation = GraphOperation;
|
||||
|
||||
protected readonly listGraphOperation = listGraphOperation;
|
||||
|
||||
protected readonly SeriesType = SeriesType;
|
||||
|
||||
protected readonly Interval = Interval;
|
||||
@ -142,5 +142,4 @@ export class PlotEditor implements OnInit, OnDestroy {
|
||||
return Object.keys(Group);
|
||||
}
|
||||
|
||||
protected readonly GraphType = GraphType;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
|
||||
import {ApiService, EntityListService, Next} from '../COMMON';
|
||||
import {Plot} from './Plot';
|
||||
import {Axis} from './axis/Axis';
|
||||
import {Graph} from './axis/graph/Graph';
|
||||
import {Graph, GraphOperation} from './axis/graph/Graph';
|
||||
import {Group} from './Group';
|
||||
import {Interval} from '../series/Interval';
|
||||
import {GraphType} from './axis/graph/GraphType';
|
||||
@ -118,14 +118,26 @@ export class PlotService extends EntityListService<Plot> {
|
||||
this.postSingle(['Graph', graph.id, 'series'], value, next);
|
||||
}
|
||||
|
||||
graphColor(graph: Graph, value: string, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'color'], value, next);
|
||||
}
|
||||
|
||||
graphFactor(graph: Graph, value: number, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'factor'], value, next);
|
||||
}
|
||||
|
||||
graphOperation(graph: Graph, value: GraphOperation, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'operation'], value, next);
|
||||
}
|
||||
|
||||
graphSeries2(graph: Graph, value: number, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'series2'], value, next);
|
||||
}
|
||||
|
||||
graphFactor2(graph: Graph, value: number, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'factor2'], value, next);
|
||||
}
|
||||
|
||||
graphColor(graph: Graph, value: string, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'color'], value, next);
|
||||
}
|
||||
|
||||
graphGroup(graph: Graph, value: Group, next?: Next<Plot>): void {
|
||||
this.postSingle(['Graph', graph.id, 'group'], value, next);
|
||||
}
|
||||
|
||||
@ -216,24 +216,21 @@ export class PlotComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
private points(graph: Graph, min: Dataset, mid: Dataset, max: Dataset): void {
|
||||
this.seriesService.oneSeriesPoints(
|
||||
graph.series,
|
||||
graph.axis.plot.interval,
|
||||
graph.axis.plot.offset,
|
||||
graph.axis.plot.duration,
|
||||
graph,
|
||||
points => {
|
||||
if (graph.series.type === SeriesType.BOOL) {
|
||||
mid.data = toBool(points, graph.factor);
|
||||
mid.data = toBool(points);
|
||||
} else if (graph.series.type === SeriesType.DELTA) {
|
||||
mid.data = toDelta(points, graph.factor);
|
||||
mid.data = toDelta(points);
|
||||
} else if (graph.series.type === SeriesType.VARYING) {
|
||||
if (min) {
|
||||
min.data = toMin(points, graph.factor);
|
||||
min.data = toMin(points);
|
||||
}
|
||||
if (max) {
|
||||
max.data = toMax(points, graph.factor);
|
||||
max.data = toMax(points);
|
||||
}
|
||||
if (mid) {
|
||||
mid.data = toAvg(points, graph.factor);
|
||||
mid.data = toAvg(points);
|
||||
}
|
||||
}
|
||||
this.chart.update();
|
||||
@ -264,14 +261,13 @@ export class PlotComponent implements AfterViewInit, OnDestroy {
|
||||
private updatePoint(graph: Graph, dataset: Dataset, date: Date, y: number): void {
|
||||
const x = date.getTime();
|
||||
const point = dataset.data.filter((p: PointElement) => p.x === x)[0];
|
||||
const yMultiplied = y * graph.factor;
|
||||
if (point) {
|
||||
if (point.y !== yMultiplied) {
|
||||
point.y = yMultiplied;
|
||||
if (point.y !== y) {
|
||||
point.y = y * graph.factor; // TODO this is wrong. we need to take GraphOperation into account
|
||||
this.chart.update();
|
||||
}
|
||||
} else {
|
||||
dataset.data.push({x: x, y: yMultiplied}); // TODO check if this is a LIVE/SCROLLING plot (right end of plot is 'now')
|
||||
dataset.data.push({x: x, y: y}); // TODO check if this is a LIVE/SCROLLING plot (right end of plot is 'now')
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,10 +9,7 @@ export class Point {
|
||||
|
||||
}
|
||||
|
||||
export type PointMapper = (p: number[][], factor: number) => Point[];
|
||||
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
export function toBool(points: number[][], factor: number): Point[] {
|
||||
export function toBool(points: number[][]): Point[] {
|
||||
const result = [];
|
||||
let postPone: Point | null = null;
|
||||
for (const p of points) {
|
||||
@ -44,45 +41,45 @@ export function toBool(points: number[][], factor: number): Point[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toDelta(points: number[][], factor: number): Point[] {
|
||||
export function toDelta(points: number[][]): Point[] {
|
||||
const result = [];
|
||||
for (const p of points) {
|
||||
result.push({
|
||||
x: p[0] * 1000,
|
||||
y: (p[1]) * factor,
|
||||
y: (p[1]),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toMin(points: number[][], factor: number): Point[] {
|
||||
export function toMin(points: number[][]): Point[] {
|
||||
const result = [];
|
||||
for (const p of points) {
|
||||
result.push({
|
||||
x: p[0] * 1000,
|
||||
y: p[1] * factor,
|
||||
y: p[1],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toMax(points: number[][], factor: number): Point[] {
|
||||
export function toMax(points: number[][]): Point[] {
|
||||
const result = [];
|
||||
for (const p of points) {
|
||||
result.push({
|
||||
x: p[0] * 1000,
|
||||
y: p[2] * factor,
|
||||
y: p[2],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toAvg(points: number[][], factor: number): Point[] {
|
||||
export function toAvg(points: number[][]): Point[] {
|
||||
const result = [];
|
||||
for (const p of points) {
|
||||
result.push({
|
||||
x: p[0] * 1000,
|
||||
y: p[3] * factor,
|
||||
y: p[3],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -4,6 +4,7 @@ import {Series} from './Series';
|
||||
import {Interval} from './Interval';
|
||||
import {AllSeriesPointResponse} from './AllSeriesPointResponse';
|
||||
import {AllSeriesPointRequest} from './AllSeriesPointRequest';
|
||||
import {Graph} from "../plot/axis/graph/Graph";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -16,12 +17,16 @@ export class SeriesService extends EntityListService<Series> {
|
||||
super(api, ['Series'], Series.fromJson, Series.equals, Series.compareName);
|
||||
}
|
||||
|
||||
oneSeriesPoints(series: Series, interval: Interval, offset: number, duration: number, next: Next<number[][]>): void {
|
||||
oneSeriesPoints(graph: Graph, next: Next<number[][]>): void {
|
||||
const request = {
|
||||
id: series.id,
|
||||
interval: interval.name,
|
||||
offset: offset,
|
||||
duration: duration,
|
||||
id: graph.series.id,
|
||||
factor: graph.factor,
|
||||
operation: graph.operation,
|
||||
id2: graph.series2?.id,
|
||||
factor2: graph.factor2,
|
||||
interval: graph.axis.plot.interval.name,
|
||||
offset: graph.axis.plot.offset,
|
||||
duration: graph.axis.plot.duration,
|
||||
};
|
||||
this.api.postList([...this.path, 'oneSeriesPoints'], request, (outer: any[]) => outer.map(validateNumber), next);
|
||||
}
|
||||
|
||||
@ -43,7 +43,6 @@ public class DemoService {
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void init() {
|
||||
topics();
|
||||
// plots();
|
||||
}
|
||||
|
||||
private void topics() {
|
||||
@ -168,6 +167,7 @@ public class DemoService {
|
||||
zuhauseEnergie();
|
||||
zuhauseTemperatur();
|
||||
eltern();
|
||||
leistungVergleich();
|
||||
}
|
||||
|
||||
private void zuhauseEnergie() {
|
||||
@ -181,30 +181,33 @@ public class DemoService {
|
||||
energy.setName("Energie");
|
||||
energy.setUnit("kWh");
|
||||
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack("a");
|
||||
electricityEnergyPurchaseGraph.setName("Bezug");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
final String stack = "a";
|
||||
|
||||
final Series electricityEnergyDelivery = seriesRepository.findByName("electricity/energy/delivery").orElseThrow();
|
||||
final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
|
||||
electricityEnergyDeliveryGraph.setType(GraphType.BAR);
|
||||
electricityEnergyDeliveryGraph.setStack("a");
|
||||
electricityEnergyDeliveryGraph.setName("Überschuss");
|
||||
electricityEnergyDeliveryGraph.setStack(stack);
|
||||
electricityEnergyDeliveryGraph.setName("Zuhause Überschuss");
|
||||
electricityEnergyDeliveryGraph.setColor("#FF00FF");
|
||||
electricityEnergyDeliveryGraph.setFactor(-1);
|
||||
energy.addGraph(electricityEnergyDeliveryGraph);
|
||||
|
||||
final Series electricityEnergyProduce = seriesRepository.findByName("electricity/energy/produce").orElseThrow();
|
||||
final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
|
||||
electricityEnergyProduceGraph.setSeries2(electricityEnergyDelivery);
|
||||
electricityEnergyProduceGraph.setName("Zuhause Eigenverbrauch");
|
||||
electricityEnergyProduceGraph.setType(GraphType.BAR);
|
||||
electricityEnergyProduceGraph.setStack("a");
|
||||
electricityEnergyProduceGraph.setName("Produktion");
|
||||
electricityEnergyProduceGraph.setColor("#0000FF");
|
||||
electricityEnergyProduceGraph.setStack(stack);
|
||||
electricityEnergyProduceGraph.setColor("#008800");
|
||||
energy.addGraph(electricityEnergyProduceGraph);
|
||||
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack(stack);
|
||||
electricityEnergyPurchaseGraph.setName("Zuhause Bezug");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
}
|
||||
|
||||
private void zuhauseTemperatur() {
|
||||
@ -236,6 +239,24 @@ public class DemoService {
|
||||
gardenTemperatureGraph.setAvg(false);
|
||||
gardenTemperatureGraph.setMax(true);
|
||||
temperature.addGraph(gardenTemperatureGraph);
|
||||
|
||||
final Series bufferTemperature = seriesRepository.findByName("heating/buffer/inside/temperature").orElseThrow();
|
||||
final Graph bufferTemperatureGraph = graphRepository.save(new Graph(temperature, bufferTemperature));
|
||||
bufferTemperatureGraph.setName("Puffer");
|
||||
bufferTemperatureGraph.setColor("#FF00FF");
|
||||
bufferTemperatureGraph.setMin(true);
|
||||
bufferTemperatureGraph.setAvg(false);
|
||||
bufferTemperatureGraph.setMax(true);
|
||||
temperature.addGraph(bufferTemperatureGraph);
|
||||
|
||||
final Series circuitTemperature = seriesRepository.findByName("heating/circuit/supply/temperature").orElseThrow();
|
||||
final Graph circuitTemperatureGraph = graphRepository.save(new Graph(temperature, circuitTemperature));
|
||||
circuitTemperatureGraph.setName("Heizkreis");
|
||||
circuitTemperatureGraph.setColor("#FF0000");
|
||||
circuitTemperatureGraph.setMin(true);
|
||||
circuitTemperatureGraph.setAvg(false);
|
||||
circuitTemperatureGraph.setMax(true);
|
||||
temperature.addGraph(circuitTemperatureGraph);
|
||||
}
|
||||
|
||||
private void eltern() {
|
||||
@ -249,14 +270,6 @@ public class DemoService {
|
||||
energy.setName("Energie");
|
||||
energy.setUnit("kWh");
|
||||
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("eltern/electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setName("Bezug");
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack("a");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
|
||||
final Series electricityEnergyDelivery = seriesRepository.findByName("eltern/electricity/energy/delivery").orElseThrow();
|
||||
final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
|
||||
electricityEnergyDeliveryGraph.setName("Überschuss");
|
||||
@ -268,11 +281,46 @@ public class DemoService {
|
||||
|
||||
final Series electricityEnergyProduce = seriesRepository.findByName("eltern/electricity/energy/produce").orElseThrow();
|
||||
final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
|
||||
electricityEnergyProduceGraph.setName("Produktion");
|
||||
electricityEnergyProduceGraph.setSeries2(electricityEnergyDelivery);
|
||||
electricityEnergyProduceGraph.setName("Eigenverbrauch");
|
||||
electricityEnergyProduceGraph.setType(GraphType.BAR);
|
||||
electricityEnergyProduceGraph.setStack("a");
|
||||
electricityEnergyProduceGraph.setColor("#0000FF");
|
||||
electricityEnergyProduceGraph.setColor("#008800");
|
||||
energy.addGraph(electricityEnergyProduceGraph);
|
||||
|
||||
final Series electricityEnergyPurchase = seriesRepository.findByName("eltern/electricity/energy/purchase").orElseThrow();
|
||||
final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
|
||||
electricityEnergyPurchaseGraph.setName("Bezug");
|
||||
electricityEnergyPurchaseGraph.setType(GraphType.BAR);
|
||||
electricityEnergyPurchaseGraph.setStack("a");
|
||||
electricityEnergyPurchaseGraph.setColor("#FF8800");
|
||||
energy.addGraph(electricityEnergyPurchaseGraph);
|
||||
}
|
||||
|
||||
private void leistungVergleich() {
|
||||
final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
|
||||
plot.setName("Leistung Vergleich");
|
||||
|
||||
final Axis power = axisRepository.save(new Axis(plot));
|
||||
plot.addAxis(power);
|
||||
plot.setDashboard(true);
|
||||
power.setRight(true);
|
||||
power.setName("Leistung");
|
||||
power.setUnit("W");
|
||||
|
||||
final Series electricityPowerProduce = seriesRepository.findByName("electricity/power/produce").orElseThrow();
|
||||
final Graph electricityPowerProduceGraph = graphRepository.save(new Graph(power, electricityPowerProduce));
|
||||
electricityPowerProduceGraph.setName("Zuhause");
|
||||
electricityPowerProduceGraph.setType(GraphType.BAR);
|
||||
electricityPowerProduceGraph.setColor("#008800");
|
||||
power.addGraph(electricityPowerProduceGraph);
|
||||
|
||||
final Series elternElectricityPowerProduce = seriesRepository.findByName("eltern/electricity/power/produce").orElseThrow();
|
||||
final Graph elternElectricityPowerProduceGraph = graphRepository.save(new Graph(power, elternElectricityPowerProduce));
|
||||
elternElectricityPowerProduceGraph.setName("Eltern");
|
||||
elternElectricityPowerProduceGraph.setType(GraphType.BAR);
|
||||
elternElectricityPowerProduceGraph.setColor("#0088FF");
|
||||
power.addGraph(elternElectricityPowerProduceGraph);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@ -3,6 +3,7 @@ package de.ph87.data;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Helpers {
|
||||
@ -15,6 +16,14 @@ public class Helpers {
|
||||
return mapper.apply(t);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T, U, R> R map(@Nullable final T t, @NonNull final U u, @NonNull final BiFunction<T, U, R> mapper) {
|
||||
if (t == null) {
|
||||
return null;
|
||||
}
|
||||
return mapper.apply(t, u);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> T or(@Nullable final T t, @NonNull final T r) {
|
||||
if (t == null) {
|
||||
|
||||
@ -2,6 +2,7 @@ package de.ph87.data.plot.axis.graph;
|
||||
|
||||
import de.ph87.data.plot.axis.Axis;
|
||||
import de.ph87.data.series.Series;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@ -41,6 +42,24 @@ public class Graph {
|
||||
@ManyToOne(optional = false)
|
||||
private Series series;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private double factor = 1;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private GraphOperation operation = GraphOperation.MINUS;
|
||||
|
||||
@Setter
|
||||
@Nullable
|
||||
@ManyToOne
|
||||
private Series series2;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private double factor2 = 1;
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
@ -66,10 +85,6 @@ public class Graph {
|
||||
@Column(nullable = false)
|
||||
private String color = "#FF0000";
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private double factor = 1;
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
|
||||
@ -56,16 +56,31 @@ public class GraphController {
|
||||
return graphService.set(id, graph -> graph.setSeries(seriesRepository.findById(value).orElseThrow()));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/color")
|
||||
public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
||||
return graphService.set(id, graph -> graph.setColor(value));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/factor")
|
||||
public PlotDto factor(@PathVariable final long id, @RequestBody final double value) {
|
||||
return graphService.set(id, graph -> graph.setFactor(value));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/operation")
|
||||
public PlotDto operation(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
||||
return graphService.set(id, graph -> graph.setOperation(GraphOperation.valueOf(value)));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/series2")
|
||||
public PlotDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long value) {
|
||||
return graphService.set(id, graph -> graph.setSeries2(value == null ? null : seriesRepository.findById(value).orElseThrow()));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/factor2")
|
||||
public PlotDto factor2(@PathVariable final long id, @RequestBody final double value) {
|
||||
return graphService.set(id, graph -> graph.setFactor2(value));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/color")
|
||||
public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
||||
return graphService.set(id, graph -> graph.setColor(value));
|
||||
}
|
||||
|
||||
@PostMapping("{id}/group")
|
||||
public PlotDto group(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
||||
return graphService.set(id, graph -> graph.setGroup(Group.valueOf(value)));
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package de.ph87.data.plot.axis.graph;
|
||||
|
||||
public class GraphDivisionByZero extends Exception {
|
||||
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
package de.ph87.data.plot.axis.graph;
|
||||
|
||||
import de.ph87.data.series.SeriesDto;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import static de.ph87.data.Helpers.map;
|
||||
|
||||
@Data
|
||||
public class GraphDto {
|
||||
|
||||
@ -14,6 +17,15 @@ public class GraphDto {
|
||||
@NonNull
|
||||
public final SeriesDto series;
|
||||
|
||||
public final double factor;
|
||||
|
||||
public final GraphOperation operation;
|
||||
|
||||
@Nullable
|
||||
public final SeriesDto series2;
|
||||
|
||||
public final double factor2;
|
||||
|
||||
@NonNull
|
||||
public final String name;
|
||||
|
||||
@ -26,8 +38,6 @@ public class GraphDto {
|
||||
@NonNull
|
||||
public final String color;
|
||||
|
||||
public final double factor;
|
||||
|
||||
@NonNull
|
||||
public final Group group;
|
||||
|
||||
@ -44,12 +54,15 @@ public class GraphDto {
|
||||
this.id = graph.getId();
|
||||
this.version = graph.getVersion();
|
||||
this.series = new SeriesDto(graph.getSeries(), false);
|
||||
this.factor = graph.getFactor();
|
||||
this.operation = graph.getOperation();
|
||||
this.series2 = map(graph.getSeries2(), false, SeriesDto::new);
|
||||
this.factor2 = graph.getFactor2();
|
||||
this.name = graph.getName();
|
||||
this.visible = graph.isVisible();
|
||||
this.type = graph.getType();
|
||||
this.fill = graph.getFill();
|
||||
this.color = graph.getColor();
|
||||
this.factor = graph.getFactor();
|
||||
this.group = graph.getGroup();
|
||||
this.stack = graph.getStack();
|
||||
this.min = graph.isMin();
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package de.ph87.data.plot.axis.graph;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public enum GraphOperation {
|
||||
MINUS((s0, s1) -> s0 - s1),
|
||||
PLUS(Double::sum),
|
||||
DIVIDE((s0, s1) -> {
|
||||
if (s1 == 0) {
|
||||
throw new GraphDivisionByZero();
|
||||
}
|
||||
return s0 / s1;
|
||||
}),
|
||||
;
|
||||
|
||||
@NonNull
|
||||
private final GraphOperationFunction<Double, Double, Double> function;
|
||||
|
||||
GraphOperation(@NonNull final GraphOperationFunction<Double, Double, Double> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public double apply(final double a, final double b) throws GraphDivisionByZero {
|
||||
return function.apply(a, b);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package de.ph87.data.plot.axis.graph;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface GraphOperationFunction<A, B, R> {
|
||||
|
||||
R apply(A a, B b) throws GraphDivisionByZero;
|
||||
|
||||
}
|
||||
@ -22,7 +22,7 @@ public class AllSeriesPointResponse {
|
||||
public final SeriesDto series;
|
||||
|
||||
@Nullable
|
||||
public final SeriesPoint point;
|
||||
public final SeriesPoint<?> point;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@ package de.ph87.data.series;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.ph87.data.plot.axis.graph.GraphOperation;
|
||||
import de.ph87.data.series.data.Interval;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -13,6 +15,15 @@ public class OneSeriesPointsRequest implements ISeriesPointRequest {
|
||||
|
||||
public final long id;
|
||||
|
||||
public final double factor;
|
||||
|
||||
public final GraphOperation operation;
|
||||
|
||||
@Nullable
|
||||
public final Long id2;
|
||||
|
||||
public final double factor2;
|
||||
|
||||
@NonNull
|
||||
public final Interval interval;
|
||||
|
||||
@ -30,11 +41,19 @@ public class OneSeriesPointsRequest implements ISeriesPointRequest {
|
||||
|
||||
public OneSeriesPointsRequest(
|
||||
@JsonProperty("id") final long id,
|
||||
@JsonProperty("factor") final double factor,
|
||||
@JsonProperty("operation") final GraphOperation operation,
|
||||
@JsonProperty("id2") @Nullable final Long id2,
|
||||
@JsonProperty("factor2") final double factor2,
|
||||
@JsonProperty("interval") final Interval interval,
|
||||
@JsonProperty("offset") final long offset,
|
||||
@JsonProperty("duration") final long duration
|
||||
) {
|
||||
this.id = id;
|
||||
this.factor = factor;
|
||||
this.operation = operation;
|
||||
this.id2 = id2;
|
||||
this.factor2 = factor2;
|
||||
this.interval = interval;
|
||||
this.offset = offset;
|
||||
this.duration = duration;
|
||||
|
||||
@ -9,6 +9,6 @@ import java.util.List;
|
||||
@JsonSerialize(using = OneSeriesPointsResponseSerializer.class)
|
||||
public class OneSeriesPointsResponse {
|
||||
|
||||
public final List<? extends SeriesPoint> points;
|
||||
public final List<? extends SeriesPoint<?>> points;
|
||||
|
||||
}
|
||||
|
||||
@ -1,11 +1,67 @@
|
||||
package de.ph87.data.series;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import de.ph87.data.plot.axis.graph.GraphDivisionByZero;
|
||||
import de.ph87.data.plot.axis.graph.GraphOperation;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface SeriesPoint {
|
||||
public interface SeriesPoint<T extends SeriesPoint<T>> {
|
||||
|
||||
ZonedDateTime getDate();
|
||||
|
||||
double getValue();
|
||||
|
||||
void toJson(final JsonGenerator jsonGenerator) throws IOException;
|
||||
|
||||
T times(final double factor);
|
||||
|
||||
T combine(@NonNull final SeriesPoint<?> other, @NonNull final GraphOperation operation) throws GraphDivisionByZero;
|
||||
|
||||
@NonNull
|
||||
static List<SeriesPoint<?>> combine(@NonNull final List<? extends SeriesPoint<?>> points1, @NonNull final List<? extends SeriesPoint<?>> points2, @NonNull final GraphOperation operation) {
|
||||
if (points1.isEmpty() || points2.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<SeriesPoint<?>> as = new ArrayList<>(points1);
|
||||
final List<SeriesPoint<?>> bs = new ArrayList<>(points2);
|
||||
SeriesPoint<?> a = as.removeFirst();
|
||||
SeriesPoint<?> b = bs.removeFirst();
|
||||
final List<SeriesPoint<?>> result = new ArrayList<>(Math.min(as.size(), bs.size()));
|
||||
while (true) {
|
||||
final int diff = a.getDate().compareTo(b.getDate());
|
||||
if (diff == 0) {
|
||||
try {
|
||||
result.add(a.combine(b, operation));
|
||||
} catch (GraphDivisionByZero e) {
|
||||
// just not add
|
||||
}
|
||||
a = null;
|
||||
b = null;
|
||||
} else if (diff < 0) {
|
||||
a = null;
|
||||
} else {
|
||||
b = null;
|
||||
}
|
||||
if (a == null) {
|
||||
if (as.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
a = as.removeFirst();
|
||||
}
|
||||
if (b == null) {
|
||||
if (bs.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
b = bs.removeFirst();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package de.ph87.data.series;
|
||||
import de.ph87.data.series.data.bool.BoolService;
|
||||
import de.ph87.data.series.data.delta.DeltaService;
|
||||
import de.ph87.data.series.data.varying.VaryingService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -27,8 +28,14 @@ public class SeriesService {
|
||||
|
||||
@NonNull
|
||||
public OneSeriesPointsResponse oneSeriesPoints(@NonNull final OneSeriesPointsRequest request) {
|
||||
final Series series = seriesRepository.findById(request.id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return new OneSeriesPointsResponse(getSeriesPoints(series, request));
|
||||
final Series series1 = seriesRepository.findById(request.id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
final List<? extends SeriesPoint<?>> points1 = getSeriesPoints(series1, request, request.factor);
|
||||
if (request.id2 != null) {
|
||||
final Series series2 = seriesRepository.findById(request.id2).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
final List<? extends SeriesPoint<?>> points2 = getSeriesPoints(series2, request, request.factor2);
|
||||
return new OneSeriesPointsResponse(SeriesPoint.combine(points1, points2, request.operation));
|
||||
}
|
||||
return new OneSeriesPointsResponse(points1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -39,19 +46,23 @@ public class SeriesService {
|
||||
|
||||
@NonNull
|
||||
private AllSeriesPointResponse.Entry map(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
|
||||
final List<? extends SeriesPoint> points = getSeriesPoints(series, request);
|
||||
final List<? extends SeriesPoint<?>> points = getSeriesPoints(series, request, null);
|
||||
final SeriesDto seriesDto = new SeriesDto(series, false);
|
||||
final SeriesPoint point = points.isEmpty() ? null : points.getFirst();
|
||||
final SeriesPoint<?> point = points.isEmpty() ? null : points.getFirst();
|
||||
return new AllSeriesPointResponse.Entry(seriesDto, point);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<? extends SeriesPoint> getSeriesPoints(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
|
||||
return switch (series.getType()) {
|
||||
private List<? extends SeriesPoint<?>> getSeriesPoints(@NonNull final Series series, @NonNull final ISeriesPointRequest request, @Nullable final Double factor) {
|
||||
final List<? extends SeriesPoint<?>> points = switch (series.getType()) {
|
||||
case BOOL -> boolService.points(series, request);
|
||||
case DELTA -> deltaService.points(series, request);
|
||||
case VARYING -> varyingService.points(series, request);
|
||||
};
|
||||
if (factor == null || factor == 1) {
|
||||
return points;
|
||||
}
|
||||
return points.stream().map(p -> p.times(factor)).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package de.ph87.data.series.data.bool;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import de.ph87.data.plot.axis.graph.GraphDivisionByZero;
|
||||
import de.ph87.data.plot.axis.graph.GraphOperation;
|
||||
import de.ph87.data.series.SeriesPoint;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@SuppressWarnings("unused") // used by repository query
|
||||
public class BoolPoint implements SeriesPoint {
|
||||
@Data
|
||||
public class BoolPoint implements SeriesPoint<BoolPoint> {
|
||||
|
||||
public final ZonedDateTime begin;
|
||||
|
||||
@ -25,6 +28,13 @@ public class BoolPoint implements SeriesPoint {
|
||||
this.terminated = bool.isTerminated();
|
||||
}
|
||||
|
||||
public BoolPoint(final ZonedDateTime begin, final ZonedDateTime end, final boolean state, final boolean terminated) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
this.state = state;
|
||||
this.terminated = terminated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(final JsonGenerator jsonGenerator) throws IOException {
|
||||
jsonGenerator.writeNumber(begin.toEpochSecond());
|
||||
@ -33,4 +43,24 @@ public class BoolPoint implements SeriesPoint {
|
||||
jsonGenerator.writeNumber(terminated ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoolPoint times(final double factor) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime getDate() {
|
||||
return begin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return state ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoolPoint combine(@NonNull final SeriesPoint<?> other, @NonNull final GraphOperation operation) throws GraphDivisionByZero {
|
||||
return new BoolPoint(begin, end, operation.apply(getValue(), other.getValue()) > 0, terminated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package de.ph87.data.series.data.delta;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import de.ph87.data.plot.axis.graph.GraphDivisionByZero;
|
||||
import de.ph87.data.plot.axis.graph.GraphOperation;
|
||||
import de.ph87.data.series.SeriesPoint;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@SuppressWarnings("unused") // used by repository query
|
||||
public class DeltaPoint implements SeriesPoint {
|
||||
@Data
|
||||
public class DeltaPoint implements SeriesPoint<DeltaPoint> {
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime date;
|
||||
@ -26,4 +29,19 @@ public class DeltaPoint implements SeriesPoint {
|
||||
jsonGenerator.writeNumber(delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeltaPoint times(final double factor) {
|
||||
return new DeltaPoint(date, delta * factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeltaPoint combine(@NonNull final SeriesPoint<?> other, @NonNull final GraphOperation operation) throws GraphDivisionByZero {
|
||||
return new DeltaPoint(date, operation.apply(getValue(), other.getValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import java.util.List;
|
||||
public interface DeltaRepo<T extends Delta> extends CrudRepository<T, DeltaId> {
|
||||
|
||||
@NonNull
|
||||
@Query("select new de.ph87.data.series.data.delta.DeltaPoint(e.id.date, sum(e.last - e.first)) from #{#entityName} e where e.id.meter.series = :series and e.id.date >= :first and e.id.date < :after group by e.id.date")
|
||||
@Query("select new de.ph87.data.series.data.delta.DeltaPoint(e.id.date, sum((e.last - e.first))) from #{#entityName} e where e.id.meter.series = :series and e.id.date >= :first and e.id.date < :after group by e.id.date")
|
||||
List<DeltaPoint> points(@NonNull Series series, @NonNull ZonedDateTime first, @NonNull ZonedDateTime after);
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package de.ph87.data.series.data.varying;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import de.ph87.data.plot.axis.graph.GraphDivisionByZero;
|
||||
import de.ph87.data.plot.axis.graph.GraphOperation;
|
||||
import de.ph87.data.series.SeriesPoint;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@SuppressWarnings("unused") // used by repository query
|
||||
public class VaryingPoint implements SeriesPoint {
|
||||
@Data
|
||||
public class VaryingPoint implements SeriesPoint<VaryingPoint> {
|
||||
|
||||
public final ZonedDateTime date;
|
||||
|
||||
@ -25,6 +28,13 @@ public class VaryingPoint implements SeriesPoint {
|
||||
this.avg = varying.getAvg();
|
||||
}
|
||||
|
||||
private VaryingPoint(final ZonedDateTime date, final double min, final double avg, final double max) {
|
||||
this.date = date;
|
||||
this.min = min;
|
||||
this.avg = avg;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(final JsonGenerator jsonGenerator) throws IOException {
|
||||
jsonGenerator.writeNumber(date.toEpochSecond());
|
||||
@ -33,4 +43,22 @@ public class VaryingPoint implements SeriesPoint {
|
||||
jsonGenerator.writeNumber(avg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaryingPoint times(final double factor) {
|
||||
return new VaryingPoint(date, min * factor, avg * factor, max * factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return avg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaryingPoint combine(@NonNull final SeriesPoint<?> other, @NonNull final GraphOperation operation) throws GraphDivisionByZero {
|
||||
if (other instanceof final VaryingPoint otherVarying) {
|
||||
return new VaryingPoint(date, operation.apply(min, otherVarying.min), operation.apply(avg, otherVarying.avg), operation.apply(max, otherVarying.max));
|
||||
}
|
||||
return new VaryingPoint(date, operation.apply(min, other.getValue()), operation.apply(avg, other.getValue()), operation.apply(max, other.getValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user