timechart.go 3.52 KB
Newer Older
liming6's avatar
liming6 committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package tui

import (
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/NimbleMarkets/ntcharts/canvas/runes"
	tchart "github.com/NimbleMarkets/ntcharts/linechart/timeserieslinechart"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	zone "github.com/lrstanley/bubblezone"
)

const (
	A = "├"
)

var (
	axisFStyle = lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#707070ff"))
)

// genXAxis 生成X轴,参数l是x轴的长度
func genXAxis(l int) string {
	t60 := l / 30
	t30 := l >= 18
	var result string
	if t30 {
		result = A + strings.Repeat("─", 14)
		result = axisFStyle.Render("30s") + result
	} else {
		return strings.Repeat("─", l)
	}
	// 长度不超过33
	if l < 33 {
		return strings.Repeat("─", l-18) + result
	}
	for i := 1; i <= t60+1; i++ {
		timeStr := strconv.Itoa(i*60) + "s"
		timeStrLen := len(timeStr)
		timeStr = axisFStyle.Render(timeStr)
		targetLen := timeStrLen + i*30
		if l < targetLen {
			// 不渲染标记,仅增加轴长度
			result = strings.Repeat("─", l-lipgloss.Width(result)) + result
			break
		}
		// 渲染标记
		result = timeStr + A + strings.Repeat("─", targetLen-lipgloss.Width(result)-timeStrLen-1) + result
	}
	return result
}

// MyTimeChartMsg 时间流表消息,用于插入数据
type MyTimeChartMsg struct {
	point tchart.TimePoint
}

// MyTimeChart 特化的时间流表,时间区域就是宽度的2倍,单位是秒
type MyTimeChart struct {
	chart         *tchart.Model      // 原始图表
	zM            *zone.Manager      // 区域管理
	points        []tchart.TimePoint // 数据点
	width, height int                // 图表的高度和宽度
	max, min      float64            // y轴的最值
}

func New(width, height int, vmin, vmax float64) *MyTimeChart {
	result := MyTimeChart{}
	result.max = vmax
	result.min = vmin
	result.width = width
	result.height = height
	zoneManager := zone.New()
	result.zM = zoneManager
	result.points = make([]tchart.TimePoint, 0)
	// 准备数据,数据间隔为1秒
	now := time.Now()
	for i := result.width * 2; i >= 0; i-- {
		result.points = append(result.points, tchart.TimePoint{Time: now.Add(-time.Duration(i) * time.Second), Value: vmin})
	}
	s := tchart.New(width, height,
		tchart.WithLineStyle(runes.ThinLineStyle),
		tchart.WithZoneManager(zoneManager),
		tchart.WithYRange(vmin, vmax),
		tchart.WithTimeSeries(result.points),
		tchart.WithXYSteps(0, 0),
	)
	result.chart = &s
	return &result
}

func (m *MyTimeChart) PutPoint(timePoint tchart.TimePoint) {
	m.points = append(m.points, timePoint)
	// 排序
	sort.Slice(m.points, func(i, j int) bool {
		return m.points[i].Time.Before(m.points[j].Time)
	})
	// 剔除不要的数据
	threshold := time.Now().Add(time.Second * time.Duration(-2*m.width))
	targetIndex := 0
	for i, p := range m.points {
		if !p.Time.Before(threshold) {
			targetIndex = i
			break
		}
	}
	points := append(make([]tchart.TimePoint, 0), m.points[targetIndex:]...)
	m.points = points
	// 更新chart
	s := tchart.New(m.width, m.height,
		tchart.WithLineStyle(runes.ThinLineStyle),
		tchart.WithZoneManager(m.zM),
		tchart.WithYRange(m.min, m.max),
		tchart.WithTimeSeries(m.points),
		tchart.WithXYSteps(0, 0),
	)
	m.chart = &s
}

func (m *MyTimeChart) Init() tea.Cmd {
	m.chart.DrawXYAxisAndLabel()
	m.chart.DrawBraille()
	return nil
}

func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case MyTimeChartMsg:
		m.PutPoint(msg.point)
		return m, nil
	}
	return m, nil
}

func (m *MyTimeChart) View() string {
	return m.zM.Scan(m.chart.View())
}