From 04a4edcb46e40f1857c704cf13766093d82ce9fd Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Tue, 16 May 2017 17:50:17 -0700 Subject: [PATCH] stuff. --- _examples/overlap/main.go | 49 ++++++++++++++++++++++++++++++++++ box_2d.go | 51 ++++++++--------------------------- candlestick_series.go | 54 +++++++++++++++++++++----------------- candlestick_series_test.go | 10 ++++--- draw.go | 2 +- util/math.go | 14 ++++++++++ util/math_test.go | 24 +++++++++++++++++ 7 files changed, 135 insertions(+), 69 deletions(-) create mode 100644 _examples/overlap/main.go diff --git a/_examples/overlap/main.go b/_examples/overlap/main.go new file mode 100644 index 0000000..249e8cf --- /dev/null +++ b/_examples/overlap/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "net/http" + + chart "github.com/wcharczuk/go-chart" + "github.com/wcharczuk/go-chart/drawing" +) + +func conditionalColor(condition bool, trueColor drawing.Color, falseColor drawing.Color) drawing.Color { + if condition { + return trueColor + } + return falseColor +} + +func drawChart(res http.ResponseWriter, req *http.Request) { + r, _ := chart.PNG(1024, 1024) + + b0 := chart.Box{Left: 100, Top: 100, Right: 400, Bottom: 200} + b1 := chart.Box{Left: 500, Top: 100, Right: 900, Bottom: 200} + b0r := b0.Corners().Rotate(45).Shift(0, 200) + + chart.Draw.Box(r, b0, chart.Style{ + StrokeColor: drawing.ColorRed, + StrokeWidth: 2, + FillColor: conditionalColor(b0.Corners().Overlaps(b1.Corners()), drawing.ColorRed, drawing.ColorTransparent), + }) + + chart.Draw.Box(r, b1, chart.Style{ + StrokeColor: drawing.ColorBlue, + StrokeWidth: 2, + FillColor: conditionalColor(b1.Corners().Overlaps(b0.Corners()), drawing.ColorRed, drawing.ColorTransparent), + }) + + chart.Draw.Box2d(r, b0r, chart.Style{ + StrokeColor: drawing.ColorGreen, + StrokeWidth: 2, + FillColor: conditionalColor(b0r.Overlaps(b0.Corners()), drawing.ColorRed, drawing.ColorTransparent), + }) + + res.Header().Set("Content-Type", "image/png") + r.Save(res) +} + +func main() { + http.HandleFunc("/", drawChart) + http.ListenAndServe(":8080", nil) +} diff --git a/box_2d.go b/box_2d.go index 8648422..3adf53e 100644 --- a/box_2d.go +++ b/box_2d.go @@ -99,51 +99,22 @@ func (bc Box2d) Equals(other Box2d) bool { // Overlaps returns if two boxes overlap. func (bc Box2d) Overlaps(other Box2d) bool { - for _, polygon := range []Box2d{bc, other} { - points := polygon.Points() - for i1 := 0; i1 < len(points); i1++ { - i2 := (i1 + 1) % len(points) + pa := bc.Points() + pb := other.Points() + for i := 0; i < 4; i++ { + for j := 0; j < 4; j++ { + pa0 := pa[i] + pa1 := pa[(i+1)%4] - p1 := polygon.Points()[i1] - p2 := polygon.Points()[i2] + pb0 := pb[j] + pb1 := pb[(j+1)%4] - normal := Point{X: p2.Y - p1.Y, Y: p1.X - p2.X} - - minA := math.MaxFloat64 - maxA := -math.MaxFloat64 - - for _, p := range bc.Points() { - projected := normal.X*p.X + normal.Y*p.Y - - if projected < minA { - minA = projected - } - if projected > maxA { - maxA = projected - } - } - - minB := math.MaxFloat64 - maxB := -math.MaxFloat64 - - for _, p := range other.Points() { - projected := normal.X*p.X + normal.Y*p.Y - - if projected < minB { - minB = projected - } - if projected > maxB { - maxB = projected - } - } - - if maxA < minB || maxB < minA { - return false + if util.Math.LinesIntersect(pa0.X, pa0.Y, pa1.X, pa1.Y, pb0.X, pb0.Y, pb1.X, pb1.Y) { + return true } } } - - return true + return false } func (bc Box2d) String() string { diff --git a/candlestick_series.go b/candlestick_series.go index cdd8112..55cfeee 100644 --- a/candlestick_series.go +++ b/candlestick_series.go @@ -35,43 +35,50 @@ type CandlestickSeries struct { Style Style YAxis YAxisType - XValues []time.Time - YValues []float64 + // CandleValues will be used in place of creating them from the `InnerSeries`. + CandleValues []CandleValue + + // InnerSeries is used if the `CandleValues` are not set. + InnerSeries ValuesProvider } // GetName implements Series.GetName. -func (cs CandlestickSeries) GetName() string { +func (cs *CandlestickSeries) GetName() string { return cs.Name } // GetStyle implements Series.GetStyle. -func (cs CandlestickSeries) GetStyle() Style { +func (cs *CandlestickSeries) GetStyle() Style { return cs.Style } // GetYAxis returns which yaxis the series is mapped to. -func (cs CandlestickSeries) GetYAxis() YAxisType { +func (cs *CandlestickSeries) GetYAxis() YAxisType { return cs.YAxis } // Len returns the length of the series. -func (cs CandlestickSeries) Len() int { - return util.Math.MinInt(len(cs.XValues), len(cs.YValues)) +func (cs *CandlestickSeries) Len() int { + return len(cs.GetCandleValues()) } -// GetValues returns the values at a given index. -func (cs CandlestickSeries) GetValues(index int) (float64, float64) { - return util.Time.ToFloat64(cs.XValues[index]), cs.YValues[index] +// GetBoundedValues returns the bounded values at a given index. +func (cs *CandlestickSeries) GetBoundedValues(index int) (x, y0, y1 float64) { + value := cs.GetCandleValues()[index] + return util.Time.ToFloat64(value.Timestamp), value.Low, value.High } -// GetRawValues returns the values at a given index. -func (cs CandlestickSeries) GetRawValues(index int) (time.Time, float64) { - return cs.XValues[index], cs.YValues[index] +// GetCandleValues returns the candle values. +func (cs CandlestickSeries) GetCandleValues() []CandleValue { + if cs.CandleValues == nil { + cs.CandleValues = cs.GenerateCandleValues() + } + return cs.CandleValues } -// CandleValues returns the candlestick values for each day represented by the inner series. -func (cs CandlestickSeries) CandleValues() []CandleValue { - totalValues := cs.Len() +// GenerateCandleValues returns the candlestick values for each day represented by the inner series. +func (cs CandlestickSeries) GenerateCandleValues() []CandleValue { + totalValues := cs.InnerSeries.Len() if totalValues == 0 { return nil } @@ -81,9 +88,10 @@ func (cs CandlestickSeries) CandleValues() []CandleValue { var year, month, day int var t time.Time - var lv, v float64 + var tv, lv, v float64 - t, v = cs.GetRawValues(0) + tv, v = cs.InnerSeries.GetValues(0) + t = util.Time.FromFloat64(tv) year, month, day = t.Year(), int(t.Month()), t.Day() lastYear, lastMonth, lastDay = year, month, day @@ -97,7 +105,8 @@ func (cs CandlestickSeries) CandleValues() []CandleValue { lv = v for i := 1; i < totalValues; i++ { - t, v = cs.GetRawValues(i) + tv, v = cs.InnerSeries.GetValues(0) + t = util.Time.FromFloat64(tv) year, month, day = t.Year(), int(t.Month()), t.Day() // if we've transitioned to a new day or we're on the last value @@ -137,11 +146,8 @@ func (cs CandlestickSeries) Render(r Renderer, canvasBox Box, xrange, yrange Ran // Validate validates the series. func (cs CandlestickSeries) Validate() error { - if cs.XValues == nil { - return fmt.Errorf("candlestick series requires `XValues` to be set") - } - if cs.YValues == nil { - return fmt.Errorf("candlestick series requires `YValues` to be set") + if cs.CandleValues == nil && cs.InnerSeries == nil { + return fmt.Errorf("candlestick series requires either `CandleValues` or `InnerSeries` to be set") } return nil } diff --git a/candlestick_series_test.go b/candlestick_series_test.go index 204c6b7..c9ac536 100644 --- a/candlestick_series_test.go +++ b/candlestick_series_test.go @@ -40,11 +40,13 @@ func TestCandlestickSeriesCandleValues(t *testing.T) { xdata, ydata := generateDummyStockData() - candleSeries := CandlestickSeries{ - XValues: xdata, - YValues: ydata, + candleSeries := &CandlestickSeries{ + InnerSeries: TimeSeries{ + XValues: xdata, + YValues: ydata, + }, } - values := candleSeries.CandleValues() + values := candleSeries.GetCandleValues() assert.Len(values, 43) // should be 60 days per the generator. } diff --git a/draw.go b/draw.go index 612adb3..951a5f3 100644 --- a/draw.go +++ b/draw.go @@ -173,7 +173,7 @@ func (d draw) CandlestickSeries(r Renderer, canvasBox Box, xrange, yrange Range, return } - candleValues := cs.CandleValues() + candleValues := cs.GetCandleValues() cb := canvasBox.Bottom cl := canvasBox.Left diff --git a/util/math.go b/util/math.go index 73f4976..294fb2c 100644 --- a/util/math.go +++ b/util/math.go @@ -251,3 +251,17 @@ func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry = int(rotatedY) + cy return } + +func (m mathUtil) LinesIntersect(l0x0, l0y0, l0x1, l0y1, l1x0, l1y0, l1x1, l1y1 float64) bool { + var s0x, s0y, s1x, s1y float64 + s0x = l0x1 - l0x0 + s0y = l0y1 - l0y0 + s1x = l1x1 - l1x0 + s1y = l1y1 - l1y0 + + var s, t float64 + s = (-s0y*(l0x0-l1x0) + s0x*(l0y0-l1y0)) / (-s1x*s0y + s0x*s1y) + t = (s1x*(l0y0-l1y0) - s1y*(l0x0-l1x0)) / (-s1x*s0y + s0x*s1y) + + return s >= 0 && s <= 1 && t >= 0 && t <= 1 +} diff --git a/util/math_test.go b/util/math_test.go index af6750a..af44e15 100644 --- a/util/math_test.go +++ b/util/math_test.go @@ -182,3 +182,27 @@ func TestRotateCoordinate45(t *testing.T) { assert.Equal(7, rx) assert.Equal(7, ry) } + +func TestLinesIntersect(t *testing.T) { + assert := assert.New(t) + + p0x := 1.0 + p0y := 1.0 + + p1x := 3.0 + p1y := 1.0 + + p2x := 2.0 + p2y := 2.0 + + p3x := 2.0 + p3y := 0.0 + + p4x := 2.0 + p4y := 2.0 + p5x := 3.0 + p5y := 2.0 + + assert.True(Math.LinesIntersect(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y)) + assert.False(Math.LinesIntersect(p0x, p0y, p1x, p1y, p4x, p4y, p5x, p5y)) +}