mostly working
This commit is contained in:
parent
26eaa1d898
commit
5f42a580a9
47 changed files with 914 additions and 637 deletions
|
@ -27,7 +27,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 100.0),
|
XValues: SeqRange(1.0, 100.0),
|
||||||
YValues: seq.RandomValuesWithMax(100, 512),
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,7 @@ func drawChartDefault(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: seq.Range(1.0, 100.0),
|
XValues: SeqRange(1.0, 100.0),
|
||||||
YValues: seq.RandomValuesWithMax(100, 512),
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: SeqRange(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: seq.RandomValuesWithMax(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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
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: seq.Range(1.0, 100.0),
|
XValues: SeqRange(1.0, 100.0),
|
||||||
YValues: seq.New(seq.NewRandom().WithLen(100).WithMax(150).WithMin(50)).Array(),
|
YValues: seq.New(seq.NewRandom().WithLen(100).WithMax(150).WithMin(50)).Array(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: SeqRange(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: seq.RandomValuesWithMax(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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
chart "github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
|
@ -24,7 +23,7 @@ func readData() ([]time.Time, []float64) {
|
||||||
var xvalues []time.Time
|
var xvalues []time.Time
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
err := chart.ReadLines("requests.csv", func(line string) error {
|
err := chart.ReadLines("requests.csv", func(line string) error {
|
||||||
parts := strings.Split(line, ",")
|
parts := chart.SplitCSV(line)
|
||||||
year := parseInt(parts[0])
|
year := parseInt(parts[0])
|
||||||
month := parseInt(parts[1])
|
month := parseInt(parts[1])
|
||||||
day := parseInt(parts[2])
|
day := parseInt(parts[2])
|
||||||
|
@ -51,84 +50,84 @@ func releases() []chart.GridLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(log chart.Logger) http.HandlerFunc {
|
||||||
xvalues, yvalues := readData()
|
return func(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.TimeSeries{
|
xvalues, yvalues := readData()
|
||||||
Name: "Prod Request Timings",
|
mainSeries := chart.TimeSeries{
|
||||||
Style: chart.Style{
|
Name: "Prod Request Timings",
|
||||||
Show: true,
|
Style: chart.Style{
|
||||||
StrokeColor: chart.ColorBlue,
|
StrokeColor: chart.ColorBlue,
|
||||||
FillColor: chart.ColorBlue.WithAlpha(100),
|
FillColor: chart.ColorBlue.WithAlpha(100),
|
||||||
},
|
|
||||||
XValues: xvalues,
|
|
||||||
YValues: yvalues,
|
|
||||||
}
|
|
||||||
|
|
||||||
linreg := &chart.LinearRegressionSeries{
|
|
||||||
Name: "Linear Regression",
|
|
||||||
Style: chart.Style{
|
|
||||||
Show: true,
|
|
||||||
StrokeColor: chart.ColorAlternateBlue,
|
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
|
||||||
},
|
|
||||||
InnerSeries: mainSeries,
|
|
||||||
}
|
|
||||||
|
|
||||||
sma := &chart.SMASeries{
|
|
||||||
Name: "SMA",
|
|
||||||
Style: chart.Style{
|
|
||||||
Show: true,
|
|
||||||
StrokeColor: chart.ColorRed,
|
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
|
||||||
},
|
|
||||||
InnerSeries: mainSeries,
|
|
||||||
}
|
|
||||||
|
|
||||||
graph := chart.Chart{
|
|
||||||
Width: 1280,
|
|
||||||
Height: 720,
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 50,
|
|
||||||
},
|
},
|
||||||
},
|
XValues: xvalues,
|
||||||
YAxis: chart.YAxis{
|
YValues: yvalues,
|
||||||
Name: "Elapsed Millis",
|
}
|
||||||
NameStyle: chart.StyleShow(),
|
|
||||||
Style: chart.StyleShow(),
|
linreg := &chart.LinearRegressionSeries{
|
||||||
TickStyle: chart.Style{
|
Name: "Linear Regression",
|
||||||
TextRotationDegrees: 45.0,
|
Style: chart.Style{
|
||||||
|
StrokeColor: chart.ColorAlternateBlue,
|
||||||
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
},
|
},
|
||||||
ValueFormatter: func(v interface{}) string {
|
InnerSeries: mainSeries,
|
||||||
return fmt.Sprintf("%d ms", int(v.(float64)))
|
}
|
||||||
|
|
||||||
|
sma := &chart.SMASeries{
|
||||||
|
Name: "SMA",
|
||||||
|
Style: chart.Style{
|
||||||
|
StrokeColor: chart.ColorRed,
|
||||||
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
},
|
},
|
||||||
},
|
InnerSeries: mainSeries,
|
||||||
XAxis: chart.XAxis{
|
}
|
||||||
Style: chart.StyleShow(),
|
|
||||||
ValueFormatter: chart.TimeHourValueFormatter,
|
graph := chart.Chart{
|
||||||
GridMajorStyle: chart.Style{
|
Log: log,
|
||||||
Show: true,
|
Width: 1280,
|
||||||
StrokeColor: chart.ColorAlternateGray,
|
Height: 720,
|
||||||
StrokeWidth: 1.0,
|
Background: chart.Style{
|
||||||
|
Padding: chart.Box{
|
||||||
|
Top: 50,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
GridLines: releases(),
|
YAxis: chart.YAxis{
|
||||||
},
|
Name: "Elapsed Millis",
|
||||||
Series: []chart.Series{
|
TickStyle: chart.Style{
|
||||||
mainSeries,
|
TextRotationDegrees: 45.0,
|
||||||
linreg,
|
},
|
||||||
chart.LastValueAnnotation(linreg),
|
ValueFormatter: func(v interface{}) string {
|
||||||
sma,
|
return fmt.Sprintf("%d ms", int(v.(float64)))
|
||||||
chart.LastValueAnnotation(sma),
|
},
|
||||||
},
|
},
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
|
GridMajorStyle: chart.Style{
|
||||||
|
StrokeColor: chart.ColorAlternateGray,
|
||||||
|
StrokeWidth: 1.0,
|
||||||
|
},
|
||||||
|
GridLines: releases(),
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
mainSeries,
|
||||||
|
linreg,
|
||||||
|
chart.LastValueAnnotation(linreg),
|
||||||
|
sma,
|
||||||
|
chart.LastValueAnnotation(sma),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", chart.ContentTypePNG)
|
||||||
|
if err := graph.Render(chart.PNG, res); err != nil {
|
||||||
|
log.Err(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
|
||||||
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypePNG)
|
|
||||||
graph.Render(chart.PNG, res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", drawChart)
|
log := chart.NewLogger()
|
||||||
|
log.Infof("listening on :8080")
|
||||||
|
http.HandleFunc("/", drawChart(log))
|
||||||
http.ListenAndServe(":8080", nil)
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,8 @@ import (
|
||||||
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -26,8 +25,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
DotWidth: 5,
|
DotWidth: 5,
|
||||||
DotColorProvider: viridisByY,
|
DotColorProvider: viridisByY,
|
||||||
},
|
},
|
||||||
XValues: seq.Range(0, 127),
|
XValues: chart.SeqRange(0, 127),
|
||||||
YValues: seq.New(seq.NewRandom().WithLen(128).WithMax(1024)).Array(),
|
YValues: chart.NewSeq(chart.NewSeqRandom().WithLen(128).WithMax(1024)).Values(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -51,8 +50,8 @@ func unit(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: seq.RangeWithStep(0, 4, 1),
|
XValues: chart.SeqRangeWithStep(0, 4, 1),
|
||||||
YValues: seq.RangeWithStep(0, 4, 1),
|
YValues: chart.SeqRangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: chart.SeqRange(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: seq.RandomValuesWithMax(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: chart.SeqRandomValuesWithMax(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.
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
"github.com/wcharczuk/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -4,8 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
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) {
|
||||||
|
@ -22,7 +21,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 := util.Time.FromFloat64(typed)
|
typedDate := chart.TimeFromFloat64(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())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -53,7 +53,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
Right: 0,
|
Right: 0,
|
||||||
Bottom: 0,
|
Bottom: 0,
|
||||||
}
|
}
|
||||||
if as.Style.IsZero() || as.Style.Show {
|
if !as.Style.Hidden {
|
||||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||||
for _, a := range as.Annotations {
|
for _, a := range as.Annotations {
|
||||||
style := a.Style.InheritFrom(seriesStyle)
|
style := a.Style.InheritFrom(seriesStyle)
|
||||||
|
@ -71,7 +71,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
|
|
||||||
// Render draws the series.
|
// Render draws the series.
|
||||||
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
if as.Style.IsZero() || as.Style.Show {
|
if !as.Style.Hidden {
|
||||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||||
for _, a := range as.Annotations {
|
for _, a := range as.Annotations {
|
||||||
style := a.Style.InheritFrom(seriesStyle)
|
style := a.Style.InheritFrom(seriesStyle)
|
||||||
|
|
|
@ -13,7 +13,6 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
as := AnnotationSeries{
|
as := AnnotationSeries{
|
||||||
Style: StyleShow(),
|
|
||||||
Annotations: []Value2{
|
Annotations: []Value2{
|
||||||
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
||||||
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
||||||
|
@ -63,7 +62,6 @@ func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
|
|
||||||
as := AnnotationSeries{
|
as := AnnotationSeries{
|
||||||
Style: Style{
|
Style: Style{
|
||||||
Show: true,
|
|
||||||
FillColor: drawing.ColorWhite,
|
FillColor: drawing.ColorWhite,
|
||||||
StrokeColor: drawing.ColorBlack,
|
StrokeColor: drawing.ColorBlack,
|
||||||
},
|
},
|
||||||
|
|
24
array.go
Normal file
24
array.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Sequence = (*Array)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewArray returns a new array from a given set of values.
|
||||||
|
// Array implements Sequence, which allows it to be used with the sequence helpers.
|
||||||
|
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]
|
||||||
|
}
|
14
bar_chart.go
14
bar_chart.go
|
@ -224,7 +224,7 @@ func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
if bc.XAxis.Show {
|
if !bc.XAxis.Hidden {
|
||||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||||
axisStyle.WriteToRenderer(r)
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
||||||
if bc.YAxis.Style.Show {
|
if !bc.YAxis.Style.Hidden {
|
||||||
axisStyle := bc.YAxis.Style.InheritFrom(bc.styleDefaultsAxes())
|
axisStyle := bc.YAxis.Style.InheritFrom(bc.styleDefaultsAxes())
|
||||||
axisStyle.WriteToRenderer(r)
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawTitle(r Renderer) {
|
func (bc BarChart) drawTitle(r Renderer) {
|
||||||
if len(bc.Title) > 0 && bc.TitleStyle.Show {
|
if len(bc.Title) > 0 && !bc.TitleStyle.Hidden {
|
||||||
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
||||||
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
||||||
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
||||||
|
@ -325,7 +325,7 @@ func (bc BarChart) styleDefaultsCanvas() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) hasAxes() bool {
|
func (bc BarChart) hasAxes() bool {
|
||||||
return bc.YAxis.Style.Show
|
return !bc.YAxis.Style.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
||||||
|
@ -345,7 +345,7 @@ func (bc BarChart) getValueFormatters() ValueFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
||||||
if bc.YAxis.Style.Show {
|
if !bc.YAxis.Style.Hidden {
|
||||||
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -391,7 +391,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
|
|
||||||
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
||||||
|
|
||||||
if bc.XAxis.Show {
|
if !bc.XAxis.Hidden {
|
||||||
xaxisHeight := DefaultVerticalTickHeight
|
xaxisHeight := DefaultVerticalTickHeight
|
||||||
|
|
||||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||||
|
@ -423,7 +423,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
axesOuterBox = axesOuterBox.Grow(xbox)
|
axesOuterBox = axesOuterBox.Grow(xbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bc.YAxis.Style.Show {
|
if !bc.YAxis.Style.Hidden {
|
||||||
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,8 @@ func TestBarChartRender(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Title: "Test Title",
|
Title: "Test Title",
|
||||||
TitleStyle: StyleShow(),
|
|
||||||
XAxis: StyleShow(),
|
|
||||||
YAxis: YAxis{
|
|
||||||
Style: StyleShow(),
|
|
||||||
},
|
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
{Value: 1.0, Label: "One"},
|
{Value: 1.0, Label: "One"},
|
||||||
{Value: 2.0, Label: "Two"},
|
{Value: 2.0, Label: "Two"},
|
||||||
|
@ -38,13 +33,8 @@ func TestBarChartRenderZero(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Title: "Test Title",
|
Title: "Test Title",
|
||||||
TitleStyle: StyleShow(),
|
|
||||||
XAxis: StyleShow(),
|
|
||||||
YAxis: YAxis{
|
|
||||||
Style: StyleShow(),
|
|
||||||
},
|
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
{Value: 0.0, Label: "One"},
|
{Value: 0.0, Label: "One"},
|
||||||
{Value: 0.0, Label: "Two"},
|
{Value: 0.0, Label: "Two"},
|
||||||
|
@ -183,12 +173,11 @@ func TestBarChartHasAxes(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
assert.False(bc.hasAxes())
|
|
||||||
bc.YAxis = YAxis{
|
|
||||||
Style: StyleShow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(bc.hasAxes())
|
assert.True(bc.hasAxes())
|
||||||
|
bc.YAxis = YAxis{
|
||||||
|
Style: Hidden(),
|
||||||
|
}
|
||||||
|
assert.False(bc.hasAxes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetDefaultCanvasBox(t *testing.T) {
|
func TestBarChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
|
@ -237,10 +226,11 @@ func TestBarChartGetAxesTicks(t *testing.T) {
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
yf := bc.getValueFormatters()
|
yf := bc.getValueFormatters()
|
||||||
|
|
||||||
|
bc.YAxis.Style.Hidden = true
|
||||||
ticks := bc.getAxesTicks(r, yr, yf)
|
ticks := bc.getAxesTicks(r, yr, yf)
|
||||||
assert.Empty(ticks)
|
assert.Empty(ticks)
|
||||||
|
|
||||||
bc.YAxis.Style.Show = true
|
bc.YAxis.Style.Hidden = false
|
||||||
ticks = bc.getAxesTicks(r, yr, yf)
|
ticks = bc.getAxesTicks(r, yr, yf)
|
||||||
assert.Len(ticks, 2)
|
assert.Len(ticks, 2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,8 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
bbs.valueBuffer.Enqueue(py)
|
bbs.valueBuffer.Enqueue(py)
|
||||||
x = px
|
x = px
|
||||||
|
|
||||||
ay := NewSeq(bbs.valueBuffer).Average()
|
ay := Seq{bbs.valueBuffer}.Average()
|
||||||
std := NewSeq(bbs.valueBuffer).StdDev()
|
std := Seq{bbs.valueBuffer}.StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
|
|
@ -12,8 +12,8 @@ func TestBollingerBandSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: SeqRange(1.0, 100.0),
|
X: LinearRange(1.0, 100.0),
|
||||||
Y: SeqRandomValuesWithMax(100, 1024),
|
Y: RandomValuesWithMax(100, 1024),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
@ -37,8 +37,8 @@ func TestBollingerBandLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: SeqRange(1.0, 100.0),
|
X: LinearRange(1.0, 100.0),
|
||||||
Y: SeqRange(1.0, 100.0),
|
Y: LinearRange(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
|
54
chart.go
54
chart.go
|
@ -32,6 +32,8 @@ type Chart struct {
|
||||||
|
|
||||||
Series []Series
|
Series []Series
|
||||||
Elements []Renderable
|
Elements []Renderable
|
||||||
|
|
||||||
|
Log Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDPI returns the dpi for the chart.
|
// GetDPI returns the dpi for the chart.
|
||||||
|
@ -74,8 +76,8 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
if len(c.Series) == 0 {
|
if len(c.Series) == 0 {
|
||||||
return errors.New("please provide at least one series")
|
return errors.New("please provide at least one series")
|
||||||
}
|
}
|
||||||
if visibleSeriesErr := c.checkHasVisibleSeries(); visibleSeriesErr != nil {
|
if err := c.checkHasVisibleSeries(); err != nil {
|
||||||
return visibleSeriesErr
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.YAxisSecondary.AxisType = YAxisSecondary
|
c.YAxisSecondary.AxisType = YAxisSecondary
|
||||||
|
@ -142,16 +144,14 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) checkHasVisibleSeries() error {
|
func (c Chart) checkHasVisibleSeries() error {
|
||||||
hasVisibleSeries := false
|
|
||||||
var style Style
|
var style Style
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
style = s.GetStyle()
|
style = s.GetStyle()
|
||||||
hasVisibleSeries = hasVisibleSeries || (style.IsZero() || style.Show)
|
if !style.Hidden {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !hasVisibleSeries {
|
return fmt.Errorf("chart render; must have (1) visible series")
|
||||||
return fmt.Errorf("must have (1) visible series; make sure if you set a style, you set .Show = true")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) validateSeries() error {
|
func (c Chart) validateSeries() error {
|
||||||
|
@ -175,7 +175,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
// note: a possible future optimization is to not scan the series values if
|
// note: a possible future optimization is to not scan the series values if
|
||||||
// all axis are represented by either custom ticks or custom ranges.
|
// all axis are represented by either custom ticks or custom ranges.
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if !s.GetStyle().Hidden {
|
||||||
seriesAxis := s.GetYAxis()
|
seriesAxis := s.GetYAxis()
|
||||||
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
||||||
seriesLength := bvp.Len()
|
seriesLength := bvp.Len()
|
||||||
|
@ -262,8 +262,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
yrange.SetMin(miny)
|
yrange.SetMin(miny)
|
||||||
yrange.SetMax(maxy)
|
yrange.SetMax(maxy)
|
||||||
|
|
||||||
// only round if we're showing the axis
|
if !c.YAxis.Style.Hidden {
|
||||||
if c.YAxis.Style.Show {
|
|
||||||
delta := yrange.GetDelta()
|
delta := yrange.GetDelta()
|
||||||
roundTo := GetRoundToForDelta(delta)
|
roundTo := GetRoundToForDelta(delta)
|
||||||
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
|
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
|
||||||
|
@ -285,7 +284,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
yrangeAlt.SetMin(minya)
|
yrangeAlt.SetMin(minya)
|
||||||
yrangeAlt.SetMax(maxya)
|
yrangeAlt.SetMax(maxya)
|
||||||
|
|
||||||
if c.YAxisSecondary.Style.Show {
|
if !c.YAxisSecondary.Style.Hidden {
|
||||||
delta := yrangeAlt.GetDelta()
|
delta := yrangeAlt.GetDelta()
|
||||||
roundTo := GetRoundToForDelta(delta)
|
roundTo := GetRoundToForDelta(delta)
|
||||||
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
|
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
|
||||||
|
@ -298,6 +297,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) checkRanges(xr, yr, yra Range) error {
|
func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
|
Debugf(c.Log, "checking xrange: %v", xr)
|
||||||
xDelta := xr.GetDelta()
|
xDelta := xr.GetDelta()
|
||||||
if math.IsInf(xDelta, 0) {
|
if math.IsInf(xDelta, 0) {
|
||||||
return errors.New("infinite x-range delta")
|
return errors.New("infinite x-range delta")
|
||||||
|
@ -309,6 +309,7 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
return errors.New("zero x-range delta; there needs to be at least (2) values")
|
return errors.New("zero x-range delta; there needs to be at least (2) values")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Debugf(c.Log, "checking yrange: %v", yr)
|
||||||
yDelta := yr.GetDelta()
|
yDelta := yr.GetDelta()
|
||||||
if math.IsInf(yDelta, 0) {
|
if math.IsInf(yDelta, 0) {
|
||||||
return errors.New("infinite y-range delta")
|
return errors.New("infinite y-range delta")
|
||||||
|
@ -318,6 +319,7 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.hasSecondarySeries() {
|
if c.hasSecondarySeries() {
|
||||||
|
Debugf(c.Log, "checking secondary yrange: %v", yra)
|
||||||
yraDelta := yra.GetDelta()
|
yraDelta := yra.GetDelta()
|
||||||
if math.IsInf(yraDelta, 0) {
|
if math.IsInf(yraDelta, 0) {
|
||||||
return errors.New("infinite secondary y-range delta")
|
return errors.New("infinite secondary y-range delta")
|
||||||
|
@ -360,17 +362,17 @@ func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) hasAxes() bool {
|
func (c Chart) hasAxes() bool {
|
||||||
return c.XAxis.Style.Show || c.YAxis.Style.Show || c.YAxisSecondary.Style.Show
|
return !c.XAxis.Style.Hidden || !c.YAxis.Style.Hidden || !c.YAxisSecondary.Style.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
||||||
if c.XAxis.Style.Show {
|
if !c.XAxis.Style.Hidden {
|
||||||
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if !c.YAxis.Style.Hidden {
|
||||||
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if !c.YAxisSecondary.Style.Hidden {
|
||||||
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -378,15 +380,15 @@ func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueForm
|
||||||
|
|
||||||
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
||||||
axesOuterBox := canvasBox.Clone()
|
axesOuterBox := canvasBox.Clone()
|
||||||
if c.XAxis.Style.Show {
|
if !c.XAxis.Style.Hidden {
|
||||||
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if !c.YAxis.Style.Hidden {
|
||||||
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if !c.YAxisSecondary.Style.Hidden {
|
||||||
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
|
@ -404,7 +406,7 @@ func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range,
|
||||||
func (c Chart) hasAnnotationSeries() bool {
|
func (c Chart) hasAnnotationSeries() bool {
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||||
if as.Style.IsZero() || as.Style.Show {
|
if !as.GetStyle().Hidden {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,7 +427,7 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
|
||||||
annotationSeriesBox := canvasBox.Clone()
|
annotationSeriesBox := canvasBox.Clone()
|
||||||
for seriesIndex, s := range c.Series {
|
for seriesIndex, s := range c.Series {
|
||||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||||
if as.Style.IsZero() || as.Style.Show {
|
if !as.GetStyle().Hidden {
|
||||||
style := c.styleDefaultsSeries(seriesIndex)
|
style := c.styleDefaultsSeries(seriesIndex)
|
||||||
var annotationBounds Box
|
var annotationBounds Box
|
||||||
if as.YAxis == YAxisPrimary {
|
if as.YAxis == YAxisPrimary {
|
||||||
|
@ -462,19 +464,19 @@ func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
||||||
if c.XAxis.Style.Show {
|
if !c.XAxis.Style.Hidden {
|
||||||
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if !c.YAxis.Style.Hidden {
|
||||||
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if !c.YAxisSecondary.Style.Hidden {
|
||||||
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
|
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if !s.GetStyle().Hidden {
|
||||||
if s.GetYAxis() == YAxisPrimary {
|
if s.GetYAxis() == YAxisPrimary {
|
||||||
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
|
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
|
||||||
} else if s.GetYAxis() == YAxisSecondary {
|
} else if s.GetYAxis() == YAxisSecondary {
|
||||||
|
@ -484,7 +486,7 @@ func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawTitle(r Renderer) {
|
func (c Chart) drawTitle(r Renderer) {
|
||||||
if len(c.Title) > 0 && c.TitleStyle.Show {
|
if len(c.Title) > 0 && !c.TitleStyle.Hidden {
|
||||||
r.SetFont(c.TitleStyle.GetFont(c.GetFont()))
|
r.SetFont(c.TitleStyle.GetFont(c.GetFont()))
|
||||||
r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor()))
|
r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor()))
|
||||||
titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
||||||
|
|
|
@ -274,25 +274,44 @@ func TestChartGetValueFormatters(t *testing.T) {
|
||||||
func TestChartHasAxes(t *testing.T) {
|
func TestChartHasAxes(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
assert.False(Chart{}.hasAxes())
|
assert.True(Chart{}.hasAxes())
|
||||||
|
assert.False(Chart{XAxis: XAxis{Style: Hidden()}, YAxis: YAxis{Style: Hidden()}, YAxisSecondary: YAxis{Style: Hidden()}}.hasAxes())
|
||||||
|
|
||||||
x := Chart{
|
x := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
Style: StyleShow(),
|
Style: Hidden(),
|
||||||
|
},
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: Shown(),
|
||||||
|
},
|
||||||
|
YAxisSecondary: YAxis{
|
||||||
|
Style: Hidden(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.True(x.hasAxes())
|
assert.True(x.hasAxes())
|
||||||
|
|
||||||
y := Chart{
|
y := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: Shown(),
|
||||||
|
},
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
Style: StyleShow(),
|
Style: Hidden(),
|
||||||
|
},
|
||||||
|
YAxisSecondary: YAxis{
|
||||||
|
Style: Hidden(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.True(y.hasAxes())
|
assert.True(y.hasAxes())
|
||||||
|
|
||||||
ya := Chart{
|
ya := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: Hidden(),
|
||||||
|
},
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: Hidden(),
|
||||||
|
},
|
||||||
YAxisSecondary: YAxis{
|
YAxisSecondary: YAxis{
|
||||||
Style: StyleShow(),
|
Style: Shown(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.True(ya.hasAxes())
|
assert.True(ya.hasAxes())
|
||||||
|
@ -306,15 +325,12 @@ func TestChartGetAxesTicks(t *testing.T) {
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
Style: StyleShow(),
|
|
||||||
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
||||||
},
|
},
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
Style: StyleShow(),
|
|
||||||
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
||||||
},
|
},
|
||||||
YAxisSecondary: YAxis{
|
YAxisSecondary: YAxis{
|
||||||
Style: StyleShow(),
|
|
||||||
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -330,20 +346,15 @@ func TestChartSingleSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Title: "Hello!",
|
Title: "Hello!",
|
||||||
TitleStyle: StyleShow(),
|
Width: 1024,
|
||||||
Width: 1024,
|
Height: 400,
|
||||||
Height: 400,
|
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
Style: StyleShow(),
|
|
||||||
Range: &ContinuousRange{
|
Range: &ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 4.0,
|
Max: 4.0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XAxis: XAxis{
|
|
||||||
Style: StyleShow(),
|
|
||||||
},
|
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
TimeSeries{
|
TimeSeries{
|
||||||
Name: "goog",
|
Name: "goog",
|
||||||
|
@ -386,8 +397,8 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -402,8 +413,8 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -413,7 +424,7 @@ func TestChartValidatesSeries(t *testing.T) {
|
||||||
c = Chart{
|
c = Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -483,8 +494,8 @@ func TestChartE2ELine(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: seq.RangeWithStep(0, 4, 1),
|
XValues: LinearRangeWithStep(0, 4, 1),
|
||||||
YValues: seq.RangeWithStep(0, 4, 1),
|
YValues: LinearRangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -524,16 +535,18 @@ func TestChartE2ELineWithFill(t *testing.T) {
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
Style: Style{
|
Style: Style{
|
||||||
Show: true,
|
|
||||||
StrokeColor: drawing.ColorBlue,
|
StrokeColor: drawing.ColorBlue,
|
||||||
FillColor: drawing.ColorRed,
|
FillColor: drawing.ColorRed,
|
||||||
},
|
},
|
||||||
XValues: seq.RangeWithStep(0, 4, 1),
|
XValues: LinearRangeWithStep(0, 4, 1),
|
||||||
YValues: seq.RangeWithStep(0, 4, 1),
|
YValues: LinearRangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Equal(5, len(c.Series[0].(ContinuousSeries).XValues))
|
||||||
|
assert.Equal(5, len(c.Series[0].(ContinuousSeries).YValues))
|
||||||
|
|
||||||
var buffer = &bytes.Buffer{}
|
var buffer = &bytes.Buffer{}
|
||||||
err := c.Render(PNG, buffer)
|
err := c.Render(PNG, buffer)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
|
|
@ -3,118 +3,78 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"strings"
|
||||||
|
|
||||||
chart "github.com/wcharczuk/go-chart"
|
chart "github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
outputPath = flag.String("output", "", "The output file")
|
outputPath = flag.String("output", "", "The output file")
|
||||||
|
inputFormat = flag.String("format", "csv", "The input format, either 'csv' or 'tsv' (defaults to 'csv')")
|
||||||
|
inputPath = flag.String("f", "", "The input file")
|
||||||
disableLinreg = flag.Bool("disable-linreg", false, "If we should omit linear regressions")
|
disableLinreg = flag.Bool("disable-linreg", false, "If we should omit linear regressions")
|
||||||
disableLastValues = flag.Bool("disable-last-values", false, "If we should omit last values")
|
disableLastValues = flag.Bool("disable-last-values", false, "If we should omit last values")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLogger returns a new logger.
|
func main() {
|
||||||
func NewLogger() *Logger {
|
flag.Parse()
|
||||||
return &Logger{
|
log := chart.NewLogger()
|
||||||
TimeFormat: time.RFC3339Nano,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger is a basic logger.
|
var rawData []byte
|
||||||
type Logger struct {
|
var err error
|
||||||
TimeFormat string
|
if *inputPath != "" {
|
||||||
Stdout io.Writer
|
if *inputPath == "-" {
|
||||||
Stderr io.Writer
|
rawData, err = ioutil.ReadAll(os.Stdin)
|
||||||
}
|
if err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
// Info writes an info message.
|
}
|
||||||
func (l *Logger) Info(arguments ...interface{}) {
|
} else {
|
||||||
l.Println(append([]interface{}{"[INFO]"}, arguments...)...)
|
rawData, err = ioutil.ReadFile(*inputPath)
|
||||||
}
|
if err != nil {
|
||||||
|
log.FatalErr(err)
|
||||||
// Infof writes an info message.
|
}
|
||||||
func (l *Logger) Infof(format string, arguments ...interface{}) {
|
}
|
||||||
l.Println(append([]interface{}{"[INFO]"}, fmt.Sprintf(format, arguments...))...)
|
} else if len(flag.Args()) > 0 {
|
||||||
}
|
rawData = []byte(flag.Args()[0])
|
||||||
|
} else {
|
||||||
// Debug writes an debug message.
|
flag.Usage()
|
||||||
func (l *Logger) Debug(arguments ...interface{}) {
|
|
||||||
l.Println(append([]interface{}{"[DEBUG]"}, arguments...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf writes an debug message.
|
|
||||||
func (l *Logger) Debugf(format string, arguments ...interface{}) {
|
|
||||||
l.Println(append([]interface{}{"[DEBUG]"}, fmt.Sprintf(format, arguments...))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error writes an error message.
|
|
||||||
func (l *Logger) Error(arguments ...interface{}) {
|
|
||||||
l.Println(append([]interface{}{"[ERROR]"}, arguments...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf writes an error message.
|
|
||||||
func (l *Logger) Errorf(format string, arguments ...interface{}) {
|
|
||||||
l.Println(append([]interface{}{"[ERROR]"}, fmt.Sprintf(format, arguments...))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err writes an error message.
|
|
||||||
func (l *Logger) Err(err error) {
|
|
||||||
if err != nil {
|
|
||||||
l.Println(append([]interface{}{"[ERROR]"}, err.Error())...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FatalErr writes an error message and exits.
|
|
||||||
func (l *Logger) FatalErr(err error) {
|
|
||||||
if err != nil {
|
|
||||||
l.Println(append([]interface{}{"[FATAL]"}, err.Error())...)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Println prints a new message.
|
var parts []string
|
||||||
func (l *Logger) Println(arguments ...interface{}) {
|
switch *inputFormat {
|
||||||
fmt.Fprintln(l.Stdout, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
case "csv":
|
||||||
}
|
parts = chart.SplitCSV(string(rawData))
|
||||||
|
case "tsv":
|
||||||
|
parts = strings.Split(string(rawData), "\t")
|
||||||
|
default:
|
||||||
|
log.FatalErr(fmt.Errorf("invalid format; must be 'csv' or 'tsv'"))
|
||||||
|
}
|
||||||
|
|
||||||
// Errorln prints a new message.
|
yvalues, err := chart.ParseFloats(parts...)
|
||||||
func (l *Logger) Errorln(arguments ...interface{}) {
|
|
||||||
fmt.Fprintln(l.Stderr, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log := NewLogger()
|
|
||||||
|
|
||||||
rawData, err := ioutil.ReadAll(os.Stdin)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FatalErr(err)
|
log.FatalErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csvParts := chart.SplitCSV(string(rawData))
|
var series []chart.Series
|
||||||
|
|
||||||
yvalues, err := chart.ParseFloats(csvParts...)
|
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "Values",
|
||||||
XValues: chart.SeqRange(0, float64(len(csvParts))), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: chart.LinearRange(1, float64(len(yvalues))),
|
||||||
YValues: yvalues,
|
YValues: yvalues,
|
||||||
}
|
}
|
||||||
|
series = append(series, mainSeries)
|
||||||
|
|
||||||
linRegSeries := &chart.LinearRegressionSeries{
|
if !*disableLinreg {
|
||||||
InnerSeries: mainSeries,
|
linRegSeries := &chart.LinearRegressionSeries{
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
series = append(series, linRegSeries)
|
||||||
}
|
}
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
Series: []chart.Series{
|
Series: series,
|
||||||
mainSeries,
|
|
||||||
linRegSeries,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var output *os.File
|
var output *os.File
|
||||||
|
@ -130,8 +90,10 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("rendering chart to", output.Name())
|
|
||||||
if err := graph.Render(chart.PNG, output); err != nil {
|
if err := graph.Render(chart.PNG, output); err != nil {
|
||||||
log.FatalErr(err)
|
log.FatalErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(os.Stdout, output.Name())
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,18 @@ func TestConcatSeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := ContinuousSeries{
|
s1 := ContinuousSeries{
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := ContinuousSeries{
|
s2 := ContinuousSeries{
|
||||||
XValues: SeqRange(11, 20.0),
|
XValues: LinearRange(11, 20.0),
|
||||||
YValues: SeqRange(10.0, 1.0),
|
YValues: LinearRange(10.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s3 := ContinuousSeries{
|
s3 := ContinuousSeries{
|
||||||
XValues: SeqRange(21, 30.0),
|
XValues: LinearRange(21, 30.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := ConcatSeries([]Series{s1, s2, s3})
|
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||||
|
|
|
@ -62,6 +62,9 @@ func (r *ContinuousRange) SetDomain(domain int) {
|
||||||
|
|
||||||
// String returns a simple string for the ContinuousRange.
|
// String returns a simple string for the ContinuousRange.
|
||||||
func (r ContinuousRange) String() string {
|
func (r ContinuousRange) String() string {
|
||||||
|
if r.GetDelta() == 0 {
|
||||||
|
return "ContinuousRange [empty]"
|
||||||
|
}
|
||||||
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
|
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,11 +82,15 @@ func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Rang
|
||||||
// Validate validates the series.
|
// Validate validates the series.
|
||||||
func (cs ContinuousSeries) Validate() error {
|
func (cs ContinuousSeries) Validate() error {
|
||||||
if len(cs.XValues) == 0 {
|
if len(cs.XValues) == 0 {
|
||||||
return fmt.Errorf("continuous series must have xvalues set")
|
return fmt.Errorf("continuous series; must have xvalues set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cs.YValues) == 0 {
|
if len(cs.YValues) == 0 {
|
||||||
return fmt.Errorf("continuous series must have yvalues set")
|
return fmt.Errorf("continuous series; must have yvalues set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cs.XValues) != len(cs.YValues) {
|
||||||
|
return fmt.Errorf("continuous series; must have same length xvalues as yvalues")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ func TestContinuousSeries(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal("Test Series", cs.GetName())
|
assert.Equal("Test Series", cs.GetName())
|
||||||
|
@ -53,20 +53,20 @@ func TestContinuousSeriesValidate(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.Nil(cs.Validate())
|
assert.Nil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: SeqRange(1.0, 10.0),
|
XValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
YValues: SeqRange(1.0, 10.0),
|
YValues: LinearRange(1.0, 10.0),
|
||||||
}
|
}
|
||||||
assert.NotNil(cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emaXValues = seq.Range(1.0, 50.0)
|
emaXValues = LinearRange(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,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
assert "github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHistogramSeries(t *testing.T) {
|
func TestHistogramSeries(t *testing.T) {
|
||||||
|
@ -12,8 +11,8 @@ func TestHistogramSeries(t *testing.T) {
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: seq.Range(1.0, 20.0),
|
XValues: LinearRange(1.0, 20.0),
|
||||||
YValues: seq.Range(10.0, -10.0),
|
YValues: LinearRange(10.0, -10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := HistogramSeries{
|
hs := HistogramSeries{
|
||||||
|
|
|
@ -35,7 +35,7 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
|
||||||
var labels []string
|
var labels []string
|
||||||
var lines []Style
|
var lines []Style
|
||||||
for index, s := range c.Series {
|
for index, s := range c.Series {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if !s.GetStyle().Hidden {
|
||||||
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
||||||
labels = append(labels, s.GetName())
|
labels = append(labels, s.GetName())
|
||||||
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
||||||
|
@ -149,7 +149,7 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable {
|
||||||
var labels []string
|
var labels []string
|
||||||
var lines []Style
|
var lines []Style
|
||||||
for index, s := range c.Series {
|
for index, s := range c.Series {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if !s.GetStyle().Hidden {
|
||||||
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
||||||
labels = append(labels, s.GetName())
|
labels = append(labels, s.GetName())
|
||||||
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
||||||
|
@ -247,7 +247,7 @@ func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
|
||||||
var labels []string
|
var labels []string
|
||||||
var lines []Style
|
var lines []Style
|
||||||
for index, s := range c.Series {
|
for index, s := range c.Series {
|
||||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
if !s.GetStyle().Hidden {
|
||||||
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
||||||
labels = append(labels, s.GetName())
|
labels = append(labels, s.GetName())
|
||||||
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
assert "github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLinearRegressionSeries(t *testing.T) {
|
func TestLinearRegressionSeries(t *testing.T) {
|
||||||
|
@ -12,8 +11,8 @@ func TestLinearRegressionSeries(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0),
|
XValues: LinearRange(1.0, 100.0),
|
||||||
YValues: seq.Range(1.0, 100.0),
|
YValues: LinearRange(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
@ -34,8 +33,8 @@ func TestLinearRegressionSeriesDesc(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(100.0, 1.0),
|
XValues: LinearRange(100.0, 1.0),
|
||||||
YValues: seq.Range(100.0, 1.0),
|
YValues: LinearRange(100.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
@ -56,8 +55,8 @@ func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
||||||
|
|
||||||
mainSeries := ContinuousSeries{
|
mainSeries := ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(100.0, 1.0),
|
XValues: LinearRange(100.0, 1.0),
|
||||||
YValues: seq.Range(100.0, 1.0),
|
YValues: LinearRange(100.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
linRegSeries := &LinearRegressionSeries{
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
|
73
linear_sequence.go
Normal file
73
linear_sequence.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
// LinearRange returns an array of values representing the range from start to end, incremented by 1.0.
|
||||||
|
func LinearRange(start, end float64) []float64 {
|
||||||
|
return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(1.0)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinearRangeWithStep returns the array values of a linear seq with a given start, end and optional step.
|
||||||
|
func LinearRangeWithStep(start, end, step float64) []float64 {
|
||||||
|
return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(step)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinearSequence returns a new linear generator.
|
||||||
|
func NewLinearSequence() *LinearSeq {
|
||||||
|
return &LinearSeq{step: 1.0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinearSeq is a stepwise generator.
|
||||||
|
type LinearSeq struct {
|
||||||
|
start float64
|
||||||
|
end float64
|
||||||
|
step float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns the start value.
|
||||||
|
func (lg LinearSeq) Start() float64 {
|
||||||
|
return lg.start
|
||||||
|
}
|
||||||
|
|
||||||
|
// End returns the end value.
|
||||||
|
func (lg LinearSeq) End() float64 {
|
||||||
|
return lg.end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step returns the step value.
|
||||||
|
func (lg LinearSeq) Step() float64 {
|
||||||
|
return lg.step
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the seq.
|
||||||
|
func (lg LinearSeq) 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 LinearSeq) 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 *LinearSeq) WithStart(start float64) *LinearSeq {
|
||||||
|
lg.start = start
|
||||||
|
return lg
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnd sets the end and returns the linear generator.
|
||||||
|
func (lg *LinearSeq) WithEnd(end float64) *LinearSeq {
|
||||||
|
lg.end = end
|
||||||
|
return lg
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStep sets the step and returns the linear generator.
|
||||||
|
func (lg *LinearSeq) WithStep(step float64) *LinearSeq {
|
||||||
|
lg.step = step
|
||||||
|
return lg
|
||||||
|
}
|
129
logger.go
Normal file
129
logger.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Logger = (*StdoutLogger)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLogger returns a new logger.
|
||||||
|
func NewLogger() Logger {
|
||||||
|
return &StdoutLogger{
|
||||||
|
TimeFormat: time.RFC3339Nano,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is a type that implements the logging interface.
|
||||||
|
type Logger interface {
|
||||||
|
Info(...interface{})
|
||||||
|
Infof(string, ...interface{})
|
||||||
|
Debug(...interface{})
|
||||||
|
Debugf(string, ...interface{})
|
||||||
|
Err(error)
|
||||||
|
FatalErr(error)
|
||||||
|
Error(...interface{})
|
||||||
|
Errorf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
Errorln(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs an info message if the logger is set.
|
||||||
|
func Info(log Logger, arguments ...interface{}) {
|
||||||
|
if log == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info(arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs an info message if the logger is set.
|
||||||
|
func Infof(log Logger, format string, arguments ...interface{}) {
|
||||||
|
if log == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof(format, arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs an debug message if the logger is set.
|
||||||
|
func Debug(log Logger, arguments ...interface{}) {
|
||||||
|
if log == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug(arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs an debug message if the logger is set.
|
||||||
|
func Debugf(log Logger, format string, arguments ...interface{}) {
|
||||||
|
if log == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf(format, arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdoutLogger is a basic logger.
|
||||||
|
type StdoutLogger struct {
|
||||||
|
TimeFormat string
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info writes an info message.
|
||||||
|
func (l *StdoutLogger) Info(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[INFO]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof writes an info message.
|
||||||
|
func (l *StdoutLogger) Infof(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[INFO]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug writes an debug message.
|
||||||
|
func (l *StdoutLogger) Debug(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[DEBUG]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf writes an debug message.
|
||||||
|
func (l *StdoutLogger) Debugf(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[DEBUG]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error writes an error message.
|
||||||
|
func (l *StdoutLogger) Error(arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf writes an error message.
|
||||||
|
func (l *StdoutLogger) Errorf(format string, arguments ...interface{}) {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, fmt.Sprintf(format, arguments...))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err writes an error message.
|
||||||
|
func (l *StdoutLogger) Err(err error) {
|
||||||
|
if err != nil {
|
||||||
|
l.Println(append([]interface{}{"[ERROR]"}, err.Error())...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalErr writes an error message and exits.
|
||||||
|
func (l *StdoutLogger) FatalErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
l.Println(append([]interface{}{"[FATAL]"}, err.Error())...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println prints a new message.
|
||||||
|
func (l *StdoutLogger) Println(arguments ...interface{}) {
|
||||||
|
fmt.Fprintln(l.Stdout, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln prints a new message.
|
||||||
|
func (l *StdoutLogger) Errorln(arguments ...interface{}) {
|
||||||
|
fmt.Fprintln(l.Stderr, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
|
||||||
|
}
|
8
parse.go
8
parse.go
|
@ -2,6 +2,7 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/exception"
|
"github.com/blend/go-sdk/exception"
|
||||||
|
@ -12,8 +13,13 @@ func ParseFloats(values ...string) ([]float64, error) {
|
||||||
var output []float64
|
var output []float64
|
||||||
var parsedValue float64
|
var parsedValue float64
|
||||||
var err error
|
var err error
|
||||||
|
var cleaned string
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if parsedValue, err = strconv.ParseFloat(value, 64); err != nil {
|
cleaned = strings.TrimSpace(strings.Replace(value, ",", "", -1))
|
||||||
|
if cleaned == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if parsedValue, err = strconv.ParseFloat(cleaned, 64); err != nil {
|
||||||
return nil, exception.New(err)
|
return nil, exception.New(err)
|
||||||
}
|
}
|
||||||
output = append(output, parsedValue)
|
output = append(output, parsedValue)
|
||||||
|
|
|
@ -116,7 +116,7 @@ func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PieChart) drawTitle(r Renderer) {
|
func (pc PieChart) drawTitle(r Renderer) {
|
||||||
if len(pc.Title) > 0 && pc.TitleStyle.Show {
|
if len(pc.Title) > 0 && !pc.TitleStyle.Hidden {
|
||||||
Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle())
|
Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
92
random_sequence.go
Normal file
92
random_sequence.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Sequence = (*RandomSeq)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomValues returns an array of random values.
|
||||||
|
func RandomValues(count int) []float64 {
|
||||||
|
return Seq{NewRandomSequence().WithLen(count)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomValuesWithMax returns an array of random values with a given average.
|
||||||
|
func RandomValuesWithMax(count int, max float64) []float64 {
|
||||||
|
return Seq{NewRandomSequence().WithMax(max).WithLen(count)}.Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandomSequence creates a new random seq.
|
||||||
|
func NewRandomSequence() *RandomSeq {
|
||||||
|
return &RandomSeq{
|
||||||
|
rnd: rand.New(rand.NewSource(time.Now().Unix())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomSeq is a random number seq generator.
|
||||||
|
type RandomSeq struct {
|
||||||
|
rnd *rand.Rand
|
||||||
|
max *float64
|
||||||
|
min *float64
|
||||||
|
len *int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements that will be generated.
|
||||||
|
func (r *RandomSeq) Len() int {
|
||||||
|
if r.len != nil {
|
||||||
|
return *r.len
|
||||||
|
}
|
||||||
|
return math.MaxInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns the value.
|
||||||
|
func (r *RandomSeq) 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 *RandomSeq) WithLen(length int) *RandomSeq {
|
||||||
|
r.len = &length
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min returns the minimum value.
|
||||||
|
func (r RandomSeq) Min() *float64 {
|
||||||
|
return r.min
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMin sets the scale and returns the Random.
|
||||||
|
func (r *RandomSeq) WithMin(min float64) *RandomSeq {
|
||||||
|
r.min = &min
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max returns the maximum value.
|
||||||
|
func (r RandomSeq) Max() *float64 {
|
||||||
|
return r.max
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMax sets the average and returns the Random.
|
||||||
|
func (r *RandomSeq) WithMax(max float64) *RandomSeq {
|
||||||
|
r.max = &max
|
||||||
|
return r
|
||||||
|
}
|
178
seq.go
178
seq.go
|
@ -3,19 +3,22 @@ package chart
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/blend/go-sdk/timeutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSeq wraps a provider with a seq.
|
// ValueSequence returns a sequence for a given values set.
|
||||||
func NewSeq(provider SeqProvider) Seq {
|
func ValueSequence(values ...float64) Seq {
|
||||||
return Seq{SeqProvider: provider}
|
return Seq{NewArray(values...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequence is a provider for values for a seq.
|
||||||
|
type Sequence interface {
|
||||||
|
Len() int
|
||||||
|
GetValue(int) float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seq is a utility wrapper for seq providers.
|
// Seq is a utility wrapper for seq providers.
|
||||||
type Seq struct {
|
type Seq struct {
|
||||||
SeqProvider
|
Sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values enumerates the seq into a slice.
|
// Values enumerates the seq into a slice.
|
||||||
|
@ -45,7 +48,7 @@ func (s Seq) Map(mapfn func(i int, v float64) float64) Seq {
|
||||||
for i := 0; i < s.Len(); i++ {
|
for i := 0; i < s.Len(); i++ {
|
||||||
mapfn(i, s.GetValue(i))
|
mapfn(i, s.GetValue(i))
|
||||||
}
|
}
|
||||||
return Seq{SeqArray(output)}
|
return Seq{Array(output)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FoldLeft collapses a seq from left to right.
|
// FoldLeft collapses a seq from left to right.
|
||||||
|
@ -142,7 +145,7 @@ func (s Seq) Sort() Seq {
|
||||||
}
|
}
|
||||||
values := s.Values()
|
values := s.Values()
|
||||||
sort.Float64s(values)
|
sort.Float64s(values)
|
||||||
return Seq{SeqArray(values)}
|
return Seq{Array(values)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Median returns the median or middle value in the sorted seq.
|
// Median returns the median or middle value in the sorted seq.
|
||||||
|
@ -247,160 +250,5 @@ func (s Seq) Normalize() Seq {
|
||||||
output[i] = (s.GetValue(i) - min) / delta
|
output[i] = (s.GetValue(i) - min) / delta
|
||||||
}
|
}
|
||||||
|
|
||||||
return Seq{SeqProvider: SeqArray(output)}
|
return Seq{Array(output)}
|
||||||
}
|
|
||||||
|
|
||||||
// SeqProvider is a provider for values for a seq.
|
|
||||||
type SeqProvider interface {
|
|
||||||
Len() int
|
|
||||||
GetValue(int) float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqArray is a wrapper for an array of floats that implements `ValuesProvider`.
|
|
||||||
type SeqArray []float64
|
|
||||||
|
|
||||||
// Len returns the value provider length.
|
|
||||||
func (a SeqArray) Len() int {
|
|
||||||
return len(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value at a given index.
|
|
||||||
func (a SeqArray) GetValue(index int) float64 {
|
|
||||||
return a[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqDays generates a seq of timestamps by day, from -days to today.
|
|
||||||
func SeqDays(days int) []time.Time {
|
|
||||||
var values []time.Time
|
|
||||||
for day := days; day >= 0; day-- {
|
|
||||||
values = append(values, time.Now().AddDate(0, 0, -day))
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqHours returns a sequence of times by the hour for a given number of hours
|
|
||||||
// after a given start.
|
|
||||||
func SeqHours(start time.Time, totalHours int) []time.Time {
|
|
||||||
times := make([]time.Time, totalHours)
|
|
||||||
|
|
||||||
last := start
|
|
||||||
for i := 0; i < totalHours; i++ {
|
|
||||||
times[i] = last
|
|
||||||
last = last.Add(time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqHoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
|
||||||
func SeqHoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
|
||||||
start, end := TimeMinMax(xdata...)
|
|
||||||
totalHours := DiffHours(start, end)
|
|
||||||
|
|
||||||
finalTimes := SeqHours(start, totalHours+1)
|
|
||||||
finalValues := make([]float64, totalHours+1)
|
|
||||||
|
|
||||||
var hoursFromStart int
|
|
||||||
for i, xd := range xdata {
|
|
||||||
hoursFromStart = DiffHours(start, xd)
|
|
||||||
finalValues[hoursFromStart] = ydata[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalTimes, finalValues
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert types implement interfaces.
|
|
||||||
var (
|
|
||||||
_ SeqProvider = (*SeqTimes)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// SeqTimes are an array of times.
|
|
||||||
// It wraps the array with methods that implement `seq.Provider`.
|
|
||||||
type SeqTimes []time.Time
|
|
||||||
|
|
||||||
// Array returns the times to an array.
|
|
||||||
func (t SeqTimes) Array() []time.Time {
|
|
||||||
return []time.Time(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the array.
|
|
||||||
func (t SeqTimes) Len() int {
|
|
||||||
return len(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns a value at an index as a time.
|
|
||||||
func (t SeqTimes) GetValue(index int) float64 {
|
|
||||||
return timeutil.ToFloat64(t[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqRange returns the array values of a linear seq with a given start, end and optional step.
|
|
||||||
func SeqRange(start, end float64) []float64 {
|
|
||||||
return Seq{NewSeqLinear().WithStart(start).WithEnd(end).WithStep(1.0)}.Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqRangeWithStep returns the array values of a linear seq with a given start, end and optional step.
|
|
||||||
func SeqRangeWithStep(start, end, step float64) []float64 {
|
|
||||||
return Seq{NewSeqLinear().WithStart(start).WithEnd(end).WithStep(step)}.Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSeqLinear returns a new linear generator.
|
|
||||||
func NewSeqLinear() *SeqLinear {
|
|
||||||
return &SeqLinear{step: 1.0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeqLinear is a stepwise generator.
|
|
||||||
type SeqLinear struct {
|
|
||||||
start float64
|
|
||||||
end float64
|
|
||||||
step float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start returns the start value.
|
|
||||||
func (lg SeqLinear) Start() float64 {
|
|
||||||
return lg.start
|
|
||||||
}
|
|
||||||
|
|
||||||
// End returns the end value.
|
|
||||||
func (lg SeqLinear) End() float64 {
|
|
||||||
return lg.end
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step returns the step value.
|
|
||||||
func (lg SeqLinear) Step() float64 {
|
|
||||||
return lg.step
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the seq.
|
|
||||||
func (lg SeqLinear) Len() int {
|
|
||||||
if lg.start < lg.end {
|
|
||||||
return int((lg.end-lg.start)/lg.step) + 1
|
|
||||||
}
|
|
||||||
return int((lg.start-lg.end)/lg.step) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value at a given index.
|
|
||||||
func (lg SeqLinear) GetValue(index int) float64 {
|
|
||||||
fi := float64(index)
|
|
||||||
if lg.start < lg.end {
|
|
||||||
return lg.start + (fi * lg.step)
|
|
||||||
}
|
|
||||||
return lg.start - (fi * lg.step)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStart sets the start and returns the linear generator.
|
|
||||||
func (lg *SeqLinear) WithStart(start float64) *SeqLinear {
|
|
||||||
lg.start = start
|
|
||||||
return lg
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnd sets the end and returns the linear generator.
|
|
||||||
func (lg *SeqLinear) WithEnd(end float64) *SeqLinear {
|
|
||||||
lg.end = end
|
|
||||||
return lg
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStep sets the step and returns the linear generator.
|
|
||||||
func (lg *SeqLinear) WithStep(step float64) *SeqLinear {
|
|
||||||
lg.step = step
|
|
||||||
return lg
|
|
||||||
}
|
}
|
||||||
|
|
136
seq_test.go
Normal file
136
seq_test.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blend/go-sdk/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSeqEach(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 TestSeqMap(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 TestSeqFoldLeft(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 TestSeqFoldRight(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 TestSeqSum(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := Seq{NewArray(1, 2, 3, 4)}
|
||||||
|
assert.Equal(10, values.Sum())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeqAverage(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 := ValueSequence(1, 2, 3, 4, 5).Normalize().Values()
|
||||||
|
|
||||||
|
assert.NotEmpty(normalized)
|
||||||
|
assert.Len(normalized, 5)
|
||||||
|
assert.Equal(0, normalized[0])
|
||||||
|
assert.Equal(0.25, normalized[1])
|
||||||
|
assert.Equal(1, normalized[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearRange(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := LinearRange(1, 100)
|
||||||
|
assert.Len(values, 100)
|
||||||
|
assert.Equal(1, values[0])
|
||||||
|
assert.Equal(100, values[99])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearRangeWithStep(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := LinearRangeWithStep(0, 100, 5)
|
||||||
|
assert.Equal(100, values[20])
|
||||||
|
assert.Len(values, 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearRangeReversed(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := LinearRange(10.0, 1.0)
|
||||||
|
assert.Equal(10, len(values))
|
||||||
|
assert.Equal(10.0, values[0])
|
||||||
|
assert.Equal(1.0, values[9])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearSequenceRegression(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// note; this assumes a 1.0 step is implicitly set in the constructor.
|
||||||
|
linearProvider := NewLinearSequence().WithStart(1.0).WithEnd(100.0)
|
||||||
|
assert.Equal(1, linearProvider.Start())
|
||||||
|
assert.Equal(100, linearProvider.End())
|
||||||
|
assert.Equal(100, linearProvider.Len())
|
||||||
|
|
||||||
|
values := Seq{linearProvider}.Values()
|
||||||
|
assert.Len(values, 100)
|
||||||
|
assert.Equal(1.0, values[0])
|
||||||
|
assert.Equal(100, values[99])
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockValuesProvider struct {
|
type mockValuesProvider struct {
|
||||||
|
@ -32,8 +31,8 @@ func TestSMASeriesGetValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValuesProvider{
|
mockSeries := mockValuesProvider{
|
||||||
seq.Range(1.0, 10.0),
|
LinearRange(1.0, 10.0),
|
||||||
seq.Range(10, 1.0),
|
LinearRange(10, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
|
@ -63,8 +62,8 @@ func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValuesProvider{
|
mockSeries := mockValuesProvider{
|
||||||
seq.Range(1.0, 10.0),
|
LinearRange(1.0, 10.0),
|
||||||
seq.Range(10, 1.0),
|
LinearRange(10, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
|
@ -89,8 +88,8 @@ func TestSMASeriesGetLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValuesProvider{
|
mockSeries := mockValuesProvider{
|
||||||
seq.Range(1.0, 100.0),
|
LinearRange(1.0, 100.0),
|
||||||
seq.Range(100, 1.0),
|
LinearRange(100, 1.0),
|
||||||
}
|
}
|
||||||
assert.Equal(100, mockSeries.Len())
|
assert.Equal(100, mockSeries.Len())
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar S
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) {
|
func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
if sbc.XAxis.Show {
|
if !sbc.XAxis.Hidden {
|
||||||
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
|
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
|
||||||
axisStyle.WriteToRenderer(r)
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
||||||
if sbc.YAxis.Show {
|
if !sbc.YAxis.Hidden {
|
||||||
axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsAxes())
|
axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsAxes())
|
||||||
axisStyle.WriteToRenderer(r)
|
axisStyle.WriteToRenderer(r)
|
||||||
r.MoveTo(canvasBox.Right, canvasBox.Top)
|
r.MoveTo(canvasBox.Right, canvasBox.Top)
|
||||||
|
@ -207,7 +207,7 @@ func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
||||||
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
||||||
r.Stroke()
|
r.Stroke()
|
||||||
|
|
||||||
ticks := SeqRangeWithStep(0.0, 1.0, 0.2)
|
ticks := LinearRangeWithStep(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()))
|
||||||
|
@ -226,7 +226,7 @@ func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sbc StackedBarChart) drawTitle(r Renderer) {
|
func (sbc StackedBarChart) drawTitle(r Renderer) {
|
||||||
if len(sbc.Title) > 0 && sbc.TitleStyle.Show {
|
if len(sbc.Title) > 0 && !sbc.TitleStyle.Hidden {
|
||||||
r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont()))
|
r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont()))
|
||||||
r.SetFontColor(sbc.TitleStyle.GetFontColor(sbc.GetColorPalette().TextColor()))
|
r.SetFontColor(sbc.TitleStyle.GetFontColor(sbc.GetColorPalette().TextColor()))
|
||||||
titleFontSize := sbc.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
titleFontSize := sbc.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
||||||
|
@ -274,7 +274,7 @@ func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box {
|
||||||
totalWidth += bar.GetWidth() + sbc.GetBarSpacing()
|
totalWidth += bar.GetWidth() + sbc.GetBarSpacing()
|
||||||
}
|
}
|
||||||
|
|
||||||
if sbc.XAxis.Show {
|
if !sbc.XAxis.Hidden {
|
||||||
xaxisHeight := DefaultVerticalTickHeight
|
xaxisHeight := DefaultVerticalTickHeight
|
||||||
|
|
||||||
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
|
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
|
||||||
|
|
27
style.go
27
style.go
|
@ -14,10 +14,18 @@ const (
|
||||||
Disabled = -1
|
Disabled = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
// StyleShow is a prebuilt style with the `Show` property set to true.
|
// Hidden is a prebuilt style with the `Hidden` property set to true.
|
||||||
func StyleShow() Style {
|
func Hidden() Style {
|
||||||
return Style{
|
return Style{
|
||||||
Show: true,
|
Hidden: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shown is a prebuilt style with the `Hidden` property set to false.
|
||||||
|
// You can also think of this as the default.
|
||||||
|
func Shown() Style {
|
||||||
|
return Style{
|
||||||
|
Hidden: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +34,7 @@ func StyleShow() Style {
|
||||||
func StyleTextDefaults() Style {
|
func StyleTextDefaults() Style {
|
||||||
font, _ := GetDefaultFont()
|
font, _ := GetDefaultFont()
|
||||||
return Style{
|
return Style{
|
||||||
Show: true,
|
Hidden: false,
|
||||||
Font: font,
|
Font: font,
|
||||||
FontColor: DefaultTextColor,
|
FontColor: DefaultTextColor,
|
||||||
FontSize: DefaultTitleFontSize,
|
FontSize: DefaultTitleFontSize,
|
||||||
|
@ -35,7 +43,7 @@ func StyleTextDefaults() Style {
|
||||||
|
|
||||||
// Style is a simple style set.
|
// Style is a simple style set.
|
||||||
type Style struct {
|
type Style struct {
|
||||||
Show bool
|
Hidden bool
|
||||||
Padding Box
|
Padding Box
|
||||||
|
|
||||||
ClassName string
|
ClassName string
|
||||||
|
@ -65,7 +73,8 @@ type Style struct {
|
||||||
|
|
||||||
// IsZero returns if the object is set or not.
|
// IsZero returns if the object is set or not.
|
||||||
func (s Style) IsZero() bool {
|
func (s Style) IsZero() bool {
|
||||||
return s.StrokeColor.IsZero() &&
|
return !s.Hidden &&
|
||||||
|
s.StrokeColor.IsZero() &&
|
||||||
s.StrokeWidth == 0 &&
|
s.StrokeWidth == 0 &&
|
||||||
s.DotColor.IsZero() &&
|
s.DotColor.IsZero() &&
|
||||||
s.DotWidth == 0 &&
|
s.DotWidth == 0 &&
|
||||||
|
@ -83,10 +92,10 @@ func (s Style) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var output []string
|
var output []string
|
||||||
if s.Show {
|
if s.Hidden {
|
||||||
output = []string{"\"show\": true"}
|
output = []string{"\"hidden\": true"}
|
||||||
} else {
|
} else {
|
||||||
output = []string{"\"show\": false"}
|
output = []string{"\"hidden\": false"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ClassName != "" {
|
if s.ClassName != "" {
|
||||||
|
|
43
times.go
Normal file
43
times.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert types implement interfaces.
|
||||||
|
var (
|
||||||
|
_ Sequence = (*Times)(nil)
|
||||||
|
_ sort.Interface = (*Times)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Times are an array of times.
|
||||||
|
// It wraps the array with methods that implement `seq.Provider`.
|
||||||
|
type Times []time.Time
|
||||||
|
|
||||||
|
// Array returns the times to an array.
|
||||||
|
func (t Times) Array() []time.Time {
|
||||||
|
return []time.Time(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the array.
|
||||||
|
func (t Times) Len() int {
|
||||||
|
return len(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns a value at an index as a time.
|
||||||
|
func (t Times) GetValue(index int) float64 {
|
||||||
|
return timeutil.ToFloat64(t[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface.
|
||||||
|
func (t Times) Swap(i, j int) {
|
||||||
|
t[i], t[j] = t[j], t[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements sort.Interface.
|
||||||
|
func (t Times) Less(i, j int) bool {
|
||||||
|
return t[i].Before(t[j])
|
||||||
|
}
|
45
timeutil.go
45
timeutil.go
|
@ -80,6 +80,11 @@ func TimeToFloat64(t time.Time) float64 {
|
||||||
return float64(t.UnixNano())
|
return float64(t.UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TimeFromFloat64 returns a time from a float64.
|
||||||
|
func TimeFromFloat64(tf float64) time.Time {
|
||||||
|
return time.Unix(0, int64(tf))
|
||||||
|
}
|
||||||
|
|
||||||
// TimeDescending sorts a given list of times ascending, or min to max.
|
// TimeDescending sorts a given list of times ascending, or min to max.
|
||||||
type TimeDescending []time.Time
|
type TimeDescending []time.Time
|
||||||
|
|
||||||
|
@ -103,3 +108,43 @@ func (a TimeAscending) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
// Less implements sort.Sorter
|
// Less implements sort.Sorter
|
||||||
func (a TimeAscending) Less(i, j int) bool { return a[i].Before(a[j]) }
|
func (a TimeAscending) Less(i, j int) bool { return a[i].Before(a[j]) }
|
||||||
|
|
||||||
|
// Days generates a seq of timestamps by day, from -days to today.
|
||||||
|
func 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hours returns a sequence of times by the hour for a given number of hours
|
||||||
|
// after a given start.
|
||||||
|
func 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 HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||||
|
start, end := TimeMinMax(xdata...)
|
||||||
|
totalHours := DiffHours(start, end)
|
||||||
|
|
||||||
|
finalTimes := Hours(start, totalHours+1)
|
||||||
|
finalValues := make([]float64, totalHours+1)
|
||||||
|
|
||||||
|
var hoursFromStart int
|
||||||
|
for i, xd := range xdata {
|
||||||
|
hoursFromStart = DiffHours(start, xd)
|
||||||
|
finalValues[hoursFromStart] = ydata[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalTimes, finalValues
|
||||||
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (b *ValueBuffer) TrimExcess() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array returns the ring buffer, in order, as an array.
|
// Array returns the ring buffer, in order, as an array.
|
||||||
func (b *ValueBuffer) Array() SeqArray {
|
func (b *ValueBuffer) Array() Array {
|
||||||
newArray := make([]float64, b.size)
|
newArray := make([]float64, b.size)
|
||||||
|
|
||||||
if b.size == 0 {
|
if b.size == 0 {
|
||||||
|
@ -163,7 +163,7 @@ func (b *ValueBuffer) Array() SeqArray {
|
||||||
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
return SeqArray(newArray)
|
return Array(newArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each calls the consumer for each element in the buffer.
|
// Each calls the consumer for each element in the buffer.
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"github.com/blend/go-sdk/assert"
|
"github.com/blend/go-sdk/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRingBuffer(t *testing.T) {
|
func TestBuffer(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewValueBuffer()
|
||||||
|
|
||||||
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 := NewValueBuffer()
|
||||||
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 TestRingBufferContents(t *testing.T) {
|
func TestBufferArray(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewValueBuffer()
|
||||||
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.Contents()
|
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,145 +140,53 @@ func TestRingBufferContents(t *testing.T) {
|
||||||
assert.Equal(5, contents[4])
|
assert.Equal(5, contents[4])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRingBufferDrain(t *testing.T) {
|
func TestBufferEach(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
buffer := NewValueBuffer()
|
||||||
buffer.Enqueue(1)
|
|
||||||
buffer.Enqueue(2)
|
|
||||||
buffer.Enqueue(3)
|
|
||||||
buffer.Enqueue(4)
|
|
||||||
buffer.Enqueue(5)
|
|
||||||
|
|
||||||
contents := buffer.Drain()
|
|
||||||
assert.Len(contents, 5)
|
|
||||||
assert.Equal(1, contents[0])
|
|
||||||
assert.Equal(2, contents[1])
|
|
||||||
assert.Equal(3, contents[2])
|
|
||||||
assert.Equal(4, contents[3])
|
|
||||||
assert.Equal(5, contents[4])
|
|
||||||
|
|
||||||
assert.Equal(0, buffer.Len())
|
|
||||||
assert.Nil(buffer.Peek())
|
|
||||||
assert.Nil(buffer.PeekBack())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRingBufferEach(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
for x := 1; x < 17; x++ {
|
||||||
buffer.Enqueue(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++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(16, called)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRingBufferEachUntil(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
|
||||||
buffer.Enqueue(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
called := 0
|
|
||||||
buffer.EachUntil(func(v interface{}) bool {
|
|
||||||
if typed, isTyped := v.(int); isTyped {
|
|
||||||
if typed > 10 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if typed == (called + 1) {
|
|
||||||
called++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(10, called)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRingBufferReverseEachUntil(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
buffer := NewRingBufferWithCapacity(32)
|
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
|
||||||
buffer.Enqueue(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
var values []int
|
|
||||||
buffer.ReverseEachUntil(func(v interface{}) bool {
|
|
||||||
if typed, isTyped := v.(int); isTyped {
|
|
||||||
if typed < 10 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
values = append(values, typed)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
panic("value is not an integer")
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Len(values, 7)
|
|
||||||
assert.Equal(16, values[0])
|
|
||||||
assert.Equal(10, values[6])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRingBufferReverseEachUntilUndersized(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
|
||||||
buffer.Enqueue(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
var values []int
|
|
||||||
buffer.ReverseEachUntil(func(v interface{}) bool {
|
|
||||||
if typed, isTyped := v.(int); isTyped {
|
|
||||||
if typed < 10 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
values = append(values, typed)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
panic("value is not an integer")
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Len(values, 7)
|
|
||||||
assert.Equal(16, values[0])
|
|
||||||
assert.Equal(10, values[6])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRingBufferConsume(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
buffer := NewRingBuffer()
|
|
||||||
|
|
||||||
for x := 1; x < 17; x++ {
|
|
||||||
buffer.Enqueue(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(16, buffer.Len())
|
|
||||||
|
|
||||||
var called int
|
|
||||||
buffer.Consume(func(v interface{}) {
|
|
||||||
if _, isTyped := v.(int); isTyped {
|
|
||||||
called++
|
called++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(16, called)
|
assert.Equal(16, called)
|
||||||
assert.Zero(buffer.Len())
|
}
|
||||||
|
|
||||||
|
func TestNewBuffer(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
empty := NewValueBuffer()
|
||||||
|
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 := NewValueBuffer(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 := NewValueBuffer(1, 2, 3, 4, 5)
|
||||||
|
for i := 0; i < 1<<10; i++ {
|
||||||
|
values.Enqueue(float64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(1<<10-1, values.PeekBack())
|
||||||
}
|
}
|
||||||
|
|
8
xaxis.go
8
xaxis.go
|
@ -108,7 +108,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
bottom = MaxInt(bottom, ty)
|
bottom = MaxInt(bottom, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
if !xa.NameStyle.Hidden && len(xa.Name) > 0 {
|
||||||
tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
|
tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
|
||||||
bottom += DefaultXAxisMargin + tb.Height()
|
bottom += DefaultXAxisMargin + tb.Height()
|
||||||
}
|
}
|
||||||
|
@ -180,16 +180,16 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
||||||
}
|
}
|
||||||
|
|
||||||
nameStyle := xa.NameStyle.InheritFrom(defaults)
|
nameStyle := xa.NameStyle.InheritFrom(defaults)
|
||||||
if xa.NameStyle.Show && len(xa.Name) > 0 {
|
if !xa.NameStyle.Hidden && len(xa.Name) > 0 {
|
||||||
tb := Draw.MeasureText(r, xa.Name, nameStyle)
|
tb := Draw.MeasureText(r, xa.Name, nameStyle)
|
||||||
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
|
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
|
||||||
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
|
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
|
||||||
Draw.Text(r, xa.Name, tx, ty, nameStyle)
|
Draw.Text(r, xa.Name, tx, ty, nameStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show {
|
if !xa.GridMajorStyle.Hidden || !xa.GridMinorStyle.Hidden {
|
||||||
for _, gl := range xa.GetGridLines(ticks) {
|
for _, gl := range xa.GetGridLines(ticks) {
|
||||||
if (gl.IsMinor && xa.GridMinorStyle.Show) || (!gl.IsMinor && xa.GridMajorStyle.Show) {
|
if (gl.IsMinor && !xa.GridMinorStyle.Hidden) || (!gl.IsMinor && !xa.GridMajorStyle.Hidden) {
|
||||||
defaults := xa.GridMajorStyle
|
defaults := xa.GridMajorStyle
|
||||||
if gl.IsMinor {
|
if gl.IsMinor {
|
||||||
defaults = xa.GridMinorStyle
|
defaults = xa.GridMinorStyle
|
||||||
|
|
10
yaxis.go
10
yaxis.go
|
@ -117,7 +117,7 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
||||||
maxy = MaxInt(maxy, ly+tbh2)
|
maxy = MaxInt(maxy, ly+tbh2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
if !ya.NameStyle.Hidden && len(ya.Name) > 0 {
|
||||||
maxx += (DefaultYAxisMargin + maxTextHeight)
|
maxx += (DefaultYAxisMargin + maxTextHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
||||||
}
|
}
|
||||||
|
|
||||||
nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
|
nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
|
||||||
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
if !ya.NameStyle.Hidden && len(ya.Name) > 0 {
|
||||||
nameStyle.GetTextOptions().WriteToRenderer(r)
|
nameStyle.GetTextOptions().WriteToRenderer(r)
|
||||||
tb := Draw.MeasureText(r, ya.Name, nameStyle)
|
tb := Draw.MeasureText(r, ya.Name, nameStyle)
|
||||||
|
|
||||||
|
@ -209,13 +209,13 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
||||||
Draw.Text(r, ya.Name, tx, ty, nameStyle)
|
Draw.Text(r, ya.Name, tx, ty, nameStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.Zero.Style.Show {
|
if !ya.Zero.Style.Hidden {
|
||||||
ya.Zero.Render(r, canvasBox, ra, false, Style{})
|
ya.Zero.Render(r, canvasBox, ra, false, Style{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.GridMajorStyle.Show || ya.GridMinorStyle.Show {
|
if !ya.GridMajorStyle.Hidden || !ya.GridMinorStyle.Hidden {
|
||||||
for _, gl := range ya.GetGridLines(ticks) {
|
for _, gl := range ya.GetGridLines(ticks) {
|
||||||
if (gl.IsMinor && ya.GridMinorStyle.Show) || (!gl.IsMinor && ya.GridMajorStyle.Show) {
|
if (gl.IsMinor && !ya.GridMinorStyle.Hidden) || (!gl.IsMinor && !ya.GridMajorStyle.Hidden) {
|
||||||
defaults := ya.GridMajorStyle
|
defaults := ya.GridMajorStyle
|
||||||
if gl.IsMinor {
|
if gl.IsMinor {
|
||||||
defaults = ya.GridMinorStyle
|
defaults = ya.GridMinorStyle
|
||||||
|
|
Loading…
Reference in a new issue