summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/jp/bar.go39
-rw-r--r--cmd/jp/canvas_other.go10
-rw-r--r--cmd/jp/canvas_windows.go10
-rw-r--r--cmd/jp/csv.go29
-rw-r--r--cmd/jp/flag.go6
-rw-r--r--cmd/jp/hist.go50
-rw-r--r--cmd/jp/line.go45
-rw-r--r--cmd/jp/main.go117
-rw-r--r--cmd/jp/scatter.go52
-rw-r--r--cmd/jp/split.go6
10 files changed, 293 insertions, 71 deletions
diff --git a/cmd/jp/bar.go b/cmd/jp/bar.go
index e1a622b..cfc39c2 100644
--- a/cmd/jp/bar.go
+++ b/cmd/jp/bar.go
@@ -2,37 +2,38 @@ package main
import (
"fmt"
+ "log"
"reflect"
- "github.com/sgreben/jp/pkg/jp"
- "github.com/sgreben/jp/pkg/jp/primitives"
+ "github.com/sgreben/jp/pkg/data"
+ "github.com/sgreben/jp/pkg/draw"
+ "github.com/sgreben/jp/pkg/plot"
)
-func barPlotData(xvv, yvv [][]reflect.Value) (x []string, y []float64) {
- for _, xv := range xvv {
- for i := range xv {
- if xv[i].IsValid() && xv[i].CanInterface() {
- x = append(x, fmt.Sprint(xv[i].Interface()))
- }
+func barPlotData(xv, yv []reflect.Value) (x []string, y []float64) {
+ for i := range xv {
+ if xv[i].IsValid() && xv[i].CanInterface() {
+ x = append(x, fmt.Sprint(xv[i].Interface()))
}
}
- for _, yv := range yvv {
- for i := range yv {
- if yv[i].IsValid() && yv[i].CanInterface() {
- yvi, ok := yv[i].Interface().(float64)
- if ok {
- y = append(y, yvi)
- }
+ for i := range yv {
+ if yv[i].IsValid() && yv[i].CanInterface() {
+ yvi, ok := yv[i].Interface().(float64)
+ if ok {
+ y = append(y, yvi)
}
}
}
return
}
-func barPlot(xvv, yvv [][]reflect.Value, box primitives.Box) string {
- groups, y := barPlotData(xvv, yvv)
- chart := jp.NewBarChart(box.Width, box.Height)
- data := new(jp.DataTable)
+func barPlot(xv, yv []reflect.Value, c draw.Canvas) string {
+ groups, y := barPlotData(xv, yv)
+ chart := plot.NewBarChart(c)
+ data := new(data.Table)
+ if len(y) == 0 {
+ log.Fatal("no valid y values given")
+ }
if len(groups) != len(y) {
for i := range y {
data.AddColumn(fmt.Sprint(i))
diff --git a/cmd/jp/canvas_other.go b/cmd/jp/canvas_other.go
new file mode 100644
index 0000000..053cef7
--- /dev/null
+++ b/cmd/jp/canvas_other.go
@@ -0,0 +1,10 @@
+// +build !windows
+
+package main
+
+var autoCanvas = map[string]string{
+ plotTypeBar: canvasTypeQuarter,
+ plotTypeLine: canvasTypeQuarter,
+ plotTypeScatter: canvasTypeBraille,
+ plotTypeHist: canvasTypeQuarter,
+}
diff --git a/cmd/jp/canvas_windows.go b/cmd/jp/canvas_windows.go
new file mode 100644
index 0000000..de22e5b
--- /dev/null
+++ b/cmd/jp/canvas_windows.go
@@ -0,0 +1,10 @@
+// +build windows
+
+package main
+
+var autoCanvas = map[string]string{
+ plotTypeBar: canvasTypeFull,
+ plotTypeLine: canvasTypeFull,
+ plotTypeScatter: canvasTypeFull,
+ plotTypeHist: canvasTypeFull,
+}
diff --git a/cmd/jp/csv.go b/cmd/jp/csv.go
new file mode 100644
index 0000000..08ce65c
--- /dev/null
+++ b/cmd/jp/csv.go
@@ -0,0 +1,29 @@
+package main
+
+import "strconv"
+
+func parseCell(cell string) interface{} {
+ f, err := strconv.ParseFloat(cell, 64)
+ if err == nil {
+ return f
+ }
+ b, err := strconv.ParseBool(cell)
+ if err == nil {
+ if b {
+ return 1
+ }
+ return 0
+ }
+ return cell
+}
+
+func parseRows(rows [][]string) (out [][]interface{}) {
+ out = make([][]interface{}, len(rows))
+ for i, row := range rows {
+ out[i] = make([]interface{}, len(row))
+ for j, cell := range row {
+ out[i][j] = parseCell(cell)
+ }
+ }
+ return
+}
diff --git a/cmd/jp/flag.go b/cmd/jp/flag.go
index 533b35d..eeec98c 100644
--- a/cmd/jp/flag.go
+++ b/cmd/jp/flag.go
@@ -6,8 +6,8 @@ import (
)
type enumVar struct {
- Choices []string // The acceptable choices the user may pass to the flag
- Value string // the current value of the flag
+ Choices []string
+ Value string
}
// Set implements the flag.Value interface.
@@ -18,7 +18,7 @@ func (so *enumVar) Set(v string) error {
return nil
}
}
- return fmt.Errorf("invalid choice; must be one of %s", strings.Join(so.Choices, ","))
+ return fmt.Errorf("must be one of [%s]", strings.Join(so.Choices, " "))
}
func (so *enumVar) String() string {
diff --git a/cmd/jp/hist.go b/cmd/jp/hist.go
new file mode 100644
index 0000000..77c04e6
--- /dev/null
+++ b/cmd/jp/hist.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "log"
+ "reflect"
+
+ "github.com/sgreben/jp/pkg/data"
+ "github.com/sgreben/jp/pkg/draw"
+ "github.com/sgreben/jp/pkg/plot"
+)
+
+func histogramData(xv []reflect.Value, nbins uint) (groups []string, counts []float64) {
+ var x []float64
+ for i := range xv {
+ if xv[i].IsValid() && xv[i].CanInterface() {
+ xvi, ok := xv[i].Interface().(float64)
+ if ok {
+ x = append(x, xvi)
+ }
+ }
+ }
+ if len(x) == 0 {
+ log.Fatal("no valid x values given")
+ }
+ bins := data.NewBins(x)
+ bins.Number = int(nbins)
+ if nbins == 0 {
+ bins.ChooseSturges()
+ }
+ hist := data.Histogram(x, bins)
+ groups = make([]string, len(hist))
+ counts = make([]float64, len(hist))
+ for i, b := range hist {
+ groups[i] = b.String()
+ counts[i] = float64(b.Count)
+ }
+ return
+}
+
+func histogram(xv []reflect.Value, c draw.Canvas, nbins uint) string {
+ groups, counts := histogramData(xv, nbins)
+ chart := plot.NewBarChart(c)
+ chart.BarPaddingX = 0
+ data := new(data.Table)
+ for _, g := range groups {
+ data.AddColumn(g)
+ }
+ data.AddRow(counts...)
+ return chart.Draw(data)
+}
diff --git a/cmd/jp/line.go b/cmd/jp/line.go
index b47b38c..8054f87 100644
--- a/cmd/jp/line.go
+++ b/cmd/jp/line.go
@@ -1,46 +1,48 @@
package main
import (
+ "log"
"reflect"
- "github.com/sgreben/jp/pkg/jp"
- "github.com/sgreben/jp/pkg/jp/primitives"
+ "github.com/sgreben/jp/pkg/data"
+ "github.com/sgreben/jp/pkg/draw"
+
+ "github.com/sgreben/jp/pkg/plot"
)
-func linePlotData(xvv, yvv [][]reflect.Value) (x, y []float64) {
- for _, xv := range xvv {
- for i := range xv {
- if xv[i].IsValid() && xv[i].CanInterface() {
- xvi, ok := xv[i].Interface().(float64)
- if ok {
- x = append(x, xvi)
- }
+func linePlotData(xv, yv []reflect.Value) (x, y []float64) {
+ for i := range xv {
+ if xv[i].IsValid() && xv[i].CanInterface() {
+ xvi, ok := xv[i].Interface().(float64)
+ if ok {
+ x = append(x, xvi)
}
}
}
- for _, yv := range yvv {
- for i := range yv {
- if yv[i].IsValid() && yv[i].CanInterface() {
- yvi, ok := yv[i].Interface().(float64)
- if ok {
- y = append(y, yvi)
- }
+ for i := range yv {
+ if yv[i].IsValid() && yv[i].CanInterface() {
+ yvi, ok := yv[i].Interface().(float64)
+ if ok {
+ y = append(y, yvi)
}
}
}
return
}
-func linePlot(xvv, yvv [][]reflect.Value, box primitives.Box) string {
- x, y := linePlotData(xvv, yvv)
- chart := jp.NewLineChart(box.Width, box.Height)
- data := new(jp.DataTable)
+func linePlot(xv, yv []reflect.Value, c draw.Canvas) string {
+ x, y := linePlotData(xv, yv)
+ chart := plot.NewLineChart(c)
+ data := new(data.Table)
data.AddColumn("x")
data.AddColumn("y")
n := len(x)
if len(y) > n {
n = len(y)
}
+ if len(y) == 0 {
+ log.Fatal("no valid y values given")
+ }
// If no valid xs are given, use the indices as x values.
if len(x) == 0 {
x = make([]float64, len(y))
@@ -51,6 +53,5 @@ func linePlot(xvv, yvv [][]reflect.Value, box primitives.Box) string {
for i := 0; i < n; i++ {
data.AddRow(x[i%len(x)], y[i%len(y)])
}
- chart.Symbol = "█"
return chart.Draw(data)
}
diff --git a/cmd/jp/main.go b/cmd/jp/main.go
index c603f68..173ac27 100644
--- a/cmd/jp/main.go
+++ b/cmd/jp/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "encoding/csv"
"encoding/json"
"flag"
"fmt"
@@ -8,24 +9,41 @@ import (
"os"
"reflect"
- "github.com/sgreben/jp/pkg/jp/primitives"
+ "github.com/sgreben/jp/pkg/draw"
"github.com/sgreben/jp/pkg/terminal"
"github.com/sgreben/jp/pkg/jsonpath"
)
type configuration struct {
- Box primitives.Box
- X string
- Y string
- XY string
- PlotType enumVar
+ Box draw.Box
+ X string
+ Y string
+ XY string
+ PlotType enumVar
+ CanvasType enumVar
+ InputType enumVar
+ HistBins uint
}
-const plotTypeLine = "line"
-const plotTypeBar = "bar"
-const plotTypeScatter = "scatter"
-const plotTypeHist = "hist"
+const (
+ plotTypeLine = "line"
+ plotTypeBar = "bar"
+ plotTypeScatter = "scatter"
+ plotTypeHist = "hist"
+)
+
+const (
+ canvasTypeFull = "full"
+ canvasTypeQuarter = "quarter"
+ canvasTypeBraille = "braille"
+ canvasTypeAuto = "auto"
+)
+
+const (
+ inputTypeCSV = "csv"
+ inputTypeJSON = "json"
+)
var config = configuration{
PlotType: enumVar{
@@ -33,23 +51,47 @@ var config = configuration{
Choices: []string{
plotTypeLine,
plotTypeBar,
+ plotTypeScatter,
+ plotTypeHist,
+ },
+ },
+ CanvasType: enumVar{
+ Value: canvasTypeAuto,
+ Choices: []string{
+ canvasTypeFull,
+ canvasTypeQuarter,
+ canvasTypeBraille,
+ canvasTypeAuto,
+ },
+ },
+ InputType: enumVar{
+ Value: inputTypeJSON,
+ Choices: []string{
+ inputTypeJSON,
+ inputTypeCSV,
},
},
}
-var xPattern *jsonpath.JSONPath
-var yPattern *jsonpath.JSONPath
-var xyPattern *jsonpath.JSONPath
+var (
+ xPattern *jsonpath.JSONPath
+ yPattern *jsonpath.JSONPath
+ xyPattern *jsonpath.JSONPath
+)
func init() {
flag.Var(&config.PlotType, "type", fmt.Sprintf("Plot type. One of %v", config.PlotType.Choices))
+ flag.Var(&config.CanvasType, "canvas", fmt.Sprintf("Canvas type. One of %v", config.CanvasType.Choices))
+ flag.Var(&config.InputType, "input", fmt.Sprintf("Input type. One of %v", config.InputType.Choices))
flag.StringVar(&config.X, "x", "", "x values (JSONPath expression)")
flag.StringVar(&config.Y, "y", "", "y values (JSONPath expression)")
flag.StringVar(&config.XY, "xy", "", "x,y value pairs (JSONPath expression). Overrides -x and -y if given.")
flag.IntVar(&config.Box.Width, "width", 0, "Plot width (default 0 (auto))")
flag.IntVar(&config.Box.Height, "height", 0, "Plot height (default 0 (auto))")
+ flag.UintVar(&config.HistBins, "bins", 0, "Number of histogram bins (default 0 (auto))")
flag.Parse()
log.SetOutput(os.Stderr)
+ log.SetFlags(log.LstdFlags | log.Lshortfile)
var err error
xPattern = jsonpath.New("x")
@@ -75,6 +117,9 @@ func init() {
if config.Box.Height == 0 {
config.Box.Height = terminal.Height() - 1
}
+ if config.CanvasType.Value == canvasTypeAuto {
+ config.CanvasType.Value = autoCanvas[config.PlotType.Value]
+ }
}
func match(in interface{}, p *jsonpath.JSONPath) [][]reflect.Value {
@@ -87,24 +132,48 @@ func match(in interface{}, p *jsonpath.JSONPath) [][]reflect.Value {
func main() {
var in interface{}
- dec := json.NewDecoder(os.Stdin)
- err := dec.Decode(&in)
- if err != nil {
- log.Println(err)
+ switch config.InputType.Value {
+ case inputTypeJSON:
+ dec := json.NewDecoder(os.Stdin)
+ err := dec.Decode(&in)
+ if err != nil {
+ log.Println(err)
+ }
+ case inputTypeCSV:
+ r := csv.NewReader(os.Stdin)
+ rows, err := r.ReadAll()
+ if err != nil {
+ log.Println(err)
+ }
+ in = parseRows(rows)
}
- fmt.Println()
- var x, y [][]reflect.Value
+ var x, y []reflect.Value
if xyPattern != nil {
x, y = split(match(in, xyPattern))
} else {
- x = match(in, xPattern)
- y = match(in, yPattern)
+ x = flatten(match(in, xPattern))
+ y = flatten(match(in, yPattern))
}
+ buffer := draw.NewBuffer(config.Box)
+ var p draw.Pixels
+ switch config.CanvasType.Value {
+ case canvasTypeBraille:
+ p = &draw.Braille{Buffer: buffer}
+ case canvasTypeQuarter:
+ p = &draw.Quarter{Buffer: buffer}
+ case canvasTypeFull:
+ p = &draw.Full{Buffer: buffer}
+ }
+ p.Clear()
+ c := draw.Canvas{Pixels: p}
switch config.PlotType.Value {
case plotTypeLine:
- fmt.Print(linePlot(x, y, config.Box))
+ fmt.Println(linePlot(x, y, c))
+ case plotTypeScatter:
+ fmt.Println(scatterPlot(x, y, c))
case plotTypeBar:
- fmt.Print(barPlot(x, y, config.Box))
+ fmt.Println(barPlot(x, y, c))
+ case plotTypeHist:
+ fmt.Println(histogram(x, c, config.HistBins))
}
-
}
diff --git a/cmd/jp/scatter.go b/cmd/jp/scatter.go
new file mode 100644
index 0000000..2fdf0e6
--- /dev/null
+++ b/cmd/jp/scatter.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "reflect"
+
+ "github.com/sgreben/jp/pkg/data"
+ "github.com/sgreben/jp/pkg/draw"
+ "github.com/sgreben/jp/pkg/plot"
+)
+
+func scatterPlotData(xv, yv []reflect.Value) (x, y []float64) {
+ for i := range xv {
+ if xv[i].IsValid() && xv[i].CanInterface() {
+ xvi, ok := xv[i].Interface().(float64)
+ if ok {
+ x = append(x, xvi)
+ }
+ }
+ }
+ for i := range yv {
+ if yv[i].IsValid() && yv[i].CanInterface() {
+ yvi, ok := yv[i].Interface().(float64)
+ if ok {
+ y = append(y, yvi)
+ }
+ }
+ }
+ return
+}
+
+func scatterPlot(xv, yv []reflect.Value, c draw.Canvas) string {
+ x, y := scatterPlotData(xv, yv)
+ chart := plot.NewScatterChart(c)
+ data := new(data.Table)
+ data.AddColumn("x")
+ data.AddColumn("y")
+ n := len(x)
+ if len(y) > n {
+ n = len(y)
+ }
+ // If no valid xs are given, use the indices as x values.
+ if len(x) == 0 {
+ x = make([]float64, len(y))
+ for i := 0; i < len(y); i++ {
+ x[i] = float64(i)
+ }
+ }
+ for i := 0; i < n; i++ {
+ data.AddRow(x[i%len(x)], y[i%len(y)])
+ }
+ return chart.Draw(data)
+}
diff --git a/cmd/jp/split.go b/cmd/jp/split.go
index 4f36825..efe59c9 100644
--- a/cmd/jp/split.go
+++ b/cmd/jp/split.go
@@ -11,10 +11,10 @@ func flatten(in [][]reflect.Value) (out []reflect.Value) {
return
}
-func split(in [][]reflect.Value) (x, y [][]reflect.Value) {
+func split(in [][]reflect.Value) (x, y []reflect.Value) {
flat := flatten(in)
n := len(flat)
- x = [][]reflect.Value{flat[:n/2]}
- y = [][]reflect.Value{flat[n/2:]}
+ x = flat[:n/2]
+ y = flat[n/2:]
return
}