updates
This commit is contained in:
parent
51f3cca5d7
commit
5936b89e89
6 changed files with 266 additions and 139 deletions
|
@ -32,11 +32,17 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
tbc := tb.Corners().Rotate(45)
|
||||
|
||||
chart.Draw.BoxCorners(r, tbc, chart.Style{
|
||||
chart.Draw.Box2d(r, tbc, chart.Style{
|
||||
StrokeColor: drawing.ColorRed,
|
||||
StrokeWidth: 2,
|
||||
})
|
||||
|
||||
tbc2 := tbc.Shift(tbc.Height(), 0)
|
||||
chart.Draw.Box2d(r, tbc2, chart.Style{
|
||||
StrokeColor: drawing.ColorGreen,
|
||||
StrokeWidth: 2,
|
||||
})
|
||||
|
||||
tbcb := tbc.Box()
|
||||
chart.Draw.Box(r, tbcb, chart.Style{
|
||||
StrokeColor: drawing.ColorBlue,
|
||||
|
|
109
box.go
109
box.go
|
@ -2,7 +2,6 @@ package chart
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
util "github.com/wcharczuk/go-chart/util"
|
||||
)
|
||||
|
@ -166,12 +165,12 @@ func (b Box) Shift(x, y int) Box {
|
|||
}
|
||||
|
||||
// Corners returns the box as a set of corners.
|
||||
func (b Box) Corners() BoxCorners {
|
||||
return BoxCorners{
|
||||
TopLeft: Point{b.Left, b.Top},
|
||||
TopRight: Point{b.Right, b.Top},
|
||||
BottomRight: Point{b.Right, b.Bottom},
|
||||
BottomLeft: Point{b.Left, b.Bottom},
|
||||
func (b Box) Corners() Box2d {
|
||||
return Box2d{
|
||||
TopLeft: Point{float64(b.Left), float64(b.Top)},
|
||||
TopRight: Point{float64(b.Right), float64(b.Top)},
|
||||
BottomRight: Point{float64(b.Right), float64(b.Bottom)},
|
||||
BottomLeft: Point{float64(b.Left), float64(b.Bottom)},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,99 +254,3 @@ func (b Box) OuterConstrain(bounds, other Box) Box {
|
|||
}
|
||||
return newBox
|
||||
}
|
||||
|
||||
// BoxCorners is a box with independent corners.
|
||||
type BoxCorners struct {
|
||||
TopLeft, TopRight, BottomRight, BottomLeft Point
|
||||
}
|
||||
|
||||
// Box return the BoxCorners as a regular box.
|
||||
func (bc BoxCorners) Box() Box {
|
||||
return Box{
|
||||
Top: util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
||||
Left: util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
||||
Right: util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
||||
Bottom: util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
|
||||
}
|
||||
}
|
||||
|
||||
// Width returns the width
|
||||
func (bc BoxCorners) Width() int {
|
||||
minLeft := util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||
maxRight := util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X)
|
||||
return maxRight - minLeft
|
||||
}
|
||||
|
||||
// Height returns the height
|
||||
func (bc BoxCorners) Height() int {
|
||||
minTop := util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||
maxBottom := util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||
return maxBottom - minTop
|
||||
}
|
||||
|
||||
// Center returns the center of the box
|
||||
func (bc BoxCorners) Center() (x, y int) {
|
||||
|
||||
left := util.Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
||||
right := util.Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
||||
x = ((right - left) >> 1) + left
|
||||
|
||||
top := util.Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
||||
bottom := util.Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||
y = ((bottom - top) >> 1) + top
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Rotate rotates the box.
|
||||
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners {
|
||||
cx, cy := bc.Center()
|
||||
|
||||
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
|
||||
|
||||
tlx, tly := util.Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
||||
trx, try := util.Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
||||
brx, bry := util.Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
||||
blx, bly := util.Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
||||
|
||||
return BoxCorners{
|
||||
TopLeft: Point{tlx, tly},
|
||||
TopRight: Point{trx, try},
|
||||
BottomRight: Point{brx, bry},
|
||||
BottomLeft: Point{blx, bly},
|
||||
}
|
||||
}
|
||||
|
||||
// Equals returns if the box equals another box.
|
||||
func (bc BoxCorners) Equals(other BoxCorners) bool {
|
||||
return bc.TopLeft.Equals(other.TopLeft) &&
|
||||
bc.TopRight.Equals(other.TopRight) &&
|
||||
bc.BottomRight.Equals(other.BottomRight) &&
|
||||
bc.BottomLeft.Equals(other.BottomLeft)
|
||||
}
|
||||
|
||||
func (bc BoxCorners) String() string {
|
||||
return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
|
||||
}
|
||||
|
||||
// Point is an X,Y pair
|
||||
type Point struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// DistanceTo calculates the distance to another point.
|
||||
func (p Point) DistanceTo(other Point) float64 {
|
||||
dx := math.Pow(float64(p.X-other.X), 2)
|
||||
dy := math.Pow(float64(p.Y-other.Y), 2)
|
||||
return math.Pow(dx+dy, 0.5)
|
||||
}
|
||||
|
||||
// Equals returns if a point equals another point.
|
||||
func (p Point) Equals(other Point) bool {
|
||||
return p.X == other.X && p.Y == other.Y
|
||||
}
|
||||
|
||||
// String returns a string representation of the point.
|
||||
func (p Point) String() string {
|
||||
return fmt.Sprintf("P{%d,%d}", p.X, p.Y)
|
||||
}
|
||||
|
|
181
box_2d.go
Normal file
181
box_2d.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
util "github.com/wcharczuk/go-chart/util"
|
||||
)
|
||||
|
||||
// Box2d is a box with (4) independent corners.
|
||||
// It is used when dealing with ~rotated~ boxes.
|
||||
type Box2d struct {
|
||||
TopLeft, TopRight, BottomRight, BottomLeft Point
|
||||
}
|
||||
|
||||
// Points returns the constituent points of the box.
|
||||
func (bc Box2d) Points() []Point {
|
||||
return []Point{
|
||||
bc.TopRight,
|
||||
bc.BottomRight,
|
||||
bc.BottomLeft,
|
||||
bc.TopLeft,
|
||||
}
|
||||
}
|
||||
|
||||
// Box return the Box2d as a regular box.
|
||||
func (bc Box2d) Box() Box {
|
||||
return Box{
|
||||
Top: int(math.Min(bc.TopLeft.Y, bc.TopRight.Y)),
|
||||
Left: int(math.Min(bc.TopLeft.X, bc.BottomLeft.X)),
|
||||
Right: int(math.Max(bc.TopRight.X, bc.BottomRight.X)),
|
||||
Bottom: int(math.Max(bc.BottomLeft.Y, bc.BottomRight.Y)),
|
||||
}
|
||||
}
|
||||
|
||||
// Width returns the width
|
||||
func (bc Box2d) Width() float64 {
|
||||
minLeft := math.Min(bc.TopLeft.X, bc.BottomLeft.X)
|
||||
maxRight := math.Max(bc.TopRight.X, bc.BottomRight.X)
|
||||
return maxRight - minLeft
|
||||
}
|
||||
|
||||
// Height returns the height
|
||||
func (bc Box2d) Height() float64 {
|
||||
minTop := math.Min(bc.TopLeft.Y, bc.TopRight.Y)
|
||||
maxBottom := math.Max(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||
return maxBottom - minTop
|
||||
}
|
||||
|
||||
// Center returns the center of the box
|
||||
func (bc Box2d) Center() (x, y float64) {
|
||||
left := util.Math.Mean(bc.TopLeft.X, bc.BottomLeft.X)
|
||||
right := util.Math.Mean(bc.TopRight.X, bc.BottomRight.X)
|
||||
x = ((right - left) / 2.0) + left
|
||||
|
||||
top := util.Math.Mean(bc.TopLeft.Y, bc.TopRight.Y)
|
||||
bottom := util.Math.Mean(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||
y = ((bottom - top) / 2.0) + top
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Rotate rotates the box.
|
||||
func (bc Box2d) Rotate(thetaDegrees float64) Box2d {
|
||||
cx, cy := bc.Center()
|
||||
|
||||
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
|
||||
|
||||
tlx, tly := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.TopLeft.X), int(bc.TopLeft.Y), thetaRadians)
|
||||
trx, try := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.TopRight.X), int(bc.TopRight.Y), thetaRadians)
|
||||
brx, bry := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.BottomRight.X), int(bc.BottomRight.Y), thetaRadians)
|
||||
blx, bly := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.BottomLeft.X), int(bc.BottomLeft.Y), thetaRadians)
|
||||
|
||||
return Box2d{
|
||||
TopLeft: Point{float64(tlx), float64(tly)},
|
||||
TopRight: Point{float64(trx), float64(try)},
|
||||
BottomRight: Point{float64(brx), float64(bry)},
|
||||
BottomLeft: Point{float64(blx), float64(bly)},
|
||||
}
|
||||
}
|
||||
|
||||
// Shift shifts a box by a given x and y value.
|
||||
func (bc Box2d) Shift(x, y float64) Box2d {
|
||||
return Box2d{
|
||||
TopLeft: bc.TopLeft.Shift(x, y),
|
||||
TopRight: bc.TopRight.Shift(x, y),
|
||||
BottomRight: bc.BottomRight.Shift(x, y),
|
||||
BottomLeft: bc.BottomLeft.Shift(x, y),
|
||||
}
|
||||
}
|
||||
|
||||
// Equals returns if the box equals another box.
|
||||
func (bc Box2d) Equals(other Box2d) bool {
|
||||
return bc.TopLeft.Equals(other.TopLeft) &&
|
||||
bc.TopRight.Equals(other.TopRight) &&
|
||||
bc.BottomRight.Equals(other.BottomRight) &&
|
||||
bc.BottomLeft.Equals(other.BottomLeft)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
p1 := polygon.Points()[i1]
|
||||
p2 := polygon.Points()[i2]
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// Point is an X,Y pair
|
||||
type Point struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
// Shift shifts a point.
|
||||
func (p Point) Shift(x, y float64) Point {
|
||||
return Point{
|
||||
X: p.X + x,
|
||||
Y: p.Y + y,
|
||||
}
|
||||
}
|
||||
|
||||
// DistanceTo calculates the distance to another point.
|
||||
func (p Point) DistanceTo(other Point) float64 {
|
||||
dx := math.Pow(float64(p.X-other.X), 2)
|
||||
dy := math.Pow(float64(p.Y-other.Y), 2)
|
||||
return math.Pow(dx+dy, 0.5)
|
||||
}
|
||||
|
||||
// Equals returns if a point equals another point.
|
||||
func (p Point) Equals(other Point) bool {
|
||||
return p.X == other.X && p.Y == other.Y
|
||||
}
|
||||
|
||||
// String returns a string representation of the point.
|
||||
func (p Point) String() string {
|
||||
return fmt.Sprintf("P{%.2f,%.2f}", p.X, p.Y)
|
||||
}
|
66
box_2d_test.go
Normal file
66
box_2d_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestBox2dCenter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bc := Box2d{
|
||||
TopLeft: Point{5, 5},
|
||||
TopRight: Point{15, 5},
|
||||
BottomRight: Point{15, 15},
|
||||
BottomLeft: Point{5, 15},
|
||||
}
|
||||
|
||||
cx, cy := bc.Center()
|
||||
assert.Equal(10, cx)
|
||||
assert.Equal(10, cy)
|
||||
}
|
||||
|
||||
func TestBox2dRotate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bc := Box2d{
|
||||
TopLeft: Point{5, 5},
|
||||
TopRight: Point{15, 5},
|
||||
BottomRight: Point{15, 15},
|
||||
BottomLeft: Point{5, 15},
|
||||
}
|
||||
|
||||
rotated := bc.Rotate(45)
|
||||
assert.True(rotated.TopLeft.Equals(Point{10, 3}), rotated.String())
|
||||
}
|
||||
|
||||
func TestBox2dOverlaps(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bc := Box2d{
|
||||
TopLeft: Point{5, 5},
|
||||
TopRight: Point{15, 5},
|
||||
BottomRight: Point{15, 15},
|
||||
BottomLeft: Point{5, 15},
|
||||
}
|
||||
|
||||
// shift meaningfully the full width of bc right.
|
||||
bc2 := bc.Shift(bc.Width()+1, 0)
|
||||
assert.False(bc.Overlaps(bc2), fmt.Sprintf("%v\n\t\tshould not overlap\n\t%v", bc, bc2))
|
||||
|
||||
// shift meaningfully the full height of bc down.
|
||||
bc3 := bc.Shift(0, bc.Height()+1)
|
||||
assert.False(bc.Overlaps(bc3), fmt.Sprintf("%v\n\t\tshould not overlap\n\t%v", bc, bc3))
|
||||
|
||||
bc4 := bc.Shift(5, 0)
|
||||
assert.True(bc.Overlaps(bc4))
|
||||
|
||||
bc5 := bc.Shift(0, 5)
|
||||
assert.True(bc.Overlaps(bc5))
|
||||
|
||||
bcr := bc.Rotate(45)
|
||||
bcr2 := bc.Rotate(45).Shift(bc.Height(), 0)
|
||||
assert.False(bcr.Overlaps(bcr2), fmt.Sprintf("%v\n\t\tshould not overlap\n\t%v", bcr, bcr2))
|
||||
}
|
29
box_test.go
29
box_test.go
|
@ -157,32 +157,3 @@ func TestBoxCenter(t *testing.T) {
|
|||
assert.Equal(15, cx)
|
||||
assert.Equal(20, cy)
|
||||
}
|
||||
|
||||
func TestBoxCornersCenter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bc := BoxCorners{
|
||||
TopLeft: Point{5, 5},
|
||||
TopRight: Point{15, 5},
|
||||
BottomRight: Point{15, 15},
|
||||
BottomLeft: Point{5, 15},
|
||||
}
|
||||
|
||||
cx, cy := bc.Center()
|
||||
assert.Equal(10, cx)
|
||||
assert.Equal(10, cy)
|
||||
}
|
||||
|
||||
func TestBoxCornersRotate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bc := BoxCorners{
|
||||
TopLeft: Point{5, 5},
|
||||
TopRight: Point{15, 5},
|
||||
BottomRight: Point{15, 15},
|
||||
BottomLeft: Point{5, 15},
|
||||
}
|
||||
|
||||
rotated := bc.Rotate(45)
|
||||
assert.True(rotated.TopLeft.Equals(Point{10, 3}), rotated.String())
|
||||
}
|
||||
|
|
12
draw.go
12
draw.go
|
@ -314,17 +314,17 @@ func (d draw) Box(r Renderer, b Box, s Style) {
|
|||
}
|
||||
|
||||
func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) {
|
||||
d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s)
|
||||
d.Box2d(r, b.Corners().Rotate(thetaDegrees), s)
|
||||
}
|
||||
|
||||
func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) {
|
||||
func (d draw) Box2d(r Renderer, bc Box2d, s Style) {
|
||||
s.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y)
|
||||
r.LineTo(bc.TopRight.X, bc.TopRight.Y)
|
||||
r.LineTo(bc.BottomRight.X, bc.BottomRight.Y)
|
||||
r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y)
|
||||
r.MoveTo(int(bc.TopLeft.X), int(bc.TopLeft.Y))
|
||||
r.LineTo(int(bc.TopRight.X), int(bc.TopRight.Y))
|
||||
r.LineTo(int(bc.BottomRight.X), int(bc.BottomRight.Y))
|
||||
r.LineTo(int(bc.BottomLeft.X), int(bc.BottomLeft.Y))
|
||||
r.Close()
|
||||
r.FillStroke()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue