go-chart/drawing/path.go
2016-07-08 17:57:14 -07:00

187 lines
5.1 KiB
Go

package drawing
import (
"fmt"
"math"
)
// PathBuilder describes the interface for path drawing.
type PathBuilder interface {
// LastPoint returns the current point of the current sub path
LastPoint() (x, y float64)
// MoveTo creates a new subpath that start at the specified point
MoveTo(x, y float64)
// LineTo adds a line to the current subpath
LineTo(x, y float64)
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
QuadCurveTo(cx, cy, x, y float64)
// CubicCurveTo adds a cubic Bézier curve to the current subpath
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
// ArcTo adds an arc to the current subpath
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
// Close creates a line from the current point to the last MoveTo
// point (if not the same) and mark the path as closed so the
// first and last lines join nicely.
Close()
}
// PathComponent represents component of a path
type PathComponent int
const (
// MoveToComponent is a MoveTo component in a Path
MoveToComponent PathComponent = iota
// LineToComponent is a LineTo component in a Path
LineToComponent
// QuadCurveToComponent is a QuadCurveTo component in a Path
QuadCurveToComponent
// CubicCurveToComponent is a CubicCurveTo component in a Path
CubicCurveToComponent
// ArcToComponent is a ArcTo component in a Path
ArcToComponent
// CloseComponent is a ArcTo component in a Path
CloseComponent
)
// Path stores points
type Path struct {
// Components is a slice of PathComponent in a Path and mark the role of each points in the Path
Components []PathComponent
// Points are combined with Components to have a specific role in the path
Points []float64
// Last Point of the Path
x, y float64
}
func (p *Path) appendToPath(cmd PathComponent, points ...float64) {
p.Components = append(p.Components, cmd)
p.Points = append(p.Points, points...)
}
// LastPoint returns the current point of the current path
func (p *Path) LastPoint() (x, y float64) {
return p.x, p.y
}
// MoveTo starts a new path at (x, y) position
func (p *Path) MoveTo(x, y float64) {
p.appendToPath(MoveToComponent, x, y)
p.x = x
p.y = y
}
// LineTo adds a line to the current path
func (p *Path) LineTo(x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(LineToComponent, x, y)
p.x = x
p.y = y
}
// QuadCurveTo adds a quadratic bezier curve to the current path
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(QuadCurveToComponent, cx, cy, x, y)
p.x = x
p.y = y
}
// CubicCurveTo adds a cubic bezier curve to the current path
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(CubicCurveToComponent, cx1, cy1, cx2, cy2, x, y)
p.x = x
p.y = y
}
// ArcTo adds an arc to the path
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
endAngle := startAngle + angle
clockWise := true
if angle < 0 {
clockWise = false
}
// normalize
if clockWise {
for endAngle < startAngle {
endAngle += math.Pi * 2.0
}
} else {
for startAngle < endAngle {
startAngle += math.Pi * 2.0
}
}
startX := cx + math.Cos(startAngle)*rx
startY := cy + math.Sin(startAngle)*ry
if len(p.Components) > 0 {
p.LineTo(startX, startY)
} else {
p.MoveTo(startX, startY)
}
p.appendToPath(ArcToComponent, cx, cy, rx, ry, startAngle, angle)
p.x = cx + math.Cos(endAngle)*rx
p.y = cy + math.Sin(endAngle)*ry
}
// Close closes the current path
func (p *Path) Close() {
p.appendToPath(CloseComponent)
}
// Copy make a clone of the current path and return it
func (p *Path) Copy() (dest *Path) {
dest = new(Path)
dest.Components = make([]PathComponent, len(p.Components))
copy(dest.Components, p.Components)
dest.Points = make([]float64, len(p.Points))
copy(dest.Points, p.Points)
dest.x, dest.y = p.x, p.y
return dest
}
// Clear reset the path
func (p *Path) Clear() {
p.Components = p.Components[0:0]
p.Points = p.Points[0:0]
return
}
// IsEmpty returns true if the path is empty
func (p *Path) IsEmpty() bool {
return len(p.Components) == 0
}
// String returns a debug text view of the path
func (p *Path) String() string {
s := ""
j := 0
for _, cmd := range p.Components {
switch cmd {
case MoveToComponent:
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2
case LineToComponent:
s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2
case QuadCurveToComponent:
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3])
j = j + 4
case CubicCurveToComponent:
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
j = j + 6
case ArcToComponent:
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
j = j + 6
case CloseComponent:
s += "Close\n"
}
}
return s
}