Move source files into v3 directory

This commit is contained in:
Caleb Bassi
2019-06-30 15:06:06 -07:00
parent f08e81d72a
commit 12333d6a7e
30 changed files with 0 additions and 0 deletions

View File

@@ -1,89 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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"
. "github.com/gizak/termui/v3"
)
type BarChart struct {
Block
BarColors []Color
LabelStyles []Style
NumStyles []Style // only Fg and Modifier are used
NumFormatter func(float64) string
Data []float64
Labels []string
BarWidth int
BarGap int
MaxVal float64
}
func NewBarChart() *BarChart {
return &BarChart{
Block: *NewBlock(),
BarColors: Theme.BarChart.Bars,
NumStyles: Theme.BarChart.Nums,
LabelStyles: Theme.BarChart.Labels,
NumFormatter: func(n float64) string { return fmt.Sprint(n) },
BarGap: 1,
BarWidth: 3,
}
}
func (self *BarChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64FromSlice(self.Data)
}
barXCoordinate := self.Inner.Min.X
for i, data := range self.Data {
// draw bar
height := int((data / maxVal) * float64(self.Inner.Dy()-1))
for x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {
for y := self.Inner.Max.Y - 2; y > (self.Inner.Max.Y-2)-height; y-- {
c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, i)))
buf.SetCell(c, image.Pt(x, y))
}
}
// draw label
if i < len(self.Labels) {
labelXCoordinate := barXCoordinate +
int((float64(self.BarWidth) / 2)) -
int((float64(rw.StringWidth(self.Labels[i])) / 2))
buf.SetString(
self.Labels[i],
SelectStyle(self.LabelStyles, i),
image.Pt(labelXCoordinate, self.Inner.Max.Y-1),
)
}
// draw number
numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2))
if numberXCoordinate <= self.Inner.Max.X {
buf.SetString(
self.NumFormatter(data),
NewStyle(
SelectStyle(self.NumStyles, i+1).Fg,
SelectColor(self.BarColors, i),
SelectStyle(self.NumStyles, i+1).Modifier,
),
image.Pt(numberXCoordinate, self.Inner.Max.Y-2),
)
}
barXCoordinate += (self.BarWidth + self.BarGap)
}
}

View File

@@ -1,57 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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"
. "github.com/gizak/termui/v3"
)
type Gauge struct {
Block
Percent int
BarColor Color
Label string
LabelStyle Style
}
func NewGauge() *Gauge {
return &Gauge{
Block: *NewBlock(),
BarColor: Theme.Gauge.Bar,
LabelStyle: Theme.Gauge.Label,
}
}
func (self *Gauge) Draw(buf *Buffer) {
self.Block.Draw(buf)
label := self.Label
if label == "" {
label = fmt.Sprintf("%d%%", self.Percent)
}
// plot bar
barWidth := int((float64(self.Percent) / 100) * float64(self.Inner.Dx()))
buf.Fill(
NewCell(' ', NewStyle(ColorClear, self.BarColor)),
image.Rect(self.Inner.Min.X, self.Inner.Min.Y, self.Inner.Min.X+barWidth, self.Inner.Max.Y),
)
// plot label
labelXCoordinate := self.Inner.Min.X + (self.Inner.Dx() / 2) - int(float64(len(label))/2)
labelYCoordinate := self.Inner.Min.Y + ((self.Inner.Dy() - 1) / 2)
if labelYCoordinate < self.Inner.Max.Y {
for i, char := range label {
style := self.LabelStyle
if labelXCoordinate+i+1 <= self.Inner.Min.X+barWidth {
style = NewStyle(self.BarColor, ColorClear, ModifierReverse)
}
buf.SetCell(NewCell(char, style), image.Pt(labelXCoordinate+i, labelYCoordinate))
}
}
}

View File

