Combining plots

A key function that allows us to do this is the plot.Align function. For us to see this in action, we need to write a that allows us to plot any number of plots to a file, as follows:

func writeToPng(a interface{}, title, filename string, width, height vg.Length) {
switch at := a.(type) {
case *plot.Plot:
dieIfErr(at.Save(width*vg.Centimeter, height*vg.Centimeter, filename))
return
case [][]*plot.Plot:
rows := len(at)
cols := len(at[0])
t := draw.Tiles{
Rows: rows,
Cols: cols,
}
img := vgimg.New(width*vg.Centimeter, height*vg.Centimeter)
dc := draw.New(img)

if title != "" {
at[0][0].Title.Text = title
}

canvases := plot.Align(at, t, dc)
for i := 0; i < t.Rows; i++ {
for j := 0; j < t.Cols; j++ {
at[i][j].Draw(canvases[i][j])
}
}

w, err := os.Create(filename)
dieIfErr(err)

png := vgimg.PngCanvas{Canvas: img}
_, err = png.WriteTo(w)
dieIfErr(err)
return
}
panic("Unreachable")
}

We'll skip the part where if a is plot.Plot, we simply call the .Save method. Instead, we'll look at the second case, where a is [][]*plot.Plot.

At first this may seem rather strange—why have a slice of slice of plots when all we want to do is to combine them in quick succession. The key to understanding this is that Gonum supports the tiling of charts so if you want four charts arranged in 2x2 fashion, it can be done. Having four charts in a row is simply a special case of a 4x1 layout.

We can arrange the layouts using a function, as follows:

func plotDecomposed(xs []time.Time, a stl.Result) [][]*plot.Plot {
plots := make([][]*plot.Plot, 4)
plots[0] = []*plot.Plot{newTSPlot(xs, a.Data, "Data")}
plots[1] = []*plot.Plot{newTSPlot(xs, a.Trend, "Trend")}
plots[2] = []*plot.Plot{newTSPlot(xs, a.Seasonal, "Seasonal")}
plots[3] = []*plot.Plot{newResidPlot(xs, a.Resid, "Residuals")}

return plots
}

Having acquired [][]*plot.Plot, we need to tell Gonum the tiling format that we're interested in, so the following code snippet defines the tiling format:

  t := draw.Tiles{
Rows: rows,
Cols: cols,
}

If you're following along with the code, you will realize that rows is 3 and cols is 1.

Next, we have to provide a canvas to draw on:

    img := vgimg.New(width*vg.Centimeter, height*vg.Centimeter)
dc := draw.New(img)

Here, we use the vgimg backend because we want to write to a PNG image. If, for example, you want to set the DPI of the image, you may use vgimg.NewWith instead, and pass in the DPI option.

dc is draw.Canvas initiated from the large piece of canvas img. Now comes the magic: canvases := plot.Align(at, t, dc) basically splits the big canvas (img) into various smaller canvases—they're still part of the big canvas, but now, each *plot.Plot object gets allocated a smaller piece of the canvas, each with their own coordinate systems that are relative to the bigger canvas.

The following code simply draws the plots onto their respective mini-canvases:

    for i := 0; i < t.Rows; i++ {
for j := 0; j < t.Cols; j++ {
at[i][j].Draw(canvases[i][j])
}
}

Naturally, this process can be recursively repeated. A Legend object in *plot.Plot simply gets a smaller chunk of the canvas, and drawing a straight line from minimum X to maximum X simply draws a horizontal line across the entire mini canvas.

And this is how plots are made.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.151.220