ADD: General Diagrams Component
This commit is contained in:
parent
af273889d8
commit
651aa1b023
@ -27,7 +27,7 @@
|
|||||||
</mat-drawer>
|
</mat-drawer>
|
||||||
<mat-drawer-content>
|
<mat-drawer-content>
|
||||||
<app-wettter-overview></app-wettter-overview>
|
<app-wettter-overview></app-wettter-overview>
|
||||||
<app-temperature></app-temperature>
|
<app-diagrams></app-diagrams>
|
||||||
</mat-drawer-content>
|
</mat-drawer-content>
|
||||||
</mat-drawer-container>
|
</mat-drawer-container>
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,11 @@ import {MatDrawer, MatDrawerContainer, MatDrawerContent} from '@angular/material
|
|||||||
import {StationList} from './station-list/station-list';
|
import {StationList} from './station-list/station-list';
|
||||||
import {WettterOverview} from './components/wettter-overview/wettter-overview';
|
import {WettterOverview} from './components/wettter-overview/wettter-overview';
|
||||||
import {TemperatureComponent} from './components/diagrams/temperature/temperature';
|
import {TemperatureComponent} from './components/diagrams/temperature/temperature';
|
||||||
|
import {Diagrams} from './components/diagrams/diagrams';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
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',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.css'
|
styleUrl: './app.css'
|
||||||
})
|
})
|
||||||
|
|||||||
3
src/app/components/diagrams/diagrams.css
Normal file
3
src/app/components/diagrams/diagrams.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.diagrams-container {
|
||||||
|
margin: 2em;
|
||||||
|
}
|
||||||
3
src/app/components/diagrams/diagrams.html
Normal file
3
src/app/components/diagrams/diagrams.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="diagrams-container">
|
||||||
|
<app-temperature #temperature [measurements]="measurements"></app-temperature>
|
||||||
|
</div>
|
||||||
22
src/app/components/diagrams/diagrams.spec.ts
Normal file
22
src/app/components/diagrams/diagrams.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
65
src/app/components/diagrams/diagrams.ts
Normal file
65
src/app/components/diagrams/diagrams.ts
Normal 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!)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,22 @@
|
|||||||
@if(measurements) {
|
@if(measurements) {
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-header>
|
<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-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<apx-chart
|
<apx-chart
|
||||||
@ -11,6 +26,7 @@
|
|||||||
[yaxis]="chartOptions.yaxis!"
|
[yaxis]="chartOptions.yaxis!"
|
||||||
[stroke]="chartOptions.stroke!"
|
[stroke]="chartOptions.stroke!"
|
||||||
[grid]="chartOptions.grid!"
|
[grid]="chartOptions.grid!"
|
||||||
|
[dataLabels]="chartOptions.dataLabels!"
|
||||||
[tooltip]="chartOptions.tooltip!"
|
[tooltip]="chartOptions.tooltip!"
|
||||||
[legend]="chartOptions.legend!"
|
[legend]="chartOptions.legend!"
|
||||||
[theme]="chartOptions.theme!"
|
[theme]="chartOptions.theme!"
|
||||||
|
|||||||
@ -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 {DefaultService, MeasurementListResponse, MeasurementResolution, StationListResponse} from '../../../api';
|
||||||
import {addDays, addHours, addMonths, addYears, startOfDay} from 'date-fns';
|
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 {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
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'
|
export type TimeResolution = '24h' | '7d' | '30d' | '1Y' | 'All'
|
||||||
|
|
||||||
@ -15,70 +18,64 @@ export type TimeResolution = '24h' | '7d' | '30d' | '1Y' | 'All'
|
|||||||
MatCardContent,
|
MatCardContent,
|
||||||
MatCardHeader,
|
MatCardHeader,
|
||||||
MatCardTitle,
|
MatCardTitle,
|
||||||
|
MatButtonToggleGroup,
|
||||||
|
MatButtonToggle,
|
||||||
|
MatIcon,
|
||||||
],
|
],
|
||||||
templateUrl: './temperature.html',
|
templateUrl: './temperature.html',
|
||||||
styleUrl: './temperature.css',
|
styleUrl: './temperature.css',
|
||||||
})
|
})
|
||||||
export class TemperatureComponent implements OnInit {
|
export class TemperatureComponent {
|
||||||
@Input() timeResolution: TimeResolution = '7d';
|
|
||||||
@Input() activeStations: StationListResponse[] = [];
|
|
||||||
|
|
||||||
resolution: MeasurementResolution = MeasurementResolution.Daily;
|
@Input() measurements: MeasurementListResponse | undefined;
|
||||||
measurements: MeasurementListResponse | undefined;
|
|
||||||
chartOptions: ApexOptions = {};
|
chartOptions: ApexOptions = {};
|
||||||
|
chartType: ChartType = 'line';
|
||||||
|
|
||||||
constructor(private api: DefaultService, private cdr: ChangeDetectorRef) {}
|
constructor(private api: DefaultService, private cdr: ChangeDetectorRef) {}
|
||||||
|
|
||||||
ngOnInit() {
|
switchChartType(type: 'bar' | 'area' | 'line') {
|
||||||
if (this.activeStations.length === 0) {
|
this.chartType = type;
|
||||||
this.api.listStationsStationsListGet().subscribe(stations => {
|
this.buildChart(this.measurements!)
|
||||||
this.activeStations = stations;
|
|
||||||
this.fetchMeasurements();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.fetchMeasurements();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
public buildChart(measurements: MeasurementListResponse) {
|
||||||
get timeIntervalStart(): string | undefined {
|
this.measurements = measurements;
|
||||||
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() {
|
|
||||||
if (!this.measurements) return;
|
if (!this.measurements) return;
|
||||||
|
|
||||||
|
const isBar = this.chartType === 'bar';
|
||||||
|
const isArea = this.chartType === 'area';
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'line',
|
type: this.chartType,
|
||||||
height: 280,
|
height: 280,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
animations: { enabled: true },
|
animations: { enabled: true },
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
|
stacked: false,
|
||||||
},
|
},
|
||||||
theme: { mode: 'dark' },
|
theme: { mode: 'dark' },
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false, // <- das hat gefehlt
|
||||||
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'smooth',
|
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 => ({
|
series: this.measurements.stations.map(s => ({
|
||||||
name: `${s.station.name} ${s.indoor ? '(innen)' : '(außen)'}`,
|
name: `${s.station.name} ${s.indoor ? '(innen)' : '(außen)'}`,
|
||||||
@ -108,13 +105,15 @@ export class TemperatureComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
|
shared: true,
|
||||||
|
intersect: false,
|
||||||
x: { format: 'dd.MM.yyyy HH:mm' },
|
x: { format: 'dd.MM.yyyy HH:mm' },
|
||||||
y: { formatter: (val: number) => `${val.toFixed(1)} °C` }
|
y: { formatter: (val: number) => `${val?.toFixed(1)} °C` }
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
labels: { colors: '#9e9e9e' }
|
labels: { colors: '#9e9e9e' }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.cdr.detectChanges()
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user