Skip to content

Commit

Permalink
move computeStats function to charts store
Browse files Browse the repository at this point in the history
  • Loading branch information
devincowan committed Jul 11, 2024
1 parent a7e9c05 commit 2f9979f
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 99 deletions.
117 changes: 19 additions & 98 deletions frontend/src/components/NodeChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
</v-btn>
<v-btn @click="resetData()" color="input" class="ma-1">
<v-icon :icon="mdiEraser"></v-icon>
Refresh Data
Reset Data
</v-btn>
<v-btn @click="computeStatisics()" color="input" class="ma-1">
<v-icon :icon="mdiEraser"></v-icon>
<v-btn @click="showMinMax" color="input" class="ma-1">
<v-icon :icon="mdiDesktopClassic"></v-icon>
Patch
</v-btn>
</v-sheet>
Expand All @@ -54,18 +54,22 @@ import {
Tooltip,
Legend,
TimeScale,
Filler,
} from 'chart.js'
import { Line } from 'vue-chartjs'
import 'chartjs-adapter-date-fns';
import { ref } from 'vue'
import { customCanvasBackgroundColor } from '@/_helpers/charts/plugins'
import { mdiDownloadBox, mdiFileDelimited, mdiCodeJson, mdiMagnifyMinusOutline, mdiEraser } from '@mdi/js'
import { mdiDownloadBox, mdiFileDelimited, mdiCodeJson, mdiMagnifyMinusOutline, mdiEraser, mdiDesktopClassic } from '@mdi/js'
import { downloadMultiNodesCsv, downloadMultiNodesJson } from '../_helpers/hydroCron';
import { useDisplay } from 'vuetify'
import zoomPlugin from 'chartjs-plugin-zoom';
import { capitalizeFirstLetter } from '@/_helpers/charts/plugins'
import TimeRangeSlider from '@/components/TimeRangeSlider.vue'
import { useChartsStore } from '@/stores/charts'
import { useAlertStore } from '@/stores/alerts'
const alertStore = useAlertStore()
const chartStore = useChartsStore()
const { lgAndUp } = useDisplay()
Expand All @@ -80,7 +84,6 @@ ChartJS.register(LinearScale, TimeScale, PointElement, LineElement, Title, Toolt
// TODO: might need a more efficient way of doing this instead of re-mapping the data
// Ideally use the store directly instead of passing it as a prop
let chartData = ref(props.data)
let minMaxData = JSON.parse(JSON.stringify(chartData.value.datasets.slice(0, 2)));
const setParsing = (datasets) => {
datasets.forEach((dataset) => {
Expand Down Expand Up @@ -226,98 +229,16 @@ const resetData = () => {
line.value.chart.update()
}
const computeStatisics = () => {
// --------------------------------------------------------------------------------
// Description: Computes node-level statistics for the SWOT series that are
// currently displayed in the graph interface.
//
// Returns
// =======
// Object { minimum: Object,
// maximum: Object,
// mean: Object }
// - minimum = the minimum value for each node along the reach, for each variable
// - maximum = the maximum value for each node along the reach, for each variable
// - mean = the mean value for each node along the reach, for each variable
// --------------------------------------------------------------------------------
// convert the chartData into an array structure that's easier to work with
let dat = [];
for (let j = 0; j <= chartData.value.datasets.length - 1; j++) {
dat[j] = [... chartData.value.datasets[j].data];
}
// Create arrays to store statistic outputs, base these off existing arrays.
// Perform this operation above the node_dist alignment to ensure that
// NaN's are not set as initial values.
let datMin = {... dat[0][0]};
let datMax = {... dat[0][0]};
let datMean = {... dat[0][0]};
// Get all keys that contain variables that we would want to plot on the y axis.
// These are the only variables for which statistics will be computed.
let keys = Object.keys(dat[0][0])
.filter((key) => !key.includes('_units'))
.filter((key) => !['time_str', 'p_dist_out', 'datetime'].includes(key));
// Get a list of all unique node distances in the dataset. We need to align all of the datasets
// such that they all have the same number of nodes. Insert NaN values where a node is missing
// from a dataset.
let node_dists = [... new Set([... new Set(dat.map(d=>d.map(x=>x.p_dist_out)))].flat())]
// Loop over node_dists. Check if node_dist exists in array, set n/a if not and exclude from min/max calculation
for (let i = 0; i <= node_dists.length - 1; i++) {
let nd = node_dists[i];
for (let j = 0; j <= dat.length-1; j++) {
// Check to see if the node distance is found in the current array
// if not, insert a new object with NaN values here.
let res = dat[j].filter(res => res.p_dist_out == nd);
if (res.length == 0) {
// Create a new object based off the previous record (or next record)
// in the case that this is the first element of the array
let newNode;
if (i > 0) {
newNode = {... dat[j][i]};
}
else {
newNode = {... dat[j][i]};
}
newNode['p_dist_out'] == nd;
for (let key of keys) {
newNode[key] = Number.NaN;
}
// Insert the new object using the previous index
dat[j].splice(i, 0, newNode);
}
}
}
// Compute statistics by iterating along the nodes of each data series
for (let i = 0; i <= node_dists.length - 1; i++) {
for (let j = 0; j <= dat.length - 1; j++ ) {
// Loop over keys and get the min and max values
for (let key of keys) {
// Compute minimum
if (parseFloat(dat[j][i][key]) < parseFloat(datMin[key])) {
datMin[key] = dat[j][i][key];
}
// Compute maximum
if (parseFloat(dat[j][i][key]) > parseFloat(datMax[key])) {
datMax[key] = dat[j][i][key];
}
// Compute mean
if (!isNaN(parseFloat(dat[j][i][key]))) {
datMean[key] = (parseFloat(datMean[key]) + parseFloat(dat[j][i][key])) / 2;
}
}
}
}
// Return the computed statistics
return {minimum: datMin,
maximum: datMax,
mean: datMean}
const showMinMax = () => {
const stats = chartStore.computeNodeStatisics()
alertStore.displayAlert({
title: 'Calculated Statistics',
text: JSON.stringify(stats),
type: 'warning',
closable: true,
duration: 10000
})
}
</script>
97 changes: 96 additions & 1 deletion frontend/src/stores/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,100 @@ export const useChartsStore = defineStore('charts', () => {
}
}

const computeNodeStatisics = () => {
// --------------------------------------------------------------------------------
// Description: Computes node-level statistics for the SWOT series that are
// currently displayed in the graph interface.
//
// Returns
// =======
// Object { minimum: Object,
// maximum: Object,
// mean: Object }
// - minimum = the minimum value for each node along the reach, for each variable
// - maximum = the maximum value for each node along the reach, for each variable
// - mean = the mean value for each node along the reach, for each variable
// --------------------------------------------------------------------------------

// convert the chartData into an array structure that's easier to work with
let dat = []
for (let j = 0; j <= nodeChartData.value.datasets.length - 1; j++) {
dat[j] = [...nodeChartData.value.datasets[j].data]
}

console.log('Computing node statistics for', dat)

// Create arrays to store statistic outputs, base these off existing arrays.
// Perform this operation above the node_dist alignment to ensure that
// NaN's are not set as initial values.
let datMin = { ...dat[0][0] }
let datMax = { ...dat[0][0] }
let datMean = { ...dat[0][0] }

// Get all keys that contain variables that we would want to plot on the y axis.
// These are the only variables for which statistics will be computed.
let keys = Object.keys(dat[0][0])
.filter((key) => !key.includes('_units'))
.filter((key) => !['time_str', 'p_dist_out', 'datetime'].includes(key))

// Get a list of all unique node distances in the dataset. We need to align all of the datasets
// such that they all have the same number of nodes. Insert NaN values where a node is missing
// from a dataset.
let node_dists = [...new Set([...new Set(dat.map((d) => d.map((x) => x.p_dist_out)))].flat())]

// Loop over node_dists. Check if node_dist exists in array, set n/a if not and exclude from min/max calculation
for (let i = 0; i <= node_dists.length - 1; i++) {
let nd = node_dists[i]
for (let j = 0; j <= dat.length - 1; j++) {
// Check to see if the node distance is found in the current array
// if not, insert a new object with NaN values here.
let res = dat[j].filter((res) => res.p_dist_out == nd)
if (res.length == 0) {
// Create a new object based off the previous record (or next record)
// in the case that this is the first element of the array
let newNode
if (i > 0) {
newNode = { ...dat[j][i] }
} else {
newNode = { ...dat[j][i] }
}
newNode['p_dist_out'] == nd
for (let key of keys) {
newNode[key] = Number.NaN
}
// Insert the new object using the previous index
dat[j].splice(i, 0, newNode)
}
}
}

// Compute statistics by iterating along the nodes of each data series
for (let i = 0; i <= node_dists.length - 1; i++) {
for (let j = 0; j <= dat.length - 1; j++) {
// Loop over keys and get the min and max values
for (let key of keys) {
// Compute minimum
if (parseFloat(dat[j][i][key]) < parseFloat(datMin[key])) {
datMin[key] = dat[j][i][key]
}
// Compute maximum
if (parseFloat(dat[j][i][key]) > parseFloat(datMax[key])) {
datMax[key] = dat[j][i][key]
}
// Compute mean
if (!isNaN(parseFloat(dat[j][i][key]))) {
datMean[key] = (parseFloat(datMean[key]) + parseFloat(dat[j][i][key])) / 2
}
}
}
}
const stats = { minimum: datMin, maximum: datMax, mean: datMean }
console.log('Node statistics', stats)

// Return the computed statistics
return stats
}

return {
updateChartData,
chartData,
Expand All @@ -478,6 +572,7 @@ export const useChartsStore = defineStore('charts', () => {
filterDatasetsBySetOfDates,
setDatasetVisibility,
getNodeTimeStamps,
chartTab
chartTab,
computeNodeStatisics
}
})

0 comments on commit 2f9979f

Please sign in to comment.