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

     1  // Copyright ©2020 The go-hep 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 hplot
     6  
     7  import (
     8  	"fmt"
     9  	"image/color"
    10  	"io"
    11  	"math"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"go-hep.org/x/hep/hplot/vgop"
    17  	"gonum.org/v1/plot/vg"
    18  	"gonum.org/v1/plot/vg/draw"
    19  	"gonum.org/v1/plot/vg/vgeps"
    20  	"gonum.org/v1/plot/vg/vgimg"
    21  	"gonum.org/v1/plot/vg/vgpdf"
    22  	"gonum.org/v1/plot/vg/vgsvg"
    23  	"gonum.org/v1/plot/vg/vgtex"
    24  )
    25  
    26  // Drawer is the interface that wraps the Draw method.
    27  type Drawer interface {
    28  	Draw(draw.Canvas)
    29  }
    30  
    31  // Save saves the plot to an image file.  The file format is determined
    32  // by the extension.
    33  //
    34  // Supported extensions are:
    35  //
    36  //	.eps, .jpg, .jpeg, .json, .pdf, .png, .svg, .tex, .tif and .tiff.
    37  //
    38  // If w or h are <= 0, the value is chosen such that it follows the Golden Ratio.
    39  // If w and h are <= 0, the values are chosen such that they follow the Golden Ratio
    40  // (the width is defaulted to vgimg.DefaultWidth).
    41  func Save(p Drawer, w, h vg.Length, fnames ...string) (err error) {
    42  	if len(fnames) == 0 {
    43  		return fmt.Errorf("hplot: need at least 1 file name")
    44  	}
    45  
    46  	w, h = Dims(w, h)
    47  
    48  	save := func(file string) error {
    49  		format := strings.ToLower(filepath.Ext(file))
    50  		if len(format) != 0 {
    51  			format = format[1:]
    52  		}
    53  
    54  		dc, err := WriterTo(p, w, h, format)
    55  		if err != nil {
    56  			return err
    57  		}
    58  
    59  		f, err := os.Create(file)
    60  		if err != nil {
    61  			return err
    62  		}
    63  		defer f.Close()
    64  
    65  		_, err = dc.WriteTo(f)
    66  		if err != nil {
    67  			return err
    68  		}
    69  
    70  		err = f.Close()
    71  		if err != nil {
    72  			return err
    73  		}
    74  
    75  		if format == "tex" {
    76  			if fig, ok := p.(*Fig); ok {
    77  				err = fig.Latex.CompileLatex(file)
    78  				if err != nil {
    79  					return fmt.Errorf("hplot: could not generate PDF: %w", err)
    80  				}
    81  			}
    82  		}
    83  		return nil
    84  	}
    85  
    86  	for _, file := range fnames {
    87  		err := save(file)
    88  		if err != nil {
    89  			return fmt.Errorf("hplot: could not save plot: %w", err)
    90  		}
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // WriterTo returns an io.WriterTo that will write the plots as
    97  // the specified image format.
    98  //
    99  // Supported formats are the same ones than hplot.Save.
   100  //
   101  // If w or h are <= 0, the value is chosen such that it follows the Golden Ratio.
   102  // If w and h are <= 0, the values are chosen such that they follow the Golden Ratio
   103  // (the width is defaulted to vgimg.DefaultWidth).
   104  func WriterTo(p Drawer, w, h vg.Length, format string) (io.WriterTo, error) {
   105  	w, h = Dims(w, h)
   106  
   107  	dpi := float64(vgimg.DefaultDPI)
   108  	if fig, ok := p.(*Fig); ok {
   109  		dpi = fig.DPI
   110  	}
   111  
   112  	c, err := newFormattedCanvas(w, h, format, dpi)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("hplot: could not create canvas: %w", err)
   115  	}
   116  	p.Draw(draw.New(c))
   117  
   118  	return c, nil
   119  }
   120  
   121  // newFormattedCanvas creates a new vg.CanvasWriterTo with the specified
   122  // image format.
   123  //
   124  // Supported formats are:
   125  //
   126  //	eps, jpg|jpeg, json, pdf, png, svg, tex and tif|tiff.
   127  func newFormattedCanvas(w, h vg.Length, format string, dpi float64) (vg.CanvasWriterTo, error) {
   128  	var c vg.CanvasWriterTo
   129  	switch format {
   130  	case "eps":
   131  		c = vgeps.New(w, h)
   132  
   133  	case "jpg", "jpeg":
   134  		c = vgimg.JpegCanvas{Canvas: vgimg.NewWith(
   135  			vgimg.UseDPI(int(dpi)),
   136  			vgimg.UseWH(w, h),
   137  		)}
   138  
   139  	case "json":
   140  		c = vgop.NewJSON(vgop.WithSize(w, h))
   141  
   142  	case "pdf":
   143  		c = vgpdf.New(w, h)
   144  
   145  	case "png":
   146  		c = vgimg.PngCanvas{Canvas: vgimg.NewWith(
   147  			vgimg.UseDPI(int(dpi)),
   148  			vgimg.UseWH(w, h),
   149  		)}
   150  
   151  	case "svg":
   152  		c = vgsvg.New(w, h)
   153  
   154  	case "tex":
   155  		c = vgtex.NewDocument(w, h)
   156  
   157  	case "tif", "tiff":
   158  		c = vgimg.TiffCanvas{Canvas: vgimg.NewWith(
   159  			vgimg.UseDPI(int(dpi)),
   160  			vgimg.UseWH(w, h),
   161  		)}
   162  
   163  	default:
   164  		return nil, fmt.Errorf("unsupported format: %q", format)
   165  	}
   166  	return c, nil
   167  }
   168  
   169  func vgtexBorder(dc draw.Canvas) {
   170  	switch dc.Canvas.(type) {
   171  	case *vgtex.Canvas:
   172  		// prevent pgf/tikz to crop-out the bounding box
   173  		// by filling the whole image with a transparent box.
   174  		dc.FillPolygon(color.Transparent, []vg.Point{
   175  			{X: dc.Min.X, Y: dc.Min.Y},
   176  			{X: dc.Max.X, Y: dc.Min.Y},
   177  			{X: dc.Max.X, Y: dc.Max.Y},
   178  			{X: dc.Min.X, Y: dc.Max.Y},
   179  		})
   180  	}
   181  }
   182  
   183  func Dims(width, height vg.Length) (w, h vg.Length) {
   184  	w = width
   185  	h = height
   186  	switch {
   187  	case w <= 0 && h <= 0:
   188  		w = vgimg.DefaultWidth
   189  		h = vgimg.DefaultWidth / math.Phi
   190  	case w <= 0:
   191  		w = h * math.Phi
   192  	case h <= 0:
   193  		h = w / math.Phi
   194  	}
   195  	return w, h
   196  }