Skip to content

Commit 5910c50

Browse files
authored
Merge pull request #16 from OpenGeoscience/load-inputs
Load inputs
2 parents e98677c + 833880c commit 5910c50

31 files changed

Lines changed: 1708 additions & 157 deletions

client/src/api/UVDATApi.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ContextWithIds,
99
Dataset,
1010
DerivedRegion,
11+
FeatureGraphData,
1112
FileItem,
1213
LayerCollection,
1314
LayerCollectionLayer,
@@ -21,6 +22,7 @@ import {
2122
RasterData,
2223
RasterMapLayer,
2324
SimulationType,
25+
TableSummary,
2426
VectorMapLayer,
2527
} from '../types';
2628

@@ -494,4 +496,29 @@ export default class UVdatApi {
494496
public static async deleteContext(contextId: number): Promise<{ detail: string }> {
495497
return (await UVdatApi.apiClient.delete(`/contexts/${contextId}/`)).data;
496498
}
499+
500+
public static async getVectorTableSummary(layerId: number): Promise<TableSummary> {
501+
return (await UVdatApi.apiClient.get('/vectorfeature/tabledata/table-summary/', { params: { layerId } })).data;
502+
}
503+
504+
public static async getFeatureGraphData(
505+
tableType: string,
506+
vectorFeatureId: number,
507+
xAxis: string = 'index',
508+
yAxis: string = 'mean_va',
509+
filter?: string,
510+
filterVal?: string,
511+
): Promise<FeatureGraphData> {
512+
const response = await UVdatApi.apiClient.get('/vectorfeature/tabledata/feature-graph/', {
513+
params: {
514+
tableType,
515+
vectorFeatureId,
516+
xAxis,
517+
yAxis,
518+
filter,
519+
filterVal,
520+
},
521+
});
522+
return response.data;
523+
}
497524
}