@@ -1,204 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
"image/color"
. "github.com/gizak/termui/v3"
)
type Image struct {
Block
Image image.Image
Monochrome bool
MonochromeThreshold uint8
MonochromeInvert bool
}
func NewImage(img image.Image) *Image {
return &Image{
Block: *NewBlock(),
MonochromeThreshold: 128,
Image: img,
}
}
func (self *Image) Draw(buf *Buffer) {
self.Block.Draw(buf)
if self.Image == nil {
return
}
bufWidth := self.Inner.Dx()
bufHeight := self.Inner.Dy()
imageWidth := self.Image.Bounds().Dx()
imageHeight := self.Image.Bounds().Dy()
if self.Monochrome {
if bufWidth > imageWidth/2 {
bufWidth = imageWidth / 2
}
if bufHeight > imageHeight/2 {
bufHeight = imageHeight / 2
}
for bx := 0; bx < bufWidth; bx++ {
for by := 0; by < bufHeight; by++ {
ul := self.colorAverage(
2*bx*imageWidth/bufWidth/2,
(2*bx+1)*imageWidth/bufWidth/2,
2*by*imageHeight/bufHeight/2,
(2*by+1)*imageHeight/bufHeight/2,
)
ur := self.colorAverage(
(2*bx+1)*imageWidth/bufWidth/2,
(2*bx+2)*imageWidth/bufWidth/2,
2*by*imageHeight/bufHeight/2,
(2*by+1)*imageHeight/bufHeight/2,
)
ll := self.colorAverage(
2*bx*imageWidth/bufWidth/2,
(2*bx+1)*imageWidth/bufWidth/2,
(2*by+1)*imageHeight/bufHeight/2,
(2*by+2)*imageHeight/bufHeight/2,
)
lr := self.colorAverage(
(2*bx+1)*imageWidth/bufWidth/2,
(2*bx+2)*imageWidth/bufWidth/2,
(2*by+1)*imageHeight/bufHeight/2,
(2*by+2)*imageHeight/bufHeight/2,
)
buf.SetCell(
NewCell(blocksChar(ul, ur, ll, lr, self.MonochromeThreshold, self.MonochromeInvert)),
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
)
}
}
} else {
if bufWidth > imageWidth {
bufWidth = imageWidth
}
if bufHeight > imageHeight {
bufHeight = imageHeight
}
for bx := 0; bx < bufWidth; bx++ {
for by := 0; by < bufHeight; by++ {
c := self.colorAverage(
bx*imageWidth/bufWidth,
(bx+1)*imageWidth/bufWidth,
by*imageHeight/bufHeight,
(by+1)*imageHeight/bufHeight,
)
buf.SetCell(
NewCell(c.ch(), NewStyle(c.fgColor(), ColorBlack)),
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
)
}
}
}
}
func (self *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {
var c colorAverager
for x := x0; x < x1; x++ {
for y := y0; y < y1; y++ {
c = c.add(
self.Image.At(
x+self.Image.Bounds().Min.X,
y+self.Image.Bounds().Min.Y,
),
)
}
}
return c
}
type colorAverager struct {
rsum, gsum, bsum, asum, count uint64
}
func (self colorAverager) add(col color.Color) colorAverager {
r, g, b, a := col.RGBA()
return colorAverager{
rsum: self.rsum + uint64(r),
gsum: self.gsum + uint64(g),
bsum: self.bsum + uint64(b),
asum: self.asum + uint64(a),
count: self.count + 1,
}
}
func (self colorAverager) RGBA() (uint32, uint32, uint32, uint32) {
if self.count == 0 {
return 0, 0, 0, 0
}
return uint32(self.rsum/self.count) & 0xffff,
uint32(self.gsum/self.count) & 0xffff,
uint32(self.bsum/self.count) & 0xffff,
uint32(self.asum/self.count) & 0xffff
}
func (self colorAverager) fgColor() Color {
return palette.Convert(self).(paletteColor).attribute
}
func (self colorAverager) ch() rune {
gray := color.GrayModel.Convert(self).(color.Gray).Y
switch {
case gray < 51:
return SHADED_BLOCKS[0]
case gray < 102:
return SHADED_BLOCKS[1]
case gray < 153:
return SHADED_BLOCKS[2]
case gray < 204:
return SHADED_BLOCKS[3]
default:
return SHADED_BLOCKS[4]
}
}
func (self colorAverager) monochrome(threshold uint8, invert bool) bool {
return self.count != 0 && (color.GrayModel.Convert(self).(color.Gray).Y < threshold != invert)
}
type paletteColor struct {
rgba color.RGBA
attribute Color
}
func (self paletteColor) RGBA() (uint32, uint32, uint32, uint32) {
return self.rgba.RGBA()
}
var palette = color.Palette([]color.Color{
paletteColor{color.RGBA{0, 0, 0, 255}, ColorBlack},
paletteColor{color.RGBA{255, 0, 0, 255}, ColorRed},
paletteColor{color.RGBA{0, 255, 0, 255}, ColorGreen},
paletteColor{color.RGBA{255, 255, 0, 255}, ColorYellow},
paletteColor{color.RGBA{0, 0, 255, 255}, ColorBlue},
paletteColor{color.RGBA{255, 0, 255, 255}, ColorMagenta},
paletteColor{color.RGBA{0, 255, 255, 255}, ColorCyan},
paletteColor{color.RGBA{255, 255, 255, 255}, ColorWhite},
})
func blocksChar(ul, ur, ll, lr colorAverager, threshold uint8, invert bool) rune {
index := 0
if ul.monochrome(threshold, invert) {
index |= 1
}
if ur.monochrome(threshold, invert) {
index |= 2
}
if ll.monochrome(threshold, invert) {
index |= 4
}
if lr.monochrome(threshold, invert) {
index |= 8
}
return IRREGULAR_BLOCKS[index]
}

View File

@@ -1,136 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
rw "github.com/mattn/go-runewidth"
. "github.com/gizak/termui/v3"
)
type List struct {
Block
Rows []string
WrapText bool
TextStyle Style
SelectedRow int
topRow int
SelectedRowStyle Style
}
func NewList() *List {
return &List{
Block: *NewBlock(),
TextStyle: Theme.List.Text,
SelectedRowStyle: Theme.List.Text,
}
}
func (self *List) Draw(buf *Buffer) {
self.Block.Draw(buf)
point := self.Inner.Min
// adjusts view into widget
if self.SelectedRow >= self.Inner.Dy()+self.topRow {
self.topRow = self.SelectedRow - self.Inner.Dy() + 1
} else if self.SelectedRow < self.topRow {
self.topRow = self.SelectedRow
}
// draw rows
for row := self.topRow; row < len(self.Rows) && point.Y < self.Inner.Max.Y; row++ {
cells := ParseStyles(self.Rows[row], self.TextStyle)
if self.WrapText {
cells = WrapCells(cells, uint(self.Inner.Dx()))
}
for j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {
style := cells[j].Style
if row == self.SelectedRow {
style = self.SelectedRowStyle
}
if cells[j].Rune == '\n' {
point = image.Pt(self.Inner.Min.X, point.Y+1)
} else {
if point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {
buf.SetCell(NewCell(ELLIPSES, style), point.Add(image.Pt(-1, 0)))
break
} else {
buf.SetCell(NewCell(cells[j].Rune, style), point)
point = point.Add(image.Pt(rw.RuneWidth(cells[j].Rune), 0))
}
}
}
point = image.Pt(self.Inner.Min.X, point.Y+1)
}
// draw UP_ARROW if needed
if self.topRow > 0 {
buf.SetCell(
NewCell(UP_ARROW, NewStyle(ColorWhite)),
image.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),
)
}
// draw DOWN_ARROW if needed
if len(self.Rows) > int(self.topRow)+self.Inner.Dy() {
buf.SetCell(
NewCell(DOWN_ARROW, NewStyle(ColorWhite)),
image.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),
)
}
}
// ScrollAmount scrolls by amount given. If amount is < 0, then scroll up.
// There is no need to set self.topRow, as this will be set automatically when drawn,
// since if the selected item is off screen then the topRow variable will change accordingly.
func (self *List) ScrollAmount(amount int) {
if len(self.Rows)-int(self.SelectedRow) <= amount {
self.SelectedRow = len(self.Rows) - 1
} else if int(self.SelectedRow)+amount < 0 {
self.SelectedRow = 0
} else {
self.SelectedRow += amount
}
}
func (self *List) ScrollUp() {
self.ScrollAmount(-1)
}
func (self *List) ScrollDown() {
self.ScrollAmount(1)
}
func (self *List) ScrollPageUp() {
// If an item is selected below top row, then go to the top row.
if self.SelectedRow > self.topRow {
self.SelectedRow = self.topRow
} else {
self.ScrollAmount(-self.Inner.Dy())
}
}
func (self *List) ScrollPageDown() {
self.ScrollAmount(self.Inner.Dy())
}
func (self *List) ScrollHalfPageUp() {
self.ScrollAmount(-int(FloorFloat64(float64(self.Inner.Dy()) / 2)))
}
func (self *List) ScrollHalfPageDown() {
self.ScrollAmount(int(FloorFloat64(float64(self.Inner.Dy()) / 2)))
}
func (self *List) ScrollTop() {
self.SelectedRow = 0
}
func (self *List) ScrollBottom() {
self.SelectedRow = len(self.Rows) - 1
}

