poller implementation
This commit is contained in:
		
							parent
							
								
									34a3c0845d
								
							
						
					
					
						commit
						dd146c72f0
					
				
							
								
								
									
										20
									
								
								config.yml
								
								
								
								
							
							
						
						
									
										20
									
								
								config.yml
								
								
								
								
							|  | @ -4,21 +4,27 @@ line-charts: | |||
|     data: | ||||
|       - label: example.com | ||||
|         script: curl -o /dev/null -s -w '%{time_total}'  http://example.com | ||||
|         color: red | ||||
|       - label: google.com | ||||
|         script: curl -o /dev/null -s -w '%{time_total}'  http://google.com | ||||
|         color: yellow | ||||
|     refresh-rate-ms: 100 | ||||
|     refresh-rate-ms: 200 | ||||
|     time-scale-sec: 1 | ||||
|     style: dots/lines | ||||
|     position: | ||||
|       x: 1 | ||||
|       y: 2 | ||||
|       x: 0 | ||||
|       y: 0 | ||||
|     size: | ||||
|       x: 3 | ||||
|       y: 4 | ||||
|       x: 15 | ||||
|       y: 15 | ||||
|   - title: mongo-count | ||||
|     data: | ||||
|       - label: posts | ||||
|         script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" | grep 2 | ||||
|         color: red | ||||
|     refresh-rate-ms: 200 | ||||
|     time-scale-sec: 1 | ||||
|     position: | ||||
|       x: 15 | ||||
|       y: 0 | ||||
|     size: | ||||
|       x: 15 | ||||
|       y: 15 | ||||
|  | @ -1,13 +1,28 @@ | |||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	. "github.com/sqshq/vcmd/layout" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| ) | ||||
| 
 | ||||
| type Config struct { | ||||
| 	LineCharts []LineChartConfig `yaml:"line-charts"` | ||||
| 	LineChartConfigs []LineChartConfig `yaml:"line-charts"` | ||||
| } | ||||
| 
 | ||||
| type DataConfig struct { | ||||
| 	Script string `yaml:"script"` | ||||
| 	Label  string `yaml:"label"` | ||||
| } | ||||
| 
 | ||||
| type LineChartConfig struct { | ||||
| 	Title         string       `yaml:"title"` | ||||
| 	DataConfig    []DataConfig `yaml:"data"` | ||||
| 	Position      Position     `yaml:"position"` | ||||
| 	Size          Size         `yaml:"size"` | ||||
| 	RefreshRateMs int          `yaml:"refresh-rate-ms"` | ||||
| 	TimeScaleSec  int          `yaml:"time-scale-sec"` | ||||
| } | ||||
| 
 | ||||