client/src/components/DataSelection/NetCDFDataConfigurator.vue

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,12 @@ export default defineComponent({
194194
}
195195
const data = getVariableInformation(newLayerSlice.value);
196196
if (data.startDate) {
197-
sliceLayerRangeStep.value = (data.max / 1e6 - data.min / 1e6) / (data.steps || 1);
198-
const startDate = new Date(data.min / 1e6);
199-
const endDate = new Date(data.max / 1e6);
197+
sliceLayerRangeStep.value = (data.max - data.min) / (data.steps || 1);
198+
const startDate = new Date(data.min);
199+
const endDate = new Date(data.max);
200200
const diffMilli = endDate.getTime() - startDate.getTime();
201201
const differenceInHours = diffMilli / (1000 * 60 * 60);
202-
sliceLayerRange.value = [data.min / 1e6, data.max / 1e6];
202+
sliceLayerRange.value = [data.min, data.max];
203203
sliceLayerRangeStep.value = Math.round(differenceInHours / (data.steps || 1)) * (1000 * 60 * 60);
204204
} else {
205205
sliceLayerRange.value = [data.min, data.max];
@@ -565,10 +565,10 @@ export default defineComponent({
565565
<span> {{ modelValue.toFixed(2) }}</span>
566566
</template>
567567
<template #prepend>
568-
<span>{{ getVariableMinMax(newLayerX)[0] }}</span>
568+
<span>{{ getVariableMinMax(newLayerX)[0].toFixed(2) }}</span>
569569
</template>
570570
<template #append>
571-
<span>{{ getVariableMinMax(newLayerX)[1] }}</span>
571+
<span>{{ getVariableMinMax(newLayerX)[1].toFixed(2) }}</span>
572572
</template>
573573
</v-range-slider>
574574
</div>
@@ -589,10 +589,10 @@ export default defineComponent({
589589
<span> {{ modelValue.toFixed(2) }}</span>
590590
</template>
591591
<template #prepend>
592-
<span>{{ getVariableInformation(newLayerY)?.min }}</span>
592+
<span>{{ getVariableInformation(newLayerY)?.min.toFixed(2) }}</span>
593593
</template>
594594
<template #append>
595-
<span>{{ getVariableInformation(newLayerY)?.max }}</span>
595+
<span>{{ getVariableInformation(newLayerY)?.max.toFixed(2) }}</span>
596596
</template>
597597
</v-range-slider>
598598
<v-tooltip v-if="getVariableInformation(newLayerY)?.geospatial === 'latitude'">
@@ -654,8 +654,8 @@ export default defineComponent({
654654
<v-range-slider
655655
v-model="sliceLayerRange"
656656
:step="sliceLayerRangeStep"
657-
:min="getVariableInformation(newLayerSlice).min / 1e6"
658-
:max="getVariableInformation(newLayerSlice).max / 1e6"
657+
:min="getVariableInformation(newLayerSlice).min"
658+
:max="getVariableInformation(newLayerSlice).max"
659659
class="pt-2"
660660
hide-details
661661
>
@@ -664,10 +664,10 @@ export default defineComponent({
664664
</template>
665665

666666
<template #prepend>
667-
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.min / 1e6) }}</span>
667+
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.min) }}</span>
668668
</template>
669669
<template #append>
670-
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.max / 1e6) }}</span>
670+
<span>{{ convertTimestampNSToDatetimeString(getVariableInformation(newLayerSlice)?.max) }}</span>
671671
</template>
672672
</v-range-slider>
673673
<v-row align="center" justify="center" class="py-2">

client/src/components/FeatureSelection/RenderFeatureChart.vue renamed to client/src/components/FeatureSelection/PropertyFeatureChart.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { drawBarChart } from '../Metadata/drawChart';
77
88
// FeatureChart TypeScript interface (as provided)
99
export default defineComponent({
10-
name: 'RenderFeatureChart',
10+
name: 'PropertyFeatureChart',
1111
props: {
1212
featureChart: {
1313
type: Object as PropType<FeatureChartWithData>,

client/src/components/FeatureSelection/SelectedFeature.vue

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
PropType, Ref, computed, defineComponent, onMounted, ref,
44
} from 'vue';
55
import MapStore from '../../MapStore';
6-
import { ClickedProps, FeatureChartWithData } from '../../types';
6+
import { ClickedProps, FeatureChartWithData, VectorFeatureTableGraph } from '../../types';
77
import { colorGenerator } from '../../map/mapColors';
8-
import RenderFeatureChart from './RenderFeatureChart.vue';
8+
import PropertyFeatureChart from './PropertyFeatureChart.vue';
9+
import VectorFeatureChart from './VectorFeatureChart.vue';
910
1011
export default defineComponent({
1112
components: {
12-
RenderFeatureChart,
13+
PropertyFeatureChart,
14+
VectorFeatureChart,
1315
},
1416
props: {
1517
data: {
@@ -28,6 +30,8 @@ export default defineComponent({
2830
const mapLayerId = ref(props.data.layerId);
2931
const featureId = ref(props.data.id as number);
3032
const enabledChartPanels: Ref<number[]> = ref([]);
33+
const enabledVectorChartPanel: Ref<number[]> = ref([]);
34+
const vectorGraphs: Ref<VectorFeatureTableGraph[]> = ref([]);
3135
const processLayerProps = () => {
3236
const found = MapStore.selectedVectorMapLayers.value.find((item) => item.id === props.data.layerId);
3337
if (found?.default_style.properties) {
@@ -65,6 +69,9 @@ export default defineComponent({
6569
});
6670
}
6771
}
72+
if (found?.default_style.vectorFeatureTableGraphs) {
73+
vectorGraphs.value = found.default_style.vectorFeatureTableGraphs;
74+
}
6875
};
6976
onMounted(() => processLayerProps());
7077
@@ -81,8 +88,10 @@ export default defineComponent({
8188
featureId,
8289
featureCharts,
8390
enabledChartPanels,
91+
enabledVectorChartPanel,
8492
selectedFeatureExpanded,
8593
toggleFeatureExpaned,
94+
vectorGraphs,
8695
};
8796
},
8897
});
@@ -137,7 +146,24 @@ export default defineComponent({
137146
</v-icon> {{ featureChart.name }}
138147
</v-expansion-panel-title>
139148
<v-expansion-panel-text>
140-
<render-feature-chart :feature-chart="featureChart" />
149+
<property-feature-chart :feature-chart="featureChart" />
150+
</v-expansion-panel-text>
151+
</v-expansion-panel>
152+
</v-expansion-panels>
153+
<v-expansion-panels v-if="vectorGraphs.length" v-model="enabledVectorChartPanel" multiple>
154+
<v-expansion-panel
155+
v-for="vectorGraph, index in vectorGraphs"
156+
:key="`${vectorGraph.name}_${index}`"
157+
:value="index"
158+
class="ma-0 pa-0"
159+
>
160+
<v-expansion-panel-title>
161+
<v-icon class="pr-2">
162+
mdi-chart-line
163+
</v-icon> {{ vectorGraph.name }}
164+
</v-expansion-panel-title>
165+
<v-expansion-panel-text class="ma-0">
166+
<vector-feature-chart :graph-info="vectorGraph" :vector-feature-id="featureId" />
141167
</v-expansion-panel-text>
142168
</v-expansion-panel>
143169
</v-expansion-panels>
@@ -146,4 +172,7 @@ export default defineComponent({
146172
</template>
147173

148174
<style scoped>
175+
.v-expansion-panel-text>>> .v-expansion-panel-text__wrapper {
176+
padding: 8px !important;
177+
}
149178
</style>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<script lang="ts">
2+
import {
3+
PropType, defineComponent, nextTick, ref, watch,
4+
} from 'vue';
5+
import * as d3 from 'd3';
6+
import UVdatApi from '../../api/UVDATApi';
7+
import { FeatureGraphData, VectorFeatureTableGraph } from '../../types';
8+
9+
export default defineComponent({
10+
name: 'FeatureGraph',
11+
props: {
12+
graphInfo: {
13+
type: Object as PropType<VectorFeatureTableGraph>,
14+
required: true,
15+
},
16+
vectorFeatureId: {
17+
type: Number as PropType<number>,
18+
required: true,
19+
},
20+
},
21+
setup(props) {
22+
const graphContainer = ref<SVGSVGElement | null>(null);
23+
const graphDialogContainer = ref<SVGSVGElement | null>(null);
24+
const graphData = ref<FeatureGraphData | null>(null);
25+
const dialogVisible = ref(false);
26+
27+
// Render graph using D3
28+
const renderGraph = (data: FeatureGraphData, container = 'graphContainer') => {
29+
const localContainer = container === 'graphContainer' ? graphContainer : graphDialogContainer;
30+
if (!localContainer.value || !data) return;
31+
32+
const svg = d3.select(localContainer.value);
33+
svg.selectAll('*').remove(); // Clear any existing content in the SVG
34+
35+
const margin = {
36+
top: 20, right: container === 'graphContainer' ? 0 : 20, bottom: 40, left: 40,
37+
};
38+
39+
// Set the maximum width to 250px
40+
const width = localContainer.value?.clientWidth || 250 - margin.left - margin.right;
41+
const height = 400 - margin.top - margin.bottom;
42+
43+
const x = d3.scaleLinear().range([0, width]);
44+
const y = d3.scaleLinear().range([height, 0]);
45+
46+
const line = d3.line()
47+
.x((d: [number, number]) => x(d[0]))
48+
.y((d: [number, number]) => y(d[1]));
49+
50+
let dataForGraph: { data: [number, number][], filterVal?: string } | undefined;
51+
52+
// Check for default data or apply filter if necessary
53+
if (data.graphs[props.vectorFeatureId]) {
54+
dataForGraph = data.graphs[props.vectorFeatureId];
55+
}
56+
57+
if (!dataForGraph) {
58+
return; // Return if no data is available
59+
}
60+
61+
// Set the domain for the axes, ensuring we handle empty arrays correctly
62+
const xExtent = d3.extent(dataForGraph.data.map((item) => item[0]));
63+
const yExtent = d3.extent(dataForGraph.data.map((item) => item[1]));
64+
65+
// Fallback to zero if data is empty
66+
x.domain(xExtent[0] !== undefined ? xExtent : [0, 1]);
67+
y.domain(yExtent[0] !== undefined ? yExtent : [0, 1]);
68+
69+
// Create the graph container
70+
const g = svg.append('g')
71+
.attr('transform', `translate(${margin.left},${margin.top})`);
72+
73+
// Add the line path to the graph
74+
svg.append('path')
75+
.attr('fill', 'none')
76+
.attr('stroke', 'steelblue')
77+
.attr('stroke-width', 1.5)
78+
.attr('d', line(dataForGraph.data.sort((a, b) => a[0] - b[0])))
79+
.attr('transform', `translate(${margin.left},${margin.top})`);
80+
81+
// Add the X-axis
82+
g.append('g')
83+
.attr('class', 'x axis')
84+
.attr('transform', `translate(0,${height})`)
85+
.call(d3.axisBottom(x).ticks(5));
86+
87+
// Add the Y-axis
88+
g.append('g')
89+
.attr('class', 'y axis')
90+
.call(d3.axisLeft(y));
91+
};
92+
93+
// Fetch feature graph data when component is mounted or props change
94+
const fetchFeatureGraphData = async () => {
95+
try {
96+
const data = await UVdatApi.getFeatureGraphData(
97+
props.graphInfo.type, // Use graphInfo.type (tableType) instead of mapLayerId
98+
props.vectorFeatureId,
99+
props.graphInfo.xAxis,
100+
props.graphInfo.yAxis,
101+
props.graphInfo.indexer,
102+
);
103+
graphData.value = data;
104+
renderGraph(data);
105+
} catch (error) {
106+
// eslint-disable-next-line no-console
107+
console.error('Error fetching feature graph data:', error);
108+
}
109+
};
110+
111+
// Watch for prop changes and refetch data
112+
watch(
113+
() => [props.graphInfo, props.vectorFeatureId],
114+
() => fetchFeatureGraphData(),
115+
{ immediate: true },
116+
);
117+
118+
// Open the dialog to display a larger graph
119+
const openDialog = () => {
120+
dialogVisible.value = true;
121+
nextTick(() => {
122+
if (graphData.value) {
123+
renderGraph(graphData.value, 'graphDialogContainer');
124+
}
125+
});
126+
};
127+
128+
return {
129+
graphContainer,
130+
graphDialogContainer,
131+
graphData,
132+
dialogVisible,
133+
openDialog,
134+
};
135+
},
136+
});
137+
</script>
138+
139+
<template>
140+
<div>
141+
<!-- Button to open dialog -->
142+
<v-btn color="primary" @click="openDialog">
143+
View Larger Graph
144+
</v-btn>
145+
146+
<!-- Graph container -->
147+
<svg ref="graphContainer" width="100%" height="400" class="selectedFeatureSVG" />
148+
149+
<!-- Dialog for larger chart -->
150+
<v-dialog v-model="dialogVisible" max-width="800px">
151+
<v-card>
152+
<v-card-title>
153+
<span class="headline">Feature Graph</span>
154+
</v-card-title>
155+
<v-card-text>
156+
<svg ref="graphDialogContainer" width="100%" height="500" />
157+
</v-card-text>
158+
<v-card-actions>
159+
<v-btn @click="dialogVisible = false">
160+
Close
161+
</v-btn>
162+
</v-card-actions>
163+
</v-card>
164+
</v-dialog>
165+
</div>
166+
</template>
167+
168+
<style scoped>
169+
.selectedFeatureSVG {
170+
max-width: 250px;
171+
width: 100%;
172+
height: auto;
173+
}
174+
175+
.line {
176+
fill: none;
177+
stroke: steelblue;
178+
stroke-width: 1.5;
179+
}
180+
181+
.axis path,
182+
.axis line {
183+
fill: none;
184+
shape-rendering: crispEdges;
185+
}
186+
187+
.x.axis text,
188+
.y.axis text {
189+
font-size: 12px;
190+
}
191+
</style>

0 commit comments

Comments
 (0)