Compare commits
18 Commits
master
...
pointers-a
Author | SHA1 | Date | |
---|---|---|---|
|
50b025e273 | ||
|
4d93883953 | ||
|
9ca1b0466a | ||
|
182c4eeed2 | ||
|
fde118dd7c | ||
|
1556c5e843 | ||
|
2f84185f46 | ||
|
c0fc2baa50 | ||
|
41d81c82db | ||
|
c47025edf6 | ||
|
1c1430e3b8 | ||
|
674e75d285 | ||
|
6a4f3936c6 | ||
|
814470733e | ||
|
472d04b3fa | ||
|
255390c710 | ||
|
376a8efa36 | ||
|
65a0895143 |
18
.gitignore
vendored
18
.gitignore
vendored
|
@ -1,19 +1 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
|
||||||
.glide/
|
|
||||||
|
|
||||||
# Other
|
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
|
||||||
coverage.html
|
|
13
.travis.yml
Normal file
13
.travis.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6.2
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u github.com/blendlabs/go-assert
|
||||||
|
- go get ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test ./...
|
9
Makefile
Normal file
9
Makefile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
all: test
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test ./...
|
||||||
|
|
||||||
|
cover:
|
||||||
|
@go test -short -covermode=set -coverprofile=profile.cov
|
||||||
|
@go tool cover -html=profile.cov
|
||||||
|
@rm profile.cov
|
99
README.md
Normal file
99
README.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
go-chart
|
||||||
|
========
|
||||||
|
[![Build Status](https://travis-ci.org/wcharczuk/go-chart.svg?branch=master)](https://travis-ci.org/wcharczuk/go-chart)[![Go Report Card](https://goreportcard.com/badge/github.com/wcharczuk/go-chart)](https://goreportcard.com/report/github.com/wcharczuk/go-chart)
|
||||||
|
|
||||||
|
Package `chart` is a very simple golang native charting library that supports timeseries and continuous
|
||||||
|
line charts.
|
||||||
|
|
||||||
|
The v1.0 release has been tagged so things should be more or less stable, if something changes please log an issue.
|
||||||
|
|
||||||
|
Master should now be on the v2.x codebase, which brings a couple new features and better handling of basics like axes labeling etc. Per usual, see `_examples` for more information.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
To install `chart` run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> go get -u github.com/wcharczuk/go-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
Most of the components are interchangeable so feel free to crib whatever you want.
|
||||||
|
|
||||||
|
# Output Examples
|
||||||
|
|
||||||
|
Spark Lines:
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/tvix_ltm.png)
|
||||||
|
|
||||||
|
Single axis:
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/goog_ltm.png)
|
||||||
|
|
||||||
|
Two axis:
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/two_axis.png)
|
||||||
|
|
||||||
|
# Other Chart Types
|
||||||
|
|
||||||
|
Pie Chart:
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/pie_chart.png)
|
||||||
|
|
||||||
|
The code for this chart can be found in `_examples/pie_chart/main.go`.
|
||||||
|
|
||||||
|
Stacked Bar:
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/stacked_bar.png)
|
||||||
|
|
||||||
|
The code for this chart can be found in `_examples/stacked_bar/main.go`.
|
||||||
|
|
||||||
|
# Code Examples
|
||||||
|
|
||||||
|
Actual chart configurations and examples can be found in the `./_examples/` directory. They are web servers, so start them with `go run main.go` then access `http://localhost:8080` to see the output.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Everything starts with the `chart.Chart` object. The bare minimum to draw a chart would be the following:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
...
|
||||||
|
"bytes"
|
||||||
|
...
|
||||||
|
"github.com/wcharczuk/go-chart" //exposes "chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
err := graph.Render(chart.PNG, buffer)
|
||||||
|
```
|
||||||
|
|
||||||
|
Explanation of the above: A `chart` can have many `Series`, a `Series` is a collection of things that need to be drawn according to the X range and the Y range(s).
|
||||||
|
|
||||||
|
Here, we have a single series with x range values as float64s, rendered to a PNG. Note; we can pass any type of `io.Writer` into `Render(...)`, meaning that we can render the chart to a file or a resonse or anything else that implements `io.Writer`.
|
||||||
|
|
||||||
|
# API Overview
|
||||||
|
|
||||||
|
Everything on the `chart.Chart` object has defaults that can be overriden. Whenever a developer sets a property on the chart object, it is to be assumed that value will be used instead of the default. One complication here
|
||||||
|
is any object's root `chart.Style` object (i.e named `Style`) and the `Show` property specifically, if any other property is set and the `Show` property is unset, it is assumed to be it's default value of `False`.
|
||||||
|
|
||||||
|
The best way to see the api in action is to look at the examples in the `./_examples/` directory.
|
||||||
|
|
||||||
|
# Design Philosophy
|
||||||
|
|
||||||
|
I wanted to make a charting library that used only native golang, that could be stood up on a server (i.e. it had built in fonts).
|
||||||
|
|
||||||
|
The goal with the API itself is to have the "zero value be useful", and to require the user to not code more than they absolutely needed.
|
||||||
|
|
||||||
|
# Contributions
|
||||||
|
|
||||||
|
This library is super early but contributions are welcome.
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -15,10 +15,14 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(), //enables / displays the x-axis
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the x-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(), //enables / displays the y-axis
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the y-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -6,23 +6,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
sbc := chart.BarChart{
|
sbc := chart.BarChart{
|
||||||
Title: "Test Bar Chart",
|
|
||||||
TitleStyle: chart.StyleShow(),
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Height: 512,
|
Height: 512,
|
||||||
BarWidth: 60,
|
BarWidth: 60,
|
||||||
XAxis: chart.StyleShow(),
|
XAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Bars: []chart.Value{
|
Bars: []chart.Value{
|
||||||
{Value: 5.25, Label: "Blue"},
|
{Value: 5.25, Label: "Blue"},
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
|
@ -1,88 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
|
||||||
profitStyle := chart.Style{
|
|
||||||
Show: true,
|
|
||||||
FillColor: drawing.ColorFromHex("13c158"),
|
|
||||||
StrokeColor: drawing.ColorFromHex("13c158"),
|
|
||||||
StrokeWidth: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
lossStyle := chart.Style{
|
|
||||||
Show: true,
|
|
||||||
FillColor: drawing.ColorFromHex("c11313"),
|
|
||||||
StrokeColor: drawing.ColorFromHex("c11313"),
|
|
||||||
StrokeWidth: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
sbc := chart.BarChart{
|
|
||||||
Title: "Bar Chart Using BaseValue",
|
|
||||||
TitleStyle: chart.StyleShow(),
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Height: 512,
|
|
||||||
BarWidth: 60,
|
|
||||||
XAxis: chart.Style{
|
|
||||||
Show: true,
|
|
||||||
},
|
|
||||||
YAxis: chart.YAxis{
|
|
||||||
Style: chart.Style{
|
|
||||||
Show: true,
|
|
||||||
},
|
|
||||||
Ticks: []chart.Tick{
|
|
||||||
{-4.0, "-4"},
|
|
||||||
{-2.0, "-2"},
|
|
||||||
{0, "0"},
|
|
||||||
{2.0, "2"},
|
|
||||||
{4.0, "4"},
|
|
||||||
{6.0, "6"},
|
|
||||||
{8.0, "8"},
|
|
||||||
{10.0, "10"},
|
|
||||||
{12.0, "12"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
UseBaseValue: true,
|
|
||||||
BaseValue: 0.0,
|
|
||||||
Bars: []chart.Value{
|
|
||||||
{Value: 10.0, Style: profitStyle, Label: "Profit"},
|
|
||||||
{Value: 12.0, Style: profitStyle, Label: "More Profit"},
|
|
||||||
{Value: 8.0, Style: profitStyle, Label: "Still Profit"},
|
|
||||||
{Value: -4.0, Style: lossStyle, Label: "Loss!"},
|
|
||||||
{Value: 3.0, Style: profitStyle, Label: "Phew Ok"},
|
|
||||||
{Value: -2.0, Style: lossStyle, Label: "Oh No!"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Header().Set("Content-Type", "image/png")
|
|
||||||
err := sbc.Render(chart.PNG, res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func port() string {
|
|
||||||
if len(os.Getenv("PORT")) > 0 {
|
|
||||||
return os.Getenv("PORT")
|
|
||||||
}
|
|
||||||
return "8080"
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
listenPort := fmt.Sprintf(":%s", port())
|
|
||||||
fmt.Printf("Listening on %s\n", listenPort)
|
|
||||||
http.HandleFunc("/", drawChart)
|
|
||||||
log.Fatal(http.ListenAndServe(listenPort, nil))
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
|
@ -4,7 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func random(min, max float64) float64 {
|
func random(min, max float64) float64 {
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Note: Additional examples on how to add Stylesheets are in the custom_stylesheets example
|
|
||||||
|
|
||||||
func inlineSVGWithClasses(res http.ResponseWriter, req *http.Request) {
|
|
||||||
res.Write([]byte(
|
|
||||||
"<!DOCTYPE html><html><head>" +
|
|
||||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/main.css\">" +
|
|
||||||
"</head>" +
|
|
||||||
"<body>"))
|
|
||||||
|
|
||||||
pie := chart.PieChart{
|
|
||||||
// Note that setting ClassName will cause all other inline styles to be dropped!
|
|
||||||
Background: chart.Style{ClassName: "background"},
|
|
||||||
Canvas: chart.Style{
|
|
||||||
ClassName: "canvas",
|
|
||||||
},
|
|
||||||
Width: 512,
|
|
||||||
Height: 512,
|
|
||||||
Values: []chart.Value{
|
|
||||||
{Value: 5, Label: "Blue", Style: chart.Style{ClassName: "blue"}},
|
|
||||||
{Value: 5, Label: "Green", Style: chart.Style{ClassName: "green"}},
|
|
||||||
{Value: 4, Label: "Gray", Style: chart.Style{ClassName: "gray"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pie.Render(chart.SVG, res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
res.Write([]byte("</body>"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func css(res http.ResponseWriter, req *http.Request) {
|
|
||||||
res.Header().Set("Content-Type", "text/css")
|
|
||||||
res.Write([]byte("svg .background { fill: white; }" +
|
|
||||||
"svg .canvas { fill: white; }" +
|
|
||||||
"svg path.blue { fill: blue; stroke: lightblue; }" +
|
|
||||||
"svg path.green { fill: green; stroke: lightgreen; }" +
|
|
||||||
"svg path.gray { fill: gray; stroke: lightgray; }" +
|
|
||||||
"svg text.blue { fill: white; }" +
|
|
||||||
"svg text.green { fill: white; }" +
|
|
||||||
"svg text.gray { fill: white; }"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", inlineSVGWithClasses)
|
|
||||||
http.HandleFunc("/main.css", css)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -16,7 +16,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: func(v interface{}) string {
|
||||||
if vf, isFloat := v.(float64); isFloat {
|
if vf, isFloat := v.(float64); isFloat {
|
||||||
return fmt.Sprintf("%0.6f", vf)
|
return fmt.Sprintf("%0.6f", vf)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -20,10 +20,14 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
FillColor: drawing.ColorFromHex("efefef"),
|
FillColor: drawing.ColorFromHex("efefef"),
|
||||||
},
|
},
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
|
@ -43,10 +47,14 @@ func drawChartDefault(res http.ResponseWriter, req *http.Request) {
|
||||||
FillColor: drawing.ColorFromHex("efefef"),
|
FillColor: drawing.ColorFromHex("efefef"),
|
||||||
},
|
},
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -14,7 +14,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 10.0,
|
Max: 10.0,
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512">\n<style type="text/css"><![CDATA[svg .background { fill: white; }svg .canvas { fill: white; }svg path.blue { fill: blue; stroke: lightblue; }svg path.green { fill: green; stroke: lightgreen; }svg path.gray { fill: gray; stroke: lightgray; }svg text.blue { fill: white; }svg text.green { fill: white; }svg text.gray { fill: white; }]]></style><path d="M 0 0
|
|
||||||
L 512 0
|
|
||||||
L 512 512
|
|
||||||
L 0 512
|
|
||||||
L 0 0" class="background"/><path d="M 5 5
|
|
||||||
L 507 5
|
|
||||||
L 507 507
|
|
||||||
L 5 507
|
|
||||||
L 5 5" class="canvas"/><path d="M 256 256
|
|
||||||
L 507 256
|
|
||||||
A 251 251 128.56 0 1 100 452
|
|
||||||
L 256 256
|
|
||||||
Z" class="blue"/><path d="M 256 256
|
|
||||||
L 100 452
|
|
||||||
A 251 251 128.56 0 1 201 12
|
|
||||||
L 256 256
|
|
||||||
Z" class="green"/><path d="M 256 256
|
|
||||||
L 201 12
|
|
||||||
A 251 251 102.85 0 1 506 256
|
|
||||||
L 256 256
|
|
||||||
Z" class="gray"/><text x="313" y="413" class="blue">Blue</text><text x="73" y="226" class="green">Green</text><text x="344" y="133" class="gray">Gray</text></svg>
|
|
Before Width: | Height: | Size: 987 B |
|
@ -1,87 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/hashworks/go-chart"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const style = "svg .background { fill: white; }" +
|
|
||||||
"svg .canvas { fill: white; }" +
|
|
||||||
"svg path.blue { fill: blue; stroke: lightblue; }" +
|
|
||||||
"svg path.green { fill: green; stroke: lightgreen; }" +
|
|
||||||
"svg path.gray { fill: gray; stroke: lightgray; }" +
|
|
||||||
"svg text.blue { fill: white; }" +
|
|
||||||
"svg text.green { fill: white; }" +
|
|
||||||
"svg text.gray { fill: white; }"
|
|
||||||
|
|
||||||
func svgWithCustomInlineCSS(res http.ResponseWriter, _ *http.Request) {
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
|
||||||
|
|
||||||
// Render the CSS with custom css
|
|
||||||
err := pieChart().Render(chart.SVGWithCSS(style, ""), res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func svgWithCustomInlineCSSNonce(res http.ResponseWriter, _ *http.Request) {
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
|
|
||||||
// This should be randomly generated on every request!
|
|
||||||
const nonce = "RAND0MBASE64"
|
|
||||||
|
|
||||||
res.Header().Set("Content-Security-Policy", fmt.Sprintf("style-src 'nonce-%s'", nonce))
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
|
||||||
|
|
||||||
// Render the CSS with custom css and a nonce.
|
|
||||||
// Try changing the nonce to a different string - your browser should block the CSS.
|
|
||||||
err := pieChart().Render(chart.SVGWithCSS(style, nonce), res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func svgWithCustomExternalCSS(res http.ResponseWriter, _ *http.Request) {
|
|
||||||
// Add external CSS
|
|
||||||
res.Write([]byte(
|
|
||||||
`<?xml version="1.0" standalone="no"?>`+
|
|
||||||
`<?xml-stylesheet href="/main.css" type="text/css"?>`+
|
|
||||||
`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">`))
|
|
||||||
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
|
||||||
err := pieChart().Render(chart.SVG, res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pieChart() chart.PieChart {
|
|
||||||
return chart.PieChart{
|
|
||||||
// Note that setting ClassName will cause all other inline styles to be dropped!
|
|
||||||
Background: chart.Style{ClassName: "background"},
|
|
||||||
Canvas: chart.Style{
|
|
||||||
ClassName: "canvas",
|
|
||||||
},
|
|
||||||
Width: 512,
|
|
||||||
Height: 512,
|
|
||||||
Values: []chart.Value{
|
|
||||||
{Value: 5, Label: "Blue", Style: chart.Style{ClassName: "blue"}},
|
|
||||||
{Value: 5, Label: "Green", Style: chart.Style{ClassName: "green"}},
|
|
||||||
{Value: 4, Label: "Gray", Style: chart.Style{ClassName: "gray"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func css(res http.ResponseWriter, req *http.Request) {
|
|
||||||
res.Header().Set("Content-Type", "text/css")
|
|
||||||
res.Write([]byte(style))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", svgWithCustomInlineCSS)
|
|
||||||
http.HandleFunc("/nonce", svgWithCustomInlineCSSNonce)
|
|
||||||
http.HandleFunc("/external", svgWithCustomExternalCSS)
|
|
||||||
http.HandleFunc("/main.css", css)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -14,7 +14,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 4.0,
|
Max: 4.0,
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -20,13 +20,17 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
Height: 500,
|
Height: 500,
|
||||||
Width: 500,
|
Width: 500,
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
/*Range: &chart.ContinuousRange{
|
/*Range: &chart.ContinuousRange{
|
||||||
Descending: true,
|
Descending: true,
|
||||||
},*/
|
},*/
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Descending: true,
|
Descending: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -16,10 +16,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
},
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.Box{
|
Padding: chart.Box{
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -16,10 +16,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
},
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.Box{
|
Padding: chart.Box{
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -17,7 +17,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: seq.RandomValuesWithMax(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we create a LinearRegressionSeries series by assignin the inner series.
|
// note we create a LinearRegressionSeries series by assignin the inner series.
|
||||||
|
|
46
_examples/market_hours/main.go
Normal file
46
_examples/market_hours/main.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
start := util.Date.Date(2016, 7, 01, util.Date.Eastern())
|
||||||
|
end := util.Date.Date(2016, 07, 21, util.Date.Eastern())
|
||||||
|
xv := seq.Time.MarketHours(start, end, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||||
|
yv := seq.New(seq.NewRandom().WithLen(len(xv)).WithAverage(200).WithScale(10)).Array()
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
|
Range: &chart.MarketHoursRange{
|
||||||
|
MarketOpen: util.NYSEOpen(),
|
||||||
|
MarketClose: util.NYSEClose(),
|
||||||
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.TimeSeries{
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
BIN
_examples/market_hours/output.png
Normal file
BIN
_examples/market_hours/output.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
|
@ -3,15 +3,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: seq.New(seq.NewRandom().WithLen(100).WithMax(150).WithMin(50)).Array(),
|
YValues: seq.New(seq.NewRandom().WithLen(100).WithAverage(100).WithScale(50)).Array(),
|
||||||
}
|
}
|
||||||
|
|
||||||
minSeries := &chart.MinSeries{
|
minSeries := &chart.MinSeries{
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -30,26 +30,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawChartRegression(res http.ResponseWriter, req *http.Request) {
|
|
||||||
pie := chart.PieChart{
|
|
||||||
Width: 512,
|
|
||||||
Height: 512,
|
|
||||||
Values: []chart.Value{
|
|
||||||
{Value: 5, Label: "Blue"},
|
|
||||||
{Value: 2, Label: "Two"},
|
|
||||||
{Value: 1, Label: "One"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
|
||||||
err := pie.Render(chart.SVG, res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", drawChart)
|
http.HandleFunc("/", drawChart)
|
||||||
http.HandleFunc("/reg", drawChartRegression)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512">
|
|
||||||
<path d="M 256 256
|
|
||||||
L 507 256
|
|
||||||
A 251 251 225.00 1 1 79 79
|
|
||||||
L 256 256
|
|
||||||
Z" style="stroke-width:5;stroke:rgba(255,255,255,1.0);fill:rgba(106,195,203,1.0)"/>
|
|
||||||
<path d="M 256 256
|
|
||||||
L 79 79
|
|
||||||
A 251 251 90.00 0 1 433 79
|
|
||||||
L 256 256
|
|
||||||
Z" style="stroke-width:5;stroke:rgba(255,255,255,1.0);fill:rgba(42,190,137,1.0)"/>
|
|
||||||
<path d="M 256 256
|
|
||||||
L 433 79
|
|
||||||
A 251 251 45.00 0 1 507 256
|
|
||||||
L 256 256
|
|
||||||
Z" style="stroke-width:5;stroke:rgba(255,255,255,1.0);fill:rgba(110,128,139,1.0)"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 565 B |
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -17,7 +17,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: seq.RandomValuesWithMax(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
polyRegSeries := &chart.PolynomialRegressionSeries{
|
polyRegSeries := &chart.PolynomialRegressionSeries{
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseInt(str string) int {
|
func parseInt(str string) int {
|
||||||
|
@ -105,7 +105,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
ValueFormatter: chart.TimeHourValueFormatter,
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
GridMajorStyle: chart.Style{
|
GridMajorStyle: chart.Style{
|
||||||
Show: true,
|
Show: true,
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/util"
|
|
||||||
|
|
||||||
chart "git.fireandbrimst.one/aw/go-chart"
|
|
||||||
)
|
|
||||||
|
|
||||||
var lock sync.Mutex
|
|
||||||
var graph *chart.Chart
|
|
||||||
var ts *chart.TimeSeries
|
|
||||||
|
|
||||||
func addData(t time.Time, e time.Duration) {
|
|
||||||
lock.Lock()
|
|
||||||
ts.XValues = append(ts.XValues, t)
|
|
||||||
ts.YValues = append(ts.YValues, util.Time.Millis(e))
|
|
||||||
lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
|
||||||
start := time.Now()
|
|
||||||
defer func() {
|
|
||||||
addData(start, time.Since(start))
|
|
||||||
}()
|
|
||||||
if len(ts.XValues) == 0 {
|
|
||||||
http.Error(res, "no data (yet)", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.Header().Set("Content-Type", "image/png")
|
|
||||||
if err := graph.Render(chart.PNG, res); err != nil {
|
|
||||||
log.Printf("%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ts = &chart.TimeSeries{
|
|
||||||
XValues: []time.Time{},
|
|
||||||
YValues: []float64{},
|
|
||||||
}
|
|
||||||
graph = &chart.Chart{
|
|
||||||
Series: []chart.Series{ts},
|
|
||||||
}
|
|
||||||
http.HandleFunc("/", drawChart)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -44,10 +44,10 @@ func unit(res http.ResponseWriter, req *http.Request) {
|
||||||
Height: 50,
|
Height: 50,
|
||||||
Width: 50,
|
Width: 50,
|
||||||
Canvas: chart.Style{
|
Canvas: chart.Style{
|
||||||
Padding: chart.BoxZero,
|
Padding: chart.Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.BoxZero,
|
Padding: chart.Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -5,21 +5,18 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
sbc := chart.StackedBarChart{
|
sbc := chart.StackedBarChart{
|
||||||
Title: "Test Stacked Bar Chart",
|
|
||||||
TitleStyle: chart.StyleShow(),
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Height: 512,
|
Height: 512,
|
||||||
XAxis: chart.StyleShow(),
|
XAxis: chart.Style{
|
||||||
YAxis: chart.StyleShow(),
|
Show: true,
|
||||||
|
},
|
||||||
|
YAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Bars: []chart.StackedBar{
|
Bars: []chart.StackedBar{
|
||||||
{
|
{
|
||||||
Name: "This is a very long string to test word break wrapping.",
|
Name: "This is a very long string to test word break wrapping.",
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -43,11 +43,11 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{Show: true},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Max: 220.0,
|
Max: 220.0,
|
||||||
Min: 180.0,
|
Min: 180.0,
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -4,17 +4,19 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
This is an example of using the `TimeSeries` to automatically coerce time.Time values into a continuous xrange.
|
This is an example of using the `TimeSeries` to automatically coerce time.Time values into a continuous xrange.
|
||||||
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropriate formatter to use for the ticks.
|
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropariate formatter to use for the ticks.
|
||||||
*/
|
*/
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.TimeSeries{
|
chart.TimeSeries{
|
||||||
|
@ -46,7 +48,9 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
|
||||||
*/
|
*/
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
ValueFormatter: chart.TimeHourValueFormatter,
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
|
@ -75,7 +79,7 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", drawChart)
|
http.HandleFunc("/", drawChart)
|
||||||
http.HandleFunc("/favicon.ico", func(res http.ResponseWriter, req *http.Request) {
|
http.HandleFunc("/favico.ico", func(res http.ResponseWriter, req *http.Request) {
|
||||||
res.Write([]byte{})
|
res.Write([]byte{})
|
||||||
})
|
})
|
||||||
http.HandleFunc("/custom", drawCustomChart)
|
http.HandleFunc("/custom", drawCustomChart)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -18,7 +18,9 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Style: chart.StyleShow(), //enables / displays the x-axis
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the x-axis
|
||||||
|
},
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: func(v interface{}) string {
|
||||||
typed := v.(float64)
|
typed := v.(float64)
|
||||||
|
@ -27,10 +29,14 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Style: chart.StyleShow(), //enables / displays the y-axis
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the y-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
YAxisSecondary: chart.YAxis{
|
YAxisSecondary: chart.YAxis{
|
||||||
Style: chart.StyleShow(), //enables / displays the secondary y-axis
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the secondary y-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -15,7 +15,9 @@ func main() {
|
||||||
|
|
||||||
ts1 := chart.ContinuousSeries{ //TimeSeries{
|
ts1 := chart.ContinuousSeries{ //TimeSeries{
|
||||||
Name: "Time Series",
|
Name: "Time Series",
|
||||||
Style: chart.StyleShow(),
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
||||||
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*AnnotationSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnnotationSeries is a series of labels on the chart.
|
// AnnotationSeries is a series of labels on the chart.
|
||||||
|
|
119
annotation_series_test.go
Normal file
119
annotation_series_test.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
as := AnnotationSeries{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
Annotations: []Value2{
|
||||||
|
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
||||||
|
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
||||||
|
{XValue: 3.0, YValue: 3.0, Label: "3.0"},
|
||||||
|
{XValue: 4.0, YValue: 4.0, Label: "4.0"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := PNG(110, 110)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
f, err := GetDefaultFont()
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
xrange := &ContinuousRange{
|
||||||
|
Min: 1.0,
|
||||||
|
Max: 4.0,
|
||||||
|
Domain: 100,
|
||||||
|
}
|
||||||
|
yrange := &ContinuousRange{
|
||||||
|
Min: 1.0,
|
||||||
|
Max: 4.0,
|
||||||
|
Domain: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := Box{
|
||||||
|
Top: 5,
|
||||||
|
Left: 5,
|
||||||
|
Right: 105,
|
||||||
|
Bottom: 105,
|
||||||
|
}
|
||||||
|
sd := Style{
|
||||||
|
FontSize: 10.0,
|
||||||
|
Font: f,
|
||||||
|
}
|
||||||
|
|
||||||
|
box := as.Measure(r, cb, xrange, yrange, sd)
|
||||||
|
assert.False(box.IsZero())
|
||||||
|
assert.Equal(-5.0, box.Top)
|
||||||
|
assert.Equal(5.0, box.Left)
|
||||||
|
assert.Equal(146.0, box.Right) //the top,left annotation sticks up 5px and out ~44px.
|
||||||
|
assert.Equal(115.0, box.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
as := AnnotationSeries{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
FillColor: drawing.ColorWhite,
|
||||||
|
StrokeColor: drawing.ColorBlack,
|
||||||
|
},
|
||||||
|
Annotations: []Value2{
|
||||||
|
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
||||||
|
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
||||||
|
{XValue: 3.0, YValue: 3.0, Label: "3.0"},
|
||||||
|
{XValue: 4.0, YValue: 4.0, Label: "4.0"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := PNG(110, 110)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
f, err := GetDefaultFont()
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
xrange := &ContinuousRange{
|
||||||
|
Min: 1.0,
|
||||||
|
Max: 4.0,
|
||||||
|
Domain: 100,
|
||||||
|
}
|
||||||
|
yrange := &ContinuousRange{
|
||||||
|
Min: 1.0,
|
||||||
|
Max: 4.0,
|
||||||
|
Domain: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := Box{
|
||||||
|
Top: 5,
|
||||||
|
Left: 5,
|
||||||
|
Right: 105,
|
||||||
|
Bottom: 105,
|
||||||
|
}
|
||||||
|
sd := Style{
|
||||||
|
FontSize: 10.0,
|
||||||
|
Font: f,
|
||||||
|
}
|
||||||
|
|
||||||
|
as.Render(r, cb, xrange, yrange, sd)
|
||||||
|
|
||||||
|
rr, isRaster := r.(*rasterRenderer)
|
||||||
|
assert.True(isRaster)
|
||||||
|
assert.NotNil(rr)
|
||||||
|
|
||||||
|
c := rr.i.At(38, 70)
|
||||||
|
converted, isRGBA := color.RGBAModel.Convert(c).(color.RGBA)
|
||||||
|
assert.True(isRGBA)
|
||||||
|
assert.Equal(0, converted.R)
|
||||||
|
assert.Equal(0, converted.G)
|
||||||
|
assert.Equal(0, converted.B)
|
||||||
|
}
|
53
bar_chart.go
53
bar_chart.go
|
@ -6,8 +6,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BarChart is a chart that draws bars on a range.
|
// BarChart is a chart that draws bars on a range.
|
||||||
|
@ -31,9 +31,6 @@ type BarChart struct {
|
||||||
|
|
||||||
BarSpacing int
|
BarSpacing int
|
||||||
|
|
||||||
UseBaseValue bool
|
|
||||||
BaseValue float64
|
|
||||||
|
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
defaultFont *truetype.Font
|
defaultFont *truetype.Font
|
||||||
|
|
||||||
|
@ -129,7 +126,7 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
||||||
yr = bc.setRangeDomains(canvasBox, yr)
|
yr = bc.setRangeDomains(canvasBox, yr)
|
||||||
}
|
}
|
||||||
bc.drawCanvas(r, canvasBox)
|
|
||||||
bc.drawBars(r, canvasBox, yr)
|
bc.drawBars(r, canvasBox, yr)
|
||||||
bc.drawXAxis(r, canvasBox)
|
bc.drawXAxis(r, canvasBox)
|
||||||
bc.drawYAxis(r, canvasBox, yr, yt)
|
bc.drawYAxis(r, canvasBox, yr, yt)
|
||||||
|
@ -142,10 +139,6 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
return r.Save(w)
|
return r.Save(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) {
|
|
||||||
Draw.Box(r, canvasBox, bc.getCanvasStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) getRanges() Range {
|
func (bc BarChart) getRanges() Range {
|
||||||
var yrange Range
|
var yrange Range
|
||||||
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
||||||
|
@ -202,21 +195,12 @@ func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||||
|
|
||||||
by = canvasBox.Bottom - yr.Translate(bar.Value)
|
by = canvasBox.Bottom - yr.Translate(bar.Value)
|
||||||
|
|
||||||
if bc.UseBaseValue {
|
|
||||||
barBox = Box{
|
|
||||||
Top: by,
|
|
||||||
Left: bxl,
|
|
||||||
Right: bxr,
|
|
||||||
Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
barBox = Box{
|
barBox = Box{
|
||||||
Top: by,
|
Top: by,
|
||||||
Left: bxl,
|
Left: bxl,
|
||||||
Right: bxr,
|
Right: bxr,
|
||||||
Bottom: canvasBox.Bottom,
|
Bottom: canvasBox.Bottom,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
||||||
|
|
||||||
|
@ -296,32 +280,7 @@ func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick)
|
||||||
|
|
||||||
func (bc BarChart) drawTitle(r Renderer) {
|
func (bc BarChart) drawTitle(r Renderer) {
|
||||||
if len(bc.Title) > 0 && bc.TitleStyle.Show {
|
if len(bc.Title) > 0 && bc.TitleStyle.Show {
|
||||||
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
Draw.TextWithin(r, bc.Title, bc.box(), bc.styleDefaultsTitle())
|
||||||
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
|
||||||
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
|
||||||
r.SetFontSize(titleFontSize)
|
|
||||||
|
|
||||||
textBox := r.MeasureText(bc.Title)
|
|
||||||
|
|
||||||
textWidth := textBox.Width()
|
|
||||||
textHeight := textBox.Height()
|
|
||||||
|
|
||||||
titleX := (bc.GetWidth() >> 1) - (textWidth >> 1)
|
|
||||||
titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
|
||||||
|
|
||||||
r.Text(bc.Title, titleX, titleY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) getCanvasStyle() Style {
|
|
||||||
return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) styleDefaultsCanvas() Style {
|
|
||||||
return Style{
|
|
||||||
FillColor: bc.GetColorPalette().CanvasColor(),
|
|
||||||
StrokeColor: bc.GetColorPalette().CanvasStrokeColor(),
|
|
||||||
StrokeWidth: DefaultCanvasStrokeWidth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,8 +397,8 @@ func (bc BarChart) box() Box {
|
||||||
dpb := bc.Background.Padding.GetBottom(50)
|
dpb := bc.Background.Padding.GetBottom(50)
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
Top: bc.Background.Padding.GetTop(20),
|
Top: 20,
|
||||||
Left: bc.Background.Padding.GetLeft(20),
|
Left: 20,
|
||||||
Right: bc.GetWidth() - dpr,
|
Right: bc.GetWidth() - dpr,
|
||||||
Bottom: bc.GetHeight() - dpb,
|
Bottom: bc.GetHeight() - dpb,
|
||||||
}
|
}
|
||||||
|
|
320
bar_chart_test.go
Normal file
320
bar_chart_test.go
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBarChartRender(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Width: 1024,
|
||||||
|
Title: "Test Title",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
|
XAxis: StyleShow(),
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0, Label: "One"},
|
||||||
|
{Value: 2.0, Label: "Two"},
|
||||||
|
{Value: 3.0, Label: "Three"},
|
||||||
|
{Value: 4.0, Label: "Four"},
|
||||||
|
{Value: 5.0, Label: "Five"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := bc.Render(PNG, buf)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.NotZero(buf.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartRenderZero(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Width: 1024,
|
||||||
|
Title: "Test Title",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
|
XAxis: StyleShow(),
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 0.0, Label: "One"},
|
||||||
|
{Value: 0.0, Label: "Two"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := bc.Render(PNG, buf)
|
||||||
|
assert.NotNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartProps(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
|
||||||
|
assert.Equal(DefaultDPI, bc.GetDPI())
|
||||||
|
bc.DPI = 100
|
||||||
|
assert.Equal(100, bc.GetDPI())
|
||||||
|
|
||||||
|
assert.Nil(bc.GetFont())
|
||||||
|
f, err := GetDefaultFont()
|
||||||
|
assert.Nil(err)
|
||||||
|
bc.Font = f
|
||||||
|
assert.NotNil(bc.GetFont())
|
||||||
|
|
||||||
|
assert.Equal(DefaultChartWidth, bc.GetWidth())
|
||||||
|
bc.Width = DefaultChartWidth - 1
|
||||||
|
assert.Equal(DefaultChartWidth-1, bc.GetWidth())
|
||||||
|
|
||||||
|
assert.Equal(DefaultChartHeight, bc.GetHeight())
|
||||||
|
bc.Height = DefaultChartHeight - 1
|
||||||
|
assert.Equal(DefaultChartHeight-1, bc.GetHeight())
|
||||||
|
|
||||||
|
assert.Equal(DefaultBarSpacing, bc.GetBarSpacing())
|
||||||
|
bc.BarSpacing = 150
|
||||||
|
assert.Equal(150, bc.GetBarSpacing())
|
||||||
|
|
||||||
|
assert.Equal(DefaultBarWidth, bc.GetBarWidth())
|
||||||
|
bc.BarWidth = 75
|
||||||
|
assert.Equal(75, bc.GetBarWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartRenderNoBars(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
err := bc.Render(PNG, bytes.NewBuffer([]byte{}))
|
||||||
|
assert.NotNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
|
||||||
|
yr := bc.getRanges()
|
||||||
|
assert.NotNil(yr)
|
||||||
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
|
assert.Equal(-math.MaxFloat64, yr.GetMax())
|
||||||
|
assert.Equal(math.MaxFloat64, yr.GetMin())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetRangesBarsMinMax(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0},
|
||||||
|
{Value: 10.0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
yr := bc.getRanges()
|
||||||
|
assert.NotNil(yr)
|
||||||
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
|
assert.Equal(10, yr.GetMax())
|
||||||
|
assert.Equal(1, yr.GetMin())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetRangesMinMax(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: 5.0,
|
||||||
|
Max: 15.0,
|
||||||
|
},
|
||||||
|
Ticks: []Tick{
|
||||||
|
{Value: 7.0, Label: "Foo"},
|
||||||
|
{Value: 11.0, Label: "Foo2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0},
|
||||||
|
{Value: 10.0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
yr := bc.getRanges()
|
||||||
|
assert.NotNil(yr)
|
||||||
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
|
assert.Equal(15, yr.GetMax())
|
||||||
|
assert.Equal(5, yr.GetMin())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetRangesTicksMinMax(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Ticks: []Tick{
|
||||||
|
{Value: 7.0, Label: "Foo"},
|
||||||
|
{Value: 11.0, Label: "Foo2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0},
|
||||||
|
{Value: 10.0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
yr := bc.getRanges()
|
||||||
|
assert.NotNil(yr)
|
||||||
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
|
assert.Equal(11, yr.GetMax())
|
||||||
|
assert.Equal(7, yr.GetMin())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartHasAxes(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
assert.False(bc.hasAxes())
|
||||||
|
bc.YAxis = YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(bc.hasAxes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
b := bc.getDefaultCanvasBox()
|
||||||
|
assert.False(b.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartSetRangeDomains(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
cb := bc.box()
|
||||||
|
yr := bc.getRanges()
|
||||||
|
yr2 := bc.setRangeDomains(cb, yr)
|
||||||
|
assert.NotZero(yr2.GetDomain())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetValueFormatters(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{}
|
||||||
|
vf := bc.getValueFormatters()
|
||||||
|
assert.NotNil(vf)
|
||||||
|
assert.Equal("1234.00", vf(1234.0))
|
||||||
|
|
||||||
|
bc.YAxis.ValueFormatter = func(_ interface{}) string { return "test" }
|
||||||
|
assert.Equal("test", bc.getValueFormatters()(1234))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartGetAxesTicks(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0},
|
||||||
|
{Value: 2.0},
|
||||||
|
{Value: 3.0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := PNG(128, 128)
|
||||||
|
assert.Nil(err)
|
||||||
|
yr := bc.getRanges()
|
||||||
|
yf := bc.getValueFormatters()
|
||||||
|
|
||||||
|
ticks := bc.getAxesTicks(r, yr, yf)
|
||||||
|
assert.Empty(ticks)
|
||||||
|
|
||||||
|
bc.YAxis.Style.Show = true
|
||||||
|
ticks = bc.getAxesTicks(r, yr, yf)
|
||||||
|
assert.Len(ticks, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartCalculateEffectiveBarSpacing(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Width: 1024,
|
||||||
|
BarWidth: 10,
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0, Label: "One"},
|
||||||
|
{Value: 2.0, Label: "Two"},
|
||||||
|
{Value: 3.0, Label: "Three"},
|
||||||
|
{Value: 4.0, Label: "Four"},
|
||||||
|
{Value: 5.0, Label: "Five"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
|
assert.NotZero(spacing)
|
||||||
|
|
||||||
|
bc.BarWidth = 250
|
||||||
|
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
|
assert.Zero(spacing)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChartCalculateEffectiveBarWidth(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BarChart{
|
||||||
|
Width: 1024,
|
||||||
|
BarWidth: 10,
|
||||||
|
Bars: []Value{
|
||||||
|
{Value: 1.0, Label: "One"},
|
||||||
|
{Value: 2.0, Label: "Two"},
|
||||||
|
{Value: 3.0, Label: "Three"},
|
||||||
|
{Value: 4.0, Label: "Four"},
|
||||||
|
{Value: 5.0, Label: "Five"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := bc.box()
|
||||||
|
|
||||||
|
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
|
assert.NotZero(spacing)
|
||||||
|
|
||||||
|
barWidth := bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
||||||
|
assert.Equal(10, barWidth)
|
||||||
|
|
||||||
|
bc.BarWidth = 250
|
||||||
|
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
|
assert.Zero(spacing)
|
||||||
|
barWidth = bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
||||||
|
assert.Equal(199, barWidth)
|
||||||
|
|
||||||
|
assert.Equal(cb.Width()+1, bc.calculateTotalBarWidth(barWidth, spacing))
|
||||||
|
|
||||||
|
bw, bs, total := bc.calculateScaledTotalWidth(cb)
|
||||||
|
assert.Equal(spacing, bs)
|
||||||
|
assert.Equal(barWidth, bw)
|
||||||
|
assert.Equal(cb.Width()+1, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarChatGetTitleFontSize(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
size := BarChart{Width: 2049, Height: 2049}.getTitleFontSize()
|
||||||
|
assert.Equal(48, size)
|
||||||
|
size = BarChart{Width: 1025, Height: 1025}.getTitleFontSize()
|
||||||
|
assert.Equal(24, size)
|
||||||
|
size = BarChart{Width: 513, Height: 513}.getTitleFontSize()
|
||||||
|
assert.Equal(18, size)
|
||||||
|
size = BarChart{Width: 257, Height: 257}.getTitleFontSize()
|
||||||
|
assert.Equal(12, size)
|
||||||
|
size = BarChart{Width: 128, Height: 128}.getTitleFontSize()
|
||||||
|
assert.Equal(10, size)
|
||||||
|
}
|
|
@ -3,12 +3,7 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*BollingerBandsSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||||
|
|
53
bollinger_band_series_test.go
Normal file
53
bollinger_band_series_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBollingerBandSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
s1 := mockValuesProvider{
|
||||||
|
X: seq.Range(1.0, 100.0),
|
||||||
|
Y: seq.RandomValuesWithMax(100, 1024),
|
||||||
|
}
|
||||||
|
|
||||||
|
bbs := &BollingerBandsSeries{
|
||||||
|
InnerSeries: s1,
|
||||||
|
}
|
||||||
|
|
||||||
|
xvalues := make([]float64, 100)
|
||||||
|
y1values := make([]float64, 100)
|
||||||
|
y2values := make([]float64, 100)
|
||||||
|
|
||||||
|
for x := 0; x < 100; x++ {
|
||||||
|
xvalues[x], y1values[x], y2values[x] = bbs.GetBoundedValues(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := bbs.GetPeriod(); x < 100; x++ {
|
||||||
|
assert.True(y1values[x] > y2values[x], fmt.Sprintf("%v vs. %v", y1values[x], y2values[x]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBollingerBandLastValue(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
s1 := mockValuesProvider{
|
||||||
|
X: seq.Range(1.0, 100.0),
|
||||||
|
Y: seq.Range(1.0, 100.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
bbs := &BollingerBandsSeries{
|
||||||
|
InnerSeries: s1,
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y1, y2 := bbs.GetBoundedLastValues()
|
||||||
|
assert.Equal(100.0, x)
|
||||||
|
assert.Equal(101, math.Floor(y1))
|
||||||
|
assert.Equal(83, math.Floor(y2))
|
||||||
|
}
|
2
box.go
2
box.go
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
188
box_test.go
Normal file
188
box_test.go
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoxClone(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
b := a.Clone()
|
||||||
|
assert.True(a.Equals(b))
|
||||||
|
assert.True(b.Equals(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxEquals(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 30, Bottom: 30}
|
||||||
|
c := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
assert.True(a.Equals(a))
|
||||||
|
assert.True(a.Equals(c))
|
||||||
|
assert.True(c.Equals(a))
|
||||||
|
assert.False(a.Equals(b))
|
||||||
|
assert.False(c.Equals(b))
|
||||||
|
assert.False(b.Equals(a))
|
||||||
|
assert.False(b.Equals(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxIsBiggerThan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
|
assert.True(a.IsBiggerThan(b))
|
||||||
|
assert.False(a.IsBiggerThan(c))
|
||||||
|
assert.True(c.IsBiggerThan(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxIsSmallerThan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
|
assert.False(a.IsSmallerThan(b))
|
||||||
|
assert.True(a.IsSmallerThan(c))
|
||||||
|
assert.False(c.IsSmallerThan(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxGrow(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 1, Left: 2, Right: 15, Bottom: 15}
|
||||||
|
b := Box{Top: 4, Left: 5, Right: 30, Bottom: 35}
|
||||||
|
c := a.Grow(b)
|
||||||
|
assert.False(c.Equals(b))
|
||||||
|
assert.False(c.Equals(a))
|
||||||
|
assert.Equal(1, c.Top)
|
||||||
|
assert.Equal(2, c.Left)
|
||||||
|
assert.Equal(30, c.Right)
|
||||||
|
assert.Equal(35, c.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxFit(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
|
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
|
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
|
fab := a.Fit(b)
|
||||||
|
assert.Equal(a.Left, fab.Left)
|
||||||
|
assert.Equal(a.Right, fab.Right)
|
||||||
|
assert.True(fab.Top < fab.Bottom)
|
||||||
|
assert.True(fab.Left < fab.Right)
|
||||||
|
assert.True(math.Abs(b.Aspect()-fab.Aspect()) < 0.02)
|
||||||
|
|
||||||
|
fac := a.Fit(c)
|
||||||
|
assert.Equal(a.Top, fac.Top)
|
||||||
|
assert.Equal(a.Bottom, fac.Bottom)
|
||||||
|
assert.True(math.Abs(c.Aspect()-fac.Aspect()) < 0.02)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxConstrain(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
|
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
|
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
|
cab := a.Constrain(b)
|
||||||
|
assert.Equal(64, cab.Top)
|
||||||
|
assert.Equal(64, cab.Left)
|
||||||
|
assert.Equal(192, cab.Right)
|
||||||
|
assert.Equal(170, cab.Bottom)
|
||||||
|
|
||||||
|
cac := a.Constrain(c)
|
||||||
|
assert.Equal(64, cac.Top)
|
||||||
|
assert.Equal(64, cac.Left)
|
||||||
|
assert.Equal(170, cac.Right)
|
||||||
|
assert.Equal(192, cac.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxOuterConstrain(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
box := NewBox(0, 0, 100, 100)
|
||||||
|
canvas := NewBox(5, 5, 95, 95)
|
||||||
|
taller := NewBox(-10, 5, 50, 50)
|
||||||
|
|
||||||
|
c := canvas.OuterConstrain(box, taller)
|
||||||
|
assert.Equal(15, c.Top, c.String())
|
||||||
|
assert.Equal(5, c.Left, c.String())
|
||||||
|
assert.Equal(95, c.Right, c.String())
|
||||||
|
assert.Equal(95, c.Bottom, c.String())
|
||||||
|
|
||||||
|
wider := NewBox(5, 5, 110, 50)
|
||||||
|
d := canvas.OuterConstrain(box, wider)
|
||||||
|
assert.Equal(5, d.Top, d.String())
|
||||||
|
assert.Equal(5, d.Left, d.String())
|
||||||
|
assert.Equal(85, d.Right, d.String())
|
||||||
|
assert.Equal(95, d.Bottom, d.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxShift(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
b := Box{
|
||||||
|
Top: 5,
|
||||||
|
Left: 5,
|
||||||
|
Right: 10,
|
||||||
|
Bottom: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
shifted := b.Shift(1, 2)
|
||||||
|
assert.Equal(7, shifted.Top)
|
||||||
|
assert.Equal(6, shifted.Left)
|
||||||
|
assert.Equal(11, shifted.Right)
|
||||||
|
assert.Equal(12, shifted.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxCenter(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
b := Box{
|
||||||
|
Top: 10,
|
||||||
|
Left: 10,
|
||||||
|
Right: 20,
|
||||||
|
Bottom: 30,
|
||||||
|
}
|
||||||
|
cx, cy := b.Center()
|
||||||
|
assert.Equal(15, cx)
|
||||||
|
assert.Equal(20, cy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxCornersCenter(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BoxCorners{
|
||||||
|
TopLeft: Point{5, 5},
|
||||||
|
TopRight: Point{15, 5},
|
||||||
|
BottomRight: Point{15, 15},
|
||||||
|
BottomLeft: Point{5, 15},
|
||||||
|
}
|
||||||
|
|
||||||
|
cx, cy := bc.Center()
|
||||||
|
assert.Equal(10, cx)
|
||||||
|
assert.Equal(10, cy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxCornersRotate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := BoxCorners{
|
||||||
|
TopLeft: Point{5, 5},
|
||||||
|
TopRight: Point{15, 5},
|
||||||
|
BottomRight: Point{15, 15},
|
||||||
|
BottomLeft: Point{5, 15},
|
||||||
|
}
|
||||||
|
|
||||||
|
rotated := bc.Rotate(45)
|
||||||
|
assert.True(rotated.TopLeft.Equals(Point{10, 3}), rotated.String())
|
||||||
|
}
|
10
chart.go
10
chart.go
|
@ -6,8 +6,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chart is what we're drawing.
|
// Chart is what we're drawing.
|
||||||
|
@ -317,6 +317,9 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
if math.IsNaN(yDelta) {
|
if math.IsNaN(yDelta) {
|
||||||
return errors.New("nan y-range delta")
|
return errors.New("nan y-range delta")
|
||||||
}
|
}
|
||||||
|
if yDelta == 0 {
|
||||||
|
return errors.New("zero y-range delta")
|
||||||
|
}
|
||||||
|
|
||||||
if c.hasSecondarySeries() {
|
if c.hasSecondarySeries() {
|
||||||
yraDelta := yra.GetDelta()
|
yraDelta := yra.GetDelta()
|
||||||
|
@ -326,6 +329,9 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
if math.IsNaN(yraDelta) {
|
if math.IsNaN(yraDelta) {
|
||||||
return errors.New("nan secondary y-range delta")
|
return errors.New("nan secondary y-range delta")
|
||||||
}
|
}
|
||||||
|
if yraDelta == 0 {
|
||||||
|
return errors.New("zero secondary y-range delta")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
576
chart_test.go
Normal file
576
chart_test.go
Normal file
|
@ -0,0 +1,576 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChartGetDPI(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
unset := Chart{}
|
||||||
|
assert.Equal(DefaultDPI, unset.GetDPI())
|
||||||
|
assert.Equal(192, unset.GetDPI(192))
|
||||||
|
|
||||||
|
set := Chart{DPI: 128}
|
||||||
|
assert.Equal(128, set.GetDPI())
|
||||||
|
assert.Equal(128, set.GetDPI(192))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetFont(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
f, err := GetDefaultFont()
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
unset := Chart{}
|
||||||
|
assert.Nil(unset.GetFont())
|
||||||
|
|
||||||
|
set := Chart{Font: f}
|
||||||
|
assert.NotNil(set.GetFont())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetWidth(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
unset := Chart{}
|
||||||
|
assert.Equal(DefaultChartWidth, unset.GetWidth())
|
||||||
|
|
||||||
|
set := Chart{Width: DefaultChartWidth + 10}
|
||||||
|
assert.Equal(DefaultChartWidth+10, set.GetWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetHeight(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
unset := Chart{}
|
||||||
|
assert.Equal(DefaultChartHeight, unset.GetHeight())
|
||||||
|
|
||||||
|
set := Chart{Height: DefaultChartHeight + 10}
|
||||||
|
assert.Equal(DefaultChartHeight+10, set.GetHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{-2.0, -1.0, 0, 1.0, 2.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{-2.1, -1.0, 0, 1.0, 2.0},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
YAxis: YAxisSecondary,
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{10.0, 11.0, 12.0, 13.0, 14.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xrange, yrange, yrangeAlt := c.getRanges()
|
||||||
|
assert.Equal(-2.0, xrange.GetMin())
|
||||||
|
assert.Equal(5.0, xrange.GetMax())
|
||||||
|
|
||||||
|
assert.Equal(-2.1, yrange.GetMin())
|
||||||
|
assert.Equal(4.5, yrange.GetMax())
|
||||||
|
|
||||||
|
assert.Equal(10.0, yrangeAlt.GetMin())
|
||||||
|
assert.Equal(14.0, yrangeAlt.GetMax())
|
||||||
|
|
||||||
|
cSet := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
||||||
|
},
|
||||||
|
YAxis: YAxis{
|
||||||
|
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
||||||
|
},
|
||||||
|
YAxisSecondary: YAxis{
|
||||||
|
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{-2.0, -1.0, 0, 1.0, 2.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{-2.1, -1.0, 0, 1.0, 2.0},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
YAxis: YAxisSecondary,
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{10.0, 11.0, 12.0, 13.0, 14.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr2, yr2, yra2 := cSet.getRanges()
|
||||||
|
assert.Equal(9.8, xr2.GetMin())
|
||||||
|
assert.Equal(19.8, xr2.GetMax())
|
||||||
|
|
||||||
|
assert.Equal(9.9, yr2.GetMin())
|
||||||
|
assert.Equal(19.9, yr2.GetMax())
|
||||||
|
|
||||||
|
assert.Equal(9.7, yra2.GetMin())
|
||||||
|
assert.Equal(19.7, yra2.GetMax())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetRangesUseTicks(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// this test asserts that ticks should supercede manual ranges when generating the overall ranges.
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Ticks: []Tick{
|
||||||
|
{0.0, "Zero"},
|
||||||
|
{1.0, "1.0"},
|
||||||
|
{2.0, "2.0"},
|
||||||
|
{3.0, "3.0"},
|
||||||
|
{4.0, "4.0"},
|
||||||
|
{5.0, "Five"},
|
||||||
|
},
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: -5.0,
|
||||||
|
Max: 5.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{-2.0, -1.0, 0, 1.0, 2.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr, yr, yar := c.getRanges()
|
||||||
|
assert.Equal(-2.0, xr.GetMin())
|
||||||
|
assert.Equal(2.0, xr.GetMax())
|
||||||
|
assert.Equal(0.0, yr.GetMin())
|
||||||
|
assert.Equal(5.0, yr.GetMax())
|
||||||
|
assert.True(yar.IsZero(), yar.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetRangesUseUserRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: -5.0,
|
||||||
|
Max: 5.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{-2.0, -1.0, 0, 1.0, 2.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr, yr, yar := c.getRanges()
|
||||||
|
assert.Equal(-2.0, xr.GetMin())
|
||||||
|
assert.Equal(2.0, xr.GetMax())
|
||||||
|
assert.Equal(-5.0, yr.GetMin())
|
||||||
|
assert.Equal(5.0, yr.GetMax())
|
||||||
|
assert.True(yar.IsZero(), yar.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetBackgroundStyle(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Background: Style{
|
||||||
|
FillColor: drawing.ColorBlack,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := c.getBackgroundStyle()
|
||||||
|
assert.Equal(bs.FillColor.String(), drawing.ColorBlack.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetCanvasStyle(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Canvas: Style{
|
||||||
|
FillColor: drawing.ColorBlack,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := c.getCanvasStyle()
|
||||||
|
assert.Equal(bs.FillColor.String(), drawing.ColorBlack.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{}
|
||||||
|
canvasBoxDefault := c.getDefaultCanvasBox()
|
||||||
|
assert.False(canvasBoxDefault.IsZero())
|
||||||
|
assert.Equal(DefaultBackgroundPadding.Top, canvasBoxDefault.Top)
|
||||||
|
assert.Equal(DefaultBackgroundPadding.Left, canvasBoxDefault.Left)
|
||||||
|
assert.Equal(c.GetWidth()-DefaultBackgroundPadding.Right, canvasBoxDefault.Right)
|
||||||
|
assert.Equal(c.GetHeight()-DefaultBackgroundPadding.Bottom, canvasBoxDefault.Bottom)
|
||||||
|
|
||||||
|
custom := Chart{
|
||||||
|
Background: Style{
|
||||||
|
Padding: Box{
|
||||||
|
Top: DefaultBackgroundPadding.Top + 1,
|
||||||
|
Left: DefaultBackgroundPadding.Left + 1,
|
||||||
|
Right: DefaultBackgroundPadding.Right + 1,
|
||||||
|
Bottom: DefaultBackgroundPadding.Bottom + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
canvasBoxCustom := custom.getDefaultCanvasBox()
|
||||||
|
assert.False(canvasBoxCustom.IsZero())
|
||||||
|
assert.Equal(DefaultBackgroundPadding.Top+1, canvasBoxCustom.Top)
|
||||||
|
assert.Equal(DefaultBackgroundPadding.Left+1, canvasBoxCustom.Left)
|
||||||
|
assert.Equal(c.GetWidth()-(DefaultBackgroundPadding.Right+1), canvasBoxCustom.Right)
|
||||||
|
assert.Equal(c.GetHeight()-(DefaultBackgroundPadding.Bottom+1), canvasBoxCustom.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetValueFormatters(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{-2.0, -1.0, 0, 1.0, 2.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{-2.1, -1.0, 0, 1.0, 2.0},
|
||||||
|
},
|
||||||
|
ContinuousSeries{
|
||||||
|
YAxis: YAxisSecondary,
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{10.0, 11.0, 12.0, 13.0, 14.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dxf, dyf, dyaf := c.getValueFormatters()
|
||||||
|
assert.NotNil(dxf)
|
||||||
|
assert.NotNil(dyf)
|
||||||
|
assert.NotNil(dyaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartHasAxes(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.False(Chart{}.hasAxes())
|
||||||
|
|
||||||
|
x := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(x.hasAxes())
|
||||||
|
|
||||||
|
y := Chart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(y.hasAxes())
|
||||||
|
|
||||||
|
ya := Chart{
|
||||||
|
YAxisSecondary: YAxis{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(ya.hasAxes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartGetAxesTicks(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
r, err := PNG(1024, 1024)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
|
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
||||||
|
},
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
|
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
||||||
|
},
|
||||||
|
YAxisSecondary: YAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
|
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
xr, yr, yar := c.getRanges()
|
||||||
|
|
||||||
|
xt, yt, yat := c.getAxesTicks(r, xr, yr, yar, FloatValueFormatter, FloatValueFormatter, FloatValueFormatter)
|
||||||
|
assert.NotEmpty(xt)
|
||||||
|
assert.NotEmpty(yt)
|
||||||
|
assert.NotEmpty(yat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartSingleSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
now := time.Now()
|
||||||
|
c := Chart{
|
||||||
|
Title: "Hello!",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
|
Width: 1024,
|
||||||
|
Height: 400,
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: 0.0,
|
||||||
|
Max: 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
TimeSeries{
|
||||||
|
Name: "goog",
|
||||||
|
XValues: []time.Time{now.AddDate(0, 0, -3), now.AddDate(0, 0, -2), now.AddDate(0, 0, -1)},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
c.Render(PNG, buffer)
|
||||||
|
assert.NotEmpty(buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartRegressionBadRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{math.Inf(1), math.Inf(1), math.Inf(1), math.Inf(1), math.Inf(1)},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 4.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
c.Render(PNG, buffer)
|
||||||
|
assert.True(true, "Render needs to finish.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
YAxis: YAxis{
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: math.Inf(-1),
|
||||||
|
Max: math.Inf(1), // this could really happen? eh.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
c.Render(PNG, buffer)
|
||||||
|
assert.True(true, "Render needs to finish.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartValidatesSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(c.validateSeries())
|
||||||
|
|
||||||
|
c = Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(c.validateSeries())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartCheckRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0},
|
||||||
|
YValues: []float64{3.10, 3.14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr, yr, yra := c.getRanges()
|
||||||
|
assert.Nil(c.checkRanges(xr, yr, yra))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartCheckRangesFailure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0},
|
||||||
|
YValues: []float64{3.14, 3.14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr, yr, yra := c.getRanges()
|
||||||
|
assert.NotNil(c.checkRanges(xr, yr, yra))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartCheckRangesWithRanges(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
XAxis: XAxis{
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: 0,
|
||||||
|
Max: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: YAxis{
|
||||||
|
Range: &ContinuousRange{
|
||||||
|
Min: 0,
|
||||||
|
Max: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0},
|
||||||
|
YValues: []float64{3.14, 3.14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xr, yr, yra := c.getRanges()
|
||||||
|
assert.Nil(c.checkRanges(xr, yr, yra))
|
||||||
|
}
|
||||||
|
|
||||||
|
func at(i image.Image, x, y int) drawing.Color {
|
||||||
|
return drawing.ColorFromAlphaMixedRGBA(i.At(x, y).RGBA())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartE2ELine(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Height: 50,
|
||||||
|
Width: 50,
|
||||||
|
Canvas: Style{
|
||||||
|
Padding: Box{IsSet: true},
|
||||||
|
},
|
||||||
|
Background: Style{
|
||||||
|
Padding: Box{IsSet: true},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = &bytes.Buffer{}
|
||||||
|
err := c.Render(PNG, buffer)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// do color tests ...
|
||||||
|
|
||||||
|
i, err := png.Decode(buffer)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// test the bottom and top of the line
|
||||||
|
assert.Equal(drawing.ColorWhite, at(i, 0, 0))
|
||||||
|
assert.Equal(drawing.ColorWhite, at(i, 49, 49))
|
||||||
|
|
||||||
|
// test a line mid point
|
||||||
|
defaultSeriesColor := GetDefaultColor(0)
|
||||||
|
assert.Equal(defaultSeriesColor, at(i, 0, 49))
|
||||||
|
assert.Equal(defaultSeriesColor, at(i, 49, 0))
|
||||||
|
assert.Equal(drawing.ColorFromHex("bddbf6"), at(i, 24, 24))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChartE2ELineWithFill(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := Chart{
|
||||||
|
Height: 50,
|
||||||
|
Width: 50,
|
||||||
|
Canvas: Style{
|
||||||
|
Padding: Box{IsSet: true},
|
||||||
|
},
|
||||||
|
Background: Style{
|
||||||
|
Padding: Box{IsSet: true},
|
||||||
|
},
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
StrokeColor: drawing.ColorBlue,
|
||||||
|
FillColor: drawing.ColorRed,
|
||||||
|
},
|
||||||
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = &bytes.Buffer{}
|
||||||
|
err := c.Render(PNG, buffer)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// do color tests ...
|
||||||
|
|
||||||
|
i, err := png.Decode(buffer)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// test the bottom and top of the line
|
||||||
|
assert.Equal(drawing.ColorWhite, at(i, 0, 0))
|
||||||
|
assert.Equal(drawing.ColorRed, at(i, 49, 49))
|
||||||
|
|
||||||
|
// test a line mid point
|
||||||
|
defaultSeriesColor := drawing.ColorBlue
|
||||||
|
assert.Equal(defaultSeriesColor, at(i, 0, 49))
|
||||||
|
assert.Equal(defaultSeriesColor, at(i, 49, 0))
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "git.fireandbrimst.one/aw/go-chart/drawing"
|
import "github.com/wcharczuk/go-chart/drawing"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ColorWhite is white.
|
// ColorWhite is white.
|
||||||
|
|
42
concat_series_test.go
Normal file
42
concat_series_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConcatSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
s1 := ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
s2 := ContinuousSeries{
|
||||||
|
XValues: seq.Range(11, 20.0),
|
||||||
|
YValues: seq.Range(10.0, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
s3 := ContinuousSeries{
|
||||||
|
XValues: seq.Range(21, 30.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||||
|
assert.Equal(30, cs.Len())
|
||||||
|
|
||||||
|
x0, y0 := cs.GetValue(0)
|
||||||
|
assert.Equal(1.0, x0)
|
||||||
|
assert.Equal(1.0, y0)
|
||||||
|
|
||||||
|
xm, ym := cs.GetValue(19)
|
||||||
|
assert.Equal(20.0, xm)
|
||||||
|
assert.Equal(1.0, ym)
|
||||||
|
|
||||||
|
xn, yn := cs.GetValue(29)
|
||||||
|
assert.Equal(30.0, xn)
|
||||||
|
assert.Equal(10.0, yn)
|
||||||
|
}
|
23
continuous_range_test.go
Normal file
23
continuous_range_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRangeTranslate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
values := []float64{1.0, 2.0, 2.5, 2.7, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
|
||||||
|
r := ContinuousRange{Domain: 1000}
|
||||||
|
r.Min, r.Max = util.Math.MinAndMax(values...)
|
||||||
|
|
||||||
|
// delta = ~7.0
|
||||||
|
// value = ~5.0
|
||||||
|
// domain = ~1000
|
||||||
|
// 5/8 * 1000 ~=
|
||||||
|
assert.Equal(0, r.Translate(1.0))
|
||||||
|
assert.Equal(1000, r.Translate(8.0))
|
||||||
|
assert.Equal(572, r.Translate(5.0))
|
||||||
|
}
|
|
@ -2,13 +2,6 @@ package chart
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*ContinuousSeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*ContinuousSeries)(nil)
|
|
||||||
_ LastValuesProvider = (*ContinuousSeries)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContinuousSeries represents a line on a chart.
|
// ContinuousSeries represents a line on a chart.
|
||||||
type ContinuousSeries struct {
|
type ContinuousSeries struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -43,11 +36,6 @@ func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
|
||||||
return cs.XValues[index], cs.YValues[index]
|
return cs.XValues[index], cs.YValues[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues gets the first x,y values.
|
|
||||||
func (cs ContinuousSeries) GetFirstValues() (float64, float64) {
|
|
||||||
return cs.XValues[0], cs.YValues[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues gets the last x,y values.
|
// GetLastValues gets the last x,y values.
|
||||||
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
||||||
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
||||||
|
|
73
continuous_series_test.go
Normal file
73
continuous_series_test.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContinuousSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cs := ContinuousSeries{
|
||||||
|
Name: "Test Series",
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal("Test Series", cs.GetName())
|
||||||
|
assert.Equal(10, cs.Len())
|
||||||
|
x0, y0 := cs.GetValues(0)
|
||||||
|
assert.Equal(1.0, x0)
|
||||||
|
assert.Equal(1.0, y0)
|
||||||
|
|
||||||
|
xn, yn := cs.GetValues(9)
|
||||||
|
assert.Equal(10.0, xn)
|
||||||
|
assert.Equal(10.0, yn)
|
||||||
|
|
||||||
|
xn, yn = cs.GetLastValues()
|
||||||
|
assert.Equal(10.0, xn)
|
||||||
|
assert.Equal(10.0, yn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContinuousSeriesValueFormatter(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cs := ContinuousSeries{
|
||||||
|
XValueFormatter: func(v interface{}) string {
|
||||||
|
return fmt.Sprintf("%f foo", v)
|
||||||
|
},
|
||||||
|
YValueFormatter: func(v interface{}) string {
|
||||||
|
return fmt.Sprintf("%f bar", v)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xf, yf := cs.GetValueFormatters()
|
||||||
|
assert.Equal("0.100000 foo", xf(0.1))
|
||||||
|
assert.Equal("0.100000 bar", yf(0.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContinuousSeriesValidate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cs := ContinuousSeries{
|
||||||
|
Name: "Test Series",
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
assert.Nil(cs.Validate())
|
||||||
|
|
||||||
|
cs = ContinuousSeries{
|
||||||
|
Name: "Test Series",
|
||||||
|
XValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
assert.NotNil(cs.Validate())
|
||||||
|
|
||||||
|
cs = ContinuousSeries{
|
||||||
|
Name: "Test Series",
|
||||||
|
YValues: seq.Range(1.0, 10.0),
|
||||||
|
}
|
||||||
|
assert.NotNil(cs.Validate())
|
||||||
|
}
|
2
draw.go
2
draw.go
|
@ -3,7 +3,7 @@ package chart
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
53
drawing/color_test.go
Normal file
53
drawing/color_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package drawing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColorFromHex(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
white := ColorFromHex("FFFFFF")
|
||||||
|
assert.Equal(ColorWhite, white)
|
||||||
|
|
||||||
|
shortWhite := ColorFromHex("FFF")
|
||||||
|
assert.Equal(ColorWhite, shortWhite)
|
||||||
|
|
||||||
|
black := ColorFromHex("000000")
|
||||||
|
assert.Equal(ColorBlack, black)
|
||||||
|
|
||||||
|
shortBlack := ColorFromHex("000")
|
||||||
|
assert.Equal(ColorBlack, shortBlack)
|
||||||
|
|
||||||
|
red := ColorFromHex("FF0000")
|
||||||
|
assert.Equal(ColorRed, red)
|
||||||
|
|
||||||
|
shortRed := ColorFromHex("F00")
|
||||||
|
assert.Equal(ColorRed, shortRed)
|
||||||
|
|
||||||
|
green := ColorFromHex("00FF00")
|
||||||
|
assert.Equal(ColorGreen, green)
|
||||||
|
|
||||||
|
shortGreen := ColorFromHex("0F0")
|
||||||
|
assert.Equal(ColorGreen, shortGreen)
|
||||||
|
|
||||||
|
blue := ColorFromHex("0000FF")
|
||||||
|
assert.Equal(ColorBlue, blue)
|
||||||
|
|
||||||
|
shortBlue := ColorFromHex("00F")
|
||||||
|
assert.Equal(ColorBlue, shortBlue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorFromAlphaMixedRGBA(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
black := ColorFromAlphaMixedRGBA(color.Black.RGBA())
|
||||||
|
assert.True(black.Equals(ColorBlack), black.String())
|
||||||
|
|
||||||
|
white := ColorFromAlphaMixedRGBA(color.White.RGBA())
|
||||||
|
assert.True(white.Equals(ColorWhite), white.String())
|
||||||
|
}
|
35
drawing/curve_test.go
Normal file
35
drawing/curve_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package drawing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type point struct {
|
||||||
|
X, Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLine struct {
|
||||||
|
inner []point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLine) LineTo(x, y float64) {
|
||||||
|
ml.inner = append(ml.inner, point{x, y})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml mockLine) Len() int {
|
||||||
|
return len(ml.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraceQuad(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// Quad
|
||||||
|
// x1, y1, cpx1, cpy2, x2, y2 float64
|
||||||
|
// do the 9->12 circle segment
|
||||||
|
quad := []float64{10, 20, 20, 20, 20, 10}
|
||||||
|
liner := &mockLine{}
|
||||||
|
TraceQuad(liner, quad, 0.5)
|
||||||
|
assert.NotZero(liner.Len())
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package drawing
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FillRule defines the type for fill rules
|
// FillRule defines the type for fill rules
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package drawing
|
package drawing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"git.fireandbrimst.one/aw/golang-image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FtLineBuilder is a builder for freetype raster glyphs.
|
// FtLineBuilder is a builder for freetype raster glyphs.
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-image/draw"
|
"golang.org/x/image/draw"
|
||||||
"git.fireandbrimst.one/aw/golang-image/math/f64"
|
"golang.org/x/image/math/f64"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
||||||
|
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-image/draw"
|
"golang.org/x/image/draw"
|
||||||
"git.fireandbrimst.one/aw/golang-image/font"
|
"golang.org/x/image/font"
|
||||||
"git.fireandbrimst.one/aw/golang-image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRasterGraphicContext creates a new Graphic context from an image.
|
// NewRasterGraphicContext creates a new Graphic context from an image.
|
||||||
|
@ -206,7 +206,7 @@ func (rgc *RasterGraphicContext) GetFont() *truetype.Font {
|
||||||
return rgc.current.Font
|
return rgc.current.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFontSize sets the font size in points (as in “a 12 point font”).
|
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||||
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) {
|
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) {
|
||||||
rgc.current.FontSizePoints = fontSizePoints
|
rgc.current.FontSizePoints = fontSizePoints
|
||||||
rgc.recalc()
|
rgc.recalc()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StackGraphicContext is a context that does thngs.
|
// StackGraphicContext is a context that does thngs.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package drawing
|
package drawing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
||||||
|
|
|
@ -3,10 +3,10 @@ package drawing
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PixelsToPoints returns the points for a given number of pixels at a DPI.
|
// PixelsToPoints returns the points for a given number of pixels at a DPI.
|
||||||
|
|
|
@ -7,13 +7,6 @@ const (
|
||||||
DefaultEMAPeriod = 12
|
DefaultEMAPeriod = 12
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*EMASeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*EMASeries)(nil)
|
|
||||||
_ LastValuesProvider = (*EMASeries)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// EMASeries is a computed series.
|
// EMASeries is a computed series.
|
||||||
type EMASeries struct {
|
type EMASeries struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -73,19 +66,6 @@ func (ema *EMASeries) GetValues(index int) (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues computes the first moving average value.
|
|
||||||
func (ema *EMASeries) GetFirstValues() (x, y float64) {
|
|
||||||
if ema.InnerSeries == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ema.cache) == 0 {
|
|
||||||
ema.ensureCachedValues()
|
|
||||||
}
|
|
||||||
x, _ = ema.InnerSeries.GetValues(0)
|
|
||||||
y = ema.cache[0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues computes the last moving average value but walking back window size samples,
|
// GetLastValues computes the last moving average value but walking back window size samples,
|
||||||
// and recomputing the last moving average chunk.
|
// and recomputing the last moving average chunk.
|
||||||
func (ema *EMASeries) GetLastValues() (x, y float64) {
|
func (ema *EMASeries) GetLastValues() (x, y float64) {
|
||||||
|
|
106
ema_series_test.go
Normal file
106
ema_series_test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emaXValues = seq.Range(1.0, 50.0)
|
||||||
|
emaYValues = []float64{
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2, 3, 4, 5, 4, 3, 2,
|
||||||
|
1, 2,
|
||||||
|
}
|
||||||
|
emaExpected = []float64{
|
||||||
|
1,
|
||||||
|
1.074074074,
|
||||||
|
1.216735254,
|
||||||
|
1.422903013,
|
||||||
|
1.68787316,
|
||||||
|
1.859141815,
|
||||||
|
1.943649828,
|
||||||
|
1.947823915,
|
||||||
|
1.877614736,
|
||||||
|
1.886680311,
|
||||||
|
1.969148437,
|
||||||
|
2.119581886,
|
||||||
|
2.33294619,
|
||||||
|
2.456431658,
|
||||||
|
2.496695979,
|
||||||
|
2.459903685,
|
||||||
|
2.351762671,
|
||||||
|
2.325706177,
|
||||||
|
2.375653867,
|
||||||
|
2.495975803,
|
||||||
|
2.681459077,
|
||||||
|
2.779128775,
|
||||||
|
2.795489607,
|
||||||
|
2.73656445,
|
||||||
|
2.607930047,
|
||||||
|
2.562898191,
|
||||||
|
2.595276103,
|
||||||
|
2.699329725,
|
||||||
|
2.869749746,
|
||||||
|
2.953471987,
|
||||||
|
2.956918506,
|
||||||
|
2.886035654,
|
||||||
|
2.746329309,
|
||||||
|
2.691045657,
|
||||||
|
2.713931163,
|
||||||
|
2.809195522,
|
||||||
|
2.971477335,
|
||||||
|
3.047664199,
|
||||||
|
3.044133518,
|
||||||
|
2.966790294,
|
||||||
|
2.821102124,
|
||||||
|
2.760279745,
|
||||||
|
2.778036801,
|
||||||
|
2.868552593,
|
||||||
|
3.026437586,
|
||||||
|
3.098553321,
|
||||||
|
3.091253075,
|
||||||
|
3.010419514,
|
||||||
|
2.86149955,
|
||||||
|
2.797684768,
|
||||||
|
}
|
||||||
|
emaDelta = 0.0001
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEMASeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
mockSeries := mockValuesProvider{
|
||||||
|
emaXValues,
|
||||||
|
emaYValues,
|
||||||
|
}
|
||||||
|
assert.Equal(50, mockSeries.Len())
|
||||||
|
|
||||||
|
ema := &EMASeries{
|
||||||
|
InnerSeries: mockSeries,
|
||||||
|
Period: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := ema.GetSigma()
|
||||||
|
assert.Equal(2.0/(26.0+1), sig)
|
||||||
|
|
||||||
|
var yvalues []float64
|
||||||
|
for x := 0; x < ema.Len(); x++ {
|
||||||
|
_, y := ema.GetValues(x)
|
||||||
|
yvalues = append(yvalues, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, yv := range yvalues {
|
||||||
|
assert.InDelta(yv, emaExpected[index], emaDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
lvx, lvy := ema.GetLastValues()
|
||||||
|
assert.Equal(50.0, lvx)
|
||||||
|
assert.InDelta(lvy, emaExpected[49], emaDelta)
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// FirstValueAnnotation returns an annotation series of just the first value of a value provider as an annotation.
|
|
||||||
func FirstValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries {
|
|
||||||
var vf ValueFormatter
|
|
||||||
if len(vfs) > 0 {
|
|
||||||
vf = vfs[0]
|
|
||||||
} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped {
|
|
||||||
_, vf = typed.GetValueFormatters()
|
|
||||||
} else {
|
|
||||||
vf = FloatValueFormatter
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstValue Value2
|
|
||||||
if typed, isTyped := innerSeries.(FirstValuesProvider); isTyped {
|
|
||||||
firstValue.XValue, firstValue.YValue = typed.GetFirstValues()
|
|
||||||
firstValue.Label = vf(firstValue.YValue)
|
|
||||||
} else {
|
|
||||||
firstValue.XValue, firstValue.YValue = innerSeries.GetValues(0)
|
|
||||||
firstValue.Label = vf(firstValue.YValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
var seriesName string
|
|
||||||
var seriesStyle Style
|
|
||||||
if typed, isTyped := innerSeries.(Series); isTyped {
|
|
||||||
seriesName = fmt.Sprintf("%s - First Value", typed.GetName())
|
|
||||||
seriesStyle = typed.GetStyle()
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnnotationSeries{
|
|
||||||
Name: seriesName,
|
|
||||||
Style: seriesStyle,
|
|
||||||
Annotations: []Value2{firstValue},
|
|
||||||
}
|
|
||||||
}
|
|
4
font.go
4
font.go
|
@ -3,8 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/roboto"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/wcharczuk/go-chart/roboto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
24
grid_line_test.go
Normal file
24
grid_line_test.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateGridLines(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ticks := []Tick{
|
||||||
|
{Value: 1.0, Label: "1.0"},
|
||||||
|
{Value: 2.0, Label: "2.0"},
|
||||||
|
{Value: 3.0, Label: "3.0"},
|
||||||
|
{Value: 4.0, Label: "4.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
gl := GenerateGridLines(ticks, Style{}, Style{})
|
||||||
|
assert.Len(gl, 2)
|
||||||
|
|
||||||
|
assert.Equal(2.0, gl[0].Value)
|
||||||
|
assert.Equal(3.0, gl[1].Value)
|
||||||
|
}
|
32
histogram_series_test.go
Normal file
32
histogram_series_test.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHistogramSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cs := ContinuousSeries{
|
||||||
|
Name: "Test Series",
|
||||||
|
XValues: seq.Range(1.0, 20.0),
|
||||||
|
YValues: seq.Range(10.0, -10.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
hs := HistogramSeries{
|
||||||
|
InnerSeries: cs,
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := 0; x < hs.Len(); x++ {
|
||||||
|
csx, csy := cs.GetValues(0)
|
||||||
|
hsx, hsy1, hsy2 := hs.GetBoundedValues(0)
|
||||||
|
assert.Equal(csx, hsx)
|
||||||
|
assert.True(hsy1 > 0)
|
||||||
|
assert.True(hsy2 <= 0)
|
||||||
|
assert.True(csy < 0 || (csy > 0 && csy == hsy1))
|
||||||
|
assert.True(csy > 0 || (csy < 0 && csy == hsy2))
|
||||||
|
}
|
||||||
|
}
|
2
jet.go
2
jet.go
|
@ -1,6 +1,6 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "git.fireandbrimst.one/aw/go-chart/drawing"
|
import "github.com/wcharczuk/go-chart/drawing"
|
||||||
|
|
||||||
// Jet is a color map provider based on matlab's jet color map.
|
// Jet is a color map provider based on matlab's jet color map.
|
||||||
func Jet(v, vmin, vmax float64) drawing.Color {
|
func Jet(v, vmin, vmax float64) drawing.Color {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"git.fireandbrimst.one/aw/go-chart/util"
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legend returns a legend renderable function.
|
// Legend returns a legend renderable function.
|
||||||
|
|
31
legend_test.go
Normal file
31
legend_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLegend(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
graph := Chart{
|
||||||
|
Series: []Series{
|
||||||
|
ContinuousSeries{
|
||||||
|
Name: "A test series",
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//note we have to do this as a separate step because we need a reference to graph
|
||||||
|
graph.Elements = []Renderable{
|
||||||
|
Legend(&graph),
|
||||||
|
}
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := graph.Render(PNG, buf)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.NotZero(buf.Len())
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
// LinearCoefficientProvider is a type that returns linear cofficients.
|
|
||||||
type LinearCoefficientProvider interface {
|
|
||||||
Coefficients() (m, b, stdev, avg float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinearCoefficients returns a fixed linear coefficient pair.
|
|
||||||
func LinearCoefficients(m, b float64) LinearCoefficientSet {
|
|
||||||
return LinearCoefficientSet{
|
|
||||||
M: m,
|
|
||||||
B: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalizedLinearCoefficients returns a fixed linear coefficient pair.
|
|
||||||
func NormalizedLinearCoefficients(m, b, stdev, avg float64) LinearCoefficientSet {
|
|
||||||
return LinearCoefficientSet{
|
|
||||||
M: m,
|
|
||||||
B: b,
|
|
||||||
StdDev: stdev,
|
|
||||||
Avg: avg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinearCoefficientSet is the m and b values for the linear equation in the form:
|
|
||||||
// y = (m*x) + b
|
|
||||||
type LinearCoefficientSet struct {
|
|
||||||
M float64
|
|
||||||
B float64
|
|
||||||
StdDev float64
|
|
||||||
Avg float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coefficients returns the coefficients.
|
|
||||||
func (lcs LinearCoefficientSet) Coefficients() (m, b, stdev, avg float64) {
|
|
||||||
m = lcs.M
|
|
||||||
b = lcs.B
|
|
||||||
stdev = lcs.StdDev
|
|
||||||
avg = lcs.Avg
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -3,16 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/seq"
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*LinearRegressionSeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*LinearRegressionSeries)(nil)
|
|
||||||
_ LastValuesProvider = (*LinearRegressionSeries)(nil)
|
|
||||||
_ LinearCoefficientProvider = (*LinearRegressionSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
||||||
|
@ -32,19 +24,6 @@ type LinearRegressionSeries struct {
|
||||||
stddevx float64
|
stddevx float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coefficients returns the linear coefficients for the series.
|
|
||||||
func (lrs LinearRegressionSeries) Coefficients() (m, b, stdev, avg float64) {
|
|
||||||
if lrs.IsZero() {
|
|
||||||
lrs.computeCoefficients()
|
|
||||||
}
|
|
||||||
|
|
||||||
m = lrs.m
|
|
||||||
b = lrs.b
|
|
||||||
stdev = lrs.stddevx
|
|
||||||
avg = lrs.avgx
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
func (lrs LinearRegressionSeries) GetName() string {
|
func (lrs LinearRegressionSeries) GetName() string {
|
||||||
return lrs.Name
|
return lrs.Name
|
||||||
|
@ -93,7 +72,7 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lrs.IsZero() {
|
if lrs.m == 0 && lrs.b == 0 {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
offset := lrs.GetOffset()
|
offset := lrs.GetOffset()
|
||||||
|
@ -103,25 +82,12 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues computes the first linear regression value.
|
|
||||||
func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) {
|
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if lrs.IsZero() {
|
|
||||||
lrs.computeCoefficients()
|
|
||||||
}
|
|
||||||
x, y = lrs.InnerSeries.GetValues(0)
|
|
||||||
y = (lrs.m * lrs.normalize(x)) + lrs.b
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues computes the last linear regression value.
|
// GetLastValues computes the last linear regression value.
|
||||||
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lrs.IsZero() {
|
if lrs.m == 0 && lrs.b == 0 {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
endIndex := lrs.GetEndIndex()
|
endIndex := lrs.GetEndIndex()
|
||||||
|
@ -130,29 +96,6 @@ func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the series.
|
|
||||||
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
|
||||||
style := lrs.Style.InheritFrom(defaults)
|
|
||||||
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates the series.
|
|
||||||
func (lrs *LinearRegressionSeries) Validate() error {
|
|
||||||
if lrs.InnerSeries == nil {
|
|
||||||
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns if we've computed the coefficients or not.
|
|
||||||
func (lrs *LinearRegressionSeries) IsZero() bool {
|
|
||||||
return lrs.m == 0 && lrs.b == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// internal helpers
|
|
||||||
//
|
|
||||||
|
|
||||||
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
|
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
|
||||||
return (xvalue - lrs.avgx) / lrs.stddevx
|
return (xvalue - lrs.avgx) / lrs.stddevx
|
||||||
}
|
}
|
||||||
|
@ -188,3 +131,17 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
|
||||||
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
|
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
|
||||||
lrs.b = (sumy / p) - (lrs.m * sumx / p)
|
lrs.b = (sumy / p) - (lrs.m * sumx / p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := lrs.Style.InheritFrom(defaults)
|
||||||
|
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the series.
|
||||||
|
func (lrs *LinearRegressionSeries) Validate() error {
|
||||||
|
if lrs.InnerSeries == nil {
|
||||||
|
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
78
linear_regression_series_test.go
Normal file
78
linear_regression_series_test.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLinearRegressionSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
mainSeries := ContinuousSeries{
|
||||||
|
Name: "A test series",
|
||||||
|
XValues: seq.Range(1.0, 100.0),
|
||||||
|
YValues: seq.Range(1.0, 100.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||||
|
assert.InDelta(1.0, lrx0, 0.0000001)
|
||||||
|
assert.InDelta(1.0, lry0, 0.0000001)
|
||||||
|
|
||||||
|
lrxn, lryn := linRegSeries.GetLastValues()
|
||||||
|
assert.InDelta(100.0, lrxn, 0.0000001)
|
||||||
|
assert.InDelta(100.0, lryn, 0.0000001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearRegressionSeriesDesc(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
mainSeries := ContinuousSeries{
|
||||||
|
Name: "A test series",
|
||||||
|
XValues: seq.Range(100.0, 1.0),
|
||||||
|
YValues: seq.Range(100.0, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||||
|
assert.InDelta(100.0, lrx0, 0.0000001)
|
||||||
|
assert.InDelta(100.0, lry0, 0.0000001)
|
||||||
|
|
||||||
|
lrxn, lryn := linRegSeries.GetLastValues()
|
||||||
|
assert.InDelta(1.0, lrxn, 0.0000001)
|
||||||
|
assert.InDelta(1.0, lryn, 0.0000001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearRegressionSeriesWindowAndOffset(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
mainSeries := ContinuousSeries{
|
||||||
|
Name: "A test series",
|
||||||
|
XValues: seq.Range(100.0, 1.0),
|
||||||
|
YValues: seq.Range(100.0, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
linRegSeries := &LinearRegressionSeries{
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
Offset: 10,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(10, linRegSeries.Len())
|
||||||
|
|
||||||
|
lrx0, lry0 := linRegSeries.GetValues(0)
|
||||||
|
assert.InDelta(90.0, lrx0, 0.0000001)
|
||||||
|
assert.InDelta(90.0, lry0, 0.0000001)
|
||||||
|
|
||||||
|
lrxn, lryn := linRegSeries.GetLastValues()
|
||||||
|
assert.InDelta(80.0, lrxn, 0.0000001)
|
||||||
|
assert.InDelta(80.0, lryn, 0.0000001)
|
||||||
|
}
|
119
linear_series.go
119
linear_series.go
|
@ -1,119 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*LinearSeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*LinearSeries)(nil)
|
|
||||||
_ LastValuesProvider = (*LinearSeries)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// LinearSeries is a series that plots a line in a given domain.
|
|
||||||
type LinearSeries struct {
|
|
||||||
Name string
|
|
||||||
Style Style
|
|
||||||
YAxis YAxisType
|
|
||||||
|
|
||||||
XValues []float64
|
|
||||||
InnerSeries LinearCoefficientProvider
|
|
||||||
|
|
||||||
m float64
|
|
||||||
b float64
|
|
||||||
stdev float64
|
|
||||||
avg float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
|
||||||
func (ls LinearSeries) GetName() string {
|
|
||||||
return ls.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStyle returns the line style.
|
|
||||||
func (ls LinearSeries) GetStyle() Style {
|
|
||||||
return ls.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
|
||||||
func (ls LinearSeries) GetYAxis() YAxisType {
|
|
||||||
return ls.YAxis
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the series.
|
|
||||||
func (ls LinearSeries) Len() int {
|
|
||||||
return len(ls.XValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEndIndex returns the effective limit end.
|
|
||||||
func (ls LinearSeries) GetEndIndex() int {
|
|
||||||
return len(ls.XValues) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValues gets a value at a given index.
|
|
||||||
func (ls *LinearSeries) GetValues(index int) (x, y float64) {
|
|
||||||
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ls.IsZero() {
|
|
||||||
ls.computeCoefficients()
|
|
||||||
}
|
|
||||||
x = ls.XValues[index]
|
|
||||||
y = (ls.m * ls.normalize(x)) + ls.b
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFirstValues computes the first linear regression value.
|
|
||||||
func (ls *LinearSeries) GetFirstValues() (x, y float64) {
|
|
||||||
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ls.IsZero() {
|
|
||||||
ls.computeCoefficients()
|
|
||||||
}
|
|
||||||
x, y = ls.GetValues(0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues computes the last linear regression value.
|
|
||||||
func (ls *LinearSeries) GetLastValues() (x, y float64) {
|
|
||||||
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ls.IsZero() {
|
|
||||||
ls.computeCoefficients()
|
|
||||||
}
|
|
||||||
x, y = ls.GetValues(ls.GetEndIndex())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders the series.
|
|
||||||
func (ls *LinearSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
|
||||||
Draw.LineSeries(r, canvasBox, xrange, yrange, ls.Style.InheritFrom(defaults), ls)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates the series.
|
|
||||||
func (ls LinearSeries) Validate() error {
|
|
||||||
if ls.InnerSeries == nil {
|
|
||||||
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns if the linear series has computed coefficients or not.
|
|
||||||
func (ls LinearSeries) IsZero() bool {
|
|
||||||
return ls.m == 0 && ls.b == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
|
|
||||||
func (ls *LinearSeries) computeCoefficients() {
|
|
||||||
ls.m, ls.b, ls.stdev, ls.avg = ls.InnerSeries.Coefficients()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ls *LinearSeries) normalize(xvalue float64) float64 {
|
|
||||||
if ls.avg > 0 && ls.stdev > 0 {
|
|
||||||
return (xvalue - ls.avg) / ls.stdev
|
|
||||||
}
|
|
||||||
return xvalue
|
|
||||||
}
|
|
88
macd_series_test.go
Normal file
88
macd_series_test.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
macdExpected = []float64{
|
||||||
|
0,
|
||||||
|
0.06381766382,
|
||||||
|
0.1641441222,
|
||||||
|
0.2817201894,
|
||||||
|
0.4033023481,
|
||||||
|
0.3924673744,
|
||||||
|
0.2983093823,
|
||||||
|
0.1561821464,
|
||||||
|
-0.008916708129,
|
||||||
|
-0.05210332292,
|
||||||
|
-0.01649503993,
|
||||||
|
0.06667130899,
|
||||||
|
0.1751344574,
|
||||||
|
0.1657328378,
|
||||||
|
0.08257097469,
|
||||||
|
-0.04265109369,
|
||||||
|
-0.1875741257,
|
||||||
|
-0.2091853882,
|
||||||
|
-0.1518975486,
|
||||||
|
-0.04781419838,
|
||||||
|
0.08025242841,
|
||||||
|
0.08881960494,
|
||||||
|
0.02183529775,
|
||||||
|
-0.08904155476,
|
||||||
|
-0.2214141128,
|
||||||
|
-0.2321805992,
|
||||||
|
-0.1656331722,
|
||||||
|
-0.05373789678,
|
||||||
|
0.08083727586,
|
||||||
|
0.09475354363,
|
||||||
|
0.03209767112,
|
||||||
|
-0.07534076818,
|
||||||
|
-0.2050442354,
|
||||||
|
-0.2138010557,
|
||||||
|
-0.1458045181,
|
||||||
|
-0.03293263556,
|
||||||
|
0.1022243734,
|
||||||
|
0.1163957964,
|
||||||
|
0.05372761902,
|
||||||
|
-0.05393941791,
|
||||||
|
-0.1840438454,
|
||||||
|
-0.1933365048,
|
||||||
|
-0.1259788988,
|
||||||
|
-0.01382225715,
|
||||||
|
0.1205656194,
|
||||||
|
0.1339326478,
|
||||||
|
0.07044017167,
|
||||||
|
-0.03805851969,
|
||||||
|
-0.1689918111,
|
||||||
|
-0.1791024416,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMACDSeries(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
mockSeries := mockValuesProvider{
|
||||||
|
emaXValues,
|
||||||
|
emaYValues,
|
||||||
|
}
|
||||||
|
assert.Equal(50, mockSeries.Len())
|
||||||
|
|
||||||
|
mas := &MACDSeries{
|
||||||
|
InnerSeries: mockSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
var yvalues []float64
|
||||||
|
for x := 0; x < mas.Len(); x++ {
|
||||||
|
_, y := mas.GetValues(x)
|
||||||
|
yvalues = append(yvalues, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotEmpty(yvalues)
|
||||||
|
for index, vy := range yvalues {
|
||||||
|
assert.InDelta(vy, macdExpected[index], emaDelta, fmt.Sprintf("delta @ %d actual: %0.9f expected: %0.9f", index, vy, macdExpected[index]))
|
||||||
|
}
|
||||||
|
}
|
195
market_hours_range.go
Normal file
195
market_hours_range.go
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarketHoursRange is a special type of range that compresses a time range into just the
|
||||||
|
// market (i.e. NYSE operating hours and days) range.
|
||||||
|
type MarketHoursRange struct {
|
||||||
|
Min time.Time
|
||||||
|
Max time.Time
|
||||||
|
|
||||||
|
MarketOpen time.Time
|
||||||
|
MarketClose time.Time
|
||||||
|
|
||||||
|
HolidayProvider util.HolidayProvider
|
||||||
|
|
||||||
|
ValueFormatter ValueFormatter
|
||||||
|
|
||||||
|
Descending bool
|
||||||
|
Domain int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDescending returns if the range is descending.
|
||||||
|
func (mhr MarketHoursRange) IsDescending() bool {
|
||||||
|
return mhr.Descending
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimezone returns the timezone for the market hours range.
|
||||||
|
func (mhr MarketHoursRange) GetTimezone() *time.Location {
|
||||||
|
return mhr.GetMarketOpen().Location()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the range is setup or not.
|
||||||
|
func (mhr MarketHoursRange) IsZero() bool {
|
||||||
|
return mhr.Min.IsZero() && mhr.Max.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMin returns the min value.
|
||||||
|
func (mhr MarketHoursRange) GetMin() float64 {
|
||||||
|
return util.Time.ToFloat64(mhr.Min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMax returns the max value.
|
||||||
|
func (mhr MarketHoursRange) GetMax() float64 {
|
||||||
|
return util.Time.ToFloat64(mhr.GetEffectiveMax())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEffectiveMax gets either the close on the max, or the max itself.
|
||||||
|
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
||||||
|
maxClose := util.Date.On(mhr.MarketClose, mhr.Max)
|
||||||
|
if maxClose.After(mhr.Max) {
|
||||||
|
return maxClose
|
||||||
|
}
|
||||||
|
return mhr.Max
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMin sets the min value.
|
||||||
|
func (mhr *MarketHoursRange) SetMin(min float64) {
|
||||||
|
mhr.Min = util.Time.FromFloat64(min)
|
||||||
|
mhr.Min = mhr.Min.In(mhr.GetTimezone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMax sets the max value.
|
||||||
|
func (mhr *MarketHoursRange) SetMax(max float64) {
|
||||||
|
mhr.Max = util.Time.FromFloat64(max)
|
||||||
|
mhr.Max = mhr.Max.In(mhr.GetTimezone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelta gets the delta.
|
||||||
|
func (mhr MarketHoursRange) GetDelta() float64 {
|
||||||
|
min := mhr.GetMin()
|
||||||
|
max := mhr.GetMax()
|
||||||
|
return max - min
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain gets the domain.
|
||||||
|
func (mhr MarketHoursRange) GetDomain() int {
|
||||||
|
return mhr.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDomain sets the domain.
|
||||||
|
func (mhr *MarketHoursRange) SetDomain(domain int) {
|
||||||
|
mhr.Domain = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
||||||
|
func (mhr MarketHoursRange) GetHolidayProvider() util.HolidayProvider {
|
||||||
|
if mhr.HolidayProvider == nil {
|
||||||
|
return func(_ time.Time) bool { return false }
|
||||||
|
}
|
||||||
|
return mhr.HolidayProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMarketOpen returns the market open time.
|
||||||
|
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
||||||
|
if mhr.MarketOpen.IsZero() {
|
||||||
|
return util.NYSEOpen()
|
||||||
|
}
|
||||||
|
return mhr.MarketOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMarketClose returns the market close time.
|
||||||
|
func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
||||||
|
if mhr.MarketClose.IsZero() {
|
||||||
|
return util.NYSEClose()
|
||||||
|
}
|
||||||
|
return mhr.MarketClose
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTicks returns the ticks for the range.
|
||||||
|
// This is to override the default continous ticks that would be generated for the range.
|
||||||
|
func (mhr *MarketHoursRange) GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick {
|
||||||
|
times := seq.Time.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = seq.Time.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = seq.Time.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = seq.Time.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = seq.Time.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
|
if timesWidth <= mhr.Domain {
|
||||||
|
return mhr.makeTicks(vf, times)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateContinuousTicks(r, mhr, false, defaults, vf)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MarketHoursRange) measureTimes(r Renderer, defaults Style, vf ValueFormatter, times []time.Time) int {
|
||||||
|
defaults.GetTextOptions().WriteToRenderer(r)
|
||||||
|
var total int
|
||||||
|
for index, t := range times {
|
||||||
|
timeLabel := vf(t)
|
||||||
|
|
||||||
|
labelBox := r.MeasureText(timeLabel)
|
||||||
|
total += labelBox.Width()
|
||||||
|
if index > 0 {
|
||||||
|
total += DefaultMinimumTickHorizontalSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MarketHoursRange) makeTicks(vf ValueFormatter, times []time.Time) []Tick {
|
||||||
|
ticks := make([]Tick, len(times))
|
||||||
|
for index, t := range times {
|
||||||
|
ticks[index] = Tick{
|
||||||
|
Value: util.Time.ToFloat64(t),
|
||||||
|
Label: vf(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ticks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr MarketHoursRange) String() string {
|
||||||
|
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(time.RFC3339), mhr.Max.Format(time.RFC3339), mhr.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate maps a given value into the ContinuousRange space.
|
||||||
|
func (mhr MarketHoursRange) Translate(value float64) int {
|
||||||
|
valueTime := util.Time.FromFloat64(value)
|
||||||
|
valueTimeEastern := valueTime.In(util.Date.Eastern())
|
||||||
|
totalSeconds := util.Date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
|
valueDelta := util.Date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
||||||
|
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
||||||
|
|
||||||
|
if mhr.IsDescending() {
|
||||||
|
return mhr.Domain - translated
|
||||||
|
}
|
||||||
|
|
||||||
|
return translated
|
||||||
|
}
|
73
market_hours_range_test.go
Normal file
73
market_hours_range_test.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
r := &MarketHoursRange{
|
||||||
|
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, util.Date.Eastern()),
|
||||||
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
||||||
|
MarketOpen: util.NYSEOpen(),
|
||||||
|
MarketClose: util.NYSEClose(),
|
||||||
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotZero(r.GetDelta())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarketHoursRangeTranslate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
r := &MarketHoursRange{
|
||||||
|
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, util.Date.Eastern()),
|
||||||
|
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
||||||
|
MarketOpen: util.NYSEOpen(),
|
||||||
|
MarketClose: util.NYSEClose(),
|
||||||
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
|
Domain: 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, util.Date.Eastern())
|
||||||
|
|
||||||
|
assert.Equal(0, r.Translate(util.Time.ToFloat64(r.Min)))
|
||||||
|
assert.Equal(400, r.Translate(util.Time.ToFloat64(weds)))
|
||||||
|
assert.Equal(1000, r.Translate(util.Time.ToFloat64(r.Max)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
r, err := PNG(1024, 1024)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
f, err := GetDefaultFont()
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
defaults := Style{
|
||||||
|
Font: f,
|
||||||
|
FontSize: 10,
|
||||||
|
FontColor: ColorBlack,
|
||||||
|
}
|
||||||
|
|
||||||
|
ra := &MarketHoursRange{
|
||||||
|
Min: util.Date.On(util.NYSEOpen(), util.Date.Date(2016, 07, 18, util.Date.Eastern())),
|
||||||
|
Max: util.Date.On(util.NYSEClose(), util.Date.Date(2016, 07, 22, util.Date.Eastern())),
|
||||||
|
MarketOpen: util.NYSEOpen(),
|
||||||
|
MarketClose: util.NYSEClose(),
|
||||||
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
|
Domain: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
ticks := ra.GetTicks(r, defaults, TimeValueFormatter)
|
||||||
|
assert.NotEmpty(ticks)
|
||||||
|
assert.Len(ticks, 5)
|
||||||
|
assert.NotEqual(util.Time.ToFloat64(ra.Min), ticks[0].Value)
|
||||||
|
assert.NotEmpty(ticks[0].Label)
|
||||||
|
}
|
396
matrix/matrix_test.go
Normal file
396
matrix/matrix_test.go
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
package matrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := New(10, 5)
|
||||||
|
rows, cols := m.Size()
|
||||||
|
assert.Equal(10, rows)
|
||||||
|
assert.Equal(5, cols)
|
||||||
|
assert.Zero(m.Get(0, 0))
|
||||||
|
assert.Zero(m.Get(9, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewWithValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := New(5, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||||
|
rows, cols := m.Size()
|
||||||
|
assert.Equal(5, rows)
|
||||||
|
assert.Equal(2, cols)
|
||||||
|
assert.Equal(1, m.Get(0, 0))
|
||||||
|
assert.Equal(10, m.Get(4, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentitiy(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
id := Identity(5)
|
||||||
|
rows, cols := id.Size()
|
||||||
|
assert.Equal(5, rows)
|
||||||
|
assert.Equal(5, cols)
|
||||||
|
assert.Equal(1, id.Get(0, 0))
|
||||||
|
assert.Equal(1, id.Get(1, 1))
|
||||||
|
assert.Equal(1, id.Get(2, 2))
|
||||||
|
assert.Equal(1, id.Get(3, 3))
|
||||||
|
assert.Equal(1, id.Get(4, 4))
|
||||||
|
assert.Equal(0, id.Get(0, 1))
|
||||||
|
assert.Equal(0, id.Get(1, 0))
|
||||||
|
assert.Equal(0, id.Get(4, 0))
|
||||||
|
assert.Equal(0, id.Get(0, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFromArrays(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
})
|
||||||
|
assert.NotNil(m)
|
||||||
|
|
||||||
|
rows, cols := m.Size()
|
||||||
|
assert.Equal(2, rows)
|
||||||
|
assert.Equal(4, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnes(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ones := Ones(5, 10)
|
||||||
|
rows, cols := ones.Size()
|
||||||
|
assert.Equal(5, rows)
|
||||||
|
assert.Equal(10, cols)
|
||||||
|
|
||||||
|
for row := 0; row < rows; row++ {
|
||||||
|
for col := 0; col < cols; col++ {
|
||||||
|
assert.Equal(1, ones.Get(row, col))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixEpsilon(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
ones := Ones(2, 2)
|
||||||
|
ones = ones.WithEpsilon(0.001)
|
||||||
|
assert.Equal(0.001, ones.Epsilon())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixArrays(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotNil(m)
|
||||||
|
|
||||||
|
arrays := m.Arrays()
|
||||||
|
|
||||||
|
assert.Equal(arrays, [][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixIsSquare(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.False(NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
}).IsSquare())
|
||||||
|
|
||||||
|
assert.False(NewFromArrays([][]float64{
|
||||||
|
{1, 2},
|
||||||
|
{3, 4},
|
||||||
|
{5, 6},
|
||||||
|
}).IsSquare())
|
||||||
|
|
||||||
|
assert.True(NewFromArrays([][]float64{
|
||||||
|
{1, 2},
|
||||||
|
{3, 4},
|
||||||
|
}).IsSquare())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixIsSymmetric(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.False(NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{2, 1, 2},
|
||||||
|
}).IsSymmetric())
|
||||||
|
|
||||||
|
assert.False(NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
}).IsSymmetric())
|
||||||
|
|
||||||
|
assert.True(NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{2, 1, 2},
|
||||||
|
{3, 2, 1},
|
||||||
|
}).IsSymmetric())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixGet(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(1, m.Get(0, 0))
|
||||||
|
assert.Equal(2, m.Get(0, 1))
|
||||||
|
assert.Equal(3, m.Get(0, 2))
|
||||||
|
assert.Equal(4, m.Get(1, 0))
|
||||||
|
assert.Equal(5, m.Get(1, 1))
|
||||||
|
assert.Equal(6, m.Get(1, 2))
|
||||||
|
assert.Equal(7, m.Get(2, 0))
|
||||||
|
assert.Equal(8, m.Get(2, 1))
|
||||||
|
assert.Equal(9, m.Get(2, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixSet(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Set(1, 1, 99)
|
||||||
|
assert.Equal(99, m.Get(1, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixCol(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal([]float64{1, 4, 7}, m.Col(0))
|
||||||
|
assert.Equal([]float64{2, 5, 8}, m.Col(1))
|
||||||
|
assert.Equal([]float64{3, 6, 9}, m.Col(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixRow(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal([]float64{1, 2, 3}, m.Row(0))
|
||||||
|
assert.Equal([]float64{4, 5, 6}, m.Row(1))
|
||||||
|
assert.Equal([]float64{7, 8, 9}, m.Row(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixSwapRows(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
m.SwapRows(0, 1)
|
||||||
|
|
||||||
|
assert.Equal([]float64{4, 5, 6}, m.Row(0))
|
||||||
|
assert.Equal([]float64{1, 2, 3}, m.Row(1))
|
||||||
|
assert.Equal([]float64{7, 8, 9}, m.Row(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixCopy(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
m2 := m.Copy()
|
||||||
|
assert.False(m == m2)
|
||||||
|
assert.True(m.Equals(m2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixDiagonalVector(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 4, 7},
|
||||||
|
{4, 2, 8},
|
||||||
|
{7, 8, 3},
|
||||||
|
})
|
||||||
|
|
||||||
|
diag := m.DiagonalVector()
|
||||||
|
assert.Equal([]float64{1, 2, 3}, diag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixDiagonalVectorLandscape(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 4, 7, 99},
|
||||||
|
{4, 2, 8, 99},
|
||||||
|
})
|
||||||
|
|
||||||
|
diag := m.DiagonalVector()
|
||||||
|
assert.Equal([]float64{1, 2}, diag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixDiagonalVectorPortrait(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 4},
|
||||||
|
{4, 2},
|
||||||
|
{99, 99},
|
||||||
|
})
|
||||||
|
|
||||||
|
diag := m.DiagonalVector()
|
||||||
|
assert.Equal([]float64{1, 2}, diag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixDiagonal(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 4, 7},
|
||||||
|
{4, 2, 8},
|
||||||
|
{7, 8, 3},
|
||||||
|
})
|
||||||
|
|
||||||
|
m2 := NewFromArrays([][]float64{
|
||||||
|
{1, 0, 0},
|
||||||
|
{0, 2, 0},
|
||||||
|
{0, 0, 3},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(m.Diagonal().Equals(m2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixEquals(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 4, 7},
|
||||||
|
{4, 2, 8},
|
||||||
|
{7, 8, 3},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.False(m.Equals(nil))
|
||||||
|
var nilMatrix *Matrix
|
||||||
|
assert.True(nilMatrix.Equals(nil))
|
||||||
|
assert.False(m.Equals(New(1, 1)))
|
||||||
|
assert.False(m.Equals(New(3, 3)))
|
||||||
|
assert.True(m.Equals(New(3, 3, 1, 4, 7, 4, 2, 8, 7, 8, 3)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixL(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
l := m.L()
|
||||||
|
assert.True(l.Equals(New(3, 3, 1, 2, 3, 0, 5, 6, 0, 0, 9)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixU(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
u := m.U()
|
||||||
|
assert.True(u.Equals(New(3, 3, 0, 0, 0, 4, 0, 0, 7, 8, 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixString(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal("1 2 3 \n4 5 6 \n7 8 9 \n", m.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixLU(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 3, 5},
|
||||||
|
{2, 4, 7},
|
||||||
|
{1, 1, 0},
|
||||||
|
})
|
||||||
|
|
||||||
|
l, u, p := m.LU()
|
||||||
|
assert.NotNil(l)
|
||||||
|
assert.NotNil(u)
|
||||||
|
assert.NotNil(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixQR(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{12, -51, 4},
|
||||||
|
{6, 167, -68},
|
||||||
|
{-4, 24, -41},
|
||||||
|
})
|
||||||
|
|
||||||
|
q, r := m.QR()
|
||||||
|
assert.NotNil(q)
|
||||||
|
assert.NotNil(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatrixTranspose(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
m := NewFromArrays([][]float64{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
{7, 8, 9},
|
||||||
|
{10, 11, 12},
|
||||||
|
})
|
||||||
|
|
||||||
|
m2 := m.Transpose()
|
||||||
|
|
||||||
|
rows, cols := m2.Size()
|
||||||
|
assert.Equal(3, rows)
|
||||||
|
assert.Equal(4, cols)
|
||||||
|
|
||||||
|
assert.Equal(1, m2.Get(0, 0))
|
||||||
|
assert.Equal(10, m2.Get(0, 3))
|
||||||
|
assert.Equal(3, m2.Get(2, 0))
|
||||||
|
}
|
22
matrix/regression_test.go
Normal file
22
matrix/regression_test.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package matrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPoly(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
var xGiven = []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||||
|
var yGiven = []float64{1, 6, 17, 34, 57, 86, 121, 162, 209, 262, 321}
|
||||||
|
var degree = 2
|
||||||
|
|
||||||
|
c, err := Poly(xGiven, yGiven, degree)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Len(c, 3)
|
||||||
|
|
||||||
|
assert.InDelta(c[0], 0.999999999, DefaultEpsilon)
|
||||||
|
assert.InDelta(c[1], 2, DefaultEpsilon)
|
||||||
|
assert.InDelta(c[2], 3, DefaultEpsilon)
|
||||||
|
}
|
19
pie_chart.go
19
pie_chart.go
|
@ -6,12 +6,11 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/util"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_pi = math.Pi
|
|
||||||
_pi2 = math.Pi / 2.0
|
_pi2 = math.Pi / 2.0
|
||||||
_pi4 = math.Pi / 4.0
|
_pi4 = math.Pi / 4.0
|
||||||
)
|
)
|
||||||
|
@ -138,12 +137,6 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
// draw the pie slices
|
// draw the pie slices
|
||||||
var rads, delta, delta2, total float64
|
var rads, delta, delta2, total float64
|
||||||
var lx, ly int
|
var lx, ly int
|
||||||
|
|
||||||
if len(values) == 1 {
|
|
||||||
pc.stylePieChartValue(0).WriteToRenderer(r)
|
|
||||||
r.MoveTo(cx, cy)
|
|
||||||
r.Circle(radius, cx, cy)
|
|
||||||
} else {
|
|
||||||
for index, v := range values {
|
for index, v := range values {
|
||||||
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -158,7 +151,6 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
r.FillStroke()
|
r.FillStroke()
|
||||||
total = total + v.Value
|
total = total + v.Value
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// draw the labels
|
// draw the labels
|
||||||
total = 0
|
total = 0
|
||||||
|
@ -173,13 +165,6 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
||||||
lx = lx - (tb.Width() >> 1)
|
lx = lx - (tb.Width() >> 1)
|
||||||
ly = ly + (tb.Height() >> 1)
|
ly = ly + (tb.Height() >> 1)
|
||||||
|
|
||||||
if lx < 0 {
|
|
||||||
lx = 0
|
|
||||||
}
|
|
||||||
if ly < 0 {
|
|
||||||
lx = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Text(v.Label, lx, ly)
|
r.Text(v.Label, lx, ly)
|
||||||
}
|
}
|
||||||
total = total + v.Value
|
total = total + v.Value
|
||||||
|
|
69
pie_chart_test.go
Normal file
69
pie_chart_test.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPieChart(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
pie := PieChart{
|
||||||
|
Canvas: Style{
|
||||||
|
FillColor: ColorLightGray,
|
||||||
|
},
|
||||||
|
Values: []Value{
|
||||||
|
{Value: 10, Label: "Blue"},
|
||||||
|
{Value: 9, Label: "Green"},
|
||||||
|
{Value: 8, Label: "Gray"},
|
||||||
|
{Value: 7, Label: "Orange"},
|
||||||
|
{Value: 6, Label: "HEANG"},
|
||||||
|
{Value: 5, Label: "??"},
|
||||||
|
{Value: 2, Label: "!!"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.NewBuffer([]byte{})
|
||||||
|
pie.Render(PNG, b)
|
||||||
|
assert.NotZero(b.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPieChartDropsZeroValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
pie := PieChart{
|
||||||
|
Canvas: Style{
|
||||||
|
FillColor: ColorLightGray,
|
||||||
|
},
|
||||||
|
Values: []Value{
|
||||||
|
{Value: 5, Label: "Blue"},
|
||||||
|
{Value: 5, Label: "Green"},
|
||||||
|
{Value: 0, Label: "Gray"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.NewBuffer([]byte{})
|
||||||
|
err := pie.Render(PNG, b)
|
||||||
|
assert.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPieChartAllZeroValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
pie := PieChart{
|
||||||
|
Canvas: Style{
|
||||||
|
FillColor: ColorLightGray,
|
||||||
|
},
|
||||||
|
Values: []Value{
|
||||||
|
{Value: 0, Label: "Blue"},
|
||||||
|
{Value: 0, Label: "Green"},
|
||||||
|
{Value: 0, Label: "Gray"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.NewBuffer([]byte{})
|
||||||
|
err := pie.Render(PNG, b)
|
||||||
|
assert.NotNil(err)
|
||||||
|
}
|
|
@ -4,15 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/matrix"
|
"github.com/wcharczuk/go-chart/matrix"
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*PolynomialRegressionSeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*PolynomialRegressionSeries)(nil)
|
|
||||||
_ LastValuesProvider = (*PolynomialRegressionSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolynomialRegressionSeries implements a polynomial regression over a given
|
// PolynomialRegressionSeries implements a polynomial regression over a given
|
||||||
|
@ -108,23 +101,6 @@ func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues computes the first poly regression value.
|
|
||||||
func (prs *PolynomialRegressionSeries) GetFirstValues() (x, y float64) {
|
|
||||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if prs.coeffs == nil {
|
|
||||||
coeffs, err := prs.computeCoefficients()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
prs.coeffs = coeffs
|
|
||||||
}
|
|
||||||
x, y = prs.InnerSeries.GetValues(0)
|
|
||||||
y = prs.apply(x)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues computes the last poly regression value.
|
// GetLastValues computes the last poly regression value.
|
||||||
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
|
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
|
||||||
|
|
35
polynomial_regression_test.go
Normal file
35
polynomial_regression_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolynomialRegression(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var xv []float64
|
||||||
|
var yv []float64
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
xv = append(xv, float64(i))
|
||||||
|
yv = append(yv, float64(i*i))
|
||||||
|
}
|
||||||
|
|
||||||
|
values := ContinuousSeries{
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
}
|
||||||
|
|
||||||
|
poly := &PolynomialRegressionSeries{
|
||||||
|
InnerSeries: values,
|
||||||
|
Degree: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
_, y := poly.GetValues(i)
|
||||||
|
assert.InDelta(float64(i*i), y, matrix.DefaultEpsilon)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
util "github.com/blendlabs/go-util"
|
||||||
"git.fireandbrimst.one/aw/go-chart/util"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PNG returns a new png/raster renderer.
|
// PNG returns a new png/raster renderer.
|
||||||
|
@ -49,10 +49,6 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
|
||||||
rr.gc.SetDPI(dpi)
|
rr.gc.SetDPI(dpi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetClassName implements the interface method. However, PNGs have no classes.
|
|
||||||
func (vr *rasterRenderer) SetClassName(_ string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStrokeColor implements the interface method.
|
// SetStrokeColor implements the interface method.
|
||||||
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
||||||
rr.s.StrokeColor = c
|
rr.s.StrokeColor = c
|
||||||
|
|
|
@ -3,8 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"git.fireandbrimst.one/aw/go-chart/drawing"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fireandbrimst.one/aw/golang-freetype/truetype"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Renderer represents the basic methods required to draw a chart.
|
// Renderer represents the basic methods required to draw a chart.
|
||||||
|
@ -18,9 +18,6 @@ type Renderer interface {
|
||||||
// SetDPI sets the DPI for the renderer.
|
// SetDPI sets the DPI for the renderer.
|
||||||
SetDPI(dpi float64)
|
SetDPI(dpi float64)
|
||||||
|
|
||||||
// SetClassName sets the current class name.
|
|
||||||
SetClassName(string)
|
|
||||||
|
|
||||||
// SetStrokeColor sets the current stroke color.
|
// SetStrokeColor sets the current stroke color.
|
||||||
SetStrokeColor(drawing.Color)
|
SetStrokeColor(drawing.Color)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
util "git.fireandbrimst.one/aw/go-chart/util"
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
192
seq/buffer_test.go
Normal file
192
seq/buffer_test.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuffer(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewBuffer()
|
||||||
|
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
assert.Equal(1, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(1, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(2)
|
||||||
|
assert.Equal(2, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(2, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(3)
|
||||||
|
assert.Equal(3, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(3, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(4)
|
||||||
|
assert.Equal(4, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(4, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(5)
|
||||||
|
assert.Equal(5, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(5, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(6)
|
||||||
|
assert.Equal(6, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(6, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(7)
|
||||||
|
assert.Equal(7, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(7, buffer.PeekBack())
|
||||||
|
|
||||||
|
buffer.Enqueue(8)
|
||||||
|
assert.Equal(8, buffer.Len())
|
||||||
|
assert.Equal(1, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value := buffer.Dequeue()
|
||||||
|
assert.Equal(1, value)
|
||||||
|
assert.Equal(7, buffer.Len())
|
||||||
|
assert.Equal(2, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(2, value)
|
||||||
|
assert.Equal(6, buffer.Len())
|
||||||
|
assert.Equal(3, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(3, value)
|
||||||
|
assert.Equal(5, buffer.Len())
|
||||||
|
assert.Equal(4, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(4, value)
|
||||||
|
assert.Equal(4, buffer.Len())
|
||||||
|
assert.Equal(5, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(5, value)
|
||||||
|
assert.Equal(3, buffer.Len())
|
||||||
|
assert.Equal(6, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(6, value)
|
||||||
|
assert.Equal(2, buffer.Len())
|
||||||
|
assert.Equal(7, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(7, value)
|
||||||
|
assert.Equal(1, buffer.Len())
|
||||||
|
assert.Equal(8, buffer.Peek())
|
||||||
|
assert.Equal(8, buffer.PeekBack())
|
||||||
|
|
||||||
|
value = buffer.Dequeue()
|
||||||
|
assert.Equal(8, value)
|
||||||
|
assert.Equal(0, buffer.Len())
|
||||||
|
assert.Zero(buffer.Peek())
|
||||||
|
assert.Zero(buffer.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferClear(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewBuffer()
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
|
||||||
|
assert.Equal(8, buffer.Len())
|
||||||
|
|
||||||
|
buffer.Clear()
|
||||||
|
assert.Equal(0, buffer.Len())
|
||||||
|
assert.Zero(buffer.Peek())
|
||||||
|
assert.Zero(buffer.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferArray(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewBuffer()
|
||||||
|
buffer.Enqueue(1)
|
||||||
|
buffer.Enqueue(2)
|
||||||
|
buffer.Enqueue(3)
|
||||||
|
buffer.Enqueue(4)
|
||||||
|
buffer.Enqueue(5)
|
||||||
|
|
||||||
|
contents := buffer.Array()
|
||||||
|
assert.Len(contents, 5)
|
||||||
|
assert.Equal(1, contents[0])
|
||||||
|
assert.Equal(2, contents[1])
|
||||||
|
assert.Equal(3, contents[2])
|
||||||
|
assert.Equal(4, contents[3])
|
||||||
|
assert.Equal(5, contents[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferEach(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buffer := NewBuffer()
|
||||||
|
|
||||||
|
for x := 1; x < 17; x++ {
|
||||||
|
buffer.Enqueue(float64(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
called := 0
|
||||||
|
buffer.Each(func(_ int, v float64) {
|
||||||
|
if v == float64(called+1) {
|
||||||
|
called++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(16, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBuffer(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
empty := NewBuffer()
|
||||||
|
assert.NotNil(empty)
|
||||||
|
assert.Zero(empty.Len())
|
||||||
|
assert.Equal(bufferDefaultCapacity, empty.Capacity())
|
||||||
|
assert.Zero(empty.Peek())
|
||||||
|
assert.Zero(empty.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBufferWithValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := NewBuffer(1, 2, 3, 4, 5)
|
||||||
|
assert.NotNil(values)
|
||||||
|
assert.Equal(5, values.Len())
|
||||||
|
assert.Equal(1, values.Peek())
|
||||||
|
assert.Equal(5, values.PeekBack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferGrowth(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := NewBuffer(1, 2, 3, 4, 5)
|
||||||
|
for i := 0; i < 1<<10; i++ {
|
||||||
|
values.Enqueue(float64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(1<<10-1, values.PeekBack())
|
||||||
|
}
|
48
seq/linear_test.go
Normal file
48
seq/linear_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRange(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := Range(1, 100)
|
||||||
|
assert.Len(values, 100)
|
||||||
|
assert.Equal(1, values[0])
|
||||||
|
assert.Equal(100, values[99])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeWithStep(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := RangeWithStep(0, 100, 5)
|
||||||
|
assert.Equal(100, values[20])
|
||||||
|
assert.Len(values, 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRangeReversed(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
values := Range(10.0, 1.0)
|
||||||
|
assert.Equal(10, len(values))
|
||||||
|
assert.Equal(10.0, values[0])
|
||||||
|
assert.Equal(1.0, values[9])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValuesRegression(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// note; this assumes a 1.0 step is implicitly set in the constructor.
|
||||||
|
linearProvider := NewLinear().WithStart(1.0).WithEnd(100.0)
|
||||||
|
assert.Equal(1, linearProvider.Start())
|
||||||
|
assert.Equal(100, linearProvider.End())
|
||||||
|
assert.Equal(100, linearProvider.Len())
|
||||||
|
|
||||||
|
values := Seq{Provider: linearProvider}.Array()
|
||||||
|
assert.Len(values, 100)
|
||||||
|
assert.Equal(1.0, values[0])
|
||||||
|
assert.Equal(100, values[99])
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ func RandomValues(count int) []float64 {
|
||||||
return Seq{NewRandom().WithLen(count)}.Array()
|
return Seq{NewRandom().WithLen(count)}.Array()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandomValuesWithMax returns an array of random values with a given average.
|
// RandomValuesWithAverage returns an array of random values with a given average.
|
||||||
func RandomValuesWithMax(count int, max float64) []float64 {
|
func RandomValuesWithMax(count int, max float64) []float64 {
|
||||||
return Seq{NewRandom().WithMax(max).WithLen(count)}.Array()
|
return Seq{NewRandom().WithMax(max).WithLen(count)}.Array()
|
||||||
}
|
}
|
||||||
|
|
20
seq/random_test.go
Normal file
20
seq/random_test.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRandomRegression(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
randomProvider := NewRandom().WithLen(4096).WithMax(256)
|
||||||
|
assert.Equal(4096, randomProvider.Len())
|
||||||
|
assert.Equal(256, *randomProvider.Max())
|
||||||
|
|
||||||
|
randomSequence := New(randomProvider)
|
||||||
|
randomValues := randomSequence.Array()
|
||||||
|
assert.Len(randomValues, 4096)
|
||||||
|
assert.InDelta(128, randomSequence.Average(), 10.0)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user