termui

TUIDashboardChartsMonitoringGo

GitHub Overview

gizak/termui

Golang terminal dashboard

Stars13,393
Watchers285
Forks798
Created:February 3, 2015
Language:Go
License:MIT License

Topics

None

Star History

gizak/termui Star History
Data as of: 7/25/2025, 11:09 AM

termui

termui is a Go TUI library specialized for building dashboards and monitoring tools. It provides rich data visualization widgets such as charts, graphs, and gauges, making it ideal for system monitoring and real-time dashboard construction.

Features

Data Visualization Widgets

  • Charts: LineChart, BarChart, PieChart, SparkLine
  • Gauges: Gauge, Battery, Progress
  • Lists: List, Table, Tree
  • Text: Paragraph, Tabs
  • Layout: Grid, Block

Performance and Real-time Updates

  • Efficient Rendering: Differential rendering
  • Real-time Data: Display streaming data
  • Memory Efficient: Minimal memory footprint
  • High-frequency Updates: Supports frequent data updates

Ease of Use

  • Simple API: Intuitive widget creation
  • Default Themes: Ready-to-use color schemes
  • Responsive Layout: Adapts to screen size
  • Event Handling: Keyboard and mouse events

Customizability

  • Style Configuration: Customize colors, borders, padding
  • Custom Widgets: Create your own widgets
  • Themes: Define custom themes
  • Layout System: Flexible grid layout

Basic Usage

Installation

go get github.com/gizak/termui/v3

Hello World

package main

import (
    "log"
    
    ui "github.com/gizak/termui/v3"
    "github.com/gizak/termui/v3/widgets"
)

func main() {
    if err := ui.Init(); err != nil {
        log.Fatalf("failed to initialize termui: %v", err)
    }
    defer ui.Close()
    
    // Create paragraph widget
    p := widgets.NewParagraph()
    p.Text = "Hello World!"
    p.SetRect(0, 0, 25, 5)
    p.Title = "Welcome"
    p.BorderStyle.Fg = ui.ColorYellow
    
    ui.Render(p)
    
    // Event loop
    for e := range ui.PollEvents() {
        if e.Type == ui.KeyboardEvent {
            break
        }
    }
}

Creating a Dashboard

package main

import (
    "log"
    "math"
    "time"
    
    ui "github.com/gizak/termui/v3"
    "github.com/gizak/termui/v3/widgets"
)

func main() {
    if err := ui.Init(); err != nil {
        log.Fatalf("failed to initialize termui: %v", err)
    }
    defer ui.Close()
    
    // Gauge
    g := widgets.NewGauge()
    g.Title = "CPU Usage"
    g.Percent = 50
    g.SetRect(0, 0, 50, 3)
    g.BarColor = ui.ColorRed
    g.LabelStyle = ui.NewStyle(ui.ColorYellow)
    
    // Sparkline
    sl := widgets.NewSparkline()
    sl.Title = "Network"
    sl.Data = []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
    sl.LineColor = ui.ColorCyan
    sl.TitleStyle.Fg = ui.ColorWhite
    
    slg := widgets.NewSparklineGroup(sl)
    slg.Title = "Traffic"
    slg.SetRect(0, 3, 50, 8)
    
    // Bar chart
    bc := widgets.NewBarChart()
    bc.Title = "Memory Usage"
    bc.SetRect(0, 8, 50, 13)
    bc.Labels = []string{"S0", "S1", "S2", "S3", "S4", "S5", "S6", "S7"}
    bc.Data = []float64{12, 18, 15, 20, 22, 19, 23, 25}
    bc.BarWidth = 5
    bc.BarColors = []ui.Color{ui.ColorGreen, ui.ColorYellow, ui.ColorRed}
    bc.LabelStyles = []ui.Style{ui.NewStyle(ui.ColorBlue)}
    bc.NumStyles = []ui.Style{ui.NewStyle(ui.ColorWhite)}
    
    // Initial render
    ui.Render(g, slg, bc)
    
    // Update timer
    uiEvents := ui.PollEvents()
    ticker := time.NewTicker(time.Second).C
    
    // Event loop
    for {
        select {
        case e := <-uiEvents:
            switch e.ID {
            case "q", "<C-c>":
                return
            }
            
        case <-ticker:
            // Update data
            g.Percent = int(50 + 30*math.Sin(float64(time.Now().Unix())))
            
            // Add data to sparkline
            sl.Data = append(sl.Data[1:], sl.Data[len(sl.Data)-1]+float64(10-20*math.Sin(float64(time.Now().Unix()))))
            
            ui.Render(g, slg, bc)
        }
    }
}

