...

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

Documentation: gonum.org/v1/plot/plotter

     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 plotter
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"image/color"
    11  	"math"
    12  
    13  	"gonum.org/v1/plot"
    14  	"gonum.org/v1/plot/vg"
    15  	"gonum.org/v1/plot/vg/draw"
    16  )
    17  
    18  // Histogram implements the Plotter interface,
    19  // drawing a histogram of the data.
    20  type Histogram struct {
    21  	// Bins is the set of bins for this histogram.
    22  	Bins []HistogramBin
    23  
    24  	// Width is the width of each bin.
    25  	Width float64
    26  
    27  	// FillColor is the color used to fill each
    28  	// bar of the histogram.  If the color is nil
    29  	// then the bars are not filled.
    30  	FillColor color.Color
    31  
    32  	// LineStyle is the style of the outline of each
    33  	// bar of the histogram.
    34  	draw.LineStyle
    35  
    36  	// LogY allows rendering with a log-scaled Y axis.
    37  	// When enabled, histogram bins with no entries will be discarded from
    38  	// the histogram's DataRange.
    39  	// The lowest Y value for the DataRange will be corrected to leave an
    40  	// arbitrary amount of height for the smallest bin entry so it is visible
    41  	// on the final plot.
    42  	LogY bool
    43  }
    44  
    45  // NewHistogram returns a new histogram
    46  // that represents the distribution of values
    47  // using the given number of bins.
    48  //
    49  // Each y value is assumed to be the frequency
    50  // count for the corresponding x.
    51  //
    52  // If the number of bins is non-positive than
    53  // a reasonable default is used.
    54  func NewHistogram(xy XYer, n int) (*Histogram, error) {
    55  	if n <= 0 {
    56  		return nil, errors.New("Histogram with non-positive number of bins")
    57  	}
    58  	bins, width := binPoints(xy, n)
    59  	return &Histogram{
    60  		Bins:      bins,
    61  		Width:     width,
    62  		FillColor: color.Gray{128},
    63  		LineStyle: DefaultLineStyle,
    64  	}, nil
    65  }
    66  
    67  // NewHist returns a new histogram, as in
    68  // NewHistogram, except that it accepts a Valuer
    69  // instead of an XYer.
    70  func NewHist(vs Valuer, n int) (*Histogram, error) {
    71  	return NewHistogram(unitYs{vs}, n)
    72  }
    73  
    74  type unitYs struct {
    75  	Valuer
    76  }
    77  
    78  func (u unitYs) XY(i int) (float64, float64) {
    79  	return u.Value(i), 1.0
    80  }
    81  
    82  // Plot implements the Plotter interface, drawing a line
    83  // that connects each point in the Line.
    84  func (h *Histogram) Plot(c draw.Canvas, p *plot.Plot) {
    85  	trX, trY := p.Transforms(&c)
    86  
    87  	for _, bin := range h.Bins {
    88  		ymin := c.Min.Y
    89  		ymax := c.Min.Y
    90  		if bin.Weight != 0 {
    91  			ymax = trY(bin.Weight)
    92  		}
    93  		xmin := trX(bin.Min)
    94  		xmax := trX(bin.Max)
    95  		pts := []vg.Point{
    96  			{X: xmin, Y: ymin},
    97  			{X: xmax, Y: ymin},
    98  			{X: xmax, Y: ymax},
    99  			{X: xmin, Y: ymax},
   100  		}
   101  		if h.FillColor != nil {
   102  			c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
   103  		}
   104  		pts = append(pts, vg.Point{X: xmin, Y: ymin})
   105  		c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
   106  	}
   107  }
   108  
   109  // DataRange returns the minimum and maximum X and Y values
   110  func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
   111  	xmin = math.Inf(+1)
   112  	xmax = math.Inf(-1)
   113  	ymin = math.Inf(+1)
   114  	ymax = math.Inf(-1)
   115  	ylow := math.Inf(+1) // ylow will hold the smallest non-zero y value.
   116  	for _, bin := range h.Bins {
   117  		if bin.Max > xmax {
   118  			xmax = bin.Max
   119  		}
   120  		if bin.Min < xmin {
   121  			xmin = bin.Min
   122  		}
   123  		if bin.Weight > ymax {
   124  			ymax = bin.Weight
   125  		}
   126  		if bin.Weight < ymin {
   127  			ymin = bin.Weight
   128  		}
   129  		if bin.Weight != 0 && bin.Weight < ylow {
   130  			ylow = bin.Weight
   131  		}
   132  	}
   133  	switch h.LogY {
   134  	case true:
   135  		if ymin == 0 && !math.IsInf(ylow, +1) {
   136  			// Reserve a bit of space for the smallest bin to be displayed still.
   137  			ymin = ylow * 0.5
   138  		}
   139  	default:
   140  		ymin = 0
   141  	}
   142  	return
   143  }
   144  
   145  // Normalize normalizes the histogram so that the
   146  // total area beneath it sums to a given value.
   147  func (h *Histogram) Normalize(sum float64) {
   148  	mass := 0.0
   149  	for _, b := range h.Bins {
   150  		mass += b.Weight
   151  	}
   152  	for i := range h.Bins {
   153  		h.Bins[i].Weight *= sum / (h.Width * mass)
   154  	}
   155  }
   156  
   157  // Thumbnail draws a rectangle in the given style of the histogram.
   158  func (h *Histogram) Thumbnail(c *draw.Canvas) {
   159  	ymin := c.Min.Y
   160  	ymax := c.Max.Y
   161  	xmin := c.Min.X
   162  	xmax := c.Max.X
   163  
   164  	pts := []vg.Point{
   165  		{X: xmin, Y: ymin},
   166  		{X: xmax, Y: ymin},
   167  		{X: xmax, Y: ymax},
   168  		{X: xmin, Y: ymax},
   169  	}
   170  	if h.FillColor != nil {
   171  		c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
   172  	}
   173  	pts = append(pts, vg.Point{X: xmin, Y: ymin})
   174  	c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
   175  }
   176  
   177  // binPoints returns a slice containing the
   178  // given number of bins, and the width of
   179  // each bin.
   180  //
   181  // If the given number of bins is not positive
   182  // then a reasonable default is used.  The
   183  // default is the square root of the sum of
   184  // the y values.
   185  func binPoints(xys XYer, n int) (bins []HistogramBin, width float64) {
   186  	xmin, xmax := Range(XValues{xys})
   187  	if n <= 0 {
   188  		m := 0.0
   189  		for i := 0; i < xys.Len(); i++ {
   190  			_, y := xys.XY(i)
   191  			m += math.Max(y, 1.0)
   192  		}
   193  		n = int(math.Ceil(math.Sqrt(m)))
   194  	}
   195  	if n < 1 || xmax <= xmin {
   196  		n = 1
   197  	}
   198  
   199  	bins = make([]HistogramBin, n)
   200  
   201  	w := (xmax - xmin) / float64(n)
   202  	if w == 0 {
   203  		w = 1
   204  	}
   205  	for i := range bins {
   206  		bins[i].Min = xmin + float64(i)*w
   207  		bins[i].Max = xmin + float64(i+1)*w
   208  	}
   209  
   210  	for i := 0; i < xys.Len(); i++ {
   211  		x, y := xys.XY(i)
   212  		bin := int((x - xmin) / w)
   213  		if x == xmax {
   214  			bin = n - 1
   215  		}
   216  		if bin < 0 || bin >= n {
   217  			panic(fmt.Sprintf("%g, xmin=%g, xmax=%g, w=%g, bin=%d, n=%d\n",
   218  				x, xmin, xmax, w, bin, n))
   219  		}
   220  		bins[bin].Weight += y
   221  	}
   222  	return bins, w
   223  }
   224  
   225  // A HistogramBin approximates the number of values
   226  // within a range by a single number (the weight).
   227  type HistogramBin struct {
   228  	Min, Max float64
   229  	Weight   float64
   230  }
   231  

View as plain text