arafatkatze / glot

Glot is a plotting library for Golang built on top of gnuplot.
MIT License
398 stars 19 forks source link

Improvement ideas #12

Open szaydel opened 6 years ago

szaydel commented 6 years ago

I have been playing around with the library a bit and made some changes over time. These are just ideas I wanted to share and see if there's much interest. Most of them are half-baked, as such no PR yet, but if you have interest in these, I am sure I can bring them closer to being useful.

diff --git c/common.go w/common.go
index e57bee1..712b8c4 100644
--- c/common.go
+++ w/common.go
@@ -139,7 +139,7 @@ func (plot *Plot) SetLogscale(axis string, base int) error {
 //  plot.AddPointGroup("Sample 1", "lines", []float64{2, 3, 4, 1})
 //  plot.SetTitle("Test Results")
 //     plot.SetYrange(-2,2)
-func (plot *Plot) SetYrange(start int, end int) error {
+func (plot *Plot) SetYrange(start, end int) error {
    return plot.Cmd(fmt.Sprintf("set yrange [%d:%d]", start, end))
 }

@@ -174,7 +174,22 @@ func (plot *Plot) SavePlot(filename string) (err error) {
    if plot.nplots == 0 {
        return &gnuplotError{fmt.Sprintf("This plot has 0 curves and therefore its a redundant plot and it can't be printed.")}
    }
-   outputFormat := "set terminal " + plot.format
+   outputFormat := "set terminal " + plot.format.String()
+   fmt.Printf("outputFormat: %s\n", outputFormat)
+   plot.CheckedCmd(outputFormat)
+   outputFileCommand := "set output " + "'" + filename + "'"
+   fmt.Printf("outputFileCommand: %s\n", outputFileCommand)
+   plot.CheckedCmd(outputFileCommand)
+   plot.CheckedCmd("replot  ")
+   return nil
+}
+
+func (plot *Plot) SaveHistogram(filename string) (err error) {
+   if plot.nplots == 0 {
+       return &gnuplotError{fmt.Sprintf("This plot has 0 curves and therefore its a redundant plot and it can't be printed.")}
+   }
+   plot.style = Histogram
+   outputFormat := "set terminal " + plot.format.String()
    plot.CheckedCmd(outputFormat)
    outputFileCommand := "set output" + "'" + filename + "'"
    plot.CheckedCmd(outputFileCommand)
@@ -196,9 +211,9 @@ func (plot *Plot) SavePlot(filename string) (err error) {
 //     plot.SetFormat("pdf")
 //  plot.SavePlot("1.pdf")
 // NOTE: png is default format for saving files.
-func (plot *Plot) SetFormat(newformat string) error {
-   allowed := []string{
-       "png", "pdf"}
+func (plot *Plot) SetFormat(newformat PlotFormat) error {
+   allowed := []PlotFormat{
+       Png, Pdf}
    for _, s := range allowed {
        if newformat == s {
            plot.format = newformat
@@ -210,3 +225,32 @@ func (plot *Plot) SetFormat(newformat string) error {
    err := &gnuplotError{fmt.Sprintf("invalid format '%s'", newformat)}
    return err
 }
+
+// SetLineStyleBrewerQualitative1 sets color palette based on
+// ColorBrewer Qualitative Paired Scheme with 9 colors.
+func (plot *Plot) SetLineStyleBrewerQualitative1() {
+   pal := `
+   # line styles
+   set style line 1 lt 1 lc rgb '#a6cee3' # light blue
+   set style line 2 lt 1 lc rgb '#1f78b4' # dark blue
+   set style line 3 lt 1 lc rgb '#b2df8a' # light green
+   set style line 4 lt 1 lc rgb '#33a02c' # dark green
+   set style line 5 lt 1 lc rgb '#fb9a99' # light red
+   set style line 6 lt 1 lc rgb '#e31a1c' # dark red
+   set style line 7 lt 1 lc rgb '#fdbf6f' # light orange
+   set style line 8 lt 1 lc rgb '#ff7f00' # dark orange
+   set style line 9 lt 1 lc rgb '#cab2d6' # light purple
+
+   # palette
+   set palette defined ( 0 '#a6cee3',\
+                         1 '#1f78b4',\
+                         2 '#b2df8a',\
+                         3 '#33a02c',\
+                         4 '#fb9a99',\
+                         5 '#e31a1c',\
+                         6 '#fdbf6f',\
+                         7 '#ff7f00',\
+                         8 '#cab2d6')`
+   plot.Cmd(pal)
+   plot.Execute()
+}
\ No newline at end of file
diff --git c/common_test.go w/common_test.go
index 0c84fc7..d31f97f 100644
--- c/common_test.go
+++ w/common_test.go
@@ -2,24 +2,123 @@ package glot

 import "testing"

-func TestSetLabels(t *testing.T) {
+var Args = PlotArgs{
+   Debug:   true,
+   Persist: true,
+}
+
+func TestSetLabels3Dims(t *testing.T) {
    dimensions := 3
-   persist := false
-   debug := false
-   plot, _ := NewPlot(dimensions, persist, debug)
+   plot, _ := NewPlot(dimensions, Args)
+   err := plot.SetLabels()
+   if err == nil {
+       t.Error("SetLabels raises error when empty string is passed")
+   }
+}
+
+func TestSetLabels2Dims(t *testing.T) {
+   dimensions := 2
+   plot, _ := NewPlot(dimensions, Args)
    err := plot.SetLabels()
    if err == nil {
        t.Error("SetLabels raises error when empty string is passed")
    }
 }

-func TestSetFormat(t *testing.T) {
+func TestSetLabels1Dims(t *testing.T) {
+   dimensions := 1
+   plot, _ := NewPlot(dimensions, Args)
+   err := plot.SetLabels()
+   if err == nil {
+       t.Error("SetLabels raises error when empty string is passed")
+   }
+}
+
+func TestSetFormat3Dims(t *testing.T) {
    dimensions := 3
-   persist := false
-   debug := false
-   plot, _ := NewPlot(dimensions, persist, debug)
-   err := plot.SetFormat("tls")
+
+   // Test unsupported formats
+   plot, _ := NewPlot(dimensions, Args)
+   err := plot.SetFormat(0)
    if err == nil {
        t.Error("SetLabels raises error when non-supported format is passed as an argument.")
    }
+
+   // Test supported formats
+   plot, _ = NewPlot(dimensions, Args)
+   err = plot.SetFormat(Pdf)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Pdf).")
+   }
+   err = plot.SetFormat(Png)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Png).")
+   }
 }
+
+func TestSetFormat2Dims(t *testing.T) {
+   dimensions := 2
+
+   // Test unsupported formats
+   plot, _ := NewPlot(dimensions, Args)
+   err := plot.SetFormat(0)
+   if err == nil {
+       t.Error("SetLabels raises error when non-supported format is passed as an argument.")
+   }
+
+   // Test supported formats
+   plot, _ = NewPlot(dimensions, Args)
+   err = plot.SetFormat(Pdf)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Pdf).")
+   }
+   err = plot.SetFormat(Png)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Png).")
+   }
+}
+
+func TestSetFormat1Dims(t *testing.T) {
+   dimensions := 1
+
+   // Test unsupported formats
+   plot, _ := NewPlot(dimensions, Args)
+   err := plot.SetFormat(0)
+   if err == nil {
+       t.Error("SetLabels raises error when non-supported format is passed as an argument.")
+   }
+
+   // Test supported formats
+   plot, _ = NewPlot(dimensions, Args)
+   err = plot.SetFormat(Pdf)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Pdf).")
+   }
+   err = plot.SetFormat(Png)
+   if err != nil {
+       t.Error("SetLabels failed to set supported format (Png).")
+   }
+}
+
+func TestLinesStyleBrewerQualitative1(t *testing.T) {
+   dimensions := 1
+
+   // Test unsupported formats
+   plot1, _ := NewPlot(dimensions, Args)
+   plot2, _ := NewPlot(dimensions, Args)
+   t1 := `
+   set multiplot layout 2,2 ;
+   plot sin(x) ls 1 ; plot sin(x/2) ls 2 ;
+   plot sin(x/4) ls 3 ; plot cos(x/2) ls 4
+   `
+   t2 := `
+   set multiplot layout 2,2 ;
+   plot sin(x) ls 5 ; plot sin(x/2) ls 6 ;
+   plot sin(x/4) ls 7 ; plot cos(x/2) ls 8
+   `
+   plot1.SetLineStyleBrewerQualitative1()
+   plot2.SetLineStyleBrewerQualitative1()
+   plot1.Cmd(t1)
+   plot2.Cmd(t2)
+
+}
\ No newline at end of file
diff --git c/core.go w/core.go
index 5146ff4..701774d 100644
--- c/core.go
+++ w/core.go
@@ -73,7 +73,9 @@ func (plot *Plot) Cmd(format string, a ...interface{}) error {
    if plot.debug {
        //buf := new(bytes.Buffer)
        //io.Copy(buf, plot.proc.handle.Stdout)
-       fmt.Printf("cmd> %v", cmd)
+       fmt.Printf("fmt> %v\n", format)
+       fmt.Printf("a>   %v\n", a)
+       fmt.Printf("cmd> %v\n", cmd)
        fmt.Printf("res> %v\n", n)
    }
    return err
diff --git c/core_test.go w/core_test.go
index 077ff59..8e8cc5f 100644
--- c/core_test.go
+++ w/core_test.go
@@ -1,6 +1,9 @@
 package glot

-import "testing"
+import (
+   "math"
+   "testing"
+)

 func TestMin(t *testing.T) {
    var v int
@@ -9,3 +12,31 @@ func TestMin(t *testing.T) {
        t.Error("Expected 1, got ", v)
    }
 }
+
+func TestCmd(t *testing.T) {
+   base2n := func(n float64) []float64 {
+       res := []float64{}
+       for i := 0.; i < n; i++ {
+           res = append(res, math.Pow(2, i))
+       }
+       return res
+   }
+   var testArgs = PlotArgs{
+       Debug:   true,
+       Format:  Pdf,
+       Persist: true,
+       Style:   Points,
+       Command: "plot ",
+   }
+   var pg = &PointGroup{
+       name:       "TestCmd",
+       dimensions: 1,
+       style:      Points,
+       castedData: base2n(9),
+   }
+   p, err := NewPlot(1, testArgs)
+   if err != nil {
+       t.Errorf("NewPlot failed creation with: %v", err)
+   }
+   p.plotX(pg)
+}
diff --git c/function.go w/function.go
index 29fee9b..ae371bd 100644
--- c/function.go
+++ w/function.go
@@ -30,7 +30,7 @@ type Func3d func(x float64, y float64) float64
 //  pointsX     :=> The x Value of the points to be plotted.  y = func(x) is plotted on the curve.
 //  style       :=> Style of the curve
 // NOTE: Currently only float64 type is supported for this function
-func (plot *Plot) AddFunc2d(name string, style string, x []float64, fct Func2d) error {
+func (plot *Plot) AddFunc2d(name string, style PointStyle, x []float64, fct Func2d) error {
    y := make([]float64, len(x))
    for index := range x {
        y[index] = fct(x[index])
@@ -67,7 +67,7 @@ func (plot *Plot) AddFunc2d(name string, style string, x []float64, fct Func2d)
 //  style       :=> Style of the curve
 //  pointsX     :=> The x Value of the points to be plotted.  y = func(x) is plotted on the curve.
 // NOTE: Currently only float64 type is supported for this function
-func (plot *Plot) AddFunc3d(name string, style string, x []float64, y []float64, fct Func3d) error {
+func (plot *Plot) AddFunc3d(name string, style PointStyle, x []float64, y []float64, fct Func3d) error {
    if len(x) != len(y) {
        return &gnuplotError{fmt.Sprintf("The length of the x-axis array and y-axis array are not same.")}
    }
diff --git c/function_test.go w/function_test.go
index c605395..c9b4871 100644
--- c/function_test.go
+++ w/function_test.go
@@ -5,16 +5,17 @@ import (
 )

 func TestAddFunc3d(t *testing.T) {
+   args := PlotArgs{
+       Debug:   false,
+       Persist: false,
+   }
    dimensions := 3
-   persist := false
-   debug := false
-   plot, _ := NewPlot(dimensions, persist, debug)
+   plot, _ := NewPlot(dimensions, args)
    fct := func(x, y float64) float64 { return x - y }
-   groupName := "Stright Line"
-   style := "lines"
+   groupName := "Straight Line"
    pointsY := []float64{1, 2, 3}
    pointsX := []float64{1, 2, 3, 4, 5}
-   err := plot.AddFunc3d(groupName, style, pointsX, pointsY, fct)
+   err := plot.AddFunc3d(groupName, Lines, pointsX, pointsY, fct)
    if err == nil {
        t.Error("TestAddFunc3d raises error when the size of X and Y arrays are not equal.")
    }
diff --git c/glot.go w/glot.go
index a21e367..0dbe541 100644
--- c/glot.go
+++ w/glot.go
@@ -25,16 +25,93 @@ import (
 type Plot struct {
    proc       *plotterProcess
    debug      bool
-   plotcmd    string
+   plotcmd    PlotCommand
    nplots     int                    // number of currently active plots
    tmpfiles   tmpfilesDb             // A temporary file used for saving data
    dimensions int                    // dimensions of the plot
    PointGroup map[string]*PointGroup // A map between Curve name and curve type. This maps a name to a given curve in a plot. Only one curve with a given name exists in a plot.
-   format     string                 // The saving format of the plot. This could be PDF, PNG, JPEG and so on.
-   style      string                 // style of the plot
+   format     PlotFormat             // The saving format of the plot. This could be PDF, PNG, JPEG and so on.
+   style      PointStyle             // style of the plot
    title      string                 // The title of the plot.
 }

+const (
+   // Points is default style
+   Points = iota
+   // Bar is a Barplot type
+   Bar
+   BoxErrorBars
+   Circle
+   Dots
+   ErrorBars
+   FillSolid
+   // Histogram, i.e. fancy barplot
+   Histogram
+   // Lines is a lineplot
+   Lines
+   // LinesPoints is a lineplot with points
+   LinesPoints
+   Steps
+   InvalidPointStyle
+
+   // Pdf is a pdf output format
+   Pdf = iota
+   // Png is a png output format
+   Png
+)
+
+// PlotStyle ...
+type PointStyle uint
+
+// String is an implementation of the Stringer Interface
+// for PointStyle type.
+func (p PointStyle) String() string {
+   var m = map[PointStyle]string{
+       Bar:          "bar",
+       BoxErrorBars: "boxerrorbars",
+       Circle:       "circle",
+       Dots:         "dots",
+       ErrorBars:    "errorbars",
+       FillSolid:    "fill solid",
+       Histogram:    "histogram",
+       Lines:        "lines",
+       LinesPoints:  "linespoints",
+       Points:       "points",
+   }
+   if _, ok := m[p]; !ok {
+       return m[Points]
+   }
+   return m[p]
+}
+
+// PlotFormat ...
+type PlotFormat uint
+
+// String is an implementation of the Stringer Interface
+// for PlotFormat type.
+func (pf PlotFormat) String() string {
+   switch pf {
+   case Pdf:
+       return "pdf"
+   case Png:
+       return "png"
+   default:
+       return "unsupported"
+   }
+}
+
+// PlotCommand ...
+type PlotCommand string
+
+// PlotArgs ...
+type PlotArgs struct {
+   Debug   bool
+   Format  PlotFormat
+   Persist bool
+   Command PlotCommand
+   Style   PointStyle
+}
+
 // NewPlot Function makes a new plot with the specified dimensions.
 //
 // Usage
@@ -46,12 +123,22 @@ type Plot struct {
 //  dimensions  :=> refers to the dimensions of the plot.
 //  debug       :=> can be used by developers to check the actual commands sent to gnu plot.
 //  persist     :=> used to make the gnu plot window stay open.
-func NewPlot(dimensions int, persist, debug bool) (*Plot, error) {
-   p := &Plot{proc: nil, debug: debug, plotcmd: "plot",
-       nplots: 0, dimensions: dimensions, style: "points", format: "png"}
+func NewPlot(dimensions int, args PlotArgs) (*Plot, error) {
+   p := &Plot{
+       proc:  nil,
+       debug: args.Debug,
+       plotcmd: func() PlotCommand {
+           if args.Command == "" {
+               return "plot"
+           } else {
+               return args.Command
+           }
+       }(),
+       nplots: 0, dimensions: dimensions,
+       style: args.Style, format: args.Format}
    p.PointGroup = make(map[string]*PointGroup) // Adding a mapping between a curve name and a curve
    p.tmpfiles = make(tmpfilesDb)
-   proc, err := newPlotterProc(persist)
+   proc, err := newPlotterProc(args.Persist)
    if err != nil {
        return nil, err
    }
@@ -63,69 +150,151 @@ func NewPlot(dimensions int, persist, debug bool) (*Plot, error) {
    return p, nil
 }

-func (plot *Plot) plotX(PointGroup *PointGroup) error {
+func (plot *Plot) pointGroupSliceLen() int {
+   pgs, err := plot.pointGroupSlice()
+   if err != nil {
+       return 0
+   }
+   return len(pgs)
+}
+func (plot *Plot) pointGroupSlice() ([]*PointGroup, error) {
+   pgsl := []*PointGroup{}
+   if len(plot.PointGroup) == 0 {
+       return []*PointGroup{},
+           &gnuplotError{fmt.Sprintf("no pointgroups were found")}
+   }
+   for _, pg := range plot.PointGroup {
+       pgsl = append(pgsl, pg)
+   }
+   return pgsl, nil
+}
+
+func (plot *Plot) plotHistogram(PointGroup *PointGroup) error {
+   x := PointGroup.castedData.([][]float64)[0]
+   // y := PointGroup.castedData.([][]float64)[1]
+   npoints := len(x)
+   // npoints := min(len(x), len(y))
+
    f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
    if err != nil {
        return err
    }
    fname := f.Name()
    plot.tmpfiles[fname] = f
-   for _, d := range PointGroup.castedData.([]float64) {
-       f.WriteString(fmt.Sprintf("%v\n", d))
+
+   for i := 0; i < npoints; i++ {
+       f.WriteString(fmt.Sprintf("%v\n", x[i]))
    }
+
    f.Close()
    cmd := plot.plotcmd
    if plot.nplots > 0 {
        cmd = plotCommand
    }
-   if PointGroup.style == "" {
-       PointGroup.style = defaultStyle
+
+   PointGroup.style = Histogram
+
+   var line string
+   if PointGroup.name == "" {
+       line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, plot.style)
+   } else {
+       line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
+           cmd, fname, PointGroup.name, PointGroup.style)
+   }
+   plot.nplots++
+   return plot.Cmd(line)
+}
+func (plot *Plot) plotX(PointGroup *PointGroup) error {
+   f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
+   if err != nil {
+       return err
+   }
+   defer f.Close()
+
+   fname := f.Name()
+   plot.tmpfiles[fname] = f
+   for _, d := range PointGroup.castedData.([]float64) {
+       f.WriteString(fmt.Sprintf("%v\n", d))
+   }
+
+   var cmd PlotCommand
+   if plot.nplots > 0 {
+       cmd = ""
+   } else {
+       cmd = plot.plotcmd
+   }
+
+   if PointGroup.style < 0 || PointGroup.style >= InvalidPointStyle {
+       PointGroup.style = Points
    }
    var line string
    if PointGroup.name == "" {
+
        line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, PointGroup.style)
    } else {
        line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
            cmd, fname, PointGroup.name, PointGroup.style)
    }
+   if plot.nplots > 0 {
+       plot.plotcmd = plot.plotcmd + ", " + PlotCommand(line)
+   } else {
+       plot.plotcmd = PlotCommand(line)
+   }
    plot.nplots++
-   return plot.Cmd(line)
+   // return plot.Cmd(line)
+   return nil
 }

 func (plot *Plot) plotXY(PointGroup *PointGroup) error {
    x := PointGroup.castedData.([][]float64)[0]
    y := PointGroup.castedData.([][]float64)[1]
    npoints := min(len(x), len(y))
-
+   pointString := ""
    f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
    if err != nil {
        return err
    }
+   defer f.Close()
+
    fname := f.Name()
    plot.tmpfiles[fname] = f

    for i := 0; i < npoints; i++ {
-       f.WriteString(fmt.Sprintf("%v %v\n", x[i], y[i]))
+       pointString += fmt.Sprintf("%v %v\n", x[i], y[i])
+       // f.WriteString(fmt.Sprintf("%v %v\n", x[i], y[i]))
+       if i%10000 == 0 { // flush every 10,000 lines
+           f.WriteString(pointString)
+           pointString = ""
+       }
    }
+   f.WriteString(pointString)

-   f.Close()
-   cmd := plot.plotcmd
+   var cmd PlotCommand
    if plot.nplots > 0 {
-       cmd = plotCommand
+       cmd = ""
+   } else {
+       cmd = plot.plotcmd
    }

-   if PointGroup.style == "" {
-       PointGroup.style = "points"
-   }
+   // if plot.nplots > 0 {
+   //  cmd = plotCommand
+   // }
    var line string
    if PointGroup.name == "" {
-       line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, PointGroup.style)
+       line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, PointGroup.style.String())
    } else {
        line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
            cmd, fname, PointGroup.name, PointGroup.style)
    }
+   if plot.nplots > 0 {
+       plot.plotcmd = plot.plotcmd + ", " + PlotCommand(line)
+   } else {
+       plot.plotcmd = PlotCommand(line)
+   }
    plot.nplots++
-   return plot.Cmd(line)
+
+   // return plot.Cmd(line)
+   return nil
 }

 func (plot *Plot) plotXYZ(points *PointGroup) error {
@@ -138,6 +307,7 @@ func (plot *Plot) plotXYZ(points *PointGroup) error {
    if err != nil {
        return err
    }
+   defer f.Close()
    fname := f.Name()
    plot.tmpfiles[fname] = f

@@ -161,3 +331,27 @@ func (plot *Plot) plotXYZ(points *PointGroup) error {
    plot.nplots++
    return plot.Cmd(line)
 }
+
+// Execute triggers generation of actual figure
+func (plot *Plot) Execute() error {
+   pgs, err := plot.pointGroupSlice()
+   if len(pgs) == 0 {
+       return err
+   }
+   for _, pg := range pgs {
+       switch pg.dimensions {
+       case 1:
+           plot.plotX(pg)
+       case 2:
+           fmt.Printf("%v\n", pg)
+           plot.plotXY(pg)
+       case 3:
+           plot.plotXYZ(pg)
+       default:
+           return &gnuplotError{
+               fmt.Sprintf("unexpected number of dimensions in pointgroup"),
+           }
+       }
+   }
+   return plot.Cmd(string(plot.plotcmd))
+}
diff --git c/glot_test.go w/glot_test.go
index a93e9be..a912ddc 100644
--- c/glot_test.go
+++ w/glot_test.go
@@ -3,9 +3,11 @@ package glot
 import "testing"

 func TestNewPlot(t *testing.T) {
-   persist := false
-   debug := true
-   _, err := NewPlot(0, persist, debug)
+   args := PlotArgs{
+       Debug:   false,
+       Persist: false,
+   }
+   _, err := NewPlot(0, args)
    if err == nil {
        t.Error("Expected error when making a 0 dimensional plot.")
    }
diff --git c/pointgroup.go w/pointgroup.go
index dce5d91..fa0fe96 100644
--- c/pointgroup.go
+++ w/pointgroup.go
@@ -1,19 +1,22 @@
 package glot

-import (
-   "fmt"
-)
+import "fmt"

-// A PointGroup refers to a set of points that need to plotted.
+// 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)
 type PointGroup struct {
    name       string      // Name of the curve
    dimensions int         // dimensions of the curve
-   style      string      // current plotting style
+   style      PointStyle  // current plotting style
    data       interface{} // Data inside the curve in any integer/float format
    castedData interface{} // The data inside the curve typecasted to float64
    set        bool        //
+   index      int         // Relative index of pointgroup in the plot
+}
+
+func (pg *PointGroup) setIndex(idx int) {
+   pg.index = idx
 }

 // AddPointGroup function adds a group of points to a plot.
@@ -26,39 +29,46 @@ type PointGroup struct {
 //  plot.AddPointGroup("Sample1", "points", []int32{51, 8, 4, 11})
 //  plot.AddPointGroup("Sample2", "points", []int32{1, 2, 4, 11})
 //  plot.SavePlot("1.png")
-func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (err error) {
+func (plot *Plot) AddPointGroup(name string, style PointStyle, data interface{}) (err error) {
    _, exists := plot.PointGroup[name]
    if exists {
        return &gnuplotError{fmt.Sprintf("A PointGroup with the name %s  already exists, please use another name of the curve or remove this curve before using another one with the same name.", name)}
    }

-   curve := &PointGroup{name: name, dimensions: plot.dimensions, data: data, set: true}
-   allowed := []string{
-       "lines", "points", "linepoints",
-       "impulses", "dots", "bar",
-       "steps", "fill solid", "histogram", "circle",
-       "errorbars", "boxerrorbars",
-       "boxes", "lp"}
-   curve.style = defaultStyle
+   curve := &PointGroup{name: name, 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
+   // order is inherently unpredictable and using an index for each group
+   // allows us to have reproducible plots.
+   curve.setIndex(plot.pointGroupSliceLen())
    discovered := 0
-   for _, s := range allowed {
-       if s == style {
-           curve.style = style
-           err = nil
+   // If the style value is an empty string and there's only a single
+   // dimension, assume histogram by default.
+
+   if style < 0 || style >= InvalidPointStyle {
+       switch plot.dimensions {
+       case 0:
+           return &gnuplotError{
+               fmt.Sprintf("Wrong number of dimensions in this plot."),
+           }
+       case 1:
+           curve.style = Histogram
+           discovered = 1
+       case 2, 3:
+           curve.style = Points
            discovered = 1
        }
+   } else {
+       discovered++
    }
+
    switch data.(type) {
    case [][]float64:
        if plot.dimensions != len(data.([][]float64)) {
            return &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.")}
        }
        curve.castedData = data.([][]float64)
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]float32:
@@ -74,11 +84,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]int:
@@ -97,11 +102,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]int8:
@@ -120,12 +120,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]int16:
@@ -144,12 +138,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]int32:
@@ -168,12 +156,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case [][]int64:
@@ -192,17 +174,10 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            }
        }
        curve.castedData = typeCasteSlice
-
-       if plot.dimensions == 2 {
-           plot.plotXY(curve)
-       } else {
-           plot.plotXYZ(curve)
-       }
        plot.PointGroup[name] = curve

    case []float64:
        curve.castedData = data.([]float64)
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []float32:
        originalSlice := data.([]float32)
@@ -211,7 +186,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []int:
        originalSlice := data.([]int)
@@ -220,7 +194,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []int8:
        originalSlice := data.([]int8)
@@ -229,7 +202,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []int16:
        originalSlice := data.([]int16)
@@ -238,7 +210,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []int32:
        originalSlice := data.([]int32)
@@ -247,7 +218,6 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    case []int64:
        originalSlice := data.([]int64)
@@ -256,14 +226,12 @@ func (plot *Plot) AddPointGroup(name string, style string, data interface{}) (er
            typeCasteSlice[i] = float64(originalSlice[i])
        }
        curve.castedData = typeCasteSlice
-       plot.plotX(curve)
        plot.PointGroup[name] = curve
    default:
-       return &gnuplotError{fmt.Sprintf("invalid number of dims ")}
-
+       return &gnuplotError{fmt.Sprintf("invalid number of dimensions")}
    }
    if discovered == 0 {
-       fmt.Printf("** style '%v' not in allowed list %v\n", style, allowed)
+       fmt.Printf("** style '%s' not supported ", style.String())
        fmt.Printf("** default to 'points'\n")
        err = &gnuplotError{fmt.Sprintf("invalid style '%s'", style)}
    }
@@ -300,7 +268,7 @@ func (plot *Plot) RemovePointGroup(name string) {
 //  plot, _ := glot.NewPlot(dimensions, persist, debug)
 //  plot.AddPointGroup("Sample1", "points", []int32{51, 8, 4, 11})
 //  plot.ResetPointGroupStyle("Sample1", "points")
-func (plot *Plot) ResetPointGroupStyle(name string, style string) (err error) {
+func (plot *Plot) ResetPointGroupStyle(name string, style PointStyle) (err error) {
    pointGroup, exists := plot.PointGroup[name]
    if !exists {
        return &gnuplotError{fmt.Sprintf("A curve with name %s does not exist.", name)}
diff --git c/pointgroup_test.go w/pointgroup_test.go
index 63f8f4c..f5a934e 100644
--- c/pointgroup_test.go
+++ w/pointgroup_test.go
@@ -1,15 +1,228 @@
 package glot

-import "testing"
+import (
+   "fmt"
+   "testing"
+)
+
+func squareInt(n int) int {
+   return n * n
+}
+func cubeInt(n int) int {
+   return n * squareInt(n)
+}
+
+func squareInt16(n int16) int16 {
+   return n * n
+}
+func cubeInt16(n int16) int16 {
+   return n * squareInt16(n)
+}

 func TestResetPointGroupStyle(t *testing.T) {
+   args := PlotArgs{
+       Debug:   false,
+       Persist: false,
+   }
    dimensions := 2
-   persist := false
-   debug := false
-   plot, _ := NewPlot(dimensions, persist, debug)
-   plot.AddPointGroup("Sample1", "points", []int32{51, 8, 4, 11})
-   err := plot.ResetPointGroupStyle("Sam", "lines")
+   plot, _ := NewPlot(dimensions, args)
+   plot.AddPointGroup("Sample1", Points, []int32{51, 8, 4, 11})
+   err := plot.ResetPointGroupStyle("Sam", Lines)
    if err == nil {
        t.Error("The specified pointgroup to be reset does not exist")
    }
 }
+
+func TestTwoPointGroups(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("TestGroup_1", Points, []float64{
+       -0.512695,
+       0.591778,
+       -0.0939544,
+       -0.510766,
+       -0.859442,
+       0.0340482,
+       0.887461,
+       0.277168,
+       -0.998753,
+       0.356656,
+   })
+   plot.AddPointGroup("TestGroup_2", Points, []float64{
+       0.712863,
+       0.975935,
+       0.875864,
+       0.737082,
+       -0.185717,
+       -0.936551,
+       0.779397,
+       0.916793,
+       0.622004,
+       -0.0860084,
+   })
+   fmt.Printf("pg: %v\n", plot.PointGroup)
+   plot.Execute()
+}
+
+func TestThreePointGroupsFloat64(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("TestGroup_1", Points, []float64{
+       -0.512695,
+       0.591778,
+       -0.0939544,
+       -0.510766,
+       -0.859442,
+       0.0340482,
+       0.887461,
+       0.277168,
+       -0.998753,
+       0.356656,
+   })
+   plot.AddPointGroup("TestGroup_2", Points, []float64{
+       0.712863,
+       0.975935,
+       0.875864,
+       0.737082,
+       -0.185717,
+       -0.936551,
+       0.779397,
+       0.916793,
+       0.622004,
+       -0.0860084,
+   })
+   plot.AddPointGroup("TestGroup_3", LinesPoints, []float64{
+       0.28927,
+       -0.945002,
+       -0.904681,
+       0.924912,
+       0.990415,
+       0.326935,
+       -0.927919,
+       0.994446,
+       0.270194,
+       -0.0378568,
+   })
+   fmt.Printf("pg: %v\n", plot.PointGroup)
+   plot.Execute()
+}
+
+func TestThreePointGroupsFloatMixed(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("TestGroup^1", Points, []float32{
+       -0.512695,
+       0.591778,
+       -0.0939544,
+       -0.510766,
+       -0.859442,
+       0.0340482,
+       0.887461,
+       0.277168,
+       -0.998753,
+       0.356656,
+   })
+   plot.AddPointGroup("TestGroup^2", Points, []float64{
+       0.712863,
+       0.975935,
+       0.875864,
+       0.737082,
+       -0.185717,
+       -0.936551,
+       0.779397,
+       0.916793,
+       0.622004,
+       -0.0860084,
+   })
+   plot.AddPointGroup("TestGroup^3", LinesPoints, []float32{
+       0.28927,
+       -0.945002,
+       -0.904681,
+       0.924912,
+       0.990415,
+       0.326935,
+       -0.927919,
+       0.994446,
+       0.270194,
+       -0.0378568,
+   })
+   fmt.Printf("%v\n", plot.PointGroup["TestGroup^1"])
+   fmt.Printf("%v\n", plot.PointGroup["TestGroup^2"])
+   fmt.Printf("%v\n", plot.PointGroup["TestGroup^3"])
+   plot.Execute()
+}
+
+func TestOnePointGroupInt8(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("TestGroup_1", Points, []int8{
+       0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+       -9, -8, -7, -6, -5, -4, -3, -2, -1, 0,
+   })
+   plot.Execute()
+}
+func TestOnePointGroupInt16(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("TestGroup_1", Points, []int16{
+       1, 2, 8, 16, 32, 64, 128, 256, 512, 1024,
+   })
+   plot.Execute()
+}
+
+func TestTwoPointGroupsInt16(t *testing.T) {
+   args := PlotArgs{
+       Debug:   true,
+       Persist: true,
+       Format:  Pdf,
+       Style:   Points,
+   }
+
+   plot, _ := NewPlot(1, args)
+   plot.AddPointGroup("PowerOfTwo^1", Points, []int16{
+       1, 2, 8, 16, 32, 64, 128, 256, 512, 1024,
+   })
+   plot.AddPointGroup("Cubed^2", Points, []int16{
+       0,
+       1,
+       cubeInt16(2),
+       cubeInt16(3),
+       cubeInt16(4),
+       cubeInt16(5),
+       cubeInt16(6),
+       cubeInt16(7),
+       cubeInt16(8),
+       cubeInt16(9),
+   })
+   plot.Execute()
+}
arafatkatze commented 6 years ago

@szaydel
This looks really amazing.
Please do send a PR and I will make some comments/suggestions and will eventually merge it.
Thanks a lot.

szaydel commented 6 years ago

@Arafatk, Let me clean-up a bit, add some tests, and implement a few things I wanted to implement and I will send a PR. Thanks for considering it!

Cheers.

szaydel commented 6 years ago

Just wanted to give an update. I am still working on a few improvements before I do a PR. I wanted to propose a model where there are distinct types representing plot styles, i.e. there's a type for histograms, a type for points, etc., and each such type encodes all necessary information, as well as allows for some degree of customization. The type is then tied to a PointGroup, and multiple PointGroups are tied to a plot area or canvas. I am thinking of this as a more modular way to structure things, and a bit more object oriented.

Basic goal is to define a Geom, a Geometric Object struct, like in this example we have a HistogramGeom which includes a style struct, and this struct satisfies a PlotStyler Interface, which generalizes configuration of the different Geoms. The goal is to have a common interface for configuring different plotting styles, which depending on the style may require using the set style command or not, etc.

// HistogramStyle describes stylistic elements that may be attributed to histogram
type HistogramStyle struct {
    Empty  bool
    Solid  bool
    Border bool
}

// HistogramGeom --
type HistogramGeom struct {
    style HistogramStyle
}
arafatkatze commented 6 years ago

@szaydel Really sorry for the late reply. This looks really good and modular and will certainly help a lot in the long term when we try to extend the library functionalities.
Please go ahead with the pull request or maybe keep it a work in progress, the very first pull request doesn't have to be directly merged but atleast I can take a look at it and make comments if needed.

szaydel commented 6 years ago

Thanks @Arafatk. I will get this stuff prepared in the next few days, or over weekend and do a PR. If you want to start a Dev branch of some sort, I can do a PR against it instead of Master.