...

Source file src/gonum.org/v1/plot/plotter/quartile.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  	"image/color"
     9  
    10  	"gonum.org/v1/plot"
    11  	"gonum.org/v1/plot/vg"
    12  	"gonum.org/v1/plot/vg/draw"
    13  )
    14  
    15  var (
    16  	// DefaultQuartMedianStyle is a fat dot.
    17  	DefaultQuartMedianStyle = draw.GlyphStyle{
    18  		Color:  color.Black,
    19  		Radius: vg.Points(1.5),
    20  		Shape:  draw.CircleGlyph{},
    21  	}
    22  
    23  	// DefaultQuartWhiskerStyle is a hairline.
    24  	DefaultQuartWhiskerStyle = draw.LineStyle{
    25  		Color:    color.Black,
    26  		Width:    vg.Points(0.5),
    27  		Dashes:   []vg.Length{},
    28  		DashOffs: 0,
    29  	}
    30  )
    31  
    32  // QuartPlot implements the Plotter interface, drawing
    33  // a plot to represent the distribution of values.
    34  //
    35  // This style of the plot appears in Tufte's "The Visual
    36  // Display of Quantitative Information".
    37  type QuartPlot struct {
    38  	fiveStatPlot
    39  
    40  	// Offset is added to the x location of each plot.
    41  	// When the Offset is zero, the plot is drawn
    42  	// centered at its x location.
    43  	Offset vg.Length
    44  
    45  	// MedianStyle is the line style for the median point.
    46  	MedianStyle draw.GlyphStyle
    47  
    48  	// WhiskerStyle is the line style used to draw the
    49  	// whiskers.
    50  	WhiskerStyle draw.LineStyle
    51  
    52  	// Horizontal dictates whether the QuartPlot should be in the vertical
    53  	// (default) or horizontal direction.
    54  	Horizontal bool
    55  }
    56  
    57  // NewQuartPlot returns a new QuartPlot that represents
    58  // the distribution of the given values.
    59  //
    60  // An error is returned if the plot is created with
    61  // no values.
    62  //
    63  // The fence values are 1.5x the interquartile before
    64  // the first quartile and after the third quartile.  Any
    65  // value that is outside of the fences are drawn as
    66  // Outside points.  The adjacent values (to which the
    67  // whiskers stretch) are the minimum and maximum
    68  // values that are not outside the fences.
    69  func NewQuartPlot(loc float64, values Valuer) (*QuartPlot, error) {
    70  	b := new(QuartPlot)
    71  	var err error
    72  	if b.fiveStatPlot, err = newFiveStat(0, loc, values); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	b.MedianStyle = DefaultQuartMedianStyle
    77  	b.WhiskerStyle = DefaultQuartWhiskerStyle
    78  
    79  	return b, err
    80  }
    81  
    82  // Plot draws the QuartPlot on Canvas c and Plot plt.
    83  func (b *QuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
    84  	if b.Horizontal {
    85  		b := &horizQuartPlot{b}
    86  		b.Plot(c, plt)
    87  		return
    88  	}
    89  
    90  	trX, trY := plt.Transforms(&c)
    91  	x := trX(b.Location)
    92  	if !c.ContainsX(x) {
    93  		return
    94  	}
    95  	x += b.Offset
    96  
    97  	med := vg.Point{X: x, Y: trY(b.Median)}
    98  	q1 := trY(b.Quartile1)
    99  	q3 := trY(b.Quartile3)
   100  	aLow := trY(b.AdjLow)
   101  	aHigh := trY(b.AdjHigh)
   102  
   103  	c.StrokeLine2(b.WhiskerStyle, x, aHigh, x, q3)
   104  	if c.ContainsY(med.Y) {
   105  		c.DrawGlyphNoClip(b.MedianStyle, med)
   106  	}
   107  	c.StrokeLine2(b.WhiskerStyle, x, aLow, x, q1)
   108  
   109  	ostyle := b.MedianStyle
   110  	ostyle.Radius = b.MedianStyle.Radius / 2
   111  	for _, out := range b.Outside {
   112  		y := trY(b.Value(out))
   113  		if c.ContainsY(y) {
   114  			c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
   115  		}
   116  	}
   117  }
   118  
   119  // DataRange returns the minimum and maximum x
   120  // and y values, implementing the plot.DataRanger
   121  // interface.
   122  func (b *QuartPlot) DataRange() (float64, float64, float64, float64) {
   123  	if b.Horizontal {
   124  		b := &horizQuartPlot{b}
   125  		return b.DataRange()
   126  	}
   127  	return b.Location, b.Location, b.Min, b.Max
   128  }
   129  
   130  // GlyphBoxes returns a slice of GlyphBoxes for the plot,
   131  // implementing the plot.GlyphBoxer interface.
   132  func (b *QuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
   133  	if b.Horizontal {
   134  		b := &horizQuartPlot{b}
   135  		return b.GlyphBoxes(plt)
   136  	}
   137  
   138  	bs := make([]plot.GlyphBox, len(b.Outside)+1)
   139  
   140  	ostyle := b.MedianStyle
   141  	ostyle.Radius = b.MedianStyle.Radius / 2
   142  	for i, out := range b.Outside {
   143  		bs[i].X = plt.X.Norm(b.Location)
   144  		bs[i].Y = plt.Y.Norm(b.Value(out))
   145  		bs[i].Rectangle = ostyle.Rectangle()
   146  		bs[i].Rectangle.Min.X += b.Offset
   147  	}
   148  	bs[len(bs)-1].X = plt.X.Norm(b.Location)
   149  	bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
   150  	bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
   151  	bs[len(bs)-1].Rectangle.Min.X += b.Offset
   152  	return bs
   153  }
   154  
   155  // OutsideLabels returns a *Labels that will plot
   156  // a label for each of the outside points.  The
   157  // labels are assumed to correspond to the
   158  // points used to create the plot.
   159  func (b *QuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
   160  	if b.Horizontal {
   161  		b := &horizQuartPlot{b}
   162  		return b.OutsideLabels(labels)
   163  	}
   164  	strs := make([]string, len(b.Outside))
   165  	for i, out := range b.Outside {
   166  		strs[i] = labels.Label(out)
   167  	}
   168  	o := quartPlotOutsideLabels{b, strs}
   169  	ls, err := NewLabels(o)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	off := 0.5 * b.MedianStyle.Radius
   174  	ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
   175  	return ls, nil
   176  }
   177  
   178  type quartPlotOutsideLabels struct {
   179  	qp     *QuartPlot
   180  	labels []string
   181  }
   182  
   183  func (o quartPlotOutsideLabels) Len() int {
   184  	return len(o.qp.Outside)
   185  }
   186  
   187  func (o quartPlotOutsideLabels) XY(i int) (float64, float64) {
   188  	return o.qp.Location, o.qp.Value(o.qp.Outside[i])
   189  }
   190  
   191  func (o quartPlotOutsideLabels) Label(i int) string {
   192  	return o.labels[i]
   193  }
   194  
   195  // horizQuartPlot is like a regular QuartPlot, however,
   196  // it draws horizontally instead of Vertically.
   197  type horizQuartPlot struct{ *QuartPlot }
   198  
   199  func (b horizQuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
   200  	trX, trY := plt.Transforms(&c)
   201  	y := trY(b.Location)
   202  	if !c.ContainsY(y) {
   203  		return
   204  	}
   205  	y += b.Offset
   206  
   207  	med := vg.Point{X: trX(b.Median), Y: y}
   208  	q1 := trX(b.Quartile1)
   209  	q3 := trX(b.Quartile3)
   210  	aLow := trX(b.AdjLow)
   211  	aHigh := trX(b.AdjHigh)
   212  
   213  	c.StrokeLine2(b.WhiskerStyle, aHigh, y, q3, y)
   214  	if c.ContainsX(med.X) {
   215  		c.DrawGlyphNoClip(b.MedianStyle, med)
   216  	}
   217  	c.StrokeLine2(b.WhiskerStyle, aLow, y, q1, y)
   218  
   219  	ostyle := b.MedianStyle
   220  	ostyle.Radius = b.MedianStyle.Radius / 2
   221  	for _, out := range b.Outside {
   222  		x := trX(b.Value(out))
   223  		if c.ContainsX(x) {
   224  			c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
   225  		}
   226  	}
   227  }
   228  
   229  // DataRange returns the minimum and maximum x
   230  // and y values, implementing the plot.DataRanger
   231  // interface.
   232  func (b horizQuartPlot) DataRange() (float64, float64, float64, float64) {
   233  	return b.Min, b.Max, b.Location, b.Location
   234  }
   235  
   236  // GlyphBoxes returns a slice of GlyphBoxes for the plot,
   237  // implementing the plot.GlyphBoxer interface.
   238  func (b horizQuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
   239  	bs := make([]plot.GlyphBox, len(b.Outside)+1)
   240  
   241  	ostyle := b.MedianStyle
   242  	ostyle.Radius = b.MedianStyle.Radius / 2
   243  	for i, out := range b.Outside {
   244  		bs[i].X = plt.X.Norm(b.Value(out))
   245  		bs[i].Y = plt.Y.Norm(b.Location)
   246  		bs[i].Rectangle = ostyle.Rectangle()
   247  		bs[i].Rectangle.Min.Y += b.Offset
   248  	}
   249  	bs[len(bs)-1].X = plt.X.Norm(b.Median)
   250  	bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
   251  	bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
   252  	bs[len(bs)-1].Rectangle.Min.Y += b.Offset
   253  	return bs
   254  }
   255  
   256  // OutsideLabels returns a *Labels that will plot
   257  // a label for each of the outside points.  The
   258  // labels are assumed to correspond to the
   259  // points used to create the plot.
   260  func (b *horizQuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
   261  	strs := make([]string, len(b.Outside))
   262  	for i, out := range b.Outside {
   263  		strs[i] = labels.Label(out)
   264  	}
   265  	o := horizQuartPlotOutsideLabels{
   266  		quartPlotOutsideLabels{b.QuartPlot, strs},
   267  	}
   268  	ls, err := NewLabels(o)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	off := 0.5 * b.MedianStyle.Radius
   273  	ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
   274  	return ls, nil
   275  }
   276  
   277  type horizQuartPlotOutsideLabels struct {
   278  	quartPlotOutsideLabels
   279  }
   280  
   281  func (o horizQuartPlotOutsideLabels) XY(i int) (float64, float64) {
   282  	return o.qp.Value(o.qp.Outside[i]), o.qp.Location
   283  }
   284  

View as plain text