...

Source file src/gonum.org/v1/plot/plotter/barchart.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  	"image/color"
    10  	"math"
    11  
    12  	"gonum.org/v1/plot"
    13  	"gonum.org/v1/plot/vg"
    14  	"gonum.org/v1/plot/vg/draw"
    15  )
    16  
    17  // A BarChart presents grouped data with rectangular bars
    18  // with lengths proportional to the data values.
    19  type BarChart struct {
    20  	Values
    21  
    22  	// Width is the width of the bars.
    23  	Width vg.Length
    24  
    25  	// Color is the fill color of the bars.
    26  	Color color.Color
    27  
    28  	// LineStyle is the style of the outline of the bars.
    29  	draw.LineStyle
    30  
    31  	// Offset is added to the X location of each bar.
    32  	// When the Offset is zero, the bars are drawn
    33  	// centered at their X location.
    34  	Offset vg.Length
    35  
    36  	// XMin is the X location of the first bar.  XMin
    37  	// can be changed to move groups of bars
    38  	// down the X axis in order to make grouped
    39  	// bar charts.
    40  	XMin float64
    41  
    42  	// Horizontal dictates whether the bars should be in the vertical
    43  	// (default) or horizontal direction. If Horizontal is true, all
    44  	// X locations and distances referred to here will actually be Y
    45  	// locations and distances.
    46  	Horizontal bool
    47  
    48  	// stackedOn is the bar chart upon which
    49  	// this bar chart is stacked.
    50  	stackedOn *BarChart
    51  }
    52  
    53  // NewBarChart returns a new bar chart with a single bar for each value.
    54  // The bars heights correspond to the values and their x locations correspond
    55  // to the index of their value in the Valuer.
    56  func NewBarChart(vs Valuer, width vg.Length) (*BarChart, error) {
    57  	if width <= 0 {
    58  		return nil, errors.New("plotter: width parameter was not positive")
    59  	}
    60  	values, err := CopyValues(vs)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return &BarChart{
    65  		Values:    values,
    66  		Width:     width,
    67  		Color:     color.Black,
    68  		LineStyle: DefaultLineStyle,
    69  	}, nil
    70  }
    71  
    72  // BarHeight returns the maximum y value of the
    73  // ith bar, taking into account any bars upon
    74  // which it is stacked.
    75  func (b *BarChart) BarHeight(i int) float64 {
    76  	ht := 0.0
    77  	if b == nil {
    78  		return 0
    79  	}
    80  	if i >= 0 && i < len(b.Values) {
    81  		ht += b.Values[i]
    82  	}
    83  	if b.stackedOn != nil {
    84  		ht += b.stackedOn.BarHeight(i)
    85  	}
    86  	return ht
    87  }
    88  
    89  // StackOn stacks a bar chart on top of another,
    90  // and sets the XMin and Offset to that of the
    91  // chart upon which it is being stacked.
    92  func (b *BarChart) StackOn(on *BarChart) {
    93  	b.XMin = on.XMin
    94  	b.Offset = on.Offset
    95  	b.stackedOn = on
    96  }
    97  
    98  // Plot implements the plot.Plotter interface.
    99  func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
   100  	trCat, trVal := plt.Transforms(&c)
   101  	if b.Horizontal {
   102  		trCat, trVal = trVal, trCat
   103  	}
   104  
   105  	for i, ht := range b.Values {
   106  		catVal := b.XMin + float64(i)
   107  		catMin := trCat(float64(catVal))
   108  		if !b.Horizontal {
   109  			if !c.ContainsX(catMin) {
   110  				continue
   111  			}
   112  		} else {
   113  			if !c.ContainsY(catMin) {
   114  				continue
   115  			}
   116  		}
   117  		catMin = catMin - b.Width/2 + b.Offset
   118  		catMax := catMin + b.Width
   119  		bottom := b.stackedOn.BarHeight(i)
   120  		valMin := trVal(bottom)
   121  		valMax := trVal(bottom + ht)
   122  
   123  		var pts []vg.Point
   124  		var poly []vg.Point
   125  		if !b.Horizontal {
   126  			pts = []vg.Point{
   127  				{X: catMin, Y: valMin},
   128  				{X: catMin, Y: valMax},
   129  				{X: catMax, Y: valMax},
   130  				{X: catMax, Y: valMin},
   131  			}
   132  			poly = c.ClipPolygonY(pts)
   133  		} else {
   134  			pts = []vg.Point{
   135  				{X: valMin, Y: catMin},
   136  				{X: valMin, Y: catMax},
   137  				{X: valMax, Y: catMax},
   138  				{X: valMax, Y: catMin},
   139  			}
   140  			poly = c.ClipPolygonX(pts)
   141  		}
   142  		c.FillPolygon(b.Color, poly)
   143  
   144  		var outline [][]vg.Point
   145  		if !b.Horizontal {
   146  			pts = append(pts, vg.Point{X: catMin, Y: valMin})
   147  			outline = c.ClipLinesY(pts)
   148  		} else {
   149  			pts = append(pts, vg.Point{X: valMin, Y: catMin})
   150  			outline = c.ClipLinesX(pts)
   151  		}
   152  		c.StrokeLines(b.LineStyle, outline...)
   153  	}
   154  }
   155  
   156  // DataRange implements the plot.DataRanger interface.
   157  func (b *BarChart) DataRange() (xmin, xmax, ymin, ymax float64) {
   158  	catMin := b.XMin
   159  	catMax := catMin + float64(len(b.Values)-1)
   160  
   161  	valMin := math.Inf(1)
   162  	valMax := math.Inf(-1)
   163  	for i, val := range b.Values {
   164  		valBot := b.stackedOn.BarHeight(i)
   165  		valTop := valBot + val
   166  		valMin = math.Min(valMin, math.Min(valBot, valTop))
   167  		valMax = math.Max(valMax, math.Max(valBot, valTop))
   168  	}
   169  	if !b.Horizontal {
   170  		return catMin, catMax, valMin, valMax
   171  	}
   172  	return valMin, valMax, catMin, catMax
   173  }
   174  
   175  // GlyphBoxes implements the GlyphBoxer interface.
   176  func (b *BarChart) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
   177  	boxes := make([]plot.GlyphBox, len(b.Values))
   178  	for i := range b.Values {
   179  		cat := b.XMin + float64(i)
   180  		if !b.Horizontal {
   181  			boxes[i].X = plt.X.Norm(cat)
   182  			boxes[i].Rectangle = vg.Rectangle{
   183  				Min: vg.Point{X: b.Offset - b.Width/2},
   184  				Max: vg.Point{X: b.Offset + b.Width/2},
   185  			}
   186  		} else {
   187  			boxes[i].Y = plt.Y.Norm(cat)
   188  			boxes[i].Rectangle = vg.Rectangle{
   189  				Min: vg.Point{Y: b.Offset - b.Width/2},
   190  				Max: vg.Point{Y: b.Offset + b.Width/2},
   191  			}
   192  		}
   193  	}
   194  	return boxes
   195  }
   196  
   197  // Thumbnail fulfills the plot.Thumbnailer interface.
   198  func (b *BarChart) Thumbnail(c *draw.Canvas) {
   199  	pts := []vg.Point{
   200  		{X: c.Min.X, Y: c.Min.Y},
   201  		{X: c.Min.X, Y: c.Max.Y},
   202  		{X: c.Max.X, Y: c.Max.Y},
   203  		{X: c.Max.X, Y: c.Min.Y},
   204  	}
   205  	poly := c.ClipPolygonY(pts)
   206  	c.FillPolygon(b.Color, poly)
   207  
   208  	pts = append(pts, vg.Point{X: c.Min.X, Y: c.Min.Y})
   209  	outline := c.ClipLinesY(pts)
   210  	c.StrokeLines(b.LineStyle, outline...)
   211  }
   212  

View as plain text