Skip to content

Commit 8b13ba6

Browse files
authored
Merge pull request #36 from codeuniversity/feature/ndvi-difference-graphs
Create NDVI difference graph
2 parents 1b9f484 + caac232 commit 8b13ba6

File tree

5 files changed

+264
-45
lines changed

5 files changed

+264
-45
lines changed

frontend/src/components/CorrelationGraphs/TemperatureNdviBarAndLine.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default {
2222
const temperatureData = ref(null)
2323
const ndviData = ref(null)
2424
const startDate = ref(1514761200) // 2018-01-01
25-
const endDate = ref(1704063599) // 2023-12-31
25+
const endDate = ref(1733007599) // 2024-11-30
2626
const temporalResolution = ref("Monthly") // options: "Daily", "Monthly"
2727
const aggregation = ref("Mean") // options: "Mean", "Median", "Max", "Min"
2828
@@ -92,7 +92,7 @@ export default {
9292
};
9393
9494
const layout = {
95-
title: 'NDVI vs. Monthly Temperature for Tempelhofer Feld (2018-2023)',
95+
title: 'NDVI vs. Monthly Temperature for Tempelhofer Feld (2018-2024)',
9696
xaxis: { title: 'Date', type: 'date', rangeslider: { visible: true } },
9797
yaxis: { title: 'Temperature (°C)' },
9898
yaxis2: {

frontend/src/components/CorrelationGraphs/TemperatureNdviScatter.vue

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
<script>
1414
import axios from 'axios'
1515
import Plotly from 'plotly.js-dist-min'
16-
import { onMounted, ref, render } from 'vue'
16+
import { onMounted, ref } from 'vue'
1717
1818
export default {
1919
name: 'TemperatureNdviScatter',
2020
setup() {
2121
const temperatureData = ref(null)
2222
const ndviData = ref(null)
2323
const startDate = ref(1514761200) // 2018-01-01
24-
const endDate = ref(1704063599) // 2023-12-31
24+
const endDate = ref(1733007599) // 2024-11-30
2525
const temporalResolution = ref("Monthly") // options: "Daily", "Monthly"
2626
const aggregation = ref("Mean") // options: "Mean", "Median", "Max", "Min"
2727
@@ -63,59 +63,59 @@
6363
console.error("Error fetching NDVI data:", error)
6464
}
6565
}
66-
67-
const calculateRegression = (xValues, yValues) => {
68-
const n = xValues.length
69-
const sumX = xValues.reduce((acc, val) => acc + val, 0)
70-
const sumY = yValues.reduce((acc, val) => acc + val, 0)
71-
const sumXY = xValues.reduce((acc, val, index) => acc + val * yValues[index], 0)
72-
const sumX2 = xValues.reduce((acc, val) => acc + val * val, 0)
73-
74-
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
75-
const intercept = (sumY - slope * sumX) / n
76-
77-
return { slope, intercept }
78-
}
7966
8067
const renderPlot = () => {
8168
if (temperatureData.value && ndviData.value) {
8269
const tempValues = temperatureData.value.data.map(entry => entry.value)
8370
const ndviValues = ndviData.value.data.map(entry => entry.value)
8471
85-
const { slope, intercept } = calculateRegression(tempValues, ndviValues)
86-
const trendLineValues = tempValues.map(x => slope * x + intercept)
87-
const trendlineEquation = `y = ${slope.toFixed(2)}x + ${intercept.toFixed(2)}`;
72+
const timestamps = temperatureData.value.data.map(entry => entry.timestamp);
73+
const monthsAndYears = timestamps.map(ts => {
74+
const date = new Date(ts * 1000)
75+
const month = date.toLocaleString('default', { month: 'long' })
76+
const year = date.getFullYear()
77+
return { month, year }
78+
})
79+
80+
// Map months to a circular grayscale gradient
81+
const months = monthsAndYears.map(({ month }) => new Date(Date.parse(month + " 1")).getMonth())
82+
const monthColors = months.map(month => {
83+
// Convert month index (0-11) to circular position
84+
const angle = (month / 12) * 2 * Math.PI // 0-2π range
85+
// Use cosine to create smooth gradient: July (π) = 0 (black), January (0) = 1 (white)
86+
const intensity = Math.round((Math.cos(angle) + 1) / 2 * 255) // Scale cosine to 0-255
87+
return `rgb(${intensity}, ${intensity}, ${intensity})`
88+
})
8889
8990
const scatterTrace = {
9091
x: tempValues,
9192
y: ndviValues,
9293
mode: 'markers',
93-
marker: { color: 'green' },
94+
marker: {
95+
color: monthColors,
96+
size: 8,
97+
line: {
98+
color: 'black',
99+
width: 1,
100+
},
101+
},
94102
type: 'scatter',
95-
name: 'Temperature vs NDVI'
96-
}
97-
98-
const trendLineTrace = {
99-
x: tempValues,
100-
y: trendLineValues,
101-
mode: 'lines',
102-
line: { color: 'grey', dash: 'dash' },
103-
type: 'scatter',
104-
name: `Trend Line: ${trendlineEquation}`
105-
}
103+
name: 'Temperature vs. NDVI',
104+
hovertemplate: '%{x}°C<br>NDVI: %{y}<br>%{customdata.month} %{customdata.year}<extra></extra>',
105+
customdata: monthsAndYears,
106+
};
106107
107108
const layout = {
108109
title: 'Scatter Plot of Temperature vs. NDVI',
109110
xaxis: { title: 'Temperature (°C)' },
110111
yaxis: { title: 'NDVI' },
111112
template: 'plotly_white',
112-
legend: { x: 1.02, y: 0.5 },
113-
}
113+
};
114114
115-
Plotly.newPlot('plotlyScatterTemperatureNdvi', [scatterTrace, trendLineTrace], layout)
115+
Plotly.newPlot('plotlyScatterTemperatureNdvi', [scatterTrace], layout)
116116
}
117117
}
118-
118+
119119
onMounted(() => {
120120
fetchTemperatureData()
121121
fetchNdviData()

frontend/src/components/MainSection.vue

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
<v-container class="main-section" fluid>
33
<TemperatureNdviBarAndLine />
44
<TemperatureNdviScatter />
5-
<Images />
5+
<TempDifferenceGraph />
6+
<SoilTempDifferenceGraph />
7+
<NdviDifferenceGraph />
8+
<NdviOverlayGraph />
9+
<!-- <Images />
610
<NdviComparisonGraph />
711
<NdviSelectMonthGraph />
8-
<NdviOverlayGraph />
912
<MedianTempGraph />
1013
<MeanSoilTempGraph />
1114
<MeanSoilMoistureGraph />
1215
<AugustMeanSoilTempGraph />
13-
<SelectMonthMeanSoilTempGraph />
14-
<TempDifferenceGraph />
15-
<SoilTempDifferenceGraph />
16+
<SelectMonthMeanSoilTempGraph /> -->
1617
</v-container>
1718
</template>
1819

@@ -30,6 +31,7 @@ import TemperatureNdviBarAndLine from "./CorrelationGraphs/TemperatureNdviBarAnd
3031
import TemperatureNdviScatter from "./CorrelationGraphs/TemperatureNdviScatter.vue"
3132
import TempDifferenceGraph from "./WeatherGraphs/TempDifferenceGraph.vue"
3233
import SoilTempDifferenceGraph from "./WeatherGraphs/SoilTempDifferenceGraph.vue"
34+
import NdviDifferenceGraph from "./NdviGraphs/NdviDifferenceGraph.vue"
3335
3436
export default {
3537
name: 'MainSection',
@@ -46,7 +48,8 @@ export default {
4648
TempDifferenceGraph,
4749
SoilTempDifferenceGraph,
4850
TemperatureNdviBarAndLine,
49-
TemperatureNdviScatter
51+
TemperatureNdviScatter,
52+
NdviDifferenceGraph
5053
}
5154
}
5255
</script>
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<template>
2+
<div>
3+
<h2>{{ graphTitle }}</h2>
4+
5+
<!-- Month Picker -->
6+
<div class="date-picker">
7+
<label>
8+
Select Month:
9+
<select v-model="selectedMonth" @change="updateGraph">
10+
<option v-for="(month, index) in months" :key="index" :value="index">
11+
{{ month }}
12+
</option>
13+
</select>
14+
</label>
15+
</div>
16+
17+
<!-- Plotly Chart -->
18+
<div ref="plotlyChart" style="width: 100%; height: 400px;"></div>
19+
</div>
20+
</template>
21+
22+
<script>
23+
import { ref, onMounted, computed } from "vue";
24+
import axios from "axios";
25+
import Plotly from "plotly.js-dist-min";
26+
27+
export default {
28+
name: "NdviDifferenceGraph",
29+
setup() {
30+
const weatherData = ref(null);
31+
const startDate = ref("2018-01-01");
32+
const endDate = ref("2024-12-05");
33+
const selectedMonth = ref(0); // Default to January (0-indexed)
34+
const months = ref([
35+
"January",
36+
"February",
37+
"March",
38+
"April",
39+
"May",
40+
"June",
41+
"July",
42+
"August",
43+
"September",
44+
"October",
45+
"November",
46+
"December",
47+
]);
48+
const plotData = ref([]);
49+
const plotlyChart = ref(null);
50+
const historicalMeans = ref([]);
51+
52+
const graphTitle = computed(() => {
53+
return `Difference from Mean NDVI (2018-2021) in ${
54+
months.value[selectedMonth.value]
55+
} for Tempelhofer Feld`;
56+
});
57+
58+
// Fetch and calculate historical means for all months (2018-2021)
59+
const fetchHistoricalMeans = async () => {
60+
const apiUrl = "https://thf-climate-run-1020174331409.europe-west3.run.app/index/ndvi";
61+
const params = {
62+
startDate: new Date("2018-01-01").getTime() / 1000,
63+
endDate: new Date("2021-12-31").getTime() / 1000,
64+
location: "TEMPELHOFER_FELD",
65+
temporalResolution: "MONTHLY",
66+
aggregation: "MEAN",
67+
};
68+
69+
try {
70+
const response = await axios.get(apiUrl, { params });
71+
const data = response.data?.data;
72+
73+
if (!data || !Array.isArray(data)) {
74+
console.error("Unexpected data format for historical means:", response);
75+
return;
76+
}
77+
78+
// Calculate means for each month (0 = January, 1 = February, etc.)
79+
const monthlyMeans = Array(12).fill(0).map((_, monthIndex) => {
80+
const monthData = data.filter((entry) => {
81+
const date = new Date(entry.timestamp * 1000);
82+
return date.getMonth() === monthIndex;
83+
});
84+
85+
const temperatures = monthData.map((entry) => entry.value);
86+
const total = temperatures.reduce((sum, temp) => sum + temp, 0);
87+
return temperatures.length > 0 ? total / temperatures.length : null;
88+
});
89+
90+
historicalMeans.value = monthlyMeans;
91+
} catch (error) {
92+
console.error("Error fetching historical means:", error);
93+
}
94+
};
95+
96+
// Fetch current weather data
97+
const fetchData = async () => {
98+
const apiUrl = "https://thf-climate-run-1020174331409.europe-west3.run.app/index/ndvi";
99+
const params = {
100+
startDate: new Date(startDate.value).getTime() / 1000,
101+
endDate: new Date(endDate.value).getTime() / 1000,
102+
location: "TEMPELHOFER_FELD",
103+
temporalResolution: "MONTHLY",
104+
aggregation: "MEAN",
105+
};
106+
107+
try {
108+
const response = await axios.get(apiUrl, { params });
109+
weatherData.value = response.data;
110+
processData(response.data);
111+
renderPlot();
112+
} catch (error) {
113+
console.error("Error fetching NDVI data:", error);
114+
}
115+
};
116+
117+
// Process data to compute deviations for the selected month
118+
const processData = (apiResponse) => {
119+
if (!apiResponse.data || !Array.isArray(apiResponse.data)) {
120+
console.log("Unexpected data format:", apiResponse);
121+
return;
122+
}
123+
124+
const historicalMean = historicalMeans.value[selectedMonth.value];
125+
if (historicalMean === null) {
126+
console.error(`No historical mean available for month: ${selectedMonth.value}`);
127+
return;
128+
}
129+
130+
const filteredData = apiResponse.data.filter((entry) => {
131+
const date = new Date(entry.timestamp * 1000);
132+
return date.getMonth() === selectedMonth.value;
133+
});
134+
135+
const years = filteredData.map((entry) =>
136+
new Date(entry.timestamp * 1000).getFullYear().toString()
137+
);
138+
const deviations = filteredData.map(
139+
(entry) => entry.value - historicalMean
140+
);
141+
142+
plotData.value = [
143+
{
144+
x: years,
145+
y: deviations,
146+
type: "bar",
147+
name: months.value[selectedMonth.value],
148+
marker: {
149+
color: deviations.map((dev) =>
150+
dev >= 0 ? "darkred" : "darkblue"
151+
),
152+
},
153+
text: deviations.map((dev) => `${dev.toFixed(2)}`),
154+
hoverinfo: "text",
155+
textposition: "none",
156+
},
157+
];
158+
};
159+
160+
// Render the Plotly chart
161+
const renderPlot = () => {
162+
const layout = {
163+
title: graphTitle.value,
164+
xaxis: { title: "Year", type: "category" },
165+
yaxis: { title: "Deviation from 2018-2021 Mean" },
166+
template: "plotly_white",
167+
};
168+
169+
Plotly.newPlot(plotlyChart.value, plotData.value, layout);
170+
};
171+
172+
// Fetch all necessary data and render the chart
173+
const fetchAndRender = async () => {
174+
await fetchHistoricalMeans(); // Fetch historical means once
175+
await fetchData(); // Fetch main data
176+
};
177+
178+
const updateGraph = () => {
179+
if (startDate.value && endDate.value) {
180+
fetchData(); // Only fetch main data, since historical means are already cached
181+
}
182+
};
183+
184+
onMounted(() => {
185+
fetchAndRender();
186+
});
187+
188+
return {
189+
weatherData,
190+
startDate,
191+
endDate,
192+
selectedMonth,
193+
months,
194+
updateGraph,
195+
plotlyChart,
196+
graphTitle,
197+
historicalMeans,
198+
};
199+
},
200+
};
201+
</script>
202+
203+
<style scoped>
204+
h2 {
205+
text-align: center;
206+
margin-bottom: 10px;
207+
}
208+
209+
.date-picker {
210+
display: flex;
211+
justify-content: center;
212+
gap: 10px;
213+
margin-bottom: 20px;
214+
}
215+
</style>
216+

0 commit comments

Comments
 (0)