Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 4 additions & 18 deletions frontend/src/components/DataViewDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,22 @@
</v-btn>
<StaticMetadata />
<v-btn
v-if="!hasResults() && isReachFeature"
@click="router.push(`/plots/${featureStore.activeFeature.properties.reach_id}`)"
v-if="!hasResults()"
@click="router.push(`/plots/${featureStore.activeFeature.properties.feature_id}`)"
color="primary"
class="ma-2"
:loading="featureStore.querying.hydrocron"
>
<v-icon :icon="mdiChartScatterPlot"></v-icon>Plot
</v-btn>
<v-btn
disabled
v-if="!hasResults() && !isReachFeature"
@click="router.push(`/feature/${featureStore.activeFeature.properties.lake_id}`)"
color="primary"
class="ma-2"
:loading="featureStore.querying.hydrocron"
>
<v-icon :icon="mdiDataMatrix"></v-icon>Lake Data Plots coming soon!
</v-btn>
</v-container>
</v-navigation-drawer>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ref } from 'vue'
import { useFeaturesStore } from '@/stores/features'
import { mdiChevronRight, mdiChevronLeft, mdiChartScatterPlot, mdiDataMatrix } from '@mdi/js'
import { mdiChevronRight, mdiChevronLeft, mdiChartScatterPlot } from '@mdi/js'
import StaticMetadata from './StaticMetadata.vue'
import { useRouter } from 'vue-router'

Expand All @@ -66,10 +56,6 @@ const hasResults = () => {
return featureStore?.activeFeature?.results !== undefined
}

const isReachFeature = computed(() => {
return featureStore?.activeFeature?.feature_type === 'Reach'
})