View File

@@ -1,48 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
. "github.com/gizak/termui/v3"
)
type Paragraph struct {
Block
Text string
TextStyle Style
WrapText bool
}
func NewParagraph() *Paragraph {
return &Paragraph{
Block: *NewBlock(),
TextStyle: Theme.Paragraph.Text,
WrapText: true,
}
}
func (self *Paragraph) Draw(buf *Buffer) {
self.Block.Draw(buf)
cells := ParseStyles(self.Text, self.TextStyle)
if self.WrapText {
cells = WrapCells(cells, uint(self.Inner.Dx()))
}
rows := SplitCells(cells, '\n')
for y, row := range rows {
if y+self.Inner.Min.Y >= self.Inner.Max.Y {
break
}
row = TrimCells(row, self.Inner.Dx())
for _, cx := range BuildCellWithXArray(row) {
x, cell := cx.X, cx.Cell
buf.SetCell(cell, image.Pt(x, y).Add(self.Inner.Min))
}
}
}

View File

@@ -1,149 +0,0 @@
package widgets
import (
"image"
"math"
. "github.com/gizak/termui/v3"
)
const (
piechartOffsetUp = -.5 * math.Pi // the northward angle
resolutionFactor = .0001 // circle resolution: precision vs. performance
fullCircle = 2.0 * math.Pi // the full circle angle
xStretch = 2.0 // horizontal adjustment
)
// PieChartLabel callback
type PieChartLabel func(dataIndex int, currentValue float64) string
type PieChart struct {
Block
Data []float64 // list of data items
Colors []Color // colors to by cycled through
LabelFormatter PieChartLabel // callback function for labels
AngleOffset float64 // which angle to start drawing at? (see piechartOffsetUp)
}
// NewPieChart Creates a new pie chart with reasonable defaults and no labels.
func NewPieChart() *PieChart {
return &PieChart{
Block: *NewBlock(),
Colors: Theme.PieChart.Slices,
AngleOffset: piechartOffsetUp,
}
}
func (self *PieChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
center := self.Inner.Min.Add(self.Inner.Size().Div(2))
radius := MinFloat64(float64(self.Inner.Dx()/2/xStretch), float64(self.Inner.Dy()/2))
// compute slice sizes
sum := SumFloat64Slice(self.Data)
sliceSizes := make([]float64, len(self.Data))
for i, v := range self.Data {
sliceSizes[i] = v / sum * fullCircle
}
borderCircle := &circle{center, radius}
middleCircle := circle{Point: center, radius: radius / 2.0}
// draw sectors
phi := self.AngleOffset
for i, size := range sliceSizes {
for j := 0.0; j < size; j += resolutionFactor {
borderPoint := borderCircle.at(phi + j)
line := line{P1: center, P2: borderPoint}
line.draw(NewCell(SHADED_BLOCKS[1], NewStyle(SelectColor(self.Colors, i))), buf)
}
phi += size
}
// draw labels
if self.LabelFormatter != nil {
phi = self.AngleOffset
for i, size := range sliceSizes {
labelPoint := middleCircle.at(phi + size/2.0)
if len(self.Data) == 1 {
labelPoint = center
}
buf.SetString(
self.LabelFormatter(i, self.Data[i]),
NewStyle(SelectColor(self.Colors, i)),
image.Pt(labelPoint.X, labelPoint.Y),
)
phi += size
}
}
}
type circle struct {
image.Point
radius float64
}
// computes the point at a given angle phi
func (self circle) at(phi float64) image.Point {
x := self.X + int(RoundFloat64(xStretch*self.radius*math.Cos(phi)))
y := self.Y + int(RoundFloat64(self.radius*math.Sin(phi)))
return image.Point{X: x, Y: y}
}
// computes the perimeter of a circle
func (self circle) perimeter() float64 {
return 2.0 * math.Pi * self.radius
}
// a line between two points
type line struct {
P1, P2 image.Point
}
// draws the line
func (self line) draw(cell Cell, buf *Buffer) {
isLeftOf := func(p1, p2 image.Point) bool {
return p1.X <= p2.X
}
isTopOf := func(p1, p2 image.Point) bool {
return p1.Y <= p2.Y
}
p1, p2 := self.P1, self.P2
buf.SetCell(NewCell('*', cell.Style), self.P2)
width, height := self.size()
if width > height { // paint left to right
if !isLeftOf(p1, p2) {
p1, p2 = p2, p1
}
flip := 1.0
if !isTopOf(p1, p2) {
flip = -1.0
}
for x := p1.X; x <= p2.X; x++ {
ratio := float64(height) / float64(width)
factor := float64(x - p1.X)
y := ratio * factor * flip
buf.SetCell(cell, image.Pt(x, int(RoundFloat64(y))+p1.Y))
}
} else { // paint top to bottom
if !isTopOf(p1, p2) {
p1, p2 = p2, p1
}
flip := 1.0
if !isLeftOf(p1, p2) {
flip = -1.0
}
for y := p1.Y; y <= p2.Y; y++ {
ratio := float64(width) / float64(height)
factor := float64(y - p1.Y)
x := ratio * factor * flip
buf.SetCell(cell, image.Pt(int(RoundFloat64(x))+p1.X, y))
}
}
}
// width and height of a line
func (self line) size() (w, h int) {
return AbsInt(self.P2.X - self.P1.X), AbsInt(self.P2.Y - self.P1.Y)
}

View File

@@ -1,227 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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"
. "github.com/gizak/termui/v3"
)
// Plot has two modes: line(default) and scatter.
// Plot also has two marker types: braille(default) and dot.
// A single braille character is a 2x4 grid of dots, so using braille
// gives 2x X resolution and 4x Y resolution over dot mode.
type Plot struct {
Block
Data [][]float64
DataLabels []string
MaxVal float64
LineColors []Color
AxesColor Color // TODO
ShowAxes bool
Marker PlotMarker
DotMarkerRune rune
PlotType PlotType
HorizontalScale int
DrawDirection DrawDirection // TODO
}
const (
xAxisLabelsHeight = 1
yAxisLabelsWidth = 4
xAxisLabelsGap = 2
yAxisLabelsGap = 1
)
type PlotType uint
const (
LineChart PlotType = iota
ScatterPlot
)
type PlotMarker uint
const (
MarkerBraille PlotMarker = iota
MarkerDot
)
type DrawDirection uint
const (
DrawLeft DrawDirection = iota
DrawRight
)
func NewPlot() *Plot {
return &Plot{
Block: *NewBlock(),
LineColors: Theme.Plot.Lines,
AxesColor: Theme.Plot.Axes,
Marker: MarkerBraille,
DotMarkerRune: DOT,
Data: [][]float64{},
HorizontalScale: 1,
DrawDirection: DrawRight,
ShowAxes: true,
PlotType: LineChart,
}
}
func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
canvas := NewCanvas()
canvas.Rectangle = drawArea
switch self.PlotType {
case ScatterPlot:
for i, line := range self.Data {
for j, val := range line {
height := int((val / maxVal) * float64(drawArea.Dy()-1))
canvas.SetPoint(
image.Pt(
(drawArea.Min.X+(j*self.HorizontalScale))*2,
(drawArea.Max.Y-height-1)*4,
),
SelectColor(self.LineColors, i),
)
}
}
case LineChart:
for i, line := range self.Data {
previousHeight := int((line[1] / maxVal) * float64(drawArea.Dy()-1))
for j, val := range line[1:] {
height := int((val / maxVal) * float64(drawArea.Dy()-1))
canvas.SetLine(
image.Pt(
(drawArea.Min.X+(j*self.HorizontalScale))*2,
(drawArea.Max.Y-previousHeight-1)*4,
),
image.Pt(
(drawArea.Min.X+((j+1)*self.HorizontalScale))*2,
(drawArea.Max.Y-height-1)*4,
),
SelectColor(self.LineColors, i),
)
previousHeight = height
}
}
}
canvas.Draw(buf)
}
func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
switch self.PlotType {
case ScatterPlot:
for i, line := range self.Data {
for j, val := range line {
height := int((val / maxVal) * float64(drawArea.Dy()-1))
point := image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height)
if point.In(drawArea) {
buf.SetCell(
NewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),
point,
)
}
}
}
case LineChart:
for i, line := range self.Data {
for j := 0; j < len(line) && j*self.HorizontalScale < drawArea.Dx(); j++ {
val := line[j]
height := int((val / maxVal) * float64(drawArea.Dy()-1))
buf.SetCell(
NewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),
image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),
)
}
}
}
}
func (self *Plot) plotAxes(buf *Buffer, maxVal float64) {
// draw origin cell
buf.SetCell(
NewCell(BOTTOM_LEFT, NewStyle(ColorWhite)),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-xAxisLabelsHeight-1),
)
// draw x axis line
for i := yAxisLabelsWidth + 1; i < self.Inner.Dx(); i++ {
buf.SetCell(
NewCell(HORIZONTAL_DASH, NewStyle(ColorWhite)),
image.Pt(i+self.Inner.Min.X, self.Inner.Max.Y-xAxisLabelsHeight-1),
)
}
// draw y axis line
for i := 0; i < self.Inner.Dy()-xAxisLabelsHeight-1; i++ {
buf.SetCell(
NewCell(VERTICAL_DASH, NewStyle(ColorWhite)),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, i+self.Inner.Min.Y),
)
}
// draw x axis labels
// draw 0
buf.SetString(
"0",
NewStyle(ColorWhite),
image.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-1),
)
// draw rest
for x := self.Inner.Min.X + yAxisLabelsWidth + (xAxisLabelsGap)*self.HorizontalScale + 1; x < self.Inner.Max.X-1; {
label := fmt.Sprintf(
"%d",
(x-(self.Inner.Min.X+yAxisLabelsWidth)-1)/(self.HorizontalScale)+1,
)
buf.SetString(
label,
NewStyle(ColorWhite),
image.Pt(x, self.Inner.Max.Y-1),
)
x += (len(label) + xAxisLabelsGap) * self.HorizontalScale
}
// draw y axis labels
verticalScale := maxVal / float64(self.Inner.Dy()-xAxisLabelsHeight-1)
for i := 0; i*(yAxisLabelsGap+1) < self.Inner.Dy()-1; i++ {
buf.SetString(
fmt.Sprintf("%.2f", float64(i)*verticalScale*(yAxisLabelsGap+1)),
NewStyle(ColorWhite),
image.Pt(self.Inner.Min.X, self.Inner.Max.Y-(i*(yAxisLabelsGap+1))-2),
)
}
}
func (self *Plot) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64From2dSlice(self.Data)
}
if self.ShowAxes {
self.plotAxes(buf, maxVal)
}
drawArea := self.Inner
if self.ShowAxes {
drawArea = image.Rect(
self.Inner.Min.X+yAxisLabelsWidth+1, self.Inner.Min.Y,
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
)
}
switch self.Marker {
case MarkerBraille:
self.renderBraille(buf, drawArea, maxVal)
case MarkerDot:
self.renderDot(buf, drawArea, maxVal)
}
}