Real-time Charts

package main

import (
    "log"
    "math"
    "time"
    
    ui "github.com/gizak/termui/v3"
    "github.com/gizak/termui/v3/widgets"
)

func main() {
    if err := ui.Init(); err != nil {
        log.Fatalf("failed to initialize termui: %v", err)
    }
    defer ui.Close()
    
    // Line chart
    lc := widgets.NewPlot()
    lc.Title = "Sine Wave"
    lc.Data = make([][]float64, 1)
    lc.SetRect(0, 0, 60, 15)
    lc.AxesColor = ui.ColorWhite
    lc.LineColors[0] = ui.ColorGreen
    lc.Marker = widgets.MarkerDot
    
    // Data generation function
    generateData := func() []float64 {
        data := make([]float64, 100)
        for i := 0; i < 100; i++ {
            data[i] = 10 + 10*math.Sin(float64(i)/5.0+float64(time.Now().UnixNano())/1e9)
        }
        return data
    }
    
    lc.Data[0] = generateData()
    
    // Pie chart
    pc := widgets.NewPieChart()
    pc.Title = "Resource Distribution"
    pc.SetRect(60, 0, 100, 15)
    pc.Data = []float64{.25, .25, .25, .25}
    pc.AngleOffset = .15
    pc.LabelFormatter = func(i int, v float64) string {
        return fmt.Sprintf("%.0f%%", v*100)
    }
    
    ui.Render(lc, pc)
    
    // Update loop
    uiEvents := ui.PollEvents()
    ticker := time.NewTicker(100 * time.Millisecond).C
    
    for {
        select {
        case e := <-uiEvents:
            switch e.ID {
            case "q", "<C-c>":
                return
            }
            
        case <-ticker:
            lc.Data[0] = generateData()
            ui.Render(lc)
        }
    }
}

Grid Layout

package main

import (
    "log"
    
    ui "github.com/gizak/termui/v3"
    "github.com/gizak/termui/v3/widgets"
)

func main() {
    if err := ui.Init(); err != nil {
        log.Fatalf("failed to initialize termui: %v", err)
    }
    defer ui.Close()
    
    // Create widgets
    p1 := widgets.NewParagraph()
    p1.Title = "Statistics"
    p1.Text = "CPU: 45%\nMemory: 2.5GB / 8GB\nDisk: 120GB / 500GB"
    p1.BorderStyle.Fg = ui.ColorYellow
    
    p2 := widgets.NewParagraph()
    p2.Title = "Logs"
    p2.Text = "[INFO] System started\n[WARN] High load detected\n[INFO] Process completed"
    p2.BorderStyle.Fg = ui.ColorMagenta
    
    l := widgets.NewList()
    l.Title = "Processes"
    l.Rows = []string{
        "[1] nginx",
        "[2] postgres",
        "[3] redis",
        "[4] app-server",
    }
    l.SelectedRowStyle = ui.NewStyle(ui.ColorGreen)
    l.BorderStyle.Fg = ui.ColorCyan
    
    // Grid definition
    grid := ui.NewGrid()
    termWidth, termHeight := ui.TerminalDimensions()
    grid.SetRect(0, 0, termWidth, termHeight)
    
    grid.Set(
        ui.NewRow(1.0/2,
            ui.NewCol(1.0/2, p1),
            ui.NewCol(1.0/2, p2),
        ),
        ui.NewRow(1.0/2,
            ui.NewCol(1.0, l),
        ),
    )
    
    ui.Render(grid)
    
    // Event handling
    previousKey := ""
    uiEvents := ui.PollEvents()
    for {
        e := <-uiEvents
        switch e.ID {
        case "q", "<C-c>":
            return
        case "j", "<Down>":
            l.ScrollDown()
        case "k", "<Up>":
            l.ScrollUp()
        case "<C-d>":
            l.ScrollHalfPageDown()
        case "<C-u>":
            l.ScrollHalfPageUp()
        case "<C-f>":
            l.ScrollPageDown()
        case "<C-b>":
            l.ScrollPageUp()
        case "<Home>":
            l.ScrollTop()
        case "<End>":
            l.ScrollBottom()
        }
        
        if previousKey == "g" {
            switch e.ID {
            case "g":
                l.ScrollTop()
            }
        }
        
        if e.ID == "g" {
            previousKey = "g"
        } else {
            previousKey = ""
        }
        
        ui.Render(grid)
    }
}

