summaryrefslogtreecommitdiffstats
path: root/pkg/plot/linechart.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/plot/linechart.go')
-rw-r--r--pkg/plot/linechart.go107
1 files changed, 107 insertions, 0 deletions
diff --git a/pkg/plot/linechart.go b/pkg/plot/linechart.go
new file mode 100644
index 0000000..f3c47f1
--- /dev/null
+++ b/pkg/plot/linechart.go
@@ -0,0 +1,107 @@
+package plot
+
+import (
+ "bytes"
+ "math"
+
+ "github.com/sgreben/jp/pkg/draw"
+)
+
+// LineChart is a line chart
+type LineChart struct{ draw.Canvas }
+
+// NewLineChart returns a new line chart
+func NewLineChart(canvas draw.Canvas) *LineChart { return &LineChart{canvas} }
+
+func (c *LineChart) drawAxes(paddingX, paddingY int, minX, maxX, minY, maxY float64) {
+ buffer := c.GetBuffer()
+ // X axis
+ buffer.SetRow(1, paddingX, buffer.Width, draw.HorizontalLine)
+ // Y axis
+ buffer.SetColumn(1, buffer.Height, paddingX, draw.VerticalLine)
+ // Corner
+ buffer.Set(1, paddingX, draw.CornerBottomLeft)
+ // Labels
+ buffer.WriteRight(1, 1, Ff(minY))
+ buffer.WriteLeft(buffer.Height-1, paddingX, Ff(maxY))
+ buffer.WriteRight(0, paddingX, Ff(minX))
+ buffer.WriteLeft(0, buffer.Width, Ff(maxX))
+}
+
+// Draw implements Chart
+func (c *LineChart) Draw(data *DataTable) string {
+ var scaleY, scaleX float64
+ var prevX, prevY int
+
+ minX, maxX, minY, maxY := minMax(data)
+ minLabelWidth := len(Ff(minY))
+ maxLabelWidth := len(Ff(maxY))
+
+ paddingX := minLabelWidth + 1
+ paddingY := 2
+ if minLabelWidth < maxLabelWidth {
+ paddingX = maxLabelWidth + 1
+ }
+ chartWidth := c.Size().Width - (paddingX+1)*c.RuneSize().Width
+ chartHeight := c.Size().Height - paddingY*c.RuneSize().Height
+ scaleX = float64(chartWidth) / (maxX - minX)
+ scaleY = float64(chartHeight) / (maxY - minY)
+
+ first := true
+ for _, point := range data.Rows {
+ if len(point) < 2 {
+ continue
+ }
+ x := int((point[0]-minX)*scaleX + float64((paddingX+1)*c.RuneSize().Width))
+ y := int((point[1]-minY)*scaleY + float64(paddingY*c.RuneSize().Height))
+
+ if first {
+ first = false
+ prevX = x
+ prevY = y
+ }
+
+ if prevX <= x {
+ c.DrawLine(prevY, prevX, y, x)
+ }
+
+ prevX = x
+ prevY = y
+ }
+ c.drawAxes(paddingX, paddingY, minX, maxX, minY, maxY)
+
+ b := bytes.NewBuffer(nil)
+ c.GetBuffer().Render(b)
+ return b.String()
+}
+
+func roundDownToPercentOfRange(x, d float64) float64 {
+ return math.Floor((x*(100-math.Copysign(5, x)))/d) * d / 100
+}
+
+func roundUpToPercentOfRange(x, d float64) float64 {
+ return math.Ceil((x*105)/d) * d / 100
+}
+
+func minMax(data *DataTable) (minX, maxX, minY, maxY float64) {
+ minX, minY = math.Inf(1), math.Inf(1)
+ maxX, maxY = math.Inf(-1), math.Inf(-1)
+
+ for _, r := range data.Rows {
+ if len(r) < 2 {
+ continue
+ }
+ maxX = math.Max(maxX, r[0])
+ minX = math.Min(minX, r[0])
+ maxY = math.Max(maxY, r[1])
+ minY = math.Min(minY, r[1])
+ }
+
+ xRange := maxX - minX
+ yRange := maxY - minY
+ minX = roundDownToPercentOfRange(minX, xRange)
+ minY = roundDownToPercentOfRange(minY, yRange)
+ maxX = roundUpToPercentOfRange(maxX, xRange)
+ maxY = roundUpToPercentOfRange(maxY, yRange)
+ return
+}