View File

@@ -1,94 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
. "github.com/gizak/termui/v3"
)
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
type Sparkline struct {
Data []float64
Title string
TitleStyle Style
LineColor Color
MaxVal float64
MaxHeight int // TODO
}
// SparklineGroup is a renderable widget which groups together the given sparklines.
type SparklineGroup struct {
Block
Sparklines []*Sparkline
}
// NewSparkline returns a unrenderable single sparkline that needs to be added to a SparklineGroup
func NewSparkline() *Sparkline {
return &Sparkline{
TitleStyle: Theme.Sparkline.Title,
LineColor: Theme.Sparkline.Line,
}
}
func NewSparklineGroup(sls ...*Sparkline) *SparklineGroup {
return &SparklineGroup{
Block: *NewBlock(),
Sparklines: sls,
}
}
func (self *SparklineGroup) Draw(buf *Buffer) {
self.Block.Draw(buf)
sparklineHeight := self.Inner.Dy() / len(self.Sparklines)
for i, sl := range self.Sparklines {
heightOffset := (sparklineHeight * (i + 1))
barHeight := sparklineHeight
if i == len(self.Sparklines)-1 {
heightOffset = self.Inner.Dy()
barHeight = self.Inner.Dy() - (sparklineHeight * i)
}
if sl.Title != "" {
barHeight--
}
maxVal := sl.MaxVal
if maxVal == 0 {
maxVal, _ = GetMaxFloat64FromSlice(sl.Data)
}
// draw line
for j := 0; j < len(sl.Data) && j < self.Inner.Dx(); j++ {
data := sl.Data[j]
height := int((data / maxVal) * float64(barHeight))
sparkChar := BARS[len(BARS)-1]
for k := 0; k < height; k++ {
buf.SetCell(
NewCell(sparkChar, NewStyle(sl.LineColor)),
image.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-k),
)
}
if height == 0 {
sparkChar = BARS[1]
buf.SetCell(
NewCell(sparkChar, NewStyle(sl.LineColor)),
image.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset),
)
}
}
if sl.Title != "" {
// draw title
buf.SetString(
TrimString(sl.Title, self.Inner.Dx()),
sl.TitleStyle,
image.Pt(self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-barHeight),
)
}
}
}

