mirror of https://github.com/etcd-io/dbtester.git
218 lines
5.3 KiB
Go
218 lines
5.3 KiB
Go
// Copyright ©2015 The gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package plotter
|
|
|
|
import (
|
|
"image/color"
|
|
"math"
|
|
|
|
"github.com/gonum/plot"
|
|
"github.com/gonum/plot/palette"
|
|
"github.com/gonum/plot/vg"
|
|
"github.com/gonum/plot/vg/draw"
|
|
)
|
|
|
|
// GridXYZ describes three dimensional data where the X and Y
|
|
// coordinates are arranged on a rectangular grid.
|
|
type GridXYZ interface {
|
|
// Dims returns the dimensions of the grid.
|
|
Dims() (c, r int)
|
|
|
|
// Z returns the value of a grid value at (c, r).
|
|
// It will panic if c or r are out of bounds for the grid.
|
|
Z(c, r int) float64
|
|
|
|
// X returns the coordinate for the column at the index x.
|
|
// It will panic if c is out of bounds for the grid.
|
|
X(c int) float64
|
|
|
|
// Y returns the coordinate for the row at the index r.
|
|
// It will panic if r is out of bounds for the grid.
|
|
Y(r int) float64
|
|
}
|
|
|
|
// HeatMap implements the Plotter interface, drawing
|
|
// a heat map of the values in the GridXYZ field.
|
|
type HeatMap struct {
|
|
GridXYZ GridXYZ
|
|
|
|
// Palette is the color palette used to render
|
|
// the heat map. Palette must not be nil or
|
|
// return a zero length []color.Color.
|
|
Palette palette.Palette
|
|
|
|
// Underflow and Overflow are colors used to fill
|
|
// heat map elements outside the dynamic range
|
|
// defined by Min and Max.
|
|
Underflow color.Color
|
|
Overflow color.Color
|
|
|
|
// Min and Max define the dynamic range of the
|
|
// heat map.
|
|
Min, Max float64
|
|
}
|
|
|
|
// NewHeatMap creates as new heat map plotter for the given data,
|
|
// using the provided palette. If g has Min and Max methods that return
|
|
// a float, those returned values are used to set the respective HeatMap
|
|
// fields. If the returned HeatMap is used when Min is greater than or
|
|
// equal to Max, the Plot method will panic.
|
|
func NewHeatMap(g GridXYZ, p palette.Palette) *HeatMap {
|
|
var min, max float64
|
|
type minMaxer interface {
|
|
Min() float64
|
|
Max() float64
|
|
}
|
|
switch g := g.(type) {
|
|
case minMaxer:
|
|
min, max = g.Min(), g.Max()
|
|
default:
|
|
min, max = math.Inf(1), math.Inf(-1)
|
|
c, r := g.Dims()
|
|
for i := 0; i < c; i++ {
|
|
for j := 0; j < r; j++ {
|
|
v := g.Z(i, j)
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
min = math.Min(min, v)
|
|
max = math.Max(max, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &HeatMap{
|
|
GridXYZ: g,
|
|
Palette: p,
|
|
Min: min,
|
|
Max: max,
|
|
}
|
|
}
|
|
|
|
// Plot implements the Plot method of the plot.Plotter interface.
|
|
func (h *HeatMap) Plot(c draw.Canvas, plt *plot.Plot) {
|
|
if h.Min >= h.Max {
|
|
panic("heatmap: non-positive Z range")
|
|
}
|
|
pal := h.Palette.Colors()
|
|
if len(pal) == 0 {
|
|
panic("heatmap: empty palette")
|
|
}
|
|
// ps scales the palette uniformly across the data range.
|
|
ps := float64(len(pal)-1) / (h.Max - h.Min)
|
|
|
|
trX, trY := plt.Transforms(&c)
|
|
|
|
var pa vg.Path
|
|
cols, rows := h.GridXYZ.Dims()
|
|
for i := 0; i < cols; i++ {
|
|
|
|
var right, left float64
|
|
switch i {
|
|
case 0:
|
|
right = (h.GridXYZ.X(i+1) - h.GridXYZ.X(i)) / 2
|
|
left = -right
|
|
case cols - 1:
|
|
right = (h.GridXYZ.X(i) - h.GridXYZ.X(i-1)) / 2
|
|
left = -right
|
|
default:
|
|
right = (h.GridXYZ.X(i+1) - h.GridXYZ.X(i)) / 2
|
|
left = -(h.GridXYZ.X(i) - h.GridXYZ.X(i-1)) / 2
|
|
}
|
|
|
|
for j := 0; j < rows; j++ {
|
|
v := h.GridXYZ.Z(i, j)
|
|
if math.IsNaN(v) || math.IsInf(v, 0) {
|
|
continue
|
|
}
|
|
|
|
pa = pa[:0]
|
|
|
|
var up, down float64
|
|
switch j {
|
|
case 0:
|
|
up = (h.GridXYZ.Y(j+1) - h.GridXYZ.Y(j)) / 2
|
|
down = -up
|
|
case rows - 1:
|
|
up = (h.GridXYZ.Y(j) - h.GridXYZ.Y(j-1)) / 2
|
|
down = -up
|
|
default:
|
|
up = (h.GridXYZ.Y(j+1) - h.GridXYZ.Y(j)) / 2
|
|
down = -(h.GridXYZ.Y(j) - h.GridXYZ.Y(j-1)) / 2
|
|
}
|
|
|
|
x, y := trX(h.GridXYZ.X(i)+left), trY(h.GridXYZ.Y(j)+down)
|
|
dx, dy := trX(h.GridXYZ.X(i)+right), trY(h.GridXYZ.Y(j)+up)
|
|
|
|
if !c.Contains(vg.Point{X: x, Y: y}) || !c.Contains(vg.Point{X: dx, Y: dy}) {
|
|
continue
|
|
}
|
|
|
|
pa.Move(vg.Point{X: x, Y: y})
|
|
pa.Line(vg.Point{X: dx, Y: y})
|
|
pa.Line(vg.Point{X: dx, Y: dy})
|
|
pa.Line(vg.Point{X: x, Y: dy})
|
|
pa.Close()
|
|
|
|
var col color.Color
|
|
switch {
|
|
case v < h.Min:
|
|
col = h.Underflow
|
|
case v > h.Max:
|
|
col = h.Overflow
|
|
default:
|
|
col = pal[int((v-h.Min)*ps+0.5)] // Apply palette scaling.
|
|
}
|
|
if col != nil {
|
|
c.SetColor(col)
|
|
c.Fill(pa)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// DataRange implements the DataRange method
|
|
// of the plot.DataRanger interface.
|
|
func (h *HeatMap) DataRange() (xmin, xmax, ymin, ymax float64) {
|
|
c, r := h.GridXYZ.Dims()
|
|
switch c {
|
|
case 1: // Make a unit length when there is no neighbour.
|
|
xmax = 0.5
|
|
xmin = -0.5
|
|
default:
|
|
xmax = h.GridXYZ.X(c-1) + (h.GridXYZ.X(c-1)-h.GridXYZ.X(c-2))/2
|
|
xmin = h.GridXYZ.X(0) - (h.GridXYZ.X(1)-h.GridXYZ.X(0))/2
|
|
}
|
|
switch r {
|
|
case 1: // Make a unit length when there is no neighbour.
|
|
ymax = 0.5
|
|
ymin = -0.5
|
|
default:
|
|
ymax = h.GridXYZ.Y(r-1) + (h.GridXYZ.Y(r-1)-h.GridXYZ.Y(r-2))/2
|
|
ymin = h.GridXYZ.Y(0) - (h.GridXYZ.Y(1)-h.GridXYZ.Y(0))/2
|
|
}
|
|
return xmin, xmax, ymin, ymax
|
|
}
|
|
|
|
// GlyphBoxes implements the GlyphBoxes method
|
|
// of the plot.GlyphBoxer interface.
|
|
func (h *HeatMap) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
|
c, r := h.GridXYZ.Dims()
|
|
b := make([]plot.GlyphBox, 0, r*c)
|
|
for i := 0; i < c; i++ {
|
|
for j := 0; j < r; j++ {
|
|
b = append(b, plot.GlyphBox{
|
|
X: plt.X.Norm(h.GridXYZ.X(i)),
|
|
Y: plt.Y.Norm(h.GridXYZ.Y(j)),
|
|
Rectangle: vg.Rectangle{
|
|
Min: vg.Point{X: -5, Y: -5},
|
|
Max: vg.Point{X: +5, Y: +5},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
return b
|
|
}
|