| func Load(location string) *Config { | ||||
|  |  | |||
|  | @ -1,31 +0,0 @@ | |||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"os/exec" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type Data struct { | ||||
| 	Label  string `yaml:"label"` | ||||
| 	Color  string `yaml:"color"` | ||||
| 	Script string `yaml:"script"` | ||||
| } | ||||
| 
 | ||||
| func (d *Data) NextValue() (float64, error) { | ||||
| 
 | ||||
| 	output, err := exec.Command("sh", "-c", d.Script).Output() | ||||
| 	if err != nil { | ||||
| 		log.Printf("%s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	trimmedOutput := strings.TrimSpace(string(output)) | ||||
| 	floatValue, err := strconv.ParseFloat(trimmedOutput, 64) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 
 | ||||
| 	return floatValue, nil | ||||
| } | ||||
|  | @ -1,10 +0,0 @@ | |||
| package config | ||||
| 
 | ||||
| type LineChartConfig struct { | ||||
| 	Title         string   `yaml:"title"` | ||||
| 	Data          []Data   `yaml:"data"` | ||||
| 	Position      Position `yaml:"position"` | ||||
| 	Size          Size     `yaml:"size"` | ||||
| 	RefreshRateMs int      `yaml:"refresh-rate-ms"` | ||||
| 	Scale         string   `yaml:"scale"` | ||||
| } | ||||
|  | @ -1,6 +0,0 @@ | |||
| package config | ||||
| 
 | ||||
| type Position struct { | ||||
| 	X int `yaml:"x"` | ||||
| 	Y int `yaml:"y"` | ||||
| } | ||||
|  | @ -1,6 +0,0 @@ | |||
| package config | ||||
| 
 | ||||
| type Size struct { | ||||
| 	X int `yaml:"x"` | ||||
| 	Y int `yaml:"y"` | ||||
| } | ||||
|  | @ -0,0 +1,6 @@ | |||
| package data | ||||
| 
 | ||||
| type Consumer interface { | ||||
| 	ConsumeValue(value string, label string) | ||||
| 	ConsumeError(err error) | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package data | ||||
| 
 | ||||
| import ( | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Poller struct { | ||||
| 	consumer Consumer | ||||
| 	script   string | ||||
| 	label    string | ||||
| 	pause    bool | ||||
| } | ||||
| 
 | ||||
| func NewPoller(consumer Consumer, script string, label string, rateMs int) Poller { | ||||
| 
 | ||||
| 	ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond))) | ||||
| 	poller := Poller{consumer, script, label, false} | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				poller.poll() | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	return poller | ||||
| } | ||||
| 
 | ||||
| func (self *Poller) TogglePause() { | ||||
| 	self.pause = !self.pause | ||||
| } | ||||
| 
 | ||||
| func (self *Poller) poll() { | ||||
| 
 | ||||
| 	if self.pause { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	output, err := exec.Command("sh", "-c", self.script).Output() | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		self.consumer.ConsumeError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	value := strings.TrimSpace(string(output)) | ||||
| 	self.consumer.ConsumeValue(value, self.label) | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| package layout | ||||
| 
 | ||||
| import ( | ||||
| 	. "github.com/sqshq/termui" | ||||
| ) | ||||
| 
 | ||||
| type Item struct { | ||||
| 	Data     Drawable | ||||
| 	Position Position | ||||
| 	Size     Size | ||||
| } | ||||
| 
 | ||||
| type Position struct { | ||||
| 	X int `yaml:"x"` | ||||
| 	Y int `yaml:"y"` | ||||
| } | ||||
| 
 | ||||
| type Size struct { | ||||
| 	X int `yaml:"x"` | ||||
| 	Y int `yaml:"y"` | ||||
| } | ||||
| 
 | ||||
| func (self *Item) MoveItem(x, y int) { | ||||
| 	self.Position.X += x | ||||
| 	self.Position.Y += y | ||||
| } | ||||
| 
 | ||||
| func (self *Item) ResizeItem(x, y int) { | ||||
| 	self.Size.X += x | ||||
| 	self.Size.Y += y | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package layout | ||||
| 
 | ||||
| import ( | ||||
| 	. "github.com/sqshq/termui" | ||||
| ) | ||||
| 
 | ||||
| type Layout struct { | ||||
| 	Block | ||||
| 	items []Item | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	columnsCount = 30 | ||||
| 	rowsCount    = 30 | ||||
| ) | ||||
| 
 | ||||
| func NewLayout(width, height int) *Layout { | ||||
| 
 | ||||
| 	block := *NewBlock() | ||||
| 	block.SetRect(0, 0, width, height) | ||||
| 
 | ||||
| 	return &Layout{ | ||||
| 		Block: block, | ||||
| 		items: make([]Item, 0), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) AddItem(drawable Drawable, position Position, size Size) { | ||||
| 	self.items = append(self.items, Item{drawable, position, size}) | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) ChangeDimensions(width, height int) { | ||||
| 	self.SetRect(0, 0, width, height) | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) Draw(buf *Buffer) { | ||||
| 
 | ||||
| 	columnWidth := float64(self.GetRect().Dx()) / columnsCount | ||||
| 	rowHeight := float64(self.GetRect().Dy()) / rowsCount | ||||
| 
 | ||||
| 	for _, item := range self.items { | ||||
| 
 | ||||
| 		x1 := float64(item.Position.X) * columnWidth | ||||
| 		y1 := float64(item.Position.Y) * rowHeight | ||||
| 		x2 := x1 + float64(item.Size.X)*columnWidth | ||||
| 		y2 := y1 + float64(item.Size.Y)*rowHeight | ||||
| 
 | ||||
| 		item.Data.SetRect(int(x1), int(y1), int(x2), int(y2)) | ||||
| 		item.Data.Draw(buf) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										94
									
								
								main.go
								
								
								
								
							
							
						
						
									
										94
									
								
								main.go
								
								
								
								
							|  | @ -3,100 +3,80 @@ package main | |||
| import ( | ||||
| 	ui "github.com/sqshq/termui" | ||||
| 	"github.com/sqshq/vcmd/config" | ||||
| 	"github.com/sqshq/vcmd/data" | ||||
| 	"github.com/sqshq/vcmd/layout" | ||||
| 	"github.com/sqshq/vcmd/widgets" | ||||
| 	"log" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| /* | ||||
|  TODO validation | ||||
|  - title uniquness and mandatory within a single type of widget | ||||
|  - label uniqueness and mandatory (if > 1 data bullets) | ||||
| */ | ||||
| func main() { | ||||
| 
 | ||||
| 	// todo error handling + validation
 | ||||
| 	cfg := config.Load("/Users/sqshq/Go/src/github.com/sqshq/vcmd/config.yml") | ||||
| 
 | ||||
| 	for _, linechart := range cfg.LineCharts { | ||||
| 		for _, data := range linechart.Data { | ||||
| 			value, _ := data.NextValue() | ||||
| 			log.Printf("%s: %s - %v", linechart.Title, data.Label, value) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	p1 := widgets.NewTimePlot() | ||||
| 	p1.Title = " CURL LATENCY STATISTICS (sec) " | ||||
| 	p1.LineColors[0] = ui.ColorYellow | ||||
| 	p1.Marker = widgets.MarkerBraille | ||||
| 
 | ||||
| 	p2 := widgets.NewTimePlot() | ||||
| 	p2.Title = " CURL LATENCY STATISTICS 2 (sec) " | ||||
| 	p2.LineColors[0] = ui.ColorYellow | ||||
| 	p2.Marker = widgets.MarkerBraille | ||||
| 
 | ||||
| 	if err := ui.Init(); err != nil { | ||||
| 		//log.Fatalf("failed to initialize termui: %v", err)
 | ||||
| 		log.Fatalf("failed to initialize termui: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	defer ui.Close() | ||||
| 	uiEvents := ui.PollEvents() | ||||
| 	events := ui.PollEvents() | ||||
| 
 | ||||
| 	layout := widgets.NewLayout(ui.TerminalDimensions()) | ||||
| 	layout.AddItem(p1, 0, 0, 6, 6) | ||||
| 	layout.AddItem(p2, 0, 6, 6, 12) | ||||
| 	pollers := make([]data.Poller, 0) | ||||
| 	lout := layout.NewLayout(ui.TerminalDimensions()) | ||||
| 
 | ||||
| 	dataTicker := time.NewTicker(200 * time.Millisecond) | ||||
| 	uiTicker := time.NewTicker(50 * time.Millisecond) | ||||
| 	for _, chartConfig := range cfg.LineChartConfigs { | ||||
| 
 | ||||
| 	pause := false | ||||
| 		chart := widgets.NewTimePlot(chartConfig.Title) | ||||
| 		lout.AddItem(chart, chartConfig.Position, chartConfig.Size) | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-dataTicker.C: | ||||
| 				if !pause { | ||||
| 					value, err := cfg.LineCharts[0].Data[0].NextValue() | ||||
| 					if err != nil { | ||||
| 						log.Printf("failed to get value: %s", err) | ||||
| 						break | ||||
| 					} | ||||
| 					p1.AddValue(value) | ||||
| 					p2.AddValue(value) | ||||
| 		for _, chartData := range chartConfig.DataConfig { | ||||
| 			pollers = append(pollers, | ||||
| 				data.NewPoller(chart, chartData.Script, chartData.Label, chartConfig.RefreshRateMs)) | ||||
| 		} | ||||
| 	} | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	ticker := time.NewTicker(50 * time.Millisecond) | ||||
| 
 | ||||
| 	for { | ||||
| 		select { | ||||
| 		case e := <-uiEvents: | ||||
| 		case e := <-events: | ||||
| 			switch e.ID { | ||||
| 			case "q", "<C-c>": // press 'q' or 'C-c' to quit
 | ||||
| 			case "q", "<C-c>": | ||||
| 				return | ||||
| 			case "<Resize>": | ||||
| 				payload := e.Payload.(ui.Resize) | ||||
| 				layout.ChangeDimensions(payload.Width, payload.Height) | ||||
| 				lout.ChangeDimensions(payload.Width, payload.Height) | ||||
| 			case "<MouseLeft>": | ||||
| 				//payload := e.Payload.(ui.Mouse)
 | ||||
| 				//x, y := payload.X, payload.Y
 | ||||
| 				//log.Printf("x: %v, y: %v", x, y)
 | ||||
| 			} | ||||
| 			//case "<MouseLeft>":
 | ||||
| 			//	payload := e.Payload.(ui.Mouse)
 | ||||
| 			//	x, y := payload.X, payload.Y
 | ||||
| 			//	log.Printf("x: %v, y: %v", x, y)
 | ||||
| 			//}
 | ||||
| 			switch e.Type { | ||||
| 			case ui.KeyboardEvent: // handle all key presses
 | ||||
| 				//log.Printf("key: %v", e.ID)
 | ||||
| 			case ui.KeyboardEvent: | ||||
| 				switch e.ID { | ||||
| 				case "<Left>": | ||||
| 					layout.MoveItem(-1, 0) | ||||
| 					// here we are going to move selection (special type of layout item)
 | ||||
| 					//lout.GetItem("").MoveItem(-1, 0)
 | ||||
| 				case "<Right>": | ||||
| 					layout.MoveItem(1, 0) | ||||
| 					//lout.GetItem(0).MoveItem(1, 0)
 | ||||
| 				case "<Down>": | ||||
| 					layout.MoveItem(0, 1) | ||||
| 					//lout.GetItem(0).MoveItem(0, 1)
 | ||||
| 				case "<Up>": | ||||
| 					layout.MoveItem(0, -1) | ||||
| 					//lout.GetItem(0).MoveItem(0, -1)
 | ||||
| 				case "p": | ||||
| 					pause = !pause | ||||
| 					for _, poller := range pollers { | ||||
| 						poller.TogglePause() | ||||
| 					} | ||||
| 				} | ||||
| 		case <-uiTicker.C: | ||||
| 			if !pause { | ||||
| 				ui.Render(layout) | ||||
| 			} | ||||
| 		case <-ticker.C: | ||||
| 			ui.Render(lout) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -9,10 +9,10 @@ const ( | |||
| 	yBrailleMultiplier = 4 | ||||
| ) | ||||
| 
 | ||||
| func braille(point image.Point) image.Point { | ||||
| func braillePoint(point image.Point) image.Point { | ||||
| 	return image.Point{X: point.X * xBrailleMultiplier, Y: point.Y * yBrailleMultiplier} | ||||
| } | ||||
| 
 | ||||
| func deBraille(point image.Point) image.Point { | ||||
| func debraillePoint(point image.Point) image.Point { | ||||
| 	return image.Point{X: point.X / xBrailleMultiplier, Y: point.Y / yBrailleMultiplier} | ||||
| } | ||||
|  |  | |||
|  | @ -1,81 +0,0 @@ | |||
| package widgets | ||||
| 
 | ||||
| import ( | ||||
| 	. "github.com/sqshq/termui" | ||||
| ) | ||||
| 
 | ||||
| type Item struct { | ||||
| 	drawable    Drawable | ||||
| 	coordinates ItemCoordinates | ||||
| } | ||||
| 
 | ||||
| type ItemCoordinates struct { | ||||
| 	x1 int | ||||
| 	y1 int | ||||
| 	x2 int | ||||
| 	y2 int | ||||
| } | ||||
| 
 | ||||
| type LayoutDimensions struct { | ||||
| 	width  int | ||||
| 	height int | ||||
| } | ||||
| 
 | ||||
| type Layout struct { | ||||
| 	Block | ||||
| 	dimensions LayoutDimensions | ||||
| 	items      []Item | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	columnsCount = 12 | ||||
| 	rowsCount    = 12 | ||||
| ) | ||||
| 
 | ||||
| func NewLayout(width, height int) *Layout { | ||||
| 
 | ||||
| 	b := *NewBlock() | ||||
| 	b.SetRect(0, 0, width, height) | ||||
| 
 | ||||
| 	return &Layout{ | ||||
| 		Block:      b, | ||||
| 		dimensions: LayoutDimensions{width, height}, | ||||
| 		items:      make([]Item, 0), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) AddItem(drawable interface{}, x1, y1, x2, y2 int) { | ||||
| 	self.items = append(self.items, Item{ | ||||
| 		drawable:    drawable.(Drawable), | ||||
| 		coordinates: ItemCoordinates{x1, y1, x2, y2}, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) MoveItem(x, y int) { | ||||
| 	self.items[0].coordinates.x1 += x | ||||
| 	self.items[0].coordinates.y1 += y | ||||
| 	self.items[0].coordinates.x2 += x | ||||
| 	self.items[0].coordinates.y2 += y | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) ChangeDimensions(width, height int) { | ||||
| 	self.dimensions = LayoutDimensions{width, height} | ||||
| 	self.SetRect(0, 0, width, height) | ||||
| } | ||||
| 
 | ||||
| func (self *Layout) Draw(buf *Buffer) { | ||||
| 
 | ||||
| 	columnWidth := float64(self.dimensions.width) / columnsCount | ||||
| 	rowHeight := float64(self.dimensions.height) / rowsCount | ||||
| 
 | ||||
| 	for _, item := range self.items { | ||||
| 
 | ||||
| 		x1 := float64(item.coordinates.x1) * columnWidth | ||||
| 		y1 := float64(item.coordinates.y1) * rowHeight | ||||
| 		x2 := float64(item.coordinates.x2) * columnWidth | ||||
| 		y2 := float64(item.coordinates.y2) * rowHeight | ||||
| 
 | ||||
| 		item.drawable.SetRect(int(x1), int(y1), int(x2), int(y2)) | ||||
| 		item.drawable.Draw(buf) | ||||
| 	} | ||||
| } | ||||
|  | @ -3,21 +3,21 @@ package widgets | |||
| import ( | ||||
| 	"fmt" | ||||
| 	"image" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	. "github.com/sqshq/termui" | ||||
| ) | ||||
| 
 | ||||
| type TimePlot struct { | ||||
| type TimePlot struct { // TODO rename to linechart
 | ||||
| 	Block | ||||
| 	DataLabels        []string | ||||
| 	MaxValueTimePoint TimePoint | ||||
| 	LineColors        []Color | ||||
| 	ShowAxes          bool | ||||
| 	DotRune           rune | ||||
| 	HorizontalScale   int | ||||
| 	Marker            PlotMarker | ||||
| 
 | ||||
| 	timePoints []TimePoint | ||||
| 	dataMutex  *sync.Mutex | ||||
|  | @ -37,21 +37,15 @@ type TimePoint struct { | |||
| 	Time  time.Time | ||||
| } | ||||
| 
 | ||||
| type PlotMarker uint | ||||
| 
 | ||||
| const ( | ||||
| 	MarkerBraille PlotMarker = iota | ||||
| 	MarkerDot | ||||
| ) | ||||
| 
 | ||||
| func NewTimePlot() *TimePlot { | ||||
| func NewTimePlot(title string) *TimePlot { | ||||
| 	block := *NewBlock() | ||||
| 	block.Title = title | ||||
| 	//self.LineColors[0] = ui.ColorYellow
 | ||||
| 	return &TimePlot{ | ||||
| 		Block:           *NewBlock(), | ||||
| 		Block:           block, | ||||
| 		LineColors:      Theme.Plot.Lines, | ||||
| 		DotRune:         DOT, | ||||
| 		HorizontalScale: 1, | ||||
| 		ShowAxes:        true, | ||||
| 		Marker:          MarkerBraille, | ||||
| 		timePoints:      make([]TimePoint, 0), | ||||
| 		dataMutex:       &sync.Mutex{}, | ||||
| 	} | ||||
|  | @ -93,10 +87,7 @@ func (self *TimePlot) Draw(buf *Buffer) { | |||
| 	self.dataMutex.Lock() | ||||
| 	self.Block.Draw(buf) | ||||
| 	self.grid = self.newPlotGrid() | ||||
| 
 | ||||
| 	if self.ShowAxes { | ||||
| 	self.plotAxes(buf) | ||||
| 	} | ||||
| 
 | ||||
| 	drawArea := image.Rect( | ||||
| 		self.Inner.Min.X+yAxisLabelsWidth+1, self.Inner.Min.Y, | ||||
|  | @ -107,13 +98,23 @@ func (self *TimePlot) Draw(buf *Buffer) { | |||
| 	self.dataMutex.Unlock() | ||||
| } | ||||
| 
 | ||||
| func (self *TimePlot) AddValue(value float64) { | ||||
| func (self *TimePlot) ConsumeValue(value string, label string) { | ||||
| 
 | ||||
| 	float, err := strconv.ParseFloat(value, 64) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Expected float number, but got %v", value) // TODO visual notification
 | ||||
| 	} | ||||
| 
 | ||||
| 	self.dataMutex.Lock() | ||||
| 	self.timePoints = append(self.timePoints, TimePoint{Value: value, Time: time.Now()}) | ||||
| 	self.timePoints = append(self.timePoints, TimePoint{Value: float, Time: time.Now()}) | ||||
| 	self.trimOutOfRangeValues() | ||||
| 	self.dataMutex.Unlock() | ||||
| } | ||||
| 
 | ||||
| func (self *TimePlot) ConsumeError(err error) { | ||||
| 	// TODO visual notification
 | ||||
| } | ||||
| 
 | ||||
| func (self *TimePlot) trimOutOfRangeValues() { | ||||
| 
 | ||||
| 	lastOutOfRangeValueIndex := -1 | ||||
|  | @ -175,8 +176,8 @@ func (self *TimePlot) renderBraille(buf *Buffer, drawArea image.Rectangle) { | |||
| 		//)
 | ||||
| 
 | ||||
| 		canvas.Line( | ||||
| 			braille(previousPoint), | ||||
| 			braille(currentPoint), | ||||
| 			braillePoint(previousPoint), | ||||
| 			braillePoint(currentPoint), | ||||
| 			SelectColor(self.LineColors, 0), //i
 | ||||
| 		) | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue