diff --git a/Display/proxy.conf.json b/Display/proxy.conf.json index 7169091..a6d1a63 100644 --- a/Display/proxy.conf.json +++ b/Display/proxy.conf.json @@ -5,6 +5,12 @@ "logLevel": "debug", "changeOrigin": true }, + "/api/power": { + "target": "http://172.23.10.3", + "secure": false, + "logLevel": "debug", + "changeOrigin": true + }, "/api/hub": { "target": "ws://172.23.10.3:80", "ws": true diff --git a/Display/src/app/app-routing.module.ts b/Display/src/app/app-routing.module.ts index e14dcfb..3bdad6a 100644 --- a/Display/src/app/app-routing.module.ts +++ b/Display/src/app/app-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { WeatherChartsComponent } from './components/weather/charts/weather-charts.component'; +import { PowerChartsComponent } from './components/power/charts/power-charts.component'; const routes: Routes = [ { @@ -11,6 +12,10 @@ const routes: Routes = [ { path: 'weather-charts', component: WeatherChartsComponent + }, + { + path: 'power-charts', + component: PowerChartsComponent } ]; diff --git a/Display/src/app/app.module.ts b/Display/src/app/app.module.ts index 1a21456..4708910 100644 --- a/Display/src/app/app.module.ts +++ b/Display/src/app/app.module.ts @@ -22,7 +22,8 @@ import { DashboardComponent } from './components/dashboard/dashboard.component'; import { WeatherChartsComponent } from './components/weather/charts/weather-charts.component'; import { WeatherCurrentComponent } from './components/weather/current/weather-current.component'; import { AlmanacComponent } from './components/weather/almanac/almanac.component'; -import { PowerComponent } from './components/power/power.component'; +import { PowerComponent } from './components/power/current/power.component'; +import { PowerChartsComponent } from './components/power/charts/power-charts.component'; const config: SocketIoConfig = { url: '/', options: {} }; @@ -35,7 +36,8 @@ const config: SocketIoConfig = { url: '/', options: {} }; WeatherChartsComponent, WeatherCurrentComponent, AlmanacComponent, - PowerComponent + PowerComponent, + PowerChartsComponent ], imports: [ BrowserModule, diff --git a/Display/src/app/components/nav/nav.component.html b/Display/src/app/components/nav/nav.component.html index 24717d3..7dd0626 100644 --- a/Display/src/app/components/nav/nav.component.html +++ b/Display/src/app/components/nav/nav.component.html @@ -24,6 +24,15 @@ Weather Charts + + + + settings_power + + + Power Charts + + diff --git a/Display/src/app/components/power/charts/power-charts.component.html b/Display/src/app/components/power/charts/power-charts.component.html new file mode 100644 index 0000000..035ae2f --- /dev/null +++ b/Display/src/app/components/power/charts/power-charts.component.html @@ -0,0 +1,33 @@ + +
+
+ + + + + {{ item.value }} + + + + + + + + keyboard_arrow_down + + + + + + +
+
+
diff --git a/Display/src/app/components/power/charts/power-charts.component.scss b/Display/src/app/components/power/charts/power-charts.component.scss new file mode 100644 index 0000000..05163c3 --- /dev/null +++ b/Display/src/app/components/power/charts/power-charts.component.scss @@ -0,0 +1,27 @@ +.chart-content { + display: flex; + flex-flow: column; + height: 100%; + } + + #chart { + flex: 1 1 auto; + } + + .chart-header { + background-color: rgb(250, 250, 250); + padding: 0 20px; + flex: 0 0 auto; + } + + .chart-button-spacer { + margin-right: 20px; + } + + .selected:after { + content: ""; + display: block; + margin: 0 auto; + width: 100%; + border-bottom: 1px solid #673ab7; + } diff --git a/Display/src/app/components/power/charts/power-charts.component.spec.ts b/Display/src/app/components/power/charts/power-charts.component.spec.ts new file mode 100644 index 0000000..3e5b015 --- /dev/null +++ b/Display/src/app/components/power/charts/power-charts.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PowerChartsComponent } from './power-charts.component'; + +describe('PowerChartsComponent', () => { + let component: PowerChartsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PowerChartsComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PowerChartsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Display/src/app/components/power/charts/power-charts.component.ts b/Display/src/app/components/power/charts/power-charts.component.ts new file mode 100644 index 0000000..0485f5c --- /dev/null +++ b/Display/src/app/components/power/charts/power-charts.component.ts @@ -0,0 +1,198 @@ +import { Component, OnInit } from '@angular/core'; +import { Chart } from 'angular-highcharts'; +import { SeriesLineOptions, SeriesWindbarbOptions } from 'highcharts'; +import { HttpClient } from '@angular/common/http'; +import { PowerStatusGrouped } from '../../../models/power/power-status-grouped'; + +import * as moment from 'moment'; + +import * as Highcharts from 'highcharts'; +import HC_exporting from 'highcharts/modules/exporting'; +HC_exporting(Highcharts); + +enum TimeSpan { + Last24Hours, + Day, + Custom +} + +@Component({ + selector: 'app-power-charts', + templateUrl: './power-charts.component.html', + styleUrls: ['./power-charts.component.scss'] +}) +export class PowerChartsComponent implements OnInit { + + public chart: Chart; + public loading = true; + public timeSpanItems: { [value: number]: string } = {}; + public timeSpans: typeof TimeSpan = TimeSpan; + public maxDate: moment.Moment = moment().endOf('day'); + + private selectedTimeSpanValue: TimeSpan = TimeSpan.Last24Hours; + private selectedDateValue: moment.Moment = moment().startOf('day'); + + constructor(private httpClient: HttpClient) { } + + ngOnInit() { + this.timeSpanItems[TimeSpan.Last24Hours] = 'Last 24 hours'; + this.timeSpanItems[TimeSpan.Day] = 'Day'; + + this.loadChart(); + } + + public get selectedTimeSpan() { + return this.selectedTimeSpanValue; + } + + public set selectedTimeSpan(value) { + this.selectedTimeSpanValue = value; + + this.loadChart(); + } + + public get selectedDate() { + return this.selectedDateValue; + } + + public set selectedDate(value) { + this.selectedDateValue = value; + + this.loadChart(); + } + + public handleDateArrowClick(value: number) { + this.selectedDate = moment(this.selectedDate).add(value, 'day'); + } + + public isSelectedDateToday(): boolean { + const isToday = moment(this.selectedDate).startOf('day').isSame(moment().startOf('day')); + + return isToday; + } + + public resetToToday() { + this.selectedDate = moment().startOf('day'); + } + + public getSelectedDateDisplayString(): string { + return moment(this.selectedDate).format('LL'); + } + + private loadPowerChart(start: moment.Moment, end: moment.Moment) { + const startString = start.toISOString(); + const endString = end.toISOString(); + + const request = this.httpClient.get(`/api/power/status/history-grouped?start=${startString}&end=${endString}&bucketMinutes=5`); + + request.subscribe(data => { + const seriesData: Array = []; + + seriesData.push({ name: 'Generation', data: [], yAxis: 0, marker: { enabled: false }, tooltip: { valueSuffix: ' W' } } as SeriesLineOptions); + seriesData.push({ name: 'Consumption', data: [], yAxis: 0, marker: { enabled: false }, tooltip: { valueSuffix: ' W' } } as SeriesLineOptions); + + data.forEach(dataElement => { + const date = Date.parse(dataElement.bucket); + seriesData[0].data.push([date, dataElement.averageGeneration < 0 ? 0 : dataElement.averageGeneration]); + seriesData[1].data.push([date, dataElement.averageConsumption < 0 ? 0 : dataElement.averageConsumption]); + }); + + const title = this.selectedTimeSpan === TimeSpan.Last24Hours ? this.timeSpanItems[TimeSpan.Last24Hours] : this.getSelectedDateDisplayString(); + + this.chart = new Chart({ + chart: { + type: 'line', + zoomType: 'x' + }, + title: { + text: title + }, + credits: { + enabled: true + }, + xAxis: { + type: 'datetime', + dateTimeLabelFormats: { + millisecond: '%H:%M:%S.%L', + second: '%H:%M:%S', + minute: '%l:%M %P', + hour: '%l:%M %P', + day: '%b %e', + week: '%e. %b', + month: '%b \'%y', + year: '%Y' + } + }, + yAxis: [ + { + labels: { + format: '{value} W', + }, + title: { + text: 'Power' + } + } + ], + time: { + useUTC: false + }, + tooltip: { + valueDecimals: 2, + shared: true, + dateTimeLabelFormats: { + day: '%A, %b %e, %Y', + hour: '%A, %b %e, %H:%M', + millisecond: '%A, %b %e, %H:%M:%S.%L', + minute: '%A, %b %e, %l:%M %P', + month: '%B %Y', + second: '%A, %b %e, %H:%M:%S', + week: 'Week from %A, %b %e, %Y', + year: '%Y' + } + }, + series: seriesData, + legend: { + enabled: true + }, + exporting: { + enabled: true + } + }); + + this.loading = false; + }); + } + + private loadChart() { + let start: moment.Moment; + let end: moment.Moment; + + this.loading = true; + + if (this.chart) { + this.chart.ref$.subscribe(o => o.showLoading()); + } + + switch (this.selectedTimeSpan) { + case TimeSpan.Last24Hours: { + start = moment().subtract(24, 'hour'); + end = moment(); + + break; + } + + case TimeSpan.Day: { + start = moment(this.selectedDate).startOf('day'); + end = moment(this.selectedDate).endOf('day'); + + break; + } + + default: { + return; + } + } + + this.loadPowerChart(start, end); + } +} diff --git a/Display/src/app/components/power/power.component.html b/Display/src/app/components/power/current/power.component.html similarity index 93% rename from Display/src/app/components/power/power.component.html rename to Display/src/app/components/power/current/power.component.html index 7279f65..79c8dc9 100644 --- a/Display/src/app/components/power/power.component.html +++ b/Display/src/app/components/power/current/power.component.html @@ -9,7 +9,7 @@ Generation - {{ latestStatus.Generation < 0 ? 0 : latestStatus.Generation }} + {{ latestStatus.Generation < 0 ? 0 : latestStatus.Generation }} W @@ -17,7 +17,7 @@ Consumption - {{ latestStatus.Consumption < 0 ? 0 : latestStatus.Consumption }} + {{ latestStatus.Consumption < 0 ? 0 : latestStatus.Consumption }} W diff --git a/Display/src/app/components/power/power.component.scss b/Display/src/app/components/power/current/power.component.scss similarity index 100% rename from Display/src/app/components/power/power.component.scss rename to Display/src/app/components/power/current/power.component.scss diff --git a/Display/src/app/components/power/power.component.spec.ts b/Display/src/app/components/power/current/power.component.spec.ts similarity index 100% rename from Display/src/app/components/power/power.component.spec.ts rename to Display/src/app/components/power/current/power.component.spec.ts diff --git a/Display/src/app/components/power/power.component.ts b/Display/src/app/components/power/current/power.component.ts similarity index 76% rename from Display/src/app/components/power/power.component.ts rename to Display/src/app/components/power/current/power.component.ts index c21fd0b..424b6ff 100644 --- a/Display/src/app/components/power/power.component.ts +++ b/Display/src/app/components/power/current/power.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { PowerService } from '../../services/power/power.service'; -import { PowerStatus } from '../../models/power/power-status'; +import { PowerService } from 'src/app/services/power/power.service'; +import { PowerStatus } from 'src/app/models/power/power-status'; @Component({ selector: 'app-power', diff --git a/Display/src/app/components/weather/charts/weather-charts.component.html b/Display/src/app/components/weather/charts/weather-charts.component.html index 6347fcf..2a53901 100644 --- a/Display/src/app/components/weather/charts/weather-charts.component.html +++ b/Display/src/app/components/weather/charts/weather-charts.component.html @@ -1,6 +1,9 @@
+ diff --git a/Display/src/app/models/power/power-status-grouped.ts b/Display/src/app/models/power/power-status-grouped.ts new file mode 100644 index 0000000..d091bb3 --- /dev/null +++ b/Display/src/app/models/power/power-status-grouped.ts @@ -0,0 +1,5 @@ +export class PowerStatusGrouped { + bucket: string; + averageGeneration: number; + averageConsumption: number; +}