summaryrefslogtreecommitdiffstats
path: root/pkg/jp/linechart.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/jp/linechart.go')
-rw-r--r--pkg/jp/linechart.go205
1 files changed, 205 insertions, 0 deletions
diff --git a/pkg/jp/linechart.go b/pkg/jp/linechart.go
new file mode 100644
index 0000000..1849a1e
--- /dev/null
+++ b/pkg/jp/linechart.go
@@ -0,0 +1,205 @@
+package jp
+
+import (
+ "math"
+ "strings"
+
+ "github.com/sgreben/jp/pkg/jp/primitives"
+)
+
+// Adapted from https://github.com/buger/goterm under the MIT License.
+
+/*
+MIT License
+
+Copyright (c) 2016 Leonid Bugaev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+// LineChart is a line chart
+type LineChart struct {
+ Buffer []string
+ Width int
+ Height int
+ Symbol string
+ chartHeight int
+ chartWidth int
+ paddingX int
+ paddingY int
+
+ data *DataTable
+}
+
+// NewLineChart returns a new line chart
+func NewLineChart(width, height int) *LineChart {
+ chart := new(LineChart)
+ chart.Width = width
+ chart.Height = height
+ chart.Buffer = primitives.Buffer(width * height)
+ chart.Symbol = primitives.PointSymbolDefault
+ chart.paddingY = 2
+ return chart
+}
+
+func (c *LineChart) drawAxes(maxX, minX, maxY, minY float64, index int) {
+ c.drawLine(c.paddingX-1, 1, c.Width-1, 1, primitives.HorizontalLine)
+ c.drawLine(c.paddingX-1, 1, c.paddingX-1, c.Height-1, primitives.VerticalLine)
+ c.set(c.paddingX-1, c.paddingY-1, primitives.CornerBottomLeft)
+
+ left := 0 // c.Width - c.paddingX + 1
+ c.writeText(primitives.Ff(minY), left, 1)
+
+ c.writeText(primitives.Ff(maxY), left, c.Height-1)
+
+ c.writeText(primitives.Ff(minX), c.paddingX, 0)
+
+ xCol := c.data.Columns[0]
+ c.writeText(c.data.Columns[0], c.Width/2-len(xCol)/2, 1)
+
+ if len(c.data.Columns) < 3 {
+ col := c.data.Columns[index]
+
+ for idx, char := range strings.Split(col, "") {
+ startFrom := c.Height/2 + len(col)/2 - idx
+
+ c.writeText(char, c.paddingX-1, startFrom)
+ }
+ }
+
+ c.writeText(primitives.Ff(maxX), c.Width-len(primitives.Ff(maxX)), 0)
+
+}
+
+func (c *LineChart) writeText(text string, x, y int) {
+ coord := y*c.Width + x
+
+ for idx, char := range strings.Split(text, "") {
+ c.Buffer[coord+idx] = char
+ }
+}
+
+// Draw implements Chart
+func (c *LineChart) Draw(data *DataTable) (out string) {
+ var scaleY, scaleX float64
+
+ c.data = data
+
+ charts := len(data.Columns) - 1
+
+ prevPoint := [2]int{-1, -1}
+
+ maxX, minX, maxY, minY := getBoundaryValues(data, -1)
+
+ c.paddingX = int(math.Max(float64(len(primitives.Ff(minY))), float64(len(primitives.Ff(maxY))))) + 1
+
+ c.chartHeight = c.Height - c.paddingY
+ c.chartWidth = c.Width - c.paddingX - 1
+
+ scaleX = float64(c.chartWidth) / (maxX - minX)
+
+ scaleY = float64(c.chartHeight) / (maxY - minY)
+
+ for i := 1; i < charts+1; i++ {
+ symbol := c.Symbol
+
+ chartData := getChartData(data, i)
+
+ for _, point := range chartData {
+ x := int((point[0]-minX)*scaleX) + c.paddingX
+ y := int((point[1])*scaleY) + c.paddingY
+ y = int((point[1]-minY)*scaleY) + c.paddingY
+
+ if prevPoint[0] == -1 {
+ prevPoint[0] = x
+ prevPoint[1] = y
+ }
+
+ if prevPoint[0] <= x {
+ c.drawLine(prevPoint[0], prevPoint[1], x, y, symbol)
+ }
+
+ prevPoint[0] = x
+ prevPoint[1] = y
+ }
+
+ c.drawAxes(maxX, minX, maxY, minY, i)
+ }
+
+ for row := c.Height - 1; row >= 0; row-- {
+ out += strings.Join(c.Buffer[row*c.Width:(row+1)*c.Width], "") + "\n"
+ }
+
+ return
+}
+
+func (c *LineChart) set(x, y int, s string) {
+ coord := y*c.Width + x
+ if coord > 0 && coord < len(c.Buffer) {
+ c.Buffer[coord] = s
+ }
+}
+
+func (c *LineChart) drawLine(x0, y0, x1, y1 int, symbol string) {
+ primitives.DrawLine(x0, y0, x1, y1, func(x, y int) { c.set(x, y, symbol) })
+}
+
+func getBoundaryValues(data *DataTable, index int) (maxX, minX, maxY, minY float64) {
+ maxX = math.Inf(-1)
+ minX = math.Inf(1)
+ maxY = math.Inf(-1)
+ minY = math.Inf(1)
+
+ for _, r := range data.Rows {
+ maxX = math.Max(maxX, r[0])
+ minX = math.Min(minX, r[0])
+
+ for idx, c := range r {
+ if idx > 0 {
+ if index == -1 || index == idx {
+ maxY = math.Max(maxY, c)
+ minY = math.Min(minY, c)
+ }
+ }
+ }
+ }
+
+ if maxY > 0 {
+ maxY = maxY * 1.1
+ } else {
+ maxY = maxY * 0.9
+ }
+
+ if minY > 0 {
+ minY = minY * 0.9
+ } else {
+ minY = minY * 1.1
+ }
+
+ return
+}
+
+func getChartData(data *DataTable, index int) (out [][]float64) {
+ for _, r := range data.Rows {
+ out = append(out, []float64{r[0], r[index]})
+ }
+
+ return
+}