ca1793b4c0
- 趋势组件 trend-chart 新增 chartType(line/bar),line 模式用内联 SVG 渲染折线+渐变面积(无 canvas) - 记录页个人最佳趋势改用折线图,峰值信息移到图表下方对称高亮块显示最佳趋势产生日期 - 修复1: linePoints 漏存绝对坐标 y 导致运行时 toFixed 崩溃 - 修复2: 折线模式漏渲染数据点数值(valueText),现圆点上方显示且 0 值不显示
109 lines
4.1 KiB
JavaScript
109 lines
4.1 KiB
JavaScript
const { getBarPercent } = require('../../utils/util')
|
|
|
|
/**
|
|
* Trend chart — pure CSS bar mode + optional SVG line mode (no canvas),
|
|
* matching the calendar-heatmap's rendering approach for consistency.
|
|
*
|
|
* chartData: [{ label, value, valueText, highlight }]
|
|
* - label: bottom caption (e.g. "一" / "7月")
|
|
* - value: numeric magnitude (seconds); drives bar height / line y
|
|
* - valueText: formatted string shown above the bar (e.g. "1分30秒")
|
|
* - highlight: truthy -> theme glow + bold label (today / this month)
|
|
*
|
|
* chartType: 'bar' (default) | 'line'
|
|
* - bar: CSS columns, height strictly proportional to value/max.
|
|
* - line: inline SVG (data URI) polyline + translucent area, rendered in an
|
|
* <image>; dots + axis labels are real WXML elements positioned by
|
|
* percentage so they stay crisp and theme-colored (SVG inside <image>
|
|
* cannot read page CSS variables). The viewBox aspect is matched to
|
|
* the container's rpx aspect (widthFix) so the stroke stays uniform
|
|
* — no non-uniform horizontal/vertical stretch.
|
|
*
|
|
* lineColor: hex used for the SVG stroke + area fill. Must be passed by the
|
|
* page because SVG inside <image> cannot read page CSS variables.
|
|
*/
|
|
const VB_W = 640
|
|
|
|
Component({
|
|
properties: {
|
|
chartData: { type: Array, value: [] },
|
|
variant: { type: String, value: 'total' },
|
|
chartType: { type: String, value: 'bar' },
|
|
lineColor: { type: String, value: '#FF6B35' }
|
|
},
|
|
data: {
|
|
bars: [],
|
|
linePoints: [],
|
|
lineSvg: ''
|
|
},
|
|
observers: {
|
|
'chartData, chartType, lineColor'(chartData, chartType, lineColor) {
|
|
this._compute(chartData, chartType, lineColor)
|
|
}
|
|
},
|
|
lifetimes: {
|
|
ready() {
|
|
this._compute(this.properties.chartData, this.properties.chartType, this.properties.lineColor)
|
|
}
|
|
},
|
|
methods: {
|
|
_compute(chartData, chartType, lineColor) {
|
|
const arr = Array.isArray(chartData) ? chartData : []
|
|
const max = arr.reduce((m, d) => Math.max(m, Number(d && d.value) || 0), 0)
|
|
const bars = arr.map((d) => {
|
|
const v = Number(d && d.value) || 0
|
|
return {
|
|
label: (d && d.label) || '',
|
|
value: v,
|
|
valueText: (d && d.valueText) || '',
|
|
highlight: !!(d && d.highlight),
|
|
percent: getBarPercent(v, max)
|
|
}
|
|
})
|
|
|
|
const N = arr.length
|
|
let linePoints = []
|
|
let lineSvg = ''
|
|
if (N > 0 && chartType === 'line') {
|
|
const lineH = this.properties.variant === 'peak' ? 170 : 200
|
|
const topPad = 16
|
|
const bottomPad = 12
|
|
const plot = lineH - topPad - bottomPad
|
|
linePoints = bars.map((b, i) => {
|
|
const x = (i + 0.5) * (VB_W / N)
|
|
const y = topPad + (1 - b.percent / 100) * plot
|
|
return {
|
|
x,
|
|
y,
|
|
xPercent: (x / VB_W) * 100,
|
|
yPercent: (y / lineH) * 100,
|
|
label: b.label,
|
|
valueText: b.valueText,
|
|
value: b.value,
|
|
highlight: b.highlight
|
|
}
|
|
})
|
|
lineSvg = this._buildLineSvg(linePoints, VB_W, lineH, lineColor || '#FF6B35')
|
|
}
|
|
this.setData({ bars, linePoints, lineSvg })
|
|
},
|
|
|
|
_buildLineSvg(points, W, H, color) {
|
|
const valid = (points || []).filter(p => p && typeof p.x === 'number' && typeof p.y === 'number')
|
|
if (valid.length === 0) return ''
|
|
const pts = valid.map(p => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(' ')
|
|
const first = valid[0]
|
|
const last = valid[valid.length - 1]
|
|
let area = `M ${first.x.toFixed(2)},${H} `
|
|
valid.forEach((p) => { area += `L ${p.x.toFixed(2)},${p.y.toFixed(2)} ` })
|
|
area += `L ${last.x.toFixed(2)},${H} Z`
|
|
const svg =
|
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none">` +
|
|
`<path d="${area}" fill="${color}" fill-opacity="0.16"/>` +
|
|
`<polyline points="${pts}" fill="none" stroke="${color}" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/>` +
|
|
`</svg>`
|
|
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
|
|
}
|
|
}
|
|
})
|