Skip to content

Commit

Permalink
plot,plotter: properly handle Log-Y scale in Histogram
Browse files Browse the repository at this point in the history
Fixes #377.

Previously, the Histogram.Plot and .DataRange method would implicitly
assume the bottom of a histogram would be 0.
This didn't pan out very well for Log-Y scale plots.
  • Loading branch information
sbinet committed Jan 17, 2019
1 parent 3cfdcb1 commit 9f5dec1
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 8 deletions.
4 changes: 2 additions & 2 deletions axis.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func makeAxis(orientation bool) (Axis, error) {
}

a := Axis{
Min: math.Inf(1),
Min: math.Inf(+1),
Max: math.Inf(-1),
LineStyle: draw.LineStyle{
Color: color.Black,
Expand Down Expand Up @@ -469,7 +469,7 @@ var _ Ticker = LogTicks{}

// Ticks returns Ticks in a specified range
func (LogTicks) Ticks(min, max float64) []Tick {
if min <= 0 {
if min <= 0 || max <= 0 {
panic("Values must be greater than 0 for a log scale.")
}

Expand Down
44 changes: 38 additions & 6 deletions plotter/histogram.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ type Histogram struct {
// LineStyle is the style of the outline of each
// bar of the histogram.
draw.LineStyle

// LogY allows rendering with a log-scaled Y axis.
// When enabled, histogram bins with no entries will be discarded from
// the histogram's DataRange.
// The lowest Y value for the DataRange will be corrected to leave an
// arbitrary amount of height for the smallest bin entry so it is visible
// on the final plot.
LogY bool
}

// NewHistogram returns a new histogram
Expand Down Expand Up @@ -77,25 +85,34 @@ func (h *Histogram) Plot(c draw.Canvas, p *plot.Plot) {
trX, trY := p.Transforms(&c)

for _, bin := range h.Bins {
ymin := c.Min.Y
ymax := c.Min.Y
if 0 != bin.Weight {
ymax = trY(bin.Weight)
}
xmin := trX(bin.Min)
xmax := trX(bin.Max)
pts := []vg.Point{
{trX(bin.Min), trY(0)},
{trX(bin.Max), trY(0)},
{trX(bin.Max), trY(bin.Weight)},
{trX(bin.Min), trY(bin.Weight)},
{xmin, ymin},
{xmax, ymin},
{xmax, ymax},
{xmin, ymax},
}
if h.FillColor != nil {
c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
}
pts = append(pts, vg.Point{X: trX(bin.Min), Y: trY(0)})
pts = append(pts, vg.Point{X: xmin, Y: ymin})
c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
}
}

// DataRange returns the minimum and maximum X and Y values
func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
xmin = math.Inf(1)
xmin = math.Inf(+1)
xmax = math.Inf(-1)
ymin = math.Inf(+1)
ymax = math.Inf(-1)
ylow := math.Inf(+1) // ylow will hold the smallest non-zero y value.
for _, bin := range h.Bins {
if bin.Max > xmax {
xmax = bin.Max
Expand All @@ -106,6 +123,21 @@ func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
if bin.Weight > ymax {
ymax = bin.Weight
}
if bin.Weight < ymin {
ymin = bin.Weight
}
if bin.Weight != 0 && bin.Weight < ylow {
ylow = bin.Weight
}
}
switch h.LogY {
case true:
if ymin == 0 && !math.IsInf(ylow, +1) {
// Reserve a bit of space for the smallest bin to be displayed still.
ymin = ylow * 0.5
}
default:
ymin = 0
}
return
}
Expand Down
48 changes: 48 additions & 0 deletions plotter/histogram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,51 @@ func TestSingletonHistogram(t *testing.T) {
case <-done:
}
}

func ExampleHistogram_logScaleY() {
p, err := plot.New()
if err != nil {
log.Panic(err)
}
p.Title.Text = "Histogram in log-y"
p.Y.Scale = plot.LogScale{}
p.Y.Tick.Marker = plot.LogTicks{}
p.Y.Label.Text = "Y"
p.X.Label.Text = "X"

h1, err := NewHist(Values{
-2, -2,
-1,
+3, +3, +3, +3,
+1, +1, +1, +1, +1, +1, +1, +1, +1, +1,
+1, +1, +1, +1, +1, +1, +1, +1, +1, +1,
}, 16)
if err != nil {
log.Fatal(err)
}
h1.LogY = true
h1.FillColor = color.RGBA{255, 0, 0, 255}

h2, err := NewHist(Values{
-3, -3, -3,
+2, +2, +2, +2, +2,
}, 16)

if err != nil {
log.Fatal(err)
}

h2.LogY = true
h2.FillColor = color.RGBA{0, 0, 255, 255}

p.Add(h1, h2, NewGrid())

err = p.Save(200, 200, "testdata/histogram_logy.png")
if err != nil {
log.Fatal(err)
}
}

func TestHistogramLogScale(t *testing.T) {
cmpimg.CheckPlot(ExampleHistogram_logScaleY, t, "histogram_logy.png")
}
Binary file added plotter/testdata/histogram_logy_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9f5dec1

Please sign in to comment.