├── LICENSE ├── vg ├── testdata │ ├── width_-1.jpg │ ├── width_-1.pdf │ ├── width_-1.png │ ├── width_0.jpg │ ├── width_0.pdf │ ├── width_0.png │ ├── width_0.tiff │ ├── width_1.jpg │ ├── width_1.pdf │ ├── width_1.png │ ├── width_1.tiff │ ├── width_-1.tiff │ ├── width_0.svg │ ├── width_-1.svg │ └── width_1.svg ├── vgpdf │ └── testdata │ │ ├── arc_golden.pdf │ │ ├── issue540_golden.pdf │ │ ├── multipage_golden.pdf │ │ ├── disable-embedded-fonts_golden.pdf │ │ └── enable-embedded-fonts_golden.pdf ├── vgimg │ ├── testdata │ │ ├── issue179_golden.jpg │ │ └── issue540_golden.png │ └── vgimg_test.go ├── fonts │ └── fonts.go ├── font_syscall_test.go ├── draw │ ├── split_horiz_example_test.go │ ├── split_vertical_example_test.go │ └── draw_test.go ├── font_syscall.go ├── vgtex │ └── canvas_test.go ├── geom.go ├── len.go ├── recorder │ └── recorder_test.go ├── vgsvg │ ├── vgsvg_test.go │ └── testdata │ │ ├── scatter_golden.svg │ │ └── scatter_line_golden.svg └── vg_test.go ├── plotter ├── testdata │ ├── gopher.png │ ├── step_golden.png │ ├── field_golden.png │ ├── barChart2_golden.png │ ├── bubbles_golden.png │ ├── errorBars_golden.png │ ├── functions_golden.png │ ├── gopher_running.png │ ├── heatMap_golden.png │ ├── histogram_golden.png │ ├── image_plot_input.png │ ├── logscale_golden.png │ ├── plotLogo_golden.png │ ├── precision_golden.png │ ├── rotation_golden.png │ ├── scatter_golden.png │ ├── color_field_golden.png │ ├── filledLine_golden.png │ ├── image_plot_golden.png │ ├── timeseries_golden.png │ ├── gopher_field_golden.png │ ├── groupedBoxPlot_golden.png │ ├── histogram_logy_golden.png │ ├── image_plot_log_golden.png │ ├── polygon_holes_golden.pdf │ ├── polygon_holes_golden.png │ ├── sankeyGrouped_golden.png │ ├── sankeySimple_golden.png │ ├── scatterColor_golden.png │ ├── colorBarVertical_golden.png │ ├── groupedQuartPlot_golden.png │ ├── invertedlogscale_golden.png │ ├── polygon_hexagons_golden.png │ ├── stackedBarChart_golden.png │ ├── verticalBarChart_golden.png │ ├── verticalBoxPlot_golden.png │ ├── clippedFilledLine_golden.png │ ├── colorBarHorizontal_golden.png │ ├── horizontalBarChart_golden.png │ ├── horizontalBoxPlot_golden.png │ ├── horizontalQuartPlot_golden.png │ ├── verticalQuartPlot_golden.png │ ├── colorBarHorizontalLog_golden.png │ ├── barChart_positiveNegative_golden.png │ ├── polygon_holes_golden.eps │ └── polygon_holes_golden.svg ├── precision_test.go ├── glyphbox.go ├── palettethumbnailer.go ├── logscale_example_test.go ├── functions_test.go ├── invertedaxis_example_test.go ├── functions.go ├── general_test.go ├── grid.go ├── timeseries_test.go ├── errbars_test.go ├── bubbles_test.go ├── colorbar_test.go ├── scatter_test.go ├── scatter.go ├── image_test.go ├── step_test.go ├── volcano ├── filledLine_test.go ├── colorbar.go ├── rotation_test.go ├── labels.go ├── quartile_test.go ├── histogram_test.go ├── scatterColor_test.go ├── polygon.go ├── image.go ├── heat_test.go └── boxplot_test.go ├── testdata ├── align_golden.png └── legend_standalone_golden.png ├── .travis ├── check-imports.sh ├── check-copyright.sh ├── check-formatting.sh └── test-coverage.sh ├── cmpimg ├── testdata │ ├── failed_input.png │ └── good_golden.png ├── checkplot.go └── cmpimg_test.go ├── palette ├── testdata │ ├── reverse_golden.png │ └── reverse_palette_golden.png ├── moreland │ ├── testdata │ │ └── moreland_golden.png │ ├── moreland.go │ └── example_test.go ├── reverse.go ├── reverse_test.go ├── hsva_test.go ├── hsva.go └── palette_test.go ├── AUTHORS ├── .gitignore ├── go.mod ├── .github ├── PULL_REQUEST_TEMPLATE └── ISSUE_TEMPLATE ├── appveyor.yml ├── doc.go ├── gob ├── gob.go └── gob_test.go ├── .travis.yml ├── plotutil ├── errorpoints_test.go ├── plotutil.go ├── errorpoints.go └── main.go ├── go.sum ├── tools └── bezier │ └── bezier.go ├── README.md ├── align_test.go ├── legend_test.go ├── align.go └── axis_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | See the gonum LICENSE file at https://github.com/gonum/license. 2 | -------------------------------------------------------------------------------- /vg/testdata/width_-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_-1.jpg -------------------------------------------------------------------------------- /vg/testdata/width_-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_-1.pdf -------------------------------------------------------------------------------- /vg/testdata/width_-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_-1.png -------------------------------------------------------------------------------- /vg/testdata/width_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_0.jpg -------------------------------------------------------------------------------- /vg/testdata/width_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_0.pdf -------------------------------------------------------------------------------- /vg/testdata/width_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_0.png -------------------------------------------------------------------------------- /vg/testdata/width_0.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_0.tiff -------------------------------------------------------------------------------- /vg/testdata/width_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_1.jpg -------------------------------------------------------------------------------- /vg/testdata/width_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_1.pdf -------------------------------------------------------------------------------- /vg/testdata/width_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_1.png -------------------------------------------------------------------------------- /vg/testdata/width_1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_1.tiff -------------------------------------------------------------------------------- /plotter/testdata/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/gopher.png -------------------------------------------------------------------------------- /testdata/align_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/testdata/align_golden.png -------------------------------------------------------------------------------- /vg/testdata/width_-1.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/testdata/width_-1.tiff -------------------------------------------------------------------------------- /.travis/check-imports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | check-imports -b "math/rand,github.com/gonum/.*" 5 | -------------------------------------------------------------------------------- /cmpimg/testdata/failed_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/cmpimg/testdata/failed_input.png -------------------------------------------------------------------------------- /cmpimg/testdata/good_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/cmpimg/testdata/good_golden.png -------------------------------------------------------------------------------- /plotter/testdata/step_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/step_golden.png -------------------------------------------------------------------------------- /vg/vgpdf/testdata/arc_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgpdf/testdata/arc_golden.pdf -------------------------------------------------------------------------------- /plotter/testdata/field_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/field_golden.png -------------------------------------------------------------------------------- /palette/testdata/reverse_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/palette/testdata/reverse_golden.png -------------------------------------------------------------------------------- /plotter/testdata/barChart2_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/barChart2_golden.png -------------------------------------------------------------------------------- /plotter/testdata/bubbles_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/bubbles_golden.png -------------------------------------------------------------------------------- /plotter/testdata/errorBars_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/errorBars_golden.png -------------------------------------------------------------------------------- /plotter/testdata/functions_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/functions_golden.png -------------------------------------------------------------------------------- /plotter/testdata/gopher_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/gopher_running.png -------------------------------------------------------------------------------- /plotter/testdata/heatMap_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/heatMap_golden.png -------------------------------------------------------------------------------- /plotter/testdata/histogram_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/histogram_golden.png -------------------------------------------------------------------------------- /plotter/testdata/image_plot_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/image_plot_input.png -------------------------------------------------------------------------------- /plotter/testdata/logscale_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/logscale_golden.png -------------------------------------------------------------------------------- /plotter/testdata/plotLogo_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/plotLogo_golden.png -------------------------------------------------------------------------------- /plotter/testdata/precision_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/precision_golden.png -------------------------------------------------------------------------------- /plotter/testdata/rotation_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/rotation_golden.png -------------------------------------------------------------------------------- /plotter/testdata/scatter_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/scatter_golden.png -------------------------------------------------------------------------------- /testdata/legend_standalone_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/testdata/legend_standalone_golden.png -------------------------------------------------------------------------------- /vg/vgimg/testdata/issue179_golden.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgimg/testdata/issue179_golden.jpg -------------------------------------------------------------------------------- /vg/vgimg/testdata/issue540_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgimg/testdata/issue540_golden.png -------------------------------------------------------------------------------- /vg/vgpdf/testdata/issue540_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgpdf/testdata/issue540_golden.pdf -------------------------------------------------------------------------------- /plotter/testdata/color_field_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/color_field_golden.png -------------------------------------------------------------------------------- /plotter/testdata/filledLine_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/filledLine_golden.png -------------------------------------------------------------------------------- /plotter/testdata/image_plot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/image_plot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/timeseries_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/timeseries_golden.png -------------------------------------------------------------------------------- /vg/vgpdf/testdata/multipage_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgpdf/testdata/multipage_golden.pdf -------------------------------------------------------------------------------- /plotter/testdata/gopher_field_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/gopher_field_golden.png -------------------------------------------------------------------------------- /plotter/testdata/groupedBoxPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/groupedBoxPlot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/histogram_logy_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/histogram_logy_golden.png -------------------------------------------------------------------------------- /plotter/testdata/image_plot_log_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/image_plot_log_golden.png -------------------------------------------------------------------------------- /plotter/testdata/polygon_holes_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/polygon_holes_golden.pdf -------------------------------------------------------------------------------- /plotter/testdata/polygon_holes_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/polygon_holes_golden.png -------------------------------------------------------------------------------- /plotter/testdata/sankeyGrouped_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/sankeyGrouped_golden.png -------------------------------------------------------------------------------- /plotter/testdata/sankeySimple_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/sankeySimple_golden.png -------------------------------------------------------------------------------- /plotter/testdata/scatterColor_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/scatterColor_golden.png -------------------------------------------------------------------------------- /palette/testdata/reverse_palette_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/palette/testdata/reverse_palette_golden.png -------------------------------------------------------------------------------- /plotter/testdata/colorBarVertical_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/colorBarVertical_golden.png -------------------------------------------------------------------------------- /plotter/testdata/groupedQuartPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/groupedQuartPlot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/invertedlogscale_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/invertedlogscale_golden.png -------------------------------------------------------------------------------- /plotter/testdata/polygon_hexagons_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/polygon_hexagons_golden.png -------------------------------------------------------------------------------- /plotter/testdata/stackedBarChart_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/stackedBarChart_golden.png -------------------------------------------------------------------------------- /plotter/testdata/verticalBarChart_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/verticalBarChart_golden.png -------------------------------------------------------------------------------- /plotter/testdata/verticalBoxPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/verticalBoxPlot_golden.png -------------------------------------------------------------------------------- /palette/moreland/testdata/moreland_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/palette/moreland/testdata/moreland_golden.png -------------------------------------------------------------------------------- /plotter/testdata/clippedFilledLine_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/clippedFilledLine_golden.png -------------------------------------------------------------------------------- /plotter/testdata/colorBarHorizontal_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/colorBarHorizontal_golden.png -------------------------------------------------------------------------------- /plotter/testdata/horizontalBarChart_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/horizontalBarChart_golden.png -------------------------------------------------------------------------------- /plotter/testdata/horizontalBoxPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/horizontalBoxPlot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/horizontalQuartPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/horizontalQuartPlot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/verticalQuartPlot_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/verticalQuartPlot_golden.png -------------------------------------------------------------------------------- /plotter/testdata/colorBarHorizontalLog_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/colorBarHorizontalLog_golden.png -------------------------------------------------------------------------------- /.travis/check-copyright.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | check-copyright -notice "Copyright ©20[0-9]{2} The Gonum Authors\. All rights reserved\." 5 | -------------------------------------------------------------------------------- /vg/vgpdf/testdata/disable-embedded-fonts_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgpdf/testdata/disable-embedded-fonts_golden.pdf -------------------------------------------------------------------------------- /vg/vgpdf/testdata/enable-embedded-fonts_golden.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/vg/vgpdf/testdata/enable-embedded-fonts_golden.pdf -------------------------------------------------------------------------------- /plotter/testdata/barChart_positiveNegative_golden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/plot/master/plotter/testdata/barChart_positiveNegative_golden.png -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ethan Burns 2 | Steve McCoy 3 | Dan Kortschak 4 | James Bell 5 | Sebastien Binet 6 | -------------------------------------------------------------------------------- /.travis/check-formatting.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | test -z "$(goimports -d .)" 4 | if [[ -n "$(gofmt -s -l .)" ]]; then 5 | echo -e '\e[31mCode not gofmt simplified in:\n\n' 6 | gofmt -s -l . 7 | echo -e "\e[0" 8 | fi 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | **/*.png 3 | !**/testdata/*_golden.png 4 | !**/testdata/*_input.png 5 | !**/testdata/*_golden.svg 6 | **/*.eps 7 | **/*.pdf 8 | **/*.jpg 9 | **/*.jpeg 10 | **/*.tex 11 | **/*.tif 12 | **/*.tiff 13 | -------------------------------------------------------------------------------- /vg/fonts/fonts.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 fonts provides the Liberation fonts from https://fedorahosted.org/liberation-fonts/ 6 | package fonts // import "gonum.org/v1/plot/vg/fonts" 7 | 8 | //go:generate go run mk-fonts.go 9 | -------------------------------------------------------------------------------- /vg/font_syscall_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 | // +build !js 6 | 7 | package vg_test 8 | 9 | import ( 10 | "testing" 11 | 12 | "gonum.org/v1/plot/vg" 13 | ) 14 | 15 | func TestVGFONTPATH(t *testing.T) { 16 | if len(vg.FontDirs) == 0 { 17 | t.Fatalf("zero length FontDirs") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gonum.org/v1/plot 2 | 3 | require ( 4 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af 5 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 6 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 7 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 8 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f 9 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 10 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4 11 | rsc.io/pdf v0.1.1 12 | ) 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | Please take a look. 2 | 3 | 17 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: off 2 | 3 | clone_folder: c:\gopath\src\gonum.org\v1\plot 4 | 5 | cache: 6 | - '%LocalAppData%\go-build' 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | environment: 13 | # Do not move these lines; they are referred to by README.md. 14 | # Versions of go that are explicitly supported by gonum. 15 | matrix: 16 | - GOROOT: 'c:\go110' 17 | - GOROOT: 'c:\go111' 18 | # - GOROOT: 'c:\go112' 19 | GOPATH: c:\gopath 20 | GOTOOLDIR: '%GOROOT%\pkg\tool\windows_amd64' 21 | PATH: '%GOPATH%\bin;%GOROOT%\bin;%PATH%' 22 | GO111MODULE: 'on' 23 | 24 | build_script: 25 | - "%GOROOT%\\bin\\go version" 26 | - "%GOROOT%\\bin\\go get -v -t ./..." 27 | 28 | test_script: 29 | - "%GOROOT%\\bin\\go test ./..." 30 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plot provides an API for setting up plots, and primitives for 6 | // drawing on plots. 7 | // 8 | // Plot is the basic type for creating a plot, setting the title, axis 9 | // labels, legend, tick marks, etc. Types implementing the Plotter 10 | // interface can draw to the data area of a plot using the primitives 11 | // made available by this package. Some standard implementations 12 | // of the Plotter interface can be found in the 13 | // gonum.org/v1/plot/plotter package 14 | // which is documented here: 15 | // https://godoc.org/gonum.org/v1/plot/plotter 16 | package plot // import "gonum.org/v1/plot" 17 | -------------------------------------------------------------------------------- /vg/draw/split_horiz_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 draw_test 6 | 7 | import ( 8 | "fmt" 9 | 10 | "gonum.org/v1/plot/vg" 11 | "gonum.org/v1/plot/vg/draw" 12 | ) 13 | 14 | // SplitHorizontal returns the left and right portions of c after splitting it 15 | // along a vertical line distance x from the left of c. 16 | func SplitHorizontal(c draw.Canvas, x vg.Length) (left, right draw.Canvas) { 17 | return draw.Crop(c, 0, c.Min.X-c.Max.X+x, 0, 0), draw.Crop(c, x, 0, 0, 0) 18 | } 19 | 20 | func ExampleCrop_splitHorizontal() { 21 | var c draw.Canvas 22 | // Split c along a vertical line centered on the canvas. 23 | left, right := SplitHorizontal(c, c.Size().X/2) 24 | fmt.Println(left.Rectangle.Size(), right.Rectangle.Size()) 25 | } 26 | -------------------------------------------------------------------------------- /vg/draw/split_vertical_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 draw_test 6 | 7 | import ( 8 | "fmt" 9 | 10 | "gonum.org/v1/plot/vg" 11 | "gonum.org/v1/plot/vg/draw" 12 | ) 13 | 14 | // SplitVertical returns the lower and upper portions of c after 15 | // splitting it along a horizontal line distance y from the 16 | // bottom of c. 17 | func SplitVertical(c draw.Canvas, y vg.Length) (lower, upper draw.Canvas) { 18 | return draw.Crop(c, 0, 0, 0, c.Min.Y-c.Max.Y+y), draw.Crop(c, 0, 0, y, 0) 19 | } 20 | 21 | func ExampleCrop_splitVertical() { 22 | var c draw.Canvas 23 | // Split c along a horizontal line centered on the canvas. 24 | bottom, top := SplitHorizontal(c, c.Size().Y/2) 25 | fmt.Println(bottom.Rectangle.Size(), top.Rectangle.Size()) 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 10 | ### What are you trying to do? 11 | 12 | 13 | ### What did you do? 14 | 15 | 16 | 17 | ### What did you expect to happen? 18 | 19 | 20 | 21 | ### What actually happened? 22 | 23 | 24 | 25 | ### What version of Go and Gonum/plot are you using? 26 | 30 | 31 | 32 | ### Does this issue reproduce with the current master? 33 | 34 | -------------------------------------------------------------------------------- /palette/reverse.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 palette 6 | 7 | import ( 8 | "image/color" 9 | ) 10 | 11 | // Reverse reverses the direction of ColorMap c. 12 | func Reverse(c ColorMap) ColorMap { 13 | return reverse{ColorMap: c} 14 | } 15 | 16 | // reverse is a ColorMap that reverses the direction of the ColorMap it 17 | // contains. 18 | type reverse struct { 19 | ColorMap 20 | } 21 | 22 | // At implements the ColorMap interface for a Reversed ColorMap. 23 | func (r reverse) At(v float64) (color.Color, error) { 24 | return r.ColorMap.At(r.Max() - (v - r.Min())) 25 | } 26 | 27 | // Palette implements the ColorMap interface for a Reversed ColorMap. 28 | func (r reverse) Palette(colors int) Palette { 29 | c := r.ColorMap.Palette(colors).Colors() 30 | c2 := make([]color.Color, len(c)) 31 | for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 { 32 | c2[i], c2[j] = c[j], c[i] 33 | } 34 | return palette(c2) 35 | } 36 | -------------------------------------------------------------------------------- /plotter/precision_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plotter_test 6 | 7 | import ( 8 | "log" 9 | "testing" 10 | 11 | "gonum.org/v1/plot" 12 | "gonum.org/v1/plot/cmpimg" 13 | "gonum.org/v1/plot/plotter" 14 | ) 15 | 16 | func TestFloatPrecision(t *testing.T) { 17 | const fname = "precision.png" 18 | 19 | cmpimg.CheckPlot(func() { 20 | p, err := plot.New() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | p.X.Label.Text = "x" 26 | p.Y.Label.Text = "y" 27 | 28 | var data = make(plotter.XYs, 10) 29 | for i := range data { 30 | data[i].X = float64(i) 31 | data[i].Y = 1300 32 | } 33 | 34 | lines, points, err := plotter.NewLinePoints(data) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | p.Add(points, lines) 39 | p.Add(plotter.NewGrid()) 40 | 41 | err = p.Save(300, 300, "testdata/"+fname) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | }, t, fname) 46 | } 47 | -------------------------------------------------------------------------------- /vg/font_syscall.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 | // +build !js 6 | 7 | package vg 8 | 9 | import ( 10 | "go/build" 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | const ( 16 | // importString is the import string expected for 17 | // this package. It is used to find the font 18 | // directory included with the package source. 19 | importString = "gonum.org/v1/plot/vg" 20 | ) 21 | 22 | func init() { 23 | FontDirs = initFontDirs() 24 | } 25 | 26 | // InitFontDirs returns the initial value for the FontDirectories variable. 27 | func initFontDirs() []string { 28 | dirs := filepath.SplitList(os.Getenv("VGFONTPATH")) 29 | 30 | if pkg, err := build.Import(importString, "", build.FindOnly); err == nil { 31 | p := filepath.Join(pkg.Dir, "fonts") 32 | if _, err := os.Stat(p); err == nil { 33 | dirs = append(dirs, p) 34 | } 35 | } 36 | 37 | if len(dirs) == 0 { 38 | dirs = []string{"./fonts"} 39 | } 40 | 41 | return dirs 42 | } 43 | -------------------------------------------------------------------------------- /plotter/glyphbox.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter 6 | 7 | import ( 8 | "image/color" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/vg" 12 | "gonum.org/v1/plot/vg/draw" 13 | ) 14 | 15 | // GlyphBoxes implements the Plotter interface, drawing 16 | // all of the glyph boxes of the plot. This is intended for 17 | // debugging. 18 | type GlyphBoxes struct { 19 | draw.LineStyle 20 | } 21 | 22 | func NewGlyphBoxes() *GlyphBoxes { 23 | g := new(GlyphBoxes) 24 | g.Color = color.RGBA{R: 255, A: 255} 25 | g.Width = vg.Points(0.25) 26 | return g 27 | } 28 | 29 | func (g GlyphBoxes) Plot(c draw.Canvas, plt *plot.Plot) { 30 | for _, b := range plt.GlyphBoxes(plt) { 31 | x := c.X(b.X) + b.Rectangle.Min.X 32 | y := c.Y(b.Y) + b.Rectangle.Min.Y 33 | c.StrokeLines(g.LineStyle, []vg.Point{ 34 | {x, y}, 35 | {x + b.Rectangle.Size().X, y}, 36 | {x + b.Rectangle.Size().X, y + b.Rectangle.Size().Y}, 37 | {x, y + b.Rectangle.Size().Y}, 38 | {x, y}, 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /palette/moreland/moreland.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 | // Some descriptive text is taken from http://www.kennethmoreland.com/, 6 | // ©2009--2016 Kenneth Moreland. 7 | 8 | // Color conversion functions are largely based on a spreadsheet 9 | // (http://www.kennethmoreland.com/color-maps/DivergingColorMapWorkshop.xls) 10 | // and a Python script (http://www.kennethmoreland.com/color-maps/diverging_map.py), 11 | // which are copyright their respective authors. 12 | 13 | // Package moreland provides color maps for pseudocoloring scalar fields. 14 | // The color maps are described at http://www.kennethmoreland.com/color-advice/ 15 | // and in the following publications: 16 | // 17 | // "Why We Use Bad Color Maps and What You Can Do About It." Kenneth Moreland. 18 | // In Proceedings of Human Vision and Electronic Imaging (HVEI), 2016. (To appear) 19 | // 20 | // "Diverging Color Maps for Scientific Visualization." Kenneth Moreland. 21 | // In Proceedings of the 5th International Symposium on Visual Computing, 22 | // December 2009. DOI 10.1007/978-3-642-10520-3_9. 23 | package moreland // import "gonum.org/v1/plot/palette/moreland" 24 | -------------------------------------------------------------------------------- /gob/gob.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 gob // import "gonum.org/v1/plot/gob" 6 | 7 | import ( 8 | "encoding/gob" 9 | "image/color" 10 | 11 | "gonum.org/v1/plot" 12 | "gonum.org/v1/plot/plotter" 13 | ) 14 | 15 | func init() { 16 | // register types for proper gob-encoding/decoding 17 | gob.Register(color.Gray16{}) 18 | 19 | // plot.Ticker 20 | gob.Register(plot.ConstantTicks{}) 21 | gob.Register(plot.DefaultTicks{}) 22 | gob.Register(plot.LogTicks{}) 23 | 24 | // plot.Normalizer 25 | gob.Register(plot.LinearScale{}) 26 | gob.Register(plot.LogScale{}) 27 | 28 | // plot.Plotter 29 | gob.Register(plotter.BarChart{}) 30 | gob.Register(plotter.Histogram{}) 31 | gob.Register(plotter.BoxPlot{}) 32 | gob.Register(plotter.YErrorBars{}) 33 | gob.Register(plotter.XErrorBars{}) 34 | gob.Register(plotter.Function{}) 35 | gob.Register(plotter.GlyphBoxes{}) 36 | gob.Register(plotter.Grid{}) 37 | gob.Register(plotter.Labels{}) 38 | gob.Register(plotter.Line{}) 39 | gob.Register(plotter.QuartPlot{}) 40 | gob.Register(plotter.Scatter{}) 41 | 42 | // plotter.XYZer 43 | gob.Register(plotter.XYZs{}) 44 | gob.Register(plotter.XYValues{}) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /plotter/palettethumbnailer.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plotter 6 | 7 | import ( 8 | "image/color" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/palette" 12 | "gonum.org/v1/plot/vg" 13 | "gonum.org/v1/plot/vg/draw" 14 | ) 15 | 16 | // PaletteThumbnailers creates a slice of plot.Thumbnailers that can be used to 17 | // add legend entries for the colors in a color palette. 18 | func PaletteThumbnailers(p palette.Palette) []plot.Thumbnailer { 19 | colors := p.Colors() 20 | thumbnailers := make([]plot.Thumbnailer, len(colors)) 21 | for i, c := range colors { 22 | thumbnailers[i] = paletteThumbnailer{color: c} 23 | } 24 | return thumbnailers 25 | } 26 | 27 | // paletteThumbnailer implements the Thumbnailer interface 28 | // for color palettes. 29 | type paletteThumbnailer struct { 30 | color color.Color 31 | } 32 | 33 | // Thumbnail satisfies the plot.Thumbnailer interface. 34 | func (t paletteThumbnailer) Thumbnail(c *draw.Canvas) { 35 | pts := []vg.Point{ 36 | {X: c.Min.X, Y: c.Min.Y}, 37 | {X: c.Min.X, Y: c.Max.Y}, 38 | {X: c.Max.X, Y: c.Max.Y}, 39 | {X: c.Max.X, Y: c.Min.Y}, 40 | } 41 | poly := c.ClipPolygonY(pts) 42 | c.FillPolygon(t.color, poly) 43 | } 44 | -------------------------------------------------------------------------------- /plotter/logscale_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | ) 18 | 19 | // Example_logScale shows how to create a plot with a log-scale on the Y-axis. 20 | func Example_logScale() { 21 | p, err := plot.New() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | p.Title.Text = "My Plot" 27 | p.Y.Scale = plot.LogScale{} 28 | p.Y.Tick.Marker = plot.LogTicks{} 29 | p.X.Label.Text = "x" 30 | p.Y.Label.Text = "f(x)" 31 | 32 | f := plotter.NewFunction(math.Exp) 33 | f.XMin = 0.2 34 | f.XMax = 10 35 | f.Color = color.RGBA{R: 255, A: 255} 36 | 37 | p.Add(f, plotter.NewGrid()) 38 | p.Legend.Add("exp(x)", f) 39 | 40 | p.X.Min = f.XMin 41 | p.X.Max = f.XMax 42 | p.Y.Min = math.Exp(f.XMin) 43 | p.Y.Max = math.Exp(f.XMax) 44 | 45 | err = p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/logscale.png") 46 | if err != nil { 47 | log.Panic(err) 48 | } 49 | } 50 | 51 | func TestLogScale(t *testing.T) { 52 | cmpimg.CheckPlot(Example_logScale, t, "logscale.png") 53 | } 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | go_import_path: gonum.org/v1/plot 5 | 6 | # Versions of go that are explicitly supported by gonum. 7 | go: 8 | - 1.12.x 9 | - 1.11.x 10 | - 1.10.x 11 | - master 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - go: master 16 | 17 | env: 18 | global: 19 | - DISPLAY: ":99.0" 20 | - GO111MODULE: "on" 21 | 22 | # Get coverage tools, and start the virtual framebuffer. 23 | before_install: 24 | # Required for format check. 25 | - go get golang.org/x/tools/cmd/goimports 26 | # Required for imports check. 27 | - go get gonum.org/v1/tools/cmd/check-imports 28 | # Required for copyright header check. 29 | - go get gonum.org/v1/tools/cmd/check-copyright 30 | # Required for coverage. 31 | - go get golang.org/x/tools/cmd/cover 32 | - go get github.com/mattn/goveralls 33 | - sh -e /etc/init.d/xvfb start 34 | 35 | # Get deps, build, test, and ensure the code is gofmt'ed. 36 | # If we are building as gonum, then we have access to the coveralls api key, so we can run coverage as well. 37 | script: 38 | - ${TRAVIS_BUILD_DIR}/.travis/check-copyright.sh 39 | - ${TRAVIS_BUILD_DIR}/.travis/check-formatting.sh 40 | - go get -d -t -v ./... 41 | - go build -v ./... 42 | - go test -v ./... 43 | - test -z "$(gofmt -d .)" 44 | - if [[ $TRAVIS_SECURE_ENV_VARS = "true" ]]; then bash ./.travis/test-coverage.sh; fi 45 | - ${TRAVIS_BUILD_DIR}/.travis/check-imports.sh 46 | -------------------------------------------------------------------------------- /vg/vgtex/canvas_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 vgtex_test 6 | 7 | import ( 8 | "log" 9 | "os" 10 | "testing" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/cmpimg" 14 | "gonum.org/v1/plot/plotter" 15 | "gonum.org/v1/plot/vg" 16 | "gonum.org/v1/plot/vg/draw" 17 | "gonum.org/v1/plot/vg/vgtex" 18 | ) 19 | 20 | // An example of making a LaTeX plot. 21 | func Example() { 22 | scatter, err := plotter.NewScatter(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | p, err := plot.New() 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | p.Add(scatter) 31 | // p.HideAxes() 32 | p.Title.Text = `A scatter plot: $\sqrt{\frac{e^{3i\pi}}{2\cos 3\pi}}$` 33 | p.X.Label.Text = `$x = \eta$` 34 | p.Y.Label.Text = `$y$ is some $\Phi$` 35 | 36 | c := vgtex.NewDocument(5*vg.Centimeter, 5*vg.Centimeter) 37 | p.Draw(draw.New(c)) 38 | c.FillString(p.Title.Font, vg.Point{2.5 * vg.Centimeter, 2.5 * vg.Centimeter}, "x") 39 | 40 | f, err := os.Create("testdata/scatter.tex") 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | defer f.Close() 45 | 46 | if _, err = c.WriteTo(f); err != nil { 47 | log.Fatal(err) 48 | } 49 | } 50 | 51 | func TestTexCanvas(t *testing.T) { 52 | cmpimg.CheckPlot(Example, t, "scatter.tex") 53 | } 54 | -------------------------------------------------------------------------------- /.travis/test-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MODE=set 4 | PROFILE_OUT="${PWD}/profile.out" 5 | ACC_OUT="${PWD}/coverage.txt" 6 | 7 | testCover() { 8 | # set the return value to 0 (successful) 9 | retval=0 10 | # get the directory to check from the parameter. Default to '.' 11 | d=${1:-.} 12 | # skip if there are no Go files here 13 | ls $d/*.go &> /dev/null || return $retval 14 | # switch to the directory to check 15 | pushd $d > /dev/null 16 | # create the coverage profile 17 | coverageresult=$(go test $TAGS -coverprofile="${PROFILE_OUT}" -covermode=${MODE}) 18 | # output the result so we can check the shell output 19 | echo ${coverageresult} 20 | # append the results to acc.out if coverage didn't fail, else set the retval to 1 (failed) 21 | ( [[ ${coverageresult} == *FAIL* ]] && retval=1 ) || ( [ -f "${PROFILE_OUT}" ] && grep -v "mode: ${MODE}" "${PROFILE_OUT}" >> "${ACC_OUT}" ) 22 | # return to our working dir 23 | popd > /dev/null 24 | # return our return value 25 | return $retval 26 | } 27 | 28 | # Init coverage.txt 29 | echo "mode: ${MODE}" > $ACC_OUT 30 | 31 | # Run test coverage on all directories containing go files. 32 | find . -type d | while read d; do testCover $d || exit; done 33 | 34 | # Upload the coverage profile to coveralls.io 35 | [ -n "$COVERALLS_TOKEN" ] && ( goveralls -coverprofile=$ACC_OUT || echo -e '\n\e[31mCoveralls failed.\n' ) 36 | 37 | # Upload to coverage profile to codedov.io 38 | [ -n "$CODECOV_TOKEN" ] && ( bash <(curl -s https://codecov.io/bash) || echo -e '\n\e[31mCodecov failed.\n' ) 39 | -------------------------------------------------------------------------------- /plotutil/errorpoints_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotutil_test 6 | 7 | import ( 8 | "golang.org/x/exp/rand" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/plotter" 12 | "gonum.org/v1/plot/plotutil" 13 | ) 14 | 15 | func ExampleErrorPoints() { 16 | rnd := rand.New(rand.NewSource(1)) 17 | 18 | // Get some random data. 19 | n, m := 5, 10 20 | pts := make([]plotter.XYer, n) 21 | for i := range pts { 22 | xys := make(plotter.XYs, m) 23 | pts[i] = xys 24 | center := float64(i) 25 | for j := range xys { 26 | xys[j].X = center + (rnd.Float64() - 0.5) 27 | xys[j].Y = center + (rnd.Float64() - 0.5) 28 | } 29 | } 30 | 31 | plt, err := plot.New() 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | mean95, err := plotutil.NewErrorPoints(plotutil.MeanAndConf95, pts...) 37 | if err != nil { 38 | panic(err) 39 | } 40 | medMinMax, err := plotutil.NewErrorPoints(plotutil.MedianAndMinMax, pts...) 41 | if err != nil { 42 | panic(err) 43 | } 44 | err = plotutil.AddLinePoints(plt, 45 | "mean and 95% confidence", mean95, 46 | "median and minimum and maximum", medMinMax) 47 | if err != nil { 48 | panic(err) 49 | } 50 | if err := plotutil.AddErrorBars(plt, mean95, medMinMax); err != nil { 51 | panic(err) 52 | } 53 | if err := plotutil.AddScatters(plt, pts[0], pts[1], pts[2], pts[3], pts[4]); err != nil { 54 | panic(err) 55 | } 56 | 57 | err = plt.Save(4, 4, "centroids.png") 58 | if err != nil { 59 | panic(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vg/geom.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 vg 6 | 7 | // A Point is a location in 2d space. 8 | // 9 | // Points are used for drawing, not for data. For 10 | // data, see the XYer interface. 11 | type Point struct { 12 | X, Y Length 13 | } 14 | 15 | // Dot returns the dot product of two points. 16 | func (p Point) Dot(q Point) Length { 17 | return p.X*q.X + p.Y*q.Y 18 | } 19 | 20 | // Add returns the component-wise sum of two points. 21 | func (p Point) Add(q Point) Point { 22 | return Point{p.X + q.X, p.Y + q.Y} 23 | } 24 | 25 | // Sub returns the component-wise difference of two points. 26 | func (p Point) Sub(q Point) Point { 27 | return Point{p.X - q.X, p.Y - q.Y} 28 | } 29 | 30 | // Scale returns the component-wise product of a point and a scalar. 31 | func (p Point) Scale(s Length) Point { 32 | return Point{p.X * s, p.Y * s} 33 | } 34 | 35 | // A Rectangle represents a rectangular region of 2d space. 36 | type Rectangle struct { 37 | Min Point 38 | Max Point 39 | } 40 | 41 | // Size returns the width and height of a Rectangle. 42 | func (r Rectangle) Size() Point { 43 | return Point{ 44 | X: r.Max.X - r.Min.X, 45 | Y: r.Max.Y - r.Min.Y, 46 | } 47 | } 48 | 49 | // Path returns the path of a Rect specified by its 50 | // upper left corner, width and height. 51 | func (r Rectangle) Path() (p Path) { 52 | p.Move(r.Min) 53 | p.Line(Point{X: r.Max.X, Y: r.Min.Y}) 54 | p.Line(r.Max) 55 | p.Line(Point{X: r.Min.X, Y: r.Max.Y}) 56 | p.Close() 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /plotter/functions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | ) 18 | 19 | // ExampleFunction draws some functions. 20 | func ExampleFunction() { 21 | quad := plotter.NewFunction(func(x float64) float64 { return x * x }) 22 | quad.Color = color.RGBA{B: 255, A: 255} 23 | 24 | exp := plotter.NewFunction(func(x float64) float64 { return math.Pow(2, x) }) 25 | exp.Dashes = []vg.Length{vg.Points(2), vg.Points(2)} 26 | exp.Width = vg.Points(2) 27 | exp.Color = color.RGBA{G: 255, A: 255} 28 | 29 | sin := plotter.NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 }) 30 | sin.Dashes = []vg.Length{vg.Points(4), vg.Points(5)} 31 | sin.Width = vg.Points(4) 32 | sin.Color = color.RGBA{R: 255, A: 255} 33 | 34 | p, err := plot.New() 35 | if err != nil { 36 | log.Panic(err) 37 | } 38 | 39 | p.Title.Text = "Functions" 40 | p.X.Label.Text = "X" 41 | p.Y.Label.Text = "Y" 42 | 43 | p.Add(quad, exp, sin) 44 | p.Legend.Add("x^2", quad) 45 | p.Legend.Add("2^x", exp) 46 | p.Legend.Add("10*sin(x)+50", sin) 47 | p.Legend.ThumbnailWidth = 0.5 * vg.Inch 48 | 49 | p.X.Min = 0 50 | p.X.Max = 10 51 | p.Y.Min = 0 52 | p.Y.Max = 100 53 | 54 | err = p.Save(200, 200, "testdata/functions.png") 55 | if err != nil { 56 | log.Panic(err) 57 | } 58 | } 59 | 60 | func TestFunction(t *testing.T) { 61 | cmpimg.CheckPlot(ExampleFunction, t, "functions.png") 62 | } 63 | -------------------------------------------------------------------------------- /plotter/invertedaxis_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2018 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | ) 18 | 19 | func Example_invertedScale() { 20 | // This example is nearly identical to the LogScale, other than 21 | // both the X and Y axes are inverted. InvertedScale expects to act 22 | // on another Normalizer - which should allow for more flexibility 23 | p, err := plot.New() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | p.Title.Text = "Example of inverted axes" 29 | p.Y.Scale = plot.InvertedScale{Normalizer: plot.LogScale{}} 30 | p.X.Scale = plot.InvertedScale{Normalizer: plot.LinearScale{}} 31 | p.Y.Tick.Marker = plot.LogTicks{} 32 | p.X.Label.Text = "x" 33 | p.Y.Label.Text = "f(x)" 34 | 35 | f := plotter.NewFunction(math.Exp) 36 | f.XMin = 0.2 37 | f.XMax = 10 38 | 39 | f.Color = color.RGBA{R: 255, A: 255} 40 | 41 | p.Add(f, plotter.NewGrid()) 42 | p.Legend.Add("exp(x)", f) 43 | 44 | // Neither .Min nor .Max for the X and Y axes are 'swapped'. 45 | // The minimal value is retained in .Min, and the maximal 46 | // value stays in .Max. 47 | p.X.Min = f.XMin 48 | p.X.Max = f.XMax 49 | p.Y.Min = math.Exp(f.XMin) 50 | p.Y.Max = math.Exp(f.XMax) 51 | 52 | err = p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/invertedlogscale.png") 53 | if err != nil { 54 | log.Panic(err) 55 | } 56 | } 57 | 58 | func TestInvertedScale(t *testing.T) { 59 | cmpimg.CheckPlot(Example_invertedScale, t, "invertedlogscale.png") 60 | } 61 | -------------------------------------------------------------------------------- /plotter/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter 6 | 7 | import ( 8 | "gonum.org/v1/plot" 9 | "gonum.org/v1/plot/vg" 10 | "gonum.org/v1/plot/vg/draw" 11 | ) 12 | 13 | // Function implements the Plotter interface, 14 | // drawing a line for the given function. 15 | type Function struct { 16 | F func(x float64) (y float64) 17 | 18 | // XMin and XMax specify the range 19 | // of x values to pass to F. 20 | XMin, XMax float64 21 | 22 | Samples int 23 | 24 | draw.LineStyle 25 | } 26 | 27 | // NewFunction returns a Function that plots F using 28 | // the default line style with 50 samples. 29 | func NewFunction(f func(float64) float64) *Function { 30 | return &Function{ 31 | F: f, 32 | Samples: 50, 33 | LineStyle: DefaultLineStyle, 34 | } 35 | } 36 | 37 | // Plot implements the Plotter interface, drawing a line 38 | // that connects each point in the Line. 39 | func (f *Function) Plot(c draw.Canvas, p *plot.Plot) { 40 | trX, trY := p.Transforms(&c) 41 | 42 | min, max := f.XMin, f.XMax 43 | if min == 0 && max == 0 { 44 | min = p.X.Min 45 | max = p.X.Max 46 | } 47 | d := (max - min) / float64(f.Samples-1) 48 | line := make([]vg.Point, f.Samples) 49 | for i := range line { 50 | x := min + float64(i)*d 51 | line[i].X = trX(x) 52 | line[i].Y = trY(f.F(x)) 53 | } 54 | c.StrokeLines(f.LineStyle, c.ClipLinesXY(line)...) 55 | } 56 | 57 | // Thumbnail draws a line in the given style down the 58 | // center of a DrawArea as a thumbnail representation 59 | // of the LineStyle of the function. 60 | func (f Function) Thumbnail(c *draw.Canvas) { 61 | y := c.Center().Y 62 | c.StrokeLine2(f.LineStyle, c.Min.X, y, c.Max.X, y) 63 | } 64 | -------------------------------------------------------------------------------- /palette/reverse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 palette_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "testing" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/cmpimg" 14 | "gonum.org/v1/plot/palette" 15 | "gonum.org/v1/plot/palette/moreland" 16 | "gonum.org/v1/plot/plotter" 17 | ) 18 | 19 | // This example creates a color bar and a second color bar where the 20 | // direction of the colors are reversed. 21 | func ExampleReverse() { 22 | p, err := plot.New() 23 | if err != nil { 24 | log.Panic(err) 25 | } 26 | l := &plotter.ColorBar{ColorMap: moreland.Kindlmann()} 27 | l2 := &plotter.ColorBar{ColorMap: palette.Reverse(moreland.Kindlmann())} 28 | l.ColorMap.SetMin(0.5) 29 | l.ColorMap.SetMax(2.5) 30 | l2.ColorMap.SetMin(2.5) 31 | l2.ColorMap.SetMax(4.5) 32 | 33 | p.Add(l, l2) 34 | p.HideY() 35 | p.X.Padding = 0 36 | p.Title.Text = "A ColorMap and its Reverse" 37 | 38 | if err = p.Save(300, 48, "testdata/reverse.png"); err != nil { 39 | log.Panic(err) 40 | } 41 | } 42 | 43 | func TestReverse(t *testing.T) { 44 | cmpimg.CheckPlot(ExampleReverse, t, "reverse.png") 45 | } 46 | 47 | // This example creates a color palette from a reversed ColorMap. 48 | func ExampleReverse_Palette() { 49 | p, err := plot.New() 50 | if err != nil { 51 | log.Panic(err) 52 | } 53 | thumbs := plotter.PaletteThumbnailers(palette.Reverse(moreland.Kindlmann()).Palette(10)) 54 | for i, t := range thumbs { 55 | p.Legend.Add(fmt.Sprint(i), t) 56 | } 57 | p.HideAxes() 58 | p.X.Padding = 0 59 | p.Y.Padding = 0 60 | 61 | if err = p.Save(35, 120, "testdata/reverse_palette.png"); err != nil { 62 | log.Panic(err) 63 | } 64 | } 65 | 66 | func TestReverse_Palette(t *testing.T) { 67 | cmpimg.CheckPlot(ExampleReverse_Palette, t, "reverse_palette.png") 68 | } 69 | -------------------------------------------------------------------------------- /plotter/general_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "log" 9 | "testing" 10 | 11 | "gonum.org/v1/plot" 12 | "gonum.org/v1/plot/cmpimg" 13 | "gonum.org/v1/plot/plotter" 14 | "gonum.org/v1/plot/vg" 15 | ) 16 | 17 | // Draw the plot logo. 18 | func Example() { 19 | p, err := plot.New() 20 | if err != nil { 21 | log.Panic(err) 22 | } 23 | 24 | plotter.DefaultLineStyle.Width = vg.Points(1) 25 | plotter.DefaultGlyphStyle.Radius = vg.Points(3) 26 | 27 | p.Y.Tick.Marker = plot.ConstantTicks([]plot.Tick{ 28 | {0, "0"}, {0.25, ""}, {0.5, "0.5"}, {0.75, ""}, {1, "1"}, 29 | }) 30 | p.X.Tick.Marker = plot.ConstantTicks([]plot.Tick{ 31 | {0, "0"}, {0.25, ""}, {0.5, "0.5"}, {0.75, ""}, {1, "1"}, 32 | }) 33 | 34 | pts := plotter.XYs{{0, 0}, {0, 1}, {0.5, 1}, {0.5, 0.6}, {0, 0.6}} 35 | line, err := plotter.NewLine(pts) 36 | if err != nil { 37 | log.Panic(err) 38 | } 39 | scatter, err := plotter.NewScatter(pts) 40 | if err != nil { 41 | log.Panic(err) 42 | } 43 | p.Add(line, scatter) 44 | 45 | pts = plotter.XYs{{1, 0}, {0.75, 0}, {0.75, 0.75}} 46 | line, err = plotter.NewLine(pts) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | scatter, err = plotter.NewScatter(pts) 51 | if err != nil { 52 | log.Panic(err) 53 | } 54 | p.Add(line, scatter) 55 | 56 | pts = plotter.XYs{{0.5, 0.5}, {1, 0.5}} 57 | line, err = plotter.NewLine(pts) 58 | if err != nil { 59 | log.Panic(err) 60 | } 61 | scatter, err = plotter.NewScatter(pts) 62 | if err != nil { 63 | log.Panic(err) 64 | } 65 | p.Add(line, scatter) 66 | 67 | err = p.Save(100, 100, "testdata/plotLogo.png") 68 | if err != nil { 69 | log.Panic(err) 70 | } 71 | } 72 | 73 | func TestMainExample(t *testing.T) { 74 | cmpimg.CheckPlot(Example, t, "plotLogo.png") 75 | } 76 | -------------------------------------------------------------------------------- /vg/len.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 vg 6 | 7 | import ( 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // A Length is a unit-independent representation of length. 13 | // Internally, the length is stored in postscript points. 14 | type Length float64 15 | 16 | // Points returns a length for the given number of points. 17 | func Points(pt float64) Length { 18 | return Length(pt) 19 | } 20 | 21 | // Common lengths. 22 | const ( 23 | Inch Length = 72 24 | Centimeter = Inch / 2.54 25 | Millimeter = Centimeter / 10 26 | ) 27 | 28 | // Dots returns the length in dots for the given resolution. 29 | func (l Length) Dots(dpi float64) float64 { 30 | return float64(l) / Inch.Points() * dpi 31 | } 32 | 33 | // Points returns the length in postscript points. 34 | func (l Length) Points() float64 { 35 | return float64(l) 36 | } 37 | 38 | // ParseLength parses a Length string. 39 | // A Length string is a possible signed floating number with a unit. 40 | // e.g. "42cm" "2.4in" "66pt" 41 | // If no unit was given, ParseLength assumes it was (postscript) points. 42 | // Currently valid units are: 43 | // mm (millimeter) 44 | // cm (centimeter) 45 | // in (inch) 46 | // pt (point) 47 | func ParseLength(value string) (Length, error) { 48 | var unit Length = 1 49 | switch { 50 | case strings.HasSuffix(value, "in"): 51 | value = value[:len(value)-len("in")] 52 | unit = Inch 53 | case strings.HasSuffix(value, "cm"): 54 | value = value[:len(value)-len("cm")] 55 | unit = Centimeter 56 | case strings.HasSuffix(value, "mm"): 57 | value = value[:len(value)-len("mm")] 58 | unit = Millimeter 59 | case strings.HasSuffix(value, "pt"): 60 | value = value[:len(value)-len("pt")] 61 | unit = 1 62 | } 63 | v, err := strconv.ParseFloat(value, 64) 64 | if err != nil { 65 | return 0, err 66 | } 67 | return Length(v) * unit, nil 68 | } 69 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= 2 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 3 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg= 4 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 5 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 6 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 7 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= 8 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 9 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de h1:xSjD6HQTqT0H/k60N5yYBtnN1OEkVy7WIo/DYyxKRO0= 10 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 11 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f h1:9kQ594xxPWRNKfTOnPjPcgrIJ19zM3ic57aI7PbMyAA= 12 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 13 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= 14 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 15 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 16 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4 h1:nYxTaCPaVoJbxx+vMVnsFb6kw5+6aJCx52m/lmM/Vog= 17 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 18 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= 19 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 20 | -------------------------------------------------------------------------------- /plotter/grid.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter 6 | 7 | import ( 8 | "image/color" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/vg" 12 | "gonum.org/v1/plot/vg/draw" 13 | ) 14 | 15 | var ( 16 | // DefaultGridLineStyle is the default style for grid lines. 17 | DefaultGridLineStyle = draw.LineStyle{ 18 | Color: color.Gray{128}, 19 | Width: vg.Points(0.25), 20 | } 21 | ) 22 | 23 | // Grid implements the plot.Plotter interface, drawing 24 | // a set of grid lines at the major tick marks. 25 | type Grid struct { 26 | // Vertical is the style of the vertical lines. 27 | Vertical draw.LineStyle 28 | 29 | // Horizontal is the style of the horizontal lines. 30 | Horizontal draw.LineStyle 31 | } 32 | 33 | // NewGrid returns a new grid with both vertical and 34 | // horizontal lines using the default grid line style. 35 | func NewGrid() *Grid { 36 | return &Grid{ 37 | Vertical: DefaultGridLineStyle, 38 | Horizontal: DefaultGridLineStyle, 39 | } 40 | } 41 | 42 | // Plot implements the plot.Plotter interface. 43 | func (g *Grid) Plot(c draw.Canvas, plt *plot.Plot) { 44 | trX, trY := plt.Transforms(&c) 45 | 46 | var ( 47 | ymin = c.Min.Y 48 | ymax = c.Max.Y 49 | xmin = c.Min.X 50 | xmax = c.Max.X 51 | ) 52 | 53 | if g.Vertical.Color == nil { 54 | goto horiz 55 | } 56 | for _, tk := range plt.X.Tick.Marker.Ticks(plt.X.Min, plt.X.Max) { 57 | if tk.IsMinor() { 58 | continue 59 | } 60 | x := trX(tk.Value) 61 | if x > xmax || x < xmin { 62 | continue 63 | } 64 | c.StrokeLine2(g.Vertical, x, ymin, x, ymax) 65 | } 66 | 67 | horiz: 68 | if g.Horizontal.Color == nil { 69 | return 70 | } 71 | for _, tk := range plt.Y.Tick.Marker.Ticks(plt.Y.Min, plt.Y.Max) { 72 | if tk.IsMinor() { 73 | continue 74 | } 75 | y := trY(tk.Value) 76 | if y > ymax || y < ymin { 77 | continue 78 | } 79 | c.StrokeLine2(g.Horizontal, xmin, y, xmax, y) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /plotter/timeseries_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "testing" 11 | "time" 12 | 13 | "golang.org/x/exp/rand" 14 | 15 | "gonum.org/v1/plot" 16 | "gonum.org/v1/plot/cmpimg" 17 | "gonum.org/v1/plot/plotter" 18 | "gonum.org/v1/plot/vg" 19 | "gonum.org/v1/plot/vg/draw" 20 | ) 21 | 22 | // Example_timeSeries draws a time series. 23 | func Example_timeSeries() { 24 | rnd := rand.New(rand.NewSource(1)) 25 | 26 | // xticks defines how we convert and display time.Time values. 27 | xticks := plot.TimeTicks{Format: "2006-01-02\n15:04"} 28 | 29 | // randomPoints returns some random x, y points 30 | // with some interesting kind of trend. 31 | randomPoints := func(n int) plotter.XYs { 32 | const ( 33 | month = 1 34 | day = 1 35 | hour = 1 36 | min = 1 37 | sec = 1 38 | nsec = 1 39 | ) 40 | pts := make(plotter.XYs, n) 41 | for i := range pts { 42 | date := time.Date(2007+i, month, day, hour, min, sec, nsec, time.UTC).Unix() 43 | pts[i].X = float64(date) 44 | pts[i].Y = float64(pts[i].X+10*rnd.Float64()) * 1e-9 45 | } 46 | return pts 47 | } 48 | 49 | n := 10 50 | data := randomPoints(n) 51 | 52 | p, err := plot.New() 53 | if err != nil { 54 | log.Panic(err) 55 | } 56 | p.Title.Text = "Time Series" 57 | p.X.Tick.Marker = xticks 58 | p.Y.Label.Text = "Number of Gophers\n(Billions)" 59 | p.Add(plotter.NewGrid()) 60 | 61 | line, points, err := plotter.NewLinePoints(data) 62 | if err != nil { 63 | log.Panic(err) 64 | } 65 | line.Color = color.RGBA{G: 255, A: 255} 66 | points.Shape = draw.CircleGlyph{} 67 | points.Color = color.RGBA{R: 255, A: 255} 68 | 69 | p.Add(line, points) 70 | 71 | err = p.Save(10*vg.Centimeter, 5*vg.Centimeter, "testdata/timeseries.png") 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | } 76 | 77 | func TestTimeSeries(t *testing.T) { 78 | cmpimg.CheckPlot(Example_timeSeries, t, "timeseries.png") 79 | } 80 | -------------------------------------------------------------------------------- /plotter/errbars_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "log" 9 | "testing" 10 | 11 | "golang.org/x/exp/rand" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg/draw" 17 | ) 18 | 19 | // ExampleErrors draws points and error bars. 20 | func ExampleErrors() { 21 | rnd := rand.New(rand.NewSource(1)) 22 | 23 | randomError := func(n int) plotter.Errors { 24 | err := make(plotter.Errors, n) 25 | for i := range err { 26 | err[i].Low = rnd.Float64() 27 | err[i].High = rnd.Float64() 28 | } 29 | return err 30 | } 31 | // randomPoints returns some random x, y points 32 | // with some interesting kind of trend. 33 | randomPoints := func(n int) plotter.XYs { 34 | pts := make(plotter.XYs, n) 35 | for i := range pts { 36 | if i == 0 { 37 | pts[i].X = rnd.Float64() 38 | } else { 39 | pts[i].X = pts[i-1].X + rnd.Float64() 40 | } 41 | pts[i].Y = pts[i].X + 10*rnd.Float64() 42 | } 43 | return pts 44 | } 45 | 46 | type errPoints struct { 47 | plotter.XYs 48 | plotter.YErrors 49 | plotter.XErrors 50 | } 51 | 52 | n := 15 53 | data := errPoints{ 54 | XYs: randomPoints(n), 55 | YErrors: plotter.YErrors(randomError(n)), 56 | XErrors: plotter.XErrors(randomError(n)), 57 | } 58 | 59 | p, err := plot.New() 60 | if err != nil { 61 | log.Panic(err) 62 | } 63 | scatter, err := plotter.NewScatter(data) 64 | if err != nil { 65 | log.Panic(err) 66 | } 67 | scatter.Shape = draw.CrossGlyph{} 68 | xerrs, err := plotter.NewXErrorBars(data) 69 | if err != nil { 70 | log.Panic(err) 71 | } 72 | yerrs, err := plotter.NewYErrorBars(data) 73 | if err != nil { 74 | log.Panic(err) 75 | } 76 | p.Add(scatter, xerrs, yerrs) 77 | 78 | err = p.Save(200, 200, "testdata/errorBars.png") 79 | if err != nil { 80 | log.Panic(err) 81 | } 82 | } 83 | 84 | func TestErrors(t *testing.T) { 85 | cmpimg.CheckPlot(ExampleErrors, t, "errorBars.png") 86 | } 87 | -------------------------------------------------------------------------------- /tools/bezier/bezier.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2013 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 bezier implements 2D Bézier curve calculation. 6 | package bezier // import "gonum.org/v1/plot/tools/bezier" 7 | 8 | import "gonum.org/v1/plot/vg" 9 | 10 | type point struct { 11 | Point, Control vg.Point 12 | } 13 | 14 | // Curve implements Bezier curve calculation according to the algorithm of Robert D. Miller. 15 | // 16 | // Graphics Gems 5, 'Quick and Simple Bézier Curve Drawing', pages 206-209. 17 | type Curve []point 18 | 19 | // NewCurve returns a Curve initialized with the control points in cp. 20 | func New(cp ...vg.Point) Curve { 21 | if len(cp) == 0 { 22 | return nil 23 | } 24 | c := make(Curve, len(cp)) 25 | for i, p := range cp { 26 | c[i].Point = p 27 | } 28 | 29 | var w vg.Length 30 | for i, p := range c { 31 | switch i { 32 | case 0: 33 | w = 1 34 | case 1: 35 | w = vg.Length(len(c)) - 1 36 | default: 37 | w *= vg.Length(len(c)-i) / vg.Length(i) 38 | } 39 | c[i].Control.X = p.Point.X * w 40 | c[i].Control.Y = p.Point.Y * w 41 | } 42 | 43 | return c 44 | } 45 | 46 | // Point returns the point at t along the curve, where 0 ≤ t ≤ 1. 47 | func (c Curve) Point(t float64) vg.Point { 48 | c[0].Point = c[0].Control 49 | u := t 50 | for i, p := range c[1:] { 51 | c[i+1].Point = vg.Point{p.Control.X * vg.Length(u), p.Control.Y * vg.Length(u)} 52 | u *= t 53 | } 54 | 55 | var ( 56 | t1 = 1 - t 57 | tt = t1 58 | ) 59 | p := c[len(c)-1].Point 60 | for i := len(c) - 2; i >= 0; i-- { 61 | p.X += c[i].Point.X * vg.Length(tt) 62 | p.Y += c[i].Point.Y * vg.Length(tt) 63 | tt *= t1 64 | } 65 | 66 | return p 67 | } 68 | 69 | // Curve returns a slice of vg.Point, p, filled with points along the Bézier curve described by c. 70 | // If the length of p is less than 2, the curve points are undefined. The length of p is not 71 | // altered by the call. 72 | func (c Curve) Curve(p []vg.Point) []vg.Point { 73 | for i, nf := 0, float64(len(p)-1); i < len(p); i++ { 74 | p[i] = c.Point(float64(i) / nf) 75 | } 76 | return p 77 | } 78 | -------------------------------------------------------------------------------- /plotter/bubbles_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | 13 | "golang.org/x/exp/rand" 14 | 15 | "gonum.org/v1/plot" 16 | "gonum.org/v1/plot/cmpimg" 17 | "gonum.org/v1/plot/plotter" 18 | "gonum.org/v1/plot/vg" 19 | "gonum.org/v1/plot/vg/draw" 20 | ) 21 | 22 | // ExampleScatter_bubbles draws some scatter points. 23 | // Each point is plotted with a different radius size depending on 24 | // external criteria. 25 | func ExampleScatter_bubbles() { 26 | rnd := rand.New(rand.NewSource(1)) 27 | 28 | // randomTriples returns some random but correlated x, y, z triples 29 | randomTriples := func(n int) plotter.XYZs { 30 | data := make(plotter.XYZs, n) 31 | for i := range data { 32 | if i == 0 { 33 | data[i].X = rnd.Float64() 34 | } else { 35 | data[i].X = data[i-1].X + 2*rnd.Float64() 36 | } 37 | data[i].Y = data[i].X + 10*rnd.Float64() 38 | data[i].Z = data[i].X 39 | } 40 | return data 41 | } 42 | 43 | n := 10 44 | scatterData := randomTriples(n) 45 | 46 | // Calculate the range of Z values. 47 | minZ, maxZ := math.Inf(1), math.Inf(-1) 48 | for _, xyz := range scatterData { 49 | if xyz.Z > maxZ { 50 | maxZ = xyz.Z 51 | } 52 | if xyz.Z < minZ { 53 | minZ = xyz.Z 54 | } 55 | } 56 | 57 | p, err := plot.New() 58 | if err != nil { 59 | log.Panic(err) 60 | } 61 | p.Title.Text = "Bubbles" 62 | p.X.Label.Text = "X" 63 | p.Y.Label.Text = "Y" 64 | 65 | sc, err := plotter.NewScatter(scatterData) 66 | if err != nil { 67 | log.Panic(err) 68 | } 69 | 70 | // Specify style for individual points. 71 | sc.GlyphStyleFunc = func(i int) draw.GlyphStyle { 72 | c := color.RGBA{R: 196, B: 128, A: 255} 73 | var minRadius, maxRadius = vg.Points(1), vg.Points(20) 74 | rng := maxRadius - minRadius 75 | _, _, z := scatterData.XYZ(i) 76 | d := (z - minZ) / (maxZ - minZ) 77 | r := vg.Length(d)*rng + minRadius 78 | return draw.GlyphStyle{Color: c, Radius: r, Shape: draw.CircleGlyph{}} 79 | } 80 | p.Add(sc) 81 | 82 | err = p.Save(200, 200, "testdata/bubbles.png") 83 | if err != nil { 84 | log.Panic(err) 85 | } 86 | } 87 | 88 | func TestNewBubbles(t *testing.T) { 89 | cmpimg.CheckPlot(ExampleScatter_bubbles, t, "bubbles.png") 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gonum Plot [![Build Status](https://travis-ci.org/gonum/plot.svg?branch=master)](https://travis-ci.org/gonum/plot) [![Build status](https://ci.appveyor.com/api/projects/status/6vtroet40gj5jhoe/branch/master?svg=true)](https://ci.appveyor.com/project/Gonum/plot/branch/master) [![codecov.io](https://codecov.io/gh/gonum/plot/branch/master/graph/badge.svg)](https://codecov.io/gh/gonum/plot) [![coveralls.io](https://coveralls.io/repos/gonum/plot/badge.svg?branch=master&service=github)](https://coveralls.io/github/gonum/plot?branch=master) [![GoDoc](https://godoc.org/gonum.org/v1/plot?status.svg)](https://godoc.org/gonum.org/v1/plot) 2 | 3 | `gonum/plot` is the new, official fork of code.google.com/p/plotinum. 4 | It provides an API for building and drawing plots in Go. 5 | *Note* that this new API is still in flux and may change. 6 | See the wiki for some [example plots](http://github.com/gonum/plot/wiki/Example-plots). 7 | 8 | For additional Plotters, see the [Community Plotters](https://github.com/gonum/plot/wiki/Community-Plotters) Wiki page. 9 | 10 | There is a discussion list on Google Groups: gonum-dev@googlegroups.com. 11 | 12 | `gonum/plot` is split into a few packages: 13 | 14 | * The `plot` package provides simple interface for laying out a plot and provides primitives for drawing to it. 15 | * The `plotter` package provides a standard set of `Plotter`s which use the primitives provided by the `plot` package for drawing lines, scatter plots, box plots, error bars, etc. to a plot. You do not need to use the `plotter` package to make use of `gonum/plot`, however: see the wiki for a tutorial on making your own custom plotters. 16 | * The `plotutil` package contains a few routines that allow some common plot types to be made very easily. This package is quite new so it is not as well tested as the others and it is bound to change. 17 | * The `vg` package provides a generic vector graphics API that sits on top of other vector graphics back-ends such as a custom EPS back-end, draw2d, SVGo, X-Window and gopdf. 18 | 19 | ## Documentation 20 | 21 | Documentation is available at: 22 | 23 | https://godoc.org/gonum.org/v1/plot 24 | 25 | ## Installation 26 | 27 | You can get `gonum/plot` using go get: 28 | 29 | `go get gonum.org/v1/plot/...` 30 | 31 | If you write a cool plotter that you think others may be interested in using, please post to the list so that we can link to it in the `gonum/plot` wiki or possibly integrate it into the `plotter` package. 32 | -------------------------------------------------------------------------------- /plotter/colorbar_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "testing" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/cmpimg" 14 | "gonum.org/v1/plot/palette/moreland" 15 | "gonum.org/v1/plot/plotter" 16 | ) 17 | 18 | func ExampleColorBar_horizontal() { 19 | p, err := plot.New() 20 | if err != nil { 21 | log.Panic(err) 22 | } 23 | l := &plotter.ColorBar{ColorMap: moreland.ExtendedBlackBody()} 24 | l.ColorMap.SetMin(0.5) 25 | l.ColorMap.SetMax(1.5) 26 | p.Add(l) 27 | p.HideY() 28 | p.X.Padding = 0 29 | p.Title.Text = "Title" 30 | 31 | if err = p.Save(300, 48, "testdata/colorBarHorizontal.png"); err != nil { 32 | log.Panic(err) 33 | } 34 | } 35 | 36 | func TestColorBar_horizontal(t *testing.T) { 37 | cmpimg.CheckPlot(ExampleColorBar_horizontal, t, "colorBarHorizontal.png") 38 | } 39 | 40 | // This example shows how to create a ColorBar on a log-transformed axis. 41 | func ExampleColorBar_horizontal_log() { 42 | p, err := plot.New() 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | colorMap, err := moreland.NewLuminance([]color.Color{color.Black, color.White}) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | l := &plotter.ColorBar{ColorMap: colorMap} 51 | l.ColorMap.SetMin(1) 52 | l.ColorMap.SetMax(100) 53 | p.Add(l) 54 | p.HideY() 55 | p.X.Padding = 0 56 | p.Title.Text = "Title" 57 | p.X.Scale = plot.LogScale{} 58 | p.X.Tick.Marker = plot.LogTicks{} 59 | 60 | if err = p.Save(300, 48, "testdata/colorBarHorizontalLog.png"); err != nil { 61 | log.Panic(err) 62 | } 63 | } 64 | 65 | func TestColorBar_horizontal_log(t *testing.T) { 66 | cmpimg.CheckPlot(ExampleColorBar_horizontal_log, t, "colorBarHorizontalLog.png") 67 | } 68 | 69 | func ExampleColorBar_vertical() { 70 | p, err := plot.New() 71 | if err != nil { 72 | log.Panic(err) 73 | } 74 | l := &plotter.ColorBar{ColorMap: moreland.ExtendedBlackBody()} 75 | l.ColorMap.SetMin(0.5) 76 | l.ColorMap.SetMax(1.5) 77 | l.Vertical = true 78 | p.Add(l) 79 | p.HideX() 80 | p.Y.Padding = 0 81 | p.Title.Text = "Title" 82 | 83 | if err = p.Save(40, 300, "testdata/colorBarVertical.png"); err != nil { 84 | log.Panic(err) 85 | } 86 | } 87 | 88 | func TestColorBar_vertical(t *testing.T) { 89 | cmpimg.CheckPlot(ExampleColorBar_vertical, t, "colorBarVertical.png") 90 | } 91 | -------------------------------------------------------------------------------- /plotter/scatter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "testing" 11 | 12 | "golang.org/x/exp/rand" 13 | 14 | "gonum.org/v1/plot" 15 | "gonum.org/v1/plot/cmpimg" 16 | "gonum.org/v1/plot/plotter" 17 | "gonum.org/v1/plot/vg" 18 | "gonum.org/v1/plot/vg/draw" 19 | ) 20 | 21 | // ExampleScatter draws some scatter points, a line, 22 | // and a line with points. 23 | func ExampleScatter() { 24 | rnd := rand.New(rand.NewSource(1)) 25 | 26 | // randomPoints returns some random x, y points 27 | // with some interesting kind of trend. 28 | randomPoints := func(n int) plotter.XYs { 29 | pts := make(plotter.XYs, n) 30 | for i := range pts { 31 | if i == 0 { 32 | pts[i].X = rnd.Float64() 33 | } else { 34 | pts[i].X = pts[i-1].X + rnd.Float64() 35 | } 36 | pts[i].Y = pts[i].X + 10*rnd.Float64() 37 | } 38 | return pts 39 | } 40 | 41 | n := 15 42 | scatterData := randomPoints(n) 43 | lineData := randomPoints(n) 44 | linePointsData := randomPoints(n) 45 | 46 | p, err := plot.New() 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | p.Title.Text = "Points Example" 51 | p.X.Label.Text = "X" 52 | p.Y.Label.Text = "Y" 53 | p.Add(plotter.NewGrid()) 54 | 55 | s, err := plotter.NewScatter(scatterData) 56 | if err != nil { 57 | log.Panic(err) 58 | } 59 | s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255} 60 | s.GlyphStyle.Radius = vg.Points(3) 61 | 62 | l, err := plotter.NewLine(lineData) 63 | if err != nil { 64 | log.Panic(err) 65 | } 66 | l.LineStyle.Width = vg.Points(1) 67 | l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)} 68 | l.LineStyle.Color = color.RGBA{B: 255, A: 255} 69 | 70 | lpLine, lpPoints, err := plotter.NewLinePoints(linePointsData) 71 | if err != nil { 72 | log.Panic(err) 73 | } 74 | lpLine.Color = color.RGBA{G: 255, A: 255} 75 | lpPoints.Shape = draw.CircleGlyph{} 76 | lpPoints.Color = color.RGBA{R: 255, A: 255} 77 | 78 | p.Add(s, l, lpLine, lpPoints) 79 | p.Legend.Add("scatter", s) 80 | p.Legend.Add("line", l) 81 | p.Legend.Add("line points", lpLine, lpPoints) 82 | 83 | err = p.Save(200, 200, "testdata/scatter.png") 84 | if err != nil { 85 | log.Panic(err) 86 | } 87 | } 88 | 89 | func TestScatter(t *testing.T) { 90 | cmpimg.CheckPlot(ExampleScatter, t, "scatter.png") 91 | } 92 | -------------------------------------------------------------------------------- /align_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plot_test 6 | 7 | import ( 8 | "math" 9 | "os" 10 | "testing" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/cmpimg" 14 | "gonum.org/v1/plot/vg" 15 | "gonum.org/v1/plot/vg/draw" 16 | "gonum.org/v1/plot/vg/vgimg" 17 | ) 18 | 19 | func ExampleAlign() { 20 | const rows, cols = 4, 3 21 | plots := make([][]*plot.Plot, rows) 22 | for j := 0; j < rows; j++ { 23 | plots[j] = make([]*plot.Plot, cols) 24 | for i := 0; i < cols; i++ { 25 | if i == 0 && j == 2 { 26 | // This shows what happens when there are nil plots. 27 | continue 28 | } 29 | 30 | p, err := plot.New() 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | if j == 0 && i == 2 { 36 | // This shows what happens when the axis padding 37 | // is different among plots. 38 | p.X.Padding, p.Y.Padding = 0, 0 39 | } 40 | 41 | if j == 1 && i == 1 { 42 | // To test the Align function, we make the axis labels 43 | // on one of the plots stick out. 44 | p.Y.Max = 1e9 45 | p.X.Max = 1e9 46 | p.X.Tick.Label.Rotation = math.Pi / 2 47 | p.X.Tick.Label.XAlign = draw.XRight 48 | p.X.Tick.Label.YAlign = draw.YCenter 49 | p.X.Tick.Label.Font.Size = 8 50 | p.Y.Tick.Label.Font.Size = 8 51 | } else { 52 | p.Y.Max = 1e9 53 | p.X.Max = 1e9 54 | p.X.Tick.Label.Font.Size = 1 55 | p.Y.Tick.Label.Font.Size = 1 56 | } 57 | 58 | plots[j][i] = p 59 | } 60 | } 61 | 62 | img := vgimg.New(vg.Points(150), vg.Points(175)) 63 | dc := draw.New(img) 64 | 65 | t := draw.Tiles{ 66 | Rows: rows, 67 | Cols: cols, 68 | PadX: vg.Millimeter, 69 | PadY: vg.Millimeter, 70 | PadTop: vg.Points(2), 71 | PadBottom: vg.Points(2), 72 | PadLeft: vg.Points(2), 73 | PadRight: vg.Points(2), 74 | } 75 | 76 | canvases := plot.Align(plots, t, dc) 77 | for j := 0; j < rows; j++ { 78 | for i := 0; i < cols; i++ { 79 | if plots[j][i] != nil { 80 | plots[j][i].Draw(canvases[j][i]) 81 | } 82 | } 83 | } 84 | 85 | w, err := os.Create("testdata/align.png") 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | png := vgimg.PngCanvas{Canvas: img} 91 | if _, err := png.WriteTo(w); err != nil { 92 | panic(err) 93 | } 94 | } 95 | 96 | func TestAlign(t *testing.T) { 97 | cmpimg.CheckPlot(ExampleAlign, t, "align.png") 98 | } 99 | -------------------------------------------------------------------------------- /plotter/scatter.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter 6 | 7 | import ( 8 | "gonum.org/v1/plot" 9 | "gonum.org/v1/plot/vg" 10 | "gonum.org/v1/plot/vg/draw" 11 | ) 12 | 13 | // Scatter implements the Plotter interface, drawing 14 | // a glyph for each of a set of points. 15 | type Scatter struct { 16 | // XYs is a copy of the points for this scatter. 17 | XYs 18 | 19 | // GlyphStyleFunc, if not nil, specifies GlyphStyles 20 | // for individual points 21 | GlyphStyleFunc func(int) draw.GlyphStyle 22 | 23 | // GlyphStyle is the style of the glyphs drawn 24 | // at each point. 25 | draw.GlyphStyle 26 | } 27 | 28 | // NewScatter returns a Scatter that uses the 29 | // default glyph style. 30 | func NewScatter(xys XYer) (*Scatter, error) { 31 | data, err := CopyXYs(xys) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &Scatter{ 36 | XYs: data, 37 | GlyphStyle: DefaultGlyphStyle, 38 | }, err 39 | } 40 | 41 | // Plot draws the Scatter, implementing the plot.Plotter 42 | // interface. 43 | func (pts *Scatter) Plot(c draw.Canvas, plt *plot.Plot) { 44 | trX, trY := plt.Transforms(&c) 45 | glyph := func(i int) draw.GlyphStyle { return pts.GlyphStyle } 46 | if pts.GlyphStyleFunc != nil { 47 | glyph = pts.GlyphStyleFunc 48 | } 49 | for i, p := range pts.XYs { 50 | c.DrawGlyph(glyph(i), vg.Point{X: trX(p.X), Y: trY(p.Y)}) 51 | } 52 | } 53 | 54 | // DataRange returns the minimum and maximum 55 | // x and y values, implementing the plot.DataRanger 56 | // interface. 57 | func (pts *Scatter) DataRange() (xmin, xmax, ymin, ymax float64) { 58 | return XYRange(pts) 59 | } 60 | 61 | // GlyphBoxes returns a slice of plot.GlyphBoxes, 62 | // implementing the plot.GlyphBoxer interface. 63 | func (pts *Scatter) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { 64 | glyph := func(i int) draw.GlyphStyle { return pts.GlyphStyle } 65 | if pts.GlyphStyleFunc != nil { 66 | glyph = pts.GlyphStyleFunc 67 | } 68 | bs := make([]plot.GlyphBox, len(pts.XYs)) 69 | for i, p := range pts.XYs { 70 | bs[i].X = plt.X.Norm(p.X) 71 | bs[i].Y = plt.Y.Norm(p.Y) 72 | r := glyph(i).Radius 73 | bs[i].Rectangle = vg.Rectangle{ 74 | Min: vg.Point{X: -r, Y: -r}, 75 | Max: vg.Point{X: +r, Y: +r}, 76 | } 77 | } 78 | return bs 79 | } 80 | 81 | // Thumbnail the thumbnail for the Scatter, 82 | // implementing the plot.Thumbnailer interface. 83 | func (pts *Scatter) Thumbnail(c *draw.Canvas) { 84 | c.DrawGlyph(pts.GlyphStyle, c.Center()) 85 | } 86 | -------------------------------------------------------------------------------- /plotter/image_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 plotter_test 6 | 7 | import ( 8 | "image/png" 9 | "log" 10 | "os" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | ) 18 | 19 | const runImageLaTeX = false 20 | 21 | // An example of embedding an image in a plot. 22 | func ExampleImage() { 23 | p, err := plot.New() 24 | if err != nil { 25 | log.Fatalf("error: %v\n", err) 26 | } 27 | p.Title.Text = "A Logo" 28 | 29 | // load an image 30 | f, err := os.Open("testdata/image_plot_input.png") 31 | if err != nil { 32 | log.Fatalf("error opening image file: %v\n", err) 33 | } 34 | defer f.Close() 35 | 36 | img, err := png.Decode(f) 37 | if err != nil { 38 | log.Fatalf("error decoding image file: %v\n", err) 39 | } 40 | 41 | p.Add(plotter.NewImage(img, 100, 100, 200, 200)) 42 | 43 | const ( 44 | w = 5 * vg.Centimeter 45 | h = 5 * vg.Centimeter 46 | ) 47 | 48 | err = p.Save(5*vg.Centimeter, 5*vg.Centimeter, "testdata/image_plot.png") 49 | if err != nil { 50 | log.Fatalf("error saving image plot: %v\n", err) 51 | } 52 | } 53 | 54 | func TestImagePlot(t *testing.T) { 55 | cmpimg.CheckPlot(ExampleImage, t, "image_plot.png") 56 | } 57 | 58 | // An example of embedding an image in a plot with non-linear axes. 59 | func ExampleImage_log() { 60 | p, err := plot.New() 61 | if err != nil { 62 | log.Fatalf("error: %v\n", err) 63 | } 64 | p.Title.Text = "A Logo" 65 | 66 | // load an image 67 | f, err := os.Open("testdata/gopher.png") 68 | if err != nil { 69 | log.Fatalf("error opening image file: %v\n", err) 70 | } 71 | defer f.Close() 72 | 73 | img, err := png.Decode(f) 74 | if err != nil { 75 | log.Fatalf("error decoding image file: %v\n", err) 76 | } 77 | 78 | p.Add(plotter.NewImage(img, 100, 100, 10000, 10000)) 79 | 80 | // Transform axes. 81 | p.X.Scale = plot.LogScale{} 82 | p.Y.Scale = plot.LogScale{} 83 | p.X.Tick.Marker = plot.LogTicks{} 84 | p.Y.Tick.Marker = plot.LogTicks{} 85 | 86 | const ( 87 | w = 5 * vg.Centimeter 88 | h = 5 * vg.Centimeter 89 | ) 90 | 91 | err = p.Save(5*vg.Centimeter, 5*vg.Centimeter, "testdata/image_plot_log.png") 92 | if err != nil { 93 | log.Fatalf("error saving image plot: %v\n", err) 94 | } 95 | } 96 | 97 | func TestImagePlot_log(t *testing.T) { 98 | cmpimg.CheckPlot(ExampleImage_log, t, "image_plot_log.png") 99 | } 100 | -------------------------------------------------------------------------------- /plotter/step_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2018 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "testing" 11 | 12 | "golang.org/x/exp/rand" 13 | 14 | "gonum.org/v1/plot" 15 | "gonum.org/v1/plot/cmpimg" 16 | "gonum.org/v1/plot/plotter" 17 | "gonum.org/v1/plot/vg" 18 | "gonum.org/v1/plot/vg/draw" 19 | ) 20 | 21 | func ExampleLine_stepLine() { 22 | rnd := rand.New(rand.NewSource(1)) 23 | 24 | // randomPoints returns some random x, y points 25 | // with some interesting kind of trend. 26 | randomPoints := func(n int, x float64) plotter.XYs { 27 | pts := make(plotter.XYs, n) 28 | for i := range pts { 29 | pts[i].X = float64(i) + x 30 | pts[i].Y = 5. + 10*rnd.Float64() 31 | } 32 | return pts 33 | } 34 | 35 | n := 4 36 | 37 | p, err := plot.New() 38 | if err != nil { 39 | log.Panic(err) 40 | } 41 | p.Title.Text = "Step Example" 42 | p.X.Label.Text = "X" 43 | p.Y.Label.Text = "Y" 44 | p.Add(plotter.NewGrid()) 45 | 46 | stepPre, err := plotter.NewLine(randomPoints(n, 0)) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | stepPre.StepStyle = plotter.PreStep 51 | stepPre.FillColor = color.RGBA{R: 196, G: 255, B: 196, A: 255} 52 | 53 | stepMid, err := plotter.NewLine(randomPoints(n, 3.5)) 54 | if err != nil { 55 | log.Panic(err) 56 | } 57 | stepMid.StepStyle = plotter.MidStep 58 | stepMid.LineStyle = draw.LineStyle{Color: color.RGBA{R: 196, B: 128, A: 255}, Width: vg.Points(1)} 59 | 60 | stepMidFilled, err := plotter.NewLine(randomPoints(n, 7)) 61 | if err != nil { 62 | log.Panic(err) 63 | } 64 | stepMidFilled.StepStyle = plotter.MidStep 65 | stepMidFilled.LineStyle = draw.LineStyle{Color: color.RGBA{R: 196, B: 128, A: 255}, Width: vg.Points(1)} 66 | stepMidFilled.FillColor = color.RGBA{R: 255, G: 196, B: 196, A: 255} 67 | 68 | stepPost, err := plotter.NewLine(randomPoints(n, 10.5)) 69 | if err != nil { 70 | log.Panic(err) 71 | } 72 | stepPost.StepStyle = plotter.PostStep 73 | stepPost.LineStyle.Width = 0 74 | stepPost.FillColor = color.RGBA{R: 196, G: 196, B: 255, A: 255} 75 | 76 | p.Add(stepPre, stepMid, stepMidFilled, stepPost) 77 | p.Legend.Add("pre", stepPre) 78 | p.Legend.Add("mid", stepMid) 79 | p.Legend.Add("midFilled", stepMidFilled) 80 | p.Legend.Add("post", stepPost) 81 | p.Legend.Top = true 82 | p.Y.Max = 20 83 | p.Y.Min = 0 84 | 85 | err = p.Save(200, 200, "testdata/step.png") 86 | if err != nil { 87 | log.Panic(err) 88 | } 89 | } 90 | 91 | func TestStep(t *testing.T) { 92 | cmpimg.CheckPlot(ExampleLine_stepLine, t, "step.png") 93 | } 94 | -------------------------------------------------------------------------------- /palette/hsva_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 | // Copyright ©2011-2012 The bíogo Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | 9 | package palette 10 | 11 | import ( 12 | "image/color" 13 | "testing" 14 | ) 15 | 16 | func withinEpsilon(a, b, epsilon uint32) bool { 17 | d := int64(a) - int64(b) 18 | if d < 0 { 19 | d = -d 20 | } 21 | return d <= int64(epsilon) 22 | } 23 | 24 | func TestColor(t *testing.T) { 25 | for r := 0; r < 256; r += 5 { 26 | for g := 0; g < 256; g += 5 { 27 | for b := 0; b < 256; b += 5 { 28 | col := color.RGBA{uint8(r), uint8(g), uint8(b), 0} 29 | cDirectR, cDirectG, cDirectB, cDirectA := col.RGBA() 30 | hsva := rgbaToHsva(col.RGBA()) 31 | if hsva.H < 0 || hsva.H > 1 { 32 | t.Errorf("unexpected values for H: want [0, 1] got:%f", hsva.H) 33 | } 34 | if hsva.S < 0 || hsva.S > 1 { 35 | t.Errorf("unexpected values for S: want [0, 1] got:%f", hsva.S) 36 | } 37 | if hsva.V < 0 || hsva.V > 1 { 38 | t.Errorf("unexpected values for V: want [0, 1] got:%f", hsva.V) 39 | } 40 | 41 | cFromHSVR, cFromHSVG, cFromHSVB, cFromHSVA := hsva.RGBA() 42 | if cFromHSVR < 0 || cFromHSVR > 0xFFFF { 43 | t.Errorf("unexpected values for H: want [0x0, 0xFFFF] got:%f", hsva.H) 44 | } 45 | if cFromHSVG < 0 || cFromHSVG > 0xFFFF { 46 | t.Errorf("unexpected values for S: want [0x0, 0xFFFF] got:%f", hsva.S) 47 | } 48 | if cFromHSVB < 0 || cFromHSVB > 0xFFFF { 49 | t.Errorf("unexpected values for V: want [0x0, 0xFFFF] got:%f", hsva.V) 50 | } 51 | if cFromHSVA < 0 || cFromHSVA > 0xFFFF { 52 | t.Errorf("unexpected values for V: want [0x0, 0xFFFF] got:%f", hsva.V) 53 | } 54 | 55 | back := rgbaToHsva(color.RGBA{uint8(cFromHSVR >> 8), uint8(cFromHSVG >> 8), uint8(cFromHSVB >> 8), uint8(cFromHSVA)}.RGBA()) 56 | if hsva != back { 57 | t.Errorf("roundtrip error: got:%#v want:%#v", back, hsva) 58 | } 59 | const epsilon = 1 60 | if !withinEpsilon(cFromHSVR, cDirectR, epsilon) { 61 | t.Errorf("roundtrip error for R: got:%d want:%d", cFromHSVR, cDirectR) 62 | } 63 | if !withinEpsilon(cFromHSVG, cDirectG, epsilon) { 64 | t.Errorf("roundtrip error for G: got:%d want:%d %d", cFromHSVG, cDirectG, cFromHSVG-cDirectG) 65 | } 66 | if !withinEpsilon(cFromHSVB, cDirectB, epsilon) { 67 | t.Errorf("roundtrip error for B: got:%d want:%d", cFromHSVB, cDirectB) 68 | } 69 | if cFromHSVA != cDirectA { 70 | t.Errorf("roundtrip error for A: got:%d want:%d", cFromHSVA, cDirectA) 71 | } 72 | if t.Failed() { 73 | return 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /legend_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2018 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 plot_test 6 | 7 | import ( 8 | "image/color" 9 | "os" 10 | "testing" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/cmpimg" 14 | "gonum.org/v1/plot/vg" 15 | "gonum.org/v1/plot/vg/draw" 16 | "gonum.org/v1/plot/vg/vgimg" 17 | ) 18 | 19 | type exampleThumbnailer struct { 20 | color.Color 21 | } 22 | 23 | // Thumbnail fulfills the plot.Thumbnailer interface. 24 | func (et exampleThumbnailer) Thumbnail(c *draw.Canvas) { 25 | pts := []vg.Point{ 26 | {c.Min.X, c.Min.Y}, 27 | {c.Min.X, c.Max.Y}, 28 | {c.Max.X, c.Max.Y}, 29 | {c.Max.X, c.Min.Y}, 30 | } 31 | poly := c.ClipPolygonY(pts) 32 | c.FillPolygon(et.Color, poly) 33 | 34 | pts = append(pts, vg.Point{X: c.Min.X, Y: c.Min.Y}) 35 | outline := c.ClipLinesY(pts) 36 | c.StrokeLines(draw.LineStyle{ 37 | Color: color.Black, 38 | Width: vg.Points(1), 39 | }, outline...) 40 | } 41 | 42 | // This example creates a some standalone legends with borders around them. 43 | func ExampleLegend_standalone() { 44 | c := vgimg.New(vg.Points(120), vg.Points(100)) 45 | dc := draw.New(c) 46 | 47 | // These example thumbnailers could be replaced with any of Plotters 48 | // in the plotter subpackage. 49 | red := exampleThumbnailer{Color: color.NRGBA{R: 255, A: 255}} 50 | green := exampleThumbnailer{Color: color.NRGBA{G: 255, A: 255}} 51 | blue := exampleThumbnailer{Color: color.NRGBA{B: 255, A: 255}} 52 | 53 | l, err := plot.NewLegend() 54 | if err != nil { 55 | panic(err) 56 | } 57 | l.Add("red", red) 58 | l.Add("green", green) 59 | l.Add("blue", blue) 60 | l.Padding = vg.Millimeter 61 | 62 | // purpleRectangle draws a purple rectangle around the given Legend. 63 | purpleRectangle := func(l plot.Legend) { 64 | r := l.Rectangle(dc) 65 | dc.StrokeLines(draw.LineStyle{ 66 | Color: color.NRGBA{R: 255, B: 255, A: 255}, 67 | Width: vg.Points(1), 68 | }, []vg.Point{ 69 | {r.Min.X, r.Min.Y}, {r.Min.X, r.Max.Y}, {r.Max.X, r.Max.Y}, 70 | {r.Max.X, r.Min.Y}, {r.Min.X, r.Min.Y}, 71 | }) 72 | } 73 | 74 | l.Draw(dc) 75 | purpleRectangle(l) 76 | 77 | l.Left = true 78 | l.Draw(dc) 79 | purpleRectangle(l) 80 | 81 | l.Top = true 82 | l.Draw(dc) 83 | purpleRectangle(l) 84 | 85 | l.Left = false 86 | l.Draw(dc) 87 | purpleRectangle(l) 88 | 89 | w, err := os.Create("testdata/legend_standalone.png") 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | png := vgimg.PngCanvas{Canvas: c} 95 | if _, err := png.WriteTo(w); err != nil { 96 | panic(err) 97 | } 98 | } 99 | 100 | func TestLegend_standalone(t *testing.T) { 101 | cmpimg.CheckPlot(ExampleLegend_standalone, t, "legend_standalone.png") 102 | } 103 | -------------------------------------------------------------------------------- /plotter/volcano: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cat >volcano_example.go <= n { 34 | panic("index out of range") 35 | } 36 | return 10 * float64(c) 37 | } 38 | func (g deciGrid) Y(r int) float64 { 39 | m, _ := g.Matrix.Dims() 40 | if r < 0 || r >= m { 41 | panic("index out of range") 42 | } 43 | return 10 * float64(r) 44 | } 45 | 46 | func main() { 47 | var levels []float64 48 | for l := 100.5; l < volcano.Matrix.(*mat64.Dense).Max(); l += 5 { 49 | levels = append(levels, l) 50 | } 51 | c := plotter.NewContour(volcano, levels, palette.Rainbow(len(levels), (palette.Yellow+palette.Red)/2, palette.Blue, 1, 1, 1)) 52 | quarterStyle := draw.LineStyle{ 53 | Color: color.Black, 54 | Width: vg.Points(0.5), 55 | Dashes: []vg.Length{0.2, 0.4}, 56 | } 57 | halfStyle := draw.LineStyle{ 58 | Color: color.Black, 59 | Width: vg.Points(0.5), 60 | Dashes: []vg.Length{5, 2, 1, 2}, 61 | } 62 | c.LineStyles = append(c.LineStyles, quarterStyle, halfStyle, quarterStyle) 63 | 64 | h := plotter.NewHeatMap(volcano, palette.Heat(len(levels)*2, 1)) 65 | 66 | p, err := plot.New() 67 | if err != nil { 68 | panic(err) 69 | } 70 | p.Title.Text = "Maunga Whau Volcano" 71 | 72 | p.Add(h) 73 | p.Add(c) 74 | 75 | p.X.Padding = 0 76 | p.Y.Padding = 0 77 | _, p.X.Max, _, p.Y.Max = h.DataRange() 78 | 79 | name := "example_volcano" 80 | 81 | for _, ext := range []string{ 82 | ".eps", 83 | ".pdf", 84 | ".svg", 85 | ".png", 86 | ".tiff", 87 | ".jpg", 88 | } { 89 | if err := p.Save(4, 4, name+ext); err != nil { 90 | panic(err) 91 | } 92 | } 93 | } 94 | 95 | // Data extracted from RDatasets volcano data for the Maunga Whau volcano topographic data. 96 | var volcano = deciGrid{mat64.NewDense(87, 61, []float64{ 97 | EOF 98 | R -q -e 'write.table(as.data.frame(volcano), file="volcano_example.go", sep=", ", eol=",\n", col.names=FALSE, row.names=FALSE, append=TRUE)' 99 | echo >> volcano_example.go '})}' 100 | go fmt volcano_example.go 101 | -------------------------------------------------------------------------------- /plotter/filledLine_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2018 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "testing" 11 | 12 | "golang.org/x/exp/rand" 13 | 14 | "gonum.org/v1/plot" 15 | "gonum.org/v1/plot/cmpimg" 16 | "gonum.org/v1/plot/plotter" 17 | ) 18 | 19 | // See https://github.com/gonum/plot/issues/488 20 | func clippedFilledLine() { 21 | rnd := rand.New(rand.NewSource(1)) 22 | 23 | // randomPoints returns some random x, y points 24 | // with some interesting kind of trend. 25 | randomPoints := func(n int, x float64) plotter.XYs { 26 | pts := make(plotter.XYs, n) 27 | for i := range pts { 28 | if i == 0 { 29 | pts[i].X = x + rnd.Float64() 30 | } else { 31 | pts[i].X = pts[i-1].X + 0.5 + rnd.Float64() 32 | } 33 | pts[i].Y = -5. + 10*rnd.Float64() 34 | } 35 | return pts 36 | } 37 | 38 | p, err := plot.New() 39 | if err != nil { 40 | log.Panic(err) 41 | } 42 | p.Title.Text = "Filled Line Example" 43 | p.X.Label.Text = "X" 44 | p.Y.Label.Text = "Y" 45 | p.Add(plotter.NewGrid()) 46 | 47 | filled, err := plotter.NewLine(randomPoints(4, 0)) 48 | if err != nil { 49 | log.Panic(err) 50 | } 51 | filled.FillColor = color.RGBA{R: 196, G: 255, B: 196, A: 255} 52 | 53 | p.Add(filled) 54 | // testing clipping 55 | p.X.Min, p.X.Max = 1, 3 56 | p.Y.Max = -1 57 | 58 | err = p.Save(200, 200, "testdata/clippedFilledLine.png") 59 | if err != nil { 60 | log.Panic(err) 61 | } 62 | } 63 | 64 | func ExampleLine_filledLine() { 65 | rnd := rand.New(rand.NewSource(1)) 66 | 67 | // randomPoints returns some random x, y points 68 | // with some interesting kind of trend. 69 | randomPoints := func(n int, x float64) plotter.XYs { 70 | pts := make(plotter.XYs, n) 71 | for i := range pts { 72 | if i == 0 { 73 | pts[i].X = x + rnd.Float64() 74 | } else { 75 | pts[i].X = pts[i-1].X + 0.5 + rnd.Float64() 76 | } 77 | pts[i].Y = -5. + 10*rnd.Float64() 78 | } 79 | return pts 80 | } 81 | 82 | p, err := plot.New() 83 | if err != nil { 84 | log.Panic(err) 85 | } 86 | p.Title.Text = "Filled Line Example" 87 | p.X.Label.Text = "X" 88 | p.Y.Label.Text = "Y" 89 | p.Add(plotter.NewGrid()) 90 | 91 | filled, err := plotter.NewLine(randomPoints(4, 0)) 92 | if err != nil { 93 | log.Panic(err) 94 | } 95 | filled.FillColor = color.RGBA{R: 196, G: 255, B: 196, A: 255} 96 | 97 | p.Add(filled) 98 | 99 | err = p.Save(200, 200, "testdata/filledLine.png") 100 | if err != nil { 101 | log.Panic(err) 102 | } 103 | } 104 | 105 | func TestFilledLine(t *testing.T) { 106 | cmpimg.CheckPlot(ExampleLine_filledLine, t, "filledLine.png") 107 | cmpimg.CheckPlot(clippedFilledLine, t, "clippedFilledLine.png") 108 | } 109 | -------------------------------------------------------------------------------- /plotter/colorbar.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plotter 6 | 7 | import ( 8 | "image" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/palette" 12 | "gonum.org/v1/plot/vg/draw" 13 | ) 14 | 15 | // ColorBar is a plot.Plotter that draws a color bar legend for a ColorMap. 16 | type ColorBar struct { 17 | ColorMap palette.ColorMap 18 | 19 | // Vertical determines wether the legend will be 20 | // plotted vertically or horizontally. 21 | // The default is false (horizontal). 22 | Vertical bool 23 | 24 | // Colors specifies the number of colors to be 25 | // shown in the legend. If Colors is not specified, 26 | // a default will be used. 27 | Colors int 28 | } 29 | 30 | // colors returns the number of colors to be shown 31 | // in the legend, substituting invalid values 32 | // with the default of one color per point. 33 | func (l *ColorBar) colors(c draw.Canvas) int { 34 | if l.Colors > 0 { 35 | return l.Colors 36 | } 37 | if l.Vertical { 38 | return int(c.Max.Y - c.Min.Y) 39 | } 40 | return int(c.Max.X - c.Min.X) 41 | } 42 | 43 | // check determines whether the ColorBar is 44 | // valid in its current configuration. 45 | func (l *ColorBar) check() { 46 | if l.ColorMap == nil { 47 | panic("plotter: nil ColorMap in ColorBar") 48 | } 49 | if l.ColorMap.Max() == l.ColorMap.Min() { 50 | panic("plotter: ColorMap Max==Min") 51 | } 52 | } 53 | 54 | // Plot implements the Plot method of the plot.Plotter interface. 55 | func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) { 56 | l.check() 57 | colors := l.colors(c) 58 | var pImg *Image 59 | delta := (l.ColorMap.Max() - l.ColorMap.Min()) / float64(colors) 60 | if l.Vertical { 61 | img := image.NewNRGBA64(image.Rectangle{ 62 | Min: image.Point{X: 0, Y: 0}, 63 | Max: image.Point{X: 1, Y: colors}, 64 | }) 65 | for i := 0; i < colors; i++ { 66 | color, err := l.ColorMap.At(l.ColorMap.Min() + delta*float64(i)) 67 | if err != nil { 68 | panic(err) 69 | } 70 | img.Set(0, colors-1-i, color) 71 | } 72 | pImg = NewImage(img, 0, l.ColorMap.Min(), 1, l.ColorMap.Max()) 73 | } else { 74 | img := image.NewNRGBA64(image.Rectangle{ 75 | Min: image.Point{X: 0, Y: 0}, 76 | Max: image.Point{X: colors, Y: 1}, 77 | }) 78 | for i := 0; i < colors; i++ { 79 | color, err := l.ColorMap.At(l.ColorMap.Min() + delta*float64(i)) 80 | if err != nil { 81 | panic(err) 82 | } 83 | img.Set(i, 0, color) 84 | } 85 | pImg = NewImage(img, l.ColorMap.Min(), 0, l.ColorMap.Max(), 1) 86 | } 87 | pImg.Plot(c, p) 88 | } 89 | 90 | // DataRange implements the DataRange method 91 | // of the plot.DataRanger interface. 92 | func (l *ColorBar) DataRange() (xmin, xmax, ymin, ymax float64) { 93 | l.check() 94 | if l.Vertical { 95 | return 0, 1, l.ColorMap.Min(), l.ColorMap.Max() 96 | } 97 | return l.ColorMap.Min(), l.ColorMap.Max(), 0, 1 98 | } 99 | -------------------------------------------------------------------------------- /palette/hsva.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 | // Copyright ©2011-2013 The bíogo Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | 9 | package palette 10 | 11 | import ( 12 | "image/color" 13 | "math" 14 | ) 15 | 16 | // HSVA represents a Hue/Saturation/Value/Alpha color. 17 | // H, S, V and A are valid within [0, 1]. 18 | type HSVA struct { 19 | H, S, V, A float64 20 | } 21 | 22 | // HSVAModel converts any color.Color to an HSVA color. 23 | var HSVAModel = color.ModelFunc(hsvaModel) 24 | 25 | func hsvaModel(c color.Color) color.Color { 26 | if _, ok := c.(HSVA); ok { 27 | return c 28 | } 29 | return rgbaToHsva(c.RGBA()) 30 | } 31 | 32 | // Convert r, g, b, a to HSVA 33 | func rgbaToHsva(r, g, b, a uint32) HSVA { 34 | red := float64(r) 35 | blue := float64(b) 36 | green := float64(g) 37 | 38 | max := math.Max(red, green) 39 | max = math.Max(max, blue) 40 | min := math.Min(red, green) 41 | min = math.Min(min, blue) 42 | chroma := max - min 43 | 44 | var hue float64 45 | switch { 46 | case chroma == 0: 47 | // This should really be math.NaN() since we have a 0 length vector, 48 | // but 0 seems to be the convention and it may simplify imports in 49 | // dependent packages. 50 | hue = 0 51 | case max == red: 52 | hue = math.Mod((green-blue)/chroma, 6) 53 | case max == green: 54 | hue = (blue-red)/chroma + 2 55 | case max == blue: 56 | hue = (red-green)/chroma + 4 57 | } 58 | 59 | hue /= 6 60 | 61 | var s float64 62 | if chroma != 0 { 63 | s = chroma / max 64 | } 65 | 66 | return HSVA{ 67 | H: math.Mod(math.Mod(hue, 1)+1, 1), 68 | S: s, 69 | V: max / math.MaxUint16, 70 | A: float64(a) / math.MaxUint16, 71 | } 72 | } 73 | 74 | // RGBA allows HSVAColor to satisfy the color.Color interface. 75 | func (c HSVA) RGBA() (r, g, b, a uint32) { 76 | var red, green, blue float64 77 | 78 | a = uint32(math.MaxUint16 * c.A) 79 | 80 | if c.V == 0 { 81 | return 82 | } 83 | 84 | if c.S == 0 { 85 | r, g, b = uint32(math.MaxUint16*c.V), uint32(math.MaxUint16*c.V), uint32(math.MaxUint16*c.V) 86 | return 87 | } 88 | 89 | chroma := c.V * c.S 90 | m := c.V - chroma 91 | 92 | if !math.IsNaN(c.H) { 93 | hue := math.Mod(c.H, 1) * 6 94 | x := chroma * (1 - math.Abs(math.Mod(hue, 2)-1)) 95 | switch math.Floor(hue) { 96 | case 0: 97 | red, green = chroma, x 98 | case 1: 99 | red, green = x, chroma 100 | case 2: 101 | green, blue = chroma, x 102 | case 3: 103 | green, blue = x, chroma 104 | case 4: 105 | red, blue = x, chroma 106 | case 5: 107 | red, blue = chroma, x 108 | } 109 | } else { 110 | red, green, blue = 0, 0, 0 111 | } 112 | 113 | r, g, b = uint32(math.MaxUint16*(red+m)), uint32(math.MaxUint16*(green+m)), uint32(math.MaxUint16*(blue+m)) 114 | 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /vg/draw/draw_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 draw 6 | 7 | import ( 8 | "image/color" 9 | "reflect" 10 | "testing" 11 | 12 | "gonum.org/v1/plot/vg" 13 | "gonum.org/v1/plot/vg/recorder" 14 | ) 15 | 16 | func TestCrop(t *testing.T) { 17 | ls := LineStyle{ 18 | Color: color.NRGBA{0, 20, 0, 123}, 19 | Width: 0.1 * vg.Inch, 20 | } 21 | var r1 recorder.Canvas 22 | c1 := NewCanvas(&r1, 6, 3) 23 | c11 := Crop(c1, 0, -3, 0, 0) 24 | c12 := Crop(c1, 3, 0, 0, 0) 25 | 26 | var r2 recorder.Canvas 27 | c2 := NewCanvas(&r2, 6, 3) 28 | c21 := Canvas{ 29 | Canvas: c2.Canvas, 30 | Rectangle: vg.Rectangle{ 31 | Min: vg.Point{X: 0, Y: 0}, 32 | Max: vg.Point{X: 3, Y: 3}, 33 | }, 34 | } 35 | c22 := Canvas{ 36 | Canvas: c2.Canvas, 37 | Rectangle: vg.Rectangle{ 38 | Min: vg.Point{X: 3, Y: 0}, 39 | Max: vg.Point{X: 6, Y: 3}, 40 | }, 41 | } 42 | str := "unexpected result: %+v != %+v" 43 | if c11.Rectangle != c21.Rectangle { 44 | t.Errorf(str, c11.Rectangle, c21.Rectangle) 45 | } 46 | if c12.Rectangle != c22.Rectangle { 47 | t.Errorf(str, c11.Rectangle, c21.Rectangle) 48 | } 49 | 50 | c11.StrokeLine2(ls, c11.Min.X, c11.Min.Y, 51 | c11.Min.X+3*vg.Inch, c11.Min.Y+3*vg.Inch) 52 | c12.StrokeLine2(ls, c12.Min.X, c12.Min.Y, 53 | c12.Min.X+3*vg.Inch, c12.Min.Y+3*vg.Inch) 54 | c21.StrokeLine2(ls, c21.Min.X, c21.Min.Y, 55 | c21.Min.X+3*vg.Inch, c21.Min.Y+3*vg.Inch) 56 | c22.StrokeLine2(ls, c22.Min.X, c22.Min.Y, 57 | c22.Min.X+3*vg.Inch, c22.Min.Y+3*vg.Inch) 58 | 59 | if !reflect.DeepEqual(r1.Actions, r2.Actions) { 60 | t.Errorf(str, r1.Actions, r2.Actions) 61 | } 62 | } 63 | 64 | func TestTile(t *testing.T) { 65 | var r recorder.Canvas 66 | c := NewCanvas(&r, 13, 7) 67 | const ( 68 | rows = 2 69 | cols = 3 70 | pad = 1 71 | ) 72 | tiles := Tiles{ 73 | Rows: rows, Cols: cols, 74 | PadTop: pad, PadBottom: pad, 75 | PadRight: pad, PadLeft: pad, 76 | PadX: pad, PadY: pad, 77 | } 78 | rectangles := [][]vg.Rectangle{ 79 | { 80 | vg.Rectangle{ 81 | Min: vg.Point{X: 1, Y: 4}, 82 | Max: vg.Point{X: 4, Y: 6}, 83 | }, 84 | vg.Rectangle{ 85 | Min: vg.Point{X: 5, Y: 4}, 86 | Max: vg.Point{X: 8, Y: 6}, 87 | }, 88 | vg.Rectangle{ 89 | Min: vg.Point{X: 9, Y: 4}, 90 | Max: vg.Point{X: 12, Y: 6}, 91 | }, 92 | }, 93 | { 94 | vg.Rectangle{ 95 | Min: vg.Point{X: 1, Y: 1}, 96 | Max: vg.Point{X: 4, Y: 3}, 97 | }, 98 | vg.Rectangle{ 99 | Min: vg.Point{X: 5, Y: 1}, 100 | Max: vg.Point{X: 8, Y: 3}, 101 | }, 102 | vg.Rectangle{ 103 | Min: vg.Point{X: 9, Y: 1}, 104 | Max: vg.Point{X: 12, Y: 3}, 105 | }, 106 | }, 107 | } 108 | for j := 0; j < rows; j++ { 109 | for i := 0; i < cols; i++ { 110 | str := "row %d col %d unexpected result: %+v != %+v" 111 | tile := tiles.At(c, i, j) 112 | if tile.Rectangle != rectangles[j][i] { 113 | t.Errorf(str, j, i, tile.Rectangle, rectangles[j][i]) 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /plotter/rotation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | "gonum.org/v1/plot/vg/draw" 18 | ) 19 | 20 | // Example_rotation gives some examples of rotating text. 21 | func Example_rotation() { 22 | n := 100 23 | xmax := 2 * math.Pi 24 | 25 | // Sin creates a sine curve. 26 | sin := func(n int, xmax float64) plotter.XYs { 27 | xy := make(plotter.XYs, n) 28 | for i := 0; i < n; i++ { 29 | xy[i].X = xmax / float64(n) * float64(i) 30 | xy[i].Y = math.Sin(xy[i].X) * 100 31 | } 32 | return xy 33 | } 34 | 35 | // These points will make up our sine curve. 36 | linePoints := sin(n, xmax) 37 | 38 | // These points are our label locations. 39 | labelPoints := sin(8, xmax) 40 | 41 | p, err := plot.New() 42 | if err != nil { 43 | log.Panic(err) 44 | } 45 | p.Title.Text = "Rotation Example" 46 | p.X.Label.Text = "X" 47 | p.Y.Label.Text = "100 × Sine X" 48 | 49 | l, err := plotter.NewLine(linePoints) 50 | if err != nil { 51 | log.Panic(err) 52 | } 53 | l.LineStyle.Width = vg.Points(1) 54 | l.LineStyle.Color = color.RGBA{B: 255, A: 255} 55 | 56 | labelData := plotter.XYLabels{ 57 | XYs: labelPoints, 58 | Labels: []string{"0", "pi/4", "pi/2", "3pi/4", "pi", "5pi/4", "3pi/2", "7pi/4", "2pi"}, 59 | } 60 | 61 | labels, err := plotter.NewLabels(labelData) 62 | if err != nil { 63 | log.Panic(err) 64 | } 65 | 66 | for i := range labels.TextStyle { 67 | x := labels.XYs[i].X 68 | 69 | // Set the label rotation to the slope of the line, so the label is 70 | // parallel with the line. 71 | labels.TextStyle[i].Rotation = math.Atan(math.Cos(x)) 72 | labels.TextStyle[i].XAlign = draw.XCenter 73 | labels.TextStyle[i].YAlign = draw.YCenter 74 | // Move the labels away from the line so they're more easily readable. 75 | if x >= math.Pi { 76 | labels.TextStyle[i].YAlign = draw.YTop 77 | } else { 78 | labels.TextStyle[i].YAlign = draw.YBottom 79 | } 80 | } 81 | 82 | p.Add(l, labels) 83 | 84 | // Add boundary boxes for debugging. 85 | p.Add(plotter.NewGlyphBoxes()) 86 | 87 | p.NominalX("0", "The number 1", "Number 2", "The number 3", "Number 4", 88 | "The number 5", "Number 6") 89 | 90 | // Change the rotation of the X tick labels to make them fit better. 91 | p.X.Tick.Label.Rotation = math.Pi / 5 92 | p.X.Tick.Label.YAlign = draw.YCenter 93 | p.X.Tick.Label.XAlign = draw.XRight 94 | 95 | // Also change the rotation of the Y tick labels. 96 | p.Y.Tick.Label.Rotation = math.Pi / 2 97 | p.Y.Tick.Label.XAlign = draw.XCenter 98 | p.Y.Tick.Label.YAlign = draw.YBottom 99 | 100 | err = p.Save(200, 150, "testdata/rotation.png") 101 | if err != nil { 102 | log.Panic(err) 103 | } 104 | } 105 | 106 | func TestRotation(t *testing.T) { 107 | cmpimg.CheckPlot(Example_rotation, t, "rotation.png") 108 | } 109 | -------------------------------------------------------------------------------- /cmpimg/checkplot.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 cmpimg 6 | 7 | import ( 8 | "bytes" 9 | "encoding/base64" 10 | "flag" 11 | "image" 12 | "image/png" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "testing" 18 | ) 19 | 20 | var GenerateTestData = flag.Bool("regen", false, "Uses the current state to regenerate the test data.") 21 | 22 | func goldenPath(path string) string { 23 | ext := filepath.Ext(path) 24 | noext := strings.TrimSuffix(path, ext) 25 | return noext + "_golden" + ext 26 | } 27 | 28 | // CheckPlot checks a generated plot against a previously created reference. 29 | // If generateTestData = true, it regenerates the reference. 30 | // For image.Image formats, a base64 encoded png representation is output to 31 | // the testing log when a difference is identified. 32 | func CheckPlot(ExampleFunc func(), t *testing.T, filenames ...string) { 33 | paths := make([]string, len(filenames)) 34 | for i, fn := range filenames { 35 | paths[i] = filepath.Join("testdata", fn) 36 | } 37 | 38 | if *GenerateTestData { 39 | // Recreate Golden images and exit. 40 | ExampleFunc() 41 | for _, path := range paths { 42 | golden := goldenPath(path) 43 | _ = os.Remove(golden) 44 | if err := os.Rename(path, golden); err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | return 49 | } 50 | 51 | // Run the example. 52 | ExampleFunc() 53 | 54 | // Read the images we've just generated and check them against the 55 | // Golden Images. 56 | for _, path := range paths { 57 | got, err := ioutil.ReadFile(path) 58 | if err != nil { 59 | t.Errorf("Failed to read %s: %v", path, err) 60 | continue 61 | } 62 | golden := goldenPath(path) 63 | want, err := ioutil.ReadFile(golden) 64 | if err != nil { 65 | t.Errorf("Failed to read golden file %s: %v", golden, err) 66 | continue 67 | } 68 | typ := filepath.Ext(path)[1:] // remove the dot in e.g. ".pdf" 69 | ok, err := Equal(typ, got, want) 70 | if err != nil { 71 | t.Errorf("failed to compare image for %s: %v", path, err) 72 | continue 73 | } 74 | if !ok { 75 | t.Errorf("image mismatch for %s\n", path) 76 | 77 | switch typ { 78 | case "jpeg", "jpg", "png", "tiff", "tif": 79 | v1, _, err := image.Decode(bytes.NewReader(got)) 80 | if err != nil { 81 | t.Errorf("failed to decode %s: %v", path, err) 82 | continue 83 | } 84 | v2, _, err := image.Decode(bytes.NewReader(want)) 85 | if err != nil { 86 | t.Errorf("failed to decode %s: %v", golden, err) 87 | continue 88 | } 89 | 90 | dst := image.NewRGBA64(v1.Bounds().Union(v2.Bounds())) 91 | rect := Diff(dst, v1, v2) 92 | t.Logf("image bounds union:%+v diff bounds intersection:%+v", dst.Bounds(), rect) 93 | 94 | var buf bytes.Buffer 95 | err = png.Encode(&buf, dst) 96 | if err != nil { 97 | t.Errorf("failed to encode difference png: %v", err) 98 | continue 99 | } 100 | t.Log("IMAGE:" + base64.StdEncoding.EncodeToString(buf.Bytes())) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /palette/palette_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 | // Copyright ©2011-2012 The bíogo Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | 9 | package palette 10 | 11 | import ( 12 | "image/color" 13 | "reflect" 14 | "testing" 15 | ) 16 | 17 | func TestRainbow(t *testing.T) { 18 | if !reflect.DeepEqual(Rainbow(10, 0, 1, 1, 1, 1), palette{ 19 | color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}, // "#FF0000FF" 20 | color.NRGBA{R: 0xff, G: 0xaa, B: 0x00, A: 0xff}, // "#FFAA00FF" 21 | color.NRGBA{R: 0xaa, G: 0xff, B: 0x00, A: 0xff}, // "#AAFF00FF" 22 | color.NRGBA{R: 0x00, G: 0xff, B: 0x00, A: 0xff}, // "#00FF00FF" 23 | color.NRGBA{R: 0x00, G: 0xff, B: 0xaa, A: 0xff}, // "#00FFAAFF" 24 | color.NRGBA{R: 0x00, G: 0xaa, B: 0xff, A: 0xff}, // "#00AAFFFF" 25 | color.NRGBA{R: 0x00, G: 0x00, B: 0xff, A: 0xff}, // "#0000FFFF" 26 | color.NRGBA{R: 0xaa, G: 0x00, B: 0xff, A: 0xff}, // "#AA00FFFF" 27 | color.NRGBA{R: 0xff, G: 0x00, B: 0xaa, A: 0xff}, // "#FF00AAFF" 28 | color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}, // "#FF0000FF" 29 | }) { 30 | t.Error("Rainbow does not agree with R rainbow") 31 | } 32 | } 33 | 34 | func TestHeat(t *testing.T) { 35 | if !reflect.DeepEqual(Heat(10, 1), palette{ 36 | color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}, // "#FF0000FF" 37 | color.NRGBA{R: 0xff, G: 0x24, B: 0x00, A: 0xff}, // "#FF2400FF" 38 | color.NRGBA{R: 0xff, G: 0x49, B: 0x00, A: 0xff}, // "#FF4900FF" 39 | color.NRGBA{R: 0xff, G: 0x6d, B: 0x00, A: 0xff}, // "#FF6D00FF" 40 | color.NRGBA{R: 0xff, G: 0x92, B: 0x00, A: 0xff}, // "#FF9200FF" 41 | color.NRGBA{R: 0xff, G: 0xb6, B: 0x00, A: 0xff}, // "#FFB600FF" 42 | color.NRGBA{R: 0xff, G: 0xdb, B: 0x00, A: 0xff}, // "#FFDB00FF" 43 | color.NRGBA{R: 0xff, G: 0xff, B: 0x00, A: 0xff}, // "#FFFF00FF" 44 | color.NRGBA{R: 0xff, G: 0xff, B: 0x3f, A: 0xff}, // "#FFFF40FF" Off by one compared to R. 45 | color.NRGBA{R: 0xff, G: 0xff, B: 0xbF, A: 0xff}, // "#FFFFBFFF" 46 | }) { 47 | t.Error("Heat does not agree with R heat.colors (ish)") 48 | } 49 | } 50 | 51 | func TestRadial(t *testing.T) { 52 | rad := Radial(10, Cyan, Magenta, 1) 53 | if !reflect.DeepEqual(rad, divergingPalette{ 54 | color.NRGBA{R: 0x7f, G: 0xff, B: 0xff, A: 0xff}, // "#80FFFFFF" Off by one compared to R. 55 | color.NRGBA{R: 0x99, G: 0xff, B: 0xff, A: 0xff}, // "#99FFFFFF" 56 | color.NRGBA{R: 0xb3, G: 0xff, B: 0xff, A: 0xff}, // "#B3FFFFFF" 57 | color.NRGBA{R: 0xcc, G: 0xff, B: 0xff, A: 0xff}, // "#CCFFFFFF" 58 | color.NRGBA{R: 0xe6, G: 0xff, B: 0xff, A: 0xff}, // "#E6FFFFFF" - middle low 59 | color.NRGBA{R: 0xff, G: 0xe6, B: 0xff, A: 0xff}, // "#FFE6FFFF" - middle high 60 | color.NRGBA{R: 0xff, G: 0xcc, B: 0xff, A: 0xff}, // "#FFCCFFFF" 61 | color.NRGBA{R: 0xff, G: 0xb3, B: 0xff, A: 0xff}, // "#FFB3FFFF" 62 | color.NRGBA{R: 0xff, G: 0x99, B: 0xff, A: 0xff}, // "#FF99FFFF" 63 | color.NRGBA{R: 0xff, G: 0x7f, B: 0xff, A: 0xff}, // "#FF80FFFF" Off by one compared to R. 64 | }) { 65 | t.Error("Radial does not agree with R cm.colors (ish)") 66 | } 67 | if l, h := rad.CriticalIndex(); l != 4 || h != 5 { 68 | t.Errorf("Radial(10...) gives unexpected critical index values: %d and %d", l, h) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plotter/testdata/polygon_holes_golden.eps: -------------------------------------------------------------------------------- 1 | %%!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator gonum.org/v1/plot/vg/vgeps 3 | %%Title: 4 | %%BoundingBox: 0 0 100 100 5 | %%CreationDate: 2017-11-09 14:19:37.614165532 +1030 ACDT m=+0.187713815 6 | %%Orientation: Portrait 7 | %%EndComments 8 | 9 | 1 setlinewidth 10 | 0 0 0 setrgbcolor 11 | 1 1 1 setrgbcolor 12 | newpath 13 | 0 0 moveto 14 | 100 0 lineto 15 | 100 100 lineto 16 | 0 100 lineto 17 | closepath 18 | fill 19 | 0 0 0 setrgbcolor 20 | /Times-Roman findfont 12 scalefont setfont 21 | 3.6641 88.445 moveto 22 | (Polygon with holes) show 23 | 62.75 3.8613 moveto 24 | (X) show 25 | /Times-Roman findfont 10 scalefont setfont 26 | 34.166 15.602 moveto 27 | (0) show 28 | 64.583 15.602 moveto 29 | (2) show 30 | 95 15.602 moveto 31 | (4) show 32 | 0.5 setlinewidth 33 | newpath 34 | 36.666 25.23 moveto 35 | 36.666 33.23 lineto 36 | stroke 37 | newpath 38 | 67.083 25.23 moveto 39 | 67.083 33.23 lineto 40 | stroke 41 | newpath 42 | 97.5 25.23 moveto 43 | 97.5 33.23 lineto 44 | stroke 45 | newpath 46 | 51.875 29.23 moveto 47 | 51.875 33.23 lineto 48 | stroke 49 | newpath 50 | 82.292 29.23 moveto 51 | 82.292 33.23 lineto 52 | stroke 53 | newpath 54 | 36.666 33.23 moveto 55 | 97.5 33.23 lineto 56 | stroke 57 | gsave 58 | 90 rotate 59 | /Times-Roman findfont 12 scalefont setfont 60 | 54.746 -11.555 moveto 61 | (Y) show 62 | grestore 63 | 15.416 33.759 moveto 64 | (0) show 65 | 15.416 54.357 moveto 66 | (2) show 67 | 15.416 74.955 moveto 68 | (4) show 69 | newpath 70 | 22.916 38.48 moveto 71 | 30.916 38.48 lineto 72 | stroke 73 | newpath 74 | 22.916 59.079 moveto 75 | 30.916 59.079 lineto 76 | stroke 77 | newpath 78 | 22.916 79.677 moveto 79 | 30.916 79.677 lineto 80 | stroke 81 | newpath 82 | 26.916 48.78 moveto 83 | 30.916 48.78 lineto 84 | stroke 85 | newpath 86 | 26.916 69.378 moveto 87 | 30.916 69.378 lineto 88 | stroke 89 | newpath 90 | 30.916 38.48 moveto 91 | 30.916 79.677 lineto 92 | stroke 93 | 0 0 1 setrgbcolor 94 | newpath 95 | 36.666 38.48 moveto 96 | 36.666 38.48 lineto 97 | 97.5 38.48 lineto 98 | 97.5 79.677 lineto 99 | 36.666 79.677 lineto 100 | closepath 101 | 44.27 43.63 moveto 102 | 44.27 43.63 lineto 103 | 59.479 43.63 lineto 104 | 59.479 53.929 lineto 105 | 44.27 53.929 lineto 106 | closepath 107 | 89.896 64.228 moveto 108 | 89.896 64.228 lineto 109 | 74.687 64.228 lineto 110 | 74.687 74.527 lineto 111 | 89.896 74.527 lineto 112 | closepath 113 | fill 114 | 0 0 0 setrgbcolor 115 | 1 setlinewidth 116 | newpath 117 | 36.666 38.48 moveto 118 | 97.5 38.48 lineto 119 | 97.5 79.677 lineto 120 | 36.666 79.677 lineto 121 | 36.666 38.48 lineto 122 | stroke 123 | newpath 124 | 44.27 43.63 moveto 125 | 59.479 43.63 lineto 126 | 59.479 53.929 lineto 127 | 44.27 53.929 lineto 128 | 44.27 43.63 lineto 129 | stroke 130 | newpath 131 | 89.896 64.228 moveto 132 | 74.687 64.228 lineto 133 | 74.687 74.527 lineto 134 | 89.896 74.527 lineto 135 | 89.896 64.228 lineto 136 | stroke 137 | 0 0 1 setrgbcolor 138 | newpath 139 | 90 38.48 moveto 140 | 90 46.332 lineto 141 | 100 46.332 lineto 142 | 100 38.48 lineto 143 | closepath 144 | fill 145 | 0 0 0 setrgbcolor 146 | newpath 147 | 90 38.48 moveto 148 | 90 46.332 lineto 149 | 100 46.332 lineto 150 | 100 38.48 lineto 151 | 90 38.48 lineto 152 | stroke 153 | 1 1 1 setrgbcolor 154 | /Times-Roman findfont 8 scalefont setfont 155 | 76.449 38.629 moveto 156 | (key) show 157 | showpage 158 | -------------------------------------------------------------------------------- /vg/recorder/recorder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 recorder 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | "strings" 11 | "testing" 12 | 13 | "gonum.org/v1/plot/vg" 14 | ) 15 | 16 | func TestRecorder(t *testing.T) { 17 | var rec Canvas 18 | rec.Actions = append(rec.Actions, &FillString{Font: "Times-Roman", Size: 12, Point: vg.Point{X: 0, Y: 10}, String: "Text"}) 19 | rec.Comment("End of preamble") 20 | rec.Scale(1, 2) 21 | rec.Rotate(0.72) 22 | rec.KeepCaller = true 23 | rec.Stroke(vg.Path{{Type: vg.MoveComp, Pos: vg.Point{X: 3, Y: 4}}}) 24 | rec.Push() 25 | rec.Pop() 26 | rec.Translate(vg.Point{3, 4}) 27 | rec.KeepCaller = false 28 | rec.SetLineWidth(100) 29 | rec.SetLineDash([]vg.Length{2, 5}, 6) 30 | rec.SetColor(color.RGBA{R: 0x65, G: 0x23, B: 0xf2}) 31 | rec.Fill(vg.Path{{Type: vg.MoveComp, Pos: vg.Point{X: 3, Y: 4}}, {Type: vg.LineComp, Pos: vg.Point{X: 2, Y: 3}}, {Type: vg.CloseComp}}) 32 | rec.DrawImage(vg.Rectangle{vg.Point{0, 0}, vg.Point{10, 10}}, img) 33 | if len(rec.Actions) != len(want) { 34 | t.Fatalf("unexpected number of actions recorded: got:%d want:%d", len(rec.Actions), len(want)) 35 | } 36 | for i, a := range rec.Actions { 37 | if got := a.Call(); !strings.HasSuffix(got, want[i]) { 38 | t.Errorf("unexpected action:\n\tgot: %#v\n\twant: %#v", got, want[i]) 39 | } 40 | } 41 | 42 | var replay Canvas 43 | err := rec.ReplayOn(&replay) 44 | if err != nil { 45 | t.Errorf("unexpected error: %v", err) 46 | } 47 | for i, a := range rec.Actions { 48 | got := replay.Actions[i].Call() 49 | want := a.Call() 50 | if !strings.HasSuffix(want, got) { 51 | t.Errorf("unexpected action:\n\tgot: %#v\n\twant: %#v", got, want) 52 | } 53 | } 54 | 55 | replay.Reset() 56 | rec.Actions = append(rec.Actions, &FillString{Font: "Foo", Size: 12, Point: vg.Point{X: 0, Y: 10}, String: "Bar"}) 57 | err = rec.ReplayOn(&replay) 58 | if !strings.HasPrefix(err.Error(), "Unknown font: Foo.") { 59 | t.Errorf("unexpected error: %v", err) 60 | } 61 | } 62 | 63 | var img image.Image = image.NewGray(image.Rect(0, 0, 20, 20)) 64 | 65 | var want = []string{ 66 | `FillString("Times-Roman", 12, 0, 10, "Text")`, 67 | `Comment("End of preamble")`, 68 | `Scale(1, 2)`, 69 | `Rotate(0.72)`, 70 | `gonum.org/v1/plot/vg/recorder/recorder_test.go:23 Stroke(vg.Path{vg.PathComp{Type:0, Pos:vg.Point{X:3, Y:4}, Control:[]vg.Point(nil), Radius:0, Start:0, Angle:0}})`, 71 | `gonum.org/v1/plot/vg/recorder/recorder_test.go:24 Push()`, 72 | `gonum.org/v1/plot/vg/recorder/recorder_test.go:25 Pop()`, 73 | `gonum.org/v1/plot/vg/recorder/recorder_test.go:26 Translate(3, 4)`, 74 | `SetLineWidth(100)`, 75 | `SetLineDash([]vg.Length{2, 5}, 6)`, 76 | `SetColor(color.RGBA{R:0x65, G:0x23, B:0xf2, A:0x0})`, 77 | `Fill(vg.Path{vg.PathComp{Type:0, Pos:vg.Point{X:3, Y:4}, Control:[]vg.Point(nil), Radius:0, Start:0, Angle:0}, vg.PathComp{Type:1, Pos:vg.Point{X:2, Y:3}, Control:[]vg.Point(nil), Radius:0, Start:0, Angle:0}, vg.PathComp{Type:4, Pos:vg.Point{X:0, Y:0}, Control:[]vg.Point(nil), Radius:0, Start:0, Angle:0}})`, 78 | `DrawImage(vg.Rectangle{Min:vg.Point{X:0, Y:0}, Max:vg.Point{X:10, Y:10}}, {image.Rectangle{Min:image.Point{X:0, Y:0}, Max:image.Point{X:20, Y:20}}, IMAGE:iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAAAAACo4kLRAAAAFElEQVR4nGJiwAJGBQeVICAAAP//JBgAKeMueQ8AAAAASUVORK5CYII=})`, 79 | } 80 | -------------------------------------------------------------------------------- /vg/vgsvg/vgsvg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2019 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 vgsvg_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "log" 11 | "testing" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | "gonum.org/v1/plot/vg/draw" 18 | "gonum.org/v1/plot/vg/vgsvg" 19 | ) 20 | 21 | func Example() { 22 | p, err := plot.New() 23 | if err != nil { 24 | log.Fatalf("could not create plot: %v", err) 25 | } 26 | p.Title.Text = "Scatter plot" 27 | p.X.Label.Text = "X" 28 | p.Y.Label.Text = "Y" 29 | 30 | scatter, err := plotter.NewScatter(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 31 | if err != nil { 32 | log.Fatalf("could not create scatter: %v", err) 33 | } 34 | p.Add(scatter) 35 | 36 | err = p.Save(5*vg.Centimeter, 5*vg.Centimeter, "testdata/scatter.svg") 37 | if err != nil { 38 | log.Fatalf("could not save SVG plot: %v", err) 39 | } 40 | } 41 | 42 | func TestSVG(t *testing.T) { 43 | cmpimg.CheckPlot(Example, t, "scatter.svg") 44 | } 45 | 46 | func TestNewWith(t *testing.T) { 47 | p, err := plot.New() 48 | if err != nil { 49 | t.Fatalf("could not create plot: %v", err) 50 | } 51 | p.Title.Text = "Scatter plot" 52 | p.X.Label.Text = "X" 53 | p.Y.Label.Text = "Y" 54 | 55 | scatter, err := plotter.NewScatter(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 56 | if err != nil { 57 | t.Fatalf("could not create scatter: %v", err) 58 | } 59 | p.Add(scatter) 60 | 61 | c := vgsvg.NewWith(vgsvg.UseWH(5*vg.Centimeter, 5*vg.Centimeter)) 62 | p.Draw(draw.New(c)) 63 | 64 | b := new(bytes.Buffer) 65 | if _, err = c.WriteTo(b); err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | want, err := ioutil.ReadFile("testdata/scatter_golden.svg") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | ok, err := cmpimg.Equal("svg", b.Bytes(), want) 75 | if err != nil { 76 | t.Fatalf("could not compare images: %v", err) 77 | } 78 | if !ok { 79 | t.Fatalf("images differ:\ngot:\n%s\nwant:\n%s\n", b.Bytes(), want) 80 | } 81 | } 82 | 83 | func TestHtmlEscape(t *testing.T) { 84 | p, err := plot.New() 85 | if err != nil { 86 | t.Fatalf("could not create plot: %v", err) 87 | } 88 | p.Title.Text = "Scatter & line plot" 89 | p.X.Label.Text = "X" 90 | p.Y.Label.Text = "Y" 91 | 92 | scatter, err := plotter.NewScatter(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 93 | if err != nil { 94 | t.Fatalf("could not create scatter: %v", err) 95 | } 96 | p.Add(scatter) 97 | 98 | line, err := plotter.NewLine(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 99 | if err != nil { 100 | t.Fatalf("could not create scatter: %v", err) 101 | } 102 | line.Width = 0.5 103 | p.Add(line) 104 | 105 | c := vgsvg.NewWith(vgsvg.UseWH(5*vg.Centimeter, 5*vg.Centimeter)) 106 | p.Draw(draw.New(c)) 107 | 108 | b := new(bytes.Buffer) 109 | if _, err = c.WriteTo(b); err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | want, err := ioutil.ReadFile("testdata/scatter_line_golden.svg") 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | ok, err := cmpimg.Equal("svg", b.Bytes(), want) 119 | if err != nil { 120 | t.Fatalf("could not compare images: %v", err) 121 | } 122 | if !ok { 123 | t.Fatalf("images differ:\ngot:\n%s\nwant:\n%s\n", b.Bytes(), want) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /plotter/labels.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter 6 | 7 | import ( 8 | "errors" 9 | 10 | "gonum.org/v1/plot" 11 | "gonum.org/v1/plot/vg" 12 | "gonum.org/v1/plot/vg/draw" 13 | ) 14 | 15 | var ( 16 | // DefaultFont is the default font for label text. 17 | DefaultFont = plot.DefaultFont 18 | 19 | // DefaultFontSize is the default font. 20 | DefaultFontSize = vg.Points(10) 21 | ) 22 | 23 | // Labels implements the Plotter interface, 24 | // drawing a set of labels at specified points. 25 | type Labels struct { 26 | XYs 27 | 28 | // Labels is the set of labels corresponding 29 | // to each point. 30 | Labels []string 31 | 32 | // TextStyle is the style of the label text. Each label 33 | // can have a different text style. 34 | TextStyle []draw.TextStyle 35 | 36 | // XOffset and YOffset are added directly to the final 37 | // label X and Y location respectively. 38 | XOffset, YOffset vg.Length 39 | } 40 | 41 | // NewLabels returns a new Labels using the DefaultFont and 42 | // the DefaultFontSize. 43 | func NewLabels(d XYLabeller) (*Labels, error) { 44 | xys, err := CopyXYs(d) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | if d.Len() != len(xys) { 50 | return nil, errors.New("Number of points does not match the number of labels") 51 | } 52 | 53 | strs := make([]string, d.Len()) 54 | for i := range strs { 55 | strs[i] = d.Label(i) 56 | } 57 | 58 | fnt, err := vg.MakeFont(DefaultFont, DefaultFontSize) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | styles := make([]draw.TextStyle, d.Len()) 64 | for i := range styles { 65 | styles[i] = draw.TextStyle{Font: fnt} 66 | } 67 | 68 | return &Labels{ 69 | XYs: xys, 70 | Labels: strs, 71 | TextStyle: styles, 72 | }, nil 73 | } 74 | 75 | // Plot implements the Plotter interface, drawing labels. 76 | func (l *Labels) Plot(c draw.Canvas, p *plot.Plot) { 77 | trX, trY := p.Transforms(&c) 78 | for i, label := range l.Labels { 79 | pt := vg.Point{X: trX(l.XYs[i].X), Y: trY(l.XYs[i].Y)} 80 | if !c.Contains(pt) { 81 | continue 82 | } 83 | pt.X += l.XOffset 84 | pt.Y += l.YOffset 85 | c.FillText(l.TextStyle[i], pt, label) 86 | } 87 | } 88 | 89 | // DataRange returns the minimum and maximum X and Y values 90 | func (l *Labels) DataRange() (xmin, xmax, ymin, ymax float64) { 91 | return XYRange(l) 92 | } 93 | 94 | // GlyphBoxes returns a slice of GlyphBoxes, 95 | // one for each of the labels, implementing the 96 | // plot.GlyphBoxer interface. 97 | func (l *Labels) GlyphBoxes(p *plot.Plot) []plot.GlyphBox { 98 | bs := make([]plot.GlyphBox, len(l.Labels)) 99 | for i, label := range l.Labels { 100 | bs[i].X = p.X.Norm(l.XYs[i].X) 101 | bs[i].Y = p.Y.Norm(l.XYs[i].Y) 102 | sty := l.TextStyle[i] 103 | bs[i].Rectangle = sty.Rectangle(label) 104 | } 105 | return bs 106 | } 107 | 108 | // XYLabeller combines the XYer and Labeller types. 109 | type XYLabeller interface { 110 | XYer 111 | Labeller 112 | } 113 | 114 | // XYLabels holds XY data with labels. 115 | // The ith label corresponds to the ith XY. 116 | type XYLabels struct { 117 | XYs 118 | Labels []string 119 | } 120 | 121 | // Label returns the label for point index i. 122 | func (l XYLabels) Label(i int) string { 123 | return l.Labels[i] 124 | } 125 | -------------------------------------------------------------------------------- /plotutil/plotutil.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotutil contains a small number of utilites for creating plots. 6 | // 7 | // This package is under active development so portions of it may change. 8 | package plotutil // import "gonum.org/v1/plot/plotutil" 9 | 10 | import ( 11 | "image/color" 12 | 13 | "gonum.org/v1/plot/vg" 14 | "gonum.org/v1/plot/vg/draw" 15 | ) 16 | 17 | // DefaultColors is a set of colors used by the Color function. 18 | var DefaultColors = SoftColors 19 | 20 | var DarkColors = []color.Color{ 21 | rgb(238, 46, 47), 22 | rgb(0, 140, 72), 23 | rgb(24, 90, 169), 24 | rgb(244, 125, 35), 25 | rgb(102, 44, 145), 26 | rgb(162, 29, 33), 27 | rgb(180, 56, 148), 28 | } 29 | 30 | var SoftColors = []color.Color{ 31 | rgb(241, 90, 96), 32 | rgb(122, 195, 106), 33 | rgb(90, 155, 212), 34 | rgb(250, 167, 91), 35 | rgb(158, 103, 171), 36 | rgb(206, 112, 88), 37 | rgb(215, 127, 180), 38 | } 39 | 40 | func rgb(r, g, b uint8) color.RGBA { 41 | return color.RGBA{r, g, b, 255} 42 | } 43 | 44 | // Color returns the ith default color, wrapping 45 | // if i is less than zero or greater than the max 46 | // number of colors in the DefaultColors slice. 47 | func Color(i int) color.Color { 48 | n := len(DefaultColors) 49 | if i < 0 { 50 | return DefaultColors[i%n+n] 51 | } 52 | return DefaultColors[i%n] 53 | } 54 | 55 | // DefaultGlyphShapes is a set of GlyphDrawers used by 56 | // the Shape function. 57 | var DefaultGlyphShapes = []draw.GlyphDrawer{ 58 | draw.RingGlyph{}, 59 | draw.SquareGlyph{}, 60 | draw.TriangleGlyph{}, 61 | draw.CrossGlyph{}, 62 | draw.PlusGlyph{}, 63 | draw.CircleGlyph{}, 64 | draw.BoxGlyph{}, 65 | draw.PyramidGlyph{}, 66 | } 67 | 68 | // Shape returns the ith default glyph shape, 69 | // wrapping if i is less than zero or greater 70 | // than the max number of GlyphDrawers 71 | // in the DefaultGlyphShapes slice. 72 | func Shape(i int) draw.GlyphDrawer { 73 | n := len(DefaultGlyphShapes) 74 | if i < 0 { 75 | return DefaultGlyphShapes[i%n+n] 76 | } 77 | return DefaultGlyphShapes[i%n] 78 | } 79 | 80 | // DefaultDashes is a set of dash patterns used by 81 | // the Dashes function. 82 | var DefaultDashes = [][]vg.Length{ 83 | {}, 84 | 85 | {vg.Points(6), vg.Points(2)}, 86 | 87 | {vg.Points(2), vg.Points(2)}, 88 | 89 | {vg.Points(1), vg.Points(1)}, 90 | 91 | {vg.Points(5), vg.Points(2), vg.Points(1), vg.Points(2)}, 92 | 93 | {vg.Points(10), vg.Points(2), vg.Points(2), vg.Points(2), 94 | vg.Points(2), vg.Points(2), vg.Points(2), vg.Points(2)}, 95 | 96 | {vg.Points(10), vg.Points(2), vg.Points(2), vg.Points(2)}, 97 | 98 | {vg.Points(5), vg.Points(2), vg.Points(5), vg.Points(2), 99 | vg.Points(2), vg.Points(2), vg.Points(2), vg.Points(2)}, 100 | 101 | {vg.Points(4), vg.Points(2), vg.Points(4), vg.Points(1), 102 | vg.Points(1), vg.Points(1), vg.Points(1), vg.Points(1), 103 | vg.Points(1), vg.Points(1)}, 104 | } 105 | 106 | // Dashes returns the ith default dash pattern, 107 | // wrapping if i is less than zero or greater 108 | // than the max number of dash patters 109 | // in the DefaultDashes slice. 110 | func Dashes(i int) []vg.Length { 111 | n := len(DefaultDashes) 112 | if i < 0 { 113 | return DefaultDashes[i%n+n] 114 | } 115 | return DefaultDashes[i%n] 116 | } 117 | -------------------------------------------------------------------------------- /plotter/quartile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "log" 9 | "testing" 10 | 11 | "golang.org/x/exp/rand" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/cmpimg" 15 | "gonum.org/v1/plot/plotter" 16 | "gonum.org/v1/plot/vg" 17 | ) 18 | 19 | func ExampleQuartPlot() { 20 | rnd := rand.New(rand.NewSource(1)) 21 | 22 | // Create the example data. 23 | n := 100 24 | uniform := make(plotter.Values, n) 25 | normal := make(plotter.Values, n) 26 | expon := make(plotter.Values, n) 27 | for i := 0; i < n; i++ { 28 | uniform[i] = rnd.Float64() 29 | normal[i] = rnd.NormFloat64() 30 | expon[i] = rnd.ExpFloat64() 31 | } 32 | 33 | // Create the QuartPlots 34 | qp1, err := plotter.NewQuartPlot(0, uniform) 35 | if err != nil { 36 | log.Panic(err) 37 | } 38 | qp2, err := plotter.NewQuartPlot(1, normal) 39 | if err != nil { 40 | log.Panic(err) 41 | } 42 | qp3, err := plotter.NewQuartPlot(2, expon) 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | 47 | // Create a vertical plot 48 | p1, err := plot.New() 49 | if err != nil { 50 | log.Panic(err) 51 | } 52 | p1.Title.Text = "Quartile Plot" 53 | p1.Y.Label.Text = "plotter.Values" 54 | p1.Add(qp1, qp2, qp3) 55 | 56 | // Set the X axis of the plot to nominal with 57 | // the given names for x=0, x=1 and x=2. 58 | p1.NominalX("Uniform\nDistribution", "Normal\nDistribution", 59 | "Exponential\nDistribution") 60 | 61 | err = p1.Save(200, 200, "testdata/verticalQuartPlot.png") 62 | if err != nil { 63 | log.Panic(err) 64 | } 65 | 66 | // Create a horizontal plot 67 | qp1.Horizontal = true 68 | qp2.Horizontal = true 69 | qp3.Horizontal = true 70 | 71 | p2, err := plot.New() 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | p2.Title.Text = "Quartile Plot" 76 | p2.X.Label.Text = "plotter.Values" 77 | p2.Add(qp1, qp2, qp3) 78 | 79 | // Set the Y axis of the plot to nominal with 80 | // the given names for y=0, y=1 and y=2. 81 | p2.NominalY("Uniform\nDistribution", "Normal\nDistribution", 82 | "Exponential\nDistribution") 83 | 84 | err = p2.Save(200, 200, "testdata/horizontalQuartPlot.png") 85 | if err != nil { 86 | log.Panic(err) 87 | } 88 | 89 | // Now, create a grouped quartile plot. 90 | 91 | p3, err := plot.New() 92 | if err != nil { 93 | log.Panic(err) 94 | } 95 | p3.Title.Text = "Box Plot" 96 | p3.Y.Label.Text = "plotter.Values" 97 | 98 | w := vg.Points(10) 99 | for x := 0.0; x < 3.0; x++ { 100 | b0, err := plotter.NewQuartPlot(x, uniform) 101 | if err != nil { 102 | log.Panic(err) 103 | } 104 | b0.Offset = -w 105 | b1, err := plotter.NewQuartPlot(x, normal) 106 | if err != nil { 107 | log.Panic(err) 108 | } 109 | b2, err := plotter.NewQuartPlot(x, expon) 110 | if err != nil { 111 | log.Panic(err) 112 | } 113 | b2.Offset = w 114 | p3.Add(b0, b1, b2) 115 | } 116 | p3.Add(plotter.NewGlyphBoxes()) 117 | 118 | p3.NominalX("Group 0", "Group 1", "Group 2") 119 | 120 | err = p3.Save(200, 200, "testdata/groupedQuartPlot.png") 121 | if err != nil { 122 | log.Panic(err) 123 | } 124 | } 125 | 126 | func TestQuartPlot(t *testing.T) { 127 | cmpimg.CheckPlot(ExampleQuartPlot, t, "verticalQuartPlot.png", 128 | "horizontalQuartPlot.png", 129 | "groupedQuartPlot.png") 130 | } 131 | -------------------------------------------------------------------------------- /plotutil/errorpoints.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotutil 6 | 7 | import ( 8 | "math" 9 | "sort" 10 | 11 | "gonum.org/v1/plot/plotter" 12 | ) 13 | 14 | // ErrorPoints holds a set of x, y pairs along 15 | // with their X and Y errors. 16 | type ErrorPoints struct { 17 | plotter.XYs 18 | plotter.XErrors 19 | plotter.YErrors 20 | } 21 | 22 | // NewErrorPoints returns a new ErrorPoints where each 23 | // point in the ErrorPoints is given by evaluating the 24 | // center function on the Xs and Ys for the corresponding 25 | // set of XY values in the pts parameter. The XError 26 | // and YError are computed likewise, using the err 27 | // function. 28 | // 29 | // This function can be useful for summarizing sets of 30 | // scatter points using a single point and error bars for 31 | // each element of the scatter. 32 | func NewErrorPoints(f func([]float64) (c, l, h float64), pts ...plotter.XYer) (*ErrorPoints, error) { 33 | 34 | c := &ErrorPoints{ 35 | XYs: make(plotter.XYs, len(pts)), 36 | XErrors: make(plotter.XErrors, len(pts)), 37 | YErrors: make(plotter.YErrors, len(pts)), 38 | } 39 | 40 | for i, xy := range pts { 41 | xs := make([]float64, xy.Len()) 42 | ys := make([]float64, xy.Len()) 43 | for j := 0; j < xy.Len(); j++ { 44 | xs[j], ys[j] = xy.XY(j) 45 | if err := plotter.CheckFloats(xs[j], ys[j]); err != nil { 46 | return nil, err 47 | } 48 | } 49 | c.XYs[i].X, c.XErrors[i].Low, c.XErrors[i].High = f(xs) 50 | if err := plotter.CheckFloats(c.XYs[i].X, c.XErrors[i].Low, c.XErrors[i].High); err != nil { 51 | return nil, err 52 | } 53 | c.XYs[i].Y, c.YErrors[i].Low, c.YErrors[i].High = f(ys) 54 | if err := plotter.CheckFloats(c.XYs[i].Y, c.YErrors[i].Low, c.YErrors[i].High); err != nil { 55 | return nil, err 56 | } 57 | } 58 | 59 | return c, nil 60 | } 61 | 62 | // MeanAndConf95 returns the mean 63 | // and the magnitude of the 95% confidence 64 | // interval on the mean as low and high 65 | // error values. 66 | // 67 | // MeanAndConf95 may be used as 68 | // the f argument to NewErrorPoints. 69 | func MeanAndConf95(vls []float64) (mean, lowerr, higherr float64) { 70 | n := float64(len(vls)) 71 | 72 | sum := 0.0 73 | for _, v := range vls { 74 | sum += v 75 | } 76 | mean = sum / n 77 | 78 | sum = 0.0 79 | for _, v := range vls { 80 | diff := v - mean 81 | sum += diff * diff 82 | } 83 | stdev := math.Sqrt(sum / n) 84 | 85 | conf := 1.96 * stdev / math.Sqrt(n) 86 | return mean, conf, conf 87 | } 88 | 89 | // MedianAndMinMax returns the median 90 | // value and error on the median given 91 | // by the minimum and maximum data 92 | // values. 93 | // 94 | // MedianAndMinMax may be used as 95 | // the f argument to NewErrorPoints. 96 | func MedianAndMinMax(vls []float64) (med, lowerr, higherr float64) { 97 | n := len(vls) 98 | if n == 0 { 99 | panic("plotutil: MedianAndMinMax: No values") 100 | } 101 | if n == 1 { 102 | return vls[0], 0, 0 103 | } 104 | sort.Float64s(vls) 105 | if n%2 == 0 { 106 | med = (vls[n/2+1]-vls[n/2])/2 + vls[n/2] 107 | } else { 108 | med = vls[n/2] 109 | } 110 | 111 | min := vls[0] 112 | max := vls[0] 113 | for _, v := range vls { 114 | min = math.Min(min, v) 115 | max = math.Max(max, v) 116 | } 117 | 118 | return med, med - min, max - med 119 | } 120 | -------------------------------------------------------------------------------- /vg/vgimg/vgimg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2012 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 vgimg_test 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "image/color" 11 | "io/ioutil" 12 | "log" 13 | "reflect" 14 | "sync" 15 | "testing" 16 | 17 | "gonum.org/v1/plot" 18 | "gonum.org/v1/plot/cmpimg" 19 | "gonum.org/v1/plot/plotter" 20 | "gonum.org/v1/plot/vg" 21 | "gonum.org/v1/plot/vg/draw" 22 | "gonum.org/v1/plot/vg/vgimg" 23 | ) 24 | 25 | func TestIssue179(t *testing.T) { 26 | scatter, err := plotter.NewScatter(plotter.XYs{{1, 1}, {0, 1}, {0, 0}}) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | p, err := plot.New() 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | p.Add(scatter) 35 | p.HideAxes() 36 | 37 | c := vgimg.JpegCanvas{Canvas: vgimg.New(5.08*vg.Centimeter, 5.08*vg.Centimeter)} 38 | p.Draw(draw.New(c)) 39 | b := bytes.NewBuffer([]byte{}) 40 | if _, err = c.WriteTo(b); err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | want, err := ioutil.ReadFile("testdata/issue179_golden.jpg") 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | ok, err := cmpimg.Equal("jpg", b.Bytes(), want) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if !ok { 54 | ioutil.WriteFile("testdata/issue179.jpg", b.Bytes(), 0644) 55 | t.Fatalf("images differ") 56 | } 57 | } 58 | 59 | func TestConcurrentInit(t *testing.T) { 60 | ft, err := vg.MakeFont("Helvetica", 10) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | var wg sync.WaitGroup 65 | wg.Add(2) 66 | go func() { 67 | c := vgimg.New(215, 215) 68 | c.FillString(ft, vg.Point{}, "hi") 69 | wg.Done() 70 | }() 71 | go func() { 72 | c := vgimg.New(215, 215) 73 | c.FillString(ft, vg.Point{}, "hi") 74 | wg.Done() 75 | }() 76 | wg.Wait() 77 | } 78 | 79 | func TestUseBackgroundColor(t *testing.T) { 80 | colors := []color.Color{color.Transparent, color.NRGBA{R: 255, A: 255}} 81 | for i, col := range colors { 82 | t.Run(fmt.Sprint(i), func(t *testing.T) { 83 | c := vgimg.NewWith(vgimg.UseWH(1, 1), vgimg.UseBackgroundColor(col)) 84 | img := c.Image() 85 | wantCol := color.RGBAModel.Convert(col) 86 | haveCol := img.At(0, 0) 87 | if !reflect.DeepEqual(haveCol, wantCol) { 88 | t.Fatalf("color should be %#v but is %#v", wantCol, haveCol) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestIssue540(t *testing.T) { 95 | p, err := plot.New() 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | xys := plotter.XYs{ 101 | plotter.XY{0, 0}, 102 | plotter.XY{1, 1}, 103 | plotter.XY{2, 2}, 104 | } 105 | 106 | p.Title.Text = "My title" 107 | p.X.Tick.Label.Font.Size = 0 // hide X-axis labels 108 | p.Y.Tick.Label.Font.Size = 0 // hide Y-axis labels 109 | 110 | lines, points, err := plotter.NewLinePoints(xys) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | lines.Color = color.RGBA{B: 255, A: 255} 115 | 116 | p.Add(lines, points) 117 | p.Add(plotter.NewGrid()) 118 | 119 | err = p.Save(100, 100, "testdata/issue540.png") 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | want, err := ioutil.ReadFile("testdata/issue540_golden.png") 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | got, err := ioutil.ReadFile("testdata/issue540.png") 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | 134 | ok, err := cmpimg.Equal("png", got, want) 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | if !ok { 139 | t.Fatalf("images differ") 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /plotter/histogram_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "image/color" 9 | "log" 10 | "math" 11 | "testing" 12 | "time" 13 | 14 | "golang.org/x/exp/rand" 15 | 16 | "gonum.org/v1/plot" 17 | "gonum.org/v1/plot/cmpimg" 18 | "gonum.org/v1/plot/plotter" 19 | "gonum.org/v1/plot/vg" 20 | ) 21 | 22 | // An example of making a histogram. 23 | func ExampleHistogram() { 24 | rnd := rand.New(rand.NewSource(1)) 25 | 26 | // stdNorm returns the probability of drawing a 27 | // value from a standard normal distribution. 28 | stdNorm := func(x float64) float64 { 29 | const sigma = 1.0 30 | const mu = 0.0 31 | const root2π = 2.50662827459517818309 32 | return 1.0 / (sigma * root2π) * math.Exp(-((x-mu)*(x-mu))/(2*sigma*sigma)) 33 | } 34 | 35 | n := 10000 36 | vals := make(plotter.Values, n) 37 | for i := 0; i < n; i++ { 38 | vals[i] = rnd.NormFloat64() 39 | } 40 | 41 | p, err := plot.New() 42 | if err != nil { 43 | log.Panic(err) 44 | } 45 | p.Title.Text = "Histogram" 46 | h, err := plotter.NewHist(vals, 16) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | h.Normalize(1) 51 | p.Add(h) 52 | 53 | // The normal distribution function 54 | norm := plotter.NewFunction(stdNorm) 55 | norm.Color = color.RGBA{R: 255, A: 255} 56 | norm.Width = vg.Points(2) 57 | p.Add(norm) 58 | 59 | err = p.Save(200, 200, "testdata/histogram.png") 60 | if err != nil { 61 | log.Panic(err) 62 | } 63 | } 64 | 65 | func TestHistogram(t *testing.T) { 66 | cmpimg.CheckPlot(ExampleHistogram, t, "histogram.png") 67 | } 68 | 69 | func TestSingletonHistogram(t *testing.T) { 70 | done := make(chan struct{}, 1) 71 | go func() { 72 | defer close(done) 73 | p, err := plot.New() 74 | if err != nil { 75 | t.Fatalf("unexpected error from plot.New: %v", err) 76 | } 77 | 78 | hist, err := plotter.NewHist(plotter.Values([]float64{1.0}), 60) 79 | if err != nil { 80 | t.Fatalf("unexpected error from NewHist: %v", err) 81 | } 82 | hist.Normalize(1) 83 | 84 | p.Add(hist) 85 | 86 | _, err = p.WriterTo(4*vg.Inch, 4*vg.Inch, "png") 87 | if err != nil { 88 | t.Fatalf("unexpected error from WriterTo: %v", err) 89 | } 90 | }() 91 | 92 | select { 93 | case <-time.After(10 * time.Second): 94 | t.Error("histogram timed out") 95 | case <-done: 96 | } 97 | } 98 | 99 | func ExampleHistogram_logScaleY() { 100 | p, err := plot.New() 101 | if err != nil { 102 | log.Panic(err) 103 | } 104 | p.Title.Text = "Histogram in log-y" 105 | p.Y.Scale = plot.LogScale{} 106 | p.Y.Tick.Marker = plot.LogTicks{} 107 | p.Y.Label.Text = "Y" 108 | p.X.Label.Text = "X" 109 | 110 | h1, err := plotter.NewHist(plotter.Values{ 111 | -2, -2, 112 | -1, 113 | +3, +3, +3, +3, 114 | +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, 115 | +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, 116 | }, 16) 117 | if err != nil { 118 | log.Fatal(err) 119 | } 120 | h1.LogY = true 121 | h1.FillColor = color.RGBA{255, 0, 0, 255} 122 | 123 | h2, err := plotter.NewHist(plotter.Values{ 124 | -3, -3, -3, 125 | +2, +2, +2, +2, +2, 126 | }, 16) 127 | 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | h2.LogY = true 133 | h2.FillColor = color.RGBA{0, 0, 255, 255} 134 | 135 | p.Add(h1, h2, plotter.NewGrid()) 136 | 137 | err = p.Save(200, 200, "testdata/histogram_logy.png") 138 | if err != nil { 139 | log.Fatal(err) 140 | } 141 | } 142 | 143 | func TestHistogramLogScale(t *testing.T) { 144 | cmpimg.CheckPlot(ExampleHistogram_logScaleY, t, "histogram_logy.png") 145 | } 146 | -------------------------------------------------------------------------------- /plotter/scatterColor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "math" 11 | "os" 12 | "testing" 13 | 14 | "golang.org/x/exp/rand" 15 | 16 | "gonum.org/v1/plot" 17 | "gonum.org/v1/plot/cmpimg" 18 | "gonum.org/v1/plot/palette/moreland" 19 | "gonum.org/v1/plot/plotter" 20 | "gonum.org/v1/plot/vg" 21 | "gonum.org/v1/plot/vg/draw" 22 | "gonum.org/v1/plot/vg/vgimg" 23 | ) 24 | 25 | // ExampleScatter_color draws a colored scatter plot. 26 | // Each point is plotted with a different color depending on 27 | // external criteria. 28 | func ExampleScatter_color() { 29 | rnd := rand.New(rand.NewSource(1)) 30 | 31 | // randomTriples returns some random but correlated x, y, z triples 32 | randomTriples := func(n int) plotter.XYZs { 33 | data := make(plotter.XYZs, n) 34 | for i := range data { 35 | if i == 0 { 36 | data[i].X = rnd.Float64() 37 | } else { 38 | data[i].X = data[i-1].X + 2*rnd.Float64() 39 | } 40 | data[i].Y = data[i].X + 10*rnd.Float64() 41 | data[i].Z = data[i].X 42 | } 43 | return data 44 | } 45 | 46 | n := 15 47 | scatterData := randomTriples(n) 48 | 49 | // Calculate the range of Z values. 50 | minZ, maxZ := math.Inf(1), math.Inf(-1) 51 | for _, xyz := range scatterData { 52 | if xyz.Z > maxZ { 53 | maxZ = xyz.Z 54 | } 55 | if xyz.Z < minZ { 56 | minZ = xyz.Z 57 | } 58 | } 59 | 60 | colors := moreland.Kindlmann() // Initialize a color map. 61 | colors.SetMax(maxZ) 62 | colors.SetMin(minZ) 63 | 64 | p, err := plot.New() 65 | if err != nil { 66 | log.Panic(err) 67 | } 68 | p.Title.Text = "Colored Points Example" 69 | p.X.Label.Text = "X" 70 | p.Y.Label.Text = "Y" 71 | p.Add(plotter.NewGrid()) 72 | 73 | sc, err := plotter.NewScatter(scatterData) 74 | if err != nil { 75 | log.Panic(err) 76 | } 77 | 78 | // Specify style and color for individual points. 79 | sc.GlyphStyleFunc = func(i int) draw.GlyphStyle { 80 | _, _, z := scatterData.XYZ(i) 81 | d := (z - minZ) / (maxZ - minZ) 82 | rng := maxZ - minZ 83 | k := d*rng + minZ 84 | c, err := colors.At(k) 85 | if err != nil { 86 | log.Panic(err) 87 | } 88 | return draw.GlyphStyle{Color: c, Radius: vg.Points(3), Shape: draw.CircleGlyph{}} 89 | } 90 | p.Add(sc) 91 | 92 | //Create a legend 93 | thumbs := plotter.PaletteThumbnailers(colors.Palette(n)) 94 | for i := len(thumbs) - 1; i >= 0; i-- { 95 | t := thumbs[i] 96 | if i != 0 && i != len(thumbs)-1 { 97 | p.Legend.Add("", t) 98 | continue 99 | } 100 | var val int 101 | switch i { 102 | case 0: 103 | val = int(minZ) 104 | case len(thumbs) - 1: 105 | val = int(maxZ) 106 | } 107 | p.Legend.Add(fmt.Sprintf("%d", val), t) 108 | } 109 | 110 | // This is the width of the legend, experimentally determined. 111 | const legendWidth = vg.Centimeter 112 | 113 | // Slide the legend over so it doesn't overlap the ScatterPlot. 114 | p.Legend.XOffs = legendWidth 115 | 116 | img := vgimg.New(300, 230) 117 | dc := draw.New(img) 118 | dc = draw.Crop(dc, 0, -legendWidth, 0, 0) // Make space for the legend. 119 | p.Draw(dc) 120 | 121 | w, err := os.Create("testdata/scatterColor.png") 122 | defer w.Close() 123 | if err != nil { 124 | log.Panic(err) 125 | } 126 | png := vgimg.PngCanvas{Canvas: img} 127 | if _, err = png.WriteTo(w); err != nil { 128 | log.Panic(err) 129 | } 130 | if err = w.Close(); err != nil { 131 | log.Panic(err) 132 | } 133 | } 134 | 135 | func TestScatterColor(t *testing.T) { 136 | cmpimg.CheckPlot(ExampleScatter_color, t, "scatterColor.png") 137 | } 138 | -------------------------------------------------------------------------------- /plotter/testdata/polygon_holes_golden.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Polygon with holes 10 | X 12 | 0 14 | 2 16 | 4 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Y 27 | 28 | 0 30 | 2 32 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | key 48 | 49 | 50 | -------------------------------------------------------------------------------- /plotter/polygon.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 plotter 6 | 7 | import ( 8 | "image/color" 9 | "math" 10 | 11 | "gonum.org/v1/plot" 12 | "gonum.org/v1/plot/vg" 13 | "gonum.org/v1/plot/vg/draw" 14 | ) 15 | 16 | // Polygon implements the Plotter interface, drawing a polygon. 17 | type Polygon struct { 18 | // XYs is a copy of the vertices of this polygon. 19 | // Each item in the array holds one ring in the 20 | // Polygon. 21 | XYs []XYs 22 | 23 | // LineStyle is the style of the line around the edge 24 | // of the polygon. 25 | draw.LineStyle 26 | 27 | // Color is the fill color of the polygon. 28 | Color color.Color 29 | } 30 | 31 | // NewPolygon returns a polygon that uses the default line style and 32 | // no fill color, where xys are the rings of the polygon. 33 | // Different backends may render overlapping rings and self-intersections 34 | // differently, but all built-in backends treat inner rings 35 | // with the opposite winding order from the outer ring as 36 | // holes. 37 | func NewPolygon(xys ...XYer) (*Polygon, error) { 38 | data := make([]XYs, len(xys)) 39 | for i, d := range xys { 40 | var err error 41 | data[i], err = CopyXYs(d) 42 | if err != nil { 43 | return nil, err 44 | } 45 | } 46 | return &Polygon{ 47 | XYs: data, 48 | LineStyle: DefaultLineStyle, 49 | }, nil 50 | } 51 | 52 | // Plot draws the polygon, implementing the plot.Plotter 53 | // interface. 54 | func (pts *Polygon) Plot(c draw.Canvas, plt *plot.Plot) { 55 | trX, trY := plt.Transforms(&c) 56 | ps := make([][]vg.Point, len(pts.XYs)) 57 | 58 | for i, ring := range pts.XYs { 59 | ps[i] = make([]vg.Point, len(ring)) 60 | for j, p := range ring { 61 | ps[i][j].X = trX(p.X) 62 | ps[i][j].Y = trY(p.Y) 63 | } 64 | ps[i] = c.ClipPolygonXY(ps[i]) 65 | } 66 | if pts.Color != nil && len(ps) > 0 { 67 | c.SetColor(pts.Color) 68 | var pa vg.Path 69 | for _, ring := range ps { 70 | if len(ring) == 0 { 71 | continue 72 | } 73 | pa.Move(ring[0]) 74 | for _, p := range ring { 75 | pa.Line(p) 76 | } 77 | pa.Close() 78 | } 79 | c.Fill(pa) 80 | } 81 | 82 | for _, ring := range ps { 83 | if len(ring) > 0 && ring[len(ring)-1] != ring[0] { 84 | ring = append(ring, ring[0]) 85 | } 86 | c.StrokeLines(pts.LineStyle, c.ClipLinesXY(ring)...) 87 | } 88 | } 89 | 90 | // DataRange returns the minimum and maximum 91 | // x and y values, implementing the plot.DataRanger 92 | // interface. 93 | func (pts *Polygon) DataRange() (xmin, xmax, ymin, ymax float64) { 94 | xmin = math.Inf(1) 95 | xmax = math.Inf(-1) 96 | ymin = math.Inf(1) 97 | ymax = math.Inf(-1) 98 | for _, ring := range pts.XYs { 99 | xmini, xmaxi := Range(XValues{ring}) 100 | ymini, ymaxi := Range(YValues{ring}) 101 | xmin = math.Min(xmin, xmini) 102 | xmax = math.Max(xmax, xmaxi) 103 | ymin = math.Min(ymin, ymini) 104 | ymax = math.Max(ymax, ymaxi) 105 | } 106 | return 107 | } 108 | 109 | // Thumbnail creates the thumbnail for the Polygon, 110 | // implementing the plot.Thumbnailer interface. 111 | func (pts *Polygon) Thumbnail(c *draw.Canvas) { 112 | if pts.Color != nil { 113 | points := []vg.Point{ 114 | {X: c.Min.X, Y: c.Min.Y}, 115 | {X: c.Min.X, Y: c.Max.Y}, 116 | {X: c.Max.X, Y: c.Max.Y}, 117 | {X: c.Max.X, Y: c.Min.Y}, 118 | } 119 | poly := c.ClipPolygonY(points) 120 | c.FillPolygon(pts.Color, poly) 121 | 122 | points = append(points, vg.Point{X: c.Min.X, Y: c.Min.Y}) 123 | c.StrokeLines(pts.LineStyle, points) 124 | } else { 125 | y := c.Center().Y 126 | c.StrokeLine2(pts.LineStyle, c.Min.X, y, c.Max.X, y) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /vg/testdata/width_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | X label 10 | 0.0 12 | 0.5 14 | 1.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Y label 31 | 32 | 0.0 34 | 0.5 36 | 1.0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /vg/testdata/width_-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | X label 10 | 0.0 12 | 0.5 14 | 1.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Y label 31 | 32 | 0.0 34 | 0.5 36 | 1.0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /palette/moreland/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 moreland_test 6 | 7 | import ( 8 | "log" 9 | "math" 10 | "os" 11 | "testing" 12 | 13 | "gonum.org/v1/gonum/mat" 14 | "gonum.org/v1/plot" 15 | "gonum.org/v1/plot/cmpimg" 16 | "gonum.org/v1/plot/palette" 17 | "gonum.org/v1/plot/palette/moreland" 18 | "gonum.org/v1/plot/plotter" 19 | "gonum.org/v1/plot/vg" 20 | "gonum.org/v1/plot/vg/draw" 21 | "gonum.org/v1/plot/vg/vgimg" 22 | ) 23 | 24 | type offsetUnitGrid struct { 25 | XOffset, YOffset float64 26 | 27 | Data *mat.Dense 28 | } 29 | 30 | func (g offsetUnitGrid) Dims() (c, r int) { r, c = g.Data.Dims(); return c, r } 31 | func (g offsetUnitGrid) Z(c, r int) float64 { return g.Data.At(r, c) } 32 | func (g offsetUnitGrid) X(c int) float64 { 33 | _, n := g.Data.Dims() 34 | if c < 0 || n <= c { 35 | panic("index out of range") 36 | } 37 | return float64(c) + g.XOffset 38 | } 39 | func (g offsetUnitGrid) Y(r int) float64 { 40 | m, _ := g.Data.Dims() 41 | if r < 0 || m <= r { 42 | panic("index out of range") 43 | } 44 | return float64(r) + g.YOffset 45 | } 46 | 47 | // This Example gives examples of plots using the palettes in this package. 48 | // The output can be found at 49 | // https://github.com/gonum/plot/blob/master/palette/moreland/testdata/moreland_golden.png. 50 | func Example() { 51 | m := offsetUnitGrid{ 52 | XOffset: -50, 53 | YOffset: -50, 54 | Data: mat.NewDense(100, 100, nil), 55 | } 56 | for i := 0; i < 100; i++ { 57 | for j := 0; j < 100; j++ { 58 | x := float64(i-50) / 10 59 | y := float64(j-50) / 10 60 | v := math.Sin(x*x+y*y) / (x*x + y*y) 61 | m.Data.Set(i, j, v) 62 | } 63 | } 64 | 65 | const ( 66 | rows = 3 67 | cols = 3 68 | ) 69 | c := vgimg.New(vg.Points(800), vg.Points(800)) 70 | dc := draw.New(c) 71 | tiles := draw.Tiles{ 72 | Rows: rows, 73 | Cols: cols, 74 | } 75 | type paletteHolder struct { 76 | name string 77 | cmap palette.Palette 78 | } 79 | palettes := []paletteHolder{ 80 | { 81 | name: "SmoothBlueRed", 82 | cmap: moreland.SmoothBlueRed().Palette(255), 83 | }, 84 | { 85 | name: "SmoothBlueTan", 86 | cmap: moreland.SmoothBlueTan().Palette(255), 87 | }, 88 | { 89 | name: "SmoothGreenPurple", 90 | cmap: moreland.SmoothGreenPurple().Palette(255), 91 | }, 92 | { 93 | name: "SmoothGreenRed", 94 | cmap: moreland.SmoothGreenRed().Palette(255), 95 | }, 96 | { 97 | name: "SmoothPurpleOrange", 98 | cmap: moreland.SmoothPurpleOrange().Palette(255), 99 | }, 100 | { 101 | name: "BlackBody", 102 | cmap: moreland.BlackBody().Palette(255), 103 | }, 104 | { 105 | name: "ExtendedBlackBody", 106 | cmap: moreland.ExtendedBlackBody().Palette(255), 107 | }, 108 | { 109 | name: "Kindlmann", 110 | cmap: moreland.Kindlmann().Palette(255), 111 | }, 112 | { 113 | name: "ExtendedKindlmann", 114 | cmap: moreland.ExtendedKindlmann().Palette(255), 115 | }, 116 | } 117 | 118 | for i, plte := range palettes { 119 | 120 | h := plotter.NewHeatMap(m, plte.cmap) 121 | 122 | p, err := plot.New() 123 | if err != nil { 124 | log.Panic(err) 125 | } 126 | p.Title.Text = plte.name 127 | 128 | p.Add(h) 129 | 130 | p.X.Padding = 0 131 | p.Y.Padding = 0 132 | p.Draw(tiles.At(dc, i%cols, i/cols)) 133 | } 134 | 135 | pngimg := vgimg.PngCanvas{Canvas: c} 136 | f, err := os.Create("testdata/moreland.png") 137 | if err != nil { 138 | log.Panic(err) 139 | } 140 | if _, err = pngimg.WriteTo(f); err != nil { 141 | log.Panic(err) 142 | } 143 | } 144 | 145 | func TestHeatMap(t *testing.T) { 146 | cmpimg.CheckPlot(Example, t, "moreland.png") 147 | } 148 | -------------------------------------------------------------------------------- /vg/testdata/width_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | X label 10 | 0.0 12 | 0.5 14 | 1.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Y label 31 | 32 | 0.0 34 | 0.5 36 | 1.0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /cmpimg/cmpimg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 cmpimg 6 | 7 | import ( 8 | "bytes" 9 | "encoding/base64" 10 | "image" 11 | "image/png" 12 | "io/ioutil" 13 | "path/filepath" 14 | "testing" 15 | ) 16 | 17 | const wantDiffEncoded = `iVBORw0KGgoAAAANSUhEUgAAAZAAAAEzEAIAAADAxR6YAAAHBklEQVR4nOzYjW3kRABA4RWiC0QXUMami2xRSRfrMu76gDKQZQ3+2907xEMg8X3SXZKxdzwZ29JTfrgAAJASWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAsR//7QX8X12v8//T9E/NvPje+R+tZjvP9882PjV/naZvf+J8lb96xb+/l+uar9d5zfPX8+rnY/PocvTZ9ebj4+j5TmyPjauNM8f8YyXrGh7NM2ZaPjXmHj8dZx4j+zUsax2zr0fHjGOG9frrOfs9WM/bjmx3bnxmP368/2MHzmtfj61j49/2+tv1bO/Ho1UdzzyO7898NsNx/NU+bHfkeMXZzz+Nnz4/3m/rPXi/He/B6r55Tt+evk3btZ73ffsGfH6MPfn8+PL1cvnydVnB779dLrfb/c/fa+zT9imaXhw9P3fH53H9fbdvwH6/lvdv/zSvT/B4e/dzL2e/7d7bZ98vOzqPvG3mWPZ42d375g1f5tzfgePI8Uk8P3GP1nAc+fWXeWT+f5rmOzH2dn5K9nt0/+b8j97ddYXnt+31vq2zHD+/3onz0XGH3m/P3tBnb8szr2Z5vdOvZzyv6Di+fDdNt9t5Dn/BAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgAAAOC/zV+wAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAIPZHAAAA///KAlB2mCuyaAAAAABJRU5ErkJggg==` 18 | 19 | func TestDiff(t *testing.T) { 20 | got, err := ioutil.ReadFile(filepath.FromSlash("./testdata/failed_input.png")) 21 | if err != nil { 22 | t.Fatalf("failed to read failed file: %v", err) 23 | } 24 | want, err := ioutil.ReadFile(filepath.FromSlash("./testdata/good_golden.png")) 25 | if err != nil { 26 | t.Fatalf("failed to read golden file: %v", err) 27 | } 28 | 29 | v1, _, err := image.Decode(bytes.NewReader(got)) 30 | if err != nil { 31 | t.Fatalf("unexpected error decoding failed file: %v", err) 32 | } 33 | v2, _, err := image.Decode(bytes.NewReader(want)) 34 | if err != nil { 35 | t.Fatalf("unexpected error decoding golden file: %v", err) 36 | } 37 | 38 | dst := image.NewRGBA64(v1.Bounds().Union(v2.Bounds())) 39 | rect := Diff(dst, v1, v2) 40 | if rect != dst.Bounds() { 41 | t.Errorf("unexpected bound for diff: got:%+v want:%+v", rect, dst.Bounds()) 42 | } 43 | 44 | var buf bytes.Buffer 45 | err = png.Encode(&buf, dst) 46 | if err != nil { 47 | t.Fatalf("failed to encode difference png: %v", err) 48 | } 49 | gotDiff := base64.StdEncoding.EncodeToString(buf.Bytes()) 50 | if gotDiff != wantDiffEncoded { 51 | t.Errorf("unexpected encoded diff value:\ngot:%s\nwant:%s", gotDiff, wantDiffEncoded) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vg/vg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 vg_test 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "path/filepath" 13 | "testing" 14 | 15 | "gonum.org/v1/plot" 16 | "gonum.org/v1/plot/cmpimg" 17 | "gonum.org/v1/plot/plotter" 18 | "gonum.org/v1/plot/vg" 19 | ) 20 | 21 | // TestLineWidth tests output against test images generated by 22 | // running tests with -tag good. 23 | func TestLineWidth(t *testing.T) { 24 | formats := []string{ 25 | // TODO: Add logic to cope with run to run eps differences. 26 | "pdf", 27 | "svg", 28 | "png", 29 | "tiff", 30 | "jpg", 31 | } 32 | 33 | const ( 34 | width = 100 35 | height = 100 36 | ) 37 | 38 | for _, w := range []vg.Length{-1, 0, 1} { 39 | for _, typ := range formats { 40 | p, err := lines(w) 41 | if err != nil { 42 | log.Fatalf("failed to create plot for %v:%s: %v", w, typ, err) 43 | } 44 | 45 | c, err := p.WriterTo(width, height, typ) 46 | if err != nil { 47 | t.Fatalf("failed to render plot for %v:%s: %v", w, typ, err) 48 | } 49 | 50 | var buf bytes.Buffer 51 | if _, err = c.WriteTo(&buf); err != nil { 52 | t.Fatalf("failed to write plot for %v:%s: %v", w, typ, err) 53 | } 54 | 55 | name := filepath.Join(".", "testdata", fmt.Sprintf("width_%v.%s", w, typ)) 56 | 57 | // Recreate Golden images. 58 | if *cmpimg.GenerateTestData { 59 | err = p.Save(width, height, name) 60 | if err != nil { 61 | log.Fatalf("failed to save %q: %v", name, err) 62 | } 63 | } 64 | 65 | want, err := ioutil.ReadFile(name) 66 | if err != nil { 67 | t.Fatalf("failed to read test image [%s]: %v\n", name, err) 68 | } 69 | 70 | ok, err := cmpimg.Equal(typ, buf.Bytes(), want) 71 | if err != nil { 72 | t.Fatalf("failed to run cmpimg test [%s]: %v\n", name, err) 73 | } 74 | 75 | if !ok { 76 | t.Errorf("image mismatch for %v:%s", w, typ) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func lines(w vg.Length) (*plot.Plot, error) { 83 | p, err := plot.New() 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | pts := plotter.XYs{{0, 0}, {0, 1}, {1, 0}, {1, 1}} 89 | line, err := plotter.NewLine(pts) 90 | line.Width = w 91 | if err != nil { 92 | return nil, err 93 | } 94 | p.Add(line) 95 | p.X.Label.Text = "X label" 96 | p.Y.Label.Text = "Y label" 97 | 98 | return p, nil 99 | } 100 | 101 | func TestParseLength(t *testing.T) { 102 | for _, table := range []struct { 103 | str string 104 | want vg.Length 105 | err error 106 | }{ 107 | { 108 | str: "42.2cm", 109 | want: 42.2 * vg.Centimeter, 110 | }, 111 | { 112 | str: "42.2mm", 113 | want: 42.2 * vg.Millimeter, 114 | }, 115 | { 116 | str: "42.2in", 117 | want: 42.2 * vg.Inch, 118 | }, 119 | { 120 | str: "42.2pt", 121 | want: 42.2, 122 | }, 123 | { 124 | str: "42.2", 125 | want: 42.2, 126 | }, 127 | { 128 | str: "999bottles", 129 | err: fmt.Errorf(`strconv.ParseFloat: parsing "999bottles": invalid syntax`), 130 | }, 131 | { 132 | str: "42inch", 133 | want: 42 * vg.Inch, 134 | err: fmt.Errorf(`strconv.ParseFloat: parsing "42inch": invalid syntax`), 135 | }, 136 | } { 137 | v, err := vg.ParseLength(table.str) 138 | if table.err != nil { 139 | if err == nil { 140 | t.Errorf("%s: expected an error (%v)\n", 141 | table.str, table.err, 142 | ) 143 | } 144 | if table.err.Error() != err.Error() { 145 | t.Errorf("%s: got error=%q. want=%q\n", 146 | table.str, err.Error(), table.err.Error(), 147 | ) 148 | } 149 | continue 150 | } 151 | if err != nil { 152 | t.Errorf("error setting flag.Value %q: %v\n", 153 | table.str, 154 | err, 155 | ) 156 | } 157 | if v != table.want { 158 | t.Errorf("%s: incorrect value. got %v, want %v\n", 159 | table.str, 160 | float64(v), float64(table.want), 161 | ) 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /align.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 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 plot 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | 11 | "gonum.org/v1/plot/vg" 12 | "gonum.org/v1/plot/vg/draw" 13 | ) 14 | 15 | // Align returns a two-dimensional row-major array of Canvases which will 16 | // produce tiled plots with DataCanvases that are evenly sized and spaced. 17 | // The arguments to the function are a two-dimensional row-major array 18 | // of plots, a tile configuration, and the canvas to which the tiled 19 | // plots are to be drawn. 20 | func Align(plots [][]*Plot, t draw.Tiles, dc draw.Canvas) [][]draw.Canvas { 21 | o := make([][]draw.Canvas, len(plots)) 22 | 23 | if len(plots) != t.Rows { 24 | panic(fmt.Errorf("plot: plots rows (%d) != tiles rows (%d)", len(plots), t.Rows)) 25 | } 26 | 27 | // Create the initial tiles. 28 | for j := 0; j < t.Rows; j++ { 29 | if len(plots[j]) != t.Cols { 30 | panic(fmt.Errorf("plot: plots row %d columns (%d) != tiles columns (%d)", j, len(plots[j]), t.Rows)) 31 | } 32 | 33 | o[j] = make([]draw.Canvas, len(plots[j])) 34 | for i := 0; i < t.Cols; i++ { 35 | o[j][i] = t.At(dc, i, j) 36 | } 37 | } 38 | 39 | type posNeg struct { 40 | p, n float64 41 | } 42 | xSpacing := make([]posNeg, t.Cols) 43 | ySpacing := make([]posNeg, t.Rows) 44 | 45 | // Calculate the maximum spacing between data canvases 46 | // for each row and column. 47 | for j, row := range plots { 48 | for i, p := range row { 49 | if p == nil { 50 | continue 51 | } 52 | c := o[j][i] 53 | dataC := p.DataCanvas(o[j][i]) 54 | xSpacing[i].n = math.Max(float64(dataC.Min.X-c.Min.X), xSpacing[i].n) 55 | xSpacing[i].p = math.Max(float64(c.Max.X-dataC.Max.X), xSpacing[i].p) 56 | ySpacing[j].n = math.Max(float64(dataC.Min.Y-c.Min.Y), ySpacing[j].n) 57 | ySpacing[j].p = math.Max(float64(c.Max.Y-dataC.Max.Y), ySpacing[j].p) 58 | } 59 | } 60 | 61 | // Calculate the total row and column spacing. 62 | var xTotalSpace float64 63 | for _, s := range xSpacing { 64 | xTotalSpace += s.n + s.p 65 | } 66 | xTotalSpace += float64(t.PadX)*float64(len(xSpacing)-1) + float64(t.PadLeft+t.PadRight) 67 | var yTotalSpace float64 68 | for _, s := range ySpacing { 69 | yTotalSpace += s.n + s.p 70 | } 71 | yTotalSpace += float64(t.PadY)*float64(len(ySpacing)-1) + float64(t.PadTop+t.PadBottom) 72 | 73 | avgWidth := vg.Length((float64(dc.Max.X-dc.Min.X) - xTotalSpace) / float64(t.Cols)) 74 | avgHeight := vg.Length((float64(dc.Max.Y-dc.Min.Y) - yTotalSpace) / float64(t.Rows)) 75 | 76 | moveVertical := make([]vg.Length, t.Cols) 77 | for j := t.Rows - 1; j >= 0; j-- { 78 | row := plots[j] 79 | var moveHorizontal vg.Length 80 | for i, p := range row { 81 | c := o[j][i] 82 | 83 | if p != nil { 84 | dataC := p.DataCanvas(c) 85 | // Adjust the horizontal and vertical spacing between 86 | // canvases to match the maximum for each column and row, 87 | // respectively. 88 | c = draw.Crop(c, 89 | vg.Length(xSpacing[i].n)-(dataC.Min.X-c.Min.X), 90 | c.Max.X-dataC.Max.X-vg.Length(xSpacing[i].p), 91 | vg.Length(ySpacing[j].n)-(dataC.Min.Y-c.Min.Y), 92 | c.Max.Y-dataC.Max.Y-vg.Length(ySpacing[j].p), 93 | ) 94 | } 95 | 96 | var width, height vg.Length 97 | if p == nil { 98 | width = c.Max.X - c.Min.X - vg.Length(xSpacing[i].p+xSpacing[i].n) 99 | height = c.Max.Y - c.Min.Y - vg.Length(ySpacing[j].p+ySpacing[j].n) 100 | } else { 101 | dataC := p.DataCanvas(c) 102 | width = dataC.Max.X - dataC.Min.X 103 | height = dataC.Max.Y - dataC.Min.Y 104 | } 105 | 106 | // Adjust the canvas so that the height and width of the 107 | // DataCanvas is the same for all plots. 108 | o[j][i] = draw.Crop(c, 109 | moveHorizontal, 110 | moveHorizontal+avgWidth-width, 111 | moveVertical[i], 112 | moveVertical[i]+avgHeight-height, 113 | ) 114 | moveHorizontal += avgWidth - width 115 | moveVertical[i] += avgHeight - height 116 | } 117 | } 118 | return o 119 | } 120 | -------------------------------------------------------------------------------- /plotter/image.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 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 plotter 6 | 7 | import ( 8 | "image" 9 | "math" 10 | 11 | "gonum.org/v1/plot" 12 | "gonum.org/v1/plot/vg" 13 | "gonum.org/v1/plot/vg/draw" 14 | ) 15 | 16 | // Image is a plotter that draws a scaled, raster image. 17 | type Image struct { 18 | img image.Image 19 | cols int 20 | rows int 21 | xmin, xmax, dx float64 22 | ymin, ymax, dy float64 23 | } 24 | 25 | // NewImage creates a new image plotter. 26 | // Image will plot img inside the rectangle defined by the 27 | // (xmin, ymin) and (xmax, ymax) points given in the data space. 28 | // The img will be scaled to fit inside the rectangle. 29 | func NewImage(img image.Image, xmin, ymin, xmax, ymax float64) *Image { 30 | bounds := img.Bounds() 31 | cols := bounds.Dx() 32 | rows := bounds.Dy() 33 | dx := math.Abs(xmax-xmin) / float64(cols) 34 | dy := math.Abs(ymax-ymin) / float64(rows) 35 | return &Image{ 36 | img: img, 37 | cols: cols, 38 | rows: rows, 39 | xmin: xmin, 40 | xmax: xmax, 41 | dx: dx, 42 | ymin: ymin, 43 | ymax: ymax, 44 | dy: dy, 45 | } 46 | } 47 | 48 | // Plot implements the Plot method of the plot.Plotter interface. 49 | func (img *Image) Plot(c draw.Canvas, p *plot.Plot) { 50 | trX, trY := p.Transforms(&c) 51 | xmin := trX(img.xmin) 52 | ymin := trY(img.ymin) 53 | xmax := trX(img.xmax) 54 | ymax := trY(img.ymax) 55 | rect := vg.Rectangle{ 56 | Min: vg.Point{X: xmin, Y: ymin}, 57 | Max: vg.Point{X: xmax, Y: ymax}, 58 | } 59 | c.DrawImage(rect, img.transformFor(p)) 60 | } 61 | 62 | // DataRange implements the DataRange method 63 | // of the plot.DataRanger interface. 64 | func (img *Image) DataRange() (xmin, xmax, ymin, ymax float64) { 65 | return img.xmin, img.xmax, img.ymin, img.ymax 66 | } 67 | 68 | // GlyphBoxes implements the GlyphBoxes method 69 | // of the plot.GlyphBoxer interface. 70 | func (img *Image) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { 71 | return nil 72 | } 73 | 74 | // transform warps the image to align with non-linear axes. 75 | func (img *Image) transformFor(p *plot.Plot) image.Image { 76 | _, xLinear := p.X.Scale.(plot.LinearScale) 77 | _, yLinear := p.Y.Scale.(plot.LinearScale) 78 | if xLinear && yLinear { 79 | return img.img 80 | } 81 | b := img.img.Bounds() 82 | o := image.NewNRGBA64(b) 83 | for c := 0; c < img.cols; c++ { 84 | // Find the equivalent image column after applying axis transforms. 85 | cTrans := int(p.X.Norm(img.x(c)) * float64(img.cols)) 86 | // Find the equivalent column of the previous image column after applying 87 | // axis transforms. 88 | cPrevTrans := int(p.X.Norm(img.x(maxInt(c-1, 0))) * float64(img.cols)) 89 | for r := 0; r < img.rows; r++ { 90 | // Find the equivalent image row after applying axis transforms. 91 | rTrans := int(p.Y.Norm(img.y(r)) * float64(img.rows)) 92 | // Find the equivalent row of the previous image row after applying 93 | // axis transforms. 94 | rPrevTrans := int(p.Y.Norm(img.y(maxInt(r-1, 0))) * float64(img.rows)) 95 | crColor := img.img.At(c, img.rows-r-1) 96 | // Set all the pixels in the new image between (cPrevTrans, rPrevTrans) 97 | // and (cTrans, rTrans) to the color at (c,r) in the original image. 98 | // TODO: Improve interpolation. 99 | for cPrime := cPrevTrans; cPrime <= cTrans; cPrime++ { 100 | for rPrime := rPrevTrans; rPrime <= rTrans; rPrime++ { 101 | o.Set(cPrime, img.rows-rPrime-1, crColor) 102 | } 103 | } 104 | } 105 | } 106 | return o 107 | } 108 | 109 | func maxInt(a, b int) int { 110 | if a > b { 111 | return a 112 | } 113 | return b 114 | } 115 | 116 | func (img *Image) x(c int) float64 { 117 | if c >= img.cols || c < 0 { 118 | panic("plotter/image: illegal range") 119 | } 120 | return img.xmin + float64(c)*img.dx 121 | } 122 | 123 | func (img *Image) y(r int) float64 { 124 | if r >= img.rows || r < 0 { 125 | panic("plotter/image: illegal range") 126 | } 127 | return img.ymin + float64(r)*img.dy 128 | } 129 | -------------------------------------------------------------------------------- /plotter/heat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "math" 11 | "os" 12 | "testing" 13 | 14 | "gonum.org/v1/gonum/mat" 15 | "gonum.org/v1/plot" 16 | "gonum.org/v1/plot/cmpimg" 17 | "gonum.org/v1/plot/palette" 18 | "gonum.org/v1/plot/plotter" 19 | "gonum.org/v1/plot/vg" 20 | "gonum.org/v1/plot/vg/draw" 21 | "gonum.org/v1/plot/vg/vgimg" 22 | ) 23 | 24 | type offsetUnitGrid struct { 25 | XOffset, YOffset float64 26 | 27 | Data mat.Matrix 28 | } 29 | 30 | func (g offsetUnitGrid) Dims() (c, r int) { r, c = g.Data.Dims(); return c, r } 31 | func (g offsetUnitGrid) Z(c, r int) float64 { return g.Data.At(r, c) } 32 | func (g offsetUnitGrid) X(c int) float64 { 33 | _, n := g.Data.Dims() 34 | if c < 0 || c >= n { 35 | panic("column index out of range") 36 | } 37 | return float64(c) + g.XOffset 38 | } 39 | func (g offsetUnitGrid) Y(r int) float64 { 40 | m, _ := g.Data.Dims() 41 | if r < 0 || r >= m { 42 | panic("row index out of range") 43 | } 44 | return float64(r) + g.YOffset 45 | } 46 | 47 | type integerTicks struct{} 48 | 49 | func (integerTicks) Ticks(min, max float64) []plot.Tick { 50 | var t []plot.Tick 51 | for i := math.Trunc(min); i <= max; i++ { 52 | t = append(t, plot.Tick{Value: i, Label: fmt.Sprint(i)}) 53 | } 54 | return t 55 | } 56 | 57 | func ExampleHeatMap() { 58 | m := offsetUnitGrid{ 59 | XOffset: -2, 60 | YOffset: -1, 61 | Data: mat.NewDense(3, 4, []float64{ 62 | 1, 2, 3, 4, 63 | 5, 6, 7, 8, 64 | 9, 10, 11, 12, 65 | })} 66 | pal := palette.Heat(12, 1) 67 | h := plotter.NewHeatMap(m, pal) 68 | 69 | p, err := plot.New() 70 | if err != nil { 71 | log.Panic(err) 72 | } 73 | p.Title.Text = "Heat map" 74 | 75 | p.X.Tick.Marker = integerTicks{} 76 | p.Y.Tick.Marker = integerTicks{} 77 | 78 | p.Add(h) 79 | 80 | // Create a legend. 81 | l, err := plot.NewLegend() 82 | if err != nil { 83 | log.Panic(err) 84 | } 85 | thumbs := plotter.PaletteThumbnailers(pal) 86 | for i := len(thumbs) - 1; i >= 0; i-- { 87 | t := thumbs[i] 88 | if i != 0 && i != len(thumbs)-1 { 89 | l.Add("", t) 90 | continue 91 | } 92 | var val float64 93 | switch i { 94 | case 0: 95 | val = h.Min 96 | case len(thumbs) - 1: 97 | val = h.Max 98 | } 99 | l.Add(fmt.Sprintf("%.2g", val), t) 100 | } 101 | 102 | p.X.Padding = 0 103 | p.Y.Padding = 0 104 | p.X.Max = 1.5 105 | p.Y.Max = 1.5 106 | 107 | img := vgimg.New(250, 175) 108 | dc := draw.New(img) 109 | 110 | l.Top = true 111 | // Calculate the width of the legend. 112 | r := l.Rectangle(dc) 113 | legendWidth := r.Max.X - r.Min.X 114 | l.YOffs = -p.Title.Font.Extents().Height // Adjust the legend down a little. 115 | 116 | l.Draw(dc) 117 | dc = draw.Crop(dc, 0, -legendWidth-vg.Millimeter, 0, 0) // Make space for the legend. 118 | p.Draw(dc) 119 | w, err := os.Create("testdata/heatMap.png") 120 | if err != nil { 121 | log.Panic(err) 122 | } 123 | png := vgimg.PngCanvas{Canvas: img} 124 | if _, err = png.WriteTo(w); err != nil { 125 | log.Panic(err) 126 | } 127 | } 128 | 129 | func TestHeatMap(t *testing.T) { 130 | cmpimg.CheckPlot(ExampleHeatMap, t, "heatMap.png") 131 | } 132 | 133 | func TestHeatMapDims(t *testing.T) { 134 | pal := palette.Heat(12, 1) 135 | 136 | for _, test := range []struct { 137 | rows int 138 | cols int 139 | }{ 140 | {rows: 1, cols: 2}, 141 | {rows: 2, cols: 1}, 142 | {rows: 2, cols: 2}, 143 | } { 144 | func() { 145 | defer func() { 146 | r := recover() 147 | if r != nil { 148 | t.Errorf("unexpected panic for rows=%d cols=%d: %v", test.rows, test.cols, r) 149 | } 150 | }() 151 | 152 | m := offsetUnitGrid{Data: mat.NewDense(test.rows, test.cols, nil)} 153 | h := plotter.NewHeatMap(m, pal) 154 | 155 | p, err := plot.New() 156 | if err != nil { 157 | t.Errorf("unexpected error: %v", err) 158 | } 159 | p.Add(h) 160 | 161 | img := vgimg.New(250, 175) 162 | dc := draw.New(img) 163 | 164 | p.Draw(dc) 165 | }() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /vg/vgsvg/testdata/scatter_golden.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Scatter plot 10 | X 12 | 0.0 14 | 0.5 16 | 1.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Y 33 | 34 | 0.0 36 | 0.5 38 | 1.0 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /vg/vgsvg/testdata/scatter_line_golden.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Scatter & line plot 10 | X 12 | 0.0 14 | 0.5 16 | 1.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Y 33 | 34 | 0.0 36 | 0.5 38 | 1.0 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /axis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plot 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | var axisSmallTickTests = []struct { 14 | min, max float64 15 | wantValues []float64 16 | wantLabels []string 17 | }{ 18 | { 19 | min: -1.9846500878911073, 20 | max: 0.4370974820125605, 21 | wantValues: []float64{-1.75, -0.75, 0.25}, 22 | wantLabels: []string{"-1.75", "-0.75", "0.25"}, 23 | }, 24 | { 25 | min: -1.985e15, 26 | max: 0.4371e15, 27 | wantValues: []float64{-1.75e15, -7.5e14, 2.5e14}, 28 | wantLabels: []string{"-1.75e+15", "-7.5e+14", "2.5e+14"}, 29 | }, 30 | { 31 | min: -1.985e-15, 32 | max: 0.4371e-15, 33 | wantValues: []float64{-1.985e-15, -7.739500000000001e-16, 4.3709999999999994e-16}, 34 | wantLabels: []string{"-1.985e-15", "-7.7395e-16", "4.371e-16"}, 35 | }, 36 | { 37 | min: math.MaxFloat64 / 4, 38 | max: math.MaxFloat64 / 3, 39 | wantValues: []float64{4.4942328371557893e+307, 5.243271643348421e+307, 5.992310449541053e+307}, 40 | wantLabels: []string{"4e+307", "5e+307", "6e+307"}, 41 | }, 42 | { 43 | min: 0.00010, 44 | max: 0.00015, 45 | wantValues: []float64{0.0001, 0.00012, 0.00014000000000000001}, 46 | wantLabels: []string{"0.0001", "0.00012", "0.00014"}, 47 | }, 48 | { 49 | min: 555.6545, 50 | max: 21800.9875, 51 | wantValues: []float64{4000, 12000, 20000}, 52 | wantLabels: []string{"4000", "12000", "20000"}, 53 | }, 54 | { 55 | min: 555.6545, 56 | max: 27800.9875, 57 | wantValues: []float64{5000, 15000, 25000}, 58 | wantLabels: []string{"5000", "15000", "25000"}, 59 | }, 60 | { 61 | min: 55.6545, 62 | max: 1555.9875, 63 | wantValues: []float64{300, 900, 1500}, 64 | wantLabels: []string{"300", "900", "1500"}, 65 | }, 66 | { 67 | min: 3.096916 - 0.125, 68 | max: 3.096916 + 0.125, 69 | wantValues: []float64{3, 3.1, 3.2}, 70 | wantLabels: []string{"3.0", "3.1", "3.2"}, 71 | }, 72 | } 73 | 74 | func TestAxisSmallTick(t *testing.T) { 75 | d := DefaultTicks{} 76 | for i, test := range axisSmallTickTests { 77 | ticks := d.Ticks(test.min, test.max) 78 | gotLabels := labelsOf(ticks) 79 | gotValues := valuesOf(ticks) 80 | if !reflect.DeepEqual(gotValues, test.wantValues) { 81 | t.Errorf("tick values mismatch %d:\ngot: %v\nwant:%v", i, gotValues, test.wantValues) 82 | } 83 | if !reflect.DeepEqual(gotLabels, test.wantLabels) { 84 | t.Errorf("tick labels mismatch %d:\ngot: %q\nwant:%q", i, gotLabels, test.wantLabels) 85 | } 86 | } 87 | } 88 | 89 | func valuesOf(ticks []Tick) []float64 { 90 | var values []float64 91 | for _, t := range ticks { 92 | if t.Label != "" { 93 | values = append(values, t.Value) 94 | } 95 | } 96 | return values 97 | } 98 | 99 | func labelsOf(ticks []Tick) []string { 100 | var labels []string 101 | for _, t := range ticks { 102 | if t.Label != "" { 103 | labels = append(labels, t.Label) 104 | } 105 | } 106 | return labels 107 | } 108 | 109 | func TestTickerFunc_Ticks(t *testing.T) { 110 | type args struct { 111 | min float64 112 | max float64 113 | } 114 | tests := []struct { 115 | name string 116 | args args 117 | want []Tick 118 | f TickerFunc 119 | }{ 120 | { 121 | name: "return exactly the same ticks as the function passed to TickerFunc", 122 | args: args{0, 3}, 123 | want: []Tick{{1, "a"}, {2, "b"}}, 124 | f: func(min, max float64) []Tick { 125 | return []Tick{{1, "a"}, {2, "b"}} 126 | }, 127 | }, 128 | } 129 | for _, tt := range tests { 130 | t.Run(tt.name, func(t *testing.T) { 131 | if got := tt.f.Ticks(tt.args.min, tt.args.max); !reflect.DeepEqual(got, tt.want) { 132 | t.Errorf("TickerFunc.Ticks() = %v, want %v", got, tt.want) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestInvertedScale_Normalize(t *testing.T) { 139 | inverter := InvertedScale{Normalizer: LinearScale{}} 140 | if got := inverter.Normalize(0, 1, 1); got != 0.0 { 141 | t.Errorf("Expected a normalization inversion %f->%f not %f", 1.0, 0.0, got) 142 | } 143 | if got := inverter.Normalize(0, 1, .5); got != 0.5 { 144 | t.Errorf("Expected a normalization inversion %f->%f not %f", 0.5, 0.5, got) 145 | } 146 | if got := inverter.Normalize(0, 1, 0); got != 1.0 { 147 | t.Errorf("Expected a normalization inversion %f->%f not %f", 0.0, 1.0, got) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /plotutil/main.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 | // +build ignore 6 | 7 | package main 8 | 9 | import ( 10 | "golang.org/x/exp/rand" 11 | 12 | "gonum.org/v1/plot" 13 | "gonum.org/v1/plot/plotter" 14 | "gonum.org/v1/plot/plotutil" 15 | ) 16 | 17 | var examples = []struct { 18 | name string 19 | mkplot func() *plot.Plot 20 | }{ 21 | {"example_errpoints", Example_errpoints}, 22 | {"example_stackedAreaChart", Example_stackedAreaChart}, 23 | } 24 | 25 | func main() { 26 | for _, ex := range examples { 27 | drawEps(ex.name, ex.mkplot) 28 | drawSvg(ex.name, ex.mkplot) 29 | drawPng(ex.name, ex.mkplot) 30 | drawTiff(ex.name, ex.mkplot) 31 | drawJpg(ex.name, ex.mkplot) 32 | drawPdf(ex.name, ex.mkplot) 33 | } 34 | } 35 | 36 | func drawEps(name string, mkplot func() *plot.Plot) { 37 | if err := mkplot().Save(4, 4, name+".eps"); err != nil { 38 | panic(err) 39 | } 40 | } 41 | 42 | func drawPdf(name string, mkplot func() *plot.Plot) { 43 | if err := mkplot().Save(4, 4, name+".pdf"); err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func drawSvg(name string, mkplot func() *plot.Plot) { 49 | if err := mkplot().Save(4, 4, name+".svg"); err != nil { 50 | panic(err) 51 | } 52 | } 53 | 54 | func drawPng(name string, mkplot func() *plot.Plot) { 55 | if err := mkplot().Save(4, 4, name+".png"); err != nil { 56 | panic(err) 57 | } 58 | } 59 | 60 | func drawTiff(name string, mkplot func() *plot.Plot) { 61 | if err := mkplot().Save(4, 4, name+".tiff"); err != nil { 62 | panic(err) 63 | } 64 | } 65 | 66 | func drawJpg(name string, mkplot func() *plot.Plot) { 67 | if err := mkplot().Save(4, 4, name+".jpg"); err != nil { 68 | panic(err) 69 | } 70 | } 71 | 72 | // Example_errpoints draws some error points. 73 | func Example_errpoints() *plot.Plot { 74 | rnd := rand.New(rand.NewSource(1)) 75 | 76 | // Get some random data. 77 | n, m := 5, 10 78 | pts := make([]plotter.XYer, n) 79 | for i := range pts { 80 | xys := make(plotter.XYs, m) 81 | pts[i] = xys 82 | center := float64(i) 83 | for j := range xys { 84 | xys[j].X = center + (rnd.Float64() - 0.5) 85 | xys[j].Y = center + (rnd.Float64() - 0.5) 86 | } 87 | } 88 | 89 | plt, err := plot.New() 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | mean95, err := plotutil.NewErrorPoints(plotutil.MeanAndConf95, pts...) 95 | if err != nil { 96 | panic(err) 97 | } 98 | medMinMax, err := plotutil.NewErrorPoints(plotutil.MedianAndMinMax, pts...) 99 | if err != nil { 100 | panic(err) 101 | } 102 | plotutil.AddLinePoints(plt, 103 | "mean and 95% confidence", mean95, 104 | "median and minimum and maximum", medMinMax) 105 | if err := plotutil.AddErrorBars(plt, mean95, medMinMax); err != nil { 106 | panic(err) 107 | } 108 | if err := plotutil.AddScatters(plt, pts[0], pts[1], pts[2], pts[3], pts[4]); err != nil { 109 | panic(err) 110 | } 111 | 112 | return plt 113 | } 114 | 115 | type stackValues struct{ vs []plotter.Values } 116 | 117 | func (n stackValues) Len() int { return n.vs[0].Len() } 118 | func (n stackValues) Value(i int) float64 { 119 | sum := 0.0 120 | for _, v := range n.vs { 121 | sum += v.Value(i) 122 | } 123 | return sum 124 | } 125 | 126 | // An example of making a stacked area chart. 127 | func Example_stackedAreaChart() *plot.Plot { 128 | p, err := plot.New() 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | p.Title.Text = "Example: Software Version Comparison" 134 | p.X.Label.Text = "Date" 135 | p.Y.Label.Text = "Users (in thousands)" 136 | 137 | p.Legend.Top = true 138 | p.Legend.Left = true 139 | 140 | vals := []plotter.Values{ 141 | {0.02, 0.015, 0, 0, 0, 0, 0}, 142 | {0, 0.48, 0.36, 0.34, 0.32, 0.32, 0.28}, 143 | {0, 0, 0.87, 1.4, 0.64, 0.32, 0.28}, 144 | {0, 0, 0, 1.26, 0.34, 0.12, 0.09}, 145 | {0, 0, 0, 0, 2.48, 2.68, 2.13}, 146 | {0, 0, 0, 0, 0, 1.32, 0.54}, 147 | {0, 0, 0, 0, 0, 0.68, 5.67}, 148 | } 149 | 150 | err = plotutil.AddStackedAreaPlots(p, plotter.Values{2007, 2008, 2009, 2010, 2011, 2012, 2013}, 151 | "Version 3.0", 152 | stackValues{vs: vals[0:7]}, 153 | "Version 2.1", 154 | stackValues{vs: vals[0:6]}, 155 | "Version 2.0.1", 156 | stackValues{vs: vals[0:5]}, 157 | "Version 2.0", 158 | stackValues{vs: vals[0:4]}, 159 | "Version 1.1", 160 | stackValues{vs: vals[0:3]}, 161 | "Version 1.0", 162 | stackValues{vs: vals[0:2]}, 163 | "Beta", 164 | stackValues{vs: vals[0:1]}, 165 | ) 166 | 167 | if err != nil { 168 | panic(err) 169 | } 170 | 171 | return p 172 | } 173 | -------------------------------------------------------------------------------- /gob/gob_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 gob_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/gob" 10 | "image/color" 11 | "os" 12 | "testing" 13 | 14 | "golang.org/x/exp/rand" 15 | 16 | "gonum.org/v1/plot" 17 | _ "gonum.org/v1/plot/gob" 18 | "gonum.org/v1/plot/plotter" 19 | "gonum.org/v1/plot/vg" 20 | "gonum.org/v1/plot/vg/draw" 21 | ) 22 | 23 | func init() { 24 | gob.Register(commaTicks{}) 25 | } 26 | 27 | func TestPersistency(t *testing.T) { 28 | rnd := rand.New(rand.NewSource(1)) 29 | 30 | // Get some random points 31 | n := 15 32 | scatterData := randomPoints(n, rnd) 33 | lineData := randomPoints(n, rnd) 34 | linePointsData := randomPoints(n, rnd) 35 | 36 | p, err := plot.New() 37 | if err != nil { 38 | t.Fatalf("error creating plot: %v\n", err) 39 | } 40 | 41 | p.Title.Text = "Plot Example" 42 | p.X.Label.Text = "X" 43 | p.Y.Label.Text = "Y" 44 | // Use a custom tick marker function that computes the default 45 | // tick marks and re-labels the major ticks with commas. 46 | p.Y.Tick.Marker = commaTicks{} 47 | 48 | // Draw a grid behind the data 49 | p.Add(plotter.NewGrid()) 50 | // Make a scatter plotter and set its style. 51 | s, err := plotter.NewScatter(scatterData) 52 | if err != nil { 53 | panic(err) 54 | } 55 | s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255} 56 | 57 | // Make a line plotter and set its style. 58 | l, err := plotter.NewLine(lineData) 59 | if err != nil { 60 | panic(err) 61 | } 62 | l.LineStyle.Width = vg.Points(1) 63 | l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)} 64 | l.LineStyle.Color = color.RGBA{B: 255, A: 255} 65 | 66 | // Make a line plotter with points and set its style. 67 | lpLine, lpPoints, err := plotter.NewLinePoints(linePointsData) 68 | if err != nil { 69 | panic(err) 70 | } 71 | lpLine.Color = color.RGBA{G: 255, A: 255} 72 | lpPoints.Shape = draw.PyramidGlyph{} 73 | lpPoints.Color = color.RGBA{R: 255, A: 255} 74 | 75 | // Add the plotters to the plot, with a legend 76 | // entry for each 77 | p.Add(s, l, lpLine, lpPoints) 78 | p.Legend.Add("scatter", s) 79 | p.Legend.Add("line", l) 80 | p.Legend.Add("line points", lpLine, lpPoints) 81 | 82 | // Save the plot to a PNG file. 83 | err = p.Save(4, 4, "test-persistency.png") 84 | if err != nil { 85 | t.Fatalf("error saving to PNG: %v\n", err) 86 | } 87 | defer os.Remove("test-persistency.png") 88 | 89 | buf := new(bytes.Buffer) 90 | enc := gob.NewEncoder(buf) 91 | err = enc.Encode(p) 92 | if err != nil { 93 | t.Fatalf("error gob-encoding plot: %v\n", err) 94 | } 95 | 96 | // TODO(sbinet): impl. BinaryMarshal for plot.Plot and vg.Font 97 | // { 98 | // dec := gob.NewDecoder(buf) 99 | // var p plot.Plot 100 | // err = dec.Decode(&p) 101 | // if err != nil { 102 | // t.Fatalf("error gob-decoding plot: %v\n", err) 103 | // } 104 | // // Save the plot to a PNG file. 105 | // err = p.Save(4, 4, "test-persistency-readback.png") 106 | // if err != nil { 107 | // t.Fatalf("error saving to PNG: %v\n", err) 108 | // } 109 | // defer os.Remove("test-persistency-readback.png") 110 | // } 111 | 112 | } 113 | 114 | // randomPoints returns some random x, y points. 115 | func randomPoints(n int, rnd *rand.Rand) plotter.XYs { 116 | pts := make(plotter.XYs, n) 117 | for i := range pts { 118 | if i == 0 { 119 | pts[i].X = rnd.Float64() 120 | } else { 121 | pts[i].X = pts[i-1].X + rnd.Float64() 122 | } 123 | pts[i].Y = pts[i].X + rnd.Float64()*1e4 124 | } 125 | return pts 126 | } 127 | 128 | // CommaTicks computes the default tick marks, but inserts commas 129 | // into the labels for the major tick marks. 130 | type commaTicks struct{} 131 | 132 | func (commaTicks) Ticks(min, max float64) []plot.Tick { 133 | tks := plot.DefaultTicks{}.Ticks(min, max) 134 | for i, t := range tks { 135 | if t.Label == "" { // Skip minor ticks, they are fine. 136 | continue 137 | } 138 | tks[i].Label = addCommas(t.Label) 139 | } 140 | return tks 141 | } 142 | 143 | // AddCommas adds commas after every 3 characters from right to left. 144 | // NOTE: This function is a quick hack, it doesn't work with decimal 145 | // points, and may have a bunch of other problems. 146 | func addCommas(s string) string { 147 | rev := "" 148 | n := 0 149 | for i := len(s) - 1; i >= 0; i-- { 150 | rev += string(s[i]) 151 | n++ 152 | if n%3 == 0 { 153 | rev += "," 154 | } 155 | } 156 | s = "" 157 | for i := len(rev) - 1; i >= 0; i-- { 158 | s += string(rev[i]) 159 | } 160 | return s 161 | } 162 | -------------------------------------------------------------------------------- /plotter/boxplot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 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 plotter_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "testing" 11 | 12 | "golang.org/x/exp/rand" 13 | 14 | "gonum.org/v1/plot" 15 | "gonum.org/v1/plot/cmpimg" 16 | "gonum.org/v1/plot/plotter" 17 | "gonum.org/v1/plot/vg" 18 | ) 19 | 20 | func ExampleBoxPlot() { 21 | rnd := rand.New(rand.NewSource(1)) 22 | 23 | // Create the sample data. 24 | const n = 100 25 | uniform := make(plotter.ValueLabels, n) 26 | normal := make(plotter.ValueLabels, n) 27 | expon := make(plotter.ValueLabels, n) 28 | for i := 0; i < n; i++ { 29 | uniform[i].Value = rnd.Float64() 30 | uniform[i].Label = fmt.Sprintf("%4.4f", uniform[i].Value) 31 | normal[i].Value = rnd.NormFloat64() 32 | normal[i].Label = fmt.Sprintf("%4.4f", normal[i].Value) 33 | expon[i].Value = rnd.ExpFloat64() 34 | expon[i].Label = fmt.Sprintf("%4.4f", expon[i].Value) 35 | } 36 | 37 | // Make boxes for our data and add them to the plot. 38 | uniBox, err := plotter.NewBoxPlot(vg.Points(20), 0, uniform) 39 | if err != nil { 40 | log.Panic(err) 41 | } 42 | normBox, err := plotter.NewBoxPlot(vg.Points(20), 1, normal) 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | expBox, err := plotter.NewBoxPlot(vg.Points(20), 2, expon) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | 51 | // Make a vertical box plot. 52 | uniLabels, err := uniBox.OutsideLabels(uniform) 53 | if err != nil { 54 | log.Panic(err) 55 | } 56 | normLabels, err := normBox.OutsideLabels(normal) 57 | if err != nil { 58 | log.Panic(err) 59 | } 60 | expLabels, err := expBox.OutsideLabels(expon) 61 | if err != nil { 62 | log.Panic(err) 63 | } 64 | 65 | p1, err := plot.New() 66 | if err != nil { 67 | log.Panic(err) 68 | } 69 | p1.Title.Text = "Vertical Box Plot" 70 | p1.Y.Label.Text = "plotter.Values" 71 | p1.Y.Max = 6 72 | p1.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels) 73 | 74 | // Set the X axis of the plot to nominal with 75 | // the given names for x=0, x=1 and x=2. 76 | p1.NominalX("Uniform\nDistribution", "Normal\nDistribution", 77 | "Exponential\nDistribution") 78 | 79 | err = p1.Save(200, 200, "testdata/verticalBoxPlot.png") 80 | if err != nil { 81 | log.Panic(err) 82 | } 83 | 84 | // Now, make the same plot but horizontal. 85 | normBox.Horizontal = true 86 | expBox.Horizontal = true 87 | uniBox.Horizontal = true 88 | // We can use the same plotters but the labels need to be recreated. 89 | uniLabels, err = uniBox.OutsideLabels(uniform) 90 | if err != nil { 91 | log.Panic(err) 92 | } 93 | normLabels, err = normBox.OutsideLabels(normal) 94 | if err != nil { 95 | log.Panic(err) 96 | } 97 | expLabels, err = expBox.OutsideLabels(expon) 98 | if err != nil { 99 | log.Panic(err) 100 | } 101 | 102 | p2, err := plot.New() 103 | if err != nil { 104 | log.Panic(err) 105 | } 106 | p2.Title.Text = "Horizontal Box Plot" 107 | p2.X.Label.Text = "plotter.Values" 108 | 109 | p2.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels) 110 | 111 | // Set the Y axis of the plot to nominal with 112 | // the given names for y=0, y=1 and y=2. 113 | p2.NominalY("Uniform\nDistribution", "Normal\nDistribution", 114 | "Exponential\nDistribution") 115 | 116 | err = p2.Save(200, 200, "testdata/horizontalBoxPlot.png") 117 | if err != nil { 118 | log.Panic(err) 119 | } 120 | 121 | // Now, make a grouped box plot. 122 | p3, err := plot.New() 123 | if err != nil { 124 | log.Panic(err) 125 | } 126 | p3.Title.Text = "Box Plot" 127 | p3.Y.Label.Text = "plotter.Values" 128 | 129 | w := vg.Points(20) 130 | for x := 0.0; x < 3.0; x++ { 131 | b0, err := plotter.NewBoxPlot(w, x, uniform) 132 | if err != nil { 133 | log.Panic(err) 134 | } 135 | b0.Offset = -w - vg.Points(3) 136 | b1, err := plotter.NewBoxPlot(w, x, normal) 137 | if err != nil { 138 | log.Panic(err) 139 | } 140 | b2, err := plotter.NewBoxPlot(w, x, expon) 141 | if err != nil { 142 | log.Panic(err) 143 | } 144 | b2.Offset = w + vg.Points(3) 145 | p3.Add(b0, b1, b2) 146 | } 147 | // Add a GlyphBox plotter for debugging. 148 | p3.Add(plotter.NewGlyphBoxes()) 149 | 150 | // Set the X axis of the plot to nominal with 151 | // the given names for x=0, x=1 and x=2. 152 | p3.NominalX("Group 0", "Group 1", "Group 2") 153 | err = p3.Save(300, 300, "testdata/groupedBoxPlot.png") 154 | if err != nil { 155 | log.Panic(err) 156 | } 157 | } 158 | 159 | func TestBoxPlot(t *testing.T) { 160 | cmpimg.CheckPlot(ExampleBoxPlot, t, "verticalBoxPlot.png", 161 | "horizontalBoxPlot.png", "groupedBoxPlot.png") 162 | } 163 | --------------------------------------------------------------------------------