diff --git a/core_test.go b/core_test.go index 8e8cc5f..2595b95 100644 --- a/core_test.go +++ b/core_test.go @@ -1,7 +1,13 @@ package glot import ( + "bytes" + "crypto/md5" + "io" + "io/ioutil" "math" + "os" + "path/filepath" "testing" ) @@ -40,3 +46,440 @@ func TestCmd(t *testing.T) { } p.plotX(pg) } + +func TestGeneratePngOutput1d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Png, + Persist: true, + Style: Points, + } + plot, err := NewPlot(1, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := makeSomeInts(make([]int, 128), true) + plot.AddPointGroup("SaveAs PNG test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.png") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 114, 231, 226, 101, 216, 182, 101, 224, + 51, 18, 92, 252, 249, 110, 248, 105, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGeneratePdfOutput1d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Pdf, + Persist: true, + Style: Points, + } + plot, err := NewPlot(1, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := makeSomeInts(make([]int, 128), true) + plot.AddPointGroup("SaveAs PDF test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.pdf") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 196, 115, 172, 87, 72, 52, 213, 2, + 151, 79, 85, 79, 8, 55, 73, 228, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGenerateSvgOutput1d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Svg, + Persist: true, + Style: Points, + } + plot, err := NewPlot(1, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := makeSomeInts(make([]int, 128), true) + plot.AddPointGroup("SaveAs SVG test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.svg") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 94, 214, 211, 163, 55, 159, 236, 46, + 130, 189, 78, 122, 220, 243, 2, 35, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGeneratePngOutput2d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Png, + Persist: true, + Style: Points, + } + plot, err := NewPlot(2, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false)} + plot.AddPointGroup("SaveAs PNG test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.png") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 134, 219, 152, 31, 199, 131, 195, 224, + 140, 175, 50, 31, 115, 132, 118, 238, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGeneratePdfOutput2d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Pdf, + Persist: true, + Style: Points, + } + plot, err := NewPlot(2, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false)} + plot.AddPointGroup("SaveAs PDF test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.pdf") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 17, 138, 162, 227, 90, 247, 240, 14, + 42, 73, 209, 125, 225, 253, 138, 13, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGenerateSvgOutput2d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Svg, + Persist: true, + Style: Points, + } + plot, err := NewPlot(2, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false)} + plot.AddPointGroup("SaveAs SVG test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.svg") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 57, 24, 100, 136, 119, 161, 201, 19, + 64, 147, 209, 92, 18, 72, 21, 36, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGeneratePngOutput3d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Png, + Persist: true, + Style: Points, + } + plot, err := NewPlot(3, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{ + makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false), + makeSomeInts(make([]int, 32), false), + } + plot.AddPointGroup("SaveAs PNG test", Points, pts) + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.png") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 218, 234, 117, 20, 238, 2, 137, 102, + 25, 29, 222, 203, 71, 99, 184, 139, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGeneratePdfOutput3d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Pdf, + Persist: true, + Style: Points, + } + plot, err := NewPlot(3, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{ + makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false), + makeSomeInts(make([]int, 32), false), + } + plot.AddPointGroup("SaveAs PDF test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.pdf") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 113, 70, 64, 149, 3, 210, 177, 245, + 62, 237, 2, 31, 137, 88, 5, 69, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func TestGenerateSvgOutput3d1(t *testing.T) { + var testArgs = PlotArgs{ + Debug: true, + Format: Svg, + Persist: true, + Style: Points, + } + plot, err := NewPlot(3, testArgs) + if err != nil { + t.Errorf("NewPlot failed creation with: %v", err) + } + pts := [][]int{ + makeSomeInts(make([]int, 32), true), + makeSomeInts(make([]int, 32), false), + makeSomeInts(make([]int, 32), false), + } + plot.AddPointGroup("SaveAs SVG test", Points, pts) + + tmpDir, err := ioutil.TempDir("", "glot") + if err != nil { + t.Errorf("%v", err) + } + tmpPlotFile := filepath.Join(tmpDir, "f.svg") + defer os.RemoveAll(tmpDir) + plot.SavePlot(tmpPlotFile) + plot.Close() + + // Open file, read it into a checksum function and generate checksum. + fh, err := os.Open(tmpPlotFile) + if err != nil { + t.Errorf("%v", err) + } + defer fh.Close() + hash := md5.New() + if _, err := io.Copy(hash, fh); err != nil { + t.Errorf("%v", err) + } + // Compare expected checksum against actual checksum. + // If there is a mismatch, test should fail. + expected := []byte{ + 232, 198, 196, 33, 52, 149, 67, 25, + 79, 84, 140, 42, 119, 223, 123, 203, + } + if bytes.Compare(expected, hash.Sum(nil)) != 0 { + t.Errorf("Failed checksum, expected: %x got: %x", + expected, hash.Sum(nil)) + } +} + +func skipOneInts(n []int, acc int) []int { + if len(n) == 0 { + return n + } + n[0] = acc + acc += 2 + return skipOneInts(n[1:], acc) +} + +func makeSomeInts(n []int, e bool) []int { + if e { + skipOneInts(n, 0) // evens, start at 0 increment by 2 + } else { + skipOneInts(n, 1) // odds, start at 1, increment by 2 + } + return n +} diff --git a/function.go b/function.go index ae371bd..3407704 100644 --- a/function.go +++ b/function.go @@ -38,7 +38,11 @@ func (plot *Plot) AddFunc2d(name string, style PointStyle, x []float64, fct Func combined := [][]float64{} combined = append(combined, x) combined = append(combined, y) - plot.AddPointGroup(name, style, combined) + curve, err := plot.AddPointGroup(name, style, combined) + if err != nil { + return &gnuplotError{err.Error()} + } + plot.plotXY(curve) return nil } @@ -79,6 +83,10 @@ func (plot *Plot) AddFunc3d(name string, style PointStyle, x []float64, y []floa combined = append(combined, x) combined = append(combined, y) combined = append(combined, z) - plot.AddPointGroup(name, style, combined) + curve, err := plot.AddPointGroup(name, style, combined) + if err != nil { + return &gnuplotError{err.Error()} + } + plot.plotXYZ(curve) return nil } diff --git a/function_test.go b/function_test.go index a911ad7..41f83cc 100644 --- a/function_test.go +++ b/function_test.go @@ -1,6 +1,8 @@ package glot import ( + "math" + "math/rand" "testing" ) @@ -21,3 +23,72 @@ func TestAddFunc3d(t *testing.T) { t.Error("TestAddFunc3d raises error when the size of X and Y arrays are not equal.") } } + +func TestAddFunc2d1(t *testing.T) { + dimensions := 2 + plot, _ := NewPlot(dimensions, funcTestArgs) + fct := func(x float64) float64 { return math.Pow(math.E, x) } + + groupName := "Natural Exponential" + pointsX := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18} + err := plot.AddFunc2d(groupName, Lines, pointsX, fct) + if err != nil { + t.Error(err.Error()) + } +} + +func TestAddFunc2d2(t *testing.T) { + dimensions := 2 + plot, _ := NewPlot(dimensions, funcTestArgs) + fct := func(x float64) float64 { return x * x } + rand.Seed(111) + groupName := "Exponential Floating Point Squared" + pointsX := make([]float64, 1024) + expFloat64(pointsX) + err := plot.AddFunc2d(groupName, Points, pointsX, fct) + if err != nil { + t.Error(err.Error()) + } +} + +func TestAddFunc2d3(t *testing.T) { + dimensions := 2 + plot, _ := NewPlot(dimensions, funcTestArgs) + fct := func(x float64) float64 { return 1 / x } + + groupName := "Squared Integers" + pointsX := make([]float64, 100) + recSquared(pointsX) + err := plot.AddFunc2d(groupName, Lines, pointsX, fct) + if err != nil { + t.Error(err.Error()) + } +} + +func TestAddFunc3d1(t *testing.T) { + dimensions := 3 + plot, _ := NewPlot(dimensions, funcTestArgs) + fct := func(x, y float64) float64 { return x - y } + groupName := "Straight Line" + pointsY := []float64{1, 2, 3, 4, 5} + pointsX := []float64{1, 2, 3, 4, 5} + err := plot.AddFunc3d(groupName, Lines, pointsX, pointsY, fct) + if err != nil { + t.Error(err.Error()) + } +} + +func expFloat64(n []float64) []float64 { + if len(n) == 0 { + return n + } + n[0] = rand.ExpFloat64() + return expFloat64(n[1:]) +} +func recSquared(n []float64) float64 { + if len(n) == 0 { + return 0 + } + n[len(n)-1] = float64((len(n) - 1) * (len(n) - 1)) + return recSquared(n[:len(n)-1]) +} diff --git a/glot.go b/glot.go index 58c0451..78b330c 100644 --- a/glot.go +++ b/glot.go @@ -62,6 +62,8 @@ const ( Pdf = iota // Png is a png output format Png + // Svg is a svg output format + Svg ) // PointStyle specifies which style to use for plotting a set of points. @@ -102,8 +104,10 @@ func (pf PlotFormat) String() string { return "pdf" case Png: return "png" + case Svg: + return "svg" default: - return "unsupported" + return "pdf" } } diff --git a/pointgroup.go b/pointgroup.go index cea1d1a..7cd8b37 100644 --- a/pointgroup.go +++ b/pointgroup.go @@ -2,6 +2,10 @@ package glot import "fmt" +const incorrectDimErrMsg = "The dimensions of this PointGroup are not " + + "compatible with the dimensions of the plot.\n " + + "If you want to make a %d-d curve you must specify a %d-d plot." + // A PointGroup refers to a set of points that need to be plotted. // It could either be a set of points or a function of co-ordinates. // For Example z = Function(x,y)(3 Dimensional) or y = Function(x) (2-Dimensional) @@ -39,6 +43,7 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) dimensions: plot.dimensions, data: data, set: true, + style: style, } // We want to make sure that pointGroups are added to figure in a // consistent and repeatable manner. Because we are using maps, the @@ -69,14 +74,16 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) switch data.(type) { case [][]float64: if plot.dimensions != len(data.([][]float64)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } curve.castedData = data.([][]float64) plot.PointGroup[name] = curve case [][]float32: if plot.dimensions != len(data.([][]float32)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]float32) typeCasteSlice := make([][]float64, len(originalSlice)) @@ -91,12 +98,10 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) case [][]int: if plot.dimensions != len(data.([][]int)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]int) - if len(originalSlice) != 2 { - return nil, &gnuplotError{fmt.Sprintf("this is not a 2d matrix")} - } typeCasteSlice := make([][]float64, len(originalSlice)) for i := 0; i < len(originalSlice); i++ { typeCasteSlice[i] = make([]float64, len(originalSlice[i])) @@ -109,12 +114,10 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) case [][]int8: if plot.dimensions != len(data.([][]int8)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]int8) - if len(originalSlice) != 2 { - return nil, &gnuplotError{fmt.Sprintf("this is not a 2d matrix")} - } typeCasteSlice := make([][]float64, len(originalSlice)) for i := 0; i < len(originalSlice); i++ { typeCasteSlice[i] = make([]float64, len(originalSlice[i])) @@ -127,12 +130,10 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) case [][]int16: if plot.dimensions != len(data.([][]int16)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]int16) - if len(originalSlice) != 2 { - return nil, &gnuplotError{fmt.Sprintf("this is not a 2d matrix")} - } typeCasteSlice := make([][]float64, len(originalSlice)) for i := 0; i < len(originalSlice); i++ { typeCasteSlice[i] = make([]float64, len(originalSlice[i])) @@ -145,12 +146,10 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) case [][]int32: if plot.dimensions != len(data.([][]int32)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]int32) - if len(originalSlice) != 2 { - return nil, &gnuplotError{fmt.Sprintf("this is not a 2d matrix")} - } typeCasteSlice := make([][]float64, len(originalSlice)) for i := 0; i < len(originalSlice); i++ { typeCasteSlice[i] = make([]float64, len(originalSlice[i])) @@ -163,12 +162,10 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) case [][]int64: if plot.dimensions != len(data.([][]int64)) { - return nil, &gnuplotError{fmt.Sprintf("The dimensions of this PointGroup are not compatible with the dimensions of the plot.\nIf you want to make a 2-d curve you must specify a 2-d plot.")} + return nil, &gnuplotError{fmt.Sprintf(incorrectDimErrMsg, + plot.dimensions, plot.dimensions)} } originalSlice := data.([][]int64) - if len(originalSlice) != 2 { - return nil, &gnuplotError{fmt.Sprintf("this is not a 2d matrix")} - } typeCasteSlice := make([][]float64, len(originalSlice)) for i := 0; i < len(originalSlice); i++ { typeCasteSlice[i] = make([]float64, len(originalSlice[i])) @@ -238,6 +235,16 @@ func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) fmt.Printf("** default to 'points'\n") err = &gnuplotError{fmt.Sprintf("invalid style '%s'", style)} } + switch curve.dimensions { + case 1: + plot.plotX(curve) + case 2: + plot.plotXY(curve) + case 3: + plot.plotXYZ(curve) + default: + return curve, &gnuplotError{fmt.Sprintf("invalid number of dimensions")} + } return curve, err }