Advanced Features

Custom Widgets

type CustomGauge struct {
    widgets.Block
    Percent int
    BarColor ui.Color
    Label string
}

func NewCustomGauge() *CustomGauge {
    return &CustomGauge{
        Block:    *widgets.NewBlock(),
        BarColor: ui.ColorGreen,
    }
}

func (g *CustomGauge) Draw(buf *ui.Buffer) {
    g.Block.Draw(buf)
    
    // Draw bar
    barWidth := int(float64(g.Inner.Dx()) * float64(g.Percent) / 100)
    for x := g.Inner.Min.X; x < g.Inner.Min.X+barWidth; x++ {
        for y := g.Inner.Min.Y; y < g.Inner.Max.Y; y++ {
            buf.SetCell(ui.NewCell('█', ui.NewStyle(g.BarColor)), image.Pt(x, y))
        }
    }
    
    // Draw label
    label := fmt.Sprintf("%s: %d%%", g.Label, g.Percent)
    labelX := g.Inner.Min.X + (g.Inner.Dx()-len(label))/2
    labelY := g.Inner.Min.Y + g.Inner.Dy()/2
    
    for i, r := range label {
        buf.SetCell(ui.NewCell(r, ui.NewStyle(ui.ColorWhite)), image.Pt(labelX+i, labelY))
    }
}

Data Streaming

func streamData(dataChan chan<- float64) {
    for {
        // Read from actual data source
        value := rand.Float64() * 100
        dataChan <- value
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // ... initialization code ...
    
    dataChan := make(chan float64, 100)
    go streamData(dataChan)
    
    data := make([]float64, 50)
    
    for {
        select {
        case value := <-dataChan:
            // Update data
            data = append(data[1:], value)
            
            // Update chart
            chart.Data[0] = data
            ui.Render(chart)
            
        case e := <-uiEvents:
            if e.ID == "q" {
                return
            }
        }
    }
}

Theme Customization

// Custom theme definition
type Theme struct {
    Background ui.Color
    Border     ui.Color
    Title      ui.Color
    Text       ui.Color
    Highlight  ui.Color
}

var DarkTheme = Theme{
    Background: ui.ColorBlack,
    Border:     ui.ColorWhite,
    Title:      ui.ColorYellow,
    Text:       ui.ColorWhite,
    Highlight:  ui.ColorGreen,
}

func applyTheme(w *widgets.Paragraph, theme Theme) {
    w.BorderStyle.Fg = theme.Border
    w.TitleStyle.Fg = theme.Title
    w.TextStyle.Fg = theme.Text
}

Ecosystem

Similar Projects

  • termdash: More advanced dashboard framework
  • tcell: Low-level terminal library
  • tui-go: Deprecated but influential library

Adoption Examples

  • gotop: System monitoring tool
  • sampler: Configuration-based monitoring tool
  • lazydocker: Docker management tool (partial use)

Advantages

  • Data Visualization: Rich chart and graph widgets
  • Dashboard-focused: Easy construction of monitoring tools
  • Simple API: Intuitive and easy to learn
  • Performance: Optimized for real-time data display
  • Customizability: Create custom widgets

Limitations

  • Limited Use Case: Not suitable for purposes other than dashboards
  • Interactivity: Not suitable for complex input handling
  • Layout: Basic grid system
  • Update Frequency: Updates have stagnated since v3

Comparison with Other Libraries

FeaturetermuiBubble Teatview
Use CaseDashboardGeneralGeneral
Data VizExcellentNeeds implBasic
Learning CostLowMediumLow
FlexibilityMediumVery HighHigh
ArchitectureWidgetElmWidget

Summary

termui is an excellent library for building dashboards and monitoring tools in Go. With rich data visualization widgets, simple API, and efficient rendering, it's ideal for real-time data display and system monitoring. While not suitable for general-purpose TUI applications, its specialization makes it one of the most productive choices for dashboard development.