|
@@ -1,320 +1,233 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div ref="chartRef" class="remote-sensing-line-chart"></div>
|
|
|
|
|
|
|
+ <div v-if="loading" class="remote-sensing-line-chart__status">{{ t("agriFile.loading") }}</div>
|
|
|
|
|
+ <div v-else-if="!normalizedData" class="remote-sensing-line-chart__status">{{ t("agriFile.noData") }}</div>
|
|
|
|
|
+ <div v-else ref="chartRef" class="remote-sensing-line-chart"></div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import * as echarts from "echarts";
|
|
import * as echarts from "echarts";
|
|
|
-import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
|
|
|
|
|
|
|
+import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
|
|
|
import { useI18n } from "@/i18n";
|
|
import { useI18n } from "@/i18n";
|
|
|
|
|
|
|
|
-const props = defineProps({
|
|
|
|
|
- chartData: {
|
|
|
|
|
- type: Object,
|
|
|
|
|
- default: null,
|
|
|
|
|
- },
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
const { t } = useI18n();
|
|
const { t } = useI18n();
|
|
|
|
|
|
|
|
const chartRef = ref(null);
|
|
const chartRef = ref(null);
|
|
|
const chartInstance = shallowRef(null);
|
|
const chartInstance = shallowRef(null);
|
|
|
|
|
+const chartData = ref(null);
|
|
|
|
|
+const loading = ref(false);
|
|
|
let resizeObserver = null;
|
|
let resizeObserver = null;
|
|
|
|
|
|
|
|
-const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
|
|
|
-const YEAR_DAY_LABELS = (() => {
|
|
|
|
|
- const labels = [];
|
|
|
|
|
- for (let month = 1; month <= 12; month++) {
|
|
|
|
|
- for (let day = 1; day <= DAYS_IN_MONTH[month - 1]; day++) {
|
|
|
|
|
- labels.push(`${month}月${day}日`);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return labels;
|
|
|
|
|
-})();
|
|
|
|
|
-
|
|
|
|
|
-const VISIBLE_X_LABEL_COUNT = 30;
|
|
|
|
|
-
|
|
|
|
|
-const getCurrentYearDayIndex = () => {
|
|
|
|
|
- const now = new Date();
|
|
|
|
|
- let index = 0;
|
|
|
|
|
- for (let m = 0; m < now.getMonth(); m++) {
|
|
|
|
|
- index += DAYS_IN_MONTH[m];
|
|
|
|
|
- }
|
|
|
|
|
- return index + now.getDate() - 1;
|
|
|
|
|
|
|
+const SERIES_COLORS = {
|
|
|
|
|
+ ndvi: "#1CC277",
|
|
|
|
|
+ ndwi: "#6277FB",
|
|
|
|
|
+ precipitation: "#E2F1FD",
|
|
|
|
|
+ avgPrecipitation: "#66BBFF",
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/** 以 centerIndex 为中心计算 dataZoom 百分比区间 */
|
|
|
|
|
-const calcDataZoomPercent = (totalCount, visibleCount = VISIBLE_X_LABEL_COUNT) => {
|
|
|
|
|
- const span = Math.min(visibleCount, totalCount);
|
|
|
|
|
- const centerIndex = Math.min(getCurrentYearDayIndex(), totalCount - 1);
|
|
|
|
|
- const halfSpan = Math.floor(span / 2);
|
|
|
|
|
- let startIndex = centerIndex - halfSpan;
|
|
|
|
|
- let endIndex = startIndex + span;
|
|
|
|
|
|
|
+const toNumber = (value) => {
|
|
|
|
|
+ const num = Number(value);
|
|
|
|
|
+ return Number.isNaN(num) ? null : num;
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- if (startIndex < 0) {
|
|
|
|
|
- startIndex = 0;
|
|
|
|
|
- endIndex = span;
|
|
|
|
|
- }
|
|
|
|
|
- if (endIndex > totalCount) {
|
|
|
|
|
- endIndex = totalCount;
|
|
|
|
|
- startIndex = Math.max(0, totalCount - span);
|
|
|
|
|
|
|
+const normalizeChartData = (raw) => {
|
|
|
|
|
+ console.log("raw", raw);
|
|
|
|
|
+ if (!raw) return null;
|
|
|
|
|
+
|
|
|
|
|
+ if (Array.isArray(raw)) {
|
|
|
|
|
+ if (!raw.length) return null;
|
|
|
|
|
+ return {
|
|
|
|
|
+ timeLabels: raw.map((item) => item.time ?? item.label ?? item.date ?? ""),
|
|
|
|
|
+ ndvi: raw.map((item) => toNumber(item.ndvi)),
|
|
|
|
|
+ ndwi: raw.map((item) => toNumber(item.ndwi)),
|
|
|
|
|
+ precipitation: raw.map((item) =>
|
|
|
|
|
+ toNumber(item.precipitation ?? item.rain ?? item.rainfall)
|
|
|
|
|
+ ),
|
|
|
|
|
+ avgPrecipitation: raw.map((item) =>
|
|
|
|
|
+ toNumber(
|
|
|
|
|
+ item.avgPrecipitation ??
|
|
|
|
|
+ item.avgRain ??
|
|
|
|
|
+ item.avgRainfall ??
|
|
|
|
|
+ item.avg_precipitation
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const spanPercent = (span / totalCount) * 100;
|
|
|
|
|
|
|
+ const timeLabels = raw.timeLabels ?? raw.times ?? raw.labels ?? [];
|
|
|
|
|
+ if (!timeLabels.length) return null;
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
- start: (startIndex / totalCount) * 100,
|
|
|
|
|
- end: (endIndex / totalCount) * 100,
|
|
|
|
|
- spanPercent,
|
|
|
|
|
|
|
+ timeLabels,
|
|
|
|
|
+ ndvi: raw.ndvi ?? [],
|
|
|
|
|
+ ndwi: raw.ndwi ?? [],
|
|
|
|
|
+ precipitation: raw.precipitation ?? raw.rain ?? [],
|
|
|
|
|
+ avgPrecipitation: raw.avgPrecipitation ?? raw.avg_rain ?? raw.avg_precipitation ?? [],
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const padSeriesToLength = (series, length) => {
|
|
|
|
|
- const padded = series.slice(0, length).map((item) => {
|
|
|
|
|
- const num = Number(item);
|
|
|
|
|
- return Number.isNaN(num) ? null : num;
|
|
|
|
|
- });
|
|
|
|
|
- while (padded.length < length) {
|
|
|
|
|
- padded.push(null);
|
|
|
|
|
- }
|
|
|
|
|
- return padded;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const mapSeriesValues = (series) =>
|
|
|
|
|
- series.map((item) => {
|
|
|
|
|
- const num = Number(item);
|
|
|
|
|
- return Number.isNaN(num) ? null : num;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
const normalizedData = computed(() => {
|
|
const normalizedData = computed(() => {
|
|
|
- const data = props.chartData;
|
|
|
|
|
- const zoneOnly = Boolean(data?.zoneOnly);
|
|
|
|
|
- if (!data?.zoneSeries?.length) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- if (!zoneOnly && !data?.standardSeries?.length) {
|
|
|
|
|
|
|
+ const data = chartData.value;
|
|
|
|
|
+ if (!data?.timeLabels?.length) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const usePartialAxis = Boolean(data.usePartialAxis);
|
|
|
|
|
- const length = usePartialAxis ? data.zoneSeries.length : YEAR_DAY_LABELS.length;
|
|
|
|
|
- const timeLabels = YEAR_DAY_LABELS.slice(0, length);
|
|
|
|
|
- const zoneSeries = usePartialAxis
|
|
|
|
|
- ? mapSeriesValues(data.zoneSeries)
|
|
|
|
|
- : padSeriesToLength(mapSeriesValues(data.zoneSeries), length);
|
|
|
|
|
- const standardSeries = zoneOnly
|
|
|
|
|
- ? []
|
|
|
|
|
- : padSeriesToLength(mapSeriesValues(data.standardSeries), length);
|
|
|
|
|
-
|
|
|
|
|
- let highlightIndex = data.highlightIndex;
|
|
|
|
|
- const validValues = zoneSeries
|
|
|
|
|
- .map((value, index) => ({ value, index }))
|
|
|
|
|
- .filter((item) => item.value != null);
|
|
|
|
|
- if (highlightIndex == null || highlightIndex < 0 || highlightIndex >= length) {
|
|
|
|
|
- highlightIndex =
|
|
|
|
|
- validValues.length > 0
|
|
|
|
|
- ? validValues.reduce((max, item) =>
|
|
|
|
|
- item.value > max.value ? item : max
|
|
|
|
|
- ).index
|
|
|
|
|
- : 0;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const length = data.timeLabels.length;
|
|
|
|
|
+ const mapSeries = (series = []) =>
|
|
|
|
|
+ series.slice(0, length).map((item) => toNumber(item));
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- timeLabels,
|
|
|
|
|
- zoneSeries,
|
|
|
|
|
- standardSeries,
|
|
|
|
|
- zoneOnly,
|
|
|
|
|
- zoneAreaFillDown: Boolean(data.zoneAreaFillDown),
|
|
|
|
|
- highlightIndex,
|
|
|
|
|
|
|
+ timeLabels: data.timeLabels.slice(0, length),
|
|
|
|
|
+ ndvi: mapSeries(data.ndvi),
|
|
|
|
|
+ ndwi: mapSeries(data.ndwi),
|
|
|
|
|
+ precipitation: mapSeries(data.precipitation),
|
|
|
|
|
+ avgPrecipitation: mapSeries(data.avgPrecipitation),
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const formatAxisValue = (value) => {
|
|
|
|
|
- const num = Number(value);
|
|
|
|
|
- if (Number.isNaN(num)) return value;
|
|
|
|
|
- return num.toFixed(2);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const calcYAxisRange = (zoneSeries, standardSeries) => {
|
|
|
|
|
- const values = [...zoneSeries, ...standardSeries].filter(
|
|
|
|
|
- (item) => item != null && !Number.isNaN(item)
|
|
|
|
|
- );
|
|
|
|
|
- if (!values.length) {
|
|
|
|
|
- return { min: 0, max: 1, interval: 0.2 };
|
|
|
|
|
|
|
+const fetchChartData = async () => {
|
|
|
|
|
+
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ zone_id: '260',
|
|
|
|
|
+ date: '2026-05-28',
|
|
|
|
|
+ };
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await VE_API.record.getZoneRsSeries(params);
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ chartData.value = normalizeChartData(res.data);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ chartData.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ chartData.value = null;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const min = Math.min(...values);
|
|
|
|
|
- const max = Math.max(...values);
|
|
|
|
|
- const span = max - min || 0.1;
|
|
|
|
|
- const padding = span * 0.05;
|
|
|
|
|
- const axisMin = Math.floor((min - padding) * 100) / 100;
|
|
|
|
|
- const axisMax = Math.ceil((max + padding) * 100) / 100;
|
|
|
|
|
- const interval = Math.max(0.01, Math.ceil(((axisMax - axisMin) / 5) * 100) / 100);
|
|
|
|
|
-
|
|
|
|
|
- return { min: axisMin, max: axisMax, interval };
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const formatHighlightValue = (value) => {
|
|
|
|
|
- const num = Number(value);
|
|
|
|
|
- if (Number.isNaN(num)) return t("agriFile.remoteSensingValuePlaceholder");
|
|
|
|
|
- return Number.isInteger(num) ? String(num) : num.toFixed(2).replace(/\.?0+$/, "");
|
|
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const buildOption = (data) => {
|
|
|
|
|
- const highlightValue = data.zoneSeries[data.highlightIndex];
|
|
|
|
|
- const totalCount = data.timeLabels.length;
|
|
|
|
|
- const dataZoomRange = calcDataZoomPercent(totalCount);
|
|
|
|
|
- const yAxisRange = calcYAxisRange(
|
|
|
|
|
- data.zoneSeries,
|
|
|
|
|
- data.zoneOnly ? [] : data.standardSeries
|
|
|
|
|
- );
|
|
|
|
|
- const zoneAreaOrigin = data.zoneAreaFillDown ? "start" : "auto";
|
|
|
|
|
-
|
|
|
|
|
- const series = [
|
|
|
|
|
- {
|
|
|
|
|
- name: "zone",
|
|
|
|
|
- type: "line",
|
|
|
|
|
- smooth: true,
|
|
|
|
|
- symbol: "none",
|
|
|
|
|
- lineStyle: {
|
|
|
|
|
- width: 2,
|
|
|
|
|
- color: "#2199F8",
|
|
|
|
|
- },
|
|
|
|
|
- areaStyle: {
|
|
|
|
|
- origin: zoneAreaOrigin,
|
|
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
- { offset: 0, color: "rgba(33, 153, 248, 0.7)" },
|
|
|
|
|
- { offset: 1, color: "rgba(33, 153, 248, 0)" },
|
|
|
|
|
- ]),
|
|
|
|
|
- },
|
|
|
|
|
- data: data.zoneSeries,
|
|
|
|
|
- markPoint:
|
|
|
|
|
- highlightValue != null
|
|
|
|
|
- ? {
|
|
|
|
|
- symbol: "circle",
|
|
|
|
|
- symbolSize: 7,
|
|
|
|
|
- itemStyle: {
|
|
|
|
|
- color: "#ffffff",
|
|
|
|
|
- borderColor: "#2199F8",
|
|
|
|
|
- borderWidth: 2,
|
|
|
|
|
- },
|
|
|
|
|
- label: {
|
|
|
|
|
- show: true,
|
|
|
|
|
- position: "top",
|
|
|
|
|
- distance: 6,
|
|
|
|
|
- color: "#2199F8",
|
|
|
|
|
- fontSize: 12,
|
|
|
|
|
- formatter: () => `[${formatHighlightValue(highlightValue)}]`,
|
|
|
|
|
- },
|
|
|
|
|
- data: [
|
|
|
|
|
- {
|
|
|
|
|
- coord: [data.highlightIndex, highlightValue],
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- }
|
|
|
|
|
- : undefined,
|
|
|
|
|
- z: 3,
|
|
|
|
|
- },
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- if (!data.zoneOnly && data.standardSeries?.length) {
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: "standard",
|
|
|
|
|
- type: "line",
|
|
|
|
|
- smooth: true,
|
|
|
|
|
- symbol: "none",
|
|
|
|
|
- lineStyle: {
|
|
|
|
|
- width: 2,
|
|
|
|
|
- color: "#9FD2FA",
|
|
|
|
|
- },
|
|
|
|
|
- data: data.standardSeries,
|
|
|
|
|
- z: 2,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- animation: false,
|
|
|
|
|
- grid: {
|
|
|
|
|
- left: 4,
|
|
|
|
|
- right: 8,
|
|
|
|
|
- top: 20,
|
|
|
|
|
- bottom: 32,
|
|
|
|
|
- containLabel: true,
|
|
|
|
|
|
|
+const buildOption = (data) => ({
|
|
|
|
|
+ animation: false,
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 4,
|
|
|
|
|
+ right: 4,
|
|
|
|
|
+ top: 28,
|
|
|
|
|
+ bottom: 8,
|
|
|
|
|
+ containLabel: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ boundaryGap: true,
|
|
|
|
|
+ data: data.timeLabels,
|
|
|
|
|
+ axisLine: { show: false },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: "#C1C1C1",
|
|
|
|
|
+ fontSize: 11,
|
|
|
},
|
|
},
|
|
|
- dataZoom: [
|
|
|
|
|
- {
|
|
|
|
|
- type: "inside",
|
|
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- start: dataZoomRange.start,
|
|
|
|
|
- end: dataZoomRange.end,
|
|
|
|
|
- zoomLock: true,
|
|
|
|
|
- minSpan: dataZoomRange.spanPercent,
|
|
|
|
|
- maxSpan: dataZoomRange.spanPercent,
|
|
|
|
|
- zoomOnMouseWheel: false,
|
|
|
|
|
- moveOnMouseMove: true,
|
|
|
|
|
- preventDefaultMouseMove: true,
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- type: "slider",
|
|
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- start: dataZoomRange.start,
|
|
|
|
|
- end: dataZoomRange.end,
|
|
|
|
|
- zoomLock: true,
|
|
|
|
|
- minSpan: dataZoomRange.spanPercent,
|
|
|
|
|
- maxSpan: dataZoomRange.spanPercent,
|
|
|
|
|
- height: 18,
|
|
|
|
|
- bottom: 4,
|
|
|
|
|
- brushSelect: false,
|
|
|
|
|
- showDetail: false,
|
|
|
|
|
- borderColor: "transparent",
|
|
|
|
|
- backgroundColor: "#F5F5F5",
|
|
|
|
|
- fillerColor: "rgba(33, 153, 248, 0.15)",
|
|
|
|
|
- handleStyle: {
|
|
|
|
|
- color: "#2199F8",
|
|
|
|
|
- borderColor: "#2199F8",
|
|
|
|
|
- },
|
|
|
|
|
- dataBackground: {
|
|
|
|
|
- lineStyle: { color: "#E0E0E0" },
|
|
|
|
|
- areaStyle: { color: "#F0F0F0" },
|
|
|
|
|
- },
|
|
|
|
|
- selectedDataBackground: {
|
|
|
|
|
- lineStyle: { color: "#2199F8" },
|
|
|
|
|
- areaStyle: { color: "rgba(33, 153, 248, 0.1)" },
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "value",
|
|
|
|
|
+ min: -1,
|
|
|
|
|
+ max: 1,
|
|
|
|
|
+ interval: 0.5,
|
|
|
|
|
+ name: t("agriFile.remoteSensingAxisIndex"),
|
|
|
|
|
+ nameLocation: "end",
|
|
|
|
|
+ nameGap: 8,
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: "#C1C1C1",
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ align: "left",
|
|
|
},
|
|
},
|
|
|
- ],
|
|
|
|
|
- xAxis: {
|
|
|
|
|
- type: "category",
|
|
|
|
|
- boundaryGap: false,
|
|
|
|
|
- data: data.timeLabels,
|
|
|
|
|
axisLine: { show: false },
|
|
axisLine: { show: false },
|
|
|
axisTick: { show: false },
|
|
axisTick: { show: false },
|
|
|
axisLabel: {
|
|
axisLabel: {
|
|
|
color: "#C1C1C1",
|
|
color: "#C1C1C1",
|
|
|
fontSize: 11,
|
|
fontSize: 11,
|
|
|
- rotate: 25,
|
|
|
|
|
- margin: 10,
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: "#EEEEEE",
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- yAxis: {
|
|
|
|
|
|
|
+ {
|
|
|
type: "value",
|
|
type: "value",
|
|
|
- min: yAxisRange.min,
|
|
|
|
|
- max: yAxisRange.max,
|
|
|
|
|
- interval: yAxisRange.interval,
|
|
|
|
|
|
|
+ min: 0,
|
|
|
|
|
+ max: 100,
|
|
|
|
|
+ interval: 25,
|
|
|
|
|
+ name: t("agriFile.remoteSensingAxisPrecipitation"),
|
|
|
|
|
+ nameLocation: "end",
|
|
|
|
|
+ nameGap: 8,
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: "#C1C1C1",
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ align: "right",
|
|
|
|
|
+ },
|
|
|
axisLine: { show: false },
|
|
axisLine: { show: false },
|
|
|
axisTick: { show: false },
|
|
axisTick: { show: false },
|
|
|
axisLabel: {
|
|
axisLabel: {
|
|
|
color: "#C1C1C1",
|
|
color: "#C1C1C1",
|
|
|
fontSize: 11,
|
|
fontSize: 11,
|
|
|
- formatter: formatAxisValue,
|
|
|
|
|
},
|
|
},
|
|
|
- splitLine: {
|
|
|
|
|
- lineStyle: {
|
|
|
|
|
- color: "#EEEEEE",
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ splitLine: { show: false },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "precipitation",
|
|
|
|
|
+ type: "bar",
|
|
|
|
|
+ yAxisIndex: 1,
|
|
|
|
|
+ barWidth: "40%",
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: SERIES_COLORS.precipitation,
|
|
|
|
|
+ borderRadius: [4, 4, 0, 0],
|
|
|
},
|
|
},
|
|
|
|
|
+ data: data.precipitation,
|
|
|
|
|
+ z: 1,
|
|
|
},
|
|
},
|
|
|
- series,
|
|
|
|
|
- };
|
|
|
|
|
-};
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "ndwi",
|
|
|
|
|
+ type: "line",
|
|
|
|
|
+ yAxisIndex: 0,
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ symbol: "none",
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ width: 2,
|
|
|
|
|
+ color: SERIES_COLORS.ndwi,
|
|
|
|
|
+ },
|
|
|
|
|
+ data: data.ndwi,
|
|
|
|
|
+ z: 2,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "avgPrecipitation",
|
|
|
|
|
+ type: "line",
|
|
|
|
|
+ yAxisIndex: 1,
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ symbol: "none",
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ width: 2,
|
|
|
|
|
+ type: "dashed",
|
|
|
|
|
+ color: SERIES_COLORS.avgPrecipitation,
|
|
|
|
|
+ },
|
|
|
|
|
+ data: data.avgPrecipitation,
|
|
|
|
|
+ z: 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "ndvi",
|
|
|
|
|
+ type: "line",
|
|
|
|
|
+ yAxisIndex: 0,
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ symbol: "none",
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ width: 2,
|
|
|
|
|
+ color: SERIES_COLORS.ndvi,
|
|
|
|
|
+ },
|
|
|
|
|
+ data: data.ndvi,
|
|
|
|
|
+ z: 4,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
const resizeChart = () => {
|
|
const resizeChart = () => {
|
|
|
chartInstance.value?.resize();
|
|
chartInstance.value?.resize();
|
|
@@ -350,13 +263,14 @@ const setupResizeObserver = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
- nextTick(() => {
|
|
|
|
|
- renderChart();
|
|
|
|
|
- setupResizeObserver();
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ fetchChartData();
|
|
|
window.addEventListener("resize", resizeChart);
|
|
window.addEventListener("resize", resizeChart);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+onActivated(() => {
|
|
|
|
|
+ fetchChartData();
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
onBeforeUnmount(() => {
|
|
onBeforeUnmount(() => {
|
|
|
resizeObserver?.disconnect();
|
|
resizeObserver?.disconnect();
|
|
|
resizeObserver = null;
|
|
resizeObserver = null;
|
|
@@ -367,13 +281,19 @@ onBeforeUnmount(() => {
|
|
|
|
|
|
|
|
watch(
|
|
watch(
|
|
|
normalizedData,
|
|
normalizedData,
|
|
|
- () => {
|
|
|
|
|
|
|
+ (data) => {
|
|
|
|
|
+ if (!data) return;
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
renderChart();
|
|
renderChart();
|
|
|
|
|
+ setupResizeObserver();
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
{ deep: true }
|
|
{ deep: true }
|
|
|
);
|
|
);
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({
|
|
|
|
|
+ refresh: fetchChartData,
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
@@ -382,5 +302,13 @@ watch(
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
height: 194px;
|
|
height: 194px;
|
|
|
margin-top: 10px;
|
|
margin-top: 10px;
|
|
|
|
|
+
|
|
|
|
|
+ &__status {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: #9a9a9a;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ padding: 14px 0;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|