Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement ideas #12

Open
szaydel opened this issue Jan 13, 2018 · 5 comments
Open

Improvement ideas #12

szaydel opened this issue Jan 13, 2018 · 5 comments
Assignees

Comments

@szaydel
Copy link
Contributor

szaydel commented Jan 13, 2018

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
Copy link
Owner

@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
Copy link
Contributor Author

szaydel commented Jan 14, 2018

@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
Copy link
Contributor Author

szaydel commented Jan 27, 2018

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
Copy link
Owner

@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
Copy link
Contributor Author

szaydel commented Feb 13, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants