mirror of
https://github.com/navidys/tvxwidgets.git
synced 2025-05-09 19:29:29 +08:00
new feature - plot widget (linechart, scatter)
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
This commit is contained in:
parent
f9a1efe3f5
commit
b1e54eab00
21
README.md
21
README.md
@ -5,10 +5,20 @@
|
||||
[](https://goreportcard.com/report/github.com/navidys/tvxwidgets)
|
||||
|
||||
tvxwidgets provides extra widgets for [tview](https://github.com/rivo/tview).
|
||||
`NOTE:` The project is at its early stages and under development, feel free to contribute and report bugs.
|
||||
|
||||

|
||||
|
||||
## Widgets
|
||||
|
||||
* [bar chart](./demos/barchart/)
|
||||
* [activity mode gauge](./demos/gauge_am/)
|
||||
* [percentage mode gauge](./demos/gauge_pm/)
|
||||
* [utilisation mode gauge](./demos/gauge_um/)
|
||||
* [message dialog (info and error)](./demos/dialog/)
|
||||
* [spinner](./demos/spinner/)
|
||||
* [plot (linechart, scatter)](./demos/plot/)
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
@ -48,12 +58,3 @@ func main() {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Widgets
|
||||
|
||||
* bar chart
|
||||
* activity mode gauge
|
||||
* percentage mode gauge
|
||||
* utilisation mode gauge
|
||||
* message dialog (info and error)
|
||||
* spinner
|
||||
|
BIN
demo.gif
BIN
demo.gif
Binary file not shown.
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 277 KiB |
@ -2,6 +2,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
@ -14,40 +15,34 @@ func main() {
|
||||
app := tview.NewApplication()
|
||||
|
||||
// spinners
|
||||
spinners := [][]*tvxwidgets.Spinner{
|
||||
{
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsCircling),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsUpDown),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBounce),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerLine),
|
||||
},
|
||||
{
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleQuarters),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerSquareCorners),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleHalves),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCorners),
|
||||
},
|
||||
{
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerArrows),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerHamburger),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStack),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStar),
|
||||
},
|
||||
{
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowHorizontal),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowVertical),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBoxBounce),
|
||||
tvxwidgets.NewSpinner().SetCustomStyle([]rune{'🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'}),
|
||||
},
|
||||
spinners := []*tvxwidgets.Spinner{
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsCircling),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsUpDown),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBounce),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerLine),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleQuarters),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerSquareCorners),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleHalves),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCorners),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerArrows),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerHamburger),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStack),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStar),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowHorizontal),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowVertical),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBoxBounce),
|
||||
tvxwidgets.NewSpinner().SetCustomStyle([]rune{'🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'}),
|
||||
}
|
||||
|
||||
spinnerGrid := tview.NewGrid()
|
||||
spinnerGrid.SetBorder(true).SetTitle("Spinners")
|
||||
spinnerRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
spinnerRow.SetBorder(true).SetTitle("Spinners")
|
||||
|
||||
for rowIdx, row := range spinners {
|
||||
for colIdx, spinner := range row {
|
||||
spinnerGrid.AddItem(spinner, rowIdx, colIdx, 1, 1, 1, 1, false)
|
||||
}
|
||||
for _, spinner := range spinners {
|
||||
spinnerRow.AddItem(spinner, 0, 1, false)
|
||||
}
|
||||
|
||||
// bar graph
|
||||
@ -96,18 +91,87 @@ func main() {
|
||||
utilFlex.SetTitle("utilisation mode gauge")
|
||||
utilFlex.SetBorder(true)
|
||||
|
||||
sinData := func() [][]float64 {
|
||||
n := 220
|
||||
data := make([][]float64, 2)
|
||||
data[0] = make([]float64, n)
|
||||
data[1] = make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
data[0][i] = 1 + math.Sin(float64(i)/5)
|
||||
data[1][i] = 1 + math.Cos(float64(i)/5)
|
||||
}
|
||||
return data
|
||||
}()
|
||||
|
||||
bmLineChart := tvxwidgets.NewPlot()
|
||||
bmLineChart.SetBorder(true)
|
||||
bmLineChart.SetTitle("line chart (braille mode)")
|
||||
bmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
tcell.ColorGreen,
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
dmLineChart := tvxwidgets.NewPlot()
|
||||
dmLineChart.SetBorder(true)
|
||||
dmLineChart.SetTitle("line chart (dot mode)")
|
||||
dmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorDarkOrange,
|
||||
})
|
||||
dmLineChart.SetAxesLabelColor(tcell.ColorGold)
|
||||
dmLineChart.SetAxesColor(tcell.ColorGold)
|
||||
dmLineChart.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmLineChart.SetDotMarkerRune('\u25c9')
|
||||
|
||||
sampleData1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
sampleData2 := []float64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||
dotChartData := [][]float64{sampleData1}
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData1[:5]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2[5:]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData1[:7]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2[3:]...)
|
||||
|
||||
dmLineChart.SetData(dotChartData)
|
||||
|
||||
firstCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
firstCol.AddItem(barGraph, 11, 0, false)
|
||||
firstCol.AddItem(bmLineChart, 15, 0, false)
|
||||
firstCol.AddItem(spinnerRow, 3, 0, false)
|
||||
|
||||
secondCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
secondCol.AddItem(amGauge, 3, 0, false)
|
||||
secondCol.AddItem(pmGauge, 3, 0, false)
|
||||
secondCol.AddItem(utilFlex, 5, 0, false)
|
||||
secondCol.AddItem(dmLineChart, 15, 0, false)
|
||||
|
||||
screenLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
screenLayout.AddItem(firstCol, 50, 0, false)
|
||||
screenLayout.AddItem(secondCol, 50, 0, false)
|
||||
screenLayout.AddItem(spinnerGrid, 15, 0, false)
|
||||
|
||||
moveDotChartData := func() {
|
||||
newData := append(dotChartData[0], dotChartData[0][0])
|
||||
dotChartData[0] = newData[1:]
|
||||
}
|
||||
|
||||
moveSinData := func(data [][]float64) [][]float64 {
|
||||
n := 220
|
||||
newData := make([][]float64, 2)
|
||||
newData[0] = make([]float64, n)
|
||||
newData[1] = make([]float64, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if i+1 < len(data[0]) {
|
||||
newData[0][i] = data[0][i+1]
|
||||
}
|
||||
if i+1 < len(data[1]) {
|
||||
newData[1][i] = data[1][i+1]
|
||||
}
|
||||
}
|
||||
|
||||
return newData
|
||||
}
|
||||
|
||||
updateSpinner := func() {
|
||||
spinnerTick := time.NewTicker(100 * time.Millisecond)
|
||||
@ -115,10 +179,8 @@ func main() {
|
||||
select {
|
||||
case <-spinnerTick.C:
|
||||
// update spinners
|
||||
for _, row := range spinners {
|
||||
for _, spinner := range row {
|
||||
spinner.Pulse()
|
||||
}
|
||||
for _, spinner := range spinners {
|
||||
spinner.Pulse()
|
||||
}
|
||||
// update gauge
|
||||
amGauge.Pulse()
|
||||
@ -158,6 +220,14 @@ func main() {
|
||||
swapGauge.SetValue(float64(randomNum))
|
||||
randomNum = rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
barGraph.SetBarValue("eth3", randomNum)
|
||||
|
||||
// move line charts
|
||||
sinData = moveSinData(sinData)
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
moveDotChartData()
|
||||
dmLineChart.SetData(dotChartData)
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
|
1
demos/plot/README.md
Normal file
1
demos/plot/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
103
demos/plot/main.go
Normal file
103
demos/plot/main.go
Normal file
@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
sinData := func() [][]float64 {
|
||||
n := 220
|
||||
data := make([][]float64, 2)
|
||||
data[0] = make([]float64, n)
|
||||
data[1] = make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
data[0][i] = 1 + math.Sin(float64(i)/5)
|
||||
data[1][i] = 1 + math.Cos(float64(i)/5)
|
||||
}
|
||||
return data
|
||||
}()
|
||||
|
||||
bmLineChart := tvxwidgets.NewPlot()
|
||||
bmLineChart.SetBorder(true)
|
||||
bmLineChart.SetTitle("line chart (braille mode)")
|
||||
bmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
tcell.ColorGreen,
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
dmLineChart := tvxwidgets.NewPlot()
|
||||
dmLineChart.SetBorder(true)
|
||||
dmLineChart.SetTitle("line chart (dot mode)")
|
||||
dmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorDarkOrange,
|
||||
})
|
||||
dmLineChart.SetAxesLabelColor(tcell.ColorGold)
|
||||
dmLineChart.SetAxesColor(tcell.ColorGold)
|
||||
dmLineChart.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmLineChart.SetDotMarkerRune('\u25c9')
|
||||
|
||||
sampleData1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
sampleData2 := []float64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||
|
||||
dotModeChartData := [][]float64{sampleData1}
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:5]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[5:]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:7]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[3:]...)
|
||||
dmLineChart.SetData(dotModeChartData)
|
||||
|
||||
scatterPlotData := make([][]float64, 2)
|
||||
scatterPlotData[0] = []float64{1, 2, 3, 4, 5}
|
||||
scatterPlotData[1] = sinData[1][4:]
|
||||
dmScatterPlot := tvxwidgets.NewPlot()
|
||||
|
||||
dmScatterPlot.SetBorder(true)
|
||||
dmScatterPlot.SetTitle("scatter plot (dot mode)")
|
||||
dmScatterPlot.SetLineColor([]tcell.Color{
|
||||
tcell.ColorMediumSlateBlue,
|
||||
tcell.ColorLightSkyBlue,
|
||||
})
|
||||
dmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
|
||||
dmScatterPlot.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmScatterPlot.SetData(scatterPlotData)
|
||||
|
||||
bmScatterPlot := tvxwidgets.NewPlot()
|
||||
bmScatterPlot.SetBorder(true)
|
||||
bmScatterPlot.SetTitle("scatter plot (braille mode)")
|
||||
bmScatterPlot.SetLineColor([]tcell.Color{
|
||||
tcell.ColorGold,
|
||||
tcell.ColorLightSkyBlue,
|
||||
})
|
||||
bmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
|
||||
bmScatterPlot.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmScatterPlot.SetData(scatterPlotData)
|
||||
|
||||
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
firstRow.AddItem(dmLineChart, 0, 1, false)
|
||||
firstRow.AddItem(bmLineChart, 0, 1, false)
|
||||
firstRow.SetRect(0, 0, 100, 15)
|
||||
|
||||
secondRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
secondRow.AddItem(dmScatterPlot, 0, 1, false)
|
||||
secondRow.AddItem(bmScatterPlot, 0, 1, false)
|
||||
secondRow.SetRect(0, 0, 100, 15)
|
||||
|
||||
layout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
layout.AddItem(firstRow, 0, 1, false)
|
||||
layout.AddItem(secondRow, 0, 1, false)
|
||||
layout.SetRect(0, 0, 100, 30)
|
||||
|
||||
if err := app.SetRoot(layout, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
BIN
demos/plot/screenshot.png
Normal file
BIN
demos/plot/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
368
plot.go
Normal file
368
plot.go
Normal file
@ -0,0 +1,368 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// Marker represents plot drawing marker (brialle or dot).
|
||||
type Marker uint
|
||||
|
||||
const (
|
||||
// plot marker.
|
||||
PlotMarkerBraille Marker = iota
|
||||
PlotMarkerDot
|
||||
)
|
||||
|
||||
// PlotType represents plot type (line chart or scatter).
|
||||
type PlotType uint
|
||||
|
||||
const (
|
||||
PlotTypeLineChart PlotType = iota
|
||||
PlotTypeScatter
|
||||
)
|
||||
|
||||
const (
|
||||
plotHorizontalScale = 1
|
||||
plotXAxisLabelsHeight = 1
|
||||
plotYAxisLabelsWidth = 4
|
||||
plotXAxisLabelsGap = 2
|
||||
plotYAxisLabelsGap = 1
|
||||
)
|
||||
|
||||
type brailleCell struct {
|
||||
cRune rune
|
||||
color tcell.Color
|
||||
}
|
||||
|
||||
// Plot represents a plot primitive used for different charts.
|
||||
type Plot struct {
|
||||
*tview.Box
|
||||
data [][]float64
|
||||
marker Marker
|
||||
ptype PlotType
|
||||
dotMarkerRune rune
|
||||
lineColors []tcell.Color
|
||||
axesColor tcell.Color
|
||||
axesLabelColor tcell.Color
|
||||
drawAxes bool
|
||||
brailleCellMap map[image.Point]brailleCell
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewPlot returns a plot widget.
|
||||
func NewPlot() *Plot {
|
||||
return &Plot{
|
||||
Box: tview.NewBox(),
|
||||
marker: PlotMarkerDot,
|
||||
ptype: PlotTypeLineChart,
|
||||
dotMarkerRune: dotRune,
|
||||
axesColor: tcell.ColorDimGray,
|
||||
axesLabelColor: tcell.ColorDimGray,
|
||||
drawAxes: true,
|
||||
lineColors: []tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (plot *Plot) Draw(screen tcell.Screen) {
|
||||
plot.Box.DrawForSubclass(screen, plot)
|
||||
|
||||
switch plot.marker {
|
||||
case PlotMarkerDot:
|
||||
plot.darwDotMarkerToScreen(screen)
|
||||
case PlotMarkerBraille:
|
||||
plot.drawBrailleMarkerToScreen(screen)
|
||||
}
|
||||
|
||||
plot.drawAxesToScreen(screen)
|
||||
}
|
||||
|
||||
// SetRect sets rect for this primitive.
|
||||
func (plot *Plot) SetRect(x, y, width, height int) {
|
||||
plot.Box.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
// SetLineColor sets chart line color.
|
||||
func (plot *Plot) SetLineColor(color []tcell.Color) {
|
||||
plot.lineColors = color
|
||||
}
|
||||
|
||||
// SetAxesColor sets axes x and y lines color.
|
||||
func (plot *Plot) SetAxesColor(color tcell.Color) {
|
||||
plot.axesColor = color
|
||||
}
|
||||
|
||||
// SetAxesLabelColor sets axes x and y label color.
|
||||
func (plot *Plot) SetAxesLabelColor(color tcell.Color) {
|
||||
plot.axesLabelColor = color
|
||||
}
|
||||
|
||||
// SetDrawAxes set true in order to draw axes to screen.
|
||||
func (plot *Plot) SetDrawAxes(draw bool) {
|
||||
plot.drawAxes = draw
|
||||
}
|
||||
|
||||
// SetMarker sets marker type braille or dot mode.
|
||||
func (plot *Plot) SetMarker(marker Marker) {
|
||||
plot.marker = marker
|
||||
}
|
||||
|
||||
// SetPlotType sets plot type (linechart or scatter).
|
||||
func (plot *Plot) SetPlotType(ptype PlotType) {
|
||||
plot.ptype = ptype
|
||||
}
|
||||
|
||||
// SetData sets plot data.
|
||||
func (plot *Plot) SetData(data [][]float64) {
|
||||
plot.mu.Lock()
|
||||
defer plot.mu.Unlock()
|
||||
|
||||
plot.brailleCellMap = make(map[image.Point]brailleCell)
|
||||
plot.data = data
|
||||
}
|
||||
|
||||
// SetDotMarkerRune sets dot marker rune.
|
||||
func (plot *Plot) SetDotMarkerRune(r rune) {
|
||||
plot.dotMarkerRune = r
|
||||
}
|
||||
|
||||
func (plot *Plot) getChartAreaRect() (int, int, int, int) {
|
||||
x, y, width, height := plot.Box.GetInnerRect()
|
||||
|
||||
if plot.drawAxes {
|
||||
x = x + plotYAxisLabelsWidth + 1
|
||||
width = width - plotYAxisLabelsWidth - 1
|
||||
height = height - plotXAxisLabelsHeight - 1
|
||||
} else {
|
||||
x++
|
||||
width--
|
||||
}
|
||||
|
||||
return x, y, width, height
|
||||
}
|
||||
|
||||
func (plot *Plot) getData() [][]float64 {
|
||||
plot.mu.Lock()
|
||||
data := plot.data
|
||||
plot.mu.Unlock()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (plot *Plot) drawAxesToScreen(screen tcell.Screen) {
|
||||
if !plot.drawAxes {
|
||||
return
|
||||
}
|
||||
|
||||
x, y, width, height := plot.Box.GetInnerRect()
|
||||
|
||||
axesStyle := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.axesColor)
|
||||
|
||||
// draw Y axis lin
|
||||
drawLine(screen,
|
||||
x+plotYAxisLabelsWidth,
|
||||
y,
|
||||
height-plotXAxisLabelsHeight-1,
|
||||
verticalLine, axesStyle)
|
||||
|
||||
// draw X axis line
|
||||
drawLine(screen,
|
||||
x+plotYAxisLabelsWidth+1,
|
||||
y+height-plotXAxisLabelsHeight-1,
|
||||
width-plotYAxisLabelsWidth-1,
|
||||
horizontalLine, axesStyle)
|
||||
|
||||
tview.PrintJoinedSemigraphics(screen,
|
||||
x+plotYAxisLabelsWidth,
|
||||
y+height-plotXAxisLabelsHeight-1,
|
||||
tview.BoxDrawingsLightUpAndRight, axesStyle)
|
||||
|
||||
// draw x axis labels
|
||||
tview.Print(screen, "0",
|
||||
x+plotYAxisLabelsWidth,
|
||||
y+height-plotXAxisLabelsHeight,
|
||||
1,
|
||||
tview.AlignLeft, plot.axesLabelColor)
|
||||
|
||||
for labelX := x + plotYAxisLabelsWidth +
|
||||
(plotXAxisLabelsGap)*plotHorizontalScale + 1; labelX < x+width-1; {
|
||||
label := fmt.Sprintf(
|
||||
"%d",
|
||||
(labelX-(x+plotYAxisLabelsWidth)-1)/(plotHorizontalScale)+1,
|
||||
)
|
||||
|
||||
tview.Print(screen, label, labelX, y+height-plotXAxisLabelsHeight, width, tview.AlignLeft, plot.axesLabelColor)
|
||||
|
||||
labelX += (len(label) + plotXAxisLabelsGap) * plotHorizontalScale
|
||||
}
|
||||
|
||||
// draw Y axis labels
|
||||
maxVal := getMaxFloat64From2dSlice(plot.getData())
|
||||
verticalScale := maxVal / float64(height-plotXAxisLabelsHeight-1)
|
||||
|
||||
for i := 0; i*(plotYAxisLabelsGap+1) < height-1; i++ {
|
||||
label := fmt.Sprintf("%.2f", float64(i)*verticalScale*(plotYAxisLabelsGap+1))
|
||||
tview.Print(screen,
|
||||
label,
|
||||
x,
|
||||
y+height-(i*(plotYAxisLabelsGap+1))-2, // nolint:gomnd
|
||||
plotYAxisLabelsWidth,
|
||||
tview.AlignLeft, plot.axesLabelColor)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocognit,cyclop
|
||||
func (plot *Plot) darwDotMarkerToScreen(screen tcell.Screen) {
|
||||
x, y, width, height := plot.getChartAreaRect()
|
||||
chartData := plot.getData()
|
||||
maxVal := getMaxFloat64From2dSlice(chartData)
|
||||
|
||||
switch plot.ptype {
|
||||
case PlotTypeLineChart:
|
||||
for i, line := range chartData {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.lineColors[i])
|
||||
|
||||
for j := 0; j < len(line) && j*plotHorizontalScale < width; j++ {
|
||||
val := line[j]
|
||||
lheight := int((val / maxVal) * float64(height-1))
|
||||
|
||||
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
|
||||
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case PlotTypeScatter:
|
||||
for i, line := range chartData {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.lineColors[i])
|
||||
|
||||
for j, val := range line {
|
||||
lheight := int((val / maxVal) * float64(height-1))
|
||||
|
||||
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
|
||||
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
|
||||
var cellMaxY int
|
||||
|
||||
x, y, width, height := plot.getChartAreaRect()
|
||||
|
||||
plot.calcBrailleLines()
|
||||
|
||||
for point := range plot.getBrailleCells() {
|
||||
if point.Y > cellMaxY {
|
||||
cellMaxY = point.Y
|
||||
}
|
||||
}
|
||||
|
||||
diffMAxY := y + height - cellMaxY - 1
|
||||
|
||||
// print to screen
|
||||
for point, cell := range plot.getBrailleCells() {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(cell.color)
|
||||
if point.X < x+width && point.Y+diffMAxY < y+height {
|
||||
tview.PrintJoinedSemigraphics(screen, point.X, point.Y+diffMAxY, cell.cRune, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) calcBrailleLines() {
|
||||
x, y, _, height := plot.getChartAreaRect()
|
||||
chartData := plot.getData()
|
||||
maxVal := getMaxFloat64From2dSlice(chartData)
|
||||
|
||||
for i, line := range chartData {
|
||||
if len(line) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
previousHeight := int((line[1] / maxVal) * float64(height-1))
|
||||
|
||||
for j, val := range line[1:] {
|
||||
lheight := int((val / maxVal) * float64(height-1))
|
||||
|
||||
plot.setBrailleLine(
|
||||
image.Pt(
|
||||
(x+(j*plotHorizontalScale))*2, // nolint:gomnd
|
||||
(y+height-previousHeight-1)*4, // nolint:gomnd
|
||||
),
|
||||
image.Pt(
|
||||
(x+((j+1)*plotHorizontalScale))*2, // nolint:gomnd
|
||||
(y+height-lheight-1)*4, // nolint:gomnd
|
||||
),
|
||||
plot.lineColors[i],
|
||||
)
|
||||
|
||||
previousHeight = lheight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) setBraillePoint(p image.Point, color tcell.Color) {
|
||||
point := image.Pt(p.X/2, p.Y/4) // nolint:gomnd
|
||||
plot.brailleCellMap[point] = brailleCell{
|
||||
plot.brailleCellMap[point].cRune | brailleRune[p.Y%4][p.X%2],
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) setBrailleLine(p0, p1 image.Point, color tcell.Color) {
|
||||
for _, p := range plot.brailleLine(p0, p1) {
|
||||
plot.setBraillePoint(p, color)
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) getBrailleCells() map[image.Point]brailleCell {
|
||||
cellMap := make(map[image.Point]brailleCell)
|
||||
for point, cvCell := range plot.brailleCellMap {
|
||||
cellMap[point] = brailleCell{cvCell.cRune + brailleOffsetRune, cvCell.color}
|
||||
}
|
||||
|
||||
return cellMap
|
||||
}
|
||||
|
||||
func (plot *Plot) brailleLine(p0, p1 image.Point) []image.Point {
|
||||
points := []image.Point{}
|
||||
leftPoint, rightPoint := p0, p1
|
||||
|
||||
if leftPoint.X > rightPoint.X {
|
||||
leftPoint, rightPoint = rightPoint, leftPoint
|
||||
}
|
||||
|
||||
xDistance := absInt(leftPoint.X - rightPoint.X)
|
||||
yDistance := absInt(leftPoint.Y - rightPoint.Y)
|
||||
slope := float64(yDistance) / float64(xDistance)
|
||||
slopeSign := 1
|
||||
|
||||
if rightPoint.Y < leftPoint.Y {
|
||||
slopeSign = -1
|
||||
}
|
||||
|
||||
targetYCoordinate := float64(leftPoint.Y)
|
||||
currentYCoordinate := leftPoint.Y
|
||||
|
||||
for i := leftPoint.X; i < rightPoint.X; i++ {
|
||||
points = append(points, image.Pt(i, currentYCoordinate))
|
||||
targetYCoordinate += (slope * float64(slopeSign))
|
||||
|
||||
for currentYCoordinate != int(targetYCoordinate) {
|
||||
points = append(points, image.Pt(i, currentYCoordinate))
|
||||
|
||||
currentYCoordinate += slopeSign
|
||||
}
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
58
utils.go
58
utils.go
@ -4,6 +4,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type drawLineMode int
|
||||
|
||||
const (
|
||||
horizontalLine drawLineMode = iota
|
||||
verticalLine
|
||||
)
|
||||
|
||||
const (
|
||||
@ -22,9 +30,18 @@ const (
|
||||
// dialog padding.
|
||||
dialogPadding = 2
|
||||
// empty space parts.
|
||||
emptySpaceParts = 2
|
||||
emptySpaceParts = 2
|
||||
brailleOffsetRune = '\u2800'
|
||||
dotRune = '\u25CF'
|
||||
)
|
||||
|
||||
var brailleRune = [4][2]rune{ // nolint:gochecknoglobals
|
||||
{'\u0001', '\u0008'},
|
||||
{'\u0002', '\u0010'},
|
||||
{'\u0004', '\u0020'},
|
||||
{'\u0040', '\u0080'},
|
||||
}
|
||||
|
||||
// getColorName returns convert tcell color to its name.
|
||||
func getColorName(color tcell.Color) string {
|
||||
for name, c := range tcell.ColorNames {
|
||||
@ -47,3 +64,42 @@ func getMessageWidth(message string) int {
|
||||
|
||||
return messageWidth
|
||||
}
|
||||
|
||||
// returns max values in 2D float64 slices.
|
||||
func getMaxFloat64From2dSlice(slices [][]float64) float64 {
|
||||
if len(slices) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var max float64
|
||||
|
||||
for _, slice := range slices {
|
||||
for _, val := range slice {
|
||||
if val > max {
|
||||
max = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
func absInt(x int) int {
|
||||
if x >= 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
return -x
|
||||
}
|
||||
|
||||
func drawLine(screen tcell.Screen, x int, y int, length int, mode drawLineMode, style tcell.Style) {
|
||||
if mode == horizontalLine {
|
||||
for i := 0; i < length; i++ {
|
||||
tview.PrintJoinedSemigraphics(screen, x+i, y, tview.BoxDrawingsLightTripleDashHorizontal, style)
|
||||
}
|
||||
} else if mode == verticalLine {
|
||||
for i := 0; i < length; i++ {
|
||||
tview.PrintJoinedSemigraphics(screen, x, y+i, tview.BoxDrawingsLightTripleDashVertical, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user