s/LineChart/Plot and add ScatterPlot mode to it

This commit is contained in:
Caleb Bassi 2019-01-24 07:56:22 -08:00
parent eeb94f8e14
commit 8c72139752
7 changed files with 198 additions and 125 deletions

View File

@ -1,5 +1,12 @@
Feel free to search/open an issue if something is missing or confusing from the changelog, since many things have been in flux.
## 2019/01/24
Breaking changes:
- Change LineChart to Plot
- Added ScatterPlot mode which plots points instead of lines between points
## 2019/01/23
Non breaking changes:

View File

@ -28,10 +28,10 @@ If you upgrade and notice something is missing or don't like a change, revert th
- [BarChart](./_examples/barchart.go)
- [Canvas](./_examples/canvas.go)
- [Gauge](./_examples/gauge.go)
- [LineChart](./_examples/linechart.go)
- [List](./_examples/list.go)
- [Paragraph](./_examples/paragraph.go)
- [PieChart](./_examples/piechart.go)
- [Plot](./_examples/plot.go)
- [Sparkline](./_examples/sparkline.go)
- [StackedBarChart](./_examples/stacked_barchart.go)
- [Table](./_examples/table.go)

View File

@ -1,72 +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.
// +build ignore
package main
import (
"log"
"math"
ui "github.com/gizak/termui"
"github.com/gizak/termui/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
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
}()
lc0 := widgets.NewLineChart()
lc0.Title = "braille-mode Line Chart"
lc0.Data = sinData
lc0.SetRect(0, 0, 50, 15)
lc0.AxesColor = ui.ColorWhite
lc0.LineColors[0] = ui.ColorGreen
lc1 := widgets.NewLineChart()
lc1.Title = "custom Line Chart"
lc1.LineType = widgets.DotLine
lc1.Data = [][]float64{[]float64{1, 2, 3, 4, 5}}
lc1.SetRect(50, 0, 75, 10)
lc1.DotChar = '+'
lc1.AxesColor = ui.ColorWhite
lc1.LineColors[0] = ui.ColorYellow
lc1.DrawDirection = widgets.DrawLeft
lc2 := widgets.NewLineChart()
lc2.Title = "dot-mode Line Chart"
lc2.LineType = widgets.DotLine
lc2.Data = make([][]float64, 2)
lc2.Data[0] = sinData[0][4:]
lc2.Data[1] = sinData[1][4:]
lc2.SetRect(0, 15, 50, 30)
lc2.AxesColor = ui.ColorWhite
lc2.LineColors[0] = ui.ColorCyan
ui.Render(lc0, lc1, lc2)
uiEvents := ui.PollEvents()
for {
e := <-uiEvents
switch e.ID {
case "q", "<C-c>":
return
}
}
}

84
_examples/plot.go Normal file
View File

@ -0,0 +1,84 @@
// 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.
// +build ignore
package main
import (
"log"
"math"
ui "github.com/gizak/termui"
"github.com/gizak/termui/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
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
}()
p0 := widgets.NewPlot()
p0.Title = "braille-mode Line Chart"
p0.Data = sinData
p0.SetRect(0, 0, 50, 15)
p0.AxesColor = ui.ColorWhite
p0.LineColors[0] = ui.ColorGreen
p1 := widgets.NewPlot()
p1.Title = "custom Line Chart"
p1.Marker = widgets.MarkerDot
p1.Data = [][]float64{[]float64{1, 2, 3, 4, 5}}
p1.SetRect(50, 0, 75, 10)
p1.DotRune = '+'
p1.AxesColor = ui.ColorWhite
p1.LineColors[0] = ui.ColorYellow
p1.DrawDirection = widgets.DrawLeft
p2 := widgets.NewPlot()
p2.Title = "dot-mode Scatter Plot"
p2.Marker = widgets.MarkerDot
p2.Data = make([][]float64, 2)
p2.Data[0] = []float64{1, 2, 3, 4, 5}
p2.Data[1] = sinData[1][4:]
p2.SetRect(0, 15, 50, 30)
p2.AxesColor = ui.ColorWhite
p2.LineColors[0] = ui.ColorCyan
p2.Type = widgets.ScatterPlot
p3 := widgets.NewPlot()
p3.Title = "dot-mode Scatter Plot"
p3.Data = make([][]float64, 2)
p3.Data[0] = []float64{1, 2, 3, 4, 5}
p3.Data[1] = sinData[1][4:]
p3.SetRect(45, 15, 80, 30)
p3.AxesColor = ui.ColorWhite
p3.LineColors[0] = ui.ColorCyan
p3.Marker = widgets.MarkerBraille
p3.Type = widgets.ScatterPlot
ui.Render(p0, p1, p2, p3)
uiEvents := ui.PollEvents()
for {
e := <-uiEvents
switch e.ID {
case "q", "<C-c>":
return
}
}
}

