...

Source file src/gonum.org/v1/plot/palette/moreland/smooth.go

Documentation: gonum.org/v1/plot/palette/moreland

     1  // Copyright ©2016 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 moreland
     6  
     7  import (
     8  	"fmt"
     9  	"image/color"
    10  	"math"
    11  
    12  	"gonum.org/v1/plot/palette"
    13  )
    14  
    15  // smoothDiverging is a smooth diverging color palette as described in
    16  // "Diverging Color Maps for Scientific Visualization." by Kenneth Moreland,
    17  // in Proceedings of the 5th International Symposium on Visual Computing,
    18  // December 2009. DOI 10.1007/978-3-642-10520-3_9.
    19  type smoothDiverging struct {
    20  	// start and end are the beginning and ending colors
    21  	start, end msh
    22  
    23  	// convergeM is the MSH magnitude of the convergence point.
    24  	// It is 88 by default.
    25  	convergeM float64
    26  
    27  	// alpha represents the opacity of the returned
    28  	// colors in the range (0,1). It is 1 by default.
    29  	alpha float64
    30  
    31  	// min and max are the minimum and maximum values of the range of
    32  	// scalars that can be mapped to colors using this palette.
    33  	min, max float64
    34  
    35  	// convergePoint is a number between min and max where the colors
    36  	// should converge.
    37  	convergePoint float64
    38  }
    39  
    40  // NewSmoothDiverging creates a new smooth diverging ColorMap as described in
    41  // "Diverging Color Maps for Scientific Visualization." by Kenneth Moreland,
    42  // in Proceedings of the 5th International Symposium on Visual Computing,
    43  // December 2009. DOI 10.1007/978-3-642-10520-3_9.
    44  //
    45  // start and end are the start- and end-point colors and
    46  // convergeM is the magnitude of the convergence point in
    47  // magnitude-saturation-hue (MSH) color space. Note that
    48  // convergeM specifies the color of the convergence point; it does not
    49  // specify the location of the convergence point.
    50  func NewSmoothDiverging(start, end color.Color, convergeM float64) palette.DivergingColorMap {
    51  	return newSmoothDiverging(colorToMSH(start), colorToMSH(end), convergeM)
    52  }
    53  
    54  // newSmoothDiverging creates a new smooth diverging ColorMap
    55  // where start and end are the start and end point colors in MSH space and
    56  // convergeM is the MSH magnitude of the convergence point. Note that
    57  // convergeM specifies the color of the convergence point; it does not
    58  // specify the location of the convergence point.
    59  func newSmoothDiverging(start, end msh, convergeM float64) palette.DivergingColorMap {
    60  	return &smoothDiverging{
    61  		start:         start,
    62  		end:           end,
    63  		convergeM:     convergeM,
    64  		convergePoint: math.NaN(),
    65  		alpha:         1,
    66  	}
    67  }
    68  
    69  // At implements the palette.ColorMap interface.
    70  func (p *smoothDiverging) At(v float64) (color.Color, error) {
    71  	if err := checkRange(p.min, p.max, v); err != nil {
    72  		return nil, err
    73  	}
    74  	convergePoint := (p.convergePoint - p.min) / (p.max - p.min)
    75  	scalar := (v - p.min) / (p.max - p.min)
    76  	o := p.interpolateMSHDiverging(scalar, convergePoint).cieLAB().cieXYZ().rgb().sRGBA(p.alpha)
    77  	if !inUnitRange(o.R) || !inUnitRange(o.G) || !inUnitRange(o.B) || !inUnitRange(o.A) {
    78  		return nil, fmt.Errorf("moreland: invalid color r:%g, g:%g, b:%g, a:%g", o.R, o.G, o.B, o.A)
    79  	}
    80  	return o, nil
    81  }
    82  
    83  func inUnitRange(v float64) bool { return 0 <= v && v <= 1 }
    84  
    85  // SetMax implements the palette.ColorMap interface.
    86  func (p *smoothDiverging) SetMax(v float64) {
    87  	p.max = v
    88  	p.convergePoint = (p.min + p.max) / 2
    89  }
    90  
    91  // SetMin implements the palette.ColorMap interface.
    92  func (p *smoothDiverging) SetMin(v float64) {
    93  	p.min = v
    94  	p.convergePoint = (p.min + p.max) / 2
    95  }
    96  
    97  // Max implements the palette.ColorMap interface.
    98  func (p *smoothDiverging) Max() float64 {
    99  	return p.max
   100  }
   101  
   102  // Min implements the palette.ColorMap interface.
   103  func (p *smoothDiverging) Min() float64 {
   104  	return p.min
   105  }
   106  
   107  // SetAlpha sets the opacity value of this color map. Zero is transparent
   108  // and one is completely opaque.
   109  // The function will panic is alpha is not between zero and one.
   110  func (p *smoothDiverging) SetAlpha(alpha float64) {
   111  	if !inUnitRange(alpha) {
   112  		panic(fmt.Errorf("invalid alpha: %g", alpha))
   113  	}
   114  	p.alpha = alpha
   115  }
   116  
   117  // Alpha returns the opacity value of this color map.
   118  func (p *smoothDiverging) Alpha() float64 {
   119  	return p.alpha
   120  }
   121  
   122  // SetConvergePoint sets the value where the diverging colors
   123  // should meet.
   124  func (p *smoothDiverging) SetConvergePoint(val float64) {
   125  	if val > p.Max() || val < p.Min() {
   126  		panic(fmt.Errorf("moreland: convergence point (%g) must be between min (%g) and max (%g)",
   127  			val, p.Min(), p.Max()))
   128  	}
   129  	p.convergePoint = val
   130  }
   131  
   132  // ConvergePoint returns the value where the diverging colors meet.
   133  func (p *smoothDiverging) ConvergePoint() float64 {
   134  	return p.convergePoint
   135  }
   136  
   137  // interpolateMSHDiverging performs a color interpolation through MSH space,
   138  // where scalar is a number between 0 and 1 that the
   139  // color should be evaluated at, and convergePoint is a number between 0 and
   140  // 1 where the colors should converge.
   141  func (p *smoothDiverging) interpolateMSHDiverging(scalar, convergePoint float64) msh {
   142  	startHTwist := hueTwist(p.start, p.convergeM)
   143  	endHTwist := hueTwist(p.end, p.convergeM)
   144  	if scalar < convergePoint {
   145  		// interpolation factor
   146  		interp := scalar / convergePoint
   147  		return msh{
   148  			M: (p.convergeM-p.start.M)*interp + p.start.M,
   149  			S: p.start.S * (1 - interp),
   150  			H: p.start.H + startHTwist*interp,
   151  		}
   152  	}
   153  	// interpolation factors
   154  	interp1 := (scalar - 1) / (convergePoint - 1)
   155  	interp2 := (scalar/convergePoint - 1)
   156  	var H float64
   157  	if scalar > convergePoint {
   158  		H = p.end.H + endHTwist*interp1
   159  	}
   160  	return msh{
   161  		M: (p.convergeM-p.end.M)*interp1 + p.end.M,
   162  		S: p.end.S * interp2,
   163  		H: H,
   164  	}
   165  }
   166  
   167  // Palette returns a palette.Palette with the specified number of colors.
   168  func (p smoothDiverging) Palette(n int) palette.Palette {
   169  	if p.Max() == 0 && p.Min() == 0 {
   170  		p.SetMin(0)
   171  		p.SetMax(1)
   172  	}
   173  	delta := (p.max - p.min) / float64(n-1)
   174  	c := make([]color.Color, n)
   175  	for i := range c {
   176  		v := p.min + delta*float64(i)
   177  		var err error
   178  		c[i], err = p.At(v)
   179  		if err != nil {
   180  			panic(err)
   181  		}
   182  	}
   183  	return plte(c)
   184  }
   185  
   186  // SmoothBlueRed is a SmoothDiverging-class ColorMap ranging from blue to red.
   187  func SmoothBlueRed() palette.DivergingColorMap {
   188  	start := msh{
   189  		M: 80,
   190  		S: 1.08,
   191  		H: -1.1,
   192  	}
   193  	end := msh{
   194  		M: 80,
   195  		S: 1.08,
   196  		H: 0.5,
   197  	}
   198  	return newSmoothDiverging(start, end, 88)
   199  }
   200  
   201  // SmoothPurpleOrange is a SmoothDiverging-class ColorMap ranging from purple to orange.
   202  func SmoothPurpleOrange() palette.DivergingColorMap {
   203  	start := msh{
   204  		M: 64.97539711,
   205  		S: 0.899434815,
   206  		H: -0.899431964,
   207  	}
   208  	end := msh{
   209  		M: 85.00850996,
   210  		S: 0.949730284,
   211  		H: 0.950636521,
   212  	}
   213  	return newSmoothDiverging(start, end, 88)
   214  }
   215  
   216  // SmoothGreenPurple is a SmoothDiverging-class ColorMap ranging from green to purple.
   217  func SmoothGreenPurple() palette.DivergingColorMap {
   218  	start := msh{
   219  		M: 78.04105346,
   220  		S: 0.885011982,
   221  		H: 2.499491379,
   222  	}
   223  	end := msh{
   224  		M: 64.97539711,
   225  		S: 0.899434815,
   226  		H: -0.899431964,
   227  	}
   228  	return newSmoothDiverging(start, end, 88)
   229  }
   230  
   231  // SmoothBlueTan is a SmoothDiverging-class ColorMap ranging from blue to tan.
   232  func SmoothBlueTan() palette.DivergingColorMap {
   233  	start := msh{
   234  		M: 79.94788321,
   235  		S: 0.798754784,
   236  		H: -1.401313221,
   237  	}
   238  	end := msh{
   239  		M: 80.07193125,
   240  		S: 0.799798811,
   241  		H: 1.401089787,
   242  	}
   243  	return newSmoothDiverging(start, end, 88)
   244  }
   245  
   246  // SmoothGreenRed is a SmoothDiverging-class ColorMap ranging from green to red.
   247  func SmoothGreenRed() palette.DivergingColorMap {
   248  	start := msh{
   249  		M: 78.04105346,
   250  		S: 0.885011982,
   251  		H: 2.499491379,
   252  	}
   253  	end := msh{
   254  		M: 76.96722122,
   255  		S: 0.949483656,
   256  		H: 0.499492043,
   257  	}
   258  	return newSmoothDiverging(start, end, 88)
   259  }
   260  

View as plain text