Compare commits

...

41 Commits

Author SHA1 Message Date
gutmet
3e6f54fa68 fix paths from git.gutmet.org to fireandbrimst.one/aw 2023-09-21 21:33:55 +02:00
Alexander Weinhold
81e9cbaa9d remove any mention of github.com/blend 2018-12-06 18:02:38 +01:00
Alexander Weinhold
ac59e921f6 remove last mentions of old repo 2018-12-06 17:50:32 +01:00
Alexander Weinhold
075ec865cb change import paths 2018-12-06 16:57:29 +01:00
Alexander Weinhold
1f5fe236d0 change import paths 2018-12-06 16:46:27 +01:00
Alexander Weinhold
ee46aad5c1 change import paths 2018-12-06 16:38:58 +01:00
Justin Kromlinger
3cb33d48d3 Add ability to set custom stylesheets for SVG renderer (#105)
* Add ability to set custom stylesheets for SVG renderer

This allow to set custom inline CSS and a optional CSP nonce. This
solves the problem mentioned in #103 and is best used with it, as seen
in the added examples. Without this one would have to write a custom
renderer.

* Add note with link to the custom_stylesheets example
2018-10-12 09:43:30 -07:00
Will Charczuk
96acfc6a9f switching the build badge 2018-10-12 09:35:03 -07:00
Will Charczuk
31d235310c removing travis references 2018-10-12 09:28:44 -07:00
Will Charczuk
70d5b73afd fixing build 2018-10-12 09:26:46 -07:00
Will Charczuk
f5889c93ae copy pasta 2018-10-12 09:24:20 -07:00
Will Charczuk
4e6c06ca87 removing coverage artifact 2018-10-12 09:23:56 -07:00
Will Charczuk
3e352f140b adds circle ci 2018-10-12 09:23:30 -07:00
Justin Kromlinger
f97f94425f Add ability to set CSS classes instead of inline styles (#103)
* Add ability to set CSS classes instead of inline styles

This allows to set a `ClassName` field in `Style` structs. Setting this
field to anything but "" will cause all other styles to be ignored. The
element will then have a `class=` tag instead with the corresponding name.

Possible reasons to use this:
* Including multiple SVGs on the same webside, using the same styles
* Desire to use strict CSP headers

* Add warning that setting `ClassName` will drop all other inline styles
2018-10-11 17:21:46 -07:00
Bernhard Reisenberger
6735e8990a draw circle if single value; do not position text on negative coordinates (#82) 2018-10-11 17:21:06 -07:00
nptrx
865ff54ab9 unification of sample and test coding styles will improve visibility (#67)
* "Style{Show: true}" to "StyleShow()"
* "Box{IsSet: true}" to "BoxZero"
2018-10-11 17:20:44 -07:00
Will Charczuk
0fb4aa53e9 adds a rerender example 2018-10-11 17:18:46 -07:00
MinJae Kwon
828d1952d8 Put a space between the badges (#101) 2018-10-04 23:57:51 -07:00
Will Charczuk
872b97b99f removing example 2018-09-10 13:14:46 -07:00
Will Charczuk
a7ff82d63f lots of changes 2018-09-10 13:11:25 -07:00
Will Charczuk
0e849b11bb sequence tweaks, removing market hours anything 2018-09-10 13:08:20 -07:00
Will Charczuk
1a09989055 fixing issues 2018-09-07 15:25:58 -07:00
Will Charczuk
1555902fc4 updates + tests 2018-09-07 12:52:30 -07:00
Will Charczuk
1144b80a46 latest go 2018-09-07 11:19:23 -07:00
Will Charczuk
1f159d195f adding IntValueFormatter 2018-09-07 11:17:11 -07:00
Yuji Yaginuma
4ed65028e4 Fix func name typo in examples (#91)
It seems that `RandomValuesWithMax` is correct.
9e3a080aa3/seq/random.go (L15)
2018-09-05 08:45:45 -07:00
David Mis
ac680bd82d Fixed order of arguments to assert.Len in test files. (#93)
* Fixed order of arguments to assert.Len in test files.

* Added BaseValue funtionality to bar chart
2018-09-05 08:45:19 -07:00
David Mis
3edccc4758 Added BaseValue funtionality to bar chart (#94) 2018-09-05 08:44:49 -07:00
Michael Bruce
d667b8c983 spelling correction (#98) 2018-09-05 08:43:52 -07:00
Andrew Poydence
0506f74600 Fix typo in example (#100) 2018-09-05 08:43:35 -07:00
Will Charczuk
9e3a080aa3 profanity tweaks 2018-04-15 16:53:01 -07:00
Will Charczuk
44990c63ed Merge branch 'master' of github.com:wcharczuk/go-chart 2018-04-15 12:43:40 -07:00
Will Charczuk
1ebbcf493d adding profanity checks 2018-04-15 12:43:32 -07:00
Nat Welch
62338336c3 Update .gitignore (#73)
* Delete .DS_Store

* Update .gitignore w/ common exclusions

From https://github.com/github/gitignore/blob/master/Go.gitignore
2018-04-15 12:36:03 -07:00
Edwin
2dc8482db3 allow 'zero y-range delta' (#72) 2018-04-15 12:35:39 -07:00
Will Charczuk
7c3982fe3d fixing tests 2018-04-05 00:47:39 -07:00
Will Charczuk
70e6cfddc5 fixing find and replace issue 2018-04-05 00:42:38 -07:00
Will Charczuk
df14434b6e changing assert 2018-04-05 00:36:12 -07:00
Will Charczuk
7d28470055 removing dep on go-util from blend 2018-04-04 22:06:34 -07:00
oneumyvakin
11e380634b Port changes 'fixing styling issues w/ the stack bar chart.' from commit a0ea012903 (#66) 2018-01-24 09:43:24 -08:00
Will Charczuk
f72f7fd57b fix for issues/56 2017-10-12 13:29:55 -07:00
135 changed files with 1239 additions and 5278 deletions

BIN
.DS_Store vendored

Binary file not shown.

18
.gitignore vendored
View File

@ -1 +1,19 @@
# 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
.DS_Store
coverage.html

View File

@ -1,13 +0,0 @@
language: go
go:
- 1.6.2
sudo: false
before_script:
- go get -u github.com/blendlabs/go-assert
- go get ./...
script:
- go test ./...

1
COVERAGE Normal file
View File

@ -0,0 +1 @@
70.89

View File

@ -1,9 +0,0 @@
all: test
test:
@go test ./...
cover:
@go test -short -covermode=set -coverprofile=profile.cov
@go tool cover -html=profile.cov
@rm profile.cov

View File

@ -1,99 +0,0 @@
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.

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -15,14 +15,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true, //enables / displays the x-axis
},
Style: chart.StyleShow(), //enables / displays the x-axis
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the y-axis
},
Style: chart.StyleShow(), //enables / displays the y-axis
},
Series: []chart.Series{
chart.ContinuousSeries{

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -6,20 +6,23 @@ import (
"net/http"
"os"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
sbc := chart.BarChart{
Title: "Test Bar Chart",
TitleStyle: chart.StyleShow(),
Background: chart.Style{
Padding: chart.Box{
Top: 40,
},
},
Height: 512,
BarWidth: 60,
XAxis: chart.Style{
Show: true,
},
XAxis: chart.StyleShow(),
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
Bars: []chart.Value{
{Value: 5.25, Label: "Blue"},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,88 @@
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.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -4,7 +4,7 @@ import (
"log"
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -8,7 +8,7 @@ import (
"strconv"
"time"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func random(min, max float64) float64 {

View File

@ -0,0 +1,58 @@
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))
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -16,9 +16,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
ValueFormatter: func(v interface{}) string {
if vf, isFloat := v.(float64); isFloat {
return fmt.Sprintf("%0.6f", vf)

View File

@ -3,9 +3,9 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -20,14 +20,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
FillColor: drawing.ColorFromHex("efefef"),
},
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
Series: []chart.Series{
chart.ContinuousSeries{
@ -47,14 +43,10 @@ func drawChartDefault(res http.ResponseWriter, req *http.Request) {
FillColor: drawing.ColorFromHex("efefef"),
},
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
Series: []chart.Series{
chart.ContinuousSeries{

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -14,9 +14,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
Range: &chart.ContinuousRange{
Min: 0.0,
Max: 10.0,

View File

@ -3,8 +3,8 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/drawing"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -0,0 +1,21 @@
<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>

After

Width:  |  Height:  |  Size: 987 B

View File

@ -0,0 +1,87 @@
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))
}

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -14,9 +14,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
Range: &chart.ContinuousRange{
Min: 0.0,
Max: 4.0,

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -20,17 +20,13 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
Height: 500,
Width: 500,
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
/*Range: &chart.ContinuousRange{
Descending: true,
},*/
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
Range: &chart.ContinuousRange{
Descending: true,
},

View File

@ -4,7 +4,7 @@ import (
"fmt"
"log"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func main() {

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -16,10 +16,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
},
YAxis: chart.YAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
},
Background: chart.Style{
Padding: chart.Box{

View File

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -16,10 +16,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
},
YAxis: chart.YAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
},
Background: chart.Style{
Padding: chart.Box{

View File

@ -3,8 +3,8 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -16,8 +16,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
mainSeries := chart.ContinuousSeries{
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.
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 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.
}
// note we create a LinearRegressionSeries series by assignin the inner series.

View File

@ -1,46 +0,0 @@
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)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

View File

@ -3,15 +3,15 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
mainSeries := chart.ContinuousSeries{
Name: "A test series",
XValues: seq.Range(1.0, 100.0),
YValues: seq.New(seq.NewRandom().WithLen(100).WithAverage(100).WithScale(50)).Array(),
YValues: seq.New(seq.NewRandom().WithLen(100).WithMax(150).WithMin(50)).Array(),
}
minSeries := &chart.MinSeries{

View File

@ -5,7 +5,7 @@ import (
"log"
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -30,7 +30,26 @@ 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() {
http.HandleFunc("/", drawChart)
http.HandleFunc("/reg", drawChartRegression)
log.Fatal(http.ListenAndServe(":8080", nil))
}

View File

@ -0,0 +1,17 @@
<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>

After

Width:  |  Height:  |  Size: 565 B

View File

@ -3,8 +3,8 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -16,8 +16,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
mainSeries := chart.ContinuousSeries{
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.
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 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.
}
polyRegSeries := &chart.PolynomialRegressionSeries{

View File

@ -7,8 +7,8 @@ import (
"strings"
"time"
"github.com/wcharczuk/go-chart"
util "github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart"
util "git.fireandbrimst.one/aw/go-chart/util"
)
func parseInt(str string) int {
@ -105,9 +105,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
},
},
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
ValueFormatter: chart.TimeHourValueFormatter,
GridMajorStyle: chart.Style{
Show: true,

View File

@ -0,0 +1,50 @@
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))
}

View File

@ -6,9 +6,9 @@ import (
_ "net/http/pprof"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -44,10 +44,10 @@ func unit(res http.ResponseWriter, req *http.Request) {
Height: 50,
Width: 50,
Canvas: chart.Style{
Padding: chart.Box{IsSet: true},
Padding: chart.BoxZero,
},
Background: chart.Style{
Padding: chart.Box{IsSet: true},
Padding: chart.BoxZero,
},
Series: []chart.Series{
chart.ContinuousSeries{

View File

@ -3,8 +3,8 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/seq"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -5,7 +5,7 @@ import (
"log"
"net/http"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -18,12 +18,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
},
},
Height: 512,
XAxis: chart.Style{
Show: true,
},
YAxis: chart.Style{
Show: true,
},
XAxis: chart.StyleShow(),
YAxis: chart.StyleShow(),
Bars: []chart.StackedBar{
{
Name: "This is a very long string to test word break wrapping.",

View File

@ -4,8 +4,8 @@ import (
"net/http"
"time"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/drawing"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -43,11 +43,11 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
TickPosition: chart.TickPositionBetweenTicks,
},
YAxis: chart.YAxis{
Style: chart.Style{Show: true},
Style: chart.StyleShow(),
Range: &chart.ContinuousRange{
Max: 220.0,
Min: 180.0,

View File

@ -3,8 +3,8 @@ package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart"
"git.fireandbrimst.one/aw/go-chart/drawing"
)
func drawChart(res http.ResponseWriter, req *http.Request) {

View File

@ -4,19 +4,17 @@ import (
"net/http"
"time"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
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.
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropariate formatter to use for the ticks.
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropriate formatter to use for the ticks.
*/
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
},
Series: []chart.Series{
chart.TimeSeries{
@ -48,9 +46,7 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
*/
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
Style: chart.StyleShow(),
ValueFormatter: chart.TimeHourValueFormatter,
},
Series: []chart.Series{
@ -79,7 +75,7 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
func main() {
http.HandleFunc("/", drawChart)
http.HandleFunc("/favico.ico", func(res http.ResponseWriter, req *http.Request) {
http.HandleFunc("/favicon.ico", func(res http.ResponseWriter, req *http.Request) {
res.Write([]byte{})
})
http.HandleFunc("/custom", drawCustomChart)

View File

@ -4,8 +4,8 @@ import (
"fmt"
"net/http"
"github.com/wcharczuk/go-chart"
util "github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart"
util "git.fireandbrimst.one/aw/go-chart/util"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
@ -18,9 +18,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true, //enables / displays the x-axis
},
Style: chart.StyleShow(), //enables / displays the x-axis
TickPosition: chart.TickPositionBetweenTicks,
ValueFormatter: func(v interface{}) string {
typed := v.(float64)
@ -29,14 +27,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
},
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the y-axis
},
Style: chart.StyleShow(), //enables / displays the y-axis
},
YAxisSecondary: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the secondary y-axis
},
Style: chart.StyleShow(), //enables / displays the secondary y-axis
},
Series: []chart.Series{
chart.ContinuousSeries{

View File

@ -5,7 +5,7 @@ import (
"log"
"os"
"github.com/wcharczuk/go-chart"
"git.fireandbrimst.one/aw/go-chart"
)
func main() {
@ -14,10 +14,8 @@ func main() {
b = 1000
ts1 := chart.ContinuousSeries{ //TimeSeries{
Name: "Time Series",
Style: chart.Style{
Show: true,
},
Name: "Time Series",
Style: chart.StyleShow(),
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},
}

View File

@ -4,7 +4,12 @@ import (
"fmt"
"math"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
)
// Interface Assertions.
var (
_ Series = (*AnnotationSeries)(nil)
)
// AnnotationSeries is a series of labels on the chart.

View File

@ -1,119 +0,0 @@
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)
}

View File

@ -6,8 +6,8 @@ import (
"io"
"math"
"github.com/golang/freetype/truetype"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// BarChart is a chart that draws bars on a range.
@ -31,6 +31,9 @@ type BarChart struct {
BarSpacing int
UseBaseValue bool
BaseValue float64
Font *truetype.Font
defaultFont *truetype.Font
@ -126,7 +129,7 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
yr = bc.setRangeDomains(canvasBox, yr)
}
bc.drawCanvas(r, canvasBox)
bc.drawBars(r, canvasBox, yr)
bc.drawXAxis(r, canvasBox)
bc.drawYAxis(r, canvasBox, yr, yt)
@ -139,6 +142,10 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
return r.Save(w)
}
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) {
Draw.Box(r, canvasBox, bc.getCanvasStyle())
}
func (bc BarChart) getRanges() Range {
var yrange Range
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
@ -195,11 +202,20 @@ func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
by = canvasBox.Bottom - yr.Translate(bar.Value)
barBox = Box{
Top: by,
Left: bxl,
Right: bxr,
Bottom: canvasBox.Bottom,
if bc.UseBaseValue {
barBox = Box{
Top: by,
Left: bxl,
Right: bxr,
Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue),
}
} else {
barBox = Box{
Top: by,
Left: bxl,
Right: bxr,
Bottom: canvasBox.Bottom,
}
}
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
@ -280,7 +296,32 @@ func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick)
func (bc BarChart) drawTitle(r Renderer) {
if len(bc.Title) > 0 && bc.TitleStyle.Show {
Draw.TextWithin(r, bc.Title, bc.box(), bc.styleDefaultsTitle())
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
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,
}
}
@ -397,8 +438,8 @@ func (bc BarChart) box() Box {
dpb := bc.Background.Padding.GetBottom(50)
return Box{
Top: 20,
Left: 20,
Top: bc.Background.Padding.GetTop(20),
Left: bc.Background.Padding.GetLeft(20),
Right: bc.GetWidth() - dpr,
Bottom: bc.GetHeight() - dpb,
}

View File

@ -1,320 +0,0 @@
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)
}

View File

@ -3,7 +3,12 @@ package chart
import (
"fmt"
"github.com/wcharczuk/go-chart/seq"
"git.fireandbrimst.one/aw/go-chart/seq"
)
// Interface Assertions.
var (
_ Series = (*BollingerBandsSeries)(nil)
)
// BollingerBandsSeries draws bollinger bands for an inner series.

View File

@ -1,53 +0,0 @@
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
View File

@ -4,7 +4,7 @@ import (
"fmt"
"math"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
)
var (

View File

@ -1,188 +0,0 @@
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())
}

View File

@ -6,8 +6,8 @@ import (
"io"
"math"
"github.com/golang/freetype/truetype"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// Chart is what we're drawing.
@ -317,9 +317,6 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
if math.IsNaN(yDelta) {
return errors.New("nan y-range delta")
}
if yDelta == 0 {
return errors.New("zero y-range delta")
}
if c.hasSecondarySeries() {
yraDelta := yra.GetDelta()
@ -329,9 +326,6 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
if math.IsNaN(yraDelta) {
return errors.New("nan secondary y-range delta")
}
if yraDelta == 0 {
return errors.New("zero secondary y-range delta")
}
}
return nil

View File

@ -1,576 +0,0 @@
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))
}

View File

@ -1,6 +1,6 @@
package chart
import "github.com/wcharczuk/go-chart/drawing"
import "git.fireandbrimst.one/aw/go-chart/drawing"
var (
// ColorWhite is white.

View File

@ -1,42 +0,0 @@
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)
}

View File

@ -1,23 +0,0 @@
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))
}

View File

@ -2,6 +2,13 @@ package chart
import "fmt"
// Interface Assertions.
var (
_ Series = (*ContinuousSeries)(nil)
_ FirstValuesProvider = (*ContinuousSeries)(nil)
_ LastValuesProvider = (*ContinuousSeries)(nil)
)
// ContinuousSeries represents a line on a chart.
type ContinuousSeries struct {
Name string
@ -36,6 +43,11 @@ func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
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.
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]

View File

@ -1,73 +0,0 @@
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())
}

View File

@ -3,7 +3,7 @@ package chart
import (
"math"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
)
var (

View File

@ -1,53 +0,0 @@
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())
}

View File

@ -1,35 +0,0 @@
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())
}

View File

@ -3,7 +3,7 @@ package drawing
import (
"image/color"
"github.com/golang/freetype/truetype"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// FillRule defines the type for fill rules

View File

@ -1,8 +1,8 @@
package drawing
import (
"github.com/golang/freetype/raster"
"golang.org/x/image/math/fixed"
"git.fireandbrimst.one/aw/golang-freetype/raster"
"git.fireandbrimst.one/aw/golang-image/math/fixed"
)
// FtLineBuilder is a builder for freetype raster glyphs.

View File

@ -4,7 +4,7 @@ import (
"image"
"image/color"
"github.com/golang/freetype/truetype"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)

View File

@ -4,10 +4,10 @@ import (
"image"
"image/color"
"golang.org/x/image/draw"
"golang.org/x/image/math/f64"
"git.fireandbrimst.one/aw/golang-image/draw"
"git.fireandbrimst.one/aw/golang-image/math/f64"
"github.com/golang/freetype/raster"
"git.fireandbrimst.one/aw/golang-freetype/raster"
)
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter

View File

@ -6,11 +6,11 @@ import (
"image/color"
"math"
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"golang.org/x/image/draw"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"git.fireandbrimst.one/aw/golang-freetype/raster"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
"git.fireandbrimst.one/aw/golang-image/draw"
"git.fireandbrimst.one/aw/golang-image/font"
"git.fireandbrimst.one/aw/golang-image/math/fixed"
)
// NewRasterGraphicContext creates a new Graphic context from an image.
@ -206,7 +206,7 @@ func (rgc *RasterGraphicContext) GetFont() *truetype.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) {
rgc.current.FontSizePoints = fontSizePoints
rgc.recalc()

View File

@ -4,7 +4,7 @@ import (
"image"
"image/color"
"github.com/golang/freetype/truetype"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// StackGraphicContext is a context that does thngs.

View File

@ -1,8 +1,8 @@
package drawing
import (
"github.com/golang/freetype/truetype"
"golang.org/x/image/math/fixed"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
"git.fireandbrimst.one/aw/golang-image/math/fixed"
)
// DrawContour draws the given closed contour at the given sub-pixel offset.

View File

@ -3,10 +3,10 @@ package drawing
import (
"math"
"golang.org/x/image/math/fixed"
"git.fireandbrimst.one/aw/golang-image/math/fixed"
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"git.fireandbrimst.one/aw/golang-freetype/raster"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// PixelsToPoints returns the points for a given number of pixels at a DPI.

View File

@ -7,6 +7,13 @@ const (
DefaultEMAPeriod = 12
)
// Interface Assertions.
var (
_ Series = (*EMASeries)(nil)
_ FirstValuesProvider = (*EMASeries)(nil)
_ LastValuesProvider = (*EMASeries)(nil)
)
// EMASeries is a computed series.
type EMASeries struct {
Name string
@ -66,6 +73,19 @@ func (ema *EMASeries) GetValues(index int) (x, y float64) {
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,
// and recomputing the last moving average chunk.
func (ema *EMASeries) GetLastValues() (x, y float64) {

View File

@ -1,106 +0,0 @@
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)
}

37
first_value_annotation.go Normal file
View File

@ -0,0 +1,37 @@
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},
}
}

View File

@ -3,8 +3,8 @@ package chart
import (
"sync"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/roboto"
"git.fireandbrimst.one/aw/go-chart/roboto"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
var (

View File

@ -1,24 +0,0 @@
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)
}

View File

@ -1,32 +0,0 @@
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
View File

@ -1,6 +1,6 @@
package chart
import "github.com/wcharczuk/go-chart/drawing"
import "git.fireandbrimst.one/aw/go-chart/drawing"
// Jet is a color map provider based on matlab's jet color map.
func Jet(v, vmin, vmax float64) drawing.Color {

View File

@ -1,8 +1,8 @@
package chart
import (
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/util"
)
// Legend returns a legend renderable function.

View File

@ -1,31 +0,0 @@
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())
}

View File

@ -0,0 +1,42 @@
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
}

View File

@ -3,8 +3,16 @@ package chart
import (
"fmt"
"github.com/wcharczuk/go-chart/seq"
util "github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart/seq"
util "git.fireandbrimst.one/aw/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
@ -24,6 +32,19 @@ type LinearRegressionSeries struct {
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.
func (lrs LinearRegressionSeries) GetName() string {
return lrs.Name
@ -72,7 +93,7 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.m == 0 && lrs.b == 0 {
if lrs.IsZero() {
lrs.computeCoefficients()
}
offset := lrs.GetOffset()
@ -82,12 +103,25 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
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.
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.m == 0 && lrs.b == 0 {
if lrs.IsZero() {
lrs.computeCoefficients()
}
endIndex := lrs.GetEndIndex()
@ -96,6 +130,29 @@ func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
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 {
return (xvalue - lrs.avgx) / lrs.stddevx
}
@ -131,17 +188,3 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
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
}

View File

@ -1,78 +0,0 @@
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 Normal file
View File

@ -0,0 +1,119 @@
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
}

View File

@ -1,88 +0,0 @@
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]))
}
}

View File

@ -1,195 +0,0 @@
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
}

View File

@ -1,73 +0,0 @@
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)
}

View File

@ -1,396 +0,0 @@
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))
}

View File

@ -1,22 +0,0 @@
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)
}

View File

@ -6,11 +6,12 @@ import (
"io"
"math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart/util"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
const (
_pi = math.Pi
_pi2 = math.Pi / 2.0
_pi4 = math.Pi / 4.0
)
@ -137,19 +138,26 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
// draw the pie slices
var rads, delta, delta2, total float64
var lx, ly int
for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
if len(values) == 1 {
pc.stylePieChartValue(0).WriteToRenderer(r)
r.MoveTo(cx, cy)
rads = util.Math.PercentToRadians(total)
delta = util.Math.PercentToRadians(v.Value)
r.Circle(radius, cx, cy)
} else {
for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
r.ArcTo(cx, cy, radius, radius, rads, delta)
r.MoveTo(cx, cy)
rads = util.Math.PercentToRadians(total)
delta = util.Math.PercentToRadians(v.Value)
r.LineTo(cx, cy)
r.Close()
r.FillStroke()
total = total + v.Value
r.ArcTo(cx, cy, radius, radius, rads, delta)
r.LineTo(cx, cy)
r.Close()
r.FillStroke()
total = total + v.Value
}
}
// draw the labels
@ -165,6 +173,13 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
lx = lx - (tb.Width() >> 1)
ly = ly + (tb.Height() >> 1)
if lx < 0 {
lx = 0
}
if ly < 0 {
lx = 0
}
r.Text(v.Label, lx, ly)
}
total = total + v.Value

View File

@ -1,69 +0,0 @@
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)
}

View File

@ -4,8 +4,15 @@ import (
"fmt"
"math"
"github.com/wcharczuk/go-chart/matrix"
util "github.com/wcharczuk/go-chart/util"
"git.fireandbrimst.one/aw/go-chart/matrix"
util "git.fireandbrimst.one/aw/go-chart/util"
)
// Interface Assertions.
var (
_ Series = (*PolynomialRegressionSeries)(nil)
_ FirstValuesProvider = (*PolynomialRegressionSeries)(nil)
_ LastValuesProvider = (*PolynomialRegressionSeries)(nil)
)
// PolynomialRegressionSeries implements a polynomial regression over a given
@ -101,6 +108,23 @@ func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
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.
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {

View File

@ -1,35 +0,0 @@
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)
}
}

