api cleaup
This commit is contained in:
parent
43212f871f
commit
65a0895143
39 changed files with 747 additions and 699 deletions
|
@ -10,7 +10,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
/*
|
||||
In this example we add a new type of series, a `SimpleMovingAverageSeries` that takes another series as a required argument.
|
||||
InnerSeries only needs to implement `ValueProvider`, so really you could chain `SimpleMovingAverageSeries` together if you wanted.
|
||||
InnerSeries only needs to implement `ValuesProvider`, so really you could chain `SimpleMovingAverageSeries` together if you wanted.
|
||||
*/
|
||||
|
||||
mainSeries := chart.ContinuousSeries{
|
||||
|
|
|
@ -10,7 +10,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
/*
|
||||
In this example we add a new type of series, a `PolynomialRegressionSeries` that takes another series as a required argument.
|
||||
InnerSeries only needs to implement `ValueProvider`, so really you could chain `PolynomialRegressionSeries` together if you wanted.
|
||||
InnerSeries only needs to implement `ValuesProvider`, so really you could chain `PolynomialRegressionSeries` together if you wanted.
|
||||
*/
|
||||
|
||||
mainSeries := chart.ContinuousSeries{
|
||||
|
|
|
@ -10,7 +10,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
/*
|
||||
In this example we add a new type of series, a `SimpleMovingAverageSeries` that takes another series as a required argument.
|
||||
InnerSeries only needs to implement `ValueProvider`, so really you could chain `SimpleMovingAverageSeries` together if you wanted.
|
||||
InnerSeries only needs to implement `ValuesProvider`, so really you could chain `SimpleMovingAverageSeries` together if you wanted.
|
||||
*/
|
||||
|
||||
mainSeries := chart.ContinuousSeries{
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||
// Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev.
|
||||
|
@ -14,9 +11,9 @@ type BollingerBandsSeries struct {
|
|||
|
||||
Period int
|
||||
K float64
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
valueBuffer *RingBuffer
|
||||
valueBuffer *ValueBuffer
|
||||
}
|
||||
|
||||
// GetName returns the name of the time series.
|
||||
|
@ -42,7 +39,9 @@ func (bbs BollingerBandsSeries) GetPeriod() int {
|
|||
return bbs.Period
|
||||
}
|
||||
|
||||
// GetK returns the K value.
|
||||
// GetK returns the K value, or the number of standard deviations above and below
|
||||
// to band the simple moving average with.
|
||||
// Typical K value is 2.0.
|
||||
func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 {
|
||||
if bbs.K == 0 {
|
||||
if len(defaults) > 0 {
|
||||
|
@ -54,35 +53,35 @@ func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 {
|
|||
}
|
||||
|
||||
// Len returns the number of elements in the series.
|
||||
func (bbs *BollingerBandsSeries) Len() int {
|
||||
func (bbs BollingerBandsSeries) Len() int {
|
||||
return bbs.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetBoundedValue gets the bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
|
||||
// GetBoundedValues gets the bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
|
||||
if bbs.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
if bbs.valueBuffer == nil || index == 0 {
|
||||
bbs.valueBuffer = NewRingBufferWithCapacity(bbs.GetPeriod())
|
||||
bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod())
|
||||
}
|
||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||
bbs.valueBuffer.Dequeue()
|
||||
}
|
||||
px, py := bbs.InnerSeries.GetValue(index)
|
||||
px, py := bbs.InnerSeries.GetValues(index)
|
||||
bbs.valueBuffer.Enqueue(py)
|
||||
x = px
|
||||
|
||||
ay := bbs.getAverage(bbs.valueBuffer)
|
||||
std := bbs.getStdDev(bbs.valueBuffer)
|
||||
ay := Sequence{bbs.valueBuffer}.Average()
|
||||
std := Sequence{bbs.valueBuffer}.StdDev()
|
||||
|
||||
y1 = ay + (bbs.GetK() * std)
|
||||
y2 = ay - (bbs.GetK() * std)
|
||||
return
|
||||
}
|
||||
|
||||
// GetBoundedLastValue returns the last bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedLastValue() (x, y1, y2 float64) {
|
||||
// GetBoundedLastValues returns the last bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) {
|
||||
if bbs.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
|
@ -93,15 +92,15 @@ func (bbs *BollingerBandsSeries) GetBoundedLastValue() (x, y1, y2 float64) {
|
|||
startAt = 0
|
||||
}
|
||||
|
||||
vb := NewRingBufferWithCapacity(period)
|
||||
vb := NewValueBufferWithCapacity(period)
|
||||
for index := startAt; index < seriesLength; index++ {
|
||||
xn, yn := bbs.InnerSeries.GetValue(index)
|
||||
xn, yn := bbs.InnerSeries.GetValues(index)
|
||||
vb.Enqueue(yn)
|
||||
x = xn
|
||||
}
|
||||
|
||||
ay := bbs.getAverage(vb)
|
||||
std := bbs.getStdDev(vb)
|
||||
ay := Sequence{vb}.Average()
|
||||
std := Sequence{vb}.StdDev()
|
||||
|
||||
y1 = ay + (bbs.GetK() * std)
|
||||
y2 = ay - (bbs.GetK() * std)
|
||||
|
@ -120,37 +119,6 @@ func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrang
|
|||
Draw.BoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod())
|
||||
}
|
||||
|
||||
func (bbs BollingerBandsSeries) getAverage(valueBuffer *RingBuffer) float64 {
|
||||
var accum float64
|
||||
valueBuffer.Each(func(v interface{}) {
|
||||
if typed, isTyped := v.(float64); isTyped {
|
||||
accum += typed
|
||||
}
|
||||
})
|
||||
return accum / float64(valueBuffer.Len())
|
||||
}
|
||||
|
||||
func (bbs BollingerBandsSeries) getVariance(valueBuffer *RingBuffer) float64 {
|
||||
if valueBuffer.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var variance float64
|
||||
m := bbs.getAverage(valueBuffer)
|
||||
|
||||
valueBuffer.Each(func(v interface{}) {
|
||||
if n, isTyped := v.(float64); isTyped {
|
||||
variance += (float64(n) - m) * (float64(n) - m)
|
||||
}
|
||||
})
|
||||
|
||||
return variance / float64(valueBuffer.Len())
|
||||
}
|
||||
|
||||
func (bbs BollingerBandsSeries) getStdDev(valueBuffer *RingBuffer) float64 {
|
||||
return math.Pow(bbs.getVariance(valueBuffer), 0.5)
|
||||
}
|
||||
|
||||
// Validate validates the series.
|
||||
func (bbs BollingerBandsSeries) Validate() error {
|
||||
if bbs.InnerSeries == nil {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
|
@ -10,9 +11,9 @@ import (
|
|||
func TestBollingerBandSeries(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
s1 := mockValueProvider{
|
||||
X: Sequence.Float64(1.0, 100.0),
|
||||
Y: Sequence.Random(100, 1024),
|
||||
s1 := mockValuesProvider{
|
||||
X: Generate.Float64(1.0, 100.0),
|
||||
Y: Generate.Random(100, 1024),
|
||||
}
|
||||
|
||||
bbs := &BollingerBandsSeries{
|
||||
|
@ -24,27 +25,27 @@ func TestBollingerBandSeries(t *testing.T) {
|
|||
y2values := make([]float64, 100)
|
||||
|
||||
for x := 0; x < 100; x++ {
|
||||
xvalues[x], y1values[x], y2values[x] = bbs.GetBoundedValue(x)
|
||||
xvalues[x], y1values[x], y2values[x] = bbs.GetBoundedValues(x)
|
||||
}
|
||||
|
||||
for x := bbs.GetPeriod(); x < 100; x++ {
|
||||
assert.True(y1values[x] > y2values[x])
|
||||
assert.True(y1values[x] > y2values[x], fmt.Sprintf("%v vs. %v", y1values[x], y2values[x]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBollingerBandLastValue(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
s1 := mockValueProvider{
|
||||
X: Sequence.Float64(1.0, 100.0),
|
||||
Y: Sequence.Float64(1.0, 100.0),
|
||||
s1 := mockValuesProvider{
|
||||
X: Generate.Float64(1.0, 100.0),
|
||||
Y: Generate.Float64(1.0, 100.0),
|
||||
}
|
||||
|
||||
bbs := &BollingerBandsSeries{
|
||||
InnerSeries: s1,
|
||||
}
|
||||
|
||||
x, y1, y2 := bbs.GetBoundedLastValue()
|
||||
x, y1, y2 := bbs.GetBoundedLastValues()
|
||||
assert.Equal(100.0, x)
|
||||
assert.Equal(101, math.Floor(y1))
|
||||
assert.Equal(83, math.Floor(y2))
|
||||
|
|
8
chart.go
8
chart.go
|
@ -177,10 +177,10 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
|||
for _, s := range c.Series {
|
||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||
seriesAxis := s.GetYAxis()
|
||||
if bvp, isBoundedValueProvider := s.(BoundedValueProvider); isBoundedValueProvider {
|
||||
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
||||
seriesLength := bvp.Len()
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy1, vy2 := bvp.GetBoundedValue(index)
|
||||
vx, vy1, vy2 := bvp.GetBoundedValues(index)
|
||||
|
||||
minx = math.Min(minx, vx)
|
||||
maxx = math.Max(maxx, vx)
|
||||
|
@ -198,10 +198,10 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
|||
seriesMappedToSecondaryAxis = true
|
||||
}
|
||||
}
|
||||
} else if vp, isValueProvider := s.(ValueProvider); isValueProvider {
|
||||
} else if vp, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
seriesLength := vp.Len()
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy := vp.GetValue(index)
|
||||
vx, vy := vp.GetValues(index)
|
||||
|
||||
minx = math.Min(minx, vx)
|
||||
maxx = math.Max(maxx, vx)
|
||||
|
|
|
@ -391,8 +391,8 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
|
|||
},
|
||||
Series: []Series{
|
||||
ContinuousSeries{
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -407,8 +407,8 @@ func TestChartValidatesSeries(t *testing.T) {
|
|||
c := Chart{
|
||||
Series: []Series{
|
||||
ContinuousSeries{
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ func TestChartValidatesSeries(t *testing.T) {
|
|||
c = Chart{
|
||||
Series: []Series{
|
||||
ContinuousSeries{
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -504,8 +504,8 @@ func TestChartE2ELine(t *testing.T) {
|
|||
},
|
||||
Series: []Series{
|
||||
ContinuousSeries{
|
||||
XValues: Sequence.Float64(0, 4, 1),
|
||||
YValues: Sequence.Float64(0, 4, 1),
|
||||
XValues: Generate.Float64(0, 4, 1),
|
||||
YValues: Generate.Float64(0, 4, 1),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -549,8 +549,8 @@ func TestChartE2ELineWithFill(t *testing.T) {
|
|||
StrokeColor: drawing.ColorBlue,
|
||||
FillColor: drawing.ColorRed,
|
||||
},
|
||||
XValues: Sequence.Float64(0, 4, 1),
|
||||
YValues: Sequence.Float64(0, 4, 1),
|
||||
XValues: Generate.Float64(0, 4, 1),
|
||||
YValues: Generate.Float64(0, 4, 1),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ type ConcatSeries []Series
|
|||
func (cs ConcatSeries) Len() int {
|
||||
total := 0
|
||||
for _, s := range cs {
|
||||
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
|
||||
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
total += typed.Len()
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ func (cs ConcatSeries) Len() int {
|
|||
func (cs ConcatSeries) GetValue(index int) (x, y float64) {
|
||||
cursor := 0
|
||||
for _, s := range cs {
|
||||
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
|
||||
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
len := typed.Len()
|
||||
if index < cursor+len {
|
||||
x, y = typed.GetValue(index - cursor) //FENCEPOSTS.
|
||||
x, y = typed.GetValues(index - cursor) //FENCEPOSTS.
|
||||
return
|
||||
}
|
||||
cursor += typed.Len()
|
||||
|
|
|
@ -10,18 +10,18 @@ func TestConcatSeries(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
s1 := ContinuousSeries{
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
|
||||
s2 := ContinuousSeries{
|
||||
XValues: Sequence.Float64(11, 20.0),
|
||||
YValues: Sequence.Float64(10.0, 1.0),
|
||||
XValues: Generate.Float64(11, 20.0),
|
||||
YValues: Generate.Float64(10.0, 1.0),
|
||||
}
|
||||
|
||||
s3 := ContinuousSeries{
|
||||
XValues: Sequence.Float64(21, 30.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(21, 30.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
|
||||
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||
|
|
|
@ -31,13 +31,13 @@ func (cs ContinuousSeries) Len() int {
|
|||
return len(cs.XValues)
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (cs ContinuousSeries) GetValue(index int) (float64, float64) {
|
||||
// GetValues gets the x,y values at a given index.
|
||||
func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
|
||||
return cs.XValues[index], cs.YValues[index]
|
||||
}
|
||||
|
||||
// GetLastValue gets the last value.
|
||||
func (cs ContinuousSeries) GetLastValue() (float64, float64) {
|
||||
// GetLastValues gets the last x,y values.
|
||||
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
||||
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
||||
}
|
||||
|
||||
|
|
|
@ -12,21 +12,21 @@ func TestContinuousSeries(t *testing.T) {
|
|||
|
||||
cs := ContinuousSeries{
|
||||
Name: "Test Series",
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
|
||||
assert.Equal("Test Series", cs.GetName())
|
||||
assert.Equal(10, cs.Len())
|
||||
x0, y0 := cs.GetValue(0)
|
||||
x0, y0 := cs.GetValues(0)
|
||||
assert.Equal(1.0, x0)
|
||||
assert.Equal(1.0, y0)
|
||||
|
||||
xn, yn := cs.GetValue(9)
|
||||
xn, yn := cs.GetValues(9)
|
||||
assert.Equal(10.0, xn)
|
||||
assert.Equal(10.0, yn)
|
||||
|
||||
xn, yn = cs.GetLastValue()
|
||||
xn, yn = cs.GetLastValues()
|
||||
assert.Equal(10.0, xn)
|
||||
assert.Equal(10.0, yn)
|
||||
}
|
||||
|
@ -53,20 +53,20 @@ func TestContinuousSeriesValidate(t *testing.T) {
|
|||
|
||||
cs := ContinuousSeries{
|
||||
Name: "Test Series",
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
assert.Nil(cs.Validate())
|
||||
|
||||
cs = ContinuousSeries{
|
||||
Name: "Test Series",
|
||||
XValues: Sequence.Float64(1.0, 10.0),
|
||||
XValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
assert.NotNil(cs.Validate())
|
||||
|
||||
cs = ContinuousSeries{
|
||||
Name: "Test Series",
|
||||
YValues: Sequence.Float64(1.0, 10.0),
|
||||
YValues: Generate.Float64(1.0, 10.0),
|
||||
}
|
||||
assert.NotNil(cs.Validate())
|
||||
}
|
||||
|
|
22
draw.go
22
draw.go
|
@ -10,7 +10,7 @@ var (
|
|||
type draw struct{}
|
||||
|
||||
// LineSeries draws a line series with a renderer.
|
||||
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider) {
|
||||
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider) {
|
||||
if vs.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
|||
cb := canvasBox.Bottom
|
||||
cl := canvasBox.Left
|
||||
|
||||
v0x, v0y := vs.GetValue(0)
|
||||
v0x, v0y := vs.GetValues(0)
|
||||
x0 := cl + xrange.Translate(v0x)
|
||||
y0 := cb - yrange.Translate(v0y)
|
||||
|
||||
|
@ -31,7 +31,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
|||
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValue(i)
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
r.LineTo(x, y)
|
||||
|
@ -47,7 +47,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
|||
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValue(i)
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
r.LineTo(x, y)
|
||||
|
@ -60,7 +60,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
|||
|
||||
style.GetDotOptions().WriteDrawingOptionsToRenderer(r)
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValue(i)
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
|
||||
|
@ -82,8 +82,8 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
|||
}
|
||||
}
|
||||
|
||||
// BoundedSeries draws a series that implements BoundedValueProvider.
|
||||
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) {
|
||||
// BoundedSeries draws a series that implements BoundedValuesProvider.
|
||||
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValuesProvider, drawOffsetIndexes ...int) {
|
||||
drawOffsetIndex := 0
|
||||
if len(drawOffsetIndexes) > 0 {
|
||||
drawOffsetIndex = drawOffsetIndexes[0]
|
||||
|
@ -92,7 +92,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
|||
cb := canvasBox.Bottom
|
||||
cl := canvasBox.Left
|
||||
|
||||
v0x, v0y1, v0y2 := bbs.GetBoundedValue(0)
|
||||
v0x, v0y1, v0y2 := bbs.GetBoundedValues(0)
|
||||
x0 := cl + xrange.Translate(v0x)
|
||||
y0 := cb - yrange.Translate(v0y1)
|
||||
|
||||
|
@ -107,7 +107,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
|||
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < bbs.Len(); i++ {
|
||||
vx, vy1, vy2 = bbs.GetBoundedValue(i)
|
||||
vx, vy1, vy2 = bbs.GetBoundedValues(i)
|
||||
|
||||
xvalues[i] = vx
|
||||
y2values[i] = vy2
|
||||
|
@ -133,7 +133,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
|||
}
|
||||
|
||||
// HistogramSeries draws a value provider as boxes from 0.
|
||||
func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider, barWidths ...int) {
|
||||
func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider, barWidths ...int) {
|
||||
if vs.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s
|
|||
|
||||
//foreach datapoint, draw a box.
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy := vs.GetValue(index)
|
||||
vx, vy := vs.GetValues(index)
|
||||
y0 := yrange.Translate(0)
|
||||
x := cl + xrange.Translate(vx)
|
||||
y := yrange.Translate(vy)
|
||||
|
|
|
@ -14,7 +14,7 @@ type EMASeries struct {
|
|||
YAxis YAxisType
|
||||
|
||||
Period int
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
cache []float64
|
||||
}
|
||||
|
@ -52,23 +52,23 @@ func (ema EMASeries) GetSigma() float64 {
|
|||
return 2.0 / (float64(ema.GetPeriod()) + 1)
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (ema *EMASeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (ema *EMASeries) GetValues(index int) (x, y float64) {
|
||||
if ema.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
if len(ema.cache) == 0 {
|
||||
ema.ensureCachedValues()
|
||||
}
|
||||
vx, _ := ema.InnerSeries.GetValue(index)
|
||||
vx, _ := ema.InnerSeries.GetValues(index)
|
||||
x = vx
|
||||
y = ema.cache[index]
|
||||
return
|
||||
}
|
||||
|
||||
// GetLastValue computes the last moving average value but walking back window size samples,
|
||||
// GetLastValues computes the last moving average value but walking back window size samples,
|
||||
// and recomputing the last moving average chunk.
|
||||
func (ema *EMASeries) GetLastValue() (x, y float64) {
|
||||
func (ema *EMASeries) GetLastValues() (x, y float64) {
|
||||
if ema.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func (ema *EMASeries) GetLastValue() (x, y float64) {
|
|||
ema.ensureCachedValues()
|
||||
}
|
||||
lastIndex := ema.InnerSeries.Len() - 1
|
||||
x, _ = ema.InnerSeries.GetValue(lastIndex)
|
||||
x, _ = ema.InnerSeries.GetValues(lastIndex)
|
||||
y = ema.cache[lastIndex]
|
||||
return
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func (ema *EMASeries) ensureCachedValues() {
|
|||
ema.cache = make([]float64, seriesLength)
|
||||
sigma := ema.GetSigma()
|
||||
for x := 0; x < seriesLength; x++ {
|
||||
_, y := ema.InnerSeries.GetValue(x)
|
||||
_, y := ema.InnerSeries.GetValues(x)
|
||||
if x == 0 {
|
||||
ema.cache[x] = y
|
||||
continue
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
emaXValues = Sequence.Float64(1.0, 50.0)
|
||||
emaXValues = Generate.Float64(1.0, 50.0)
|
||||
emaYValues = []float64{
|
||||
1, 2, 3, 4, 5, 4, 3, 2,
|
||||
1, 2, 3, 4, 5, 4, 3, 2,
|
||||
|
@ -75,7 +75,7 @@ var (
|
|||
func TestEMASeries(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockSeries := mockValueProvider{
|
||||
mockSeries := mockValuesProvider{
|
||||
emaXValues,
|
||||
emaYValues,
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func TestEMASeries(t *testing.T) {
|
|||
|
||||
var yvalues []float64
|
||||
for x := 0; x < ema.Len(); x++ {
|
||||
_, y := ema.GetValue(x)
|
||||
_, y := ema.GetValues(x)
|
||||
yvalues = append(yvalues, y)
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ func TestEMASeries(t *testing.T) {
|
|||
assert.InDelta(yv, emaExpected[index], emaDelta)
|
||||
}
|
||||
|
||||
lvx, lvy := ema.GetLastValue()
|
||||
lvx, lvy := ema.GetLastValues()
|
||||
assert.Equal(50.0, lvx)
|
||||
assert.InDelta(lvy, emaExpected[49], emaDelta)
|
||||
}
|
||||
|
|
190
generate.go
Normal file
190
generate.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Generate contains some sequence generation utilities.
|
||||
// These utilities can be useful for generating test data.
|
||||
Generate = &generate{
|
||||
rnd: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
}
|
||||
)
|
||||
|
||||
type generate struct {
|
||||
rnd *rand.Rand
|
||||
}
|
||||
|
||||
// Float64 produces an array of floats from [start,end] by optional steps.
|
||||
func (g generate) Float64(start, end float64, steps ...float64) []float64 {
|
||||
var values []float64
|
||||
step := 1.0
|
||||
if len(steps) > 0 {
|
||||
step = steps[0]
|
||||
}
|
||||
|
||||
if start < end {
|
||||
for x := start; x <= end; x += step {
|
||||
values = append(values, x)
|
||||
}
|
||||
} else {
|
||||
for x := start; x >= end; x = x - step {
|
||||
values = append(values, x)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Random generates a fixed length sequence of random values between (0, scale).
|
||||
func (g generate) Random(samples int, scale float64) []float64 {
|
||||
values := make([]float64, samples)
|
||||
|
||||
for x := 0; x < samples; x++ {
|
||||
values[x] = g.rnd.Float64() * scale
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Random generates a fixed length sequence of random values with a given average, above and below that average by (-scale, scale)
|
||||
func (g generate) RandomWithAverage(samples int, average, scale float64) []float64 {
|
||||
values := make([]float64, samples)
|
||||
|
||||
for x := 0; x < samples; x++ {
|
||||
jitter := scale - (g.rnd.Float64() * (2 * scale))
|
||||
values[x] = average + jitter
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Days generates a sequence of timestamps by day, from -days to today.
|
||||
func (g generate) 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 (g generate) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
todayOpen := Date.On(marketOpen, cursor)
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if (cursor.Equal(todayOpen) || cursor.After(todayOpen)) && (cursor.Equal(todayClose) || cursor.Before(todayClose)) && isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
if cursor.After(todayClose) {
|
||||
cursor = Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||
} else {
|
||||
cursor = Date.NextHour(cursor)
|
||||
}
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (g generate) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if isValidTradingDay {
|
||||
todayOpen := Date.On(marketOpen, cursor)
|
||||
todayNoon := Date.NoonOn(cursor)
|
||||
today2pm := Date.On(Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
||||
}
|
||||
|
||||
cursor = Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (g generate) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
}
|
||||
|
||||
cursor = Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (g generate) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
}
|
||||
|
||||
cursor = cursor.AddDate(0, 0, 2)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (g generate) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketClose, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
|
||||
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (g generate) 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 (g generate) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||
start := Date.Start(xdata)
|
||||
end := Date.End(xdata)
|
||||
|
||||
totalHours := Math.AbsInt(Date.DiffHours(start, end))
|
||||
|
||||
finalTimes := g.Hours(start, totalHours+1)
|
||||
finalValues := make([]float64, totalHours+1)
|
||||
|
||||
var hoursFromStart int
|
||||
for i, xd := range xdata {
|
||||
hoursFromStart = Date.DiffHours(start, xd)
|
||||
finalValues[hoursFromStart] = ydata[i]
|
||||
}
|
||||
|
||||
return finalTimes, finalValues
|
||||
}
|
|
@ -10,10 +10,10 @@ import (
|
|||
func TestSequenceFloat64(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
asc := Sequence.Float64(1.0, 10.0)
|
||||
asc := Generate.Float64(1.0, 10.0)
|
||||
assert.Len(asc, 10)
|
||||
|
||||
desc := Sequence.Float64(10.0, 1.0)
|
||||
desc := Generate.Float64(10.0, 1.0)
|
||||
assert.Len(desc, 10)
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ func TestSequenceMarketHours(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
|
||||
mh := Sequence.MarketHours(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
|
||||
mh := Generate.MarketHours(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
|
||||
assert.Len(mh, 8)
|
||||
assert.Equal(Date.Eastern(), mh[0].Location())
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func TestSequenceMarketHours(t *testing.T) {
|
|||
func TestSequenceMarketQuarters(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
|
||||
mh := Sequence.MarketHourQuarters(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
|
||||
mh := Generate.MarketHourQuarters(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
|
||||
assert.Len(mh, 4)
|
||||
assert.Equal(9, mh[0].Hour())
|
||||
assert.Equal(30, mh[0].Minute())
|
||||
|
@ -48,7 +48,7 @@ func TestSequenceHours(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
|
||||
seq := Sequence.Hours(today, 24)
|
||||
seq := Generate.Hours(today, 24)
|
||||
|
||||
end := Date.End(seq)
|
||||
assert.Len(seq, 24)
|
||||
|
@ -81,7 +81,7 @@ func TestSequenceHoursFill(t *testing.T) {
|
|||
0.6,
|
||||
}
|
||||
|
||||
filledTimes, filledValues := Sequence.HoursFill(xdata, ydata)
|
||||
filledTimes, filledValues := Generate.HoursFilled(xdata, ydata)
|
||||
assert.Len(filledTimes, Date.DiffHours(Date.Start(xdata), Date.End(xdata))+1)
|
||||
assert.Equal(len(filledValues), len(filledTimes))
|
||||
|
|
@ -9,7 +9,7 @@ type HistogramSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
}
|
||||
|
||||
// GetName implements Series.GetName.
|
||||
|
@ -27,19 +27,19 @@ func (hs HistogramSeries) GetYAxis() YAxisType {
|
|||
return hs.YAxis
|
||||
}
|
||||
|
||||
// Len implements BoundedValueProvider.Len.
|
||||
// Len implements BoundedValuesProvider.Len.
|
||||
func (hs HistogramSeries) Len() int {
|
||||
return hs.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue implements ValueProvider.GetValue.
|
||||
func (hs HistogramSeries) GetValue(index int) (x, y float64) {
|
||||
return hs.InnerSeries.GetValue(index)
|
||||
// GetValues implements ValuesProvider.GetValues.
|
||||
func (hs HistogramSeries) GetValues(index int) (x, y float64) {
|
||||
return hs.InnerSeries.GetValues(index)
|
||||
}
|
||||
|
||||
// GetBoundedValue implements BoundedValueProvider.GetBoundedValue
|
||||
func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
|
||||
vx, vy := hs.InnerSeries.GetValue(index)
|
||||
// GetBoundedValues implements BoundedValuesProvider.GetBoundedValue
|
||||
func (hs HistogramSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
|
||||
vx, vy := hs.InnerSeries.GetValues(index)
|
||||
|
||||
x = vx
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ func TestHistogramSeries(t *testing.T) {
|
|||
|
||||
cs := ContinuousSeries{
|
||||
Name: "Test Series",
|
||||
XValues: Sequence.Float64(1.0, 20.0),
|
||||
YValues: Sequence.Float64(10.0, -10.0),
|
||||
XValues: Generate.Float64(1.0, 20.0),
|
||||
YValues: Generate.Float64(10.0, -10.0),
|
||||
}
|
||||
|
||||
hs := HistogramSeries{
|
||||
|
@ -20,8 +20,8 @@ func TestHistogramSeries(t *testing.T) {
|
|||
}
|
||||
|
||||
for x := 0; x < hs.Len(); x++ {
|
||||
csx, csy := cs.GetValue(0)
|
||||
hsx, hsy1, hsy2 := hs.GetBoundedValue(0)
|
||||
csx, csy := cs.GetValues(0)
|
||||
hsx, hsy1, hsy2 := hs.GetBoundedValues(0)
|
||||
assert.Equal(csx, hsx)
|
||||
assert.True(hsy1 > 0)
|
||||
assert.True(hsy2 <= 0)
|
||||
|
|
|
@ -3,7 +3,7 @@ package chart
|
|||
import "fmt"
|
||||
|
||||
// LastValueAnnotation returns an annotation series of just the last value of a value provider.
|
||||
func LastValueAnnotation(innerSeries ValueProvider, vfs ...ValueFormatter) AnnotationSeries {
|
||||
func LastValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries {
|
||||
var vf ValueFormatter
|
||||
if len(vfs) > 0 {
|
||||
vf = vfs[0]
|
||||
|
@ -14,11 +14,11 @@ func LastValueAnnotation(innerSeries ValueProvider, vfs ...ValueFormatter) Annot
|
|||
}
|
||||
|
||||
var lastValue Value2
|
||||
if typed, isTyped := innerSeries.(LastValueProvider); isTyped {
|
||||
lastValue.XValue, lastValue.YValue = typed.GetLastValue()
|
||||
if typed, isTyped := innerSeries.(LastValuesProvider); isTyped {
|
||||
lastValue.XValue, lastValue.YValue = typed.GetLastValues()
|
||||
lastValue.Label = vf(lastValue.YValue)
|
||||
} else {
|
||||
lastValue.XValue, lastValue.YValue = innerSeries.GetValue(innerSeries.Len() - 1)
|
||||
lastValue.XValue, lastValue.YValue = innerSeries.GetValues(innerSeries.Len() - 1)
|
||||
lastValue.Label = vf(lastValue.YValue)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ type LinearRegressionSeries struct {
|
|||
|
||||
Limit int
|
||||
Offset int
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
m float64
|
||||
b float64
|
||||
|
@ -62,8 +62,8 @@ func (lrs LinearRegressionSeries) GetOffset() int {
|
|||
return lrs.Offset
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (lrs *LinearRegressionSeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -72,13 +72,13 @@ func (lrs *LinearRegressionSeries) GetValue(index int) (x, y float64) {
|
|||
}
|
||||
offset := lrs.GetOffset()
|
||||
effectiveIndex := Math.MinInt(index+offset, lrs.InnerSeries.Len())
|
||||
x, y = lrs.InnerSeries.GetValue(effectiveIndex)
|
||||
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
|
||||
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
||||
return
|
||||
}
|
||||
|
||||
// GetLastValue computes the last linear regression value.
|
||||
func (lrs *LinearRegressionSeries) GetLastValue() (x, y float64) {
|
||||
// GetLastValues computes the last linear regression value.
|
||||
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func (lrs *LinearRegressionSeries) GetLastValue() (x, y float64) {
|
|||
lrs.computeCoefficients()
|
||||
}
|
||||
endIndex := lrs.GetEndIndex()
|
||||
x, y = lrs.InnerSeries.GetValue(endIndex)
|
||||
x, y = lrs.InnerSeries.GetValues(endIndex)
|
||||
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
||||
return
|
||||
}
|
||||
|
@ -102,18 +102,18 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
|
|||
|
||||
p := float64(endIndex - startIndex)
|
||||
|
||||
xvalues := NewRingBufferWithCapacity(lrs.Len())
|
||||
xvalues := NewValueBufferWithCapacity(lrs.Len())
|
||||
for index := startIndex; index < endIndex; index++ {
|
||||
x, _ := lrs.InnerSeries.GetValue(index)
|
||||
x, _ := lrs.InnerSeries.GetValues(index)
|
||||
xvalues.Enqueue(x)
|
||||
}
|
||||
|
||||
lrs.avgx = xvalues.Average()
|
||||
lrs.stddevx = xvalues.StdDev()
|
||||
lrs.avgx = Sequence{xvalues}.Average()
|
||||
lrs.stddevx = Sequence{xvalues}.StdDev()
|
||||
|
||||
var sumx, sumy, sumxx, sumxy float64
|
||||
for index := startIndex; index < endIndex; index++ {
|
||||
x, y := lrs.InnerSeries.GetValue(index)
|
||||
x, y := lrs.InnerSeries.GetValues(index)
|
||||
|
||||
x = lrs.normalize(x)
|
||||
|
||||
|
|
|
@ -11,19 +11,19 @@ func TestLinearRegressionSeries(t *testing.T) {
|
|||
|
||||
mainSeries := ContinuousSeries{
|
||||
Name: "A test series",
|
||||
XValues: Sequence.Float64(1.0, 100.0),
|
||||
YValues: Sequence.Float64(1.0, 100.0),
|
||||
XValues: Generate.Float64(1.0, 100.0),
|
||||
YValues: Generate.Float64(1.0, 100.0),
|
||||
}
|
||||
|
||||
linRegSeries := &LinearRegressionSeries{
|
||||
InnerSeries: mainSeries,
|
||||
}
|
||||
|
||||
lrx0, lry0 := linRegSeries.GetValue(0)
|
||||
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||
assert.InDelta(1.0, lrx0, 0.0000001)
|
||||
assert.InDelta(1.0, lry0, 0.0000001)
|
||||
|
||||
lrxn, lryn := linRegSeries.GetLastValue()
|
||||
lrxn, lryn := linRegSeries.GetLastValues()
|
||||
assert.InDelta(100.0, lrxn, 0.0000001)
|
||||
assert.InDelta(100.0, lryn, 0.0000001)
|
||||
}
|
||||
|
@ -33,19 +33,19 @@ func TestLinearRegressionSeriesDesc(t *testing.T) {
|
|||
|
||||
mainSeries := ContinuousSeries{
|
||||
Name: "A test series",
|
||||
XValues: Sequence.Float64(100.0, 1.0),
|
||||
YValues: Sequence.Float64(100.0, 1.0),
|
||||
XValues: Generate.Float64(100.0, 1.0),
|
||||
YValues: Generate.Float64(100.0, 1.0),
|
||||
}
|
||||
|
||||
linRegSeries := &LinearRegressionSeries{
|
||||
InnerSeries: mainSeries,
|
||||
}
|
||||
|
||||
lrx0, lry0 := linRegSeries.GetValue(0)
|
||||
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||
assert.InDelta(100.0, lrx0, 0.0000001)
|
||||
assert.InDelta(100.0, lry0, 0.0000001)
|
||||
|
||||
lrxn, lryn := linRegSeries.GetLastValue()
|
||||
lrxn, lryn := linRegSeries.GetLastValues()
|
||||
assert.InDelta(1.0, lrxn, 0.0000001)
|
||||
assert.InDelta(1.0, lryn, 0.0000001)
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
|||
|
||||
mainSeries := ContinuousSeries{
|
||||
Name: "A test series",
|
||||
XValues: Sequence.Float64(100.0, 1.0),
|
||||
YValues: Sequence.Float64(100.0, 1.0),
|
||||
XValues: Generate.Float64(100.0, 1.0),
|
||||
YValues: Generate.Float64(100.0, 1.0),
|
||||
}
|
||||
|
||||
linRegSeries := &LinearRegressionSeries{
|
||||
|
@ -67,11 +67,11 @@ func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
|||
|
||||
assert.Equal(10, linRegSeries.Len())
|
||||
|
||||
lrx0, lry0 := linRegSeries.GetValue(0)
|
||||
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||
assert.InDelta(90.0, lrx0, 0.0000001)
|
||||
assert.InDelta(90.0, lry0, 0.0000001)
|
||||
|
||||
lrxn, lryn := linRegSeries.GetLastValue()
|
||||
lrxn, lryn := linRegSeries.GetLastValues()
|
||||
assert.InDelta(80.0, lrxn, 0.0000001)
|
||||
assert.InDelta(80.0, lryn, 0.0000001)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ type MACDSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
PrimaryPeriod int
|
||||
SecondaryPeriod int
|
||||
|
@ -89,8 +89,8 @@ func (macd MACDSeries) Len() int {
|
|||
return macd.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||
func (macd *MACDSeries) GetValue(index int) (x float64, y float64) {
|
||||
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||
func (macd *MACDSeries) GetValues(index int) (x float64, y float64) {
|
||||
if macd.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
|
@ -99,10 +99,10 @@ func (macd *MACDSeries) GetValue(index int) (x float64, y float64) {
|
|||
macd.ensureChildSeries()
|
||||
}
|
||||
|
||||
_, lv := macd.macdl.GetValue(index)
|
||||
_, sv := macd.signal.GetValue(index)
|
||||
_, lv := macd.macdl.GetValues(index)
|
||||
_, sv := macd.signal.GetValues(index)
|
||||
|
||||
x, _ = macd.InnerSeries.GetValue(index)
|
||||
x, _ = macd.InnerSeries.GetValues(index)
|
||||
y = lv - sv
|
||||
|
||||
return
|
||||
|
@ -130,7 +130,7 @@ type MACDSignalSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
PrimaryPeriod int
|
||||
SecondaryPeriod int
|
||||
|
@ -191,8 +191,8 @@ func (macds *MACDSignalSeries) Len() int {
|
|||
return macds.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||
func (macds *MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
||||
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||
func (macds *MACDSignalSeries) GetValues(index int) (x float64, y float64) {
|
||||
if macds.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
|
@ -200,8 +200,8 @@ func (macds *MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
|||
if macds.signal == nil {
|
||||
macds.ensureSignal()
|
||||
}
|
||||
x, _ = macds.InnerSeries.GetValue(index)
|
||||
_, y = macds.signal.GetValue(index)
|
||||
x, _ = macds.InnerSeries.GetValues(index)
|
||||
_, y = macds.signal.GetValues(index)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,7 @@ type MACDLineSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
PrimaryPeriod int
|
||||
SecondaryPeriod int
|
||||
|
@ -300,8 +300,8 @@ func (macdl *MACDLineSeries) Len() int {
|
|||
return macdl.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||
func (macdl *MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
||||
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||
func (macdl *MACDLineSeries) GetValues(index int) (x float64, y float64) {
|
||||
if macdl.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
|
@ -309,10 +309,10 @@ func (macdl *MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
|||
macdl.ensureEMASeries()
|
||||
}
|
||||
|
||||
x, _ = macdl.InnerSeries.GetValue(index)
|
||||
x, _ = macdl.InnerSeries.GetValues(index)
|
||||
|
||||
_, emav1 := macdl.ema1.GetValue(index)
|
||||
_, emav2 := macdl.ema2.GetValue(index)
|
||||
_, emav1 := macdl.ema1.GetValues(index)
|
||||
_, emav2 := macdl.ema2.GetValues(index)
|
||||
|
||||
y = emav2 - emav1
|
||||
return
|
||||
|
|
|
@ -65,7 +65,7 @@ var (
|
|||
func TestMACDSeries(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockSeries := mockValueProvider{
|
||||
mockSeries := mockValuesProvider{
|
||||
emaXValues,
|
||||
emaYValues,
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func TestMACDSeries(t *testing.T) {
|
|||
|
||||
var yvalues []float64
|
||||
for x := 0; x < mas.Len(); x++ {
|
||||
_, y := mas.GetValue(x)
|
||||
_, y := mas.GetValues(x)
|
||||
yvalues = append(yvalues, y)
|
||||
}
|
||||
|
||||
|
|
|
@ -112,31 +112,31 @@ func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
|||
// GetTicks returns the ticks for the range.
|
||||
// This is to override the default continous ticks that would be generated for the range.
|
||||
func (mhr *MarketHoursRange) GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick {
|
||||
times := Sequence.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
times := Generate.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
||||
if timesWidth <= mhr.Domain {
|
||||
return mhr.makeTicks(vf, times)
|
||||
}
|
||||
|
||||
times = Sequence.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
times = Generate.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||
if timesWidth <= mhr.Domain {
|
||||
return mhr.makeTicks(vf, times)
|
||||
}
|
||||
|
||||
times = Sequence.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
times = Generate.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||
if timesWidth <= mhr.Domain {
|
||||
return mhr.makeTicks(vf, times)
|
||||
}
|
||||
|
||||
times = Sequence.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
times = Generate.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||
if timesWidth <= mhr.Domain {
|
||||
return mhr.makeTicks(vf, times)
|
||||
}
|
||||
|
||||
times = Sequence.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
times = Generate.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||
if timesWidth <= mhr.Domain {
|
||||
return mhr.makeTicks(vf, times)
|
||||
|
|
|
@ -10,7 +10,7 @@ type MinSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
minValue *float64
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ func (ms MinSeries) Len() int {
|
|||
return ms.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (ms *MinSeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (ms *MinSeries) GetValues(index int) (x, y float64) {
|
||||
ms.ensureMinValue()
|
||||
x, _ = ms.InnerSeries.GetValue(index)
|
||||
x, _ = ms.InnerSeries.GetValues(index)
|
||||
y = *ms.minValue
|
||||
return
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func (ms *MinSeries) ensureMinValue() {
|
|||
minValue := math.MaxFloat64
|
||||
var y float64
|
||||
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
||||
_, y = ms.InnerSeries.GetValue(x)
|
||||
_, y = ms.InnerSeries.GetValues(x)
|
||||
if y < minValue {
|
||||
minValue = y
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ type MaxSeries struct {
|
|||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
maxValue *float64
|
||||
}
|
||||
|
@ -101,10 +101,10 @@ func (ms MaxSeries) Len() int {
|
|||
return ms.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (ms *MaxSeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (ms *MaxSeries) GetValues(index int) (x, y float64) {
|
||||
ms.ensureMaxValue()
|
||||
x, _ = ms.InnerSeries.GetValue(index)
|
||||
x, _ = ms.InnerSeries.GetValues(index)
|
||||
y = *ms.maxValue
|
||||
return
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ func (ms *MaxSeries) ensureMaxValue() {
|
|||
maxValue := -math.MaxFloat64
|
||||
var y float64
|
||||
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
||||
_, y = ms.InnerSeries.GetValue(x)
|
||||
_, y = ms.InnerSeries.GetValues(x)
|
||||
if y > maxValue {
|
||||
maxValue = y
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ type PolynomialRegressionSeries struct {
|
|||
Limit int
|
||||
Offset int
|
||||
Degree int
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
coeffs []float64
|
||||
}
|
||||
|
@ -79,8 +79,8 @@ func (prs *PolynomialRegressionSeries) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetValue returns the series value for a given index.
|
||||
func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues returns the series value for a given index.
|
||||
func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
|
||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -95,13 +95,13 @@ func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) {
|
|||
|
||||
offset := prs.GetOffset()
|
||||
effectiveIndex := Math.MinInt(index+offset, prs.InnerSeries.Len())
|
||||
x, y = prs.InnerSeries.GetValue(effectiveIndex)
|
||||
x, y = prs.InnerSeries.GetValues(effectiveIndex)
|
||||
y = prs.apply(x)
|
||||
return
|
||||
}
|
||||
|
||||
// GetLastValue computes the last poly regression value.
|
||||
func (prs *PolynomialRegressionSeries) GetLastValue() (x, y float64) {
|
||||
// GetLastValues computes the last poly regression value.
|
||||
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
|
||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func (prs *PolynomialRegressionSeries) GetLastValue() (x, y float64) {
|
|||
prs.coeffs = coeffs
|
||||
}
|
||||
endIndex := prs.GetEndIndex()
|
||||
x, y = prs.InnerSeries.GetValue(endIndex)
|
||||
x, y = prs.InnerSeries.GetValues(endIndex)
|
||||
y = prs.apply(x)
|
||||
return
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) {
|
|||
yvalues = make([]float64, endIndex-startIndex)
|
||||
|
||||
for index := startIndex; index < endIndex; index++ {
|
||||
x, y := prs.InnerSeries.GetValue(index)
|
||||
x, y := prs.InnerSeries.GetValues(index)
|
||||
xvalues[index-startIndex] = x
|
||||
yvalues[index-startIndex] = y
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestPolynomialRegression(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
_, y := poly.GetValue(i)
|
||||
_, y := poly.GetValues(i)
|
||||
assert.InDelta(float64(i*i), y, matrix.DefaultEpsilon)
|
||||
}
|
||||
}
|
||||
|
|
252
ring_buffer.go
252
ring_buffer.go
|
@ -1,252 +0,0 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ringBufferMinimumGrow = 4
|
||||
ringBufferShrinkThreshold = 32
|
||||
ringBufferGrowFactor = 200
|
||||
ringBufferDefaultCapacity = 4
|
||||
)
|
||||
|
||||
var (
|
||||
emptyArray = make([]interface{}, 0)
|
||||
)
|
||||
|
||||
// NewRingBuffer creates a new, empty, RingBuffer.
|
||||
func NewRingBuffer() *RingBuffer {
|
||||
return &RingBuffer{
|
||||
array: make([]interface{}, ringBufferDefaultCapacity),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRingBufferWithCapacity creates a new RingBuffer pre-allocated with the given capacity.
|
||||
func NewRingBufferWithCapacity(capacity int) *RingBuffer {
|
||||
return &RingBuffer{
|
||||
array: make([]interface{}, capacity),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRingBufferFromSlice createsa ring buffer out of a slice.
|
||||
func NewRingBufferFromSlice(values []interface{}) *RingBuffer {
|
||||
return &RingBuffer{
|
||||
array: values,
|
||||
head: 0,
|
||||
tail: len(values) - 1,
|
||||
size: len(values),
|
||||
}
|
||||
}
|
||||
|
||||
// RingBuffer is a fifo buffer that is backed by a pre-allocated array, instead of allocating
|
||||
// a whole new node object for each element (which saves GC churn).
|
||||
// Enqueue can be O(n), Dequeue can be O(1).
|
||||
type RingBuffer struct {
|
||||
array []interface{}
|
||||
head int
|
||||
tail int
|
||||
size int
|
||||
}
|
||||
|
||||
// Len returns the length of the ring buffer (as it is currently populated).
|
||||
// Actual memory footprint may be different.
|
||||
func (rb *RingBuffer) Len() int {
|
||||
return rb.size
|
||||
}
|
||||
|
||||
// TotalLen returns the total size of the ring bufffer, including empty elements.
|
||||
func (rb *RingBuffer) TotalLen() int {
|
||||
return len(rb.array)
|
||||
}
|
||||
|
||||
// Clear removes all objects from the RingBuffer.
|
||||
func (rb *RingBuffer) Clear() {
|
||||
if rb.head < rb.tail {
|
||||
arrayClear(rb.array, rb.head, rb.size)
|
||||
} else {
|
||||
arrayClear(rb.array, rb.head, len(rb.array)-rb.head)
|
||||
arrayClear(rb.array, 0, rb.tail)
|
||||
}
|
||||
|
||||
rb.head = 0
|
||||
rb.tail = 0
|
||||
rb.size = 0
|
||||
}
|
||||
|
||||
// Enqueue adds an element to the "back" of the RingBuffer.
|
||||
func (rb *RingBuffer) Enqueue(object interface{}) {
|
||||
if rb.size == len(rb.array) {
|
||||
newCapacity := int(len(rb.array) * int(ringBufferGrowFactor/100))
|
||||
if newCapacity < (len(rb.array) + ringBufferMinimumGrow) {
|
||||
newCapacity = len(rb.array) + ringBufferMinimumGrow
|
||||
}
|
||||
rb.setCapacity(newCapacity)
|
||||
}
|
||||
|
||||
rb.array[rb.tail] = object
|
||||
rb.tail = (rb.tail + 1) % len(rb.array)
|
||||
rb.size++
|
||||
}
|
||||
|
||||
// Dequeue removes the first element from the RingBuffer.
|
||||
func (rb *RingBuffer) Dequeue() interface{} {
|
||||
if rb.size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
removed := rb.array[rb.head]
|
||||
rb.head = (rb.head + 1) % len(rb.array)
|
||||
rb.size--
|
||||
return removed
|
||||
}
|
||||
|
||||
// Peek returns but does not remove the first element.
|
||||
func (rb *RingBuffer) Peek() interface{} {
|
||||
if rb.size == 0 {
|
||||
return nil
|
||||
}
|
||||
return rb.array[rb.head]
|
||||
}
|
||||
|
||||
// PeekBack returns but does not remove the last element.
|
||||
func (rb *RingBuffer) PeekBack() interface{} {
|
||||
if rb.size == 0 {
|
||||
return nil
|
||||
}
|
||||
if rb.tail == 0 {
|
||||
return rb.array[len(rb.array)-1]
|
||||
}
|
||||
return rb.array[rb.tail-1]
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) setCapacity(capacity int) {
|
||||
newArray := make([]interface{}, capacity)
|
||||
if rb.size > 0 {
|
||||
if rb.head < rb.tail {
|
||||
arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
|
||||
} else {
|
||||
arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
|
||||
arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
|
||||
}
|
||||
}
|
||||
rb.array = newArray
|
||||
rb.head = 0
|
||||
if rb.size == capacity {
|
||||
rb.tail = 0
|
||||
} else {
|
||||
rb.tail = rb.size
|
||||
}
|
||||
}
|
||||
|
||||
// TrimExcess resizes the buffer to better fit the contents.
|
||||
func (rb *RingBuffer) TrimExcess() {
|
||||
threshold := float64(len(rb.array)) * 0.9
|
||||
if rb.size < int(threshold) {
|
||||
rb.setCapacity(rb.size)
|
||||
}
|
||||
}
|
||||
|
||||
// AsSlice returns the ring buffer, in order, as a slice.
|
||||
func (rb *RingBuffer) AsSlice() []interface{} {
|
||||
newArray := make([]interface{}, rb.size)
|
||||
|
||||
if rb.size == 0 {
|
||||
return newArray
|
||||
}
|
||||
|
||||
if rb.head < rb.tail {
|
||||
arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
|
||||
} else {
|
||||
arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
|
||||
arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
|
||||
}
|
||||
|
||||
return newArray
|
||||
}
|
||||
|
||||
// Each calls the consumer for each element in the buffer.
|
||||
func (rb *RingBuffer) Each(consumer func(value interface{})) {
|
||||
if rb.size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if rb.head < rb.tail {
|
||||
for cursor := rb.head; cursor < rb.tail; cursor++ {
|
||||
consumer(rb.array[cursor])
|
||||
}
|
||||
} else {
|
||||
for cursor := rb.head; cursor < len(rb.array); cursor++ {
|
||||
consumer(rb.array[cursor])
|
||||
}
|
||||
for cursor := 0; cursor < rb.tail; cursor++ {
|
||||
consumer(rb.array[cursor])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rb *RingBuffer) String() string {
|
||||
var values []string
|
||||
for _, elem := range rb.AsSlice() {
|
||||
values = append(values, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return strings.Join(values, " <= ")
|
||||
}
|
||||
|
||||
// Average returns the float average of the values in the buffer.
|
||||
func (rb *RingBuffer) Average() float64 {
|
||||
var accum float64
|
||||
rb.Each(func(v interface{}) {
|
||||
if typed, isTyped := v.(float64); isTyped {
|
||||
accum += typed
|
||||
}
|
||||
})
|
||||
return accum / float64(rb.Len())
|
||||
}
|
||||
|
||||
// Variance computes the variance of the buffer.
|
||||
func (rb *RingBuffer) Variance() float64 {
|
||||
if rb.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var variance float64
|
||||
m := rb.Average()
|
||||
|
||||
rb.Each(func(v interface{}) {
|
||||
if n, isTyped := v.(float64); isTyped {
|
||||
variance += (float64(n) - m) * (float64(n) - m)
|
||||
}
|
||||
})
|
||||
|
||||
return variance / float64(rb.Len())
|
||||
}
|
||||
|
||||
// StdDev returns the standard deviation.
|
||||
func (rb *RingBuffer) StdDev() float64 {
|
||||
return math.Pow(rb.Variance(), 0.5)
|
||||
}
|
||||
|
||||
func arrayClear(source []interface{}, index, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
absoluteIndex := x + index
|
||||
source[absoluteIndex] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func arrayCopy(source []interface{}, sourceIndex int, destination []interface{}, destinationIndex, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
from := sourceIndex + x
|
||||
to := destinationIndex + x
|
||||
|
||||
destination[to] = source[from]
|
||||
}
|
||||
}
|
214
sequence.go
214
sequence.go
|
@ -1,190 +1,82 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
import "math"
|
||||
|
||||
var (
|
||||
// Sequence contains some sequence utilities.
|
||||
// These utilities can be useful for generating test data.
|
||||
Sequence = &sequence{
|
||||
rnd: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
}
|
||||
)
|
||||
|
||||
type sequence struct {
|
||||
rnd *rand.Rand
|
||||
// SequenceProvider is a provider for values for a sequence.
|
||||
type SequenceProvider interface {
|
||||
Len() int
|
||||
GetValue(int) float64
|
||||
}
|
||||
|
||||
// Float64 produces an array of floats from [start,end] by optional steps.
|
||||
func (s sequence) Float64(start, end float64, steps ...float64) []float64 {
|
||||
var values []float64
|
||||
step := 1.0
|
||||
if len(steps) > 0 {
|
||||
step = steps[0]
|
||||
// Sequence is a utility wrapper for sequence providers.
|
||||
type Sequence struct {
|
||||
SequenceProvider
|
||||
}
|
||||
|
||||
if start < end {
|
||||
for x := start; x <= end; x += step {
|
||||
values = append(values, x)
|
||||
// Each applies the `mapfn` to all values in the value provider.
|
||||
func (s Sequence) Each(mapfn func(int, float64)) {
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
mapfn(i, s.GetValue(i))
|
||||
}
|
||||
} else {
|
||||
for x := start; x >= end; x = x - step {
|
||||
values = append(values, x)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Random generates a fixed length sequence of random values between (0, scale).
|
||||
func (s sequence) Random(samples int, scale float64) []float64 {
|
||||
values := make([]float64, samples)
|
||||
|
||||
for x := 0; x < samples; x++ {
|
||||
values[x] = s.rnd.Float64() * scale
|
||||
// Map applies the `mapfn` to all values in the value provider,
|
||||
// returning a new sequence.
|
||||
func (s Sequence) Map(mapfn func(i int, v float64) float64) Sequence {
|
||||
output := make([]float64, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
mapfn(i, s.GetValue(i))
|
||||
}
|
||||
return Sequence{Array(output)}
|
||||
}
|
||||
|
||||
return values
|
||||
// Average returns the float average of the values in the buffer.
|
||||
func (s Sequence) Average() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Random generates a fixed length sequence of random values with a given average, above and below that average by (-scale, scale)
|
||||
func (s sequence) RandomWithAverage(samples int, average, scale float64) []float64 {
|
||||
values := make([]float64, samples)
|
||||
|
||||
for x := 0; x < samples; x++ {
|
||||
jitter := scale - (s.rnd.Float64() * (2 * scale))
|
||||
values[x] = average + jitter
|
||||
var accum float64
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
accum += s.GetValue(i)
|
||||
}
|
||||
return accum / float64(s.Len())
|
||||
}
|
||||
|
||||
return values
|
||||
// Variance computes the variance of the buffer.
|
||||
func (s Sequence) Variance() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Days generates a sequence of timestamps by day, from -days to today.
|
||||
func (s sequence) 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
|
||||
m := s.Average()
|
||||
var variance, v float64
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
v = s.GetValue(i)
|
||||
variance += (v - m) * (v - m)
|
||||
}
|
||||
|
||||
func (s sequence) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
todayOpen := Date.On(marketOpen, cursor)
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if (cursor.Equal(todayOpen) || cursor.After(todayOpen)) && (cursor.Equal(todayClose) || cursor.Before(todayClose)) && isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
if cursor.After(todayClose) {
|
||||
cursor = Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||
} else {
|
||||
cursor = Date.NextHour(cursor)
|
||||
}
|
||||
}
|
||||
return times
|
||||
return variance / float64(s.Len())
|
||||
}
|
||||
|
||||
func (s sequence) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if isValidTradingDay {
|
||||
todayOpen := Date.On(marketOpen, cursor)
|
||||
todayNoon := Date.NoonOn(cursor)
|
||||
today2pm := Date.On(Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
||||
// StdDev returns the standard deviation.
|
||||
func (s Sequence) StdDev() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
cursor = Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
return math.Pow(s.Variance(), 0.5)
|
||||
}
|
||||
|
||||
func (s sequence) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
// 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)
|
||||
}
|
||||
|
||||
cursor = Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (s sequence) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketOpen, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
}
|
||||
|
||||
cursor = cursor.AddDate(0, 0, 2)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (s sequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := Date.On(marketClose, from)
|
||||
toClose := Date.On(marketClose, to)
|
||||
|
||||
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (s sequence) 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
|
||||
}
|
||||
|
||||
// HoursFill adds zero values for the data bounded by the start and end of the xdata array.
|
||||
func (s sequence) HoursFill(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||
start := Date.Start(xdata)
|
||||
end := Date.End(xdata)
|
||||
|
||||
totalHours := Math.AbsInt(Date.DiffHours(start, end))
|
||||
|
||||
finalTimes := s.Hours(start, totalHours+1)
|
||||
finalValues := make([]float64, totalHours+1)
|
||||
|
||||
var hoursFromStart int
|
||||
for i, xd := range xdata {
|
||||
hoursFromStart = Date.DiffHours(start, xd)
|
||||
finalValues[hoursFromStart] = ydata[i]
|
||||
}
|
||||
|
||||
return finalTimes, finalValues
|
||||
// GetValue returns the value at a given index.
|
||||
func (a Array) GetValue(index int) float64 {
|
||||
return a[index]
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ type SMASeries struct {
|
|||
YAxis YAxisType
|
||||
|
||||
Period int
|
||||
InnerSeries ValueProvider
|
||||
InnerSeries ValuesProvider
|
||||
}
|
||||
|
||||
// GetName returns the name of the time series.
|
||||
|
@ -48,25 +48,25 @@ func (sma SMASeries) GetPeriod(defaults ...int) int {
|
|||
return sma.Period
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (sma SMASeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (sma SMASeries) GetValues(index int) (x, y float64) {
|
||||
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
px, _ := sma.InnerSeries.GetValue(index)
|
||||
px, _ := sma.InnerSeries.GetValues(index)
|
||||
x = px
|
||||
y = sma.getAverage(index)
|
||||
return
|
||||
}
|
||||
|
||||
// GetLastValue computes the last moving average value but walking back window size samples,
|
||||
// GetLastValues computes the last moving average value but walking back window size samples,
|
||||
// and recomputing the last moving average chunk.
|
||||
func (sma SMASeries) GetLastValue() (x, y float64) {
|
||||
func (sma SMASeries) GetLastValues() (x, y float64) {
|
||||
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
|
||||
return
|
||||
}
|
||||
seriesLen := sma.InnerSeries.Len()
|
||||
px, _ := sma.InnerSeries.GetValue(seriesLen - 1)
|
||||
px, _ := sma.InnerSeries.GetValues(seriesLen - 1)
|
||||
x = px
|
||||
y = sma.getAverage(seriesLen - 1)
|
||||
return
|
||||
|
@ -78,7 +78,7 @@ func (sma SMASeries) getAverage(index int) float64 {
|
|||
var accum float64
|
||||
var count float64
|
||||
for x := index; x >= floor; x-- {
|
||||
_, vy := sma.InnerSeries.GetValue(x)
|
||||
_, vy := sma.InnerSeries.GetValues(x)
|
||||
accum += vy
|
||||
count += 1.0
|
||||
}
|
||||
|
|
|
@ -6,16 +6,16 @@ import (
|
|||
"github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
type mockValueProvider struct {
|
||||
type mockValuesProvider struct {
|
||||
X []float64
|
||||
Y []float64
|
||||
}
|
||||
|
||||
func (m mockValueProvider) Len() int {
|
||||
func (m mockValuesProvider) Len() int {
|
||||
return Math.MinInt(len(m.X), len(m.Y))
|
||||
}
|
||||
|
||||
func (m mockValueProvider) GetValue(index int) (x, y float64) {
|
||||
func (m mockValuesProvider) GetValues(index int) (x, y float64) {
|
||||
if index < 0 {
|
||||
panic("negative index at GetValue()")
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ func (m mockValueProvider) GetValue(index int) (x, y float64) {
|
|||
func TestSMASeriesGetValue(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockSeries := mockValueProvider{
|
||||
Sequence.Float64(1.0, 10.0),
|
||||
Sequence.Float64(10, 1.0),
|
||||
mockSeries := mockValuesProvider{
|
||||
Generate.Float64(1.0, 10.0),
|
||||
Generate.Float64(10, 1.0),
|
||||
}
|
||||
assert.Equal(10, mockSeries.Len())
|
||||
|
||||
|
@ -43,7 +43,7 @@ func TestSMASeriesGetValue(t *testing.T) {
|
|||
|
||||
var yvalues []float64
|
||||
for x := 0; x < mas.Len(); x++ {
|
||||
_, y := mas.GetValue(x)
|
||||
_, y := mas.GetValues(x)
|
||||
yvalues = append(yvalues, y)
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,9 @@ func TestSMASeriesGetValue(t *testing.T) {
|
|||
func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockSeries := mockValueProvider{
|
||||
Sequence.Float64(1.0, 10.0),
|
||||
Sequence.Float64(10, 1.0),
|
||||
mockSeries := mockValuesProvider{
|
||||
Generate.Float64(1.0, 10.0),
|
||||
Generate.Float64(10, 1.0),
|
||||
}
|
||||
assert.Equal(10, mockSeries.Len())
|
||||
|
||||
|
@ -74,11 +74,11 @@ func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
|||
|
||||
var yvalues []float64
|
||||
for x := 0; x < mas.Len(); x++ {
|
||||
_, y := mas.GetValue(x)
|
||||
_, y := mas.GetValues(x)
|
||||
yvalues = append(yvalues, y)
|
||||
}
|
||||
|
||||
lx, ly := mas.GetLastValue()
|
||||
lx, ly := mas.GetLastValues()
|
||||
assert.Equal(10.0, lx)
|
||||
assert.Equal(5.5, ly)
|
||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||
|
@ -87,9 +87,9 @@ func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
|||
func TestSMASeriesGetLastValue(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockSeries := mockValueProvider{
|
||||
Sequence.Float64(1.0, 100.0),
|
||||
Sequence.Float64(100, 1.0),
|
||||
mockSeries := mockValuesProvider{
|
||||
Generate.Float64(1.0, 100.0),
|
||||
Generate.Float64(100, 1.0),
|
||||
}
|
||||
assert.Equal(100, mockSeries.Len())
|
||||
|
||||
|
@ -100,11 +100,11 @@ func TestSMASeriesGetLastValue(t *testing.T) {
|
|||
|
||||
var yvalues []float64
|
||||
for x := 0; x < mas.Len(); x++ {
|
||||
_, y := mas.GetValue(x)
|
||||
_, y := mas.GetValues(x)
|
||||
yvalues = append(yvalues, y)
|
||||
}
|
||||
|
||||
lx, ly := mas.GetLastValue()
|
||||
lx, ly := mas.GetLastValues()
|
||||
assert.Equal(100.0, lx)
|
||||
assert.Equal(6, ly)
|
||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||
|
|
|
@ -200,7 +200,7 @@ func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
|||
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
||||
r.Stroke()
|
||||
|
||||
ticks := Sequence.Float64(1.0, 0.0, 0.2)
|
||||
ticks := Generate.Float64(1.0, 0.0, 0.2)
|
||||
for _, t := range ticks {
|
||||
axisStyle.GetStrokeOptions().WriteToRenderer(r)
|
||||
ty := canvasBox.Bottom - int(t*float64(canvasBox.Height()))
|
||||
|
|
|
@ -31,15 +31,15 @@ func (ts TimeSeries) Len() int {
|
|||
return len(ts.XValues)
|
||||
}
|
||||
|
||||
// GetValue gets a value at a given index.
|
||||
func (ts TimeSeries) GetValue(index int) (x, y float64) {
|
||||
// GetValues gets a value at a given index.
|
||||
func (ts TimeSeries) GetValues(index int) (x, y float64) {
|
||||
x = Time.ToFloat64(ts.XValues[index])
|
||||
y = ts.YValues[index]
|
||||
return
|
||||
}
|
||||
|
||||
// GetLastValue gets the last value.
|
||||
func (ts TimeSeries) GetLastValue() (x, y float64) {
|
||||
// GetLastValues gets the last value.
|
||||
func (ts TimeSeries) GetLastValues() (x, y float64) {
|
||||
x = Time.ToFloat64(ts.XValues[len(ts.XValues)-1])
|
||||
y = ts.YValues[len(ts.YValues)-1]
|
||||
return
|
||||
|
|
|
@ -24,7 +24,7 @@ func TestTimeSeriesGetValue(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
x0, y0 := ts.GetValue(0)
|
||||
x0, y0 := ts.GetValues(0)
|
||||
assert.NotZero(x0)
|
||||
assert.Equal(1.0, y0)
|
||||
}
|
||||
|
|
229
value_buffer.go
Normal file
229
value_buffer.go
Normal file
|
@ -0,0 +1,229 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
valueBufferMinimumGrow = 4
|
||||
valueBufferShrinkThreshold = 32
|
||||
valueBufferGrowFactor = 200
|
||||
valueBufferDefaultCapacity = 4
|
||||
)
|
||||
|
||||
var (
|
||||
emptyArray = make([]float64, 0)
|
||||
)
|
||||
|
||||
// NewValueBuffer creates a new value buffer with an optional set of values.
|
||||
func NewValueBuffer(values ...float64) *ValueBuffer {
|
||||
var tail int
|
||||
array := make([]float64, Math.MaxInt(len(values), valueBufferDefaultCapacity))
|
||||
if len(values) > 0 {
|
||||
copy(array, values)
|
||||
tail = len(values)
|
||||
}
|
||||
return &ValueBuffer{
|
||||
array: array,
|
||||
head: 0,
|
||||
tail: tail,
|
||||
size: len(values),
|
||||
}
|
||||
}
|
||||
|
||||
// NewValueBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
||||
func NewValueBufferWithCapacity(capacity int) *ValueBuffer {
|
||||
return &ValueBuffer{
|
||||
array: make([]float64, capacity),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ValueBuffer is a fifo buffer that is backed by a pre-allocated array, instead of allocating
|
||||
// a whole new node object for each element (which saves GC churn).
|
||||
// Enqueue can be O(n), Dequeue can be O(1).
|
||||
type ValueBuffer struct {
|
||||
array []float64
|
||||
head int
|
||||
tail int
|
||||
size int
|
||||
}
|
||||
|
||||
// Len returns the length of the ValueBuffer (as it is currently populated).
|
||||
// Actual memory footprint may be different.
|
||||
func (vb *ValueBuffer) Len() int {
|
||||
return vb.size
|
||||
}
|
||||
|
||||
// GetValue implements sequence provider.
|
||||
func (vb *ValueBuffer) GetValue(index int) float64 {
|
||||
effectiveIndex := (vb.head + index) % len(vb.array)
|
||||
return vb.array[effectiveIndex]
|
||||
}
|
||||
|
||||
// Capacity returns the total size of the ValueBuffer, including empty elements.
|
||||
func (vb *ValueBuffer) Capacity() int {
|
||||
return len(vb.array)
|
||||
}
|
||||
|
||||
// SetCapacity sets the capacity of the ValueBuffer.
|
||||
func (vb *ValueBuffer) SetCapacity(capacity int) {
|
||||
newArray := make([]float64, capacity)
|
||||
if vb.size > 0 {
|
||||
if vb.head < vb.tail {
|
||||
arrayCopy(vb.array, vb.head, newArray, 0, vb.size)
|
||||
} else {
|
||||
arrayCopy(vb.array, vb.head, newArray, 0, len(vb.array)-vb.head)
|
||||
arrayCopy(vb.array, 0, newArray, len(vb.array)-vb.head, vb.tail)
|
||||
}
|
||||
}
|
||||
vb.array = newArray
|
||||
vb.head = 0
|
||||
if vb.size == capacity {
|
||||
vb.tail = 0
|
||||
} else {
|
||||
vb.tail = vb.size
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all objects from the ValueBuffer.
|
||||
func (vb *ValueBuffer) Clear() {
|
||||
if vb.head < vb.tail {
|
||||
arrayClear(vb.array, vb.head, vb.size)
|
||||
} else {
|
||||
arrayClear(vb.array, vb.head, len(vb.array)-vb.head)
|
||||
arrayClear(vb.array, 0, vb.tail)
|
||||
}
|
||||
|
||||
vb.head = 0
|
||||
vb.tail = 0
|
||||
vb.size = 0
|
||||
}
|
||||
|
||||
// Enqueue adds an element to the "back" of the ValueBuffer.
|
||||
func (vb *ValueBuffer) Enqueue(value float64) {
|
||||
if vb.size == len(vb.array) {
|
||||
newCapacity := int(len(vb.array) * int(valueBufferGrowFactor/100))
|
||||
if newCapacity < (len(vb.array) + valueBufferMinimumGrow) {
|
||||
newCapacity = len(vb.array) + valueBufferMinimumGrow
|
||||
}
|
||||
vb.SetCapacity(newCapacity)
|
||||
}
|
||||
|
||||
vb.array[vb.tail] = value
|
||||
vb.tail = (vb.tail + 1) % len(vb.array)
|
||||
vb.size++
|
||||
}
|
||||
|
||||
// Dequeue removes the first element from the RingBuffer.
|
||||
func (vb *ValueBuffer) Dequeue() float64 {
|
||||
if vb.size == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
removed := vb.array[vb.head]
|
||||
vb.head = (vb.head + 1) % len(vb.array)
|
||||
vb.size--
|
||||
return removed
|
||||
}
|
||||
|
||||
// Peek returns but does not remove the first element.
|
||||
func (vb *ValueBuffer) Peek() float64 {
|
||||
if vb.size == 0 {
|
||||
return 0
|
||||
}
|
||||
return vb.array[vb.head]
|
||||
}
|
||||
|
||||
// PeekBack returns but does not remove the last element.
|
||||
func (vb *ValueBuffer) PeekBack() float64 {
|
||||
if vb.size == 0 {
|
||||
return 0
|
||||
}
|
||||
if vb.tail == 0 {
|
||||
return vb.array[len(vb.array)-1]
|
||||
}
|
||||
return vb.array[vb.tail-1]
|
||||
}
|
||||
|
||||
// TrimExcess resizes the buffer to better fit the contents.
|
||||
func (vb *ValueBuffer) TrimExcess() {
|
||||
threshold := float64(len(vb.array)) * 0.9
|
||||
if vb.size < int(threshold) {
|
||||
vb.SetCapacity(vb.size)
|
||||
}
|
||||
}
|
||||
|
||||
// Array returns the ring buffer, in order, as an array.
|
||||
func (vb *ValueBuffer) Array() Array {
|
||||
newArray := make([]float64, vb.size)
|
||||
|
||||
if vb.size == 0 {
|
||||
return newArray
|
||||
}
|
||||
|
||||
if vb.head < vb.tail {
|
||||
arrayCopy(vb.array, vb.head, newArray, 0, vb.size)
|
||||
} else {
|
||||
arrayCopy(vb.array, vb.head, newArray, 0, len(vb.array)-vb.head)
|
||||
arrayCopy(vb.array, 0, newArray, len(vb.array)-vb.head, vb.tail)
|
||||
}
|
||||
|
||||
return Array(newArray)
|
||||
}
|
||||
|
||||
// Each calls the consumer for each element in the buffer.
|
||||
func (vb *ValueBuffer) Each(mapfn func(int, float64)) {
|
||||
if vb.size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var index int
|
||||
if vb.head < vb.tail {
|
||||
for cursor := vb.head; cursor < vb.tail; cursor++ {
|
||||
mapfn(index, vb.array[cursor])
|
||||
index++
|
||||
}
|
||||
} else {
|
||||
for cursor := vb.head; cursor < len(vb.array); cursor++ {
|
||||
mapfn(index, vb.array[cursor])
|
||||
index++
|
||||
}
|
||||
for cursor := 0; cursor < vb.tail; cursor++ {
|
||||
mapfn(index, vb.array[cursor])
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation for value buffers.
|
||||
func (vb *ValueBuffer) String() string {
|
||||
var values []string
|
||||
for _, elem := range vb.Array() {
|
||||
values = append(values, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return strings.Join(values, " <= ")
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Util methods
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
func arrayClear(source []float64, index, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
absoluteIndex := x + index
|
||||
source[absoluteIndex] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
from := sourceIndex + x
|
||||
to := destinationIndex + x
|
||||
|
||||
destination[to] = source[from]
|
||||
}
|
||||
}
|
|
@ -6,10 +6,10 @@ import (
|
|||
"github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestRingBuffer(t *testing.T) {
|
||||
func TestValueBuffer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewRingBuffer()
|
||||
buffer := NewValueBuffer()
|
||||
|
||||
buffer.Enqueue(1)
|
||||
assert.Equal(1, buffer.Len())
|
||||
|
@ -96,14 +96,14 @@ func TestRingBuffer(t *testing.T) {
|
|||
value = buffer.Dequeue()
|
||||
assert.Equal(8, value)
|
||||
assert.Equal(0, buffer.Len())
|
||||
assert.Nil(buffer.Peek())
|
||||
assert.Nil(buffer.PeekBack())
|
||||
assert.Zero(buffer.Peek())
|
||||
assert.Zero(buffer.PeekBack())
|
||||
}
|
||||
|
||||
func TestRingBufferClear(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewRingBuffer()
|
||||
buffer := NewValueBuffer()
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
|
@ -117,21 +117,21 @@ func TestRingBufferClear(t *testing.T) {
|
|||
|
||||
buffer.Clear()
|
||||
assert.Equal(0, buffer.Len())
|
||||
assert.Nil(buffer.Peek())
|
||||
assert.Nil(buffer.PeekBack())
|
||||
assert.Zero(buffer.Peek())
|
||||
assert.Zero(buffer.PeekBack())
|
||||
}
|
||||
|
||||
func TestRingBufferAsSlice(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewRingBuffer()
|
||||
buffer := NewValueBuffer()
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(2)
|
||||
buffer.Enqueue(3)
|
||||
buffer.Enqueue(4)
|
||||
buffer.Enqueue(5)
|
||||
|
||||
contents := buffer.AsSlice()
|
||||
contents := buffer.Array()
|
||||
assert.Len(contents, 5)
|
||||
assert.Equal(1, contents[0])
|
||||
assert.Equal(2, contents[1])
|
||||
|
@ -143,20 +143,40 @@ func TestRingBufferAsSlice(t *testing.T) {
|
|||
func TestRingBufferEach(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewRingBuffer()
|
||||
buffer := NewValueBuffer()
|
||||
|
||||
for x := 1; x < 17; x++ {
|
||||
buffer.Enqueue(x)
|
||||
buffer.Enqueue(float64(x))
|
||||
}
|
||||
|
||||
called := 0
|
||||
buffer.Each(func(v interface{}) {
|
||||
if typed, isTyped := v.(int); isTyped {
|
||||
if typed == (called + 1) {
|
||||
buffer.Each(func(_ int, v float64) {
|
||||
if v == float64(called+1) {
|
||||
called++
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
assert.Equal(16, called)
|
||||
}
|
||||
|
||||
func TestNewValueBuffer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
empty := NewValueBuffer()
|
||||
assert.NotNil(empty)
|
||||
assert.Zero(empty.Len())
|
||||
assert.Equal(valueBufferDefaultCapacity, empty.Capacity())
|
||||
assert.Zero(empty.Peek())
|
||||
assert.Zero(empty.PeekBack())
|
||||
}
|
||||
|
||||
func TestNewValueBufferWithValues(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := NewValueBuffer(1, 2, 3, 4)
|
||||
assert.NotNil(values)
|
||||
assert.Equal(4, values.Len())
|
||||
assert.Equal(valueBufferDefaultCapacity, values.Capacity())
|
||||
assert.Equal(1, values.Peek())
|
||||
assert.Equal(4, values.PeekBack())
|
||||
}
|
|
@ -2,38 +2,38 @@ package chart
|
|||
|
||||
import "github.com/wcharczuk/go-chart/drawing"
|
||||
|
||||
// ValueProvider is a type that produces values.
|
||||
type ValueProvider interface {
|
||||
// ValuesProvider is a type that produces values.
|
||||
type ValuesProvider interface {
|
||||
Len() int
|
||||
GetValue(index int) (float64, float64)
|
||||
GetValues(index int) (float64, float64)
|
||||
}
|
||||
|
||||
// BoundedValueProvider allows series to return a range.
|
||||
type BoundedValueProvider interface {
|
||||
// BoundedValuesProvider allows series to return a range.
|
||||
type BoundedValuesProvider interface {
|
||||
Len() int
|
||||
GetBoundedValue(index int) (x, y1, y2 float64)
|
||||
GetBoundedValues(index int) (x, y1, y2 float64)
|
||||
}
|
||||
|
||||
// LastValueProvider is a special type of value provider that can return it's (potentially computed) last value.
|
||||
type LastValueProvider interface {
|
||||
GetLastValue() (x, y float64)
|
||||
// LastValuesProvider is a special type of value provider that can return it's (potentially computed) last value.
|
||||
type LastValuesProvider interface {
|
||||
GetLastValues() (x, y float64)
|
||||
}
|
||||
|
||||
// BoundedLastValueProvider is a special type of value provider that can return it's (potentially computed) bounded last value.
|
||||
type BoundedLastValueProvider interface {
|
||||
GetBoundedLastValue() (x, y1, y2 float64)
|
||||
// BoundedLastValuesProvider is a special type of value provider that can return it's (potentially computed) bounded last value.
|
||||
type BoundedLastValuesProvider interface {
|
||||
GetBoundedLastValues() (x, y1, y2 float64)
|
||||
}
|
||||
|
||||
// FullValueProvider is an interface that combines `ValueProvider` and `LastValueProvider`
|
||||
type FullValueProvider interface {
|
||||
ValueProvider
|
||||
LastValueProvider
|
||||
// FullValuesProvider is an interface that combines `ValuesProvider` and `LastValuesProvider`
|
||||
type FullValuesProvider interface {
|
||||
ValuesProvider
|
||||
LastValuesProvider
|
||||
}
|
||||
|
||||
// FullBoundedValueProvider is an interface that combines `BoundedValueProvider` and `BoundedLastValueProvider`
|
||||
type FullBoundedValueProvider interface {
|
||||
BoundedValueProvider
|
||||
BoundedLastValueProvider
|
||||
// FullBoundedValuesProvider is an interface that combines `BoundedValuesProvider` and `BoundedLastValuesProvider`
|
||||
type FullBoundedValuesProvider interface {
|
||||
BoundedValuesProvider
|
||||
BoundedLastValuesProvider
|
||||
}
|
||||
|
||||
// SizeProvider is a provider for integer size.
|
||||
|
|
Loading…
Reference in a new issue