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