...

Source file src/gonum.org/v1/plot/vg/vggio/vggio_test.go

Documentation: gonum.org/v1/plot/vg/vggio

     1  // Copyright ©2020 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 vggio
     6  
     7  import (
     8  	"fmt"
     9  	"image"
    10  	"image/color"
    11  	"image/png"
    12  	"math"
    13  	"os"
    14  	"runtime"
    15  	"testing"
    16  
    17  	"gioui.org/layout"
    18  	"gioui.org/op"
    19  	"gonum.org/v1/plot"
    20  	"gonum.org/v1/plot/cmpimg"
    21  	"gonum.org/v1/plot/plotter"
    22  	"gonum.org/v1/plot/vg"
    23  	"gonum.org/v1/plot/vg/draw"
    24  )
    25  
    26  const deltaGio = 0.05 // empirical value from experimentation.
    27  
    28  // init makes sure the headless display is ready for tests with Gio.
    29  // On GitHub Actions and on linux, that headless display may take some time to
    30  // be properly available and appears to be setup "on demand".
    31  // So we request it by trying to take a screenshot twice:
    32  //   - the first time around might fail
    33  //   - the second time shouldn't.
    34  func init() {
    35  	if runtime.GOOS != "linux" {
    36  		return
    37  	}
    38  
    39  	const (
    40  		w   = 20 * vg.Centimeter
    41  		h   = 15 * vg.Centimeter
    42  		dpi = 96
    43  	)
    44  	gtx := layout.Context{
    45  		Ops: new(op.Ops),
    46  		Constraints: layout.Exact(image.Pt(
    47  			int(w.Dots(dpi)),
    48  			int(h.Dots(dpi)),
    49  		)),
    50  	}
    51  
    52  	var err error
    53  	for try := 0; try < 2; try++ {
    54  		_, err = New(gtx, w, h, UseDPI(dpi)).Screenshot()
    55  		if err == nil {
    56  			return
    57  		}
    58  	}
    59  
    60  	panic(fmt.Errorf("vg/vggio_test: could not setup headless display: %+v", err))
    61  }
    62  
    63  func TestCanvas(t *testing.T) {
    64  	if runtime.GOOS == "darwin" {
    65  		t.Skip("TODO: github actions for darwin with headless setup.")
    66  	}
    67  
    68  	const fname = "testdata/func.png"
    69  
    70  	const (
    71  		w   = 20 * vg.Centimeter
    72  		h   = 15 * vg.Centimeter
    73  		dpi = 96
    74  	)
    75  
    76  	cmpimg.CheckPlotApprox(func() {
    77  		p := plot.New()
    78  		p.Title.Text = "My title"
    79  		p.X.Label.Text = "X"
    80  		p.Y.Label.Text = "Y"
    81  
    82  		quad := plotter.NewFunction(func(x float64) float64 { return x * x })
    83  		quad.Color = color.RGBA{B: 255, A: 255}
    84  
    85  		exp := plotter.NewFunction(func(x float64) float64 { return math.Pow(2, x) })
    86  		exp.Dashes = []vg.Length{vg.Points(2), vg.Points(2)}
    87  		exp.Width = vg.Points(2)
    88  		exp.Color = color.RGBA{G: 255, A: 255}
    89  
    90  		sin := plotter.NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 })
    91  		sin.Dashes = []vg.Length{vg.Points(4), vg.Points(5)}
    92  		sin.Width = vg.Points(4)
    93  		sin.Color = color.RGBA{R: 255, A: 255}
    94  
    95  		p.Add(quad, exp, sin)
    96  		p.Legend.Add("x^2", quad)
    97  		p.Legend.Add("2^x", exp)
    98  		p.Legend.Add("10*sin(x)+50", sin)
    99  		p.Legend.ThumbnailWidth = 0.5 * vg.Inch
   100  
   101  		p.X.Min = 0
   102  		p.X.Max = 10
   103  		p.Y.Min = 0
   104  		p.Y.Max = 100
   105  
   106  		p.Add(plotter.NewGrid())
   107  
   108  		gtx := layout.Context{
   109  			Ops: new(op.Ops),
   110  			Constraints: layout.Exact(image.Pt(
   111  				int(w.Dots(dpi)),
   112  				int(h.Dots(dpi)),
   113  			)),
   114  		}
   115  		cnv := New(gtx, w, h, UseDPI(dpi))
   116  		p.Draw(draw.New(cnv))
   117  
   118  		img, err := cnv.Screenshot()
   119  		if err != nil {
   120  			t.Fatalf("could not create screenshot: %+v", err)
   121  		}
   122  		f, err := os.Create(fname)
   123  		if err != nil {
   124  			t.Fatalf("could not create output file: %+v", err)
   125  		}
   126  		defer f.Close()
   127  
   128  		err = png.Encode(f, img)
   129  		if err != nil {
   130  			t.Fatalf("could not encode screenshot: %+v", err)
   131  		}
   132  
   133  		err = f.Close()
   134  		if err != nil {
   135  			t.Fatalf("could not save screenshot: %+v", err)
   136  		}
   137  	}, t, deltaGio, "func.png",
   138  	)
   139  }
   140  
   141  func TestCollectionName(t *testing.T) {
   142  	for _, tc := range []struct {
   143  		name string
   144  		want string
   145  	}{
   146  		{"Liberation", "Liberation"},
   147  		{"LiberationSerif-Bold", "LiberationSerif"},
   148  		{"LiberationSerif-BoldItalic", "LiberationSerif"},
   149  		{"LiberationSerif-BoldItalic-Extra", "LiberationSerif"},
   150  
   151  		{"LiberationMono", "LiberationMono"},
   152  		{"LiberationMono-Regular", "LiberationMono"},
   153  
   154  		{"Times-Roman", "Times"},
   155  		{"Times-Bold", "Times"},
   156  	} {
   157  		got := collectionName(tc.name)
   158  		if got != tc.want {
   159  			t.Errorf(
   160  				"%s: invalid collection name: got=%q, want=%q",
   161  				tc.name, got, tc.want,
   162  			)
   163  		}
   164  	}
   165  }
   166  
   167  func TestLabels(t *testing.T) {
   168  	if runtime.GOOS == "darwin" {
   169  		t.Skip("TODO: github actions for darwin with headless setup.")
   170  	}
   171  
   172  	const fname = "testdata/labels.png"
   173  
   174  	const (
   175  		w   = 20 * vg.Centimeter
   176  		h   = 15 * vg.Centimeter
   177  		dpi = 96
   178  	)
   179  
   180  	cmpimg.CheckPlotApprox(func() {
   181  		p := plot.New()
   182  		p.Title.Text = "Labels"
   183  		p.X.Min = -1
   184  		p.X.Max = +1
   185  		p.Y.Min = -1
   186  		p.Y.Max = +1
   187  
   188  		const (
   189  			left   = 0.00
   190  			middle = 0.02
   191  			right  = 0.04
   192  		)
   193  
   194  		labels, err := plotter.NewLabels(plotter.XYLabels{
   195  			XYs: []plotter.XY{
   196  				{X: -0.8 + left, Y: -0.5},   // Aq + y-align bottom
   197  				{X: -0.6 + middle, Y: -0.5}, // Aq + y-align center
   198  				{X: -0.4 + right, Y: -0.5},  // Aq + y-align top
   199  
   200  				{X: -0.8 + left, Y: +0.5}, // ditto for Aq\nAq
   201  				{X: -0.6 + middle, Y: +0.5},
   202  				{X: -0.4 + right, Y: +0.5},
   203  
   204  				{X: +0.0 + left, Y: +0}, // ditto for Bg\nBg\nBg
   205  				{X: +0.2 + middle, Y: +0},
   206  				{X: +0.4 + right, Y: +0},
   207  			},
   208  			Labels: []string{
   209  				"Aq", "Aq", "Aq",
   210  				"Aq\nAq", "Aq\nAq", "Aq\nAq",
   211  
   212  				"Bg\nBg\nBg",
   213  				"Bg\nBg\nBg",
   214  				"Bg\nBg\nBg",
   215  			},
   216  		})
   217  		if err != nil {
   218  			t.Fatalf("could not creates labels plotter: %+v", err)
   219  		}
   220  		for i := range labels.TextStyle {
   221  			sty := &labels.TextStyle[i]
   222  			sty.Font.Size = vg.Length(34)
   223  		}
   224  		labels.TextStyle[0].YAlign = draw.YBottom
   225  		labels.TextStyle[1].YAlign = draw.YCenter
   226  		labels.TextStyle[2].YAlign = draw.YTop
   227  
   228  		labels.TextStyle[3].YAlign = draw.YBottom
   229  		labels.TextStyle[4].YAlign = draw.YCenter
   230  		labels.TextStyle[5].YAlign = draw.YTop
   231  
   232  		labels.TextStyle[6].YAlign = draw.YBottom
   233  		labels.TextStyle[7].YAlign = draw.YCenter
   234  		labels.TextStyle[8].YAlign = draw.YTop
   235  
   236  		lred, err := plotter.NewLabels(plotter.XYLabels{
   237  			XYs: []plotter.XY{
   238  				{X: -0.8 + left, Y: +0.5},
   239  				{X: +0.0 + left, Y: +0},
   240  			},
   241  			Labels: []string{
   242  				"Aq", "Bg",
   243  			},
   244  		})
   245  		if err != nil {
   246  			t.Fatalf("could not creates labels plotter: %+v", err)
   247  		}
   248  		for i := range lred.TextStyle {
   249  			sty := &lred.TextStyle[i]
   250  			sty.Font.Size = vg.Length(34)
   251  			sty.Color = color.RGBA{R: 255, A: 255}
   252  			sty.YAlign = draw.YBottom
   253  		}
   254  
   255  		m5 := plotter.NewFunction(func(float64) float64 { return -0.5 })
   256  		m5.LineStyle.Color = color.RGBA{R: 255, A: 255}
   257  
   258  		l0 := plotter.NewFunction(func(float64) float64 { return 0 })
   259  		l0.LineStyle.Color = color.RGBA{G: 255, A: 255}
   260  
   261  		p5 := plotter.NewFunction(func(float64) float64 { return +0.5 })
   262  		p5.LineStyle.Color = color.RGBA{B: 255, A: 255}
   263  
   264  		p.Add(labels, lred, m5, l0, p5)
   265  		p.Add(plotter.NewGrid())
   266  		p.Add(plotter.NewGlyphBoxes())
   267  
   268  		gtx := layout.Context{
   269  			Ops: new(op.Ops),
   270  			Constraints: layout.Exact(image.Pt(
   271  				int(w.Dots(dpi)),
   272  				int(h.Dots(dpi)),
   273  			)),
   274  		}
   275  		cnv := New(gtx, w, h, UseDPI(dpi))
   276  		p.Draw(draw.New(cnv))
   277  
   278  		img, err := cnv.Screenshot()
   279  		if err != nil {
   280  			t.Fatalf("could not create screenshot: %+v", err)
   281  		}
   282  		f, err := os.Create(fname)
   283  		if err != nil {
   284  			t.Fatalf("could not create output file: %+v", err)
   285  		}
   286  		defer f.Close()
   287  
   288  		err = png.Encode(f, img)
   289  		if err != nil {
   290  			t.Fatalf("could not encode screenshot: %+v", err)
   291  		}
   292  
   293  		err = f.Close()
   294  		if err != nil {
   295  			t.Fatalf("could not save screenshot: %+v", err)
   296  		}
   297  	}, t, deltaGio, "labels.png",
   298  	)
   299  }
   300  
   301  func TestPaths(t *testing.T) {
   302  	if runtime.GOOS == "darwin" {
   303  		t.Skip("TODO: github actions for darwin with headless setup.")
   304  	}
   305  
   306  	const fname = "testdata/paths.png"
   307  
   308  	const (
   309  		w   = 20 * vg.Centimeter
   310  		h   = 15 * vg.Centimeter
   311  		dpi = 96
   312  	)
   313  
   314  	cmpimg.CheckPlotApprox(func() {
   315  		p := plot.New()
   316  		p.Title.Text = "Paths"
   317  		p.X.Min = -1
   318  		p.X.Max = +1
   319  		p.Y.Min = -1
   320  		p.Y.Max = +1
   321  
   322  		newScatter := func(c color.Color, sty draw.GlyphDrawer, x, y float64) *plotter.Scatter {
   323  			t.Helper()
   324  
   325  			pts := make(plotter.XYs, 1)
   326  			pts[0].X = x
   327  			pts[0].Y = y
   328  
   329  			plt, err := plotter.NewScatter(pts)
   330  			if err != nil {
   331  				t.Fatal(err)
   332  			}
   333  			plt.GlyphStyle.Color = c
   334  			plt.GlyphStyle.Radius = vg.Points(10)
   335  			plt.GlyphStyle.Shape = sty
   336  			return plt
   337  		}
   338  
   339  		p.Add(
   340  			newScatter(
   341  				color.RGBA{R: 255, A: 255},
   342  				draw.CircleGlyph{},
   343  				-0.8, -0.8,
   344  			),
   345  			newScatter(
   346  				color.RGBA{B: 255, A: 255},
   347  				draw.RingGlyph{},
   348  				-0.6, -0.6,
   349  			),
   350  			newScatter(
   351  				color.RGBA{R: 255, A: 255},
   352  				draw.SquareGlyph{},
   353  				-0.4, -0.4,
   354  			),
   355  			newScatter(
   356  				color.RGBA{B: 255, A: 255},
   357  				draw.BoxGlyph{},
   358  				-0.2, -0.2,
   359  			),
   360  			newScatter(
   361  				color.RGBA{R: 255, A: 255},
   362  				draw.TriangleGlyph{},
   363  				0, 0,
   364  			),
   365  			newScatter(
   366  				color.RGBA{B: 255, A: 255},
   367  				draw.PyramidGlyph{},
   368  				0.2, 0.2,
   369  			),
   370  			newScatter(
   371  				color.RGBA{R: 255, A: 255},
   372  				draw.PlusGlyph{},
   373  				0.4, 0.4,
   374  			),
   375  			newScatter(
   376  				color.RGBA{B: 255, A: 255},
   377  				draw.CrossGlyph{},
   378  				0.6, 0.6,
   379  			),
   380  		)
   381  
   382  		p.Add(plotter.NewGrid())
   383  		p.Add(plotter.NewGlyphBoxes())
   384  
   385  		gtx := layout.Context{
   386  			Ops: new(op.Ops),
   387  			Constraints: layout.Exact(image.Pt(
   388  				int(w.Dots(dpi)),
   389  				int(h.Dots(dpi)),
   390  			)),
   391  		}
   392  		cnv := New(gtx, w, h, UseDPI(dpi), UseBackgroundColor(color.Transparent))
   393  		p.Draw(draw.New(cnv))
   394  
   395  		img, err := cnv.Screenshot()
   396  		if err != nil {
   397  			t.Fatalf("could not create screenshot: %+v", err)
   398  		}
   399  		f, err := os.Create(fname)
   400  		if err != nil {
   401  			t.Fatalf("could not create output file: %+v", err)
   402  		}
   403  		defer f.Close()
   404  
   405  		err = png.Encode(f, img)
   406  		if err != nil {
   407  			t.Fatalf("could not encode screenshot: %+v", err)
   408  		}
   409  
   410  		err = f.Close()
   411  		if err != nil {
   412  			t.Fatalf("could not save screenshot: %+v", err)
   413  		}
   414  	}, t, deltaGio, "paths.png",
   415  	)
   416  }
   417  
   418  // An example of embedding an image in a plot.
   419  func TestImage(t *testing.T) {
   420  	if runtime.GOOS == "darwin" {
   421  		t.Skip("TODO: github actions for darwin with headless setup.")
   422  	}
   423  
   424  	const fname = "testdata/image.png"
   425  
   426  	const (
   427  		w   = 20 * vg.Centimeter
   428  		h   = 15 * vg.Centimeter
   429  		dpi = 96
   430  	)
   431  
   432  	cmpimg.CheckPlotApprox(func() {
   433  		p := plot.New()
   434  		p.Title.Text = "A Logo"
   435  
   436  		// load an image
   437  		src, err := os.Open("../../plotter/testdata/gopher.png")
   438  		if err != nil {
   439  			t.Fatalf("error opening image file: %v\n", err)
   440  		}
   441  		defer src.Close()
   442  
   443  		img, err := png.Decode(src)
   444  		if err != nil {
   445  			t.Fatalf("error decoding image file: %v\n", err)
   446  		}
   447  
   448  		p.Add(plotter.NewImage(img, 100, 100, 200, 200))
   449  
   450  		gtx := layout.Context{
   451  			Ops: new(op.Ops),
   452  			Constraints: layout.Exact(image.Pt(
   453  				int(w.Dots(dpi)),
   454  				int(h.Dots(dpi)),
   455  			)),
   456  		}
   457  		cnv := New(gtx, w, h, UseDPI(dpi), UseBackgroundColor(color.Transparent))
   458  		p.Draw(draw.New(cnv))
   459  
   460  		scr, err := cnv.Screenshot()
   461  		if err != nil {
   462  			t.Fatalf("could not create screenshot: %+v", err)
   463  		}
   464  
   465  		out, err := os.Create(fname)
   466  		if err != nil {
   467  			t.Fatalf("could not create output file: %+v", err)
   468  		}
   469  		defer out.Close()
   470  
   471  		err = png.Encode(out, scr)
   472  		if err != nil {
   473  			t.Fatalf("could not encode screenshot: %+v", err)
   474  		}
   475  
   476  		err = out.Close()
   477  		if err != nil {
   478  			t.Fatalf("could not save screenshot: %+v", err)
   479  		}
   480  	}, t, deltaGio, "image.png",
   481  	)
   482  }
   483  

View as plain text