View File

@ -6,9 +6,9 @@ import (
"io"
"math"
util "github.com/blendlabs/go-util"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/util"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// PNG returns a new png/raster renderer.
@ -49,6 +49,10 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
rr.gc.SetDPI(dpi)
}
// SetClassName implements the interface method. However, PNGs have no classes.
func (vr *rasterRenderer) SetClassName(_ string) {
}
// SetStrokeColor implements the interface method.
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
rr.s.StrokeColor = c

View File

@ -3,8 +3,8 @@ package chart
import (
"io"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"git.fireandbrimst.one/aw/go-chart/drawing"
"git.fireandbrimst.one/aw/golang-freetype/truetype"
)
// Renderer represents the basic methods required to draw a chart.
@ -18,6 +18,9 @@ type Renderer interface {
// SetDPI sets the DPI for the renderer.
SetDPI(dpi float64)
// SetClassName sets the current class name.
SetClassName(string)
// SetStrokeColor sets the current stroke color.
SetStrokeColor(drawing.Color)

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
util "github.com/wcharczuk/go-chart/util"
util "git.fireandbrimst.one/aw/go-chart/util"
)
const (

View File

@ -1,192 +0,0 @@
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())
}

View File

@ -1,48 +0,0 @@
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])
}

View File

@ -11,7 +11,7 @@ func RandomValues(count int) []float64 {
return Seq{NewRandom().WithLen(count)}.Array()
}
// RandomValuesWithAverage returns an array of random values with a given average.
// RandomValuesWithMax returns an array of random values with a given average.
func RandomValuesWithMax(count int, max float64) []float64 {
return Seq{NewRandom().WithMax(max).WithLen(count)}.Array()
}

View File

@ -1,20 +0,0 @@
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