From 7858457772d74b52aa9a1f4544164140a34e429a Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Sun, 17 Jul 2016 01:42:31 -0700 Subject: [PATCH] histogram still needs tweaking. --- drawing_helpers.go | 38 ++++++++++++++- histogram_series.go | 57 +++++++++++++++++++++++ macd_series.go | 110 ++++++++++++++++++++++++++++++++++++++++++++ macd_series_test.go | 29 ++++++++++++ 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 histogram_series.go create mode 100644 macd_series.go create mode 100644 macd_series_test.go diff --git a/drawing_helpers.go b/drawing_helpers.go index 85262d0..06b41d5 100644 --- a/drawing_helpers.go +++ b/drawing_helpers.go @@ -1,6 +1,10 @@ package chart -import "github.com/wcharczuk/go-chart/drawing" +import ( + "math" + + "github.com/wcharczuk/go-chart/drawing" +) // DrawLineSeries draws a line series with a renderer. func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider) { @@ -102,6 +106,38 @@ func DrawBoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, r.FillStroke() } +// DrawHistogramSeries draws a value provider as boxes from 0. +func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider, barWidths ...int) { + if vs.Len() == 0 { + return + } + + //calculate bar width? + seriesLength := vs.Len() + barWidth := int(math.Floor(float64(xrange.Domain) / float64(seriesLength))) + if len(barWidths) > 0 { + barWidth = barWidths[0] + } + + cb := canvasBox.Bottom + cl := canvasBox.Left + + //foreach datapoint, draw a box. + for index := 0; index < seriesLength; index++ { + vx, vy := vs.GetValue(index) + y0 := yrange.Translate(0) + x := cl + xrange.Translate(vx) + y := yrange.Translate(vy) + + DrawBox(r, Box{ + Top: cb - y0, + Left: x - (barWidth >> 1), + Right: x + (barWidth >> 1), + Bottom: cb - y, + }, s) + } +} + // MeasureAnnotation measures how big an annotation would be. func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string) Box { r.SetFillColor(s.GetFillColor(DefaultAnnotationFillColor)) diff --git a/histogram_series.go b/histogram_series.go new file mode 100644 index 0000000..3f0b34c --- /dev/null +++ b/histogram_series.go @@ -0,0 +1,57 @@ +package chart + +// HistogramSeries is a special type of series that draws as a histogram. +// Some peculiarities; it will always be lower bounded at 0 (at the very least). +// This may alter ranges a bit and generally you want to put a histogram series on it's own y-axis. +type HistogramSeries struct { + Name string + Style Style + YAxis YAxisType + InnerSeries ValueProvider +} + +// GetName implements Series.GetName. +func (hs HistogramSeries) GetName() string { + return hs.Name +} + +// GetStyle implements Series.GetStyle. +func (hs HistogramSeries) GetStyle() Style { + return hs.Style +} + +// GetYAxis returns which yaxis the series is mapped to. +func (hs HistogramSeries) GetYAxis() YAxisType { + return hs.YAxis +} + +// Len implements BoundedValueProvider.Len. +func (hs HistogramSeries) Len() int { + return hs.InnerSeries.Len() +} + +// GetValue implements ValueProvider.GetValue. +func (hs HistogramSeries) GetValue(index int) (x, y float64) { + return hs.InnerSeries.GetValue(index) +} + +// GetBoundedValue implements BoundedValueProvider.GetBoundedValue +func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) { + vx, vy := hs.InnerSeries.GetValue(index) + + x = vx + + if vy > 0 { + y1 = vy + return + } + + y2 = vy + return +} + +// Render implements Series.Render. +func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { + style := hs.Style.WithDefaultsFrom(defaults) + DrawHistogramSeries(r, canvasBox, xrange, yrange, style, hs) +} diff --git a/macd_series.go b/macd_series.go new file mode 100644 index 0000000..d7d0899 --- /dev/null +++ b/macd_series.go @@ -0,0 +1,110 @@ +package chart + +const ( + // DefaultMACDWindowPrimary is the long window. + DefaultMACDWindowPrimary = 26 + // DefaultMACDWindowSecondary is the short window. + DefaultMACDWindowSecondary = 12 +) + +// MACDSeries (or Moving Average Convergence Divergence) is a special type of series that +// computes the difference between two different EMA values for a given index, as denoted by WindowPrimary(26) and WindowSecondary(12). +type MACDSeries struct { + Name string + Style Style + YAxis YAxisType + InnerSeries ValueProvider + + WindowPrimary int + WindowSecondary int + + Sigma float64 +} + +// GetWindows returns the primary and secondary window sizes. +func (macd MACDSeries) GetWindows() (w1, w2 int) { + if macd.WindowPrimary == 0 { + w1 = DefaultMACDWindowPrimary + } else { + w1 = macd.WindowPrimary + } + if macd.WindowSecondary == 0 { + w2 = DefaultMACDWindowSecondary + } else { + w2 = macd.WindowSecondary + } + return +} + +// GetSigma returns the smoothing factor for the serise. +func (macd MACDSeries) GetSigma(defaults ...float64) float64 { + if macd.Sigma == 0 { + if len(defaults) > 0 { + return defaults[0] + } + return DefaultExponentialMovingAverageSigma + } + return macd.Sigma +} + +// GetName returns the name of the time series. +func (macd MACDSeries) GetName() string { + return macd.Name +} + +// GetStyle returns the line style. +func (macd MACDSeries) GetStyle() Style { + return macd.Style +} + +// GetYAxis returns which YAxis the series draws on. +func (macd MACDSeries) GetYAxis() YAxisType { + return macd.YAxis +} + +// Len returns the number of elements in the series. +func (macd MACDSeries) Len() int { + if macd.InnerSeries == nil { + return 0 + } + + w1, _ := macd.GetWindows() + innerLen := macd.InnerSeries.Len() + if innerLen > w1 { + return innerLen - w1 + } + return 0 +} + +// GetValue gets a value at a given index. +func (macd MACDSeries) GetValue(index int) (x float64, y float64) { + if macd.InnerSeries == nil { + return + } + + w1, w2 := macd.GetWindows() + + effectiveIndex := index + w1 + x, _ = macd.InnerSeries.GetValue(effectiveIndex) + + ema1 := macd.computeEMA(w1, effectiveIndex) + ema2 := macd.computeEMA(w2, effectiveIndex) + + y = ema1 - ema2 + return +} + +func (macd MACDSeries) computeEMA(windowSize int, index int) float64 { + _, v := macd.InnerSeries.GetValue(index) + if windowSize == 1 { + return v + } + sig := macd.GetSigma() + return sig*v + ((1.0 - sig) * macd.computeEMA(windowSize-1, index-1)) +} + +// Render renders the series. +func (macd MACDSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { + style := macd.Style.WithDefaultsFrom(defaults) + DrawLineSeries(r, canvasBox, xrange, yrange, style, macd) +} diff --git a/macd_series_test.go b/macd_series_test.go new file mode 100644 index 0000000..6d78ee1 --- /dev/null +++ b/macd_series_test.go @@ -0,0 +1,29 @@ +package chart + +import ( + "testing" + + "github.com/blendlabs/go-assert" +) + +func TestMACDSeries(t *testing.T) { + assert := assert.New(t) + + mockSeries := mockValueProvider{ + Seq(1.0, 100.0), + SeqRand(100.0, 256), + } + assert.Equal(100, mockSeries.Len()) + + mas := &MACDSeries{ + InnerSeries: mockSeries, + } + + var yvalues []float64 + for x := 0; x < mas.Len(); x++ { + _, y := mas.GetValue(x) + yvalues = append(yvalues, y) + } + + assert.NotEmpty(yvalues) +}