package tui import ( "sort" "strconv" "strings" "sync" "time" "get-container/cmd/hytop/tchart" "github.com/NimbleMarkets/ntcharts/canvas/runes" 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("#4d4d4dff")) ) // 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 { Points map[string][]tchart.TimePoint } // MyTimeChart 特化的时间流表,时间区域就是宽度的2倍,单位是秒 type MyTimeChart struct { chart *tchart.Model // 原始图表 zM *zone.Manager // 区域管理 dataSet map[string][]tchart.TimePoint // 数据点 width, height int // 图表的高度和宽度 max, min float64 // y轴的最值 dataStyle map[string]lipgloss.Style // 记录每个数据集的样式 lockDataSet sync.RWMutex // 保护dataSet的并发读写 lockChart sync.RWMutex // 保护chart的数据集的并发读写 } // New 新建图表,其中dataSet的Key为数据集名称,value为数据集的颜色 func NewTimeChart(width, height int, vmin, vmax float64, dataSet map[string]lipgloss.Color) *MyTimeChart { result := MyTimeChart{} result.max = vmax result.min = vmin result.width = width result.height = height zoneManager := zone.New() result.zM = zoneManager result.dataSet = make(map[string][]tchart.TimePoint) result.dataStyle = make(map[string]lipgloss.Style) result.lockChart = sync.RWMutex{} result.lockDataSet = sync.RWMutex{} result.lockDataSet.Lock() for k, v := range dataSet { result.dataSet[k] = make([]tchart.TimePoint, (result.width*2)+1) result.dataStyle[k] = lipgloss.NewStyle().Foreground(v) } initPoints := make([]tchart.TimePoint, (result.width*2)+1) // 准备数据,数据间隔为1秒 now := time.Now() for i := result.width * 2; i >= 0; i-- { initPoints[i] = tchart.TimePoint{Time: now.Add(time.Duration(-i) * time.Second), Value: vmin} } for k := range dataSet { copy(result.dataSet[k], initPoints) } result.lockDataSet.Unlock() s := tchart.New(width, height+1, tchart.WithLineStyle(runes.ThinLineStyle), tchart.WithZoneManager(zoneManager), tchart.WithYRange(vmin, vmax), tchart.WithXYSteps(0, 0), ) wg := sync.WaitGroup{} wg.Add(len(dataSet)) for k := range dataSet { points := result.dataSet[k] style := result.dataStyle[k] go func(ps []tchart.TimePoint, st lipgloss.Style, key string) { result.lockChart.Lock() s.SetDataSet(key, ps) s.SetDataSetStyle(key, st) result.lockChart.Unlock() wg.Done() }(points, style, k) } wg.Wait() result.chart = &s return &result } func (m *MyTimeChart) PutPoint(points map[string][]tchart.TimePoint) { // 更新chart s := tchart.New(m.width, m.height+1, tchart.WithLineStyle(runes.ThinLineStyle), tchart.WithZoneManager(m.zM), tchart.WithYRange(m.min, m.max), tchart.WithXYSteps(0, 0), ) m.chart = &s wg := sync.WaitGroup{} wg.Add(len(m.dataSet)) keys, index := make([]string, len(m.dataSet)), 0 for k := range m.dataSet { keys[index] = k index++ } for _, k := range keys { newPoints := points[k] oldPoints := m.dataSet[k] go func(ops, nps []tchart.TimePoint, ds map[string][]tchart.TimePoint, key string) { ops = append(ops, nps...) sort.Slice(ops, func(i, j int) bool { return ops[i].Time.Before(ops[j].Time) }) threshold := time.Now().Add(time.Second * time.Duration(-2*m.width)) targetIndex := 0 for i, p := range ops { if !p.Time.Before(threshold) { targetIndex = i break } } nps = append(make([]tchart.TimePoint, 0), ops[targetIndex:]...) m.lockDataSet.Lock() ds[key] = nps m.lockDataSet.Unlock() m.lockChart.Lock() s.SetDataSet(key, nps) s.SetDataSetStyle(key, m.dataStyle[key]) m.lockChart.Unlock() wg.Done() }(oldPoints, newPoints, m.dataSet, k) } wg.Wait() m.chart.DrawXYAxisAndLabel() m.chart.DrawBrailleAll() } func (m *MyTimeChart) Init() tea.Cmd { m.chart.DrawXYAxisAndLabel() m.chart.DrawBrailleAll() return nil } func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { switch msg := inputMsg.(type) { case MyTimeChartMsg: m.PutPoint(msg.Points) return m, nil } return m, nil } func (m *MyTimeChart) View() string { rl := m.lockChart.RLocker() rl.Lock() str := m.zM.Scan(m.chart.View()) rl.Unlock() return str[m.width+1:] }