using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using XCharts.Runtime;
public class ImportWaterQualityChart : MonoBehaviour
{
[SerializeField] public XCharts.Runtime.LineChart lineChart;
[SerializeField] private Button customButton;
[SerializeField] private TMP_InputField startDateInput;
[SerializeField] private TMP_InputField startTimeInput;
[SerializeField] private TMP_InputField endDateInput;
[SerializeField] private TMP_InputField endTimeInput;
[SerializeField] private WebDataUpdater webDataUpdater;
[SerializeField] private TextMeshProUGUI Tips;
[SerializeField] private Button dayButton; // 天按钮(当天24小时)
[SerializeField] private Button weekButton; // 周按钮(本周7天)
[SerializeField] private Button monthButton; // 月按钮(本月所有天)
private bool isCustomDateOnly = false;
private TimeRange currentRange = TimeRange.Day;
private DateTime customStartTime;
private DateTime customEndTime;
private List<ImportHourlyData> importHourlyData = new List<ImportHourlyData>();
private Coroutine hideTipsCoroutine;
private bool isCustomWithHour = false;
public WebDataUpdater WebDataUpdater;
private Dictionary<string, string> fieldNameMap = new Dictionary<string, string>
{
{"W01001_Rtd", "PH"},
//{"W01010_Rtd", "水温"},
{"W21003_Rtd", "氨氮"},
{"W21011_Rtd", "总磷"},
//{"W00000_Rtd", "污水"},
{"W21001_Rtd", "总氮"},
{"W01018_Rtd", "COD"},
{"W00000l_Rtd", "总量"}
};
private void Awake()
{
if (lineChart == null)
lineChart = GetComponent<XCharts.Runtime.LineChart>();
DateTime now = DateTime.Now;
// 日期输入框默认值
startDateInput.text = now.AddDays(-1).ToString("yyyy-MM-dd");
endDateInput.text = now.ToString("yyyy-MM-dd");
// 小时输入框默认值 + 提示文本
startTimeInput.text = "22";
startTimeInput.placeholder.GetComponent<TextMeshProUGUI>().text = "输入0-23(可选)";
endTimeInput.text = "14";
endTimeInput.placeholder.GetComponent<TextMeshProUGUI>().text = "输入0-23(可选)";
if (webDataUpdater == null)
webDataUpdater = FindObjectOfType<WebDataUpdater>();
// 绑定天/周/月按钮点击事件
if (dayButton != null) dayButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Day));
if (weekButton != null) weekButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Week));
if (monthButton != null) monthButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Month));
if (customButton != null) customButton.onClick.AddListener(() => OnApplyCustomButtonClicked());
}
private void OnEnable()
{
WebDataUpdater.ImportHourlyUpdated += OnImportHourlyDataUpdated;
UpdateTimeRange(currentRange);
}
private void OnDisable()
{
WebDataUpdater.ImportHourlyUpdated -= OnImportHourlyDataUpdated;
}
/// 天/周/月按钮点击回调
private void OnTimeRangeButtonClicked(TimeRange targetRange)
{
currentRange = targetRange; // 更新当前时间范围
UpdateTimeRange(currentRange); // 触发数据请求和图表刷新
ShowTips($"{GetRangeDisplayName(targetRange)}数据加载中..."); // 提示用户
}
private string GetRangeDisplayName(TimeRange range)
{
return range switch
{
TimeRange.Day => "当天",
TimeRange.Week => "本周",
TimeRange.Month => "本月",
_ => "自定义"
};
}
private void OnImportHourlyDataUpdated(List<ImportHourlyData> data, string type, string from)
{
if (type != "between" || from != "import")
{
return;
}
importHourlyData = data ?? new List<ImportHourlyData>();
//Debug.Log($"接收主动请求的小时数据量: {importHourlyData.Count} 条");
RefreshChartWithData();
}
public void OnApplyCustomButtonClicked()
{
if (TryParseCustomTimeRange(out DateTime start, out DateTime end, out bool userEnteredTime))
{
//Debug.Log($"解析后的自定义时间范围:start={start:yyyy-MM-dd HH:mm:ss}, end={end:yyyy-MM-dd HH:mm:ss}");
customStartTime = start;
customEndTime = end;
StartCoroutine(WebDataUpdater.GetData(start, end, "between", "import"));
currentRange = TimeRange.Custom;
}
}
/// <summary>
/// 解析自定义时间输入,优化小时模式处理
/// </summary>
private bool TryParseCustomTimeRange(out DateTime start, out DateTime end, out bool userEnteredTime)
{
start = DateTime.Now;
end = DateTime.Now;
userEnteredTime = false;
isCustomDateOnly = false;
isCustomWithHour = false; // 重置标志位
bool hasValidStartHour = false;
bool hasValidEndHour = false;
try
{
string startDate = startDateInput.text.Trim();
string startTime = startTimeInput.text.Trim();
string endDate = endDateInput.text.Trim();
string endTime = endTimeInput.text.Trim();
bool hasStartDate = !string.IsNullOrEmpty(startDate);
bool hasStartTime = !string.IsNullOrEmpty(startTime);
bool hasEndDate = !string.IsNullOrEmpty(endDate);
bool hasEndTime = !string.IsNullOrEmpty(endTime);
// 处理开始时间(仅小时):新增有效小时判断
if (hasStartTime)
{
if (startTime.Contains(":"))
{
startTime = startTime.Split(':')[0].Trim();
startTimeInput.text = startTime; // 更新输入框显示
}
if (int.TryParse(startTime, out int startHour) && startHour >= 0 && startHour <= 23)
{
startTime = $"{startHour:00}:00";
userEnteredTime = true;
hasValidStartHour = true; // 标记为“有效小时”
}
else
{
ShowTips("开始小时必须是0-23之间的数字");
return false;
}
}
// 处理结束时间(仅小时):新增有效小时判断
if (hasEndTime)
{
if (endTime.Contains(":"))
{
endTime = endTime.Split(':')[0].Trim();
endTimeInput.text = endTime;
}
if (int.TryParse(endTime, out int endHour) && endHour >= 0 && endHour <= 23)
{
endTime = $"{endHour:00}:00";
userEnteredTime = true;
hasValidEndHour = true; // 标记为“有效小时”
}
else
{
ShowTips("结束小时必须是0-23之间的数字");
return false;
}
}
isCustomWithHour = hasValidStartHour || hasValidEndHour; // 关键修复:仅当有至少一个有效小时时,才判定为“按小时显示”
isCustomDateOnly = hasStartDate && hasEndDate && !isCustomWithHour; // 仅当“有日期+无任何有效小时”时,才判定为“按天显示”
// 补全默认时间(无有效小时时,默认当天00:00/23:00)
if (hasStartDate && !hasValidStartHour)
startTime = "00:00";
if (hasEndDate && !hasValidEndHour)
endTime = "23:00";
string startDateTimeStr = string.IsNullOrEmpty(startTime) ? startDate : $"{startDate} {startTime}";
string endDateTimeStr = string.IsNullOrEmpty(endTime) ? endDate : $"{endDate} {endTime}";
if (DateTime.TryParse(startDateTimeStr, out start) && DateTime.TryParse(endDateTimeStr, out end))
{
if (end < start)
{
ShowTips("结束时间不能早于开始时间");
return false;
}
return true;
}
}
catch (Exception e)
{
Debug.LogError($"时间解析异常: {e.Message}\n{e.StackTrace}");
}
ShowTips("请选择正确的时间范围,例如格式为2025-9-12 18");
return false;
}
private void ShowTips(string message)
{
if (Tips == null) return;
if (hideTipsCoroutine != null)
StopCoroutine(hideTipsCoroutine);
Tips.text = message;
Tips.gameObject.SetActive(true);
hideTipsCoroutine = StartCoroutine(HideTipsAfterDelay(2f));
}
private IEnumerator HideTipsAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
Tips.gameObject.SetActive(false);
hideTipsCoroutine = null;
}
/// <summary>
/// 刷新图表
/// </summary>
/// <param name="range"></param>
private void UpdateTimeRange(TimeRange range)
{
DateTime start = DateTime.Now;
DateTime end = DateTime.Now;
switch (range)
{
case TimeRange.Day:
// 当天:00:00到当前时间
start = DateTime.Today;
end = DateTime.Now;
break;
case TimeRange.Week: // 新增:本周逻辑(周一00:00到当前时间)
int daysToMonday = (int)DateTime.Now.DayOfWeek - (int)DayOfWeek.Monday;
if (daysToMonday < 0) daysToMonday += 7; // 处理周日(转为上周周一)
start = DateTime.Today.AddDays(-daysToMonday); // 本周一00:00
end = DateTime.Now; // 当前时间
break;
case TimeRange.Month:
// 本月:1号00:00到当前时间(自动适配30/31天)
start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
end = DateTime.Now;
break;
case TimeRange.Custom:
start = customStartTime;
end = customEndTime;
break;
}
if (webDataUpdater != null)
{
StartCoroutine(webDataUpdater.GetData(start, end, "between", "import"));
}
else
{
Debug.LogError("WebDataUpdater未赋值,请检查引用");
}
}
/// <summary>
/// 获取当前时间范围
/// </summary>
private void RefreshChartWithData()
{
var xAxis = lineChart.EnsureChartComponent<XAxis>();
List<string> labels;
if (currentRange == TimeRange.Custom)
{
labels = GenerateCustomTimeLabels(customStartTime, customEndTime);
}
else
{
labels = GenerateTimeLabels(currentRange);
}
xAxis.data.Clear();
xAxis.data.AddRange(labels);
xAxis.axisLabel.SetComponentDirty();
foreach (var serie in lineChart.series)
{
serie.ClearData();
var fieldName = fieldNameMap.FirstOrDefault(kv => kv.Value == serie.serieName).Key;
if (!string.IsNullOrEmpty(fieldName))
{
FillSerieWithRealData(serie, labels, fieldName);
}
else
{
Debug.LogWarning($"未找到与系列名 {serie.serieName} 匹配的字段,请检查配置");
}
}
lineChart.RefreshChart();
}
/// <summary>
/// 填充系列数据
/// </summary>
/// <param name="serie"></param>
/// <param name="labels"></param>
/// <param name="dataField"></param>
private void FillSerieWithRealData(Serie serie, List<string> labels, string dataField)
{
serie.ClearData();
if (importHourlyData.Count > 0)
{
var firstTime = DateTime.TryParse(importHourlyData[0].create_time, out var ft) ? ft : DateTime.MinValue;
var lastTime = DateTime.TryParse(importHourlyData.Last().create_time, out var lt) ? lt : DateTime.MinValue;
//Debug.Log($"数据时间范围: {firstTime:yyyy-MM-dd HH:mm} 至 {lastTime:yyyy-MM-dd HH:mm}");
}
foreach (var label in labels)
{
if (DateTime.TryParse(label, out DateTime labelTime))
{
var dataPoint = importHourlyData.FirstOrDefault(d =>
{
if (!DateTime.TryParse(d.create_time, out DateTime dataTime))
{
Debug.LogWarning($"数据时间格式错误: {d.create_time}");
return false;
}
// 修复:dataTime <= labelTime.AddHours(1)
return dataTime >= labelTime && dataTime <= labelTime.AddHours(1);
});
if (dataPoint != null && TryGetDataValue(dataPoint, dataField, out double value))
{
serie.AddYData((float)value);
//Debug.Log($"匹配成功: {labelTime:HH:mm} 对应值: {value}");
}
else
{
serie.AddYData(0f);
// Debug.LogWarning($"无匹配数据: {labelTime:yyyy-MM-dd HH:mm}");
}
}
else
{
serie.AddYData(0f);
Debug.LogWarning($"标签时间解析失败: {label}");
}
}
}
/// <summary>
/// 填充指定系列数据
/// </summary>
/// <param name="data"></param>
/// <param name="fieldName"></param>
/// <param name="value"></param>
/// <returns></returns>
private bool TryGetDataValue(ImportHourlyData data, string fieldName, out double value)
{
value = 0;
string fieldValue = fieldName switch
{
"W01001_Rtd" => data.W01001_Rtd,
//"W01010_Rtd" => data.W01010_Rtd,
"W21003_Rtd" => data.W21003_Rtd,
"W21011_Rtd" => data.W21011_Rtd,
//"W00000_Rtd" => data.W00000_Rtd,
"W21001_Rtd" => data.W21001_Rtd,
"W01018_Rtd" => data.W01018_Rtd,
"W00000l_Rtd" => data.W00000l_Rtd,
_ => null
};
double.TryParse(fieldValue, out value);
return true;
}
/// <summary>
/// 尝试从数据点中获取指定字段的值
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
private List<string> GenerateTimeLabels(TimeRange range)
{
List<string> labels = new List<string>();
DateTime now = DateTime.Now;
switch (range)
{
case TimeRange.Day: // 当天标签:00:00~23:00
for (int hour = 0; hour < 24; hour++)
{
labels.Add($"{hour:00}:00");
}
break;
case TimeRange.Week: // 本周标签(仅显示日期,格式:MM-dd)
int daysToMonday = (int)now.DayOfWeek - (int)DayOfWeek.Monday;
if (daysToMonday < 0) daysToMonday += 7;
DateTime monday = now.Date.AddDays(-daysToMonday);
for (int i = 0; i < 7; i++)
{
DateTime currentDay = monday.AddDays(i);
labels.Add($"{currentDay:MM-dd}"); // 仅显示月-日,例如:09-19
}
break;
case TimeRange.Month: // 本月标签:1号~当月最后一天(自动适配30/31天)
DateTime firstDayOfMonth = new DateTime(now.Year, now.Month, 1);
DateTime lastDayOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month));
for (DateTime d = firstDayOfMonth; d <= lastDayOfMonth; d = d.AddDays(1))
{
labels.Add($"{d:MM-dd}"); // 格式:09-19
}
break;
}
return labels;
}
/// <summary>
/// 获取指定时间范围内的时间标签
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
private List<string> GenerateCustomTimeLabels(DateTime start, DateTime end)
{
List<string> labels = new List<string>();
TimeSpan span = end - start;
// 优化1:用户输入了有效小时 → 强制按小时生成标签(忽略跨度)
if (isCustomWithHour)
{
// 计算从开始到结束的所有小时点(包含开始,不超过结束)
DateTime currentHour = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0);
while (currentHour <= end)
{
labels.Add($"{currentHour:yyyy-MM-dd HH:00}"); // 小时级格式:月-日 时:00
currentHour = currentHour.AddHours(1);
}
}
// 优化2:用户未输入小时 → 按时间跨度判断(天/月)
else
{
// 跨度≤31天 → 按天生成标签
if (span.TotalDays <= 31)
{
DateTime currentDay = start.Date; // 取当天0点
while (currentDay <= end.Date)
{
labels.Add($"{currentDay:MM-dd}"); // 天级格式:月-日
currentDay = currentDay.AddDays(1);
}
}
// 跨度>31天 → 按月生成标签
else
{
DateTime currentMonth = new DateTime(start.Year, start.Month, 1);
while (currentMonth <= end)
{
labels.Add($"{currentMonth:yyyy-MM}"); // 月级格式:年-月
currentMonth = currentMonth.AddMonths(1);
}
}
}
//Debug.Log($"生成标签数量: {labels.Count}");
if (labels.Count > 0)
{
//Debug.Log($"标签范围: {labels[0]} 至 {labels.Last()}");
}
return labels;
}
/// <summary>
/// 公开接口:获取当前图表的时间标签列表(X轴)
/// </summary>
/// <returns>时间标签(如“00:00”“09-19”“2025-09”)</returns>
public List<string> GetCurrentTimeLabels()
{
var xAxis = lineChart?.EnsureChartComponent<XAxis>();
return xAxis?.data ?? new List<string>();
}
/// <summary>
/// 公开接口:获取指定水质指标的数值列表
/// </summary>
/// <param name="indicatorName">指标名(如“PH”“氨氮”,需与fieldNameMap的值一致)</param>
/// <returns>数值列表(无数据时为0)</returns>
public List<float> GetIndicatorData(string indicatorName)
{
List<float> result = new List<float>();
var timeLabels = GetCurrentTimeLabels();
foreach (var timeLabel in timeLabels)
{
if (DateTime.TryParse(timeLabel, out DateTime labelTime))
{
// 从实际数据中找与 labelTime 匹配的数值
float value = FindValueByTimestamp(indicatorName, labelTime);
result.Add(value);
}
else
{
result.Add(0f);
}
}
return result;
}
/// <summary>
/// 根据时间戳获取指定指标的值
/// </summary>
/// <param name="indicator"></param>
/// <param name="targetTime"></param>
/// <returns></returns>
private float FindValueByTimestamp(string indicator, DateTime targetTime)
{
// 从 importHourlyData 中找和 targetTime 匹配的数据
var dataPoint = importHourlyData.FirstOrDefault(d =>
{
if (DateTime.TryParse(d.create_time, out DateTime dataTime))
{
return dataTime.Hour == targetTime.Hour;
}
return false;
});
return dataPoint != null ? GetValueFromDataPoint(dataPoint, indicator) : 0f;
}
/// <summary>
/// 公开接口:获取所有需要导出的水质指标名
/// </summary>
public List<string> GetExportIndicatorNames()
{
List<string> allIndicators = new List<string>();
foreach (var indicator in fieldNameMap.Values)
{
allIndicators.Add(indicator);
}
return allIndicators;
}
/// <summary>
/// 从数据点中提取指定指标的数值(根据fieldNameMap映射关系)
/// </summary>
/// <param name="dataPoint">实际监测数据点(ImportHourlyData实例)</param>
/// <param name="indicatorName">指标名(如"PH")</param>
/// <returns>指标数值(未找到时返回0)</returns>
private float GetValueFromDataPoint(ImportHourlyData dataPoint, string indicatorName)
{
var fieldEntry = fieldNameMap.FirstOrDefault(kv => kv.Value == indicatorName);
if (string.IsNullOrEmpty(fieldEntry.Key))
{
Debug.LogWarning($"未找到指标 {indicatorName} 对应的字段映射");
return 0f;
}
string targetField = fieldEntry.Key;
switch (targetField)
{
case "W01001_Rtd": // PH
if (float.TryParse(dataPoint.W01001_Rtd, out float phValue))
return phValue;
else
return 0f;
case "W21003_Rtd": // 氨氮
if (float.TryParse(dataPoint.W21003_Rtd, out float ammoniaValue))
return ammoniaValue;
else
return 0f;
case "W21011_Rtd": // 总磷
if (float.TryParse(dataPoint.W21011_Rtd, out float totalPhosphorusValue))
return totalPhosphorusValue;
else
return 0f;
case "W21001_Rtd": // 总氮
if (float.TryParse(dataPoint.W21001_Rtd, out float totalNitrogenValue))
return totalNitrogenValue;
else
return 0f;
case "W01018_Rtd": // COD
if (float.TryParse(dataPoint.W01018_Rtd, out float codValue))
return codValue;
else
return 0f;
case "W00000l_Rtd": // 总量
if (float.TryParse(dataPoint.W00000l_Rtd, out float totalValue))
return totalValue;
else
return 0f;
default:
Debug.LogWarning($"未处理的字段 {targetField},请检查fieldNameMap配置");
return 0f;
}
}
public enum TimeRange
{
Day,
Month,
Week,
Custom
}
}
最新发布