diff --git a/README.md b/README.md index 0a8696c..173fd06 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,32 @@ median, _ := stats.Median(sf.Values) std := stat.StdDev(sf.Values, nil) ``` +## Plotting (only Windows, macOS and Linux) + +```go +import ( + "github.com/rocketlaunchr/dataframe-go/plot" + c "github.com/rocketlaunchr/dataframe-go/plot/wcharczuk/go-chart" +) + +sales := dataframe.NewSeriesFloat64("sales", nil, 50.3, nil, 23.4, 56.2, 89, 32, 84.2, 72, 89) +cs, _ := c.S(ctx, sales, nil) + +graph := chart.Chart{Series: []chart.Series{cs}} + +plt, _ := plot.Open("Monthly sales", 450, 300) +graph.Render(chart.SVG, plt) +plt.Display(plot.SVG) +<-plt.Closed + +``` + +Output: + +

+plot +

+ ## Importing Data The `imports` sub-package has support for importing csv, jsonl and directly from a SQL database. The `DictateDataType` option can be set to specify the true underlying data type. Alternatively, `InferDataTypes` option can be set. diff --git a/helpers.go b/helpers.go index 75fd554..d36a80a 100644 --- a/helpers.go +++ b/helpers.go @@ -26,6 +26,21 @@ func B(b bool) int { return 0 } +// IsValidFloat64 returns true if f is neither Nan nor ±Inf. +// Otherwise it returns false. +func IsValidFloat64(f float64) bool { + + if isNaN(f) { + return false + } + + if isInf(f, 0) { + return false + } + + return true +} + // BoolValueFormatter is used by SetValueToStringFormatter // to display an int as a bool. If the encountered value // is not a 0 or 1, it will panic. diff --git a/plot.png b/plot.png new file mode 100644 index 0000000..8e9e5bd Binary files /dev/null and b/plot.png differ diff --git a/plot/plot.go b/plot/plot.go index 14a4a88..35c16e3 100644 --- a/plot/plot.go +++ b/plot/plot.go @@ -1,3 +1,5 @@ +// Copyright 2018-20 PJ Engineering and Business Solutions Pty. Ltd. All rights reserved. + package plot import ( diff --git a/plot/wcharczuk/go-chart/chart.go b/plot/wcharczuk/go-chart/chart.go new file mode 100644 index 0000000..3aca6ff --- /dev/null +++ b/plot/wcharczuk/go-chart/chart.go @@ -0,0 +1,107 @@ +// Copyright 2018-20 PJ Engineering and Business Solutions Pty. Ltd. All rights reserved. + +package chart + +import ( + "context" + "time" + + "github.com/wcharczuk/go-chart" + + dataframe "github.com/rocketlaunchr/dataframe-go" +) + +// S converts a SeriesFloat64 to a chart.Series for usage with the "github.com/wcharczuk/go-chart" package. +// Currently x can be nil, a SeriesFloat64 or a SeriesTime. nil values in the x and y Series are ignored. +func S(ctx context.Context, y *dataframe.SeriesFloat64, x interface{}, r ...dataframe.Range) (chart.Series, error) { + + var out chart.Series + + if len(r) == 0 { + r = append(r, dataframe.Range{}) + } + + yNRows := y.NRows(dataframe.DontLock) + + start, end, err := r[0].Limits(yNRows) + if err != nil { + return nil, err + } + + switch xx := x.(type) { + case nil: + cs := chart.ContinuousSeries{Name: y.Name(dataframe.DontLock)} + + xVals := []float64{} + yVals := []float64{} + + // Remove nil values + for i, j := 0, start; j < end+1; i, j = i+1, j+1 { + yval := y.Values[j] + + if dataframe.IsValidFloat64(yval) { + yVals = append(yVals, yval) + xVals = append(xVals, float64(i)) + } + } + + cs.XValues = xVals + cs.YValues = yVals + + out = cs + case *dataframe.SeriesFloat64: + + cs := chart.ContinuousSeries{Name: y.Name(dataframe.DontLock)} + + xVals := []float64{} + yVals := []float64{} + + // Remove nil values + for i, j := 0, start; j < end+1; i, j = i+1, j+1 { + yval := y.Values[j] + xval := xx.Values[j] + + if dataframe.IsValidFloat64(yval) { + // Check x val is valid + if dataframe.IsValidFloat64(xval) { + yVals = append(yVals, yval) + xVals = append(xVals, xval) + } + } + } + + cs.XValues = xVals + cs.YValues = yVals + + out = cs + case *dataframe.SeriesTime: + + cs := chart.TimeSeries{Name: y.Name(dataframe.DontLock)} + + xVals := []time.Time{} + yVals := []float64{} + + // Remove nil values + for i, j := 0, start; j < end+1; i, j = i+1, j+1 { + yval := y.Values[j] + xval := xx.Values[j] + + if dataframe.IsValidFloat64(yval) { + // Check x val is valid + if xval != nil { + yVals = append(yVals, yval) + xVals = append(xVals, *xval) + } + } + } + + cs.XValues = xVals + cs.YValues = yVals + + out = cs + default: + panic("unrecognized x") + } + + return out, nil +}