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 }