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 * ; dots + axis labels are real WXML elements positioned by * percentage so they stay crisp and theme-colored (SVG inside * 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 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 = `` + `` + `` + `` return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) } } })