From 488018d30536dcfff46e28062237722c72cd2762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=AE=B8=E9=9C=86?= <148036253+programinglearner@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:55:50 +0800 Subject: [PATCH 1/2] Add HorizontalStackedBar widget (horizontal stacked bar chart) --- _examples/horizontal_stacked_barchart.go | 44 +++++++++++ widgets/horizontal_stacked_barchart.go | 94 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 _examples/horizontal_stacked_barchart.go create mode 100644 widgets/horizontal_stacked_barchart.go diff --git a/_examples/horizontal_stacked_barchart.go b/_examples/horizontal_stacked_barchart.go new file mode 100644 index 00000000..f439d0e7 --- /dev/null +++ b/_examples/horizontal_stacked_barchart.go @@ -0,0 +1,44 @@ +// Copyright 2017 Zack Guo . All rights reserved. +// Use of this source code is governed by a MIT license that can +// be found in the LICENSE file. + +// +build ignore + +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() + + hsbc := widgets.NewHorizontalStackedBar() + hsbc.Title = "Student's Marks: X-Axis=Grade% (Math, English, Science, Computer Science), Y-Axis=Name" + hsbc.Labels = []string{"Ken", "Rob", "Dennis", "Linus"} + + hsbc.Data = make([][]float64, 4) + hsbc.Data[0] = []float64{90, 85, 90, 80} + hsbc.Data[1] = []float64{70, 85, 75, 60} + hsbc.Data[2] = []float64{75, 60, 80, 85} + hsbc.Data[3] = []float64{100, 100, 100, 100} + hsbc.SetRect(5, 5, 100, 30) + hsbc.BarHeight = 2 + + ui.Render(hsbc) + + uiEvents := ui.PollEvents() + for { + e := <-uiEvents + switch e.ID { + case "q", "": + return + } + } +} diff --git a/widgets/horizontal_stacked_barchart.go b/widgets/horizontal_stacked_barchart.go new file mode 100644 index 00000000..7b053d37 --- /dev/null +++ b/widgets/horizontal_stacked_barchart.go @@ -0,0 +1,94 @@ +// Copyright 2017 Zack Guo . All rights reserved. +// Use of this source code is governed by a MIT license that can +// be found in the LICENSE file. + +package widgets + +import ( + "fmt" + "image" + + rw "github.com/mattn/go-runewidth" // 必須匯入,因為 Draw 中使用了 rw.StringWidth + + . "github.com/gizak/termui/v3" +) + +type HorizontalStackedBar struct { + Block + BarColors []Color + LabelStyles []Style + NumStyles []Style // only Fg and Modifier are used + NumFormatter func(float64) string + Data [][]float64 + Labels []string + BarHeight int + BarGap int + MaxVal float64 +} + +func NewHorizontalStackedBar() *HorizontalStackedBar { + return &HorizontalStackedBar{ + Block: *NewBlock(), + BarColors: Theme.StackedBarChart.Bars, + LabelStyles: Theme.StackedBarChart.Labels, + NumStyles: Theme.StackedBarChart.Nums, + NumFormatter: func(n float64) string { return fmt.Sprint(n) }, + BarGap: 2, + BarHeight: 3, + } +} + +func (self *HorizontalStackedBar) Draw(buf *Buffer) { + self.Block.Draw(buf) + + maxVal := self.MaxVal + if maxVal == 0 { + for _, data := range self.Data { + maxVal = MaxFloat64(maxVal, SumFloat64Slice(data)) + } + } + + barYCoordinate := self.Inner.Min.Y+1 + + labelWidth := 10 + + for i, bar := range self.Data { + stackX := labelWidth + + for j, data := range bar { + width := int((data / maxVal) * float64(self.Inner.Dx()-labelWidth-1)) + + for y := barYCoordinate; y < MinInt(barYCoordinate+self.BarHeight, self.Inner.Max.Y); y++ { + for x := self.Inner.Min.X + stackX; x < MinInt(self.Inner.Min.X+stackX+width, self.Inner.Max.X); x++ { + c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, j))) + buf.SetCell(c, image.Pt(x, y)) + } + } + + textX := self.Inner.Min.X + stackX + width/2 - rw.StringWidth(self.NumFormatter(data))/2 + textY := barYCoordinate+self.BarHeight + buf.SetString( + self.NumFormatter(data), + NewStyle( + SelectStyle(self.NumStyles, j+1).Fg, + ColorClear, + SelectStyle(self.NumStyles, j+1).Modifier, + ), + image.Pt(textX, textY), + ) + + stackX += width + } + + if i < len(self.Labels) { + labelY := barYCoordinate+self.BarHeight/2 + buf.SetString( + TrimString(self.Labels[i], labelWidth-2), + SelectStyle(self.LabelStyles, i), + image.Pt(self.Inner.Min.X+1, labelY), + ) + } + + barYCoordinate += (self.BarHeight + self.BarGap) + } +} From 06c21b658e0f1bef227211c2c4aa34599f6c45f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=AE=B8=E9=9C=86?= <148036253+programinglearner@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:26:39 +0800 Subject: [PATCH 2/2] Add HorizontalStackedBar widget (horizontal stacked bar chart) --- _examples/horizontal_stacked_barchart.go | 2 +- widgets/horizontal_stacked_barchart.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_examples/horizontal_stacked_barchart.go b/_examples/horizontal_stacked_barchart.go index f439d0e7..60543741 100644 --- a/_examples/horizontal_stacked_barchart.go +++ b/_examples/horizontal_stacked_barchart.go @@ -29,7 +29,7 @@ func main() { hsbc.Data[2] = []float64{75, 60, 80, 85} hsbc.Data[3] = []float64{100, 100, 100, 100} hsbc.SetRect(5, 5, 100, 30) - hsbc.BarHeight = 2 + hsbc.BarHeight = 3 ui.Render(hsbc) diff --git a/widgets/horizontal_stacked_barchart.go b/widgets/horizontal_stacked_barchart.go index 7b053d37..89a233ed 100644 --- a/widgets/horizontal_stacked_barchart.go +++ b/widgets/horizontal_stacked_barchart.go @@ -8,7 +8,7 @@ import ( "fmt" "image" - rw "github.com/mattn/go-runewidth" // 必須匯入,因為 Draw 中使用了 rw.StringWidth + rw "github.com/mattn/go-runewidth" . "github.com/gizak/termui/v3" ) @@ -66,7 +66,7 @@ func (self *HorizontalStackedBar) Draw(buf *Buffer) { } textX := self.Inner.Min.X + stackX + width/2 - rw.StringWidth(self.NumFormatter(data))/2 - textY := barYCoordinate+self.BarHeight + textY := barYCoordinate+self.BarHeight/2 buf.SetString( self.NumFormatter(data), NewStyle(