View File

@@ -1,96 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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"
. "github.com/gizak/termui/v3"
)
type StackedBarChart struct {
Block
BarColors []Color
LabelStyles []Style
NumStyles []Style // only Fg and Modifier are used
NumFormatter func(float64) string
Data [][]float64
Labels []string
BarWidth int
BarGap int
MaxVal float64
}
func NewStackedBarChart() *StackedBarChart {
return &StackedBarChart{
Block: *NewBlock(),
BarColors: Theme.StackedBarChart.Bars,
LabelStyles: Theme.StackedBarChart.Labels,
NumStyles: Theme.StackedBarChart.Nums,
NumFormatter: func(n float64) string { return fmt.Sprint(n) },
BarGap: 1,
BarWidth: 3,
}
}
func (self *StackedBarChart) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
if maxVal == 0 {
for _, data := range self.Data {
maxVal = MaxFloat64(maxVal, SumFloat64Slice(data))
}
}
barXCoordinate := self.Inner.Min.X
for i, bar := range self.Data {
// draw stacked bars
stackedBarYCoordinate := 0
for j, data := range bar {
// draw each stacked bar
height := int((data / maxVal) * float64(self.Inner.Dy()-1))
for x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {
for y := (self.Inner.Max.Y - 2) - stackedBarYCoordinate; y > (self.Inner.Max.Y-2)-stackedBarYCoordinate-height; y-- {
c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, j)))
buf.SetCell(c, image.Pt(x, y))
}
}
// draw number
numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2)) - 1
buf.SetString(
self.NumFormatter(data),
NewStyle(
SelectStyle(self.NumStyles, j+1).Fg,
SelectColor(self.BarColors, j),
SelectStyle(self.NumStyles, j+1).Modifier,
),
image.Pt(numberXCoordinate, (self.Inner.Max.Y-2)-stackedBarYCoordinate),
)
stackedBarYCoordinate += height
}
// draw label
if i < len(self.Labels) {
labelXCoordinate := barXCoordinate + MaxInt(
int((float64(self.BarWidth)/2))-int((float64(rw.StringWidth(self.Labels[i]))/2)),
0,
)
buf.SetString(
TrimString(self.Labels[i], self.BarWidth),
SelectStyle(self.LabelStyles, i),
image.Pt(labelXCoordinate, self.Inner.Max.Y-1),
)
}
barXCoordinate += (self.BarWidth + self.BarGap)
}
}

