This commit is contained in:
Will Charczuk 2017-05-16 17:50:17 -07:00
parent 5936b89e89
commit 04a4edcb46
7 changed files with 135 additions and 69 deletions

49
_examples/overlap/main.go Normal file
View file

@ -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)
}

View file

@ -99,52 +99,23 @@ func (bc Box2d) Equals(other Box2d) bool {
// Overlaps returns if two boxes overlap. // Overlaps returns if two boxes overlap.
func (bc Box2d) Overlaps(other Box2d) bool { func (bc Box2d) Overlaps(other Box2d) bool {
for _, polygon := range []Box2d{bc, other} { pa := bc.Points()
points := polygon.Points() pb := other.Points()
for i1 := 0; i1 < len(points); i1++ { for i := 0; i < 4; i++ {
i2 := (i1 + 1) % len(points) for j := 0; j < 4; j++ {
pa0 := pa[i]
pa1 := pa[(i+1)%4]
p1 := polygon.Points()[i1] pb0 := pb[j]
p2 := polygon.Points()[i2] 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 { func (bc Box2d) String() string {
return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String()) return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())

View file

@ -35,43 +35,50 @@ type CandlestickSeries struct {
Style Style Style Style
YAxis YAxisType YAxis YAxisType
XValues []time.Time // CandleValues will be used in place of creating them from the `InnerSeries`.
YValues []float64 CandleValues []CandleValue
// InnerSeries is used if the `CandleValues` are not set.
InnerSeries ValuesProvider
} }
// GetName implements Series.GetName. // GetName implements Series.GetName.
func (cs CandlestickSeries) GetName() string { func (cs *CandlestickSeries) GetName() string {
return cs.Name return cs.Name
} }
// GetStyle implements Series.GetStyle. // GetStyle implements Series.GetStyle.
func (cs CandlestickSeries) GetStyle() Style { func (cs *CandlestickSeries) GetStyle() Style {
return cs.Style return cs.Style
} }
// GetYAxis returns which yaxis the series is mapped to. // GetYAxis returns which yaxis the series is mapped to.
func (cs CandlestickSeries) GetYAxis() YAxisType { func (cs *CandlestickSeries) GetYAxis() YAxisType {
return cs.YAxis return cs.YAxis
} }
// Len returns the length of the series. // Len returns the length of the series.
func (cs CandlestickSeries) Len() int { func (cs *CandlestickSeries) Len() int {
return util.Math.MinInt(len(cs.XValues), len(cs.YValues)) return len(cs.GetCandleValues())
} }
// GetValues returns the values at a given index. // GetBoundedValues returns the bounded values at a given index.
func (cs CandlestickSeries) GetValues(index int) (float64, float64) { func (cs *CandlestickSeries) GetBoundedValues(index int) (x, y0, y1 float64) {
return util.Time.ToFloat64(cs.XValues[index]), cs.YValues[index] value := cs.GetCandleValues()[index]
return util.Time.ToFloat64(value.Timestamp), value.Low, value.High
} }
// GetRawValues returns the values at a given index. // GetCandleValues returns the candle values.
func (cs CandlestickSeries) GetRawValues(index int) (time.Time, float64) { func (cs CandlestickSeries) GetCandleValues() []CandleValue {
return cs.XValues[index], cs.YValues[index] if cs.CandleValues == nil {
cs.CandleValues = cs.GenerateCandleValues()
}
return cs.CandleValues
} }
// CandleValues returns the candlestick values for each day represented by the inner series. // GenerateCandleValues returns the candlestick values for each day represented by the inner series.
func (cs CandlestickSeries) CandleValues() []CandleValue { func (cs CandlestickSeries) GenerateCandleValues() []CandleValue {
totalValues := cs.Len() totalValues := cs.InnerSeries.Len()
if totalValues == 0 { if totalValues == 0 {
return nil return nil
} }
@ -81,9 +88,10 @@ func (cs CandlestickSeries) CandleValues() []CandleValue {
var year, month, day int var year, month, day int
var t time.Time 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() year, month, day = t.Year(), int(t.Month()), t.Day()
lastYear, lastMonth, lastDay = year, month, day lastYear, lastMonth, lastDay = year, month, day
@ -97,7 +105,8 @@ func (cs CandlestickSeries) CandleValues() []CandleValue {
lv = v lv = v
for i := 1; i < totalValues; i++ { 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() 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 // 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. // Validate validates the series.
func (cs CandlestickSeries) Validate() error { func (cs CandlestickSeries) Validate() error {
if cs.XValues == nil { if cs.CandleValues == nil && cs.InnerSeries == nil {
return fmt.Errorf("candlestick series requires `XValues` to be set") return fmt.Errorf("candlestick series requires either `CandleValues` or `InnerSeries` to be set")
}
if cs.YValues == nil {
return fmt.Errorf("candlestick series requires `YValues` to be set")
} }
return nil return nil
} }

View file

@ -40,11 +40,13 @@ func TestCandlestickSeriesCandleValues(t *testing.T) {
xdata, ydata := generateDummyStockData() xdata, ydata := generateDummyStockData()
candleSeries := CandlestickSeries{ candleSeries := &CandlestickSeries{
InnerSeries: TimeSeries{
XValues: xdata, XValues: xdata,
YValues: ydata, YValues: ydata,
},
} }
values := candleSeries.CandleValues() values := candleSeries.GetCandleValues()
assert.Len(values, 43) // should be 60 days per the generator. assert.Len(values, 43) // should be 60 days per the generator.
} }

View file

@ -173,7 +173,7 @@ func (d draw) CandlestickSeries(r Renderer, canvasBox Box, xrange, yrange Range,
return return
} }
candleValues := cs.CandleValues() candleValues := cs.GetCandleValues()
cb := canvasBox.Bottom cb := canvasBox.Bottom
cl := canvasBox.Left cl := canvasBox.Left

View file

@ -251,3 +251,17 @@ func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx,
ry = int(rotatedY) + cy ry = int(rotatedY) + cy
return 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
}

View file

@ -182,3 +182,27 @@ func TestRotateCoordinate45(t *testing.T) {
assert.Equal(7, rx) assert.Equal(7, rx)
assert.Equal(7, ry) 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))
}