View File

@ -54,6 +54,14 @@ func (self *Canvas) Line(p0, p1 image.Point, color Color) {
}
}
func (self *Canvas) Point(p image.Point, color Color) {
point := image.Pt(p.X/2, p.Y/4)
self.CellMap[point] = Cell{
self.CellMap[point].Rune | BRAILLE[p.X%4][p.Y%2],
NewStyle(color),
}
}
func (self *Canvas) Draw(buf *Buffer) {
for point, cell := range self.CellMap {
if point.In(self.Rectangle) {

View File

@ -31,7 +31,7 @@ type RootTheme struct {
BarChart BarChartTheme
Gauge GaugeTheme
LineChart LineChartTheme
Plot PlotTheme
List ListTheme
Paragraph ParagraphTheme
PieChart PieChartTheme
@ -57,7 +57,7 @@ type GaugeTheme struct {
Label Style
}
type LineChartTheme struct {
type PlotTheme struct {
Lines []Color
Axes Color
}
@ -136,7 +136,7 @@ var Theme = RootTheme{
Line: ColorWhite,
},
LineChart: LineChartTheme{
Plot: PlotTheme{
Lines: StandardColors,
Axes: ColorWhite,
},

View File

@ -11,20 +11,25 @@ import (
. "github.com/gizak/termui"
)
// LineChart has two modes: braille(default) and dot.
// 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 LineChart struct {
type Plot struct {
Block
Data [][]float64
DataLabels []string
Data [][]float64
DataLabels []string
MaxVal float64
LineColors []Color
AxesColor Color // TODO
ShowAxes bool
Marker PlotMarker
DotRune rune
Type PlotType
HorizontalScale int
LineType LineType
DotChar rune
LineColors []Color
AxesColor Color // TODO
MaxVal float64
ShowAxes bool
DrawDirection DrawDirection // TODO
}
@ -35,11 +40,18 @@ const (
yAxisLabelsGap = 1
)
type LineType int
type PlotType uint
const (
BrailleLine LineType = iota
DotLine
LineChart PlotType = iota
ScatterPlot
)
type PlotMarker uint
const (
MarkerBraille PlotMarker = iota
MarkerDot
)
type DrawDirection uint
@ -49,60 +61,93 @@ const (
DrawRight
)
func NewLineChart() *LineChart {
return &LineChart{
func NewPlot() *Plot {
return &Plot{
Block: *NewBlock(),
LineColors: Theme.LineChart.Lines,
AxesColor: Theme.LineChart.Axes,
LineType: BrailleLine,
DotChar: DOT,
LineColors: Theme.Plot.Lines,
AxesColor: Theme.Plot.Axes,
Marker: MarkerBraille,
DotRune: DOT,
Data: [][]float64{},
HorizontalScale: 1,
DrawDirection: DrawRight,
ShowAxes: true,
Type: LineChart,
}
}
func (self *LineChart) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
canvas := NewCanvas()
canvas.Rectangle = drawArea
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.Line(
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
switch self.Type {
case ScatterPlot:
for i, line := range self.Data {
for j, val := range line {
height := int((val / maxVal) * float64(drawArea.Dy()-1))
canvas.Point(
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.Line(
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 *LineChart) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
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.DotChar, NewStyle(SelectColor(self.LineColors, i))),
image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),
)
func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {
switch self.Type {
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.DotRune, 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.DotRune, NewStyle(SelectColor(self.LineColors, i))),
image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),
)
}
}
}
}
func (self *LineChart) plotAxes(buf *Buffer, maxVal float64) {
func (self *Plot) plotAxes(buf *Buffer, maxVal float64) {
// draw origin cell
buf.SetCell(
NewCell(BOTTOM_LEFT, NewStyle(ColorWhite)),
@ -153,7 +198,7 @@ func (self *LineChart) plotAxes(buf *Buffer, maxVal float64) {
}
}
func (self *LineChart) Draw(buf *Buffer) {
func (self *Plot) Draw(buf *Buffer) {
self.Block.Draw(buf)
maxVal := self.MaxVal
@ -173,9 +218,10 @@ func (self *LineChart) Draw(buf *Buffer) {
)
}
if self.LineType == BrailleLine {
switch self.Marker {
case MarkerBraille:
self.renderBraille(buf, drawArea, maxVal)
} else {
case MarkerDot:
self.renderDot(buf, drawArea, maxVal)
}
}