snapshot
This commit is contained in:
parent
3cb33d48d3
commit
26eaa1d898
76 changed files with 1076 additions and 1717 deletions
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,15 +43,15 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
Show: true,
|
Show: true,
|
||||||
},
|
},
|
||||||
Ticks: []chart.Tick{
|
Ticks: []chart.Tick{
|
||||||
{-4.0, "-4"},
|
{Value: -4.0, Label: "-4"},
|
||||||
{-2.0, "-2"},
|
{Value: -2.0, Label: "-2"},
|
||||||
{0, "0"},
|
{Value: 0, Label: "0"},
|
||||||
{2.0, "2"},
|
{Value: 2.0, Label: "2"},
|
||||||
{4.0, "4"},
|
{Value: 4.0, Label: "4"},
|
||||||
{6.0, "6"},
|
{Value: 6.0, Label: "6"},
|
||||||
{8.0, "8"},
|
{Value: 8.0, Label: "8"},
|
||||||
{10.0, "10"},
|
{Value: 10.0, Label: "10"},
|
||||||
{12.0, "12"},
|
{Value: 12.0, Label: "12"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
UseBaseValue: true,
|
UseBaseValue: true,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func random(min, max float64) float64 {
|
func random(min, max float64) float64 {
|
||||||
|
|
|
@ -2,9 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wcharczuk/go-chart"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: Additional examples on how to add Stylesheets are in the custom_stylesheets example
|
// Note: Additional examples on how to add Stylesheets are in the custom_stylesheets example
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hashworks/go-chart"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
const style = "svg .background { fill: white; }" +
|
const style = "svg .background { fill: white; }" +
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -20,12 +20,12 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
Max: 4.0,
|
Max: 4.0,
|
||||||
},
|
},
|
||||||
Ticks: []chart.Tick{
|
Ticks: []chart.Tick{
|
||||||
{0.0, "0.00"},
|
{Value: 0.0, Label: "0.00"},
|
||||||
{2.0, "2.00"},
|
{Value: 2.0, Label: "2.00"},
|
||||||
{4.0, "4.00"},
|
{Value: 4.0, Label: "4.00"},
|
||||||
{6.0, "6.00"},
|
{Value: 6.0, Label: "6.00"},
|
||||||
{8.0, "Eight"},
|
{Value: 8.0, Label: "Eight"},
|
||||||
{10.0, "Ten"},
|
{Value: 10.0, Label: "Ten"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseInt(str string) int {
|
func parseInt(str string) int {
|
||||||
|
@ -24,7 +23,7 @@ func parseFloat64(str string) float64 {
|
||||||
func readData() ([]time.Time, []float64) {
|
func readData() ([]time.Time, []float64) {
|
||||||
var xvalues []time.Time
|
var xvalues []time.Time
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
err := util.File.ReadByLines("requests.csv", func(line string) error {
|
err := chart.ReadLines("requests.csv", func(line string) error {
|
||||||
parts := strings.Split(line, ",")
|
parts := strings.Split(line, ",")
|
||||||
year := parseInt(parts[0])
|
year := parseInt(parts[0])
|
||||||
month := parseInt(parts[1])
|
month := parseInt(parts[1])
|
||||||
|
@ -43,12 +42,12 @@ func readData() ([]time.Time, []float64) {
|
||||||
|
|
||||||
func releases() []chart.GridLine {
|
func releases() []chart.GridLine {
|
||||||
return []chart.GridLine{
|
return []chart.GridLine{
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 15, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 2, 15, 30, 0, 0, time.UTC))},
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: util.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
|
{Value: chart.TimeToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
|
|
||||||
chart "github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +16,7 @@ var ts *chart.TimeSeries
|
||||||
func addData(t time.Time, e time.Duration) {
|
func addData(t time.Time, e time.Duration) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
ts.XValues = append(ts.XValues, t)
|
ts.XValues = append(ts.XValues, t)
|
||||||
ts.YValues = append(ts.YValues, util.Time.Millis(e))
|
ts.YValues = append(ts.YValues, chart.TimeMillis(e))
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
|
@ -62,10 +60,10 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
||||||
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
||||||
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
||||||
box.Top = util.Math.MinInt(box.Top, ab.Top)
|
box.Top = MinInt(box.Top, ab.Top)
|
||||||
box.Left = util.Math.MinInt(box.Left, ab.Left)
|
box.Left = MinInt(box.Left, ab.Left)
|
||||||
box.Right = util.Math.MaxInt(box.Right, ab.Right)
|
box.Right = MaxInt(box.Right, ab.Right)
|
||||||
box.Bottom = util.Math.MaxInt(box.Bottom, ab.Bottom)
|
box.Bottom = MaxInt(box.Bottom, ab.Bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box
|
return box
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BarChart is a chart that draws bars on a range.
|
// BarChart is a chart that draws bars on a range.
|
||||||
|
@ -410,7 +409,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
||||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||||
|
|
||||||
xaxisHeight = util.Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +476,7 @@ func (bc BarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getTitleFontSize() float64 {
|
func (bc BarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := util.Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
effectiveDimension := MinInt(bc.GetWidth(), bc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
|
@ -22,7 +20,7 @@ type BollingerBandsSeries struct {
|
||||||
K float64
|
K float64
|
||||||
InnerSeries ValuesProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
valueBuffer *seq.Buffer
|
valueBuffer *ValueBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
|
@ -72,7 +70,7 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer == nil || index == 0 {
|
if bbs.valueBuffer == nil || index == 0 {
|
||||||
bbs.valueBuffer = seq.NewBufferWithCapacity(bbs.GetPeriod())
|
bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod())
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||||
bbs.valueBuffer.Dequeue()
|
bbs.valueBuffer.Dequeue()
|
||||||
|
@ -81,8 +79,8 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
bbs.valueBuffer.Enqueue(py)
|
bbs.valueBuffer.Enqueue(py)
|
||||||
x = px
|
x = px
|
||||||
|
|
||||||
ay := seq.New(bbs.valueBuffer).Average()
|
ay := NewSeq(bbs.valueBuffer).Average()
|
||||||
std := seq.New(bbs.valueBuffer).StdDev()
|
std := NewSeq(bbs.valueBuffer).StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
@ -101,15 +99,15 @@ func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) {
|
||||||
startAt = 0
|
startAt = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
vb := seq.NewBufferWithCapacity(period)
|
vb := NewValueBufferWithCapacity(period)
|
||||||
for index := startAt; index < seriesLength; index++ {
|
for index := startAt; index < seriesLength; index++ {
|
||||||
xn, yn := bbs.InnerSeries.GetValues(index)
|
xn, yn := bbs.InnerSeries.GetValues(index)
|
||||||
vb.Enqueue(yn)
|
vb.Enqueue(yn)
|
||||||
x = xn
|
x = xn
|
||||||
}
|
}
|
||||||
|
|
||||||
ay := seq.Seq{Provider: vb}.Average()
|
ay := Seq{vb}.Average()
|
||||||
std := seq.Seq{Provider: vb}.StdDev()
|
std := Seq{vb}.StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
|
|
@ -6,15 +6,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBollingerBandSeries(t *testing.T) {
|
func TestBollingerBandSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: seq.Range(1.0, 100.0),
|
X: SeqRange(1.0, 100.0),
|
||||||
Y: seq.RandomValuesWithMax(100, 1024),
|
Y: SeqRandomValuesWithMax(100, 1024),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
@ -38,8 +37,8 @@ func TestBollingerBandLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: seq.Range(1.0, 100.0),
|
X: SeqRange(1.0, 100.0),
|
||||||
Y: seq.Range(1.0, 100.0),
|
Y: SeqRange(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
|
56
box.go
56
box.go
|
@ -3,8 +3,6 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -91,12 +89,12 @@ func (b Box) GetBottom(defaults ...int) int {
|
||||||
|
|
||||||
// Width returns the width
|
// Width returns the width
|
||||||
func (b Box) Width() int {
|
func (b Box) Width() int {
|
||||||
return util.Math.AbsInt(b.Right - b.Left)
|
return AbsInt(b.Right - b.Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height
|
// Height returns the height
|
||||||
func (b Box) Height() int {
|
func (b Box) Height() int {
|
||||||
return util.Math.AbsInt(b.Bottom - b.Top)
|
return AbsInt(b.Bottom - b.Top)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center returns the center of the box
|
// Center returns the center of the box
|
||||||
|
@ -148,10 +146,10 @@ func (b Box) Equals(other Box) bool {
|
||||||
// Grow grows a box based on another box.
|
// Grow grows a box based on another box.
|
||||||
func (b Box) Grow(other Box) Box {
|
func (b Box) Grow(other Box) Box {
|
||||||
return Box{
|
return Box{
|
||||||
Top: util.Math.MinInt(b.Top, other.Top),
|
Top: MinInt(b.Top, other.Top),
|
||||||
Left: util.Math.MinInt(b.Left, other.Left),
|
Left: MinInt(b.Left, other.Left),
|
||||||
Right: util.Math.MaxInt(b.Right, other.Right),
|
Right: MaxInt(b.Right, other.Right),
|
||||||
Bottom: util.Math.MaxInt(b.Bottom, other.Bottom),
|
Bottom: MaxInt(b.Bottom, other.Bottom),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,10 +220,10 @@ func (b Box) Fit(other Box) Box {
|
||||||
func (b Box) Constrain(other Box) Box {
|
func (b Box) Constrain(other Box) Box {
|
||||||
newBox := b.Clone()
|
newBox := b.Clone()
|
||||||
|
|
||||||
newBox.Top = util.Math.MaxInt(newBox.Top, other.Top)
|
newBox.Top = MaxInt(newBox.Top, other.Top)
|
||||||
newBox.Left = util.Math.MaxInt(newBox.Left, other.Left)
|
newBox.Left = MaxInt(newBox.Left, other.Left)
|
||||||
newBox.Right = util.Math.MinInt(newBox.Right, other.Right)
|
newBox.Right = MinInt(newBox.Right, other.Right)
|
||||||
newBox.Bottom = util.Math.MinInt(newBox.Bottom, other.Bottom)
|
newBox.Bottom = MinInt(newBox.Bottom, other.Bottom)
|
||||||
|
|
||||||
return newBox
|
return newBox
|
||||||
}
|
}
|
||||||
|
@ -264,36 +262,36 @@ type BoxCorners struct {
|
||||||
// Box return the BoxCorners as a regular box.
|
// Box return the BoxCorners as a regular box.
|
||||||
func (bc BoxCorners) Box() Box {
|
func (bc BoxCorners) Box() Box {
|
||||||
return Box{
|
return Box{
|
||||||
Top: util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
Top: MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
||||||
Left: util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
Left: MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
||||||
Right: util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
Right: MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
||||||
Bottom: util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
|
Bottom: MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width
|
// Width returns the width
|
||||||
func (bc BoxCorners) Width() int {
|
func (bc BoxCorners) Width() int {
|
||||||
minLeft := util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
minLeft := MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
maxRight := util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X)
|
maxRight := MaxInt(bc.TopRight.X, bc.BottomRight.X)
|
||||||
return maxRight - minLeft
|
return maxRight - minLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height
|
// Height returns the height
|
||||||
func (bc BoxCorners) Height() int {
|
func (bc BoxCorners) Height() int {
|
||||||
minTop := util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
minTop := MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
maxBottom := util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
maxBottom := MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||||
return maxBottom - minTop
|
return maxBottom - minTop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center returns the center of the box
|
// Center returns the center of the box
|
||||||
func (bc BoxCorners) Center() (x, y int) {
|
func (bc BoxCorners) Center() (x, y int) {
|
||||||
|
|
||||||
left := util.Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
left := MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
right := util.Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
right := MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
||||||
x = ((right - left) >> 1) + left
|
x = ((right - left) >> 1) + left
|
||||||
|
|
||||||
top := util.Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
top := MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
bottom := util.Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
bottom := MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||||
y = ((bottom - top) >> 1) + top
|
y = ((bottom - top) >> 1) + top
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -303,12 +301,12 @@ func (bc BoxCorners) Center() (x, y int) {
|
||||||
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners {
|
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners {
|
||||||
cx, cy := bc.Center()
|
cx, cy := bc.Center()
|
||||||
|
|
||||||
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
|
thetaRadians := DegreesToRadians(thetaDegrees)
|
||||||
|
|
||||||
tlx, tly := util.Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
tlx, tly := RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
||||||
trx, try := util.Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
trx, try := RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
||||||
brx, bry := util.Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
brx, bry := RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
||||||
blx, bly := util.Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
blx, bly := RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
||||||
|
|
||||||
return BoxCorners{
|
return BoxCorners{
|
||||||
TopLeft: Point{tlx, tly},
|
TopLeft: Point{tlx, tly},
|
||||||
|
|
9
chart.go
9
chart.go
|
@ -7,7 +7,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chart is what we're drawing.
|
// Chart is what we're drawing.
|
||||||
|
@ -266,8 +265,8 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
// only round if we're showing the axis
|
// only round if we're showing the axis
|
||||||
if c.YAxis.Style.Show {
|
if c.YAxis.Style.Show {
|
||||||
delta := yrange.GetDelta()
|
delta := yrange.GetDelta()
|
||||||
roundTo := util.Math.GetRoundToForDelta(delta)
|
roundTo := GetRoundToForDelta(delta)
|
||||||
rmin, rmax := util.Math.RoundDown(yrange.GetMin(), roundTo), util.Math.RoundUp(yrange.GetMax(), roundTo)
|
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
|
||||||
|
|
||||||
yrange.SetMin(rmin)
|
yrange.SetMin(rmin)
|
||||||
yrange.SetMax(rmax)
|
yrange.SetMax(rmax)
|
||||||
|
@ -288,8 +287,8 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
|
|
||||||
if c.YAxisSecondary.Style.Show {
|
if c.YAxisSecondary.Style.Show {
|
||||||
delta := yrangeAlt.GetDelta()
|
delta := yrangeAlt.GetDelta()
|
||||||
roundTo := util.Math.GetRoundToForDelta(delta)
|
roundTo := GetRoundToForDelta(delta)
|
||||||
rmin, rmax := util.Math.RoundDown(yrangeAlt.GetMin(), roundTo), util.Math.RoundUp(yrangeAlt.GetMax(), roundTo)
|
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
|
||||||
yrangeAlt.SetMin(rmin)
|
yrangeAlt.SetMin(rmin)
|
||||||
yrangeAlt.SetMax(rmax)
|
yrangeAlt.SetMax(rmax)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChartGetDPI(t *testing.T) {
|
func TestChartGetDPI(t *testing.T) {
|
||||||
|
@ -388,8 +386,8 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -404,8 +402,8 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -415,7 +413,7 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c = Chart{
|
c = Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
137
cmd/chart/main.go
Normal file
137
cmd/chart/main.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
chart "github.com/wcharczuk/go-chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputPath = flag.String("output", "", "The output file")
|
||||||
|
disableLinreg = flag.Bool("disable-linreg", false, "If we should omit linear regressions")
|
||||||
|
disableLastValues = flag.Bool("disable-last-values", false, "If we should omit last values")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLogger returns a new logger.
|
||||||
|
func NewLogger() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
TimeFormat: time.RFC3339Nano,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is a basic logger.
|
||||||
|
type Logger struct {
|
||||||
|
TimeFormat string
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info writes an info message.
|
||||||
|
func (l *Logger) Info(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[INFO]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof writes an info message.
|
||||||
|
func (l *Logger) Infof(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[INFO]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug writes an debug message.
|
||||||
|
func (l *Logger) Debug(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[DEBUG]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf writes an debug message.
|
||||||
|
func (l *Logger) Debugf(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[DEBUG]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error writes an error message.
|
||||||
|
func (l *Logger) Error(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf writes an error message.
|
||||||
|
func (l *Logger) Errorf(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err writes an error message.
|
||||||
|
func (l *Logger) Err(err error) {
|
||||||
|
if err != nil {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, err.Error())...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalErr writes an error message and exits.
|
||||||
|
func (l *Logger) FatalErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
l.Println(append([]interface{}{"[FATAL]"}, err.Error())...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println prints a new message.
|
||||||
|
func (l *Logger) Println(arguments ...interface{}) {
|
||||||
|
fmt.Fprintln(l.Stdout, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln prints a new message.
|
||||||
|
func (l *Logger) Errorln(arguments ...interface{}) {
|
||||||
|
fmt.Fprintln(l.Stderr, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := NewLogger()
|
||||||
|
|
||||||
|
rawData, err := ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
csvParts := chart.SplitCSV(string(rawData))
|
||||||
|
|
||||||
|
yvalues, err := chart.ParseFloats(csvParts...)
|
||||||
|
|
||||||
|
mainSeries := chart.ContinuousSeries{
|
||||||
|
Name: "A test series",
|
||||||
|
XValues: chart.SeqRange(0, float64(len(csvParts))), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
|
YValues: yvalues,
|
||||||
|
}
|
||||||
|
|
||||||
|
linRegSeries := &chart.LinearRegressionSeries{
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
Series: []chart.Series{
|
||||||
|
mainSeries,
|
||||||
|
linRegSeries,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var output *os.File
|
||||||
|
if *outputPath != "" {
|
||||||
|
output, err = os.Create(*outputPath)
|
||||||
|
if err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output, err = ioutil.TempFile("", "*.png")
|
||||||
|
if err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("rendering chart to", output.Name())
|
||||||
|
if err := graph.Render(chart.PNG, output); err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,25 +4,24 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
assert "github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConcatSeries(t *testing.T) {
|
func TestConcatSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := ContinuousSeries{
|
s1 := ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := ContinuousSeries{
|
s2 := ContinuousSeries{
|
||||||
XValues: seq.Range(11, 20.0),
|
XValues: SeqRange(11, 20.0),
|
||||||
YValues: seq.Range(10.0, 1.0),
|
YValues: SeqRange(10.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s3 := ContinuousSeries{
|
s3 := ContinuousSeries{
|
||||||
XValues: seq.Range(21, 30.0),
|
XValues: SeqRange(21, 30.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := ConcatSeries([]Series{s1, s2, s3})
|
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||||
|
|
|
@ -4,14 +4,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRangeTranslate(t *testing.T) {
|
func TestRangeTranslate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
values := []float64{1.0, 2.0, 2.5, 2.7, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
|
values := []float64{1.0, 2.0, 2.5, 2.7, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
|
||||||
r := ContinuousRange{Domain: 1000}
|
r := ContinuousRange{Domain: 1000}
|
||||||
r.Min, r.Max = util.Math.MinAndMax(values...)
|
r.Min, r.Max = MinMax(values...)
|
||||||
|
|
||||||
// delta = ~7.0
|
// delta = ~7.0
|
||||||
// value = ~5.0
|
// value = ~5.0
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
assert "github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContinuousSeries(t *testing.T) {
|
func TestContinuousSeries(t *testing.T) {
|
||||||
|
@ -13,8 +12,8 @@ func TestContinuousSeries(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal("Test Series", cs.GetName())
|
assert.Equal("Test Series", cs.GetName())
|
||||||
|
@ -54,20 +53,20 @@ func TestContinuousSeriesValidate(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.Nil(cs.Validate())
|
assert.Nil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: seq.Range(1.0, 10.0),
|
XValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
YValues: seq.Range(1.0, 10.0),
|
YValues: SeqRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
}
|
}
|
||||||
|
|
6
draw.go
6
draw.go
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -40,8 +38,8 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
y = cb - yrange.Translate(vy)
|
y = cb - yrange.Translate(vy)
|
||||||
r.LineTo(x, y)
|
r.LineTo(x, y)
|
||||||
}
|
}
|
||||||
r.LineTo(x, util.Math.MinInt(cb, cb-yv0))
|
r.LineTo(x, MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, util.Math.MinInt(cb, cb-yv0))
|
r.LineTo(x0, MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, y0)
|
r.LineTo(x0, y0)
|
||||||
r.Fill()
|
r.Fill()
|
||||||
}
|
}
|
||||||
|
|
51
fileutil.go
Normal file
51
fileutil.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/exception"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadLines reads a file and calls the handler for each line.
|
||||||
|
func ReadLines(filePath string, handler func(string) error) error {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return exception.New(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
err = handler(line)
|
||||||
|
if err != nil {
|
||||||
|
return exception.New(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadChunks reads a file in `chunkSize` pieces, dispatched to the handler.
|
||||||
|
func ReadChunks(filePath string, chunkSize int, handler func([]byte) error) error {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return exception.New(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
chunk := make([]byte, chunkSize)
|
||||||
|
for {
|
||||||
|
readBytes, err := f.Read(chunk)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
readData := chunk[:readBytes]
|
||||||
|
err = handler(readData)
|
||||||
|
if err != nil {
|
||||||
|
return exception.New(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legend returns a legend renderable function.
|
// Legend returns a legend renderable function.
|
||||||
|
@ -69,7 +68,7 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
|
||||||
}
|
}
|
||||||
legendContent.Bottom += tb.Height()
|
legendContent.Bottom += tb.Height()
|
||||||
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
||||||
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
||||||
labelCount++
|
labelCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,8 +163,8 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable {
|
||||||
for x := 0; x < len(labels); x++ {
|
for x := 0; x < len(labels); x++ {
|
||||||
if len(labels[x]) > 0 {
|
if len(labels[x]) > 0 {
|
||||||
textBox = r.MeasureText(labels[x])
|
textBox = r.MeasureText(labels[x])
|
||||||
textHeight = util.Math.MaxInt(textBox.Height(), textHeight)
|
textHeight = MaxInt(textBox.Height(), textHeight)
|
||||||
textWidth = util.Math.MaxInt(textBox.Width(), textWidth)
|
textWidth = MaxInt(textBox.Width(), textWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +280,7 @@ func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
|
||||||
}
|
}
|
||||||
legendContent.Bottom += tb.Height()
|
legendContent.Bottom += tb.Height()
|
||||||
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
||||||
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
||||||
labelCount++
|
labelCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
|
@ -62,7 +59,7 @@ func (lrs LinearRegressionSeries) GetYAxis() YAxisType {
|
||||||
|
|
||||||
// Len returns the number of elements in the series.
|
// Len returns the number of elements in the series.
|
||||||
func (lrs LinearRegressionSeries) Len() int {
|
func (lrs LinearRegressionSeries) Len() int {
|
||||||
return util.Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
|
return MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLimit returns the window size.
|
// GetLimit returns the window size.
|
||||||
|
@ -77,7 +74,7 @@ func (lrs LinearRegressionSeries) GetLimit() int {
|
||||||
func (lrs LinearRegressionSeries) GetEndIndex() int {
|
func (lrs LinearRegressionSeries) GetEndIndex() int {
|
||||||
windowEnd := lrs.GetOffset() + lrs.GetLimit()
|
windowEnd := lrs.GetOffset() + lrs.GetLimit()
|
||||||
innerSeriesLastIndex := lrs.InnerSeries.Len() - 1
|
innerSeriesLastIndex := lrs.InnerSeries.Len() - 1
|
||||||
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
|
return MinInt(windowEnd, innerSeriesLastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOffset returns the data offset.
|
// GetOffset returns the data offset.
|
||||||
|
@ -97,7 +94,7 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
offset := lrs.GetOffset()
|
offset := lrs.GetOffset()
|
||||||
effectiveIndex := util.Math.MinInt(index+offset, lrs.InnerSeries.Len())
|
effectiveIndex := MinInt(index+offset, lrs.InnerSeries.Len())
|
||||||
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
|
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
|
||||||
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
||||||
return
|
return
|
||||||
|
@ -164,14 +161,14 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
|
||||||
|
|
||||||
p := float64(endIndex - startIndex)
|
p := float64(endIndex - startIndex)
|
||||||
|
|
||||||
xvalues := seq.NewBufferWithCapacity(lrs.Len())
|
xvalues := NewValueBufferWithCapacity(lrs.Len())
|
||||||
for index := startIndex; index < endIndex; index++ {
|
for index := startIndex; index < endIndex; index++ {
|
||||||
x, _ := lrs.InnerSeries.GetValues(index)
|
x, _ := lrs.InnerSeries.GetValues(index)
|
||||||
xvalues.Enqueue(x)
|
xvalues.Enqueue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
lrs.avgx = seq.Seq{Provider: xvalues}.Average()
|
lrs.avgx = Seq{xvalues}.Average()
|
||||||
lrs.stddevx = seq.Seq{Provider: xvalues}.StdDev()
|
lrs.stddevx = Seq{xvalues}.StdDev()
|
||||||
|
|
||||||
var sumx, sumy, sumxx, sumxy float64
|
var sumx, sumy, sumxx, sumxy float64
|
||||||
for index := startIndex; index < endIndex; index++ {
|
for index := startIndex; index < endIndex; index++ {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package util
|
package chart
|
||||||
|
|
||||||
import (
|
import "math"
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_pi = math.Pi
|
_pi = math.Pi
|
||||||
|
@ -18,182 +16,86 @@ const (
|
||||||
_r2d = (180.0 / math.Pi)
|
_r2d = (180.0 / math.Pi)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// MinMax returns the minimum and maximum of a given set of values.
|
||||||
// Math contains helper methods for common math operations.
|
func MinMax(values ...float64) (min, max float64) {
|
||||||
Math = &mathUtil{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type mathUtil struct{}
|
|
||||||
|
|
||||||
// Max returns the maximum value of a group of floats.
|
|
||||||
func (m mathUtil) Max(values ...float64) float64 {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
max := values[0]
|
|
||||||
for _, v := range values {
|
|
||||||
if max < v {
|
|
||||||
max = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinAndMax returns both the min and max in one pass.
|
|
||||||
func (m mathUtil) MinAndMax(values ...float64) (min float64, max float64) {
|
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
min = values[0]
|
|
||||||
max = values[0]
|
max = values[0]
|
||||||
for _, v := range values[1:] {
|
min = values[0]
|
||||||
if max < v {
|
var value float64
|
||||||
max = v
|
for index := 1; index < len(values); index++ {
|
||||||
|
value = values[index]
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
}
|
}
|
||||||
if min > v {
|
if value > max {
|
||||||
min = v
|
max = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoundToForDelta returns a `roundTo` value for a given delta.
|
// MinInt returns the minimum int.
|
||||||
func (m mathUtil) GetRoundToForDelta(delta float64) float64 {
|
func MinInt(values ...int) (min int) {
|
||||||
startingDeltaBound := math.Pow(10.0, 10.0)
|
if len(values) == 0 {
|
||||||
for cursor := startingDeltaBound; cursor > 0; cursor /= 10.0 {
|
return
|
||||||
if delta > cursor {
|
|
||||||
return cursor / 10.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0.0
|
min = values[0]
|
||||||
|
var value int
|
||||||
|
for index := 1; index < len(values); index++ {
|
||||||
|
value = values[index]
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUp rounds up to a given roundTo value.
|
// MaxInt returns the maximum int.
|
||||||
func (m mathUtil) RoundUp(value, roundTo float64) float64 {
|
func MaxInt(values ...int) (max int) {
|
||||||
if roundTo < 0.000000000000001 {
|
if len(values) == 0 {
|
||||||
return value
|
return
|
||||||
}
|
|
||||||
d1 := math.Ceil(value / roundTo)
|
|
||||||
return d1 * roundTo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundDown rounds down to a given roundTo value.
|
max = values[0]
|
||||||
func (m mathUtil) RoundDown(value, roundTo float64) float64 {
|
var value int
|
||||||
if roundTo < 0.000000000000001 {
|
for index := 1; index < len(values); index++ {
|
||||||
return value
|
value = values[index]
|
||||||
|
if value > max {
|
||||||
|
max = value
|
||||||
}
|
}
|
||||||
d1 := math.Floor(value / roundTo)
|
}
|
||||||
return d1 * roundTo
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize returns a set of numbers on the interval [0,1] for a given set of inputs.
|
// AbsInt returns the absolute value of an int.
|
||||||
// An example: 4,3,2,1 => 0.4, 0.3, 0.2, 0.1
|
func AbsInt(value int) int {
|
||||||
// Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc.
|
|
||||||
func (m mathUtil) Normalize(values ...float64) []float64 {
|
|
||||||
var total float64
|
|
||||||
for _, v := range values {
|
|
||||||
total += v
|
|
||||||
}
|
|
||||||
output := make([]float64, len(values))
|
|
||||||
for x, v := range values {
|
|
||||||
output[x] = m.RoundDown(v/total, 0.0001)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinInt returns the minimum of a set of integers.
|
|
||||||
func (m mathUtil) MinInt(values ...int) int {
|
|
||||||
min := math.MaxInt32
|
|
||||||
for _, v := range values {
|
|
||||||
if v < min {
|
|
||||||
min = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxInt returns the maximum of a set of integers.
|
|
||||||
func (m mathUtil) MaxInt(values ...int) int {
|
|
||||||
max := math.MinInt32
|
|
||||||
for _, v := range values {
|
|
||||||
if v > max {
|
|
||||||
max = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
// AbsInt returns the absolute value of an integer.
|
|
||||||
func (m mathUtil) AbsInt(value int) int {
|
|
||||||
if value < 0 {
|
if value < 0 {
|
||||||
return -value
|
return -value
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbsInt64 returns the absolute value of a long.
|
|
||||||
func (m mathUtil) AbsInt64(value int64) int64 {
|
|
||||||
if value < 0 {
|
|
||||||
return -value
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mean returns the mean of a set of values
|
|
||||||
func (m mathUtil) Mean(values ...float64) float64 {
|
|
||||||
return m.Sum(values...) / float64(len(values))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeanInt returns the mean of a set of integer values.
|
|
||||||
func (m mathUtil) MeanInt(values ...int) int {
|
|
||||||
return m.SumInt(values...) / len(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum sums a set of values.
|
|
||||||
func (m mathUtil) Sum(values ...float64) float64 {
|
|
||||||
var total float64
|
|
||||||
for _, v := range values {
|
|
||||||
total += v
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// SumInt sums a set of values.
|
|
||||||
func (m mathUtil) SumInt(values ...int) int {
|
|
||||||
var total int
|
|
||||||
for _, v := range values {
|
|
||||||
total += v
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// PercentDifference computes the percentage difference between two values.
|
|
||||||
// The formula is (v2-v1)/v1.
|
|
||||||
func (m mathUtil) PercentDifference(v1, v2 float64) float64 {
|
|
||||||
if v1 == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return (v2 - v1) / v1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DegreesToRadians returns degrees as radians.
|
// DegreesToRadians returns degrees as radians.
|
||||||
func (m mathUtil) DegreesToRadians(degrees float64) float64 {
|
func DegreesToRadians(degrees float64) float64 {
|
||||||
return degrees * _d2r
|
return degrees * _d2r
|
||||||
}
|
}
|
||||||
|
|
||||||
// RadiansToDegrees translates a radian value to a degree value.
|
// RadiansToDegrees translates a radian value to a degree value.
|
||||||
func (m mathUtil) RadiansToDegrees(value float64) float64 {
|
func RadiansToDegrees(value float64) float64 {
|
||||||
return math.Mod(value, _2pi) * _r2d
|
return math.Mod(value, _2pi) * _r2d
|
||||||
}
|
}
|
||||||
|
|
||||||
// PercentToRadians converts a normalized value (0,1) to radians.
|
// PercentToRadians converts a normalized value (0,1) to radians.
|
||||||
func (m mathUtil) PercentToRadians(pct float64) float64 {
|
func PercentToRadians(pct float64) float64 {
|
||||||
return m.DegreesToRadians(360.0 * pct)
|
return DegreesToRadians(360.0 * pct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RadianAdd adds a delta to a base in radians.
|
// RadianAdd adds a delta to a base in radians.
|
||||||
func (m mathUtil) RadianAdd(base, delta float64) float64 {
|
func RadianAdd(base, delta float64) float64 {
|
||||||
value := base + delta
|
value := base + delta
|
||||||
if value > _2pi {
|
if value > _2pi {
|
||||||
return math.Mod(value, _2pi)
|
return math.Mod(value, _2pi)
|
||||||
|
@ -204,7 +106,7 @@ func (m mathUtil) RadianAdd(base, delta float64) float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DegreesAdd adds a delta to a base in radians.
|
// DegreesAdd adds a delta to a base in radians.
|
||||||
func (m mathUtil) DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
|
func DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
|
||||||
value := baseDegrees + deltaDegrees
|
value := baseDegrees + deltaDegrees
|
||||||
if value > _2pi {
|
if value > _2pi {
|
||||||
return math.Mod(value, 360.0)
|
return math.Mod(value, 360.0)
|
||||||
|
@ -215,19 +117,20 @@ func (m mathUtil) DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DegreesToCompass returns the degree value in compass / clock orientation.
|
// DegreesToCompass returns the degree value in compass / clock orientation.
|
||||||
func (m mathUtil) DegreesToCompass(deg float64) float64 {
|
func DegreesToCompass(deg float64) float64 {
|
||||||
return m.DegreesAdd(deg, -90.0)
|
return DegreesAdd(deg, -90.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CirclePoint returns the absolute position of a circle diameter point given
|
// CirclePoint returns the absolute position of a circle diameter point given
|
||||||
// by the radius and the theta.
|
// by the radius and the theta.
|
||||||
func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
|
func CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
|
||||||
x = cx + int(radius*math.Sin(thetaRadians))
|
x = cx + int(radius*math.Sin(thetaRadians))
|
||||||
y = cy - int(radius*math.Cos(thetaRadians))
|
y = cy - int(radius*math.Cos(thetaRadians))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
|
// RotateCoordinate rotates a coordinate around a given center by a theta in radians.
|
||||||
|
func RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
|
||||||
tempX, tempY := float64(x-cx), float64(y-cy)
|
tempX, tempY := float64(x-cx), float64(y-cy)
|
||||||
rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)
|
rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)
|
||||||
rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)
|
rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)
|
||||||
|
@ -235,3 +138,115 @@ func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx,
|
||||||
ry = int(rotatedY) + cy
|
ry = int(rotatedY) + cy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundUp rounds up to a given roundTo value.
|
||||||
|
func RoundUp(value, roundTo float64) float64 {
|
||||||
|
if roundTo < 0.000000000000001 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
d1 := math.Ceil(value / roundTo)
|
||||||
|
return d1 * roundTo
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundDown rounds down to a given roundTo value.
|
||||||
|
func RoundDown(value, roundTo float64) float64 {
|
||||||
|
if roundTo < 0.000000000000001 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
d1 := math.Floor(value / roundTo)
|
||||||
|
return d1 * roundTo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize returns a set of numbers on the interval [0,1] for a given set of inputs.
|
||||||
|
// An example: 4,3,2,1 => 0.4, 0.3, 0.2, 0.1
|
||||||
|
// Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc.
|
||||||
|
func Normalize(values ...float64) []float64 {
|
||||||
|
var total float64
|
||||||
|
for _, v := range values {
|
||||||
|
total += v
|
||||||
|
}
|
||||||
|
output := make([]float64, len(values))
|
||||||
|
for x, v := range values {
|
||||||
|
output[x] = RoundDown(v/total, 0.0001)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mean returns the mean of a set of values
|
||||||
|
func Mean(values ...float64) float64 {
|
||||||
|
return Sum(values...) / float64(len(values))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeanInt returns the mean of a set of integer values.
|
||||||
|
func MeanInt(values ...int) int {
|
||||||
|
return SumInt(values...) / len(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum sums a set of values.
|
||||||
|
func Sum(values ...float64) float64 {
|
||||||
|
var total float64
|
||||||
|
for _, v := range values {
|
||||||
|
total += v
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumInt sums a set of values.
|
||||||
|
func SumInt(values ...int) int {
|
||||||
|
var total int
|
||||||
|
for _, v := range values {
|
||||||
|
total += v
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// PercentDifference computes the percentage difference between two values.
|
||||||
|
// The formula is (v2-v1)/v1.
|
||||||
|
func PercentDifference(v1, v2 float64) float64 {
|
||||||
|
if v1 == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (v2 - v1) / v1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoundToForDelta returns a `roundTo` value for a given delta.
|
||||||
|
func GetRoundToForDelta(delta float64) float64 {
|
||||||
|
startingDeltaBound := math.Pow(10.0, 10.0)
|
||||||
|
for cursor := startingDeltaBound; cursor > 0; cursor /= 10.0 {
|
||||||
|
if delta > cursor {
|
||||||
|
return cursor / 10.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundPlaces rounds an input to a given places.
|
||||||
|
func RoundPlaces(input float64, places int) (rounded float64) {
|
||||||
|
if math.IsNaN(input) {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := 1.0
|
||||||
|
if input < 0 {
|
||||||
|
sign = -1
|
||||||
|
input *= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
precision := math.Pow(10, float64(places))
|
||||||
|
digit := input * precision
|
||||||
|
_, decimal := math.Modf(digit)
|
||||||
|
|
||||||
|
if decimal >= 0.5 {
|
||||||
|
rounded = math.Ceil(digit)
|
||||||
|
} else {
|
||||||
|
rounded = math.Floor(digit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rounded / precision * sign
|
||||||
|
}
|
||||||
|
|
||||||
|
func f64i(value float64) int {
|
||||||
|
r := RoundPlaces(value, 0)
|
||||||
|
return int(r)
|
||||||
|
}
|
36
parse.go
Normal file
36
parse.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/exception"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseFloats parses a list of floats.
|
||||||
|
func ParseFloats(values ...string) ([]float64, error) {
|
||||||
|
var output []float64
|
||||||
|
var parsedValue float64
|
||||||
|
var err error
|
||||||
|
for _, value := range values {
|
||||||
|
if parsedValue, err = strconv.ParseFloat(value, 64); err != nil {
|
||||||
|
return nil, exception.New(err)
|
||||||
|
}
|
||||||
|
output = append(output, parsedValue)
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimes parses a list of times with a given format.
|
||||||
|
func ParseTimes(layout string, values ...string) ([]time.Time, error) {
|
||||||
|
var output []time.Time
|
||||||
|
var parsedValue time.Time
|
||||||
|
var err error
|
||||||
|
for _, value := range values {
|
||||||
|
if parsedValue, err = time.Parse(layout, value); err != nil {
|
||||||
|
return nil, exception.New(err)
|
||||||
|
}
|
||||||
|
output = append(output, parsedValue)
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
26
pie_chart.go
26
pie_chart.go
|
@ -4,16 +4,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_pi = math.Pi
|
|
||||||
_pi2 = math.Pi / 2.0
|
|
||||||
_pi4 = math.Pi / 4.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PieChart is a chart that draws sections of a circle based on percentages.
|
// PieChart is a chart that draws sections of a circle based on percentages.
|
||||||
|
@ -131,7 +123,7 @@ func (pc PieChart) drawTitle(r Renderer) {
|
||||||
|
|
||||||
func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
cx, cy := canvasBox.Center()
|
cx, cy := canvasBox.Center()
|
||||||
diameter := util.Math.MinInt(canvasBox.Width(), canvasBox.Height())
|
diameter := MinInt(canvasBox.Width(), canvasBox.Height())
|
||||||
radius := float64(diameter >> 1)
|
radius := float64(diameter >> 1)
|
||||||
labelRadius := (radius * 2.0) / 3.0
|
labelRadius := (radius * 2.0) / 3.0
|
||||||
|
|
||||||
|
@ -148,8 +140,8 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
||||||
|
|
||||||
r.MoveTo(cx, cy)
|
r.MoveTo(cx, cy)
|
||||||
rads = util.Math.PercentToRadians(total)
|
rads = PercentToRadians(total)
|
||||||
delta = util.Math.PercentToRadians(v.Value)
|
delta = PercentToRadians(v.Value)
|
||||||
|
|
||||||
r.ArcTo(cx, cy, radius, radius, rads, delta)
|
r.ArcTo(cx, cy, radius, radius, rads, delta)
|
||||||
|
|
||||||
|
@ -165,9 +157,9 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
for index, v := range values {
|
for index, v := range values {
|
||||||
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
||||||
if len(v.Label) > 0 {
|
if len(v.Label) > 0 {
|
||||||
delta2 = util.Math.PercentToRadians(total + (v.Value / 2.0))
|
delta2 = PercentToRadians(total + (v.Value / 2.0))
|
||||||
delta2 = util.Math.RadianAdd(delta2, _pi2)
|
delta2 = RadianAdd(delta2, _pi2)
|
||||||
lx, ly = util.Math.CirclePoint(cx, cy, labelRadius, delta2)
|
lx, ly = CirclePoint(cx, cy, labelRadius, delta2)
|
||||||
|
|
||||||
tb := r.MeasureText(v.Label)
|
tb := r.MeasureText(v.Label)
|
||||||
lx = lx - (tb.Width() >> 1)
|
lx = lx - (tb.Width() >> 1)
|
||||||
|
@ -199,7 +191,7 @@ func (pc PieChart) getDefaultCanvasBox() Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
|
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
|
||||||
circleDiameter := util.Math.MinInt(canvasBox.Width(), canvasBox.Height())
|
circleDiameter := MinInt(canvasBox.Width(), canvasBox.Height())
|
||||||
|
|
||||||
square := Box{
|
square := Box{
|
||||||
Right: circleDiameter,
|
Right: circleDiameter,
|
||||||
|
@ -245,7 +237,7 @@ func (pc PieChart) stylePieChartValue(index int) Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getScaledFontSize() float64 {
|
func (pc PieChart) getScaledFontSize() float64 {
|
||||||
effectiveDimension := util.Math.MinInt(pc.GetWidth(), pc.GetHeight())
|
effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48.0
|
return 48.0
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
@ -284,7 +276,7 @@ func (pc PieChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getTitleFontSize() float64 {
|
func (pc PieChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := util.Math.MinInt(pc.GetWidth(), pc.GetHeight())
|
effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/matrix"
|
"github.com/wcharczuk/go-chart/matrix"
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
|
@ -47,7 +46,7 @@ func (prs PolynomialRegressionSeries) GetYAxis() YAxisType {
|
||||||
|
|
||||||
// Len returns the number of elements in the series.
|
// Len returns the number of elements in the series.
|
||||||
func (prs PolynomialRegressionSeries) Len() int {
|
func (prs PolynomialRegressionSeries) Len() int {
|
||||||
return util.Math.MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
|
return MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLimit returns the window size.
|
// GetLimit returns the window size.
|
||||||
|
@ -62,7 +61,7 @@ func (prs PolynomialRegressionSeries) GetLimit() int {
|
||||||
func (prs PolynomialRegressionSeries) GetEndIndex() int {
|
func (prs PolynomialRegressionSeries) GetEndIndex() int {
|
||||||
windowEnd := prs.GetOffset() + prs.GetLimit()
|
windowEnd := prs.GetOffset() + prs.GetLimit()
|
||||||
innerSeriesLastIndex := prs.InnerSeries.Len() - 1
|
innerSeriesLastIndex := prs.InnerSeries.Len() - 1
|
||||||
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
|
return MinInt(windowEnd, innerSeriesLastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOffset returns the data offset.
|
// GetOffset returns the data offset.
|
||||||
|
@ -102,7 +101,7 @@ func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := prs.GetOffset()
|
offset := prs.GetOffset()
|
||||||
effectiveIndex := util.Math.MinInt(index+offset, prs.InnerSeries.Len())
|
effectiveIndex := MinInt(index+offset, prs.InnerSeries.Len())
|
||||||
x, y = prs.InnerSeries.GetValues(effectiveIndex)
|
x, y = prs.InnerSeries.GetValues(effectiveIndex)
|
||||||
y = prs.apply(x)
|
y = prs.apply(x)
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PNG returns a new png/raster renderer.
|
// PNG returns a new png/raster renderer.
|
||||||
|
@ -50,8 +49,7 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetClassName implements the interface method. However, PNGs have no classes.
|
// SetClassName implements the interface method. However, PNGs have no classes.
|
||||||
func (vr *rasterRenderer) SetClassName(_ string) {
|
func (rr *rasterRenderer) SetClassName(_ string) {}
|
||||||
}
|
|
||||||
|
|
||||||
// SetStrokeColor implements the interface method.
|
// SetStrokeColor implements the interface method.
|
||||||
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
||||||
|
@ -196,7 +194,7 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
|
||||||
return textBox
|
return textBox
|
||||||
}
|
}
|
||||||
|
|
||||||
return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians)).Box()
|
return textBox.Corners().Rotate(RadiansToDegrees(*rr.rotateRadians)).Box()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTextRotation sets a text rotation.
|
// SetTextRotation sets a text rotation.
|
||||||
|
|
|
@ -1,33 +1,25 @@
|
||||||
package seq
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New wraps a provider with a seq.
|
// NewSeq wraps a provider with a seq.
|
||||||
func New(provider Provider) Seq {
|
func NewSeq(provider SeqProvider) Seq {
|
||||||
return Seq{Provider: provider}
|
return Seq{SeqProvider: provider}
|
||||||
}
|
|
||||||
|
|
||||||
// Values returns a new seq composed of a given set of values.
|
|
||||||
func Values(values ...float64) Seq {
|
|
||||||
return Seq{Provider: Array(values)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider is a provider for values for a seq.
|
|
||||||
type Provider interface {
|
|
||||||
Len() int
|
|
||||||
GetValue(int) float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seq is a utility wrapper for seq providers.
|
// Seq is a utility wrapper for seq providers.
|
||||||
type Seq struct {
|
type Seq struct {
|
||||||
Provider
|
SeqProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array enumerates the seq into a slice.
|
// Values enumerates the seq into a slice.
|
||||||
func (s Seq) Array() (output []float64) {
|
func (s Seq) Values() (output []float64) {
|
||||||
if s.Len() == 0 {
|
if s.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -53,7 +45,7 @@ func (s Seq) Map(mapfn func(i int, v float64) float64) Seq {
|
||||||
for i := 0; i < s.Len(); i++ {
|
for i := 0; i < s.Len(); i++ {
|
||||||
mapfn(i, s.GetValue(i))
|
mapfn(i, s.GetValue(i))
|
||||||
}
|
}
|
||||||
return Seq{Array(output)}
|
return Seq{SeqArray(output)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FoldLeft collapses a seq from left to right.
|
// FoldLeft collapses a seq from left to right.
|
||||||
|
@ -148,9 +140,9 @@ func (s Seq) Sort() Seq {
|
||||||
if s.Len() == 0 {
|
if s.Len() == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
values := s.Array()
|
values := s.Values()
|
||||||
sort.Float64s(values)
|
sort.Float64s(values)
|
||||||
return Seq{Provider: Array(values)}
|
return Seq{SeqArray(values)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Median returns the median or middle value in the sorted seq.
|
// Median returns the median or middle value in the sorted seq.
|
||||||
|
@ -255,5 +247,160 @@ func (s Seq) Normalize() Seq {
|
||||||
output[i] = (s.GetValue(i) - min) / delta
|
output[i] = (s.GetValue(i) - min) / delta
|
||||||
}
|
}
|
||||||
|
|
||||||
return Seq{Provider: Array(output)}
|
return Seq{SeqProvider: SeqArray(output)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqProvider is a provider for values for a seq.
|
||||||
|
type SeqProvider interface {
|
||||||
|
Len() int
|
||||||
|
GetValue(int) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqArray is a wrapper for an array of floats that implements `ValuesProvider`.
|
||||||
|
type SeqArray []float64
|
||||||
|
|
||||||
|
// Len returns the value provider length.
|
||||||
|
func (a SeqArray) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns the value at a given index.
|
||||||
|
func (a SeqArray) GetValue(index int) float64 {
|
||||||
|
return a[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqDays generates a seq of timestamps by day, from -days to today.
|
||||||
|
func SeqDays(days int) []time.Time {
|
||||||
|
var values []time.Time
|
||||||
|
for day := days; day >= 0; day-- {
|
||||||
|
values = append(values, time.Now().AddDate(0, 0, -day))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqHours returns a sequence of times by the hour for a given number of hours
|
||||||
|
// after a given start.
|
||||||
|
func SeqHours(start time.Time, totalHours int) []time.Time {
|
||||||
|
times := make([]time.Time, totalHours)
|
||||||
|
|
||||||
|
last := start
|
||||||
|
for i := 0; i < totalHours; i++ {
|
||||||
|
times[i] = last
|
||||||
|
last = last.Add(time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqHoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
||||||
|
func SeqHoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||||
|
start, end := TimeMinMax(xdata...)
|
||||||
|
totalHours := DiffHours(start, end)
|
||||||
|
|
||||||
|
finalTimes := SeqHours(start, totalHours+1)
|
||||||
|
finalValues := make([]float64, totalHours+1)
|
||||||
|
|
||||||
|
var hoursFromStart int
|
||||||
|
for i, xd := range xdata {
|
||||||
|
hoursFromStart = DiffHours(start, xd)
|
||||||
|
finalValues[hoursFromStart] = ydata[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalTimes, finalValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert types implement interfaces.
|
||||||
|
var (
|
||||||
|
_ SeqProvider = (*SeqTimes)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// SeqTimes are an array of times.
|
||||||
|
// It wraps the array with methods that implement `seq.Provider`.
|
||||||
|
type SeqTimes []time.Time
|
||||||
|
|
||||||
|
// Array returns the times to an array.
|
||||||
|
func (t SeqTimes) Array() []time.Time {
|
||||||
|
return []time.Time(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the array.
|
||||||
|
func (t SeqTimes) Len() int {
|
||||||
|
return len(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns a value at an index as a time.
|
||||||
|
func (t SeqTimes) GetValue(index int) float64 {
|
||||||
|
return timeutil.ToFloat64(t[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqRange returns the array values of a linear seq with a given start, end and optional step.
|
||||||
|
func SeqRange(start, end float64) []float64 {
|
||||||
|
return Seq{NewSeqLinear().WithStart(start).WithEnd(end).WithStep(1.0)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqRangeWithStep returns the array values of a linear seq with a given start, end and optional step.
|
||||||
|
func SeqRangeWithStep(start, end, step float64) []float64 {
|
||||||
|
return Seq{NewSeqLinear().WithStart(start).WithEnd(end).WithStep(step)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSeqLinear returns a new linear generator.
|
||||||
|
func NewSeqLinear() *SeqLinear {
|
||||||
|
return &SeqLinear{step: 1.0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeqLinear is a stepwise generator.
|
||||||
|
type SeqLinear struct {
|
||||||
|
start float64
|
||||||
|
end float64
|
||||||
|
step float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns the start value.
|
||||||
|
func (lg SeqLinear) Start() float64 {
|
||||||
|
return lg.start
|
||||||
|
}
|
||||||
|
|
||||||
|
// End returns the end value.
|
||||||
|
func (lg SeqLinear) End() float64 {
|
||||||
|
return lg.end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step returns the step value.
|
||||||
|
func (lg SeqLinear) Step() float64 {
|
||||||
|
return lg.step
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the seq.
|
||||||
|
func (lg SeqLinear) Len() int {
|
||||||
|
if lg.start < lg.end {
|
||||||
|
return int((lg.end-lg.start)/lg.step) + 1
|
||||||
|
}
|
||||||
|
return int((lg.start-lg.end)/lg.step) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns the value at a given index.
|
||||||
|
func (lg SeqLinear) GetValue(index int) float64 {
|
||||||
|
fi := float64(index)
|
||||||
|
if lg.start < lg.end {
|
||||||
|
return lg.start + (fi * lg.step)
|
||||||
|
}
|
||||||
|
return lg.start - (fi * lg.step)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStart sets the start and returns the linear generator.
|
||||||
|
func (lg *SeqLinear) WithStart(start float64) *SeqLinear {
|
||||||
|
lg.start = start
|
||||||
|
return lg
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnd sets the end and returns the linear generator.
|
||||||
|
func (lg *SeqLinear) WithEnd(end float64) *SeqLinear {
|
||||||
|
lg.end = end
|
||||||
|
return lg
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStep sets the step and returns the linear generator.
|
||||||
|
func (lg *SeqLinear) WithStep(step float64) *SeqLinear {
|
||||||
|
lg.step = step
|
||||||
|
return lg
|
||||||
}
|
}
|
19
seq/array.go
19
seq/array.go
|
@ -1,19 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
// NewArray creates a new array.
|
|
||||||
func NewArray(values ...float64) Array {
|
|
||||||
return Array(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array is a wrapper for an array of floats that implements `ValuesProvider`.
|
|
||||||
type Array []float64
|
|
||||||
|
|
||||||
// Len returns the value provider length.
|
|
||||||
func (a Array) Len() int {
|
|
||||||
return len(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value at a given index.
|
|
||||||
func (a Array) GetValue(index int) float64 {
|
|
||||||
return a[index]
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
// Range returns the array values of a linear seq with a given start, end and optional step.
|
|
||||||
func Range(start, end float64) []float64 {
|
|
||||||
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(1.0)}.Array()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeWithStep returns the array values of a linear seq with a given start, end and optional step.
|
|
||||||
func RangeWithStep(start, end, step float64) []float64 {
|
|
||||||
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(step)}.Array()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLinear returns a new linear generator.
|
|
||||||
func NewLinear() *Linear {
|
|
||||||
return &Linear{step: 1.0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linear is a stepwise generator.
|
|
||||||
type Linear struct {
|
|
||||||
start float64
|
|
||||||
end float64
|
|
||||||
step float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start returns the start value.
|
|
||||||
func (lg Linear) Start() float64 {
|
|
||||||
return lg.start
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the end value.
|
|
||||||
func (lg Linear) End() float64 {
|
|
||||||
return lg.end
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step returns the step value.
|
|
||||||
func (lg Linear) Step() float64 {
|
|
||||||
return lg.step
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the seq.
|
|
||||||
func (lg Linear) Len() int {
|
|
||||||
if lg.start < lg.end {
|
|
||||||
return int((lg.end-lg.start)/lg.step) + 1
|
|
||||||
}
|
|
||||||
return int((lg.start-lg.end)/lg.step) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value at a given index.
|
|
||||||
func (lg Linear) GetValue(index int) float64 {
|
|
||||||
fi := float64(index)
|
|
||||||
if lg.start < lg.end {
|
|
||||||
return lg.start + (fi * lg.step)
|
|
||||||
}
|
|
||||||
return lg.start - (fi * lg.step)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStart sets the start and returns the linear generator.
|
|
||||||
func (lg *Linear) WithStart(start float64) *Linear {
|
|
||||||
lg.start = start
|
|
||||||
return lg
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnd sets the end and returns the linear generator.
|
|
||||||
func (lg *Linear) WithEnd(end float64) *Linear {
|
|
||||||
lg.end = end
|
|
||||||
return lg
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStep sets the step and returns the linear generator.
|
|
||||||
func (lg *Linear) WithStep(step float64) *Linear {
|
|
||||||
lg.step = step
|
|
||||||
return lg
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRange(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Range(1, 100)
|
|
||||||
assert.Len(values, 100)
|
|
||||||
assert.Equal(1, values[0])
|
|
||||||
assert.Equal(100, values[99])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRangeWithStep(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := RangeWithStep(0, 100, 5)
|
|
||||||
assert.Equal(100, values[20])
|
|
||||||
assert.Len(values, 21)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRangeReversed(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Range(10.0, 1.0)
|
|
||||||
assert.Equal(10, len(values))
|
|
||||||
assert.Equal(10.0, values[0])
|
|
||||||
assert.Equal(1.0, values[9])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValuesRegression(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// note; this assumes a 1.0 step is implicitly set in the constructor.
|
|
||||||
linearProvider := NewLinear().WithStart(1.0).WithEnd(100.0)
|
|
||||||
assert.Equal(1, linearProvider.Start())
|
|
||||||
assert.Equal(100, linearProvider.End())
|
|
||||||
assert.Equal(100, linearProvider.Len())
|
|
||||||
|
|
||||||
values := Seq{Provider: linearProvider}.Array()
|
|
||||||
assert.Len(values, 100)
|
|
||||||
assert.Equal(1.0, values[0])
|
|
||||||
assert.Equal(100, values[99])
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RandomValues returns an array of random values.
|
|
||||||
func RandomValues(count int) []float64 {
|
|
||||||
return Seq{NewRandom().WithLen(count)}.Array()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomValuesWithMax returns an array of random values with a given average.
|
|
||||||
func RandomValuesWithMax(count int, max float64) []float64 {
|
|
||||||
return Seq{NewRandom().WithMax(max).WithLen(count)}.Array()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRandom creates a new random seq.
|
|
||||||
func NewRandom() *Random {
|
|
||||||
return &Random{
|
|
||||||
rnd: rand.New(rand.NewSource(time.Now().Unix())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random is a random number seq generator.
|
|
||||||
type Random struct {
|
|
||||||
rnd *rand.Rand
|
|
||||||
max *float64
|
|
||||||
min *float64
|
|
||||||
len *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements that will be generated.
|
|
||||||
func (r *Random) Len() int {
|
|
||||||
if r.len != nil {
|
|
||||||
return *r.len
|
|
||||||
}
|
|
||||||
return math.MaxInt32
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value.
|
|
||||||
func (r *Random) GetValue(_ int) float64 {
|
|
||||||
if r.min != nil && r.max != nil {
|
|
||||||
var delta float64
|
|
||||||
|
|
||||||
if *r.max > *r.min {
|
|
||||||
delta = *r.max - *r.min
|
|
||||||
} else {
|
|
||||||
delta = *r.min - *r.max
|
|
||||||
}
|
|
||||||
|
|
||||||
return *r.min + (r.rnd.Float64() * delta)
|
|
||||||
} else if r.max != nil {
|
|
||||||
return r.rnd.Float64() * *r.max
|
|
||||||
} else if r.min != nil {
|
|
||||||
return *r.min + (r.rnd.Float64())
|
|
||||||
}
|
|
||||||
return r.rnd.Float64()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLen sets a maximum len
|
|
||||||
func (r *Random) WithLen(length int) *Random {
|
|
||||||
r.len = &length
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min returns the minimum value.
|
|
||||||
func (r Random) Min() *float64 {
|
|
||||||
return r.min
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMin sets the scale and returns the Random.
|
|
||||||
func (r *Random) WithMin(min float64) *Random {
|
|
||||||
r.min = &min
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max returns the maximum value.
|
|
||||||
func (r Random) Max() *float64 {
|
|
||||||
return r.max
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMax sets the average and returns the Random.
|
|
||||||
func (r *Random) WithMax(max float64) *Random {
|
|
||||||
r.max = &max
|
|
||||||
return r
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRandomRegression(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
randomProvider := NewRandom().WithLen(4096).WithMax(256)
|
|
||||||
assert.Equal(4096, randomProvider.Len())
|
|
||||||
assert.Equal(256, *randomProvider.Max())
|
|
||||||
|
|
||||||
randomSequence := New(randomProvider)
|
|
||||||
randomValues := randomSequence.Array()
|
|
||||||
assert.Len(randomValues, 4096)
|
|
||||||
assert.InDelta(128, randomSequence.Average(), 10.0)
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSequenceEach(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
values.Each(func(i int, v float64) {
|
|
||||||
assert.Equal(i, v-1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceMap(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
mapped := values.Map(func(i int, v float64) float64 {
|
|
||||||
assert.Equal(i, v-1)
|
|
||||||
return v * 2
|
|
||||||
})
|
|
||||||
assert.Equal(4, mapped.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceFoldLeft(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
ten := values.FoldLeft(func(_ int, vp, v float64) float64 {
|
|
||||||
return vp + v
|
|
||||||
})
|
|
||||||
assert.Equal(10, ten)
|
|
||||||
|
|
||||||
orderTest := Seq{NewArray(10, 3, 2, 1)}
|
|
||||||
four := orderTest.FoldLeft(func(_ int, vp, v float64) float64 {
|
|
||||||
return vp - v
|
|
||||||
})
|
|
||||||
assert.Equal(4, four)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceFoldRight(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
ten := values.FoldRight(func(_ int, vp, v float64) float64 {
|
|
||||||
return vp + v
|
|
||||||
})
|
|
||||||
assert.Equal(10, ten)
|
|
||||||
|
|
||||||
orderTest := Seq{NewArray(10, 3, 2, 1)}
|
|
||||||
notFour := orderTest.FoldRight(func(_ int, vp, v float64) float64 {
|
|
||||||
return vp - v
|
|
||||||
})
|
|
||||||
assert.Equal(-14, notFour)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceSum(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
assert.Equal(10, values.Sum())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceAverage(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4)}
|
|
||||||
assert.Equal(2.5, values.Average())
|
|
||||||
|
|
||||||
valuesOdd := Seq{NewArray(1, 2, 3, 4, 5)}
|
|
||||||
assert.Equal(3, valuesOdd.Average())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceVariance(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := Seq{NewArray(1, 2, 3, 4, 5)}
|
|
||||||
assert.Equal(2, values.Variance())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceNormalize(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
normalized := Values(1, 2, 3, 4, 5).Normalize().Array()
|
|
||||||
|
|
||||||
assert.NotEmpty(normalized)
|
|
||||||
assert.Len(normalized, 5)
|
|
||||||
assert.Equal(0, normalized[0])
|
|
||||||
assert.Equal(0.25, normalized[1])
|
|
||||||
assert.Equal(1, normalized[4])
|
|
||||||
}
|
|
50
seq/time.go
50
seq/time.go
|
@ -1,50 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Time is a utility singleton with helper functions for time seq generation.
|
|
||||||
var Time timeSequence
|
|
||||||
|
|
||||||
type timeSequence struct{}
|
|
||||||
|
|
||||||
// Days generates a seq of timestamps by day, from -days to today.
|
|
||||||
func (ts timeSequence) Days(days int) []time.Time {
|
|
||||||
var values []time.Time
|
|
||||||
for day := days; day >= 0; day-- {
|
|
||||||
values = append(values, time.Now().AddDate(0, 0, -day))
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time {
|
|
||||||
times := make([]time.Time, totalHours)
|
|
||||||
|
|
||||||
last := start
|
|
||||||
for i := 0; i < totalHours; i++ {
|
|
||||||
times[i] = last
|
|
||||||
last = last.Add(time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
|
||||||
func (ts timeSequence) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
|
||||||
start, end := util.Time.StartAndEnd(xdata...)
|
|
||||||
totalHours := util.Time.DiffHours(start, end)
|
|
||||||
|
|
||||||
finalTimes := ts.Hours(start, totalHours+1)
|
|
||||||
finalValues := make([]float64, totalHours+1)
|
|
||||||
|
|
||||||
var hoursFromStart int
|
|
||||||
for i, xd := range xdata {
|
|
||||||
hoursFromStart = util.Time.DiffHours(start, xd)
|
|
||||||
finalValues[hoursFromStart] = ydata[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalTimes, finalValues
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTimeHours(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
|
|
||||||
seq := Time.Hours(today, 24)
|
|
||||||
|
|
||||||
end := util.Time.End(seq...)
|
|
||||||
assert.Len(seq, 24)
|
|
||||||
assert.Equal(2016, end.Year())
|
|
||||||
assert.Equal(07, int(end.Month()))
|
|
||||||
assert.Equal(02, end.Day())
|
|
||||||
assert.Equal(11, end.Hour())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceHoursFill(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
xdata := []time.Time{
|
|
||||||
time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 01, 13, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 01, 14, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 02, 4, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 02, 5, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 03, 12, 0, 0, 0, time.UTC),
|
|
||||||
time.Date(2016, 07, 03, 14, 0, 0, 0, time.UTC),
|
|
||||||
}
|
|
||||||
|
|
||||||
ydata := []float64{
|
|
||||||
1.1,
|
|
||||||
1.2,
|
|
||||||
1.4,
|
|
||||||
0.8,
|
|
||||||
2.1,
|
|
||||||
0.4,
|
|
||||||
0.6,
|
|
||||||
}
|
|
||||||
|
|
||||||
filledTimes, filledValues := Time.HoursFilled(xdata, ydata)
|
|
||||||
expected := util.Time.DiffHours(util.Time.Start(xdata...), util.Time.End(xdata...)) + 1
|
|
||||||
assert.Len(filledTimes, expected)
|
|
||||||
assert.Equal(len(filledValues), len(filledTimes))
|
|
||||||
|
|
||||||
assert.NotZero(filledValues[0])
|
|
||||||
assert.NotZero(filledValues[len(filledValues)-1])
|
|
||||||
|
|
||||||
assert.NotZero(filledValues[16])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeStart(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
times := []time.Time{
|
|
||||||
time.Now().AddDate(0, 0, -4),
|
|
||||||
time.Now().AddDate(0, 0, -2),
|
|
||||||
time.Now().AddDate(0, 0, -1),
|
|
||||||
time.Now().AddDate(0, 0, -3),
|
|
||||||
time.Now().AddDate(0, 0, -5),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.InTimeDelta(util.Time.Start(times...), times[4], time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeEnd(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
times := []time.Time{
|
|
||||||
time.Now().AddDate(0, 0, -4),
|
|
||||||
time.Now().AddDate(0, 0, -2),
|
|
||||||
time.Now().AddDate(0, 0, -1),
|
|
||||||
time.Now().AddDate(0, 0, -3),
|
|
||||||
time.Now().AddDate(0, 0, -5),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.InTimeDelta(util.Time.End(times...), times[2], time.Millisecond)
|
|
||||||
}
|
|
31
seq/times.go
31
seq/times.go
|
@ -1,31 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assert types implement interfaces.
|
|
||||||
var (
|
|
||||||
_ Provider = (*Times)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Times are an array of times.
|
|
||||||
// It wraps the array with methods that implement `seq.Provider`.
|
|
||||||
type Times []time.Time
|
|
||||||
|
|
||||||
// Array returns the times to an array.
|
|
||||||
func (t Times) Array() []time.Time {
|
|
||||||
return []time.Time(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the array.
|
|
||||||
func (t Times) Len() int {
|
|
||||||
return len(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns a value at an index as a time.
|
|
||||||
func (t Times) GetValue(index int) float64 {
|
|
||||||
return util.Time.ToFloat64(t[index])
|
|
||||||
}
|
|
32
seq/util.go
32
seq/util.go
|
@ -1,32 +0,0 @@
|
||||||
package seq
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
func round(input float64, places int) (rounded float64) {
|
|
||||||
if math.IsNaN(input) {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
sign := 1.0
|
|
||||||
if input < 0 {
|
|
||||||
sign = -1
|
|
||||||
input *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
precision := math.Pow(10, float64(places))
|
|
||||||
digit := input * precision
|
|
||||||
_, decimal := math.Modf(digit)
|
|
||||||
|
|
||||||
if decimal >= 0.5 {
|
|
||||||
rounded = math.Ceil(digit)
|
|
||||||
} else {
|
|
||||||
rounded = math.Floor(digit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rounded / precision * sign
|
|
||||||
}
|
|
||||||
|
|
||||||
func f64i(value float64) int {
|
|
||||||
r := round(value, 0)
|
|
||||||
return int(r)
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -96,7 +94,7 @@ func (sma SMASeries) GetLastValues() (x, y float64) {
|
||||||
|
|
||||||
func (sma SMASeries) getAverage(index int) float64 {
|
func (sma SMASeries) getAverage(index int) float64 {
|
||||||
period := sma.GetPeriod()
|
period := sma.GetPeriod()
|
||||||
floor := util.Math.MaxInt(0, index-period)
|
floor := MaxInt(0, index-period)
|
||||||
var accum float64
|
var accum float64
|
||||||
var count float64
|
var count float64
|
||||||
for x := index; x >= floor; x-- {
|
for x := index; x >= floor; x-- {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockValuesProvider struct {
|
type mockValuesProvider struct {
|
||||||
|
@ -14,14 +13,14 @@ type mockValuesProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockValuesProvider) Len() int {
|
func (m mockValuesProvider) Len() int {
|
||||||
return util.Math.MinInt(len(m.X), len(m.Y))
|
return MinInt(len(m.X), len(m.Y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockValuesProvider) GetValues(index int) (x, y float64) {
|
func (m mockValuesProvider) GetValues(index int) (x, y float64) {
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
panic("negative index at GetValue()")
|
panic("negative index at GetValue()")
|
||||||
}
|
}
|
||||||
if index >= util.Math.MinInt(len(m.X), len(m.Y)) {
|
if index >= MinInt(len(m.X), len(m.Y)) {
|
||||||
panic("index is outside the length of m.X or m.Y")
|
panic("index is outside the length of m.X or m.Y")
|
||||||
}
|
}
|
||||||
x = m.X[index]
|
x = m.X[index]
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StackedBar is a bar within a StackedBarChart.
|
// StackedBar is a bar within a StackedBarChart.
|
||||||
|
@ -154,7 +152,7 @@ func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar S
|
||||||
Top: yoffset,
|
Top: yoffset,
|
||||||
Left: bxl,
|
Left: bxl,
|
||||||
Right: bxr,
|
Right: bxr,
|
||||||
Bottom: util.Math.MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth),
|
Bottom: MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth),
|
||||||
}
|
}
|
||||||
Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
|
Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
|
||||||
yoffset += barHeight
|
yoffset += barHeight
|
||||||
|
@ -209,7 +207,7 @@ func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
||||||
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
||||||
r.Stroke()
|
r.Stroke()
|
||||||
|
|
||||||
ticks := seq.RangeWithStep(0.0, 1.0, 0.2)
|
ticks := SeqRangeWithStep(0.0, 1.0, 0.2)
|
||||||
for _, t := range ticks {
|
for _, t := range ticks {
|
||||||
axisStyle.GetStrokeOptions().WriteToRenderer(r)
|
axisStyle.GetStrokeOptions().WriteToRenderer(r)
|
||||||
ty := canvasBox.Bottom - int(t*float64(canvasBox.Height()))
|
ty := canvasBox.Bottom - int(t*float64(canvasBox.Height()))
|
||||||
|
@ -294,7 +292,7 @@ func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box {
|
||||||
lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle)
|
lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle)
|
||||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||||
|
|
||||||
xaxisHeight = util.Math.MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Box{
|
return Box{
|
||||||
|
@ -346,7 +344,7 @@ func (sbc StackedBarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sbc StackedBarChart) getTitleFontSize() float64 {
|
func (sbc StackedBarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := util.Math.MinInt(sbc.GetWidth(), sbc.GetHeight())
|
effectiveDimension := MinInt(sbc.GetWidth(), sbc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
57
stringutil.go
Normal file
57
stringutil.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// SplitCSV splits a corpus by the `,`, dropping leading or trailing whitespace unless quoted.
|
||||||
|
func SplitCSV(text string) (output []string) {
|
||||||
|
if len(text) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state int
|
||||||
|
var word []rune
|
||||||
|
var opened rune
|
||||||
|
for _, r := range text {
|
||||||
|
switch state {
|
||||||
|
case 0: // word
|
||||||
|
if isQuote(r) {
|
||||||
|
opened = r
|
||||||
|
state = 1
|
||||||
|
} else if isCSVDelim(r) {
|
||||||
|
output = append(output, strings.TrimSpace(string(word)))
|
||||||
|
word = nil
|
||||||
|
} else {
|
||||||
|
word = append(word, r)
|
||||||
|
}
|
||||||
|
case 1: // we're in a quoted section
|
||||||
|
if matchesQuote(opened, r) {
|
||||||
|
state = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
word = append(word, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(word) > 0 {
|
||||||
|
output = append(output, strings.TrimSpace(string(word)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCSVDelim(r rune) bool {
|
||||||
|
return r == rune(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuote(r rune) bool {
|
||||||
|
return r == '"' || r == '\'' || r == '“' || r == '”' || r == '`'
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesQuote(a, b rune) bool {
|
||||||
|
if a == '“' && b == '”' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a == '”' && b == '“' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return a == b
|
||||||
|
}
|
22
stringutil_test.go
Normal file
22
stringutil_test.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitCSV(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.Empty(SplitCSV(""))
|
||||||
|
assert.Equal([]string{"foo"}, SplitCSV("foo"))
|
||||||
|
assert.Equal([]string{"foo", "bar"}, SplitCSV("foo,bar"))
|
||||||
|
assert.Equal([]string{"foo", "bar"}, SplitCSV("foo, bar"))
|
||||||
|
assert.Equal([]string{"foo", "bar"}, SplitCSV(" foo , bar "))
|
||||||
|
assert.Equal([]string{"foo", "bar", "baz"}, SplitCSV("foo,bar,baz"))
|
||||||
|
assert.Equal([]string{"foo", "bar", "baz,buzz"}, SplitCSV("foo,bar,\"baz,buzz\""))
|
||||||
|
assert.Equal([]string{"foo", "bar", "baz,'buzz'"}, SplitCSV("foo,bar,\"baz,'buzz'\""))
|
||||||
|
assert.Equal([]string{"foo", "bar", "baz,'buzz"}, SplitCSV("foo,bar,\"baz,'buzz\""))
|
||||||
|
assert.Equal([]string{"foo", "bar", "baz,\"buzz\""}, SplitCSV("foo,bar,'baz,\"buzz\"'"))
|
||||||
|
}
|
4
style.go
4
style.go
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -164,6 +163,7 @@ func (s Style) String() string {
|
||||||
return "{" + strings.Join(output, ", ") + "}"
|
return "{" + strings.Join(output, ", ") + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClassName returns the class name or a default.
|
||||||
func (s Style) GetClassName(defaults ...string) string {
|
func (s Style) GetClassName(defaults ...string) string {
|
||||||
if s.ClassName == "" {
|
if s.ClassName == "" {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
|
@ -351,7 +351,7 @@ func (s Style) WriteToRenderer(r Renderer) {
|
||||||
|
|
||||||
r.ClearTextRotation()
|
r.ClearTextRotation()
|
||||||
if s.GetTextRotationDegrees() != 0 {
|
if s.GetTextRotationDegrees() != 0 {
|
||||||
r.SetTextRotation(util.Math.DegreesToRadians(s.GetTextRotationDegrees()))
|
r.SetTextRotation(DegreesToRadians(s.GetTextRotationDegrees()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
text.go
4
text.go
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextHorizontalAlign is an enum for the horizontal alignment options.
|
// TextHorizontalAlign is an enum for the horizontal alignment options.
|
||||||
|
@ -149,7 +147,7 @@ func (t text) MeasureLines(r Renderer, lines []string, style Style) Box {
|
||||||
var output Box
|
var output Box
|
||||||
for index, line := range lines {
|
for index, line := range lines {
|
||||||
lineBox := r.MeasureText(line)
|
lineBox := r.MeasureText(line)
|
||||||
output.Right = util.Math.MaxInt(lineBox.Right, output.Right)
|
output.Right = MaxInt(lineBox.Right, output.Right)
|
||||||
output.Bottom += lineBox.Height()
|
output.Bottom += lineBox.Height()
|
||||||
if index < len(lines)-1 {
|
if index < len(lines)-1 {
|
||||||
output.Bottom += +style.GetTextLineSpacing()
|
output.Bottom += +style.GetTextLineSpacing()
|
||||||
|
|
10
tick.go
10
tick.go
|
@ -4,8 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TicksProvider is a type that provides ticks.
|
// TicksProvider is a type that provides ticks.
|
||||||
|
@ -85,15 +83,15 @@ func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style,
|
||||||
rangeDelta := math.Abs(max - min)
|
rangeDelta := math.Abs(max - min)
|
||||||
tickStep := rangeDelta / float64(intermediateTickCount)
|
tickStep := rangeDelta / float64(intermediateTickCount)
|
||||||
|
|
||||||
roundTo := util.Math.GetRoundToForDelta(rangeDelta) / 10
|
roundTo := GetRoundToForDelta(rangeDelta) / 10
|
||||||
intermediateTickCount = util.Math.MinInt(intermediateTickCount, DefaultTickCountSanityCheck)
|
intermediateTickCount = MinInt(intermediateTickCount, DefaultTickCountSanityCheck)
|
||||||
|
|
||||||
for x := 1; x < intermediateTickCount; x++ {
|
for x := 1; x < intermediateTickCount; x++ {
|
||||||
var tickValue float64
|
var tickValue float64
|
||||||
if ra.IsDescending() {
|
if ra.IsDescending() {
|
||||||
tickValue = max - util.Math.RoundUp(tickStep*float64(x), roundTo)
|
tickValue = max - RoundUp(tickStep*float64(x), roundTo)
|
||||||
} else {
|
} else {
|
||||||
tickValue = min + util.Math.RoundUp(tickStep*float64(x), roundTo)
|
tickValue = min + RoundUp(tickStep*float64(x), roundTo)
|
||||||
}
|
}
|
||||||
ticks = append(ticks, Tick{
|
ticks = append(ticks, Tick{
|
||||||
Value: tickValue,
|
Value: tickValue,
|
||||||
|
|
|
@ -3,8 +3,6 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
|
@ -43,21 +41,21 @@ func (ts TimeSeries) Len() int {
|
||||||
|
|
||||||
// GetValues gets x, y values at a given index.
|
// GetValues gets x, y values at a given index.
|
||||||
func (ts TimeSeries) GetValues(index int) (x, y float64) {
|
func (ts TimeSeries) GetValues(index int) (x, y float64) {
|
||||||
x = util.Time.ToFloat64(ts.XValues[index])
|
x = TimeToFloat64(ts.XValues[index])
|
||||||
y = ts.YValues[index]
|
y = ts.YValues[index]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues gets the first values.
|
// GetFirstValues gets the first values.
|
||||||
func (ts TimeSeries) GetFirstValues() (x, y float64) {
|
func (ts TimeSeries) GetFirstValues() (x, y float64) {
|
||||||
x = util.Time.ToFloat64(ts.XValues[0])
|
x = TimeToFloat64(ts.XValues[0])
|
||||||
y = ts.YValues[0]
|
y = ts.YValues[0]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastValues gets the last values.
|
// GetLastValues gets the last values.
|
||||||
func (ts TimeSeries) GetLastValues() (x, y float64) {
|
func (ts TimeSeries) GetLastValues() (x, y float64) {
|
||||||
x = util.Time.ToFloat64(ts.XValues[len(ts.XValues)-1])
|
x = TimeToFloat64(ts.XValues[len(ts.XValues)-1])
|
||||||
y = ts.YValues[len(ts.YValues)-1]
|
y = ts.YValues[len(ts.YValues)-1]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
105
timeutil.go
Normal file
105
timeutil.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SecondsPerXYZ
|
||||||
|
const (
|
||||||
|
SecondsPerHour = 60 * 60
|
||||||
|
SecondsPerDay = 60 * 60 * 24
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeMillis returns a duration as a float millis.
|
||||||
|
func TimeMillis(d time.Duration) float64 {
|
||||||
|
return float64(d) / float64(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffHours returns the difference in hours between two times.
|
||||||
|
func DiffHours(t1, t2 time.Time) (hours int) {
|
||||||
|
t1n := t1.Unix()
|
||||||
|
t2n := t2.Unix()
|
||||||
|
var diff int64
|
||||||
|
if t1n > t2n {
|
||||||
|
diff = t1n - t2n
|
||||||
|
} else {
|
||||||
|
diff = t2n - t1n
|
||||||
|
}
|
||||||
|
return int(diff / (SecondsPerHour))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeMin returns the minimum and maximum times in a given range.
|
||||||
|
func TimeMin(times ...time.Time) (min time.Time) {
|
||||||
|
if len(times) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
min = times[0]
|
||||||
|
for index := 1; index < len(times); index++ {
|
||||||
|
if times[index].Before(min) {
|
||||||
|
min = times[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeMax returns the minimum and maximum times in a given range.
|
||||||
|
func TimeMax(times ...time.Time) (max time.Time) {
|
||||||
|
if len(times) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
max = times[0]
|
||||||
|
|
||||||
|
for index := 1; index < len(times); index++ {
|
||||||
|
if times[index].After(max) {
|
||||||
|
max = times[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeMinMax returns the minimum and maximum times in a given range.
|
||||||
|
func TimeMinMax(times ...time.Time) (min, max time.Time) {
|
||||||
|
if len(times) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
min = times[0]
|
||||||
|
max = times[0]
|
||||||
|
|
||||||
|
for index := 1; index < len(times); index++ {
|
||||||
|
if times[index].Before(min) {
|
||||||
|
min = times[index]
|
||||||
|
}
|
||||||
|
if times[index].After(max) {
|
||||||
|
max = times[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeToFloat64 returns a float64 representation of a time.
|
||||||
|
func TimeToFloat64(t time.Time) float64 {
|
||||||
|
return float64(t.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeDescending sorts a given list of times ascending, or min to max.
|
||||||
|
type TimeDescending []time.Time
|
||||||
|
|
||||||
|
// Len implements sort.Sorter
|
||||||
|
func (d TimeDescending) Len() int { return len(d) }
|
||||||
|
|
||||||
|
// Swap implements sort.Sorter
|
||||||
|
func (d TimeDescending) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||||
|
|
||||||
|
// Less implements sort.Sorter
|
||||||
|
func (d TimeDescending) Less(i, j int) bool { return d[i].After(d[j]) }
|
||||||
|
|
||||||
|
// TimeAscending sorts a given list of times ascending, or min to max.
|
||||||
|
type TimeAscending []time.Time
|
||||||
|
|
||||||
|
// Len implements sort.Sorter
|
||||||
|
func (a TimeAscending) Len() int { return len(a) }
|
||||||
|
|
||||||
|
// Swap implements sort.Sorter
|
||||||
|
func (a TimeAscending) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
// Less implements sort.Sorter
|
||||||
|
func (a TimeAscending) Less(i, j int) bool { return a[i].Before(a[j]) }
|
186
util/date.go
186
util/date.go
|
@ -1,186 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AllDaysMask is a bitmask of all the days of the week.
|
|
||||||
AllDaysMask = 1<<uint(time.Sunday) | 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday) | 1<<uint(time.Saturday)
|
|
||||||
// WeekDaysMask is a bitmask of all the weekdays of the week.
|
|
||||||
WeekDaysMask = 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday)
|
|
||||||
//WeekendDaysMask is a bitmask of the weekend days of the week.
|
|
||||||
WeekendDaysMask = 1<<uint(time.Sunday) | 1<<uint(time.Saturday)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DaysOfWeek are all the time.Weekday in an array for utility purposes.
|
|
||||||
DaysOfWeek = []time.Weekday{
|
|
||||||
time.Sunday,
|
|
||||||
time.Monday,
|
|
||||||
time.Tuesday,
|
|
||||||
time.Wednesday,
|
|
||||||
time.Thursday,
|
|
||||||
time.Friday,
|
|
||||||
time.Saturday,
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeekDays are the business time.Weekday in an array.
|
|
||||||
WeekDays = []time.Weekday{
|
|
||||||
time.Monday,
|
|
||||||
time.Tuesday,
|
|
||||||
time.Wednesday,
|
|
||||||
time.Thursday,
|
|
||||||
time.Friday,
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeekendDays are the weekend time.Weekday in an array.
|
|
||||||
WeekendDays = []time.Weekday{
|
|
||||||
time.Sunday,
|
|
||||||
time.Saturday,
|
|
||||||
}
|
|
||||||
|
|
||||||
//Epoch is unix epoc saved for utility purposes.
|
|
||||||
Epoch = time.Unix(0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Date contains utility functions that operate on dates.
|
|
||||||
var Date date
|
|
||||||
|
|
||||||
type date struct{}
|
|
||||||
|
|
||||||
func (d date) MustEastern() *time.Location {
|
|
||||||
if eastern, err := d.Eastern(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return eastern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eastern returns the eastern timezone.
|
|
||||||
func (d date) Eastern() (*time.Location, error) {
|
|
||||||
// Try POSIX
|
|
||||||
est, err := time.LoadLocation("America/New_York")
|
|
||||||
if err != nil {
|
|
||||||
// Try Windows
|
|
||||||
est, err = time.LoadLocation("EST")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return est, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d date) MustPacific() *time.Location {
|
|
||||||
if pst, err := d.Pacific(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return pst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pacific returns the pacific timezone.
|
|
||||||
func (d date) Pacific() (*time.Location, error) {
|
|
||||||
// Try POSIX
|
|
||||||
pst, err := time.LoadLocation("America/Los_Angeles")
|
|
||||||
if err != nil {
|
|
||||||
// Try Windows
|
|
||||||
pst, err = time.LoadLocation("PST")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeUTC returns a new time.Time for the given clock components in UTC.
|
|
||||||
// It is meant to be used with the `OnDate` function.
|
|
||||||
func (d date) TimeUTC(hour, min, sec, nsec int) time.Time {
|
|
||||||
return time.Date(0, 0, 0, hour, min, sec, nsec, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns a new time.Time for the given clock components.
|
|
||||||
// It is meant to be used with the `OnDate` function.
|
|
||||||
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
|
||||||
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DateUTC returns a new time.Time for the given date comonents at (noon) in UTC.
|
|
||||||
func (d date) DateUTC(year, month, day int) time.Time {
|
|
||||||
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DateUTC returns a new time.Time for the given date comonents at (noon) in a given location.
|
|
||||||
func (d date) Date(year, month, day int, loc *time.Location) time.Time {
|
|
||||||
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnDate returns the clock components of clock (hour,minute,second) on the date components of d.
|
|
||||||
func (d date) OnDate(clock, date time.Time) time.Time {
|
|
||||||
tzAdjusted := date.In(clock.Location())
|
|
||||||
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoonOnDate is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
|
|
||||||
func (d date) NoonOnDate(cd time.Time) time.Time {
|
|
||||||
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWeekDay returns if the day is a monday->friday.
|
|
||||||
func (d date) IsWeekDay(day time.Weekday) bool {
|
|
||||||
return !d.IsWeekendDay(day)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWeekendDay returns if the day is a monday->friday.
|
|
||||||
func (d date) IsWeekendDay(day time.Weekday) bool {
|
|
||||||
return day == time.Saturday || day == time.Sunday
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
|
|
||||||
func (d date) Before(before, reference time.Time) bool {
|
|
||||||
tzAdjustedBefore := before.In(reference.Location())
|
|
||||||
if tzAdjustedBefore.Year() < reference.Year() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if tzAdjustedBefore.Month() < reference.Month() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < reference.Day()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
_secondsPerHour = 60 * 60
|
|
||||||
_secondsPerDay = 60 * 60 * 24
|
|
||||||
)
|
|
||||||
|
|
||||||
// NextDay returns the timestamp advanced a day.
|
|
||||||
func (d date) NextDay(ts time.Time) time.Time {
|
|
||||||
return ts.AddDate(0, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextHour returns the next timestamp on the hour.
|
|
||||||
func (d date) NextHour(ts time.Time) time.Time {
|
|
||||||
//advance a full hour ...
|
|
||||||
advanced := ts.Add(time.Hour)
|
|
||||||
minutes := time.Duration(advanced.Minute()) * time.Minute
|
|
||||||
final := advanced.Add(-minutes)
|
|
||||||
return time.Date(final.Year(), final.Month(), final.Day(), final.Hour(), 0, 0, 0, final.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextDayOfWeek returns the next instance of a given weekday after a given timestamp.
|
|
||||||
func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time {
|
|
||||||
afterWeekday := after.Weekday()
|
|
||||||
if afterWeekday == dayOfWeek {
|
|
||||||
return after.AddDate(0, 0, 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1 vs 5 ~ add 4 days
|
|
||||||
if afterWeekday < dayOfWeek {
|
|
||||||
dayDelta := int(dayOfWeek - afterWeekday)
|
|
||||||
return after.AddDate(0, 0, dayDelta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5 vs 1, add 7-(5-1) ~ 3 days
|
|
||||||
dayDelta := 7 - int(afterWeekday-dayOfWeek)
|
|
||||||
return after.AddDate(0, 0, dayDelta)
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parse(v string) time.Time {
|
|
||||||
ts, _ := time.Parse("2006-01-02", v)
|
|
||||||
return ts
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateEastern(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
eastern, err := Date.Eastern()
|
|
||||||
assert.Nil(err)
|
|
||||||
assert.NotNil(eastern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTime(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
ts := Date.Time(5, 6, 7, 8, time.UTC)
|
|
||||||
assert.Equal(05, ts.Hour())
|
|
||||||
assert.Equal(06, ts.Minute())
|
|
||||||
assert.Equal(07, ts.Second())
|
|
||||||
assert.Equal(8, ts.Nanosecond())
|
|
||||||
assert.Equal(time.UTC, ts.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateDate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
ts := Date.Date(2015, 5, 6, time.UTC)
|
|
||||||
assert.Equal(2015, ts.Year())
|
|
||||||
assert.Equal(5, ts.Month())
|
|
||||||
assert.Equal(6, ts.Day())
|
|
||||||
assert.Equal(time.UTC, ts.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateOnDate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
eastern := Date.MustEastern()
|
|
||||||
assert.NotNil(eastern)
|
|
||||||
|
|
||||||
ts := Date.OnDate(Date.Time(5, 4, 3, 2, time.UTC), Date.Date(2016, 6, 7, eastern))
|
|
||||||
assert.Equal(2016, ts.Year())
|
|
||||||
assert.Equal(6, ts.Month())
|
|
||||||
assert.Equal(7, ts.Day())
|
|
||||||
assert.Equal(5, ts.Hour())
|
|
||||||
assert.Equal(4, ts.Minute())
|
|
||||||
assert.Equal(3, ts.Second())
|
|
||||||
assert.Equal(2, ts.Nanosecond())
|
|
||||||
assert.Equal(time.UTC, ts.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateNoonOnDate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
noon := Date.NoonOnDate(time.Date(2016, 04, 03, 02, 01, 0, 0, time.UTC))
|
|
||||||
|
|
||||||
assert.Equal(2016, noon.Year())
|
|
||||||
assert.Equal(4, noon.Month())
|
|
||||||
assert.Equal(3, noon.Day())
|
|
||||||
assert.Equal(12, noon.Hour())
|
|
||||||
assert.Equal(0, noon.Minute())
|
|
||||||
assert.Equal(time.UTC, noon.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateBefore(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
assert.True(Date.Before(parse("2015-07-02"), parse("2016-07-01")))
|
|
||||||
assert.True(Date.Before(parse("2016-06-01"), parse("2016-07-01")))
|
|
||||||
assert.True(Date.Before(parse("2016-07-01"), parse("2016-07-02")))
|
|
||||||
|
|
||||||
assert.False(Date.Before(parse("2016-07-01"), parse("2016-07-01")))
|
|
||||||
assert.False(Date.Before(parse("2016-07-03"), parse("2016-07-01")))
|
|
||||||
assert.False(Date.Before(parse("2016-08-03"), parse("2016-07-01")))
|
|
||||||
assert.False(Date.Before(parse("2017-08-03"), parse("2016-07-01")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateBeforeHandlesTimezones(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
tuesdayUTC := time.Date(2016, 8, 02, 22, 00, 0, 0, time.UTC)
|
|
||||||
mondayUTC := time.Date(2016, 8, 01, 1, 00, 0, 0, time.UTC)
|
|
||||||
sundayEST := time.Date(2016, 7, 31, 22, 00, 0, 0, Date.MustEastern())
|
|
||||||
|
|
||||||
assert.True(Date.Before(sundayEST, tuesdayUTC))
|
|
||||||
assert.False(Date.Before(sundayEST, mondayUTC))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateNextHour(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.MustEastern())
|
|
||||||
next := Date.NextHour(start)
|
|
||||||
assert.Equal(2015, next.Year())
|
|
||||||
assert.Equal(07, next.Month())
|
|
||||||
assert.Equal(01, next.Day())
|
|
||||||
assert.Equal(10, next.Hour())
|
|
||||||
assert.Equal(00, next.Minute())
|
|
||||||
|
|
||||||
next = Date.NextHour(next)
|
|
||||||
assert.Equal(11, next.Hour())
|
|
||||||
|
|
||||||
next = Date.NextHour(next)
|
|
||||||
assert.Equal(12, next.Hour())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateNextDayOfWeek(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
weds := Date.Date(2016, 8, 10, time.UTC)
|
|
||||||
fri := Date.Date(2016, 8, 12, time.UTC)
|
|
||||||
sun := Date.Date(2016, 8, 14, time.UTC)
|
|
||||||
mon := Date.Date(2016, 8, 15, time.UTC)
|
|
||||||
weds2 := Date.Date(2016, 8, 17, time.UTC)
|
|
||||||
|
|
||||||
nextFri := Date.NextDayOfWeek(weds, time.Friday)
|
|
||||||
nextSunday := Date.NextDayOfWeek(weds, time.Sunday)
|
|
||||||
nextMonday := Date.NextDayOfWeek(weds, time.Monday)
|
|
||||||
nextWeds := Date.NextDayOfWeek(weds, time.Wednesday)
|
|
||||||
|
|
||||||
assert.Equal(fri.Year(), nextFri.Year())
|
|
||||||
assert.Equal(fri.Month(), nextFri.Month())
|
|
||||||
assert.Equal(fri.Day(), nextFri.Day())
|
|
||||||
|
|
||||||
assert.Equal(sun.Year(), nextSunday.Year())
|
|
||||||
assert.Equal(sun.Month(), nextSunday.Month())
|
|
||||||
assert.Equal(sun.Day(), nextSunday.Day())
|
|
||||||
|
|
||||||
assert.Equal(mon.Year(), nextMonday.Year())
|
|
||||||
assert.Equal(mon.Month(), nextMonday.Month())
|
|
||||||
assert.Equal(mon.Day(), nextMonday.Day())
|
|
||||||
|
|
||||||
assert.Equal(weds2.Year(), nextWeds.Year())
|
|
||||||
assert.Equal(weds2.Month(), nextWeds.Month())
|
|
||||||
assert.Equal(weds2.Day(), nextWeds.Day())
|
|
||||||
|
|
||||||
assert.Equal(time.UTC, nextFri.Location())
|
|
||||||
assert.Equal(time.UTC, nextSunday.Location())
|
|
||||||
assert.Equal(time.UTC, nextMonday.Location())
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// File contains file utility functions
|
|
||||||
File = fileUtil{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileUtil struct{}
|
|
||||||
|
|
||||||
// ReadByLines reads a file and calls the handler for each line.
|
|
||||||
func (fu fileUtil) ReadByLines(filePath string, handler func(line string) error) error {
|
|
||||||
var f *os.File
|
|
||||||
var err error
|
|
||||||
if f, err = os.Open(filePath); err == nil {
|
|
||||||
defer f.Close()
|
|
||||||
var line string
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line = scanner.Text()
|
|
||||||
err = handler(line)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByChunks reads a file in `chunkSize` pieces, dispatched to the handler.
|
|
||||||
func (fu fileUtil) ReadByChunks(filePath string, chunkSize int, handler func(line []byte) error) error {
|
|
||||||
var f *os.File
|
|
||||||
var err error
|
|
||||||
if f, err = os.Open(filePath); err == nil {
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
chunk := make([]byte, chunkSize)
|
|
||||||
for {
|
|
||||||
readBytes, err := f.Read(chunk)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
readData := chunk[:readBytes]
|
|
||||||
err = handler(readData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,151 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMinAndMax(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []float64{1.0, 2.0, 3.0, 4.0}
|
|
||||||
min, max := Math.MinAndMax(values...)
|
|
||||||
assert.Equal(1.0, min)
|
|
||||||
assert.Equal(4.0, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinAndMaxReversed(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []float64{4.0, 2.0, 3.0, 1.0}
|
|
||||||
min, max := Math.MinAndMax(values...)
|
|
||||||
assert.Equal(1.0, min)
|
|
||||||
assert.Equal(4.0, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinAndMaxEmpty(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []float64{}
|
|
||||||
min, max := Math.MinAndMax(values...)
|
|
||||||
assert.Equal(0.0, min)
|
|
||||||
assert.Equal(0.0, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRoundToForDelta(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
assert.Equal(100.0, Math.GetRoundToForDelta(1001.00))
|
|
||||||
assert.Equal(10.0, Math.GetRoundToForDelta(101.00))
|
|
||||||
assert.Equal(1.0, Math.GetRoundToForDelta(11.00))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoundUp(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
assert.Equal(0.5, Math.RoundUp(0.49, 0.1))
|
|
||||||
assert.Equal(1.0, Math.RoundUp(0.51, 1.0))
|
|
||||||
assert.Equal(0.4999, Math.RoundUp(0.49988, 0.0001))
|
|
||||||
assert.Equal(0.123, Math.RoundUp(0.123, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoundDown(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
assert.Equal(0.5, Math.RoundDown(0.51, 0.1))
|
|
||||||
assert.Equal(1.0, Math.RoundDown(1.01, 1.0))
|
|
||||||
assert.Equal(0.5001, Math.RoundDown(0.50011, 0.0001))
|
|
||||||
assert.Equal(0.123, Math.RoundDown(0.123, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPercentDifference(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
assert.Equal(0.5, Math.PercentDifference(1.0, 1.5))
|
|
||||||
assert.Equal(-0.5, Math.PercentDifference(2.0, 1.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalize(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
values := []float64{10, 9, 8, 7, 6}
|
|
||||||
normalized := Math.Normalize(values...)
|
|
||||||
assert.Len(normalized, 5)
|
|
||||||
assert.Equal(0.25, normalized[0])
|
|
||||||
assert.Equal(0.1499, normalized[4])
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_degreesToRadians = map[float64]float64{
|
|
||||||
0: 0, // !_2pi b/c no irrational nums in floats.
|
|
||||||
45: _pi4,
|
|
||||||
90: _pi2,
|
|
||||||
135: _3pi4,
|
|
||||||
180: _pi,
|
|
||||||
225: _5pi4,
|
|
||||||
270: _3pi2,
|
|
||||||
315: _7pi4,
|
|
||||||
}
|
|
||||||
|
|
||||||
_compassToRadians = map[float64]float64{
|
|
||||||
0: _pi2,
|
|
||||||
45: _pi4,
|
|
||||||
90: 0, // !_2pi b/c no irrational nums in floats.
|
|
||||||
135: _7pi4,
|
|
||||||
180: _3pi2,
|
|
||||||
225: _5pi4,
|
|
||||||
270: _pi,
|
|
||||||
315: _3pi4,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDegreesToRadians(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
for d, r := range _degreesToRadians {
|
|
||||||
assert.Equal(r, Math.DegreesToRadians(d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPercentToRadians(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
for d, r := range _degreesToRadians {
|
|
||||||
assert.Equal(r, Math.PercentToRadians(d/360.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRadiansToDegrees(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
for d, r := range _degreesToRadians {
|
|
||||||
assert.Equal(d, Math.RadiansToDegrees(r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRadianAdd(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
assert.Equal(_pi, Math.RadianAdd(_pi2, _pi2))
|
|
||||||
assert.Equal(_3pi2, Math.RadianAdd(_pi2, _pi))
|
|
||||||
assert.Equal(_pi, Math.RadianAdd(_pi, _2pi))
|
|
||||||
assert.Equal(_pi, Math.RadianAdd(_pi, -_2pi))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRotateCoordinate90(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
cx, cy := 10, 10
|
|
||||||
x, y := 5, 10
|
|
||||||
|
|
||||||
rx, ry := Math.RotateCoordinate(cx, cy, x, y, Math.DegreesToRadians(90))
|
|
||||||
assert.Equal(10, rx)
|
|
||||||
assert.Equal(5, ry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRotateCoordinate45(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
cx, cy := 10, 10
|
|
||||||
x, y := 5, 10
|
|
||||||
|
|
||||||
rx, ry := Math.RotateCoordinate(cx, cy, x, y, Math.DegreesToRadians(45))
|
|
||||||
assert.Equal(7, rx)
|
|
||||||
assert.Equal(7, ry)
|
|
||||||
}
|
|
99
util/time.go
99
util/time.go
|
@ -1,99 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Time contains time utility functions.
|
|
||||||
Time = timeUtil{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type timeUtil struct{}
|
|
||||||
|
|
||||||
// Millis returns the duration as milliseconds.
|
|
||||||
func (tu timeUtil) Millis(d time.Duration) float64 {
|
|
||||||
return float64(d) / float64(time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeToFloat64 returns a float64 representation of a time.
|
|
||||||
func (tu timeUtil) ToFloat64(t time.Time) float64 {
|
|
||||||
return float64(t.UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64ToTime returns a time from a float64.
|
|
||||||
func (tu timeUtil) FromFloat64(tf float64) time.Time {
|
|
||||||
return time.Unix(0, int64(tf))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tu timeUtil) DiffDays(t1, t2 time.Time) (days int) {
|
|
||||||
t1n := t1.Unix()
|
|
||||||
t2n := t2.Unix()
|
|
||||||
var diff int64
|
|
||||||
if t1n > t2n {
|
|
||||||
diff = t1n - t2n //yields seconds
|
|
||||||
} else {
|
|
||||||
diff = t2n - t1n //yields seconds
|
|
||||||
}
|
|
||||||
return int(diff / (_secondsPerDay))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tu timeUtil) DiffHours(t1, t2 time.Time) (hours int) {
|
|
||||||
t1n := t1.Unix()
|
|
||||||
t2n := t2.Unix()
|
|
||||||
var diff int64
|
|
||||||
if t1n > t2n {
|
|
||||||
diff = t1n - t2n
|
|
||||||
} else {
|
|
||||||
diff = t2n - t1n
|
|
||||||
}
|
|
||||||
return int(diff / (_secondsPerHour))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start returns the earliest (min) time in a list of times.
|
|
||||||
func (tu timeUtil) Start(times ...time.Time) time.Time {
|
|
||||||
if len(times) == 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := times[0]
|
|
||||||
for _, t := range times[1:] {
|
|
||||||
if t.Before(start) {
|
|
||||||
start = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start returns the earliest (min) time in a list of times.
|
|
||||||
func (tu timeUtil) End(times ...time.Time) time.Time {
|
|
||||||
if len(times) == 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := times[0]
|
|
||||||
for _, t := range times[1:] {
|
|
||||||
if t.After(end) {
|
|
||||||
end = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartAndEnd returns the start and end of a given set of time in one pass.
|
|
||||||
func (tu timeUtil) StartAndEnd(values ...time.Time) (start time.Time, end time.Time) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
start = values[0]
|
|
||||||
end = values[0]
|
|
||||||
|
|
||||||
for _, v := range values[1:] {
|
|
||||||
if end.Before(v) {
|
|
||||||
end = v
|
|
||||||
}
|
|
||||||
if start.After(v) {
|
|
||||||
start = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTimeDiffDays(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
|
||||||
t2 := time.Date(2017, 01, 10, 3, 0, 0, 0, time.UTC)
|
|
||||||
t3 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
assert.Equal(48, Time.DiffDays(t2, t1))
|
|
||||||
assert.Equal(2, Time.DiffDays(t3, t1)) // technically we should round down.
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeDiffHours(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
|
||||||
t2 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
|
||||||
t3 := time.Date(2017, 02, 28, 12, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
assert.Equal(68, Time.DiffHours(t2, t1))
|
|
||||||
assert.Equal(24, Time.DiffHours(t1, t3))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeStartAndEnd(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []time.Time{
|
|
||||||
time.Now().AddDate(0, 0, -1),
|
|
||||||
time.Now().AddDate(0, 0, -2),
|
|
||||||
time.Now().AddDate(0, 0, -3),
|
|
||||||
time.Now().AddDate(0, 0, -4),
|
|
||||||
}
|
|
||||||
min, max := Time.StartAndEnd(values...)
|
|
||||||
assert.Equal(values[3], min)
|
|
||||||
assert.Equal(values[0], max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeStartAndEndReversed(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []time.Time{
|
|
||||||
time.Now().AddDate(0, 0, -4),
|
|
||||||
time.Now().AddDate(0, 0, -2),
|
|
||||||
time.Now().AddDate(0, 0, -3),
|
|
||||||
time.Now().AddDate(0, 0, -1),
|
|
||||||
}
|
|
||||||
min, max := Time.StartAndEnd(values...)
|
|
||||||
assert.Equal(values[0], min)
|
|
||||||
assert.Equal(values[3], max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeStartAndEndEmpty(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
values := []time.Time{}
|
|
||||||
min, max := Time.StartAndEnd(values...)
|
|
||||||
assert.Equal(time.Time{}, min)
|
|
||||||
assert.Equal(time.Time{}, max)
|
|
||||||
}
|
|
6
value.go
6
value.go
|
@ -1,7 +1,5 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import util "github.com/wcharczuk/go-chart/util"
|
|
||||||
|
|
||||||
// Value is a chart value.
|
// Value is a chart value.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Style Style
|
Style Style
|
||||||
|
@ -23,7 +21,7 @@ func (vs Values) Values() []float64 {
|
||||||
|
|
||||||
// ValuesNormalized returns normalized values.
|
// ValuesNormalized returns normalized values.
|
||||||
func (vs Values) ValuesNormalized() []float64 {
|
func (vs Values) ValuesNormalized() []float64 {
|
||||||
return util.Math.Normalize(vs.Values()...)
|
return Normalize(vs.Values()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize returns the values normalized.
|
// Normalize returns the values normalized.
|
||||||
|
@ -40,7 +38,7 @@ func (vs Values) Normalize() []Value {
|
||||||
output = append(output, Value{
|
output = append(output, Value{
|
||||||
Style: v.Style,
|
Style: v.Style,
|
||||||
Label: v.Label,
|
Label: v.Label,
|
||||||
Value: util.Math.RoundDown(v.Value/total, 0.0001),
|
Value: RoundDown(v.Value/total, 0.0001),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package seq
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,19 +12,15 @@ const (
|
||||||
bufferDefaultCapacity = 4
|
bufferDefaultCapacity = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// NewValueBuffer creates a new value buffer with an optional set of values.
|
||||||
emptyArray = make([]float64, 0)
|
func NewValueBuffer(values ...float64) *ValueBuffer {
|
||||||
)
|
|
||||||
|
|
||||||
// NewBuffer creates a new value buffer with an optional set of values.
|
|
||||||
func NewBuffer(values ...float64) *Buffer {
|
|
||||||
var tail int
|
var tail int
|
||||||
array := make([]float64, util.Math.MaxInt(len(values), bufferDefaultCapacity))
|
array := make([]float64, MaxInt(len(values), bufferDefaultCapacity))
|
||||||
if len(values) > 0 {
|
if len(values) > 0 {
|
||||||
copy(array, values)
|
copy(array, values)
|
||||||
tail = len(values)
|
tail = len(values)
|
||||||
}
|
}
|
||||||
return &Buffer{
|
return &ValueBuffer{
|
||||||
array: array,
|
array: array,
|
||||||
head: 0,
|
head: 0,
|
||||||
tail: tail,
|
tail: tail,
|
||||||
|
@ -34,9 +28,9 @@ func NewBuffer(values ...float64) *Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
// NewValueBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
||||||
func NewBufferWithCapacity(capacity int) *Buffer {
|
func NewValueBufferWithCapacity(capacity int) *ValueBuffer {
|
||||||
return &Buffer{
|
return &ValueBuffer{
|
||||||
array: make([]float64, capacity),
|
array: make([]float64, capacity),
|
||||||
head: 0,
|
head: 0,
|
||||||
tail: 0,
|
tail: 0,
|
||||||
|
@ -44,11 +38,11 @@ func NewBufferWithCapacity(capacity int) *Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer is a fifo datastructure that is backed by a pre-allocated array.
|
// ValueBuffer is a fifo datastructure that is backed by a pre-allocated array.
|
||||||
// Instead of allocating a whole new node object for each element, array elements are re-used (which saves GC churn).
|
// Instead of allocating a whole new node object for each element, array elements are re-used (which saves GC churn).
|
||||||
// Enqueue can be O(n), Dequeue is generally O(1).
|
// Enqueue can be O(n), Dequeue is generally O(1).
|
||||||
// Buffer implements `seq.Provider`
|
// Buffer implements `seq.Provider`
|
||||||
type Buffer struct {
|
type ValueBuffer struct {
|
||||||
array []float64
|
array []float64
|
||||||
head int
|
head int
|
||||||
tail int
|
tail int
|
||||||
|
@ -57,23 +51,23 @@ type Buffer struct {
|
||||||
|
|
||||||
// Len returns the length of the Buffer (as it is currently populated).
|
// Len returns the length of the Buffer (as it is currently populated).
|
||||||
// Actual memory footprint may be different.
|
// Actual memory footprint may be different.
|
||||||
func (b *Buffer) Len() int {
|
func (b *ValueBuffer) Len() int {
|
||||||
return b.size
|
return b.size
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue implements seq provider.
|
// GetValue implements seq provider.
|
||||||
func (b *Buffer) GetValue(index int) float64 {
|
func (b *ValueBuffer) GetValue(index int) float64 {
|
||||||
effectiveIndex := (b.head + index) % len(b.array)
|
effectiveIndex := (b.head + index) % len(b.array)
|
||||||
return b.array[effectiveIndex]
|
return b.array[effectiveIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capacity returns the total size of the Buffer, including empty elements.
|
// Capacity returns the total size of the Buffer, including empty elements.
|
||||||
func (b *Buffer) Capacity() int {
|
func (b *ValueBuffer) Capacity() int {
|
||||||
return len(b.array)
|
return len(b.array)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCapacity sets the capacity of the Buffer.
|
// SetCapacity sets the capacity of the Buffer.
|
||||||
func (b *Buffer) SetCapacity(capacity int) {
|
func (b *ValueBuffer) SetCapacity(capacity int) {
|
||||||
newArray := make([]float64, capacity)
|
newArray := make([]float64, capacity)
|
||||||
if b.size > 0 {
|
if b.size > 0 {
|
||||||
if b.head < b.tail {
|
if b.head < b.tail {
|
||||||
|
@ -93,7 +87,7 @@ func (b *Buffer) SetCapacity(capacity int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear removes all objects from the Buffer.
|
// Clear removes all objects from the Buffer.
|
||||||
func (b *Buffer) Clear() {
|
func (b *ValueBuffer) Clear() {
|
||||||
b.array = make([]float64, bufferDefaultCapacity)
|
b.array = make([]float64, bufferDefaultCapacity)
|
||||||
b.head = 0
|
b.head = 0
|
||||||
b.tail = 0
|
b.tail = 0
|
||||||
|
@ -101,7 +95,7 @@ func (b *Buffer) Clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue adds an element to the "back" of the Buffer.
|
// Enqueue adds an element to the "back" of the Buffer.
|
||||||
func (b *Buffer) Enqueue(value float64) {
|
func (b *ValueBuffer) Enqueue(value float64) {
|
||||||
if b.size == len(b.array) {
|
if b.size == len(b.array) {
|
||||||
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
|
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
|
||||||
if newCapacity < (len(b.array) + bufferMinimumGrow) {
|
if newCapacity < (len(b.array) + bufferMinimumGrow) {
|
||||||
|
@ -116,7 +110,7 @@ func (b *Buffer) Enqueue(value float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dequeue removes the first element from the RingBuffer.
|
// Dequeue removes the first element from the RingBuffer.
|
||||||
func (b *Buffer) Dequeue() float64 {
|
func (b *ValueBuffer) Dequeue() float64 {
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -128,7 +122,7 @@ func (b *Buffer) Dequeue() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek returns but does not remove the first element.
|
// Peek returns but does not remove the first element.
|
||||||
func (b *Buffer) Peek() float64 {
|
func (b *ValueBuffer) Peek() float64 {
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -136,7 +130,7 @@ func (b *Buffer) Peek() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeekBack returns but does not remove the last element.
|
// PeekBack returns but does not remove the last element.
|
||||||
func (b *Buffer) PeekBack() float64 {
|
func (b *ValueBuffer) PeekBack() float64 {
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -147,7 +141,7 @@ func (b *Buffer) PeekBack() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimExcess resizes the capacity of the buffer to better fit the contents.
|
// TrimExcess resizes the capacity of the buffer to better fit the contents.
|
||||||
func (b *Buffer) TrimExcess() {
|
func (b *ValueBuffer) TrimExcess() {
|
||||||
threshold := float64(len(b.array)) * 0.9
|
threshold := float64(len(b.array)) * 0.9
|
||||||
if b.size < int(threshold) {
|
if b.size < int(threshold) {
|
||||||
b.SetCapacity(b.size)
|
b.SetCapacity(b.size)
|
||||||
|
@ -155,7 +149,7 @@ func (b *Buffer) TrimExcess() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array returns the ring buffer, in order, as an array.
|
// Array returns the ring buffer, in order, as an array.
|
||||||
func (b *Buffer) Array() Array {
|
func (b *ValueBuffer) Array() SeqArray {
|
||||||
newArray := make([]float64, b.size)
|
newArray := make([]float64, b.size)
|
||||||
|
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
|
@ -169,11 +163,11 @@ func (b *Buffer) Array() Array {
|
||||||
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array(newArray)
|
return SeqArray(newArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each calls the consumer for each element in the buffer.
|
// Each calls the consumer for each element in the buffer.
|
||||||
func (b *Buffer) Each(mapfn func(int, float64)) {
|
func (b *ValueBuffer) Each(mapfn func(int, float64)) {
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -197,7 +191,7 @@ func (b *Buffer) Each(mapfn func(int, float64)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string representation for value buffers.
|
// String returns a string representation for value buffers.
|
||||||
func (b *Buffer) String() string {
|
func (b *ValueBuffer) String() string {
|
||||||
var values []string
|
var values []string
|
||||||
for _, elem := range b.Array() {
|
for _, elem := range b.Array() {
|
||||||
values = append(values, fmt.Sprintf("%v", elem))
|
values = append(values, fmt.Sprintf("%v", elem))
|
|
@ -1,4 +1,4 @@
|
||||||
package seq
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -6,10 +6,10 @@ import (
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuffer(t *testing.T) {
|
func TestRingBuffer(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewBuffer()
|
buffer := NewRingBuffer()
|
||||||
|
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
assert.Equal(1, buffer.Len())
|
assert.Equal(1, buffer.Len())
|
||||||
|
@ -96,14 +96,14 @@ func TestBuffer(t *testing.T) {
|
||||||
value = buffer.Dequeue()
|
value = buffer.Dequeue()
|
||||||
assert.Equal(8, value)
|
assert.Equal(8, value)
|
||||||
assert.Equal(0, buffer.Len())
|
assert.Equal(0, buffer.Len())
|
||||||
assert.Zero(buffer.Peek())
|
assert.Nil(buffer.Peek())
|
||||||
assert.Zero(buffer.PeekBack())
|
assert.Nil(buffer.PeekBack())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferClear(t *testing.T) {
|
func TestRingBufferClear(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewBuffer()
|
buffer := NewRingBuffer()
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
|
@ -117,21 +117,21 @@ func TestBufferClear(t *testing.T) {
|
||||||
|
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
assert.Equal(0, buffer.Len())
|
assert.Equal(0, buffer.Len())
|
||||||
assert.Zero(buffer.Peek())
|
assert.Nil(buffer.Peek())
|
||||||
assert.Zero(buffer.PeekBack())
|
assert.Nil(buffer.PeekBack())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferArray(t *testing.T) {
|
func TestRingBufferContents(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewBuffer()
|
buffer := NewRingBuffer()
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
buffer.Enqueue(2)
|
buffer.Enqueue(2)
|
||||||
buffer.Enqueue(3)
|
buffer.Enqueue(3)
|
||||||
buffer.Enqueue(4)
|
buffer.Enqueue(4)
|
||||||
buffer.Enqueue(5)
|
buffer.Enqueue(5)
|
||||||
|
|
||||||
contents := buffer.Array()
|
contents := buffer.Contents()
|
||||||
assert.Len(contents, 5)
|
assert.Len(contents, 5)
|
||||||
assert.Equal(1, contents[0])
|
assert.Equal(1, contents[0])
|
||||||
assert.Equal(2, contents[1])
|
assert.Equal(2, contents[1])
|
||||||
|
@ -140,53 +140,145 @@ func TestBufferArray(t *testing.T) {
|
||||||
assert.Equal(5, contents[4])
|
assert.Equal(5, contents[4])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferEach(t *testing.T) {
|
func TestRingBufferDrain(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewBuffer()
|
buffer := NewRingBuffer()
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(2)
|
||||||
|
buffer.Enqueue(3)
|
||||||
|
buffer.Enqueue(4)
|
||||||
|
buffer.Enqueue(5)
|
||||||
|
|
||||||
|
contents := buffer.Drain()
|
||||||
|
assert.Len(contents, 5)
|
||||||
|
assert.Equal(1, contents[0])
|
||||||
|
assert.Equal(2, contents[1])
|
||||||
|
assert.Equal(3, contents[2])
|
||||||
|
assert.Equal(4, contents[3])
|
||||||
|
assert.Equal(5, contents[4])
|
||||||
|
|
||||||
|
assert.Equal(0, buffer.Len())
|
||||||
|
assert.Nil(buffer.Peek())
|
||||||
|
assert.Nil(buffer.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRingBufferEach(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewRingBuffer()
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
for x := 1; x < 17; x++ {
|
||||||
buffer.Enqueue(float64(x))
|
buffer.Enqueue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
called := 0
|
called := 0
|
||||||
buffer.Each(func(_ int, v float64) {
|
buffer.Each(func(v interface{}) {
|
||||||
if v == float64(called+1) {
|
if typed, isTyped := v.(int); isTyped {
|
||||||
|
if typed == (called + 1) {
|
||||||
called++
|
called++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(16, called)
|
assert.Equal(16, called)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBuffer(t *testing.T) {
|
func TestRingBufferEachUntil(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
empty := NewBuffer()
|
buffer := NewRingBuffer()
|
||||||
assert.NotNil(empty)
|
|
||||||
assert.Zero(empty.Len())
|
for x := 1; x < 17; x++ {
|
||||||
assert.Equal(bufferDefaultCapacity, empty.Capacity())
|
buffer.Enqueue(x)
|
||||||
assert.Zero(empty.Peek())
|
|
||||||
assert.Zero(empty.PeekBack())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBufferWithValues(t *testing.T) {
|
called := 0
|
||||||
|
buffer.EachUntil(func(v interface{}) bool {
|
||||||
|
if typed, isTyped := v.(int); isTyped {
|
||||||
|
if typed > 10 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if typed == (called + 1) {
|
||||||
|
called++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(10, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRingBufferReverseEachUntil(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
values := NewBuffer(1, 2, 3, 4, 5)
|
buffer := NewRingBufferWithCapacity(32)
|
||||||
assert.NotNil(values)
|
|
||||||
assert.Equal(5, values.Len())
|
for x := 1; x < 17; x++ {
|
||||||
assert.Equal(1, values.Peek())
|
buffer.Enqueue(x)
|
||||||
assert.Equal(5, values.PeekBack())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferGrowth(t *testing.T) {
|
var values []int
|
||||||
|
buffer.ReverseEachUntil(func(v interface{}) bool {
|
||||||
|
if typed, isTyped := v.(int); isTyped {
|
||||||
|
if typed < 10 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
values = append(values, typed)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
panic("value is not an integer")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(values, 7)
|
||||||
|
assert.Equal(16, values[0])
|
||||||
|
assert.Equal(10, values[6])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRingBufferReverseEachUntilUndersized(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
values := NewBuffer(1, 2, 3, 4, 5)
|
buffer := NewRingBuffer()
|
||||||
for i := 0; i < 1<<10; i++ {
|
|
||||||
values.Enqueue(float64(i))
|
for x := 1; x < 17; x++ {
|
||||||
|
buffer.Enqueue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(1<<10-1, values.PeekBack())
|
var values []int
|
||||||
|
buffer.ReverseEachUntil(func(v interface{}) bool {
|
||||||
|
if typed, isTyped := v.(int); isTyped {
|
||||||
|
if typed < 10 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
values = append(values, typed)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
panic("value is not an integer")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(values, 7)
|
||||||
|
assert.Equal(16, values[0])
|
||||||
|
assert.Equal(10, values[6])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRingBufferConsume(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewRingBuffer()
|
||||||
|
|
||||||
|
for x := 1; x < 17; x++ {
|
||||||
|
buffer.Enqueue(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(16, buffer.Len())
|
||||||
|
|
||||||
|
var called int
|
||||||
|
buffer.Consume(func(v interface{}) {
|
||||||
|
if _, isTyped := v.(int); isTyped {
|
||||||
|
called++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(16, called)
|
||||||
|
assert.Zero(buffer.Len())
|
||||||
}
|
}
|
|
@ -5,14 +5,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTimeValueFormatterWithFormat(t *testing.T) {
|
func TestTimeValueFormatterWithFormat(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
d := time.Now()
|
d := time.Now()
|
||||||
di := util.Time.ToFloat64(d)
|
di := TimeToFloat64(d)
|
||||||
df := float64(di)
|
df := float64(di)
|
||||||
|
|
||||||
s := formatTime(d, DefaultDateFormat)
|
s := formatTime(d, DefaultDateFormat)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SVG returns a new png/raster renderer.
|
// SVG returns a new png/raster renderer.
|
||||||
|
@ -30,7 +29,7 @@ func SVG(width, height int) (Renderer, error) {
|
||||||
|
|
||||||
// SVGWithCSS returns a new png/raster renderer with attached custom CSS
|
// SVGWithCSS returns a new png/raster renderer with attached custom CSS
|
||||||
// The optional nonce argument sets a CSP nonce.
|
// The optional nonce argument sets a CSP nonce.
|
||||||
func SVGWithCSS(css string, nonce string) (func(width, height int)(Renderer, error)) {
|
func SVGWithCSS(css string, nonce string) func(width, height int) (Renderer, error) {
|
||||||
return func(width, height int) (Renderer, error) {
|
return func(width, height int) (Renderer, error) {
|
||||||
buffer := bytes.NewBuffer([]byte{})
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
canvas := newCanvas(buffer)
|
canvas := newCanvas(buffer)
|
||||||
|
@ -114,8 +113,8 @@ func (vr *vectorRenderer) QuadCurveTo(cx, cy, x, y int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
|
func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
|
||||||
startAngle = util.Math.RadianAdd(startAngle, _pi2)
|
startAngle = RadianAdd(startAngle, _pi2)
|
||||||
endAngle := util.Math.RadianAdd(startAngle, delta)
|
endAngle := RadianAdd(startAngle, delta)
|
||||||
|
|
||||||
startx := cx + int(rx*math.Sin(startAngle))
|
startx := cx + int(rx*math.Sin(startAngle))
|
||||||
starty := cy - int(ry*math.Cos(startAngle))
|
starty := cy - int(ry*math.Cos(startAngle))
|
||||||
|
@ -129,7 +128,7 @@ func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
|
||||||
endx := cx + int(rx*math.Sin(endAngle))
|
endx := cx + int(rx*math.Sin(endAngle))
|
||||||
endy := cy - int(ry*math.Cos(endAngle))
|
endy := cy - int(ry*math.Cos(endAngle))
|
||||||
|
|
||||||
dd := util.Math.RadiansToDegrees(delta)
|
dd := RadiansToDegrees(delta)
|
||||||
|
|
||||||
largeArcFlag := 0
|
largeArcFlag := 0
|
||||||
if delta > _pi {
|
if delta > _pi {
|
||||||
|
@ -206,7 +205,7 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) {
|
||||||
if vr.c.textTheta == nil {
|
if vr.c.textTheta == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
box = box.Corners().Rotate(util.Math.RadiansToDegrees(*vr.c.textTheta)).Box()
|
box = box.Corners().Rotate(RadiansToDegrees(*vr.c.textTheta)).Box()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -272,7 +271,7 @@ func (c *canvas) Text(x, y int, body string, style Style) {
|
||||||
if c.textTheta == nil {
|
if c.textTheta == nil {
|
||||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s>%s</text>`, x, y, c.styleAsSVG(style), body)))
|
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s>%s</text>`, x, y, c.styleAsSVG(style), body)))
|
||||||
} else {
|
} else {
|
||||||
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, util.Math.RadiansToDegrees(*c.textTheta), x, y)
|
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, RadiansToDegrees(*c.textTheta), x, y)
|
||||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
xaxis.go
12
xaxis.go
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XAxis represents the horizontal axis.
|
// XAxis represents the horizontal axis.
|
||||||
|
@ -105,9 +103,9 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
left = util.Math.MinInt(left, ltx)
|
left = MinInt(left, ltx)
|
||||||
right = util.Math.MaxInt(right, rtx)
|
right = MaxInt(right, rtx)
|
||||||
bottom = util.Math.MaxInt(bottom, ty)
|
bottom = MaxInt(bottom, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
||||||
|
@ -159,7 +157,7 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
||||||
ty = canvasBox.Bottom + (2 * DefaultXAxisMargin)
|
ty = canvasBox.Bottom + (2 * DefaultXAxisMargin)
|
||||||
}
|
}
|
||||||
Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
|
Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
|
||||||
maxTextHeight = util.Math.MaxInt(maxTextHeight, tb.Height())
|
maxTextHeight = MaxInt(maxTextHeight, tb.Height())
|
||||||
break
|
break
|
||||||
case TickPositionBetweenTicks:
|
case TickPositionBetweenTicks:
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
|
@ -175,7 +173,7 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
||||||
}, finalTickStyle)
|
}, finalTickStyle)
|
||||||
|
|
||||||
ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle)
|
ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle)
|
||||||
maxTextHeight = util.Math.MaxInt(maxTextHeight, ftb.Height())
|
maxTextHeight = MaxInt(maxTextHeight, ftb.Height())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
14
yaxis.go
14
yaxis.go
|
@ -2,8 +2,6 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// YAxis is a veritcal rule of the range.
|
// YAxis is a veritcal rule of the range.
|
||||||
|
@ -105,18 +103,18 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
finalTextX = tx - tb.Width()
|
finalTextX = tx - tb.Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTextHeight = util.Math.MaxInt(tb.Height(), maxTextHeight)
|
maxTextHeight = MaxInt(tb.Height(), maxTextHeight)
|
||||||
|
|
||||||
if ya.AxisType == YAxisPrimary {
|
if ya.AxisType == YAxisPrimary {
|
||||||
minx = canvasBox.Right
|
minx = canvasBox.Right
|
||||||
maxx = util.Math.MaxInt(maxx, tx+tb.Width())
|
maxx = MaxInt(maxx, tx+tb.Width())
|
||||||
} else if ya.AxisType == YAxisSecondary {
|
} else if ya.AxisType == YAxisSecondary {
|
||||||
minx = util.Math.MinInt(minx, finalTextX)
|
minx = MinInt(minx, finalTextX)
|
||||||
maxx = util.Math.MaxInt(maxx, tx)
|
maxx = MaxInt(maxx, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
miny = util.Math.MinInt(miny, ly-tbh2)
|
miny = MinInt(miny, ly-tbh2)
|
||||||
maxy = util.Math.MaxInt(maxy, ly+tbh2)
|
maxy = MaxInt(maxy, ly+tbh2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
||||||
|
|
Loading…
Reference in a new issue