stuff.
This commit is contained in:
parent
5936b89e89
commit
04a4edcb46
7 changed files with 135 additions and 69 deletions
49
_examples/overlap/main.go
Normal file
49
_examples/overlap/main.go
Normal 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)
|
||||||
|
}
|
55
box_2d.go
55
box_2d.go
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
}
|
}
|
||||||
|
|
2
draw.go
2
draw.go
|
@ -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
|
||||||
|
|
14
util/math.go
14
util/math.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue