go-hep.org/x/hep@v0.38.1/hplot/s2d.go (about)

     1  // Copyright ©2016 The go-hep 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 hplot
     6  
     7  import (
     8  	"image/color"
     9  	"math"
    10  
    11  	"gonum.org/v1/plot"
    12  	"gonum.org/v1/plot/plotter"
    13  	"gonum.org/v1/plot/vg"
    14  	"gonum.org/v1/plot/vg/draw"
    15  )
    16  
    17  // S2D plots a set of 2-dim points with error bars.
    18  type S2D struct {
    19  	Data plotter.XYer
    20  
    21  	// GlyphStyle is the style of the glyphs drawn
    22  	// at each point.
    23  	draw.GlyphStyle
    24  
    25  	// LineStyle is the style of the line drawn
    26  	// connecting each point.
    27  	// Use zero width to disable.
    28  	LineStyle draw.LineStyle
    29  
    30  	// XErrs is the x error bars plotter.
    31  	XErrs *plotter.XErrorBars
    32  
    33  	// YErrs is the y error bars plotter.
    34  	YErrs *plotter.YErrorBars
    35  
    36  	// Band displays a colored band between the y-min and y-max error bars.
    37  	Band *Band
    38  
    39  	// Steps controls the style of the connecting
    40  	// line (NoSteps, HiSteps, etc...)
    41  	Steps StepsKind
    42  }
    43  
    44  // withXErrBars enables the X error bars
    45  func (pts *S2D) withXErrBars() error {
    46  	xerr, ok := pts.Data.(plotter.XErrorer)
    47  	if !ok {
    48  		return nil
    49  	}
    50  
    51  	type xerrT struct {
    52  		plotter.XYer
    53  		plotter.XErrorer
    54  	}
    55  	xplt, err := plotter.NewXErrorBars(xerrT{pts.Data, xerr})
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	pts.XErrs = xplt
    61  	return nil
    62  }
    63  
    64  // withYErrBars enables the Y error bars
    65  func (pts *S2D) withYErrBars() error {
    66  	yerr, ok := pts.Data.(plotter.YErrorer)
    67  	if !ok {
    68  		return nil
    69  	}
    70  
    71  	type yerrT struct {
    72  		plotter.XYer
    73  		plotter.YErrorer
    74  	}
    75  	yplt, err := plotter.NewYErrorBars(yerrT{pts.Data, yerr})
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	pts.YErrs = yplt
    81  	return nil
    82  }
    83  
    84  // withBand enables the band between ymin-ymax error bars.
    85  func (pts *S2D) withBand() error {
    86  	yerr, ok := pts.Data.(plotter.YErrorer)
    87  	if !ok {
    88  		return nil
    89  	}
    90  
    91  	var (
    92  		top plotter.XYs
    93  		bot plotter.XYs
    94  	)
    95  
    96  	switch pts.Steps {
    97  
    98  	case NoSteps:
    99  		top = make(plotter.XYs, pts.Data.Len())
   100  		bot = make(plotter.XYs, pts.Data.Len())
   101  		for i := range top {
   102  			x, y := pts.Data.XY(i)
   103  			ymin, ymax := yerr.YError(i)
   104  			top[i].X = x
   105  			top[i].Y = y + math.Abs(ymax)
   106  			bot[i].X = x
   107  			bot[i].Y = y - math.Abs(ymin)
   108  		}
   109  
   110  	case HiSteps:
   111  		top = make(plotter.XYs, 2*pts.Data.Len())
   112  		bot = make(plotter.XYs, 2*pts.Data.Len())
   113  		xerr := pts.Data.(plotter.XErrorer)
   114  		for i := range top {
   115  			idata := i / 2
   116  			x, y := pts.Data.XY(idata)
   117  			xmin, xmax := xerr.XError(idata)
   118  			ymin, ymax := yerr.YError(idata)
   119  			switch {
   120  			case i%2 != 0:
   121  				top[i].X = x + math.Abs(xmax)
   122  				top[i].Y = y + math.Abs(ymax)
   123  				bot[i].X = x + math.Abs(xmax)
   124  				bot[i].Y = y - math.Abs(ymin)
   125  			default:
   126  				top[i].X = x - math.Abs(xmin)
   127  				top[i].Y = y + math.Abs(ymax)
   128  				bot[i].X = x - math.Abs(xmin)
   129  				bot[i].Y = y - math.Abs(ymin)
   130  			}
   131  		}
   132  	case PreSteps:
   133  		panic("presteps not implemented")
   134  	case MidSteps:
   135  		panic("midsteps not implemented")
   136  	case PostSteps:
   137  		panic("poststeps not implemented")
   138  	}
   139  	pts.Band = NewBand(color.Gray{200}, top, bot)
   140  	return nil
   141  }
   142  
   143  // NewS2D creates a 2-dim scatter plot from a XYer.
   144  func NewS2D(data plotter.XYer, opts ...Options) *S2D {
   145  	s := &S2D{
   146  		Data:       data,
   147  		GlyphStyle: plotter.DefaultGlyphStyle,
   148  	}
   149  	s.GlyphStyle.Shape = draw.CrossGlyph{}
   150  
   151  	cfg := newConfig(opts)
   152  
   153  	s.Steps = cfg.steps
   154  
   155  	if cfg.bars.xerrs {
   156  		_ = s.withXErrBars()
   157  	}
   158  
   159  	if cfg.bars.yerrs {
   160  		_ = s.withYErrBars()
   161  	}
   162  
   163  	if cfg.band {
   164  		_ = s.withBand()
   165  	}
   166  
   167  	if cfg.glyph != (draw.GlyphStyle{}) {
   168  		s.GlyphStyle = cfg.glyph
   169  	}
   170  
   171  	switch s.Steps {
   172  	case HiSteps:
   173  		// check we have ErrX for all data points.
   174  		xerrs := s.Data.(plotter.XErrorer)
   175  		for i := range s.Data.Len() {
   176  			xmin, xmax := xerrs.XError(i)
   177  			if xmin == 0 && xmax == 0 {
   178  				panic("s2d with HiSteps needs XErr informations for all points")
   179  			}
   180  		}
   181  	}
   182  
   183  	return s
   184  }
   185  
   186  // Plot draws the Scatter, implementing the plot.Plotter
   187  // interface.
   188  func (pts *S2D) Plot(c draw.Canvas, plt *plot.Plot) {
   189  	trX, trY := plt.Transforms(&c)
   190  	if pts.Band != nil {
   191  		pts.Band.Plot(c, plt)
   192  	}
   193  
   194  	for i := range pts.Data.Len() {
   195  		x, y := pts.Data.XY(i)
   196  		c.DrawGlyph(pts.GlyphStyle, vg.Point{X: trX(x), Y: trY(y)})
   197  	}
   198  
   199  	if pts.LineStyle.Width > 0 {
   200  
   201  		data, err := plotter.CopyXYs(pts.Data)
   202  		if err != nil {
   203  			panic(err)
   204  		}
   205  
   206  		switch pts.Steps {
   207  		case HiSteps:
   208  			xerr := pts.Data.(plotter.XErrorer)
   209  			dsteps := make(plotter.XYs, 0, 2*len(data))
   210  			for i, d := range data {
   211  				xmin, xmax := xerr.XError(i)
   212  				dsteps = append(dsteps, plotter.XY{X: d.X - xmin, Y: d.Y})
   213  				dsteps = append(dsteps, plotter.XY{X: d.X + xmax, Y: d.Y})
   214  			}
   215  			data = dsteps
   216  		case PreSteps:
   217  			var (
   218  				prev   plotter.XY
   219  				dsteps = make(plotter.XYs, 0, 2*len(data))
   220  			)
   221  			prev.X, prev.Y = data.XY(0)
   222  			dsteps = append(dsteps, prev)
   223  			for _, pt := range data[1:] {
   224  				dsteps = append(dsteps, plotter.XY{X: prev.X, Y: pt.Y})
   225  				dsteps = append(dsteps, pt)
   226  				prev = pt
   227  			}
   228  			data = dsteps
   229  		case MidSteps:
   230  			var (
   231  				prev   plotter.XY
   232  				dsteps = make(plotter.XYs, 0, 2*len(data))
   233  			)
   234  			prev.X, prev.Y = data.XY(0)
   235  			dsteps = append(dsteps, prev)
   236  			for _, pt := range data[1:] {
   237  				dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: prev.Y})
   238  				dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: pt.Y})
   239  				dsteps = append(dsteps, pt)
   240  				prev = pt
   241  			}
   242  			data = dsteps
   243  		case PostSteps:
   244  			var (
   245  				prev   plotter.XY
   246  				dsteps = make(plotter.XYs, 0, 2*len(data))
   247  			)
   248  			prev.X, prev.Y = data.XY(0)
   249  			dsteps = append(dsteps, prev)
   250  			for _, pt := range data[1:] {
   251  				dsteps = append(dsteps, plotter.XY{X: pt.X, Y: prev.Y})
   252  				dsteps = append(dsteps, pt)
   253  				prev = pt
   254  			}
   255  			data = dsteps
   256  		case NoSteps:
   257  			// ok.
   258  		}
   259  
   260  		line := plotter.Line{
   261  			XYs:       data,
   262  			LineStyle: pts.LineStyle,
   263  		}
   264  		line.Plot(c, plt)
   265  	}
   266  
   267  	if pts.XErrs != nil {
   268  		pts.XErrs.LineStyle.Color = pts.GlyphStyle.Color
   269  		pts.XErrs.Plot(c, plt)
   270  	}
   271  	if pts.YErrs != nil {
   272  		pts.YErrs.LineStyle.Color = pts.GlyphStyle.Color
   273  		pts.YErrs.Plot(c, plt)
   274  	}
   275  }
   276  
   277  // DataRange returns the minimum and maximum
   278  // x and y values, implementing the plot.DataRanger
   279  // interface.
   280  func (pts *S2D) DataRange() (xmin, xmax, ymin, ymax float64) {
   281  	if dr, ok := pts.Data.(plot.DataRanger); ok {
   282  		xmin, xmax, ymin, ymax = dr.DataRange()
   283  	} else {
   284  		xmin, xmax, ymin, ymax = plotter.XYRange(pts.Data)
   285  	}
   286  
   287  	if pts.XErrs != nil {
   288  		xmin1, xmax1, ymin1, ymax1 := pts.XErrs.DataRange()
   289  		xmin = math.Min(xmin1, xmin)
   290  		xmax = math.Max(xmax1, xmax)
   291  		ymin = math.Min(ymin1, ymin)
   292  		ymax = math.Max(ymax1, ymax)
   293  	}
   294  
   295  	if pts.YErrs != nil {
   296  		xmin1, xmax1, ymin1, ymax1 := pts.YErrs.DataRange()
   297  		xmin = math.Min(xmin1, xmin)
   298  		xmax = math.Max(xmax1, xmax)
   299  		ymin = math.Min(ymin1, ymin)
   300  		ymax = math.Max(ymax1, ymax)
   301  	}
   302  
   303  	return xmin, xmax, ymin, ymax
   304  }
   305  
   306  // GlyphBoxes returns a slice of plot.GlyphBoxes,
   307  // implementing the plot.GlyphBoxer interface.
   308  func (pts *S2D) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
   309  	bs := make([]plot.GlyphBox, pts.Data.Len())
   310  	for i := range pts.Data.Len() {
   311  		x, y := pts.Data.XY(i)
   312  		bs[i].X = plt.X.Norm(x)
   313  		bs[i].Y = plt.Y.Norm(y)
   314  		bs[i].Rectangle = pts.GlyphStyle.Rectangle()
   315  	}
   316  	if pts.XErrs != nil {
   317  		bs = append(bs, pts.XErrs.GlyphBoxes(plt)...)
   318  	}
   319  	if pts.YErrs != nil {
   320  		bs = append(bs, pts.YErrs.GlyphBoxes(plt)...)
   321  	}
   322  	return bs
   323  }
   324  
   325  // Thumbnail the thumbnail for the Scatter,
   326  // implementing the plot.Thumbnailer interface.
   327  func (pts *S2D) Thumbnail(c *draw.Canvas) {
   328  	ymin := c.Min.Y
   329  	ymax := c.Max.Y
   330  	xmin := c.Min.X
   331  	xmax := c.Max.X
   332  
   333  	if pts.Band != nil {
   334  		box := []vg.Point{
   335  			{X: xmin, Y: ymin},
   336  			{X: xmax, Y: ymin},
   337  			{X: xmax, Y: ymax},
   338  			{X: xmin, Y: ymax},
   339  			{X: xmin, Y: ymin},
   340  		}
   341  		c.FillPolygon(pts.Band.FillColor, c.ClipPolygonXY(box))
   342  	}
   343  
   344  	if pts.LineStyle.Width != 0 {
   345  		ymid := c.Center().Y
   346  		line := []vg.Point{{X: xmin, Y: ymid}, {X: xmax, Y: ymid}}
   347  		c.StrokeLines(pts.LineStyle, c.ClipLinesX(line)...)
   348  
   349  	}
   350  
   351  	if pts.GlyphStyle != (draw.GlyphStyle{}) {
   352  		c.DrawGlyph(pts.GlyphStyle, c.Center())
   353  		if pts.YErrs != nil {
   354  			var (
   355  				yerrs = pts.YErrs
   356  				vsize = 0.5 * ((ymax - ymin) * 0.95)
   357  				x     = c.Center().X
   358  				ylo   = c.Center().Y - vsize
   359  				yup   = c.Center().Y + vsize
   360  				xylo  = vg.Point{X: x, Y: ylo}
   361  				xyup  = vg.Point{X: x, Y: yup}
   362  				line  = []vg.Point{xylo, xyup}
   363  				bar   = c.ClipLinesY(line)
   364  			)
   365  			c.StrokeLines(yerrs.LineStyle, bar...)
   366  			for _, pt := range []vg.Point{xylo, xyup} {
   367  				if c.Contains(pt) {
   368  					c.StrokeLine2(yerrs.LineStyle,
   369  						pt.X-yerrs.CapWidth/2,
   370  						pt.Y,
   371  						pt.X+yerrs.CapWidth/2,
   372  						pt.Y,
   373  					)
   374  				}
   375  			}
   376  		}
   377  	}
   378  }