Skip to content

Commit

Permalink
Merge pull request #16 from OpenGeoscience/load-inputs
Browse files Browse the repository at this point in the history
Load inputs
  • Loading branch information
BryonLewis authored Feb 13, 2025
2 parents e98677c + 833880c commit 5910c50
Show file tree
Hide file tree
Showing 31 changed files with 1,708 additions and 157 deletions.
27 changes: 27 additions & 0 deletions client/src/api/UVDATApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ContextWithIds,
Dataset,
DerivedRegion,
FeatureGraphData,
FileItem,
LayerCollection,
LayerCollectionLayer,
Expand All @@ -21,6 +22,7 @@ import {
RasterData,
RasterMapLayer,
SimulationType,
TableSummary,
VectorMapLayer,
} from '../types';

Expand Down Expand Up @@ -494,4 +496,29 @@ export default class UVdatApi {
public static async deleteContext(contextId: number): Promise<{ detail: string }> {
return (await UVdatApi.apiClient.delete(`/contexts/${contextId}/`)).data;
}

public static async getVectorTableSummary(layerId: number): Promise<TableSummary> {
return (await UVdatApi.apiClient.get('/vectorfeature/tabledata/table-summary/', { params: { layerId } })).data;
}

public static async getFeatureGraphData(
tableType: string,
vectorFeatureId: number,
xAxis: string = 'index',
yAxis: string = 'mean_va',
filter?: string,
filterVal?: string,
): Promise<FeatureGraphData> {
const response = await UVdatApi.apiClient.get('/vectorfeature/tabledata/feature-graph/', {
params: {
tableType,
vectorFeatureId,
xAxis,
yAxis,
filter,
filterVal,
},
});
return response.data;
}
}
24 changes: 12 additions & 12 deletions client/src/components/DataSelection/NetCDFDataConfigurator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ export default defineComponent({
}
const data = getVariableInformation(newLayerSlice.value);
if (data.startDate) {
sliceLayerRangeStep.value = (data.max / 1e6 - data.min / 1e6) / (data.steps || 1);
const startDate = new Date(data.min / 1e6);
const endDate = new Date(data.max / 1e6);
sliceLayerRangeStep.value = (data.max - data.min) / (data.steps || 1);
const startDate = new Date(data.min);
const endDate = new Date(data.max);
const diffMilli = endDate.getTime() - startDate.getTime();
const differenceInHours = diffMilli / (1000 * 60 * 60);
sliceLayerRange.value = [data.min / 1e6, data.max / 1e6];
sliceLayerRange.value = [data.min, data.max];
sliceLayerRangeStep.value = Math.round(differenceInHours / (data.steps || 1)) * (1000 * 60 * 60);
} else {
sliceLayerRange.value = [data.min, data.max];
Expand Down Expand Up @@ -565,10 +565,10 @@ export default defineComponent({
<span> {{ modelValue.toFixed(2) }}</span>
</template>
<template #prepend>
<span>{{ getVariableMinMax(newLayerX)[0] }}</span>
<span>{{ getVariableMinMax(newLayerX)[0].toFixed(2) }}</span>
</template>
<template #append>
<span>{{ getVariableMinMax(newLayerX)[1] }}</span>
<span>{{ getVariableMinMax(newLayerX)[1].toFixed(2) }}</span>
</template>
</v-range-slider>
</div>
Expand All @@ -589,10 +589,10 @@ export default defineComponent({
<span> {{ modelValue.toFixed(2) }}</span>
</template>
<template #prepend>
<span>{{ getVariableInformation(newLayerY)?.min }}</span>
<span>{{ getVariableInformation(newLayerY)?.min.toFixed(2) }}</span>
</template>
<template #append>
<span>{{ getVariableInformation(newLayerY)?.max }}</span>
<span>{{ getVariableInformation(newLayerY)?.max.toFixed(2) }}</span>
</template>
</v-range-slider>
<v-tooltip v-if="getVariableInformation(newLayerY)?.geospatial === 'latitude'">
Expand Down Expand Up @@ -654,8 +654,8 @@ export default defineComponent({
<v-range-slider
v-model="sliceLayerRange"
:step="sliceLayerRangeStep"
:min="getVariableInformation(newLayerSlice).min / 1e6"
:max="getVariableInformation(newLayerSlice).max / 1e6"
:min="getVariableInformation(newLayerSlice).min"
:max="getVariableInformation(newLayerSlice).max"
class="pt-2"
hide-details
>
Expand All @@ -664,10 +664,10 @@ export default defineComponent({
</template>

<template #prepend>
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.min / 1e6) }}</span>
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.min) }}</span>
</template>
<template #append>
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.max / 1e6) }}</span>
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.max) }}</span>
</template>
</v-range-slider>
<v-row align="center" justify="center" class="py-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { drawBarChart } from '../Metadata/drawChart';
// FeatureChart TypeScript interface (as provided)
export default defineComponent({
name: 'RenderFeatureChart',
name: 'PropertyFeatureChart',
props: {
featureChart: {
type: Object as PropType<FeatureChartWithData>,
Expand Down
37 changes: 33 additions & 4 deletions client/src/components/FeatureSelection/SelectedFeature.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
PropType, Ref, computed, defineComponent, onMounted, ref,
} from 'vue';
import MapStore from '../../MapStore';
import { ClickedProps, FeatureChartWithData } from '../../types';
import { ClickedProps, FeatureChartWithData, VectorFeatureTableGraph } from '../../types';
import { colorGenerator } from '../../map/mapColors';
import RenderFeatureChart from './RenderFeatureChart.vue';
import PropertyFeatureChart from './PropertyFeatureChart.vue';
import VectorFeatureChart from './VectorFeatureChart.vue';
export default defineComponent({
components: {
RenderFeatureChart,
PropertyFeatureChart,
VectorFeatureChart,
},
props: {
data: {
Expand All @@ -28,6 +30,8 @@ export default defineComponent({
const mapLayerId = ref(props.data.layerId);
const featureId = ref(props.data.id as number);
const enabledChartPanels: Ref<number[]> = ref([]);
const enabledVectorChartPanel: Ref<number[]> = ref([]);
const vectorGraphs: Ref<VectorFeatureTableGraph[]> = ref([]);
const processLayerProps = () => {
const found = MapStore.selectedVectorMapLayers.value.find((item) => item.id === props.data.layerId);
if (found?.default_style.properties) {
Expand Down Expand Up @@ -65,6 +69,9 @@ export default defineComponent({
});
}
}
if (found?.default_style.vectorFeatureTableGraphs) {
vectorGraphs.value = found.default_style.vectorFeatureTableGraphs;
}
};
onMounted(() => processLayerProps());
Expand All @@ -81,8 +88,10 @@ export default defineComponent({
featureId,
featureCharts,
enabledChartPanels,
enabledVectorChartPanel,
selectedFeatureExpanded,
toggleFeatureExpaned,
vectorGraphs,
};
},
});
Expand Down Expand Up @@ -137,7 +146,24 @@ export default defineComponent({
</v-icon> {{ featureChart.name }}
</v-expansion-panel-title>
<v-expansion-panel-text>
<render-feature-chart :feature-chart="featureChart" />
<property-feature-chart :feature-chart="featureChart" />
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
<v-expansion-panels v-if="vectorGraphs.length" v-model="enabledVectorChartPanel" multiple>
<v-expansion-panel
v-for="vectorGraph, index in vectorGraphs"
:key="`${vectorGraph.name}_${index}`"
:value="index"
class="ma-0 pa-0"
>
<v-expansion-panel-title>
<v-icon class="pr-2">
mdi-chart-line
</v-icon> {{ vectorGraph.name }}
</v-expansion-panel-title>
<v-expansion-panel-text class="ma-0">
<vector-feature-chart :graph-info="vectorGraph" :vector-feature-id="featureId" />
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
Expand All @@ -146,4 +172,7 @@ export default defineComponent({
</template>

<style scoped>
.v-expansion-panel-text>>> .v-expansion-panel-text__wrapper {
padding: 8px !important;
}
</style>
191 changes: 191 additions & 0 deletions client/src/components/FeatureSelection/VectorFeatureChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<script lang="ts">
import {
PropType, defineComponent, nextTick, ref, watch,
} from 'vue';
import * as d3 from 'd3';
import UVdatApi from '../../api/UVDATApi';
import { FeatureGraphData, VectorFeatureTableGraph } from '../../types';
export default defineComponent({
name: 'FeatureGraph',
props: {
graphInfo: {
type: Object as PropType<VectorFeatureTableGraph>,
required: true,
},
vectorFeatureId: {
type: Number as PropType<number>,
required: true,
},
},
setup(props) {
const graphContainer = ref<SVGSVGElement | null>(null);
const graphDialogContainer = ref<SVGSVGElement | null>(null);
const graphData = ref<FeatureGraphData | null>(null);
const dialogVisible = ref(false);
// Render graph using D3
const renderGraph = (data: FeatureGraphData, container = 'graphContainer') => {
const localContainer = container === 'graphContainer' ? graphContainer : graphDialogContainer;
if (!localContainer.value || !data) return;
const svg = d3.select(localContainer.value);
svg.selectAll('*').remove(); // Clear any existing content in the SVG
const margin = {
top: 20, right: container === 'graphContainer' ? 0 : 20, bottom: 40, left: 40,
};
// Set the maximum width to 250px
const width = localContainer.value?.clientWidth || 250 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const x = d3.scaleLinear().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const line = d3.line()
.x((d: [number, number]) => x(d[0]))
.y((d: [number, number]) => y(d[1]));
let dataForGraph: { data: [number, number][], filterVal?: string } | undefined;
// Check for default data or apply filter if necessary
if (data.graphs[props.vectorFeatureId]) {
dataForGraph = data.graphs[props.vectorFeatureId];
}
if (!dataForGraph) {
return; // Return if no data is available
}
// Set the domain for the axes, ensuring we handle empty arrays correctly
const xExtent = d3.extent(dataForGraph.data.map((item) => item[0]));
const yExtent = d3.extent(dataForGraph.data.map((item) => item[1]));
// Fallback to zero if data is empty
x.domain(xExtent[0] !== undefined ? xExtent : [0, 1]);
y.domain(yExtent[0] !== undefined ? yExtent : [0, 1]);
// Create the graph container
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Add the line path to the graph
svg.append('path')
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('stroke-width', 1.5)
.attr('d', line(dataForGraph.data.sort((a, b) => a[0] - b[0])))
.attr('transform', `translate(${margin.left},${margin.top})`);
// Add the X-axis
g.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x).ticks(5));
// Add the Y-axis
g.append('g')
.attr('class', 'y axis')
.call(d3.axisLeft(y));
};
// Fetch feature graph data when component is mounted or props change
const fetchFeatureGraphData = async () => {
try {
const data = await UVdatApi.getFeatureGraphData(
props.graphInfo.type, // Use graphInfo.type (tableType) instead of mapLayerId
props.vectorFeatureId,
props.graphInfo.xAxis,
props.graphInfo.yAxis,
props.graphInfo.indexer,
);
graphData.value = data;
renderGraph(data);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error fetching feature graph data:', error);
}
};
// Watch for prop changes and refetch data
watch(
() => [props.graphInfo, props.vectorFeatureId],
() => fetchFeatureGraphData(),
{ immediate: true },
);
// Open the dialog to display a larger graph
const openDialog = () => {
dialogVisible.value = true;
nextTick(() => {
if (graphData.value) {
renderGraph(graphData.value, 'graphDialogContainer');
}
});
};
return {
graphContainer,
graphDialogContainer,
graphData,
dialogVisible,
openDialog,
};
},
});
</script>

<template>
<div>
<!-- Button to open dialog -->
<v-btn color="primary" @click="openDialog">
View Larger Graph
</v-btn>

<!-- Graph container -->
<svg ref="graphContainer" width="100%" height="400" class="selectedFeatureSVG" />

<!-- Dialog for larger chart -->
<v-dialog v-model="dialogVisible" max-width="800px">
<v-card>
<v-card-title>
<span class="headline">Feature Graph</span>
</v-card-title>
<v-card-text>
<svg ref="graphDialogContainer" width="100%" height="500" />
</v-card-text>
<v-card-actions>
<v-btn @click="dialogVisible = false">
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>

<style scoped>
.selectedFeatureSVG {
max-width: 250px;
width: 100%;
height: auto;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5;
}
.axis path,
.axis line {
fill: none;
shape-rendering: crispEdges;
}
.x.axis text,
.y.axis text {
font-size: 12px;
}
</style>
Loading

0 comments on commit 5910c50

Please sign in to comment.