go-hep.org/x/hep@v0.38.1/hplot/README.md (about)

     1  hplot
     2  ====
     3  
     4  [![GoDoc](https://godoc.org/go-hep.org/x/hep/hplot?status.svg)](https://godoc.org/go-hep.org/x/hep/hplot)
     5  
     6  `hplot` is a WIP package relying on `gonum/plot` to plot histograms,
     7  n-tuples and functions.
     8  
     9  ## Installation
    10  
    11  ```sh
    12  $ go get go-hep.org/x/hep/hplot
    13  ```
    14  
    15  ## Documentation
    16  
    17  Is available on ``godoc``:
    18  
    19  https://godoc.org/go-hep.org/x/hep/hplot
    20  
    21  
    22  ## Examples
    23  
    24  ### 1D histogram
    25  
    26  ![hist-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h1d_plot_golden.png)
    27  
    28  [embedmd]:# (h1d_example_test.go go /func ExampleH1D/ /\n}/)
    29  ```go
    30  func ExampleH1D() {
    31  	const npoints = 10000
    32  
    33  	// Create a normal distribution.
    34  	dist := distuv.Normal{
    35  		Mu:    0,
    36  		Sigma: 1,
    37  		Src:   rand.New(rand.NewPCG(0, 0)),
    38  	}
    39  
    40  	// Draw some random values from the standard
    41  	// normal distribution.
    42  	hist := hbook.NewH1D(20, -4, +4)
    43  	for range npoints {
    44  		v := dist.Rand()
    45  		hist.Fill(v, 1)
    46  	}
    47  
    48  	// normalize histogram
    49  	area := 0.0
    50  	for _, bin := range hist.Binning.Bins {
    51  		area += bin.SumW() * bin.XWidth()
    52  	}
    53  	hist.Scale(1 / area)
    54  
    55  	// Make a plot and set its title.
    56  	p := hplot.New()
    57  	p.Title.Text = "Histogram"
    58  	p.X.Label.Text = "X"
    59  	p.Y.Label.Text = "Y"
    60  
    61  	// Create a histogram of our values drawn
    62  	// from the standard normal.
    63  	h := hplot.NewH1D(hist)
    64  	h.Infos.Style = hplot.HInfoSummary
    65  	p.Add(h)
    66  
    67  	// The normal distribution function
    68  	norm := hplot.NewFunction(dist.Prob)
    69  	norm.Color = color.RGBA{R: 255, A: 255}
    70  	norm.Width = vg.Points(2)
    71  	p.Add(norm)
    72  
    73  	// draw a grid
    74  	p.Add(hplot.NewGrid())
    75  
    76  	// Save the plot to a PNG file.
    77  	if err := p.Save(6*vg.Inch, -1, "testdata/h1d_plot.png"); err != nil {
    78  		log.Fatalf("error saving plot: %v\n", err)
    79  	}
    80  }
    81  ```
    82  
    83  ### 1D histogram with y-error bars
    84  
    85  ![hist-yerrs-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h1d_yerrs_golden.png)
    86  
    87  [embedmd]:# (h1d_example_test.go go /func ExampleH1D_withYErrBars/ /\n}/)
    88  ```go
    89  func ExampleH1D_withYErrBars() {
    90  	const npoints = 100
    91  
    92  	// Create a normal distribution.
    93  	dist := distuv.Normal{
    94  		Mu:    0,
    95  		Sigma: 1,
    96  		Src:   rand.New(rand.NewPCG(0, 0)),
    97  	}
    98  
    99  	// Draw some random values from the standard
   100  	// normal distribution.
   101  	hist := hbook.NewH1D(20, -4, +4)
   102  	for range npoints {
   103  		v := dist.Rand()
   104  		hist.Fill(v, 1)
   105  	}
   106  
   107  	// normalize histogram
   108  	area := 0.0
   109  	for _, bin := range hist.Binning.Bins {
   110  		area += bin.SumW() * bin.XWidth()
   111  	}
   112  	hist.Scale(1 / area)
   113  
   114  	// Make a plot and set its title.
   115  	p := hplot.New()
   116  	p.Title.Text = "Histogram"
   117  	p.X.Label.Text = "X"
   118  	p.Y.Label.Text = "Y"
   119  
   120  	// Create a histogram of our values drawn
   121  	// from the standard normal.
   122  	h := hplot.NewH1D(hist,
   123  		hplot.WithHInfo(hplot.HInfoSummary),
   124  		hplot.WithYErrBars(true),
   125  	)
   126  	h.YErrs.LineStyle.Color = color.RGBA{R: 255, A: 255}
   127  	p.Add(h)
   128  
   129  	// The normal distribution function
   130  	norm := hplot.NewFunction(dist.Prob)
   131  	norm.Color = color.RGBA{R: 255, A: 255}
   132  	norm.Width = vg.Points(2)
   133  	p.Add(norm)
   134  
   135  	// draw a grid
   136  	p.Add(hplot.NewGrid())
   137  
   138  	// Save the plot to a PNG file.
   139  	if err := p.Save(6*vg.Inch, -1, "testdata/h1d_yerrs.png"); err != nil {
   140  		log.Fatalf("error saving plot: %v\n", err)
   141  	}
   142  }
   143  ```
   144  
   145  ### 1D histogram with y-error bars, no lines
   146  
   147  ![hist-glyphs-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h1d_glyphs_golden.png)
   148  
   149  [embedmd]:# (h1d_example_test.go go /func ExampleH1D_withYErrBarsAndData/ /\n}/)
   150  ```go
   151  func ExampleH1D_withYErrBarsAndData() {
   152  	const npoints = 100
   153  
   154  	// Create a normal distribution.
   155  	dist := distuv.Normal{
   156  		Mu:    0,
   157  		Sigma: 1,
   158  		Src:   rand.New(rand.NewPCG(0, 0)),
   159  	}
   160  
   161  	// Draw some random values from the standard
   162  	// normal distribution.
   163  	hist := hbook.NewH1D(20, -4, +4)
   164  	for range npoints {
   165  		v := dist.Rand()
   166  		hist.Fill(v, 1)
   167  	}
   168  
   169  	// normalize histogram
   170  	area := 0.0
   171  	for _, bin := range hist.Binning.Bins {
   172  		area += bin.SumW() * bin.XWidth()
   173  	}
   174  	hist.Scale(1 / area)
   175  
   176  	// Make a plot and set its title.
   177  	p := hplot.New()
   178  	p.Title.Text = "Histogram"
   179  	p.X.Label.Text = "X"
   180  	p.Y.Label.Text = "Y"
   181  
   182  	p.Legend.Top = true
   183  	p.Legend.Left = true
   184  
   185  	// Create a histogram of our values drawn
   186  	// from the standard normal.
   187  	h := hplot.NewH1D(hist,
   188  		hplot.WithHInfo(hplot.HInfoSummary),
   189  		hplot.WithYErrBars(true),
   190  		hplot.WithGlyphStyle(draw.GlyphStyle{
   191  			Shape:  draw.CrossGlyph{},
   192  			Color:  color.Black,
   193  			Radius: vg.Points(2),
   194  		}),
   195  	)
   196  	h.GlyphStyle.Shape = draw.CircleGlyph{}
   197  	h.YErrs.LineStyle.Color = color.Black
   198  	h.LineStyle.Width = 0 // disable histogram lines
   199  	p.Add(h)
   200  	p.Legend.Add("data", h)
   201  
   202  	// The normal distribution function
   203  	norm := hplot.NewFunction(dist.Prob)
   204  	norm.Color = color.RGBA{R: 255, A: 255}
   205  	norm.Width = vg.Points(2)
   206  	p.Add(norm)
   207  	p.Legend.Add("model", norm)
   208  
   209  	// draw a grid
   210  	p.Add(hplot.NewGrid())
   211  
   212  	// Save the plot to a PNG file.
   213  	if err := p.Save(6*vg.Inch, -1, "testdata/h1d_glyphs.png"); err != nil {
   214  		log.Fatalf("error saving plot: %v\n", err)
   215  	}
   216  }
   217  ```
   218  
   219  ### 1D histogram with y-error bars and error bands
   220  
   221  ![hist-yerrs-band-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h1d_yerrs_band_golden.png)
   222  
   223  [embedmd]:# (h1d_example_test.go go /func ExampleH1D_withYErrBars_withBand/ /\n}/)
   224  ```go
   225  func ExampleH1D_withYErrBars_withBand() {
   226  	const npoints = 100
   227  
   228  	// Create a normal distribution.
   229  	dist := distuv.Normal{
   230  		Mu:    0,
   231  		Sigma: 1,
   232  		Src:   rand.New(rand.NewPCG(0, 0)),
   233  	}
   234  
   235  	// Draw some random values from the standard
   236  	// normal distribution.
   237  	hist := hbook.NewH1D(20, -4, +4)
   238  	for range npoints {
   239  		v := dist.Rand()
   240  		hist.Fill(v, 1)
   241  	}
   242  
   243  	// normalize histogram
   244  	area := 0.0
   245  	for _, bin := range hist.Binning.Bins {
   246  		area += bin.SumW() * bin.XWidth()
   247  	}
   248  	hist.Scale(1 / area)
   249  
   250  	// Make a plot and set its title.
   251  	p := hplot.New()
   252  	p.Title.Text = "Histogram"
   253  	p.X.Label.Text = "X"
   254  	p.Y.Label.Text = "Y"
   255  
   256  	// Create a histogram of our values drawn
   257  	// from the standard normal.
   258  	h := hplot.NewH1D(hist,
   259  		hplot.WithHInfo(hplot.HInfoSummary),
   260  		hplot.WithYErrBars(true),
   261  		hplot.WithBand(true),
   262  	)
   263  	h.YErrs.LineStyle.Color = color.RGBA{R: 255, A: 255}
   264  	p.Add(h)
   265  
   266  	// The normal distribution function
   267  	norm := hplot.NewFunction(dist.Prob)
   268  	norm.Color = color.RGBA{R: 255, A: 255}
   269  	norm.Width = vg.Points(2)
   270  	p.Add(norm)
   271  
   272  	// draw a grid
   273  	p.Add(hplot.NewGrid())
   274  
   275  	// Save the plot to a PNG file.
   276  	if err := p.Save(6*vg.Inch, -1, "testdata/h1d_yerrs_band.png"); err != nil {
   277  		log.Fatalf("error saving plot: %v\n", err)
   278  	}
   279  }
   280  ```
   281  
   282  ### Tiles of 1D histograms
   283  
   284  ![tiled-plot](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/tiled_plot_histogram_golden.png)
   285  
   286  [embedmd]:# (tiledplot_example_test.go go /func ExampleTiledPlot/ /\n}/)
   287  ```go
   288  func ExampleTiledPlot() {
   289  	tp := hplot.NewTiledPlot(draw.Tiles{Cols: 3, Rows: 2})
   290  
   291  	// Create a normal distribution.
   292  	dist := distuv.Normal{
   293  		Mu:    0,
   294  		Sigma: 1,
   295  		Src:   rand.New(rand.NewPCG(0, 0)),
   296  	}
   297  
   298  	newHist := func(p *hplot.Plot) {
   299  		const npoints = 10000
   300  		hist := hbook.NewH1D(20, -4, +4)
   301  		for range npoints {
   302  			v := dist.Rand()
   303  			hist.Fill(v, 1)
   304  		}
   305  
   306  		h := hplot.NewH1D(hist)
   307  		p.Add(h)
   308  	}
   309  
   310  	for i := range tp.Tiles.Rows {
   311  		for j := range tp.Tiles.Cols {
   312  			p := tp.Plot(j, i)
   313  			p.X.Min = -5
   314  			p.X.Max = +5
   315  			newHist(p)
   316  			p.Title.Text = fmt.Sprintf("hist - (%02d, %02d)", j, i)
   317  		}
   318  	}
   319  
   320  	// remove plot at (1,0)
   321  	tp.Plots[1] = nil
   322  
   323  	err := tp.Save(15*vg.Centimeter, -1, "testdata/tiled_plot_histogram.png")
   324  	if err != nil {
   325  		log.Fatalf("error: %+v\n", err)
   326  	}
   327  }
   328  ```
   329  
   330  ![tiled-plot-aligned](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/tiled_plot_aligned_histogram_golden.png)
   331  
   332  [embedmd]:# (tiledplot_example_test.go go /func ExampleTiledPlot_align/ /\n}/)
   333  ```go
   334  func ExampleTiledPlot_align() {
   335  	tp := hplot.NewTiledPlot(draw.Tiles{
   336  		Cols: 3, Rows: 3,
   337  		PadX: 20, PadY: 20,
   338  	})
   339  	tp.Align = true
   340  
   341  	points := func(i, j int) []hbook.Point2D {
   342  		n := i*tp.Tiles.Cols + j + 1
   343  		i += 1
   344  		j = int(math.Pow(10, float64(n)))
   345  
   346  		var pts []hbook.Point2D
   347  		for ii := range 10 {
   348  			pts = append(pts, hbook.Point2D{
   349  				X: float64(i + ii),
   350  				Y: float64(j + ii + 1),
   351  			})
   352  		}
   353  		return pts
   354  
   355  	}
   356  
   357  	for i := range tp.Tiles.Rows {
   358  		for j := range tp.Tiles.Cols {
   359  			p := tp.Plot(j, i)
   360  			p.X.Min = -5
   361  			p.X.Max = +5
   362  			s := hplot.NewS2D(hbook.NewS2D(points(i, j)...))
   363  			s.GlyphStyle.Color = color.RGBA{R: 255, A: 255}
   364  			s.GlyphStyle.Radius = vg.Points(4)
   365  			p.Add(s)
   366  
   367  			p.Title.Text = fmt.Sprintf("hist - (%02d, %02d)", j, i)
   368  		}
   369  	}
   370  
   371  	// remove plot at (1,1)
   372  	tp.Plots[4] = nil
   373  
   374  	err := tp.Save(15*vg.Centimeter, -1, "testdata/tiled_plot_aligned_histogram.png")
   375  	if err != nil {
   376  		log.Fatalf("error: %+v\n", err)
   377  	}
   378  }
   379  ```
   380  
   381  ### Subplots
   382  
   383  ![sub-plot](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/sub_plot_golden.png)
   384  
   385  https://godoc.org/go-hep.org/x/hep/hplot#example-package--Subplot
   386  
   387  ### Ratio-plots
   388  
   389  ![ratio-plot](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/diff_plot_golden.png)
   390  
   391  [embedmd]:# (ratioplot_example_test.go go /func ExampleRatioPlot/ /\n}/)
   392  ```go
   393  func ExampleRatioPlot() {
   394  
   395  	const npoints = 10000
   396  
   397  	// Create a normal distribution.
   398  	dist := distuv.Normal{
   399  		Mu:    0,
   400  		Sigma: 1,
   401  		Src:   rand.New(rand.NewPCG(0, 0)),
   402  	}
   403  
   404  	hist1 := hbook.NewH1D(20, -4, +4)
   405  	hist2 := hbook.NewH1D(20, -4, +4)
   406  
   407  	for range npoints {
   408  		v1 := dist.Rand() - 0.5
   409  		v2 := dist.Rand() + 0.5
   410  		hist1.Fill(v1, 1)
   411  		hist2.Fill(v2, 1)
   412  	}
   413  
   414  	rp := hplot.NewRatioPlot()
   415  	rp.Ratio = 0.3
   416  
   417  	// Make a plot and set its title.
   418  	rp.Top.Title.Text = "Histos"
   419  	rp.Top.Y.Label.Text = "Y"
   420  
   421  	// Create a histogram of our values drawn
   422  	// from the standard normal.
   423  	h1 := hplot.NewH1D(hist1)
   424  	h1.FillColor = color.NRGBA{R: 255, A: 100}
   425  	rp.Top.Add(h1)
   426  
   427  	h2 := hplot.NewH1D(hist2)
   428  	h2.FillColor = color.NRGBA{B: 255, A: 100}
   429  	rp.Top.Add(h2)
   430  
   431  	rp.Top.Add(hplot.NewGrid())
   432  
   433  	hist3 := hbook.NewH1D(20, -4, +4)
   434  	for i := range hist3.Len() {
   435  		v1 := hist1.Value(i)
   436  		v2 := hist2.Value(i)
   437  		x1, _ := hist1.XY(i)
   438  		hist3.Fill(x1, v1-v2)
   439  	}
   440  
   441  	hdiff := hplot.NewH1D(hist3)
   442  
   443  	rp.Bottom.X.Label.Text = "X"
   444  	rp.Bottom.Y.Label.Text = "Delta-Y"
   445  	rp.Bottom.Add(hdiff)
   446  	rp.Bottom.Add(hplot.NewGrid())
   447  
   448  	const (
   449  		width  = 15 * vg.Centimeter
   450  		height = width / math.Phi
   451  	)
   452  
   453  	err := hplot.Save(rp, width, height, "testdata/diff_plot.png")
   454  	if err != nil {
   455  		log.Fatalf("error: %v\n", err)
   456  	}
   457  }
   458  ```
   459  
   460  ### LaTeX-plots
   461  
   462  [latex-plot (PDF)](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/latex_plot_golden.pdf)
   463  
   464  https://godoc.org/go-hep.org/x/hep/hplot#example-package--Latexplot
   465  
   466  ### 2D histogram
   467  
   468  [embedmd]:# (h2d_example_test.go go /func ExampleH2D/ /\n}/)
   469  ```go
   470  func ExampleH2D() {
   471  	h := hbook.NewH2D(100, -10, 10, 100, -10, 10)
   472  
   473  	const npoints = 10000
   474  
   475  	dist, ok := distmv.NewNormal(
   476  		[]float64{0, 1},
   477  		mat.NewSymDense(2, []float64{4, 0, 0, 2}),
   478  		rand.New(rand.NewPCG(1234, 1234)),
   479  	)
   480  	if !ok {
   481  		log.Fatalf("error creating distmv.Normal")
   482  	}
   483  
   484  	v := make([]float64, 2)
   485  	// Draw some random values from the standard
   486  	// normal distribution.
   487  	for range npoints {
   488  		v = dist.Rand(v)
   489  		h.Fill(v[0], v[1], 1)
   490  	}
   491  
   492  	p := hplot.New()
   493  	p.Title.Text = "Hist-2D"
   494  	p.X.Label.Text = "x"
   495  	p.Y.Label.Text = "y"
   496  
   497  	p.Add(hplot.NewH2D(h, nil))
   498  	p.Add(plotter.NewGrid())
   499  	err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/h2d_plot.png")
   500  	if err != nil {
   501  		log.Fatal(err)
   502  	}
   503  }
   504  ```
   505  ![h2d-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h2d_plot_golden.png)
   506  
   507  ### Scatter2D
   508  
   509  [embedmd]:# (s2d_example_test.go go /func ExampleS2D/ /\n}/)
   510  ```go
   511  func ExampleS2D() {
   512  	const npoints = 1000
   513  
   514  	dist, ok := distmv.NewNormal(
   515  		[]float64{0, 1},
   516  		mat.NewSymDense(2, []float64{4, 0, 0, 2}),
   517  		rand.New(rand.NewPCG(1234, 1234)),
   518  	)
   519  	if !ok {
   520  		log.Fatalf("error creating distmv.Normal")
   521  	}
   522  
   523  	s2d := hbook.NewS2D()
   524  
   525  	v := make([]float64, 2)
   526  	// Draw some random values from the standard
   527  	// normal distribution.
   528  	for range npoints {
   529  		v = dist.Rand(v)
   530  		s2d.Fill(hbook.Point2D{X: v[0], Y: v[1]})
   531  	}
   532  
   533  	p := hplot.New()
   534  	p.Title.Text = "Scatter-2D"
   535  	p.X.Label.Text = "X"
   536  	p.Y.Label.Text = "Y"
   537  	p.Add(plotter.NewGrid())
   538  
   539  	s := hplot.NewS2D(s2d)
   540  	s.GlyphStyle.Color = color.RGBA{R: 255, A: 255}
   541  	s.GlyphStyle.Radius = vg.Points(2)
   542  
   543  	p.Add(s)
   544  
   545  	err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/s2d.png")
   546  	if err != nil {
   547  		log.Fatal(err)
   548  	}
   549  }
   550  ```
   551  ![s2d-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/s2d_golden.png)
   552  ![s2d-errbars-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/s2d_errbars_golden.png)
   553  ![s2d-band-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/s2d_band_golden.png)
   554  ![s2d-steps-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/s2d_steps_golden.png)
   555  ![s2d-steps-band-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/s2d_steps_band_golden.png)
   556  
   557  ### Vertical lines
   558  
   559  [embedmd]:# (line_example_test.go go /func ExampleVLine/ /\n}/)
   560  ```go
   561  func ExampleVLine() {
   562  	p := hplot.New()
   563  	p.Title.Text = "vlines"
   564  	p.X.Min = 0
   565  	p.X.Max = 10
   566  	p.Y.Min = 0
   567  	p.Y.Max = 10
   568  
   569  	var (
   570  		left  = color.RGBA{B: 255, A: 255}
   571  		right = color.RGBA{R: 255, A: 255}
   572  	)
   573  
   574  	p.Add(
   575  		hplot.VLine(2.5, left, nil),
   576  		hplot.VLine(5, nil, nil),
   577  		hplot.VLine(7.5, nil, right),
   578  	)
   579  
   580  	err := p.Save(10*vg.Centimeter, -1, "testdata/vline.png")
   581  	if err != nil {
   582  		log.Fatalf("error: %+v", err)
   583  	}
   584  }
   585  ```
   586  ![vline-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/vline_golden.png)
   587  
   588  ### Horizontal lines
   589  
   590  [embedmd]:# (line_example_test.go go /func ExampleHLine/ /\n}/)
   591  ```go
   592  func ExampleHLine() {
   593  	p := hplot.New()
   594  	p.Title.Text = "hlines"
   595  	p.X.Min = 0
   596  	p.X.Max = 10
   597  	p.Y.Min = 0
   598  	p.Y.Max = 10
   599  
   600  	var (
   601  		top    = color.RGBA{B: 255, A: 255}
   602  		bottom = color.RGBA{R: 255, A: 255}
   603  	)
   604  
   605  	p.Add(
   606  		hplot.HLine(2.5, nil, bottom),
   607  		hplot.HLine(5, nil, nil),
   608  		hplot.HLine(7.5, top, nil),
   609  	)
   610  
   611  	err := p.Save(10*vg.Centimeter, -1, "testdata/hline.png")
   612  	if err != nil {
   613  		log.Fatalf("error: %+v", err)
   614  	}
   615  }
   616  ```
   617  ![hline-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/hline_golden.png)
   618  
   619  ### Band between lines
   620  
   621  [embedmd]:# (band_example_test.go go /func ExampleBand/ /\n}/)
   622  ```go
   623  func ExampleBand() {
   624  	const (
   625  		npoints = 100
   626  		xmax    = 10
   627  	)
   628  
   629  	// Create a normal distribution.
   630  	dist := distuv.Normal{
   631  		Mu:    0,
   632  		Sigma: 1,
   633  		Src:   rand.New(rand.NewPCG(0, 0)),
   634  	}
   635  
   636  	topData := make(plotter.XYs, npoints)
   637  	botData := make(plotter.XYs, npoints)
   638  
   639  	// Draw some random values from the standard
   640  	// normal distribution.
   641  	for i := range npoints {
   642  		x := float64(i+1) / xmax
   643  
   644  		v1 := dist.Rand()
   645  		v2 := dist.Rand()
   646  
   647  		topData[i].X = x
   648  		topData[i].Y = 1/x + v1 + 10
   649  
   650  		botData[i].X = x
   651  		botData[i].Y = math.Log(x) + v2
   652  	}
   653  
   654  	top, err := hplot.NewLine(topData)
   655  	if err != nil {
   656  		log.Fatalf("error: %+v", err)
   657  	}
   658  	top.LineStyle.Color = color.RGBA{R: 255, A: 255}
   659  
   660  	bot, err := hplot.NewLine(botData)
   661  	if err != nil {
   662  		log.Fatalf("error: %+v", err)
   663  	}
   664  	bot.LineStyle.Color = color.RGBA{B: 255, A: 255}
   665  
   666  	tp := hplot.NewTiledPlot(draw.Tiles{Cols: 1, Rows: 2})
   667  
   668  	tp.Plots[0].Title.Text = "Band"
   669  	tp.Plots[0].Add(
   670  		top,
   671  		bot,
   672  		hplot.NewBand(color.Gray{200}, topData, botData),
   673  	)
   674  
   675  	tp.Plots[1].Title.Text = "Band"
   676  	var (
   677  		blue = color.RGBA{B: 255, A: 255}
   678  		grey = color.Gray{200}
   679  		band = hplot.NewBand(grey, topData, botData)
   680  	)
   681  	band.LineStyle = plotter.DefaultLineStyle
   682  	band.LineStyle.Color = blue
   683  	tp.Plots[1].Add(band)
   684  
   685  	err = tp.Save(10*vg.Centimeter, -1, "testdata/band.png")
   686  	if err != nil {
   687  		log.Fatalf("error: %+v", err)
   688  	}
   689  }
   690  ```
   691  ![band-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/band_golden.png)
   692  
   693  ### Plot with borders
   694  
   695  One can specify extra-space between the image borders (the physical file canvas) and the actual plot data.
   696  
   697  ![plot-border-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/h1d_borders_golden.png)
   698  
   699  [embedmd]:# (h1d_example_test.go go /func ExampleH1D_withPlotBorders/ /\n}/)
   700  ```go
   701  func ExampleH1D_withPlotBorders() {
   702  	const npoints = 10000
   703  
   704  	// Create a normal distribution.
   705  	dist := distuv.Normal{
   706  		Mu:    0,
   707  		Sigma: 1,
   708  		Src:   rand.New(rand.NewPCG(0, 0)),
   709  	}
   710  
   711  	// Draw some random values from the standard
   712  	// normal distribution.
   713  	hist := hbook.NewH1D(20, -4, +4)
   714  	for range npoints {
   715  		v := dist.Rand()
   716  		hist.Fill(v, 1)
   717  	}
   718  
   719  	// normalize histogram
   720  	area := 0.0
   721  	for _, bin := range hist.Binning.Bins {
   722  		area += bin.SumW() * bin.XWidth()
   723  	}
   724  	hist.Scale(1 / area)
   725  
   726  	// Make a plot and set its title.
   727  	p := hplot.New()
   728  	p.Title.Text = "Histogram"
   729  	p.X.Label.Text = "X"
   730  	p.Y.Label.Text = "Y"
   731  
   732  	// Create a histogram of our values drawn
   733  	// from the standard normal.
   734  	h := hplot.NewH1D(hist)
   735  	h.Infos.Style = hplot.HInfoSummary
   736  	p.Add(h)
   737  
   738  	// The normal distribution function
   739  	norm := hplot.NewFunction(dist.Prob)
   740  	norm.Color = color.RGBA{R: 255, A: 255}
   741  	norm.Width = vg.Points(2)
   742  	p.Add(norm)
   743  
   744  	// draw a grid
   745  	p.Add(hplot.NewGrid())
   746  
   747  	fig := hplot.Figure(p,
   748  		hplot.WithDPI(96),
   749  		hplot.WithBorder(hplot.Border{
   750  			Right:  25,
   751  			Left:   20,
   752  			Top:    25,
   753  			Bottom: 20,
   754  		}),
   755  	)
   756  
   757  	// Save the plot to a PNG file.
   758  	if err := hplot.Save(fig, 6*vg.Inch, -1, "testdata/h1d_borders.png"); err != nil {
   759  		log.Fatalf("error saving plot: %v\n", err)
   760  	}
   761  }
   762  ```
   763  
   764  ### Stack of 1D histograms
   765  
   766  ![hstack-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/hstack_golden.png)
   767  
   768  [embedmd]:# (hstack_example_test.go go /func ExampleHStack/ /\n}/)
   769  ```go
   770  func ExampleHStack() {
   771  	h1 := hbook.NewH1D(100, -10, 10)
   772  	h2 := hbook.NewH1D(100, -10, 10)
   773  	h3 := hbook.NewH1D(100, -10, 10)
   774  
   775  	const seed = 1234
   776  	fillH1(h1, 10000, -2, 1, seed)
   777  	fillH1(h2, 10000, +3, 3, seed)
   778  	fillH1(h3, 10000, +4, 1, seed)
   779  
   780  	colors := []color.Color{
   781  		color.NRGBA{122, 195, 106, 150},
   782  		color.NRGBA{90, 155, 212, 150},
   783  		color.NRGBA{250, 167, 91, 150},
   784  	}
   785  
   786  	hh1 := hplot.NewH1D(h1)
   787  	hh1.FillColor = colors[0]
   788  	hh1.LineStyle.Color = color.Black
   789  
   790  	hh2 := hplot.NewH1D(h2)
   791  	hh2.FillColor = colors[1]
   792  	hh2.LineStyle.Width = 0
   793  
   794  	hh3 := hplot.NewH1D(h3)
   795  	hh3.FillColor = colors[2]
   796  	hh3.LineStyle.Color = color.Black
   797  
   798  	hs := []*hplot.H1D{hh1, hh2, hh3}
   799  
   800  	tp := hplot.NewTiledPlot(draw.Tiles{Cols: 1, Rows: 3})
   801  	tp.Align = true
   802  
   803  	{
   804  		p := tp.Plots[0]
   805  		p.Title.Text = "Histograms"
   806  		p.Y.Label.Text = "Y"
   807  		p.Add(hh1, hh2, hh3, hplot.NewGrid())
   808  		p.Legend.Add("h1", hh1)
   809  		p.Legend.Add("h2", hh2)
   810  		p.Legend.Add("h3", hh3)
   811  		p.Legend.Top = true
   812  		p.Legend.Left = true
   813  	}
   814  
   815  	{
   816  		p := tp.Plot(0, 1)
   817  		p.Title.Text = "HStack - stack: OFF"
   818  		p.Y.Label.Text = "Y"
   819  		hstack := hplot.NewHStack(hs)
   820  		hstack.Stack = hplot.HStackOff
   821  		p.Add(hstack, hplot.NewGrid())
   822  		p.Legend.Add("h1", hs[0])
   823  		p.Legend.Add("h2", hs[1])
   824  		p.Legend.Add("h3", hs[2])
   825  		p.Legend.Top = true
   826  		p.Legend.Left = true
   827  	}
   828  
   829  	{
   830  		p := tp.Plot(0, 2)
   831  		p.Title.Text = "Hstack - stack: ON"
   832  		p.X.Label.Text = "X"
   833  		p.Y.Label.Text = "Y"
   834  		hstack := hplot.NewHStack(hs, hplot.WithLogY(false))
   835  		p.Add(hstack, hplot.NewGrid())
   836  		p.Legend.Add("h1", hs[0])
   837  		p.Legend.Add("h2", hs[1])
   838  		p.Legend.Add("h3", hs[2])
   839  		p.Legend.Top = true
   840  		p.Legend.Left = true
   841  	}
   842  
   843  	err := tp.Save(15*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack.png")
   844  	if err != nil {
   845  		log.Fatalf("error: %+v", err)
   846  	}
   847  
   848  }
   849  ```
   850  
   851  ### Stack of 1D histograms with a band
   852  
   853  ![hstack-band-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/hstack_band_golden.png)
   854  
   855  [embedmd]:# (hstack_example_test.go go /func ExampleHStack_withBand/ /\n}/)
   856  ```go
   857  func ExampleHStack_withBand() {
   858  	h1 := hbook.NewH1D(50, -8, 12)
   859  	h2 := hbook.NewH1D(50, -8, 12)
   860  	h3 := hbook.NewH1D(50, -8, 12)
   861  
   862  	const seed = 1234
   863  	fillH1(h1, 2000, -2, 1, seed)
   864  	fillH1(h2, 2000, +3, 3, seed)
   865  	fillH1(h3, 2000, +4, 1, seed)
   866  
   867  	colors := []color.Color{
   868  		color.NRGBA{122, 195, 106, 150},
   869  		color.NRGBA{90, 155, 212, 150},
   870  		color.NRGBA{250, 167, 91, 150},
   871  	}
   872  
   873  	hh1 := hplot.NewH1D(h1, hplot.WithBand(true))
   874  	hh1.FillColor = colors[0]
   875  	hh1.LineStyle.Color = color.Black
   876  	hh1.Band.FillColor = color.NRGBA{G: 210, A: 200}
   877  
   878  	hh2 := hplot.NewH1D(h2, hplot.WithBand(false))
   879  	hh2.FillColor = colors[1]
   880  	hh2.LineStyle.Width = 0
   881  
   882  	hh3 := hplot.NewH1D(h3, hplot.WithBand(true))
   883  	hh3.FillColor = colors[2]
   884  	hh3.LineStyle.Color = color.Black
   885  	hh3.Band.FillColor = color.NRGBA{R: 220, A: 200}
   886  
   887  	hs := []*hplot.H1D{hh1, hh2, hh3}
   888  
   889  	hh4 := hplot.NewH1D(h1)
   890  	hh4.FillColor = colors[0]
   891  	hh4.LineStyle.Color = color.Black
   892  
   893  	hh5 := hplot.NewH1D(h2)
   894  	hh5.FillColor = colors[1]
   895  	hh5.LineStyle.Width = 0
   896  
   897  	hh6 := hplot.NewH1D(h3)
   898  	hh6.FillColor = colors[2]
   899  	hh6.LineStyle.Color = color.Black
   900  
   901  	hsHistoNoBand := []*hplot.H1D{hh4, hh5, hh6}
   902  
   903  	tp := hplot.NewTiledPlot(draw.Tiles{Cols: 2, Rows: 2})
   904  	tp.Align = true
   905  
   906  	{
   907  		p := tp.Plot(0, 0)
   908  		p.Title.Text = "Histos With or Without Band, Stack: OFF"
   909  		p.Title.Padding = 10
   910  		p.X.Label.Text = "X"
   911  		p.Y.Label.Text = "Y"
   912  		hstack := hplot.NewHStack(hs, hplot.WithBand(true))
   913  		hstack.Stack = hplot.HStackOff
   914  		p.Add(hstack, hplot.NewGrid())
   915  		p.Legend.Add("h1", hs[0])
   916  		p.Legend.Add("h2", hs[1])
   917  		p.Legend.Add("h3", hs[2])
   918  		p.Legend.Top = true
   919  		p.Legend.Left = true
   920  	}
   921  
   922  	{
   923  		p := tp.Plot(1, 0)
   924  		p.Title.Text = "Histos Without Band, Stack: OFF"
   925  		p.Title.Padding = 10
   926  		p.X.Label.Text = "X"
   927  		p.Y.Label.Text = "Y"
   928  		hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true))
   929  		hstack.Stack = hplot.HStackOff
   930  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
   931  		p.Add(hstack, hplot.NewGrid())
   932  		p.Legend.Add("h1", hs[0])
   933  		p.Legend.Add("h2", hs[1])
   934  		p.Legend.Add("h3", hs[2])
   935  		p.Legend.Top = true
   936  		p.Legend.Left = true
   937  	}
   938  
   939  	{
   940  		p := tp.Plot(0, 1)
   941  		p.Title.Text = "Histos With or Without Band, Stack: ON"
   942  		p.Title.Padding = 10
   943  		p.X.Label.Text = "X"
   944  		p.Y.Label.Text = "Y"
   945  		hstack := hplot.NewHStack(hs, hplot.WithBand(true))
   946  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
   947  		p.Add(hstack, hplot.NewGrid())
   948  		p.Legend.Add("h1", hs[0])
   949  		p.Legend.Add("h2", hs[1])
   950  		p.Legend.Add("h3", hs[2])
   951  		p.Legend.Top = true
   952  		p.Legend.Left = true
   953  	}
   954  
   955  	{
   956  		p := tp.Plot(1, 1)
   957  		p.Title.Text = "Histos Without Band, Stack: ON"
   958  		p.Title.Padding = 10
   959  		p.X.Label.Text = "X"
   960  		p.Y.Label.Text = "Y"
   961  		hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true))
   962  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
   963  		p.Add(hstack, hplot.NewGrid())
   964  		p.Legend.Add("h1", hs[0])
   965  		p.Legend.Add("h2", hs[1])
   966  		p.Legend.Add("h3", hs[2])
   967  		p.Legend.Top = true
   968  		p.Legend.Left = true
   969  	}
   970  
   971  	err := tp.Save(25*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_band.png")
   972  	if err != nil {
   973  		log.Fatalf("error: %+v", err)
   974  	}
   975  }
   976  ```
   977  
   978  ### Stack of 1D histograms with a band, with a log-y scale
   979  
   980  ![hstack-logy-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/hstack_logy_golden.png)
   981  
   982  [embedmd]:# (hstack_example_test.go go /func ExampleHStack_withLogY/ /\n}/)
   983  ```go
   984  func ExampleHStack_withLogY() {
   985  	h1 := hbook.NewH1D(50, -8, 12)
   986  	h2 := hbook.NewH1D(50, -8, 12)
   987  	h3 := hbook.NewH1D(50, -8, 12)
   988  
   989  	const seed = 1234
   990  	fillH1(h1, 2000, -2, 1, seed)
   991  	fillH1(h2, 2000, +3, 3, seed)
   992  	fillH1(h3, 2000, +4, 1, seed)
   993  
   994  	colors := []color.Color{
   995  		color.NRGBA{122, 195, 106, 150},
   996  		color.NRGBA{90, 155, 212, 150},
   997  		color.NRGBA{250, 167, 91, 150},
   998  	}
   999  	logy := hplot.WithLogY(true)
  1000  
  1001  	hh1 := hplot.NewH1D(h1, hplot.WithBand(true), logy)
  1002  	hh1.FillColor = colors[0]
  1003  	hh1.LineStyle.Color = color.Black
  1004  	hh1.Band.FillColor = color.NRGBA{G: 210, A: 200}
  1005  
  1006  	hh2 := hplot.NewH1D(h2, hplot.WithBand(false), logy)
  1007  	hh2.FillColor = colors[1]
  1008  	hh2.LineStyle.Width = 0
  1009  
  1010  	hh3 := hplot.NewH1D(h3, hplot.WithBand(true), logy)
  1011  	hh3.FillColor = colors[2]
  1012  	hh3.LineStyle.Color = color.Black
  1013  	hh3.Band.FillColor = color.NRGBA{R: 220, A: 200}
  1014  
  1015  	hs := []*hplot.H1D{hh1, hh2, hh3}
  1016  
  1017  	hh4 := hplot.NewH1D(h1, logy)
  1018  	hh4.FillColor = colors[0]
  1019  	hh4.LineStyle.Color = color.Black
  1020  
  1021  	hh5 := hplot.NewH1D(h2, logy)
  1022  	hh5.FillColor = colors[1]
  1023  	hh5.LineStyle.Width = 0
  1024  
  1025  	hh6 := hplot.NewH1D(h3, logy)
  1026  	hh6.FillColor = colors[2]
  1027  	hh6.LineStyle.Color = color.Black
  1028  
  1029  	hsHistoNoBand := []*hplot.H1D{hh4, hh5, hh6}
  1030  
  1031  	tp := hplot.NewTiledPlot(draw.Tiles{Cols: 2, Rows: 2})
  1032  	tp.Align = true
  1033  
  1034  	{
  1035  		p := tp.Plot(0, 0)
  1036  		p.Title.Text = "Histos With or Without Band, Stack: OFF"
  1037  		p.Title.Padding = 10
  1038  		p.Y.Scale = plot.LogScale{}
  1039  		p.Y.Tick.Marker = plot.LogTicks{}
  1040  		p.X.Label.Text = "X"
  1041  		p.Y.Label.Text = "Y"
  1042  		hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy)
  1043  		hstack.Stack = hplot.HStackOff
  1044  		p.Add(hstack, hplot.NewGrid())
  1045  		p.Legend.Add("h1", hs[0])
  1046  		p.Legend.Add("h2", hs[1])
  1047  		p.Legend.Add("h3", hs[2])
  1048  		p.Legend.Top = true
  1049  		p.Legend.Left = true
  1050  	}
  1051  
  1052  	{
  1053  		p := tp.Plot(1, 0)
  1054  		p.Title.Text = "Histos Without Band, Stack: OFF"
  1055  		p.Title.Padding = 10
  1056  		p.Y.Scale = plot.LogScale{}
  1057  		p.Y.Tick.Marker = plot.LogTicks{}
  1058  		p.X.Label.Text = "X"
  1059  		p.Y.Label.Text = "Y"
  1060  		hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy)
  1061  		hstack.Stack = hplot.HStackOff
  1062  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
  1063  		p.Add(hstack, hplot.NewGrid())
  1064  		p.Legend.Add("h1", hs[0])
  1065  		p.Legend.Add("h2", hs[1])
  1066  		p.Legend.Add("h3", hs[2])
  1067  		p.Legend.Top = true
  1068  		p.Legend.Left = true
  1069  	}
  1070  
  1071  	{
  1072  		p := tp.Plot(0, 1)
  1073  		p.Title.Text = "Histos With or Without Band, Stack: ON"
  1074  		p.Title.Padding = 10
  1075  		p.Y.Scale = plot.LogScale{}
  1076  		p.Y.Tick.Marker = plot.LogTicks{}
  1077  		p.X.Label.Text = "X"
  1078  		p.Y.Label.Text = "Y"
  1079  		hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy)
  1080  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
  1081  		p.Add(hstack, hplot.NewGrid())
  1082  		p.Legend.Add("h1", hs[0])
  1083  		p.Legend.Add("h2", hs[1])
  1084  		p.Legend.Add("h3", hs[2])
  1085  		p.Legend.Top = true
  1086  		p.Legend.Left = true
  1087  	}
  1088  
  1089  	{
  1090  		p := tp.Plot(1, 1)
  1091  		p.Title.Text = "Histos Without Band, Stack: ON"
  1092  		p.Title.Padding = 10
  1093  		p.Y.Scale = plot.LogScale{}
  1094  		p.Y.Tick.Marker = plot.LogTicks{}
  1095  		p.X.Label.Text = "X"
  1096  		p.Y.Label.Text = "Y"
  1097  		hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy)
  1098  		hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200}
  1099  		p.Add(hstack, hplot.NewGrid())
  1100  		p.Legend.Add("h1", hs[0])
  1101  		p.Legend.Add("h2", hs[1])
  1102  		p.Legend.Add("h3", hs[2])
  1103  		p.Legend.Top = true
  1104  		p.Legend.Left = true
  1105  	}
  1106  
  1107  	err := tp.Save(25*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_logy.png")
  1108  	if err != nil {
  1109  		log.Fatalf("error: %+v", err)
  1110  	}
  1111  }
  1112  ```
  1113  
  1114  ## Labels
  1115  
  1116  ![label-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/label_plot_golden.png)
  1117  
  1118  [embedmd]:# (label_example_test.go go /func ExampleLabel/ /\n}/)
  1119  ```go
  1120  func ExampleLabel() {
  1121  
  1122  	// Creating a new plot
  1123  	p := hplot.New()
  1124  	p.Title.Text = "Plot labels"
  1125  	p.X.Min = -10
  1126  	p.X.Max = +10
  1127  	p.Y.Min = -10
  1128  	p.Y.Max = +10
  1129  
  1130  	// Default labels
  1131  	l1 := hplot.NewLabel(-8, 5, "(-8,5)\nDefault label")
  1132  	p.Add(l1)
  1133  
  1134  	// Label with normalized coordinates.
  1135  	l3 := hplot.NewLabel(
  1136  		0.5, 0.5,
  1137  		"(0.5,0.5)\nLabel with relative coords",
  1138  		hplot.WithLabelNormalized(true),
  1139  	)
  1140  	p.Add(l3)
  1141  
  1142  	// Label with normalized coordinates and auto-adjustement.
  1143  	l4 := hplot.NewLabel(
  1144  		0.95, 0.95,
  1145  		"(0.95,0.95)\nLabel at the canvas edge, with AutoAdjust",
  1146  		hplot.WithLabelNormalized(true),
  1147  		hplot.WithLabelAutoAdjust(true),
  1148  	)
  1149  	p.Add(l4)
  1150  
  1151  	// Label with a customed TextStyle
  1152  	usrFont := font.Font{
  1153  		Typeface: "Liberation",
  1154  		Variant:  "Mono",
  1155  		Weight:   xfnt.WeightBold,
  1156  		Style:    xfnt.StyleNormal,
  1157  		Size:     12,
  1158  	}
  1159  	sty := text.Style{
  1160  		Color: plotutil.Color(2),
  1161  		Font:  usrFont,
  1162  	}
  1163  	l5 := hplot.NewLabel(
  1164  		0.0, 0.1,
  1165  		"(0.0,0.1)\nLabel with a user-defined font",
  1166  		hplot.WithLabelTextStyle(sty),
  1167  		hplot.WithLabelNormalized(true),
  1168  	)
  1169  	p.Add(l5)
  1170  
  1171  	p.Add(plotter.NewGlyphBoxes())
  1172  	p.Add(hplot.NewGrid())
  1173  
  1174  	// Save the plot to a PNG file.
  1175  	err := p.Save(15*vg.Centimeter, -1, "testdata/label_plot.png")
  1176  	if err != nil {
  1177  		log.Fatalf("error saving plot: %v\n", err)
  1178  	}
  1179  }
  1180  ```
  1181  
  1182  ## Time series
  1183  
  1184  ![timeseries-example](https://codeberg.org/go-hep/hep/raw/branch/main/hplot/testdata/timeseries_monthly_golden.png)
  1185  
  1186  [embedmd]:# (ticks_example_test.go go /func ExampleTicks_monthly/ /\n}/)
  1187  ```go
  1188  func ExampleTicks_monthly() {
  1189  	cnv := epok.UTCUnixTimeConverter{}
  1190  
  1191  	p := hplot.New()
  1192  	p.Title.Text = "Time series (monthly)"
  1193  	p.Y.Label.Text = "Goroutines"
  1194  
  1195  	p.Y.Min = 0
  1196  	p.Y.Max = 4
  1197  	p.X.AutoRescale = true
  1198  	p.X.Tick.Marker = epok.Ticks{
  1199  		Ruler: epok.Rules{
  1200  			Major: epok.Rule{
  1201  				Freq:  epok.Monthly,
  1202  				Range: epok.RangeFrom(1, 13, 2),
  1203  			},
  1204  		},
  1205  		Format:    "2006\nJan-02\n15:04:05",
  1206  		Converter: cnv,
  1207  	}
  1208  
  1209  	xysFrom := func(vs ...float64) plotter.XYs {
  1210  		o := make(plotter.XYs, len(vs))
  1211  		for i := range o {
  1212  			o[i].X = vs[i]
  1213  			o[i].Y = float64(i + 1)
  1214  		}
  1215  		return o
  1216  	}
  1217  	data := xysFrom(
  1218  		cnv.FromTime(parse("2010-01-02 01:02:03")),
  1219  		cnv.FromTime(parse("2010-02-01 01:02:03")),
  1220  		cnv.FromTime(parse("2010-02-04 11:22:33")),
  1221  		cnv.FromTime(parse("2010-03-04 01:02:03")),
  1222  		cnv.FromTime(parse("2010-04-05 01:02:03")),
  1223  		cnv.FromTime(parse("2010-04-05 01:02:03")),
  1224  		cnv.FromTime(parse("2010-05-01 00:02:03")),
  1225  		cnv.FromTime(parse("2010-05-04 04:04:04")),
  1226  		cnv.FromTime(parse("2010-05-08 11:12:13")),
  1227  		cnv.FromTime(parse("2010-06-15 01:02:03")),
  1228  		cnv.FromTime(parse("2010-07-04 04:04:43")),
  1229  		cnv.FromTime(parse("2010-07-14 14:17:09")),
  1230  		cnv.FromTime(parse("2010-08-04 21:22:23")),
  1231  		cnv.FromTime(parse("2010-08-15 11:12:13")),
  1232  		cnv.FromTime(parse("2010-09-01 21:52:53")),
  1233  		cnv.FromTime(parse("2010-10-25 01:19:23")),
  1234  		cnv.FromTime(parse("2010-11-30 11:32:53")),
  1235  		cnv.FromTime(parse("2010-12-24 23:59:59")),
  1236  		cnv.FromTime(parse("2010-12-31 23:59:59")),
  1237  		cnv.FromTime(parse("2011-01-12 01:02:03")),
  1238  	)
  1239  
  1240  	line, pnts, err := hplot.NewLinePoints(data)
  1241  	if err != nil {
  1242  		log.Fatalf("could not create plotter: %+v", err)
  1243  	}
  1244  
  1245  	line.Color = color.RGBA{B: 255, A: 255}
  1246  	pnts.Shape = draw.CircleGlyph{}
  1247  	pnts.Color = color.RGBA{R: 255, A: 255}
  1248  
  1249  	p.Add(line, pnts, hplot.NewGrid())
  1250  
  1251  	err = p.Save(20*vg.Centimeter, 10*vg.Centimeter, "testdata/timeseries_monthly.png")
  1252  	if err != nil {
  1253  		log.Fatalf("could not save plot: %+v", err)
  1254  	}
  1255  }
  1256  ```