Browse Source

Finished accurate graph tracing

master
Patrick Gaskin 2 years ago
parent
commit
72dbf5d9d0
Signed by: geek1011 GPG Key ID: A2FD79F68A2AB707
  1. 94
      main.go
  2. 10
      util.go

94
main.go

@ -1,7 +1,6 @@
package main
import (
"errors"
"fmt"
"image"
"math"
@ -14,7 +13,7 @@ func main() {
var (
isLineColor = colorMatcher(99, 168, 94)
isMaxLineColor = colorMatcher(194, 68, 68)
isMinLineColor = colorMatcher(119, 195, 107)
isMinLineColor = colorMatcher(119, 195, 107) // MUST be different from line color or will get problems tracing
isGridColor = colorMatcher(215, 215, 214)
isBgColor = colorMatcher(255, 255, 254)
)
@ -46,13 +45,19 @@ func main() {
fmt.Println(dateExtrapolator(14387)) // Jan 8, 2019
var curPrice float64
var curStock bool
if err := traceLine(img, graphBounds, isLineColor, func(x, y int, isDotted bool) {
date, price := dateExtrapolator(x), priceExtrapolator(y)
if curPrice == price {
inStock, date, price := !isDotted, dateExtrapolator(x), priceExtrapolator(y)
if curPrice == price && curStock == inStock {
return
}
fmt.Println(date, price)
curPrice = price
curPrice, curStock = price, inStock
is := 1
if inStock {
is = 0
}
fmt.Printf("%d,%s,%.2f\n", is, date.Format("2006-01-02"), curPrice)
}); err != nil {
panic(err)
}
@ -64,55 +69,33 @@ func main() {
// traceLine traces the grid line.
func traceLine(img image.Image, graphBounds image.Rectangle, line colorMatcherFunc, cb func(x, y int, isDotted bool)) error {
curY := -1
for y := graphBounds.Min.Y; y < graphBounds.Max.Y-1; y++ {
if line(img.At(graphBounds.Min.X, y)) && line(img.At(graphBounds.Min.X, y+1)) {
curY = y
break
curDotted := false
for x := graphBounds.Min.X; x < graphBounds.Max.X; x++ {
curY := -1
for y := graphBounds.Min.Y; y < graphBounds.Max.Y; y++ {
onLine := line(img.At(x, y))
if x > 2 && x < graphBounds.Max.X-2 {
if line(img.At(x-2, y)) && line(img.At(x+2, y)) {
if onLine {
curDotted = false
} else {
onLine = true
curDotted = true
}
}
}
if onLine {
curY = y
break
}
}
}
if curY == -1 {
return errors.New("could not find two pixel high line start at beginning of graph")
}
curX := graphBounds.Min.X
wasMovingVertically := false
for {
cb(curX, curY, false)
// Calculate directions can go in, ignore gap of 1px. TODO: dotted line.
for oldX, movingUp, movingDown := curX, false, false; oldX == curX; {
movingVertically := movingUp || movingDown
fmt.Println(curX, curY, wasMovingVertically)
// ?? <- can skip 1 if needed
// UU <- going up
// oRF <- going right
// .RF o is current position
// DD <- going down
// ?? <- can skip 1 if needed
// ^--- going far right
canGoUp := (line(img.At(curX, curY-1)) && line(img.At(curX+1, curY-1)) || line(img.At(curX, curY-2)) && line(img.At(curX+1, curY-2)))
canGoRight := line(img.At(curX+1, curY)) && line(img.At(curX+1, curY+1))
canGoFarRight := line(img.At(curX+2, curY)) && line(img.At(curX+2, curY+1))
canGoDown := (line(img.At(curX, curY+2)) && line(img.At(curX+1, curY+2)) || line(img.At(curX, curY+3)) && line(img.At(curX+1, curY+3)))
if canGoFarRight && !wasMovingVertically && (canGoUp || canGoDown) {
return fmt.Errorf("line forks at (%d,%d): can go up/down at the same time as right", curX, curY)
} else if !movingVertically && canGoUp && canGoDown {
return fmt.Errorf("line tees at (%d,%d): can go up and down at the same time", curX, curY)
} else if movingVertically && canGoFarRight {
wasMovingVertically = true
curX++
} else if (!movingVertically || movingUp) && !wasMovingVertically && canGoUp {
movingUp = true
curY--
} else if (!movingVertically || movingDown) && !wasMovingVertically && canGoDown {
movingDown = true
curY++
} else if !movingVertically && canGoRight {
wasMovingVertically = false
curX++
} else {
return fmt.Errorf("line ends at (%d,%d): cannot go anywhere", curX, curY)
if curY == -1 {
if x < graphBounds.Max.X {
return fmt.Errorf("line ended early at %d", x)
}
break
}
cb(x, curY, curDotted)
}
return nil
}
@ -173,10 +156,11 @@ func getPriceExtrapolator(img image.Image, graphBounds image.Rectangle, maxPrice
}
// getDateExtrapolator gets an extrapolator to extract the date from the image
// based on an x value relative to the image (NOT the grid).
// based on an x value relative to the image (NOT the grid). It allows extrapolating
// up to 100 days before or after the known range.
func getDateExtrapolator(img image.Image, gridBounds image.Rectangle, startDate, endDate time.Time) func(x int) time.Time {
dates := getDaysBetween(startDate, endDate)
startIndex, dates, endIndex := getDaysBetween(startDate, endDate, 100)
return func(x int) time.Time {
return dates[int(math.Round(extrapolate(gridBounds.Min.X+1, gridBounds.Max.X, 0, float64(len(dates)), x)))]
return dates[int(math.Round(extrapolate(gridBounds.Min.X+1, gridBounds.Max.X, float64(startIndex), float64(endIndex), x)))]
}
}

10
util.go

@ -8,12 +8,12 @@ import (
"time"
)
func getDaysBetween(start, end time.Time) []time.Time {
var dates []time.Time
for t := start; t.Before(end); t = t.AddDate(0, 0, 1) {
dates = append(dates, t)
func getDaysBetween(start, end time.Time, extraRoom int) (startIndex int, dates []time.Time, endIndex int) {
var arr []time.Time
for t := start.AddDate(0, 0, -extraRoom); t.Before(end.AddDate(0, 0, extraRoom)); t = t.AddDate(0, 0, 1) {
arr = append(arr, t)
}
return dates
return extraRoom, arr, len(arr) - extraRoom
}
func pctColor(img image.Image, fn colorMatcherFunc, p int, isColumn bool) float64 {