...

Source file src/gonum.org/v1/plot/plotter/field.go

Documentation: gonum.org/v1/plot/plotter

     1  // Copyright ©2019 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 plotter
     6  
     7  import (
     8  	"math"
     9  
    10  	"gonum.org/v1/plot"
    11  	"gonum.org/v1/plot/vg"
    12  	"gonum.org/v1/plot/vg/draw"
    13  )
    14  
    15  // FieldXY describes a two dimensional vector field where the
    16  // X and Y coordinates are arranged on a rectangular grid.
    17  type FieldXY interface {
    18  	// Dims returns the dimensions of the grid.
    19  	Dims() (c, r int)
    20  
    21  	// Vector returns the value of a vector field at (c, r).
    22  	// It will panic if c or r are out of bounds for the field.
    23  	Vector(c, r int) XY
    24  
    25  	// X returns the coordinate for the column at the index c.
    26  	// It will panic if c is out of bounds for the grid.
    27  	X(c int) float64
    28  
    29  	// Y returns the coordinate for the row at the index r.
    30  	// It will panic if r is out of bounds for the grid.
    31  	Y(r int) float64
    32  }
    33  
    34  // Field implements the Plotter interface, drawing
    35  // a vector field of the values in the FieldXY field.
    36  type Field struct {
    37  	FieldXY FieldXY
    38  
    39  	// DrawGlyph is the user hook to draw a field
    40  	// vector glyph. The function should draw a unit
    41  	// vector to (1, 0) on the vg.Canvas, c with the
    42  	// sty LineStyle. The Field plotter will rotate
    43  	// and scale the unit vector appropriately.
    44  	// If the magnitude of v is zero, no scaling or
    45  	// rotation is performed.
    46  	//
    47  	// The direction and magnitude of v can be used
    48  	// to determine properties of the glyph drawing
    49  	// but should not be used to determine size or
    50  	// directions of the glyph.
    51  	//
    52  	// If DrawGlyph is nil, a simple arrow will be
    53  	// drawn.
    54  	DrawGlyph func(c vg.Canvas, sty draw.LineStyle, v XY)
    55  
    56  	// LineStyle is the style of the line used to
    57  	// render vectors when DrawGlyph is nil.
    58  	// Otherwise it is passed to DrawGlyph.
    59  	LineStyle draw.LineStyle
    60  
    61  	// max define the dynamic range of the field.
    62  	max float64
    63  }
    64  
    65  // NewField creates a new vector field plotter.
    66  func NewField(f FieldXY) *Field {
    67  	max := math.Inf(-1)
    68  	c, r := f.Dims()
    69  	for i := 0; i < c; i++ {
    70  		for j := 0; j < r; j++ {
    71  			v := f.Vector(i, j)
    72  			d := math.Hypot(v.X, v.Y)
    73  			if math.IsNaN(d) {
    74  				continue
    75  			}
    76  			max = math.Max(max, d)
    77  		}
    78  	}
    79  
    80  	return &Field{
    81  		FieldXY:   f,
    82  		LineStyle: DefaultLineStyle,
    83  		max:       max,
    84  	}
    85  }
    86  
    87  // Plot implements the Plot method of the plot.Plotter interface.
    88  func (f *Field) Plot(c draw.Canvas, plt *plot.Plot) {
    89  	c.Push()
    90  	defer c.Pop()
    91  	c.SetLineStyle(f.LineStyle)
    92  
    93  	trX, trY := plt.Transforms(&c)
    94  
    95  	cols, rows := f.FieldXY.Dims()
    96  	for i := 0; i < cols; i++ {
    97  		var right, left float64
    98  		switch i {
    99  		case 0:
   100  			if cols == 1 {
   101  				right = 0.5
   102  			} else {
   103  				right = (f.FieldXY.X(1) - f.FieldXY.X(0)) / 2
   104  			}
   105  			left = -right
   106  		case cols - 1:
   107  			right = (f.FieldXY.X(cols-1) - f.FieldXY.X(cols-2)) / 2
   108  			left = -right
   109  		default:
   110  			right = (f.FieldXY.X(i+1) - f.FieldXY.X(i)) / 2
   111  			left = -(f.FieldXY.X(i) - f.FieldXY.X(i-1)) / 2
   112  		}
   113  
   114  		for j := 0; j < rows; j++ {
   115  			var up, down float64
   116  			switch j {
   117  			case 0:
   118  				if rows == 1 {
   119  					up = 0.5
   120  				} else {
   121  					up = (f.FieldXY.Y(1) - f.FieldXY.Y(0)) / 2
   122  				}
   123  				down = -up
   124  			case rows - 1:
   125  				up = (f.FieldXY.Y(rows-1) - f.FieldXY.Y(rows-2)) / 2
   126  				down = -up
   127  			default:
   128  				up = (f.FieldXY.Y(j+1) - f.FieldXY.Y(j)) / 2
   129  				down = -(f.FieldXY.Y(j) - f.FieldXY.Y(j-1)) / 2
   130  			}
   131  
   132  			x, y := trX(f.FieldXY.X(i)+left), trY(f.FieldXY.Y(j)+down)
   133  			dx, dy := trX(f.FieldXY.X(i)+right), trY(f.FieldXY.Y(j)+up)
   134  
   135  			if !c.Contains(vg.Point{X: x, Y: y}) || !c.Contains(vg.Point{X: dx, Y: dy}) {
   136  				continue
   137  			}
   138  
   139  			c.Push()
   140  			c.Translate(vg.Point{X: (x + dx) / 2, Y: (y + dy) / 2})
   141  
   142  			v := f.FieldXY.Vector(i, j)
   143  			s := math.Hypot(v.X, v.Y) / (2 * f.max)
   144  			// Do not scale when the vector is zero, otherwise the
   145  			// user cannot render special-case glyphs for that case.
   146  			if s != 0 {
   147  				c.Rotate(math.Atan2(v.Y, v.X))
   148  				c.Scale(s*float64(dx-x), s*float64(dy-y))
   149  			}
   150  			v.X /= f.max
   151  			v.Y /= f.max
   152  
   153  			if f.DrawGlyph == nil {
   154  				drawVector(c, v)
   155  			} else {
   156  				f.DrawGlyph(c, f.LineStyle, v)
   157  			}
   158  			c.Pop()
   159  		}
   160  	}
   161  }
   162  
   163  func drawVector(c vg.Canvas, v XY) {
   164  	if math.Hypot(v.X, v.Y) == 0 {
   165  		return
   166  	}
   167  	// TODO(kortschak): Improve this arrow.
   168  	var pa vg.Path
   169  	pa.Move(vg.Point{})
   170  	pa.Line(vg.Point{X: 1, Y: 0})
   171  	pa.Close()
   172  	c.Stroke(pa)
   173  }
   174  
   175  // DataRange implements the DataRange method
   176  // of the plot.DataRanger interface.
   177  func (f *Field) DataRange() (xmin, xmax, ymin, ymax float64) {
   178  	c, r := f.FieldXY.Dims()
   179  	switch c {
   180  	case 1: // Make a unit length when there is no neighbour.
   181  		xmax = f.FieldXY.X(0) + 0.5
   182  		xmin = f.FieldXY.X(0) - 0.5
   183  	default:
   184  		xmax = f.FieldXY.X(c-1) + (f.FieldXY.X(c-1)-f.FieldXY.X(c-2))/2
   185  		xmin = f.FieldXY.X(0) - (f.FieldXY.X(1)-f.FieldXY.X(0))/2
   186  	}
   187  	switch r {
   188  	case 1: // Make a unit length when there is no neighbour.
   189  		ymax = f.FieldXY.Y(0) + 0.5
   190  		ymin = f.FieldXY.Y(0) - 0.5
   191  	default:
   192  		ymax = f.FieldXY.Y(r-1) + (f.FieldXY.Y(r-1)-f.FieldXY.Y(r-2))/2
   193  		ymin = f.FieldXY.Y(0) - (f.FieldXY.Y(1)-f.FieldXY.Y(0))/2
   194  	}
   195  	return xmin, xmax, ymin, ymax
   196  }
   197  
   198  // GlyphBoxes implements the GlyphBoxes method
   199  // of the plot.GlyphBoxer interface.
   200  func (f *Field) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
   201  	c, r := f.FieldXY.Dims()
   202  	b := make([]plot.GlyphBox, 0, r*c)
   203  	for i := 0; i < c; i++ {
   204  		for j := 0; j < r; j++ {
   205  			b = append(b, plot.GlyphBox{
   206  				X: plt.X.Norm(f.FieldXY.X(i)),
   207  				Y: plt.Y.Norm(f.FieldXY.Y(j)),
   208  				Rectangle: vg.Rectangle{
   209  					Min: vg.Point{X: -5, Y: -5},
   210  					Max: vg.Point{X: +5, Y: +5},
   211  				},
   212  			})
   213  		}
   214  	}
   215  	return b
   216  }
   217  

View as plain text