go-hep.org/x/hep@v0.38.1/hplot/h1d.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  	"errors"
     9  	"fmt"
    10  	"image/color"
    11  	"math"
    12  
    13  	"go-hep.org/x/hep/hbook"
    14  	"gonum.org/v1/plot"
    15  	"gonum.org/v1/plot/font"
    16  	"gonum.org/v1/plot/plotter"
    17  	"gonum.org/v1/plot/text"
    18  	"gonum.org/v1/plot/vg"
    19  	"gonum.org/v1/plot/vg/draw"
    20  )
    21  
    22  // H1D implements the plotter.Plotter interface,
    23  // drawing a histogram of the data.
    24  type H1D struct {
    25  	// Hist is the histogramming data
    26  	Hist *hbook.H1D
    27  
    28  	// FillColor is the color used to fill each
    29  	// bar of the histogram.  If the color is nil
    30  	// then the bars are not filled.
    31  	FillColor color.Color
    32  
    33  	// LineStyle is the style of the outline of each
    34  	// bar of the histogram.
    35  	draw.LineStyle
    36  
    37  	// GlyphStyle is the style of the glyphs drawn
    38  	// at the top of each histogram bar.
    39  	GlyphStyle draw.GlyphStyle
    40  
    41  	// LogY allows rendering with a log-scaled Y axis.
    42  	// When enabled, histogram bins with no entries will be discarded from
    43  	// the histogram's DataRange.
    44  	// The lowest Y value for the DataRange will be corrected to leave an
    45  	// arbitrary amount of height for the smallest bin entry so it is visible
    46  	// on the final plot.
    47  	LogY bool
    48  
    49  	// InfoStyle is the style of infos displayed for
    50  	// the histogram (entries, mean, rms).
    51  	Infos HInfos
    52  
    53  	// YErrs is the y error bars plotter.
    54  	YErrs *plotter.YErrorBars
    55  
    56  	// Band displays a colored band between the y-min and y-max error bars.
    57  	// The band is shown in the legend thumbnail only if there is no filling.
    58  	Band *BinnedErrBand
    59  }
    60  
    61  type HInfoStyle uint32
    62  
    63  const (
    64  	HInfoNone    HInfoStyle = 0
    65  	HInfoEntries HInfoStyle = 1 << iota
    66  	HInfoMean
    67  	HInfoRMS
    68  	HInfoStdDev
    69  	HInfoSummary HInfoStyle = HInfoEntries | HInfoMean | HInfoStdDev
    70  )
    71  
    72  type HInfos struct {
    73  	Style HInfoStyle
    74  }
    75  
    76  // NewH1FromXYer returns a new histogram
    77  // that represents the distribution of values
    78  // using the given number of bins.
    79  //
    80  // Each y value is assumed to be the frequency
    81  // count for the corresponding x.
    82  //
    83  // It panics if the number of bins is non-positive.
    84  func NewH1FromXYer(xy plotter.XYer, n int, opts ...Options) *H1D {
    85  	if n <= 0 {
    86  		panic(errors.New("hplot: histogram with non-positive number of bins"))
    87  	}
    88  	h := newHistFromXYer(xy, n)
    89  	return NewH1D(h, opts...)
    90  }
    91  
    92  // NewH1FromValuer returns a new histogram, as in
    93  // NewH1FromXYer, except that it accepts a plotter.Valuer
    94  // instead of an XYer.
    95  func NewH1FromValuer(vs plotter.Valuer, n int, opts ...Options) *H1D {
    96  	return NewH1FromXYer(unitYs{vs}, n, opts...)
    97  }
    98  
    99  type unitYs struct {
   100  	plotter.Valuer
   101  }
   102  
   103  func (u unitYs) XY(i int) (float64, float64) {
   104  	return u.Value(i), 1.0
   105  }
   106  
   107  // NewH1D returns a new histogram, as in
   108  // NewH1DFromXYer, except that it accepts a hbook.H1D
   109  // instead of a plotter.XYer
   110  func NewH1D(h *hbook.H1D, opts ...Options) *H1D {
   111  	h1 := &H1D{
   112  		Hist:      h,
   113  		LineStyle: plotter.DefaultLineStyle,
   114  	}
   115  
   116  	cfg := newConfig(opts)
   117  
   118  	h1.LogY = cfg.log.y
   119  	h1.Infos = cfg.hinfos
   120  
   121  	if cfg.band {
   122  		h1.Band = h1.withBand()
   123  	}
   124  
   125  	if cfg.bars.yerrs {
   126  		h1.YErrs = h1.withYErrBars(nil)
   127  	}
   128  
   129  	if cfg.glyph != (draw.GlyphStyle{}) {
   130  		h1.GlyphStyle = cfg.glyph
   131  	}
   132  
   133  	return h1
   134  }
   135  
   136  // withYErrBars enables the Y error bars
   137  func (h *H1D) withYErrBars(yoffs []float64) *plotter.YErrorBars {
   138  	bins := h.Hist.Binning.Bins
   139  	if yoffs == nil {
   140  		yoffs = make([]float64, len(bins))
   141  	}
   142  	data := make(plotter.XYs, 0, len(bins))
   143  	yerr := make(plotter.YErrors, 0, len(bins))
   144  	for i, bin := range bins {
   145  		if bin.Entries() == 0 {
   146  			continue
   147  		}
   148  		data = append(data, plotter.XY{
   149  			X: bin.XMid(),
   150  			Y: yoffs[i] + bin.SumW(),
   151  		})
   152  		ey := 0.5 * bin.ErrW()
   153  		yerr = append(yerr, struct{ Low, High float64 }{ey, ey})
   154  	}
   155  
   156  	type yerrT struct {
   157  		plotter.XYer
   158  		plotter.YErrorer
   159  	}
   160  
   161  	yplt, err := plotter.NewYErrorBars(yerrT{data, yerr})
   162  	if err != nil {
   163  		panic(err)
   164  	}
   165  	yplt.LineStyle.Color = h.LineStyle.Color
   166  	yplt.LineStyle.Width = h.LineStyle.Width
   167  
   168  	return yplt
   169  }
   170  
   171  // withBand enables the band between ymin-ymax error bars.
   172  func (h1 *H1D) withBand() *BinnedErrBand {
   173  	b := NewBinnedErrBand(h1.Hist.Counts())
   174  	b.FillColor = color.Gray{200}
   175  	b.LogY = h1.LogY
   176  	return b
   177  }
   178  
   179  // DataRange returns the minimum and maximum X and Y values
   180  func (h *H1D) DataRange() (xmin, xmax, ymin, ymax float64) {
   181  
   182  	if !h.LogY {
   183  		xmin, xmax, ymin, ymax = h.Hist.DataRange()
   184  		if h.YErrs != nil {
   185  			xmin1, xmax1, ymin1, ymax1 := h.YErrs.DataRange()
   186  			xmin = math.Min(xmin, xmin1)
   187  			ymin = math.Min(ymin, ymin1)
   188  			xmax = math.Max(xmax, xmax1)
   189  			ymax = math.Max(ymax, ymax1)
   190  		}
   191  		if h.Band != nil {
   192  			xmin1, xmax1, ymin1, ymax1 := h.Band.DataRange()
   193  			xmin = math.Min(xmin, xmin1)
   194  			ymin = math.Min(ymin, ymin1)
   195  			xmax = math.Max(xmax, xmax1)
   196  			ymax = math.Max(ymax, ymax1)
   197  		}
   198  		return
   199  	}
   200  
   201  	xmin = math.Inf(+1)
   202  	xmax = math.Inf(-1)
   203  	ymin = math.Inf(+1)
   204  	ymax = math.Inf(-1)
   205  	ylow := math.Inf(+1) // ylow will hold the smallest positive y value.
   206  	for _, bin := range h.Hist.Binning.Bins {
   207  		xmax = math.Max(bin.XMax(), xmax)
   208  		xmin = math.Min(bin.XMin(), xmin)
   209  		ymax = math.Max(bin.SumW(), ymax)
   210  		ymin = math.Min(bin.SumW(), ymin)
   211  		if bin.SumW() > 0 {
   212  			ylow = math.Min(bin.SumW(), ylow)
   213  		}
   214  	}
   215  
   216  	if ymin == 0 && !math.IsInf(ylow, +1) {
   217  		// Reserve a bit of space for the smallest bin to be displayed still.
   218  		ymin = ylow * 0.5
   219  	}
   220  
   221  	if h.YErrs != nil {
   222  		xmin1, xmax1, ymin1, ymax1 := h.YErrs.DataRange()
   223  		xmin = math.Min(xmin, xmin1)
   224  		ymin = math.Min(ymin, ymin1)
   225  		xmax = math.Max(xmax, xmax1)
   226  		ymax = math.Min(ymax, ymax1)
   227  	}
   228  
   229  	if h.Band != nil {
   230  		xmin1, xmax1, ymin1, ymax1 := h.Band.DataRange()
   231  		xmin = math.Min(xmin, xmin1)
   232  		ymin = math.Min(ymin, ymin1)
   233  		xmax = math.Max(xmax, xmax1)
   234  		ymax = math.Max(ymax, ymax1)
   235  	}
   236  
   237  	return
   238  }
   239  
   240  // Plot implements the Plotter interface, drawing a line
   241  // that connects each point in the Line.
   242  func (h *H1D) Plot(c draw.Canvas, p *plot.Plot) {
   243  	trX, trY := p.Transforms(&c)
   244  	var pts []vg.Point
   245  	hist := h.Hist
   246  	bins := h.Hist.Binning.Bins
   247  	nbins := len(bins)
   248  
   249  	yfct := func(sumw float64) (ymin, ymax vg.Length) {
   250  		return trY(0), trY(sumw)
   251  	}
   252  	if h.LogY {
   253  		yfct = func(sumw float64) (ymin, ymax vg.Length) {
   254  			ymin = c.Min.Y
   255  			ymax = c.Min.Y
   256  			if sumw != 0 {
   257  				ymax = trY(sumw)
   258  			}
   259  			return ymin, ymax
   260  		}
   261  	}
   262  
   263  	var glyphs []vg.Point
   264  
   265  	for i, bin := range bins {
   266  		xmin := trX(bin.XMin())
   267  		xmax := trX(bin.XMax())
   268  		sumw := bin.SumW()
   269  		ymin, ymax := yfct(sumw)
   270  		switch i {
   271  		case 0:
   272  			pts = append(pts, vg.Point{X: xmin, Y: ymin})
   273  			pts = append(pts, vg.Point{X: xmin, Y: ymax})
   274  			pts = append(pts, vg.Point{X: xmax, Y: ymax})
   275  
   276  		case nbins - 1:
   277  			lft := bins[i-1]
   278  			xlft := trX(lft.XMax())
   279  			_, ylft := yfct(lft.SumW())
   280  			pts = append(pts, vg.Point{X: xlft, Y: ylft})
   281  			pts = append(pts, vg.Point{X: xmin, Y: ymax})
   282  			pts = append(pts, vg.Point{X: xmax, Y: ymax})
   283  			pts = append(pts, vg.Point{X: xmax, Y: ymin})
   284  
   285  		default:
   286  			lft := bins[i-1]
   287  			xlft := trX(lft.XMax())
   288  			_, ylft := yfct(lft.SumW())
   289  			pts = append(pts, vg.Point{X: xlft, Y: ylft})
   290  			pts = append(pts, vg.Point{X: xmin, Y: ymax})
   291  			pts = append(pts, vg.Point{X: xmax, Y: ymax})
   292  		}
   293  
   294  		if h.GlyphStyle.Radius != 0 {
   295  			x := trX(bin.XMid())
   296  			_, y := yfct(bin.SumW())
   297  			// capture glyph location, to be drawn after
   298  			// the histogram line, if any.
   299  			glyphs = append(glyphs, vg.Point{X: x, Y: y})
   300  		}
   301  	}
   302  
   303  	if h.FillColor != nil {
   304  		c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
   305  	}
   306  
   307  	if h.Band != nil {
   308  		h.Band.Plot(c, p)
   309  	}
   310  
   311  	c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
   312  
   313  	if h.YErrs != nil {
   314  		h.YErrs.Plot(c, p)
   315  	}
   316  
   317  	if h.GlyphStyle.Radius != 0 {
   318  		for _, glyph := range glyphs {
   319  			c.DrawGlyph(h.GlyphStyle, glyph)
   320  		}
   321  	}
   322  
   323  	if h.Infos.Style != HInfoNone {
   324  		fnt := font.From(DefaultStyle.Fonts.Tick, DefaultStyle.Fonts.Tick.Size)
   325  		sty := text.Style{
   326  			Font:    fnt,
   327  			Handler: p.Title.TextStyle.Handler,
   328  		}
   329  		legend := histLegend{
   330  			ColWidth:  DefaultStyle.Fonts.Tick.Size,
   331  			TextStyle: sty,
   332  		}
   333  
   334  		for i := uint32(0); i < 32; i++ {
   335  			switch h.Infos.Style & (1 << i) {
   336  			case HInfoEntries:
   337  				legend.Add("Entries", hist.Entries())
   338  			case HInfoMean:
   339  				legend.Add("Mean", hist.XMean())
   340  			case HInfoRMS:
   341  				legend.Add("RMS", hist.XRMS())
   342  			case HInfoStdDev:
   343  				legend.Add("Std Dev", hist.XStdDev())
   344  			default:
   345  			}
   346  		}
   347  		legend.Top = true
   348  
   349  		legend.draw(c)
   350  	}
   351  }
   352  
   353  // GlyphBoxes returns a slice of GlyphBoxes,
   354  // one for each of the bins, implementing the
   355  // plot.GlyphBoxer interface.
   356  func (h *H1D) GlyphBoxes(p *plot.Plot) []plot.GlyphBox {
   357  	bins := h.Hist.Binning.Bins
   358  	bs := make([]plot.GlyphBox, 0, len(bins))
   359  	for i := range bins {
   360  		bin := bins[i]
   361  		y := bin.SumW()
   362  		if h.LogY && y == 0 {
   363  			continue
   364  		}
   365  		var box plot.GlyphBox
   366  		xmin := bin.XMin()
   367  		w := p.X.Norm(bin.XWidth())
   368  		box.X = p.X.Norm(xmin + 0.5*w)
   369  		box.Y = p.Y.Norm(y)
   370  		box.Rectangle.Min.X = vg.Length(xmin - 0.5*w)
   371  		box.Rectangle.Min.Y = vg.Length(y - 0.5*w)
   372  		box.Rectangle.Max.X = vg.Length(w)
   373  		box.Rectangle.Max.Y = vg.Length(0)
   374  
   375  		r := vg.Points(5)
   376  		box.Rectangle.Min = vg.Point{X: 0, Y: 0}
   377  		box.Rectangle.Max = vg.Point{X: 0, Y: r}
   378  		bs = append(bs, box)
   379  	}
   380  	return bs
   381  }
   382  
   383  // Normalize normalizes the histogram so that the
   384  // total area beneath it sums to a given value.
   385  // func (h *Histogram) Normalize(sum float64) {
   386  // 	mass := 0.0
   387  // 	for _, b := range h.Bins {
   388  // 		mass += b.Weight
   389  // 	}
   390  // 	for i := range h.Bins {
   391  // 		h.Bins[i].Weight *= sum / (h.Width * mass)
   392  // 	}
   393  // }
   394  
   395  // Thumbnail draws a rectangle in the given style of the histogram.
   396  func (h *H1D) Thumbnail(c *draw.Canvas) {
   397  	ymin := c.Min.Y
   398  	ymax := c.Max.Y
   399  	xmin := c.Min.X
   400  	xmax := c.Max.X
   401  	dy := ymax - ymin
   402  
   403  	// Style of the histogram
   404  	hasFill := h.FillColor != nil
   405  	hasLine := h.LineStyle.Width != 0
   406  	hasGlyph := h.GlyphStyle != (draw.GlyphStyle{})
   407  	hasBand := h.Band != nil
   408  
   409  	// Define default behaviour with priority
   410  	// 1) w/  fill: boxline, disregard band
   411  	// 2) w/o fill: skyline, band and markers
   412  	drawFill := hasFill
   413  	drawBand := !drawFill && hasBand
   414  	drawGlyph := hasGlyph
   415  	drawSkyLine := !drawFill && hasLine
   416  	drawBoxLine := hasFill && hasLine
   417  
   418  	if drawFill {
   419  		pts := []vg.Point{
   420  			{X: xmin, Y: ymin},
   421  			{X: xmax, Y: ymin},
   422  			{X: xmax, Y: ymax},
   423  			{X: xmin, Y: ymax},
   424  			{X: xmin, Y: ymin},
   425  		}
   426  		c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
   427  	}
   428  
   429  	if drawBand {
   430  		pts := []vg.Point{
   431  			{X: xmin, Y: ymin + 0.0*dy},
   432  			{X: xmax, Y: ymin + 0.0*dy},
   433  			{X: xmax, Y: ymax - 0.0*dy},
   434  			{X: xmin, Y: ymax - 0.0*dy},
   435  			{X: xmin, Y: ymin + 0.0*dy},
   436  		}
   437  		c.FillPolygon(h.Band.FillColor, c.ClipPolygonXY(pts))
   438  	}
   439  
   440  	if drawBoxLine {
   441  		line := []vg.Point{
   442  			{X: xmin, Y: ymin},
   443  			{X: xmax, Y: ymin},
   444  			{X: xmax, Y: ymax},
   445  			{X: xmin, Y: ymax},
   446  			{X: xmin, Y: ymin},
   447  		}
   448  		c.StrokeLines(h.LineStyle, c.ClipLinesX(line)...)
   449  	}
   450  
   451  	if drawSkyLine {
   452  		ymid := c.Center().Y
   453  		line := []vg.Point{{X: xmin, Y: ymid}, {X: xmax, Y: ymid}}
   454  		c.StrokeLines(h.LineStyle, c.ClipLinesX(line)...)
   455  	}
   456  
   457  	if drawGlyph {
   458  		c.DrawGlyph(h.GlyphStyle, c.Center())
   459  		if h.YErrs != nil {
   460  			var (
   461  				yerrs = h.YErrs
   462  				vsize = 0.5 * dy * 0.95
   463  				x     = c.Center().X
   464  				ylo   = c.Center().Y - vsize
   465  				yup   = c.Center().Y + vsize
   466  				xylo  = vg.Point{X: x, Y: ylo}
   467  				xyup  = vg.Point{X: x, Y: yup}
   468  				line  = []vg.Point{xylo, xyup}
   469  				bar   = c.ClipLinesY(line)
   470  			)
   471  			c.StrokeLines(yerrs.LineStyle, bar...)
   472  			for _, pt := range []vg.Point{xylo, xyup} {
   473  				if c.Contains(pt) {
   474  					c.StrokeLine2(yerrs.LineStyle,
   475  						pt.X-yerrs.CapWidth/2,
   476  						pt.Y,
   477  						pt.X+yerrs.CapWidth/2,
   478  						pt.Y,
   479  					)
   480  				}
   481  			}
   482  		}
   483  	}
   484  }
   485  
   486  func newHistFromXYer(xys plotter.XYer, n int) *hbook.H1D {
   487  	xmin, xmax := plotter.Range(plotter.XValues{XYer: xys})
   488  	h := hbook.NewH1D(n, xmin, xmax)
   489  
   490  	for i := range xys.Len() {
   491  		x, y := xys.XY(i)
   492  		h.Fill(x, y)
   493  	}
   494  
   495  	return h
   496  }
   497  
   498  // A Legend gives a description of the meaning of different
   499  // data elements of the plot.  Each legend entry has a name
   500  // and a thumbnail, where the thumbnail shows a small
   501  // sample of the display style of the corresponding data.
   502  type histLegend struct {
   503  	// TextStyle is the style given to the legend
   504  	// entry texts.
   505  	TextStyle draw.TextStyle
   506  
   507  	// Padding is the amount of padding to add
   508  	// betweeneach entry of the legend.  If Padding
   509  	// is zero then entries are spaced based on the
   510  	// font size.
   511  	Padding vg.Length
   512  
   513  	// Top and Left specify the location of the legend.
   514  	// If Top is true the legend is located along the top
   515  	// edge of the plot, otherwise it is located along
   516  	// the bottom edge.  If Left is true then the legend
   517  	// is located along the left edge of the plot, and the
   518  	// text is positioned after the icons, otherwise it is
   519  	// located along the right edge and the text is
   520  	// positioned before the icons.
   521  	Top, Left bool
   522  
   523  	// XOffs and YOffs are added to the legend's
   524  	// final position.
   525  	XOffs, YOffs vg.Length
   526  
   527  	// ColWidth is the width of legend names
   528  	ColWidth vg.Length
   529  
   530  	// entries are all of the legendEntries described
   531  	// by this legend.
   532  	entries []legendEntry
   533  }
   534  
   535  // A legendEntry represents a single line of a legend, it
   536  // has a name and an icon.
   537  type legendEntry struct {
   538  	// text is the text associated with this entry.
   539  	text string
   540  
   541  	// value is the value associated with this entry
   542  	value string
   543  }
   544  
   545  // draw draws the legend to the given canvas.
   546  func (l *histLegend) draw(c draw.Canvas) {
   547  	textx := c.Min.X
   548  	hdr := l.entryWidth() //+ l.TextStyle.Width(" ")
   549  	l.ColWidth = hdr
   550  	if !l.Left {
   551  		textx = c.Max.X - l.ColWidth
   552  	}
   553  	textx += l.XOffs
   554  
   555  	enth := l.entryHeight()
   556  	y := c.Max.Y - enth
   557  	if !l.Top {
   558  		y = c.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1)
   559  	}
   560  	y += l.YOffs
   561  
   562  	colx := &draw.Canvas{
   563  		Canvas: c.Canvas,
   564  		Rectangle: vg.Rectangle{
   565  			Min: vg.Point{X: c.Min.X, Y: y},
   566  			Max: vg.Point{X: 2 * l.ColWidth, Y: enth},
   567  		},
   568  	}
   569  	for _, e := range l.entries {
   570  		yoffs := (enth - l.TextStyle.Height(e.text)) / 2
   571  		txt := l.TextStyle
   572  		txt.XAlign = draw.XLeft
   573  		c.FillText(txt, vg.Point{X: textx - hdr, Y: colx.Min.Y + yoffs}, e.text)
   574  		txt.XAlign = draw.XRight
   575  		c.FillText(txt, vg.Point{X: textx + hdr, Y: colx.Min.Y + yoffs}, e.value)
   576  		colx.Min.Y -= enth + l.Padding
   577  	}
   578  
   579  	bboxXmin := textx - hdr - l.TextStyle.Width(" ")
   580  	bboxXmax := c.Max.X
   581  	bboxYmin := colx.Min.Y + enth
   582  	bboxYmax := c.Max.Y
   583  	bbox := []vg.Point{
   584  		{X: bboxXmin, Y: bboxYmax},
   585  		{X: bboxXmin, Y: bboxYmin},
   586  		{X: bboxXmax, Y: bboxYmin},
   587  		{X: bboxXmax, Y: bboxYmax},
   588  		{X: bboxXmin, Y: bboxYmax},
   589  	}
   590  	c.StrokeLines(plotter.DefaultLineStyle, bbox)
   591  }
   592  
   593  // entryHeight returns the height of the tallest legend
   594  // entry text.
   595  func (l *histLegend) entryHeight() (height vg.Length) {
   596  	for _, e := range l.entries {
   597  		if h := l.TextStyle.Height(e.text); h > height {
   598  			height = h
   599  		}
   600  	}
   601  	return
   602  }
   603  
   604  // entryWidth returns the width of the largest legend
   605  // entry text.
   606  func (l *histLegend) entryWidth() (width vg.Length) {
   607  	for _, e := range l.entries {
   608  		if w := l.TextStyle.Width(e.value); w > width {
   609  			width = w
   610  		}
   611  	}
   612  	return
   613  }
   614  
   615  // Add adds an entry to the legend with the given name.
   616  // The entry's thumbnail is drawn as the composite of all of the
   617  // thumbnails.
   618  func (l *histLegend) Add(name string, value any) {
   619  	str := ""
   620  	switch value.(type) {
   621  	case float64, float32:
   622  		str = fmt.Sprintf("%6.4g ", value)
   623  	default:
   624  		str = fmt.Sprintf("%v ", value)
   625  	}
   626  	l.entries = append(l.entries, legendEntry{text: name, value: str})
   627  }
   628  
   629  var (
   630  	_ plot.Plotter     = (*H1D)(nil)
   631  	_ plot.Thumbnailer = (*H1D)(nil)
   632  )