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()) }