ADD: General Diagrams Component

This commit is contained in:
sebastian 2026-06-30 16:56:30 +02:00
parent af273889d8
commit 651aa1b023
8 changed files with 158 additions and 49 deletions

View File

@ -27,7 +27,7 @@
</mat-drawer>
<mat-drawer-content>
<app-wettter-overview></app-wettter-overview>
<app-temperature></app-temperature>
<app-diagrams></app-diagrams>
</mat-drawer-content>
</mat-drawer-container>

View File

@ -7,10 +7,11 @@ import {MatDrawer, MatDrawerContainer, MatDrawerContent} from '@angular/material
import {StationList} from './station-list/station-list';
import {WettterOverview} from './components/wettter-overview/wettter-overview';
import {TemperatureComponent} from './components/diagrams/temperature/temperature';
import {Diagrams} from './components/diagrams/diagrams';
@Component({
selector: 'app-root',
imports: [RouterOutlet, MatToolbar, MatIcon, MatIconButton, RouterLink, MatButton, MatDrawerContainer, MatDrawer, MatDrawerContent, StationList, WettterOverview, TemperatureComponent],
imports: [RouterOutlet, MatToolbar, MatIcon, MatIconButton, RouterLink, MatButton, MatDrawerContainer, MatDrawer, MatDrawerContent, StationList, WettterOverview, TemperatureComponent, Diagrams],
templateUrl: './app.html',
styleUrl: './app.css'
})

View File

@ -0,0 +1,3 @@
.diagrams-container {
margin: 2em;
}

View File

@ -0,0 +1,3 @@
<div class="diagrams-container">
<app-temperature #temperature [measurements]="measurements"></app-temperature>
</div>

