glot icon indicating copy to clipboard operation
glot copied to clipboard

Improvement ideas

Open szaydel opened this issue 6 years ago • 5 comments

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()
+}

szaydel avatar Jan 13 '18 05:01 szaydel

@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.

arafatkatze avatar Jan 13 '18 17:01 arafatkatze

@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 avatar Jan 14 '18 02:01 szaydel

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
}

szaydel avatar Jan 27 '18 15:01 szaydel

@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.

arafatkatze avatar Feb 04 '18 10:02 arafatkatze

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.

szaydel avatar Feb 13 '18 16:02 szaydel