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

Improve writing of points data #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 82 additions & 44 deletions glot.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,101 +63,139 @@ func NewPlot(dimensions int, persist, debug bool) (*Plot, error) {
return p, nil
}

func (plot *Plot) plotX(PointGroup *PointGroup) error {
f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
func (plot *Plot) plotX(pg *PointGroup) error {
f, err := writePointsHelper(pg)
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))
}
f.Close()
cmd := plot.plotcmd
if plot.nplots > 0 {
cmd = plotCommand
}
if PointGroup.style == "" {
PointGroup.style = defaultStyle
if pg.style == "" {
pg.style = defaultStyle
}
var line string
if PointGroup.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, PointGroup.style)
if pg.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, pg.style)
} else {
line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
cmd, fname, PointGroup.name, PointGroup.style)
cmd, fname, pg.name, pg.style)
}
plot.nplots++
return plot.Cmd(line)
}

func (plot *Plot) plotXY(PointGroup *PointGroup) error {
x := PointGroup.castedData.([][]float64)[0]
y := PointGroup.castedData.([][]float64)[1]
npoints := min(len(x), len(y))

f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
func (plot *Plot) plotXY(pg *PointGroup) error {
f, err := writePointsHelper(pg)
if err != nil {
return err
}
fname := f.Name()
plot.tmpfiles[fname] = f

for i := 0; i < npoints; i++ {
f.WriteString(fmt.Sprintf("%v %v\n", x[i], y[i]))
}

f.Close()
cmd := plot.plotcmd
if plot.nplots > 0 {
cmd = plotCommand
}

if PointGroup.style == "" {
PointGroup.style = "points"
if pg.style == "" {
pg.style = "points"
}
var line string
if PointGroup.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, PointGroup.style)
if pg.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, pg.style)
} else {
line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
cmd, fname, PointGroup.name, PointGroup.style)
cmd, fname, pg.name, pg.style)
}
plot.nplots++
return plot.Cmd(line)
}

func (plot *Plot) plotXYZ(points *PointGroup) error {
x := points.castedData.([][]float64)[0]
y := points.castedData.([][]float64)[1]
z := points.castedData.([][]float64)[2]
npoints := min(len(x), len(y))
npoints = min(npoints, len(z))
f, err := ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
func (plot *Plot) plotXYZ(pg *PointGroup) error {
f, err := writePointsHelper(pg)
if err != nil {
return err
}
fname := f.Name()
plot.tmpfiles[fname] = f

for i := 0; i < npoints; i++ {
f.WriteString(fmt.Sprintf("%v %v %v\n", x[i], y[i], z[i]))
}

f.Close()
cmd := "splot" // Force 3D plot
if plot.nplots > 0 {
cmd = plotCommand
}

var line string
if points.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, points.style)
if pg.name == "" {
line = fmt.Sprintf("%s \"%s\" with %s", cmd, fname, pg.style)
} else {
line = fmt.Sprintf("%s \"%s\" title \"%s\" with %s",
cmd, fname, points.name, points.style)
cmd, fname, pg.name, pg.style)
}
plot.nplots++
return plot.Cmd(line)
}

func writePointsHelper(points *PointGroup) (f *os.File, err error) {
var npoints int // number of records to write to file
var pointString string
f, err = ioutil.TempFile(os.TempDir(), gGnuplotPrefix)
if err != nil {
return nil, err
}
defer f.Close()
switch points.dimensions {
case 1:
x := points.castedData.([][]float64)[0]
npoints = len(x)
for i := 0; i < npoints; i++ {
pointString += fmt.Sprintf("%v\n", x[i])
if i%10000 == 0 { // flush every 10,000 lines
f.WriteString(pointString)
pointString = ""
}
}
f.WriteString(pointString)
case 2:
x := points.castedData.([][]float64)[0]
y := points.castedData.([][]float64)[1]
npoints = minValue(len(x), len(y))
for i := 0; i < npoints; i++ {
pointString += 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)
case 3:
x := points.castedData.([][]float64)[0]
y := points.castedData.([][]float64)[1]
z := points.castedData.([][]float64)[2]
npoints = minValue(len(x), len(y), len(z))
for i := 0; i < npoints; i++ {
pointString += fmt.Sprintf("%v %v %v\n", x[i], y[i], z[i])
if i%10000 == 0 { // flush every 10,000 lines
f.WriteString(pointString)
pointString = ""
}
}
f.WriteString(pointString)
default:
return nil, &gnuplotError{
fmt.Sprintf("invalid number of dims '%v'", points.dimensions),
}
}
return
}

func minValue(n ...int) int {
v := n[0]
for i := 1; i < len(n); i++ {
if n[i] < v {
v = n[i] // swap in smaller value
}
}
return v
}
168 changes: 167 additions & 1 deletion glot_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package glot

import "testing"
import (
"bytes"
"crypto/sha1"
"io"
"math"
"math/rand"
"os"
"testing"
)

func TestNewPlot(t *testing.T) {
persist := false
Expand All @@ -10,3 +18,161 @@ func TestNewPlot(t *testing.T) {
t.Error("Expected error when making a 0 dimensional plot.")
}
}

func TestMinValue(t *testing.T) {
tests := []struct {
inputs []int
expected int
}{
{
[]int{2, 1, 6, -1, -5}, -5,
},
{
[]int{10, 1000, -1000, 1}, -1000,
},
{
[]int{1, 2, 3, 4, 5}, 1,
},
}
for _, test := range tests {
result := minValue(test.inputs...)
if result != test.expected {
t.Errorf("expected %d, received: %d\n", test.expected, result)

}
}
}

func TestPopulateSlice(t *testing.T) {
expected := []float64{
0.028195518620623417, 0.9118100887768767,
1.0880881171536092, 0.8212952160091804,
-0.33866344995966, 1.4866716553726071,
-0.7511760243714543, 1.8465685075494818,
0.6862981484621127, 0.21741348950326284,
}
s := make([]float64, 10)
r := rand.New(rand.NewSource(321)) // Should always expect same results
populateSlice(s, r)
for i := range s {
if !floatsEq(s[i], expected[i]) {
t.Errorf("expected: %f received: %f", expected[i], s[i])
}
}
}

// floatsEq compares two floating point values with precision defined by
// the value of ε.
func floatsEq(a, b float64) bool {
ε := 0.00000001 // Error
if math.Abs(a-b) < ε {
return true
}
return false
}

func TestPlotX1(t *testing.T) {
want := []byte{
126, 112, 122, 148, 146, 237, 178, 64, 107, 104,
12, 209, 108, 1, 136, 192, 195, 121, 229, 186,
}
p, _ := NewPlot(1, true, true)
p.debug = true
p.format = "pdf"
points := [][]float64{generatePoints(2018, 1000)}
p.AddPointGroup("testRandomNormal", "", points)
for k := range p.tmpfiles {
// Only one entry in map
if ok, err := confirmCheckSum(k, want); err != nil {
t.Errorf("%v", err)
} else if !ok {
t.Errorf("%v", "Failed to checksum generated data.")
}
}
}

func TestPlotXY1(t *testing.T) {
want := []byte{
59, 73, 90, 10, 15, 97, 254, 213, 151, 75,
236, 203, 106, 196, 63, 67, 52, 94, 111, 33,
}
p, _ := NewPlot(2, true, true)
p.debug = true
p.format = "pdf"
points := [][]float64{
generatePoints(-1, 1000),
generatePoints(1, 1000),
}
p.AddPointGroup("testRandomNormal", "", points)
for k := range p.tmpfiles {
// Only one entry in map
if ok, err := confirmCheckSum(k, want); err != nil {
t.Errorf("%v", err)
} else if !ok {
t.Errorf("%v", "Failed to checksum generated data.")
}
}
}

func TestPlotXYZ1(t *testing.T) {
want := []byte{
189, 141, 23, 248, 204, 159, 175, 78, 178, 209,
162, 43, 8, 214, 143, 161, 186, 175, 247, 178,
}
p, _ := NewPlot(3, true, true)
p.debug = true
p.format = "pdf"
points := [][]float64{
generatePoints(-1, 1000),
generatePoints(1, 1000),
generatePoints(2, 1000),
}
p.AddPointGroup("testRandomNormal", "", points)
for k := range p.tmpfiles {
// Only one entry in map
if ok, err := confirmCheckSum(k, want); err != nil {
t.Errorf("%v", err)
} else if !ok {
t.Errorf("%v", "Failed to checksum generated data.")
}
}
}

// confirmCheckSum is used for testing expected checksum of file
// that we generated during the test against actual checksum.
// If these two don't agree, something has gone wrong.
func confirmCheckSum(fn string, expected []byte) (bool, error) {
f, err := os.Open(fn)
if err != nil {
return false, err
}
defer f.Close()
h := sha1.New()
if _, err := io.Copy(h, f); err != nil {
return false, err
}
if bytes.Compare(h.Sum(nil), expected) != 0 {
return false, nil
}
return true, nil
}

func generatePoints(seed int64, length int) []float64 {
if length == 0 {
return []float64{}
}
pts := make([]float64, length)
r := rand.New(rand.NewSource(seed))
for i := 0; i < length; i++ {
pts[i] = r.NormFloat64()
}
return pts
}

func populateSlice(s []float64, rs *rand.Rand) ([]float64, *rand.Rand) {
if len(s) == 0 {
return s, rs
}
s[0] = rs.NormFloat64()
return populateSlice(s[1:], rs)
}