View File

@@ -1,136 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
. "github.com/gizak/termui/v3"
)
/*Table is like:
┌ Awesome Table ───────────────────────────────────────────────┐
│ Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
│──────────────────────────────────────────────────────────────│
│ Some Item #1 | AAA | 123 | CCCCC | EEEEE | GGGGG | IIIII |
│──────────────────────────────────────────────────────────────│
│ Some Item #2 | BBB | 456 | DDDDD | FFFFF | HHHHH | JJJJJ |
└──────────────────────────────────────────────────────────────┘
*/
type Table struct {
Block
Rows [][]string
ColumnWidths []int
TextStyle Style
RowSeparator bool
TextAlignment Alignment
RowStyles map[int]Style
FillRow bool
// ColumnResizer is called on each Draw. Can be used for custom column sizing.
ColumnResizer func()
}
func NewTable() *Table {
return &Table{
Block: *NewBlock(),
TextStyle: Theme.Table.Text,
RowSeparator: true,
RowStyles: make(map[int]Style),
ColumnResizer: func() {},
}
}
func (self *Table) Draw(buf *Buffer) {
self.Block.Draw(buf)
self.ColumnResizer()
columnWidths := self.ColumnWidths
if len(columnWidths) == 0 {
columnCount := len(self.Rows[0])
columnWidth := self.Inner.Dx() / columnCount
for i := 0; i < columnCount; i++ {
columnWidths = append(columnWidths, columnWidth)
}
}
yCoordinate := self.Inner.Min.Y
// draw rows
for i := 0; i < len(self.Rows) && yCoordinate < self.Inner.Max.Y; i++ {
row := self.Rows[i]
colXCoordinate := self.Inner.Min.X
rowStyle := self.TextStyle
// get the row style if one exists
if style, ok := self.RowStyles[i]; ok {
rowStyle = style
}
if self.FillRow {
blankCell := NewCell(' ', rowStyle)
buf.Fill(blankCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))
}
// draw row cells
for j := 0; j < len(row); j++ {
col := ParseStyles(row[j], rowStyle)
// draw row cell
if len(col) > columnWidths[j] || self.TextAlignment == AlignLeft {
for _, cx := range BuildCellWithXArray(col) {
k, cell := cx.X, cx.Cell
if k == columnWidths[j] || colXCoordinate+k == self.Inner.Max.X {
cell.Rune = ELLIPSES
buf.SetCell(cell, image.Pt(colXCoordinate+k-1, yCoordinate))
break
} else {
buf.SetCell(cell, image.Pt(colXCoordinate+k, yCoordinate))
}
}
} else if self.TextAlignment == AlignCenter {
xCoordinateOffset := (columnWidths[j] - len(col)) / 2
stringXCoordinate := xCoordinateOffset + colXCoordinate
for _, cx := range BuildCellWithXArray(col) {
k, cell := cx.X, cx.Cell
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
}
} else if self.TextAlignment == AlignRight {
stringXCoordinate := MinInt(colXCoordinate+columnWidths[j], self.Inner.Max.X) - len(col)
for _, cx := range BuildCellWithXArray(col) {
k, cell := cx.X, cx.Cell
buf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))
}
}
colXCoordinate += columnWidths[j] + 1
}
// draw vertical separators
separatorStyle := self.Block.BorderStyle
separatorXCoordinate := self.Inner.Min.X
verticalCell := NewCell(VERTICAL_LINE, separatorStyle)
for i, width := range columnWidths {
if self.FillRow && i < len(columnWidths)-1 {
verticalCell.Style.Bg = rowStyle.Bg
} else {
verticalCell.Style.Bg = self.Block.BorderStyle.Bg
}
separatorXCoordinate += width
buf.SetCell(verticalCell, image.Pt(separatorXCoordinate, yCoordinate))
separatorXCoordinate++
}
yCoordinate++
// draw horizontal separator
horizontalCell := NewCell(HORIZONTAL_LINE, separatorStyle)
if self.RowSeparator && yCoordinate < self.Inner.Max.Y && i != len(self.Rows)-1 {
buf.Fill(horizontalCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))
yCoordinate++
}
}
}

