Aggregated data
This commit is contained in:
parent
91da191874
commit
efebd4221b
|
@ -22,6 +22,7 @@
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"cytoscape": "^3.26.0",
|
"cytoscape": "^3.26.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"linq-to-typescript": "^11.0.0",
|
"linq-to-typescript": "^11.0.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
@ -7103,6 +7104,11 @@
|
||||||
"url": "https://opencollective.com/date-fns"
|
"url": "https://opencollective.com/date-fns"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -23246,6 +23252,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||||
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
|
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"cytoscape": "^3.26.0",
|
"cytoscape": "^3.26.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"linq-to-typescript": "^11.0.0",
|
"linq-to-typescript": "^11.0.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
|
|
@ -5,6 +5,38 @@ import { DataRecord } from 'src/dataCache/data';
|
||||||
import { S3Access } from 'src/dataCache/S3/S3Access';
|
import { S3Access } from 'src/dataCache/S3/S3Access';
|
||||||
import { parseCsv } from '../Log/graph.util';
|
import { parseCsv } from '../Log/graph.util';
|
||||||
|
|
||||||
|
export const fetchDailyData = (
|
||||||
|
date: string,
|
||||||
|
s3Credentials?: I_S3Credentials
|
||||||
|
): Promise<FetchResult<DataRecord>> => {
|
||||||
|
const s3Path = `${date}.csv`;
|
||||||
|
if (s3Credentials && s3Credentials.s3Bucket) {
|
||||||
|
const s3Access = new S3Access(
|
||||||
|
s3Credentials.s3Bucket,
|
||||||
|
s3Credentials.s3Region,
|
||||||
|
s3Credentials.s3Provider,
|
||||||
|
s3Credentials.s3Key,
|
||||||
|
s3Credentials.s3Secret
|
||||||
|
);
|
||||||
|
return s3Access
|
||||||
|
.get(s3Path)
|
||||||
|
.then(async (r) => {
|
||||||
|
if (r.status === 404) {
|
||||||
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
|
} else if (r.status === 200) {
|
||||||
|
const text = await r.text();
|
||||||
|
//console.log(parseCsv(text));
|
||||||
|
return parseCsv(text);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
return Promise.resolve(FetchResult.tryLater);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchData = (
|
export const fetchData = (
|
||||||
timestamp: UnixTime,
|
timestamp: UnixTime,
|
||||||
s3Credentials?: I_S3Credentials
|
s3Credentials?: I_S3Credentials
|
||||||
|
|
|
@ -159,20 +159,56 @@ export const getChartOptions = (
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
yaxis: {
|
yaxis: {
|
||||||
axisBorder: {
|
min:
|
||||||
show: false
|
chartInfo.min >= 0
|
||||||
},
|
? 0
|
||||||
axisTicks: {
|
: chartInfo.max <= 0
|
||||||
show: false
|
? 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: {
|
labels: {
|
||||||
show: false,
|
formatter: function (value: number) {
|
||||||
formatter: function (val) {
|
return formatPowerForGraph(
|
||||||
return val + '%';
|
value,
|
||||||
|
Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min))
|
||||||
|
).value.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// yaxis: {
|
||||||
|
// axisBorder: {
|
||||||
|
// show: false
|
||||||
|
// },
|
||||||
|
// axisTicks: {
|
||||||
|
// show: false
|
||||||
|
// },
|
||||||
|
// labels: {
|
||||||
|
// show: false,
|
||||||
|
// formatter: function (val) {
|
||||||
|
// return val + '%';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
chart: {
|
chart: {
|
||||||
|
@ -189,8 +225,15 @@ export const getChartOptions = (
|
||||||
},
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formatter: function (val) {
|
formatter: function (val, opts) {
|
||||||
return val + '%';
|
return (
|
||||||
|
formatPowerForGraph(
|
||||||
|
val,
|
||||||
|
Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min))
|
||||||
|
).value.toFixed(2) +
|
||||||
|
' ' +
|
||||||
|
chartInfo.unit
|
||||||
|
);
|
||||||
},
|
},
|
||||||
offsetY: -20,
|
offsetY: -20,
|
||||||
style: {
|
style: {
|
||||||
|
@ -213,19 +256,66 @@ export const getChartOptions = (
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
axisBorder: {
|
min:
|
||||||
show: false
|
chartInfo.min >= 0
|
||||||
},
|
? 0
|
||||||
axisTicks: {
|
: chartInfo.max <= 0
|
||||||
show: false
|
? 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: {
|
labels: {
|
||||||
show: false,
|
formatter: function (value: number) {
|
||||||
formatter: function (val) {
|
return formatPowerForGraph(
|
||||||
return val + '%';
|
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;
|
return chartOptions;
|
||||||
|
|
|
@ -1,302 +1,246 @@
|
||||||
import { Box, Card, Container, Grid, Typography } from '@mui/material';
|
import { Box, Card, Container, Grid, Typography } from '@mui/material';
|
||||||
import ReactApexChart from 'react-apexcharts';
|
import ReactApexChart from 'react-apexcharts';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import DataCache from 'src/dataCache/dataCache';
|
|
||||||
import { TimeRange, TimeSpan, UnixTime } from 'src/dataCache/time';
|
|
||||||
import {
|
|
||||||
BehaviorSubject,
|
|
||||||
combineLatest,
|
|
||||||
startWith,
|
|
||||||
tap,
|
|
||||||
throttleTime
|
|
||||||
} from 'rxjs';
|
|
||||||
import { RecordSeries } from 'src/dataCache/data';
|
|
||||||
import { createTimes } from '../Log/graph.util';
|
|
||||||
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
||||||
import { getChartOptions } from './chartOptions';
|
import { getChartOptions } from './chartOptions';
|
||||||
import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart';
|
import {
|
||||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
chartAggregatedDataInterface,
|
||||||
|
chartDataInterface,
|
||||||
|
overviewInterface,
|
||||||
|
transformInputToAggregatedData,
|
||||||
|
transformInputToDailyData
|
||||||
|
} from 'src/interfaces/Chart';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||||
const MAX_NUMBER = 9999999;
|
|
||||||
|
|
||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
s3Credentials: I_S3Credentials;
|
s3Credentials: I_S3Credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Overview(props: OverviewProps) {
|
function Overview(props: OverviewProps) {
|
||||||
const numOfPointsToFetch = 100;
|
// const numOfPointsToFetch = 100;
|
||||||
const [timeRange, setTimeRange] = useState(
|
// const [timeRange, setTimeRange] = useState(
|
||||||
createTimes(
|
// createTimes(
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
// UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||||
numOfPointsToFetch
|
// numOfPointsToFetch
|
||||||
)
|
// )
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
|
// const cache = useMemo(() => {
|
||||||
|
// return new DataCache(
|
||||||
|
// fetchData,
|
||||||
|
// TimeSpan.fromSeconds(2),
|
||||||
|
// props.s3Credentials
|
||||||
|
// );
|
||||||
|
// }, []);
|
||||||
|
|
||||||
const [chartData, setChartData] = useState<chartDataInterface>({
|
//const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
|
||||||
soc: [],
|
|
||||||
temperature: [],
|
|
||||||
dcPower: [],
|
|
||||||
gridPower: [],
|
|
||||||
pvProduction: [],
|
|
||||||
dcBusVoltage: []
|
|
||||||
});
|
|
||||||
|
|
||||||
const [chartOverview, setChartOverview] = useState<overviewInterface>({
|
|
||||||
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 pathsToSearch = [
|
|
||||||
'/Battery/Soc',
|
|
||||||
'/Battery/Temperature',
|
|
||||||
'/Battery/Dc/Power',
|
|
||||||
'/GridMeter/Ac/Power/Active',
|
|
||||||
'/PvOnDc/Dc/Power',
|
|
||||||
'/DcDc/Dc/Link/Voltage'
|
|
||||||
];
|
|
||||||
|
|
||||||
const cache = useMemo(() => {
|
|
||||||
return new DataCache(
|
|
||||||
fetchData,
|
|
||||||
TimeSpan.fromSeconds(2),
|
|
||||||
props.s3Credentials
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
|
|
||||||
const [dailyData, setDailyData] = useState(true);
|
const [dailyData, setDailyData] = useState(true);
|
||||||
const [weeklyData, setWeeklyData] = useState(false);
|
const [weeklyData, setWeeklyData] = useState(false);
|
||||||
const [monthlyData, setMonthlyData] = useState(false);
|
const [monthlyData, setMonthlyData] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [chartState, setChartState] = useState(0);
|
||||||
|
|
||||||
const transformToGraphData = (
|
// const [chartData, setChartData] = useState<chartDataInterface>({
|
||||||
input: RecordSeries
|
// soc: [],
|
||||||
): {
|
// temperature: [],
|
||||||
chartData: chartDataInterface;
|
// dcPower: [],
|
||||||
chartOverview: overviewInterface;
|
// gridPower: [],
|
||||||
} => {
|
// pvProduction: [],
|
||||||
const data = {};
|
// dcBusVoltage: []
|
||||||
const overviewData = {};
|
// });
|
||||||
|
//
|
||||||
|
// const [chartOverview, setChartOverview] = useState<overviewInterface>({
|
||||||
|
// 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 chartData: chartDataInterface = {
|
const [dailyDataArray, setDailyDataArray] = useState<
|
||||||
soc: [],
|
{
|
||||||
temperature: [],
|
chartData: chartDataInterface;
|
||||||
dcPower: [],
|
chartOverview: overviewInterface;
|
||||||
gridPower: [],
|
}[]
|
||||||
pvProduction: [],
|
>([]);
|
||||||
dcBusVoltage: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartOverview: overviewInterface = {
|
const [chartAggregatedData, setChartAggregatedData] =
|
||||||
|
useState<chartAggregatedDataInterface>({
|
||||||
|
soc: [{ data: [] }],
|
||||||
|
pvProduction: [{ data: [] }],
|
||||||
|
dcPower: [{ data: [] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const [chartAggregatedOverview, setChartAggregatedOverview] =
|
||||||
|
useState<overviewInterface>({
|
||||||
soc: { magnitude: 0, unit: '', min: 0, max: 0 },
|
soc: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||||
temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
|
temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||||
dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||||
gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||||
pvProduction: { 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 }
|
||||||
};
|
|
||||||
|
|
||||||
pathsToSearch.forEach((path) => {
|
|
||||||
data[path] = [];
|
|
||||||
overviewData[path] = {
|
|
||||||
magnitude: 0,
|
|
||||||
unit: '',
|
|
||||||
min: MAX_NUMBER,
|
|
||||||
max: -MAX_NUMBER
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//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),
|
|
||||||
Math.abs(overviewData[path].min)
|
|
||||||
);
|
|
||||||
let magnitude = 0;
|
|
||||||
|
|
||||||
if (value < 0) {
|
|
||||||
value = -value;
|
|
||||||
}
|
|
||||||
while (value >= 1000) {
|
|
||||||
value /= 1000;
|
|
||||||
magnitude++;
|
|
||||||
}
|
|
||||||
overviewData[path].magnitude = prefixes[magnitude];
|
|
||||||
});
|
|
||||||
|
|
||||||
let path = '/Battery/Soc';
|
|
||||||
chartData.soc = [{ name: 'State of Charge', data: data[path] }];
|
|
||||||
|
|
||||||
chartOverview.soc = {
|
|
||||||
unit: '(%)',
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
min: 0,
|
|
||||||
max: 100
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/Battery/Temperature';
|
|
||||||
chartData.temperature = [
|
|
||||||
{ name: 'Battery Temperature:', data: data[path] }
|
|
||||||
];
|
|
||||||
|
|
||||||
chartOverview.temperature = {
|
|
||||||
unit: '(°C)',
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/Battery/Dc/Power';
|
|
||||||
chartData.dcPower = [{ name: 'Battery Power', data: data[path] }];
|
|
||||||
|
|
||||||
chartOverview.dcPower = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + 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] }];
|
|
||||||
|
|
||||||
chartOverview.gridPower = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/PvOnDc/Dc/Power';
|
|
||||||
chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }];
|
|
||||||
|
|
||||||
chartOverview.pvProduction = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + 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] }];
|
|
||||||
|
|
||||||
chartOverview.dcBusVoltage = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + overviewData[path].magnitude + 'V' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
chartData: chartData,
|
|
||||||
chartOverview: chartOverview
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const left = cache.gotData.pipe(
|
const resultPromise: Promise<{
|
||||||
startWith(UnixTime.fromTicks(0)),
|
chartData: chartDataInterface;
|
||||||
throttleTime(200, undefined, { leading: true, trailing: true })
|
chartOverview: overviewInterface;
|
||||||
|
}> = transformInputToDailyData(
|
||||||
|
props.s3Credentials,
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
||||||
|
UnixTime.now()
|
||||||
);
|
);
|
||||||
|
|
||||||
const right = times$.pipe(
|
resultPromise
|
||||||
tap((times) => {
|
.then((result) => {
|
||||||
//console.log(times);
|
// setChartData(result.chartData);
|
||||||
|
// setChartOverview(result.chartOverview);
|
||||||
|
|
||||||
|
setDailyDataArray((prevData) =>
|
||||||
|
prevData.concat({
|
||||||
|
chartData: result.chartData,
|
||||||
|
chartOverview: result.chartOverview
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
})
|
})
|
||||||
);
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
const combined = combineLatest([left, right]);
|
// const left = cache.gotData.pipe(
|
||||||
|
// startWith(UnixTime.fromTicks(0)),
|
||||||
const subscription = combined.subscribe(([_, times]) => {
|
// throttleTime(200, undefined, { leading: true, trailing: true })
|
||||||
const timeSeries = cache.getSeries(times);
|
// );
|
||||||
const result: {
|
//
|
||||||
chartData: chartDataInterface;
|
// const right = times$.pipe(
|
||||||
chartOverview: overviewInterface;
|
// tap((times) => {
|
||||||
} = transformToGraphData(timeSeries);
|
// //console.log(times);
|
||||||
|
// })
|
||||||
setChartData(result.chartData);
|
// );
|
||||||
setChartOverview(result.chartOverview);
|
//
|
||||||
});
|
// const combined = combineLatest([left, right]);
|
||||||
return () => subscription.unsubscribe();
|
//
|
||||||
|
// 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 handleBeforeZoom = (chartContext, { xaxis }) => {
|
||||||
const startX = parseInt(xaxis.min) / 1000;
|
const startX = parseInt(xaxis.min) / 1000;
|
||||||
const endX = parseInt(xaxis.max) / 1000;
|
const endX = parseInt(xaxis.max) / 1000;
|
||||||
|
|
||||||
const times = createTimes(
|
//console.log(UnixTime.fromTicks(startX));
|
||||||
TimeRange.fromTimes(UnixTime.fromTicks(startX), UnixTime.fromTicks(endX)),
|
//console.log(endX);
|
||||||
numOfPointsToFetch
|
|
||||||
|
setLoading(true);
|
||||||
|
const resultPromise: Promise<{
|
||||||
|
chartData: chartDataInterface;
|
||||||
|
chartOverview: overviewInterface;
|
||||||
|
}> = transformInputToDailyData(
|
||||||
|
props.s3Credentials,
|
||||||
|
UnixTime.fromTicks(startX),
|
||||||
|
UnixTime.fromTicks(endX)
|
||||||
);
|
);
|
||||||
|
|
||||||
cache.getSeries(times);
|
resultPromise
|
||||||
times$.next(times);
|
.then((result) => {
|
||||||
};
|
//setChartData(result.chartData);
|
||||||
|
//setChartOverview(result.chartOverview);
|
||||||
|
|
||||||
const handleDoubleClick = () => {
|
setDailyDataArray((prevData) =>
|
||||||
const times = createTimes(
|
prevData.concat({
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
chartData: result.chartData,
|
||||||
numOfPointsToFetch
|
chartOverview: result.chartOverview
|
||||||
);
|
})
|
||||||
cache.getSeries(times);
|
);
|
||||||
times$.next(times);
|
|
||||||
|
setLoading(false);
|
||||||
|
setChartState(dailyDataArray.length);
|
||||||
|
})
|
||||||
|
.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 = () => {
|
const handle24HourData = () => {
|
||||||
setDailyData(true);
|
setDailyData(true);
|
||||||
setWeeklyData(false);
|
setWeeklyData(false);
|
||||||
setMonthlyData(false);
|
setMonthlyData(false);
|
||||||
const times = createTimes(
|
// const times = createTimes(
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
// UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||||
numOfPointsToFetch
|
// numOfPointsToFetch
|
||||||
);
|
// );
|
||||||
cache.getSeries(times);
|
// cache.getSeries(times);
|
||||||
times$.next(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);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWeekData = () => {
|
const handleWeekData = () => {
|
||||||
setDailyData(false);
|
setDailyData(false);
|
||||||
setWeeklyData(true);
|
setWeeklyData(true);
|
||||||
setMonthlyData(false);
|
setMonthlyData(false);
|
||||||
//fetchData(12312,props.s3Credentials);
|
|
||||||
|
const resultPromise: Promise<{
|
||||||
|
chartAggregatedData: chartAggregatedDataInterface;
|
||||||
|
chartOverview: overviewInterface;
|
||||||
|
}> = transformInputToAggregatedData(props.s3Credentials);
|
||||||
|
|
||||||
|
resultPromise
|
||||||
|
.then((result) => {
|
||||||
|
setChartAggregatedData(result.chartAggregatedData);
|
||||||
|
setChartAggregatedOverview(result.chartOverview);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
// const times = createTimes(
|
// const times = createTimes(
|
||||||
// UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)),
|
// UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)),
|
||||||
// numOfPointsToFetch
|
// numOfPointsToFetch
|
||||||
|
@ -305,6 +249,18 @@ function Overview(props: OverviewProps) {
|
||||||
// times$.next(times);
|
// times$.next(times);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGoBack = () => {
|
||||||
|
if (chartState > 0) {
|
||||||
|
setChartState(chartState - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGoForward = () => {
|
||||||
|
if (chartState + 1 < dailyDataArray.length) {
|
||||||
|
setChartState(chartState + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMonthData = () => {
|
const handleMonthData = () => {
|
||||||
setDailyData(false);
|
setDailyData(false);
|
||||||
setWeeklyData(false);
|
setWeeklyData(false);
|
||||||
|
@ -317,18 +273,32 @@ function Overview(props: OverviewProps) {
|
||||||
// times$.next(times);
|
// times$.next(times);
|
||||||
};
|
};
|
||||||
|
|
||||||
const series = [
|
|
||||||
{
|
|
||||||
name: 'Inflation',
|
|
||||||
data: [2.3, 3.1, 4.0, 10.1, 4.0, 3.6, 3.2, 2.3, 1.4, 0.8, 0.5, 0.2]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const renderGraphs = () => {
|
const renderGraphs = () => {
|
||||||
|
if (loading) {
|
||||||
|
// Display a loading indicator while waiting for the data
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
maxWidth="xl"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||||
|
<Typography variant="body2" style={{ color: 'black' }} mt={2}>
|
||||||
|
Fetching data...
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={6} md={6}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handle24HourData}
|
onClick={handle24HourData}
|
||||||
|
@ -368,6 +338,44 @@ function Overview(props: OverviewProps) {
|
||||||
<FormattedMessage id="lastmonth" defaultMessage="Last Month" />
|
<FormattedMessage id="lastmonth" defaultMessage="Last Month" />
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
justifyContent="flex-end"
|
||||||
|
alignItems="center"
|
||||||
|
item
|
||||||
|
xs={6}
|
||||||
|
md={6}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!(chartState > 0)}
|
||||||
|
onClick={handleGoBack}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="goback" defaultMessage="Go Back" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!(chartState < dailyDataArray.length - 1)}
|
||||||
|
onClick={handleGoForward}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="goback" defaultMessage="Go Forward" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<Grid
|
<Grid
|
||||||
|
@ -413,14 +421,17 @@ function Overview(props: OverviewProps) {
|
||||||
{dailyData && (
|
{dailyData && (
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(chartOverview.soc, 'daily'),
|
...getChartOptions(
|
||||||
|
dailyDataArray[chartState].chartOverview.soc,
|
||||||
|
'daily'
|
||||||
|
),
|
||||||
chart: {
|
chart: {
|
||||||
events: {
|
events: {
|
||||||
beforeZoom: handleBeforeZoom
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
series={chartData.soc}
|
series={dailyDataArray[chartState].chartData.soc}
|
||||||
type="area"
|
type="area"
|
||||||
height={350}
|
height={350}
|
||||||
/>
|
/>
|
||||||
|
@ -429,9 +440,12 @@ function Overview(props: OverviewProps) {
|
||||||
{weeklyData && (
|
{weeklyData && (
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(chartOverview.soc, 'weekly')
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.soc,
|
||||||
|
'weekly'
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
series={series}
|
series={chartAggregatedData.soc}
|
||||||
type="bar"
|
type="bar"
|
||||||
height={350}
|
height={350}
|
||||||
/>
|
/>
|
||||||
|
@ -440,9 +454,12 @@ function Overview(props: OverviewProps) {
|
||||||
{monthlyData && (
|
{monthlyData && (
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(chartOverview.soc, 'monthly')
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.soc,
|
||||||
|
'monthly'
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
series={series}
|
series={chartAggregatedData.soc}
|
||||||
type="bar"
|
type="bar"
|
||||||
height={350}
|
height={350}
|
||||||
/>
|
/>
|
||||||
|
@ -481,21 +498,22 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div onDoubleClick={handleDoubleClick}>
|
<ReactApexChart
|
||||||
<ReactApexChart
|
options={{
|
||||||
options={{
|
...getChartOptions(
|
||||||
...getChartOptions(chartOverview.temperature, 'daily'),
|
dailyDataArray[chartState].chartOverview.temperature,
|
||||||
chart: {
|
'daily'
|
||||||
events: {
|
),
|
||||||
beforeZoom: handleBeforeZoom
|
chart: {
|
||||||
}
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
series={chartData.temperature}
|
}}
|
||||||
type="area"
|
series={dailyDataArray[chartState].chartData.temperature}
|
||||||
height={350}
|
type="area"
|
||||||
/>
|
height={350}
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -539,21 +557,53 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div onDoubleClick={handleDoubleClick}>
|
|
||||||
|
{dailyData && (
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(chartOverview.pvProduction, 'daily'),
|
...getChartOptions(
|
||||||
|
dailyDataArray[chartState].chartOverview.pvProduction,
|
||||||
|
'daily'
|
||||||
|
),
|
||||||
chart: {
|
chart: {
|
||||||
events: {
|
events: {
|
||||||
beforeZoom: handleBeforeZoom
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
series={chartData.pvProduction}
|
series={dailyDataArray[chartState].chartData.pvProduction}
|
||||||
type="area"
|
type="area"
|
||||||
height={350}
|
height={350}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{weeklyData && (
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.pvProduction,
|
||||||
|
'weekly'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
series={chartAggregatedData.pvProduction}
|
||||||
|
type="bar"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{monthlyData && (
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.pvProduction,
|
||||||
|
'monthly'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
series={chartAggregatedData.pvProduction}
|
||||||
|
type="bar"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
|
@ -588,21 +638,22 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div onDoubleClick={handleDoubleClick}>
|
<ReactApexChart
|
||||||
<ReactApexChart
|
options={{
|
||||||
options={{
|
...getChartOptions(
|
||||||
...getChartOptions(chartOverview.gridPower, 'daily'),
|
dailyDataArray[chartState].chartOverview.gridPower,
|
||||||
chart: {
|
'daily'
|
||||||
events: {
|
),
|
||||||
beforeZoom: handleBeforeZoom
|
chart: {
|
||||||
}
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
series={chartData.gridPower}
|
}}
|
||||||
type="area"
|
series={dailyDataArray[chartState].chartData.gridPower}
|
||||||
height={350}
|
type="area"
|
||||||
/>
|
height={350}
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -645,21 +696,53 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div onDoubleClick={handleDoubleClick}>
|
|
||||||
|
{dailyData && (
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(chartOverview.dcPower, 'daily'),
|
...getChartOptions(
|
||||||
|
dailyDataArray[chartState].chartOverview.dcPower,
|
||||||
|
'daily'
|
||||||
|
),
|
||||||
chart: {
|
chart: {
|
||||||
events: {
|
events: {
|
||||||
beforeZoom: handleBeforeZoom
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
series={chartData.dcPower}
|
series={dailyDataArray[chartState].chartData.dcPower}
|
||||||
type="area"
|
type="area"
|
||||||
height={350}
|
height={350}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{weeklyData && (
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.dcPower,
|
||||||
|
'weekly'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
series={chartAggregatedData.dcPower}
|
||||||
|
type="bar"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{monthlyData && (
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
...getChartOptions(
|
||||||
|
chartAggregatedOverview.dcPower,
|
||||||
|
'monthly'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
series={chartAggregatedData.dcPower}
|
||||||
|
type="bar"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
|
@ -694,21 +777,22 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div onDoubleClick={handleDoubleClick}>
|
<ReactApexChart
|
||||||
<ReactApexChart
|
options={{
|
||||||
options={{
|
...getChartOptions(
|
||||||
...getChartOptions(chartOverview.dcBusVoltage, 'daily'),
|
dailyDataArray[chartState].chartOverview.dcBusVoltage,
|
||||||
chart: {
|
'daily'
|
||||||
events: {
|
),
|
||||||
beforeZoom: handleBeforeZoom
|
chart: {
|
||||||
}
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
series={chartData.dcBusVoltage}
|
}}
|
||||||
type="area"
|
series={dailyDataArray[chartState].chartData.dcBusVoltage}
|
||||||
height={350}
|
type="area"
|
||||||
/>
|
height={350}
|
||||||
</div>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { isDefined } from './utils/maybe';
|
||||||
import { I_CsvEntry } from 'src/content/dashboards/Log/graph.util';
|
import { I_CsvEntry } from 'src/content/dashboards/Log/graph.util';
|
||||||
|
|
||||||
export type DataRecord = Record<string, I_CsvEntry>;
|
export type DataRecord = Record<string, I_CsvEntry>;
|
||||||
|
|
||||||
export type DataPoint = Timestamped<Maybe<DataRecord>>;
|
export type DataPoint = Timestamped<Maybe<DataRecord>>;
|
||||||
export type RecordSeries = Array<DataPoint>;
|
export type RecordSeries = Array<DataPoint>;
|
||||||
|
export type DataRecordSeries = Array<DataRecord>;
|
||||||
export type PointSeries = Array<Timestamped<Maybe<I_CsvEntry>>>;
|
export type PointSeries = Array<Timestamped<Maybe<I_CsvEntry>>>;
|
||||||
export type DataSeries = Array<Maybe<I_CsvEntry>>;
|
export type DataSeries = Array<Maybe<I_CsvEntry>>;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import { ApexOptions } from 'apexcharts';
|
import { ApexOptions } from 'apexcharts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
fetchDailyData,
|
||||||
|
fetchData
|
||||||
|
} from '../content/dashboards/Installations/fetchData';
|
||||||
|
import { FetchResult } from '../dataCache/dataCache';
|
||||||
|
import { I_S3Credentials } from './S3Types';
|
||||||
|
import { UnixTime } from '../dataCache/time';
|
||||||
|
|
||||||
export interface overviewInterface {
|
export interface overviewInterface {
|
||||||
soc: chartInfoInterface;
|
soc: chartInfoInterface;
|
||||||
|
@ -16,6 +24,12 @@ export interface chartInfoInterface {
|
||||||
max: number;
|
max: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface chartAggregatedDataInterface {
|
||||||
|
soc: [{ data: number[] }];
|
||||||
|
pvProduction: [{ data: number[] }];
|
||||||
|
dcPower: [{ data: number[] }];
|
||||||
|
}
|
||||||
|
|
||||||
export interface chartDataInterface {
|
export interface chartDataInterface {
|
||||||
soc: ApexOptions['series'];
|
soc: ApexOptions['series'];
|
||||||
temperature: ApexOptions['series'];
|
temperature: ApexOptions['series'];
|
||||||
|
@ -24,3 +38,360 @@ export interface chartDataInterface {
|
||||||
pvProduction: ApexOptions['series'];
|
pvProduction: ApexOptions['series'];
|
||||||
dcBusVoltage: ApexOptions['series'];
|
dcBusVoltage: ApexOptions['series'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const transformInputToDailyData = async (
|
||||||
|
s3Credentials: I_S3Credentials,
|
||||||
|
startTimestamp: UnixTime,
|
||||||
|
endTimestamp: UnixTime
|
||||||
|
): Promise<{
|
||||||
|
chartData: chartDataInterface;
|
||||||
|
chartOverview: overviewInterface;
|
||||||
|
}> => {
|
||||||
|
const data = {};
|
||||||
|
const overviewData = {};
|
||||||
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
|
const MAX_NUMBER = 9999999;
|
||||||
|
const pathsToSearch = [
|
||||||
|
'/Battery/Soc',
|
||||||
|
'/Battery/Temperature',
|
||||||
|
'/Battery/Dc/Power',
|
||||||
|
'/GridMeter/Ac/Power/Active',
|
||||||
|
'/PvOnDc/Dc/Power',
|
||||||
|
'/DcDc/Dc/Link/Voltage'
|
||||||
|
];
|
||||||
|
|
||||||
|
const chartData: chartDataInterface = {
|
||||||
|
soc: [],
|
||||||
|
temperature: [],
|
||||||
|
dcPower: [],
|
||||||
|
gridPower: [],
|
||||||
|
pvProduction: [],
|
||||||
|
dcBusVoltage: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartOverview: overviewInterface = {
|
||||||
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
pathsToSearch.forEach((path) => {
|
||||||
|
data[path] = [];
|
||||||
|
overviewData[path] = {
|
||||||
|
magnitude: 0,
|
||||||
|
unit: '',
|
||||||
|
min: MAX_NUMBER,
|
||||||
|
max: -MAX_NUMBER
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 ||
|
||||||
|
result === FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
// Handle not available or try later case
|
||||||
|
} else {
|
||||||
|
//console.log('Received data:', result);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
pathsToSearch.forEach((path) => {
|
||||||
|
const timestamp = startUnixTime.ticks * 1000;
|
||||||
|
|
||||||
|
const adjustedTimestamp = new Date(timestamp);
|
||||||
|
adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1);
|
||||||
|
|
||||||
|
if (result[path]) {
|
||||||
|
const value = result[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]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
||||||
|
if (startUnixTime.ticks % 2 != 0) {
|
||||||
|
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
Math.abs(overviewData[path].min)
|
||||||
|
);
|
||||||
|
let magnitude = 0;
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
while (value >= 1000) {
|
||||||
|
value /= 1000;
|
||||||
|
magnitude++;
|
||||||
|
}
|
||||||
|
overviewData[path].magnitude = prefixes[magnitude];
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = '/Battery/Soc';
|
||||||
|
chartData.soc = [{ name: 'State of Charge', data: data[path] }];
|
||||||
|
|
||||||
|
chartOverview.soc = {
|
||||||
|
unit: '(%)',
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
path = '/Battery/Temperature';
|
||||||
|
chartData.temperature = [{ name: 'Battery Temperature:', data: data[path] }];
|
||||||
|
|
||||||
|
chartOverview.temperature = {
|
||||||
|
unit: '(°C)',
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
min: overviewData[path].min,
|
||||||
|
max: overviewData[path].max
|
||||||
|
};
|
||||||
|
|
||||||
|
path = '/Battery/Dc/Power';
|
||||||
|
chartData.dcPower = [{ name: 'Battery Power', data: data[path] }];
|
||||||
|
|
||||||
|
chartOverview.dcPower = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + 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] }];
|
||||||
|
|
||||||
|
chartOverview.gridPower = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||||
|
min: overviewData[path].min,
|
||||||
|
max: overviewData[path].max
|
||||||
|
};
|
||||||
|
|
||||||
|
path = '/PvOnDc/Dc/Power';
|
||||||
|
chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }];
|
||||||
|
|
||||||
|
chartOverview.pvProduction = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + 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] }];
|
||||||
|
|
||||||
|
chartOverview.dcBusVoltage = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + overviewData[path].magnitude + 'V' + ')',
|
||||||
|
min: overviewData[path].min,
|
||||||
|
max: overviewData[path].max
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartData: chartData,
|
||||||
|
chartOverview: chartOverview
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformInputToAggregatedData = async (
|
||||||
|
s3Credentials: I_S3Credentials
|
||||||
|
): Promise<{
|
||||||
|
chartAggregatedData: chartAggregatedDataInterface;
|
||||||
|
chartOverview: overviewInterface;
|
||||||
|
}> => {
|
||||||
|
const data = {};
|
||||||
|
const overviewData = {};
|
||||||
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
|
const MAX_NUMBER = 9999999;
|
||||||
|
|
||||||
|
let currentDate = dayjs().add(1, 'day');
|
||||||
|
let currentDay = currentDate.subtract(1, 'week');
|
||||||
|
|
||||||
|
const pathsToSearch = [
|
||||||
|
'/AvgSoc',
|
||||||
|
'/AvgPvPower',
|
||||||
|
'/BatteryPowerAverage',
|
||||||
|
'/GridMeter/Ac/Power/Active',
|
||||||
|
'/PvOnDc/Dc/Power',
|
||||||
|
'/DcDc/Dc/Link/Voltage'
|
||||||
|
];
|
||||||
|
|
||||||
|
const chartAggregatedData: chartAggregatedDataInterface = {
|
||||||
|
soc: [{ data: [] }],
|
||||||
|
pvProduction: [{ data: [] }],
|
||||||
|
dcPower: [{ data: [] }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartOverview: overviewInterface = {
|
||||||
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
pathsToSearch.forEach((path) => {
|
||||||
|
data[path] = [];
|
||||||
|
overviewData[path] = {
|
||||||
|
magnitude: 0,
|
||||||
|
unit: '',
|
||||||
|
min: MAX_NUMBER,
|
||||||
|
max: -MAX_NUMBER
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
while (currentDay.isBefore(currentDate)) {
|
||||||
|
// console.log('Current day:', currentDay.format('YYYY-MM-DD'));
|
||||||
|
|
||||||
|
let result = await Promise.resolve(
|
||||||
|
fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
result === FetchResult.notAvailable ||
|
||||||
|
result === FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
// Handle not available or try later case
|
||||||
|
} else {
|
||||||
|
console.log('Received data:', result);
|
||||||
|
pathsToSearch.forEach((path) => {
|
||||||
|
if (result[path]) {
|
||||||
|
if (result[path].value < overviewData[path].min) {
|
||||||
|
overviewData[path].min = result[path].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result[path].value > overviewData[path].max) {
|
||||||
|
overviewData[path].max = result[path].value;
|
||||||
|
}
|
||||||
|
data[path].push(result[path].value as number);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Add one day to move to the next day
|
||||||
|
currentDay = currentDay.add(1, 'day');
|
||||||
|
}
|
||||||
|
|
||||||
|
pathsToSearch.forEach((path) => {
|
||||||
|
let value = Math.max(
|
||||||
|
Math.abs(overviewData[path].max),
|
||||||
|
Math.abs(overviewData[path].min)
|
||||||
|
);
|
||||||
|
let magnitude = 0;
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
while (value >= 1000) {
|
||||||
|
value /= 1000;
|
||||||
|
magnitude++;
|
||||||
|
}
|
||||||
|
overviewData[path].magnitude = prefixes[magnitude];
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = '/AvgSoc';
|
||||||
|
chartAggregatedData.soc[0].data = data[path];
|
||||||
|
|
||||||
|
chartOverview.soc = {
|
||||||
|
unit: '(%)',
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
path = '/AvgPvPower';
|
||||||
|
chartAggregatedData.pvProduction[0].data = data[path];
|
||||||
|
|
||||||
|
chartOverview.pvProduction = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||||
|
min: overviewData[path].min,
|
||||||
|
max: overviewData[path].max
|
||||||
|
};
|
||||||
|
|
||||||
|
path = '/BatteryPowerAverage';
|
||||||
|
chartAggregatedData.dcPower[0].data = data[path];
|
||||||
|
|
||||||
|
chartOverview.dcPower = {
|
||||||
|
magnitude: overviewData[path].magnitude,
|
||||||
|
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||||
|
min: overviewData[path].min,
|
||||||
|
max: overviewData[path].max
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartAggregatedData: chartAggregatedData,
|
||||||
|
chartOverview: chartOverview
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue