Compare commits
18 commits
main
...
pointers-a
Author | SHA1 | Date | |
---|---|---|---|
|
50b025e273 | ||
|
4d93883953 | ||
|
9ca1b0466a | ||
|
182c4eeed2 | ||
|
fde118dd7c | ||
|
1556c5e843 | ||
|
2f84185f46 | ||
|
c0fc2baa50 | ||
|
41d81c82db | ||
|
c47025edf6 | ||
|
1c1430e3b8 | ||
|
674e75d285 | ||
|
6a4f3936c6 | ||
|
814470733e | ||
|
472d04b3fa | ||
|
255390c710 | ||
|
376a8efa36 | ||
|
65a0895143 |
BIN
.DS_Store
vendored
Normal file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
@ -20,12 +20,18 @@ func drawLargeChart(res http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
numSeriesInt64 = int64(1)
|
numSeriesInt64 = int64(1)
|
||||||
}
|
}
|
||||||
|
if numSeriesInt64 == 0 {
|
||||||
|
numSeriesInt64 = 1
|
||||||
|
}
|
||||||
numSeries := int(numSeriesInt64)
|
numSeries := int(numSeriesInt64)
|
||||||
|
|
||||||
numValuesInt64, err := strconv.ParseInt(r.FormValue("values"), 10, 64)
|
numValuesInt64, err := strconv.ParseInt(r.FormValue("values"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
numValuesInt64 = int64(100)
|
numValuesInt64 = int64(100)
|
||||||
}
|
}
|
||||||
|
if numValuesInt64 == 0 {
|
||||||
|
numValuesInt64 = int64(100)
|
||||||
|
}
|
||||||
numValues := int(numValuesInt64)
|
numValues := int(numValuesInt64)
|
||||||
|
|
||||||
series := make([]chart.Series, numSeries)
|
series := make([]chart.Series, numSeries)
|
||||||
|
|
BIN
_examples/benchmark_line_charts/output.png
Normal file
After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -30,8 +31,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: chart.Sequence.Random(100.0, 256.0),
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,8 +58,8 @@ func drawChartDefault(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: chart.Sequence.Random(100.0, 256.0),
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
BIN
_examples/descending/output.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
@ -4,19 +4,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
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.
|
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{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Sequence.Random(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we create a LinearRegressionSeries series by assignin the inner series.
|
// note we create a LinearRegressionSeries series by assignin the inner series.
|
||||||
|
|
|
@ -4,13 +4,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
start := chart.Date.Date(2016, 7, 01, chart.Date.Eastern())
|
start := util.Date.Date(2016, 7, 01, util.Date.Eastern())
|
||||||
end := chart.Date.Date(2016, 07, 21, chart.Date.Eastern())
|
end := util.Date.Date(2016, 07, 21, util.Date.Eastern())
|
||||||
xv := chart.Sequence.MarketHours(start, end, chart.NYSEOpen, chart.NYSEClose, chart.Date.IsNYSEHoliday)
|
xv := seq.Time.MarketHours(start, end, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||||
yv := chart.Sequence.RandomWithAverage(len(xv), 200, 10)
|
yv := seq.New(seq.NewRandom().WithLen(len(xv)).WithAverage(200).WithScale(10)).Array()
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
|
@ -18,9 +20,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
ValueFormatter: chart.TimeHourValueFormatter,
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
Range: &chart.MarketHoursRange{
|
Range: &chart.MarketHoursRange{
|
||||||
MarketOpen: chart.NYSEOpen,
|
MarketOpen: util.NYSEOpen(),
|
||||||
MarketClose: chart.NYSEClose,
|
MarketClose: util.NYSEClose(),
|
||||||
HolidayProvider: chart.Date.IsNYSEHoliday,
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: chart.Sequence.RandomWithAverage(100, 100, 50),
|
YValues: seq.New(seq.NewRandom().WithLen(100).WithAverage(100).WithScale(50)).Array(),
|
||||||
}
|
}
|
||||||
|
|
||||||
minSeries := &chart.MinSeries{
|
minSeries := &chart.MinSeries{
|
||||||
|
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
@ -4,19 +4,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
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.
|
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{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Sequence.Random(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
polyRegSeries := &chart.PolynomialRegressionSeries{
|
polyRegSeries := &chart.PolynomialRegressionSeries{
|
||||||
|
|
BIN
_examples/poly_regression/output.png
Normal file
After Width: | Height: | Size: 54 KiB |
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseInt(str string) int {
|
func parseInt(str string) int {
|
||||||
|
@ -23,7 +24,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 := chart.File.ReadByLines("requests.csv", func(line string) {
|
err := util.File.ReadByLines("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])
|
||||||
|
@ -32,6 +33,7 @@ func readData() ([]time.Time, []float64) {
|
||||||
elapsedMillis := parseFloat64(parts[4])
|
elapsedMillis := parseFloat64(parts[4])
|
||||||
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
|
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
|
||||||
yvalues = append(yvalues, elapsedMillis)
|
yvalues = append(yvalues, elapsedMillis)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
|
@ -41,12 +43,12 @@ func readData() ([]time.Time, []float64) {
|
||||||
|
|
||||||
func releases() []chart.GridLine {
|
func releases() []chart.GridLine {
|
||||||
return []chart.GridLine{
|
return []chart.GridLine{
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 2, 15, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 15, 30, 0, 0, time.UTC))},
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
|
||||||
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +127,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
||||||
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
res.Header().Set("Content-Type", chart.ContentTypePNG)
|
||||||
graph.Render(chart.SVG, res)
|
graph.Render(chart.PNG, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -25,8 +26,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
DotWidth: 5,
|
DotWidth: 5,
|
||||||
DotColorProvider: viridisByY,
|
DotColorProvider: viridisByY,
|
||||||
},
|
},
|
||||||
XValues: chart.Sequence.Random(128, 1024),
|
XValues: seq.Range(0, 127),
|
||||||
YValues: chart.Sequence.Random(128, 1024),
|
YValues: seq.New(seq.NewRandom().WithLen(128).WithMax(1024)).Array(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -50,8 +51,8 @@ func unit(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: chart.Sequence.Float64(0, 4, 1),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: chart.Sequence.Float64(0, 4, 1),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
|
@ -4,19 +4,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Sequence.Float64(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Sequence.Random(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithMax(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we create a SimpleMovingAverage series by assignin the inner series.
|
// note we create a SimpleMovingAverage series by assignin the inner series.
|
||||||
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
_examples/text_rotation/output.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -23,7 +24,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: func(v interface{}) string {
|
||||||
typed := v.(float64)
|
typed := v.(float64)
|
||||||
typedDate := chart.Time.FromFloat64(typed)
|
typedDate := util.Time.FromFloat64(typed)
|
||||||
return fmt.Sprintf("%d-%d\n%d", typedDate.Month(), typedDate.Day(), typedDate.Year())
|
return fmt.Sprintf("%d-%d\n%d", typedDate.Month(), typedDate.Day(), typedDate.Year())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
|
@ -4,8 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
//"time"
|
|
||||||
"github.com/wcharczuk/go-chart" //exposes "chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -18,8 +18,6 @@ func main() {
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
Show: true,
|
Show: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
//XValues: []time.Time{time.Unix(3*b,0),time.Unix(4*b,0),time.Unix(5*b,0),time.Unix(6*b,0),time.Unix(7*b,0),time.Unix(8*b,0),time.Unix(9*b,0),time.Unix(10*b,0)},
|
|
||||||
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
||||||
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnnotationSeries is a series of labels on the chart.
|
// AnnotationSeries is a series of labels on the chart.
|
||||||
|
@ -55,10 +57,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 = Math.MinInt(box.Top, ab.Top)
|
box.Top = util.Math.MinInt(box.Top, ab.Top)
|
||||||
box.Left = Math.MinInt(box.Left, ab.Left)
|
box.Left = util.Math.MinInt(box.Left, ab.Left)
|
||||||
box.Right = Math.MaxInt(box.Right, ab.Right)
|
box.Right = util.Math.MaxInt(box.Right, ab.Right)
|
||||||
box.Bottom = Math.MaxInt(box.Bottom, ab.Bottom)
|
box.Bottom = util.Math.MaxInt(box.Bottom, ab.Bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box
|
return box
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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.
|
||||||
|
@ -368,7 +369,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 = Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = util.Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +436,7 @@ func (bc BarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getTitleFontSize() float64 {
|
func (bc BarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
effectiveDimension := util.Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -2,7 +2,8 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||||
|
@ -14,9 +15,9 @@ type BollingerBandsSeries struct {
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
K float64
|
K float64
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
valueBuffer *RingBuffer
|
valueBuffer *seq.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
|
@ -42,7 +43,9 @@ func (bbs BollingerBandsSeries) GetPeriod() int {
|
||||||
return bbs.Period
|
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 {
|
func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 {
|
||||||
if bbs.K == 0 {
|
if bbs.K == 0 {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
|
@ -54,35 +57,35 @@ func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of elements in the series.
|
// Len returns the number of elements in the series.
|
||||||
func (bbs *BollingerBandsSeries) Len() int {
|
func (bbs BollingerBandsSeries) Len() int {
|
||||||
return bbs.InnerSeries.Len()
|
return bbs.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoundedValue gets the bounded value for the series.
|
// GetBoundedValues gets the bounded value for the series.
|
||||||
func (bbs *BollingerBandsSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
|
func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
|
||||||
if bbs.InnerSeries == nil {
|
if bbs.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer == nil || index == 0 {
|
if bbs.valueBuffer == nil || index == 0 {
|
||||||
bbs.valueBuffer = NewRingBufferWithCapacity(bbs.GetPeriod())
|
bbs.valueBuffer = seq.NewBufferWithCapacity(bbs.GetPeriod())
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||||
bbs.valueBuffer.Dequeue()
|
bbs.valueBuffer.Dequeue()
|
||||||
}
|
}
|
||||||
px, py := bbs.InnerSeries.GetValue(index)
|
px, py := bbs.InnerSeries.GetValues(index)
|
||||||
bbs.valueBuffer.Enqueue(py)
|
bbs.valueBuffer.Enqueue(py)
|
||||||
x = px
|
x = px
|
||||||
|
|
||||||
ay := bbs.getAverage(bbs.valueBuffer)
|
ay := seq.New(bbs.valueBuffer).Average()
|
||||||
std := bbs.getStdDev(bbs.valueBuffer)
|
std := seq.New(bbs.valueBuffer).StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoundedLastValue returns the last bounded value for the series.
|
// GetBoundedLastValues returns the last bounded value for the series.
|
||||||
func (bbs *BollingerBandsSeries) GetBoundedLastValue() (x, y1, y2 float64) {
|
func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) {
|
||||||
if bbs.InnerSeries == nil {
|
if bbs.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -93,15 +96,15 @@ func (bbs *BollingerBandsSeries) GetBoundedLastValue() (x, y1, y2 float64) {
|
||||||
startAt = 0
|
startAt = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
vb := NewRingBufferWithCapacity(period)
|
vb := seq.NewBufferWithCapacity(period)
|
||||||
for index := startAt; index < seriesLength; index++ {
|
for index := startAt; index < seriesLength; index++ {
|
||||||
xn, yn := bbs.InnerSeries.GetValue(index)
|
xn, yn := bbs.InnerSeries.GetValues(index)
|
||||||
vb.Enqueue(yn)
|
vb.Enqueue(yn)
|
||||||
x = xn
|
x = xn
|
||||||
}
|
}
|
||||||
|
|
||||||
ay := bbs.getAverage(vb)
|
ay := seq.Seq{Provider: vb}.Average()
|
||||||
std := bbs.getStdDev(vb)
|
std := seq.Seq{Provider: vb}.StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
@ -120,37 +123,6 @@ func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrang
|
||||||
Draw.BoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod())
|
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.
|
// Validate validates the series.
|
||||||
func (bbs BollingerBandsSeries) Validate() error {
|
func (bbs BollingerBandsSeries) Validate() error {
|
||||||
if bbs.InnerSeries == nil {
|
if bbs.InnerSeries == nil {
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-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 := mockValueProvider{
|
s1 := mockValuesProvider{
|
||||||
X: Sequence.Float64(1.0, 100.0),
|
X: seq.Range(1.0, 100.0),
|
||||||
Y: Sequence.Random(100, 1024),
|
Y: seq.RandomValuesWithMax(100, 1024),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
@ -24,27 +26,27 @@ func TestBollingerBandSeries(t *testing.T) {
|
||||||
y2values := make([]float64, 100)
|
y2values := make([]float64, 100)
|
||||||
|
|
||||||
for x := 0; x < 100; x++ {
|
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++ {
|
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) {
|
func TestBollingerBandLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValueProvider{
|
s1 := mockValuesProvider{
|
||||||
X: Sequence.Float64(1.0, 100.0),
|
X: seq.Range(1.0, 100.0),
|
||||||
Y: Sequence.Float64(1.0, 100.0),
|
Y: seq.Range(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
InnerSeries: s1,
|
InnerSeries: s1,
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y1, y2 := bbs.GetBoundedLastValue()
|
x, y1, y2 := bbs.GetBoundedLastValues()
|
||||||
assert.Equal(100.0, x)
|
assert.Equal(100.0, x)
|
||||||
assert.Equal(101, math.Floor(y1))
|
assert.Equal(101, math.Floor(y1))
|
||||||
assert.Equal(83, math.Floor(y2))
|
assert.Equal(83, math.Floor(y2))
|
||||||
|
|
56
box.go
|
@ -3,6 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -89,12 +91,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 Math.AbsInt(b.Right - b.Left)
|
return util.Math.AbsInt(b.Right - b.Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height
|
// Height returns the height
|
||||||
func (b Box) Height() int {
|
func (b Box) Height() int {
|
||||||
return Math.AbsInt(b.Bottom - b.Top)
|
return util.Math.AbsInt(b.Bottom - b.Top)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center returns the center of the box
|
// Center returns the center of the box
|
||||||
|
@ -146,10 +148,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: Math.MinInt(b.Top, other.Top),
|
Top: util.Math.MinInt(b.Top, other.Top),
|
||||||
Left: Math.MinInt(b.Left, other.Left),
|
Left: util.Math.MinInt(b.Left, other.Left),
|
||||||
Right: Math.MaxInt(b.Right, other.Right),
|
Right: util.Math.MaxInt(b.Right, other.Right),
|
||||||
Bottom: Math.MaxInt(b.Bottom, other.Bottom),
|
Bottom: util.Math.MaxInt(b.Bottom, other.Bottom),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,10 +222,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 = Math.MaxInt(newBox.Top, other.Top)
|
newBox.Top = util.Math.MaxInt(newBox.Top, other.Top)
|
||||||
newBox.Left = Math.MaxInt(newBox.Left, other.Left)
|
newBox.Left = util.Math.MaxInt(newBox.Left, other.Left)
|
||||||
newBox.Right = Math.MinInt(newBox.Right, other.Right)
|
newBox.Right = util.Math.MinInt(newBox.Right, other.Right)
|
||||||
newBox.Bottom = Math.MinInt(newBox.Bottom, other.Bottom)
|
newBox.Bottom = util.Math.MinInt(newBox.Bottom, other.Bottom)
|
||||||
|
|
||||||
return newBox
|
return newBox
|
||||||
}
|
}
|
||||||
|
@ -262,36 +264,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: Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
Top: util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
||||||
Left: Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
Left: util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
||||||
Right: Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
Right: util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
||||||
Bottom: Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
|
Bottom: util.Math.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 := Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
minLeft := util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
maxRight := Math.MaxInt(bc.TopRight.X, bc.BottomRight.X)
|
maxRight := util.Math.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 := Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
minTop := util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
maxBottom := Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
maxBottom := util.Math.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 := Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
left := util.Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
right := Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
right := util.Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
||||||
x = ((right - left) >> 1) + left
|
x = ((right - left) >> 1) + left
|
||||||
|
|
||||||
top := Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
top := util.Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
bottom := Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
bottom := util.Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||||
y = ((bottom - top) >> 1) + top
|
y = ((bottom - top) >> 1) + top
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -301,12 +303,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 := Math.DegreesToRadians(thetaDegrees)
|
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
|
||||||
|
|
||||||
tlx, tly := Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
tlx, tly := util.Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
||||||
trx, try := Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
trx, try := util.Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
||||||
brx, bry := Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
brx, bry := util.Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
||||||
blx, bly := Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
blx, bly := util.Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
||||||
|
|
||||||
return BoxCorners{
|
return BoxCorners{
|
||||||
TopLeft: Point{tlx, tly},
|
TopLeft: Point{tlx, tly},
|
||||||
|
|
17
chart.go
|
@ -7,6 +7,7 @@ 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.
|
||||||
|
@ -177,10 +178,10 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||||
seriesAxis := s.GetYAxis()
|
seriesAxis := s.GetYAxis()
|
||||||
if bvp, isBoundedValueProvider := s.(BoundedValueProvider); isBoundedValueProvider {
|
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
||||||
seriesLength := bvp.Len()
|
seriesLength := bvp.Len()
|
||||||
for index := 0; index < seriesLength; index++ {
|
for index := 0; index < seriesLength; index++ {
|
||||||
vx, vy1, vy2 := bvp.GetBoundedValue(index)
|
vx, vy1, vy2 := bvp.GetBoundedValues(index)
|
||||||
|
|
||||||
minx = math.Min(minx, vx)
|
minx = math.Min(minx, vx)
|
||||||
maxx = math.Max(maxx, vx)
|
maxx = math.Max(maxx, vx)
|
||||||
|
@ -198,10 +199,10 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
seriesMappedToSecondaryAxis = true
|
seriesMappedToSecondaryAxis = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if vp, isValueProvider := s.(ValueProvider); isValueProvider {
|
} else if vp, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||||
seriesLength := vp.Len()
|
seriesLength := vp.Len()
|
||||||
for index := 0; index < seriesLength; index++ {
|
for index := 0; index < seriesLength; index++ {
|
||||||
vx, vy := vp.GetValue(index)
|
vx, vy := vp.GetValues(index)
|
||||||
|
|
||||||
minx = math.Min(minx, vx)
|
minx = math.Min(minx, vx)
|
||||||
maxx = math.Max(maxx, vx)
|
maxx = math.Max(maxx, vx)
|
||||||
|
@ -265,8 +266,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 := Math.GetRoundToForDelta(delta)
|
roundTo := util.Math.GetRoundToForDelta(delta)
|
||||||
rmin, rmax := Math.RoundDown(yrange.GetMin(), roundTo), Math.RoundUp(yrange.GetMax(), roundTo)
|
rmin, rmax := util.Math.RoundDown(yrange.GetMin(), roundTo), util.Math.RoundUp(yrange.GetMax(), roundTo)
|
||||||
|
|
||||||
yrange.SetMin(rmin)
|
yrange.SetMin(rmin)
|
||||||
yrange.SetMax(rmax)
|
yrange.SetMax(rmax)
|
||||||
|
@ -287,8 +288,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 := Math.GetRoundToForDelta(delta)
|
roundTo := util.Math.GetRoundToForDelta(delta)
|
||||||
rmin, rmax := Math.RoundDown(yrangeAlt.GetMin(), roundTo), Math.RoundUp(yrangeAlt.GetMax(), roundTo)
|
rmin, rmax := util.Math.RoundDown(yrangeAlt.GetMin(), roundTo), util.Math.RoundUp(yrangeAlt.GetMax(), roundTo)
|
||||||
yrangeAlt.SetMin(rmin)
|
yrangeAlt.SetMin(rmin)
|
||||||
yrangeAlt.SetMax(rmax)
|
yrangeAlt.SetMax(rmax)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-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) {
|
||||||
|
@ -391,8 +392,8 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -407,8 +408,8 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -418,7 +419,7 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c = Chart{
|
c = Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -504,8 +505,8 @@ func TestChartE2ELine(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: Sequence.Float64(0, 4, 1),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: Sequence.Float64(0, 4, 1),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -549,8 +550,8 @@ func TestChartE2ELineWithFill(t *testing.T) {
|
||||||
StrokeColor: drawing.ColorBlue,
|
StrokeColor: drawing.ColorBlue,
|
||||||
FillColor: drawing.ColorRed,
|
FillColor: drawing.ColorRed,
|
||||||
},
|
},
|
||||||
XValues: Sequence.Float64(0, 4, 1),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: Sequence.Float64(0, 4, 1),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ type ConcatSeries []Series
|
||||||
func (cs ConcatSeries) Len() int {
|
func (cs ConcatSeries) Len() int {
|
||||||
total := 0
|
total := 0
|
||||||
for _, s := range cs {
|
for _, s := range cs {
|
||||||
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
|
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||||
total += typed.Len()
|
total += typed.Len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ func (cs ConcatSeries) Len() int {
|
||||||
func (cs ConcatSeries) GetValue(index int) (x, y float64) {
|
func (cs ConcatSeries) GetValue(index int) (x, y float64) {
|
||||||
cursor := 0
|
cursor := 0
|
||||||
for _, s := range cs {
|
for _, s := range cs {
|
||||||
if typed, isValueProvider := s.(ValueProvider); isValueProvider {
|
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||||
len := typed.Len()
|
len := typed.Len()
|
||||||
if index < cursor+len {
|
if index < cursor+len {
|
||||||
x, y = typed.GetValue(index - cursor) //FENCEPOSTS.
|
x, y = typed.GetValues(index - cursor) //FENCEPOSTS.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cursor += typed.Len()
|
cursor += typed.Len()
|
||||||
|
|
|
@ -4,24 +4,25 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-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: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := ContinuousSeries{
|
s2 := ContinuousSeries{
|
||||||
XValues: Sequence.Float64(11, 20.0),
|
XValues: seq.Range(11, 20.0),
|
||||||
YValues: Sequence.Float64(10.0, 1.0),
|
YValues: seq.Range(10.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s3 := ContinuousSeries{
|
s3 := ContinuousSeries{
|
||||||
XValues: Sequence.Float64(21, 30.0),
|
XValues: seq.Range(21, 30.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := ConcatSeries([]Series{s1, s2, s3})
|
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-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 = Math.MinAndMax(values...)
|
r.Min, r.Max = util.Math.MinAndMax(values...)
|
||||||
|
|
||||||
// delta = ~7.0
|
// delta = ~7.0
|
||||||
// value = ~5.0
|
// value = ~5.0
|
||||||
|
|
|
@ -31,13 +31,13 @@ func (cs ContinuousSeries) Len() int {
|
||||||
return len(cs.XValues)
|
return len(cs.XValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets the x,y values at a given index.
|
||||||
func (cs ContinuousSeries) GetValue(index int) (float64, float64) {
|
func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
|
||||||
return cs.XValues[index], cs.YValues[index]
|
return cs.XValues[index], cs.YValues[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastValue gets the last value.
|
// GetLastValues gets the last x,y values.
|
||||||
func (cs ContinuousSeries) GetLastValue() (float64, float64) {
|
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
||||||
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContinuousSeries(t *testing.T) {
|
func TestContinuousSeries(t *testing.T) {
|
||||||
|
@ -12,21 +13,21 @@ func TestContinuousSeries(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal("Test Series", cs.GetName())
|
assert.Equal("Test Series", cs.GetName())
|
||||||
assert.Equal(10, cs.Len())
|
assert.Equal(10, cs.Len())
|
||||||
x0, y0 := cs.GetValue(0)
|
x0, y0 := cs.GetValues(0)
|
||||||
assert.Equal(1.0, x0)
|
assert.Equal(1.0, x0)
|
||||||
assert.Equal(1.0, y0)
|
assert.Equal(1.0, y0)
|
||||||
|
|
||||||
xn, yn := cs.GetValue(9)
|
xn, yn := cs.GetValues(9)
|
||||||
assert.Equal(10.0, xn)
|
assert.Equal(10.0, xn)
|
||||||
assert.Equal(10.0, yn)
|
assert.Equal(10.0, yn)
|
||||||
|
|
||||||
xn, yn = cs.GetLastValue()
|
xn, yn = cs.GetLastValues()
|
||||||
assert.Equal(10.0, xn)
|
assert.Equal(10.0, xn)
|
||||||
assert.Equal(10.0, yn)
|
assert.Equal(10.0, yn)
|
||||||
}
|
}
|
||||||
|
@ -53,20 +54,20 @@ func TestContinuousSeriesValidate(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.Nil(cs.Validate())
|
assert.Nil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: Sequence.Float64(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
YValues: Sequence.Float64(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
}
|
}
|
||||||
|
|
32
draw.go
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Draw contains helpers for drawing common objects.
|
// Draw contains helpers for drawing common objects.
|
||||||
|
@ -10,7 +14,7 @@ var (
|
||||||
type draw struct{}
|
type draw struct{}
|
||||||
|
|
||||||
// LineSeries draws a line series with a renderer.
|
// 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 {
|
if vs.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -18,7 +22,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
cb := canvasBox.Bottom
|
cb := canvasBox.Bottom
|
||||||
cl := canvasBox.Left
|
cl := canvasBox.Left
|
||||||
|
|
||||||
v0x, v0y := vs.GetValue(0)
|
v0x, v0y := vs.GetValues(0)
|
||||||
x0 := cl + xrange.Translate(v0x)
|
x0 := cl + xrange.Translate(v0x)
|
||||||
y0 := cb - yrange.Translate(v0y)
|
y0 := cb - yrange.Translate(v0y)
|
||||||
|
|
||||||
|
@ -31,13 +35,13 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
|
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
|
||||||
r.MoveTo(x0, y0)
|
r.MoveTo(x0, y0)
|
||||||
for i := 1; i < vs.Len(); i++ {
|
for i := 1; i < vs.Len(); i++ {
|
||||||
vx, vy = vs.GetValue(i)
|
vx, vy = vs.GetValues(i)
|
||||||
x = cl + xrange.Translate(vx)
|
x = cl + xrange.Translate(vx)
|
||||||
y = cb - yrange.Translate(vy)
|
y = cb - yrange.Translate(vy)
|
||||||
r.LineTo(x, y)
|
r.LineTo(x, y)
|
||||||
}
|
}
|
||||||
r.LineTo(x, Math.MinInt(cb, cb-yv0))
|
r.LineTo(x, util.Math.MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, Math.MinInt(cb, cb-yv0))
|
r.LineTo(x0, util.Math.MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, y0)
|
r.LineTo(x0, y0)
|
||||||
r.Fill()
|
r.Fill()
|
||||||
}
|
}
|
||||||
|
@ -47,7 +51,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
|
|
||||||
r.MoveTo(x0, y0)
|
r.MoveTo(x0, y0)
|
||||||
for i := 1; i < vs.Len(); i++ {
|
for i := 1; i < vs.Len(); i++ {
|
||||||
vx, vy = vs.GetValue(i)
|
vx, vy = vs.GetValues(i)
|
||||||
x = cl + xrange.Translate(vx)
|
x = cl + xrange.Translate(vx)
|
||||||
y = cb - yrange.Translate(vy)
|
y = cb - yrange.Translate(vy)
|
||||||
r.LineTo(x, y)
|
r.LineTo(x, y)
|
||||||
|
@ -60,7 +64,7 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
|
|
||||||
style.GetDotOptions().WriteDrawingOptionsToRenderer(r)
|
style.GetDotOptions().WriteDrawingOptionsToRenderer(r)
|
||||||
for i := 0; i < vs.Len(); i++ {
|
for i := 0; i < vs.Len(); i++ {
|
||||||
vx, vy = vs.GetValue(i)
|
vx, vy = vs.GetValues(i)
|
||||||
x = cl + xrange.Translate(vx)
|
x = cl + xrange.Translate(vx)
|
||||||
y = cb - yrange.Translate(vy)
|
y = cb - yrange.Translate(vy)
|
||||||
|
|
||||||
|
@ -82,8 +86,8 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoundedSeries draws a series that implements BoundedValueProvider.
|
// BoundedSeries draws a series that implements BoundedValuesProvider.
|
||||||
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) {
|
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValuesProvider, drawOffsetIndexes ...int) {
|
||||||
drawOffsetIndex := 0
|
drawOffsetIndex := 0
|
||||||
if len(drawOffsetIndexes) > 0 {
|
if len(drawOffsetIndexes) > 0 {
|
||||||
drawOffsetIndex = drawOffsetIndexes[0]
|
drawOffsetIndex = drawOffsetIndexes[0]
|
||||||
|
@ -92,7 +96,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
||||||
cb := canvasBox.Bottom
|
cb := canvasBox.Bottom
|
||||||
cl := canvasBox.Left
|
cl := canvasBox.Left
|
||||||
|
|
||||||
v0x, v0y1, v0y2 := bbs.GetBoundedValue(0)
|
v0x, v0y1, v0y2 := bbs.GetBoundedValues(0)
|
||||||
x0 := cl + xrange.Translate(v0x)
|
x0 := cl + xrange.Translate(v0x)
|
||||||
y0 := cb - yrange.Translate(v0y1)
|
y0 := cb - yrange.Translate(v0y1)
|
||||||
|
|
||||||
|
@ -107,7 +111,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
||||||
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||||
r.MoveTo(x0, y0)
|
r.MoveTo(x0, y0)
|
||||||
for i := 1; i < bbs.Len(); i++ {
|
for i := 1; i < bbs.Len(); i++ {
|
||||||
vx, vy1, vy2 = bbs.GetBoundedValue(i)
|
vx, vy1, vy2 = bbs.GetBoundedValues(i)
|
||||||
|
|
||||||
xvalues[i] = vx
|
xvalues[i] = vx
|
||||||
y2values[i] = vy2
|
y2values[i] = vy2
|
||||||
|
@ -133,7 +137,7 @@ func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, sty
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistogramSeries draws a value provider as boxes from 0.
|
// 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 {
|
if vs.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -150,7 +154,7 @@ func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s
|
||||||
|
|
||||||
//foreach datapoint, draw a box.
|
//foreach datapoint, draw a box.
|
||||||
for index := 0; index < seriesLength; index++ {
|
for index := 0; index < seriesLength; index++ {
|
||||||
vx, vy := vs.GetValue(index)
|
vx, vy := vs.GetValues(index)
|
||||||
y0 := yrange.Translate(0)
|
y0 := yrange.Translate(0)
|
||||||
x := cl + xrange.Translate(vx)
|
x := cl + xrange.Translate(vx)
|
||||||
y := yrange.Translate(vy)
|
y := yrange.Translate(vy)
|
||||||
|
|
|
@ -14,7 +14,7 @@ type EMASeries struct {
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
cache []float64
|
cache []float64
|
||||||
}
|
}
|
||||||
|
@ -52,23 +52,23 @@ func (ema EMASeries) GetSigma() float64 {
|
||||||
return 2.0 / (float64(ema.GetPeriod()) + 1)
|
return 2.0 / (float64(ema.GetPeriod()) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (ema *EMASeries) GetValue(index int) (x, y float64) {
|
func (ema *EMASeries) GetValues(index int) (x, y float64) {
|
||||||
if ema.InnerSeries == nil {
|
if ema.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(ema.cache) == 0 {
|
if len(ema.cache) == 0 {
|
||||||
ema.ensureCachedValues()
|
ema.ensureCachedValues()
|
||||||
}
|
}
|
||||||
vx, _ := ema.InnerSeries.GetValue(index)
|
vx, _ := ema.InnerSeries.GetValues(index)
|
||||||
x = vx
|
x = vx
|
||||||
y = ema.cache[index]
|
y = ema.cache[index]
|
||||||
return
|
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.
|
// 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 {
|
if ema.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func (ema *EMASeries) GetLastValue() (x, y float64) {
|
||||||
ema.ensureCachedValues()
|
ema.ensureCachedValues()
|
||||||
}
|
}
|
||||||
lastIndex := ema.InnerSeries.Len() - 1
|
lastIndex := ema.InnerSeries.Len() - 1
|
||||||
x, _ = ema.InnerSeries.GetValue(lastIndex)
|
x, _ = ema.InnerSeries.GetValues(lastIndex)
|
||||||
y = ema.cache[lastIndex]
|
y = ema.cache[lastIndex]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ func (ema *EMASeries) ensureCachedValues() {
|
||||||
ema.cache = make([]float64, seriesLength)
|
ema.cache = make([]float64, seriesLength)
|
||||||
sigma := ema.GetSigma()
|
sigma := ema.GetSigma()
|
||||||
for x := 0; x < seriesLength; x++ {
|
for x := 0; x < seriesLength; x++ {
|
||||||
_, y := ema.InnerSeries.GetValue(x)
|
_, y := ema.InnerSeries.GetValues(x)
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
ema.cache[x] = y
|
ema.cache[x] = y
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emaXValues = Sequence.Float64(1.0, 50.0)
|
emaXValues = seq.Range(1.0, 50.0)
|
||||||
emaYValues = []float64{
|
emaYValues = []float64{
|
||||||
1, 2, 3, 4, 5, 4, 3, 2,
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
1, 2, 3, 4, 5, 4, 3, 2,
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
@ -75,7 +76,7 @@ var (
|
||||||
func TestEMASeries(t *testing.T) {
|
func TestEMASeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValuesProvider{
|
||||||
emaXValues,
|
emaXValues,
|
||||||
emaYValues,
|
emaYValues,
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ func TestEMASeries(t *testing.T) {
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
for x := 0; x < ema.Len(); x++ {
|
for x := 0; x < ema.Len(); x++ {
|
||||||
_, y := ema.GetValue(x)
|
_, y := ema.GetValues(x)
|
||||||
yvalues = append(yvalues, y)
|
yvalues = append(yvalues, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ func TestEMASeries(t *testing.T) {
|
||||||
assert.InDelta(yv, emaExpected[index], emaDelta)
|
assert.InDelta(yv, emaExpected[index], emaDelta)
|
||||||
}
|
}
|
||||||
|
|
||||||
lvx, lvy := ema.GetLastValue()
|
lvx, lvy := ema.GetLastValues()
|
||||||
assert.Equal(50.0, lvx)
|
assert.Equal(50.0, lvx)
|
||||||
assert.InDelta(lvy, emaExpected[49], emaDelta)
|
assert.InDelta(lvy, emaExpected[49], emaDelta)
|
||||||
}
|
}
|
||||||
|
|
3
font.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
"github.com/wcharczuk/go-chart/roboto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -17,7 +18,7 @@ func GetDefaultFont() (*truetype.Font, error) {
|
||||||
_defaultFontLock.Lock()
|
_defaultFontLock.Lock()
|
||||||
defer _defaultFontLock.Unlock()
|
defer _defaultFontLock.Unlock()
|
||||||
if _defaultFont == nil {
|
if _defaultFont == nil {
|
||||||
font, err := truetype.Parse(roboto)
|
font, err := truetype.Parse(roboto.Roboto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ type HistogramSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName implements Series.GetName.
|
// GetName implements Series.GetName.
|
||||||
|
@ -27,19 +27,19 @@ func (hs HistogramSeries) GetYAxis() YAxisType {
|
||||||
return hs.YAxis
|
return hs.YAxis
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len implements BoundedValueProvider.Len.
|
// Len implements BoundedValuesProvider.Len.
|
||||||
func (hs HistogramSeries) Len() int {
|
func (hs HistogramSeries) Len() int {
|
||||||
return hs.InnerSeries.Len()
|
return hs.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue implements ValueProvider.GetValue.
|
// GetValues implements ValuesProvider.GetValues.
|
||||||
func (hs HistogramSeries) GetValue(index int) (x, y float64) {
|
func (hs HistogramSeries) GetValues(index int) (x, y float64) {
|
||||||
return hs.InnerSeries.GetValue(index)
|
return hs.InnerSeries.GetValues(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoundedValue implements BoundedValueProvider.GetBoundedValue
|
// GetBoundedValues implements BoundedValuesProvider.GetBoundedValue
|
||||||
func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
|
func (hs HistogramSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
|
||||||
vx, vy := hs.InnerSeries.GetValue(index)
|
vx, vy := hs.InnerSeries.GetValues(index)
|
||||||
|
|
||||||
x = vx
|
x = vx
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHistogramSeries(t *testing.T) {
|
func TestHistogramSeries(t *testing.T) {
|
||||||
|
@ -11,8 +12,8 @@ func TestHistogramSeries(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: Sequence.Float64(1.0, 20.0),
|
XValues: seq.Range(1.0, 20.0),
|
||||||
YValues: Sequence.Float64(10.0, -10.0),
|
YValues: seq.Range(10.0, -10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := HistogramSeries{
|
hs := HistogramSeries{
|
||||||
|
@ -20,8 +21,8 @@ func TestHistogramSeries(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := 0; x < hs.Len(); x++ {
|
for x := 0; x < hs.Len(); x++ {
|
||||||
csx, csy := cs.GetValue(0)
|
csx, csy := cs.GetValues(0)
|
||||||
hsx, hsy1, hsy2 := hs.GetBoundedValue(0)
|
hsx, hsy1, hsy2 := hs.GetBoundedValues(0)
|
||||||
assert.Equal(csx, hsx)
|
assert.Equal(csx, hsx)
|
||||||
assert.True(hsy1 > 0)
|
assert.True(hsy1 > 0)
|
||||||
assert.True(hsy2 <= 0)
|
assert.True(hsy2 <= 0)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package chart
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// LastValueAnnotation returns an annotation series of just the last value of a value provider.
|
// 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
|
var vf ValueFormatter
|
||||||
if len(vfs) > 0 {
|
if len(vfs) > 0 {
|
||||||
vf = vfs[0]
|
vf = vfs[0]
|
||||||
|
@ -14,11 +14,11 @@ func LastValueAnnotation(innerSeries ValueProvider, vfs ...ValueFormatter) Annot
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastValue Value2
|
var lastValue Value2
|
||||||
if typed, isTyped := innerSeries.(LastValueProvider); isTyped {
|
if typed, isTyped := innerSeries.(LastValuesProvider); isTyped {
|
||||||
lastValue.XValue, lastValue.YValue = typed.GetLastValue()
|
lastValue.XValue, lastValue.YValue = typed.GetLastValues()
|
||||||
lastValue.Label = vf(lastValue.YValue)
|
lastValue.Label = vf(lastValue.YValue)
|
||||||
} else {
|
} else {
|
||||||
lastValue.XValue, lastValue.YValue = innerSeries.GetValue(innerSeries.Len() - 1)
|
lastValue.XValue, lastValue.YValue = innerSeries.GetValues(innerSeries.Len() - 1)
|
||||||
lastValue.Label = vf(lastValue.YValue)
|
lastValue.Label = vf(lastValue.YValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
legend.go
|
@ -1,6 +1,9 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "github.com/wcharczuk/go-chart/drawing"
|
import (
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
// Legend returns a legend renderable function.
|
// Legend returns a legend renderable function.
|
||||||
func Legend(c *Chart, userDefaults ...Style) Renderable {
|
func Legend(c *Chart, userDefaults ...Style) Renderable {
|
||||||
|
@ -66,7 +69,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 = Math.MaxInt(legendContent.Right, right)
|
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
|
||||||
labelCount++
|
labelCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,8 +164,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 = Math.MaxInt(textBox.Height(), textHeight)
|
textHeight = util.Math.MaxInt(textBox.Height(), textHeight)
|
||||||
textWidth = Math.MaxInt(textBox.Width(), textWidth)
|
textWidth = util.Math.MaxInt(textBox.Width(), textWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +281,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 = Math.MaxInt(legendContent.Right, right)
|
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
|
||||||
labelCount++
|
labelCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
||||||
// linear regression for the values.
|
// linear regression for the values.
|
||||||
|
@ -11,7 +16,7 @@ type LinearRegressionSeries struct {
|
||||||
|
|
||||||
Limit int
|
Limit int
|
||||||
Offset int
|
Offset int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
m float64
|
m float64
|
||||||
b float64
|
b float64
|
||||||
|
@ -36,7 +41,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 Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
|
return util.Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLimit returns the window size.
|
// GetLimit returns the window size.
|
||||||
|
@ -51,7 +56,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 Math.MinInt(windowEnd, innerSeriesLastIndex)
|
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOffset returns the data offset.
|
// GetOffset returns the data offset.
|
||||||
|
@ -62,8 +67,8 @@ func (lrs LinearRegressionSeries) GetOffset() int {
|
||||||
return lrs.Offset
|
return lrs.Offset
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (lrs *LinearRegressionSeries) GetValue(index int) (x, y float64) {
|
func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,14 +76,14 @@ func (lrs *LinearRegressionSeries) GetValue(index int) (x, y float64) {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
offset := lrs.GetOffset()
|
offset := lrs.GetOffset()
|
||||||
effectiveIndex := Math.MinInt(index+offset, lrs.InnerSeries.Len())
|
effectiveIndex := util.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
|
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastValue computes the last linear regression value.
|
// GetLastValues computes the last linear regression value.
|
||||||
func (lrs *LinearRegressionSeries) GetLastValue() (x, y float64) {
|
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +91,7 @@ func (lrs *LinearRegressionSeries) GetLastValue() (x, y float64) {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
endIndex := lrs.GetEndIndex()
|
endIndex := lrs.GetEndIndex()
|
||||||
x, y = lrs.InnerSeries.GetValue(endIndex)
|
x, y = lrs.InnerSeries.GetValues(endIndex)
|
||||||
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -102,18 +107,18 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
|
||||||
|
|
||||||
p := float64(endIndex - startIndex)
|
p := float64(endIndex - startIndex)
|
||||||
|
|
||||||
xvalues := NewRingBufferWithCapacity(lrs.Len())
|
xvalues := seq.NewBufferWithCapacity(lrs.Len())
|
||||||
for index := startIndex; index < endIndex; index++ {
|
for index := startIndex; index < endIndex; index++ {
|
||||||
x, _ := lrs.InnerSeries.GetValue(index)
|
x, _ := lrs.InnerSeries.GetValues(index)
|
||||||
xvalues.Enqueue(x)
|
xvalues.Enqueue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
lrs.avgx = xvalues.Average()
|
lrs.avgx = seq.Seq{Provider: xvalues}.Average()
|
||||||
lrs.stddevx = xvalues.StdDev()
|
lrs.stddevx = seq.Seq{Provider: 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++ {
|
||||||
x, y := lrs.InnerSeries.GetValue(index)
|
x, y := lrs.InnerSeries.GetValues(index)
|
||||||
|
|
||||||
x = lrs.normalize(x)
|
x = lrs.normalize(x)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLinearRegressionSeries(t *testing.T) {
|
func TestLinearRegressionSeries(t *testing.T) {
|
||||||
|
@ -11,19 +12,19 @@ func TestLinearRegressionSeries(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: Sequence.Float64(1.0, 100.0),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: Sequence.Float64(1.0, 100.0),
|
YValues: seq.Range(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
InnerSeries: mainSeries,
|
InnerSeries: mainSeries,
|
||||||
}
|
}
|
||||||
|
|
||||||
lrx0, lry0 := linRegSeries.GetValue(0)
|
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||||
assert.InDelta(1.0, lrx0, 0.0000001)
|
assert.InDelta(1.0, lrx0, 0.0000001)
|
||||||
assert.InDelta(1.0, lry0, 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, lrxn, 0.0000001)
|
||||||
assert.InDelta(100.0, lryn, 0.0000001)
|
assert.InDelta(100.0, lryn, 0.0000001)
|
||||||
}
|
}
|
||||||
|
@ -33,19 +34,19 @@ func TestLinearRegressionSeriesDesc(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: Sequence.Float64(100.0, 1.0),
|
XValues: seq.Range(100.0, 1.0),
|
||||||
YValues: Sequence.Float64(100.0, 1.0),
|
YValues: seq.Range(100.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
InnerSeries: mainSeries,
|
InnerSeries: mainSeries,
|
||||||
}
|
}
|
||||||
|
|
||||||
lrx0, lry0 := linRegSeries.GetValue(0)
|
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||||
assert.InDelta(100.0, lrx0, 0.0000001)
|
assert.InDelta(100.0, lrx0, 0.0000001)
|
||||||
assert.InDelta(100.0, lry0, 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, lrxn, 0.0000001)
|
||||||
assert.InDelta(1.0, lryn, 0.0000001)
|
assert.InDelta(1.0, lryn, 0.0000001)
|
||||||
}
|
}
|
||||||
|
@ -55,8 +56,8 @@ func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: Sequence.Float64(100.0, 1.0),
|
XValues: seq.Range(100.0, 1.0),
|
||||||
YValues: Sequence.Float64(100.0, 1.0),
|
YValues: seq.Range(100.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
@ -67,11 +68,11 @@ func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(10, linRegSeries.Len())
|
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, lrx0, 0.0000001)
|
||||||
assert.InDelta(90.0, lry0, 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, lrxn, 0.0000001)
|
||||||
assert.InDelta(80.0, lryn, 0.0000001)
|
assert.InDelta(80.0, lryn, 0.0000001)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ type MACDSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
SecondaryPeriod int
|
SecondaryPeriod int
|
||||||
|
@ -89,8 +89,8 @@ func (macd MACDSeries) Len() int {
|
||||||
return macd.InnerSeries.Len()
|
return macd.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||||
func (macd *MACDSeries) GetValue(index int) (x float64, y float64) {
|
func (macd *MACDSeries) GetValues(index int) (x float64, y float64) {
|
||||||
if macd.InnerSeries == nil {
|
if macd.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -99,10 +99,10 @@ func (macd *MACDSeries) GetValue(index int) (x float64, y float64) {
|
||||||
macd.ensureChildSeries()
|
macd.ensureChildSeries()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, lv := macd.macdl.GetValue(index)
|
_, lv := macd.macdl.GetValues(index)
|
||||||
_, sv := macd.signal.GetValue(index)
|
_, sv := macd.signal.GetValues(index)
|
||||||
|
|
||||||
x, _ = macd.InnerSeries.GetValue(index)
|
x, _ = macd.InnerSeries.GetValues(index)
|
||||||
y = lv - sv
|
y = lv - sv
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -130,7 +130,7 @@ type MACDSignalSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
SecondaryPeriod int
|
SecondaryPeriod int
|
||||||
|
@ -191,8 +191,8 @@ func (macds *MACDSignalSeries) Len() int {
|
||||||
return macds.InnerSeries.Len()
|
return macds.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||||
func (macds *MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
func (macds *MACDSignalSeries) GetValues(index int) (x float64, y float64) {
|
||||||
if macds.InnerSeries == nil {
|
if macds.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -200,8 +200,8 @@ func (macds *MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
||||||
if macds.signal == nil {
|
if macds.signal == nil {
|
||||||
macds.ensureSignal()
|
macds.ensureSignal()
|
||||||
}
|
}
|
||||||
x, _ = macds.InnerSeries.GetValue(index)
|
x, _ = macds.InnerSeries.GetValues(index)
|
||||||
_, y = macds.signal.GetValue(index)
|
_, y = macds.signal.GetValues(index)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ type MACDLineSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
PrimaryPeriod int
|
PrimaryPeriod int
|
||||||
SecondaryPeriod int
|
SecondaryPeriod int
|
||||||
|
@ -300,8 +300,8 @@ func (macdl *MACDLineSeries) Len() int {
|
||||||
return macdl.InnerSeries.Len()
|
return macdl.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index. For MACD it is the signal value.
|
// GetValues gets a value at a given index. For MACD it is the signal value.
|
||||||
func (macdl *MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
func (macdl *MACDLineSeries) GetValues(index int) (x float64, y float64) {
|
||||||
if macdl.InnerSeries == nil {
|
if macdl.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -309,10 +309,10 @@ func (macdl *MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
||||||
macdl.ensureEMASeries()
|
macdl.ensureEMASeries()
|
||||||
}
|
}
|
||||||
|
|
||||||
x, _ = macdl.InnerSeries.GetValue(index)
|
x, _ = macdl.InnerSeries.GetValues(index)
|
||||||
|
|
||||||
_, emav1 := macdl.ema1.GetValue(index)
|
_, emav1 := macdl.ema1.GetValues(index)
|
||||||
_, emav2 := macdl.ema2.GetValue(index)
|
_, emav2 := macdl.ema2.GetValues(index)
|
||||||
|
|
||||||
y = emav2 - emav1
|
y = emav2 - emav1
|
||||||
return
|
return
|
||||||
|
|
|
@ -65,7 +65,7 @@ var (
|
||||||
func TestMACDSeries(t *testing.T) {
|
func TestMACDSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValuesProvider{
|
||||||
emaXValues,
|
emaXValues,
|
||||||
emaYValues,
|
emaYValues,
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ func TestMACDSeries(t *testing.T) {
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
for x := 0; x < mas.Len(); x++ {
|
for x := 0; x < mas.Len(); x++ {
|
||||||
_, y := mas.GetValue(x)
|
_, y := mas.GetValues(x)
|
||||||
yvalues = append(yvalues, y)
|
yvalues = append(yvalues, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarketHoursRange is a special type of range that compresses a time range into just the
|
// MarketHoursRange is a special type of range that compresses a time range into just the
|
||||||
|
@ -14,7 +17,7 @@ type MarketHoursRange struct {
|
||||||
MarketOpen time.Time
|
MarketOpen time.Time
|
||||||
MarketClose time.Time
|
MarketClose time.Time
|
||||||
|
|
||||||
HolidayProvider HolidayProvider
|
HolidayProvider util.HolidayProvider
|
||||||
|
|
||||||
ValueFormatter ValueFormatter
|
ValueFormatter ValueFormatter
|
||||||
|
|
||||||
|
@ -39,17 +42,17 @@ func (mhr MarketHoursRange) IsZero() bool {
|
||||||
|
|
||||||
// GetMin returns the min value.
|
// GetMin returns the min value.
|
||||||
func (mhr MarketHoursRange) GetMin() float64 {
|
func (mhr MarketHoursRange) GetMin() float64 {
|
||||||
return Time.ToFloat64(mhr.Min)
|
return util.Time.ToFloat64(mhr.Min)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMax returns the max value.
|
// GetMax returns the max value.
|
||||||
func (mhr MarketHoursRange) GetMax() float64 {
|
func (mhr MarketHoursRange) GetMax() float64 {
|
||||||
return Time.ToFloat64(mhr.GetEffectiveMax())
|
return util.Time.ToFloat64(mhr.GetEffectiveMax())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEffectiveMax gets either the close on the max, or the max itself.
|
// GetEffectiveMax gets either the close on the max, or the max itself.
|
||||||
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
||||||
maxClose := Date.On(mhr.MarketClose, mhr.Max)
|
maxClose := util.Date.On(mhr.MarketClose, mhr.Max)
|
||||||
if maxClose.After(mhr.Max) {
|
if maxClose.After(mhr.Max) {
|
||||||
return maxClose
|
return maxClose
|
||||||
}
|
}
|
||||||
|
@ -58,13 +61,13 @@ func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
||||||
|
|
||||||
// SetMin sets the min value.
|
// SetMin sets the min value.
|
||||||
func (mhr *MarketHoursRange) SetMin(min float64) {
|
func (mhr *MarketHoursRange) SetMin(min float64) {
|
||||||
mhr.Min = Time.FromFloat64(min)
|
mhr.Min = util.Time.FromFloat64(min)
|
||||||
mhr.Min = mhr.Min.In(mhr.GetTimezone())
|
mhr.Min = mhr.Min.In(mhr.GetTimezone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMax sets the max value.
|
// SetMax sets the max value.
|
||||||
func (mhr *MarketHoursRange) SetMax(max float64) {
|
func (mhr *MarketHoursRange) SetMax(max float64) {
|
||||||
mhr.Max = Time.FromFloat64(max)
|
mhr.Max = util.Time.FromFloat64(max)
|
||||||
mhr.Max = mhr.Max.In(mhr.GetTimezone())
|
mhr.Max = mhr.Max.In(mhr.GetTimezone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +89,9 @@ func (mhr *MarketHoursRange) SetDomain(domain int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
||||||
func (mhr MarketHoursRange) GetHolidayProvider() HolidayProvider {
|
func (mhr MarketHoursRange) GetHolidayProvider() util.HolidayProvider {
|
||||||
if mhr.HolidayProvider == nil {
|
if mhr.HolidayProvider == nil {
|
||||||
return defaultHolidayProvider
|
return func(_ time.Time) bool { return false }
|
||||||
}
|
}
|
||||||
return mhr.HolidayProvider
|
return mhr.HolidayProvider
|
||||||
}
|
}
|
||||||
|
@ -96,7 +99,7 @@ func (mhr MarketHoursRange) GetHolidayProvider() HolidayProvider {
|
||||||
// GetMarketOpen returns the market open time.
|
// GetMarketOpen returns the market open time.
|
||||||
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
||||||
if mhr.MarketOpen.IsZero() {
|
if mhr.MarketOpen.IsZero() {
|
||||||
return NYSEOpen()
|
return util.NYSEOpen()
|
||||||
}
|
}
|
||||||
return mhr.MarketOpen
|
return mhr.MarketOpen
|
||||||
}
|
}
|
||||||
|
@ -104,7 +107,7 @@ func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
||||||
// GetMarketClose returns the market close time.
|
// GetMarketClose returns the market close time.
|
||||||
func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
||||||
if mhr.MarketClose.IsZero() {
|
if mhr.MarketClose.IsZero() {
|
||||||
return NYSEClose()
|
return util.NYSEClose()
|
||||||
}
|
}
|
||||||
return mhr.MarketClose
|
return mhr.MarketClose
|
||||||
}
|
}
|
||||||
|
@ -112,31 +115,31 @@ func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
||||||
// GetTicks returns the ticks for the range.
|
// GetTicks returns the ticks for the range.
|
||||||
// This is to override the default continous ticks that would be generated 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 {
|
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 := seq.Time.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = Sequence.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.Time.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = Sequence.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.Time.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = Sequence.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.Time.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = Sequence.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.Time.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
|
@ -165,7 +168,7 @@ func (mhr *MarketHoursRange) makeTicks(vf ValueFormatter, times []time.Time) []T
|
||||||
ticks := make([]Tick, len(times))
|
ticks := make([]Tick, len(times))
|
||||||
for index, t := range times {
|
for index, t := range times {
|
||||||
ticks[index] = Tick{
|
ticks[index] = Tick{
|
||||||
Value: Time.ToFloat64(t),
|
Value: util.Time.ToFloat64(t),
|
||||||
Label: vf(t),
|
Label: vf(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,10 +181,10 @@ func (mhr MarketHoursRange) String() string {
|
||||||
|
|
||||||
// Translate maps a given value into the ContinuousRange space.
|
// Translate maps a given value into the ContinuousRange space.
|
||||||
func (mhr MarketHoursRange) Translate(value float64) int {
|
func (mhr MarketHoursRange) Translate(value float64) int {
|
||||||
valueTime := Time.FromFloat64(value)
|
valueTime := util.Time.FromFloat64(value)
|
||||||
valueTimeEastern := valueTime.In(Date.Eastern())
|
valueTimeEastern := valueTime.In(util.Date.Eastern())
|
||||||
totalSeconds := Date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
totalSeconds := util.Date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
valueDelta := Date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
valueDelta := util.Date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
||||||
|
|
||||||
if mhr.IsDescending() {
|
if mhr.IsDescending() {
|
||||||
|
|
|
@ -5,17 +5,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
r := &MarketHoursRange{
|
||||||
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern()),
|
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, util.Date.Eastern()),
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern()),
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
||||||
MarketOpen: NYSEOpen(),
|
MarketOpen: util.NYSEOpen(),
|
||||||
MarketClose: NYSEClose(),
|
MarketClose: util.NYSEClose(),
|
||||||
HolidayProvider: Date.IsNYSEHoliday,
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotZero(r.GetDelta())
|
assert.NotZero(r.GetDelta())
|
||||||
|
@ -25,19 +26,19 @@ func TestMarketHoursRangeTranslate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
r := &MarketHoursRange{
|
||||||
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern()),
|
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, util.Date.Eastern()),
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern()),
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
||||||
MarketOpen: NYSEOpen(),
|
MarketOpen: util.NYSEOpen(),
|
||||||
MarketClose: NYSEClose(),
|
MarketClose: util.NYSEClose(),
|
||||||
HolidayProvider: Date.IsNYSEHoliday,
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
Domain: 1000,
|
Domain: 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, Date.Eastern())
|
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, util.Date.Eastern())
|
||||||
|
|
||||||
assert.Equal(0, r.Translate(Time.ToFloat64(r.Min)))
|
assert.Equal(0, r.Translate(util.Time.ToFloat64(r.Min)))
|
||||||
assert.Equal(400, r.Translate(Time.ToFloat64(weds)))
|
assert.Equal(400, r.Translate(util.Time.ToFloat64(weds)))
|
||||||
assert.Equal(1000, r.Translate(Time.ToFloat64(r.Max)))
|
assert.Equal(1000, r.Translate(util.Time.ToFloat64(r.Max)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
||||||
|
@ -56,17 +57,17 @@ func TestMarketHoursRangeGetTicks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ra := &MarketHoursRange{
|
ra := &MarketHoursRange{
|
||||||
Min: Date.On(NYSEOpen(), Date.Date(2016, 07, 18, Date.Eastern())),
|
Min: util.Date.On(util.NYSEOpen(), util.Date.Date(2016, 07, 18, util.Date.Eastern())),
|
||||||
Max: Date.On(NYSEClose(), Date.Date(2016, 07, 22, Date.Eastern())),
|
Max: util.Date.On(util.NYSEClose(), util.Date.Date(2016, 07, 22, util.Date.Eastern())),
|
||||||
MarketOpen: NYSEOpen(),
|
MarketOpen: util.NYSEOpen(),
|
||||||
MarketClose: NYSEClose(),
|
MarketClose: util.NYSEClose(),
|
||||||
HolidayProvider: Date.IsNYSEHoliday,
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
Domain: 1024,
|
Domain: 1024,
|
||||||
}
|
}
|
||||||
|
|
||||||
ticks := ra.GetTicks(r, defaults, TimeValueFormatter)
|
ticks := ra.GetTicks(r, defaults, TimeValueFormatter)
|
||||||
assert.NotEmpty(ticks)
|
assert.NotEmpty(ticks)
|
||||||
assert.Len(ticks, 5)
|
assert.Len(ticks, 5)
|
||||||
assert.NotEqual(Time.ToFloat64(ra.Min), ticks[0].Value)
|
assert.NotEqual(util.Time.ToFloat64(ra.Min), ticks[0].Value)
|
||||||
assert.NotEmpty(ticks[0].Label)
|
assert.NotEmpty(ticks[0].Label)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type MinSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
minValue *float64
|
minValue *float64
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,10 @@ func (ms MinSeries) Len() int {
|
||||||
return ms.InnerSeries.Len()
|
return ms.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (ms *MinSeries) GetValue(index int) (x, y float64) {
|
func (ms *MinSeries) GetValues(index int) (x, y float64) {
|
||||||
ms.ensureMinValue()
|
ms.ensureMinValue()
|
||||||
x, _ = ms.InnerSeries.GetValue(index)
|
x, _ = ms.InnerSeries.GetValues(index)
|
||||||
y = *ms.minValue
|
y = *ms.minValue
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func (ms *MinSeries) ensureMinValue() {
|
||||||
minValue := math.MaxFloat64
|
minValue := math.MaxFloat64
|
||||||
var y float64
|
var y float64
|
||||||
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
||||||
_, y = ms.InnerSeries.GetValue(x)
|
_, y = ms.InnerSeries.GetValues(x)
|
||||||
if y < minValue {
|
if y < minValue {
|
||||||
minValue = y
|
minValue = y
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ type MaxSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
maxValue *float64
|
maxValue *float64
|
||||||
}
|
}
|
||||||
|
@ -101,10 +101,10 @@ func (ms MaxSeries) Len() int {
|
||||||
return ms.InnerSeries.Len()
|
return ms.InnerSeries.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (ms *MaxSeries) GetValue(index int) (x, y float64) {
|
func (ms *MaxSeries) GetValues(index int) (x, y float64) {
|
||||||
ms.ensureMaxValue()
|
ms.ensureMaxValue()
|
||||||
x, _ = ms.InnerSeries.GetValue(index)
|
x, _ = ms.InnerSeries.GetValues(index)
|
||||||
y = *ms.maxValue
|
y = *ms.maxValue
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func (ms *MaxSeries) ensureMaxValue() {
|
||||||
maxValue := -math.MaxFloat64
|
maxValue := -math.MaxFloat64
|
||||||
var y float64
|
var y float64
|
||||||
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
for x := 0; x < ms.InnerSeries.Len(); x++ {
|
||||||
_, y = ms.InnerSeries.GetValue(x)
|
_, y = ms.InnerSeries.GetValues(x)
|
||||||
if y > maxValue {
|
if y > maxValue {
|
||||||
maxValue = y
|
maxValue = y
|
||||||
}
|
}
|
||||||
|
|
25
pie_chart.go
|
@ -4,8 +4,15 @@ 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 (
|
||||||
|
_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.
|
||||||
|
@ -123,7 +130,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 := Math.MinInt(canvasBox.Width(), canvasBox.Height())
|
diameter := util.Math.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
|
||||||
|
|
||||||
|
@ -134,8 +141,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 = Math.PercentToRadians(total)
|
rads = util.Math.PercentToRadians(total)
|
||||||
delta = Math.PercentToRadians(v.Value)
|
delta = util.Math.PercentToRadians(v.Value)
|
||||||
|
|
||||||
r.ArcTo(cx, cy, radius, radius, rads, delta)
|
r.ArcTo(cx, cy, radius, radius, rads, delta)
|
||||||
|
|
||||||
|
@ -150,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 = Math.PercentToRadians(total + (v.Value / 2.0))
|
delta2 = util.Math.PercentToRadians(total + (v.Value / 2.0))
|
||||||
delta2 = Math.RadianAdd(delta2, _pi2)
|
delta2 = util.Math.RadianAdd(delta2, _pi2)
|
||||||
lx, ly = Math.CirclePoint(cx, cy, labelRadius, delta2)
|
lx, ly = util.Math.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)
|
||||||
|
@ -177,7 +184,7 @@ func (pc PieChart) getDefaultCanvasBox() Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
|
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
|
||||||
circleDiameter := Math.MinInt(canvasBox.Width(), canvasBox.Height())
|
circleDiameter := util.Math.MinInt(canvasBox.Width(), canvasBox.Height())
|
||||||
|
|
||||||
square := Box{
|
square := Box{
|
||||||
Right: circleDiameter,
|
Right: circleDiameter,
|
||||||
|
@ -223,7 +230,7 @@ func (pc PieChart) stylePieChartValue(index int) Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getScaledFontSize() float64 {
|
func (pc PieChart) getScaledFontSize() float64 {
|
||||||
effectiveDimension := Math.MinInt(pc.GetWidth(), pc.GetHeight())
|
effectiveDimension := util.Math.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 {
|
||||||
|
@ -262,7 +269,7 @@ func (pc PieChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) getTitleFontSize() float64 {
|
func (pc PieChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := Math.MinInt(pc.GetWidth(), pc.GetHeight())
|
effectiveDimension := util.Math.MinInt(pc.GetWidth(), pc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/matrix"
|
"github.com/wcharczuk/go-chart/matrix"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolynomialRegressionSeries implements a polynomial regression over a given
|
// PolynomialRegressionSeries implements a polynomial regression over a given
|
||||||
|
@ -17,7 +18,7 @@ type PolynomialRegressionSeries struct {
|
||||||
Limit int
|
Limit int
|
||||||
Offset int
|
Offset int
|
||||||
Degree int
|
Degree int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
coeffs []float64
|
coeffs []float64
|
||||||
}
|
}
|
||||||
|
@ -39,7 +40,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 Math.MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
|
return util.Math.MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLimit returns the window size.
|
// GetLimit returns the window size.
|
||||||
|
@ -54,7 +55,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 Math.MinInt(windowEnd, innerSeriesLastIndex)
|
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOffset returns the data offset.
|
// GetOffset returns the data offset.
|
||||||
|
@ -79,8 +80,8 @@ func (prs *PolynomialRegressionSeries) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue returns the series value for a given index.
|
// GetValues returns the series value for a given index.
|
||||||
func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) {
|
func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -94,14 +95,14 @@ func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := prs.GetOffset()
|
offset := prs.GetOffset()
|
||||||
effectiveIndex := Math.MinInt(index+offset, prs.InnerSeries.Len())
|
effectiveIndex := util.Math.MinInt(index+offset, prs.InnerSeries.Len())
|
||||||
x, y = prs.InnerSeries.GetValue(effectiveIndex)
|
x, y = prs.InnerSeries.GetValues(effectiveIndex)
|
||||||
y = prs.apply(x)
|
y = prs.apply(x)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastValue computes the last poly regression value.
|
// GetLastValues computes the last poly regression value.
|
||||||
func (prs *PolynomialRegressionSeries) GetLastValue() (x, y float64) {
|
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -113,7 +114,7 @@ func (prs *PolynomialRegressionSeries) GetLastValue() (x, y float64) {
|
||||||
prs.coeffs = coeffs
|
prs.coeffs = coeffs
|
||||||
}
|
}
|
||||||
endIndex := prs.GetEndIndex()
|
endIndex := prs.GetEndIndex()
|
||||||
x, y = prs.InnerSeries.GetValue(endIndex)
|
x, y = prs.InnerSeries.GetValues(endIndex)
|
||||||
y = prs.apply(x)
|
y = prs.apply(x)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -138,7 +139,7 @@ func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) {
|
||||||
yvalues = make([]float64, endIndex-startIndex)
|
yvalues = make([]float64, endIndex-startIndex)
|
||||||
|
|
||||||
for index := startIndex; index < endIndex; index++ {
|
for index := startIndex; index < endIndex; index++ {
|
||||||
x, y := prs.InnerSeries.GetValue(index)
|
x, y := prs.InnerSeries.GetValues(index)
|
||||||
xvalues[index-startIndex] = x
|
xvalues[index-startIndex] = x
|
||||||
yvalues[index-startIndex] = y
|
yvalues[index-startIndex] = y
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func TestPolynomialRegression(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
_, y := poly.GetValue(i)
|
_, y := poly.GetValues(i)
|
||||||
assert.InDelta(float64(i*i), y, matrix.DefaultEpsilon)
|
assert.InDelta(float64(i*i), y, matrix.DefaultEpsilon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
util "github.com/blendlabs/go-util"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
@ -191,7 +192,7 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
|
||||||
return textBox
|
return textBox
|
||||||
}
|
}
|
||||||
|
|
||||||
return textBox.Corners().Rotate(Math.RadiansToDegrees(*rr.rotateRadians)).Box()
|
return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians)).Box()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTextRotation sets a text rotation.
|
// SetTextRotation sets a text rotation.
|
||||||
|
|
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]
|
|
||||||
}
|
|
||||||
}
|
|
19
seq/array.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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]
|
||||||
|
}
|
226
seq/buffer.go
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bufferMinimumGrow = 4
|
||||||
|
bufferShrinkThreshold = 32
|
||||||
|
bufferGrowFactor = 200
|
||||||
|
bufferDefaultCapacity = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyArray = make([]float64, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBuffer creates a new value buffer with an optional set of values.
|
||||||
|
func NewBuffer(values ...float64) *Buffer {
|
||||||
|
var tail int
|
||||||
|
array := make([]float64, util.Math.MaxInt(len(values), bufferDefaultCapacity))
|
||||||
|
if len(values) > 0 {
|
||||||
|
copy(array, values)
|
||||||
|
tail = len(values)
|
||||||
|
}
|
||||||
|
return &Buffer{
|
||||||
|
array: array,
|
||||||
|
head: 0,
|
||||||
|
tail: tail,
|
||||||
|
size: len(values),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
||||||
|
func NewBufferWithCapacity(capacity int) *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
array: make([]float64, capacity),
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer 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).
|
||||||
|
// Enqueue can be O(n), Dequeue is generally O(1).
|
||||||
|
// Buffer implements `seq.Provider`
|
||||||
|
type Buffer struct {
|
||||||
|
array []float64
|
||||||
|
head int
|
||||||
|
tail int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the Buffer (as it is currently populated).
|
||||||
|
// Actual memory footprint may be different.
|
||||||
|
func (b *Buffer) Len() int {
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue implements seq provider.
|
||||||
|
func (b *Buffer) GetValue(index int) float64 {
|
||||||
|
effectiveIndex := (b.head + index) % len(b.array)
|
||||||
|
return b.array[effectiveIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity returns the total size of the Buffer, including empty elements.
|
||||||
|
func (b *Buffer) Capacity() int {
|
||||||
|
return len(b.array)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity sets the capacity of the Buffer.
|
||||||
|
func (b *Buffer) SetCapacity(capacity int) {
|
||||||
|
newArray := make([]float64, capacity)
|
||||||
|
if b.size > 0 {
|
||||||
|
if b.head < b.tail {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||||
|
} else {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||||
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.array = newArray
|
||||||
|
b.head = 0
|
||||||
|
if b.size == capacity {
|
||||||
|
b.tail = 0
|
||||||
|
} else {
|
||||||
|
b.tail = b.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all objects from the Buffer.
|
||||||
|
func (b *Buffer) Clear() {
|
||||||
|
b.array = make([]float64, bufferDefaultCapacity)
|
||||||
|
b.head = 0
|
||||||
|
b.tail = 0
|
||||||
|
b.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue adds an element to the "back" of the Buffer.
|
||||||
|
func (b *Buffer) Enqueue(value float64) {
|
||||||
|
if b.size == len(b.array) {
|
||||||
|
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
|
||||||
|
if newCapacity < (len(b.array) + bufferMinimumGrow) {
|
||||||
|
newCapacity = len(b.array) + bufferMinimumGrow
|
||||||
|
}
|
||||||
|
b.SetCapacity(newCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.array[b.tail] = value
|
||||||
|
b.tail = (b.tail + 1) % len(b.array)
|
||||||
|
b.size++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue removes the first element from the RingBuffer.
|
||||||
|
func (b *Buffer) Dequeue() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removed := b.array[b.head]
|
||||||
|
b.head = (b.head + 1) % len(b.array)
|
||||||
|
b.size--
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns but does not remove the first element.
|
||||||
|
func (b *Buffer) Peek() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return b.array[b.head]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekBack returns but does not remove the last element.
|
||||||
|
func (b *Buffer) PeekBack() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if b.tail == 0 {
|
||||||
|
return b.array[len(b.array)-1]
|
||||||
|
}
|
||||||
|
return b.array[b.tail-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimExcess resizes the capacity of the buffer to better fit the contents.
|
||||||
|
func (b *Buffer) TrimExcess() {
|
||||||
|
threshold := float64(len(b.array)) * 0.9
|
||||||
|
if b.size < int(threshold) {
|
||||||
|
b.SetCapacity(b.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array returns the ring buffer, in order, as an array.
|
||||||
|
func (b *Buffer) Array() Array {
|
||||||
|
newArray := make([]float64, b.size)
|
||||||
|
|
||||||
|
if b.size == 0 {
|
||||||
|
return newArray
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.head < b.tail {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||||
|
} else {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||||
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each calls the consumer for each element in the buffer.
|
||||||
|
func (b *Buffer) Each(mapfn func(int, float64)) {
|
||||||
|
if b.size == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var index int
|
||||||
|
if b.head < b.tail {
|
||||||
|
for cursor := b.head; cursor < b.tail; cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for cursor := b.head; cursor < len(b.array); cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
for cursor := 0; cursor < b.tail; cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation for value buffers.
|
||||||
|
func (b *Buffer) String() string {
|
||||||
|
var values []string
|
||||||
|
for _, elem := range b.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]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package seq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -6,10 +6,10 @@ import (
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRingBuffer(t *testing.T) {
|
func TestBuffer(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewBuffer()
|
||||||
|
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
assert.Equal(1, buffer.Len())
|
assert.Equal(1, buffer.Len())
|
||||||
|
@ -96,14 +96,14 @@ func TestRingBuffer(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.Nil(buffer.Peek())
|
assert.Zero(buffer.Peek())
|
||||||
assert.Nil(buffer.PeekBack())
|
assert.Zero(buffer.PeekBack())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRingBufferClear(t *testing.T) {
|
func TestBufferClear(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewBuffer()
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
buffer.Enqueue(1)
|
buffer.Enqueue(1)
|
||||||
|
@ -117,21 +117,21 @@ func TestRingBufferClear(t *testing.T) {
|
||||||
|
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
assert.Equal(0, buffer.Len())
|
assert.Equal(0, buffer.Len())
|
||||||
assert.Nil(buffer.Peek())
|
assert.Zero(buffer.Peek())
|
||||||
assert.Nil(buffer.PeekBack())
|
assert.Zero(buffer.PeekBack())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRingBufferAsSlice(t *testing.T) {
|
func TestBufferArray(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewBuffer()
|
||||||
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.AsSlice()
|
contents := buffer.Array()
|
||||||
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,23 +140,53 @@ func TestRingBufferAsSlice(t *testing.T) {
|
||||||
assert.Equal(5, contents[4])
|
assert.Equal(5, contents[4])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRingBufferEach(t *testing.T) {
|
func TestBufferEach(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewBuffer()
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
for x := 1; x < 17; x++ {
|
||||||
buffer.Enqueue(x)
|
buffer.Enqueue(float64(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
called := 0
|
called := 0
|
||||||
buffer.Each(func(v interface{}) {
|
buffer.Each(func(_ int, v float64) {
|
||||||
if typed, isTyped := v.(int); isTyped {
|
if v == float64(called+1) {
|
||||||
if typed == (called + 1) {
|
called++
|
||||||
called++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(16, called)
|
assert.Equal(16, called)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewBuffer(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
empty := NewBuffer()
|
||||||
|
assert.NotNil(empty)
|
||||||
|
assert.Zero(empty.Len())
|
||||||
|
assert.Equal(bufferDefaultCapacity, empty.Capacity())
|
||||||
|
assert.Zero(empty.Peek())
|
||||||
|
assert.Zero(empty.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBufferWithValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := NewBuffer(1, 2, 3, 4, 5)
|
||||||
|
assert.NotNil(values)
|
||||||
|
assert.Equal(5, values.Len())
|
||||||
|
assert.Equal(1, values.Peek())
|
||||||
|
assert.Equal(5, values.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferGrowth(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := NewBuffer(1, 2, 3, 4, 5)
|
||||||
|
for i := 0; i < 1<<10; i++ {
|
||||||
|
values.Enqueue(float64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(1<<10-1, values.PeekBack())
|
||||||
|
}
|
73
seq/linear.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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
|
||||||
|
}
|
48
seq/linear_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-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])
|
||||||
|
}
|
88
seq/random.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomValuesWithAverage 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
|
||||||
|
}
|
20
seq/random_test.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-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)
|
||||||
|
}
|
259
seq/sequence.go
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New wraps a provider with a seq.
|
||||||
|
func New(provider Provider) Seq {
|
||||||
|
return Seq{Provider: 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.
|
||||||
|
type Seq struct {
|
||||||
|
Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array enumerates the seq into a slice.
|
||||||
|
func (s Seq) Array() (output []float64) {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output = make([]float64, s.Len())
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
output[i] = s.GetValue(i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each applies the `mapfn` to all values in the value provider.
|
||||||
|
func (s Seq) Each(mapfn func(int, float64)) {
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
mapfn(i, s.GetValue(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map applies the `mapfn` to all values in the value provider,
|
||||||
|
// returning a new seq.
|
||||||
|
func (s Seq) Map(mapfn func(i int, v float64) float64) Seq {
|
||||||
|
output := make([]float64, s.Len())
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
mapfn(i, s.GetValue(i))
|
||||||
|
}
|
||||||
|
return Seq{Array(output)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FoldLeft collapses a seq from left to right.
|
||||||
|
func (s Seq) FoldLeft(mapfn func(i int, v0, v float64) float64) (v0 float64) {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Len() == 1 {
|
||||||
|
return s.GetValue(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
v0 = s.GetValue(0)
|
||||||
|
for i := 1; i < s.Len(); i++ {
|
||||||
|
v0 = mapfn(i, v0, s.GetValue(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FoldRight collapses a seq from right to left.
|
||||||
|
func (s Seq) FoldRight(mapfn func(i int, v0, v float64) float64) (v0 float64) {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Len() == 1 {
|
||||||
|
return s.GetValue(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
v0 = s.GetValue(s.Len() - 1)
|
||||||
|
for i := s.Len() - 2; i >= 0; i-- {
|
||||||
|
v0 = mapfn(i, v0, s.GetValue(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min returns the minimum value in the seq.
|
||||||
|
func (s Seq) Min() float64 {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
min := s.GetValue(0)
|
||||||
|
var value float64
|
||||||
|
for i := 1; i < s.Len(); i++ {
|
||||||
|
value = s.GetValue(i)
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max returns the maximum value in the seq.
|
||||||
|
func (s Seq) Max() float64 {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
max := s.GetValue(0)
|
||||||
|
var value float64
|
||||||
|
for i := 1; i < s.Len(); i++ {
|
||||||
|
value = s.GetValue(i)
|
||||||
|
if value > max {
|
||||||
|
max = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinMax returns the minimum and the maximum in one pass.
|
||||||
|
func (s Seq) MinMax() (min, max float64) {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
min = s.GetValue(0)
|
||||||
|
max = min
|
||||||
|
var value float64
|
||||||
|
for i := 1; i < s.Len(); i++ {
|
||||||
|
value = s.GetValue(i)
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
|
}
|
||||||
|
if value > max {
|
||||||
|
max = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort returns the seq sorted in ascending order.
|
||||||
|
// This fully enumerates the seq.
|
||||||
|
func (s Seq) Sort() Seq {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
values := s.Array()
|
||||||
|
sort.Float64s(values)
|
||||||
|
return Seq{Provider: Array(values)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Median returns the median or middle value in the sorted seq.
|
||||||
|
func (s Seq) Median() (median float64) {
|
||||||
|
l := s.Len()
|
||||||
|
if l == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted := s.Sort()
|
||||||
|
if l%2 == 0 {
|
||||||
|
v0 := sorted.GetValue(l/2 - 1)
|
||||||
|
v1 := sorted.GetValue(l/2 + 1)
|
||||||
|
median = (v0 + v1) / 2
|
||||||
|
} else {
|
||||||
|
median = float64(sorted.GetValue(l << 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum adds all the elements of a series together.
|
||||||
|
func (s Seq) Sum() (accum float64) {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
accum += s.GetValue(i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Average returns the float average of the values in the buffer.
|
||||||
|
func (s Seq) Average() float64 {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Sum() / float64(s.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variance computes the variance of the buffer.
|
||||||
|
func (s Seq) Variance() float64 {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
m := s.Average()
|
||||||
|
var variance, v float64
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
v = s.GetValue(i)
|
||||||
|
variance += (v - m) * (v - m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return variance / float64(s.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdDev returns the standard deviation.
|
||||||
|
func (s Seq) StdDev() float64 {
|
||||||
|
if s.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return math.Pow(s.Variance(), 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Percentile finds the relative standing in a slice of floats.
|
||||||
|
// `percent` should be given on the interval [0,1.0).
|
||||||
|
func (s Seq) Percentile(percent float64) (percentile float64) {
|
||||||
|
l := s.Len()
|
||||||
|
if l == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if percent < 0 || percent > 1.0 {
|
||||||
|
panic("percent out of range [0.0, 1.0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted := s.Sort()
|
||||||
|
index := percent * float64(l)
|
||||||
|
if index == float64(int64(index)) {
|
||||||
|
i := f64i(index)
|
||||||
|
ci := sorted.GetValue(i - 1)
|
||||||
|
c := sorted.GetValue(i)
|
||||||
|
percentile = (ci + c) / 2.0
|
||||||
|
} else {
|
||||||
|
i := f64i(index)
|
||||||
|
percentile = sorted.GetValue(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return percentile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize maps every value to the interval [0, 1.0].
|
||||||
|
func (s Seq) Normalize() Seq {
|
||||||
|
min, max := s.MinMax()
|
||||||
|
|
||||||
|
delta := max - min
|
||||||
|
output := make([]float64, s.Len())
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
output[i] = (s.GetValue(i) - min) / delta
|
||||||
|
}
|
||||||
|
|
||||||
|
return Seq{Provider: Array(output)}
|
||||||
|
}
|
95
seq/sequence_test.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-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])
|
||||||
|
}
|
171
seq/time.go
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
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) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := util.Date.On(marketOpen, from)
|
||||||
|
toClose := util.Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
todayOpen := util.Date.On(marketOpen, cursor)
|
||||||
|
todayClose := util.Date.On(marketClose, cursor)
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && util.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 = util.Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||||
|
} else {
|
||||||
|
cursor = util.Date.NextHour(cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts timeSequence) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := util.Date.On(marketOpen, from)
|
||||||
|
toClose := util.Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||||
|
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayOpen := util.Date.On(marketOpen, cursor)
|
||||||
|
todayNoon := util.Date.NoonOn(cursor)
|
||||||
|
today2pm := util.Date.On(util.Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
||||||
|
todayClose := util.Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = util.Date.NextDay(cursor)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts timeSequence) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := util.Date.On(marketOpen, from)
|
||||||
|
toClose := util.Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayClose := util.Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = util.Date.NextDay(cursor)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts timeSequence) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := util.Date.On(marketOpen, from)
|
||||||
|
toClose := util.Date.On(marketClose, to)
|
||||||
|
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
todayClose := util.Date.On(marketClose, cursor)
|
||||||
|
times = append(times, todayClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = cursor.AddDate(0, 0, 2)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts timeSequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||||
|
var times []time.Time
|
||||||
|
cursor := util.Date.On(marketClose, from)
|
||||||
|
toClose := util.Date.On(marketClose, to)
|
||||||
|
|
||||||
|
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
||||||
|
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||||
|
if isValidTradingDay {
|
||||||
|
times = append(times, cursor)
|
||||||
|
}
|
||||||
|
cursor = util.Date.NextDayOfWeek(cursor, time.Monday)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := Time.Start(xdata)
|
||||||
|
end := Time.End(xdata)
|
||||||
|
|
||||||
|
totalHours := util.Math.AbsInt(util.Date.DiffHours(start, end))
|
||||||
|
|
||||||
|
finalTimes := ts.Hours(start, totalHours+1)
|
||||||
|
finalValues := make([]float64, totalHours+1)
|
||||||
|
|
||||||
|
var hoursFromStart int
|
||||||
|
for i, xd := range xdata {
|
||||||
|
hoursFromStart = util.Date.DiffHours(start, xd)
|
||||||
|
finalValues[hoursFromStart] = ydata[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalTimes, finalValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns the earliest (min) time in a list of times.
|
||||||
|
func (ts timeSequence) 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 (ts timeSequence) 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
|
||||||
|
}
|
111
seq/time_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeMarketHours(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
||||||
|
mh := Time.MarketHours(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||||
|
assert.Len(mh, 8)
|
||||||
|
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeMarketHourQuarters(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
||||||
|
mh := Time.MarketHourQuarters(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||||
|
assert.Len(mh, 4)
|
||||||
|
assert.Equal(9, mh[0].Hour())
|
||||||
|
assert.Equal(30, mh[0].Minute())
|
||||||
|
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
||||||
|
|
||||||
|
assert.Equal(12, mh[1].Hour())
|
||||||
|
assert.Equal(00, mh[1].Minute())
|
||||||
|
assert.Equal(util.Date.Eastern(), mh[1].Location())
|
||||||
|
|
||||||
|
assert.Equal(14, mh[2].Hour())
|
||||||
|
assert.Equal(00, mh[2].Minute())
|
||||||
|
assert.Equal(util.Date.Eastern(), mh[2].Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := 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)
|
||||||
|
assert.Len(filledTimes, util.Date.DiffHours(Time.Start(xdata), Time.End(xdata))+1)
|
||||||
|
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(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(Time.End(times), times[2], time.Millisecond)
|
||||||
|
}
|
32
seq/util.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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)
|
||||||
|
}
|
190
sequence.go
|
@ -1,190 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (s sequence) Random(samples int, scale float64) []float64 {
|
|
||||||
values := make([]float64, samples)
|
|
||||||
|
|
||||||
for x := 0; x < samples; x++ {
|
|
||||||
values[x] = s.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 (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
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = Date.NextDay(cursor)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/blendlabs/go-assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSequenceFloat64(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
asc := Sequence.Float64(1.0, 10.0)
|
|
||||||
assert.Len(asc, 10)
|
|
||||||
|
|
||||||
desc := Sequence.Float64(10.0, 1.0)
|
|
||||||
assert.Len(desc, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
assert.Len(mh, 8)
|
|
||||||
assert.Equal(Date.Eastern(), mh[0].Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
assert.Len(mh, 4)
|
|
||||||
assert.Equal(9, mh[0].Hour())
|
|
||||||
assert.Equal(30, mh[0].Minute())
|
|
||||||
assert.Equal(Date.Eastern(), mh[0].Location())
|
|
||||||
|
|
||||||
assert.Equal(12, mh[1].Hour())
|
|
||||||
assert.Equal(00, mh[1].Minute())
|
|
||||||
assert.Equal(Date.Eastern(), mh[1].Location())
|
|
||||||
|
|
||||||
assert.Equal(14, mh[2].Hour())
|
|
||||||
assert.Equal(00, mh[2].Minute())
|
|
||||||
assert.Equal(Date.Eastern(), mh[2].Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
end := Date.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 := Sequence.HoursFill(xdata, ydata)
|
|
||||||
assert.Len(filledTimes, Date.DiffHours(Date.Start(xdata), Date.End(xdata))+1)
|
|
||||||
assert.Equal(len(filledValues), len(filledTimes))
|
|
||||||
|
|
||||||
assert.NotZero(filledValues[0])
|
|
||||||
assert.NotZero(filledValues[len(filledValues)-1])
|
|
||||||
|
|
||||||
assert.NotZero(filledValues[16])
|
|
||||||
}
|
|
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultSimpleMovingAveragePeriod is the default number of values to average.
|
// DefaultSimpleMovingAveragePeriod is the default number of values to average.
|
||||||
|
@ -14,7 +18,7 @@ type SMASeries struct {
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
|
|
||||||
Period int
|
Period int
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValuesProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
|
@ -48,25 +52,25 @@ func (sma SMASeries) GetPeriod(defaults ...int) int {
|
||||||
return sma.Period
|
return sma.Period
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (sma SMASeries) GetValue(index int) (x, y float64) {
|
func (sma SMASeries) GetValues(index int) (x, y float64) {
|
||||||
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
|
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
px, _ := sma.InnerSeries.GetValue(index)
|
px, _ := sma.InnerSeries.GetValues(index)
|
||||||
x = px
|
x = px
|
||||||
y = sma.getAverage(index)
|
y = sma.getAverage(index)
|
||||||
return
|
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.
|
// 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 {
|
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seriesLen := sma.InnerSeries.Len()
|
seriesLen := sma.InnerSeries.Len()
|
||||||
px, _ := sma.InnerSeries.GetValue(seriesLen - 1)
|
px, _ := sma.InnerSeries.GetValues(seriesLen - 1)
|
||||||
x = px
|
x = px
|
||||||
y = sma.getAverage(seriesLen - 1)
|
y = sma.getAverage(seriesLen - 1)
|
||||||
return
|
return
|
||||||
|
@ -74,11 +78,11 @@ func (sma SMASeries) GetLastValue() (x, y float64) {
|
||||||
|
|
||||||
func (sma SMASeries) getAverage(index int) float64 {
|
func (sma SMASeries) getAverage(index int) float64 {
|
||||||
period := sma.GetPeriod()
|
period := sma.GetPeriod()
|
||||||
floor := Math.MaxInt(0, index-period)
|
floor := util.Math.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-- {
|
||||||
_, vy := sma.InnerSeries.GetValue(x)
|
_, vy := sma.InnerSeries.GetValues(x)
|
||||||
accum += vy
|
accum += vy
|
||||||
count += 1.0
|
count += 1.0
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,24 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockValueProvider struct {
|
type mockValuesProvider struct {
|
||||||
X []float64
|
X []float64
|
||||||
Y []float64
|
Y []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockValueProvider) Len() int {
|
func (m mockValuesProvider) Len() int {
|
||||||
return Math.MinInt(len(m.X), len(m.Y))
|
return util.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 {
|
if index < 0 {
|
||||||
panic("negative index at GetValue()")
|
panic("negative index at GetValue()")
|
||||||
}
|
}
|
||||||
if index > Math.MinInt(len(m.X), len(m.Y)) {
|
if index >= util.Math.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]
|
||||||
|
@ -30,9 +32,9 @@ func (m mockValueProvider) GetValue(index int) (x, y float64) {
|
||||||
func TestSMASeriesGetValue(t *testing.T) {
|
func TestSMASeriesGetValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValuesProvider{
|
||||||
Sequence.Float64(1.0, 10.0),
|
seq.Range(1.0, 10.0),
|
||||||
Sequence.Float64(10, 1.0),
|
seq.Range(10, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ func TestSMASeriesGetValue(t *testing.T) {
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
for x := 0; x < mas.Len(); x++ {
|
for x := 0; x < mas.Len(); x++ {
|
||||||
_, y := mas.GetValue(x)
|
_, y := mas.GetValues(x)
|
||||||
yvalues = append(yvalues, y)
|
yvalues = append(yvalues, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,9 +63,9 @@ func TestSMASeriesGetValue(t *testing.T) {
|
||||||
func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValuesProvider{
|
||||||
Sequence.Float64(1.0, 10.0),
|
seq.Range(1.0, 10.0),
|
||||||
Sequence.Float64(10, 1.0),
|
seq.Range(10, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
|
@ -74,11 +76,11 @@ func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
for x := 0; x < mas.Len(); x++ {
|
for x := 0; x < mas.Len(); x++ {
|
||||||
_, y := mas.GetValue(x)
|
_, y := mas.GetValues(x)
|
||||||
yvalues = append(yvalues, y)
|
yvalues = append(yvalues, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
lx, ly := mas.GetLastValue()
|
lx, ly := mas.GetLastValues()
|
||||||
assert.Equal(10.0, lx)
|
assert.Equal(10.0, lx)
|
||||||
assert.Equal(5.5, ly)
|
assert.Equal(5.5, ly)
|
||||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||||
|
@ -87,9 +89,9 @@ func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
func TestSMASeriesGetLastValue(t *testing.T) {
|
func TestSMASeriesGetLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValuesProvider{
|
||||||
Sequence.Float64(1.0, 100.0),
|
seq.Range(1.0, 100.0),
|
||||||
Sequence.Float64(100, 1.0),
|
seq.Range(100, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(100, mockSeries.Len())
|
assert.Equal(100, mockSeries.Len())
|
||||||
|
|
||||||
|
@ -100,11 +102,11 @@ func TestSMASeriesGetLastValue(t *testing.T) {
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
for x := 0; x < mas.Len(); x++ {
|
for x := 0; x < mas.Len(); x++ {
|
||||||
_, y := mas.GetValue(x)
|
_, y := mas.GetValues(x)
|
||||||
yvalues = append(yvalues, y)
|
yvalues = append(yvalues, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
lx, ly := mas.GetLastValue()
|
lx, ly := mas.GetLastValues()
|
||||||
assert.Equal(100.0, lx)
|
assert.Equal(100.0, lx)
|
||||||
assert.Equal(6, ly)
|
assert.Equal(6, ly)
|
||||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||||
|
|
|
@ -7,6 +7,8 @@ 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.
|
||||||
|
@ -145,7 +147,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: Math.MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth),
|
Bottom: util.Math.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
|
||||||
|
@ -200,7 +202,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 := Sequence.Float64(1.0, 0.0, 0.2)
|
ticks := seq.RangeWithStep(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()))
|
||||||
|
@ -252,7 +254,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 = Math.MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = util.Math.MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Box{
|
return Box{
|
||||||
|
@ -304,7 +306,7 @@ func (sbc StackedBarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sbc StackedBarChart) getTitleFontSize() float64 {
|
func (sbc StackedBarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := Math.MinInt(sbc.GetWidth(), sbc.GetHeight())
|
effectiveDimension := util.Math.MinInt(sbc.GetWidth(), sbc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
3
style.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
util "github.com/blendlabs/go-util"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
@ -330,7 +331,7 @@ func (s Style) WriteToRenderer(r Renderer) {
|
||||||
|
|
||||||
r.ClearTextRotation()
|
r.ClearTextRotation()
|
||||||
if s.GetTextRotationDegrees() != 0 {
|
if s.GetTextRotationDegrees() != 0 {
|
||||||
r.SetTextRotation(Math.DegreesToRadians(s.GetTextRotationDegrees()))
|
r.SetTextRotation(util.Math.DegreesToRadians(s.GetTextRotationDegrees()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
text.go
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"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.
|
||||||
type TextHorizontalAlign int
|
type TextHorizontalAlign int
|
||||||
|
@ -145,7 +149,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 = Math.MaxInt(lineBox.Right, output.Right)
|
output.Right = util.Math.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
|
@ -4,6 +4,8 @@ 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.
|
||||||
|
@ -83,15 +85,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 := Math.GetRoundToForDelta(rangeDelta) / 10
|
roundTo := util.Math.GetRoundToForDelta(rangeDelta) / 10
|
||||||
intermediateTickCount = Math.MinInt(intermediateTickCount, 1<<10)
|
intermediateTickCount = util.Math.MinInt(intermediateTickCount, 1<<10)
|
||||||
|
|
||||||
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 - Math.RoundUp(tickStep*float64(x), roundTo)
|
tickValue = max - util.Math.RoundUp(tickStep*float64(x), roundTo)
|
||||||
} else {
|
} else {
|
||||||
tickValue = min + Math.RoundUp(tickStep*float64(x), roundTo)
|
tickValue = min + util.Math.RoundUp(tickStep*float64(x), roundTo)
|
||||||
}
|
}
|
||||||
ticks = append(ticks, Tick{
|
ticks = append(ticks, Tick{
|
||||||
Value: tickValue,
|
Value: tickValue,
|
||||||
|
|
|
@ -3,6 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TimeSeries is a line on a chart.
|
// TimeSeries is a line on a chart.
|
||||||
|
@ -31,16 +33,16 @@ func (ts TimeSeries) Len() int {
|
||||||
return len(ts.XValues)
|
return len(ts.XValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValues gets a value at a given index.
|
||||||
func (ts TimeSeries) GetValue(index int) (x, y float64) {
|
func (ts TimeSeries) GetValues(index int) (x, y float64) {
|
||||||
x = Time.ToFloat64(ts.XValues[index])
|
x = util.Time.ToFloat64(ts.XValues[index])
|
||||||
y = ts.YValues[index]
|
y = ts.YValues[index]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastValue gets the last value.
|
// GetLastValues gets the last value.
|
||||||
func (ts TimeSeries) GetLastValue() (x, y float64) {
|
func (ts TimeSeries) GetLastValues() (x, y float64) {
|
||||||
x = Time.ToFloat64(ts.XValues[len(ts.XValues)-1])
|
x = util.Time.ToFloat64(ts.XValues[len(ts.XValues)-1])
|
||||||
y = ts.YValues[len(ts.YValues)-1]
|
y = ts.YValues[len(ts.YValues)-1]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestTimeSeriesGetValue(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
x0, y0 := ts.GetValue(0)
|
x0, y0 := ts.GetValues(0)
|
||||||
assert.NotZero(x0)
|
assert.NotZero(x0)
|
||||||
assert.Equal(1.0, y0)
|
assert.Equal(1.0, y0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -394,33 +394,3 @@ func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time {
|
||||||
dayDelta := 7 - int(afterWeekday-dayOfWeek)
|
dayDelta := 7 - int(afterWeekday-dayOfWeek)
|
||||||
return after.AddDate(0, 0, dayDelta)
|
return after.AddDate(0, 0, dayDelta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start returns the earliest (min) time in a list of times.
|
|
||||||
func (d date) 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 (d date) 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
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -237,34 +237,6 @@ func TestDateIsNYSEHoliday(t *testing.T) {
|
||||||
assert.Equal(holidays, 55)
|
assert.Equal(holidays, 55)
|
||||||
}
|
}
|
||||||
|
|
||||||
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(Date.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(Date.End(times), times[2], time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateDiffDays(t *testing.T) {
|
func TestDateDiffDays(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
@ -47,7 +47,7 @@ func (m mathUtil) MinAndMax(values ...float64) (min float64, max float64) {
|
||||||
}
|
}
|
||||||
min = values[0]
|
min = values[0]
|
||||||
max = values[0]
|
max = values[0]
|
||||||
for _, v := range values {
|
for _, v := range values[1:] {
|
||||||
if max < v {
|
if max < v {
|
||||||
max = v
|
max = v
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ func (m mathUtil) MinAndMaxOfTime(values ...time.Time) (min time.Time, max time.
|
||||||
min = values[0]
|
min = values[0]
|
||||||
max = values[0]
|
max = values[0]
|
||||||
|
|
||||||
for _, v := range values {
|
for _, v := range values[1:] {
|
||||||
if max.Before(v) {
|
if max.Before(v) {
|
||||||
max = v
|
max = v
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -1,4 +1,4 @@
|
||||||
package chart
|
package util
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
6
value.go
|
@ -1,5 +1,7 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
|
import util "github.com/blendlabs/go-util"
|
||||||
|
|
||||||
// Value is a chart value.
|
// Value is a chart value.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Style Style
|
Style Style
|
||||||
|
@ -21,7 +23,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 Math.Normalize(vs.Values()...)
|
return util.Math.Normalize(vs.Values()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize returns the values normalized.
|
// Normalize returns the values normalized.
|
||||||
|
@ -38,7 +40,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: Math.RoundDown(v.Value/total, 0.0001),
|
Value: util.Math.RoundDown(v.Value/total, 0.0001),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-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 := Time.ToFloat64(d)
|
di := util.Time.ToFloat64(d)
|
||||||
df := float64(di)
|
df := float64(di)
|
||||||
|
|
||||||
s := formatTime(d, DefaultDateFormat)
|
s := formatTime(d, DefaultDateFormat)
|
||||||
|
|
|
@ -2,38 +2,38 @@ package chart
|
||||||
|
|
||||||
import "github.com/wcharczuk/go-chart/drawing"
|
import "github.com/wcharczuk/go-chart/drawing"
|
||||||
|
|
||||||
// ValueProvider is a type that produces values.
|
// ValuesProvider is a type that produces values.
|
||||||
type ValueProvider interface {
|
type ValuesProvider interface {
|
||||||
Len() int
|
Len() int
|
||||||
GetValue(index int) (float64, float64)
|
GetValues(index int) (float64, float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoundedValueProvider allows series to return a range.
|
// BoundedValuesProvider allows series to return a range.
|
||||||
type BoundedValueProvider interface {
|
type BoundedValuesProvider interface {
|
||||||
Len() int
|
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.
|
// LastValuesProvider is a special type of value provider that can return it's (potentially computed) last value.
|
||||||
type LastValueProvider interface {
|
type LastValuesProvider interface {
|
||||||
GetLastValue() (x, y float64)
|
GetLastValues() (x, y float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoundedLastValueProvider is a special type of value provider that can return it's (potentially computed) bounded last value.
|
// BoundedLastValuesProvider is a special type of value provider that can return it's (potentially computed) bounded last value.
|
||||||
type BoundedLastValueProvider interface {
|
type BoundedLastValuesProvider interface {
|
||||||
GetBoundedLastValue() (x, y1, y2 float64)
|
GetBoundedLastValues() (x, y1, y2 float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullValueProvider is an interface that combines `ValueProvider` and `LastValueProvider`
|
// FullValuesProvider is an interface that combines `ValuesProvider` and `LastValuesProvider`
|
||||||
type FullValueProvider interface {
|
type FullValuesProvider interface {
|
||||||
ValueProvider
|
ValuesProvider
|
||||||
LastValueProvider
|
LastValuesProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullBoundedValueProvider is an interface that combines `BoundedValueProvider` and `BoundedLastValueProvider`
|
// FullBoundedValuesProvider is an interface that combines `BoundedValuesProvider` and `BoundedLastValuesProvider`
|
||||||
type FullBoundedValueProvider interface {
|
type FullBoundedValuesProvider interface {
|
||||||
BoundedValueProvider
|
BoundedValuesProvider
|
||||||
BoundedLastValueProvider
|
BoundedLastValuesProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// SizeProvider is a provider for integer size.
|
// SizeProvider is a provider for integer size.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
|
|
||||||
|
util "github.com/blendlabs/go-util"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
@ -89,8 +90,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 = Math.RadianAdd(startAngle, _pi2)
|
startAngle = util.Math.RadianAdd(startAngle, _pi2)
|
||||||
endAngle := Math.RadianAdd(startAngle, delta)
|
endAngle := util.Math.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))
|
||||||
|
@ -104,7 +105,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 := Math.RadiansToDegrees(delta)
|
dd := util.Math.RadiansToDegrees(delta)
|
||||||
|
|
||||||
vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f 0 1 %d %d", int(rx), int(ry), dd, endx, endy))
|
vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f 0 1 %d %d", int(rx), int(ry), dd, endx, endy))
|
||||||
}
|
}
|
||||||
|
@ -176,7 +177,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(Math.RadiansToDegrees(*vr.c.textTheta)).Box()
|
box = box.Corners().Rotate(util.Math.RadiansToDegrees(*vr.c.textTheta)).Box()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,7 +232,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" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body)))
|
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body)))
|
||||||
} else {
|
} else {
|
||||||
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, Math.RadiansToDegrees(*c.textTheta), x, y)
|
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, util.Math.RadiansToDegrees(*c.textTheta), x, y)
|
||||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s"%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s"%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
xaxis.go
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
// XAxis represents the horizontal axis.
|
// XAxis represents the horizontal axis.
|
||||||
type XAxis struct {
|
type XAxis struct {
|
||||||
|
@ -101,9 +105,9 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
left = Math.MinInt(left, ltx)
|
left = util.Math.MinInt(left, ltx)
|
||||||
right = Math.MaxInt(right, rtx)
|
right = util.Math.MaxInt(right, rtx)
|
||||||
bottom = Math.MaxInt(bottom, ty)
|
bottom = util.Math.MaxInt(bottom, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
||||||
|
@ -155,7 +159,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 = Math.MaxInt(maxTextHeight, tb.Height())
|
maxTextHeight = util.Math.MaxInt(maxTextHeight, tb.Height())
|
||||||
break
|
break
|
||||||
case TickPositionBetweenTicks:
|
case TickPositionBetweenTicks:
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
|
@ -171,7 +175,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 = Math.MaxInt(maxTextHeight, ftb.Height())
|
maxTextHeight = util.Math.MaxInt(maxTextHeight, ftb.Height())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
18
yaxis.go
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
// YAxis is a veritcal rule of the range.
|
// YAxis is a veritcal rule of the range.
|
||||||
// There can be (2) y-axes; a primary and secondary.
|
// There can be (2) y-axes; a primary and secondary.
|
||||||
|
@ -101,18 +105,18 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
finalTextX = tx - tb.Width()
|
finalTextX = tx - tb.Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTextHeight = Math.MaxInt(tb.Height(), maxTextHeight)
|
maxTextHeight = util.Math.MaxInt(tb.Height(), maxTextHeight)
|
||||||
|
|
||||||
if ya.AxisType == YAxisPrimary {
|
if ya.AxisType == YAxisPrimary {
|
||||||
minx = canvasBox.Right
|
minx = canvasBox.Right
|
||||||
maxx = Math.MaxInt(maxx, tx+tb.Width())
|
maxx = util.Math.MaxInt(maxx, tx+tb.Width())
|
||||||
} else if ya.AxisType == YAxisSecondary {
|
} else if ya.AxisType == YAxisSecondary {
|
||||||
minx = Math.MinInt(minx, finalTextX)
|
minx = util.Math.MinInt(minx, finalTextX)
|
||||||
maxx = Math.MaxInt(maxx, tx)
|
maxx = util.Math.MaxInt(maxx, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
miny = Math.MinInt(miny, ly-tbh2)
|
miny = util.Math.MinInt(miny, ly-tbh2)
|
||||||
maxy = Math.MaxInt(maxy, ly+tbh2)
|
maxy = util.Math.MaxInt(maxy, ly+tbh2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
||||||
|
|