From 458021456554843422a5ae1cf1b65e861f6697bf Mon Sep 17 00:00:00 2001 From: Noe Date: Mon, 22 Jan 2024 15:10:23 +0100 Subject: [PATCH] Added Aggregated data (weekly and monthly) Added set date button Added Zoom in Zoom out buttons --- csharp/App/Backend/Database/Db.cs | 4 + .../src/AggregationService/Aggregator.cs | 6 +- typescript/frontend-marios2/deploy_stage.sh | 1 + .../src/Resources/formatPower.tsx | 12 +- .../dashboards/Overview/chartOptions.tsx | 546 +++--- .../content/dashboards/Overview/overview.tsx | 1598 +++++++++++------ .../frontend-marios2/src/dataCache/data.ts | 23 - .../frontend-marios2/src/interfaces/Chart.tsx | 259 +-- 8 files changed, 1435 insertions(+), 1014 deletions(-) create mode 100755 typescript/frontend-marios2/deploy_stage.sh diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index ba94f4bb3..8d2eab32a 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -22,6 +22,10 @@ public static partial class Db .GetFiles() .OrderBy(f => f.LastWriteTime) .Last().Name; + + Console.WriteLine("latestdb is "+latestDb); + + //This is the file connection from the DbBackups folder var fileConnection = new SQLiteConnection("DbBackups/" + latestDb); diff --git a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs index 4fca504a3..71fa78c80 100644 --- a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs +++ b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs @@ -57,7 +57,7 @@ public static class Aggregator Console.WriteLine("-----------------------------------------------------------------------------------------------------------------"); // Wait until the next rounded hour - await Task.Delay(timeUntilNextDay); + //await Task.Delay(timeUntilNextDay); while (true) { @@ -68,8 +68,8 @@ public static class Aggregator dailyAggregatedData.Save("DailyData"); if (await dailyAggregatedData.PushToS3()) { - DeleteHourlyData("HourlyData",currentTime.ToUnixTime()); - dailyAggregatedData.DeleteDailyData("DailyData"); + //DeleteHourlyData("HourlyData",currentTime.ToUnixTime()); + //dailyAggregatedData.DeleteDailyData("DailyData"); } } diff --git a/typescript/frontend-marios2/deploy_stage.sh b/typescript/frontend-marios2/deploy_stage.sh new file mode 100755 index 000000000..d894cc979 --- /dev/null +++ b/typescript/frontend-marios2/deploy_stage.sh @@ -0,0 +1 @@ +npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/stage.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve' diff --git a/typescript/frontend-marios2/src/Resources/formatPower.tsx b/typescript/frontend-marios2/src/Resources/formatPower.tsx index a54dcc960..4a7a04d67 100644 --- a/typescript/frontend-marios2/src/Resources/formatPower.tsx +++ b/typescript/frontend-marios2/src/Resources/formatPower.tsx @@ -1,9 +1,12 @@ -export function formatPowerForGraph(value, max): { value: number } { +export function formatPowerForGraph(value, magnitude): { value: number } { if (isNaN(value)) { return null; } + + const prefixes = ['', 'k', 'M', 'G', 'T']; + let negative = false; - if (max > 1000) { + if (magnitude > 0) { value = parseFloat(value); if (value < 0) { @@ -11,10 +14,9 @@ export function formatPowerForGraph(value, max): { value: number } { negative = true; } - value = value / 1000; - - while (value >= 1000) { + while (magnitude > 0) { value /= 1000; + magnitude--; } } diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx index 2a738f01e..842c1422d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx @@ -1,105 +1,196 @@ -// chartOptions.ts - import { ApexOptions } from 'apexcharts'; import { chartInfoInterface } from 'src/interfaces/Chart'; import { findPower, formatPowerForGraph } from 'src/Resources/formatPower'; export const getChartOptions = ( chartInfo: chartInfoInterface, - type: string + type: string, + dateList: string[] ): ApexOptions => { - const chartOptions: ApexOptions = - type == 'daily' - ? { - chart: { - id: 'area-datetime', - toolbar: { - show: false - }, - type: 'area', - height: 350, - zoom: { - autoScaleYaxis: false - } + return type.includes('daily') + ? { + chart: { + id: 'area-datetime', + toolbar: { + show: false }, + type: 'area', + height: 350, + zoom: { + autoScaleYaxis: false + } + }, - dataLabels: { - enabled: false - }, + dataLabels: { + enabled: false + }, - fill: { - type: 'gradient', - gradient: { - shadeIntensity: 1, - opacityFrom: 0.7, - opacityTo: 0.9, - stops: [0, 100] + colors: ['#3498db', '#2ecc71', '#282828'], + xaxis: { + type: 'datetime', + labels: { + datetimeFormatter: { + year: 'yyyy', + month: "MMM 'yy", + day: 'dd MMM', + hour: 'HH:mm', + minute: 'mm' } - }, - //colors: ['#FF5733', '#3498db'], - colors: ['#3498db', '#2ecc71'], - //colors: ['#1abc9c', '#e91e63'], - xaxis: { - type: 'datetime', - labels: { - datetimeFormatter: { - year: 'yyyy', - month: "MMM 'yy", - day: 'dd MMM', - hour: 'HH:mm', - minute: 'mm' - } - } - }, - stroke: { - curve: 'smooth', - width: 2 - }, - yaxis: { - min: - chartInfo.min >= 0 - ? 0 - : chartInfo.max <= 0 - ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * - findPower(chartInfo.min).value - : undefined, - max: - chartInfo.min >= 0 - ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * - findPower(chartInfo.max).value - : chartInfo.max <= 0 - ? 0 - : undefined, - title: { - text: chartInfo.unit, - style: { - fontSize: '12px' + } + }, + stroke: { + curve: 'smooth', + width: 2 + }, + yaxis: + type === 'dailyoverview' + ? [ + { + seriesName: 'Grid Power', + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil( + chartInfo.min / findPower(chartInfo.min).value + ) * findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil( + chartInfo.max / findPower(chartInfo.max).value + ) * findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -190, + offsetX: 25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + chartInfo.magnitude + ).value.toString(); + } + } + }, + { + seriesName: 'Pv Production', + show: false, + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil( + chartInfo.min / findPower(chartInfo.min).value + ) * findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil( + chartInfo.max / findPower(chartInfo.max).value + ) * findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -190, + offsetX: 25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + chartInfo.magnitude + ).value.toString(); + } + } + }, + + { + seriesName: 'State Of Charge', + opposite: true, + + min: 0, + max: 100, + title: { + text: '(%)', + style: { + fontSize: '12px' + }, + offsetY: -190, + offsetX: -25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph(value, 0).value.toString(); + } + } + } + ] + : { + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil( + chartInfo.min / findPower(chartInfo.min).value + ) * findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil( + chartInfo.max / findPower(chartInfo.max).value + ) * findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -190, + offsetX: 25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + chartInfo.magnitude + ).value.toString(); + } + } }, - offsetY: -160, - offsetX: 25, - rotate: 0 - }, - labels: { - formatter: function (value: number) { - return formatPowerForGraph( - value, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toString(); - } - } - }, - tooltip: { - x: { - format: 'dd MMM HH:mm:ss' - }, - y: { - formatter: function (val, opts) { + tooltip: { + x: { + format: 'dd MMM HH:mm:ss' + }, + y: { + formatter: function (val, { seriesIndex, w }) { + const seriesName = w.config.series[seriesIndex].name; + if (seriesName === 'State Of Charge') { + return val.toFixed(2) + ' %'; + } else { return ( - formatPowerForGraph( - val, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toFixed(2) + + formatPowerForGraph(val, chartInfo.magnitude).value.toFixed( + 2 + ) + ' ' + chartInfo.unit ); @@ -107,216 +198,93 @@ export const getChartOptions = ( } } } - : type == 'monthly' - ? { - chart: { - height: 350, - type: 'bar' - }, - plotOptions: { - bar: { - borderRadius: 10, - dataLabels: { - position: 'top' // top, center, bottom - } - } - }, - dataLabels: { - enabled: true, - formatter: function (val) { - return val + '%'; - }, - offsetY: -20, - style: { - fontSize: '12px', - colors: ['#304758'] - } - }, - - xaxis: { - categories: [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ], - position: 'bottom', - axisBorder: { - show: false - }, - axisTicks: { - show: false - }, - tooltip: { - enabled: true - } - }, - - yaxis: { - min: - chartInfo.min >= 0 - ? 0 - : chartInfo.max <= 0 - ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * - findPower(chartInfo.min).value - : undefined, - max: - chartInfo.min >= 0 - ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * - findPower(chartInfo.max).value - : chartInfo.max <= 0 - ? 0 - : undefined, - title: { - text: chartInfo.unit, - style: { - fontSize: '12px' - }, - offsetY: -160, - offsetX: 25, - rotate: 0 - }, - labels: { - formatter: function (value: number) { - return formatPowerForGraph( - value, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toString(); - } + } + : { + chart: { + height: 380, + type: 'bar', + stacked: chartInfo.unit !== '(%)' && type != 'overview' + }, + plotOptions: { + bar: { + borderRadius: 10, + dataLabels: { + position: 'top' } } + }, + dataLabels: { + enabled: false, + formatter: function (val) { + return ( + formatPowerForGraph(val, chartInfo.magnitude).value.toFixed(2) + + ' ' + + chartInfo.unit + ); + }, + offsetY: -20, + style: { + fontSize: '12px', + colors: ['#304758'] + } + }, - // - // yaxis: { - // axisBorder: { - // show: false - // }, - // axisTicks: { - // show: false - // }, - // labels: { - // show: false, - // formatter: function (val) { - // return val + '%'; - // } - // } - // } - } - : { - chart: { - height: 350, - type: 'bar' + xaxis: { + categories: dateList, + position: 'bottom', + axisBorder: { + show: false }, - plotOptions: { - bar: { - borderRadius: 10, - dataLabels: { - position: 'top' // top, center, bottom - } + axisTicks: { + show: false + }, + tooltip: { + enabled: true + } + }, + yaxis: { + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * + findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * + findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: type === 'monthly' ? -176 : -178, + offsetX: 25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + chartInfo.magnitude + ).value.toString(); } - }, - dataLabels: { - enabled: true, - formatter: function (val, opts) { + } + }, + tooltip: { + y: { + formatter: function (val) { return ( - formatPowerForGraph( - val, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toFixed(2) + + formatPowerForGraph(val, chartInfo.magnitude).value.toFixed(2) + ' ' + chartInfo.unit ); - }, - offsetY: -20, - style: { - fontSize: '12px', - colors: ['#304758'] - } - }, - - xaxis: { - categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], - position: 'bottom', - axisBorder: { - show: false - }, - axisTicks: { - show: false - }, - tooltip: { - enabled: true - } - }, - yaxis: { - min: - chartInfo.min >= 0 - ? 0 - : chartInfo.max <= 0 - ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * - findPower(chartInfo.min).value - : undefined, - max: - chartInfo.min >= 0 - ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * - findPower(chartInfo.max).value - : chartInfo.max <= 0 - ? 0 - : undefined, - title: { - text: chartInfo.unit, - style: { - fontSize: '12px' - }, - offsetY: -160, - offsetX: 25, - rotate: 0 - }, - labels: { - formatter: function (value: number) { - return formatPowerForGraph( - value, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toString(); - } - } - }, - tooltip: { - y: { - formatter: function (val, opts) { - return ( - formatPowerForGraph( - val, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toFixed(2) + - ' ' + - chartInfo.unit - ); - } } } - // yaxis: { - // axisBorder: { - // show: false - // }, - // axisTicks: { - // show: false - // }, - // labels: { - // show: false, - // formatter: function (val) { - // return val + '%'; - // } - // } - // } - }; - - return chartOptions; + } + }; }; diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index f291d8b96..6b0d8caa1 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -1,4 +1,4 @@ -import { Box, Card, Container, Grid, Typography } from '@mui/material'; +import { Box, Card, Container, Grid, Modal, Typography } from '@mui/material'; import ReactApexChart from 'react-apexcharts'; import React, { useEffect, useState } from 'react'; import { I_S3Credentials } from 'src/interfaces/S3Types'; @@ -14,53 +14,42 @@ import Button from '@mui/material/Button'; import { FormattedMessage } from 'react-intl'; import CircularProgress from '@mui/material/CircularProgress'; import { TimeSpan, UnixTime } from '../../../dataCache/time'; +import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import dayjs from 'dayjs'; interface OverviewProps { s3Credentials: I_S3Credentials; } -function Overview(props: OverviewProps) { - // const numOfPointsToFetch = 100; - // const [timeRange, setTimeRange] = useState( - // createTimes( - // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), - // numOfPointsToFetch - // ) - // ); - // - // const cache = useMemo(() => { - // return new DataCache( - // fetchData, - // TimeSpan.fromSeconds(2), - // props.s3Credentials - // ); - // }, []); +const computeLast7Days = (): string[] => { + const currentDate = new Date(); + const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + let currentDayIndex = currentDate.getDay() + 1; + const last7Days = []; - //const times$ = useMemo(() => new BehaviorSubject(timeRange), []); + for (let i = 0; i <= 7; i++) { + last7Days.push(daysOfWeek[currentDayIndex]); + currentDayIndex++; + if (currentDayIndex > 6) { + currentDayIndex = 0; + } + } + return last7Days; +}; + +function Overview(props: OverviewProps) { const [dailyData, setDailyData] = useState(true); const [weeklyData, setWeeklyData] = useState(false); const [monthlyData, setMonthlyData] = useState(false); const [loading, setLoading] = useState(true); const [chartState, setChartState] = useState(0); - - // const [chartData, setChartData] = useState({ - // soc: [], - // temperature: [], - // dcPower: [], - // gridPower: [], - // pvProduction: [], - // dcBusVoltage: [] - // }); - // - // const [chartOverview, setChartOverview] = useState({ - // soc: { magnitude: 0, unit: '', min: 0, max: 0 }, - // temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, - // dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - // gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - // pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, - // dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } - // }); - + const [monthlyDateList, setMonthlyDateList] = useState([]); + const [weeklyDateList, setWeeklyDateList] = useState([]); + const [isDateModalOpen, setIsDateModalOpen] = useState(false); + const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false); + const [dateSelectionError, setDateSelectionError] = useState(''); + const [dateOpen, setDateOpen] = useState(false); const [dailyDataArray, setDailyDataArray] = useState< { chartData: chartDataInterface; @@ -68,22 +57,18 @@ function Overview(props: OverviewProps) { }[] >([]); - const [chartAggregatedData, setChartAggregatedData] = - useState({ - soc: [{ data: [] }], - pvProduction: [{ data: [] }], - dcPower: [{ data: [] }] - }); + const [startDate, setStartDate] = useState(dayjs().add(-1, 'day')); + const [endDate, setEndDate] = useState(dayjs()); - const [chartAggregatedOverview, setChartAggregatedOverview] = - useState({ - soc: { magnitude: 0, unit: '', min: 0, max: 0 }, - temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } - }); + const [weeklyDataArray, setWeeklyDataArray] = useState<{ + chartData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + }>({ chartData: null, chartOverview: null }); + + const [monthlyDataArray, setMonthlyDataArray] = useState<{ + chartData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + }>({ chartData: null, chartOverview: null }); useEffect(() => { const resultPromise: Promise<{ @@ -97,9 +82,6 @@ function Overview(props: OverviewProps) { resultPromise .then((result) => { - // setChartData(result.chartData); - // setChartOverview(result.chartOverview); - setDailyDataArray((prevData) => prevData.concat({ chartData: result.chartData, @@ -111,40 +93,12 @@ function Overview(props: OverviewProps) { .catch((error) => { console.error('Error:', error); }); - - // const left = cache.gotData.pipe( - // startWith(UnixTime.fromTicks(0)), - // throttleTime(200, undefined, { leading: true, trailing: true }) - // ); - // - // const right = times$.pipe( - // tap((times) => { - // //console.log(times); - // }) - // ); - // - // const combined = combineLatest([left, right]); - // - // const subscription = combined.subscribe(([_, times]) => { - // const timeSeries = cache.getSeries(times); - // const result: { - // chartData: chartDataInterface; - // chartOverview: overviewInterface; - // } = transformInputToDailyData(timeSeries); - // - // setChartData(result.chartData); - // setChartOverview(result.chartOverview); - // }); - // return () => subscription.unsubscribe(); }, []); const handleBeforeZoom = (chartContext, { xaxis }) => { const startX = parseInt(xaxis.min) / 1000; const endX = parseInt(xaxis.max) / 1000; - //console.log(UnixTime.fromTicks(startX)); - //console.log(endX); - setLoading(true); const resultPromise: Promise<{ chartData: chartDataInterface; @@ -157,9 +111,6 @@ function Overview(props: OverviewProps) { resultPromise .then((result) => { - //setChartData(result.chartData); - //setChartOverview(result.chartOverview); - setDailyDataArray((prevData) => prevData.concat({ chartData: result.chartData, @@ -173,52 +124,12 @@ function Overview(props: OverviewProps) { .catch((error) => { console.error('Error:', error); }); - - // const times = createTimes( - // TimeRange.fromTimes(UnixTime.fromTicks(startX), UnixTime.fromTicks(endX)), - // numOfPointsToFetch - // ); - // - // cache.getSeries(times); - // times$.next(times); }; const handle24HourData = () => { setDailyData(true); setWeeklyData(false); setMonthlyData(false); - // const times = createTimes( - // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), - // numOfPointsToFetch - // ); - // cache.getSeries(times); - // times$.next(times); - // setLoading(true); - // const resultPromise: Promise<{ - // chartData: chartDataInterface; - // chartOverview: overviewInterface; - // }> = transformInputToDailyData( - // props.s3Credentials, - // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start, - // UnixTime.now() - // ); - // - // resultPromise - // .then((result) => { - // //setChartData(result.chartData); - // //setChartOverview(result.chartOverview); - // setDailyDataArray((prevData) => - // prevData.concat({ - // chartData: result.chartData, - // chartOverview: result.chartOverview - // }) - // ); - // setLoading(false); - // setChartState(dailyDataArray.length); - // }) - // .catch((error) => { - // console.error('Error:', error); - // }); setChartState(0); }; @@ -227,26 +138,85 @@ function Overview(props: OverviewProps) { setWeeklyData(true); setMonthlyData(false); + if (weeklyDataArray.chartData != null) { + return; + } + setLoading(true); + const resultPromise: Promise<{ chartAggregatedData: chartAggregatedDataInterface; chartOverview: overviewInterface; - }> = transformInputToAggregatedData(props.s3Credentials); + }> = transformInputToAggregatedData(props.s3Credentials, 'weekly'); resultPromise .then((result) => { - setChartAggregatedData(result.chartAggregatedData); - setChartAggregatedOverview(result.chartOverview); + setWeeklyDataArray({ + chartData: result.chartAggregatedData, + chartOverview: result.chartOverview + }); + //setChartAggregatedData(result.chartAggregatedData); + //setChartAggregatedOverview(result.chartOverview); + setWeeklyDateList(computeLast7Days()); + setLoading(false); }) .catch((error) => { console.error('Error:', error); }); + }; - // const times = createTimes( - // UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)), - // numOfPointsToFetch - // ); - // cache.getSeries(times); - // times$.next(times); + const handleSetDate = () => { + setDateOpen(true); + setIsDateModalOpen(true); + }; + + const handleOkOnErrorDateModal = () => { + setErrorDateModalOpen(false); + }; + + const handleCancel = () => { + setIsDateModalOpen(false); + setDateOpen(false); + }; + + const handleConfirm = () => { + setIsDateModalOpen(false); + setDateOpen(false); + + if (endDate.isAfter(dayjs())) { + setDateSelectionError('You cannot ask for future data'); + setErrorDateModalOpen(true); + return; + } else if (startDate.isAfter(endDate)) { + setDateSelectionError('Εnd date must precede start date'); + setErrorDateModalOpen(true); + return; + } + + setLoading(true); + const resultPromise: Promise<{ + chartData: chartDataInterface; + chartOverview: overviewInterface; + }> = transformInputToDailyData( + props.s3Credentials, + UnixTime.fromTicks(startDate.unix()), + UnixTime.fromTicks(endDate.unix()) + ); + + resultPromise + .then((result) => { + setDailyDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setLoading(false); + setChartState(dailyDataArray.length); + }) + .catch((error) => { + console.error('Error:', error); + }); }; const handleGoBack = () => { @@ -265,38 +235,154 @@ function Overview(props: OverviewProps) { setDailyData(false); setWeeklyData(false); setMonthlyData(true); - // const times = createTimes( - // UnixTime.now().rangeBefore(TimeSpan.fromWeeks(4)), - // numOfPointsToFetch - // ); - // cache.getSeries(times); - // times$.next(times); + + if (monthlyDataArray.chartData != null) { + return; + } + setLoading(true); + + const resultPromise: Promise<{ + chartAggregatedData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + dateList: string[]; + }> = transformInputToAggregatedData(props.s3Credentials, 'monthly'); + + resultPromise + .then((result) => { + setMonthlyDataArray({ + chartData: result.chartAggregatedData, + chartOverview: result.chartOverview + }); + //setChartAggregatedData(result.chartAggregatedData); + //setChartAggregatedOverview(result.chartOverview); + setMonthlyDateList(result.dateList); + setLoading(false); + }) + .catch((error) => { + console.error('Error:', error); + }); }; const renderGraphs = () => { - if (loading) { - // Display a loading indicator while waiting for the data - return ( - - - - Fetching data... - - - ); - } - return ( + {isErrorDateModalOpen && ( + {}}> + + + {dateSelectionError} + + + + + + )} + + {isDateModalOpen && ( + + {}}> + + setStartDate(newDate)} + sx={{ + marginTop: 2 + }} + /> + + setEndDate(newDate)} + sx={{ + marginTop: 2 + }} + /> + +
+ + + +
+
+
+
+ )} + {dailyData && ( + <> + + + )} - - + + + + )} + + {loading && ( + - - + + + Fetching data... + + + )} - - - - - - - + {dailyData && ( + - - - - - - - - - + - + > + + + + + + + + + + - {dailyData && ( - - )} - - {weeklyData && ( - - )} - - {monthlyData && ( - - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {dailyData && ( - - )} + ]} + height={420} + /> + + + + )} - {weeklyData && ( - - )} - - {monthlyData && ( - - )} - - - - - + + + + + + + + + + + + + {weeklyData && ( + + )} + + {monthlyData && ( + + )} + + + + )} + + + - - - - - - - - - - - - - - - - - - - - - + > + + + + + + + - - - {dailyData && ( - - )} + }} + series={[dailyDataArray[chartState].chartData.soc]} + type="area" + height={400} + /> + )} - {weeklyData && ( - - )} + {weeklyData && ( + + )} - {monthlyData && ( - - )} - - - - - + )} + + + + - - - - - - - - - - + > + + + + + + + + + + + {dailyData && ( + + )} + + {weeklyData && ( + + )} + + {monthlyData && ( + + )} + + + + + + + + + + + + + + + + + + {dailyData && ( + + )} + + {weeklyData && ( + + )} + + {monthlyData && ( + + )} + + + + + + + + + + + + + + + {dailyData && ( + + )} + {weeklyData && ( + + )} + + {monthlyData && ( + + )} + + + + + {dailyData && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} - + )}
); diff --git a/typescript/frontend-marios2/src/dataCache/data.ts b/typescript/frontend-marios2/src/dataCache/data.ts index 3bd7306e5..62791170a 100644 --- a/typescript/frontend-marios2/src/dataCache/data.ts +++ b/typescript/frontend-marios2/src/dataCache/data.ts @@ -1,30 +1,7 @@ import { Maybe } from 'yup'; import { Timestamped } from './types'; -import { isDefined } from './utils/maybe'; import { I_CsvEntry } from 'src/content/dashboards/Log/graph.util'; export type DataRecord = Record; export type DataPoint = Timestamped>; export type RecordSeries = Array; -export type DataRecordSeries = Array; -export type PointSeries = Array>>; -export type DataSeries = Array>; - -export function getPoints( - recordSeries: RecordSeries, - series: keyof DataRecord -): PointSeries { - return recordSeries.map((p) => ({ - time: p.time, - value: isDefined(p.value) ? p.value[series] : undefined - })); -} - -export function getData( - recordSeries: RecordSeries, - series: keyof DataRecord -): DataSeries { - return recordSeries.map((p) => - isDefined(p.value) ? p.value[series] : undefined - ); -} diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index 6f4a1238e..0e4b896ba 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -1,4 +1,3 @@ -import { ApexOptions } from 'apexcharts'; import dayjs from 'dayjs'; import { fetchDailyData, @@ -15,6 +14,7 @@ export interface overviewInterface { gridPower: chartInfoInterface; pvProduction: chartInfoInterface; dcBusVoltage: chartInfoInterface; + overview: chartInfoInterface; } export interface chartInfoInterface { @@ -25,18 +25,22 @@ export interface chartInfoInterface { } export interface chartAggregatedDataInterface { - soc: [{ data: number[] }]; - pvProduction: [{ data: number[] }]; - dcPower: [{ data: number[] }]; + minsoc: { name: string; data: number[] }; + maxsoc: { name: string; data: number[] }; + pvProduction: { name: string; data: number[] }; + dcChargingPower: { name: string; data: number[] }; + dcDischargingPower: { name: string; data: number[] }; + gridImportPower: { name: string; data: number[] }; + gridExportPower: { name: string; data: number[] }; } export interface chartDataInterface { - soc: ApexOptions['series']; - temperature: ApexOptions['series']; - dcPower: ApexOptions['series']; - gridPower: ApexOptions['series']; - pvProduction: ApexOptions['series']; - dcBusVoltage: ApexOptions['series']; + soc: { name: string; data: number[] }; + temperature: { name: string; data: number[] }; + dcPower: { name: string; data: number[] }; + gridPower: { name: string; data: number[] }; + pvProduction: { name: string; data: number[] }; + dcBusVoltage: { name: string; data: number[] }; } export const transformInputToDailyData = async ( @@ -61,12 +65,12 @@ export const transformInputToDailyData = async ( ]; const chartData: chartDataInterface = { - soc: [], - temperature: [], - dcPower: [], - gridPower: [], - pvProduction: [], - dcBusVoltage: [] + soc: { name: 'State Of Charge', data: [] }, + temperature: { name: 'Battery Temperature', data: [] }, + dcPower: { name: 'Battery Power', data: [] }, + gridPower: { name: 'Grid Power', data: [] }, + pvProduction: { name: 'Pv Production', data: [] }, + dcBusVoltage: { name: 'DC Bus Voltage', data: [] } }; const chartOverview: overviewInterface = { @@ -75,7 +79,8 @@ export const transformInputToDailyData = async ( dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } + dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }, + overview: { magnitude: 0, unit: '', min: 0, max: 0 } }; pathsToSearch.forEach((path) => { @@ -88,25 +93,14 @@ export const transformInputToDailyData = async ( }; }); - // const currentTimestamp = UnixTime.now(); - // const twentyFourHoursAgoRange = currentTimestamp.rangeBefore( - // TimeSpan.fromDays(1) - // ); - let startTimestampToNum = Number(startTimestamp); if (startTimestampToNum % 2 != 0) { startTimestampToNum += 1; } let startUnixTime = UnixTime.fromTicks(startTimestampToNum); - let diff = endTimestamp.ticks - startUnixTime.ticks; - //console.log('Current Unix Timestamp:', currentTimestamp); - //console.log('Unix Timestamp 24 hours ago (even):', startTimestamp); - while (startUnixTime < endTimestamp) { - // console.log('Current day:', currentDay.format('YYYY-MM-DD')); - let result = await Promise.resolve(fetchData(startUnixTime, s3Credentials)); if ( result === FetchResult.notAvailable || @@ -148,40 +142,6 @@ export const transformInputToDailyData = async ( console.log('Try next timestamp: ', startUnixTime); } - //console.log(input); - // input.forEach((item) => { - // const csvContent = item.value; - // pathsToSearch.forEach((path) => { - // if (csvContent) { - // const timestamp = item.time.ticks * 1000; - // - // const adjustedTimestamp = new Date(timestamp); - // adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1); - // - // if (csvContent[path]) { - // const value = csvContent[path]; - // - // if (value.value < overviewData[path].min) { - // overviewData[path].min = value.value; - // } - // - // if (value.value > overviewData[path].max) { - // overviewData[path].max = value.value; - // } - // - // data[path].push([adjustedTimestamp, value.value]); - // } else { - // //data[path].push([adjustedTimestamp, null]); - // } - // } else { - // // data[path].push([ - // // addHours(new Date(item.time.ticks * 1000), 2), - // // null - // // ]); - // } - // }); - // }); - pathsToSearch.forEach((path) => { let value = Math.max( Math.abs(overviewData[path].max), @@ -196,11 +156,11 @@ export const transformInputToDailyData = async ( value /= 1000; magnitude++; } - overviewData[path].magnitude = prefixes[magnitude]; + overviewData[path].magnitude = magnitude; }); let path = '/Battery/Soc'; - chartData.soc = [{ name: 'State of Charge', data: data[path] }]; + chartData.soc.data = data[path]; chartOverview.soc = { unit: '(%)', @@ -210,7 +170,7 @@ export const transformInputToDailyData = async ( }; path = '/Battery/Temperature'; - chartData.temperature = [{ name: 'Battery Temperature:', data: data[path] }]; + chartData.temperature.data = data[path]; chartOverview.temperature = { unit: '(°C)', @@ -220,45 +180,63 @@ export const transformInputToDailyData = async ( }; path = '/Battery/Dc/Power'; - chartData.dcPower = [{ name: 'Battery Power', data: data[path] }]; + chartData.dcPower.data = data[path]; chartOverview.dcPower = { magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', + unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')', min: overviewData[path].min, max: overviewData[path].max }; path = '/GridMeter/Ac/Power/Active'; - chartData.gridPower = [{ name: 'Grid Power', data: data[path] }]; + chartData.gridPower.data = data[path]; chartOverview.gridPower = { magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', + unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')', min: overviewData[path].min, max: overviewData[path].max }; path = '/PvOnDc/Dc/Power'; - chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }]; + chartData.pvProduction.data = data[path]; chartOverview.pvProduction = { magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', + unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')', min: overviewData[path].min, max: overviewData[path].max }; path = '/DcDc/Dc/Link/Voltage'; - chartData.dcBusVoltage = [{ name: 'DC Bus Voltage', data: data[path] }]; - + chartData.dcBusVoltage.data = data[path]; chartOverview.dcBusVoltage = { magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'V' + ')', + unit: '(' + prefixes[overviewData[path].magnitude] + 'V' + ')', min: overviewData[path].min, max: overviewData[path].max }; + chartOverview.overview = { + magnitude: Math.max( + overviewData['/GridMeter/Ac/Power/Active'].magnitude, + overviewData['/PvOnDc/Dc/Power'].magnitude + ), + unit: '(kW)', + min: Math.min( + overviewData['/GridMeter/Ac/Power/Active'].min, + overviewData['/PvOnDc/Dc/Power'].min + ), + max: Math.max( + overviewData['/GridMeter/Ac/Power/Active'].max, + overviewData['/PvOnDc/Dc/Power'].max + ) + }; + + console.log('min = ', chartOverview.overview.min); + console.log('max = ', chartOverview.overview.max); + return { chartData: chartData, chartOverview: chartOverview @@ -266,32 +244,42 @@ export const transformInputToDailyData = async ( }; export const transformInputToAggregatedData = async ( - s3Credentials: I_S3Credentials + s3Credentials: I_S3Credentials, + type: string ): Promise<{ chartAggregatedData: chartAggregatedDataInterface; chartOverview: overviewInterface; + dateList: string[]; }> => { const data = {}; const overviewData = {}; - const prefixes = ['', 'k', 'M', 'G', 'T']; const MAX_NUMBER = 9999999; + const dateList = []; let currentDate = dayjs().add(1, 'day'); - let currentDay = currentDate.subtract(1, 'week'); + let currentDay = + type === 'weekly' + ? currentDate.subtract(1, 'week') + : currentDate.subtract(1, 'month'); const pathsToSearch = [ - '/AvgSoc', - '/AvgPvPower', - '/BatteryPowerAverage', - '/GridMeter/Ac/Power/Active', - '/PvOnDc/Dc/Power', - '/DcDc/Dc/Link/Voltage' + '/MinSoc', + '/MaxSoc', + '/SumPvPower', + '/SumDischargingBatteryPower', + '/SumChargingBatteryPower', + '/SumGridImportPower', + '/SumGridExportPower' ]; const chartAggregatedData: chartAggregatedDataInterface = { - soc: [{ data: [] }], - pvProduction: [{ data: [] }], - dcPower: [{ data: [] }] + minsoc: { name: 'min SOC', data: [] }, + maxsoc: { name: 'max SOC', data: [] }, + pvProduction: { name: 'Pv Power', data: [] }, + dcChargingPower: { name: 'Charging Battery Power', data: [] }, + dcDischargingPower: { name: 'Discharging Battery Power', data: [] }, + gridImportPower: { name: 'Grid Import Power', data: [] }, + gridExportPower: { name: 'Grid Export Power', data: [] } }; const chartOverview: overviewInterface = { @@ -300,7 +288,8 @@ export const transformInputToAggregatedData = async ( dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } + dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }, + overview: { magnitude: 0, unit: '', min: 0, max: 0 } }; pathsToSearch.forEach((path) => { @@ -313,11 +302,23 @@ export const transformInputToAggregatedData = async ( }; }); - while (currentDay.isBefore(currentDate)) { + let fake_data = [ + 'temp0', + 'temp1', + 'temp2', + 'temp3', + 'temp4', + 'temp5', + 'temp6' + ]; + + //while (currentDay.isBefore(currentDate)) { + for (let i = 0; i < 7; i++) { // console.log('Current day:', currentDay.format('YYYY-MM-DD')); let result = await Promise.resolve( - fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials) + //fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials) + fetchDailyData(fake_data[i], s3Credentials) ); if ( result === FetchResult.notAvailable || @@ -326,6 +327,7 @@ export const transformInputToAggregatedData = async ( // Handle not available or try later case } else { console.log('Received data:', result); + dateList.push(currentDay.format('DD-MM')); pathsToSearch.forEach((path) => { if (result[path]) { if (result[path].value < overviewData[path].min) { @@ -357,11 +359,14 @@ export const transformInputToAggregatedData = async ( value /= 1000; magnitude++; } - overviewData[path].magnitude = prefixes[magnitude]; + overviewData[path].magnitude = magnitude; }); - let path = '/AvgSoc'; - chartAggregatedData.soc[0].data = data[path]; + let path = '/MinSoc'; + chartAggregatedData.minsoc.data = data[path]; + + path = '/MaxSoc'; + chartAggregatedData.maxsoc.data = data[path]; chartOverview.soc = { unit: '(%)', @@ -370,28 +375,78 @@ export const transformInputToAggregatedData = async ( max: 100 }; - path = '/AvgPvPower'; - chartAggregatedData.pvProduction[0].data = data[path]; + path = '/SumPvPower'; + chartAggregatedData.pvProduction.data = data[path]; chartOverview.pvProduction = { magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', + unit: '(kWh)', min: overviewData[path].min, max: overviewData[path].max }; - path = '/BatteryPowerAverage'; - chartAggregatedData.dcPower[0].data = data[path]; + path = '/SumChargingBatteryPower'; + chartAggregatedData.dcChargingPower.data = data[path]; + + path = '/SumDischargingBatteryPower'; + chartAggregatedData.dcDischargingPower.data = data[path]; chartOverview.dcPower = { - magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', - min: overviewData[path].min, - max: overviewData[path].max + magnitude: Math.max( + overviewData['/SumChargingBatteryPower'].magnitude, + overviewData['/SumDischargingBatteryPower'].magnitude + ), + unit: '(kWh)', + min: Math.min( + overviewData['/SumChargingBatteryPower'].min, + overviewData['/SumDischargingBatteryPower'].min + ), + max: Math.max( + overviewData['/SumChargingBatteryPower'].max, + overviewData['/SumDischargingBatteryPower'].max + ) + }; + + path = '/SumGridImportPower'; + chartAggregatedData.gridImportPower.data = data[path]; + + path = '/SumGridExportPower'; + chartAggregatedData.gridExportPower.data = data[path]; + + chartOverview.gridPower = { + magnitude: Math.max( + overviewData['/SumGridImportPower'].magnitude, + overviewData['/SumGridExportPower'].magnitude + ), + unit: '(kWh)', + min: Math.min( + overviewData['/SumGridImportPower'].min, + overviewData['/SumGridExportPower'].min + ), + max: Math.max( + overviewData['/SumGridImportPower'].max, + overviewData['/SumGridExportPower'].max + ) + }; + + chartOverview.overview = { + magnitude: 0, + unit: '(kWh)', + min: Math.min( + overviewData['/SumGridImportPower'].min, + overviewData['/SumGridExportPower'].min, + overviewData['/SumPvPower'].min + ), + max: Math.max( + overviewData['/SumGridImportPower'].max, + overviewData['/SumGridExportPower'].max, + overviewData['/SumPvPower'].max + ) }; return { chartAggregatedData: chartAggregatedData, - chartOverview: chartOverview + chartOverview: chartOverview, + dateList: dateList }; };