58 lines
4.2 KiB
Plaintext
58 lines
4.2 KiB
Plaintext
<template>
|
|
<view class="trend-chart"><view class="scale-column"><text v-for="scale in scaleLabels" :key="scale" class="scale-label">{{ scale }}</text></view><view class="trend-plot"><view class="trend-lines"><view class="trend-grid-line trend-grid-line-top"></view><view class="trend-grid-line trend-grid-line-upper"></view><view class="trend-grid-line trend-grid-line-lower"></view><view class="trend-grid-line trend-grid-line-bottom"></view><view v-for="segment in averageSegments" :key="segment.key" class="trend-line-segment trend-line-average" :style="segment.style"></view><view v-for="segment in studentSegments" :key="segment.key" class="trend-line-segment trend-line-student" :style="segment.style"></view><view v-for="point in points" :key="point.label + '-average'" class="trend-point trend-point-average" :style="pointStyle(point, false)"></view><view v-for="point in points" :key="point.label + '-student'" class="trend-point trend-point-student" :style="pointStyle(point, true)"></view></view><view class="trend-labels"><text v-for="point in points" :key="point.label" class="trend-day">{{ point.label }}</text></view></view></view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { StudyReportTrendPoint } from '../api/student'
|
|
|
|
type TrendLineSegment = { key: string, style: string }
|
|
const props = defineProps<{ scaleLabels: string[], points: StudyReportTrendPoint[] }>()
|
|
const studentSegments = computed((): TrendLineSegment[] => buildSegments(true))
|
|
const averageSegments = computed((): TrendLineSegment[] => buildSegments(false))
|
|
|
|
function buildSegments(isStudent: boolean): TrendLineSegment[] {
|
|
const segments: TrendLineSegment[] = []
|
|
if (props.points.length < 2) return segments
|
|
const chartWidth = 270
|
|
const horizontalStep = (chartWidth - 16) / (props.points.length - 1)
|
|
for (let index = 0; index < props.points.length - 1; index++) {
|
|
const currentHeight = isStudent ? props.points[index].valueHeight : props.points[index].averageHeight
|
|
const nextHeight = isStudent ? props.points[index + 1].valueHeight : props.points[index + 1].averageHeight
|
|
const verticalStep = nextHeight - currentHeight
|
|
const lineWidth = Math.sqrt(horizontalStep * horizontalStep + verticalStep * verticalStep) / chartWidth * 100
|
|
const angle = -Math.atan2(verticalStep, horizontalStep) * 180 / Math.PI
|
|
const left = 8 + horizontalStep * index
|
|
segments.push({ key: `${isStudent ? 'student' : 'average'}-${index}`, style: `left:${left / chartWidth * 100}%;bottom:${currentHeight + 8}px;width:${lineWidth}%;transform:rotate(${angle}deg);` })
|
|
}
|
|
return segments
|
|
}
|
|
|
|
function pointStyle(point: StudyReportTrendPoint, isStudent: boolean): string {
|
|
const index = props.points.indexOf(point)
|
|
const left = props.points.length > 1 ? 8 + (270 - 16) / (props.points.length - 1) * index : 135
|
|
const pointHeight = isStudent ? point.valueHeight : point.averageHeight
|
|
return `left:${left / 270 * 100}%;bottom:${pointHeight + 8}px;`
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.trend-chart { display: flex; flex-direction: row; height: 132px; margin-top: 10px; }
|
|
.scale-column { display: flex; flex-direction: column; justify-content: space-between; width: 35px; padding-bottom: 18px; }
|
|
.scale-label { font-size: 10px; color: #8992a5; }
|
|
.trend-plot { display: flex; flex-direction: column; flex: 1; padding-bottom: 18px; border-bottom: 1px solid #edf0f5; }
|
|
.trend-lines { position: relative; height: 90px; }
|
|
.trend-grid-line { position: absolute; left: 0; right: 0; height: 1px; background-color: #edf0f5; }
|
|
.trend-grid-line-top { top: 8px; }
|
|
.trend-grid-line-upper { top: 32px; }
|
|
.trend-grid-line-lower { top: 56px; }
|
|
.trend-grid-line-bottom { bottom: 8px; }
|
|
.trend-line-segment { position: absolute; height: 2px; transform-origin: left center; }
|
|
.trend-line-student { background-color: #6558e9; }
|
|
.trend-line-average { height: 1px; background-color: #9aa8bc; }
|
|
.trend-point { position: absolute; width: 7px; height: 7px; margin-left: -4px; margin-bottom: -4px; border-radius: 4px; background-color: #ffffff; }
|
|
.trend-point-student { border: 2px solid #6558e9; }
|
|
.trend-point-average { border: 2px solid #9aa8bc; }
|
|
.trend-labels { display: flex; flex-direction: row; justify-content: space-between; margin-top: 6px; }
|
|
.trend-day { font-size: 10px; color: #8992a5; }
|
|
</style>
|