View File

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Diagrams } from './diagrams';
describe('Diagrams', () => {
let component: Diagrams;
let fixture: ComponentFixture<Diagrams>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Diagrams],
}).compileComponents();
fixture = TestBed.createComponent(Diagrams);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,65 @@
import {ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core';
import {TemperatureComponent, TimeResolution} from './temperature/temperature';
import {DefaultService, MeasurementListResponse, MeasurementResolution, StationListResponse} from '../../api';
import {ApexOptions} from 'ng-apexcharts';
import {addDays, addHours, addMonths, addYears, startOfDay} from 'date-fns';
@Component({
selector: 'app-diagrams',
imports: [
TemperatureComponent
],
templateUrl: './diagrams.html',
styleUrl: './diagrams.css',
})
export class Diagrams {
timeResolution: TimeResolution = '7d';
@Input() activeStations: StationListResponse[] = [];
@ViewChild('temperature') temperatureDiagram?: TemperatureComponent
resolution: MeasurementResolution = MeasurementResolution.Daily;
measurements: MeasurementListResponse | undefined;
constructor(private api: DefaultService) {
}
ngOnInit() {
if (this.activeStations.length === 0) {
this.api.listStationsStationsListGet().subscribe(stations => {
this.activeStations = stations;
this.fetchMeasurements();
});
} else {
this.fetchMeasurements();
}
}
get timeIntervalStart(): string | undefined {
const now = new Date();
const map: Record<TimeResolution, Date | undefined> = {
'24h': addHours(now, -24),
'7d': startOfDay(addDays(now, -7)),
'30d': startOfDay(addMonths(now, -1)),
'1Y': startOfDay(addYears(now, -1)),
'All': undefined
};
return map[this.timeResolution]?.toISOString();
}
fetchMeasurements() {
const stationIds = this.activeStations.map(s => s.id);
const now = new Date().toISOString();
this.api.getMeasurementsMeasurementsGet(
stationIds, undefined, this.timeIntervalStart, now, this.resolution
).subscribe(data => {
this.measurements = data;
this.buildCharts();
});
}
buildCharts() {
this.temperatureDiagram?.buildChart(this.measurements!)
}
}

View File

@ -1,7 +1,22 @@
@if(measurements) {
<mat-card>
<mat-card-header>
<mat-card-title>Temperaturverlauf</mat-card-title>
<mat-card-title>Temperaturverlauf
<mat-button-toggle-group name="fontStyle" aria-label="Font Style" (valueChange)="switchChartType($event)" [value]="'line'">
<mat-button-toggle value="line">
<mat-icon>line_axis</mat-icon>
Line
</mat-button-toggle>
<mat-button-toggle value="bar">
<mat-icon>bar_chart</mat-icon>
Balken
</mat-button-toggle>
<mat-button-toggle value="area">
<mat-icon>area_chart</mat-icon>
Fläche
</mat-button-toggle>
</mat-button-toggle-group>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<apx-chart
@ -11,6 +26,7 @@
[yaxis]="chartOptions.yaxis!"
[stroke]="chartOptions.stroke!"
[grid]="chartOptions.grid!"
[dataLabels]="chartOptions.dataLabels!"
[tooltip]="chartOptions.tooltip!"
[legend]="chartOptions.legend!"
[theme]="chartOptions.theme!"

View File

@ -1,9 +1,12 @@
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
import {ChangeDetectorRef, Component, Input, OnInit, ViewChild} from '@angular/core';
import {DefaultService, MeasurementListResponse, MeasurementResolution, StationListResponse} from '../../../api';
import {addDays, addHours, addMonths, addYears, startOfDay} from 'date-fns';
import {ApexOptions, ChartComponent} from 'ng-apexcharts';
import {ApexOptions, ChartComponent, ChartType} from 'ng-apexcharts';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {MatProgressSpinner} from '@angular/material/progress-spinner';
import {MatButton} from '@angular/material/button';
import {MatButtonToggle, MatButtonToggleGroup} from '@angular/material/button-toggle';
import {MatIcon} from '@angular/material/icon';
export type TimeResolution = '24h' | '7d' | '30d' | '1Y' | 'All'
@ -15,70 +18,64 @@ export type TimeResolution = '24h' | '7d' | '30d' | '1Y' | 'All'
MatCardContent,
MatCardHeader,
MatCardTitle,
MatButtonToggleGroup,
MatButtonToggle,
MatIcon,
],
templateUrl: './temperature.html',
styleUrl: './temperature.css',
})
export class TemperatureComponent implements OnInit {
@Input() timeResolution: TimeResolution = '7d';
@Input() activeStations: StationListResponse[] = [];
export class TemperatureComponent {
resolution: MeasurementResolution = MeasurementResolution.Daily;
measurements: MeasurementListResponse | undefined;
@Input() measurements: MeasurementListResponse | undefined;
chartOptions: ApexOptions = {};
chartType: ChartType = 'line';
constructor(private api: DefaultService, private cdr: ChangeDetectorRef) {}
ngOnInit() {
if (this.activeStations.length === 0) {
this.api.listStationsStationsListGet().subscribe(stations => {
this.activeStations = stations;
this.fetchMeasurements();
});
} else {
this.fetchMeasurements();
}
switchChartType(type: 'bar' | 'area' | 'line') {
this.chartType = type;
this.buildChart(this.measurements!)
}
get timeIntervalStart(): string | undefined {
const now = new Date();
const map: Record<TimeResolution, Date | undefined> = {
'24h': addHours(now, -24),
'7d': startOfDay(addDays(now, -7)),
'30d': startOfDay(addMonths(now, -1)),
'1Y': startOfDay(addYears(now, -1)),
'All': undefined
};
return map[this.timeResolution]?.toISOString();
}
fetchMeasurements() {
const stationIds = this.activeStations.map(s => s.id);
const now = new Date().toISOString();
this.api.getMeasurementsMeasurementsGet(
stationIds, undefined, this.timeIntervalStart, now, this.resolution
).subscribe(data => {
this.measurements = data;
this.buildChart();
});
}
buildChart() {
public buildChart(measurements: MeasurementListResponse) {
this.measurements = measurements;
if (!this.measurements) return;
const isBar = this.chartType === 'bar';
const isArea = this.chartType === 'area';
this.chartOptions = {
chart: {
type: 'line',
type: this.chartType,
height: 280,
toolbar: { show: false },
animations: { enabled: true },
background: 'transparent',
stacked: false,
},
theme: { mode: 'dark' },
dataLabels: {
enabled: false, // <- das hat gefehlt
},
stroke: {
curve: 'smooth',
width: 2,
width: isBar ? 0 : 2,
},
fill: {
type: isArea ? 'gradient' : 'solid',
opacity: isArea ? 0.3 : (isBar ? 0.85 : 1),
gradient: isArea ? {
shadeIntensity: 1,
opacityFrom: 0.4,
opacityTo: 0.05,
stops: [0, 100]
} : undefined,
},
plotOptions: {
bar: {
columnWidth: '70%',
borderRadius: 3,
}
},
series: this.measurements.stations.map(s => ({
name: `${s.station.name} ${s.indoor ? '(innen)' : '(außen)'}`,
@ -108,13 +105,15 @@ export class TemperatureComponent implements OnInit {
},
tooltip: {
theme: 'dark',
shared: true,
intersect: false,
x: { format: 'dd.MM.yyyy HH:mm' },
y: { formatter: (val: number) => `${val.toFixed(1)} °C` }
y: { formatter: (val: number) => `${val?.toFixed(1)} °C` }
},
legend: {
labels: { colors: '#9e9e9e' }
},
};
this.cdr.detectChanges()
this.cdr.detectChanges();
}
}