View File

@@ -1,71 +0,0 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. 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 (
"image"
. "github.com/gizak/termui/v3"
)
// TabPane is a renderable widget which can be used to conditionally render certain tabs/views.
// TabPane shows a list of Tab names.
// The currently selected tab can be found through the `ActiveTabIndex` field.
type TabPane struct {
Block
TabNames []string
ActiveTabIndex int
ActiveTabStyle Style
InactiveTabStyle Style
}
func NewTabPane(names ...string) *TabPane {
return &TabPane{
Block: *NewBlock(),
TabNames: names,
ActiveTabStyle: Theme.Tab.Active,
InactiveTabStyle: Theme.Tab.Inactive,
}
}
func (self *TabPane) FocusLeft() {
if self.ActiveTabIndex > 0 {
self.ActiveTabIndex--
}
}
func (self *TabPane) FocusRight() {
if self.ActiveTabIndex < len(self.TabNames)-1 {
self.ActiveTabIndex++
}
}
func (self *TabPane) Draw(buf *Buffer) {
self.Block.Draw(buf)
xCoordinate := self.Inner.Min.X
for i, name := range self.TabNames {
ColorPair := self.InactiveTabStyle
if i == self.ActiveTabIndex {
ColorPair = self.ActiveTabStyle
}
buf.SetString(
TrimString(name, self.Inner.Max.X-xCoordinate),
ColorPair,
image.Pt(xCoordinate, self.Inner.Min.Y),
)
xCoordinate += 1 + len(name)
if i < len(self.TabNames)-1 && xCoordinate < self.Inner.Max.X {
buf.SetCell(
NewCell(VERTICAL_LINE, NewStyle(ColorWhite)),
image.Pt(xCoordinate, self.Inner.Min.Y),
)
}
xCoordinate += 2
}
}