@@ -107,10 +107,12 @@ func (s *Server) Stats(c *gin.Context) {
|
|||||||
func (s *Server) Chart(c *gin.Context) {
|
func (s *Server) Chart(c *gin.Context) {
|
||||||
groupID := c.Query("groupId")
|
groupID := c.Query("groupId")
|
||||||
|
|
||||||
twentyFourHoursAgo := time.Now().Add(-24 * time.Hour)
|
now := time.Now()
|
||||||
|
endHour := now.Truncate(time.Hour)
|
||||||
|
startHour := endHour.Add(-23 * time.Hour)
|
||||||
|
|
||||||
var hourlyStats []models.GroupHourlyStat
|
var hourlyStats []models.GroupHourlyStat
|
||||||
query := s.DB.Where("time >= ? ", twentyFourHoursAgo)
|
query := s.DB.Where("time >= ? AND time < ?", startHour, endHour.Add(time.Hour))
|
||||||
if groupID != "" {
|
if groupID != "" {
|
||||||
query = query.Where("group_id = ?", groupID)
|
query = query.Where("group_id = ?", groupID)
|
||||||
}
|
}
|
||||||
@@ -121,7 +123,7 @@ func (s *Server) Chart(c *gin.Context) {
|
|||||||
|
|
||||||
statsByHour := make(map[time.Time]map[string]int64)
|
statsByHour := make(map[time.Time]map[string]int64)
|
||||||
for _, stat := range hourlyStats {
|
for _, stat := range hourlyStats {
|
||||||
hour := stat.Time.Truncate(time.Hour)
|
hour := stat.Time.Local().Truncate(time.Hour)
|
||||||
if _, ok := statsByHour[hour]; !ok {
|
if _, ok := statsByHour[hour]; !ok {
|
||||||
statsByHour[hour] = make(map[string]int64)
|
statsByHour[hour] = make(map[string]int64)
|
||||||
}
|
}
|
||||||
@@ -133,7 +135,7 @@ func (s *Server) Chart(c *gin.Context) {
|
|||||||
var successData, failureData []int64
|
var successData, failureData []int64
|
||||||
|
|
||||||
for i := range 24 {
|
for i := range 24 {
|
||||||
hour := twentyFourHoursAgo.Add(time.Duration(i+1) * time.Hour).Truncate(time.Hour)
|
hour := startHour.Add(time.Duration(i) * time.Hour)
|
||||||
labels = append(labels, hour.Format(time.RFC3339))
|
labels = append(labels, hour.Format(time.RFC3339))
|
||||||
|
|
||||||
if data, ok := statsByHour[hour]; ok {
|
if data, ok := statsByHour[hour]; ok {
|
||||||
|
@@ -94,7 +94,7 @@ const visibleLabels = computed(() => {
|
|||||||
|
|
||||||
return labels
|
return labels
|
||||||
.map((label, index) => ({ text: formatTimeLabel(label), index }))
|
.map((label, index) => ({ text: formatTimeLabel(label), index }))
|
||||||
.filter((_, i) => i % step === 0);
|
.filter((_, i) => i % step === 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 位置计算函数
|
// 位置计算函数
|
||||||
@@ -115,7 +115,7 @@ const getYPosition = (value: number) => {
|
|||||||
return padding.top + (1 - ratio) * plotHeight;
|
return padding.top + (1 - ratio) * plotHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to find segments of non-zero data
|
// Helper to find segments of non-zero data (用于填充区域)
|
||||||
const getSegments = (data: number[]) => {
|
const getSegments = (data: number[]) => {
|
||||||
const segments: Array<Array<{ value: number; index: number }>> = [];
|
const segments: Array<Array<{ value: number; index: number }>> = [];
|
||||||
let currentSegment: Array<{ value: number; index: number }> = [];
|
let currentSegment: Array<{ value: number; index: number }> = [];
|
||||||
@@ -138,25 +138,41 @@ const getSegments = (data: number[]) => {
|
|||||||
return segments;
|
return segments;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成线条路径(处理零值点)
|
// 生成线条路径(连续线条,包括0值点)
|
||||||
const generateLinePath = (data: number[]) => {
|
const generateLinePath = (data: number[]) => {
|
||||||
const segments = getSegments(data);
|
if (data.length === 0) {
|
||||||
const pathParts: string[] = [];
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
segments.forEach(segment => {
|
// 找到第一个和最后一个非0值的位置
|
||||||
if (segment.length > 1) {
|
let firstNonZeroIndex = -1;
|
||||||
const segmentPath = segment
|
let lastNonZeroIndex = -1;
|
||||||
.map((point, pointIndex) => {
|
|
||||||
const x = getXPosition(point.index);
|
for (let i = 0; i < data.length; i++) {
|
||||||
const y = getYPosition(point.value);
|
if (data[i] > 0) {
|
||||||
return `${pointIndex === 0 ? "M" : "L"} ${x},${y}`;
|
if (firstNonZeroIndex === -1) {
|
||||||
})
|
firstNonZeroIndex = i;
|
||||||
.join(" ");
|
}
|
||||||
pathParts.push(segmentPath);
|
lastNonZeroIndex = i;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return pathParts.join(" ");
|
// 如果没有非0值,返回空路径
|
||||||
|
if (firstNonZeroIndex === -1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成连续的路径,从第一个非0值到最后一个非0值
|
||||||
|
const pathCommands: string[] = [];
|
||||||
|
|
||||||
|
for (let i = firstNonZeroIndex; i <= lastNonZeroIndex; i++) {
|
||||||
|
const x = getXPosition(i);
|
||||||
|
const y = getYPosition(data[i]);
|
||||||
|
const command = i === firstNonZeroIndex ? "M" : "L";
|
||||||
|
pathCommands.push(`${command} ${x},${y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathCommands.join(" ");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成填充区域路径(只为有数据的区域填充)
|
// 生成填充区域路径(只为有数据的区域填充)
|
||||||
@@ -502,18 +518,6 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
:style="{ opacity: isErrorDataset(dataset.label) ? 0.8 : 1 }"
|
:style="{ opacity: isErrorDataset(dataset.label) ? 0.8 : 1 }"
|
||||||
/>
|
/>
|
||||||
<!-- 零值点用灰色小点表示 -->
|
|
||||||
<circle
|
|
||||||
v-else
|
|
||||||
:cx="getXPosition(pointIndex)"
|
|
||||||
:cy="getYPosition(value)"
|
|
||||||
r="1.5"
|
|
||||||
fill="#d1d5db"
|
|
||||||
stroke="#d1d5db"
|
|
||||||
stroke-width="1"
|
|
||||||
class="data-point-zero"
|
|
||||||
opacity="0.6"
|
|
||||||
/>
|
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user