fixed dependencies
This commit is contained in:
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
633
vendor/gonum.org/v1/plot/vg/draw/canvas.go
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package draw // import "gonum.org/v1/plot/vg/draw"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gonum.org/v1/plot/text"
|
||||
"gonum.org/v1/plot/vg"
|
||||
)
|
||||
|
||||
// formats holds the registered canvas image formats
|
||||
var formats = struct {
|
||||
sync.RWMutex
|
||||
m map[string]func(w, h vg.Length) vg.CanvasWriterTo
|
||||
}{
|
||||
m: make(map[string]func(w, h vg.Length) vg.CanvasWriterTo),
|
||||
}
|
||||
|
||||
// Formats returns the sorted list of registered vg formats.
|
||||
func Formats() []string {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
list := make([]string, 0, len(formats.m))
|
||||
for name := range formats.m {
|
||||
list = append(list, name)
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// RegisterFormat registers an image format for use by NewFormattedCanvas.
|
||||
// name is the name of the format, like "jpeg" or "png".
|
||||
// fn is the construction function to call for the format.
|
||||
//
|
||||
// RegisterFormat panics if fn is nil.
|
||||
func RegisterFormat(name string, fn func(w, h vg.Length) vg.CanvasWriterTo) {
|
||||
formats.Lock()
|
||||
defer formats.Unlock()
|
||||
|
||||
if fn == nil {
|
||||
panic("draw: RegisterFormat with nil function")
|
||||
}
|
||||
formats.m[name] = fn
|
||||
}
|
||||
|
||||
// A Canvas is a vector graphics canvas along with
|
||||
// an associated Rectangle defining a section of the canvas
|
||||
// to which drawing should take place.
|
||||
type Canvas struct {
|
||||
vg.Canvas
|
||||
vg.Rectangle
|
||||
}
|
||||
|
||||
// XAlignment specifies text alignment in the X direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using XAlignment(desired number).
|
||||
type XAlignment = text.XAlignment
|
||||
|
||||
const (
|
||||
// XLeft aligns the left edge of the text with the specified location.
|
||||
XLeft = text.XLeft
|
||||
// XCenter aligns the horizontal center of the text with the specified location.
|
||||
XCenter = text.XCenter
|
||||
// XRight aligns the right edge of the text with the specified location.
|
||||
XRight = text.XRight
|
||||
)
|
||||
|
||||
// YAlignment specifies text alignment in the Y direction. Three preset
|
||||
// options are available, but an arbitrary alignment
|
||||
// can also be specified using YAlignment(desired number).
|
||||
type YAlignment = text.YAlignment
|
||||
|
||||
const (
|
||||
// YTop aligns the top of of the text with the specified location.
|
||||
YTop = text.YTop
|
||||
// YCenter aligns the vertical center of the text with the specified location.
|
||||
YCenter = text.YCenter
|
||||
// YBottom aligns the bottom of the text with the specified location.
|
||||
YBottom = text.YBottom
|
||||
)
|
||||
|
||||
// Position specifies the text position.
|
||||
const (
|
||||
PosLeft = text.PosLeft
|
||||
PosBottom = text.PosBottom
|
||||
PosCenter = text.PosCenter
|
||||
PosTop = text.PosTop
|
||||
PosRight = text.PosRight
|
||||
)
|
||||
|
||||
// LineStyle describes what a line will look like.
|
||||
type LineStyle struct {
|
||||
// Color is the color of the line.
|
||||
Color color.Color
|
||||
|
||||
// Width is the width of the line.
|
||||
Width vg.Length
|
||||
|
||||
Dashes []vg.Length
|
||||
DashOffs vg.Length
|
||||
}
|
||||
|
||||
// A GlyphStyle specifies the look of a glyph used to draw
|
||||
// a point on a plot.
|
||||
type GlyphStyle struct {
|
||||
// Color is the color used to draw the glyph.
|
||||
color.Color
|
||||
|
||||
// Radius specifies the size of the glyph's radius.
|
||||
Radius vg.Length
|
||||
|
||||
// Shape draws the shape of the glyph.
|
||||
Shape GlyphDrawer
|
||||
}
|
||||
|
||||
// A GlyphDrawer wraps the DrawGlyph function.
|
||||
type GlyphDrawer interface {
|
||||
// DrawGlyph draws the glyph at the given
|
||||
// point, with the given color and radius.
|
||||
DrawGlyph(*Canvas, GlyphStyle, vg.Point)
|
||||
}
|
||||
|
||||
// DrawGlyph draws the given glyph to the draw
|
||||
// area. If the point is not within the Canvas
|
||||
// or the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyph(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil || !c.Contains(pt) {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// DrawGlyphNoClip draws the given glyph to the draw
|
||||
// area. If the sty.Shape is nil then nothing is drawn.
|
||||
func (c *Canvas) DrawGlyphNoClip(sty GlyphStyle, pt vg.Point) {
|
||||
if sty.Shape == nil {
|
||||
return
|
||||
}
|
||||
c.SetColor(sty.Color)
|
||||
sty.Shape.DrawGlyph(c, sty, pt)
|
||||
}
|
||||
|
||||
// Rectangle returns the rectangle surrounding this glyph,
|
||||
// assuming that it is drawn centered at 0,0
|
||||
func (g GlyphStyle) Rectangle() vg.Rectangle {
|
||||
return vg.Rectangle{
|
||||
Min: vg.Point{X: -g.Radius, Y: -g.Radius},
|
||||
Max: vg.Point{X: +g.Radius, Y: +g.Radius},
|
||||
}
|
||||
}
|
||||
|
||||
// CircleGlyph is a glyph that draws a solid circle.
|
||||
type CircleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the GlyphDrawer interface.
|
||||
func (CircleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// RingGlyph is a glyph that draws the outline of a circle.
|
||||
type RingGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (RingGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
p := make(vg.Path, 0, 3)
|
||||
p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
|
||||
p.Arc(pt, sty.Radius, 0, 2*math.Pi)
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
const (
|
||||
cosπover4 = vg.Length(.707106781202420)
|
||||
sinπover6 = vg.Length(.500000000025921)
|
||||
cosπover6 = vg.Length(.866025403769473)
|
||||
)
|
||||
|
||||
// SquareGlyph is a glyph that draws the outline of a square.
|
||||
type SquareGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (SquareGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// BoxGlyph is a glyph that draws a filled square.
|
||||
type BoxGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (BoxGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
|
||||
p := make(vg.Path, 0, 5)
|
||||
p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
|
||||
p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
|
||||
p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// TriangleGlyph is a glyph that draws the outline of a triangle.
|
||||
type TriangleGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (TriangleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// PyramidGlyph is a glyph that draws a filled triangle.
|
||||
type PyramidGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PyramidGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
|
||||
p := make(vg.Path, 0, 4)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// PlusGlyph is a glyph that draws a plus sign
|
||||
type PlusGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (PlusGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// CrossGlyph is a glyph that draws a big X.
|
||||
type CrossGlyph struct{}
|
||||
|
||||
// DrawGlyph implements the Glyph interface.
|
||||
func (CrossGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
|
||||
c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
|
||||
r := sty.Radius * cosπover4
|
||||
p := make(vg.Path, 0, 2)
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y - r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y + r})
|
||||
c.Stroke(p)
|
||||
p = p[:0]
|
||||
p.Move(vg.Point{X: pt.X - r, Y: pt.Y + r})
|
||||
p.Line(vg.Point{X: pt.X + r, Y: pt.Y - r})
|
||||
c.Stroke(p)
|
||||
}
|
||||
|
||||
// New returns a new (bounded) draw.Canvas.
|
||||
func New(c vg.CanvasSizer) Canvas {
|
||||
w, h := c.Size()
|
||||
return NewCanvas(c, w, h)
|
||||
}
|
||||
|
||||
// NewFormattedCanvas creates a new vg.CanvasWriterTo with the specified
|
||||
// image format. Supported formats need to be registered by importing one or
|
||||
// more of the following packages:
|
||||
//
|
||||
// - gonum.org/v1/plot/vg/vgeps: provides eps
|
||||
// - gonum.org/v1/plot/vg/vgimg: provides png, jpg|jpeg, tif|tiff
|
||||
// - gonum.org/v1/plot/vg/vgpdf: provides pdf
|
||||
// - gonum.org/v1/plot/vg/vgsvg: provides svg
|
||||
// - gonum.org/v1/plot/vg/vgtex: provides tex
|
||||
func NewFormattedCanvas(w, h vg.Length, format string) (vg.CanvasWriterTo, error) {
|
||||
formats.RLock()
|
||||
defer formats.RUnlock()
|
||||
|
||||
for name, fn := range formats.m {
|
||||
if format != name {
|
||||
continue
|
||||
}
|
||||
return fn(w, h), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported format: %q", format)
|
||||
}
|
||||
|
||||
// NewCanvas returns a new (bounded) draw.Canvas of the given size.
|
||||
func NewCanvas(c vg.Canvas, w, h vg.Length) Canvas {
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: 0, Y: 0},
|
||||
Max: vg.Point{X: w, Y: h},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Center returns the center point of the area
|
||||
func (c *Canvas) Center() vg.Point {
|
||||
return vg.Point{
|
||||
X: (c.Max.X-c.Min.X)/2 + c.Min.X,
|
||||
Y: (c.Max.Y-c.Min.Y)/2 + c.Min.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the Canvas contains the point.
|
||||
func (c *Canvas) Contains(p vg.Point) bool {
|
||||
return c.ContainsX(p.X) && c.ContainsY(p.Y)
|
||||
}
|
||||
|
||||
// ContainsX returns true if the Canvas contains the
|
||||
// x coordinate.
|
||||
func (c *Canvas) ContainsX(x vg.Length) bool {
|
||||
return x <= c.Max.X+slop && x >= c.Min.X-slop
|
||||
}
|
||||
|
||||
// ContainsY returns true if the Canvas contains the
|
||||
// y coordinate.
|
||||
func (c *Canvas) ContainsY(y vg.Length) bool {
|
||||
return y <= c.Max.Y+slop && y >= c.Min.Y-slop
|
||||
}
|
||||
|
||||
// X returns the value of x, given in the unit range,
|
||||
// in the drawing coordinates of this draw area.
|
||||
// A value of 0, for example, will return the minimum
|
||||
// x value of the draw area and a value of 1 will
|
||||
// return the maximum.
|
||||
func (c *Canvas) X(x float64) vg.Length {
|
||||
return vg.Length(x)*(c.Max.X-c.Min.X) + c.Min.X
|
||||
}
|
||||
|
||||
// Y returns the value of x, given in the unit range,
|
||||
// in the drawing coordinates of this draw area.
|
||||
// A value of 0, for example, will return the minimum
|
||||
// y value of the draw area and a value of 1 will
|
||||
// return the maximum.
|
||||
func (c *Canvas) Y(y float64) vg.Length {
|
||||
return vg.Length(y)*(c.Max.Y-c.Min.Y) + c.Min.Y
|
||||
}
|
||||
|
||||
// Crop returns a new Canvas corresponding to the Canvas
|
||||
// c with the given lengths added to the minimum
|
||||
// and maximum x and y values of the Canvas's Rectangle.
|
||||
// Note that cropping the right and top sides of the canvas
|
||||
// requires specifying negative values of right and top.
|
||||
func Crop(c Canvas, left, right, bottom, top vg.Length) Canvas {
|
||||
minpt := vg.Point{
|
||||
X: c.Min.X + left,
|
||||
Y: c.Min.Y + bottom,
|
||||
}
|
||||
maxpt := vg.Point{
|
||||
X: c.Max.X + right,
|
||||
Y: c.Max.Y + top,
|
||||
}
|
||||
return Canvas{
|
||||
Canvas: c,
|
||||
Rectangle: vg.Rectangle{Min: minpt, Max: maxpt},
|
||||
}
|
||||
}
|
||||
|
||||
// Tiles creates regular subcanvases from a Canvas.
|
||||
type Tiles struct {
|
||||
// Cols and Rows specify the number of rows and columns of tiles.
|
||||
Cols, Rows int
|
||||
// PadTop, PadBottom, PadRight, and PadLeft specify the padding
|
||||
// on the corresponding side of each tile.
|
||||
PadTop, PadBottom, PadRight, PadLeft vg.Length
|
||||
// PadX and PadY specify the padding between columns and rows
|
||||
// of tiles respectively..
|
||||
PadX, PadY vg.Length
|
||||
}
|
||||
|
||||
// At returns the subcanvas within c that corresponds to the
|
||||
// tile at column x, row y.
|
||||
func (ts Tiles) At(c Canvas, x, y int) Canvas {
|
||||
tileH := (c.Max.Y - c.Min.Y - ts.PadTop - ts.PadBottom -
|
||||
vg.Length(ts.Rows-1)*ts.PadY) / vg.Length(ts.Rows)
|
||||
tileW := (c.Max.X - c.Min.X - ts.PadLeft - ts.PadRight -
|
||||
vg.Length(ts.Cols-1)*ts.PadX) / vg.Length(ts.Cols)
|
||||
|
||||
ymax := c.Max.Y - ts.PadTop - vg.Length(y)*(ts.PadY+tileH)
|
||||
ymin := ymax - tileH
|
||||
xmin := c.Min.X + ts.PadLeft + vg.Length(x)*(ts.PadX+tileW)
|
||||
xmax := xmin + tileW
|
||||
|
||||
return Canvas{
|
||||
Canvas: vg.Canvas(c),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: xmin, Y: ymin},
|
||||
Max: vg.Point{X: xmax, Y: ymax},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetLineStyle sets the current line style
|
||||
func (c *Canvas) SetLineStyle(sty LineStyle) {
|
||||
c.SetColor(sty.Color)
|
||||
c.SetLineWidth(sty.Width)
|
||||
c.SetLineDash(sty.Dashes, sty.DashOffs)
|
||||
}
|
||||
|
||||
// StrokeLines draws a line connecting a set of points
|
||||
// in the given Canvas.
|
||||
func (c *Canvas) StrokeLines(sty LineStyle, lines ...[]vg.Point) {
|
||||
if len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetLineStyle(sty)
|
||||
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
p := make(vg.Path, 0, len(l))
|
||||
p.Move(l[0])
|
||||
for _, pt := range l[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
c.Stroke(p)
|
||||
}
|
||||
}
|
||||
|
||||
// StrokeLine2 draws a line between two points in the given
|
||||
// Canvas.
|
||||
func (c *Canvas) StrokeLine2(sty LineStyle, x0, y0, x1, y1 vg.Length) {
|
||||
c.StrokeLines(sty, []vg.Point{{X: x0, Y: y0}, {X: x1, Y: y1}})
|
||||
}
|
||||
|
||||
// ClipLinesXY returns a slice of lines that
|
||||
// represent the given line clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipLinesXY(lines ...[]vg.Point) [][]vg.Point {
|
||||
return c.ClipLinesY(c.ClipLinesX(lines...)...)
|
||||
}
|
||||
|
||||
// ClipLinesX returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipLinesX(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClipLinesY returns a slice of lines that
|
||||
// represent the given line clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipLinesY(lines ...[]vg.Point) (clipped [][]vg.Point) {
|
||||
lines1 := make([][]vg.Point, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
ls := clipLine(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, line)
|
||||
lines1 = append(lines1, ls...)
|
||||
}
|
||||
clipped = make([][]vg.Point, 0, len(lines1))
|
||||
for _, line := range lines1 {
|
||||
ls := clipLine(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1}, line)
|
||||
clipped = append(clipped, ls...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// clipLine performs clipping of a line by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipLine(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (lines [][]vg.Point) {
|
||||
l := make([]vg.Point, 0, len(pts))
|
||||
for i := 1; i < len(pts); i++ {
|
||||
cur, next := pts[i-1], pts[i]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
l = append(l, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
l = append(l, cur, isect(cur, next, clip, norm))
|
||||
lines = append(lines, l)
|
||||
l = []vg.Point{}
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
l = append(l, isect(cur, next, clip, norm))
|
||||
}
|
||||
if nextIn && i == len(pts)-1 {
|
||||
l = append(l, next)
|
||||
}
|
||||
}
|
||||
if len(l) > 1 {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FillPolygon fills a polygon with the given color.
|
||||
func (c *Canvas) FillPolygon(clr color.Color, pts []vg.Point) {
|
||||
if len(pts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetColor(clr)
|
||||
p := make(vg.Path, 0, len(pts)+1)
|
||||
p.Move(pts[0])
|
||||
for _, pt := range pts[1:] {
|
||||
p.Line(pt)
|
||||
}
|
||||
p.Close()
|
||||
c.Fill(p)
|
||||
}
|
||||
|
||||
// ClipPolygonXY returns a slice of lines that
|
||||
// represent the given polygon clipped in both
|
||||
// X and Y directions.
|
||||
func (c *Canvas) ClipPolygonXY(pts []vg.Point) []vg.Point {
|
||||
return c.ClipPolygonY(c.ClipPolygonX(pts))
|
||||
}
|
||||
|
||||
// ClipPolygonX returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// X direction.
|
||||
func (c *Canvas) ClipPolygonX(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0},
|
||||
clipPoly(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, pts))
|
||||
}
|
||||
|
||||
// ClipPolygonY returns a slice of lines that
|
||||
// represent the given polygon clipped in the
|
||||
// Y direction.
|
||||
func (c *Canvas) ClipPolygonY(pts []vg.Point) []vg.Point {
|
||||
return clipPoly(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1},
|
||||
clipPoly(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, pts))
|
||||
}
|
||||
|
||||
// clipPoly performs clipping of a polygon by a single
|
||||
// clipping line specified by the norm, clip point,
|
||||
// and in function.
|
||||
func clipPoly(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (clipped []vg.Point) {
|
||||
clipped = make([]vg.Point, 0, len(pts))
|
||||
for i := 0; i < len(pts); i++ {
|
||||
j := i + 1
|
||||
if i == len(pts)-1 {
|
||||
j = 0
|
||||
}
|
||||
cur, next := pts[i], pts[j]
|
||||
curIn, nextIn := in(cur, clip), in(next, clip)
|
||||
switch {
|
||||
case curIn && nextIn:
|
||||
clipped = append(clipped, cur)
|
||||
|
||||
case curIn && !nextIn:
|
||||
clipped = append(clipped, cur, isect(cur, next, clip, norm))
|
||||
|
||||
case !curIn && !nextIn:
|
||||
// do nothing
|
||||
|
||||
default: // !curIn && nextIn
|
||||
clipped = append(clipped, isect(cur, next, clip, norm))
|
||||
}
|
||||
}
|
||||
n := len(clipped)
|
||||
return clipped[:n:n]
|
||||
}
|
||||
|
||||
// slop is some slop for floating point equality
|
||||
const slop = 3e-8 // ≈ √1⁻¹⁵
|
||||
|
||||
func isLeft(p, clip vg.Point) bool {
|
||||
return p.X <= clip.X+slop
|
||||
}
|
||||
|
||||
func isRight(p, clip vg.Point) bool {
|
||||
return p.X >= clip.X-slop
|
||||
}
|
||||
|
||||
func isBelow(p, clip vg.Point) bool {
|
||||
return p.Y <= clip.Y+slop
|
||||
}
|
||||
|
||||
func isAbove(p, clip vg.Point) bool {
|
||||
return p.Y >= clip.Y-slop
|
||||
}
|
||||
|
||||
// isect returns the intersection of a line p0→p1 with the
|
||||
// clipping line specified by the clip point and normal.
|
||||
func isect(p0, p1, clip, norm vg.Point) vg.Point {
|
||||
// t = (norm · (p0 - clip)) / (norm · (p0 - p1))
|
||||
t := p0.Sub(clip).Dot(norm) / p0.Sub(p1).Dot(norm)
|
||||
|
||||
// p = p0 + t*(p1 - p0)
|
||||
return p1.Sub(p0).Scale(t).Add(p0)
|
||||
}
|
||||
|
||||
// FillText fills lines of text in the draw area.
|
||||
// pt specifies the location where the text is to be drawn.
|
||||
func (c *Canvas) FillText(sty TextStyle, pt vg.Point, txt string) {
|
||||
sty.Handler.Draw(c, txt, sty, pt)
|
||||
}
|
||||
Reference in New Issue
Block a user