featureStore.$subscribe((mutation, state) => {
if (state.activeFeature !== null) {
// && typeof mutation.events.newValue === 'object'
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/TheLeafletMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ onMounted(async () => {

mapStore.generateLakesFeatures()

let url = 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_swot_lakes/MapServer/WmsServer?'
let url = 'https://arcgis.cuahsi.org/arcgis/services/SWOT/SWOT_PLD_v201/MapServer/WmsServer?'
const lakesWMS = L.tileLayer.wms(url, {
layers: 0,
transparent: 'true',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const router = createRouter({
component: ApiView
},
{
path: '/plots/:reachId?',
path: '/plots/:featureId?',
name: 'plots',
component: () => import('../views/ChartsView.vue'),
props: true
Expand Down
50 changes: 45 additions & 5 deletions frontend/src/stores/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ export const useChartsStore = defineStore(
}
])

// a collection of charts that can be created in the lake view
const lakeCharts = ref([
{
abbreviation: 'wse/time',
xvar: swotVariables.value.find((v) => v.abbreviation == 'time_str'),
yvar: swotVariables.value.find((v) => v.abbreviation == 'wse'),
title: 'Water Surface Elevation',
name: 'WSE vs Time',
help: swotVariables.value.find((v) => v.abbreviation == 'wse').definition
},
{
abbreviation: 'area/time',
xvar: swotVariables.value.find((v) => v.abbreviation == 'time_str'),
yvar: swotVariables.value.find((v) => v.abbreviation == 'area_total'),
title: 'Water Surface Area',
help: swotVariables.value.find((v) => v.abbreviation == 'area_total').definition,
name: 'WSA vs Time'
}
])

const updateNodeChartData = (data) => {
// This function is used to update the nodeChartData object
// data = the new data series to set in the object.
Expand Down Expand Up @@ -255,6 +275,19 @@ export const useChartsStore = defineStore(
}
}

const determineQualityLabel = (dataPoint) => {
// Support reach_q, node_q, or quality_f as the quality label
let qualityLabel = null
if ('quality_f' in dataPoint) {
qualityLabel = 'quality_f'
} else if ('reach_q' in dataPoint) {
qualityLabel = 'reach_q'
} else if ('node_q' in dataPoint) {
qualityLabel = 'node_q'
}
return qualityLabel
}

const dataQualityFilterSingleDataset = (dataset) => {
// Filter the dataset to only include points that have a data quality flag

Expand All @@ -263,7 +296,10 @@ export const useChartsStore = defineStore(
}

dataset.data = dataset.data.filter((dataPoint) => {
const qualityLabel = dataPoint.reach_q ? 'reach_q' : 'node_q'
let qualityLabel = determineQualityLabel(dataPoint)
if (!qualityLabel) {
return true // If no quality label, keep the point
}
if (!dataQualityFlags.value.includes(parseInt(dataPoint[qualityLabel]))) {
return false
}
Expand Down Expand Up @@ -651,13 +687,16 @@ export const useChartsStore = defineStore(
}

const getPointStyle = (dataPoint) => {
const qualityLabel = dataPoint.reach_q ? 'reach_q' : 'node_q'
let qualityLabel = determineQualityLabel(dataPoint)
if (!qualityLabel) {
return true // If no quality label, keep the point
}
if (!dataQualityFlags.value.includes(parseInt(dataPoint[qualityLabel]))) {
return false
}

// https://www.chartjs.org/docs/latest/configuration/elements.html#point-configuration
const dataQuality = dataPoint.reach_q ? dataPoint.reach_q : dataPoint.node_q
const dataQuality = dataPoint[qualityLabel]
let pointStyle = 'circle'
// Values of 0, 1, 2, and 3 indicate good, suspect, degraded, and bad measurements, respectively
const dataQualityOption = dataQualityOptions.find((option) => option.value == dataQuality)
Expand All @@ -668,13 +707,13 @@ export const useChartsStore = defineStore(
}

const getPointBorderColor = (dataPoint) => {
const qualityLabel = dataPoint.reach_q ? 'reach_q' : 'node_q'
let qualityLabel = determineQualityLabel(dataPoint)
if (!dataQualityFlags.value.includes(parseInt(dataPoint[qualityLabel]))) {
return false
}

// https://www.chartjs.org/docs/latest/configuration/elements.html#point-configuration
const dataQuality = dataPoint.reach_q ? dataPoint.reach_q : dataPoint.node_q
const dataQuality = dataPoint[qualityLabel]
let pointBorderColor = 'white'
// Values of 0, 1, 2, and 3 indicate good, suspect, degraded, and bad measurements, respectively
const dataQualityOption = dataQualityOptions.find((option) => option.value == dataQuality)
Expand Down Expand Up @@ -967,6 +1006,7 @@ export const useChartsStore = defineStore(
chartTab,
nodeCharts,
reachCharts,
lakeCharts,
showStatistics,
symbology,
updateSymbology,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/stores/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const useFeaturesStore = defineStore(
}

function selectFeature(feature) {
// add a common feature_id in addition to reach_id or lake_id
feature.properties.feature_id = feature.properties.reach_id || feature?.properties?.lake_id
mapStore.selectFeature(feature)
selectedFeatures.value.push(feature)
activeFeature.value = feature
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/stores/hydrologic.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ export const useHydrologicStore = defineStore('hydrologic', () => {
fileType: 'node',
plottable: false
},
{
abbreviation: 'quality_f',
name: 'Lake Quality Flag',
unit: '',
definition:
'Summary quality indicator for the lake measurement. Values of 0, 1, 2, and 3 indicate good, suspect, degraded, and bad measurements, respectively. Measurements that are marked as suspect may have large errors. Measurements that are marked as degraded very likely do have large errors. Measurements that are marked as bad may be nonsensicial and should be ignored.',
default: false,
always: true,
selectable: false,
fileType: 'priorlake',
plottable: false
},
{
abbreviation: 'node_dist',
name: 'Node Dispersion',
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/stores/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,7 @@ export const useMapStore = defineStore('map', () => {
console.warn('Lakes features already generated, skipping.')
return lakesFeatures.value
}
const url =
'https://arcgis.cuahsi.org/arcgis/rest/services/SWOT/world_swot_lakes/FeatureServer/0'
const url = 'https://arcgis.cuahsi.org/arcgis/rest/services/SWOT/SWOT_PLD_v201/FeatureServer/0'
lakesFeatures.value = esriLeaflet.featureLayer({
url: url,
simplifyFactor: 0.35,
Expand Down
87 changes: 50 additions & 37 deletions frontend/src/views/ChartsView.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
<template>
<template v-if="activeFeatureIsReach">
<v-container v-if="hasData" fluid fill-height>
<v-tabs v-model="chartStore.chartTab" align-tabs="center" fixed-tabs color="primary" grow>
<v-tab value="timeseries">
<v-icon :icon="mdiTimelineClock"></v-icon>
Reach Averaged
</v-tab>
<v-tab value="distance">
<v-icon :icon="mdiMapMarkerDistance"></v-icon>
Node Profile
</v-tab>
</v-tabs>
<TimeSeriesCharts v-if="chartStore.chartTab === 'timeseries'" />
<DistanceCharts v-if="chartStore.chartTab === 'distance'" />
</v-container>

<v-container v-if="fetchingData">
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading data...
</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
<template v-if="activeFeature">
<template v-if="hasData">
<v-container v-if="!activeIsLake" fluid fill-height>
<v-tabs v-model="chartStore.chartTab" align-tabs="center" fixed-tabs color="primary" grow>
<v-tab value="timeseries">
<v-icon :icon="mdiTimelineClock"></v-icon>
Reach Averaged
</v-tab>
<v-tab value="distance">
<v-icon :icon="mdiMapMarkerDistance"></v-icon>
Node Profile
</v-tab>
</v-tabs>
<TimeSeriesCharts v-if="chartStore.chartTab === 'timeseries'" />
<DistanceCharts v-if="chartStore.chartTab === 'distance'" />
</v-container>
<v-container v-else fluid fill-height>
<TimeSeriesCharts />
</v-container>
</template>
<template v-else>
<v-container v-if="fetchingData">
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading data...
</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
<v-container v-else>
<v-sheet border="md" class="pa-6 mx-auto ma-4" max-width="1200" rounded>
<span>
No data available for the selected feature. Please try querying HydroCron again or
selecting a different feature from the
<router-link :to="{ path: `/` }">Map</router-link>.
</span>
</v-sheet>
</v-container>
</template>
</template>
<template v-else>
<v-container>
<v-sheet border="md" class="pa-6 mx-auto ma-4" max-width="1200" rounded>
<span v-if="!hasData && !fetchingData">
<span>
You don't have any data to view yet. Use the
<router-link :to="{ path: `/` }">Map</router-link> to make selections.
</span>
<span v-else>
Plots are only available for reaches. Use the
<router-link :to="{ path: `/` }">Map</router-link> to select a reach.
</span>
</v-sheet>
</v-container>
</template>
Expand All @@ -55,7 +66,7 @@ import { queryHydroCron, getNodeDataForReach } from '../_helpers/hydroCron'
// TODO: register chartjs globally
import { ChartJS } from '@/_helpers/charts/charts' // eslint-disable-line

const props = defineProps({ reachId: String })
const props = defineProps({ featureId: String })
const router = useRouter()

const chartStore = useChartsStore()
Expand All @@ -77,17 +88,17 @@ watch(activeFeature, async (newActiveFeature) => {
onMounted(async () => {
mapStore.generateReachesFeatures()
if (activeFeature.value) {
// set the reach id in the url from the active feature
if (props.reachId === '') {
router.replace({ params: { reachId: activeFeature.value.properties.reach_id } })
// set the feature id in the url from the active feature
if (props.featureId === '') {
router.replace({ params: { featureId: activeFeature.value.properties.feature_id } })
}
runQuery()
}
const currentRoute = router.currentRoute.value
chartStore.checkQueryParams(currentRoute)
if (props.reachId !== '') {
if (props.featureId !== '') {
querying.value.hydrocron = true
featuresStore.setActiveFeatureById(props.reachId)
featuresStore.setActiveFeatureById(props.featureId)
}
})

Expand All @@ -97,6 +108,10 @@ const runQuery = async () => {
chartStore.buildChart(selectedFeatures.value)
querying.value.hydrocron = false

// only get node data if the feature is a reach
if (activeIsLake.value) {
return
}
querying.value.nodes = true
await getNodeDataForReach(activeFeature.value)
querying.value.nodes = false
Expand All @@ -109,7 +124,5 @@ let hasData = computed(() => chartStore.chartData && chartStore.chartData.datase
let fetchingData = computed(
() => !hasData.value && (querying.value.hydrocron || querying.value.nodes)
)
let activeFeatureIsReach = computed(() => {
return activeFeature.value && activeFeature.value.feature_type.toLowerCase() === 'reach'
})
let activeIsLake = computed(() => activeFeature.value?.feature_type.toLowerCase() === 'priorlake')
</script>
21 changes: 16 additions & 5 deletions frontend/src/views/TimeSeriesCharts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<v-card class="elevation-1" color="input">
<v-card-title> Variables </v-card-title>
<v-tabs v-model="activePlt" direction="vertical" color="primary">
<v-tab v-for="plt in chartStore.reachCharts" :value="plt" :key="plt.abbreviation">
<v-tab v-for="plt in timeSeriesCharts" :value="plt" :key="plt.abbreviation">
<template v-if="lgAndUp">
{{ plt.name }}
</template>
Expand All @@ -29,7 +29,7 @@
<v-divider class="my-2" vertical v-if="lgAndUp"></v-divider>
<v-col sm="10">
<v-window v-model="activePlt">
<v-window-item v-for="plt in chartStore.reachCharts" :key="plt.abbreviation" :value="plt">
<v-window-item v-for="plt in timeSeriesCharts" :key="plt.abbreviation" :value="plt">
<LineChart v-if="plt" class="chart" :data="chartStore.chartData" :chosenPlot="plt" />
</v-window-item>
</v-window>
Expand All @@ -45,16 +45,27 @@ import PlotActions from '../components/PlotActions.vue'
import DataQuality from '@/components/DataQuality.vue'
import TimeRangeSelector from '@/components/TimeRangeSelector.vue'
import { useChartsStore } from '../stores/charts'
import { computed } from 'vue'
import { useFeaturesStore } from '@/stores/features'
import { useDisplay } from 'vuetify'
import { onMounted } from 'vue'
import { onMounted, computed } from 'vue'
import { storeToRefs } from 'pinia'

const { lgAndUp } = useDisplay()
const chartStore = useChartsStore()
const featuresStore = useFeaturesStore()

const { activePlt, activeReachChart, reachCharts, lakeCharts } = storeToRefs(chartStore)
const { activeFeature } = storeToRefs(featuresStore)

let hasData = computed(() => chartStore.chartData && chartStore.chartData.datasets?.length > 0)
const { activePlt, activeReachChart } = storeToRefs(chartStore)

let timeSeriesCharts = computed(() => {
// decide which charts to show based on feature type
if (activeFeature.value?.feature_type === 'PriorLake') {
return lakeCharts.value
}
return reachCharts.value
})

onMounted(() => {})
</script>
Expand Down