Compare commits
12 commits
main
...
candlestic
Author | SHA1 | Date | |
---|---|---|---|
|
de6df027fc | ||
|
e3e851d2d1 | ||
|
b537fd02cb | ||
|
5cf4f5f0d7 | ||
|
04a4edcb46 | ||
|
5936b89e89 | ||
|
51f3cca5d7 | ||
|
7ba2992824 | ||
|
7d1401898a | ||
|
e39acdfb76 | ||
|
566d798b32 | ||
|
73e3e439c5 |
33
.github/workflows/ci.yml
vendored
|
@ -1,33 +0,0 @@
|
||||||
name: "Continuous Integration"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
paths: [ "*.go" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
paths: [ "*.go" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
name: "Tests"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
GOOS: "linux"
|
|
||||||
GOARCH: "amd64"
|
|
||||||
GO111MODULE: "on"
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: 1.21
|
|
||||||
|
|
||||||
- name: Check out go-incr
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Run all tests
|
|
||||||
run: go test ./...
|
|
18
.gitignore
vendored
|
@ -1,20 +1,2 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
|
||||||
.glide/
|
|
||||||
|
|
||||||
# Other
|
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
coverage.html
|
|
||||||
.idea
|
|
||||||
|
|
13
.travis.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.1
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u github.com/blendlabs/go-assert
|
||||||
|
- go get ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test ./...
|
1
COVERAGE
|
@ -1 +0,0 @@
|
||||||
29.02
|
|
1
LICENSE
|
@ -1,7 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 William Charczuk.
|
Copyright (c) 2016 William Charczuk.
|
||||||
Copyright (c) 2024 Zeni Kim.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
13
Makefile
|
@ -1,10 +1,9 @@
|
||||||
all: new-install test
|
all: test
|
||||||
|
|
||||||
new-install:
|
|
||||||
@go get -v -u ./...
|
|
||||||
|
|
||||||
generate:
|
|
||||||
@go generate ./...
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./...
|
@go test ./...
|
||||||
|
|
||||||
|
cover:
|
||||||
|
@go test -short -covermode=set -coverprofile=profile.cov
|
||||||
|
@go tool cover -html=profile.cov
|
||||||
|
@rm profile.cov
|
|
@ -1,4 +0,0 @@
|
||||||
go-sdk:
|
|
||||||
excludeFiles: [ "*_test.go" ]
|
|
||||||
importsContain: [ github.com/blend/go-sdk/* ]
|
|
||||||
description: "please don't use go-sdk in this repo"
|
|
35
README.md
|
@ -1,18 +1,20 @@
|
||||||
go-chart
|
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)
|
||||||
|
|
||||||
This project starts from a full copy from [https://git.smarteching.com/zeni/go-chart](https://git.smarteching.com/zeni/go-chart). 28 Oct 2024.
|
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 v3.x codebase, which overhauls the api significantly. Per usual, see `examples` for more information.
|
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
|
# Installation
|
||||||
|
|
||||||
To install `chart` run the following:
|
To install `chart` run the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
> go get git.smarteching.com/zeni/go-chart/v2@latest
|
> go get -u github.com/wcharczuk/go-chart
|
||||||
```
|
```
|
||||||
|
|
||||||
Most of the components are interchangeable so feel free to crib whatever you want.
|
Most of the components are interchangeable so feel free to crib whatever you want.
|
||||||
|
@ -21,35 +23,33 @@ Most of the components are interchangeable so feel free to crib whatever you wan
|
||||||
|
|
||||||
Spark Lines:
|
Spark Lines:
|
||||||
|
|
||||||
![](https://git.smarteching.com/zeni/go-chart/raw/branch/main/_images/tvix_ltm.png)
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/tvix_ltm.png)
|
||||||
|
|
||||||
Single axis:
|
Single axis:
|
||||||
|
|
||||||
![](https://git.smarteching.com/zeni/go-chart/raw/branch/main/_images/goog_ltm.png)
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/goog_ltm.png)
|
||||||
|
|
||||||
Two axis:
|
Two axis:
|
||||||
|
|
||||||
![](https://git.smarteching.com/zeni/go-chart/raw/branch/main/_images/two_axis.png)
|
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/two_axis.png)
|
||||||
|
|
||||||
# Other Chart Types
|
# Other Chart Types
|
||||||
|
|
||||||
Pie Chart:
|
Pie Chart:
|
||||||
|
|
||||||
![](https://git.smarteching.com/zeni/go-chart/raw/branch/main/_images/pie_chart.png)
|
![](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`.
|
The code for this chart can be found in `_examples/pie_chart/main.go`.
|
||||||
|
|
||||||
Stacked Bar:
|
Stacked Bar:
|
||||||
|
|
||||||
![](https://git.smarteching.com/zeni/go-chart/raw/branch/main/_images/stacked_bar.png)
|
![](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`.
|
The code for this chart can be found in `_examples/stacked_bar/main.go`.
|
||||||
|
|
||||||
# Code Examples
|
# Code Examples
|
||||||
|
|
||||||
Actual chart configurations and examples can be found in the `./examples/` directory. They are simple CLI programs that write to `output.png` (they are also updated with `go generate`.
|
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.
|
||||||
|
|
||||||
If folder ends in "web", has web servers, so start them with `go run main.go` then access `http://localhost:8080` to see the output.
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ import (
|
||||||
...
|
...
|
||||||
"bytes"
|
"bytes"
|
||||||
...
|
...
|
||||||
"git.smarteching.com/zeni/go-chart/v2" //exposes "chart"
|
"github.com/wcharczuk/go-chart" //exposes "chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
|
@ -83,7 +83,8 @@ Here, we have a single series with x range values as float64s, rendered to a PNG
|
||||||
|
|
||||||
# API Overview
|
# 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.
|
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.
|
The best way to see the api in action is to look at the examples in the `./_examples/` directory.
|
||||||
|
|
||||||
|
@ -95,4 +96,4 @@ The goal with the API itself is to have the "zero value be useful", and to requi
|
||||||
|
|
||||||
# Contributions
|
# Contributions
|
||||||
|
|
||||||
Contributions are welcome though this library is in a holding pattern for the forseable future.
|
This library is super early but contributions are welcome.
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
aliceblue #f0f8ff 240,248,255
|
|
||||||
antiquewhite #faebd7 250,235,215
|
|
||||||
aqua #00ffff 0,255,255
|
|
||||||
aquamarine #7fffd4 127,255,212
|
|
||||||
azure #f0ffff 240,255,255
|
|
||||||
beige #f5f5dc 245,245,220
|
|
||||||
bisque #ffe4c4 255,228,196
|
|
||||||
black #000000 0,0,0
|
|
||||||
blanchedalmond #ffebcd 255,235,205
|
|
||||||
blue #0000ff 0,0,255
|
|
||||||
blueviolet #8a2be2 138,43,226
|
|
||||||
brown #a52a2a 165,42,42
|
|
||||||
burlywood #deb887 222,184,135
|
|
||||||
cadetblue #5f9ea0 95,158,160
|
|
||||||
chartreuse #7fff00 127,255,0
|
|
||||||
chocolate #d2691e 210,105,30
|
|
||||||
coral #ff7f50 255,127,80
|
|
||||||
cornflowerblue #6495ed 100,149,237
|
|
||||||
cornsilk #fff8dc 255,248,220
|
|
||||||
crimson #dc143c 220,20,60
|
|
||||||
cyan #00ffff 0,255,255
|
|
||||||
darkblue #00008b 0,0,139
|
|
||||||
darkcyan #008b8b 0,139,139
|
|
||||||
darkgoldenrod #b8860b 184,134,11
|
|
||||||
darkgray #a9a9a9 169,169,169
|
|
||||||
darkgreen #006400 0,100,0
|
|
||||||
darkgrey #a9a9a9 169,169,169
|
|
||||||
darkkhaki #bdb76b 189,183,107
|
|
||||||
darkmagenta #8b008b 139,0,139
|
|
||||||
darkolivegreen #556b2f 85,107,47
|
|
||||||
darkorange #ff8c00 255,140,0
|
|
||||||
darkorchid #9932cc 153,50,204
|
|
||||||
darkred #8b0000 139,0,0
|
|
||||||
darksalmon #e9967a 233,150,122
|
|
||||||
darkseagreen #8fbc8f 143,188,143
|
|
||||||
darkslateblue #483d8b 72,61,139
|
|
||||||
darkslategray #2f4f4f 47,79,79
|
|
||||||
darkslategrey #2f4f4f 47,79,79
|
|
||||||
darkturquoise #00ced1 0,206,209
|
|
||||||
darkviolet #9400d3 148,0,211
|
|
||||||
deeppink #ff1493 255,20,147
|
|
||||||
deepskyblue #00bfff 0,191,255
|
|
||||||
dimgray #696969 105,105,105
|
|
||||||
dimgrey #696969 105,105,105
|
|
||||||
dodgerblue #1e90ff 30,144,255
|
|
||||||
firebrick #b22222 178,34,34
|
|
||||||
floralwhite #fffaf0 255,250,240
|
|
||||||
forestgreen #228b22 34,139,34
|
|
||||||
fuchsia #ff00ff 255,0,255
|
|
||||||
gainsboro #dcdcdc 220,220,220
|
|
||||||
ghostwhite #f8f8ff 248,248,255
|
|
||||||
gold #ffd700 255,215,0
|
|
||||||
goldenrod #daa520 218,165,32
|
|
||||||
gray #808080 128,128,128
|
|
||||||
green #008000 0,128,0
|
|
||||||
greenyellow #adff2f 173,255,47
|
|
||||||
grey #808080 128,128,128
|
|
||||||
honeydew #f0fff0 240,255,240
|
|
||||||
hotpink #ff69b4 255,105,180
|
|
||||||
indianred #cd5c5c 205,92,92
|
|
||||||
indigo #4b0082 75,0,130
|
|
||||||
ivory #fffff0 255,255,240
|
|
||||||
khaki #f0e68c 240,230,140
|
|
||||||
lavender #e6e6fa 230,230,250
|
|
||||||
lavenderblush #fff0f5 255,240,245
|
|
||||||
lawngreen #7cfc00 124,252,0
|
|
||||||
lemonchiffon #fffacd 255,250,205
|
|
||||||
lightblue #add8e6 173,216,230
|
|
||||||
lightcoral #f08080 240,128,128
|
|
||||||
lightcyan #e0ffff 224,255,255
|
|
||||||
lightgoldenrodyellow #fafad2 250,250,210
|
|
||||||
lightgray #d3d3d3 211,211,211
|
|
||||||
lightgreen #90ee90 144,238,144
|
|
||||||
lightgrey #d3d3d3 211,211,211
|
|
||||||
lightpink #ffb6c1 255,182,193
|
|
||||||
lightsalmon #ffa07a 255,160,122
|
|
||||||
lightseagreen #20b2aa 32,178,170
|
|
||||||
lightskyblue #87cefa 135,206,250
|
|
||||||
lightslategray #778899 119,136,153
|
|
||||||
lightslategrey #778899 119,136,153
|
|
||||||
lightsteelblue #b0c4de 176,196,222
|
|
||||||
lightyellow #ffffe0 255,255,224
|
|
||||||
lime #00ff00 0,255,0
|
|
||||||
limegreen #32cd32 50,205,50
|
|
||||||
linen #faf0e6 250,240,230
|
|
||||||
magenta #ff00ff 255,0,255
|
|
||||||
maroon #800000 128,0,0
|
|
||||||
mediumaquamarine #66cdaa 102,205,170
|
|
||||||
mediumblue #0000cd 0,0,205
|
|
||||||
mediumorchid #ba55d3 186,85,211
|
|
||||||
mediumpurple #9370db 147,112,219
|
|
||||||
mediumseagreen #3cb371 60,179,113
|
|
||||||
mediumslateblue #7b68ee 123,104,238
|
|
||||||
mediumspringgreen #00fa9a 0,250,154
|
|
||||||
mediumturquoise #48d1cc 72,209,204
|
|
||||||
mediumvioletred #c71585 199,21,133
|
|
||||||
midnightblue #191970 25,25,112
|
|
||||||
mintcream #f5fffa 245,255,250
|
|
||||||
mistyrose #ffe4e1 255,228,225
|
|
||||||
moccasin #ffe4b5 255,228,181
|
|
||||||
navajowhite #ffdead 255,222,173
|
|
||||||
navy #000080 0,0,128
|
|
||||||
oldlace #fdf5e6 253,245,230
|
|
||||||
olive #808000 128,128,0
|
|
||||||
olivedrab #6b8e23 107,142,35
|
|
||||||
orange #ffa500 255,165,0
|
|
||||||
orangered #ff4500 255,69,0
|
|
||||||
orchid #da70d6 218,112,214
|
|
||||||
palegoldenrod #eee8aa 238,232,170
|
|
||||||
palegreen #98fb98 152,251,152
|
|
||||||
paleturquoise #afeeee 175,238,238
|
|
||||||
palevioletred #db7093 219,112,147
|
|
||||||
papayawhip #ffefd5 255,239,213
|
|
||||||
peachpuff #ffdab9 255,218,185
|
|
||||||
peru #cd853f 205,133,63
|
|
||||||
pink #ffc0cb 255,192,203
|
|
||||||
plum #dda0dd 221,160,221
|
|
||||||
powderblue #b0e0e6 176,224,230
|
|
||||||
purple #800080 128,0,128
|
|
||||||
red #ff0000 255,0,0
|
|
||||||
rosybrown #bc8f8f 188,143,143
|
|
||||||
royalblue #4169e1 65,105,225
|
|
||||||
saddlebrown #8b4513 139,69,19
|
|
||||||
salmon #fa8072 250,128,114
|
|
||||||
sandybrown #f4a460 244,164,96
|
|
||||||
seagreen #2e8b57 46,139,87
|
|
||||||
seashell #fff5ee 255,245,238
|
|
||||||
sienna #a0522d 160,82,45
|
|
||||||
silver #c0c0c0 192,192,192
|
|
||||||
skyblue #87ceeb 135,206,235
|
|
||||||
slateblue #6a5acd 106,90,205
|
|
||||||
slategray #708090 112,128,144
|
|
||||||
slategrey #708090 112,128,144
|
|
||||||
snow #fffafa 255,250,250
|
|
||||||
springgreen #00ff7f 0,255,127
|
|
||||||
steelblue #4682b4 70,130,180
|
|
||||||
tan #d2b48c 210,180,140
|
|
||||||
teal #008080 0,128,128
|
|
||||||
thistle #d8bfd8 216,191,216
|
|
||||||
tomato #ff6347 255,99,71
|
|
||||||
turquoise #40e0d0 64,224,208
|
|
||||||
violet #ee82ee 238,130,238
|
|
||||||
wheat #f5deb3 245,222,179
|
|
||||||
white #ffffff 255,255,255
|
|
||||||
whitesmoke #f5f5f5 245,245,245
|
|
||||||
yellow #ffff00 255,255,0
|
|
||||||
yellowgreen #9acd32 154,205,50
|
|
|
@ -1,14 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add an `Annotation` series, which is a special type of series that
|
In this example we add an `Annotation` series, which is a special type of series that
|
||||||
draws annotation labels at given X and Y values (as translated by their respective ranges).
|
draws annotation labels at given X and Y values (as translated by their respective ranges).
|
||||||
|
@ -38,7 +37,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/annotations/output.png
Normal file
After Width: | Height: | Size: 14 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
||||||
|
@ -17,14 +15,19 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Name: "The XAxis",
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the x-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Name: "The YAxis",
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the y-axis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
},
|
},
|
||||||
|
@ -34,7 +37,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/axes/output.png
Normal file
After Width: | Height: | Size: 22 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
||||||
|
@ -16,9 +14,20 @@ func main() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Name: "The XAxis",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Name: "The YAxis",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
},
|
},
|
||||||
|
@ -28,7 +37,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/axes_labels/output.png
Normal file
After Width: | Height: | Size: 24 KiB |
|
@ -1,26 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
graph := chart.BarChart{
|
sbc := chart.BarChart{
|
||||||
Title: "Test Bar Chart",
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Height: 512,
|
Height: 512,
|
||||||
BarWidth: 60,
|
BarWidth: 60,
|
||||||
|
XAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Bars: []chart.Value{
|
Bars: []chart.Value{
|
||||||
{Value: 5.25, Label: "Blue"},
|
{Value: 5.25, Label: "Blue"},
|
||||||
{Value: 4.88, Label: "Green"},
|
{Value: 4.88, Label: "Green"},
|
||||||
|
@ -33,11 +33,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Header().Set("Content-Type", "image/png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
err := graph.Render(chart.PNG, res)
|
err := sbc.Render(chart.PNG, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering chart: %v\n", err)
|
fmt.Printf("Error rendering chart: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func port() string {
|
func port() string {
|
BIN
_examples/bar_chart/output.png
Normal file
After Width: | Height: | Size: 16 KiB |
43
_examples/basic/main.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
graph := chart.Chart{
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.ContinuousSeries{
|
||||||
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawChartWide(res http.ResponseWriter, req *http.Request) {
|
||||||
|
graph := chart.Chart{
|
||||||
|
Width: 1920, //this overrides the default.
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.HandleFunc("/wide", drawChartWide)
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
BIN
_examples/basic/output.png
Normal file
After Width: | Height: | Size: 11 KiB |
79
_examples/benchmark_line_charts/main.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Usage: http://localhost:8080?series=100&values=1000
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
func random(min, max float64) float64 {
|
||||||
|
return rand.Float64()*(max-min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawLargeChart(res http.ResponseWriter, r *http.Request) {
|
||||||
|
numSeriesInt64, err := strconv.ParseInt(r.FormValue("series"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
numSeriesInt64 = int64(1)
|
||||||
|
}
|
||||||
|
if numSeriesInt64 == 0 {
|
||||||
|
numSeriesInt64 = 1
|
||||||
|
}
|
||||||
|
numSeries := int(numSeriesInt64)
|
||||||
|
|
||||||
|
numValuesInt64, err := strconv.ParseInt(r.FormValue("values"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
numValuesInt64 = int64(100)
|
||||||
|
}
|
||||||
|
if numValuesInt64 == 0 {
|
||||||
|
numValuesInt64 = int64(100)
|
||||||
|
}
|
||||||
|
numValues := int(numValuesInt64)
|
||||||
|
|
||||||
|
series := make([]chart.Series, numSeries)
|
||||||
|
|
||||||
|
for i := 0; i < numSeries; i++ {
|
||||||
|
xValues := make([]time.Time, numValues)
|
||||||
|
yValues := make([]float64, numValues)
|
||||||
|
|
||||||
|
for j := 0; j < numValues; j++ {
|
||||||
|
xValues[j] = time.Now().AddDate(0, 0, (numValues-j)*-1)
|
||||||
|
yValues[j] = random(float64(-500), float64(500))
|
||||||
|
}
|
||||||
|
|
||||||
|
series[i] = chart.TimeSeries{
|
||||||
|
Name: fmt.Sprintf("aaa.bbb.hostname-%v.ccc.ddd.eee.fff.ggg.hhh.iii.jjj.kkk.lll.mmm.nnn.value", i),
|
||||||
|
XValues: xValues,
|
||||||
|
YValues: yValues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Name: "Time",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Name: "Value",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
Series: series,
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawLargeChart)
|
||||||
|
http.HandleFunc("/favico.ico", func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.Write([]byte{})
|
||||||
|
})
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
BIN
_examples/benchmark_line_charts/output.png
Normal file
After Width: | Height: | Size: 68 KiB |
82
_examples/candlestick_series/main.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
chart "github.com/wcharczuk/go-chart"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stockData() (times []time.Time, prices []float64) {
|
||||||
|
start := time.Date(2017, 05, 15, 9, 30, 0, 0, util.Date.Eastern())
|
||||||
|
price := 256.0
|
||||||
|
for day := 0; day < 60; day++ {
|
||||||
|
cursor := start.AddDate(0, 0, day)
|
||||||
|
|
||||||
|
if util.Date.IsNYSEHoliday(cursor) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for minute := 0; minute < ((6 * 60) + 30); minute++ {
|
||||||
|
cursor = cursor.Add(time.Minute)
|
||||||
|
|
||||||
|
if rand.Float64() >= 0.5 {
|
||||||
|
price = price + (rand.Float64() * (price * 0.01))
|
||||||
|
} else {
|
||||||
|
price = price - (rand.Float64() * (price * 0.01))
|
||||||
|
}
|
||||||
|
|
||||||
|
times = append(times, cursor)
|
||||||
|
prices = append(prices, price)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
xv, yv := stockData()
|
||||||
|
|
||||||
|
priceSeries := chart.TimeSeries{
|
||||||
|
Name: "SPY",
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: false,
|
||||||
|
StrokeColor: chart.GetDefaultColor(0),
|
||||||
|
},
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
}
|
||||||
|
|
||||||
|
candleSeries := chart.CandlestickSeries{
|
||||||
|
Name: "SPY",
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{Show: true, FontSize: 8, TextRotationDegrees: 45},
|
||||||
|
TickPosition: chart.TickPositionUnderTick,
|
||||||
|
Range: &chart.MarketHoursRange{},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
candleSeries,
|
||||||
|
priceSeries,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
err := graph.Render(chart.PNG, res)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
|
@ -1,15 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
In this example we use a custom `ValueFormatter` for the y axis, letting us specify how to format text of the y-axis ticks.
|
In this example we use a custom `ValueFormatter` for the y axis, letting us specify how to format text of the y-axis ticks.
|
||||||
You can also do this for the x-axis, or the secondary y-axis.
|
You can also do this for the x-axis, or the secondary y-axis.
|
||||||
|
@ -18,6 +16,9 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: func(v interface{}) string {
|
||||||
if vf, isFloat := v.(float64); isFloat {
|
if vf, isFloat := v.(float64); isFloat {
|
||||||
return fmt.Sprintf("%0.6f", vf)
|
return fmt.Sprintf("%0.6f", vf)
|
||||||
|
@ -32,7 +33,12 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
f, _ := os.Create("output.png")
|
|
||||||
defer f.Close()
|
res.Header().Set("Content-Type", "image/png")
|
||||||
graph.Render(chart.PNG, f)
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/custom_formatters/output.png
Normal file
After Width: | Height: | Size: 27 KiB |
75
_examples/custom_padding/main.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
graph := chart.Chart{
|
||||||
|
Background: chart.Style{
|
||||||
|
Padding: chart.Box{
|
||||||
|
Top: 50,
|
||||||
|
Left: 25,
|
||||||
|
Right: 25,
|
||||||
|
Bottom: 10,
|
||||||
|
},
|
||||||
|
FillColor: drawing.ColorFromHex("efefef"),
|
||||||
|
},
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 100.0),
|
||||||
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawChartDefault(res http.ResponseWriter, req *http.Request) {
|
||||||
|
graph := chart.Chart{
|
||||||
|
Background: chart.Style{
|
||||||
|
FillColor: drawing.ColorFromHex("efefef"),
|
||||||
|
},
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.ContinuousSeries{
|
||||||
|
XValues: seq.Range(1.0, 100.0),
|
||||||
|
YValues: seq.RandomValuesWithMax(100, 512),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.HandleFunc("/default", drawChartDefault)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
BIN
_examples/custom_padding/output.png
Normal file
After Width: | Height: | Size: 66 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
In this example we set a custom range for the y-axis, overriding the automatic range generation.
|
In this example we set a custom range for the y-axis, overriding the automatic range generation.
|
||||||
Note: the chart will still generate the ticks automatically based on the custom range, so the intervals may be a bit weird.
|
Note: the chart will still generate the ticks automatically based on the custom range, so the intervals may be a bit weird.
|
||||||
|
@ -16,6 +14,9 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 10.0,
|
Max: 10.0,
|
||||||
|
@ -28,7 +29,12 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
f, _ := os.Create("output.png")
|
|
||||||
defer f.Close()
|
res.Header().Set("Content-Type", "image/png")
|
||||||
graph.Render(chart.PNG, f)
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/custom_ranges/output.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -1,15 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
In this example we set some custom colors for the series and the chart background and canvas.
|
In this example we set some custom colors for the series and the chart background and canvas.
|
||||||
*/
|
*/
|
||||||
|
@ -23,6 +21,7 @@ func main() {
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true, //note; if we set ANY other properties, we must set this to true.
|
||||||
StrokeColor: drawing.ColorRed, // will supercede defaults
|
StrokeColor: drawing.ColorRed, // will supercede defaults
|
||||||
FillColor: drawing.ColorRed.WithAlpha(64), // will supercede defaults
|
FillColor: drawing.ColorRed.WithAlpha(64), // will supercede defaults
|
||||||
},
|
},
|
||||||
|
@ -32,7 +31,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/custom_styles/output.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
In this example we set a custom set of ticks to use for the y-axis. It can be (almost) whatever you want, including some custom labels for ticks.
|
In this example we set a custom set of ticks to use for the y-axis. It can be (almost) whatever you want, including some custom labels for ticks.
|
||||||
Custom ticks will supercede a custom range, which will supercede automatic generation based on series values.
|
Custom ticks will supercede a custom range, which will supercede automatic generation based on series values.
|
||||||
|
@ -16,17 +14,20 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 4.0,
|
Max: 4.0,
|
||||||
},
|
},
|
||||||
Ticks: []chart.Tick{
|
Ticks: []chart.Tick{
|
||||||
{Value: 0.0, Label: "0.00"},
|
{0.0, "0.00"},
|
||||||
{Value: 2.0, Label: "2.00"},
|
{2.0, "2.00"},
|
||||||
{Value: 4.0, Label: "4.00"},
|
{4.0, "4.00"},
|
||||||
{Value: 6.0, Label: "6.00"},
|
{6.0, "6.00"},
|
||||||
{Value: 8.0, Label: "Eight"},
|
{8.0, "Eight"},
|
||||||
{Value: 10.0, Label: "Ten"},
|
{10.0, "Ten"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
|
@ -36,7 +37,12 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
f, _ := os.Create("output.png")
|
|
||||||
defer f.Close()
|
res.Header().Set("Content-Type", "image/png")
|
||||||
graph.Render(chart.PNG, f)
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/custom_ticks/output.png
Normal file
After Width: | Height: | Size: 11 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
The below will draw the same chart as the `basic` example, except with both the x and y axes turned on.
|
||||||
|
@ -22,11 +20,17 @@ func main() {
|
||||||
Height: 500,
|
Height: 500,
|
||||||
Width: 500,
|
Width: 500,
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
/*Range: &chart.ContinuousRange{
|
/*Range: &chart.ContinuousRange{
|
||||||
Descending: true,
|
Descending: true,
|
||||||
},*/
|
},*/
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Descending: true,
|
Descending: true,
|
||||||
},
|
},
|
||||||
|
@ -34,6 +38,7 @@ func main() {
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
||||||
},
|
},
|
||||||
|
@ -43,7 +48,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/descending/output.png
Normal file
After Width: | Height: | Size: 19 KiB |
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add a `Renderable` or a custom component to the `Elements` array.
|
In this example we add a `Renderable` or a custom component to the `Elements` array.
|
||||||
|
@ -17,6 +15,12 @@ func main() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.Box{
|
Padding: chart.Box{
|
||||||
Top: 20,
|
Top: 20,
|
||||||
|
@ -37,7 +41,11 @@ func main() {
|
||||||
chart.Legend(&graph),
|
chart.Legend(&graph),
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/legend/output.png
Normal file
After Width: | Height: | Size: 24 KiB |
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add a `Renderable` or a custom component to the `Elements` array.
|
In this example we add a `Renderable` or a custom component to the `Elements` array.
|
||||||
|
@ -17,6 +15,12 @@ func main() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.Box{
|
Padding: chart.Box{
|
||||||
Top: 20,
|
Top: 20,
|
||||||
|
@ -97,7 +101,11 @@ func main() {
|
||||||
chart.LegendLeft(&graph),
|
chart.LegendLeft(&graph),
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/legend_left/output.png
Normal file
After Width: | Height: | Size: 44 KiB |
|
@ -1,14 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add a new type of series, a `SimpleMovingAverageSeries` that takes another series as a required argument.
|
In this example we add a new type of series, a `SimpleMovingAverageSeries` that takes another series as a required argument.
|
||||||
|
@ -17,8 +16,8 @@ func main() {
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(1.0).WithEnd(100.0)}.Values(), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Seq{Sequence: chart.NewRandomSequence().WithLen(100).WithMin(0).WithMax(100)}.Values(), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we create a LinearRegressionSeries series by assignin the inner series.
|
// note we create a LinearRegressionSeries series by assignin the inner series.
|
||||||
|
@ -34,7 +33,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/linear_regression/output.png
Normal file
After Width: | Height: | Size: 60 KiB |
46
_examples/market_hours/main.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
start := util.Date.Date(2016, 7, 01, util.Date.Eastern())
|
||||||
|
end := util.Date.Date(2016, 07, 21, util.Date.Eastern())
|
||||||
|
xv := seq.Time.MarketHours(start, end, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||||
|
yv := seq.New(seq.NewRandom().WithLen(len(xv)).WithAverage(200).WithScale(10)).Array()
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
|
Range: &chart.MarketHoursRange{
|
||||||
|
MarketOpen: util.NYSEOpen(),
|
||||||
|
MarketClose: util.NYSEClose(),
|
||||||
|
HolidayProvider: util.Date.IsNYSEHoliday,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
chart.TimeSeries{
|
||||||
|
XValues: xv,
|
||||||
|
YValues: yv,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
BIN
_examples/market_hours/output.png
Normal file
After Width: | Height: | Size: 67 KiB |
|
@ -1,22 +1,22 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(1.0).WithEnd(100.0)}.Values(),
|
XValues: seq.Range(1.0, 100.0),
|
||||||
YValues: chart.Seq{Sequence: chart.NewRandomSequence().WithLen(100).WithMin(50).WithMax(150)}.Values(),
|
YValues: seq.New(seq.NewRandom().WithLen(100).WithAverage(100).WithScale(50)).Array(),
|
||||||
}
|
}
|
||||||
|
|
||||||
minSeries := &chart.MinSeries{
|
minSeries := &chart.MinSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.ColorAlternateGray,
|
StrokeColor: chart.ColorAlternateGray,
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
},
|
},
|
||||||
|
@ -25,6 +25,7 @@ func main() {
|
||||||
|
|
||||||
maxSeries := &chart.MaxSeries{
|
maxSeries := &chart.MaxSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.ColorAlternateGray,
|
StrokeColor: chart.ColorAlternateGray,
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
},
|
},
|
||||||
|
@ -36,6 +37,8 @@ func main() {
|
||||||
Height: 1080,
|
Height: 1080,
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Name: "Random Values",
|
Name: "Random Values",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Min: 25,
|
Min: 25,
|
||||||
Max: 175,
|
Max: 175,
|
||||||
|
@ -43,19 +46,25 @@ func main() {
|
||||||
},
|
},
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Name: "Random Other Values",
|
Name: "Random Other Values",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
mainSeries,
|
mainSeries,
|
||||||
minSeries,
|
minSeries,
|
||||||
maxSeries,
|
maxSeries,
|
||||||
chart.LastValueAnnotationSeries(minSeries),
|
chart.LastValueAnnotation(minSeries),
|
||||||
chart.LastValueAnnotationSeries(maxSeries),
|
chart.LastValueAnnotation(maxSeries),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.Elements = []chart.Renderable{chart.Legend(&graph)}
|
graph.Elements = []chart.Renderable{chart.Legend(&graph)}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/min_max/output.png
Normal file
After Width: | Height: | Size: 170 KiB |
49
_examples/overlap/main.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
chart "github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func conditionalColor(condition bool, trueColor drawing.Color, falseColor drawing.Color) drawing.Color {
|
||||||
|
if condition {
|
||||||
|
return trueColor
|
||||||
|
}
|
||||||
|
return falseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
r, _ := chart.PNG(1024, 1024)
|
||||||
|
|
||||||
|
b0 := chart.Box{Left: 100, Top: 100, Right: 400, Bottom: 200}
|
||||||
|
b1 := chart.Box{Left: 500, Top: 100, Right: 900, Bottom: 200}
|
||||||
|
b0r := b0.Corners().Rotate(45).Shift(0, 200)
|
||||||
|
|
||||||
|
chart.Draw.Box(r, b0, chart.Style{
|
||||||
|
StrokeColor: drawing.ColorRed,
|
||||||
|
StrokeWidth: 2,
|
||||||
|
FillColor: conditionalColor(b0.Corners().Overlaps(b1.Corners()), drawing.ColorRed, drawing.ColorTransparent),
|
||||||
|
})
|
||||||
|
|
||||||
|
chart.Draw.Box(r, b1, chart.Style{
|
||||||
|
StrokeColor: drawing.ColorBlue,
|
||||||
|
StrokeWidth: 2,
|
||||||
|
FillColor: conditionalColor(b1.Corners().Overlaps(b0.Corners()), drawing.ColorRed, drawing.ColorTransparent),
|
||||||
|
})
|
||||||
|
|
||||||
|
chart.Draw.Box2d(r, b0r, chart.Style{
|
||||||
|
StrokeColor: drawing.ColorGreen,
|
||||||
|
StrokeWidth: 2,
|
||||||
|
FillColor: conditionalColor(b0r.Overlaps(b0.Corners()), drawing.ColorRed, drawing.ColorTransparent),
|
||||||
|
})
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
r.Save(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -30,26 +30,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawChartRegression(res http.ResponseWriter, req *http.Request) {
|
|
||||||
pie := chart.PieChart{
|
|
||||||
Width: 512,
|
|
||||||
Height: 512,
|
|
||||||
Values: []chart.Value{
|
|
||||||
{Value: 5, Label: "Blue"},
|
|
||||||
{Value: 2, Label: "Two"},
|
|
||||||
{Value: 1, Label: "One"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Header().Set("Content-Type", chart.ContentTypeSVG)
|
|
||||||
err := pie.Render(chart.SVG, res)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", drawChart)
|
http.HandleFunc("/", drawChart)
|
||||||
http.HandleFunc("/reg", drawChartRegression)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
@ -1,14 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add a new type of series, a `PolynomialRegressionSeries` that takes another series as a required argument.
|
In this example we add a new type of series, a `PolynomialRegressionSeries` that takes another series as a required argument.
|
||||||
|
@ -17,8 +16,8 @@ func main() {
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(1.0).WithEnd(100.0)}.Values(), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Seq{Sequence: chart.NewRandomSequence().WithLen(100).WithMin(0).WithMax(100)}.Values(), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithAverage(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
polyRegSeries := &chart.PolynomialRegressionSeries{
|
polyRegSeries := &chart.PolynomialRegressionSeries{
|
||||||
|
@ -33,7 +32,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/poly_regression/output.png
Normal file
After Width: | Height: | Size: 54 KiB |
137
_examples/request_timings/main.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseInt(str string) int {
|
||||||
|
v, _ := strconv.Atoi(str)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat64(str string) float64 {
|
||||||
|
v, _ := strconv.ParseFloat(str, 64)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData() ([]time.Time, []float64) {
|
||||||
|
var xvalues []time.Time
|
||||||
|
var yvalues []float64
|
||||||
|
err := util.File.ReadByLines("requests.csv", func(line string) error {
|
||||||
|
parts := strings.Split(line, ",")
|
||||||
|
year := parseInt(parts[0])
|
||||||
|
month := parseInt(parts[1])
|
||||||
|
day := parseInt(parts[2])
|
||||||
|
hour := parseInt(parts[3])
|
||||||
|
elapsedMillis := parseFloat64(parts[4])
|
||||||
|
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
|
||||||
|
yvalues = append(yvalues, elapsedMillis)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
return xvalues, yvalues
|
||||||
|
}
|
||||||
|
|
||||||
|
func releases() []chart.GridLine {
|
||||||
|
return []chart.GridLine{
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 2, 15, 30, 0, 0, time.UTC))},
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
|
||||||
|
{Value: util.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
xvalues, yvalues := readData()
|
||||||
|
mainSeries := chart.TimeSeries{
|
||||||
|
Name: "Prod Request Timings",
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
StrokeColor: chart.ColorBlue,
|
||||||
|
FillColor: chart.ColorBlue.WithAlpha(100),
|
||||||
|
},
|
||||||
|
XValues: xvalues,
|
||||||
|
YValues: yvalues,
|
||||||
|
}
|
||||||
|
|
||||||
|
linreg := &chart.LinearRegressionSeries{
|
||||||
|
Name: "Linear Regression",
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
StrokeColor: chart.ColorAlternateBlue,
|
||||||
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
|
},
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
sma := &chart.SMASeries{
|
||||||
|
Name: "SMA",
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
StrokeColor: chart.ColorRed,
|
||||||
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
|
},
|
||||||
|
InnerSeries: mainSeries,
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := chart.Chart{
|
||||||
|
Width: 1280,
|
||||||
|
Height: 720,
|
||||||
|
Background: chart.Style{
|
||||||
|
Padding: chart.Box{
|
||||||
|
Top: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Name: "Elapsed Millis",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
|
TickStyle: chart.Style{
|
||||||
|
TextRotationDegrees: 45.0,
|
||||||
|
},
|
||||||
|
ValueFormatter: func(v interface{}) string {
|
||||||
|
return fmt.Sprintf("%d ms", int(v.(float64)))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
|
GridMajorStyle: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
StrokeColor: chart.ColorAlternateGray,
|
||||||
|
StrokeWidth: 1.0,
|
||||||
|
},
|
||||||
|
GridLines: releases(),
|
||||||
|
},
|
||||||
|
Series: []chart.Series{
|
||||||
|
mainSeries,
|
||||||
|
linreg,
|
||||||
|
chart.LastValueAnnotation(linreg),
|
||||||
|
sma,
|
||||||
|
chart.LastValueAnnotation(sma),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", chart.ContentTypePNG)
|
||||||
|
graph.Render(chart.PNG, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
@ -6,8 +6,9 @@ import (
|
||||||
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -20,12 +21,13 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeWidth: chart.Disabled,
|
StrokeWidth: chart.Disabled,
|
||||||
DotWidth: 5,
|
DotWidth: 5,
|
||||||
DotColorProvider: viridisByY,
|
DotColorProvider: viridisByY,
|
||||||
},
|
},
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(0).WithEnd(127)}.Values(),
|
XValues: seq.Range(0, 127),
|
||||||
YValues: chart.Seq{Sequence: chart.NewRandomSequence().WithLen(128).WithMin(0).WithMax(1024)}.Values(),
|
YValues: seq.New(seq.NewRandom().WithLen(128).WithMax(1024)).Array(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -42,15 +44,15 @@ func unit(res http.ResponseWriter, req *http.Request) {
|
||||||
Height: 50,
|
Height: 50,
|
||||||
Width: 50,
|
Width: 50,
|
||||||
Canvas: chart.Style{
|
Canvas: chart.Style{
|
||||||
Padding: chart.BoxZero,
|
Padding: chart.Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Background: chart.Style{
|
Background: chart.Style{
|
||||||
Padding: chart.BoxZero,
|
Padding: chart.Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(0).WithEnd(4)}.Values(),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(0).WithEnd(4)}.Values(),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
@ -1,18 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
mainSeries := chart.ContinuousSeries{
|
||||||
Name: "A test series",
|
Name: "A test series",
|
||||||
XValues: chart.Seq{Sequence: chart.NewLinearSequence().WithStart(1.0).WithEnd(100.0)}.Values(), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
XValues: seq.Range(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
|
||||||
YValues: chart.Seq{Sequence: chart.NewRandomSequence().WithLen(100).WithMin(0).WithMax(100)}.Values(), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
YValues: seq.RandomValuesWithMax(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we create a SimpleMovingAverage series by assignin the inner series.
|
// note we create a SimpleMovingAverage series by assignin the inner series.
|
||||||
|
@ -28,7 +28,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
BIN
_examples/simple_moving_average/output.png
Normal file
After Width: | Height: | Size: 68 KiB |
|
@ -1,20 +1,22 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
sbc := chart.StackedBarChart{
|
sbc := chart.StackedBarChart{
|
||||||
Title: "Test Stacked Bar Chart",
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Height: 512,
|
Height: 512,
|
||||||
|
XAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
YAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Bars: []chart.StackedBar{
|
Bars: []chart.StackedBar{
|
||||||
{
|
{
|
||||||
Name: "This is a very long string to test word break wrapping.",
|
Name: "This is a very long string to test word break wrapping.",
|
||||||
|
@ -47,7 +49,14 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
err := sbc.Render(chart.PNG, res)
|
||||||
sbc.Render(chart.PNG, f)
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering chart: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1,21 +1,20 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
xv, yv := xvalues(), yvalues()
|
xv, yv := xvalues(), yvalues()
|
||||||
|
|
||||||
priceSeries := chart.TimeSeries{
|
priceSeries := chart.TimeSeries{
|
||||||
Name: "SPY",
|
Name: "SPY",
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.GetDefaultColor(0),
|
StrokeColor: chart.GetDefaultColor(0),
|
||||||
},
|
},
|
||||||
XValues: xv,
|
XValues: xv,
|
||||||
|
@ -25,6 +24,7 @@ func main() {
|
||||||
smaSeries := chart.SMASeries{
|
smaSeries := chart.SMASeries{
|
||||||
Name: "SPY - SMA",
|
Name: "SPY - SMA",
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: drawing.ColorRed,
|
StrokeColor: drawing.ColorRed,
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
StrokeDashArray: []float64{5.0, 5.0},
|
||||||
},
|
},
|
||||||
|
@ -34,6 +34,7 @@ func main() {
|
||||||
bbSeries := &chart.BollingerBandsSeries{
|
bbSeries := &chart.BollingerBandsSeries{
|
||||||
Name: "SPY - Bol. Bands",
|
Name: "SPY - Bol. Bands",
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: drawing.ColorFromHex("efefef"),
|
StrokeColor: drawing.ColorFromHex("efefef"),
|
||||||
FillColor: drawing.ColorFromHex("efefef").WithAlpha(64),
|
FillColor: drawing.ColorFromHex("efefef").WithAlpha(64),
|
||||||
},
|
},
|
||||||
|
@ -42,9 +43,11 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
},
|
},
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{Show: true},
|
||||||
Range: &chart.ContinuousRange{
|
Range: &chart.ContinuousRange{
|
||||||
Max: 220.0,
|
Max: 220.0,
|
||||||
Min: 180.0,
|
Min: 180.0,
|
||||||
|
@ -57,9 +60,8 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func xvalues() []time.Time {
|
func xvalues() []time.Time {
|
||||||
|
@ -76,3 +78,8 @@ func xvalues() []time.Time {
|
||||||
func yvalues() []float64 {
|
func yvalues() []float64 {
|
||||||
return []float64{212.47, 212.59, 211.76, 211.37, 210.18, 208.00, 206.79, 209.33, 210.77, 210.82, 210.50, 209.79, 209.38, 210.07, 208.35, 207.95, 210.57, 208.66, 208.92, 208.66, 209.42, 210.59, 209.98, 208.32, 203.97, 197.83, 189.50, 187.27, 194.46, 199.27, 199.28, 197.67, 191.77, 195.41, 195.55, 192.59, 197.43, 194.79, 195.85, 196.74, 196.01, 198.45, 200.18, 199.73, 195.45, 196.46, 193.90, 193.60, 192.90, 192.87, 188.01, 188.12, 191.63, 192.13, 195.00, 198.47, 197.79, 199.41, 201.21, 201.33, 201.52, 200.25, 199.29, 202.35, 203.27, 203.37, 203.11, 201.85, 205.26, 207.51, 207.00, 206.60, 208.95, 208.83, 207.93, 210.39, 211.00, 210.36, 210.15, 210.04, 208.08, 208.56, 207.74, 204.84, 202.54, 205.62, 205.47, 208.73, 208.55, 209.31, 209.07, 209.35, 209.32, 209.56, 208.69, 210.68, 208.53, 205.61, 209.62, 208.35, 206.95, 205.34, 205.87, 201.88, 202.90, 205.03, 208.03, 204.86, 200.02, 201.67, 203.50, 206.02, 205.68, 205.21, 207.40, 205.93, 203.87, 201.02, 201.36, 198.82, 194.05, 191.92, 192.11, 193.66, 188.83, 191.93, 187.81, 188.06, 185.65, 186.69, 190.52, 187.64, 190.20, 188.13, 189.11, 193.72, 193.65, 190.16, 191.30, 191.60, 187.95, 185.42, 185.43, 185.27, 182.86, 186.63, 189.78, 192.88, 192.09, 192.00, 194.78, 192.32, 193.20, 195.54, 195.09, 193.56, 198.11, 199.00, 199.78, 200.43, 200.59, 198.40, 199.38, 199.54, 202.76, 202.50, 202.17, 203.34, 204.63, 204.38, 204.67, 204.56, 203.21, 203.12, 203.24, 205.12, 206.02, 205.52, 206.92, 206.25, 204.19, 206.42, 203.95, 204.50, 204.02, 205.92, 208.00, 208.01, 207.78, 209.24, 209.90, 210.10, 208.97, 208.97, 208.61, 208.92, 209.35, 207.45, 206.33, 207.97, 206.16, 205.01, 204.97, 205.72, 205.89, 208.45, 206.50, 206.56, 204.76, 206.78, 204.85, 204.91, 204.20, 205.49, 205.21, 207.87, 209.28, 209.34, 210.24, 209.84, 210.27, 210.91, 210.28, 211.35, 211.68, 212.37, 212.08, 210.07, 208.45, 208.04, 207.75, 208.37, 206.52, 207.85, 208.44, 208.10, 210.81, 203.24, 199.60, 203.20, 206.66, 209.48, 209.92, 208.41, 209.66, 209.53, 212.65, 213.40, 214.95, 214.92, 216.12, 215.83}
|
return []float64{212.47, 212.59, 211.76, 211.37, 210.18, 208.00, 206.79, 209.33, 210.77, 210.82, 210.50, 209.79, 209.38, 210.07, 208.35, 207.95, 210.57, 208.66, 208.92, 208.66, 209.42, 210.59, 209.98, 208.32, 203.97, 197.83, 189.50, 187.27, 194.46, 199.27, 199.28, 197.67, 191.77, 195.41, 195.55, 192.59, 197.43, 194.79, 195.85, 196.74, 196.01, 198.45, 200.18, 199.73, 195.45, 196.46, 193.90, 193.60, 192.90, 192.87, 188.01, 188.12, 191.63, 192.13, 195.00, 198.47, 197.79, 199.41, 201.21, 201.33, 201.52, 200.25, 199.29, 202.35, 203.27, 203.37, 203.11, 201.85, 205.26, 207.51, 207.00, 206.60, 208.95, 208.83, 207.93, 210.39, 211.00, 210.36, 210.15, 210.04, 208.08, 208.56, 207.74, 204.84, 202.54, 205.62, 205.47, 208.73, 208.55, 209.31, 209.07, 209.35, 209.32, 209.56, 208.69, 210.68, 208.53, 205.61, 209.62, 208.35, 206.95, 205.34, 205.87, 201.88, 202.90, 205.03, 208.03, 204.86, 200.02, 201.67, 203.50, 206.02, 205.68, 205.21, 207.40, 205.93, 203.87, 201.02, 201.36, 198.82, 194.05, 191.92, 192.11, 193.66, 188.83, 191.93, 187.81, 188.06, 185.65, 186.69, 190.52, 187.64, 190.20, 188.13, 189.11, 193.72, 193.65, 190.16, 191.30, 191.60, 187.95, 185.42, 185.43, 185.27, 182.86, 186.63, 189.78, 192.88, 192.09, 192.00, 194.78, 192.32, 193.20, 195.54, 195.09, 193.56, 198.11, 199.00, 199.78, 200.43, 200.59, 198.40, 199.38, 199.54, 202.76, 202.50, 202.17, 203.34, 204.63, 204.38, 204.67, 204.56, 203.21, 203.12, 203.24, 205.12, 206.02, 205.52, 206.92, 206.25, 204.19, 206.42, 203.95, 204.50, 204.02, 205.92, 208.00, 208.01, 207.78, 209.24, 209.90, 210.10, 208.97, 208.97, 208.61, 208.92, 209.35, 207.45, 206.33, 207.97, 206.16, 205.01, 204.97, 205.72, 205.89, 208.45, 206.50, 206.56, 204.76, 206.78, 204.85, 204.91, 204.20, 205.49, 205.21, 207.87, 209.28, 209.34, 210.24, 209.84, 210.27, 210.91, 210.28, 211.35, 211.68, 212.37, 212.08, 210.07, 208.45, 208.04, 207.75, 208.37, 206.52, 207.85, 208.44, 208.10, 210.81, 203.24, 199.60, 203.20, 206.66, 209.48, 209.92, 208.41, 209.66, 209.53, 212.65, 213.40, 214.95, 214.92, 216.12, 215.83}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
BIN
_examples/stock_analysis/output.png
Normal file
After Width: | Height: | Size: 66 KiB |
|
@ -1,15 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
f, _ := chart.GetDefaultFont()
|
f, _ := chart.GetDefaultFont()
|
||||||
r, _ := chart.PNG(1024, 1024)
|
r, _ := chart.PNG(1024, 1024)
|
||||||
|
|
||||||
|
@ -34,18 +32,28 @@ func main() {
|
||||||
|
|
||||||
tbc := tb.Corners().Rotate(45)
|
tbc := tb.Corners().Rotate(45)
|
||||||
|
|
||||||
chart.Draw.BoxCorners(r, tbc, chart.Style{
|
chart.Draw.Box2d(r, tbc, chart.Style{
|
||||||
StrokeColor: drawing.ColorRed,
|
StrokeColor: drawing.ColorRed,
|
||||||
StrokeWidth: 2,
|
StrokeWidth: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tbc2 := tbc.Shift(tbc.Height(), 0)
|
||||||
|
chart.Draw.Box2d(r, tbc2, chart.Style{
|
||||||
|
StrokeColor: drawing.ColorGreen,
|
||||||
|
StrokeWidth: 2,
|
||||||
|
})
|
||||||
|
|
||||||
tbcb := tbc.Box()
|
tbcb := tbc.Box()
|
||||||
chart.Draw.Box(r, tbcb, chart.Style{
|
chart.Draw.Box(r, tbcb, chart.Style{
|
||||||
StrokeColor: drawing.ColorBlue,
|
StrokeColor: drawing.ColorBlue,
|
||||||
StrokeWidth: 2,
|
StrokeWidth: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
file, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer file.Close()
|
r.Save(res)
|
||||||
r.Save(file)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
@ -4,15 +4,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
chart "git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
/*
|
/*
|
||||||
This is an example of using the `TimeSeries` to automatically coerce time.Time values into a continuous xrange.
|
This is an example of using the `TimeSeries` to automatically coerce time.Time values into a continuous xrange.
|
||||||
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropriate formatter to use for the ticks.
|
Note: chart.TimeSeries implements `ValueFormatterProvider` and as a result gives the XAxis the appropariate formatter to use for the ticks.
|
||||||
*/
|
*/
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.TimeSeries{
|
chart.TimeSeries{
|
||||||
XValues: []time.Time{
|
XValues: []time.Time{
|
||||||
|
@ -43,6 +48,9 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
|
||||||
*/
|
*/
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
ValueFormatter: chart.TimeHourValueFormatter,
|
ValueFormatter: chart.TimeHourValueFormatter,
|
||||||
},
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
|
@ -71,7 +79,7 @@ func drawCustomChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", drawChart)
|
http.HandleFunc("/", drawChart)
|
||||||
http.HandleFunc("/favicon.ico", func(res http.ResponseWriter, req *http.Request) {
|
http.HandleFunc("/favico.ico", func(res http.ResponseWriter, req *http.Request) {
|
||||||
res.Write([]byte{})
|
res.Write([]byte{})
|
||||||
})
|
})
|
||||||
http.HandleFunc("/custom", drawCustomChart)
|
http.HandleFunc("/custom", drawCustomChart)
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -1,15 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"net/http"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In this example we add a second series, and assign it to the secondary y axis, giving that series it's own range.
|
In this example we add a second series, and assign it to the secondary y axis, giving that series it's own range.
|
||||||
|
@ -19,13 +18,26 @@ func main() {
|
||||||
|
|
||||||
graph := chart.Chart{
|
graph := chart.Chart{
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the x-axis
|
||||||
|
},
|
||||||
TickPosition: chart.TickPositionBetweenTicks,
|
TickPosition: chart.TickPositionBetweenTicks,
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: func(v interface{}) string {
|
||||||
typed := v.(float64)
|
typed := v.(float64)
|
||||||
typedDate := chart.TimeFromFloat64(typed)
|
typedDate := util.Time.FromFloat64(typed)
|
||||||
return fmt.Sprintf("%d-%d\n%d", typedDate.Month(), typedDate.Day(), typedDate.Year())
|
return fmt.Sprintf("%d-%d\n%d", typedDate.Month(), typedDate.Day(), typedDate.Year())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the y-axis
|
||||||
|
},
|
||||||
|
},
|
||||||
|
YAxisSecondary: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true, //enables / displays the secondary y-axis
|
||||||
|
},
|
||||||
|
},
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
||||||
chart.ContinuousSeries{
|
chart.ContinuousSeries{
|
||||||
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
@ -39,7 +51,11 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := os.Create("output.png")
|
res.Header().Set("Content-Type", "image/png")
|
||||||
defer f.Close()
|
graph.Render(chart.PNG, res)
|
||||||
graph.Render(chart.PNG, f)
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
@ -1,27 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run main.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
"github.com/wcharczuk/go-chart"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var b float64
|
var b float64
|
||||||
b = 1000
|
b = 1000
|
||||||
|
|
||||||
ts1 := chart.ContinuousSeries{ //TimeSeries{
|
ts1 := chart.ContinuousSeries{ //TimeSeries{
|
||||||
Name: "Time Series",
|
Name: "Time Series",
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
XValues: []float64{10 * b, 20 * b, 30 * b, 40 * b, 50 * b, 60 * b, 70 * b, 80 * b},
|
||||||
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
YValues: []float64{1.0, 2.0, 30.0, 4.0, 50.0, 6.0, 7.0, 88.0},
|
||||||
}
|
}
|
||||||
|
|
||||||
ts2 := chart.ContinuousSeries{ //TimeSeries{
|
ts2 := chart.ContinuousSeries{ //TimeSeries{
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: chart.GetDefaultColor(1),
|
StrokeColor: chart.GetDefaultColor(1),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -33,11 +36,15 @@ func main() {
|
||||||
|
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
Name: "The XAxis",
|
Name: "The XAxis",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
ValueFormatter: chart.TimeMinuteValueFormatter, //TimeHourValueFormatter,
|
ValueFormatter: chart.TimeMinuteValueFormatter, //TimeHourValueFormatter,
|
||||||
},
|
},
|
||||||
|
|
||||||
YAxis: chart.YAxis{
|
YAxis: chart.YAxis{
|
||||||
Name: "The YAxis",
|
Name: "The YAxis",
|
||||||
|
NameStyle: chart.StyleShow(),
|
||||||
|
Style: chart.StyleShow(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Series: []chart.Series{
|
Series: []chart.Series{
|
BIN
_examples/twopoint/output.png
Normal file
After Width: | Height: | Size: 40 KiB |
|
@ -3,11 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
var (
|
|
||||||
_ Series = (*AnnotationSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnnotationSeries is a series of labels on the chart.
|
// AnnotationSeries is a series of labels on the chart.
|
||||||
|
@ -53,17 +50,17 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
Right: 0,
|
Right: 0,
|
||||||
Bottom: 0,
|
Bottom: 0,
|
||||||
}
|
}
|
||||||
if !as.Style.Hidden {
|
if as.Style.IsZero() || as.Style.Show {
|
||||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||||
for _, a := range as.Annotations {
|
for _, a := range as.Annotations {
|
||||||
style := a.Style.InheritFrom(seriesStyle)
|
style := a.Style.InheritFrom(seriesStyle)
|
||||||
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
||||||
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
||||||
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
||||||
box.Top = MinInt(box.Top, ab.Top)
|
box.Top = util.Math.MinInt(box.Top, ab.Top)
|
||||||
box.Left = MinInt(box.Left, ab.Left)
|
box.Left = util.Math.MinInt(box.Left, ab.Left)
|
||||||
box.Right = MaxInt(box.Right, ab.Right)
|
box.Right = util.Math.MaxInt(box.Right, ab.Right)
|
||||||
box.Bottom = MaxInt(box.Bottom, ab.Bottom)
|
box.Bottom = util.Math.MaxInt(box.Bottom, ab.Bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box
|
return box
|
||||||
|
@ -71,7 +68,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
|
|
||||||
// Render draws the series.
|
// Render draws the series.
|
||||||
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
if !as.Style.Hidden {
|
if as.Style.IsZero() || as.Style.Show {
|
||||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||||
for _, a := range as.Annotations {
|
for _, a := range as.Annotations {
|
||||||
style := a.Style.InheritFrom(seriesStyle)
|
style := a.Style.InheritFrom(seriesStyle)
|
||||||
|
|
|
@ -4,14 +4,17 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/blendlabs/go-assert"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAnnotationSeriesMeasure(t *testing.T) {
|
func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
as := AnnotationSeries{
|
as := AnnotationSeries{
|
||||||
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
Annotations: []Value2{
|
Annotations: []Value2{
|
||||||
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
{XValue: 1.0, YValue: 1.0, Label: "1.0"},
|
||||||
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
{XValue: 2.0, YValue: 2.0, Label: "2.0"},
|
||||||
|
@ -21,10 +24,10 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := PNG(110, 110)
|
r, err := PNG(110, 110)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
f, err := GetDefaultFont()
|
f, err := GetDefaultFont()
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
xrange := &ContinuousRange{
|
xrange := &ContinuousRange{
|
||||||
Min: 1.0,
|
Min: 1.0,
|
||||||
|
@ -49,18 +52,19 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
box := as.Measure(r, cb, xrange, yrange, sd)
|
box := as.Measure(r, cb, xrange, yrange, sd)
|
||||||
testutil.AssertFalse(t, box.IsZero())
|
assert.False(box.IsZero())
|
||||||
testutil.AssertEqual(t, -5.0, box.Top)
|
assert.Equal(-5.0, box.Top)
|
||||||
testutil.AssertEqual(t, 5.0, box.Left)
|
assert.Equal(5.0, box.Left)
|
||||||
testutil.AssertEqual(t, 146.0, box.Right) //the top,left annotation sticks up 5px and out ~44px.
|
assert.Equal(146.0, box.Right) //the top,left annotation sticks up 5px and out ~44px.
|
||||||
testutil.AssertEqual(t, 115.0, box.Bottom)
|
assert.Equal(115.0, box.Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnnotationSeriesRender(t *testing.T) {
|
func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
as := AnnotationSeries{
|
as := AnnotationSeries{
|
||||||
Style: Style{
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
FillColor: drawing.ColorWhite,
|
FillColor: drawing.ColorWhite,
|
||||||
StrokeColor: drawing.ColorBlack,
|
StrokeColor: drawing.ColorBlack,
|
||||||
},
|
},
|
||||||
|
@ -73,10 +77,10 @@ func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := PNG(110, 110)
|
r, err := PNG(110, 110)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
f, err := GetDefaultFont()
|
f, err := GetDefaultFont()
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
xrange := &ContinuousRange{
|
xrange := &ContinuousRange{
|
||||||
Min: 1.0,
|
Min: 1.0,
|
||||||
|
@ -103,13 +107,13 @@ func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
as.Render(r, cb, xrange, yrange, sd)
|
as.Render(r, cb, xrange, yrange, sd)
|
||||||
|
|
||||||
rr, isRaster := r.(*rasterRenderer)
|
rr, isRaster := r.(*rasterRenderer)
|
||||||
testutil.AssertTrue(t, isRaster)
|
assert.True(isRaster)
|
||||||
testutil.AssertNotNil(t, rr)
|
assert.NotNil(rr)
|
||||||
|
|
||||||
c := rr.i.At(38, 70)
|
c := rr.i.At(38, 70)
|
||||||
converted, isRGBA := color.RGBAModel.Convert(c).(color.RGBA)
|
converted, isRGBA := color.RGBAModel.Convert(c).(color.RGBA)
|
||||||
testutil.AssertTrue(t, isRGBA)
|
assert.True(isRGBA)
|
||||||
testutil.AssertEqual(t, 0, converted.R)
|
assert.Equal(0, converted.R)
|
||||||
testutil.AssertEqual(t, 0, converted.G)
|
assert.Equal(0, converted.G)
|
||||||
testutil.AssertEqual(t, 0, converted.B)
|
assert.Equal(0, converted.B)
|
||||||
}
|
}
|
||||||
|
|
24
array.go
|
@ -1,24 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ Sequence = (*Array)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewArray returns a new array from a given set of values.
|
|
||||||
// Array implements Sequence, which allows it to be used with the sequence helpers.
|
|
||||||
func NewArray(values ...float64) Array {
|
|
||||||
return Array(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array is a wrapper for an array of floats that implements `ValuesProvider`.
|
|
||||||
type Array []float64
|
|
||||||
|
|
||||||
// Len returns the value provider length.
|
|
||||||
func (a Array) Len() int {
|
|
||||||
return len(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue returns the value at a given index.
|
|
||||||
func (a Array) GetValue(index int) float64 {
|
|
||||||
return a[index]
|
|
||||||
}
|
|
95
bar_chart.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BarChart is a chart that draws bars on a range.
|
// BarChart is a chart that draws bars on a range.
|
||||||
|
@ -30,9 +31,6 @@ type BarChart struct {
|
||||||
|
|
||||||
BarSpacing int
|
BarSpacing int
|
||||||
|
|
||||||
UseBaseValue bool
|
|
||||||
BaseValue float64
|
|
||||||
|
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
defaultFont *truetype.Font
|
defaultFont *truetype.Font
|
||||||
|
|
||||||
|
@ -128,7 +126,7 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
||||||
yr = bc.setRangeDomains(canvasBox, yr)
|
yr = bc.setRangeDomains(canvasBox, yr)
|
||||||
}
|
}
|
||||||
bc.drawCanvas(r, canvasBox)
|
|
||||||
bc.drawBars(r, canvasBox, yr)
|
bc.drawBars(r, canvasBox, yr)
|
||||||
bc.drawXAxis(r, canvasBox)
|
bc.drawXAxis(r, canvasBox)
|
||||||
bc.drawYAxis(r, canvasBox, yr, yt)
|
bc.drawYAxis(r, canvasBox, yr, yt)
|
||||||
|
@ -141,10 +139,6 @@ func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
return r.Save(w)
|
return r.Save(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) {
|
|
||||||
Draw.Box(r, canvasBox, bc.getCanvasStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) getRanges() Range {
|
func (bc BarChart) getRanges() Range {
|
||||||
var yrange Range
|
var yrange Range
|
||||||
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
||||||
|
@ -201,21 +195,12 @@ func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||||
|
|
||||||
by = canvasBox.Bottom - yr.Translate(bar.Value)
|
by = canvasBox.Bottom - yr.Translate(bar.Value)
|
||||||
|
|
||||||
if bc.UseBaseValue {
|
|
||||||
barBox = Box{
|
|
||||||
Top: by,
|
|
||||||
Left: bxl,
|
|
||||||
Right: bxr,
|
|
||||||
Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
barBox = Box{
|
barBox = Box{
|
||||||
Top: by,
|
Top: by,
|
||||||
Left: bxl,
|
Left: bxl,
|
||||||
Right: bxr,
|
Right: bxr,
|
||||||
Bottom: canvasBox.Bottom,
|
Bottom: canvasBox.Bottom,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
||||||
|
|
||||||
|
@ -224,7 +209,7 @@ func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
if !bc.XAxis.Hidden {
|
if bc.XAxis.Show {
|
||||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||||
axisStyle.WriteToRenderer(r)
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -263,44 +248,44 @@ func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
||||||
if !bc.YAxis.Style.Hidden {
|
if bc.YAxis.Style.Show {
|
||||||
bc.YAxis.Render(r, canvasBox, yr, bc.styleDefaultsAxes(), ticks)
|
axisStyle := bc.YAxis.Style.InheritFrom(bc.styleDefaultsAxes())
|
||||||
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
r.MoveTo(canvasBox.Right, canvasBox.Top)
|
||||||
|
r.LineTo(canvasBox.Right, canvasBox.Bottom)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
r.MoveTo(canvasBox.Right, canvasBox.Bottom)
|
||||||
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
var ty int
|
||||||
|
var tb Box2d
|
||||||
|
for _, t := range ticks {
|
||||||
|
ty = canvasBox.Bottom - yr.Translate(t.Value)
|
||||||
|
|
||||||
|
axisStyle.GetStrokeOptions().WriteToRenderer(r)
|
||||||
|
r.MoveTo(canvasBox.Right, ty)
|
||||||
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
axisStyle.GetTextOptions().WriteToRenderer(r)
|
||||||
|
tb = r.MeasureText(t.Label)
|
||||||
|
Draw.Text(r, t.Label, canvasBox.Right+DefaultYAxisMargin+5, ty+(int(tb.Height())>>1), axisStyle)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) drawTitle(r Renderer) {
|
func (bc BarChart) drawTitle(r Renderer) {
|
||||||
if len(bc.Title) > 0 && !bc.TitleStyle.Hidden {
|
if len(bc.Title) > 0 && bc.TitleStyle.Show {
|
||||||
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
Draw.TextWithin(r, bc.Title, bc.box(), bc.styleDefaultsTitle())
|
||||||
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
|
||||||
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
|
||||||
r.SetFontSize(titleFontSize)
|
|
||||||
|
|
||||||
textBox := r.MeasureText(bc.Title)
|
|
||||||
|
|
||||||
textWidth := textBox.Width()
|
|
||||||
textHeight := textBox.Height()
|
|
||||||
|
|
||||||
titleX := (bc.GetWidth() >> 1) - (textWidth >> 1)
|
|
||||||
titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
|
||||||
|
|
||||||
r.Text(bc.Title, titleX, titleY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) getCanvasStyle() Style {
|
|
||||||
return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BarChart) styleDefaultsCanvas() Style {
|
|
||||||
return Style{
|
|
||||||
FillColor: bc.GetColorPalette().CanvasColor(),
|
|
||||||
StrokeColor: bc.GetColorPalette().CanvasStrokeColor(),
|
|
||||||
StrokeWidth: DefaultCanvasStrokeWidth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) hasAxes() bool {
|
func (bc BarChart) hasAxes() bool {
|
||||||
return !bc.YAxis.Style.Hidden
|
return bc.YAxis.Style.Show
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
||||||
|
@ -320,7 +305,7 @@ func (bc BarChart) getValueFormatters() ValueFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
||||||
if !bc.YAxis.Style.Hidden {
|
if bc.YAxis.Style.Show {
|
||||||
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -366,7 +351,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
|
|
||||||
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
||||||
|
|
||||||
if !bc.XAxis.Hidden {
|
if bc.XAxis.Show {
|
||||||
xaxisHeight := DefaultVerticalTickHeight
|
xaxisHeight := DefaultVerticalTickHeight
|
||||||
|
|
||||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||||
|
@ -384,7 +369,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
||||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||||
|
|
||||||
xaxisHeight = MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = util.Math.MinInt(int(linesBox.Height())+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +383,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
axesOuterBox = axesOuterBox.Grow(xbox)
|
axesOuterBox = axesOuterBox.Grow(xbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bc.YAxis.Style.Hidden {
|
if bc.YAxis.Style.Show {
|
||||||
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
|
@ -412,8 +397,8 @@ func (bc BarChart) box() Box {
|
||||||
dpb := bc.Background.Padding.GetBottom(50)
|
dpb := bc.Background.Padding.GetBottom(50)
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
Top: bc.Background.Padding.GetTop(20),
|
Top: 20,
|
||||||
Left: bc.Background.Padding.GetLeft(20),
|
Left: 20,
|
||||||
Right: bc.GetWidth() - dpr,
|
Right: bc.GetWidth() - dpr,
|
||||||
Bottom: bc.GetHeight() - dpb,
|
Bottom: bc.GetHeight() - dpb,
|
||||||
}
|
}
|
||||||
|
@ -451,7 +436,7 @@ func (bc BarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getTitleFontSize() float64 {
|
func (bc BarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := MinInt(bc.GetWidth(), bc.GetHeight())
|
effectiveDimension := util.Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -5,15 +5,20 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
assert "github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBarChartRender(t *testing.T) {
|
func TestBarChartRender(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Title: "Test Title",
|
Title: "Test Title",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
|
XAxis: StyleShow(),
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
{Value: 1.0, Label: "One"},
|
{Value: 1.0, Label: "One"},
|
||||||
{Value: 2.0, Label: "Two"},
|
{Value: 2.0, Label: "Two"},
|
||||||
|
@ -25,16 +30,21 @@ func TestBarChartRender(t *testing.T) {
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
err := bc.Render(PNG, buf)
|
err := bc.Render(PNG, buf)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
testutil.AssertNotZero(t, buf.Len())
|
assert.NotZero(buf.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartRenderZero(t *testing.T) {
|
func TestBarChartRenderZero(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Title: "Test Title",
|
Title: "Test Title",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
|
XAxis: StyleShow(),
|
||||||
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
{Value: 0.0, Label: "One"},
|
{Value: 0.0, Label: "One"},
|
||||||
{Value: 0.0, Label: "Two"},
|
{Value: 0.0, Label: "Two"},
|
||||||
|
@ -43,64 +53,64 @@ func TestBarChartRenderZero(t *testing.T) {
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
err := bc.Render(PNG, buf)
|
err := bc.Render(PNG, buf)
|
||||||
testutil.AssertNotNil(t, err)
|
assert.NotNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartProps(t *testing.T) {
|
func TestBarChartProps(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
|
|
||||||
testutil.AssertEqual(t, DefaultDPI, bc.GetDPI())
|
assert.Equal(DefaultDPI, bc.GetDPI())
|
||||||
bc.DPI = 100
|
bc.DPI = 100
|
||||||
testutil.AssertEqual(t, 100, bc.GetDPI())
|
assert.Equal(100, bc.GetDPI())
|
||||||
|
|
||||||
testutil.AssertNil(t, bc.GetFont())
|
assert.Nil(bc.GetFont())
|
||||||
f, err := GetDefaultFont()
|
f, err := GetDefaultFont()
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
bc.Font = f
|
bc.Font = f
|
||||||
testutil.AssertNotNil(t, bc.GetFont())
|
assert.NotNil(bc.GetFont())
|
||||||
|
|
||||||
testutil.AssertEqual(t, DefaultChartWidth, bc.GetWidth())
|
assert.Equal(DefaultChartWidth, bc.GetWidth())
|
||||||
bc.Width = DefaultChartWidth - 1
|
bc.Width = DefaultChartWidth - 1
|
||||||
testutil.AssertEqual(t, DefaultChartWidth-1, bc.GetWidth())
|
assert.Equal(DefaultChartWidth-1, bc.GetWidth())
|
||||||
|
|
||||||
testutil.AssertEqual(t, DefaultChartHeight, bc.GetHeight())
|
assert.Equal(DefaultChartHeight, bc.GetHeight())
|
||||||
bc.Height = DefaultChartHeight - 1
|
bc.Height = DefaultChartHeight - 1
|
||||||
testutil.AssertEqual(t, DefaultChartHeight-1, bc.GetHeight())
|
assert.Equal(DefaultChartHeight-1, bc.GetHeight())
|
||||||
|
|
||||||
testutil.AssertEqual(t, DefaultBarSpacing, bc.GetBarSpacing())
|
assert.Equal(DefaultBarSpacing, bc.GetBarSpacing())
|
||||||
bc.BarSpacing = 150
|
bc.BarSpacing = 150
|
||||||
testutil.AssertEqual(t, 150, bc.GetBarSpacing())
|
assert.Equal(150, bc.GetBarSpacing())
|
||||||
|
|
||||||
testutil.AssertEqual(t, DefaultBarWidth, bc.GetBarWidth())
|
assert.Equal(DefaultBarWidth, bc.GetBarWidth())
|
||||||
bc.BarWidth = 75
|
bc.BarWidth = 75
|
||||||
testutil.AssertEqual(t, 75, bc.GetBarWidth())
|
assert.Equal(75, bc.GetBarWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartRenderNoBars(t *testing.T) {
|
func TestBarChartRenderNoBars(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
err := bc.Render(PNG, bytes.NewBuffer([]byte{}))
|
err := bc.Render(PNG, bytes.NewBuffer([]byte{}))
|
||||||
testutil.AssertNotNil(t, err)
|
assert.NotNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetRanges(t *testing.T) {
|
func TestBarChartGetRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
|
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
testutil.AssertNotNil(t, yr)
|
assert.NotNil(yr)
|
||||||
testutil.AssertFalse(t, yr.IsZero())
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
testutil.AssertEqual(t, -math.MaxFloat64, yr.GetMax())
|
assert.Equal(-math.MaxFloat64, yr.GetMax())
|
||||||
testutil.AssertEqual(t, math.MaxFloat64, yr.GetMin())
|
assert.Equal(math.MaxFloat64, yr.GetMin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetRangesBarsMinMax(t *testing.T) {
|
func TestBarChartGetRangesBarsMinMax(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
|
@ -110,15 +120,15 @@ func TestBarChartGetRangesBarsMinMax(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
testutil.AssertNotNil(t, yr)
|
assert.NotNil(yr)
|
||||||
testutil.AssertFalse(t, yr.IsZero())
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 10, yr.GetMax())
|
assert.Equal(10, yr.GetMax())
|
||||||
testutil.AssertEqual(t, 1, yr.GetMin())
|
assert.Equal(1, yr.GetMin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetRangesMinMax(t *testing.T) {
|
func TestBarChartGetRangesMinMax(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
@ -138,15 +148,15 @@ func TestBarChartGetRangesMinMax(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
testutil.AssertNotNil(t, yr)
|
assert.NotNil(yr)
|
||||||
testutil.AssertFalse(t, yr.IsZero())
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 15, yr.GetMax())
|
assert.Equal(15, yr.GetMax())
|
||||||
testutil.AssertEqual(t, 5, yr.GetMin())
|
assert.Equal(5, yr.GetMin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetRangesTicksMinMax(t *testing.T) {
|
func TestBarChartGetRangesTicksMinMax(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
@ -162,56 +172,57 @@ func TestBarChartGetRangesTicksMinMax(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
testutil.AssertNotNil(t, yr)
|
assert.NotNil(yr)
|
||||||
testutil.AssertFalse(t, yr.IsZero())
|
assert.False(yr.IsZero())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 11, yr.GetMax())
|
assert.Equal(11, yr.GetMax())
|
||||||
testutil.AssertEqual(t, 7, yr.GetMin())
|
assert.Equal(7, yr.GetMin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartHasAxes(t *testing.T) {
|
func TestBarChartHasAxes(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
testutil.AssertTrue(t, bc.hasAxes())
|
assert.False(bc.hasAxes())
|
||||||
bc.YAxis = YAxis{
|
bc.YAxis = YAxis{
|
||||||
Style: Hidden(),
|
Style: StyleShow(),
|
||||||
}
|
}
|
||||||
testutil.AssertFalse(t, bc.hasAxes())
|
|
||||||
|
assert.True(bc.hasAxes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetDefaultCanvasBox(t *testing.T) {
|
func TestBarChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
b := bc.getDefaultCanvasBox()
|
b := bc.getDefaultCanvasBox()
|
||||||
testutil.AssertFalse(t, b.IsZero())
|
assert.False(b.IsZero())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartSetRangeDomains(t *testing.T) {
|
func TestBarChartSetRangeDomains(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
cb := bc.box()
|
cb := bc.box()
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
yr2 := bc.setRangeDomains(cb, yr)
|
yr2 := bc.setRangeDomains(cb, yr)
|
||||||
testutil.AssertNotZero(t, yr2.GetDomain())
|
assert.NotZero(yr2.GetDomain())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetValueFormatters(t *testing.T) {
|
func TestBarChartGetValueFormatters(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{}
|
bc := BarChart{}
|
||||||
vf := bc.getValueFormatters()
|
vf := bc.getValueFormatters()
|
||||||
testutil.AssertNotNil(t, vf)
|
assert.NotNil(vf)
|
||||||
testutil.AssertEqual(t, "1234.00", vf(1234.0))
|
assert.Equal("1234.00", vf(1234.0))
|
||||||
|
|
||||||
bc.YAxis.ValueFormatter = func(_ interface{}) string { return "test" }
|
bc.YAxis.ValueFormatter = func(_ interface{}) string { return "test" }
|
||||||
testutil.AssertEqual(t, "test", bc.getValueFormatters()(1234))
|
assert.Equal("test", bc.getValueFormatters()(1234))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartGetAxesTicks(t *testing.T) {
|
func TestBarChartGetAxesTicks(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Bars: []Value{
|
Bars: []Value{
|
||||||
|
@ -222,21 +233,20 @@ func TestBarChartGetAxesTicks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := PNG(128, 128)
|
r, err := PNG(128, 128)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
yr := bc.getRanges()
|
yr := bc.getRanges()
|
||||||
yf := bc.getValueFormatters()
|
yf := bc.getValueFormatters()
|
||||||
|
|
||||||
bc.YAxis.Style.Hidden = true
|
|
||||||
ticks := bc.getAxesTicks(r, yr, yf)
|
ticks := bc.getAxesTicks(r, yr, yf)
|
||||||
testutil.AssertEmpty(t, ticks)
|
assert.Empty(ticks)
|
||||||
|
|
||||||
bc.YAxis.Style.Hidden = false
|
bc.YAxis.Style.Show = true
|
||||||
ticks = bc.getAxesTicks(r, yr, yf)
|
ticks = bc.getAxesTicks(r, yr, yf)
|
||||||
testutil.AssertLen(t, ticks, 2)
|
assert.Len(ticks, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartCalculateEffectiveBarSpacing(t *testing.T) {
|
func TestBarChartCalculateEffectiveBarSpacing(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
|
@ -251,15 +261,15 @@ func TestBarChartCalculateEffectiveBarSpacing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
testutil.AssertNotZero(t, spacing)
|
assert.NotZero(spacing)
|
||||||
|
|
||||||
bc.BarWidth = 250
|
bc.BarWidth = 250
|
||||||
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
testutil.AssertZero(t, spacing)
|
assert.Zero(spacing)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChartCalculateEffectiveBarWidth(t *testing.T) {
|
func TestBarChartCalculateEffectiveBarWidth(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
bc := BarChart{
|
bc := BarChart{
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
|
@ -276,35 +286,35 @@ func TestBarChartCalculateEffectiveBarWidth(t *testing.T) {
|
||||||
cb := bc.box()
|
cb := bc.box()
|
||||||
|
|
||||||
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
spacing := bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
testutil.AssertNotZero(t, spacing)
|
assert.NotZero(spacing)
|
||||||
|
|
||||||
barWidth := bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
barWidth := bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
||||||
testutil.AssertEqual(t, 10, barWidth)
|
assert.Equal(10, barWidth)
|
||||||
|
|
||||||
bc.BarWidth = 250
|
bc.BarWidth = 250
|
||||||
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
spacing = bc.calculateEffectiveBarSpacing(bc.box())
|
||||||
testutil.AssertZero(t, spacing)
|
assert.Zero(spacing)
|
||||||
barWidth = bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
barWidth = bc.calculateEffectiveBarWidth(bc.box(), spacing)
|
||||||
testutil.AssertEqual(t, 199, barWidth)
|
assert.Equal(199, barWidth)
|
||||||
|
|
||||||
testutil.AssertEqual(t, cb.Width()+1, bc.calculateTotalBarWidth(barWidth, spacing))
|
assert.Equal(cb.Width()+1, bc.calculateTotalBarWidth(barWidth, spacing))
|
||||||
|
|
||||||
bw, bs, total := bc.calculateScaledTotalWidth(cb)
|
bw, bs, total := bc.calculateScaledTotalWidth(cb)
|
||||||
testutil.AssertEqual(t, spacing, bs)
|
assert.Equal(spacing, bs)
|
||||||
testutil.AssertEqual(t, barWidth, bw)
|
assert.Equal(barWidth, bw)
|
||||||
testutil.AssertEqual(t, cb.Width()+1, total)
|
assert.Equal(cb.Width()+1, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBarChatGetTitleFontSize(t *testing.T) {
|
func TestBarChatGetTitleFontSize(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
size := BarChart{Width: 2049, Height: 2049}.getTitleFontSize()
|
size := BarChart{Width: 2049, Height: 2049}.getTitleFontSize()
|
||||||
testutil.AssertEqual(t, 48, size)
|
assert.Equal(48, size)
|
||||||
size = BarChart{Width: 1025, Height: 1025}.getTitleFontSize()
|
size = BarChart{Width: 1025, Height: 1025}.getTitleFontSize()
|
||||||
testutil.AssertEqual(t, 24, size)
|
assert.Equal(24, size)
|
||||||
size = BarChart{Width: 513, Height: 513}.getTitleFontSize()
|
size = BarChart{Width: 513, Height: 513}.getTitleFontSize()
|
||||||
testutil.AssertEqual(t, 18, size)
|
assert.Equal(18, size)
|
||||||
size = BarChart{Width: 257, Height: 257}.getTitleFontSize()
|
size = BarChart{Width: 257, Height: 257}.getTitleFontSize()
|
||||||
testutil.AssertEqual(t, 12, size)
|
assert.Equal(12, size)
|
||||||
size = BarChart{Width: 128, Height: 128}.getTitleFontSize()
|
size = BarChart{Width: 128, Height: 128}.getTitleFontSize()
|
||||||
testutil.AssertEqual(t, 10, size)
|
assert.Equal(10, size)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,8 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
|
||||||
|
|
||||||
// Interface Assertions.
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
var (
|
|
||||||
_ Series = (*BollingerBandsSeries)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||||
|
@ -20,7 +17,7 @@ type BollingerBandsSeries struct {
|
||||||
K float64
|
K float64
|
||||||
InnerSeries ValuesProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
valueBuffer *ValueBuffer
|
valueBuffer *seq.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
|
@ -70,7 +67,7 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer == nil || index == 0 {
|
if bbs.valueBuffer == nil || index == 0 {
|
||||||
bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod())
|
bbs.valueBuffer = seq.NewBufferWithCapacity(bbs.GetPeriod())
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||||
bbs.valueBuffer.Dequeue()
|
bbs.valueBuffer.Dequeue()
|
||||||
|
@ -79,8 +76,8 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
bbs.valueBuffer.Enqueue(py)
|
bbs.valueBuffer.Enqueue(py)
|
||||||
x = px
|
x = px
|
||||||
|
|
||||||
ay := Seq{bbs.valueBuffer}.Average()
|
ay := seq.New(bbs.valueBuffer).Average()
|
||||||
std := Seq{bbs.valueBuffer}.StdDev()
|
std := seq.New(bbs.valueBuffer).StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
@ -99,15 +96,15 @@ func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) {
|
||||||
startAt = 0
|
startAt = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
vb := NewValueBufferWithCapacity(period)
|
vb := seq.NewBufferWithCapacity(period)
|
||||||
for index := startAt; index < seriesLength; index++ {
|
for index := startAt; index < seriesLength; index++ {
|
||||||
xn, yn := bbs.InnerSeries.GetValues(index)
|
xn, yn := bbs.InnerSeries.GetValues(index)
|
||||||
vb.Enqueue(yn)
|
vb.Enqueue(yn)
|
||||||
x = xn
|
x = xn
|
||||||
}
|
}
|
||||||
|
|
||||||
ay := Seq{vb}.Average()
|
ay := seq.Seq{Provider: vb}.Average()
|
||||||
std := Seq{vb}.StdDev()
|
std := seq.Seq{Provider: vb}.StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
|
|
@ -5,15 +5,16 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBollingerBandSeries(t *testing.T) {
|
func TestBollingerBandSeries(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: LinearRange(1.0, 100.0),
|
X: seq.Range(1.0, 100.0),
|
||||||
Y: RandomValuesWithMax(100, 1024),
|
Y: seq.RandomValuesWithMax(100, 1024),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
@ -29,16 +30,16 @@ func TestBollingerBandSeries(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := bbs.GetPeriod(); x < 100; x++ {
|
for x := bbs.GetPeriod(); x < 100; x++ {
|
||||||
testutil.AssertTrue(t, y1values[x] > y2values[x], fmt.Sprintf("%v vs. %v", y1values[x], y2values[x]))
|
assert.True(y1values[x] > y2values[x], fmt.Sprintf("%v vs. %v", y1values[x], y2values[x]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBollingerBandLastValue(t *testing.T) {
|
func TestBollingerBandLastValue(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := mockValuesProvider{
|
s1 := mockValuesProvider{
|
||||||
X: LinearRange(1.0, 100.0),
|
X: seq.Range(1.0, 100.0),
|
||||||
Y: LinearRange(1.0, 100.0),
|
Y: seq.Range(1.0, 100.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
bbs := &BollingerBandsSeries{
|
bbs := &BollingerBandsSeries{
|
||||||
|
@ -46,7 +47,7 @@ func TestBollingerBandLastValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y1, y2 := bbs.GetBoundedLastValues()
|
x, y1, y2 := bbs.GetBoundedLastValues()
|
||||||
testutil.AssertEqual(t, 100.0, x)
|
assert.Equal(100.0, x)
|
||||||
testutil.AssertEqual(t, 101, math.Floor(y1))
|
assert.Equal(101, math.Floor(y1))
|
||||||
testutil.AssertEqual(t, 83, math.Floor(y2))
|
assert.Equal(83, math.Floor(y2))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// BoundedLastValuesAnnotationSeries returns a last value annotation series for a bounded values provider.
|
|
||||||
func BoundedLastValuesAnnotationSeries(innerSeries FullBoundedValuesProvider, vfs ...ValueFormatter) AnnotationSeries {
|
|
||||||
lvx, lvy1, lvy2 := innerSeries.GetBoundedLastValues()
|
|
||||||
|
|
||||||
var vf ValueFormatter
|
|
||||||
if len(vfs) > 0 {
|
|
||||||
vf = vfs[0]
|
|
||||||
} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped {
|
|
||||||
_, vf = typed.GetValueFormatters()
|
|
||||||
} else {
|
|
||||||
vf = FloatValueFormatter
|
|
||||||
}
|
|
||||||
|
|
||||||
label1 := vf(lvy1)
|
|
||||||
label2 := vf(lvy2)
|
|
||||||
|
|
||||||
var seriesName string
|
|
||||||
var seriesStyle Style
|
|
||||||
if typed, isTyped := innerSeries.(Series); isTyped {
|
|
||||||
seriesName = fmt.Sprintf("%s - Last Values", typed.GetName())
|
|
||||||
seriesStyle = typed.GetStyle()
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnnotationSeries{
|
|
||||||
Name: seriesName,
|
|
||||||
Style: seriesStyle,
|
|
||||||
Annotations: []Value2{
|
|
||||||
{XValue: lvx, YValue: lvy1, Label: label1},
|
|
||||||
{XValue: lvx, YValue: lvy2, Label: label2},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
147
box.go
|
@ -2,7 +2,8 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -89,12 +90,12 @@ func (b Box) GetBottom(defaults ...int) int {
|
||||||
|
|
||||||
// Width returns the width
|
// Width returns the width
|
||||||
func (b Box) Width() int {
|
func (b Box) Width() int {
|
||||||
return AbsInt(b.Right - b.Left)
|
return util.Math.AbsInt(b.Right - b.Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height
|
// Height returns the height
|
||||||
func (b Box) Height() int {
|
func (b Box) Height() int {
|
||||||
return AbsInt(b.Bottom - b.Top)
|
return util.Math.AbsInt(b.Bottom - b.Top)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center returns the center of the box
|
// Center returns the center of the box
|
||||||
|
@ -146,10 +147,10 @@ func (b Box) Equals(other Box) bool {
|
||||||
// Grow grows a box based on another box.
|
// Grow grows a box based on another box.
|
||||||
func (b Box) Grow(other Box) Box {
|
func (b Box) Grow(other Box) Box {
|
||||||
return Box{
|
return Box{
|
||||||
Top: MinInt(b.Top, other.Top),
|
Top: util.Math.MinInt(b.Top, other.Top),
|
||||||
Left: MinInt(b.Left, other.Left),
|
Left: util.Math.MinInt(b.Left, other.Left),
|
||||||
Right: MaxInt(b.Right, other.Right),
|
Right: util.Math.MaxInt(b.Right, other.Right),
|
||||||
Bottom: MaxInt(b.Bottom, other.Bottom),
|
Bottom: util.Math.MaxInt(b.Bottom, other.Bottom),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,12 +165,12 @@ func (b Box) Shift(x, y int) Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corners returns the box as a set of corners.
|
// Corners returns the box as a set of corners.
|
||||||
func (b Box) Corners() BoxCorners {
|
func (b Box) Corners() Box2d {
|
||||||
return BoxCorners{
|
return Box2d{
|
||||||
TopLeft: Point{b.Left, b.Top},
|
TopLeft: Point{float64(b.Left), float64(b.Top)},
|
||||||
TopRight: Point{b.Right, b.Top},
|
TopRight: Point{float64(b.Right), float64(b.Top)},
|
||||||
BottomRight: Point{b.Right, b.Bottom},
|
BottomRight: Point{float64(b.Right), float64(b.Bottom)},
|
||||||
BottomLeft: Point{b.Left, b.Bottom},
|
BottomLeft: Point{float64(b.Left), float64(b.Bottom)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,10 +221,10 @@ func (b Box) Fit(other Box) Box {
|
||||||
func (b Box) Constrain(other Box) Box {
|
func (b Box) Constrain(other Box) Box {
|
||||||
newBox := b.Clone()
|
newBox := b.Clone()
|
||||||
|
|
||||||
newBox.Top = MaxInt(newBox.Top, other.Top)
|
newBox.Top = util.Math.MaxInt(newBox.Top, other.Top)
|
||||||
newBox.Left = MaxInt(newBox.Left, other.Left)
|
newBox.Left = util.Math.MaxInt(newBox.Left, other.Left)
|
||||||
newBox.Right = MinInt(newBox.Right, other.Right)
|
newBox.Right = util.Math.MinInt(newBox.Right, other.Right)
|
||||||
newBox.Bottom = MinInt(newBox.Bottom, other.Bottom)
|
newBox.Bottom = util.Math.MinInt(newBox.Bottom, other.Bottom)
|
||||||
|
|
||||||
return newBox
|
return newBox
|
||||||
}
|
}
|
||||||
|
@ -253,115 +254,3 @@ func (b Box) OuterConstrain(bounds, other Box) Box {
|
||||||
}
|
}
|
||||||
return newBox
|
return newBox
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Box) Validate() error {
|
|
||||||
if b.Left < 0 {
|
|
||||||
return fmt.Errorf("invalid left; must be >= 0")
|
|
||||||
}
|
|
||||||
if b.Right < 0 {
|
|
||||||
return fmt.Errorf("invalid right; must be > 0")
|
|
||||||
}
|
|
||||||
if b.Top < 0 {
|
|
||||||
return fmt.Errorf("invalid top; must be > 0")
|
|
||||||
}
|
|
||||||
if b.Bottom < 0 {
|
|
||||||
return fmt.Errorf("invalid bottom; must be > 0")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoxCorners is a box with independent corners.
|
|
||||||
type BoxCorners struct {
|
|
||||||
TopLeft, TopRight, BottomRight, BottomLeft Point
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box return the BoxCorners as a regular box.
|
|
||||||
func (bc BoxCorners) Box() Box {
|
|
||||||
return Box{
|
|
||||||
Top: MinInt(bc.TopLeft.Y, bc.TopRight.Y),
|
|
||||||
Left: MinInt(bc.TopLeft.X, bc.BottomLeft.X),
|
|
||||||
Right: MaxInt(bc.TopRight.X, bc.BottomRight.X),
|
|
||||||
Bottom: MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width returns the width
|
|
||||||
func (bc BoxCorners) Width() int {
|
|
||||||
minLeft := MinInt(bc.TopLeft.X, bc.BottomLeft.X)
|
|
||||||
maxRight := MaxInt(bc.TopRight.X, bc.BottomRight.X)
|
|
||||||
return maxRight - minLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
// Height returns the height
|
|
||||||
func (bc BoxCorners) Height() int {
|
|
||||||
minTop := MinInt(bc.TopLeft.Y, bc.TopRight.Y)
|
|
||||||
maxBottom := MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
|
||||||
return maxBottom - minTop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center returns the center of the box
|
|
||||||
func (bc BoxCorners) Center() (x, y int) {
|
|
||||||
|
|
||||||
left := MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
|
|
||||||
right := MeanInt(bc.TopRight.X, bc.BottomRight.X)
|
|
||||||
x = ((right - left) >> 1) + left
|
|
||||||
|
|
||||||
top := MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
|
|
||||||
bottom := MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
|
|
||||||
y = ((bottom - top) >> 1) + top
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate rotates the box.
|
|
||||||
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners {
|
|
||||||
cx, cy := bc.Center()
|
|
||||||
|
|
||||||
thetaRadians := DegreesToRadians(thetaDegrees)
|
|
||||||
|
|
||||||
tlx, tly := RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
|
|
||||||
trx, try := RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
|
|
||||||
brx, bry := RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
|
|
||||||
blx, bly := RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
|
|
||||||
|
|
||||||
return BoxCorners{
|
|
||||||
TopLeft: Point{tlx, tly},
|
|
||||||
TopRight: Point{trx, try},
|
|
||||||
BottomRight: Point{brx, bry},
|
|
||||||
BottomLeft: Point{blx, bly},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns if the box equals another box.
|
|
||||||
func (bc BoxCorners) Equals(other BoxCorners) bool {
|
|
||||||
return bc.TopLeft.Equals(other.TopLeft) &&
|
|
||||||
bc.TopRight.Equals(other.TopRight) &&
|
|
||||||
bc.BottomRight.Equals(other.BottomRight) &&
|
|
||||||
bc.BottomLeft.Equals(other.BottomLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc BoxCorners) String() string {
|
|
||||||
return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point is an X,Y pair
|
|
||||||
type Point struct {
|
|
||||||
X, Y int
|
|
||||||
}
|
|
||||||
|
|
||||||
// DistanceTo calculates the distance to another point.
|
|
||||||
func (p Point) DistanceTo(other Point) float64 {
|
|
||||||
dx := math.Pow(float64(p.X-other.X), 2)
|
|
||||||
dy := math.Pow(float64(p.Y-other.Y), 2)
|
|
||||||
return math.Pow(dx+dy, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns if a point equals another point.
|
|
||||||
func (p Point) Equals(other Point) bool {
|
|
||||||
return p.X == other.X && p.Y == other.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the point.
|
|
||||||
func (p Point) String() string {
|
|
||||||
return fmt.Sprintf("P{%d,%d}", p.X, p.Y)
|
|
||||||
}
|
|
||||||
|
|
183
box_2d.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Box2d is a box with (4) independent corners.
|
||||||
|
// It is used when dealing with ~rotated~ boxes.
|
||||||
|
type Box2d struct {
|
||||||
|
TopLeft, TopRight, BottomRight, BottomLeft Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Points returns the constituent points of the box.
|
||||||
|
func (bc Box2d) Points() []Point {
|
||||||
|
return []Point{
|
||||||
|
bc.TopRight,
|
||||||
|
bc.BottomRight,
|
||||||
|
bc.BottomLeft,
|
||||||
|
bc.TopLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Box return the Box2d as a regular box.
|
||||||
|
func (bc Box2d) Box() Box {
|
||||||
|
return Box{
|
||||||
|
Top: int(bc.Top()),
|
||||||
|
Left: int(bc.Left()),
|
||||||
|
Right: int(bc.Right()),
|
||||||
|
Bottom: int(bc.Bottom()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top returns the top-most corner y value.
|
||||||
|
func (bc Box2d) Top() float64 {
|
||||||
|
return math.Min(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left returns the left-most corner x value.
|
||||||
|
func (bc Box2d) Left() float64 {
|
||||||
|
return math.Min(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right returns the right-most corner x value.
|
||||||
|
func (bc Box2d) Right() float64 {
|
||||||
|
return math.Max(bc.TopRight.X, bc.BottomRight.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom returns the bottom-most corner y value.
|
||||||
|
func (bc Box2d) Bottom() float64 {
|
||||||
|
return math.Max(bc.BottomLeft.Y, bc.BottomLeft.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width returns the width
|
||||||
|
func (bc Box2d) Width() float64 {
|
||||||
|
minLeft := math.Min(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
|
maxRight := math.Max(bc.TopRight.X, bc.BottomRight.X)
|
||||||
|
return maxRight - minLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the height
|
||||||
|
func (bc Box2d) Height() float64 {
|
||||||
|
minTop := math.Min(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
|
maxBottom := math.Max(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||||
|
return maxBottom - minTop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center returns the center of the box
|
||||||
|
func (bc Box2d) Center() (x, y float64) {
|
||||||
|
left := util.Math.Mean(bc.TopLeft.X, bc.BottomLeft.X)
|
||||||
|
right := util.Math.Mean(bc.TopRight.X, bc.BottomRight.X)
|
||||||
|
x = ((right - left) / 2.0) + left
|
||||||
|
|
||||||
|
top := util.Math.Mean(bc.TopLeft.Y, bc.TopRight.Y)
|
||||||
|
bottom := util.Math.Mean(bc.BottomLeft.Y, bc.BottomRight.Y)
|
||||||
|
y = ((bottom - top) / 2.0) + top
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate rotates the box.
|
||||||
|
func (bc Box2d) Rotate(thetaDegrees float64) Box2d {
|
||||||
|
cx, cy := bc.Center()
|
||||||
|
|
||||||
|
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
|
||||||
|
|
||||||
|
tlx, tly := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.TopLeft.X), int(bc.TopLeft.Y), thetaRadians)
|
||||||
|
trx, try := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.TopRight.X), int(bc.TopRight.Y), thetaRadians)
|
||||||
|
brx, bry := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.BottomRight.X), int(bc.BottomRight.Y), thetaRadians)
|
||||||
|
blx, bly := util.Math.RotateCoordinate(int(cx), int(cy), int(bc.BottomLeft.X), int(bc.BottomLeft.Y), thetaRadians)
|
||||||
|
|
||||||
|
return Box2d{
|
||||||
|
TopLeft: Point{float64(tlx), float64(tly)},
|
||||||
|
TopRight: Point{float64(trx), float64(try)},
|
||||||
|
BottomRight: Point{float64(brx), float64(bry)},
|
||||||
|
BottomLeft: Point{float64(blx), float64(bly)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift shifts a box by a given x and y value.
|
||||||
|
func (bc Box2d) Shift(x, y float64) Box2d {
|
||||||
|
return Box2d{
|
||||||
|
TopLeft: bc.TopLeft.Shift(x, y),
|
||||||
|
TopRight: bc.TopRight.Shift(x, y),
|
||||||
|
BottomRight: bc.BottomRight.Shift(x, y),
|
||||||
|
BottomLeft: bc.BottomLeft.Shift(x, y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns if the box equals another box.
|
||||||
|
func (bc Box2d) Equals(other Box2d) bool {
|
||||||
|
return bc.TopLeft.Equals(other.TopLeft) &&
|
||||||
|
bc.TopRight.Equals(other.TopRight) &&
|
||||||
|
bc.BottomRight.Equals(other.BottomRight) &&
|
||||||
|
bc.BottomLeft.Equals(other.BottomLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlaps returns if two boxes overlap.
|
||||||
|
func (bc Box2d) Overlaps(other Box2d) bool {
|
||||||
|
pa := bc.Points()
|
||||||
|
pb := other.Points()
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
for j := 0; j < 4; j++ {
|
||||||
|
pa0 := pa[i]
|
||||||
|
pa1 := pa[(i+1)%4]
|
||||||
|
|
||||||
|
pb0 := pb[j]
|
||||||
|
pb1 := pb[(j+1)%4]
|
||||||
|
|
||||||
|
if util.Math.LinesIntersect(pa0.X, pa0.Y, pa1.X, pa1.Y, pb0.X, pb0.Y, pb1.X, pb1.Y) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow grows a box by a given set of dimensions.
|
||||||
|
func (bc Box2d) Grow(by Box) Box2d {
|
||||||
|
top, left, right, bottom := float64(by.Top), float64(by.Left), float64(by.Right), float64(by.Bottom)
|
||||||
|
return Box2d{
|
||||||
|
TopLeft: Point{X: bc.TopLeft.X - left, Y: bc.TopLeft.Y - top},
|
||||||
|
TopRight: Point{X: bc.TopRight.X + right, Y: bc.TopRight.Y - top},
|
||||||
|
BottomRight: Point{X: bc.BottomRight.X + right, Y: bc.BottomRight.Y + bottom},
|
||||||
|
BottomLeft: Point{X: bc.BottomLeft.X - left, Y: bc.BottomLeft.Y + bottom},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc Box2d) String() string {
|
||||||
|
return fmt.Sprintf("Box2d{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point is an X,Y pair
|
||||||
|
type Point struct {
|
||||||
|
X, Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift shifts a point.
|
||||||
|
func (p Point) Shift(x, y float64) Point {
|
||||||
|
return Point{
|
||||||
|
X: p.X + x,
|
||||||
|
Y: p.Y + y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DistanceTo calculates the distance to another point.
|
||||||
|
func (p Point) DistanceTo(other Point) float64 {
|
||||||
|
dx := math.Pow(p.X-other.X, 2)
|
||||||
|
dy := math.Pow(p.Y-other.Y, 2)
|
||||||
|
return math.Pow(dx+dy, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns if a point equals another point.
|
||||||
|
func (p Point) Equals(other Point) bool {
|
||||||
|
return p.X == other.X && p.Y == other.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the point.
|
||||||
|
func (p Point) String() string {
|
||||||
|
return fmt.Sprintf("(%.2f,%.2f)", p.X, p.Y)
|
||||||
|
}
|
66
box_2d_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBox2dCenter(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := Box2d{
|
||||||
|
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 TestBox2dRotate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := Box2d{
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBox2dOverlaps(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
bc := Box2d{
|
||||||
|
TopLeft: Point{5, 5},
|
||||||
|
TopRight: Point{15, 5},
|
||||||
|
BottomRight: Point{15, 15},
|
||||||
|
BottomLeft: Point{5, 15},
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift meaningfully the full width of bc right.
|
||||||
|
bc2 := bc.Shift(bc.Width()+1, 0)
|
||||||
|
assert.False(bc.Overlaps(bc2), fmt.Sprintf("%v\n\t\tshould not overlap\n\t%v", bc, bc2))
|
||||||
|
|
||||||
|
// shift meaningfully the full height of bc down.
|
||||||
|
bc3 := bc.Shift(0, bc.Height()+1)
|
||||||
|
assert.False(bc.Overlaps(bc3), fmt.Sprintf("%v\n\t\tshould not overlap\n\t%v", bc, bc3))
|
||||||
|
|
||||||
|
bc4 := bc.Shift(5, 0)
|
||||||
|
assert.True(bc.Overlaps(bc4))
|
||||||
|
|
||||||
|
bc5 := bc.Shift(0, 5)
|
||||||
|
assert.True(bc.Overlaps(bc5))
|
||||||
|
|
||||||
|
bcr := bc.Rotate(45)
|
||||||
|
bcr2 := bc.Rotate(45).Shift(bc.Width()/2.0, 0)
|
||||||
|
assert.True(bcr.Overlaps(bcr2), fmt.Sprintf("%v\n\t\tshould overlap\n\t%v", bcr, bcr2))
|
||||||
|
}
|
153
box_test.go
|
@ -4,131 +4,131 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBoxClone(t *testing.T) {
|
func TestBoxClone(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
b := a.Clone()
|
b := a.Clone()
|
||||||
testutil.AssertTrue(t, a.Equals(b))
|
assert.True(a.Equals(b))
|
||||||
testutil.AssertTrue(t, b.Equals(a))
|
assert.True(b.Equals(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxEquals(t *testing.T) {
|
func TestBoxEquals(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
b := Box{Top: 10, Left: 10, Right: 30, Bottom: 30}
|
b := Box{Top: 10, Left: 10, Right: 30, Bottom: 30}
|
||||||
c := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
c := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
testutil.AssertTrue(t, a.Equals(a))
|
assert.True(a.Equals(a))
|
||||||
testutil.AssertTrue(t, a.Equals(c))
|
assert.True(a.Equals(c))
|
||||||
testutil.AssertTrue(t, c.Equals(a))
|
assert.True(c.Equals(a))
|
||||||
testutil.AssertFalse(t, a.Equals(b))
|
assert.False(a.Equals(b))
|
||||||
testutil.AssertFalse(t, c.Equals(b))
|
assert.False(c.Equals(b))
|
||||||
testutil.AssertFalse(t, b.Equals(a))
|
assert.False(b.Equals(a))
|
||||||
testutil.AssertFalse(t, b.Equals(c))
|
assert.False(b.Equals(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxIsBiggerThan(t *testing.T) {
|
func TestBoxIsBiggerThan(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
testutil.AssertTrue(t, a.IsBiggerThan(b))
|
assert.True(a.IsBiggerThan(b))
|
||||||
testutil.AssertFalse(t, a.IsBiggerThan(c))
|
assert.False(a.IsBiggerThan(c))
|
||||||
testutil.AssertTrue(t, c.IsBiggerThan(a))
|
assert.True(c.IsBiggerThan(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxIsSmallerThan(t *testing.T) {
|
func TestBoxIsSmallerThan(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
testutil.AssertFalse(t, a.IsSmallerThan(b))
|
assert.False(a.IsSmallerThan(b))
|
||||||
testutil.AssertTrue(t, a.IsSmallerThan(c))
|
assert.True(a.IsSmallerThan(c))
|
||||||
testutil.AssertFalse(t, c.IsSmallerThan(a))
|
assert.False(c.IsSmallerThan(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxGrow(t *testing.T) {
|
func TestBoxGrow(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 1, Left: 2, Right: 15, Bottom: 15}
|
a := Box{Top: 1, Left: 2, Right: 15, Bottom: 15}
|
||||||
b := Box{Top: 4, Left: 5, Right: 30, Bottom: 35}
|
b := Box{Top: 4, Left: 5, Right: 30, Bottom: 35}
|
||||||
c := a.Grow(b)
|
c := a.Grow(b)
|
||||||
testutil.AssertFalse(t, c.Equals(b))
|
assert.False(c.Equals(b))
|
||||||
testutil.AssertFalse(t, c.Equals(a))
|
assert.False(c.Equals(a))
|
||||||
testutil.AssertEqual(t, 1, c.Top)
|
assert.Equal(1, c.Top)
|
||||||
testutil.AssertEqual(t, 2, c.Left)
|
assert.Equal(2, c.Left)
|
||||||
testutil.AssertEqual(t, 30, c.Right)
|
assert.Equal(30, c.Right)
|
||||||
testutil.AssertEqual(t, 35, c.Bottom)
|
assert.Equal(35, c.Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxFit(t *testing.T) {
|
func TestBoxFit(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
fab := a.Fit(b)
|
fab := a.Fit(b)
|
||||||
testutil.AssertEqual(t, a.Left, fab.Left)
|
assert.Equal(a.Left, fab.Left)
|
||||||
testutil.AssertEqual(t, a.Right, fab.Right)
|
assert.Equal(a.Right, fab.Right)
|
||||||
testutil.AssertTrue(t, fab.Top < fab.Bottom)
|
assert.True(fab.Top < fab.Bottom)
|
||||||
testutil.AssertTrue(t, fab.Left < fab.Right)
|
assert.True(fab.Left < fab.Right)
|
||||||
testutil.AssertTrue(t, math.Abs(b.Aspect()-fab.Aspect()) < 0.02)
|
assert.True(math.Abs(b.Aspect()-fab.Aspect()) < 0.02)
|
||||||
|
|
||||||
fac := a.Fit(c)
|
fac := a.Fit(c)
|
||||||
testutil.AssertEqual(t, a.Top, fac.Top)
|
assert.Equal(a.Top, fac.Top)
|
||||||
testutil.AssertEqual(t, a.Bottom, fac.Bottom)
|
assert.Equal(a.Bottom, fac.Bottom)
|
||||||
testutil.AssertTrue(t, math.Abs(c.Aspect()-fac.Aspect()) < 0.02)
|
assert.True(math.Abs(c.Aspect()-fac.Aspect()) < 0.02)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxConstrain(t *testing.T) {
|
func TestBoxConstrain(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
cab := a.Constrain(b)
|
cab := a.Constrain(b)
|
||||||
testutil.AssertEqual(t, 64, cab.Top)
|
assert.Equal(64, cab.Top)
|
||||||
testutil.AssertEqual(t, 64, cab.Left)
|
assert.Equal(64, cab.Left)
|
||||||
testutil.AssertEqual(t, 192, cab.Right)
|
assert.Equal(192, cab.Right)
|
||||||
testutil.AssertEqual(t, 170, cab.Bottom)
|
assert.Equal(170, cab.Bottom)
|
||||||
|
|
||||||
cac := a.Constrain(c)
|
cac := a.Constrain(c)
|
||||||
testutil.AssertEqual(t, 64, cac.Top)
|
assert.Equal(64, cac.Top)
|
||||||
testutil.AssertEqual(t, 64, cac.Left)
|
assert.Equal(64, cac.Left)
|
||||||
testutil.AssertEqual(t, 170, cac.Right)
|
assert.Equal(170, cac.Right)
|
||||||
testutil.AssertEqual(t, 192, cac.Bottom)
|
assert.Equal(192, cac.Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxOuterConstrain(t *testing.T) {
|
func TestBoxOuterConstrain(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
box := NewBox(0, 0, 100, 100)
|
box := NewBox(0, 0, 100, 100)
|
||||||
canvas := NewBox(5, 5, 95, 95)
|
canvas := NewBox(5, 5, 95, 95)
|
||||||
taller := NewBox(-10, 5, 50, 50)
|
taller := NewBox(-10, 5, 50, 50)
|
||||||
|
|
||||||
c := canvas.OuterConstrain(box, taller)
|
c := canvas.OuterConstrain(box, taller)
|
||||||
testutil.AssertEqual(t, 15, c.Top, c.String())
|
assert.Equal(15, c.Top, c.String())
|
||||||
testutil.AssertEqual(t, 5, c.Left, c.String())
|
assert.Equal(5, c.Left, c.String())
|
||||||
testutil.AssertEqual(t, 95, c.Right, c.String())
|
assert.Equal(95, c.Right, c.String())
|
||||||
testutil.AssertEqual(t, 95, c.Bottom, c.String())
|
assert.Equal(95, c.Bottom, c.String())
|
||||||
|
|
||||||
wider := NewBox(5, 5, 110, 50)
|
wider := NewBox(5, 5, 110, 50)
|
||||||
d := canvas.OuterConstrain(box, wider)
|
d := canvas.OuterConstrain(box, wider)
|
||||||
testutil.AssertEqual(t, 5, d.Top, d.String())
|
assert.Equal(5, d.Top, d.String())
|
||||||
testutil.AssertEqual(t, 5, d.Left, d.String())
|
assert.Equal(5, d.Left, d.String())
|
||||||
testutil.AssertEqual(t, 85, d.Right, d.String())
|
assert.Equal(85, d.Right, d.String())
|
||||||
testutil.AssertEqual(t, 95, d.Bottom, d.String())
|
assert.Equal(95, d.Bottom, d.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxShift(t *testing.T) {
|
func TestBoxShift(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
b := Box{
|
b := Box{
|
||||||
Top: 5,
|
Top: 5,
|
||||||
|
@ -138,14 +138,14 @@ func TestBoxShift(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
shifted := b.Shift(1, 2)
|
shifted := b.Shift(1, 2)
|
||||||
testutil.AssertEqual(t, 7, shifted.Top)
|
assert.Equal(7, shifted.Top)
|
||||||
testutil.AssertEqual(t, 6, shifted.Left)
|
assert.Equal(6, shifted.Left)
|
||||||
testutil.AssertEqual(t, 11, shifted.Right)
|
assert.Equal(11, shifted.Right)
|
||||||
testutil.AssertEqual(t, 12, shifted.Bottom)
|
assert.Equal(12, shifted.Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoxCenter(t *testing.T) {
|
func TestBoxCenter(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
b := Box{
|
b := Box{
|
||||||
Top: 10,
|
Top: 10,
|
||||||
|
@ -154,35 +154,6 @@ func TestBoxCenter(t *testing.T) {
|
||||||
Bottom: 30,
|
Bottom: 30,
|
||||||
}
|
}
|
||||||
cx, cy := b.Center()
|
cx, cy := b.Center()
|
||||||
testutil.AssertEqual(t, 15, cx)
|
assert.Equal(15, cx)
|
||||||
testutil.AssertEqual(t, 20, cy)
|
assert.Equal(20, cy)
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoxCornersCenter(t *testing.T) {
|
|
||||||
// replaced new assertions helper
|
|
||||||
|
|
||||||
bc := BoxCorners{
|
|
||||||
TopLeft: Point{5, 5},
|
|
||||||
TopRight: Point{15, 5},
|
|
||||||
BottomRight: Point{15, 15},
|
|
||||||
BottomLeft: Point{5, 15},
|
|
||||||
}
|
|
||||||
|
|
||||||
cx, cy := bc.Center()
|
|
||||||
testutil.AssertEqual(t, 10, cx)
|
|
||||||
testutil.AssertEqual(t, 10, cy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoxCornersRotate(t *testing.T) {
|
|
||||||
// replaced new assertions helper
|
|
||||||
|
|
||||||
bc := BoxCorners{
|
|
||||||
TopLeft: Point{5, 5},
|
|
||||||
TopRight: Point{15, 5},
|
|
||||||
BottomRight: Point{15, 15},
|
|
||||||
BottomLeft: Point{5, 15},
|
|
||||||
}
|
|
||||||
|
|
||||||
rotated := bc.Rotate(45)
|
|
||||||
testutil.AssertTrue(t, rotated.TopLeft.Equals(Point{10, 3}), rotated.String())
|
|
||||||
}
|
}
|
||||||
|
|
157
candlestick_series.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CandleValue is a day's data for a candlestick plot.
|
||||||
|
type CandleValue struct {
|
||||||
|
Timestamp time.Time
|
||||||
|
High float64
|
||||||
|
Low float64
|
||||||
|
Open float64
|
||||||
|
Close float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string value for the candle value.
|
||||||
|
func (cv CandleValue) String() string {
|
||||||
|
return fmt.Sprintf("candle %s high: %.2f low: %.2f open: %.2f close: %.2f", cv.Timestamp.Format("2006-01-02"), cv.High, cv.Low, cv.Open, cv.Close)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the value is zero or not.
|
||||||
|
func (cv CandleValue) IsZero() bool {
|
||||||
|
return cv.Timestamp.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CandlestickSeries is a special type of series that takes a norma value provider
|
||||||
|
// and maps it to day value stats (high, low, open, close).
|
||||||
|
type CandlestickSeries struct {
|
||||||
|
Name string
|
||||||
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
|
||||||
|
// CandleValues will be used in place of creating them from the `InnerSeries`.
|
||||||
|
CandleValues []CandleValue
|
||||||
|
|
||||||
|
// InnerSeries is used if the `CandleValues` are not set.
|
||||||
|
InnerSeries ValuesProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName implements Series.GetName.
|
||||||
|
func (cs *CandlestickSeries) GetName() string {
|
||||||
|
return cs.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle implements Series.GetStyle.
|
||||||
|
func (cs *CandlestickSeries) GetStyle() Style {
|
||||||
|
return cs.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which yaxis the series is mapped to.
|
||||||
|
func (cs *CandlestickSeries) GetYAxis() YAxisType {
|
||||||
|
return cs.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the series.
|
||||||
|
func (cs *CandlestickSeries) Len() int {
|
||||||
|
return len(cs.GetCandleValues())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoundedValues returns the bounded values at a given index.
|
||||||
|
func (cs *CandlestickSeries) GetBoundedValues(index int) (x, y0, y1 float64) {
|
||||||
|
value := cs.GetCandleValues()[index]
|
||||||
|
return util.Time.ToFloat64(value.Timestamp), value.Low, value.High
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCandleValues returns the candle values.
|
||||||
|
func (cs CandlestickSeries) GetCandleValues() []CandleValue {
|
||||||
|
if cs.CandleValues == nil {
|
||||||
|
cs.CandleValues = cs.GenerateCandleValues()
|
||||||
|
}
|
||||||
|
return cs.CandleValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCandleValues returns the candlestick values for each day represented by the inner series.
|
||||||
|
func (cs CandlestickSeries) GenerateCandleValues() []CandleValue {
|
||||||
|
if cs.InnerSeries == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
totalValues := cs.InnerSeries.Len()
|
||||||
|
if totalValues == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var values []CandleValue
|
||||||
|
var lastYear, lastMonth, lastDay int
|
||||||
|
var year, month, day int
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
var tv, lv, v float64
|
||||||
|
|
||||||
|
tv, v = cs.InnerSeries.GetValues(0)
|
||||||
|
t = util.Time.FromFloat64(tv)
|
||||||
|
year, month, day = t.Year(), int(t.Month()), t.Day()
|
||||||
|
|
||||||
|
lastYear, lastMonth, lastDay = year, month, day
|
||||||
|
|
||||||
|
value := CandleValue{
|
||||||
|
Timestamp: cs.newTimestamp(year, month, day),
|
||||||
|
Open: v,
|
||||||
|
Low: v,
|
||||||
|
High: v,
|
||||||
|
}
|
||||||
|
lv = v
|
||||||
|
|
||||||
|
for i := 1; i < totalValues; i++ {
|
||||||
|
tv, v = cs.InnerSeries.GetValues(i)
|
||||||
|
t = util.Time.FromFloat64(tv)
|
||||||
|
year, month, day = t.Year(), int(t.Month()), t.Day()
|
||||||
|
|
||||||
|
// if we've transitioned to a new day or we're on the last value
|
||||||
|
if lastYear != year || lastMonth != month || lastDay != day || i == (totalValues-1) {
|
||||||
|
value.Close = lv
|
||||||
|
values = append(values, value)
|
||||||
|
|
||||||
|
value = CandleValue{
|
||||||
|
Timestamp: cs.newTimestamp(year, month, day),
|
||||||
|
Open: v,
|
||||||
|
High: v,
|
||||||
|
Low: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
lastYear = year
|
||||||
|
lastMonth = month
|
||||||
|
lastDay = day
|
||||||
|
} else {
|
||||||
|
value.Low = math.Min(value.Low, v)
|
||||||
|
value.High = math.Max(value.High, v)
|
||||||
|
}
|
||||||
|
lv = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs CandlestickSeries) newTimestamp(year, month, day int) time.Time {
|
||||||
|
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, util.Date.Eastern())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render implements Series.Render.
|
||||||
|
func (cs CandlestickSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := cs.Style.InheritFrom(defaults)
|
||||||
|
Draw.CandlestickSeries(r, canvasBox, xrange, yrange, style, cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the series.
|
||||||
|
func (cs CandlestickSeries) Validate() error {
|
||||||
|
if cs.CandleValues == nil && cs.InnerSeries == nil {
|
||||||
|
return fmt.Errorf("candlestick series requires either `CandleValues` or `InnerSeries` to be set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
52
candlestick_series_test.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateDummyStockData() (times []time.Time, prices []float64) {
|
||||||
|
start := util.Date.On(util.NYSEOpen(), time.Date(2017, 05, 15, 0, 0, 0, 0, util.Date.Eastern()))
|
||||||
|
cursor := start
|
||||||
|
for day := 0; day < 60; day++ {
|
||||||
|
|
||||||
|
if util.Date.IsWeekendDay(cursor.Weekday()) {
|
||||||
|
cursor = start.AddDate(0, 0, day)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for hour := 0; hour < 7; hour++ {
|
||||||
|
for minute := 0; minute < 60; minute++ {
|
||||||
|
times = append(times, cursor)
|
||||||
|
prices = append(prices, rand.Float64()*256)
|
||||||
|
cursor = cursor.Add(time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = cursor.Add(time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = start.AddDate(0, 0, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCandlestickSeriesCandleValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
xdata, ydata := generateDummyStockData()
|
||||||
|
|
||||||
|
candleSeries := &CandlestickSeries{
|
||||||
|
InnerSeries: TimeSeries{
|
||||||
|
XValues: xdata,
|
||||||
|
YValues: ydata,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
values := candleSeries.GetCandleValues()
|
||||||
|
assert.Len(values, 43) // should be 60 days per the generator.
|
||||||
|
}
|
82
chart.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chart is what we're drawing.
|
// Chart is what we're drawing.
|
||||||
|
@ -32,8 +33,6 @@ type Chart struct {
|
||||||
|
|
||||||
Series []Series
|
Series []Series
|
||||||
Elements []Renderable
|
Elements []Renderable
|
||||||
|
|
||||||
Log Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDPI returns the dpi for the chart.
|
// GetDPI returns the dpi for the chart.
|
||||||
|
@ -76,8 +75,8 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
if len(c.Series) == 0 {
|
if len(c.Series) == 0 {
|
||||||
return errors.New("please provide at least one series")
|
return errors.New("please provide at least one series")
|
||||||
}
|
}
|
||||||
if err := c.checkHasVisibleSeries(); err != nil {
|
if visibleSeriesErr := c.checkHasVisibleSeries(); visibleSeriesErr != nil {
|
||||||
return err
|
return visibleSeriesErr
|
||||||
}
|
}
|
||||||
|
|
||||||
c.YAxisSecondary.AxisType = YAxisSecondary
|
c.YAxisSecondary.AxisType = YAxisSecondary
|
||||||
|
@ -103,8 +102,6 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
canvasBox := c.getDefaultCanvasBox()
|
canvasBox := c.getDefaultCanvasBox()
|
||||||
xf, yf, yfa := c.getValueFormatters()
|
xf, yf, yfa := c.getValueFormatters()
|
||||||
|
|
||||||
Debugf(c.Log, "chart; canvas box: %v", canvasBox)
|
|
||||||
|
|
||||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||||
|
|
||||||
err = c.checkRanges(xr, yr, yra)
|
err = c.checkRanges(xr, yr, yra)
|
||||||
|
@ -118,8 +115,6 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
||||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||||
|
|
||||||
Debugf(c.Log, "chart; axes adjusted canvas box: %v", canvasBox)
|
|
||||||
|
|
||||||
// do a second pass in case things haven't settled yet.
|
// do a second pass in case things haven't settled yet.
|
||||||
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
||||||
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
||||||
|
@ -130,8 +125,6 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa)
|
canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa)
|
||||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||||
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
||||||
|
|
||||||
Debugf(c.Log, "chart; annotation adjusted canvas box: %v", canvasBox)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.drawCanvas(r, canvasBox)
|
c.drawCanvas(r, canvasBox)
|
||||||
|
@ -150,15 +143,17 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) checkHasVisibleSeries() error {
|
func (c Chart) checkHasVisibleSeries() error {
|
||||||
|
hasVisibleSeries := false
|
||||||
var style Style
|
var style Style
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
style = s.GetStyle()
|
style = s.GetStyle()
|
||||||
if !style.Hidden {
|
hasVisibleSeries = hasVisibleSeries || (style.IsZero() || style.Show)
|
||||||
|
}
|
||||||
|
if !hasVisibleSeries {
|
||||||
|
return fmt.Errorf("must have (1) visible series; make sure if you set a style, you set .Show = true")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return fmt.Errorf("chart render; must have (1) visible series")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Chart) validateSeries() error {
|
func (c Chart) validateSeries() error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -181,7 +176,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
// note: a possible future optimization is to not scan the series values if
|
// note: a possible future optimization is to not scan the series values if
|
||||||
// all axis are represented by either custom ticks or custom ranges.
|
// all axis are represented by either custom ticks or custom ranges.
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
if !s.GetStyle().Hidden {
|
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||||
seriesAxis := s.GetYAxis()
|
seriesAxis := s.GetYAxis()
|
||||||
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
||||||
seriesLength := bvp.Len()
|
seriesLength := bvp.Len()
|
||||||
|
@ -268,10 +263,11 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
yrange.SetMin(miny)
|
yrange.SetMin(miny)
|
||||||
yrange.SetMax(maxy)
|
yrange.SetMax(maxy)
|
||||||
|
|
||||||
if !c.YAxis.Style.Hidden {
|
// only round if we're showing the axis
|
||||||
|
if c.YAxis.Style.Show {
|
||||||
delta := yrange.GetDelta()
|
delta := yrange.GetDelta()
|
||||||
roundTo := GetRoundToForDelta(delta)
|
roundTo := util.Math.GetRoundToForDelta(delta)
|
||||||
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
|
rmin, rmax := util.Math.RoundDown(yrange.GetMin(), roundTo), util.Math.RoundUp(yrange.GetMax(), roundTo)
|
||||||
|
|
||||||
yrange.SetMin(rmin)
|
yrange.SetMin(rmin)
|
||||||
yrange.SetMax(rmax)
|
yrange.SetMax(rmax)
|
||||||
|
@ -290,10 +286,10 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
yrangeAlt.SetMin(minya)
|
yrangeAlt.SetMin(minya)
|
||||||
yrangeAlt.SetMax(maxya)
|
yrangeAlt.SetMax(maxya)
|
||||||
|
|
||||||
if !c.YAxisSecondary.Style.Hidden {
|
if c.YAxisSecondary.Style.Show {
|
||||||
delta := yrangeAlt.GetDelta()
|
delta := yrangeAlt.GetDelta()
|
||||||
roundTo := GetRoundToForDelta(delta)
|
roundTo := util.Math.GetRoundToForDelta(delta)
|
||||||
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
|
rmin, rmax := util.Math.RoundDown(yrangeAlt.GetMin(), roundTo), util.Math.RoundUp(yrangeAlt.GetMax(), roundTo)
|
||||||
yrangeAlt.SetMin(rmin)
|
yrangeAlt.SetMin(rmin)
|
||||||
yrangeAlt.SetMax(rmax)
|
yrangeAlt.SetMax(rmax)
|
||||||
}
|
}
|
||||||
|
@ -303,7 +299,6 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) checkRanges(xr, yr, yra Range) error {
|
func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
Debugf(c.Log, "checking xrange: %v", xr)
|
|
||||||
xDelta := xr.GetDelta()
|
xDelta := xr.GetDelta()
|
||||||
if math.IsInf(xDelta, 0) {
|
if math.IsInf(xDelta, 0) {
|
||||||
return errors.New("infinite x-range delta")
|
return errors.New("infinite x-range delta")
|
||||||
|
@ -315,7 +310,6 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
return errors.New("zero x-range delta; there needs to be at least (2) values")
|
return errors.New("zero x-range delta; there needs to be at least (2) values")
|
||||||
}
|
}
|
||||||
|
|
||||||
Debugf(c.Log, "checking yrange: %v", yr)
|
|
||||||
yDelta := yr.GetDelta()
|
yDelta := yr.GetDelta()
|
||||||
if math.IsInf(yDelta, 0) {
|
if math.IsInf(yDelta, 0) {
|
||||||
return errors.New("infinite y-range delta")
|
return errors.New("infinite y-range delta")
|
||||||
|
@ -323,9 +317,11 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
if math.IsNaN(yDelta) {
|
if math.IsNaN(yDelta) {
|
||||||
return errors.New("nan y-range delta")
|
return errors.New("nan y-range delta")
|
||||||
}
|
}
|
||||||
|
if yDelta == 0 {
|
||||||
|
return errors.New("zero y-range delta")
|
||||||
|
}
|
||||||
|
|
||||||
if c.hasSecondarySeries() {
|
if c.hasSecondarySeries() {
|
||||||
Debugf(c.Log, "checking secondary yrange: %v", yra)
|
|
||||||
yraDelta := yra.GetDelta()
|
yraDelta := yra.GetDelta()
|
||||||
if math.IsInf(yraDelta, 0) {
|
if math.IsInf(yraDelta, 0) {
|
||||||
return errors.New("infinite secondary y-range delta")
|
return errors.New("infinite secondary y-range delta")
|
||||||
|
@ -333,6 +329,9 @@ func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||||
if math.IsNaN(yraDelta) {
|
if math.IsNaN(yraDelta) {
|
||||||
return errors.New("nan secondary y-range delta")
|
return errors.New("nan secondary y-range delta")
|
||||||
}
|
}
|
||||||
|
if yraDelta == 0 {
|
||||||
|
return errors.New("zero secondary y-range delta")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -368,17 +367,17 @@ func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) hasAxes() bool {
|
func (c Chart) hasAxes() bool {
|
||||||
return !c.XAxis.Style.Hidden || !c.YAxis.Style.Hidden || !c.YAxisSecondary.Style.Hidden
|
return c.XAxis.Style.Show || c.YAxis.Style.Show || c.YAxisSecondary.Style.Show
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
||||||
if !c.XAxis.Style.Hidden {
|
if c.XAxis.Style.Show {
|
||||||
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
||||||
}
|
}
|
||||||
if !c.YAxis.Style.Hidden {
|
if c.YAxis.Style.Show {
|
||||||
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
||||||
}
|
}
|
||||||
if !c.YAxisSecondary.Style.Hidden {
|
if c.YAxisSecondary.Style.Show {
|
||||||
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -386,19 +385,16 @@ func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueForm
|
||||||
|
|
||||||
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
||||||
axesOuterBox := canvasBox.Clone()
|
axesOuterBox := canvasBox.Clone()
|
||||||
if !c.XAxis.Style.Hidden {
|
if c.XAxis.Style.Show {
|
||||||
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
||||||
Debugf(c.Log, "chart; x-axis measured %v", axesBounds)
|
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if !c.YAxis.Style.Hidden {
|
if c.YAxis.Style.Show {
|
||||||
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
||||||
Debugf(c.Log, "chart; y-axis measured %v", axesBounds)
|
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if !c.YAxisSecondary.Style.Hidden && c.hasSecondarySeries() {
|
if c.YAxisSecondary.Style.Show {
|
||||||
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
||||||
Debugf(c.Log, "chart; y-axis secondary measured %v", axesBounds)
|
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,7 +411,7 @@ func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range,
|
||||||
func (c Chart) hasAnnotationSeries() bool {
|
func (c Chart) hasAnnotationSeries() bool {
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||||
if !as.GetStyle().Hidden {
|
if as.Style.IsZero() || as.Style.Show {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,7 +432,7 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
|
||||||
annotationSeriesBox := canvasBox.Clone()
|
annotationSeriesBox := canvasBox.Clone()
|
||||||
for seriesIndex, s := range c.Series {
|
for seriesIndex, s := range c.Series {
|
||||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||||
if !as.GetStyle().Hidden {
|
if as.Style.IsZero() || as.Style.Show {
|
||||||
style := c.styleDefaultsSeries(seriesIndex)
|
style := c.styleDefaultsSeries(seriesIndex)
|
||||||
var annotationBounds Box
|
var annotationBounds Box
|
||||||
if as.YAxis == YAxisPrimary {
|
if as.YAxis == YAxisPrimary {
|
||||||
|
@ -473,19 +469,19 @@ func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
||||||
if !c.XAxis.Style.Hidden {
|
if c.XAxis.Style.Show {
|
||||||
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
||||||
}
|
}
|
||||||
if !c.YAxis.Style.Hidden {
|
if c.YAxis.Style.Show {
|
||||||
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
||||||
}
|
}
|
||||||
if !c.YAxisSecondary.Style.Hidden {
|
if c.YAxisSecondary.Style.Show {
|
||||||
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
|
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
||||||
if !s.GetStyle().Hidden {
|
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||||
if s.GetYAxis() == YAxisPrimary {
|
if s.GetYAxis() == YAxisPrimary {
|
||||||
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
|
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
|
||||||
} else if s.GetYAxis() == YAxisSecondary {
|
} else if s.GetYAxis() == YAxisSecondary {
|
||||||
|
@ -495,7 +491,7 @@ func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawTitle(r Renderer) {
|
func (c Chart) drawTitle(r Renderer) {
|
||||||
if len(c.Title) > 0 && !c.TitleStyle.Hidden {
|
if len(c.Title) > 0 && c.TitleStyle.Show {
|
||||||
r.SetFont(c.TitleStyle.GetFont(c.GetFont()))
|
r.SetFont(c.TitleStyle.GetFont(c.GetFont()))
|
||||||
r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor()))
|
r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor()))
|
||||||
titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
||||||
|
@ -506,8 +502,8 @@ func (c Chart) drawTitle(r Renderer) {
|
||||||
textWidth := textBox.Width()
|
textWidth := textBox.Width()
|
||||||
textHeight := textBox.Height()
|
textHeight := textBox.Height()
|
||||||
|
|
||||||
titleX := (c.GetWidth() >> 1) - (textWidth >> 1)
|
titleX := (int(c.GetWidth()) >> 1) - (int(textWidth) >> 1)
|
||||||
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + int(textHeight)
|
||||||
|
|
||||||
r.Text(c.Title, titleX, titleY)
|
r.Text(c.Title, titleX, titleY)
|
||||||
}
|
}
|
||||||
|
|
312
chart_test.go
|
@ -8,57 +8,58 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/drawing"
|
"github.com/blendlabs/go-assert"
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChartGetDPI(t *testing.T) {
|
func TestChartGetDPI(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
unset := Chart{}
|
unset := Chart{}
|
||||||
testutil.AssertEqual(t, DefaultDPI, unset.GetDPI())
|
assert.Equal(DefaultDPI, unset.GetDPI())
|
||||||
testutil.AssertEqual(t, 192, unset.GetDPI(192))
|
assert.Equal(192, unset.GetDPI(192))
|
||||||
|
|
||||||
set := Chart{DPI: 128}
|
set := Chart{DPI: 128}
|
||||||
testutil.AssertEqual(t, 128, set.GetDPI())
|
assert.Equal(128, set.GetDPI())
|
||||||
testutil.AssertEqual(t, 128, set.GetDPI(192))
|
assert.Equal(128, set.GetDPI(192))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetFont(t *testing.T) {
|
func TestChartGetFont(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
f, err := GetDefaultFont()
|
f, err := GetDefaultFont()
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
unset := Chart{}
|
unset := Chart{}
|
||||||
testutil.AssertNil(t, unset.GetFont())
|
assert.Nil(unset.GetFont())
|
||||||
|
|
||||||
set := Chart{Font: f}
|
set := Chart{Font: f}
|
||||||
testutil.AssertNotNil(t, set.GetFont())
|
assert.NotNil(set.GetFont())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetWidth(t *testing.T) {
|
func TestChartGetWidth(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
unset := Chart{}
|
unset := Chart{}
|
||||||
testutil.AssertEqual(t, DefaultChartWidth, unset.GetWidth())
|
assert.Equal(DefaultChartWidth, unset.GetWidth())
|
||||||
|
|
||||||
set := Chart{Width: DefaultChartWidth + 10}
|
set := Chart{Width: DefaultChartWidth + 10}
|
||||||
testutil.AssertEqual(t, DefaultChartWidth+10, set.GetWidth())
|
assert.Equal(DefaultChartWidth+10, set.GetWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetHeight(t *testing.T) {
|
func TestChartGetHeight(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
unset := Chart{}
|
unset := Chart{}
|
||||||
testutil.AssertEqual(t, DefaultChartHeight, unset.GetHeight())
|
assert.Equal(DefaultChartHeight, unset.GetHeight())
|
||||||
|
|
||||||
set := Chart{Height: DefaultChartHeight + 10}
|
set := Chart{Height: DefaultChartHeight + 10}
|
||||||
testutil.AssertEqual(t, DefaultChartHeight+10, set.GetHeight())
|
assert.Equal(DefaultChartHeight+10, set.GetHeight())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetRanges(t *testing.T) {
|
func TestChartGetRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
|
@ -79,14 +80,14 @@ func TestChartGetRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xrange, yrange, yrangeAlt := c.getRanges()
|
xrange, yrange, yrangeAlt := c.getRanges()
|
||||||
testutil.AssertEqual(t, -2.0, xrange.GetMin())
|
assert.Equal(-2.0, xrange.GetMin())
|
||||||
testutil.AssertEqual(t, 5.0, xrange.GetMax())
|
assert.Equal(5.0, xrange.GetMax())
|
||||||
|
|
||||||
testutil.AssertEqual(t, -2.1, yrange.GetMin())
|
assert.Equal(-2.1, yrange.GetMin())
|
||||||
testutil.AssertEqual(t, 4.5, yrange.GetMax())
|
assert.Equal(4.5, yrange.GetMax())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 10.0, yrangeAlt.GetMin())
|
assert.Equal(10.0, yrangeAlt.GetMin())
|
||||||
testutil.AssertEqual(t, 14.0, yrangeAlt.GetMax())
|
assert.Equal(14.0, yrangeAlt.GetMax())
|
||||||
|
|
||||||
cSet := Chart{
|
cSet := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
|
@ -116,18 +117,18 @@ func TestChartGetRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xr2, yr2, yra2 := cSet.getRanges()
|
xr2, yr2, yra2 := cSet.getRanges()
|
||||||
testutil.AssertEqual(t, 9.8, xr2.GetMin())
|
assert.Equal(9.8, xr2.GetMin())
|
||||||
testutil.AssertEqual(t, 19.8, xr2.GetMax())
|
assert.Equal(19.8, xr2.GetMax())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 9.9, yr2.GetMin())
|
assert.Equal(9.9, yr2.GetMin())
|
||||||
testutil.AssertEqual(t, 19.9, yr2.GetMax())
|
assert.Equal(19.9, yr2.GetMax())
|
||||||
|
|
||||||
testutil.AssertEqual(t, 9.7, yra2.GetMin())
|
assert.Equal(9.7, yra2.GetMin())
|
||||||
testutil.AssertEqual(t, 19.7, yra2.GetMax())
|
assert.Equal(19.7, yra2.GetMax())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetRangesUseTicks(t *testing.T) {
|
func TestChartGetRangesUseTicks(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
// this test asserts that ticks should supercede manual ranges when generating the overall ranges.
|
// this test asserts that ticks should supercede manual ranges when generating the overall ranges.
|
||||||
|
|
||||||
|
@ -155,15 +156,15 @@ func TestChartGetRangesUseTicks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xr, yr, yar := c.getRanges()
|
xr, yr, yar := c.getRanges()
|
||||||
testutil.AssertEqual(t, -2.0, xr.GetMin())
|
assert.Equal(-2.0, xr.GetMin())
|
||||||
testutil.AssertEqual(t, 2.0, xr.GetMax())
|
assert.Equal(2.0, xr.GetMax())
|
||||||
testutil.AssertEqual(t, 0.0, yr.GetMin())
|
assert.Equal(0.0, yr.GetMin())
|
||||||
testutil.AssertEqual(t, 5.0, yr.GetMax())
|
assert.Equal(5.0, yr.GetMax())
|
||||||
testutil.AssertTrue(t, yar.IsZero(), yar.String())
|
assert.True(yar.IsZero(), yar.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetRangesUseUserRanges(t *testing.T) {
|
func TestChartGetRangesUseUserRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
@ -181,15 +182,15 @@ func TestChartGetRangesUseUserRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xr, yr, yar := c.getRanges()
|
xr, yr, yar := c.getRanges()
|
||||||
testutil.AssertEqual(t, -2.0, xr.GetMin())
|
assert.Equal(-2.0, xr.GetMin())
|
||||||
testutil.AssertEqual(t, 2.0, xr.GetMax())
|
assert.Equal(2.0, xr.GetMax())
|
||||||
testutil.AssertEqual(t, -5.0, yr.GetMin())
|
assert.Equal(-5.0, yr.GetMin())
|
||||||
testutil.AssertEqual(t, 5.0, yr.GetMax())
|
assert.Equal(5.0, yr.GetMax())
|
||||||
testutil.AssertTrue(t, yar.IsZero(), yar.String())
|
assert.True(yar.IsZero(), yar.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetBackgroundStyle(t *testing.T) {
|
func TestChartGetBackgroundStyle(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Background: Style{
|
Background: Style{
|
||||||
|
@ -198,11 +199,11 @@ func TestChartGetBackgroundStyle(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bs := c.getBackgroundStyle()
|
bs := c.getBackgroundStyle()
|
||||||
testutil.AssertEqual(t, bs.FillColor.String(), drawing.ColorBlack.String())
|
assert.Equal(bs.FillColor.String(), drawing.ColorBlack.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetCanvasStyle(t *testing.T) {
|
func TestChartGetCanvasStyle(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Canvas: Style{
|
Canvas: Style{
|
||||||
|
@ -211,19 +212,19 @@ func TestChartGetCanvasStyle(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bs := c.getCanvasStyle()
|
bs := c.getCanvasStyle()
|
||||||
testutil.AssertEqual(t, bs.FillColor.String(), drawing.ColorBlack.String())
|
assert.Equal(bs.FillColor.String(), drawing.ColorBlack.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetDefaultCanvasBox(t *testing.T) {
|
func TestChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{}
|
c := Chart{}
|
||||||
canvasBoxDefault := c.getDefaultCanvasBox()
|
canvasBoxDefault := c.getDefaultCanvasBox()
|
||||||
testutil.AssertFalse(t, canvasBoxDefault.IsZero())
|
assert.False(canvasBoxDefault.IsZero())
|
||||||
testutil.AssertEqual(t, DefaultBackgroundPadding.Top, canvasBoxDefault.Top)
|
assert.Equal(DefaultBackgroundPadding.Top, canvasBoxDefault.Top)
|
||||||
testutil.AssertEqual(t, DefaultBackgroundPadding.Left, canvasBoxDefault.Left)
|
assert.Equal(DefaultBackgroundPadding.Left, canvasBoxDefault.Left)
|
||||||
testutil.AssertEqual(t, c.GetWidth()-DefaultBackgroundPadding.Right, canvasBoxDefault.Right)
|
assert.Equal(c.GetWidth()-DefaultBackgroundPadding.Right, canvasBoxDefault.Right)
|
||||||
testutil.AssertEqual(t, c.GetHeight()-DefaultBackgroundPadding.Bottom, canvasBoxDefault.Bottom)
|
assert.Equal(c.GetHeight()-DefaultBackgroundPadding.Bottom, canvasBoxDefault.Bottom)
|
||||||
|
|
||||||
custom := Chart{
|
custom := Chart{
|
||||||
Background: Style{
|
Background: Style{
|
||||||
|
@ -236,15 +237,15 @@ func TestChartGetDefaultCanvasBox(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
canvasBoxCustom := custom.getDefaultCanvasBox()
|
canvasBoxCustom := custom.getDefaultCanvasBox()
|
||||||
testutil.AssertFalse(t, canvasBoxCustom.IsZero())
|
assert.False(canvasBoxCustom.IsZero())
|
||||||
testutil.AssertEqual(t, DefaultBackgroundPadding.Top+1, canvasBoxCustom.Top)
|
assert.Equal(DefaultBackgroundPadding.Top+1, canvasBoxCustom.Top)
|
||||||
testutil.AssertEqual(t, DefaultBackgroundPadding.Left+1, canvasBoxCustom.Left)
|
assert.Equal(DefaultBackgroundPadding.Left+1, canvasBoxCustom.Left)
|
||||||
testutil.AssertEqual(t, c.GetWidth()-(DefaultBackgroundPadding.Right+1), canvasBoxCustom.Right)
|
assert.Equal(c.GetWidth()-(DefaultBackgroundPadding.Right+1), canvasBoxCustom.Right)
|
||||||
testutil.AssertEqual(t, c.GetHeight()-(DefaultBackgroundPadding.Bottom+1), canvasBoxCustom.Bottom)
|
assert.Equal(c.GetHeight()-(DefaultBackgroundPadding.Bottom+1), canvasBoxCustom.Bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetValueFormatters(t *testing.T) {
|
func TestChartGetValueFormatters(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
|
@ -265,95 +266,90 @@ func TestChartGetValueFormatters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dxf, dyf, dyaf := c.getValueFormatters()
|
dxf, dyf, dyaf := c.getValueFormatters()
|
||||||
testutil.AssertNotNil(t, dxf)
|
assert.NotNil(dxf)
|
||||||
testutil.AssertNotNil(t, dyf)
|
assert.NotNil(dyf)
|
||||||
testutil.AssertNotNil(t, dyaf)
|
assert.NotNil(dyaf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartHasAxes(t *testing.T) {
|
func TestChartHasAxes(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
testutil.AssertTrue(t, Chart{}.hasAxes())
|
assert.False(Chart{}.hasAxes())
|
||||||
testutil.AssertFalse(t, Chart{XAxis: XAxis{Style: Hidden()}, YAxis: YAxis{Style: Hidden()}, YAxisSecondary: YAxis{Style: Hidden()}}.hasAxes())
|
|
||||||
|
|
||||||
x := Chart{
|
x := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
Style: Hidden(),
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
},
|
},
|
||||||
YAxis: YAxis{
|
|
||||||
Style: Shown(),
|
|
||||||
},
|
|
||||||
YAxisSecondary: YAxis{
|
|
||||||
Style: Hidden(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testutil.AssertTrue(t, x.hasAxes())
|
assert.True(x.hasAxes())
|
||||||
|
|
||||||
y := Chart{
|
y := Chart{
|
||||||
XAxis: XAxis{
|
|
||||||
Style: Shown(),
|
|
||||||
},
|
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
Style: Hidden(),
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
},
|
},
|
||||||
YAxisSecondary: YAxis{
|
|
||||||
Style: Hidden(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testutil.AssertTrue(t, y.hasAxes())
|
assert.True(y.hasAxes())
|
||||||
|
|
||||||
ya := Chart{
|
ya := Chart{
|
||||||
XAxis: XAxis{
|
|
||||||
Style: Hidden(),
|
|
||||||
},
|
|
||||||
YAxis: YAxis{
|
|
||||||
Style: Hidden(),
|
|
||||||
},
|
|
||||||
YAxisSecondary: YAxis{
|
YAxisSecondary: YAxis{
|
||||||
Style: Shown(),
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testutil.AssertTrue(t, ya.hasAxes())
|
assert.True(ya.hasAxes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartGetAxesTicks(t *testing.T) {
|
func TestChartGetAxesTicks(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
r, err := PNG(1024, 1024)
|
r, err := PNG(1024, 1024)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
Range: &ContinuousRange{Min: 9.8, Max: 19.8},
|
||||||
},
|
},
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
Range: &ContinuousRange{Min: 9.9, Max: 19.9},
|
||||||
},
|
},
|
||||||
YAxisSecondary: YAxis{
|
YAxisSecondary: YAxis{
|
||||||
|
Style: Style{Show: true},
|
||||||
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
Range: &ContinuousRange{Min: 9.7, Max: 19.7},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
xr, yr, yar := c.getRanges()
|
xr, yr, yar := c.getRanges()
|
||||||
|
|
||||||
xt, yt, yat := c.getAxesTicks(r, xr, yr, yar, FloatValueFormatter, FloatValueFormatter, FloatValueFormatter)
|
xt, yt, yat := c.getAxesTicks(r, xr, yr, yar, FloatValueFormatter, FloatValueFormatter, FloatValueFormatter)
|
||||||
testutil.AssertNotEmpty(t, xt)
|
assert.NotEmpty(xt)
|
||||||
testutil.AssertNotEmpty(t, yt)
|
assert.NotEmpty(yt)
|
||||||
testutil.AssertNotEmpty(t, yat)
|
assert.NotEmpty(yat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartSingleSeries(t *testing.T) {
|
func TestChartSingleSeries(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Title: "Hello!",
|
Title: "Hello!",
|
||||||
|
TitleStyle: StyleShow(),
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Height: 400,
|
Height: 400,
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
Range: &ContinuousRange{
|
Range: &ContinuousRange{
|
||||||
Min: 0.0,
|
Min: 0.0,
|
||||||
Max: 4.0,
|
Max: 4.0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
XAxis: XAxis{
|
||||||
|
Style: StyleShow(),
|
||||||
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
TimeSeries{
|
TimeSeries{
|
||||||
Name: "goog",
|
Name: "goog",
|
||||||
|
@ -365,11 +361,11 @@ func TestChartSingleSeries(t *testing.T) {
|
||||||
|
|
||||||
buffer := bytes.NewBuffer([]byte{})
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
c.Render(PNG, buffer)
|
c.Render(PNG, buffer)
|
||||||
testutil.AssertNotEmpty(t, buffer.Bytes())
|
assert.NotEmpty(buffer.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartRegressionBadRanges(t *testing.T) {
|
func TestChartRegressionBadRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
|
@ -381,11 +377,11 @@ func TestChartRegressionBadRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
buffer := bytes.NewBuffer([]byte{})
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
c.Render(PNG, buffer)
|
c.Render(PNG, buffer)
|
||||||
testutil.AssertTrue(t, true, "Render needs to finish.")
|
assert.True(true, "Render needs to finish.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartRegressionBadRangesByUser(t *testing.T) {
|
func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
YAxis: YAxis{
|
YAxis: YAxis{
|
||||||
|
@ -396,43 +392,43 @@ func TestChartRegressionBadRangesByUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
buffer := bytes.NewBuffer([]byte{})
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
c.Render(PNG, buffer)
|
c.Render(PNG, buffer)
|
||||||
testutil.AssertTrue(t, true, "Render needs to finish.")
|
assert.True(true, "Render needs to finish.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartValidatesSeries(t *testing.T) {
|
func TestChartValidatesSeries(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.AssertNil(t, c.validateSeries())
|
assert.Nil(c.validateSeries())
|
||||||
|
|
||||||
c = Chart{
|
c = Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.AssertNotNil(t, c.validateSeries())
|
assert.NotNil(c.validateSeries())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartCheckRanges(t *testing.T) {
|
func TestChartCheckRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
|
@ -444,11 +440,27 @@ func TestChartCheckRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xr, yr, yra := c.getRanges()
|
xr, yr, yra := c.getRanges()
|
||||||
testutil.AssertNil(t, c.checkRanges(xr, yr, yra))
|
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) {
|
func TestChartCheckRangesWithRanges(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
XAxis: XAxis{
|
XAxis: XAxis{
|
||||||
|
@ -472,7 +484,7 @@ func TestChartCheckRangesWithRanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xr, yr, yra := c.getRanges()
|
xr, yr, yra := c.getRanges()
|
||||||
testutil.AssertNil(t, c.checkRanges(xr, yr, yra))
|
assert.Nil(c.checkRanges(xr, yr, yra))
|
||||||
}
|
}
|
||||||
|
|
||||||
func at(i image.Image, x, y int) drawing.Color {
|
func at(i image.Image, x, y int) drawing.Color {
|
||||||
|
@ -480,115 +492,85 @@ func at(i image.Image, x, y int) drawing.Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartE2ELine(t *testing.T) {
|
func TestChartE2ELine(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Height: 50,
|
Height: 50,
|
||||||
Width: 50,
|
Width: 50,
|
||||||
TitleStyle: Hidden(),
|
|
||||||
XAxis: HideXAxis(),
|
|
||||||
YAxis: HideYAxis(),
|
|
||||||
YAxisSecondary: HideYAxis(),
|
|
||||||
Canvas: Style{
|
Canvas: Style{
|
||||||
Padding: BoxZero,
|
Padding: Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Background: Style{
|
Background: Style{
|
||||||
Padding: BoxZero,
|
Padding: Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
XValues: LinearRangeWithStep(0, 4, 1),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: LinearRangeWithStep(0, 4, 1),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = &bytes.Buffer{}
|
var buffer = &bytes.Buffer{}
|
||||||
err := c.Render(PNG, buffer)
|
err := c.Render(PNG, buffer)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
// do color tests ...
|
// do color tests ...
|
||||||
|
|
||||||
i, err := png.Decode(buffer)
|
i, err := png.Decode(buffer)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
// test the bottom and top of the line
|
// test the bottom and top of the line
|
||||||
testutil.AssertEqual(t, drawing.ColorWhite, at(i, 0, 0))
|
assert.Equal(drawing.ColorWhite, at(i, 0, 0))
|
||||||
testutil.AssertEqual(t, drawing.ColorWhite, at(i, 49, 49))
|
assert.Equal(drawing.ColorWhite, at(i, 49, 49))
|
||||||
|
|
||||||
// test a line mid point
|
// test a line mid point
|
||||||
defaultSeriesColor := GetDefaultColor(0)
|
defaultSeriesColor := GetDefaultColor(0)
|
||||||
testutil.AssertEqual(t, defaultSeriesColor, at(i, 0, 49))
|
assert.Equal(defaultSeriesColor, at(i, 0, 49))
|
||||||
testutil.AssertEqual(t, defaultSeriesColor, at(i, 49, 0))
|
assert.Equal(defaultSeriesColor, at(i, 49, 0))
|
||||||
testutil.AssertEqual(t, drawing.ColorFromHex("bddbf6"), at(i, 24, 24))
|
assert.Equal(drawing.ColorFromHex("bddbf6"), at(i, 24, 24))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChartE2ELineWithFill(t *testing.T) {
|
func TestChartE2ELineWithFill(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
logBuffer := new(bytes.Buffer)
|
|
||||||
|
|
||||||
c := Chart{
|
c := Chart{
|
||||||
Height: 50,
|
Height: 50,
|
||||||
Width: 50,
|
Width: 50,
|
||||||
Canvas: Style{
|
Canvas: Style{
|
||||||
Padding: BoxZero,
|
Padding: Box{IsSet: true},
|
||||||
},
|
},
|
||||||
Background: Style{
|
Background: Style{
|
||||||
Padding: BoxZero,
|
Padding: Box{IsSet: true},
|
||||||
},
|
},
|
||||||
TitleStyle: Hidden(),
|
|
||||||
XAxis: HideXAxis(),
|
|
||||||
YAxis: HideYAxis(),
|
|
||||||
YAxisSecondary: HideYAxis(),
|
|
||||||
Series: []Series{
|
Series: []Series{
|
||||||
ContinuousSeries{
|
ContinuousSeries{
|
||||||
Style: Style{
|
Style: Style{
|
||||||
|
Show: true,
|
||||||
StrokeColor: drawing.ColorBlue,
|
StrokeColor: drawing.ColorBlue,
|
||||||
FillColor: drawing.ColorRed,
|
FillColor: drawing.ColorRed,
|
||||||
},
|
},
|
||||||
XValues: LinearRangeWithStep(0, 4, 1),
|
XValues: seq.RangeWithStep(0, 4, 1),
|
||||||
YValues: LinearRangeWithStep(0, 4, 1),
|
YValues: seq.RangeWithStep(0, 4, 1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Log: NewLogger(OptLoggerStdout(logBuffer), OptLoggerStderr(logBuffer)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.AssertEqual(t, 5, len(c.Series[0].(ContinuousSeries).XValues))
|
|
||||||
testutil.AssertEqual(t, 5, len(c.Series[0].(ContinuousSeries).YValues))
|
|
||||||
|
|
||||||
var buffer = &bytes.Buffer{}
|
var buffer = &bytes.Buffer{}
|
||||||
err := c.Render(PNG, buffer)
|
err := c.Render(PNG, buffer)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// do color tests ...
|
||||||
|
|
||||||
i, err := png.Decode(buffer)
|
i, err := png.Decode(buffer)
|
||||||
testutil.AssertNil(t, err)
|
assert.Nil(err)
|
||||||
|
|
||||||
// test the bottom and top of the line
|
// test the bottom and top of the line
|
||||||
testutil.AssertEqual(t, drawing.ColorWhite, at(i, 0, 0))
|
assert.Equal(drawing.ColorWhite, at(i, 0, 0))
|
||||||
testutil.AssertEqual(t, drawing.ColorRed, at(i, 49, 49))
|
assert.Equal(drawing.ColorRed, at(i, 49, 49))
|
||||||
|
|
||||||
// test a line mid point
|
// test a line mid point
|
||||||
defaultSeriesColor := drawing.ColorBlue
|
defaultSeriesColor := drawing.ColorBlue
|
||||||
testutil.AssertEqual(t, defaultSeriesColor, at(i, 0, 49))
|
assert.Equal(defaultSeriesColor, at(i, 0, 49))
|
||||||
testutil.AssertEqual(t, defaultSeriesColor, at(i, 49, 0))
|
assert.Equal(defaultSeriesColor, at(i, 49, 0))
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Chart_cve(t *testing.T) {
|
|
||||||
poc := StackedBarChart{
|
|
||||||
Title: "poc",
|
|
||||||
Bars: []StackedBar{
|
|
||||||
{
|
|
||||||
Name: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
|
||||||
Values: []Value{
|
|
||||||
{Value: 1, Label: "infinite"},
|
|
||||||
{Value: 1, Label: "loop"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var imgContent bytes.Buffer
|
|
||||||
err := poc.Render(PNG, &imgContent)
|
|
||||||
testutil.AssertNotNil(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
outputPath = flag.String("output", "", "The output file")
|
|
||||||
|
|
||||||
inputFormat = flag.String("format", "csv", "The input format, either 'csv' or 'tsv' (defaults to 'csv')")
|
|
||||||
inputPath = flag.String("f", "", "The input file")
|
|
||||||
reverse = flag.Bool("reverse", false, "If we should reverse the inputs")
|
|
||||||
|
|
||||||
hideLegend = flag.Bool("hide-legend", false, "If we should omit the chart legend")
|
|
||||||
hideSMA = flag.Bool("hide-sma", false, "If we should omit simple moving average")
|
|
||||||
hideLinreg = flag.Bool("hide-linreg", false, "If we should omit linear regressions")
|
|
||||||
hideLastValues = flag.Bool("hide-last-values", false, "If we should omit last values")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
log := chart.NewLogger()
|
|
||||||
|
|
||||||
var rawData []byte
|
|
||||||
var err error
|
|
||||||
if *inputPath != "" {
|
|
||||||
if *inputPath == "-" {
|
|
||||||
rawData, err = ioutil.ReadAll(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rawData, err = ioutil.ReadFile(*inputPath)
|
|
||||||
if err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if len(flag.Args()) > 0 {
|
|
||||||
rawData = []byte(flag.Args()[0])
|
|
||||||
} else {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts []string
|
|
||||||
switch *inputFormat {
|
|
||||||
case "csv":
|
|
||||||
parts = chart.SplitCSV(string(rawData))
|
|
||||||
case "tsv":
|
|
||||||
parts = strings.Split(string(rawData), "\t")
|
|
||||||
default:
|
|
||||||
log.FatalErr(fmt.Errorf("invalid format; must be 'csv' or 'tsv'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
yvalues, err := chart.ParseFloats(parts...)
|
|
||||||
if err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *reverse {
|
|
||||||
yvalues = chart.ValueSequence(yvalues...).Reverse().Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
var series []chart.Series
|
|
||||||
mainSeries := chart.ContinuousSeries{
|
|
||||||
Name: "Values",
|
|
||||||
XValues: chart.LinearRange(1, float64(len(yvalues))),
|
|
||||||
YValues: yvalues,
|
|
||||||
}
|
|
||||||
series = append(series, mainSeries)
|
|
||||||
|
|
||||||
smaSeries := &chart.SMASeries{
|
|
||||||
Name: "SMA",
|
|
||||||
Style: chart.Style{
|
|
||||||
Hidden: *hideSMA,
|
|
||||||
StrokeColor: chart.ColorRed,
|
|
||||||
StrokeDashArray: []float64{5.0, 5.0},
|
|
||||||
},
|
|
||||||
InnerSeries: mainSeries,
|
|
||||||
}
|
|
||||||
series = append(series, smaSeries)
|
|
||||||
|
|
||||||
linRegSeries := &chart.LinearRegressionSeries{
|
|
||||||
Name: "Values - Lin. Reg.",
|
|
||||||
Style: chart.Style{
|
|
||||||
Hidden: *hideLinreg,
|
|
||||||
},
|
|
||||||
InnerSeries: mainSeries,
|
|
||||||
}
|
|
||||||
series = append(series, linRegSeries)
|
|
||||||
|
|
||||||
mainLastValue := chart.LastValueAnnotationSeries(mainSeries)
|
|
||||||
mainLastValue.Style = chart.Style{
|
|
||||||
Hidden: *hideLastValues,
|
|
||||||
}
|
|
||||||
series = append(series, mainLastValue)
|
|
||||||
|
|
||||||
linregLastValue := chart.LastValueAnnotationSeries(linRegSeries)
|
|
||||||
linregLastValue.Style = chart.Style{
|
|
||||||
Hidden: (*hideLastValues || *hideLinreg),
|
|
||||||
}
|
|
||||||
series = append(series, linregLastValue)
|
|
||||||
|
|
||||||
smaLastValue := chart.LastValueAnnotationSeries(smaSeries)
|
|
||||||
smaLastValue.Style = chart.Style{
|
|
||||||
Hidden: (*hideLastValues || *hideSMA),
|
|
||||||
}
|
|
||||||
series = append(series, smaLastValue)
|
|
||||||
|
|
||||||
graph := chart.Chart{
|
|
||||||
Background: chart.Style{
|
|
||||||
Padding: chart.Box{
|
|
||||||
Top: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Series: series,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*hideLegend {
|
|
||||||
graph.Elements = []chart.Renderable{chart.LegendThin(&graph)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var output *os.File
|
|
||||||
if *outputPath != "" {
|
|
||||||
output, err = os.Create(*outputPath)
|
|
||||||
if err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output, err = ioutil.TempFile("", "*.png")
|
|
||||||
if err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := graph.Render(chart.PNG, output); err != nil {
|
|
||||||
log.FatalErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stdout, output.Name())
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "git.smarteching.com/zeni/go-chart/v2/drawing"
|
import "github.com/wcharczuk/go-chart/drawing"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ColorWhite is white.
|
// ColorWhite is white.
|
||||||
|
|
|
@ -3,39 +3,40 @@ package chart
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConcatSeries(t *testing.T) {
|
func TestConcatSeries(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
s1 := ContinuousSeries{
|
s1 := ContinuousSeries{
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := ContinuousSeries{
|
s2 := ContinuousSeries{
|
||||||
XValues: LinearRange(11, 20.0),
|
XValues: seq.Range(11, 20.0),
|
||||||
YValues: LinearRange(10.0, 1.0),
|
YValues: seq.Range(10.0, 1.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
s3 := ContinuousSeries{
|
s3 := ContinuousSeries{
|
||||||
XValues: LinearRange(21, 30.0),
|
XValues: seq.Range(21, 30.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := ConcatSeries([]Series{s1, s2, s3})
|
cs := ConcatSeries([]Series{s1, s2, s3})
|
||||||
testutil.AssertEqual(t, 30, cs.Len())
|
assert.Equal(30, cs.Len())
|
||||||
|
|
||||||
x0, y0 := cs.GetValue(0)
|
x0, y0 := cs.GetValue(0)
|
||||||
testutil.AssertEqual(t, 1.0, x0)
|
assert.Equal(1.0, x0)
|
||||||
testutil.AssertEqual(t, 1.0, y0)
|
assert.Equal(1.0, y0)
|
||||||
|
|
||||||
xm, ym := cs.GetValue(19)
|
xm, ym := cs.GetValue(19)
|
||||||
testutil.AssertEqual(t, 20.0, xm)
|
assert.Equal(20.0, xm)
|
||||||
testutil.AssertEqual(t, 1.0, ym)
|
assert.Equal(1.0, ym)
|
||||||
|
|
||||||
xn, yn := cs.GetValue(29)
|
xn, yn := cs.GetValue(29)
|
||||||
testutil.AssertEqual(t, 30.0, xn)
|
assert.Equal(30.0, xn)
|
||||||
testutil.AssertEqual(t, 10.0, yn)
|
assert.Equal(10.0, yn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,6 @@ func (r *ContinuousRange) SetDomain(domain int) {
|
||||||
|
|
||||||
// String returns a simple string for the ContinuousRange.
|
// String returns a simple string for the ContinuousRange.
|
||||||
func (r ContinuousRange) String() string {
|
func (r ContinuousRange) String() string {
|
||||||
if r.GetDelta() == 0 {
|
|
||||||
return "ContinuousRange [empty]"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
|
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,20 +3,21 @@ package chart
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRangeTranslate(t *testing.T) {
|
func TestRangeTranslate(t *testing.T) {
|
||||||
// replaced new assertions helper
|
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}
|
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 := ContinuousRange{Domain: 1000}
|
||||||
r.Min, r.Max = MinMax(values...)
|
r.Min, r.Max = util.Math.MinAndMax(values...)
|
||||||
|
|
||||||
// delta = ~7.0
|
// delta = ~7.0
|
||||||
// value = ~5.0
|
// value = ~5.0
|
||||||
// domain = ~1000
|
// domain = ~1000
|
||||||
// 5/8 * 1000 ~=
|
// 5/8 * 1000 ~=
|
||||||
testutil.AssertEqual(t, 0, r.Translate(1.0))
|
assert.Equal(0, r.Translate(1.0))
|
||||||
testutil.AssertEqual(t, 1000, r.Translate(8.0))
|
assert.Equal(1000, r.Translate(8.0))
|
||||||
testutil.AssertEqual(t, 572, r.Translate(5.0))
|
assert.Equal(572, r.Translate(5.0))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,6 @@ package chart
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*ContinuousSeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*ContinuousSeries)(nil)
|
|
||||||
_ LastValuesProvider = (*ContinuousSeries)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContinuousSeries represents a line on a chart.
|
// ContinuousSeries represents a line on a chart.
|
||||||
type ContinuousSeries struct {
|
type ContinuousSeries struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -43,11 +36,6 @@ func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
|
||||||
return cs.XValues[index], cs.YValues[index]
|
return cs.XValues[index], cs.YValues[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues gets the first x,y values.
|
|
||||||
func (cs ContinuousSeries) GetFirstValues() (float64, float64) {
|
|
||||||
return cs.XValues[0], cs.YValues[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues gets the last x,y values.
|
// GetLastValues gets the last x,y values.
|
||||||
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
||||||
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
||||||
|
@ -82,15 +70,11 @@ func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Rang
|
||||||
// Validate validates the series.
|
// Validate validates the series.
|
||||||
func (cs ContinuousSeries) Validate() error {
|
func (cs ContinuousSeries) Validate() error {
|
||||||
if len(cs.XValues) == 0 {
|
if len(cs.XValues) == 0 {
|
||||||
return fmt.Errorf("continuous series; must have xvalues set")
|
return fmt.Errorf("continuous series must have xvalues set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cs.YValues) == 0 {
|
if len(cs.YValues) == 0 {
|
||||||
return fmt.Errorf("continuous series; must have yvalues set")
|
return fmt.Errorf("continuous series must have yvalues set")
|
||||||
}
|
|
||||||
|
|
||||||
if len(cs.XValues) != len(cs.YValues) {
|
|
||||||
return fmt.Errorf("continuous series; must have same length xvalues as yvalues")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,35 +4,36 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/seq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContinuousSeries(t *testing.T) {
|
func TestContinuousSeries(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.AssertEqual(t, "Test Series", cs.GetName())
|
assert.Equal("Test Series", cs.GetName())
|
||||||
testutil.AssertEqual(t, 10, cs.Len())
|
assert.Equal(10, cs.Len())
|
||||||
x0, y0 := cs.GetValues(0)
|
x0, y0 := cs.GetValues(0)
|
||||||
testutil.AssertEqual(t, 1.0, x0)
|
assert.Equal(1.0, x0)
|
||||||
testutil.AssertEqual(t, 1.0, y0)
|
assert.Equal(1.0, y0)
|
||||||
|
|
||||||
xn, yn := cs.GetValues(9)
|
xn, yn := cs.GetValues(9)
|
||||||
testutil.AssertEqual(t, 10.0, xn)
|
assert.Equal(10.0, xn)
|
||||||
testutil.AssertEqual(t, 10.0, yn)
|
assert.Equal(10.0, yn)
|
||||||
|
|
||||||
xn, yn = cs.GetLastValues()
|
xn, yn = cs.GetLastValues()
|
||||||
testutil.AssertEqual(t, 10.0, xn)
|
assert.Equal(10.0, xn)
|
||||||
testutil.AssertEqual(t, 10.0, yn)
|
assert.Equal(10.0, yn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContinuousSeriesValueFormatter(t *testing.T) {
|
func TestContinuousSeriesValueFormatter(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
XValueFormatter: func(v interface{}) string {
|
XValueFormatter: func(v interface{}) string {
|
||||||
|
@ -44,29 +45,29 @@ func TestContinuousSeriesValueFormatter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
xf, yf := cs.GetValueFormatters()
|
xf, yf := cs.GetValueFormatters()
|
||||||
testutil.AssertEqual(t, "0.100000 foo", xf(0.1))
|
assert.Equal("0.100000 foo", xf(0.1))
|
||||||
testutil.AssertEqual(t, "0.100000 bar", yf(0.1))
|
assert.Equal("0.100000 bar", yf(0.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContinuousSeriesValidate(t *testing.T) {
|
func TestContinuousSeriesValidate(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
cs := ContinuousSeries{
|
cs := ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
testutil.AssertNil(t, cs.Validate())
|
assert.Nil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
XValues: LinearRange(1.0, 10.0),
|
XValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
testutil.AssertNotNil(t, cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
|
|
||||||
cs = ContinuousSeries{
|
cs = ContinuousSeries{
|
||||||
Name: "Test Series",
|
Name: "Test Series",
|
||||||
YValues: LinearRange(1.0, 10.0),
|
YValues: seq.Range(1.0, 10.0),
|
||||||
}
|
}
|
||||||
testutil.AssertNotNil(t, cs.Validate())
|
assert.NotNil(cs.Validate())
|
||||||
}
|
}
|
||||||
|
|
BIN
debug.test
Executable file
315
donut_chart.go
|
@ -1,315 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DonutChart is a chart that draws sections of a circle based on percentages with an hole.
|
|
||||||
type DonutChart struct {
|
|
||||||
Title string
|
|
||||||
TitleStyle Style
|
|
||||||
|
|
||||||
ColorPalette ColorPalette
|
|
||||||
|
|
||||||
Width int
|
|
||||||
Height int
|
|
||||||
DPI float64
|
|
||||||
|
|
||||||
Background Style
|
|
||||||
Canvas Style
|
|
||||||
SliceStyle Style
|
|
||||||
|
|
||||||
Font *truetype.Font
|
|
||||||
defaultFont *truetype.Font
|
|
||||||
|
|
||||||
Values []Value
|
|
||||||
Elements []Renderable
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDPI returns the dpi for the chart.
|
|
||||||
func (pc DonutChart) GetDPI(defaults ...float64) float64 {
|
|
||||||
if pc.DPI == 0 {
|
|
||||||
if len(defaults) > 0 {
|
|
||||||
return defaults[0]
|
|
||||||
}
|
|
||||||
return DefaultDPI
|
|
||||||
}
|
|
||||||
return pc.DPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFont returns the text font.
|
|
||||||
func (pc DonutChart) GetFont() *truetype.Font {
|
|
||||||
if pc.Font == nil {
|
|
||||||
return pc.defaultFont
|
|
||||||
}
|
|
||||||
return pc.Font
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWidth returns the chart width or the default value.
|
|
||||||
func (pc DonutChart) GetWidth() int {
|
|
||||||
if pc.Width == 0 {
|
|
||||||
return DefaultChartWidth
|
|
||||||
}
|
|
||||||
return pc.Width
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeight returns the chart height or the default value.
|
|
||||||
func (pc DonutChart) GetHeight() int {
|
|
||||||
if pc.Height == 0 {
|
|
||||||
return DefaultChartWidth
|
|
||||||
}
|
|
||||||
return pc.Height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders the chart with the given renderer to the given io.Writer.
|
|
||||||
func (pc DonutChart) Render(rp RendererProvider, w io.Writer) error {
|
|
||||||
if len(pc.Values) == 0 {
|
|
||||||
return errors.New("please provide at least one value")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := rp(pc.GetWidth(), pc.GetHeight())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc.Font == nil {
|
|
||||||
defaultFont, err := GetDefaultFont()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pc.defaultFont = defaultFont
|
|
||||||
}
|
|
||||||
r.SetDPI(pc.GetDPI(DefaultDPI))
|
|
||||||
|
|
||||||
canvasBox := pc.getDefaultCanvasBox()
|
|
||||||
canvasBox = pc.getCircleAdjustedCanvasBox(canvasBox)
|
|
||||||
|
|
||||||
pc.drawBackground(r)
|
|
||||||
pc.drawCanvas(r, canvasBox)
|
|
||||||
|
|
||||||
finalValues, err := pc.finalizeValues(pc.Values)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pc.drawSlices(r, canvasBox, finalValues)
|
|
||||||
pc.drawTitle(r)
|
|
||||||
for _, a := range pc.Elements {
|
|
||||||
a(r, canvasBox, pc.styleDefaultsElements())
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Save(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) drawBackground(r Renderer) {
|
|
||||||
Draw.Box(r, Box{
|
|
||||||
Right: pc.GetWidth(),
|
|
||||||
Bottom: pc.GetHeight(),
|
|
||||||
}, pc.getBackgroundStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) drawCanvas(r Renderer, canvasBox Box) {
|
|
||||||
Draw.Box(r, canvasBox, pc.getCanvasStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) drawTitle(r Renderer) {
|
|
||||||
if len(pc.Title) > 0 && !pc.TitleStyle.Hidden {
|
|
||||||
Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
|
|
||||||
cx, cy := canvasBox.Center()
|
|
||||||
diameter := MinInt(canvasBox.Width(), canvasBox.Height())
|
|
||||||
radius := float64(diameter>>1) / 1.1
|
|
||||||
labelRadius := (radius * 2.83) / 3.0
|
|
||||||
|
|
||||||
// draw the donut slices
|
|
||||||
var rads, delta, delta2, total float64
|
|
||||||
var lx, ly int
|
|
||||||
|
|
||||||
if len(values) == 1 {
|
|
||||||
pc.styleDonutChartValue(0).WriteToRenderer(r)
|
|
||||||
r.MoveTo(cx, cy)
|
|
||||||
r.Circle(radius, cx, cy)
|
|
||||||
} else {
|
|
||||||
for index, v := range values {
|
|
||||||
v.Style.InheritFrom(pc.styleDonutChartValue(index)).WriteToRenderer(r)
|
|
||||||
r.MoveTo(cx, cy)
|
|
||||||
rads = PercentToRadians(total)
|
|
||||||
delta = PercentToRadians(v.Value)
|
|
||||||
|
|
||||||
r.ArcTo(cx, cy, (radius / 1.25), (radius / 1.25), rads, delta)
|
|
||||||
|
|
||||||
r.LineTo(cx, cy)
|
|
||||||
r.Close()
|
|
||||||
r.FillStroke()
|
|
||||||
total = total + v.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//making the donut hole
|
|
||||||
v := Value{Value: 100, Label: "center"}
|
|
||||||
styletemp := pc.SliceStyle.InheritFrom(Style{
|
|
||||||
StrokeColor: ColorWhite, StrokeWidth: 4.0, FillColor: ColorWhite, FontColor: ColorWhite, //Font: pc.GetFont(),//FontSize: pc.getScaledFontSize(),
|
|
||||||
})
|
|
||||||
v.Style.InheritFrom(styletemp).WriteToRenderer(r)
|
|
||||||
r.MoveTo(cx, cy)
|
|
||||||
r.ArcTo(cx, cy, (radius / 3.5), (radius / 3.5), DegreesToRadians(0), DegreesToRadians(359))
|
|
||||||
r.LineTo(cx, cy)
|
|
||||||
r.Close()
|
|
||||||
r.FillStroke()
|
|
||||||
|
|
||||||
// draw the labels
|
|
||||||
total = 0
|
|
||||||
for index, v := range values {
|
|
||||||
v.Style.InheritFrom(pc.styleDonutChartValue(index)).WriteToRenderer(r)
|
|
||||||
if len(v.Label) > 0 {
|
|
||||||
delta2 = PercentToRadians(total + (v.Value / 2.0))
|
|
||||||
delta2 = RadianAdd(delta2, _pi2)
|
|
||||||
lx, ly = CirclePoint(cx, cy, labelRadius, delta2)
|
|
||||||
|
|
||||||
tb := r.MeasureText(v.Label)
|
|
||||||
lx = lx - (tb.Width() >> 1)
|
|
||||||
ly = ly + (tb.Height() >> 1)
|
|
||||||
|
|
||||||
r.Text(v.Label, lx, ly)
|
|
||||||
}
|
|
||||||
total = total + v.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) finalizeValues(values []Value) ([]Value, error) {
|
|
||||||
finalValues := Values(values).Normalize()
|
|
||||||
if len(finalValues) == 0 {
|
|
||||||
return nil, fmt.Errorf("donut chart must contain at least (1) non-zero value")
|
|
||||||
}
|
|
||||||
return finalValues, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getDefaultCanvasBox() Box {
|
|
||||||
return pc.Box()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
|
|
||||||
circleDiameter := MinInt(canvasBox.Width(), canvasBox.Height())
|
|
||||||
|
|
||||||
square := Box{
|
|
||||||
Right: circleDiameter,
|
|
||||||
Bottom: circleDiameter,
|
|
||||||
}
|
|
||||||
|
|
||||||
return canvasBox.Fit(square)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getBackgroundStyle() Style {
|
|
||||||
return pc.Background.InheritFrom(pc.styleDefaultsBackground())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getCanvasStyle() Style {
|
|
||||||
return pc.Canvas.InheritFrom(pc.styleDefaultsCanvas())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDefaultsCanvas() Style {
|
|
||||||
return Style{
|
|
||||||
FillColor: pc.GetColorPalette().CanvasColor(),
|
|
||||||
StrokeColor: pc.GetColorPalette().CanvasStrokeColor(),
|
|
||||||
StrokeWidth: DefaultStrokeWidth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDefaultsDonutChartValue() Style {
|
|
||||||
return Style{
|
|
||||||
StrokeColor: pc.GetColorPalette().TextColor(),
|
|
||||||
StrokeWidth: 4.0,
|
|
||||||
FillColor: pc.GetColorPalette().TextColor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDonutChartValue(index int) Style {
|
|
||||||
return pc.SliceStyle.InheritFrom(Style{
|
|
||||||
StrokeColor: ColorWhite,
|
|
||||||
StrokeWidth: 4.0,
|
|
||||||
FillColor: pc.GetColorPalette().GetSeriesColor(index),
|
|
||||||
FontSize: pc.getScaledFontSize(),
|
|
||||||
FontColor: pc.GetColorPalette().TextColor(),
|
|
||||||
Font: pc.GetFont(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getScaledFontSize() float64 {
|
|
||||||
effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight())
|
|
||||||
if effectiveDimension >= 2048 {
|
|
||||||
return 48.0
|
|
||||||
} else if effectiveDimension >= 1024 {
|
|
||||||
return 24.0
|
|
||||||
} else if effectiveDimension > 512 {
|
|
||||||
return 18.0
|
|
||||||
} else if effectiveDimension > 256 {
|
|
||||||
return 12.0
|
|
||||||
}
|
|
||||||
return 10.0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDefaultsBackground() Style {
|
|
||||||
return Style{
|
|
||||||
FillColor: pc.GetColorPalette().BackgroundColor(),
|
|
||||||
StrokeColor: pc.GetColorPalette().BackgroundStrokeColor(),
|
|
||||||
StrokeWidth: DefaultStrokeWidth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDefaultsElements() Style {
|
|
||||||
return Style{
|
|
||||||
Font: pc.GetFont(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) styleDefaultsTitle() Style {
|
|
||||||
return pc.TitleStyle.InheritFrom(Style{
|
|
||||||
FontColor: pc.GetColorPalette().TextColor(),
|
|
||||||
Font: pc.GetFont(),
|
|
||||||
FontSize: pc.getTitleFontSize(),
|
|
||||||
TextHorizontalAlign: TextHorizontalAlignCenter,
|
|
||||||
TextVerticalAlign: TextVerticalAlignTop,
|
|
||||||
TextWrap: TextWrapWord,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc DonutChart) getTitleFontSize() float64 {
|
|
||||||
effectiveDimension := MinInt(pc.GetWidth(), pc.GetHeight())
|
|
||||||
if effectiveDimension >= 2048 {
|
|
||||||
return 48
|
|
||||||
} else if effectiveDimension >= 1024 {
|
|
||||||
return 24
|
|
||||||
} else if effectiveDimension >= 512 {
|
|
||||||
return 18
|
|
||||||
} else if effectiveDimension >= 256 {
|
|
||||||
return 12
|
|
||||||
}
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetColorPalette returns the color palette for the chart.
|
|
||||||
func (pc DonutChart) GetColorPalette() ColorPalette {
|
|
||||||
if pc.ColorPalette != nil {
|
|
||||||
return pc.ColorPalette
|
|
||||||
}
|
|
||||||
return AlternateColorPalette
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box returns the chart bounds as a box.
|
|
||||||
func (pc DonutChart) Box() Box {
|
|
||||||
dpr := pc.Background.Padding.GetRight(DefaultBackgroundPadding.Right)
|
|
||||||
dpb := pc.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom)
|
|
||||||
|
|
||||||
return Box{
|
|
||||||
Top: pc.Background.Padding.GetTop(DefaultBackgroundPadding.Top),
|
|
||||||
Left: pc.Background.Padding.GetLeft(DefaultBackgroundPadding.Left),
|
|
||||||
Right: pc.GetWidth() - dpr,
|
|
||||||
Bottom: pc.GetHeight() - dpb,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDonutChart(t *testing.T) {
|
|
||||||
// replaced new assertions helper
|
|
||||||
|
|
||||||
pie := DonutChart{
|
|
||||||
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)
|
|
||||||
testutil.AssertNotZero(t, b.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDonutChartDropsZeroValues(t *testing.T) {
|
|
||||||
// replaced new assertions helper
|
|
||||||
|
|
||||||
pie := DonutChart{
|
|
||||||
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)
|
|
||||||
testutil.AssertNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDonutChartAllZeroValues(t *testing.T) {
|
|
||||||
// replaced new assertions helper
|
|
||||||
|
|
||||||
pie := DonutChart{
|
|
||||||
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)
|
|
||||||
testutil.AssertNotNil(t, err)
|
|
||||||
}
|
|
103
draw.go
|
@ -2,6 +2,8 @@ package chart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -38,8 +40,8 @@ func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style
|
||||||
y = cb - yrange.Translate(vy)
|
y = cb - yrange.Translate(vy)
|
||||||
r.LineTo(x, y)
|
r.LineTo(x, y)
|
||||||
}
|
}
|
||||||
r.LineTo(x, MinInt(cb, cb-yv0))
|
r.LineTo(x, util.Math.MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, MinInt(cb, cb-yv0))
|
r.LineTo(x0, util.Math.MinInt(cb, cb-yv0))
|
||||||
r.LineTo(x0, y0)
|
r.LineTo(x0, y0)
|
||||||
r.Fill()
|
r.Fill()
|
||||||
}
|
}
|
||||||
|
@ -166,14 +168,73 @@ func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d draw) CandlestickSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, cs CandlestickSeries) {
|
||||||
|
if cs.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
candleValues := cs.GetCandleValues()
|
||||||
|
|
||||||
|
cb := canvasBox.Bottom
|
||||||
|
cl := canvasBox.Left
|
||||||
|
|
||||||
|
var cv CandleValue
|
||||||
|
for index := 0; index < len(candleValues); index++ {
|
||||||
|
cv = candleValues[index]
|
||||||
|
|
||||||
|
y0 := yrange.Translate(cv.Open)
|
||||||
|
y1 := yrange.Translate(cv.Close)
|
||||||
|
|
||||||
|
x0 := cl + xrange.Translate(util.Time.ToFloat64(util.Date.On(util.NYSEOpen(), cv.Timestamp)))
|
||||||
|
x1 := cl + xrange.Translate(util.Time.ToFloat64(util.Date.On(util.NYSEClose(), cv.Timestamp)))
|
||||||
|
|
||||||
|
x := x0 + ((x1 - x0) >> 1)
|
||||||
|
|
||||||
|
// draw open / close box.
|
||||||
|
if cv.Open < cv.Close {
|
||||||
|
d.Box(r, Box{
|
||||||
|
Top: cb - y0,
|
||||||
|
Left: x0,
|
||||||
|
Right: x1,
|
||||||
|
Bottom: cb - y1,
|
||||||
|
}, style.InheritFrom(Style{FillColor: ColorAlternateGreen}))
|
||||||
|
} else {
|
||||||
|
d.Box(r, Box{
|
||||||
|
Top: cb - y1,
|
||||||
|
Left: x0,
|
||||||
|
Right: x1,
|
||||||
|
Bottom: cb - y0,
|
||||||
|
}, style.InheritFrom(Style{FillColor: ColorRed}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw high / low t bars
|
||||||
|
y0 = yrange.Translate(cv.High)
|
||||||
|
y1 = yrange.Translate(cv.Low)
|
||||||
|
|
||||||
|
style.InheritFrom(Style{StrokeColor: DefaultStrokeColor}).WriteToRenderer(r)
|
||||||
|
|
||||||
|
r.MoveTo(x0, cb-y0)
|
||||||
|
r.LineTo(x1, cb-y0)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
r.MoveTo(x, cb-y0)
|
||||||
|
r.LineTo(x, cb-y1)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
r.MoveTo(x0, cb-y1)
|
||||||
|
r.LineTo(x1, cb-y1)
|
||||||
|
r.Stroke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MeasureAnnotation measures how big an annotation would be.
|
// MeasureAnnotation measures how big an annotation would be.
|
||||||
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
|
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
|
||||||
style.WriteToRenderer(r)
|
style.WriteToRenderer(r)
|
||||||
defer r.ResetStyle()
|
defer r.ResetStyle()
|
||||||
|
|
||||||
textBox := r.MeasureText(label)
|
textBox := r.MeasureText(label)
|
||||||
textWidth := textBox.Width()
|
textWidth := int(textBox.Width())
|
||||||
textHeight := textBox.Height()
|
textHeight := int(textBox.Height())
|
||||||
halfTextHeight := textHeight >> 1
|
halfTextHeight := textHeight >> 1
|
||||||
|
|
||||||
pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
|
pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
|
||||||
|
@ -201,8 +262,8 @@ func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, lab
|
||||||
defer r.ResetStyle()
|
defer r.ResetStyle()
|
||||||
|
|
||||||
textBox := r.MeasureText(label)
|
textBox := r.MeasureText(label)
|
||||||
textWidth := textBox.Width()
|
textWidth := int(textBox.Width())
|
||||||
halfTextHeight := textBox.Height() >> 1
|
halfTextHeight := int(textBox.Height()) >> 1
|
||||||
|
|
||||||
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||||
|
|
||||||
|
@ -253,17 +314,17 @@ func (d draw) Box(r Renderer, b Box, s Style) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) {
|
func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) {
|
||||||
d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s)
|
d.Box2d(r, b.Corners().Rotate(thetaDegrees), s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) {
|
func (d draw) Box2d(r Renderer, bc Box2d, s Style) {
|
||||||
s.GetFillAndStrokeOptions().WriteToRenderer(r)
|
s.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||||
defer r.ResetStyle()
|
defer r.ResetStyle()
|
||||||
|
|
||||||
r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y)
|
r.MoveTo(int(bc.TopLeft.X), int(bc.TopLeft.Y))
|
||||||
r.LineTo(bc.TopRight.X, bc.TopRight.Y)
|
r.LineTo(int(bc.TopRight.X), int(bc.TopRight.Y))
|
||||||
r.LineTo(bc.BottomRight.X, bc.BottomRight.Y)
|
r.LineTo(int(bc.BottomRight.X), int(bc.BottomRight.Y))
|
||||||
r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y)
|
r.LineTo(int(bc.BottomLeft.X), int(bc.BottomLeft.Y))
|
||||||
r.Close()
|
r.Close()
|
||||||
r.FillStroke()
|
r.FillStroke()
|
||||||
}
|
}
|
||||||
|
@ -276,7 +337,7 @@ func (d draw) Text(r Renderer, text string, x, y int, style Style) {
|
||||||
r.Text(text, x, y)
|
r.Text(text, x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d draw) MeasureText(r Renderer, text string, style Style) Box {
|
func (d draw) MeasureText(r Renderer, text string, style Style) Box2d {
|
||||||
style.GetTextOptions().WriteToRenderer(r)
|
style.GetTextOptions().WriteToRenderer(r)
|
||||||
defer r.ResetStyle()
|
defer r.ResetStyle()
|
||||||
|
|
||||||
|
@ -295,11 +356,9 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
|
||||||
|
|
||||||
switch style.GetTextVerticalAlign() {
|
switch style.GetTextVerticalAlign() {
|
||||||
case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
|
case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
|
||||||
y = y - linesBox.Height()
|
y = y - int(linesBox.Height())
|
||||||
case TextVerticalAlignMiddle:
|
case TextVerticalAlignMiddle, TextVerticalAlignMiddleBaseline:
|
||||||
y = y + (box.Height() >> 1) - (linesBox.Height() >> 1)
|
y = (y - int(linesBox.Height())) >> 1
|
||||||
case TextVerticalAlignMiddleBaseline:
|
|
||||||
y = y + (box.Height() >> 1) - linesBox.Height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx, ty int
|
var tx, ty int
|
||||||
|
@ -307,19 +366,19 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
|
||||||
lineBox := r.MeasureText(line)
|
lineBox := r.MeasureText(line)
|
||||||
switch style.GetTextHorizontalAlign() {
|
switch style.GetTextHorizontalAlign() {
|
||||||
case TextHorizontalAlignCenter:
|
case TextHorizontalAlignCenter:
|
||||||
tx = box.Left + ((box.Width() - lineBox.Width()) >> 1)
|
tx = box.Left + ((int(box.Width()) - int(lineBox.Width())) >> 1)
|
||||||
case TextHorizontalAlignRight:
|
case TextHorizontalAlignRight:
|
||||||
tx = box.Right - lineBox.Width()
|
tx = box.Right - int(lineBox.Width())
|
||||||
default:
|
default:
|
||||||
tx = box.Left
|
tx = box.Left
|
||||||
}
|
}
|
||||||
if style.TextRotationDegrees == 0 {
|
if style.TextRotationDegrees == 0 {
|
||||||
ty = y + lineBox.Height()
|
ty = y + int(lineBox.Height())
|
||||||
} else {
|
} else {
|
||||||
ty = y
|
ty = y
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Text(line, tx, ty)
|
r.Text(line, tx, ty)
|
||||||
y += lineBox.Height() + style.GetTextLineSpacing()
|
y += int(lineBox.Height()) + style.GetTextLineSpacing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
162
drawing/color.go
|
@ -2,46 +2,27 @@ package drawing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basic Colors from:
|
|
||||||
// https://www.w3.org/wiki/CSS/Properties/color/keywords
|
|
||||||
var (
|
var (
|
||||||
// ColorTransparent is a fully transparent color.
|
// ColorTransparent is a fully transparent color.
|
||||||
ColorTransparent = Color{R: 255, G: 255, B: 255, A: 0}
|
ColorTransparent = Color{}
|
||||||
|
|
||||||
// ColorWhite is white.
|
// ColorWhite is white.
|
||||||
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
|
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
|
||||||
|
|
||||||
// ColorBlack is black.
|
// ColorBlack is black.
|
||||||
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
|
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
|
||||||
|
|
||||||
// ColorRed is red.
|
// ColorRed is red.
|
||||||
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
|
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
|
||||||
|
|
||||||
// ColorGreen is green.
|
// ColorGreen is green.
|
||||||
ColorGreen = Color{R: 0, G: 128, B: 0, A: 255}
|
ColorGreen = Color{R: 0, G: 255, B: 0, A: 255}
|
||||||
|
|
||||||
// ColorBlue is blue.
|
// ColorBlue is blue.
|
||||||
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
|
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
|
||||||
// ColorSilver is a known color.
|
|
||||||
ColorSilver = Color{R: 192, G: 192, B: 192, A: 255}
|
|
||||||
// ColorMaroon is a known color.
|
|
||||||
ColorMaroon = Color{R: 128, G: 0, B: 0, A: 255}
|
|
||||||
// ColorPurple is a known color.
|
|
||||||
ColorPurple = Color{R: 128, G: 0, B: 128, A: 255}
|
|
||||||
// ColorFuchsia is a known color.
|
|
||||||
ColorFuchsia = Color{R: 255, G: 0, B: 255, A: 255}
|
|
||||||
// ColorLime is a known color.
|
|
||||||
ColorLime = Color{R: 0, G: 255, B: 0, A: 255}
|
|
||||||
// ColorOlive is a known color.
|
|
||||||
ColorOlive = Color{R: 128, G: 128, B: 0, A: 255}
|
|
||||||
// ColorYellow is a known color.
|
|
||||||
ColorYellow = Color{R: 255, G: 255, B: 0, A: 255}
|
|
||||||
// ColorNavy is a known color.
|
|
||||||
ColorNavy = Color{R: 0, G: 0, B: 128, A: 255}
|
|
||||||
// ColorTeal is a known color.
|
|
||||||
ColorTeal = Color{R: 0, G: 128, B: 128, A: 255}
|
|
||||||
// ColorAqua is a known color.
|
|
||||||
ColorAqua = Color{R: 0, G: 255, B: 255, A: 255}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseHex(hex string) uint8 {
|
func parseHex(hex string) uint8 {
|
||||||
|
@ -49,97 +30,8 @@ func parseHex(hex string) uint8 {
|
||||||
return uint8(v)
|
return uint8(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseColor parses a color from a string.
|
|
||||||
func ParseColor(rawColor string) Color {
|
|
||||||
if strings.HasPrefix(rawColor, "rgba") {
|
|
||||||
return ColorFromRGBA(rawColor)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(rawColor, "rgb") {
|
|
||||||
return ColorFromRGB(rawColor)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(rawColor, "#") {
|
|
||||||
return ColorFromHex(rawColor)
|
|
||||||
}
|
|
||||||
return ColorFromKnown(rawColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rgbaexpr = regexp.MustCompile(`rgba\((?P<R>.+),(?P<G>.+),(?P<B>.+),(?P<A>.+)\)`)
|
|
||||||
|
|
||||||
// ColorFromRGBA returns a color from an `rgba()` css function.
|
|
||||||
func ColorFromRGBA(rgba string) (output Color) {
|
|
||||||
values := rgbaexpr.FindStringSubmatch(rgba)
|
|
||||||
for i, name := range rgbaexpr.SubexpNames() {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i >= len(values) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch name {
|
|
||||||
case "R":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.R = uint8(parsed)
|
|
||||||
case "G":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.G = uint8(parsed)
|
|
||||||
case "B":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.B = uint8(parsed)
|
|
||||||
case "A":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseFloat(value, 32)
|
|
||||||
if parsed > 1 {
|
|
||||||
parsed = 1
|
|
||||||
} else if parsed < 0 {
|
|
||||||
parsed = 0
|
|
||||||
}
|
|
||||||
output.A = uint8(parsed * 255)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var rgbexpr = regexp.MustCompile(`rgb\((?P<R>.+),(?P<G>.+),(?P<B>.+)\)`)
|
|
||||||
|
|
||||||
// ColorFromRGB returns a color from an `rgb()` css function.
|
|
||||||
func ColorFromRGB(rgb string) (output Color) {
|
|
||||||
output.A = 255
|
|
||||||
values := rgbexpr.FindStringSubmatch(rgb)
|
|
||||||
for i, name := range rgbaexpr.SubexpNames() {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i >= len(values) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch name {
|
|
||||||
case "R":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.R = uint8(parsed)
|
|
||||||
case "G":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.G = uint8(parsed)
|
|
||||||
case "B":
|
|
||||||
value := strings.TrimSpace(values[i])
|
|
||||||
parsed, _ := strconv.ParseInt(value, 10, 16)
|
|
||||||
output.B = uint8(parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorFromHex returns a color from a css hex code.
|
// ColorFromHex returns a color from a css hex code.
|
||||||
//
|
|
||||||
// NOTE: it will trim a leading '#' character if present.
|
|
||||||
func ColorFromHex(hex string) Color {
|
func ColorFromHex(hex string) Color {
|
||||||
if strings.HasPrefix(hex, "#") {
|
|
||||||
hex = strings.TrimPrefix(hex, "#")
|
|
||||||
}
|
|
||||||
var c Color
|
var c Color
|
||||||
if len(hex) == 3 {
|
if len(hex) == 3 {
|
||||||
c.R = parseHex(string(hex[0])) * 0x11
|
c.R = parseHex(string(hex[0])) * 0x11
|
||||||
|
@ -154,46 +46,6 @@ func ColorFromHex(hex string) Color {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColorFromKnown returns an internal color from a known (basic) color name.
|
|
||||||
func ColorFromKnown(known string) Color {
|
|
||||||
switch strings.ToLower(known) {
|
|
||||||
case "transparent":
|
|
||||||
return ColorTransparent
|
|
||||||
case "white":
|
|
||||||
return ColorWhite
|
|
||||||
case "black":
|
|
||||||
return ColorBlack
|
|
||||||
case "red":
|
|
||||||
return ColorRed
|
|
||||||
case "blue":
|
|
||||||
return ColorBlue
|
|
||||||
case "green":
|
|
||||||
return ColorGreen
|
|
||||||
case "silver":
|
|
||||||
return ColorSilver
|
|
||||||
case "maroon":
|
|
||||||
return ColorMaroon
|
|
||||||
case "purple":
|
|
||||||
return ColorPurple
|
|
||||||
case "fuchsia":
|
|
||||||
return ColorFuchsia
|
|
||||||
case "lime":
|
|
||||||
return ColorLime
|
|
||||||
case "olive":
|
|
||||||
return ColorOlive
|
|
||||||
case "yellow":
|
|
||||||
return ColorYellow
|
|
||||||
case "navy":
|
|
||||||
return ColorNavy
|
|
||||||
case "teal":
|
|
||||||
return ColorTeal
|
|
||||||
case "aqua":
|
|
||||||
return ColorAqua
|
|
||||||
default:
|
|
||||||
return Color{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
|
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
|
||||||
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color {
|
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color {
|
||||||
fa := float64(a) / 255.0
|
fa := float64(a) / 255.0
|
||||||
|
|
|
@ -1,114 +1,53 @@
|
||||||
package drawing
|
package drawing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
"github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestColorFromHex(t *testing.T) {
|
func TestColorFromHex(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
white := ColorFromHex("FFFFFF")
|
white := ColorFromHex("FFFFFF")
|
||||||
testutil.AssertEqual(t, ColorWhite, white)
|
assert.Equal(ColorWhite, white)
|
||||||
|
|
||||||
shortWhite := ColorFromHex("FFF")
|
shortWhite := ColorFromHex("FFF")
|
||||||
testutil.AssertEqual(t, ColorWhite, shortWhite)
|
assert.Equal(ColorWhite, shortWhite)
|
||||||
|
|
||||||
black := ColorFromHex("000000")
|
black := ColorFromHex("000000")
|
||||||
testutil.AssertEqual(t, ColorBlack, black)
|
assert.Equal(ColorBlack, black)
|
||||||
|
|
||||||
shortBlack := ColorFromHex("000")
|
shortBlack := ColorFromHex("000")
|
||||||
testutil.AssertEqual(t, ColorBlack, shortBlack)
|
assert.Equal(ColorBlack, shortBlack)
|
||||||
|
|
||||||
red := ColorFromHex("FF0000")
|
red := ColorFromHex("FF0000")
|
||||||
testutil.AssertEqual(t, ColorRed, red)
|
assert.Equal(ColorRed, red)
|
||||||
|
|
||||||
shortRed := ColorFromHex("F00")
|
shortRed := ColorFromHex("F00")
|
||||||
testutil.AssertEqual(t, ColorRed, shortRed)
|
assert.Equal(ColorRed, shortRed)
|
||||||
|
|
||||||
green := ColorFromHex("008000")
|
green := ColorFromHex("00FF00")
|
||||||
testutil.AssertEqual(t, ColorGreen, green)
|
assert.Equal(ColorGreen, green)
|
||||||
|
|
||||||
// shortGreen := ColorFromHex("0F0")
|
shortGreen := ColorFromHex("0F0")
|
||||||
// testutil.AssertEqual(t, ColorGreen, shortGreen)
|
assert.Equal(ColorGreen, shortGreen)
|
||||||
|
|
||||||
blue := ColorFromHex("0000FF")
|
blue := ColorFromHex("0000FF")
|
||||||
testutil.AssertEqual(t, ColorBlue, blue)
|
assert.Equal(ColorBlue, blue)
|
||||||
|
|
||||||
shortBlue := ColorFromHex("00F")
|
shortBlue := ColorFromHex("00F")
|
||||||
testutil.AssertEqual(t, ColorBlue, shortBlue)
|
assert.Equal(ColorBlue, shortBlue)
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorFromHex_handlesHash(t *testing.T) {
|
|
||||||
withHash := ColorFromHex("#FF0000")
|
|
||||||
testutil.AssertEqual(t, ColorRed, withHash)
|
|
||||||
|
|
||||||
withoutHash := ColorFromHex("#FF0000")
|
|
||||||
testutil.AssertEqual(t, ColorRed, withoutHash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorFromAlphaMixedRGBA(t *testing.T) {
|
func TestColorFromAlphaMixedRGBA(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
black := ColorFromAlphaMixedRGBA(color.Black.RGBA())
|
black := ColorFromAlphaMixedRGBA(color.Black.RGBA())
|
||||||
testutil.AssertTrue(t, black.Equals(ColorBlack), black.String())
|
assert.True(black.Equals(ColorBlack), black.String())
|
||||||
|
|
||||||
white := ColorFromAlphaMixedRGBA(color.White.RGBA())
|
white := ColorFromAlphaMixedRGBA(color.White.RGBA())
|
||||||
testutil.AssertTrue(t, white.Equals(ColorWhite), white.String())
|
assert.True(white.Equals(ColorWhite), white.String())
|
||||||
}
|
|
||||||
|
|
||||||
func Test_ColorFromRGBA(t *testing.T) {
|
|
||||||
value := "rgba(192, 192, 192, 1.0)"
|
|
||||||
parsed := ColorFromRGBA(value)
|
|
||||||
testutil.AssertEqual(t, ColorSilver, parsed)
|
|
||||||
|
|
||||||
value = "rgba(192,192,192,1.0)"
|
|
||||||
parsed = ColorFromRGBA(value)
|
|
||||||
testutil.AssertEqual(t, ColorSilver, parsed)
|
|
||||||
|
|
||||||
value = "rgba(192,192,192,1.5)"
|
|
||||||
parsed = ColorFromRGBA(value)
|
|
||||||
testutil.AssertEqual(t, ColorSilver, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseColor(t *testing.T) {
|
|
||||||
testCases := [...]struct {
|
|
||||||
Input string
|
|
||||||
Expected Color
|
|
||||||
}{
|
|
||||||
{"", Color{}},
|
|
||||||
{"white", ColorWhite},
|
|
||||||
{"WHITE", ColorWhite}, // caps!
|
|
||||||
{"black", ColorBlack},
|
|
||||||
{"red", ColorRed},
|
|
||||||
{"green", ColorGreen},
|
|
||||||
{"blue", ColorBlue},
|
|
||||||
{"silver", ColorSilver},
|
|
||||||
{"maroon", ColorMaroon},
|
|
||||||
{"purple", ColorPurple},
|
|
||||||
{"fuchsia", ColorFuchsia},
|
|
||||||
{"lime", ColorLime},
|
|
||||||
{"olive", ColorOlive},
|
|
||||||
{"yellow", ColorYellow},
|
|
||||||
{"navy", ColorNavy},
|
|
||||||
{"teal", ColorTeal},
|
|
||||||
{"aqua", ColorAqua},
|
|
||||||
|
|
||||||
{"rgba(192, 192, 192, 1.0)", ColorSilver},
|
|
||||||
{"rgba(192,192,192,1.0)", ColorSilver},
|
|
||||||
{"rgb(192, 192, 192)", ColorSilver},
|
|
||||||
{"rgb(192,192,192)", ColorSilver},
|
|
||||||
|
|
||||||
{"#FF0000", ColorRed},
|
|
||||||
{"#008000", ColorGreen},
|
|
||||||
{"#0000FF", ColorBlue},
|
|
||||||
{"#F00", ColorRed},
|
|
||||||
{"#080", Color{0, 136, 0, 255}},
|
|
||||||
{"#00F", ColorBlue},
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, tc := range testCases {
|
|
||||||
actual := ParseColor(tc.Input)
|
|
||||||
testutil.AssertEqual(t, tc.Expected, actual, fmt.Sprintf("test case: %d -> %s", index, tc.Input))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package drawing
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2/testutil"
|
assert "github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type point struct {
|
type point struct {
|
||||||
|
@ -23,7 +23,7 @@ func (ml mockLine) Len() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraceQuad(t *testing.T) {
|
func TestTraceQuad(t *testing.T) {
|
||||||
// replaced new assertions helper
|
assert := assert.New(t)
|
||||||
|
|
||||||
// Quad
|
// Quad
|
||||||
// x1, y1, cpx1, cpy2, x2, y2 float64
|
// x1, y1, cpx1, cpy2, x2, y2 float64
|
||||||
|
@ -31,5 +31,5 @@ func TestTraceQuad(t *testing.T) {
|
||||||
quad := []float64{10, 20, 20, 20, 20, 10}
|
quad := []float64{10, 20, 20, 20, 20, 10}
|
||||||
liner := &mockLine{}
|
liner := &mockLine{}
|
||||||
TraceQuad(liner, quad, 0.5)
|
TraceQuad(liner, quad, 0.5)
|
||||||
testutil.AssertNotZero(t, liner.Len())
|
assert.NotZero(liner.Len())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,6 @@ const (
|
||||||
DefaultEMAPeriod = 12
|
DefaultEMAPeriod = 12
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface Assertions.
|
|
||||||
var (
|
|
||||||
_ Series = (*EMASeries)(nil)
|
|
||||||
_ FirstValuesProvider = (*EMASeries)(nil)
|
|
||||||
_ LastValuesProvider = (*EMASeries)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// EMASeries is a computed series.
|
// EMASeries is a computed series.
|
||||||
type EMASeries struct {
|
type EMASeries struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -73,19 +66,6 @@ func (ema *EMASeries) GetValues(index int) (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstValues computes the first moving average value.
|
|
||||||
func (ema *EMASeries) GetFirstValues() (x, y float64) {
|
|
||||||
if ema.InnerSeries == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ema.cache) == 0 {
|
|
||||||
ema.ensureCachedValues()
|
|
||||||
}
|
|
||||||
x, _ = ema.InnerSeries.GetValues(0)
|
|
||||||
y = ema.cache[0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValues computes the last moving average value but walking back window size samples,
|
// GetLastValues computes the last moving average value but walking back window size samples,
|
||||||
// and recomputing the last moving average chunk.
|
// and recomputing the last moving average chunk.
|
||||||
func (ema *EMASeries) GetLastValues() (x, y float64) {
|
func (ema *EMASeries) GetLastValues() (x, y float64) {
|
||||||
|
|