...

Source file src/gonum.org/v1/plot/vg/draw/canvas.go

Documentation: gonum.org/v1/plot/vg/draw

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package draw // import "gonum.org/v1/plot/vg/draw"
     6  
     7  import (
     8  	"fmt"
     9  	"image/color"
    10  	"math"
    11  	"sort"
    12  	"sync"
    13  
    14  	"gonum.org/v1/plot/text"
    15  	"gonum.org/v1/plot/vg"
    16  )
    17  
    18  // formats holds the registered canvas image formats
    19  var formats = struct {
    20  	sync.RWMutex
    21  	m map[string]func(w, h vg.Length) vg.CanvasWriterTo
    22  }{
    23  	m: make(map[string]func(w, h vg.Length) vg.CanvasWriterTo),
    24  }
    25  
    26  // Formats returns the sorted list of registered vg formats.
    27  func Formats() []string {
    28  	formats.RLock()
    29  	defer formats.RUnlock()
    30  
    31  	list := make([]string, 0, len(formats.m))
    32  	for name := range formats.m {
    33  		list = append(list, name)
    34  	}
    35  	sort.Strings(list)
    36  	return list
    37  }
    38  
    39  // RegisterFormat registers an image format for use by NewFormattedCanvas.
    40  // name is the name of the format, like "jpeg" or "png".
    41  // fn is the construction function to call for the format.
    42  //
    43  // RegisterFormat panics if fn is nil.
    44  func RegisterFormat(name string, fn func(w, h vg.Length) vg.CanvasWriterTo) {
    45  	formats.Lock()
    46  	defer formats.Unlock()
    47  
    48  	if fn == nil {
    49  		panic("draw: RegisterFormat with nil function")
    50  	}
    51  	formats.m[name] = fn
    52  }
    53  
    54  // A Canvas is a vector graphics canvas along with
    55  // an associated Rectangle defining a section of the canvas
    56  // to which drawing should take place.
    57  type Canvas struct {
    58  	vg.Canvas
    59  	vg.Rectangle
    60  }
    61  
    62  // XAlignment specifies text alignment in the X direction. Three preset
    63  // options are available, but an arbitrary alignment
    64  // can also be specified using XAlignment(desired number).
    65  type XAlignment = text.XAlignment
    66  
    67  const (
    68  	// XLeft aligns the left edge of the text with the specified location.
    69  	XLeft = text.XLeft
    70  	// XCenter aligns the horizontal center of the text with the specified location.
    71  	XCenter = text.XCenter
    72  	// XRight aligns the right edge of the text with the specified location.
    73  	XRight = text.XRight
    74  )
    75  
    76  // YAlignment specifies text alignment in the Y direction. Three preset
    77  // options are available, but an arbitrary alignment
    78  // can also be specified using YAlignment(desired number).
    79  type YAlignment = text.YAlignment
    80  
    81  const (
    82  	// YTop aligns the top of of the text with the specified location.
    83  	YTop = text.YTop
    84  	// YCenter aligns the vertical center of the text with the specified location.
    85  	YCenter = text.YCenter
    86  	// YBottom aligns the bottom of the text with the specified location.
    87  	YBottom = text.YBottom
    88  )
    89  
    90  // Position specifies the text position.
    91  const (
    92  	PosLeft   = text.PosLeft
    93  	PosBottom = text.PosBottom
    94  	PosCenter = text.PosCenter
    95  	PosTop    = text.PosTop
    96  	PosRight  = text.PosRight
    97  )
    98  
    99  // LineStyle describes what a line will look like.
   100  type LineStyle struct {
   101  	// Color is the color of the line.
   102  	Color color.Color
   103  
   104  	// Width is the width of the line.
   105  	Width vg.Length
   106  
   107  	Dashes   []vg.Length
   108  	DashOffs vg.Length
   109  }
   110  
   111  // A GlyphStyle specifies the look of a glyph used to draw
   112  // a point on a plot.
   113  type GlyphStyle struct {
   114  	// Color is the color used to draw the glyph.
   115  	color.Color
   116  
   117  	// Radius specifies the size of the glyph's radius.
   118  	Radius vg.Length
   119  
   120  	// Shape draws the shape of the glyph.
   121  	Shape GlyphDrawer
   122  }
   123  
   124  // A GlyphDrawer wraps the DrawGlyph function.
   125  type GlyphDrawer interface {
   126  	// DrawGlyph draws the glyph at the given
   127  	// point, with the given color and radius.
   128  	DrawGlyph(*Canvas, GlyphStyle, vg.Point)
   129  }
   130  
   131  // DrawGlyph draws the given glyph to the draw
   132  // area.  If the point is not within the Canvas
   133  // or the sty.Shape is nil then nothing is drawn.
   134  func (c *Canvas) DrawGlyph(sty GlyphStyle, pt vg.Point) {
   135  	if sty.Shape == nil || !c.Contains(pt) {
   136  		return
   137  	}
   138  	c.SetColor(sty.Color)
   139  	sty.Shape.DrawGlyph(c, sty, pt)
   140  }
   141  
   142  // DrawGlyphNoClip draws the given glyph to the draw
   143  // area.  If the sty.Shape is nil then nothing is drawn.
   144  func (c *Canvas) DrawGlyphNoClip(sty GlyphStyle, pt vg.Point) {
   145  	if sty.Shape == nil {
   146  		return
   147  	}
   148  	c.SetColor(sty.Color)
   149  	sty.Shape.DrawGlyph(c, sty, pt)
   150  }
   151  
   152  // Rectangle returns the rectangle surrounding this glyph,
   153  // assuming that it is drawn centered at 0,0
   154  func (g GlyphStyle) Rectangle() vg.Rectangle {
   155  	return vg.Rectangle{
   156  		Min: vg.Point{X: -g.Radius, Y: -g.Radius},
   157  		Max: vg.Point{X: +g.Radius, Y: +g.Radius},
   158  	}
   159  }
   160  
   161  // CircleGlyph is a glyph that draws a solid circle.
   162  type CircleGlyph struct{}
   163  
   164  // DrawGlyph implements the GlyphDrawer interface.
   165  func (CircleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   166  	p := make(vg.Path, 0, 3)
   167  	p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
   168  	p.Arc(pt, sty.Radius, 0, 2*math.Pi)
   169  	p.Close()
   170  	c.Fill(p)
   171  }
   172  
   173  // RingGlyph is a glyph that draws the outline of a circle.
   174  type RingGlyph struct{}
   175  
   176  // DrawGlyph implements the Glyph interface.
   177  func (RingGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   178  	c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
   179  	p := make(vg.Path, 0, 3)
   180  	p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y})
   181  	p.Arc(pt, sty.Radius, 0, 2*math.Pi)
   182  	p.Close()
   183  	c.Stroke(p)
   184  }
   185  
   186  const (
   187  	cosπover4 = vg.Length(.707106781202420)
   188  	sinπover6 = vg.Length(.500000000025921)
   189  	cosπover6 = vg.Length(.866025403769473)
   190  )
   191  
   192  // SquareGlyph is a glyph that draws the outline of a square.
   193  type SquareGlyph struct{}
   194  
   195  // DrawGlyph implements the Glyph interface.
   196  func (SquareGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   197  	c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
   198  	x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
   199  	p := make(vg.Path, 0, 5)
   200  	p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
   201  	p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
   202  	p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
   203  	p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
   204  	p.Close()
   205  	c.Stroke(p)
   206  }
   207  
   208  // BoxGlyph is a glyph that draws a filled square.
   209  type BoxGlyph struct{}
   210  
   211  // DrawGlyph implements the Glyph interface.
   212  func (BoxGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   213  	x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4
   214  	p := make(vg.Path, 0, 5)
   215  	p.Move(vg.Point{X: pt.X - x, Y: pt.Y - x})
   216  	p.Line(vg.Point{X: pt.X + x, Y: pt.Y - x})
   217  	p.Line(vg.Point{X: pt.X + x, Y: pt.Y + x})
   218  	p.Line(vg.Point{X: pt.X - x, Y: pt.Y + x})
   219  	p.Close()
   220  	c.Fill(p)
   221  }
   222  
   223  // TriangleGlyph is a glyph that draws the outline of a triangle.
   224  type TriangleGlyph struct{}
   225  
   226  // DrawGlyph implements the Glyph interface.
   227  func (TriangleGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   228  	c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
   229  	r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
   230  	p := make(vg.Path, 0, 4)
   231  	p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
   232  	p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
   233  	p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
   234  	p.Close()
   235  	c.Stroke(p)
   236  }
   237  
   238  // PyramidGlyph is a glyph that draws a filled triangle.
   239  type PyramidGlyph struct{}
   240  
   241  // DrawGlyph implements the Glyph interface.
   242  func (PyramidGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   243  	r := sty.Radius + (sty.Radius-sty.Radius*sinπover6)/2
   244  	p := make(vg.Path, 0, 4)
   245  	p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
   246  	p.Line(vg.Point{X: pt.X - r*cosπover6, Y: pt.Y - r*sinπover6})
   247  	p.Line(vg.Point{X: pt.X + r*cosπover6, Y: pt.Y - r*sinπover6})
   248  	p.Close()
   249  	c.Fill(p)
   250  }
   251  
   252  // PlusGlyph is a glyph that draws a plus sign
   253  type PlusGlyph struct{}
   254  
   255  // DrawGlyph implements the Glyph interface.
   256  func (PlusGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   257  	c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
   258  	r := sty.Radius
   259  	p := make(vg.Path, 0, 2)
   260  	p.Move(vg.Point{X: pt.X, Y: pt.Y + r})
   261  	p.Line(vg.Point{X: pt.X, Y: pt.Y - r})
   262  	c.Stroke(p)
   263  	p = p[:0]
   264  	p.Move(vg.Point{X: pt.X - r, Y: pt.Y})
   265  	p.Line(vg.Point{X: pt.X + r, Y: pt.Y})
   266  	c.Stroke(p)
   267  }
   268  
   269  // CrossGlyph is a glyph that draws a big X.
   270  type CrossGlyph struct{}
   271  
   272  // DrawGlyph implements the Glyph interface.
   273  func (CrossGlyph) DrawGlyph(c *Canvas, sty GlyphStyle, pt vg.Point) {
   274  	c.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)})
   275  	r := sty.Radius * cosπover4
   276  	p := make(vg.Path, 0, 2)
   277  	p.Move(vg.Point{X: pt.X - r, Y: pt.Y - r})
   278  	p.Line(vg.Point{X: pt.X + r, Y: pt.Y + r})
   279  	c.Stroke(p)
   280  	p = p[:0]
   281  	p.Move(vg.Point{X: pt.X - r, Y: pt.Y + r})
   282  	p.Line(vg.Point{X: pt.X + r, Y: pt.Y - r})
   283  	c.Stroke(p)
   284  }
   285  
   286  // New returns a new (bounded) draw.Canvas.
   287  func New(c vg.CanvasSizer) Canvas {
   288  	w, h := c.Size()
   289  	return NewCanvas(c, w, h)
   290  }
   291  
   292  // NewFormattedCanvas creates a new vg.CanvasWriterTo with the specified
   293  // image format. Supported formats need to be registered by importing one or
   294  // more of the following packages:
   295  //
   296  //   - gonum.org/v1/plot/vg/vgeps: provides eps
   297  //   - gonum.org/v1/plot/vg/vgimg: provides png, jpg|jpeg, tif|tiff
   298  //   - gonum.org/v1/plot/vg/vgpdf: provides pdf
   299  //   - gonum.org/v1/plot/vg/vgsvg: provides svg
   300  //   - gonum.org/v1/plot/vg/vgtex: provides tex
   301  func NewFormattedCanvas(w, h vg.Length, format string) (vg.CanvasWriterTo, error) {
   302  	formats.RLock()
   303  	defer formats.RUnlock()
   304  
   305  	for name, fn := range formats.m {
   306  		if format != name {
   307  			continue
   308  		}
   309  		return fn(w, h), nil
   310  	}
   311  	return nil, fmt.Errorf("unsupported format: %q", format)
   312  }
   313  
   314  // NewCanvas returns a new (bounded) draw.Canvas of the given size.
   315  func NewCanvas(c vg.Canvas, w, h vg.Length) Canvas {
   316  	return Canvas{
   317  		Canvas: c,
   318  		Rectangle: vg.Rectangle{
   319  			Min: vg.Point{X: 0, Y: 0},
   320  			Max: vg.Point{X: w, Y: h},
   321  		},
   322  	}
   323  }
   324  
   325  // Center returns the center point of the area
   326  func (c *Canvas) Center() vg.Point {
   327  	return vg.Point{
   328  		X: (c.Max.X-c.Min.X)/2 + c.Min.X,
   329  		Y: (c.Max.Y-c.Min.Y)/2 + c.Min.Y,
   330  	}
   331  }
   332  
   333  // Contains returns true if the Canvas contains the point.
   334  func (c *Canvas) Contains(p vg.Point) bool {
   335  	return c.ContainsX(p.X) && c.ContainsY(p.Y)
   336  }
   337  
   338  // ContainsX returns true if the Canvas contains the
   339  // x coordinate.
   340  func (c *Canvas) ContainsX(x vg.Length) bool {
   341  	return x <= c.Max.X+slop && x >= c.Min.X-slop
   342  }
   343  
   344  // ContainsY returns true if the Canvas contains the
   345  // y coordinate.
   346  func (c *Canvas) ContainsY(y vg.Length) bool {
   347  	return y <= c.Max.Y+slop && y >= c.Min.Y-slop
   348  }
   349  
   350  // X returns the value of x, given in the unit range,
   351  // in the drawing coordinates of this draw area.
   352  // A value of 0, for example, will return the minimum
   353  // x value of the draw area and a value of 1 will
   354  // return the maximum.
   355  func (c *Canvas) X(x float64) vg.Length {
   356  	return vg.Length(x)*(c.Max.X-c.Min.X) + c.Min.X
   357  }
   358  
   359  // Y returns the value of x, given in the unit range,
   360  // in the drawing coordinates of this draw area.
   361  // A value of 0, for example, will return the minimum
   362  // y value of the draw area and a value of 1 will
   363  // return the maximum.
   364  func (c *Canvas) Y(y float64) vg.Length {
   365  	return vg.Length(y)*(c.Max.Y-c.Min.Y) + c.Min.Y
   366  }
   367  
   368  // Crop returns a new Canvas corresponding to the Canvas
   369  // c with the given lengths added to the minimum
   370  // and maximum x and y values of the Canvas's Rectangle.
   371  // Note that cropping the right and top sides of the canvas
   372  // requires specifying negative values of right and top.
   373  func Crop(c Canvas, left, right, bottom, top vg.Length) Canvas {
   374  	minpt := vg.Point{
   375  		X: c.Min.X + left,
   376  		Y: c.Min.Y + bottom,
   377  	}
   378  	maxpt := vg.Point{
   379  		X: c.Max.X + right,
   380  		Y: c.Max.Y + top,
   381  	}
   382  	return Canvas{
   383  		Canvas:    c,
   384  		Rectangle: vg.Rectangle{Min: minpt, Max: maxpt},
   385  	}
   386  }
   387  
   388  // Tiles creates regular subcanvases from a Canvas.
   389  type Tiles struct {
   390  	// Cols and Rows specify the number of rows and columns of tiles.
   391  	Cols, Rows int
   392  	// PadTop, PadBottom, PadRight, and PadLeft specify the padding
   393  	// on the corresponding side of each tile.
   394  	PadTop, PadBottom, PadRight, PadLeft vg.Length
   395  	// PadX and PadY specify the padding between columns and rows
   396  	// of tiles respectively..
   397  	PadX, PadY vg.Length
   398  }
   399  
   400  // At returns the subcanvas within c that corresponds to the
   401  // tile at column x, row y.
   402  func (ts Tiles) At(c Canvas, x, y int) Canvas {
   403  	tileH := (c.Max.Y - c.Min.Y - ts.PadTop - ts.PadBottom -
   404  		vg.Length(ts.Rows-1)*ts.PadY) / vg.Length(ts.Rows)
   405  	tileW := (c.Max.X - c.Min.X - ts.PadLeft - ts.PadRight -
   406  		vg.Length(ts.Cols-1)*ts.PadX) / vg.Length(ts.Cols)
   407  
   408  	ymax := c.Max.Y - ts.PadTop - vg.Length(y)*(ts.PadY+tileH)
   409  	ymin := ymax - tileH
   410  	xmin := c.Min.X + ts.PadLeft + vg.Length(x)*(ts.PadX+tileW)
   411  	xmax := xmin + tileW
   412  
   413  	return Canvas{
   414  		Canvas: vg.Canvas(c),
   415  		Rectangle: vg.Rectangle{
   416  			Min: vg.Point{X: xmin, Y: ymin},
   417  			Max: vg.Point{X: xmax, Y: ymax},
   418  		},
   419  	}
   420  }
   421  
   422  // SetLineStyle sets the current line style
   423  func (c *Canvas) SetLineStyle(sty LineStyle) {
   424  	c.SetColor(sty.Color)
   425  	c.SetLineWidth(sty.Width)
   426  	c.SetLineDash(sty.Dashes, sty.DashOffs)
   427  }
   428  
   429  // StrokeLines draws a line connecting a set of points
   430  // in the given Canvas.
   431  func (c *Canvas) StrokeLines(sty LineStyle, lines ...[]vg.Point) {
   432  	if len(lines) == 0 {
   433  		return
   434  	}
   435  
   436  	c.SetLineStyle(sty)
   437  
   438  	for _, l := range lines {
   439  		if len(l) == 0 {
   440  			continue
   441  		}
   442  		p := make(vg.Path, 0, len(l))
   443  		p.Move(l[0])
   444  		for _, pt := range l[1:] {
   445  			p.Line(pt)
   446  		}
   447  		c.Stroke(p)
   448  	}
   449  }
   450  
   451  // StrokeLine2 draws a line between two points in the given
   452  // Canvas.
   453  func (c *Canvas) StrokeLine2(sty LineStyle, x0, y0, x1, y1 vg.Length) {
   454  	c.StrokeLines(sty, []vg.Point{{X: x0, Y: y0}, {X: x1, Y: y1}})
   455  }
   456  
   457  // ClipLinesXY returns a slice of lines that
   458  // represent the given line clipped in both
   459  // X and Y directions.
   460  func (c *Canvas) ClipLinesXY(lines ...[]vg.Point) [][]vg.Point {
   461  	return c.ClipLinesY(c.ClipLinesX(lines...)...)
   462  }
   463  
   464  // ClipLinesX returns a slice of lines that
   465  // represent the given line clipped in the
   466  // X direction.
   467  func (c *Canvas) ClipLinesX(lines ...[]vg.Point) (clipped [][]vg.Point) {
   468  	lines1 := make([][]vg.Point, 0, len(lines))
   469  	for _, line := range lines {
   470  		ls := clipLine(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0}, line)
   471  		lines1 = append(lines1, ls...)
   472  	}
   473  	clipped = make([][]vg.Point, 0, len(lines1))
   474  	for _, line := range lines1 {
   475  		ls := clipLine(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, line)
   476  		clipped = append(clipped, ls...)
   477  	}
   478  	return
   479  }
   480  
   481  // ClipLinesY returns a slice of lines that
   482  // represent the given line clipped in the
   483  // Y direction.
   484  func (c *Canvas) ClipLinesY(lines ...[]vg.Point) (clipped [][]vg.Point) {
   485  	lines1 := make([][]vg.Point, 0, len(lines))
   486  	for _, line := range lines {
   487  		ls := clipLine(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, line)
   488  		lines1 = append(lines1, ls...)
   489  	}
   490  	clipped = make([][]vg.Point, 0, len(lines1))
   491  	for _, line := range lines1 {
   492  		ls := clipLine(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1}, line)
   493  		clipped = append(clipped, ls...)
   494  	}
   495  	return
   496  }
   497  
   498  // clipLine performs clipping of a line by a single
   499  // clipping line specified by the norm, clip point,
   500  // and in function.
   501  func clipLine(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (lines [][]vg.Point) {
   502  	l := make([]vg.Point, 0, len(pts))
   503  	for i := 1; i < len(pts); i++ {
   504  		cur, next := pts[i-1], pts[i]
   505  		curIn, nextIn := in(cur, clip), in(next, clip)
   506  		switch {
   507  		case curIn && nextIn:
   508  			l = append(l, cur)
   509  
   510  		case curIn && !nextIn:
   511  			l = append(l, cur, isect(cur, next, clip, norm))
   512  			lines = append(lines, l)
   513  			l = []vg.Point{}
   514  
   515  		case !curIn && !nextIn:
   516  			// do nothing
   517  
   518  		default: // !curIn && nextIn
   519  			l = append(l, isect(cur, next, clip, norm))
   520  		}
   521  		if nextIn && i == len(pts)-1 {
   522  			l = append(l, next)
   523  		}
   524  	}
   525  	if len(l) > 1 {
   526  		lines = append(lines, l)
   527  	}
   528  	return
   529  }
   530  
   531  // FillPolygon fills a polygon with the given color.
   532  func (c *Canvas) FillPolygon(clr color.Color, pts []vg.Point) {
   533  	if len(pts) == 0 {
   534  		return
   535  	}
   536  
   537  	c.SetColor(clr)
   538  	p := make(vg.Path, 0, len(pts)+1)
   539  	p.Move(pts[0])
   540  	for _, pt := range pts[1:] {
   541  		p.Line(pt)
   542  	}
   543  	p.Close()
   544  	c.Fill(p)
   545  }
   546  
   547  // ClipPolygonXY returns a slice of lines that
   548  // represent the given polygon clipped in both
   549  // X and Y directions.
   550  func (c *Canvas) ClipPolygonXY(pts []vg.Point) []vg.Point {
   551  	return c.ClipPolygonY(c.ClipPolygonX(pts))
   552  }
   553  
   554  // ClipPolygonX returns a slice of lines that
   555  // represent the given polygon clipped in the
   556  // X direction.
   557  func (c *Canvas) ClipPolygonX(pts []vg.Point) []vg.Point {
   558  	return clipPoly(isLeft, vg.Point{X: c.Max.X, Y: c.Min.Y}, vg.Point{X: -1, Y: 0},
   559  		clipPoly(isRight, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 1, Y: 0}, pts))
   560  }
   561  
   562  // ClipPolygonY returns a slice of lines that
   563  // represent the given polygon clipped in the
   564  // Y direction.
   565  func (c *Canvas) ClipPolygonY(pts []vg.Point) []vg.Point {
   566  	return clipPoly(isBelow, vg.Point{X: c.Min.X, Y: c.Max.Y}, vg.Point{X: 0, Y: 1},
   567  		clipPoly(isAbove, vg.Point{X: c.Min.X, Y: c.Min.Y}, vg.Point{X: 0, Y: -1}, pts))
   568  }
   569  
   570  // clipPoly performs clipping of a polygon by a single
   571  // clipping line specified by the norm, clip point,
   572  // and in function.
   573  func clipPoly(in func(vg.Point, vg.Point) bool, clip, norm vg.Point, pts []vg.Point) (clipped []vg.Point) {
   574  	clipped = make([]vg.Point, 0, len(pts))
   575  	for i := 0; i < len(pts); i++ {
   576  		j := i + 1
   577  		if i == len(pts)-1 {
   578  			j = 0
   579  		}
   580  		cur, next := pts[i], pts[j]
   581  		curIn, nextIn := in(cur, clip), in(next, clip)
   582  		switch {
   583  		case curIn && nextIn:
   584  			clipped = append(clipped, cur)
   585  
   586  		case curIn && !nextIn:
   587  			clipped = append(clipped, cur, isect(cur, next, clip, norm))
   588  
   589  		case !curIn && !nextIn:
   590  			// do nothing
   591  
   592  		default: // !curIn && nextIn
   593  			clipped = append(clipped, isect(cur, next, clip, norm))
   594  		}
   595  	}
   596  	n := len(clipped)
   597  	return clipped[:n:n]
   598  }
   599  
   600  // slop is some slop for floating point equality
   601  const slop = 3e-8 // ≈ √1⁻¹⁵
   602  
   603  func isLeft(p, clip vg.Point) bool {
   604  	return p.X <= clip.X+slop
   605  }
   606  
   607  func isRight(p, clip vg.Point) bool {
   608  	return p.X >= clip.X-slop
   609  }
   610  
   611  func isBelow(p, clip vg.Point) bool {
   612  	return p.Y <= clip.Y+slop
   613  }
   614  
   615  func isAbove(p, clip vg.Point) bool {
   616  	return p.Y >= clip.Y-slop
   617  }
   618  
   619  // isect returns the intersection of a line p0→p1 with the
   620  // clipping line specified by the clip point and normal.
   621  func isect(p0, p1, clip, norm vg.Point) vg.Point {
   622  	// t = (norm · (p0 - clip)) / (norm · (p0 - p1))
   623  	t := p0.Sub(clip).Dot(norm) / p0.Sub(p1).Dot(norm)
   624  
   625  	// p = p0 + t*(p1 - p0)
   626  	return p1.Sub(p0).Scale(t).Add(p0)
   627  }
   628  
   629  // FillText fills lines of text in the draw area.
   630  // pt specifies the location where the text is to be drawn.
   631  func (c *Canvas) FillText(sty TextStyle, pt vg.Point, txt string) {
   632  	sty.Handler.Draw(c, txt, sty, pt)
   633  }
   634  

View as plain text