打包文件
This commit is contained in:
parent
436c2049bc
commit
a679420e87
@ -10,13 +10,18 @@ require (
|
||||
github.com/redis/go-redis/v9 v9.6.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/segmentio/kafka-go v0.4.47
|
||||
github.com/vicanso/go-charts/v2 v2.6.10
|
||||
google.golang.org/protobuf v1.35.2
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -13,10 +13,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
@ -51,8 +55,13 @@ github.com/segmentio/kafka-go v0.4.47/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVO
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/vicanso/go-charts/v2 v2.6.10 h1:Nb2YBekEbUBPbvohnUO1oYMy31v75brUPk6n/fq+JXw=
|
||||
github.com/vicanso/go-charts/v2 v2.6.10/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
@ -63,6 +72,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
||||
21
src/server/vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
21
src/server/vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go_import_path: github.com/dustin/go-humanize
|
||||
go:
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- 1.16.x
|
||||
- stable
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go vet .
|
||||
- go install -v -race ./...
|
||||
- go test -v -race ./...
|
||||
21
src/server/vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
21
src/server/vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
||||
124
src/server/vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
124
src/server/vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
# Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize)
|
||||
|
||||
Just a few functions for helping humanize times and sizes.
|
||||
|
||||
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||
|
||||
See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for
|
||||
complete documentation.
|
||||
|
||||
## Sizes
|
||||
|
||||
This lets you take numbers like `82854982` and convert them to useful
|
||||
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||
```
|
||||
|
||||
## Times
|
||||
|
||||
This lets you take a `time.Time` and spit it out in relative terms.
|
||||
For example, `12 seconds ago` or `3 days from now`.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||
```
|
||||
|
||||
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||
conversation one day. It's pretty neat.
|
||||
|
||||
## Ordinals
|
||||
|
||||
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||
to label ordinals.
|
||||
|
||||
0 -> 0th
|
||||
1 -> 1st
|
||||
2 -> 2nd
|
||||
3 -> 3rd
|
||||
4 -> 4th
|
||||
[...]
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||
```
|
||||
|
||||
## Commas
|
||||
|
||||
Want to shove commas into numbers? Be my guest.
|
||||
|
||||
0 -> 0
|
||||
100 -> 100
|
||||
1000 -> 1,000
|
||||
1000000000 -> 1,000,000,000
|
||||
-100000 -> -100,000
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||
```
|
||||
|
||||
## Ftoa
|
||||
|
||||
Nicer float64 formatter that removes trailing zeros.
|
||||
|
||||
```go
|
||||
fmt.Printf("%f", 2.24) // 2.240000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||
fmt.Printf("%f", 2.0) // 2.000000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||
```
|
||||
|
||||
## SI notation
|
||||
|
||||
Format numbers with [SI notation][sinotation].
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||
```
|
||||
|
||||
## English-specific functions
|
||||
|
||||
The following functions are in the `humanize/english` subpackage.
|
||||
|
||||
### Plurals
|
||||
|
||||
Simple English pluralization
|
||||
|
||||
```go
|
||||
english.PluralWord(1, "object", "") // object
|
||||
english.PluralWord(42, "object", "") // objects
|
||||
english.PluralWord(2, "bus", "") // buses
|
||||
english.PluralWord(99, "locus", "loci") // loci
|
||||
|
||||
english.Plural(1, "object", "") // 1 object
|
||||
english.Plural(42, "object", "") // 42 objects
|
||||
english.Plural(2, "bus", "") // 2 buses
|
||||
english.Plural(99, "locus", "loci") // 99 loci
|
||||
```
|
||||
|
||||
### Word series
|
||||
|
||||
Format comma-separated words lists with conjuctions:
|
||||
|
||||
```go
|
||||
english.WordSeries([]string{"foo"}, "and") // foo
|
||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||
|
||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||
```
|
||||
|
||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
||||
31
src/server/vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
31
src/server/vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// order of magnitude (to a max order)
|
||||
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
if mag == maxmag && maxmag >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
|
||||
// total order of magnitude
|
||||
// (same as above, but with no upper limit)
|
||||
func oom(n, b *big.Int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
189
src/server/vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
189
src/server/vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bigIECExp = big.NewInt(1024)
|
||||
|
||||
// BigByte is one byte in bit.Ints
|
||||
BigByte = big.NewInt(1)
|
||||
// BigKiByte is 1,024 bytes in bit.Ints
|
||||
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||
// BigRiByte is 1,024 y bytes in bit.Ints
|
||||
BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp)
|
||||
// BigQiByte is 1,024 r bytes in bit.Ints
|
||||
BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp)
|
||||
)
|
||||
|
||||
var (
|
||||
bigSIExp = big.NewInt(1000)
|
||||
|
||||
// BigSIByte is one SI byte in big.Ints
|
||||
BigSIByte = big.NewInt(1)
|
||||
// BigKByte is 1,000 SI bytes in big.Ints
|
||||
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||
// BigRByte is 1,000 SI y bytes in big.Ints
|
||||
BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp)
|
||||
// BigQByte is 1,000 SI r bytes in big.Ints
|
||||
BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp)
|
||||
)
|
||||
|
||||
var bigBytesSizeTable = map[string]*big.Int{
|
||||
"b": BigByte,
|
||||
"kib": BigKiByte,
|
||||
"kb": BigKByte,
|
||||
"mib": BigMiByte,
|
||||
"mb": BigMByte,
|
||||
"gib": BigGiByte,
|
||||
"gb": BigGByte,
|
||||
"tib": BigTiByte,
|
||||
"tb": BigTByte,
|
||||
"pib": BigPiByte,
|
||||
"pb": BigPByte,
|
||||
"eib": BigEiByte,
|
||||
"eb": BigEByte,
|
||||
"zib": BigZiByte,
|
||||
"zb": BigZByte,
|
||||
"yib": BigYiByte,
|
||||
"yb": BigYByte,
|
||||
"rib": BigRiByte,
|
||||
"rb": BigRByte,
|
||||
"qib": BigQiByte,
|
||||
"qb": BigQByte,
|
||||
// Without suffix
|
||||
"": BigByte,
|
||||
"ki": BigKiByte,
|
||||
"k": BigKByte,
|
||||
"mi": BigMiByte,
|
||||
"m": BigMByte,
|
||||
"gi": BigGiByte,
|
||||
"g": BigGByte,
|
||||
"ti": BigTiByte,
|
||||
"t": BigTByte,
|
||||
"pi": BigPiByte,
|
||||
"p": BigPByte,
|
||||
"ei": BigEiByte,
|
||||
"e": BigEByte,
|
||||
"z": BigZByte,
|
||||
"zi": BigZiByte,
|
||||
"y": BigYByte,
|
||||
"yi": BigYiByte,
|
||||
"r": BigRByte,
|
||||
"ri": BigRiByte,
|
||||
"q": BigQByte,
|
||||
"qi": BigQiByte,
|
||||
}
|
||||
|
||||
var ten = big.NewInt(10)
|
||||
|
||||
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||
if s.Cmp(ten) < 0 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
c := (&big.Int{}).Set(s)
|
||||
val, mag := oomm(c, base, len(sizes)-1)
|
||||
suffix := sizes[mag]
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
|
||||
}
|
||||
|
||||
// BigBytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigBytes(82854982) -> 83 MB
|
||||
func BigBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"}
|
||||
return humanateBigBytes(s, bigSIExp, sizes)
|
||||
}
|
||||
|
||||
// BigIBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigIBytes(82854982) -> 79 MiB
|
||||
func BigIBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"}
|
||||
return humanateBigBytes(s, bigIECExp, sizes)
|
||||
}
|
||||
|
||||
// ParseBigBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See also: BigBytes, BigIBytes.
|
||||
//
|
||||
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||
func ParseBigBytes(s string) (*big.Int, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
val := &big.Rat{}
|
||||
_, err := fmt.Sscanf(num, "%f", val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||
mv := (&big.Rat{}).SetInt(m)
|
||||
val.Mul(val, mv)
|
||||
rv := &big.Int{}
|
||||
rv.Div(val.Num(), val.Denom())
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
143
src/server/vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
143
src/server/vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
// Bytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// Bytes(82854982) -> 83 MB
|
||||
func Bytes(s uint64) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1000, sizes)
|
||||
}
|
||||
|
||||
// IBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// IBytes(82854982) -> 79 MiB
|
||||
func IBytes(s uint64) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// ParseBytes("42 MB") -> 42000000, nil
|
||||
// ParseBytes("42 mib") -> 44040192, nil
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
116
src/server/vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
116
src/server/vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Comma produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Comma(834142) -> 834,142
|
||||
func Comma(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
parts := []string{"", "", "", "", "", "", ""}
|
||||
j := len(parts) - 1
|
||||
|
||||
for v > 999 {
|
||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
v = v / 1000
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(v))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
|
||||
// Commaf produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Commaf(834142.32) -> 834,142.32
|
||||
func Commaf(v float64) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// CommafWithDigits works like the Commaf but limits the resulting
|
||||
// string to the given number of decimal places.
|
||||
//
|
||||
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
||||
func CommafWithDigits(f float64, decimals int) string {
|
||||
return stripTrailingDigits(Commaf(f), decimals)
|
||||
}
|
||||
|
||||
// BigComma produces a string form of the given big.Int in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigComma(b *big.Int) string {
|
||||
sign := ""
|
||||
if b.Sign() < 0 {
|
||||
sign = "-"
|
||||
b.Abs(b)
|
||||
}
|
||||
|
||||
athousand := big.NewInt(1000)
|
||||
c := (&big.Int{}).Set(b)
|
||||
_, m := oom(c, athousand)
|
||||
parts := make([]string, m+1)
|
||||
j := len(parts) - 1
|
||||
|
||||
mod := &big.Int{}
|
||||
for b.Cmp(athousand) >= 0 {
|
||||
b.DivMod(b, athousand, mod)
|
||||
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
41
src/server/vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
41
src/server/vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build go1.6
|
||||
// +build go1.6
|
||||
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BigCommaf produces a string form of the given big.Float in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigCommaf(v *big.Float) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v.Sign() < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v.Abs(v)
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(v.Text('f', -1), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
49
src/server/vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
49
src/server/vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stripTrailingZeros(s string) string {
|
||||
if !strings.ContainsRune(s, '.') {
|
||||
return s
|
||||
}
|
||||
offset := len(s) - 1
|
||||
for offset > 0 {
|
||||
if s[offset] == '.' {
|
||||
offset--
|
||||
break
|
||||
}
|
||||
if s[offset] != '0' {
|
||||
break
|
||||
}
|
||||
offset--
|
||||
}
|
||||
return s[:offset+1]
|
||||
}
|
||||
|
||||
func stripTrailingDigits(s string, digits int) string {
|
||||
if i := strings.Index(s, "."); i >= 0 {
|
||||
if digits <= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i++
|
||||
if i+digits >= len(s) {
|
||||
return s
|
||||
}
|
||||
return s[:i+digits]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Ftoa converts a float to a string with no trailing zeros.
|
||||
func Ftoa(num float64) string {
|
||||
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||
}
|
||||
|
||||
// FtoaWithDigits converts a float to a string but limits the resulting string
|
||||
// to the given number of decimal places, and no trailing zeros.
|
||||
func FtoaWithDigits(num float64, digits int) string {
|
||||
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
||||
}
|
||||
8
src/server/vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
8
src/server/vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||
|
||||
Durations can be turned into strings such as "3 days ago", numbers
|
||||
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||
"79 MiB" (whichever you prefer).
|
||||
*/
|
||||
package humanize
|
||||
192
src/server/vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
192
src/server/vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
package humanize
|
||||
|
||||
/*
|
||||
Slightly adapted from the source to fit go-humanize.
|
||||
|
||||
Author: https://github.com/gorhill
|
||||
Source: https://gist.github.com/gorhill/5285193
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
renderFloatPrecisionMultipliers = [...]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
renderFloatPrecisionRounders = [...]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
)
|
||||
|
||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||
// * thousands separator
|
||||
// * decimal separator
|
||||
// * decimal precision
|
||||
//
|
||||
// Usage: s := RenderFloat(format, n)
|
||||
// The format parameter tells how to render the number n.
|
||||
//
|
||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||
//
|
||||
// Examples of format strings, given n = 12345.6789:
|
||||
// "#,###.##" => "12,345.67"
|
||||
// "#,###." => "12,345"
|
||||
// "#,###" => "12345,678"
|
||||
// "#\u202F###,##" => "12 345,68"
|
||||
// "#.###,###### => 12.345,678900
|
||||
// "" (aka default format) => 12,345.67
|
||||
//
|
||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||
// There is also a version for integer number, FormatInteger(),
|
||||
// which is convenient for calls within template.
|
||||
func FormatFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < (0.0 - math.MaxFloat64) {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
format := []rune(format)
|
||||
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatIndx := []int{}
|
||||
for i, char := range format {
|
||||
if char != '#' && char != '0' {
|
||||
formatIndx = append(formatIndx, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatIndx) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatIndx[0] == 0 {
|
||||
if format[formatIndx[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatIndx) == 2 {
|
||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(format[formatIndx[0]])
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatIndx) == 1 {
|
||||
decimalStr = string(format[formatIndx[0]])
|
||||
precision = len(format) - formatIndx[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 {
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.FormatInt(int64(intf), 10)
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
// FormatInteger produces a formatted number as string.
|
||||
// See FormatFloat.
|
||||
func FormatInteger(format string, n int) string {
|
||||
return FormatFloat(format, float64(n))
|
||||
}
|
||||
25
src/server/vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
25
src/server/vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package humanize
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Ordinal gives you the input number in a rank/ordinal format.
|
||||
//
|
||||
// Ordinal(3) -> 3rd
|
||||
func Ordinal(x int) string {
|
||||
suffix := "th"
|
||||
switch x % 10 {
|
||||
case 1:
|
||||
if x%100 != 11 {
|
||||
suffix = "st"
|
||||
}
|
||||
case 2:
|
||||
if x%100 != 12 {
|
||||
suffix = "nd"
|
||||
}
|
||||
case 3:
|
||||
if x%100 != 13 {
|
||||
suffix = "rd"
|
||||
}
|
||||
}
|
||||
return strconv.Itoa(x) + suffix
|
||||
}
|
||||
127
src/server/vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
127
src/server/vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var siPrefixTable = map[float64]string{
|
||||
-30: "q", // quecto
|
||||
-27: "r", // ronto
|
||||
-24: "y", // yocto
|
||||
-21: "z", // zepto
|
||||
-18: "a", // atto
|
||||
-15: "f", // femto
|
||||
-12: "p", // pico
|
||||
-9: "n", // nano
|
||||
-6: "µ", // micro
|
||||
-3: "m", // milli
|
||||
0: "",
|
||||
3: "k", // kilo
|
||||
6: "M", // mega
|
||||
9: "G", // giga
|
||||
12: "T", // tera
|
||||
15: "P", // peta
|
||||
18: "E", // exa
|
||||
21: "Z", // zetta
|
||||
24: "Y", // yotta
|
||||
27: "R", // ronna
|
||||
30: "Q", // quetta
|
||||
}
|
||||
|
||||
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||
|
||||
// revfmap reverses the map and precomputes the power multiplier
|
||||
func revfmap(in map[float64]string) map[string]float64 {
|
||||
rv := map[string]float64{}
|
||||
for k, v := range in {
|
||||
rv[v] = math.Pow(10, k)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var riParseRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ri := `^([\-0-9.]+)\s?([`
|
||||
for _, v := range siPrefixTable {
|
||||
ri += v
|
||||
}
|
||||
ri += `]?)(.*)`
|
||||
|
||||
riParseRegex = regexp.MustCompile(ri)
|
||||
}
|
||||
|
||||
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||
// and returns the prefix along with the value adjusted to be within
|
||||
// that prefix.
|
||||
//
|
||||
// See also: SI, ParseSI.
|
||||
//
|
||||
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||
func ComputeSI(input float64) (float64, string) {
|
||||
if input == 0 {
|
||||
return 0, ""
|
||||
}
|
||||
mag := math.Abs(input)
|
||||
exponent := math.Floor(logn(mag, 10))
|
||||
exponent = math.Floor(exponent/3) * 3
|
||||
|
||||
value := mag / math.Pow(10, exponent)
|
||||
|
||||
// Handle special case where value is exactly 1000.0
|
||||
// Should return 1 M instead of 1000 k
|
||||
if value == 1000.0 {
|
||||
exponent += 3
|
||||
value = mag / math.Pow(10, exponent)
|
||||
}
|
||||
|
||||
value = math.Copysign(value, input)
|
||||
|
||||
prefix := siPrefixTable[exponent]
|
||||
return value, prefix
|
||||
}
|
||||
|
||||
// SI returns a string with default formatting.
|
||||
//
|
||||
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||
//
|
||||
// See also: ComputeSI, ParseSI.
|
||||
//
|
||||
// e.g. SI(1000000, "B") -> 1 MB
|
||||
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||
func SI(input float64, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return Ftoa(value) + " " + prefix + unit
|
||||
}
|
||||
|
||||
// SIWithDigits works like SI but limits the resulting string to the
|
||||
// given number of decimal places.
|
||||
//
|
||||
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
||||
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
||||
func SIWithDigits(input float64, decimals int, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
||||
}
|
||||
|
||||
var errInvalid = errors.New("invalid input")
|
||||
|
||||
// ParseSI parses an SI string back into the number and unit.
|
||||
//
|
||||
// See also: SI, ComputeSI.
|
||||
//
|
||||
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||
func ParseSI(input string) (float64, string, error) {
|
||||
found := riParseRegex.FindStringSubmatch(input)
|
||||
if len(found) != 4 {
|
||||
return 0, "", errInvalid
|
||||
}
|
||||
mag := revSIPrefixTable[found[2]]
|
||||
unit := found[3]
|
||||
|
||||
base, err := strconv.ParseFloat(found[1], 64)
|
||||
return base * mag, unit, err
|
||||
}
|
||||
117
src/server/vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
117
src/server/vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Seconds-based time units
|
||||
const (
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
LongTime = 37 * Year
|
||||
)
|
||||
|
||||
// Time formats a time into a relative string.
|
||||
//
|
||||
// Time(someT) -> "3 weeks ago"
|
||||
func Time(then time.Time) string {
|
||||
return RelTime(then, time.Now(), "ago", "from now")
|
||||
}
|
||||
|
||||
// A RelTimeMagnitude struct contains a relative time point at which
|
||||
// the relative format of time will switch to a new format string. A
|
||||
// slice of these in ascending order by their "D" field is passed to
|
||||
// CustomRelTime to format durations.
|
||||
//
|
||||
// The Format field is a string that may contain a "%s" which will be
|
||||
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||
// now") and a "%d" that will be replaced by the quantity.
|
||||
//
|
||||
// The DivBy field is the amount of time the time difference must be
|
||||
// divided by in order to display correctly.
|
||||
//
|
||||
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||
// DivBy should be time.Minute so whatever the duration is will be
|
||||
// expressed in minutes.
|
||||
type RelTimeMagnitude struct {
|
||||
D time.Duration
|
||||
Format string
|
||||
DivBy time.Duration
|
||||
}
|
||||
|
||||
var defaultMagnitudes = []RelTimeMagnitude{
|
||||
{time.Second, "now", time.Second},
|
||||
{2 * time.Second, "1 second %s", 1},
|
||||
{time.Minute, "%d seconds %s", time.Second},
|
||||
{2 * time.Minute, "1 minute %s", 1},
|
||||
{time.Hour, "%d minutes %s", time.Minute},
|
||||
{2 * time.Hour, "1 hour %s", 1},
|
||||
{Day, "%d hours %s", time.Hour},
|
||||
{2 * Day, "1 day %s", 1},
|
||||
{Week, "%d days %s", Day},
|
||||
{2 * Week, "1 week %s", 1},
|
||||
{Month, "%d weeks %s", Week},
|
||||
{2 * Month, "1 month %s", 1},
|
||||
{Year, "%d months %s", Month},
|
||||
{18 * Month, "1 year %s", 1},
|
||||
{2 * Year, "2 years %s", 1},
|
||||
{LongTime, "%d years %s", Year},
|
||||
{math.MaxInt64, "a long while %s", 1},
|
||||
}
|
||||
|
||||
// RelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times and two labels. In addition to the generic time
|
||||
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||
// the label corresponding to the smaller time is applied.
|
||||
//
|
||||
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||
}
|
||||
|
||||
// CustomRelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times two labels and a table of relative time formats.
|
||||
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||
// labels are used applied so that the label corresponding to the
|
||||
// smaller time is applied.
|
||||
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||
lbl := albl
|
||||
diff := b.Sub(a)
|
||||
|
||||
if a.After(b) {
|
||||
lbl = blbl
|
||||
diff = a.Sub(b)
|
||||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
return magnitudes[i].D > diff
|
||||
})
|
||||
|
||||
if n >= len(magnitudes) {
|
||||
n = len(magnitudes) - 1
|
||||
}
|
||||
mag := magnitudes[n]
|
||||
args := []interface{}{}
|
||||
escaped := false
|
||||
for _, ch := range mag.Format {
|
||||
if escaped {
|
||||
switch ch {
|
||||
case 's':
|
||||
args = append(args, lbl)
|
||||
case 'd':
|
||||
args = append(args, diff/mag.DivBy)
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = ch == '%'
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(mag.Format, args...)
|
||||
}
|
||||
20
src/server/vendor/github.com/golang/freetype/AUTHORS
generated
vendored
Normal file
20
src/server/vendor/github.com/golang/freetype/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# This is the official list of Freetype-Go authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
#
|
||||
# Freetype-Go is derived from Freetype, which is written in C. The latter
|
||||
# is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Google Inc.
|
||||
Jeff R. Allen <jra@nella.org>
|
||||
Maksim Kochkin <maxxarts@gmail.com>
|
||||
Michael Fogleman <fogleman@gmail.com>
|
||||
Rémy Oudompheng <oudomphe@phare.normalesup.org>
|
||||
Roger Peppe <rogpeppe@gmail.com>
|
||||
Steven Edwards <steven@stephenwithav.com>
|
||||
38
src/server/vendor/github.com/golang/freetype/CONTRIBUTORS
generated
vendored
Normal file
38
src/server/vendor/github.com/golang/freetype/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# This is the official list of people who can contribute
|
||||
# (and typically have contributed) code to the Freetype-Go repository.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# The submission process automatically checks to make sure
|
||||
# that people submitting code are listed in this file (by email address).
|
||||
#
|
||||
# Names should be added to this file only after verifying that
|
||||
# the individual or the individual's organization has agreed to
|
||||
# the appropriate Contributor License Agreement, found here:
|
||||
#
|
||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
#
|
||||
# The agreement for individuals can be filled out on the web.
|
||||
#
|
||||
# When adding J Random Contributor's name to this file,
|
||||
# either J's name or J's organization's name should be
|
||||
# added to the AUTHORS file, depending on whether the
|
||||
# individual or corporate CLA was used.
|
||||
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Jeff R. Allen <jra@nella.org> <jeff.allen@gmail.com>
|
||||
Maksim Kochkin <maxxarts@gmail.com>
|
||||
Michael Fogleman <fogleman@gmail.com>
|
||||
Nigel Tao <nigeltao@golang.org>
|
||||
Rémy Oudompheng <oudomphe@phare.normalesup.org> <remyoudompheng@gmail.com>
|
||||
Rob Pike <r@golang.org>
|
||||
Roger Peppe <rogpeppe@gmail.com>
|
||||
Russ Cox <rsc@golang.org>
|
||||
Steven Edwards <steven@stephenwithav.com>
|
||||
12
src/server/vendor/github.com/golang/freetype/LICENSE
generated
vendored
Normal file
12
src/server/vendor/github.com/golang/freetype/LICENSE
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
Use of the Freetype-Go software is subject to your choice of exactly one of
|
||||
the following two licenses:
|
||||
* The FreeType License, which is similar to the original BSD license with
|
||||
an advertising clause, or
|
||||
* The GNU General Public License (GPL), version 2 or later.
|
||||
|
||||
The text of these licenses are available in the licenses/ftl.txt and the
|
||||
licenses/gpl.txt files respectively. They are also available at
|
||||
http://freetype.sourceforge.net/license.html
|
||||
|
||||
The Luxi fonts in the testdata directory are licensed separately. See the
|
||||
testdata/COPYING file for details.
|
||||
245
src/server/vendor/github.com/golang/freetype/raster/geom.go
generated
vendored
Normal file
245
src/server/vendor/github.com/golang/freetype/raster/geom.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package raster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// maxAbs returns the maximum of abs(a) and abs(b).
|
||||
func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 {
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// pNeg returns the vector -p, or equivalently p rotated by 180 degrees.
|
||||
func pNeg(p fixed.Point26_6) fixed.Point26_6 {
|
||||
return fixed.Point26_6{-p.X, -p.Y}
|
||||
}
|
||||
|
||||
// pDot returns the dot product p·q.
|
||||
func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
|
||||
px, py := int64(p.X), int64(p.Y)
|
||||
qx, qy := int64(q.X), int64(q.Y)
|
||||
return fixed.Int52_12(px*qx + py*qy)
|
||||
}
|
||||
|
||||
// pLen returns the length of the vector p.
|
||||
func pLen(p fixed.Point26_6) fixed.Int26_6 {
|
||||
// TODO(nigeltao): use fixed point math.
|
||||
x := float64(p.X)
|
||||
y := float64(p.Y)
|
||||
return fixed.Int26_6(math.Sqrt(x*x + y*y))
|
||||
}
|
||||
|
||||
// pNorm returns the vector p normalized to the given length, or zero if p is
|
||||
// degenerate.
|
||||
func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 {
|
||||
d := pLen(p)
|
||||
if d == 0 {
|
||||
return fixed.Point26_6{}
|
||||
}
|
||||
s, t := int64(length), int64(d)
|
||||
x := int64(p.X) * s / t
|
||||
y := int64(p.Y) * s / t
|
||||
return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)}
|
||||
}
|
||||
|
||||
// pRot45CW returns the vector p rotated clockwise by 45 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}.
|
||||
func pRot45CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||
px, py := int64(p.X), int64(p.Y)
|
||||
qx := (+px - py) * 181 / 256
|
||||
qy := (+px + py) * 181 / 256
|
||||
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||
}
|
||||
|
||||
// pRot90CW returns the vector p rotated clockwise by 90 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}.
|
||||
func pRot90CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
return fixed.Point26_6{-p.Y, p.X}
|
||||
}
|
||||
|
||||
// pRot135CW returns the vector p rotated clockwise by 135 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}.
|
||||
func pRot135CW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||
px, py := int64(p.X), int64(p.Y)
|
||||
qx := (-px - py) * 181 / 256
|
||||
qy := (+px - py) * 181 / 256
|
||||
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||
}
|
||||
|
||||
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}.
|
||||
func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||
px, py := int64(p.X), int64(p.Y)
|
||||
qx := (+px + py) * 181 / 256
|
||||
qy := (-px + py) * 181 / 256
|
||||
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||
}
|
||||
|
||||
// pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}.
|
||||
func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
return fixed.Point26_6{p.Y, -p.X}
|
||||
}
|
||||
|
||||
// pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees.
|
||||
//
|
||||
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}.
|
||||
func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 {
|
||||
// 181/256 is approximately 1/√2, or sin(π/4).
|
||||
px, py := int64(p.X), int64(p.Y)
|
||||
qx := (-px + py) * 181 / 256
|
||||
qy := (-px - py) * 181 / 256
|
||||
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
|
||||
}
|
||||
|
||||
// An Adder accumulates points on a curve.
|
||||
type Adder interface {
|
||||
// Start starts a new curve at the given point.
|
||||
Start(a fixed.Point26_6)
|
||||
// Add1 adds a linear segment to the current curve.
|
||||
Add1(b fixed.Point26_6)
|
||||
// Add2 adds a quadratic segment to the current curve.
|
||||
Add2(b, c fixed.Point26_6)
|
||||
// Add3 adds a cubic segment to the current curve.
|
||||
Add3(b, c, d fixed.Point26_6)
|
||||
}
|
||||
|
||||
// A Path is a sequence of curves, and a curve is a start point followed by a
|
||||
// sequence of linear, quadratic or cubic segments.
|
||||
type Path []fixed.Int26_6
|
||||
|
||||
// String returns a human-readable representation of a Path.
|
||||
func (p Path) String() string {
|
||||
s := ""
|
||||
for i := 0; i < len(p); {
|
||||
if i != 0 {
|
||||
s += " "
|
||||
}
|
||||
switch p[i] {
|
||||
case 0:
|
||||
s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
|
||||
i += 4
|
||||
case 1:
|
||||
s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
|
||||
i += 4
|
||||
case 2:
|
||||
s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5]))
|
||||
i += 6
|
||||
case 3:
|
||||
s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7]))
|
||||
i += 8
|
||||
default:
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Clear cancels any previous calls to p.Start or p.AddXxx.
|
||||
func (p *Path) Clear() {
|
||||
*p = (*p)[:0]
|
||||
}
|
||||
|
||||
// Start starts a new curve at the given point.
|
||||
func (p *Path) Start(a fixed.Point26_6) {
|
||||
*p = append(*p, 0, a.X, a.Y, 0)
|
||||
}
|
||||
|
||||
// Add1 adds a linear segment to the current curve.
|
||||
func (p *Path) Add1(b fixed.Point26_6) {
|
||||
*p = append(*p, 1, b.X, b.Y, 1)
|
||||
}
|
||||
|
||||
// Add2 adds a quadratic segment to the current curve.
|
||||
func (p *Path) Add2(b, c fixed.Point26_6) {
|
||||
*p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2)
|
||||
}
|
||||
|
||||
// Add3 adds a cubic segment to the current curve.
|
||||
func (p *Path) Add3(b, c, d fixed.Point26_6) {
|
||||
*p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3)
|
||||
}
|
||||
|
||||
// AddPath adds the Path q to p.
|
||||
func (p *Path) AddPath(q Path) {
|
||||
*p = append(*p, q...)
|
||||
}
|
||||
|
||||
// AddStroke adds a stroked Path.
|
||||
func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||
Stroke(p, q, width, cr, jr)
|
||||
}
|
||||
|
||||
// firstPoint returns the first point in a non-empty Path.
|
||||
func (p Path) firstPoint() fixed.Point26_6 {
|
||||
return fixed.Point26_6{p[1], p[2]}
|
||||
}
|
||||
|
||||
// lastPoint returns the last point in a non-empty Path.
|
||||
func (p Path) lastPoint() fixed.Point26_6 {
|
||||
return fixed.Point26_6{p[len(p)-3], p[len(p)-2]}
|
||||
}
|
||||
|
||||
// addPathReversed adds q reversed to p.
|
||||
// For example, if q consists of a linear segment from A to B followed by a
|
||||
// quadratic segment from B to C to D, then the values of q looks like:
|
||||
// index: 01234567890123
|
||||
// value: 0AA01BB12CCDD2
|
||||
// So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A).
|
||||
func addPathReversed(p Adder, q Path) {
|
||||
if len(q) == 0 {
|
||||
return
|
||||
}
|
||||
i := len(q) - 1
|
||||
for {
|
||||
switch q[i] {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
i -= 4
|
||||
p.Add1(
|
||||
fixed.Point26_6{q[i-2], q[i-1]},
|
||||
)
|
||||
case 2:
|
||||
i -= 6
|
||||
p.Add2(
|
||||
fixed.Point26_6{q[i+2], q[i+3]},
|
||||
fixed.Point26_6{q[i-2], q[i-1]},
|
||||
)
|
||||
case 3:
|
||||
i -= 8
|
||||
p.Add3(
|
||||
fixed.Point26_6{q[i+4], q[i+5]},
|
||||
fixed.Point26_6{q[i+2], q[i+3]},
|
||||
fixed.Point26_6{q[i-2], q[i-1]},
|
||||
)
|
||||
default:
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
}
|
||||
}
|
||||
287
src/server/vendor/github.com/golang/freetype/raster/paint.go
generated
vendored
Normal file
287
src/server/vendor/github.com/golang/freetype/raster/paint.go
generated
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package raster
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
)
|
||||
|
||||
// A Span is a horizontal segment of pixels with constant alpha. X0 is an
|
||||
// inclusive bound and X1 is exclusive, the same as for slices. A fully opaque
|
||||
// Span has Alpha == 0xffff.
|
||||
type Span struct {
|
||||
Y, X0, X1 int
|
||||
Alpha uint32
|
||||
}
|
||||
|
||||
// A Painter knows how to paint a batch of Spans. Rasterization may involve
|
||||
// Painting multiple batches, and done will be true for the final batch. The
|
||||
// Spans' Y values are monotonically increasing during a rasterization. Paint
|
||||
// may use all of ss as scratch space during the call.
|
||||
type Painter interface {
|
||||
Paint(ss []Span, done bool)
|
||||
}
|
||||
|
||||
// The PainterFunc type adapts an ordinary function to the Painter interface.
|
||||
type PainterFunc func(ss []Span, done bool)
|
||||
|
||||
// Paint just delegates the call to f.
|
||||
func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) }
|
||||
|
||||
// An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using
|
||||
// the Over Porter-Duff composition operator.
|
||||
type AlphaOverPainter struct {
|
||||
Image *image.Alpha
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface.
|
||||
func (r AlphaOverPainter) Paint(ss []Span, done bool) {
|
||||
b := r.Image.Bounds()
|
||||
for _, s := range ss {
|
||||
if s.Y < b.Min.Y {
|
||||
continue
|
||||
}
|
||||
if s.Y >= b.Max.Y {
|
||||
return
|
||||
}
|
||||
if s.X0 < b.Min.X {
|
||||
s.X0 = b.Min.X
|
||||
}
|
||||
if s.X1 > b.Max.X {
|
||||
s.X1 = b.Max.X
|
||||
}
|
||||
if s.X0 >= s.X1 {
|
||||
continue
|
||||
}
|
||||
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
|
||||
p := r.Image.Pix[base+s.X0 : base+s.X1]
|
||||
a := int(s.Alpha >> 8)
|
||||
for i, c := range p {
|
||||
v := int(c)
|
||||
p[i] = uint8((v*255 + (255-v)*a) / 255)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewAlphaOverPainter creates a new AlphaOverPainter for the given image.
|
||||
func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter {
|
||||
return AlphaOverPainter{m}
|
||||
}
|
||||
|
||||
// An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using
|
||||
// the Src Porter-Duff composition operator.
|
||||
type AlphaSrcPainter struct {
|
||||
Image *image.Alpha
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface.
|
||||
func (r AlphaSrcPainter) Paint(ss []Span, done bool) {
|
||||
b := r.Image.Bounds()
|
||||
for _, s := range ss {
|
||||
if s.Y < b.Min.Y {
|
||||
continue
|
||||
}
|
||||
if s.Y >= b.Max.Y {
|
||||
return
|
||||
}
|
||||
if s.X0 < b.Min.X {
|
||||
s.X0 = b.Min.X
|
||||
}
|
||||
if s.X1 > b.Max.X {
|
||||
s.X1 = b.Max.X
|
||||
}
|
||||
if s.X0 >= s.X1 {
|
||||
continue
|
||||
}
|
||||
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
|
||||
p := r.Image.Pix[base+s.X0 : base+s.X1]
|
||||
color := uint8(s.Alpha >> 8)
|
||||
for i := range p {
|
||||
p[i] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image.
|
||||
func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter {
|
||||
return AlphaSrcPainter{m}
|
||||
}
|
||||
|
||||
// An RGBAPainter is a Painter that paints Spans onto a *image.RGBA.
|
||||
type RGBAPainter struct {
|
||||
// Image is the image to compose onto.
|
||||
Image *image.RGBA
|
||||
// Op is the Porter-Duff composition operator.
|
||||
Op draw.Op
|
||||
// cr, cg, cb and ca are the 16-bit color to paint the spans.
|
||||
cr, cg, cb, ca uint32
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface.
|
||||
func (r *RGBAPainter) Paint(ss []Span, done bool) {
|
||||
b := r.Image.Bounds()
|
||||
for _, s := range ss {
|
||||
if s.Y < b.Min.Y {
|
||||
continue
|
||||
}
|
||||
if s.Y >= b.Max.Y {
|
||||
return
|
||||
}
|
||||
if s.X0 < b.Min.X {
|
||||
s.X0 = b.Min.X
|
||||
}
|
||||
if s.X1 > b.Max.X {
|
||||
s.X1 = b.Max.X
|
||||
}
|
||||
if s.X0 >= s.X1 {
|
||||
continue
|
||||
}
|
||||
// This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go.
|
||||
ma := s.Alpha
|
||||
const m = 1<<16 - 1
|
||||
i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4
|
||||
i1 := i0 + (s.X1-s.X0)*4
|
||||
if r.Op == draw.Over {
|
||||
for i := i0; i < i1; i += 4 {
|
||||
dr := uint32(r.Image.Pix[i+0])
|
||||
dg := uint32(r.Image.Pix[i+1])
|
||||
db := uint32(r.Image.Pix[i+2])
|
||||
da := uint32(r.Image.Pix[i+3])
|
||||
a := (m - (r.ca * ma / m)) * 0x101
|
||||
r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8)
|
||||
r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8)
|
||||
r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8)
|
||||
r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8)
|
||||
}
|
||||
} else {
|
||||
for i := i0; i < i1; i += 4 {
|
||||
r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8)
|
||||
r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8)
|
||||
r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8)
|
||||
r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets the color to paint the spans.
|
||||
func (r *RGBAPainter) SetColor(c color.Color) {
|
||||
r.cr, r.cg, r.cb, r.ca = c.RGBA()
|
||||
}
|
||||
|
||||
// NewRGBAPainter creates a new RGBAPainter for the given image.
|
||||
func NewRGBAPainter(m *image.RGBA) *RGBAPainter {
|
||||
return &RGBAPainter{Image: m}
|
||||
}
|
||||
|
||||
// A MonochromePainter wraps another Painter, quantizing each Span's alpha to
|
||||
// be either fully opaque or fully transparent.
|
||||
type MonochromePainter struct {
|
||||
Painter Painter
|
||||
y, x0, x1 int
|
||||
}
|
||||
|
||||
// Paint delegates to the wrapped Painter after quantizing each Span's alpha
|
||||
// value and merging adjacent fully opaque Spans.
|
||||
func (m *MonochromePainter) Paint(ss []Span, done bool) {
|
||||
// We compact the ss slice, discarding any Spans whose alpha quantizes to zero.
|
||||
j := 0
|
||||
for _, s := range ss {
|
||||
if s.Alpha >= 0x8000 {
|
||||
if m.y == s.Y && m.x1 == s.X0 {
|
||||
m.x1 = s.X1
|
||||
} else {
|
||||
ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1}
|
||||
j++
|
||||
m.y, m.x0, m.x1 = s.Y, s.X0, s.X1
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
// Flush the accumulated Span.
|
||||
finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1}
|
||||
if j < len(ss) {
|
||||
ss[j] = finalSpan
|
||||
j++
|
||||
m.Painter.Paint(ss[:j], true)
|
||||
} else if j == len(ss) {
|
||||
m.Painter.Paint(ss, false)
|
||||
if cap(ss) > 0 {
|
||||
ss = ss[:1]
|
||||
} else {
|
||||
ss = make([]Span, 1)
|
||||
}
|
||||
ss[0] = finalSpan
|
||||
m.Painter.Paint(ss, true)
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
// Reset the accumulator, so that this Painter can be re-used.
|
||||
m.y, m.x0, m.x1 = 0, 0, 0
|
||||
} else {
|
||||
m.Painter.Paint(ss[:j], false)
|
||||
}
|
||||
}
|
||||
|
||||
// NewMonochromePainter creates a new MonochromePainter that wraps the given
|
||||
// Painter.
|
||||
func NewMonochromePainter(p Painter) *MonochromePainter {
|
||||
return &MonochromePainter{Painter: p}
|
||||
}
|
||||
|
||||
// A GammaCorrectionPainter wraps another Painter, performing gamma-correction
|
||||
// on each Span's alpha value.
|
||||
type GammaCorrectionPainter struct {
|
||||
// Painter is the wrapped Painter.
|
||||
Painter Painter
|
||||
// a is the precomputed alpha values for linear interpolation, with fully
|
||||
// opaque == 0xffff.
|
||||
a [256]uint16
|
||||
// gammaIsOne is whether gamma correction is a no-op.
|
||||
gammaIsOne bool
|
||||
}
|
||||
|
||||
// Paint delegates to the wrapped Painter after performing gamma-correction on
|
||||
// each Span.
|
||||
func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) {
|
||||
if !g.gammaIsOne {
|
||||
const n = 0x101
|
||||
for i, s := range ss {
|
||||
if s.Alpha == 0 || s.Alpha == 0xffff {
|
||||
continue
|
||||
}
|
||||
p, q := s.Alpha/n, s.Alpha%n
|
||||
// The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1].
|
||||
a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q
|
||||
ss[i].Alpha = (a + n/2) / n
|
||||
}
|
||||
}
|
||||
g.Painter.Paint(ss, done)
|
||||
}
|
||||
|
||||
// SetGamma sets the gamma value.
|
||||
func (g *GammaCorrectionPainter) SetGamma(gamma float64) {
|
||||
g.gammaIsOne = gamma == 1
|
||||
if g.gammaIsOne {
|
||||
return
|
||||
}
|
||||
for i := 0; i < 256; i++ {
|
||||
a := float64(i) / 0xff
|
||||
a = math.Pow(a, gamma)
|
||||
g.a[i] = uint16(0xffff * a)
|
||||
}
|
||||
}
|
||||
|
||||
// NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps
|
||||
// the given Painter.
|
||||
func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter {
|
||||
g := &GammaCorrectionPainter{Painter: p}
|
||||
g.SetGamma(gamma)
|
||||
return g
|
||||
}
|
||||
601
src/server/vendor/github.com/golang/freetype/raster/raster.go
generated
vendored
Normal file
601
src/server/vendor/github.com/golang/freetype/raster/raster.go
generated
vendored
Normal file
@ -0,0 +1,601 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
// Package raster provides an anti-aliasing 2-D rasterizer.
|
||||
//
|
||||
// It is part of the larger Freetype suite of font-related packages, but the
|
||||
// raster package is not specific to font rasterization, and can be used
|
||||
// standalone without any other Freetype package.
|
||||
//
|
||||
// Rasterization is done by the same area/coverage accumulation algorithm as
|
||||
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
|
||||
// description of the area/coverage algorithm is at
|
||||
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
|
||||
package raster // import "github.com/golang/freetype/raster"
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
|
||||
// area/coverage for the pixel at (xi, yi).
|
||||
type cell struct {
|
||||
xi int
|
||||
area, cover int
|
||||
next int
|
||||
}
|
||||
|
||||
type Rasterizer struct {
|
||||
// If false, the default behavior is to use the even-odd winding fill
|
||||
// rule during Rasterize.
|
||||
UseNonZeroWinding bool
|
||||
// An offset (in pixels) to the painted spans.
|
||||
Dx, Dy int
|
||||
|
||||
// The width of the Rasterizer. The height is implicit in len(cellIndex).
|
||||
width int
|
||||
// splitScaleN is the scaling factor used to determine how many times
|
||||
// to decompose a quadratic or cubic segment into a linear approximation.
|
||||
splitScale2, splitScale3 int
|
||||
|
||||
// The current pen position.
|
||||
a fixed.Point26_6
|
||||
// The current cell and its area/coverage being accumulated.
|
||||
xi, yi int
|
||||
area, cover int
|
||||
|
||||
// Saved cells.
|
||||
cell []cell
|
||||
// Linked list of cells, one per row.
|
||||
cellIndex []int
|
||||
// Buffers.
|
||||
cellBuf [256]cell
|
||||
cellIndexBuf [64]int
|
||||
spanBuf [64]Span
|
||||
}
|
||||
|
||||
// findCell returns the index in r.cell for the cell corresponding to
|
||||
// (r.xi, r.yi). The cell is created if necessary.
|
||||
func (r *Rasterizer) findCell() int {
|
||||
if r.yi < 0 || r.yi >= len(r.cellIndex) {
|
||||
return -1
|
||||
}
|
||||
xi := r.xi
|
||||
if xi < 0 {
|
||||
xi = -1
|
||||
} else if xi > r.width {
|
||||
xi = r.width
|
||||
}
|
||||
i, prev := r.cellIndex[r.yi], -1
|
||||
for i != -1 && r.cell[i].xi <= xi {
|
||||
if r.cell[i].xi == xi {
|
||||
return i
|
||||
}
|
||||
i, prev = r.cell[i].next, i
|
||||
}
|
||||
c := len(r.cell)
|
||||
if c == cap(r.cell) {
|
||||
buf := make([]cell, c, 4*c)
|
||||
copy(buf, r.cell)
|
||||
r.cell = buf[0 : c+1]
|
||||
} else {
|
||||
r.cell = r.cell[0 : c+1]
|
||||
}
|
||||
r.cell[c] = cell{xi, 0, 0, i}
|
||||
if prev == -1 {
|
||||
r.cellIndex[r.yi] = c
|
||||
} else {
|
||||
r.cell[prev].next = c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi).
|
||||
func (r *Rasterizer) saveCell() {
|
||||
if r.area != 0 || r.cover != 0 {
|
||||
i := r.findCell()
|
||||
if i != -1 {
|
||||
r.cell[i].area += r.area
|
||||
r.cell[i].cover += r.cover
|
||||
}
|
||||
r.area = 0
|
||||
r.cover = 0
|
||||
}
|
||||
}
|
||||
|
||||
// setCell sets the (xi, yi) cell that r is accumulating area/coverage for.
|
||||
func (r *Rasterizer) setCell(xi, yi int) {
|
||||
if r.xi != xi || r.yi != yi {
|
||||
r.saveCell()
|
||||
r.xi, r.yi = xi, yi
|
||||
}
|
||||
}
|
||||
|
||||
// scan accumulates area/coverage for the yi'th scanline, going from
|
||||
// x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates)
|
||||
// and from y0f to y1f fractional vertical units within that scanline.
|
||||
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) {
|
||||
// Break the 26.6 fixed point X co-ordinates into integral and fractional parts.
|
||||
x0i := int(x0) / 64
|
||||
x0f := x0 - fixed.Int26_6(64*x0i)
|
||||
x1i := int(x1) / 64
|
||||
x1f := x1 - fixed.Int26_6(64*x1i)
|
||||
|
||||
// A perfectly horizontal scan.
|
||||
if y0f == y1f {
|
||||
r.setCell(x1i, yi)
|
||||
return
|
||||
}
|
||||
dx, dy := x1-x0, y1f-y0f
|
||||
// A single cell scan.
|
||||
if x0i == x1i {
|
||||
r.area += int((x0f + x1f) * dy)
|
||||
r.cover += int(dy)
|
||||
return
|
||||
}
|
||||
// There are at least two cells. Apart from the first and last cells,
|
||||
// all intermediate cells go through the full width of the cell,
|
||||
// or 64 units in 26.6 fixed point format.
|
||||
var (
|
||||
p, q, edge0, edge1 fixed.Int26_6
|
||||
xiDelta int
|
||||
)
|
||||
if dx > 0 {
|
||||
p, q = (64-x0f)*dy, dx
|
||||
edge0, edge1, xiDelta = 0, 64, 1
|
||||
} else {
|
||||
p, q = x0f*dy, -dx
|
||||
edge0, edge1, xiDelta = 64, 0, -1
|
||||
}
|
||||
yDelta, yRem := p/q, p%q
|
||||
if yRem < 0 {
|
||||
yDelta -= 1
|
||||
yRem += q
|
||||
}
|
||||
// Do the first cell.
|
||||
xi, y := x0i, y0f
|
||||
r.area += int((x0f + edge1) * yDelta)
|
||||
r.cover += int(yDelta)
|
||||
xi, y = xi+xiDelta, y+yDelta
|
||||
r.setCell(xi, yi)
|
||||
if xi != x1i {
|
||||
// Do all the intermediate cells.
|
||||
p = 64 * (y1f - y + yDelta)
|
||||
fullDelta, fullRem := p/q, p%q
|
||||
if fullRem < 0 {
|
||||
fullDelta -= 1
|
||||
fullRem += q
|
||||
}
|
||||
yRem -= q
|
||||
for xi != x1i {
|
||||
yDelta = fullDelta
|
||||
yRem += fullRem
|
||||
if yRem >= 0 {
|
||||
yDelta += 1
|
||||
yRem -= q
|
||||
}
|
||||
r.area += int(64 * yDelta)
|
||||
r.cover += int(yDelta)
|
||||
xi, y = xi+xiDelta, y+yDelta
|
||||
r.setCell(xi, yi)
|
||||
}
|
||||
}
|
||||
// Do the last cell.
|
||||
yDelta = y1f - y
|
||||
r.area += int((edge0 + x1f) * yDelta)
|
||||
r.cover += int(yDelta)
|
||||
}
|
||||
|
||||
// Start starts a new curve at the given point.
|
||||
func (r *Rasterizer) Start(a fixed.Point26_6) {
|
||||
r.setCell(int(a.X/64), int(a.Y/64))
|
||||
r.a = a
|
||||
}
|
||||
|
||||
// Add1 adds a linear segment to the current curve.
|
||||
func (r *Rasterizer) Add1(b fixed.Point26_6) {
|
||||
x0, y0 := r.a.X, r.a.Y
|
||||
x1, y1 := b.X, b.Y
|
||||
dx, dy := x1-x0, y1-y0
|
||||
// Break the 26.6 fixed point Y co-ordinates into integral and fractional
|
||||
// parts.
|
||||
y0i := int(y0) / 64
|
||||
y0f := y0 - fixed.Int26_6(64*y0i)
|
||||
y1i := int(y1) / 64
|
||||
y1f := y1 - fixed.Int26_6(64*y1i)
|
||||
|
||||
if y0i == y1i {
|
||||
// There is only one scanline.
|
||||
r.scan(y0i, x0, y0f, x1, y1f)
|
||||
|
||||
} else if dx == 0 {
|
||||
// This is a vertical line segment. We avoid calling r.scan and instead
|
||||
// manipulate r.area and r.cover directly.
|
||||
var (
|
||||
edge0, edge1 fixed.Int26_6
|
||||
yiDelta int
|
||||
)
|
||||
if dy > 0 {
|
||||
edge0, edge1, yiDelta = 0, 64, 1
|
||||
} else {
|
||||
edge0, edge1, yiDelta = 64, 0, -1
|
||||
}
|
||||
x0i, yi := int(x0)/64, y0i
|
||||
x0fTimes2 := (int(x0) - (64 * x0i)) * 2
|
||||
// Do the first pixel.
|
||||
dcover := int(edge1 - y0f)
|
||||
darea := int(x0fTimes2 * dcover)
|
||||
r.area += darea
|
||||
r.cover += dcover
|
||||
yi += yiDelta
|
||||
r.setCell(x0i, yi)
|
||||
// Do all the intermediate pixels.
|
||||
dcover = int(edge1 - edge0)
|
||||
darea = int(x0fTimes2 * dcover)
|
||||
for yi != y1i {
|
||||
r.area += darea
|
||||
r.cover += dcover
|
||||
yi += yiDelta
|
||||
r.setCell(x0i, yi)
|
||||
}
|
||||
// Do the last pixel.
|
||||
dcover = int(y1f - edge0)
|
||||
darea = int(x0fTimes2 * dcover)
|
||||
r.area += darea
|
||||
r.cover += dcover
|
||||
|
||||
} else {
|
||||
// There are at least two scanlines. Apart from the first and last
|
||||
// scanlines, all intermediate scanlines go through the full height of
|
||||
// the row, or 64 units in 26.6 fixed point format.
|
||||
var (
|
||||
p, q, edge0, edge1 fixed.Int26_6
|
||||
yiDelta int
|
||||
)
|
||||
if dy > 0 {
|
||||
p, q = (64-y0f)*dx, dy
|
||||
edge0, edge1, yiDelta = 0, 64, 1
|
||||
} else {
|
||||
p, q = y0f*dx, -dy
|
||||
edge0, edge1, yiDelta = 64, 0, -1
|
||||
}
|
||||
xDelta, xRem := p/q, p%q
|
||||
if xRem < 0 {
|
||||
xDelta -= 1
|
||||
xRem += q
|
||||
}
|
||||
// Do the first scanline.
|
||||
x, yi := x0, y0i
|
||||
r.scan(yi, x, y0f, x+xDelta, edge1)
|
||||
x, yi = x+xDelta, yi+yiDelta
|
||||
r.setCell(int(x)/64, yi)
|
||||
if yi != y1i {
|
||||
// Do all the intermediate scanlines.
|
||||
p = 64 * dx
|
||||
fullDelta, fullRem := p/q, p%q
|
||||
if fullRem < 0 {
|
||||
fullDelta -= 1
|
||||
fullRem += q
|
||||
}
|
||||
xRem -= q
|
||||
for yi != y1i {
|
||||
xDelta = fullDelta
|
||||
xRem += fullRem
|
||||
if xRem >= 0 {
|
||||
xDelta += 1
|
||||
xRem -= q
|
||||
}
|
||||
r.scan(yi, x, edge0, x+xDelta, edge1)
|
||||
x, yi = x+xDelta, yi+yiDelta
|
||||
r.setCell(int(x)/64, yi)
|
||||
}
|
||||
}
|
||||
// Do the last scanline.
|
||||
r.scan(yi, x, edge0, x1, y1f)
|
||||
}
|
||||
// The next lineTo starts from b.
|
||||
r.a = b
|
||||
}
|
||||
|
||||
// Add2 adds a quadratic segment to the current curve.
|
||||
func (r *Rasterizer) Add2(b, c fixed.Point26_6) {
|
||||
// Calculate nSplit (the number of recursive decompositions) based on how
|
||||
// 'curvy' it is. Specifically, how much the middle point b deviates from
|
||||
// (a+c)/2.
|
||||
dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2)
|
||||
nsplit := 0
|
||||
for dev > 0 {
|
||||
dev /= 4
|
||||
nsplit++
|
||||
}
|
||||
// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit
|
||||
// is 16.
|
||||
const maxNsplit = 16
|
||||
if nsplit > maxNsplit {
|
||||
panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit))
|
||||
}
|
||||
// Recursively decompose the curve nSplit levels deep.
|
||||
var (
|
||||
pStack [2*maxNsplit + 3]fixed.Point26_6
|
||||
sStack [maxNsplit + 1]int
|
||||
i int
|
||||
)
|
||||
sStack[0] = nsplit
|
||||
pStack[0] = c
|
||||
pStack[1] = b
|
||||
pStack[2] = r.a
|
||||
for i >= 0 {
|
||||
s := sStack[i]
|
||||
p := pStack[2*i:]
|
||||
if s > 0 {
|
||||
// Split the quadratic curve p[:3] into an equivalent set of two
|
||||
// shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2],
|
||||
// and p[0] is unchanged.
|
||||
mx := p[1].X
|
||||
p[4].X = p[2].X
|
||||
p[3].X = (p[4].X + mx) / 2
|
||||
p[1].X = (p[0].X + mx) / 2
|
||||
p[2].X = (p[1].X + p[3].X) / 2
|
||||
my := p[1].Y
|
||||
p[4].Y = p[2].Y
|
||||
p[3].Y = (p[4].Y + my) / 2
|
||||
p[1].Y = (p[0].Y + my) / 2
|
||||
p[2].Y = (p[1].Y + p[3].Y) / 2
|
||||
// The two shorter curves have one less split to do.
|
||||
sStack[i] = s - 1
|
||||
sStack[i+1] = s - 1
|
||||
i++
|
||||
} else {
|
||||
// Replace the level-0 quadratic with a two-linear-piece
|
||||
// approximation.
|
||||
midx := (p[0].X + 2*p[1].X + p[2].X) / 4
|
||||
midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4
|
||||
r.Add1(fixed.Point26_6{midx, midy})
|
||||
r.Add1(p[0])
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add3 adds a cubic segment to the current curve.
|
||||
func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) {
|
||||
// Calculate nSplit (the number of recursive decompositions) based on how
|
||||
// 'curvy' it is.
|
||||
dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2)
|
||||
dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3)
|
||||
nsplit := 0
|
||||
for dev2 > 0 || dev3 > 0 {
|
||||
dev2 /= 8
|
||||
dev3 /= 4
|
||||
nsplit++
|
||||
}
|
||||
// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so
|
||||
// maxNsplit is 16.
|
||||
const maxNsplit = 16
|
||||
if nsplit > maxNsplit {
|
||||
panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit))
|
||||
}
|
||||
// Recursively decompose the curve nSplit levels deep.
|
||||
var (
|
||||
pStack [3*maxNsplit + 4]fixed.Point26_6
|
||||
sStack [maxNsplit + 1]int
|
||||
i int
|
||||
)
|
||||
sStack[0] = nsplit
|
||||
pStack[0] = d
|
||||
pStack[1] = c
|
||||
pStack[2] = b
|
||||
pStack[3] = r.a
|
||||
for i >= 0 {
|
||||
s := sStack[i]
|
||||
p := pStack[3*i:]
|
||||
if s > 0 {
|
||||
// Split the cubic curve p[:4] into an equivalent set of two
|
||||
// shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3],
|
||||
// and p[0] is unchanged.
|
||||
m01x := (p[0].X + p[1].X) / 2
|
||||
m12x := (p[1].X + p[2].X) / 2
|
||||
m23x := (p[2].X + p[3].X) / 2
|
||||
p[6].X = p[3].X
|
||||
p[5].X = m23x
|
||||
p[1].X = m01x
|
||||
p[2].X = (m01x + m12x) / 2
|
||||
p[4].X = (m12x + m23x) / 2
|
||||
p[3].X = (p[2].X + p[4].X) / 2
|
||||
m01y := (p[0].Y + p[1].Y) / 2
|
||||
m12y := (p[1].Y + p[2].Y) / 2
|
||||
m23y := (p[2].Y + p[3].Y) / 2
|
||||
p[6].Y = p[3].Y
|
||||
p[5].Y = m23y
|
||||
p[1].Y = m01y
|
||||
p[2].Y = (m01y + m12y) / 2
|
||||
p[4].Y = (m12y + m23y) / 2
|
||||
p[3].Y = (p[2].Y + p[4].Y) / 2
|
||||
// The two shorter curves have one less split to do.
|
||||
sStack[i] = s - 1
|
||||
sStack[i+1] = s - 1
|
||||
i++
|
||||
} else {
|
||||
// Replace the level-0 cubic with a two-linear-piece approximation.
|
||||
midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8
|
||||
midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8
|
||||
r.Add1(fixed.Point26_6{midx, midy})
|
||||
r.Add1(p[0])
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath adds the given Path.
|
||||
func (r *Rasterizer) AddPath(p Path) {
|
||||
for i := 0; i < len(p); {
|
||||
switch p[i] {
|
||||
case 0:
|
||||
r.Start(
|
||||
fixed.Point26_6{p[i+1], p[i+2]},
|
||||
)
|
||||
i += 4
|
||||
case 1:
|
||||
r.Add1(
|
||||
fixed.Point26_6{p[i+1], p[i+2]},
|
||||
)
|
||||
i += 4
|
||||
case 2:
|
||||
r.Add2(
|
||||
fixed.Point26_6{p[i+1], p[i+2]},
|
||||
fixed.Point26_6{p[i+3], p[i+4]},
|
||||
)
|
||||
i += 6
|
||||
case 3:
|
||||
r.Add3(
|
||||
fixed.Point26_6{p[i+1], p[i+2]},
|
||||
fixed.Point26_6{p[i+3], p[i+4]},
|
||||
fixed.Point26_6{p[i+5], p[i+6]},
|
||||
)
|
||||
i += 8
|
||||
default:
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddStroke adds a stroked Path.
|
||||
func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||
Stroke(r, q, width, cr, jr)
|
||||
}
|
||||
|
||||
// areaToAlpha converts an area value to a uint32 alpha value. A completely
|
||||
// filled pixel corresponds to an area of 64*64*2, and an alpha of 0xffff. The
|
||||
// conversion of area values greater than this depends on the winding rule:
|
||||
// even-odd or non-zero.
|
||||
func (r *Rasterizer) areaToAlpha(area int) uint32 {
|
||||
// The C Freetype implementation (version 2.3.12) does "alpha := area>>1"
|
||||
// without the +1. Round-to-nearest gives a more symmetric result than
|
||||
// round-down. The C implementation also returns 8-bit alpha, not 16-bit
|
||||
// alpha.
|
||||
a := (area + 1) >> 1
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
alpha := uint32(a)
|
||||
if r.UseNonZeroWinding {
|
||||
if alpha > 0x0fff {
|
||||
alpha = 0x0fff
|
||||
}
|
||||
} else {
|
||||
alpha &= 0x1fff
|
||||
if alpha > 0x1000 {
|
||||
alpha = 0x2000 - alpha
|
||||
} else if alpha == 0x1000 {
|
||||
alpha = 0x0fff
|
||||
}
|
||||
}
|
||||
// alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to
|
||||
// 16-bit alpha.
|
||||
return alpha<<4 | alpha>>8
|
||||
}
|
||||
|
||||
// Rasterize converts r's accumulated curves into Spans for p. The Spans passed
|
||||
// to p are non-overlapping, and sorted by Y and then X. They all have non-zero
|
||||
// width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final
|
||||
// Span, which has Y, X0, X1 and A all equal to zero.
|
||||
func (r *Rasterizer) Rasterize(p Painter) {
|
||||
r.saveCell()
|
||||
s := 0
|
||||
for yi := 0; yi < len(r.cellIndex); yi++ {
|
||||
xi, cover := 0, 0
|
||||
for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
|
||||
if cover != 0 && r.cell[c].xi > xi {
|
||||
alpha := r.areaToAlpha(cover * 64 * 2)
|
||||
if alpha != 0 {
|
||||
xi0, xi1 := xi, r.cell[c].xi
|
||||
if xi0 < 0 {
|
||||
xi0 = 0
|
||||
}
|
||||
if xi1 >= r.width {
|
||||
xi1 = r.width
|
||||
}
|
||||
if xi0 < xi1 {
|
||||
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
|
||||
s++
|
||||
}
|
||||
}
|
||||
}
|
||||
cover += r.cell[c].cover
|
||||
alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area)
|
||||
xi = r.cell[c].xi + 1
|
||||
if alpha != 0 {
|
||||
xi0, xi1 := r.cell[c].xi, xi
|
||||
if xi0 < 0 {
|
||||
xi0 = 0
|
||||
}
|
||||
if xi1 >= r.width {
|
||||
xi1 = r.width
|
||||
}
|
||||
if xi0 < xi1 {
|
||||
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
|
||||
s++
|
||||
}
|
||||
}
|
||||
if s > len(r.spanBuf)-2 {
|
||||
p.Paint(r.spanBuf[:s], false)
|
||||
s = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Paint(r.spanBuf[:s], true)
|
||||
}
|
||||
|
||||
// Clear cancels any previous calls to r.Start or r.AddXxx.
|
||||
func (r *Rasterizer) Clear() {
|
||||
r.a = fixed.Point26_6{}
|
||||
r.xi = 0
|
||||
r.yi = 0
|
||||
r.area = 0
|
||||
r.cover = 0
|
||||
r.cell = r.cell[:0]
|
||||
for i := 0; i < len(r.cellIndex); i++ {
|
||||
r.cellIndex[i] = -1
|
||||
}
|
||||
}
|
||||
|
||||
// SetBounds sets the maximum width and height of the rasterized image and
|
||||
// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
|
||||
func (r *Rasterizer) SetBounds(width, height int) {
|
||||
if width < 0 {
|
||||
width = 0
|
||||
}
|
||||
if height < 0 {
|
||||
height = 0
|
||||
}
|
||||
// Use the same ssN heuristic as the C Freetype (version 2.4.0)
|
||||
// implementation.
|
||||
ss2, ss3 := 32, 16
|
||||
if width > 24 || height > 24 {
|
||||
ss2, ss3 = 2*ss2, 2*ss3
|
||||
if width > 120 || height > 120 {
|
||||
ss2, ss3 = 2*ss2, 2*ss3
|
||||
}
|
||||
}
|
||||
r.width = width
|
||||
r.splitScale2 = ss2
|
||||
r.splitScale3 = ss3
|
||||
r.cell = r.cellBuf[:0]
|
||||
if height > len(r.cellIndexBuf) {
|
||||
r.cellIndex = make([]int, height)
|
||||
} else {
|
||||
r.cellIndex = r.cellIndexBuf[:height]
|
||||
}
|
||||
r.Clear()
|
||||
}
|
||||
|
||||
// NewRasterizer creates a new Rasterizer with the given bounds.
|
||||
func NewRasterizer(width, height int) *Rasterizer {
|
||||
r := new(Rasterizer)
|
||||
r.SetBounds(width, height)
|
||||
return r
|
||||
}
|
||||
483
src/server/vendor/github.com/golang/freetype/raster/stroke.go
generated
vendored
Normal file
483
src/server/vendor/github.com/golang/freetype/raster/stroke.go
generated
vendored
Normal file
@ -0,0 +1,483 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package raster
|
||||
|
||||
import (
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// Two points are considered practically equal if the square of the distance
|
||||
// between them is less than one quarter (i.e. 1024 / 4096).
|
||||
const epsilon = fixed.Int52_12(1024)
|
||||
|
||||
// A Capper signifies how to begin or end a stroked path.
|
||||
type Capper interface {
|
||||
// Cap adds a cap to p given a pivot point and the normal vector of a
|
||||
// terminal segment. The normal's length is half of the stroke width.
|
||||
Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6)
|
||||
}
|
||||
|
||||
// The CapperFunc type adapts an ordinary function to be a Capper.
|
||||
type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6)
|
||||
|
||||
func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||
f(p, halfWidth, pivot, n1)
|
||||
}
|
||||
|
||||
// A Joiner signifies how to join interior nodes of a stroked path.
|
||||
type Joiner interface {
|
||||
// Join adds a join to the two sides of a stroked path given a pivot
|
||||
// point and the normal vectors of the trailing and leading segments.
|
||||
// Both normals have length equal to half of the stroke width.
|
||||
Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
|
||||
}
|
||||
|
||||
// The JoinerFunc type adapts an ordinary function to be a Joiner.
|
||||
type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
|
||||
|
||||
func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||
f(lhs, rhs, halfWidth, pivot, n0, n1)
|
||||
}
|
||||
|
||||
// RoundCapper adds round caps to a stroked path.
|
||||
var RoundCapper Capper = CapperFunc(roundCapper)
|
||||
|
||||
func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||
// The cubic Bézier approximation to a circle involves the magic number
|
||||
// (√2 - 1) * 4/3, which is approximately 35/64.
|
||||
const k = 35
|
||||
e := pRot90CCW(n1)
|
||||
side := pivot.Add(e)
|
||||
start, end := pivot.Sub(n1), pivot.Add(n1)
|
||||
d, e := n1.Mul(k), e.Mul(k)
|
||||
p.Add3(start.Add(e), side.Sub(d), side)
|
||||
p.Add3(side.Add(d), end.Add(e), end)
|
||||
}
|
||||
|
||||
// ButtCapper adds butt caps to a stroked path.
|
||||
var ButtCapper Capper = CapperFunc(buttCapper)
|
||||
|
||||
func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||
p.Add1(pivot.Add(n1))
|
||||
}
|
||||
|
||||
// SquareCapper adds square caps to a stroked path.
|
||||
var SquareCapper Capper = CapperFunc(squareCapper)
|
||||
|
||||
func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
|
||||
e := pRot90CCW(n1)
|
||||
side := pivot.Add(e)
|
||||
p.Add1(side.Sub(n1))
|
||||
p.Add1(side.Add(n1))
|
||||
p.Add1(pivot.Add(n1))
|
||||
}
|
||||
|
||||
// RoundJoiner adds round joins to a stroked path.
|
||||
var RoundJoiner Joiner = JoinerFunc(roundJoiner)
|
||||
|
||||
func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||
dot := pDot(pRot90CW(n0), n1)
|
||||
if dot >= 0 {
|
||||
addArc(lhs, pivot, n0, n1)
|
||||
rhs.Add1(pivot.Sub(n1))
|
||||
} else {
|
||||
lhs.Add1(pivot.Add(n1))
|
||||
addArc(rhs, pivot, pNeg(n0), pNeg(n1))
|
||||
}
|
||||
}
|
||||
|
||||
// BevelJoiner adds bevel joins to a stroked path.
|
||||
var BevelJoiner Joiner = JoinerFunc(bevelJoiner)
|
||||
|
||||
func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
|
||||
lhs.Add1(pivot.Add(n1))
|
||||
rhs.Add1(pivot.Sub(n1))
|
||||
}
|
||||
|
||||
// addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of
|
||||
// the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The
|
||||
// two vectors n0 and n1 must be of equal length.
|
||||
func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) {
|
||||
// r2 is the square of the length of n0.
|
||||
r2 := pDot(n0, n0)
|
||||
if r2 < epsilon {
|
||||
// The arc radius is so small that we collapse to a straight line.
|
||||
p.Add1(pivot.Add(n1))
|
||||
return
|
||||
}
|
||||
// We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus
|
||||
// a final quadratic segment from s to n1. Each 45-degree segment has
|
||||
// control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled,
|
||||
// rotated and translated. tan(π/8) is approximately 27/64.
|
||||
const tpo8 = 27
|
||||
var s fixed.Point26_6
|
||||
// We determine which octant the angle between n0 and n1 is in via three
|
||||
// dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135
|
||||
// degrees.
|
||||
m0 := pRot45CW(n0)
|
||||
m1 := pRot90CW(n0)
|
||||
m2 := pRot90CW(m0)
|
||||
if pDot(m1, n1) >= 0 {
|
||||
if pDot(n0, n1) >= 0 {
|
||||
if pDot(m2, n1) <= 0 {
|
||||
// n1 is between 0 and 45 degrees clockwise of n0.
|
||||
s = n0
|
||||
} else {
|
||||
// n1 is between 45 and 90 degrees clockwise of n0.
|
||||
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
|
||||
s = m0
|
||||
}
|
||||
} else {
|
||||
pm1, n0t := pivot.Add(m1), n0.Mul(tpo8)
|
||||
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
|
||||
p.Add2(pm1.Add(n0t), pm1)
|
||||
if pDot(m0, n1) >= 0 {
|
||||
// n1 is between 90 and 135 degrees clockwise of n0.
|
||||
s = m1
|
||||
} else {
|
||||
// n1 is between 135 and 180 degrees clockwise of n0.
|
||||
p.Add2(pm1.Sub(n0t), pivot.Add(m2))
|
||||
s = m2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if pDot(n0, n1) >= 0 {
|
||||
if pDot(m0, n1) >= 0 {
|
||||
// n1 is between 0 and 45 degrees counter-clockwise of n0.
|
||||
s = n0
|
||||
} else {
|
||||
// n1 is between 45 and 90 degrees counter-clockwise of n0.
|
||||
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
|
||||
s = pNeg(m2)
|
||||
}
|
||||
} else {
|
||||
pm1, n0t := pivot.Sub(m1), n0.Mul(tpo8)
|
||||
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
|
||||
p.Add2(pm1.Add(n0t), pm1)
|
||||
if pDot(m2, n1) <= 0 {
|
||||
// n1 is between 90 and 135 degrees counter-clockwise of n0.
|
||||
s = pNeg(m1)
|
||||
} else {
|
||||
// n1 is between 135 and 180 degrees counter-clockwise of n0.
|
||||
p.Add2(pm1.Sub(n0t), pivot.Sub(m0))
|
||||
s = pNeg(m0)
|
||||
}
|
||||
}
|
||||
}
|
||||
// The final quadratic segment has two endpoints s and n1 and the middle
|
||||
// control point is a multiple of s.Add(n1), i.e. it is on the angle
|
||||
// bisector of those two points. The multiple ranges between 128/256 and
|
||||
// 150/256 as the angle between s and n1 ranges between 0 and 45 degrees.
|
||||
//
|
||||
// When the angle is 0 degrees (i.e. s and n1 are coincident) then
|
||||
// s.Add(n1) is twice s and so the middle control point of the degenerate
|
||||
// quadratic segment should be half s.Add(n1), and half = 128/256.
|
||||
//
|
||||
// When the angle is 45 degrees then 150/256 is the ratio of the lengths of
|
||||
// the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}.
|
||||
//
|
||||
// d is the normalized dot product between s and n1. Since the angle ranges
|
||||
// between 0 and 45 degrees then d ranges between 256/256 and 181/256.
|
||||
d := 256 * pDot(s, n1) / r2
|
||||
multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2
|
||||
p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1))
|
||||
}
|
||||
|
||||
// midpoint returns the midpoint of two Points.
|
||||
func midpoint(a, b fixed.Point26_6) fixed.Point26_6 {
|
||||
return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2}
|
||||
}
|
||||
|
||||
// angleGreaterThan45 returns whether the angle between two vectors is more
|
||||
// than 45 degrees.
|
||||
func angleGreaterThan45(v0, v1 fixed.Point26_6) bool {
|
||||
v := pRot45CCW(v0)
|
||||
return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0
|
||||
}
|
||||
|
||||
// interpolate returns the point (1-t)*a + t*b.
|
||||
func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 {
|
||||
s := 1<<12 - t
|
||||
x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X)
|
||||
y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y)
|
||||
return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)}
|
||||
}
|
||||
|
||||
// curviest2 returns the value of t for which the quadratic parametric curve
|
||||
// (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature.
|
||||
//
|
||||
// The curvature of the parametric curve f(t) = (x(t), y(t)) is
|
||||
// |x′y″-y′x″| / (x′²+y′²)^(3/2).
|
||||
//
|
||||
// Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e.
|
||||
// The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex),
|
||||
// which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t.
|
||||
//
|
||||
// Thus, curvature is extreme where the denominator is extreme, i.e. where
|
||||
// (x′²+y′²) is extreme. The first order condition is that
|
||||
// 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0.
|
||||
// Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey).
|
||||
func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 {
|
||||
dx := int64(b.X - a.X)
|
||||
dy := int64(b.Y - a.Y)
|
||||
ex := int64(c.X - 2*b.X + a.X)
|
||||
ey := int64(c.Y - 2*b.Y + a.Y)
|
||||
if ex == 0 && ey == 0 {
|
||||
return 2048
|
||||
}
|
||||
return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey))
|
||||
}
|
||||
|
||||
// A stroker holds state for stroking a path.
|
||||
type stroker struct {
|
||||
// p is the destination that records the stroked path.
|
||||
p Adder
|
||||
// u is the half-width of the stroke.
|
||||
u fixed.Int26_6
|
||||
// cr and jr specify how to end and connect path segments.
|
||||
cr Capper
|
||||
jr Joiner
|
||||
// r is the reverse path. Stroking a path involves constructing two
|
||||
// parallel paths 2*u apart. The first path is added immediately to p,
|
||||
// the second path is accumulated in r and eventually added in reverse.
|
||||
r Path
|
||||
// a is the most recent segment point. anorm is the segment normal of
|
||||
// length u at that point.
|
||||
a, anorm fixed.Point26_6
|
||||
}
|
||||
|
||||
// addNonCurvy2 adds a quadratic segment to the stroker, where the segment
|
||||
// defined by (k.a, b, c) achieves maximum curvature at either k.a or c.
|
||||
func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) {
|
||||
// We repeatedly divide the segment at its middle until it is straight
|
||||
// enough to approximate the stroke by just translating the control points.
|
||||
// ds and ps are stacks of depths and points. t is the top of the stack.
|
||||
const maxDepth = 5
|
||||
var (
|
||||
ds [maxDepth + 1]int
|
||||
ps [2*maxDepth + 3]fixed.Point26_6
|
||||
t int
|
||||
)
|
||||
// Initially the ps stack has one quadratic segment of depth zero.
|
||||
ds[0] = 0
|
||||
ps[2] = k.a
|
||||
ps[1] = b
|
||||
ps[0] = c
|
||||
anorm := k.anorm
|
||||
var cnorm fixed.Point26_6
|
||||
|
||||
for {
|
||||
depth := ds[t]
|
||||
a := ps[2*t+2]
|
||||
b := ps[2*t+1]
|
||||
c := ps[2*t+0]
|
||||
ab := b.Sub(a)
|
||||
bc := c.Sub(b)
|
||||
abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12)
|
||||
bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12)
|
||||
if abIsSmall && bcIsSmall {
|
||||
// Approximate the segment by a circular arc.
|
||||
cnorm = pRot90CCW(pNorm(bc, k.u))
|
||||
mac := midpoint(a, c)
|
||||
addArc(k.p, mac, anorm, cnorm)
|
||||
addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm))
|
||||
} else if depth < maxDepth && angleGreaterThan45(ab, bc) {
|
||||
// Divide the segment in two and push both halves on the stack.
|
||||
mab := midpoint(a, b)
|
||||
mbc := midpoint(b, c)
|
||||
t++
|
||||
ds[t+0] = depth + 1
|
||||
ds[t-1] = depth + 1
|
||||
ps[2*t+2] = a
|
||||
ps[2*t+1] = mab
|
||||
ps[2*t+0] = midpoint(mab, mbc)
|
||||
ps[2*t-1] = mbc
|
||||
continue
|
||||
} else {
|
||||
// Translate the control points.
|
||||
bnorm := pRot90CCW(pNorm(c.Sub(a), k.u))
|
||||
cnorm = pRot90CCW(pNorm(bc, k.u))
|
||||
k.p.Add2(b.Add(bnorm), c.Add(cnorm))
|
||||
k.r.Add2(b.Sub(bnorm), c.Sub(cnorm))
|
||||
}
|
||||
if t == 0 {
|
||||
k.a, k.anorm = c, cnorm
|
||||
return
|
||||
}
|
||||
t--
|
||||
anorm = cnorm
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Add1 adds a linear segment to the stroker.
|
||||
func (k *stroker) Add1(b fixed.Point26_6) {
|
||||
bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u))
|
||||
if len(k.r) == 0 {
|
||||
k.p.Start(k.a.Add(bnorm))
|
||||
k.r.Start(k.a.Sub(bnorm))
|
||||
} else {
|
||||
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, bnorm)
|
||||
}
|
||||
k.p.Add1(b.Add(bnorm))
|
||||
k.r.Add1(b.Sub(bnorm))
|
||||
k.a, k.anorm = b, bnorm
|
||||
}
|
||||
|
||||
// Add2 adds a quadratic segment to the stroker.
|
||||
func (k *stroker) Add2(b, c fixed.Point26_6) {
|
||||
ab := b.Sub(k.a)
|
||||
bc := c.Sub(b)
|
||||
abnorm := pRot90CCW(pNorm(ab, k.u))
|
||||
if len(k.r) == 0 {
|
||||
k.p.Start(k.a.Add(abnorm))
|
||||
k.r.Start(k.a.Sub(abnorm))
|
||||
} else {
|
||||
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm)
|
||||
}
|
||||
|
||||
// Approximate nearly-degenerate quadratics by linear segments.
|
||||
abIsSmall := pDot(ab, ab) < epsilon
|
||||
bcIsSmall := pDot(bc, bc) < epsilon
|
||||
if abIsSmall || bcIsSmall {
|
||||
acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u))
|
||||
k.p.Add1(c.Add(acnorm))
|
||||
k.r.Add1(c.Sub(acnorm))
|
||||
k.a, k.anorm = c, acnorm
|
||||
return
|
||||
}
|
||||
|
||||
// The quadratic segment (k.a, b, c) has a point of maximum curvature.
|
||||
// If this occurs at an end point, we process the segment as a whole.
|
||||
t := curviest2(k.a, b, c)
|
||||
if t <= 0 || 4096 <= t {
|
||||
k.addNonCurvy2(b, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we perform a de Casteljau decomposition at the point of
|
||||
// maximum curvature and process the two straighter parts.
|
||||
mab := interpolate(k.a, b, t)
|
||||
mbc := interpolate(b, c, t)
|
||||
mabc := interpolate(mab, mbc, t)
|
||||
|
||||
// If the vectors ab and bc are close to being in opposite directions,
|
||||
// then the decomposition can become unstable, so we approximate the
|
||||
// quadratic segment by two linear segments joined by an arc.
|
||||
bcnorm := pRot90CCW(pNorm(bc, k.u))
|
||||
if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 {
|
||||
pArc := pDot(abnorm, bc) < 0
|
||||
|
||||
k.p.Add1(mabc.Add(abnorm))
|
||||
if pArc {
|
||||
z := pRot90CW(abnorm)
|
||||
addArc(k.p, mabc, abnorm, z)
|
||||
addArc(k.p, mabc, z, bcnorm)
|
||||
}
|
||||
k.p.Add1(mabc.Add(bcnorm))
|
||||
k.p.Add1(c.Add(bcnorm))
|
||||
|
||||
k.r.Add1(mabc.Sub(abnorm))
|
||||
if !pArc {
|
||||
z := pRot90CW(abnorm)
|
||||
addArc(&k.r, mabc, pNeg(abnorm), z)
|
||||
addArc(&k.r, mabc, z, pNeg(bcnorm))
|
||||
}
|
||||
k.r.Add1(mabc.Sub(bcnorm))
|
||||
k.r.Add1(c.Sub(bcnorm))
|
||||
|
||||
k.a, k.anorm = c, bcnorm
|
||||
return
|
||||
}
|
||||
|
||||
// Process the decomposed parts.
|
||||
k.addNonCurvy2(mab, mabc)
|
||||
k.addNonCurvy2(mbc, c)
|
||||
}
|
||||
|
||||
// Add3 adds a cubic segment to the stroker.
|
||||
func (k *stroker) Add3(b, c, d fixed.Point26_6) {
|
||||
panic("freetype/raster: stroke unimplemented for cubic segments")
|
||||
}
|
||||
|
||||
// stroke adds the stroked Path q to p, where q consists of exactly one curve.
|
||||
func (k *stroker) stroke(q Path) {
|
||||
// Stroking is implemented by deriving two paths each k.u apart from q.
|
||||
// The left-hand-side path is added immediately to k.p; the right-hand-side
|
||||
// path is accumulated in k.r. Once we've finished adding the LHS to k.p,
|
||||
// we add the RHS in reverse order.
|
||||
k.r = make(Path, 0, len(q))
|
||||
k.a = fixed.Point26_6{q[1], q[2]}
|
||||
for i := 4; i < len(q); {
|
||||
switch q[i] {
|
||||
case 1:
|
||||
k.Add1(
|
||||
fixed.Point26_6{q[i+1], q[i+2]},
|
||||
)
|
||||
i += 4
|
||||
case 2:
|
||||
k.Add2(
|
||||
fixed.Point26_6{q[i+1], q[i+2]},
|
||||
fixed.Point26_6{q[i+3], q[i+4]},
|
||||
)
|
||||
i += 6
|
||||
case 3:
|
||||
k.Add3(
|
||||
fixed.Point26_6{q[i+1], q[i+2]},
|
||||
fixed.Point26_6{q[i+3], q[i+4]},
|
||||
fixed.Point26_6{q[i+5], q[i+6]},
|
||||
)
|
||||
i += 8
|
||||
default:
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
}
|
||||
if len(k.r) == 0 {
|
||||
return
|
||||
}
|
||||
// TODO(nigeltao): if q is a closed curve then we should join the first and
|
||||
// last segments instead of capping them.
|
||||
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))
|
||||
addPathReversed(k.p, k.r)
|
||||
pivot := q.firstPoint()
|
||||
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
|
||||
}
|
||||
|
||||
// Stroke adds q stroked with the given width to p. The result is typically
|
||||
// self-intersecting and should be rasterized with UseNonZeroWinding.
|
||||
// cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner.
|
||||
func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
|
||||
if len(q) == 0 {
|
||||
return
|
||||
}
|
||||
if cr == nil {
|
||||
cr = RoundCapper
|
||||
}
|
||||
if jr == nil {
|
||||
jr = RoundJoiner
|
||||
}
|
||||
if q[0] != 0 {
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
s := stroker{p: p, u: width / 2, cr: cr, jr: jr}
|
||||
i := 0
|
||||
for j := 4; j < len(q); {
|
||||
switch q[j] {
|
||||
case 0:
|
||||
s.stroke(q[i:j])
|
||||
i, j = j, j+4
|
||||
case 1:
|
||||
j += 4
|
||||
case 2:
|
||||
j += 6
|
||||
case 3:
|
||||
j += 8
|
||||
default:
|
||||
panic("freetype/raster: bad path")
|
||||
}
|
||||
}
|
||||
s.stroke(q[i:])
|
||||
}
|
||||
507
src/server/vendor/github.com/golang/freetype/truetype/face.go
generated
vendored
Normal file
507
src/server/vendor/github.com/golang/freetype/truetype/face.go
generated
vendored
Normal file
@ -0,0 +1,507 @@
|
||||
// Copyright 2015 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package truetype
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func powerOf2(i int) bool {
|
||||
return i != 0 && (i&(i-1)) == 0
|
||||
}
|
||||
|
||||
// Options are optional arguments to NewFace.
|
||||
type Options struct {
|
||||
// Size is the font size in points, as in "a 10 point font size".
|
||||
//
|
||||
// A zero value means to use a 12 point font size.
|
||||
Size float64
|
||||
|
||||
// DPI is the dots-per-inch resolution.
|
||||
//
|
||||
// A zero value means to use 72 DPI.
|
||||
DPI float64
|
||||
|
||||
// Hinting is how to quantize the glyph nodes.
|
||||
//
|
||||
// A zero value means to use no hinting.
|
||||
Hinting font.Hinting
|
||||
|
||||
// GlyphCacheEntries is the number of entries in the glyph mask image
|
||||
// cache.
|
||||
//
|
||||
// If non-zero, it must be a power of 2.
|
||||
//
|
||||
// A zero value means to use 512 entries.
|
||||
GlyphCacheEntries int
|
||||
|
||||
// SubPixelsX is the number of sub-pixel locations a glyph's dot is
|
||||
// quantized to, in the horizontal direction. For example, a value of 8
|
||||
// means that the dot is quantized to 1/8th of a pixel. This quantization
|
||||
// only affects the glyph mask image, not its bounding box or advance
|
||||
// width. A higher value gives a more faithful glyph image, but reduces the
|
||||
// effectiveness of the glyph cache.
|
||||
//
|
||||
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
|
||||
//
|
||||
// A zero value means to use 4 sub-pixel locations.
|
||||
SubPixelsX int
|
||||
|
||||
// SubPixelsY is the number of sub-pixel locations a glyph's dot is
|
||||
// quantized to, in the vertical direction. For example, a value of 8
|
||||
// means that the dot is quantized to 1/8th of a pixel. This quantization
|
||||
// only affects the glyph mask image, not its bounding box or advance
|
||||
// width. A higher value gives a more faithful glyph image, but reduces the
|
||||
// effectiveness of the glyph cache.
|
||||
//
|
||||
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
|
||||
//
|
||||
// A zero value means to use 1 sub-pixel location.
|
||||
SubPixelsY int
|
||||
}
|
||||
|
||||
func (o *Options) size() float64 {
|
||||
if o != nil && o.Size > 0 {
|
||||
return o.Size
|
||||
}
|
||||
return 12
|
||||
}
|
||||
|
||||
func (o *Options) dpi() float64 {
|
||||
if o != nil && o.DPI > 0 {
|
||||
return o.DPI
|
||||
}
|
||||
return 72
|
||||
}
|
||||
|
||||
func (o *Options) hinting() font.Hinting {
|
||||
if o != nil {
|
||||
switch o.Hinting {
|
||||
case font.HintingVertical, font.HintingFull:
|
||||
// TODO: support vertical hinting.
|
||||
return font.HintingFull
|
||||
}
|
||||
}
|
||||
return font.HintingNone
|
||||
}
|
||||
|
||||
func (o *Options) glyphCacheEntries() int {
|
||||
if o != nil && powerOf2(o.GlyphCacheEntries) {
|
||||
return o.GlyphCacheEntries
|
||||
}
|
||||
// 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel
|
||||
// locations in the X and Y direction.
|
||||
return 512
|
||||
}
|
||||
|
||||
func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) {
|
||||
if o != nil {
|
||||
switch o.SubPixelsX {
|
||||
case 1, 2, 4, 8, 16, 32, 64:
|
||||
return subPixels(o.SubPixelsX)
|
||||
}
|
||||
}
|
||||
// This default value of 4 isn't based on anything scientific, merely as
|
||||
// small a number as possible that looks almost as good as no quantization,
|
||||
// or returning subPixels(64).
|
||||
return subPixels(4)
|
||||
}
|
||||
|
||||
func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) {
|
||||
if o != nil {
|
||||
switch o.SubPixelsX {
|
||||
case 1, 2, 4, 8, 16, 32, 64:
|
||||
return subPixels(o.SubPixelsX)
|
||||
}
|
||||
}
|
||||
// This default value of 1 isn't based on anything scientific, merely that
|
||||
// vertical sub-pixel glyph rendering is pretty rare. Baseline locations
|
||||
// can usually afford to snap to the pixel grid, so the vertical direction
|
||||
// doesn't have the deal with the horizontal's fractional advance widths.
|
||||
return subPixels(1)
|
||||
}
|
||||
|
||||
// subPixels returns q and the bias and mask that leads to q quantized
|
||||
// sub-pixel locations per full pixel.
|
||||
//
|
||||
// For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16,
|
||||
// because we want to round fractions of fixed.Int26_6 as:
|
||||
// - 0 to 7 rounds to 0.
|
||||
// - 8 to 23 rounds to 16.
|
||||
// - 24 to 39 rounds to 32.
|
||||
// - 40 to 55 rounds to 48.
|
||||
// - 56 to 63 rounds to 64.
|
||||
// which means to add 8 and then bitwise-and with -16, in two's complement
|
||||
// representation.
|
||||
//
|
||||
// When q == 1, we want bias == 32 and mask == -64.
|
||||
// When q == 2, we want bias == 16 and mask == -32.
|
||||
// When q == 4, we want bias == 8 and mask == -16.
|
||||
// ...
|
||||
// When q == 64, we want bias == 0 and mask == -1. (The no-op case).
|
||||
// The pattern is clear.
|
||||
func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) {
|
||||
return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q)
|
||||
}
|
||||
|
||||
// glyphCacheEntry caches the arguments and return values of rasterize.
|
||||
type glyphCacheEntry struct {
|
||||
key glyphCacheKey
|
||||
val glyphCacheVal
|
||||
}
|
||||
|
||||
type glyphCacheKey struct {
|
||||
index Index
|
||||
fx, fy uint8
|
||||
}
|
||||
|
||||
type glyphCacheVal struct {
|
||||
advanceWidth fixed.Int26_6
|
||||
offset image.Point
|
||||
gw int
|
||||
gh int
|
||||
}
|
||||
|
||||
type indexCacheEntry struct {
|
||||
rune rune
|
||||
index Index
|
||||
}
|
||||
|
||||
// NewFace returns a new font.Face for the given Font.
|
||||
func NewFace(f *Font, opts *Options) font.Face {
|
||||
a := &face{
|
||||
f: f,
|
||||
hinting: opts.hinting(),
|
||||
scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),
|
||||
glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()),
|
||||
}
|
||||
a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX()
|
||||
a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY()
|
||||
|
||||
// Fill the cache with invalid entries. Valid glyph cache entries have fx
|
||||
// and fy in the range [0, 64). Valid index cache entries have rune >= 0.
|
||||
for i := range a.glyphCache {
|
||||
a.glyphCache[i].key.fy = 0xff
|
||||
}
|
||||
for i := range a.indexCache {
|
||||
a.indexCache[i].rune = -1
|
||||
}
|
||||
|
||||
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
|
||||
b := f.Bounds(a.scale)
|
||||
xmin := +int(b.Min.X) >> 6
|
||||
ymin := -int(b.Max.Y) >> 6
|
||||
xmax := +int(b.Max.X+63) >> 6
|
||||
ymax := -int(b.Min.Y-63) >> 6
|
||||
a.maxw = xmax - xmin
|
||||
a.maxh = ymax - ymin
|
||||
a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache)))
|
||||
a.r.SetBounds(a.maxw, a.maxh)
|
||||
a.p = facePainter{a}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
type face struct {
|
||||
f *Font
|
||||
hinting font.Hinting
|
||||
scale fixed.Int26_6
|
||||
subPixelX uint32
|
||||
subPixelBiasX fixed.Int26_6
|
||||
subPixelMaskX fixed.Int26_6
|
||||
subPixelY uint32
|
||||
subPixelBiasY fixed.Int26_6
|
||||
subPixelMaskY fixed.Int26_6
|
||||
masks *image.Alpha
|
||||
glyphCache []glyphCacheEntry
|
||||
r raster.Rasterizer
|
||||
p raster.Painter
|
||||
paintOffset int
|
||||
maxw int
|
||||
maxh int
|
||||
glyphBuf GlyphBuf
|
||||
indexCache [indexCacheLen]indexCacheEntry
|
||||
|
||||
// TODO: clip rectangle?
|
||||
}
|
||||
|
||||
const indexCacheLen = 256
|
||||
|
||||
func (a *face) index(r rune) Index {
|
||||
const mask = indexCacheLen - 1
|
||||
c := &a.indexCache[r&mask]
|
||||
if c.rune == r {
|
||||
return c.index
|
||||
}
|
||||
i := a.f.Index(r)
|
||||
c.rune = r
|
||||
c.index = i
|
||||
return i
|
||||
}
|
||||
|
||||
// Close satisfies the font.Face interface.
|
||||
func (a *face) Close() error { return nil }
|
||||
|
||||
// Metrics satisfies the font.Face interface.
|
||||
func (a *face) Metrics() font.Metrics {
|
||||
scale := float64(a.scale)
|
||||
fupe := float64(a.f.FUnitsPerEm())
|
||||
return font.Metrics{
|
||||
Height: a.scale,
|
||||
Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)),
|
||||
Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)),
|
||||
}
|
||||
}
|
||||
|
||||
// Kern satisfies the font.Face interface.
|
||||
func (a *face) Kern(r0, r1 rune) fixed.Int26_6 {
|
||||
i0 := a.index(r0)
|
||||
i1 := a.index(r1)
|
||||
kern := a.f.Kern(a.scale, i0, i1)
|
||||
if a.hinting != font.HintingNone {
|
||||
kern = (kern + 32) &^ 63
|
||||
}
|
||||
return kern
|
||||
}
|
||||
|
||||
// Glyph satisfies the font.Face interface.
|
||||
func (a *face) Glyph(dot fixed.Point26_6, r rune) (
|
||||
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||
|
||||
// Quantize to the sub-pixel granularity.
|
||||
dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX
|
||||
dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY
|
||||
|
||||
// Split the coordinates into their integer and fractional parts.
|
||||
ix, fx := int(dotX>>6), dotX&0x3f
|
||||
iy, fy := int(dotY>>6), dotY&0x3f
|
||||
|
||||
index := a.index(r)
|
||||
cIndex := uint32(index)
|
||||
cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX)
|
||||
cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY)
|
||||
cIndex &= uint32(len(a.glyphCache) - 1)
|
||||
a.paintOffset = a.maxh * int(cIndex)
|
||||
k := glyphCacheKey{
|
||||
index: index,
|
||||
fx: uint8(fx),
|
||||
fy: uint8(fy),
|
||||
}
|
||||
var v glyphCacheVal
|
||||
if a.glyphCache[cIndex].key != k {
|
||||
var ok bool
|
||||
v, ok = a.rasterize(index, fx, fy)
|
||||
if !ok {
|
||||
return image.Rectangle{}, nil, image.Point{}, 0, false
|
||||
}
|
||||
a.glyphCache[cIndex] = glyphCacheEntry{k, v}
|
||||
} else {
|
||||
v = a.glyphCache[cIndex].val
|
||||
}
|
||||
|
||||
dr.Min = image.Point{
|
||||
X: ix + v.offset.X,
|
||||
Y: iy + v.offset.Y,
|
||||
}
|
||||
dr.Max = image.Point{
|
||||
X: dr.Min.X + v.gw,
|
||||
Y: dr.Min.Y + v.gh,
|
||||
}
|
||||
return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true
|
||||
}
|
||||
|
||||
func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
|
||||
return fixed.Rectangle26_6{}, 0, false
|
||||
}
|
||||
xmin := +a.glyphBuf.Bounds.Min.X
|
||||
ymin := -a.glyphBuf.Bounds.Max.Y
|
||||
xmax := +a.glyphBuf.Bounds.Max.X
|
||||
ymax := -a.glyphBuf.Bounds.Min.Y
|
||||
if xmin > xmax || ymin > ymax {
|
||||
return fixed.Rectangle26_6{}, 0, false
|
||||
}
|
||||
return fixed.Rectangle26_6{
|
||||
Min: fixed.Point26_6{
|
||||
X: xmin,
|
||||
Y: ymin,
|
||||
},
|
||||
Max: fixed.Point26_6{
|
||||
X: xmax,
|
||||
Y: ymax,
|
||||
},
|
||||
}, a.glyphBuf.AdvanceWidth, true
|
||||
}
|
||||
|
||||
func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return a.glyphBuf.AdvanceWidth, true
|
||||
}
|
||||
|
||||
// rasterize returns the advance width, integer-pixel offset to render at, and
|
||||
// the width and height of the given glyph at the given sub-pixel offsets.
|
||||
//
|
||||
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
|
||||
func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) {
|
||||
if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil {
|
||||
return glyphCacheVal{}, false
|
||||
}
|
||||
// Calculate the integer-pixel bounds for the glyph.
|
||||
xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6
|
||||
ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6
|
||||
xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6
|
||||
ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6
|
||||
if xmin > xmax || ymin > ymax {
|
||||
return glyphCacheVal{}, false
|
||||
}
|
||||
// A TrueType's glyph's nodes can have negative co-ordinates, but the
|
||||
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
|
||||
// the pixel offsets, based on the font's FUnit metrics, that let a
|
||||
// negative co-ordinate in TrueType space be non-negative in rasterizer
|
||||
// space. xmin and ymin are typically <= 0.
|
||||
fx -= fixed.Int26_6(xmin << 6)
|
||||
fy -= fixed.Int26_6(ymin << 6)
|
||||
// Rasterize the glyph's vectors.
|
||||
a.r.Clear()
|
||||
pixOffset := a.paintOffset * a.maxw
|
||||
clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh])
|
||||
e0 := 0
|
||||
for _, e1 := range a.glyphBuf.Ends {
|
||||
a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy)
|
||||
e0 = e1
|
||||
}
|
||||
a.r.Rasterize(a.p)
|
||||
return glyphCacheVal{
|
||||
a.glyphBuf.AdvanceWidth,
|
||||
image.Point{xmin, ymin},
|
||||
xmax - xmin,
|
||||
ymax - ymin,
|
||||
}, true
|
||||
}
|
||||
|
||||
func clear(pix []byte) {
|
||||
for i := range pix {
|
||||
pix[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// drawContour draws the given closed contour with the given offset.
|
||||
func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
|
||||
if len(ps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// The low bit of each point's Flags value is whether the point is on the
|
||||
// curve. Truetype fonts only have quadratic Bézier curves, not cubics.
|
||||
// Thus, two consecutive off-curve points imply an on-curve point in the
|
||||
// middle of those two.
|
||||
//
|
||||
// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details.
|
||||
|
||||
// ps[0] is a truetype.Point measured in FUnits and positive Y going
|
||||
// upwards. start is the same thing measured in fixed point units and
|
||||
// positive Y going downwards, and offset by (dx, dy).
|
||||
start := fixed.Point26_6{
|
||||
X: dx + ps[0].X,
|
||||
Y: dy - ps[0].Y,
|
||||
}
|
||||
var others []Point
|
||||
if ps[0].Flags&0x01 != 0 {
|
||||
others = ps[1:]
|
||||
} else {
|
||||
last := fixed.Point26_6{
|
||||
X: dx + ps[len(ps)-1].X,
|
||||
Y: dy - ps[len(ps)-1].Y,
|
||||
}
|
||||
if ps[len(ps)-1].Flags&0x01 != 0 {
|
||||
start = last
|
||||
others = ps[:len(ps)-1]
|
||||
} else {
|
||||
start = fixed.Point26_6{
|
||||
X: (start.X + last.X) / 2,
|
||||
Y: (start.Y + last.Y) / 2,
|
||||
}
|
||||
others = ps
|
||||
}
|
||||
}
|
||||
a.r.Start(start)
|
||||
q0, on0 := start, true
|
||||
for _, p := range others {
|
||||
q := fixed.Point26_6{
|
||||
X: dx + p.X,
|
||||
Y: dy - p.Y,
|
||||
}
|
||||
on := p.Flags&0x01 != 0
|
||||
if on {
|
||||
if on0 {
|
||||
a.r.Add1(q)
|
||||
} else {
|
||||
a.r.Add2(q0, q)
|
||||
}
|
||||
} else {
|
||||
if on0 {
|
||||
// No-op.
|
||||
} else {
|
||||
mid := fixed.Point26_6{
|
||||
X: (q0.X + q.X) / 2,
|
||||
Y: (q0.Y + q.Y) / 2,
|
||||
}
|
||||
a.r.Add2(q0, mid)
|
||||
}
|
||||
}
|
||||
q0, on0 = q, on
|
||||
}
|
||||
// Close the curve.
|
||||
if on0 {
|
||||
a.r.Add1(start)
|
||||
} else {
|
||||
a.r.Add2(q0, start)
|
||||
}
|
||||
}
|
||||
|
||||
// facePainter is like a raster.AlphaSrcPainter, with an additional Y offset
|
||||
// (face.paintOffset) to the painted spans.
|
||||
type facePainter struct {
|
||||
a *face
|
||||
}
|
||||
|
||||
func (p facePainter) Paint(ss []raster.Span, done bool) {
|
||||
m := p.a.masks
|
||||
b := m.Bounds()
|
||||
b.Min.Y = p.a.paintOffset
|
||||
b.Max.Y = p.a.paintOffset + p.a.maxh
|
||||
for _, s := range ss {
|
||||
s.Y += p.a.paintOffset
|
||||
if s.Y < b.Min.Y {
|
||||
continue
|
||||
}
|
||||
if s.Y >= b.Max.Y {
|
||||
return
|
||||
}
|
||||
if s.X0 < b.Min.X {
|
||||
s.X0 = b.Min.X
|
||||
}
|
||||
if s.X1 > b.Max.X {
|
||||
s.X1 = b.Max.X
|
||||
}
|
||||
if s.X0 >= s.X1 {
|
||||
continue
|
||||
}
|
||||
base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X
|
||||
p := m.Pix[base+s.X0 : base+s.X1]
|
||||
color := uint8(s.Alpha >> 8)
|
||||
for i := range p {
|
||||
p[i] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
522
src/server/vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
Normal file
522
src/server/vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
Normal file
@ -0,0 +1,522 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package truetype
|
||||
|
||||
import (
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// TODO: implement VerticalHinting.
|
||||
|
||||
// A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off'
|
||||
// control point.
|
||||
type Point struct {
|
||||
X, Y fixed.Int26_6
|
||||
// The Flags' LSB means whether or not this Point is 'on' the contour.
|
||||
// Other bits are reserved for internal use.
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a
|
||||
// series of glyphs from a Font.
|
||||
type GlyphBuf struct {
|
||||
// AdvanceWidth is the glyph's advance width.
|
||||
AdvanceWidth fixed.Int26_6
|
||||
// Bounds is the glyph's bounding box.
|
||||
Bounds fixed.Rectangle26_6
|
||||
// Points contains all Points from all contours of the glyph. If hinting
|
||||
// was used to load a glyph then Unhinted contains those Points before they
|
||||
// were hinted, and InFontUnits contains those Points before they were
|
||||
// hinted and scaled.
|
||||
Points, Unhinted, InFontUnits []Point
|
||||
// Ends is the point indexes of the end point of each contour. The length
|
||||
// of Ends is the number of contours in the glyph. The i'th contour
|
||||
// consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is
|
||||
// interpreted to mean zero.
|
||||
Ends []int
|
||||
|
||||
font *Font
|
||||
scale fixed.Int26_6
|
||||
hinting font.Hinting
|
||||
hinter hinter
|
||||
// phantomPoints are the co-ordinates of the synthetic phantom points
|
||||
// used for hinting and bounding box calculations.
|
||||
phantomPoints [4]Point
|
||||
// pp1x is the X co-ordinate of the first phantom point. The '1' is
|
||||
// using 1-based indexing; pp1x is almost always phantomPoints[0].X.
|
||||
// TODO: eliminate this and consistently use phantomPoints[0].X.
|
||||
pp1x fixed.Int26_6
|
||||
// metricsSet is whether the glyph's metrics have been set yet. For a
|
||||
// compound glyph, a sub-glyph may override the outer glyph's metrics.
|
||||
metricsSet bool
|
||||
// tmp is a scratch buffer.
|
||||
tmp []Point
|
||||
}
|
||||
|
||||
// Flags for decoding a glyph's contours. These flags are documented at
|
||||
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
|
||||
const (
|
||||
flagOnCurve = 1 << iota
|
||||
flagXShortVector
|
||||
flagYShortVector
|
||||
flagRepeat
|
||||
flagPositiveXShortVector
|
||||
flagPositiveYShortVector
|
||||
|
||||
// The remaining flags are for internal use.
|
||||
flagTouchedX
|
||||
flagTouchedY
|
||||
)
|
||||
|
||||
// The same flag bits (0x10 and 0x20) are overloaded to have two meanings,
|
||||
// dependent on the value of the flag{X,Y}ShortVector bits.
|
||||
const (
|
||||
flagThisXIsSame = flagPositiveXShortVector
|
||||
flagThisYIsSame = flagPositiveYShortVector
|
||||
)
|
||||
|
||||
// Load loads a glyph's contours from a Font, overwriting any previously loaded
|
||||
// contours for this GlyphBuf. scale is the number of 26.6 fixed point units in
|
||||
// 1 em, i is the glyph index, and h is the hinting policy.
|
||||
func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error {
|
||||
g.Points = g.Points[:0]
|
||||
g.Unhinted = g.Unhinted[:0]
|
||||
g.InFontUnits = g.InFontUnits[:0]
|
||||
g.Ends = g.Ends[:0]
|
||||
g.font = f
|
||||
g.hinting = h
|
||||
g.scale = scale
|
||||
g.pp1x = 0
|
||||
g.phantomPoints = [4]Point{}
|
||||
g.metricsSet = false
|
||||
|
||||
if h != font.HintingNone {
|
||||
if err := g.hinter.init(f, scale); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.load(0, i, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal,
|
||||
// and should be cleaned up once we have all the testScaling tests passing,
|
||||
// plus additional tests for Freetype-Go's bounding boxes matching C Freetype's.
|
||||
pp1x := g.pp1x
|
||||
if h != font.HintingNone {
|
||||
pp1x = g.phantomPoints[0].X
|
||||
}
|
||||
if pp1x != 0 {
|
||||
for i := range g.Points {
|
||||
g.Points[i].X -= pp1x
|
||||
}
|
||||
}
|
||||
|
||||
advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X
|
||||
if h != font.HintingNone {
|
||||
if len(f.hdmx) >= 8 {
|
||||
if n := u32(f.hdmx, 4); n > 3+uint32(i) {
|
||||
for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] {
|
||||
if fixed.Int26_6(hdmx[0]) == scale>>6 {
|
||||
advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
advanceWidth = (advanceWidth + 32) &^ 63
|
||||
}
|
||||
g.AdvanceWidth = advanceWidth
|
||||
|
||||
// Set g.Bounds to the 'control box', which is the bounding box of the
|
||||
// Bézier curves' control points. This is easier to calculate, no smaller
|
||||
// than and often equal to the tightest possible bounding box of the curves
|
||||
// themselves. This approach is what C Freetype does. We can't just scale
|
||||
// the nominal bounding box in the glyf data as the hinting process and
|
||||
// phantom point adjustment may move points outside of that box.
|
||||
if len(g.Points) == 0 {
|
||||
g.Bounds = fixed.Rectangle26_6{}
|
||||
} else {
|
||||
p := g.Points[0]
|
||||
g.Bounds.Min.X = p.X
|
||||
g.Bounds.Max.X = p.X
|
||||
g.Bounds.Min.Y = p.Y
|
||||
g.Bounds.Max.Y = p.Y
|
||||
for _, p := range g.Points[1:] {
|
||||
if g.Bounds.Min.X > p.X {
|
||||
g.Bounds.Min.X = p.X
|
||||
} else if g.Bounds.Max.X < p.X {
|
||||
g.Bounds.Max.X = p.X
|
||||
}
|
||||
if g.Bounds.Min.Y > p.Y {
|
||||
g.Bounds.Min.Y = p.Y
|
||||
} else if g.Bounds.Max.Y < p.Y {
|
||||
g.Bounds.Max.Y = p.Y
|
||||
}
|
||||
}
|
||||
// Snap the box to the grid, if hinting is on.
|
||||
if h != font.HintingNone {
|
||||
g.Bounds.Min.X &^= 63
|
||||
g.Bounds.Min.Y &^= 63
|
||||
g.Bounds.Max.X += 63
|
||||
g.Bounds.Max.X &^= 63
|
||||
g.Bounds.Max.Y += 63
|
||||
g.Bounds.Max.Y &^= 63
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) {
|
||||
// The recursion limit here is arbitrary, but defends against malformed glyphs.
|
||||
if recursion >= 32 {
|
||||
return UnsupportedError("excessive compound glyph recursion")
|
||||
}
|
||||
// Find the relevant slice of g.font.glyf.
|
||||
var g0, g1 uint32
|
||||
if g.font.locaOffsetFormat == locaOffsetFormatShort {
|
||||
g0 = 2 * uint32(u16(g.font.loca, 2*int(i)))
|
||||
g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2))
|
||||
} else {
|
||||
g0 = u32(g.font.loca, 4*int(i))
|
||||
g1 = u32(g.font.loca, 4*int(i)+4)
|
||||
}
|
||||
|
||||
// Decode the contour count and nominal bounding box, from the first
|
||||
// 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4
|
||||
// and 6, are unused.
|
||||
glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0)
|
||||
if g0+10 <= g1 {
|
||||
glyf = g.font.glyf[g0:g1]
|
||||
ne = int(int16(u16(glyf, 0)))
|
||||
boundsXMin = fixed.Int26_6(int16(u16(glyf, 2)))
|
||||
boundsYMax = fixed.Int26_6(int16(u16(glyf, 8)))
|
||||
}
|
||||
|
||||
// Create the phantom points.
|
||||
uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0)
|
||||
uvm := g.font.unscaledVMetric(i, boundsYMax)
|
||||
g.phantomPoints = [4]Point{
|
||||
{X: boundsXMin - uhm.LeftSideBearing},
|
||||
{X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth},
|
||||
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing},
|
||||
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight},
|
||||
}
|
||||
if len(glyf) == 0 {
|
||||
g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true)
|
||||
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
|
||||
g.Points = g.Points[:len(g.Points)-4]
|
||||
// TODO: also trim g.InFontUnits and g.Unhinted?
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load and hint the contours.
|
||||
if ne < 0 {
|
||||
if ne != -1 {
|
||||
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
|
||||
// "the values -2, -3, and so forth, are reserved for future use."
|
||||
return UnsupportedError("negative number of contours")
|
||||
}
|
||||
pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing))
|
||||
if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
np0, ne0 := len(g.Points), len(g.Ends)
|
||||
program := g.loadSimple(glyf, ne)
|
||||
g.addPhantomsAndScale(np0, np0, true, true)
|
||||
pp1x = g.Points[len(g.Points)-4].X
|
||||
if g.hinting != font.HintingNone {
|
||||
if len(program) != 0 {
|
||||
err := g.hinter.run(
|
||||
program,
|
||||
g.Points[np0:],
|
||||
g.Unhinted[np0:],
|
||||
g.InFontUnits[np0:],
|
||||
g.Ends[ne0:],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Drop the four phantom points.
|
||||
g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4]
|
||||
g.Unhinted = g.Unhinted[:len(g.Unhinted)-4]
|
||||
}
|
||||
if useMyMetrics {
|
||||
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
|
||||
}
|
||||
g.Points = g.Points[:len(g.Points)-4]
|
||||
if np0 != 0 {
|
||||
// The hinting program expects the []Ends values to be indexed
|
||||
// relative to the inner glyph, not the outer glyph, so we delay
|
||||
// adding np0 until after the hinting program (if any) has run.
|
||||
for i := ne0; i < len(g.Ends); i++ {
|
||||
g.Ends[i] += np0
|
||||
}
|
||||
}
|
||||
}
|
||||
if useMyMetrics && !g.metricsSet {
|
||||
g.metricsSet = true
|
||||
g.pp1x = pp1x
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadOffset is the initial offset for loadSimple and loadCompound. The first
|
||||
// 10 bytes are the number of contours and the bounding box.
|
||||
const loadOffset = 10
|
||||
|
||||
func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) {
|
||||
offset := loadOffset
|
||||
for i := 0; i < ne; i++ {
|
||||
g.Ends = append(g.Ends, 1+int(u16(glyf, offset)))
|
||||
offset += 2
|
||||
}
|
||||
|
||||
// Note the TrueType hinting instructions.
|
||||
instrLen := int(u16(glyf, offset))
|
||||
offset += 2
|
||||
program = glyf[offset : offset+instrLen]
|
||||
offset += instrLen
|
||||
|
||||
if ne == 0 {
|
||||
return program
|
||||
}
|
||||
|
||||
np0 := len(g.Points)
|
||||
np1 := np0 + int(g.Ends[len(g.Ends)-1])
|
||||
|
||||
// Decode the flags.
|
||||
for i := np0; i < np1; {
|
||||
c := uint32(glyf[offset])
|
||||
offset++
|
||||
g.Points = append(g.Points, Point{Flags: c})
|
||||
i++
|
||||
if c&flagRepeat != 0 {
|
||||
count := glyf[offset]
|
||||
offset++
|
||||
for ; count > 0; count-- {
|
||||
g.Points = append(g.Points, Point{Flags: c})
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the co-ordinates.
|
||||
var x int16
|
||||
for i := np0; i < np1; i++ {
|
||||
f := g.Points[i].Flags
|
||||
if f&flagXShortVector != 0 {
|
||||
dx := int16(glyf[offset])
|
||||
offset++
|
||||
if f&flagPositiveXShortVector == 0 {
|
||||
x -= dx
|
||||
} else {
|
||||
x += dx
|
||||
}
|
||||
} else if f&flagThisXIsSame == 0 {
|
||||
x += int16(u16(glyf, offset))
|
||||
offset += 2
|
||||
}
|
||||
g.Points[i].X = fixed.Int26_6(x)
|
||||
}
|
||||
var y int16
|
||||
for i := np0; i < np1; i++ {
|
||||
f := g.Points[i].Flags
|
||||
if f&flagYShortVector != 0 {
|
||||
dy := int16(glyf[offset])
|
||||
offset++
|
||||
if f&flagPositiveYShortVector == 0 {
|
||||
y -= dy
|
||||
} else {
|
||||
y += dy
|
||||
}
|
||||
} else if f&flagThisYIsSame == 0 {
|
||||
y += int16(u16(glyf, offset))
|
||||
offset += 2
|
||||
}
|
||||
g.Points[i].Y = fixed.Int26_6(y)
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index,
|
||||
glyf []byte, useMyMetrics bool) error {
|
||||
|
||||
// Flags for decoding a compound glyph. These flags are documented at
|
||||
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
|
||||
const (
|
||||
flagArg1And2AreWords = 1 << iota
|
||||
flagArgsAreXYValues
|
||||
flagRoundXYToGrid
|
||||
flagWeHaveAScale
|
||||
flagUnused
|
||||
flagMoreComponents
|
||||
flagWeHaveAnXAndYScale
|
||||
flagWeHaveATwoByTwo
|
||||
flagWeHaveInstructions
|
||||
flagUseMyMetrics
|
||||
flagOverlapCompound
|
||||
)
|
||||
np0, ne0 := len(g.Points), len(g.Ends)
|
||||
offset := loadOffset
|
||||
for {
|
||||
flags := u16(glyf, offset)
|
||||
component := Index(u16(glyf, offset+2))
|
||||
dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false
|
||||
if flags&flagArg1And2AreWords != 0 {
|
||||
dx = fixed.Int26_6(int16(u16(glyf, offset+4)))
|
||||
dy = fixed.Int26_6(int16(u16(glyf, offset+6)))
|
||||
offset += 8
|
||||
} else {
|
||||
dx = fixed.Int26_6(int16(int8(glyf[offset+4])))
|
||||
dy = fixed.Int26_6(int16(int8(glyf[offset+5])))
|
||||
offset += 6
|
||||
}
|
||||
if flags&flagArgsAreXYValues == 0 {
|
||||
return UnsupportedError("compound glyph transform vector")
|
||||
}
|
||||
if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 {
|
||||
hasTransform = true
|
||||
switch {
|
||||
case flags&flagWeHaveAScale != 0:
|
||||
transform[0] = int16(u16(glyf, offset+0))
|
||||
transform[3] = transform[0]
|
||||
offset += 2
|
||||
case flags&flagWeHaveAnXAndYScale != 0:
|
||||
transform[0] = int16(u16(glyf, offset+0))
|
||||
transform[3] = int16(u16(glyf, offset+2))
|
||||
offset += 4
|
||||
case flags&flagWeHaveATwoByTwo != 0:
|
||||
transform[0] = int16(u16(glyf, offset+0))
|
||||
transform[1] = int16(u16(glyf, offset+2))
|
||||
transform[2] = int16(u16(glyf, offset+4))
|
||||
transform[3] = int16(u16(glyf, offset+6))
|
||||
offset += 8
|
||||
}
|
||||
}
|
||||
savedPP := g.phantomPoints
|
||||
np0 := len(g.Points)
|
||||
componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0)
|
||||
if err := g.load(recursion+1, component, componentUMM); err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&flagUseMyMetrics == 0 {
|
||||
g.phantomPoints = savedPP
|
||||
}
|
||||
if hasTransform {
|
||||
for j := np0; j < len(g.Points); j++ {
|
||||
p := &g.Points[j]
|
||||
newX := 0 +
|
||||
fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) +
|
||||
fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14)
|
||||
newY := 0 +
|
||||
fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) +
|
||||
fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14)
|
||||
p.X, p.Y = newX, newY
|
||||
}
|
||||
}
|
||||
dx = g.font.scale(g.scale * dx)
|
||||
dy = g.font.scale(g.scale * dy)
|
||||
if flags&flagRoundXYToGrid != 0 {
|
||||
dx = (dx + 32) &^ 63
|
||||
dy = (dy + 32) &^ 63
|
||||
}
|
||||
for j := np0; j < len(g.Points); j++ {
|
||||
p := &g.Points[j]
|
||||
p.X += dx
|
||||
p.Y += dy
|
||||
}
|
||||
// TODO: also adjust g.InFontUnits and g.Unhinted?
|
||||
if flags&flagMoreComponents == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
instrLen := 0
|
||||
if g.hinting != font.HintingNone && offset+2 <= len(glyf) {
|
||||
instrLen = int(u16(glyf, offset))
|
||||
offset += 2
|
||||
}
|
||||
|
||||
g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0)
|
||||
points, ends := g.Points[np0:], g.Ends[ne0:]
|
||||
g.Points = g.Points[:len(g.Points)-4]
|
||||
for j := range points {
|
||||
points[j].Flags &^= flagTouchedX | flagTouchedY
|
||||
}
|
||||
|
||||
if instrLen == 0 {
|
||||
if !g.metricsSet {
|
||||
copy(g.phantomPoints[:], points[len(points)-4:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hint the compound glyph.
|
||||
program := glyf[offset : offset+instrLen]
|
||||
// Temporarily adjust the ends to be relative to this compound glyph.
|
||||
if np0 != 0 {
|
||||
for i := range ends {
|
||||
ends[i] -= np0
|
||||
}
|
||||
}
|
||||
// Hinting instructions of a composite glyph completely refer to the
|
||||
// (already) hinted subglyphs.
|
||||
g.tmp = append(g.tmp[:0], points...)
|
||||
if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil {
|
||||
return err
|
||||
}
|
||||
if np0 != 0 {
|
||||
for i := range ends {
|
||||
ends[i] += np0
|
||||
}
|
||||
}
|
||||
if !g.metricsSet {
|
||||
copy(g.phantomPoints[:], points[len(points)-4:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) {
|
||||
// Add the four phantom points.
|
||||
g.Points = append(g.Points, g.phantomPoints[:]...)
|
||||
// Scale the points.
|
||||
if simple && g.hinting != font.HintingNone {
|
||||
g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...)
|
||||
}
|
||||
for i := np1; i < len(g.Points); i++ {
|
||||
p := &g.Points[i]
|
||||
p.X = g.font.scale(g.scale * p.X)
|
||||
p.Y = g.font.scale(g.scale * p.Y)
|
||||
}
|
||||
if g.hinting == font.HintingNone {
|
||||
return
|
||||
}
|
||||
// Round the 1st phantom point to the grid, shifting all other points equally.
|
||||
// Note that "all other points" starts from np0, not np1.
|
||||
// TODO: delete this adjustment and the np0/np1 distinction, when
|
||||
// we update the compatibility tests to C Freetype 2.5.3.
|
||||
// See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06
|
||||
if adjust {
|
||||
pp1x := g.Points[len(g.Points)-4].X
|
||||
if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 {
|
||||
for i := np0; i < len(g.Points); i++ {
|
||||
g.Points[i].X += dx
|
||||
}
|
||||
}
|
||||
}
|
||||
if simple {
|
||||
g.Unhinted = append(g.Unhinted, g.Points[np1:]...)
|
||||
}
|
||||
// Round the 2nd and 4th phantom point to the grid.
|
||||
p := &g.Points[len(g.Points)-3]
|
||||
p.X = (p.X + 32) &^ 63
|
||||
p = &g.Points[len(g.Points)-1]
|
||||
p.Y = (p.Y + 32) &^ 63
|
||||
}
|
||||
1770
src/server/vendor/github.com/golang/freetype/truetype/hint.go
generated
vendored
Normal file
1770
src/server/vendor/github.com/golang/freetype/truetype/hint.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
289
src/server/vendor/github.com/golang/freetype/truetype/opcodes.go
generated
vendored
Normal file
289
src/server/vendor/github.com/golang/freetype/truetype/opcodes.go
generated
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package truetype
|
||||
|
||||
// The Truetype opcodes are summarized at
|
||||
// https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html
|
||||
|
||||
const (
|
||||
opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis
|
||||
opSVTCA1 = 0x01 // .
|
||||
opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis
|
||||
opSPVTCA1 = 0x03 // .
|
||||
opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis
|
||||
opSFVTCA1 = 0x05 // .
|
||||
opSPVTL0 = 0x06 // Set Projection Vector To Line
|
||||
opSPVTL1 = 0x07 // .
|
||||
opSFVTL0 = 0x08 // Set Freedom Vector To Line
|
||||
opSFVTL1 = 0x09 // .
|
||||
opSPVFS = 0x0a // Set Projection Vector From Stack
|
||||
opSFVFS = 0x0b // Set Freedom Vector From Stack
|
||||
opGPV = 0x0c // Get Projection Vector
|
||||
opGFV = 0x0d // Get Freedom Vector
|
||||
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
|
||||
opISECT = 0x0f // moves point p to the InterSECTion of two lines
|
||||
opSRP0 = 0x10 // Set Reference Point 0
|
||||
opSRP1 = 0x11 // Set Reference Point 1
|
||||
opSRP2 = 0x12 // Set Reference Point 2
|
||||
opSZP0 = 0x13 // Set Zone Pointer 0
|
||||
opSZP1 = 0x14 // Set Zone Pointer 1
|
||||
opSZP2 = 0x15 // Set Zone Pointer 2
|
||||
opSZPS = 0x16 // Set Zone PointerS
|
||||
opSLOOP = 0x17 // Set LOOP variable
|
||||
opRTG = 0x18 // Round To Grid
|
||||
opRTHG = 0x19 // Round To Half Grid
|
||||
opSMD = 0x1a // Set Minimum Distance
|
||||
opELSE = 0x1b // ELSE clause
|
||||
opJMPR = 0x1c // JuMP Relative
|
||||
opSCVTCI = 0x1d // Set Control Value Table Cut-In
|
||||
opSSWCI = 0x1e // Set Single Width Cut-In
|
||||
opSSW = 0x1f // Set Single Width
|
||||
opDUP = 0x20 // DUPlicate top stack element
|
||||
opPOP = 0x21 // POP top stack element
|
||||
opCLEAR = 0x22 // CLEAR the stack
|
||||
opSWAP = 0x23 // SWAP the top two elements on the stack
|
||||
opDEPTH = 0x24 // DEPTH of the stack
|
||||
opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack
|
||||
opMINDEX = 0x26 // Move the INDEXed element to the top of the stack
|
||||
opALIGNPTS = 0x27 // ALIGN PoinTS
|
||||
op_0x28 = 0x28 // deprecated
|
||||
opUTP = 0x29 // UnTouch Point
|
||||
opLOOPCALL = 0x2a // LOOP and CALL function
|
||||
opCALL = 0x2b // CALL function
|
||||
opFDEF = 0x2c // Function DEFinition
|
||||
opENDF = 0x2d // END Function definition
|
||||
opMDAP0 = 0x2e // Move Direct Absolute Point
|
||||
opMDAP1 = 0x2f // .
|
||||
opIUP0 = 0x30 // Interpolate Untouched Points through the outline
|
||||
opIUP1 = 0x31 // .
|
||||
opSHP0 = 0x32 // SHift Point using reference point
|
||||
opSHP1 = 0x33 // .
|
||||
opSHC0 = 0x34 // SHift Contour using reference point
|
||||
opSHC1 = 0x35 // .
|
||||
opSHZ0 = 0x36 // SHift Zone using reference point
|
||||
opSHZ1 = 0x37 // .
|
||||
opSHPIX = 0x38 // SHift point by a PIXel amount
|
||||
opIP = 0x39 // Interpolate Point
|
||||
opMSIRP0 = 0x3a // Move Stack Indirect Relative Point
|
||||
opMSIRP1 = 0x3b // .
|
||||
opALIGNRP = 0x3c // ALIGN to Reference Point
|
||||
opRTDG = 0x3d // Round To Double Grid
|
||||
opMIAP0 = 0x3e // Move Indirect Absolute Point
|
||||
opMIAP1 = 0x3f // .
|
||||
opNPUSHB = 0x40 // PUSH N Bytes
|
||||
opNPUSHW = 0x41 // PUSH N Words
|
||||
opWS = 0x42 // Write Store
|
||||
opRS = 0x43 // Read Store
|
||||
opWCVTP = 0x44 // Write Control Value Table in Pixel units
|
||||
opRCVT = 0x45 // Read Control Value Table entry
|
||||
opGC0 = 0x46 // Get Coordinate projected onto the projection vector
|
||||
opGC1 = 0x47 // .
|
||||
opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector
|
||||
opMD0 = 0x49 // Measure Distance
|
||||
opMD1 = 0x4a // .
|
||||
opMPPEM = 0x4b // Measure Pixels Per EM
|
||||
opMPS = 0x4c // Measure Point Size
|
||||
opFLIPON = 0x4d // set the auto FLIP Boolean to ON
|
||||
opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF
|
||||
opDEBUG = 0x4f // DEBUG call
|
||||
opLT = 0x50 // Less Than
|
||||
opLTEQ = 0x51 // Less Than or EQual
|
||||
opGT = 0x52 // Greater Than
|
||||
opGTEQ = 0x53 // Greater Than or EQual
|
||||
opEQ = 0x54 // EQual
|
||||
opNEQ = 0x55 // Not EQual
|
||||
opODD = 0x56 // ODD
|
||||
opEVEN = 0x57 // EVEN
|
||||
opIF = 0x58 // IF test
|
||||
opEIF = 0x59 // End IF
|
||||
opAND = 0x5a // logical AND
|
||||
opOR = 0x5b // logical OR
|
||||
opNOT = 0x5c // logical NOT
|
||||
opDELTAP1 = 0x5d // DELTA exception P1
|
||||
opSDB = 0x5e // Set Delta Base in the graphics state
|
||||
opSDS = 0x5f // Set Delta Shift in the graphics state
|
||||
opADD = 0x60 // ADD
|
||||
opSUB = 0x61 // SUBtract
|
||||
opDIV = 0x62 // DIVide
|
||||
opMUL = 0x63 // MULtiply
|
||||
opABS = 0x64 // ABSolute value
|
||||
opNEG = 0x65 // NEGate
|
||||
opFLOOR = 0x66 // FLOOR
|
||||
opCEILING = 0x67 // CEILING
|
||||
opROUND00 = 0x68 // ROUND value
|
||||
opROUND01 = 0x69 // .
|
||||
opROUND10 = 0x6a // .
|
||||
opROUND11 = 0x6b // .
|
||||
opNROUND00 = 0x6c // No ROUNDing of value
|
||||
opNROUND01 = 0x6d // .
|
||||
opNROUND10 = 0x6e // .
|
||||
opNROUND11 = 0x6f // .
|
||||
opWCVTF = 0x70 // Write Control Value Table in Funits
|
||||
opDELTAP2 = 0x71 // DELTA exception P2
|
||||
opDELTAP3 = 0x72 // DELTA exception P3
|
||||
opDELTAC1 = 0x73 // DELTA exception C1
|
||||
opDELTAC2 = 0x74 // DELTA exception C2
|
||||
opDELTAC3 = 0x75 // DELTA exception C3
|
||||
opSROUND = 0x76 // Super ROUND
|
||||
opS45ROUND = 0x77 // Super ROUND 45 degrees
|
||||
opJROT = 0x78 // Jump Relative On True
|
||||
opJROF = 0x79 // Jump Relative On False
|
||||
opROFF = 0x7a // Round OFF
|
||||
op_0x7b = 0x7b // deprecated
|
||||
opRUTG = 0x7c // Round Up To Grid
|
||||
opRDTG = 0x7d // Round Down To Grid
|
||||
opSANGW = 0x7e // Set ANGle Weight
|
||||
opAA = 0x7f // Adjust Angle
|
||||
opFLIPPT = 0x80 // FLIP PoinT
|
||||
opFLIPRGON = 0x81 // FLIP RanGe ON
|
||||
opFLIPRGOFF = 0x82 // FLIP RanGe OFF
|
||||
op_0x83 = 0x83 // deprecated
|
||||
op_0x84 = 0x84 // deprecated
|
||||
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
|
||||
opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line
|
||||
opSDPVTL1 = 0x87 // .
|
||||
opGETINFO = 0x88 // GET INFOrmation
|
||||
opIDEF = 0x89 // Instruction DEFinition
|
||||
opROLL = 0x8a // ROLL the top three stack elements
|
||||
opMAX = 0x8b // MAXimum of top two stack elements
|
||||
opMIN = 0x8c // MINimum of top two stack elements
|
||||
opSCANTYPE = 0x8d // SCANTYPE
|
||||
opINSTCTRL = 0x8e // INSTRuction execution ConTRoL
|
||||
op_0x8f = 0x8f
|
||||
op_0x90 = 0x90
|
||||
op_0x91 = 0x91
|
||||
op_0x92 = 0x92
|
||||
op_0x93 = 0x93
|
||||
op_0x94 = 0x94
|
||||
op_0x95 = 0x95
|
||||
op_0x96 = 0x96
|
||||
op_0x97 = 0x97
|
||||
op_0x98 = 0x98
|
||||
op_0x99 = 0x99
|
||||
op_0x9a = 0x9a
|
||||
op_0x9b = 0x9b
|
||||
op_0x9c = 0x9c
|
||||
op_0x9d = 0x9d
|
||||
op_0x9e = 0x9e
|
||||
op_0x9f = 0x9f
|
||||
op_0xa0 = 0xa0
|
||||
op_0xa1 = 0xa1
|
||||
op_0xa2 = 0xa2
|
||||
op_0xa3 = 0xa3
|
||||
op_0xa4 = 0xa4
|
||||
op_0xa5 = 0xa5
|
||||
op_0xa6 = 0xa6
|
||||
op_0xa7 = 0xa7
|
||||
op_0xa8 = 0xa8
|
||||
op_0xa9 = 0xa9
|
||||
op_0xaa = 0xaa
|
||||
op_0xab = 0xab
|
||||
op_0xac = 0xac
|
||||
op_0xad = 0xad
|
||||
op_0xae = 0xae
|
||||
op_0xaf = 0xaf
|
||||
opPUSHB000 = 0xb0 // PUSH Bytes
|
||||
opPUSHB001 = 0xb1 // .
|
||||
opPUSHB010 = 0xb2 // .
|
||||
opPUSHB011 = 0xb3 // .
|
||||
opPUSHB100 = 0xb4 // .
|
||||
opPUSHB101 = 0xb5 // .
|
||||
opPUSHB110 = 0xb6 // .
|
||||
opPUSHB111 = 0xb7 // .
|
||||
opPUSHW000 = 0xb8 // PUSH Words
|
||||
opPUSHW001 = 0xb9 // .
|
||||
opPUSHW010 = 0xba // .
|
||||
opPUSHW011 = 0xbb // .
|
||||
opPUSHW100 = 0xbc // .
|
||||
opPUSHW101 = 0xbd // .
|
||||
opPUSHW110 = 0xbe // .
|
||||
opPUSHW111 = 0xbf // .
|
||||
opMDRP00000 = 0xc0 // Move Direct Relative Point
|
||||
opMDRP00001 = 0xc1 // .
|
||||
opMDRP00010 = 0xc2 // .
|
||||
opMDRP00011 = 0xc3 // .
|
||||
opMDRP00100 = 0xc4 // .
|
||||
opMDRP00101 = 0xc5 // .
|
||||
opMDRP00110 = 0xc6 // .
|
||||
opMDRP00111 = 0xc7 // .
|
||||
opMDRP01000 = 0xc8 // .
|
||||
opMDRP01001 = 0xc9 // .
|
||||
opMDRP01010 = 0xca // .
|
||||
opMDRP01011 = 0xcb // .
|
||||
opMDRP01100 = 0xcc // .
|
||||
opMDRP01101 = 0xcd // .
|
||||
opMDRP01110 = 0xce // .
|
||||
opMDRP01111 = 0xcf // .
|
||||
opMDRP10000 = 0xd0 // .
|
||||
opMDRP10001 = 0xd1 // .
|
||||
opMDRP10010 = 0xd2 // .
|
||||
opMDRP10011 = 0xd3 // .
|
||||
opMDRP10100 = 0xd4 // .
|
||||
opMDRP10101 = 0xd5 // .
|
||||
opMDRP10110 = 0xd6 // .
|
||||
opMDRP10111 = 0xd7 // .
|
||||
opMDRP11000 = 0xd8 // .
|
||||
opMDRP11001 = 0xd9 // .
|
||||
opMDRP11010 = 0xda // .
|
||||
opMDRP11011 = 0xdb // .
|
||||
opMDRP11100 = 0xdc // .
|
||||
opMDRP11101 = 0xdd // .
|
||||
opMDRP11110 = 0xde // .
|
||||
opMDRP11111 = 0xdf // .
|
||||
opMIRP00000 = 0xe0 // Move Indirect Relative Point
|
||||
opMIRP00001 = 0xe1 // .
|
||||
opMIRP00010 = 0xe2 // .
|
||||
opMIRP00011 = 0xe3 // .
|
||||
opMIRP00100 = 0xe4 // .
|
||||
opMIRP00101 = 0xe5 // .
|
||||
opMIRP00110 = 0xe6 // .
|
||||
opMIRP00111 = 0xe7 // .
|
||||
opMIRP01000 = 0xe8 // .
|
||||
opMIRP01001 = 0xe9 // .
|
||||
opMIRP01010 = 0xea // .
|
||||
opMIRP01011 = 0xeb // .
|
||||
opMIRP01100 = 0xec // .
|
||||
opMIRP01101 = 0xed // .
|
||||
opMIRP01110 = 0xee // .
|
||||
opMIRP01111 = 0xef // .
|
||||
opMIRP10000 = 0xf0 // .
|
||||
opMIRP10001 = 0xf1 // .
|
||||
opMIRP10010 = 0xf2 // .
|
||||
opMIRP10011 = 0xf3 // .
|
||||
opMIRP10100 = 0xf4 // .
|
||||
opMIRP10101 = 0xf5 // .
|
||||
opMIRP10110 = 0xf6 // .
|
||||
opMIRP10111 = 0xf7 // .
|
||||
opMIRP11000 = 0xf8 // .
|
||||
opMIRP11001 = 0xf9 // .
|
||||
opMIRP11010 = 0xfa // .
|
||||
opMIRP11011 = 0xfb // .
|
||||
opMIRP11100 = 0xfc // .
|
||||
opMIRP11101 = 0xfd // .
|
||||
opMIRP11110 = 0xfe // .
|
||||
opMIRP11111 = 0xff // .
|
||||
)
|
||||
|
||||
// popCount is the number of stack elements that each opcode pops.
|
||||
var popCount = [256]uint8{
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
|
||||
0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f
|
||||
1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f
|
||||
0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f
|
||||
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f
|
||||
2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
|
||||
2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f
|
||||
0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff
|
||||
}
|
||||
653
src/server/vendor/github.com/golang/freetype/truetype/truetype.go
generated
vendored
Normal file
653
src/server/vendor/github.com/golang/freetype/truetype/truetype.go
generated
vendored
Normal file
@ -0,0 +1,653 @@
|
||||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
// Package truetype provides a parser for the TTF and TTC file formats.
|
||||
// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/
|
||||
// and http://www.microsoft.com/typography/otspec/
|
||||
//
|
||||
// Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font
|
||||
// metrics and control points. All these methods take a scale parameter, which
|
||||
// is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For
|
||||
// example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to
|
||||
// fixed.Int26_6(10 << 6).
|
||||
//
|
||||
// To measure a TrueType font in ideal FUnit space, use scale equal to
|
||||
// font.FUnitsPerEm().
|
||||
package truetype // import "github.com/golang/freetype/truetype"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// An Index is a Font's index of a rune.
|
||||
type Index uint16
|
||||
|
||||
// A NameID identifies a name table entry.
|
||||
//
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
|
||||
type NameID uint16
|
||||
|
||||
const (
|
||||
NameIDCopyright NameID = 0
|
||||
NameIDFontFamily = 1
|
||||
NameIDFontSubfamily = 2
|
||||
NameIDUniqueSubfamilyID = 3
|
||||
NameIDFontFullName = 4
|
||||
NameIDNameTableVersion = 5
|
||||
NameIDPostscriptName = 6
|
||||
NameIDTrademarkNotice = 7
|
||||
NameIDManufacturerName = 8
|
||||
NameIDDesignerName = 9
|
||||
NameIDFontDescription = 10
|
||||
NameIDFontVendorURL = 11
|
||||
NameIDFontDesignerURL = 12
|
||||
NameIDFontLicense = 13
|
||||
NameIDFontLicenseURL = 14
|
||||
NameIDPreferredFamily = 16
|
||||
NameIDPreferredSubfamily = 17
|
||||
NameIDCompatibleName = 18
|
||||
NameIDSampleText = 19
|
||||
)
|
||||
|
||||
const (
|
||||
// A 32-bit encoding consists of a most-significant 16-bit Platform ID and a
|
||||
// least-significant 16-bit Platform Specific ID. The magic numbers are
|
||||
// specified at https://www.microsoft.com/typography/otspec/name.htm
|
||||
unicodeEncodingBMPOnly = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only)
|
||||
unicodeEncodingFull = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire)
|
||||
microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol)
|
||||
microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2)
|
||||
microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4)
|
||||
)
|
||||
|
||||
// An HMetric holds the horizontal metrics of a single glyph.
|
||||
type HMetric struct {
|
||||
AdvanceWidth, LeftSideBearing fixed.Int26_6
|
||||
}
|
||||
|
||||
// A VMetric holds the vertical metrics of a single glyph.
|
||||
type VMetric struct {
|
||||
AdvanceHeight, TopSideBearing fixed.Int26_6
|
||||
}
|
||||
|
||||
// A FormatError reports that the input is not a valid TrueType font.
|
||||
type FormatError string
|
||||
|
||||
func (e FormatError) Error() string {
|
||||
return "freetype: invalid TrueType format: " + string(e)
|
||||
}
|
||||
|
||||
// An UnsupportedError reports that the input uses a valid but unimplemented
|
||||
// TrueType feature.
|
||||
type UnsupportedError string
|
||||
|
||||
func (e UnsupportedError) Error() string {
|
||||
return "freetype: unsupported TrueType feature: " + string(e)
|
||||
}
|
||||
|
||||
// u32 returns the big-endian uint32 at b[i:].
|
||||
func u32(b []byte, i int) uint32 {
|
||||
return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3])
|
||||
}
|
||||
|
||||
// u16 returns the big-endian uint16 at b[i:].
|
||||
func u16(b []byte, i int) uint16 {
|
||||
return uint16(b[i])<<8 | uint16(b[i+1])
|
||||
}
|
||||
|
||||
// readTable returns a slice of the TTF data given by a table's directory entry.
|
||||
func readTable(ttf []byte, offsetLength []byte) ([]byte, error) {
|
||||
offset := int(u32(offsetLength, 0))
|
||||
if offset < 0 {
|
||||
return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset)))
|
||||
}
|
||||
length := int(u32(offsetLength, 4))
|
||||
if length < 0 {
|
||||
return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length)))
|
||||
}
|
||||
end := offset + length
|
||||
if end < 0 || end > len(ttf) {
|
||||
return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length)))
|
||||
}
|
||||
return ttf[offset:end], nil
|
||||
}
|
||||
|
||||
// parseSubtables returns the offset and platformID of the best subtable in
|
||||
// table, where best favors a Unicode cmap encoding, and failing that, a
|
||||
// Microsoft cmap encoding. offset is the offset of the first subtable in
|
||||
// table, and size is the size of each subtable.
|
||||
//
|
||||
// If pred is non-nil, then only subtables that satisfy that predicate will be
|
||||
// considered.
|
||||
func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) (
|
||||
bestOffset int, bestPID uint32, retErr error) {
|
||||
|
||||
if len(table) < 4 {
|
||||
return 0, 0, FormatError(name + " too short")
|
||||
}
|
||||
nSubtables := int(u16(table, 2))
|
||||
if len(table) < size*nSubtables+offset {
|
||||
return 0, 0, FormatError(name + " too short")
|
||||
}
|
||||
ok := false
|
||||
for i := 0; i < nSubtables; i, offset = i+1, offset+size {
|
||||
if pred != nil && !pred(table[offset:]) {
|
||||
continue
|
||||
}
|
||||
// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32.
|
||||
// All values are big-endian.
|
||||
pidPsid := u32(table, offset)
|
||||
// We prefer the Unicode cmap encoding. Failing to find that, we fall
|
||||
// back onto the Microsoft cmap encoding.
|
||||
if pidPsid == unicodeEncodingBMPOnly || pidPsid == unicodeEncodingFull {
|
||||
bestOffset, bestPID, ok = offset, pidPsid>>16, true
|
||||
break
|
||||
|
||||
} else if pidPsid == microsoftSymbolEncoding ||
|
||||
pidPsid == microsoftUCS2Encoding ||
|
||||
pidPsid == microsoftUCS4Encoding {
|
||||
|
||||
bestOffset, bestPID, ok = offset, pidPsid>>16, true
|
||||
// We don't break out of the for loop, so that Unicode can override Microsoft.
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return 0, 0, UnsupportedError(name + " encoding")
|
||||
}
|
||||
return bestOffset, bestPID, nil
|
||||
}
|
||||
|
||||
const (
|
||||
locaOffsetFormatUnknown int = iota
|
||||
locaOffsetFormatShort
|
||||
locaOffsetFormatLong
|
||||
)
|
||||
|
||||
// A cm holds a parsed cmap entry.
|
||||
type cm struct {
|
||||
start, end, delta, offset uint32
|
||||
}
|
||||
|
||||
// A Font represents a Truetype font.
|
||||
type Font struct {
|
||||
// Tables sliced from the TTF data. The different tables are documented
|
||||
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
|
||||
cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte
|
||||
|
||||
cmapIndexes []byte
|
||||
|
||||
// Cached values derived from the raw ttf data.
|
||||
cm []cm
|
||||
locaOffsetFormat int
|
||||
nGlyph, nHMetric, nKern int
|
||||
fUnitsPerEm int32
|
||||
ascent int32 // In FUnits.
|
||||
descent int32 // In FUnits; typically negative.
|
||||
bounds fixed.Rectangle26_6 // In FUnits.
|
||||
// Values from the maxp section.
|
||||
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
|
||||
}
|
||||
|
||||
func (f *Font) parseCmap() error {
|
||||
const (
|
||||
cmapFormat4 = 4
|
||||
cmapFormat12 = 12
|
||||
languageIndependent = 0
|
||||
)
|
||||
|
||||
offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = int(u32(f.cmap, offset+4))
|
||||
if offset <= 0 || offset > len(f.cmap) {
|
||||
return FormatError("bad cmap offset")
|
||||
}
|
||||
|
||||
cmapFormat := u16(f.cmap, offset)
|
||||
switch cmapFormat {
|
||||
case cmapFormat4:
|
||||
language := u16(f.cmap, offset+4)
|
||||
if language != languageIndependent {
|
||||
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
||||
}
|
||||
segCountX2 := int(u16(f.cmap, offset+6))
|
||||
if segCountX2%2 == 1 {
|
||||
return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2))
|
||||
}
|
||||
segCount := segCountX2 / 2
|
||||
offset += 14
|
||||
f.cm = make([]cm, segCount)
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].end = uint32(u16(f.cmap, offset))
|
||||
offset += 2
|
||||
}
|
||||
offset += 2
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].start = uint32(u16(f.cmap, offset))
|
||||
offset += 2
|
||||
}
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].delta = uint32(u16(f.cmap, offset))
|
||||
offset += 2
|
||||
}
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].offset = uint32(u16(f.cmap, offset))
|
||||
offset += 2
|
||||
}
|
||||
f.cmapIndexes = f.cmap[offset:]
|
||||
return nil
|
||||
|
||||
case cmapFormat12:
|
||||
if u16(f.cmap, offset+2) != 0 {
|
||||
return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4]))
|
||||
}
|
||||
length := u32(f.cmap, offset+4)
|
||||
language := u32(f.cmap, offset+8)
|
||||
if language != languageIndependent {
|
||||
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
||||
}
|
||||
nGroups := u32(f.cmap, offset+12)
|
||||
if length != 12*nGroups+16 {
|
||||
return FormatError("inconsistent cmap length")
|
||||
}
|
||||
offset += 16
|
||||
f.cm = make([]cm, nGroups)
|
||||
for i := uint32(0); i < nGroups; i++ {
|
||||
f.cm[i].start = u32(f.cmap, offset+0)
|
||||
f.cm[i].end = u32(f.cmap, offset+4)
|
||||
f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start
|
||||
offset += 12
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat))
|
||||
}
|
||||
|
||||
func (f *Font) parseHead() error {
|
||||
if len(f.head) != 54 {
|
||||
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
|
||||
}
|
||||
f.fUnitsPerEm = int32(u16(f.head, 18))
|
||||
f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36)))
|
||||
f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38)))
|
||||
f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40)))
|
||||
f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42)))
|
||||
switch i := u16(f.head, 50); i {
|
||||
case 0:
|
||||
f.locaOffsetFormat = locaOffsetFormatShort
|
||||
case 1:
|
||||
f.locaOffsetFormat = locaOffsetFormatLong
|
||||
default:
|
||||
return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Font) parseHhea() error {
|
||||
if len(f.hhea) != 36 {
|
||||
return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea)))
|
||||
}
|
||||
f.ascent = int32(int16(u16(f.hhea, 4)))
|
||||
f.descent = int32(int16(u16(f.hhea, 6)))
|
||||
f.nHMetric = int(u16(f.hhea, 34))
|
||||
if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) {
|
||||
return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Font) parseKern() error {
|
||||
// Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says:
|
||||
// "Previous versions of the 'kern' table defined both the version and nTables fields in the header
|
||||
// as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged
|
||||
// (although AAT can sense an old kerning table and still make correct use of it). Microsoft
|
||||
// Windows still uses the older format for the 'kern' table and will not recognize the newer one.
|
||||
// Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS
|
||||
// and Windows should use the old format."
|
||||
// Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format,
|
||||
// just like the C Freetype implementation.
|
||||
if len(f.kern) == 0 {
|
||||
if f.nKern != 0 {
|
||||
return FormatError("bad kern table length")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if len(f.kern) < 18 {
|
||||
return FormatError("kern data too short")
|
||||
}
|
||||
version, offset := u16(f.kern, 0), 2
|
||||
if version != 0 {
|
||||
return UnsupportedError(fmt.Sprintf("kern version: %d", version))
|
||||
}
|
||||
|
||||
n, offset := u16(f.kern, offset), offset+2
|
||||
if n == 0 {
|
||||
return UnsupportedError("kern nTables: 0")
|
||||
}
|
||||
// TODO: support multiple subtables. In practice, almost all .ttf files
|
||||
// have only one subtable, if they have a kern table at all. But it's not
|
||||
// impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium)
|
||||
// has 3 subtables. Those subtables appear to be disjoint, rather than
|
||||
// being the same kerning pairs encoded in three different ways.
|
||||
//
|
||||
// For now, we'll use only the first subtable.
|
||||
|
||||
offset += 2 // Skip the version.
|
||||
length, offset := int(u16(f.kern, offset)), offset+2
|
||||
coverage, offset := u16(f.kern, offset), offset+2
|
||||
if coverage != 0x0001 {
|
||||
// We only support horizontal kerning.
|
||||
return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage))
|
||||
}
|
||||
f.nKern, offset = int(u16(f.kern, offset)), offset+2
|
||||
if 6*f.nKern != length-14 {
|
||||
return FormatError("bad kern table length")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Font) parseMaxp() error {
|
||||
if len(f.maxp) != 32 {
|
||||
return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp)))
|
||||
}
|
||||
f.nGlyph = int(u16(f.maxp, 4))
|
||||
f.maxTwilightPoints = u16(f.maxp, 16)
|
||||
f.maxStorage = u16(f.maxp, 18)
|
||||
f.maxFunctionDefs = u16(f.maxp, 20)
|
||||
f.maxStackElements = u16(f.maxp, 24)
|
||||
return nil
|
||||
}
|
||||
|
||||
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer.
|
||||
func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 {
|
||||
if x >= 0 {
|
||||
x += fixed.Int26_6(f.fUnitsPerEm) / 2
|
||||
} else {
|
||||
x -= fixed.Int26_6(f.fUnitsPerEm) / 2
|
||||
}
|
||||
return x / fixed.Int26_6(f.fUnitsPerEm)
|
||||
}
|
||||
|
||||
// Bounds returns the union of a Font's glyphs' bounds.
|
||||
func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 {
|
||||
b := f.bounds
|
||||
b.Min.X = f.scale(scale * b.Min.X)
|
||||
b.Min.Y = f.scale(scale * b.Min.Y)
|
||||
b.Max.X = f.scale(scale * b.Max.X)
|
||||
b.Max.Y = f.scale(scale * b.Max.Y)
|
||||
return b
|
||||
}
|
||||
|
||||
// FUnitsPerEm returns the number of FUnits in a Font's em-square's side.
|
||||
func (f *Font) FUnitsPerEm() int32 {
|
||||
return f.fUnitsPerEm
|
||||
}
|
||||
|
||||
// Index returns a Font's index for the given rune.
|
||||
func (f *Font) Index(x rune) Index {
|
||||
c := uint32(x)
|
||||
for i, j := 0, len(f.cm); i < j; {
|
||||
h := i + (j-i)/2
|
||||
cm := &f.cm[h]
|
||||
if c < cm.start {
|
||||
j = h
|
||||
} else if cm.end < c {
|
||||
i = h + 1
|
||||
} else if cm.offset == 0 {
|
||||
return Index(c + cm.delta)
|
||||
} else {
|
||||
offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start))
|
||||
return Index(u16(f.cmapIndexes, offset))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Name returns the Font's name value for the given NameID. It returns "" if
|
||||
// there was an error, or if that name was not found.
|
||||
func (f *Font) Name(id NameID) string {
|
||||
x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool {
|
||||
return NameID(u16(b, 6)) == id
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8)
|
||||
// Return the ASCII value of the encoded string.
|
||||
// The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1.
|
||||
src := f.name[offset : offset+length]
|
||||
var dst []byte
|
||||
if platformID != 1 { // UTF-16.
|
||||
if len(src)&1 != 0 {
|
||||
return ""
|
||||
}
|
||||
dst = make([]byte, len(src)/2)
|
||||
for i := range dst {
|
||||
dst[i] = printable(u16(src, 2*i))
|
||||
}
|
||||
} else { // ASCII.
|
||||
dst = make([]byte, len(src))
|
||||
for i, c := range src {
|
||||
dst[i] = printable(uint16(c))
|
||||
}
|
||||
}
|
||||
return string(dst)
|
||||
}
|
||||
|
||||
func printable(r uint16) byte {
|
||||
if 0x20 <= r && r < 0x7f {
|
||||
return byte(r)
|
||||
}
|
||||
return '?'
|
||||
}
|
||||
|
||||
// unscaledHMetric returns the unscaled horizontal metrics for the glyph with
|
||||
// the given index.
|
||||
func (f *Font) unscaledHMetric(i Index) (h HMetric) {
|
||||
j := int(i)
|
||||
if j < 0 || f.nGlyph <= j {
|
||||
return HMetric{}
|
||||
}
|
||||
if j >= f.nHMetric {
|
||||
p := 4 * (f.nHMetric - 1)
|
||||
return HMetric{
|
||||
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)),
|
||||
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))),
|
||||
}
|
||||
}
|
||||
return HMetric{
|
||||
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)),
|
||||
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))),
|
||||
}
|
||||
}
|
||||
|
||||
// HMetric returns the horizontal metrics for the glyph with the given index.
|
||||
func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric {
|
||||
h := f.unscaledHMetric(i)
|
||||
h.AdvanceWidth = f.scale(scale * h.AdvanceWidth)
|
||||
h.LeftSideBearing = f.scale(scale * h.LeftSideBearing)
|
||||
return h
|
||||
}
|
||||
|
||||
// unscaledVMetric returns the unscaled vertical metrics for the glyph with
|
||||
// the given index. yMax is the top of the glyph's bounding box.
|
||||
func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) {
|
||||
j := int(i)
|
||||
if j < 0 || f.nGlyph <= j {
|
||||
return VMetric{}
|
||||
}
|
||||
if 4*j+4 <= len(f.vmtx) {
|
||||
return VMetric{
|
||||
AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)),
|
||||
TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))),
|
||||
}
|
||||
}
|
||||
// The OS/2 table has grown over time.
|
||||
// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html
|
||||
// says that it was originally 68 bytes. Optional fields, including
|
||||
// the ascender and descender, are described at
|
||||
// http://www.microsoft.com/typography/otspec/os2.htm
|
||||
if len(f.os2) >= 72 {
|
||||
sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68)))
|
||||
sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70)))
|
||||
return VMetric{
|
||||
AdvanceHeight: sTypoAscender - sTypoDescender,
|
||||
TopSideBearing: sTypoAscender - yMax,
|
||||
}
|
||||
}
|
||||
return VMetric{
|
||||
AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm),
|
||||
TopSideBearing: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// VMetric returns the vertical metrics for the glyph with the given index.
|
||||
func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric {
|
||||
// TODO: should 0 be bounds.YMax?
|
||||
v := f.unscaledVMetric(i, 0)
|
||||
v.AdvanceHeight = f.scale(scale * v.AdvanceHeight)
|
||||
v.TopSideBearing = f.scale(scale * v.TopSideBearing)
|
||||
return v
|
||||
}
|
||||
|
||||
// Kern returns the horizontal adjustment for the given glyph pair. A positive
|
||||
// kern means to move the glyphs further apart.
|
||||
func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 {
|
||||
if f.nKern == 0 {
|
||||
return 0
|
||||
}
|
||||
g := uint32(i0)<<16 | uint32(i1)
|
||||
lo, hi := 0, f.nKern
|
||||
for lo < hi {
|
||||
i := (lo + hi) / 2
|
||||
ig := u32(f.kern, 18+6*i)
|
||||
if ig < g {
|
||||
lo = i + 1
|
||||
} else if ig > g {
|
||||
hi = i
|
||||
} else {
|
||||
return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i))))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Parse returns a new Font for the given TTF or TTC data.
|
||||
//
|
||||
// For TrueType Collections, the first font in the collection is parsed.
|
||||
func Parse(ttf []byte) (font *Font, err error) {
|
||||
return parse(ttf, 0)
|
||||
}
|
||||
|
||||
func parse(ttf []byte, offset int) (font *Font, err error) {
|
||||
if len(ttf)-offset < 12 {
|
||||
err = FormatError("TTF data is too short")
|
||||
return
|
||||
}
|
||||
originalOffset := offset
|
||||
magic, offset := u32(ttf, offset), offset+4
|
||||
switch magic {
|
||||
case 0x00010000:
|
||||
// No-op.
|
||||
case 0x74746366: // "ttcf" as a big-endian uint32.
|
||||
if originalOffset != 0 {
|
||||
err = FormatError("recursive TTC")
|
||||
return
|
||||
}
|
||||
ttcVersion, offset := u32(ttf, offset), offset+4
|
||||
if ttcVersion != 0x00010000 && ttcVersion != 0x00020000 {
|
||||
err = FormatError("bad TTC version")
|
||||
return
|
||||
}
|
||||
numFonts, offset := int(u32(ttf, offset)), offset+4
|
||||
if numFonts <= 0 {
|
||||
err = FormatError("bad number of TTC fonts")
|
||||
return
|
||||
}
|
||||
if len(ttf[offset:])/4 < numFonts {
|
||||
err = FormatError("TTC offset table is too short")
|
||||
return
|
||||
}
|
||||
// TODO: provide an API to select which font in a TrueType collection to return,
|
||||
// not just the first one. This may require an API to parse a TTC's name tables,
|
||||
// so users of this package can select the font in a TTC by name.
|
||||
offset = int(u32(ttf, offset))
|
||||
if offset <= 0 || offset > len(ttf) {
|
||||
err = FormatError("bad TTC offset")
|
||||
return
|
||||
}
|
||||
return parse(ttf, offset)
|
||||
default:
|
||||
err = FormatError("bad TTF version")
|
||||
return
|
||||
}
|
||||
n, offset := int(u16(ttf, offset)), offset+2
|
||||
offset += 6 // Skip the searchRange, entrySelector and rangeShift.
|
||||
if len(ttf) < 16*n+offset {
|
||||
err = FormatError("TTF data is too short")
|
||||
return
|
||||
}
|
||||
f := new(Font)
|
||||
// Assign the table slices.
|
||||
for i := 0; i < n; i++ {
|
||||
x := 16*i + offset
|
||||
switch string(ttf[x : x+4]) {
|
||||
case "cmap":
|
||||
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "cvt ":
|
||||
f.cvt, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "fpgm":
|
||||
f.fpgm, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "glyf":
|
||||
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "hdmx":
|
||||
f.hdmx, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "head":
|
||||
f.head, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "hhea":
|
||||
f.hhea, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "hmtx":
|
||||
f.hmtx, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "kern":
|
||||
f.kern, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "loca":
|
||||
f.loca, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "maxp":
|
||||
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "name":
|
||||
f.name, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "OS/2":
|
||||
f.os2, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "prep":
|
||||
f.prep, err = readTable(ttf, ttf[x+8:x+16])
|
||||
case "vmtx":
|
||||
f.vmtx, err = readTable(ttf, ttf[x+8:x+16])
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Parse and sanity-check the TTF data.
|
||||
if err = f.parseHead(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.parseMaxp(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.parseCmap(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.parseKern(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.parseHhea(); err != nil {
|
||||
return
|
||||
}
|
||||
font = f
|
||||
return
|
||||
}
|
||||
20
src/server/vendor/github.com/vicanso/go-charts/v2/.gitignore
generated
vendored
Normal file
20
src/server/vendor/github.com/vicanso/go-charts/v2/.gitignore
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
*.png
|
||||
*.svg
|
||||
tmp
|
||||
NotoSansSC.ttf
|
||||
.vscode
|
||||
21
src/server/vendor/github.com/vicanso/go-charts/v2/LICENSE
generated
vendored
Normal file
21
src/server/vendor/github.com/vicanso/go-charts/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Tree Xie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
20
src/server/vendor/github.com/vicanso/go-charts/v2/Makefile
generated
vendored
Normal file
20
src/server/vendor/github.com/vicanso/go-charts/v2/Makefile
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
export GO111MODULE = on
|
||||
|
||||
.PHONY: default test test-cover dev hooks
|
||||
|
||||
|
||||
# for test
|
||||
test:
|
||||
go test -race -cover ./...
|
||||
|
||||
test-cover:
|
||||
go test -race -coverprofile=test.out ./... && go tool cover --html=test.out
|
||||
|
||||
bench:
|
||||
go test --benchmem -bench=. ./...
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
hooks:
|
||||
cp hooks/* .git/hooks/
|
||||
540
src/server/vendor/github.com/vicanso/go-charts/v2/README.md
generated
vendored
Normal file
540
src/server/vendor/github.com/vicanso/go-charts/v2/README.md
generated
vendored
Normal file
@ -0,0 +1,540 @@
|
||||
# go-charts
|
||||
|
||||
[](https://github.com/vicanso/go-charts/blob/master/LICENSE)
|
||||
[](https://github.com/vicanso/go-charts/actions)
|
||||
|
||||
[中文](./README_zh.md)
|
||||
|
||||
`go-charts` base on [go-chart](https://github.com/wcharczuk/go-chart),it is simpler way for generating charts, which supports `svg` and `png` format and themes: `light`, `dark`, `grafana` and `ant`. The default format is `png` and the default theme is `light`.
|
||||
|
||||
`Apache ECharts` is popular among Front-end developers, so `go-charts` supports the option of `Apache ECharts`. Developers can generate charts almost the same as `Apache ECharts`.
|
||||
|
||||
Screenshot of common charts, the left part is light theme, the right part is grafana theme.
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/go-charts.png" alt="go-charts">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/go-table.png" alt="go-table">
|
||||
</p>
|
||||
|
||||
## Chart Type
|
||||
|
||||
These chart types are supported: `line`, `bar`, `horizontal bar`, `pie`, `radar` or `funnel` and `table`.
|
||||
|
||||
## Example
|
||||
|
||||
More examples can be found in the [./examples/](./examples/) directory.
|
||||
|
||||
|
||||
### Line Chart
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
charts "github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
120,
|
||||
132,
|
||||
101,
|
||||
134,
|
||||
90,
|
||||
230,
|
||||
210,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.LineRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Line"),
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
"Direct",
|
||||
"Search Engine",
|
||||
}, charts.PositionCenter),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Bar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
2.0,
|
||||
4.9,
|
||||
7.0,
|
||||
23.2,
|
||||
25.6,
|
||||
76.7,
|
||||
135.6,
|
||||
162.2,
|
||||
32.6,
|
||||
20.0,
|
||||
6.4,
|
||||
3.3,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.BarRender(
|
||||
values,
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Rainfall",
|
||||
"Evaporation",
|
||||
}, charts.PositionRight),
|
||||
charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage),
|
||||
charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax,
|
||||
charts.SeriesMarkDataTypeMin),
|
||||
// custom option func
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.SeriesList[1].MarkPoint = charts.NewMarkPoint(
|
||||
charts.SeriesMarkDataTypeMax,
|
||||
charts.SeriesMarkDataTypeMin,
|
||||
)
|
||||
opt.SeriesList[1].MarkLine = charts.NewMarkLine(
|
||||
charts.SeriesMarkDataTypeAverage,
|
||||
)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Horizontal Bar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
18203,
|
||||
23489,
|
||||
29034,
|
||||
104970,
|
||||
131744,
|
||||
630230,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.HorizontalBarRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("World Population"),
|
||||
charts.PaddingOptionFunc(charts.Box{
|
||||
Top: 20,
|
||||
Right: 40,
|
||||
Bottom: 20,
|
||||
Left: 20,
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"2011",
|
||||
"2012",
|
||||
}),
|
||||
charts.YAxisDataOptionFunc([]string{
|
||||
"Brazil",
|
||||
"Indonesia",
|
||||
"USA",
|
||||
"India",
|
||||
"China",
|
||||
"World",
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Pie Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := []float64{
|
||||
1048,
|
||||
735,
|
||||
580,
|
||||
484,
|
||||
300,
|
||||
}
|
||||
p, err := charts.PieRender(
|
||||
values,
|
||||
charts.TitleOptionFunc(charts.TitleOption{
|
||||
Text: "Rainfall vs Evaporation",
|
||||
Subtext: "Fake Data",
|
||||
Left: charts.PositionCenter,
|
||||
}),
|
||||
charts.PaddingOptionFunc(charts.Box{
|
||||
Top: 20,
|
||||
Right: 20,
|
||||
Bottom: 20,
|
||||
Left: 20,
|
||||
}),
|
||||
charts.LegendOptionFunc(charts.LegendOption{
|
||||
Orient: charts.OrientVertical,
|
||||
Data: []string{
|
||||
"Search Engine",
|
||||
"Direct",
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
},
|
||||
Left: charts.PositionLeft,
|
||||
}),
|
||||
charts.PieSeriesShowLabel(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Radar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
4200,
|
||||
3000,
|
||||
20000,
|
||||
35000,
|
||||
50000,
|
||||
18000,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.RadarRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Basic Radar Chart"),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Allocated Budget",
|
||||
"Actual Spending",
|
||||
}),
|
||||
charts.RadarIndicatorOptionFunc([]string{
|
||||
"Sales",
|
||||
"Administration",
|
||||
"Information Technology",
|
||||
"Customer Support",
|
||||
"Development",
|
||||
"Marketing",
|
||||
}, []float64{
|
||||
6500,
|
||||
16000,
|
||||
30000,
|
||||
38000,
|
||||
52000,
|
||||
25000,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Funnel Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := []float64{
|
||||
100,
|
||||
80,
|
||||
60,
|
||||
40,
|
||||
20,
|
||||
}
|
||||
p, err := charts.FunnelRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Funnel"),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Show",
|
||||
"Click",
|
||||
"Visit",
|
||||
"Inquiry",
|
||||
"Order",
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Table
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
header := []string{
|
||||
"Name",
|
||||
"Age",
|
||||
"Address",
|
||||
"Tag",
|
||||
"Action",
|
||||
}
|
||||
data := [][]string{
|
||||
{
|
||||
"John Brown",
|
||||
"32",
|
||||
"New York No. 1 Lake Park",
|
||||
"nice, developer",
|
||||
"Send Mail",
|
||||
},
|
||||
{
|
||||
"Jim Green ",
|
||||
"42",
|
||||
"London No. 1 Lake Park",
|
||||
"wow",
|
||||
"Send Mail",
|
||||
},
|
||||
{
|
||||
"Joe Black ",
|
||||
"32",
|
||||
"Sidney No. 1 Lake Park",
|
||||
"cool, teacher",
|
||||
"Send Mail",
|
||||
},
|
||||
}
|
||||
spans := map[int]int{
|
||||
0: 2,
|
||||
1: 1,
|
||||
// 设置第三列的span
|
||||
2: 3,
|
||||
3: 2,
|
||||
4: 2,
|
||||
}
|
||||
p, err := charts.TableRender(
|
||||
header,
|
||||
data,
|
||||
spans,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### ECharts Render
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buf, err := charts.RenderEChartsToPNG(`{
|
||||
"title": {
|
||||
"text": "Line"
|
||||
},
|
||||
"xAxis": {
|
||||
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"data": [150, 230, 224, 218, 135, 147, 260]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
## ECharts Option
|
||||
|
||||
The name with `[]` is new parameter, others are the same as `echarts`.
|
||||
|
||||
- `[type]` The canvas type, support `svg` and `png`, default is `svg`
|
||||
- `[theme]` The theme, support `dark`, `light` and `grafana`, default is `light`
|
||||
- `[fontFamily]` The font family for chart
|
||||
- `[padding]` The padding of chart
|
||||
- `[box]` The canvas box of chart
|
||||
- `[width]` The width of chart
|
||||
- `[height]` The height of chart
|
||||
- `title` Title component, including main title and subtitle
|
||||
- `title.text` The main title text, supporting for \n for newlines
|
||||
- `title.subtext`Subtitle text, supporting for \n for newlines
|
||||
- `title.left` Distance between title component and the left side of the container. Left value can be instant pixel value like 20; it can also be a percentage value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'.
|
||||
- `title.top` Distance between title component and the top side of the container. Top value can be instant pixel value like 20
|
||||
- `title.textStyle.color` Text color for title
|
||||
- `title.textStyle.fontSize` Text font size for title
|
||||
- `title.textStyle.fontFamily` Text font family for title, it will change the font family for chart
|
||||
- `xAxis` The x axis in cartesian(rectangular) coordinate. `go-charts` only support one x axis.
|
||||
- `xAxis.boundaryGap` The boundary gap on both sides of a coordinate axis. The setting and behavior of category axes and non-category axes are different. If set `null` or `true`, the label appear in the center part of two axis ticks.
|
||||
- `xAxis.splitNumber` Number of segments that the axis is split into. Note that this number serves only as a recommendation, and the true segments may be adjusted based on readability
|
||||
- `xAxis.data` Category data, only support string array.
|
||||
- `yAxis` The y axis in cartesian(rectangular) coordinate, it support 2 y axis
|
||||
- `yAxis.min` The minimum value of axis. It will be automatically computed to make sure axis tick is equally distributed when not set
|
||||
- `yAxis.max` The maximum value of axis. It will be automatically computed to make sure axis tick is equally distributed when not se.
|
||||
- `yAxis.axisLabel.formatter` Formatter of axis label, which supports string template: `"formatter": "{value} kg"`
|
||||
- `yAxis.axisLine.lineStyle.color` The color for line
|
||||
- `legend` Legend component
|
||||
- `legend.show` Whether to show legend
|
||||
- `legend.data` Data array of legend, only support string array: ["Email", "Video Ads"]
|
||||
- `legend.align` Legend marker and text aligning. Support `left` and `right`, default is `left`
|
||||
- `legend.padding` legend space around content
|
||||
- `legend.left` Distance between legend component and the left side of the container. Left value can be instant pixel value like 20; it can also be a percentage value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'.
|
||||
- `legend.top` Distance between legend component and the top side of the container. Top value can be instant pixel value like 20
|
||||
- `radar` Coordinate for radar charts
|
||||
- `radar.indicator` Indicator of radar chart, which is used to assign multiple variables(dimensions) in radar chart
|
||||
- `radar.indicator.name` Indicator's name
|
||||
- `radar.indicator.max` The maximum value of indicator
|
||||
- `radar.indicator.min` The minimum value of indicator, default value is 0.
|
||||
- `series` The series for chart
|
||||
- `series.name` Series name used for displaying in legend.
|
||||
- `series.type` Series type: `line`, `bar`, `pie`, `radar` or `funnel`
|
||||
- `series.radius` Radius of Pie chart:`50%`, default is `40%`
|
||||
- `series.yAxisIndex` Index of y axis to combine with, which is useful for multiple y axes in one chart
|
||||
- `series.label.show` Whether to show label
|
||||
- `series.label.distance` Distance to the host graphic element
|
||||
- `series.label.color` Label color
|
||||
- `series.itemStyle.color` Color for the series's item
|
||||
- `series.markPoint` Mark point in a chart.
|
||||
- `series.markPoint.symbolSize` Symbol size, default is `30`
|
||||
- `series.markPoint.data` Data array for mark points, each of which is an object and the type only support `max` and `min`: `[{"type": "max"}, {"type": "min"}]`
|
||||
- `series.markLine` Mark line in a chart
|
||||
- `series.markPoint.data` Data array for mark points, each of which is an object and the type only support `max`, `min` and `average`: `[{"type": "max"}, {"type": "min"}, {"type": "average"}]``
|
||||
- `series.data` Data array of series, which can be in the following forms:
|
||||
- `value` It's a float array: [1.1, 2,3, 5.2]
|
||||
- `object` It's a object value array: [{"value": 1048, "name": "Search Engine"},{"value": 735,"name": "Direct"}]
|
||||
- `[children]` The options of children chart
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
Generate a png chart will be less than 20ms. It's better than using `chrome headless` with `echarts`.
|
||||
|
||||
```bash
|
||||
BenchmarkMultiChartPNGRender-8 78 15216336 ns/op 2298308 B/op 1148 allocs/op
|
||||
BenchmarkMultiChartSVGRender-8 367 3356325 ns/op 20597282 B/op 3088 allocs/op
|
||||
```
|
||||
576
src/server/vendor/github.com/vicanso/go-charts/v2/README_zh.md
generated
vendored
Normal file
576
src/server/vendor/github.com/vicanso/go-charts/v2/README_zh.md
generated
vendored
Normal file
@ -0,0 +1,576 @@
|
||||
# go-charts
|
||||
|
||||
[](https://github.com/vicanso/go-charts/blob/master/LICENSE)
|
||||
[](https://github.com/vicanso/go-charts/actions)
|
||||
|
||||
`go-charts`基于[go-chart](https://github.com/wcharczuk/go-chart),更简单方便的形式生成数据图表,支持`svg`与`png`两种方式的输出,支持主题`light`, `dark`, `grafana`以及`ant`。默认的输入格式为`png`,默认主题为`light`。
|
||||
|
||||
`Apache ECharts`在前端开发中得到众多开发者的认可,因此`go-charts`提供了兼容`Apache ECharts`的配置参数,简单快捷的生成相似的图表(`svg`或`png`),方便插入至Email或分享使用。下面为常用的图表截图(主题为light与grafana):
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/go-charts.png" alt="go-charts">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/go-table.png" alt="go-table">
|
||||
</p
|
||||
|
||||
## 支持图表类型
|
||||
|
||||
支持以下的图表类型:`line`, `bar`, `horizontal bar`, `pie`, `radar`, `funnel` 以及 `table`
|
||||
|
||||
|
||||
## 示例
|
||||
|
||||
|
||||
下面的示例为`go-charts`两种方式的参数配置:golang的参数配置、echarts的JSON配置,输出相同的折线图。
|
||||
更多的示例参考:[./examples/](./examples/)目录
|
||||
|
||||
### Line Chart
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
charts "github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
120,
|
||||
132,
|
||||
101,
|
||||
134,
|
||||
90,
|
||||
230,
|
||||
210,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.LineRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Line"),
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
"Direct",
|
||||
"Search Engine",
|
||||
}, charts.PositionCenter),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Bar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
2.0,
|
||||
4.9,
|
||||
7.0,
|
||||
23.2,
|
||||
25.6,
|
||||
76.7,
|
||||
135.6,
|
||||
162.2,
|
||||
32.6,
|
||||
20.0,
|
||||
6.4,
|
||||
3.3,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.BarRender(
|
||||
values,
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Rainfall",
|
||||
"Evaporation",
|
||||
}, charts.PositionRight),
|
||||
charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage),
|
||||
charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax,
|
||||
charts.SeriesMarkDataTypeMin),
|
||||
// custom option func
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.SeriesList[1].MarkPoint = charts.NewMarkPoint(
|
||||
charts.SeriesMarkDataTypeMax,
|
||||
charts.SeriesMarkDataTypeMin,
|
||||
)
|
||||
opt.SeriesList[1].MarkLine = charts.NewMarkLine(
|
||||
charts.SeriesMarkDataTypeAverage,
|
||||
)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Horizontal Bar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
18203,
|
||||
23489,
|
||||
29034,
|
||||
104970,
|
||||
131744,
|
||||
630230,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.HorizontalBarRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("World Population"),
|
||||
charts.PaddingOptionFunc(charts.Box{
|
||||
Top: 20,
|
||||
Right: 40,
|
||||
Bottom: 20,
|
||||
Left: 20,
|
||||
}),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"2011",
|
||||
"2012",
|
||||
}),
|
||||
charts.YAxisDataOptionFunc([]string{
|
||||
"Brazil",
|
||||
"Indonesia",
|
||||
"USA",
|
||||
"India",
|
||||
"China",
|
||||
"World",
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Pie Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := []float64{
|
||||
1048,
|
||||
735,
|
||||
580,
|
||||
484,
|
||||
300,
|
||||
}
|
||||
p, err := charts.PieRender(
|
||||
values,
|
||||
charts.TitleOptionFunc(charts.TitleOption{
|
||||
Text: "Rainfall vs Evaporation",
|
||||
Subtext: "Fake Data",
|
||||
Left: charts.PositionCenter,
|
||||
}),
|
||||
charts.PaddingOptionFunc(charts.Box{
|
||||
Top: 20,
|
||||
Right: 20,
|
||||
Bottom: 20,
|
||||
Left: 20,
|
||||
}),
|
||||
charts.LegendOptionFunc(charts.LegendOption{
|
||||
Orient: charts.OrientVertical,
|
||||
Data: []string{
|
||||
"Search Engine",
|
||||
"Direct",
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
},
|
||||
Left: charts.PositionLeft,
|
||||
}),
|
||||
charts.PieSeriesShowLabel(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Radar Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := [][]float64{
|
||||
{
|
||||
4200,
|
||||
3000,
|
||||
20000,
|
||||
35000,
|
||||
50000,
|
||||
18000,
|
||||
},
|
||||
{
|
||||
// snip...
|
||||
},
|
||||
}
|
||||
p, err := charts.RadarRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Basic Radar Chart"),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Allocated Budget",
|
||||
"Actual Spending",
|
||||
}),
|
||||
charts.RadarIndicatorOptionFunc([]string{
|
||||
"Sales",
|
||||
"Administration",
|
||||
"Information Technology",
|
||||
"Customer Support",
|
||||
"Development",
|
||||
"Marketing",
|
||||
}, []float64{
|
||||
6500,
|
||||
16000,
|
||||
30000,
|
||||
38000,
|
||||
52000,
|
||||
25000,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Funnel Chart
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
values := []float64{
|
||||
100,
|
||||
80,
|
||||
60,
|
||||
40,
|
||||
20,
|
||||
}
|
||||
p, err := charts.FunnelRender(
|
||||
values,
|
||||
charts.TitleTextOptionFunc("Funnel"),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Show",
|
||||
"Click",
|
||||
"Visit",
|
||||
"Inquiry",
|
||||
"Order",
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
### Table
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
header := []string{
|
||||
"Name",
|
||||
"Age",
|
||||
"Address",
|
||||
"Tag",
|
||||
"Action",
|
||||
}
|
||||
data := [][]string{
|
||||
{
|
||||
"John Brown",
|
||||
"32",
|
||||
"New York No. 1 Lake Park",
|
||||
"nice, developer",
|
||||
"Send Mail",
|
||||
},
|
||||
{
|
||||
"Jim Green ",
|
||||
"42",
|
||||
"London No. 1 Lake Park",
|
||||
"wow",
|
||||
"Send Mail",
|
||||
},
|
||||
{
|
||||
"Joe Black ",
|
||||
"32",
|
||||
"Sidney No. 1 Lake Park",
|
||||
"cool, teacher",
|
||||
"Send Mail",
|
||||
},
|
||||
}
|
||||
spans := map[int]int{
|
||||
0: 2,
|
||||
1: 1,
|
||||
// 设置第三列的span
|
||||
2: 3,
|
||||
3: 2,
|
||||
4: 2,
|
||||
}
|
||||
p, err := charts.TableRender(
|
||||
header,
|
||||
data,
|
||||
spans,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
### ECharts Render
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buf, err := charts.RenderEChartsToPNG(`{
|
||||
"title": {
|
||||
"text": "Line"
|
||||
},
|
||||
"xAxis": {
|
||||
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"data": [150, 230, 224, 218, 135, 147, 260]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
// snip...
|
||||
}
|
||||
```
|
||||
|
||||
## 常用函数
|
||||
|
||||
`go-charts`针对常用的几种图表提供了简单的调用方式以及几种常用的Option设置,便捷的生成常用图表。
|
||||
|
||||
- `LineRender`: 折线图表,第一个参数为二维浮点数,对应图表中的点,支持不定长的OptionFunc参数,用于指定其它的属性
|
||||
- `BarRender`: 柱状图表,第一个参数为二维浮点数,对应柱状图的高度,支持不定长的OptionFunc参数,用于指定其它的属性
|
||||
- `PieRender`: 饼图表,第一个参数为浮点数数组,对应各占比,支持不定长的OptionFunc参数,用于指定其它的属性
|
||||
- `RadarRender`: 雷达图,第一个参数为二维浮点数,对应雷达图中的各值,支持不定长的OptionFunc参数,用于指定其它的属性
|
||||
- `FunnelRender`: 漏斗图,第一个参数为浮点数数组,对应各占比,支持不定长的OptionFunc参数,用于指定其它的属性
|
||||
- `PNGTypeOption`: 指定输出PNG
|
||||
- `FontFamilyOptionFunc`: 指定使用的字体
|
||||
- `ThemeOptionFunc`: 指定使用的主题类型
|
||||
- `TitleOptionFunc`: 指定标题相关属性
|
||||
- `LegendOptionFunc`: 指定图例相关属性
|
||||
- `XAxisOptionFunc`: 指定x轴的相关属性
|
||||
- `YAxisOptionFunc`: 指定y轴的相关属性
|
||||
- `WidthOptionFunc`: 指定宽度
|
||||
- `HeightOptionFunc`: 指定高度
|
||||
- `PaddingOptionFunc`: 指定空白填充区域
|
||||
- `BoxOptionFunc`: 指定内容区域
|
||||
- `ChildOptionFunc`: 指定子图表
|
||||
- `RadarIndicatorOptionFunc`: 雷达图指示器相关属性
|
||||
- `BackgroundColorOptionFunc`: 设置背景图颜色
|
||||
|
||||
## ECharts参数说明
|
||||
|
||||
名称有[]的参数非echarts的原有参数,为`go-charts`的新增参数,可根据实际使用场景添加。
|
||||
|
||||
- `[type]` 画布类型,支持`svg`与`png`,默认为`svg`
|
||||
- `[theme]` 颜色主题,支持`dark`、`light`以及`grafana`模式,默认为`light`
|
||||
- `[fontFamily]` 字体,全局的字体设置
|
||||
- `[padding]` 图表的内边距,单位px。支持以下几种模式的设置
|
||||
- `padding: 5` 设置内边距为5
|
||||
- `padding: [5, 10]` 设置上下的内边距为 5,左右的内边距为 10
|
||||
- `padding:[5, 10, 5, 10]` 分别设置`上右下左`边距
|
||||
- `[box]` 图表的区域,以{"left": Int, "right": Int, "top": Int, "bottom": Int}的形式配置
|
||||
- `[width]` 画布宽度,默认为600
|
||||
- `[height]` 画布高度,默认为400
|
||||
- `title` 图表标题,包括标题内容、高度、颜色等
|
||||
- `title.text` 标题文本,支持以`\n`的形式换行
|
||||
- `title.subtext` 副标题文本,支持以`\n`的形式换行
|
||||
- `title.left` 标题与容器左侧的距离,可设置为`left`, `right`, `center`, `20%` 以及 `20` 这样的具体数值
|
||||
- `title.top` 标题与容器顶部的距离,暂仅支持具体数值,如`20`
|
||||
- `title.textStyle.color` 标题文字颜色
|
||||
- `title.textStyle.fontSize` 标题文字字体大小
|
||||
- `title.textStyle.fontFamily` 标题文字的字体系列,需要注意此配置是会影响整个图表的字体
|
||||
- `xAxis` 直角坐标系grid中的x轴,由于go-charts仅支持单一个x轴,因此若参数为数组多个x轴,只使用第一个配置
|
||||
- `xAxis.boundaryGap` 坐标轴两边留白策略,仅支持三种设置方式`null`, `true`或者`false`。`null`或`true`时则数据点展示在两个刻度中间
|
||||
- `xAxis.splitNumber` 坐标轴的分割段数,需要注意的是这个分割段数只是个预估值,最后实际显示的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整
|
||||
- `xAxis.data` x轴的展示文案,暂只支持字符串数组,如["Mon", "Tue"],其数量需要与展示点一致
|
||||
- `yAxis` 直角坐标系grid中的y轴,最多支持两个y轴
|
||||
- `yAxis.min` 坐标轴刻度最小值,若不设置则自动计算
|
||||
- `yAxis.max` 坐标轴刻度最大值,若不设置则自动计算
|
||||
- `yAxis.axisLabel.formatter` 刻度标签的内容格式器,如`"formatter": "{value} kg"`
|
||||
- `yAxis.axisLine.lineStyle.color` 坐标轴颜色
|
||||
- `legend` 图表中不同系列的标记
|
||||
- `legend.show` 图例是否显示,如果不需要展示需要设置为`false`
|
||||
- `legend.data` 图例的数据数组,为字符串数组,如["Email", "Video Ads"]
|
||||
- `legend.align` 图例标记和文本的对齐,可设置为`left`或者`right`,默认为标记靠左`left`
|
||||
- `legend.padding` legend的padding,配置方式与图表的`padding`一致
|
||||
- `legend.left` legend离容器左侧的距离,其值可以为具体的像素值(20)或百分比(20%)、`left`或者`right`
|
||||
- `legend.top` legend离容器顶部的距离,暂仅支持数值形式
|
||||
- `radar` 雷达图的坐标系
|
||||
- `radar.indicator` 雷达图的指示器,用来指定雷达图中的多个变量(维度)
|
||||
- `radar.indicator.name` 指示器名称
|
||||
- `radar.indicator.max` 指示器的最大值,可选,建议设置
|
||||
- `radar.indicator.min` 指示器的最小值,可选,默认为 0
|
||||
- `series` 图表的数据项列表
|
||||
- `series.name` 图表的名称,与`legend.data`对应,两者只只设置其一
|
||||
- `series.type` 图表的展示类型,暂支持`line`, `bar`, `pie`, `radar` 以及 `funnel`。需要注意只有`line`与`bar`可以混用
|
||||
- `series.radius` 饼图的半径值,如`50%`,默认为`40%`
|
||||
- `series.yAxisIndex` 该数据项使用的y轴,默认为0,对yAxis的配置对应
|
||||
- `series.label.show` 是否显示文本标签(默认为对应的值)
|
||||
- `series.label.distance` 距离图形元素的距离
|
||||
- `series.label.color` 文本标签的颜色
|
||||
- `series.itemStyle.color` 该数据项展示时使用的颜色
|
||||
- `series.markPoint` 图表的标注配置
|
||||
- `series.markPoint.symbolSize` 标注的大小,默认为30
|
||||
- `series.markPoint.data` 标注类型,仅支持数组形式,其类型只支持`max`与`min`,如:`[{"type": "max"}, {"type": "min"}]
|
||||
- `series.markLine` 图表的标线配置
|
||||
- `series.markPoint.data` 标线类型,仅支持数组形式,其类型只支持`max`、`min`以及`average`,如:`[{"type": "max"}, {"type": "min"}, {"type": "average"}]
|
||||
- `series.data` 数据项对应的数据数组,支持以下形式的数据:
|
||||
- `数值` 常用形式,数组数据为浮点数组,如[1.1, 2,3, 5.2]
|
||||
- `结构体` pie图表或bar图表中指定样式使用,如[{"value": 1048, "name": "Search Engine"},{"value": 735,"name": "Direct"}]
|
||||
- `[children]` 嵌套的子图表参数列表,图表支持嵌套的形式=
|
||||
|
||||
## 性能
|
||||
|
||||
|
||||
简单的图表生成PNG在20ms左右,而SVG的性能则更快,性能上比起使用`chrome headless`加载`echarts`图表展示页面再截图生成的方式大幅度提升,满足简单的图表生成需求。
|
||||
|
||||
```bash
|
||||
BenchmarkMultiChartPNGRender-8 78 15216336 ns/op 2298308 B/op 1148 allocs/op
|
||||
BenchmarkMultiChartSVGRender-8 367 3356325 ns/op 20597282 B/op 3088 allocs/op
|
||||
```
|
||||
|
||||
## 中文字符
|
||||
|
||||
默认使用的字符为`roboto`为英文字体库,因此如果需要显示中文字符需要增加中文字体库,`InstallFont`函数可添加对应的字体库,成功添加之后则指定`title.textStyle.fontFamily`即可。
|
||||
在浏览器中使用`svg`时,如果指定的`fontFamily`不支持中文字符,展示的中文并不会乱码,但是会导致在计算字符宽度等错误。
|
||||
|
||||
字体文件可以在[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk)下载,注意下载时选择字体格式为 `ttf` 格式,如果选用 `otf` 格式可能会加载失败,字体尽量选择Bold类型,否则生成的图片会有点模糊。
|
||||
|
||||
|
||||
示例见 [examples/chinese/main.go](examples/chinese/main.go)
|
||||
|
||||
73
src/server/vendor/github.com/vicanso/go-charts/v2/alias.go
generated
vendored
Normal file
73
src/server/vendor/github.com/vicanso/go-charts/v2/alias.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
type Box = chart.Box
|
||||
type Style = chart.Style
|
||||
type Color = drawing.Color
|
||||
|
||||
var BoxZero = chart.BoxZero
|
||||
|
||||
type Point struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
const (
|
||||
ChartTypeLine = "line"
|
||||
ChartTypeBar = "bar"
|
||||
ChartTypePie = "pie"
|
||||
ChartTypeRadar = "radar"
|
||||
ChartTypeFunnel = "funnel"
|
||||
// horizontal bar
|
||||
ChartTypeHorizontalBar = "horizontalBar"
|
||||
)
|
||||
|
||||
const (
|
||||
ChartOutputSVG = "svg"
|
||||
ChartOutputPNG = "png"
|
||||
)
|
||||
|
||||
const (
|
||||
PositionLeft = "left"
|
||||
PositionRight = "right"
|
||||
PositionCenter = "center"
|
||||
PositionTop = "top"
|
||||
PositionBottom = "bottom"
|
||||
)
|
||||
|
||||
const (
|
||||
AlignLeft = "left"
|
||||
AlignRight = "right"
|
||||
AlignCenter = "center"
|
||||
)
|
||||
|
||||
const (
|
||||
OrientHorizontal = "horizontal"
|
||||
OrientVertical = "vertical"
|
||||
)
|
||||
339
src/server/vendor/github.com/vicanso/go-charts/v2/axis.go
generated
vendored
Normal file
339
src/server/vendor/github.com/vicanso/go-charts/v2/axis.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type axisPainter struct {
|
||||
p *Painter
|
||||
opt *AxisOption
|
||||
}
|
||||
|
||||
func NewAxisPainter(p *Painter, opt AxisOption) *axisPainter {
|
||||
return &axisPainter{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type AxisOption struct {
|
||||
// The theme of chart
|
||||
Theme ColorPalette
|
||||
// Formatter for y axis text value
|
||||
Formatter string
|
||||
// The label of axis
|
||||
Data []string
|
||||
// The boundary gap on both sides of a coordinate axis.
|
||||
// Nil or *true means the center part of two axis ticks
|
||||
BoundaryGap *bool
|
||||
// The flag for show axis, set this to *false will hide axis
|
||||
Show *bool
|
||||
// The position of axis, it can be 'left', 'top', 'right' or 'bottom'
|
||||
Position string
|
||||
// Number of segments that the axis is split into. Note that this number serves only as a recommendation.
|
||||
SplitNumber int
|
||||
// The line color of axis
|
||||
StrokeColor Color
|
||||
// The line width
|
||||
StrokeWidth float64
|
||||
// The length of the axis tick
|
||||
TickLength int
|
||||
// The first axis
|
||||
FirstAxis int
|
||||
// The margin value of label
|
||||
LabelMargin int
|
||||
// The font size of label
|
||||
FontSize float64
|
||||
// The font of label
|
||||
Font *truetype.Font
|
||||
// The color of label
|
||||
FontColor Color
|
||||
// The flag for show axis split line, set this to true will show axis split line
|
||||
SplitLineShow bool
|
||||
// The color of split line
|
||||
SplitLineColor Color
|
||||
// The text rotation of label
|
||||
TextRotation float64
|
||||
// The offset of label
|
||||
LabelOffset Box
|
||||
Unit int
|
||||
}
|
||||
|
||||
func (a *axisPainter) Render() (Box, error) {
|
||||
opt := a.opt
|
||||
top := a.p
|
||||
theme := opt.Theme
|
||||
if theme == nil {
|
||||
theme = top.theme
|
||||
}
|
||||
if isFalse(opt.Show) {
|
||||
return BoxZero, nil
|
||||
}
|
||||
|
||||
strokeWidth := opt.StrokeWidth
|
||||
if strokeWidth == 0 {
|
||||
strokeWidth = 1
|
||||
}
|
||||
|
||||
font := opt.Font
|
||||
if font == nil {
|
||||
font = a.p.font
|
||||
}
|
||||
if font == nil {
|
||||
font = theme.GetFont()
|
||||
}
|
||||
fontColor := opt.FontColor
|
||||
if fontColor.IsZero() {
|
||||
fontColor = theme.GetTextColor()
|
||||
}
|
||||
fontSize := opt.FontSize
|
||||
if fontSize == 0 {
|
||||
fontSize = theme.GetFontSize()
|
||||
}
|
||||
strokeColor := opt.StrokeColor
|
||||
if strokeColor.IsZero() {
|
||||
strokeColor = theme.GetAxisStrokeColor()
|
||||
}
|
||||
|
||||
data := opt.Data
|
||||
formatter := opt.Formatter
|
||||
if len(formatter) != 0 {
|
||||
for index, text := range data {
|
||||
data[index] = strings.ReplaceAll(formatter, "{value}", text)
|
||||
}
|
||||
}
|
||||
dataCount := len(data)
|
||||
tickCount := dataCount
|
||||
|
||||
boundaryGap := true
|
||||
if isFalse(opt.BoundaryGap) {
|
||||
boundaryGap = false
|
||||
}
|
||||
isVertical := opt.Position == PositionLeft ||
|
||||
opt.Position == PositionRight
|
||||
|
||||
labelPosition := ""
|
||||
if !boundaryGap {
|
||||
tickCount--
|
||||
labelPosition = PositionLeft
|
||||
}
|
||||
if isVertical && boundaryGap {
|
||||
labelPosition = PositionCenter
|
||||
}
|
||||
|
||||
// 如果小于0,则表示不处理
|
||||
tickLength := getDefaultInt(opt.TickLength, 5)
|
||||
labelMargin := getDefaultInt(opt.LabelMargin, 5)
|
||||
|
||||
style := Style{
|
||||
StrokeColor: strokeColor,
|
||||
StrokeWidth: strokeWidth,
|
||||
Font: font,
|
||||
FontColor: fontColor,
|
||||
FontSize: fontSize,
|
||||
}
|
||||
top.SetDrawingStyle(style).OverrideTextStyle(style)
|
||||
|
||||
isTextRotation := opt.TextRotation != 0
|
||||
|
||||
if isTextRotation {
|
||||
top.SetTextRotation(opt.TextRotation)
|
||||
}
|
||||
textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data)
|
||||
if isTextRotation {
|
||||
top.ClearTextRotation()
|
||||
}
|
||||
|
||||
// 增加30px来计算文本展示区域
|
||||
textFillWidth := float64(textMaxWidth + 20)
|
||||
// 根据文本宽度计算较为符合的展示项
|
||||
fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth)
|
||||
|
||||
unit := opt.Unit
|
||||
if unit <= 0 {
|
||||
|
||||
unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount))
|
||||
unit = chart.MaxInt(unit, opt.SplitNumber)
|
||||
// 偶数
|
||||
if unit%2 == 0 && dataCount%(unit+1) == 0 {
|
||||
unit++
|
||||
}
|
||||
}
|
||||
|
||||
width := 0
|
||||
height := 0
|
||||
// 垂直
|
||||
if isVertical {
|
||||
width = textMaxWidth + tickLength<<1
|
||||
height = top.Height()
|
||||
} else {
|
||||
width = top.Width()
|
||||
height = tickLength<<1 + textMaxHeight
|
||||
}
|
||||
padding := Box{}
|
||||
switch opt.Position {
|
||||
case PositionTop:
|
||||
padding.Top = top.Height() - height
|
||||
case PositionLeft:
|
||||
padding.Right = top.Width() - width
|
||||
case PositionRight:
|
||||
padding.Left = top.Width() - width
|
||||
default:
|
||||
padding.Top = top.Height() - defaultXAxisHeight
|
||||
}
|
||||
|
||||
p := top.Child(PainterPaddingOption(padding))
|
||||
|
||||
x0 := 0
|
||||
y0 := 0
|
||||
x1 := 0
|
||||
y1 := 0
|
||||
ticksPaddingTop := 0
|
||||
ticksPaddingLeft := 0
|
||||
labelPaddingTop := 0
|
||||
labelPaddingLeft := 0
|
||||
labelPaddingRight := 0
|
||||
orient := ""
|
||||
textAlign := ""
|
||||
|
||||
switch opt.Position {
|
||||
case PositionTop:
|
||||
labelPaddingTop = 0
|
||||
x1 = p.Width()
|
||||
y0 = labelMargin + int(opt.FontSize)
|
||||
ticksPaddingTop = int(opt.FontSize)
|
||||
y1 = y0
|
||||
orient = OrientHorizontal
|
||||
case PositionLeft:
|
||||
x0 = p.Width()
|
||||
y0 = 0
|
||||
x1 = p.Width()
|
||||
y1 = p.Height()
|
||||
orient = OrientVertical
|
||||
textAlign = AlignRight
|
||||
ticksPaddingLeft = textMaxWidth + tickLength
|
||||
labelPaddingRight = width - textMaxWidth
|
||||
case PositionRight:
|
||||
orient = OrientVertical
|
||||
y1 = p.Height()
|
||||
labelPaddingLeft = width - textMaxWidth
|
||||
default:
|
||||
labelPaddingTop = height
|
||||
x1 = p.Width()
|
||||
orient = OrientHorizontal
|
||||
}
|
||||
|
||||
if strokeWidth > 0 {
|
||||
p.Child(PainterPaddingOption(Box{
|
||||
Top: ticksPaddingTop,
|
||||
Left: ticksPaddingLeft,
|
||||
})).Ticks(TicksOption{
|
||||
Count: tickCount,
|
||||
Length: tickLength,
|
||||
Unit: unit,
|
||||
Orient: orient,
|
||||
First: opt.FirstAxis,
|
||||
})
|
||||
p.LineStroke([]Point{
|
||||
{
|
||||
X: x0,
|
||||
Y: y0,
|
||||
},
|
||||
{
|
||||
X: x1,
|
||||
Y: y1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
p.Child(PainterPaddingOption(Box{
|
||||
Left: labelPaddingLeft,
|
||||
Top: labelPaddingTop,
|
||||
Right: labelPaddingRight,
|
||||
})).MultiText(MultiTextOption{
|
||||
First: opt.FirstAxis,
|
||||
Align: textAlign,
|
||||
TextList: data,
|
||||
Orient: orient,
|
||||
Unit: unit,
|
||||
Position: labelPosition,
|
||||
TextRotation: opt.TextRotation,
|
||||
Offset: opt.LabelOffset,
|
||||
})
|
||||
// 显示辅助线
|
||||
if opt.SplitLineShow {
|
||||
style.StrokeColor = opt.SplitLineColor
|
||||
style.StrokeWidth = 1
|
||||
top.OverrideDrawingStyle(style)
|
||||
if isVertical {
|
||||
x0 := p.Width()
|
||||
x1 := top.Width()
|
||||
if opt.Position == PositionRight {
|
||||
x0 = 0
|
||||
x1 = top.Width() - p.Width()
|
||||
}
|
||||
yValues := autoDivide(height, tickCount)
|
||||
yValues = yValues[0 : len(yValues)-1]
|
||||
for _, y := range yValues {
|
||||
top.LineStroke([]Point{
|
||||
{
|
||||
X: x0,
|
||||
Y: y,
|
||||
},
|
||||
{
|
||||
X: x1,
|
||||
Y: y,
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
y0 := p.Height() - defaultXAxisHeight
|
||||
y1 := top.Height() - defaultXAxisHeight
|
||||
for index, x := range autoDivide(width, tickCount) {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
top.LineStroke([]Point{
|
||||
{
|
||||
X: x,
|
||||
Y: y0,
|
||||
},
|
||||
{
|
||||
X: x,
|
||||
Y: y1,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Box{
|
||||
Bottom: height,
|
||||
Right: width,
|
||||
}, nil
|
||||
}
|
||||
253
src/server/vendor/github.com/vicanso/go-charts/v2/bar_chart.go
generated
vendored
Normal file
253
src/server/vendor/github.com/vicanso/go-charts/v2/bar_chart.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type barChart struct {
|
||||
p *Painter
|
||||
opt *BarChartOption
|
||||
}
|
||||
|
||||
// NewBarChart returns a bar chart renderer
|
||||
func NewBarChart(p *Painter, opt BarChartOption) *barChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &barChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type BarChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The x axis option
|
||||
XAxis XAxisOption
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The y axis option
|
||||
YAxisOptions []YAxisOption
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
BarWidth int
|
||||
// Margin of bar
|
||||
BarMargin int
|
||||
}
|
||||
|
||||
func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
p := b.p
|
||||
opt := b.opt
|
||||
seriesPainter := result.seriesPainter
|
||||
|
||||
xRange := NewRange(AxisRangeOption{
|
||||
Painter: b.p,
|
||||
DivideCount: len(opt.XAxis.Data),
|
||||
Size: seriesPainter.Width(),
|
||||
})
|
||||
x0, x1 := xRange.GetRange(0)
|
||||
width := int(x1 - x0)
|
||||
// 每一块之间的margin
|
||||
margin := 10
|
||||
// 每一个bar之间的margin
|
||||
barMargin := 5
|
||||
if width < 20 {
|
||||
margin = 2
|
||||
barMargin = 2
|
||||
} else if width < 50 {
|
||||
margin = 5
|
||||
barMargin = 3
|
||||
}
|
||||
if opt.BarMargin > 0 {
|
||||
barMargin = opt.BarMargin
|
||||
}
|
||||
seriesCount := len(seriesList)
|
||||
// 总的宽度-两个margin-(总数-1)的barMargin
|
||||
barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount
|
||||
if opt.BarWidth > 0 && opt.BarWidth < barWidth {
|
||||
barWidth = opt.BarWidth
|
||||
// 重新计算margin
|
||||
margin = (width - seriesCount*barWidth - barMargin*(seriesCount-1)) / 2
|
||||
}
|
||||
barMaxHeight := seriesPainter.Height()
|
||||
theme := opt.Theme
|
||||
seriesNames := seriesList.Names()
|
||||
|
||||
markPointPainter := NewMarkPointPainter(seriesPainter)
|
||||
markLinePainter := NewMarkLinePainter(seriesPainter)
|
||||
rendererList := []Renderer{
|
||||
markPointPainter,
|
||||
markLinePainter,
|
||||
}
|
||||
for index := range seriesList {
|
||||
series := seriesList[index]
|
||||
yRange := result.axisRanges[series.AxisIndex]
|
||||
seriesColor := theme.GetSeriesColor(series.index)
|
||||
|
||||
divideValues := xRange.AutoDivide()
|
||||
points := make([]Point, len(series.Data))
|
||||
var labelPainter *SeriesLabelPainter
|
||||
if series.Label.Show {
|
||||
labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{
|
||||
P: seriesPainter,
|
||||
SeriesNames: seriesNames,
|
||||
Label: series.Label,
|
||||
Theme: opt.Theme,
|
||||
Font: opt.Font,
|
||||
})
|
||||
rendererList = append(rendererList, labelPainter)
|
||||
}
|
||||
|
||||
for j, item := range series.Data {
|
||||
if j >= xRange.divideCount {
|
||||
continue
|
||||
}
|
||||
x := divideValues[j]
|
||||
x += margin
|
||||
if index != 0 {
|
||||
x += index * (barWidth + barMargin)
|
||||
}
|
||||
|
||||
h := int(yRange.getHeight(item.Value))
|
||||
fillColor := seriesColor
|
||||
if !item.Style.FillColor.IsZero() {
|
||||
fillColor = item.Style.FillColor
|
||||
}
|
||||
top := barMaxHeight - h
|
||||
|
||||
if series.RoundRadius <= 0 {
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
}).Rect(chart.Box{
|
||||
Top: top,
|
||||
Left: x,
|
||||
Right: x + barWidth,
|
||||
Bottom: barMaxHeight - 1,
|
||||
})
|
||||
} else {
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
}).RoundedRect(chart.Box{
|
||||
Top: top,
|
||||
Left: x,
|
||||
Right: x + barWidth,
|
||||
Bottom: barMaxHeight - 1,
|
||||
}, series.RoundRadius)
|
||||
}
|
||||
// 用于生成marker point
|
||||
points[j] = Point{
|
||||
// 居中的位置
|
||||
X: x + barWidth>>1,
|
||||
Y: top,
|
||||
}
|
||||
// 用于生成marker point
|
||||
points[j] = Point{
|
||||
// 居中的位置
|
||||
X: x + barWidth>>1,
|
||||
Y: top,
|
||||
}
|
||||
// 如果label不需要展示,则返回
|
||||
if labelPainter == nil {
|
||||
continue
|
||||
}
|
||||
y := barMaxHeight - h
|
||||
radians := float64(0)
|
||||
fontColor := series.Label.Color
|
||||
if series.Label.Position == PositionBottom {
|
||||
y = barMaxHeight
|
||||
radians = -math.Pi / 2
|
||||
if fontColor.IsZero() {
|
||||
if isLightColor(fillColor) {
|
||||
fontColor = defaultLightFontColor
|
||||
} else {
|
||||
fontColor = defaultDarkFontColor
|
||||
}
|
||||
}
|
||||
}
|
||||
labelPainter.Add(LabelValue{
|
||||
Index: index,
|
||||
Value: item.Value,
|
||||
X: x + barWidth>>1,
|
||||
Y: y,
|
||||
// 旋转
|
||||
Radians: radians,
|
||||
FontColor: fontColor,
|
||||
Offset: series.Label.Offset,
|
||||
FontSize: series.Label.FontSize,
|
||||
})
|
||||
}
|
||||
|
||||
markPointPainter.Add(markPointRenderOption{
|
||||
FillColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Series: series,
|
||||
Points: points,
|
||||
})
|
||||
markLinePainter.Add(markLineRenderOption{
|
||||
FillColor: seriesColor,
|
||||
FontColor: opt.Theme.GetTextColor(),
|
||||
StrokeColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Series: series,
|
||||
Range: yRange,
|
||||
})
|
||||
}
|
||||
// 最大、最小的mark point
|
||||
err := doRender(rendererList...)
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
|
||||
return p.box, nil
|
||||
}
|
||||
|
||||
func (b *barChart) Render() (Box, error) {
|
||||
p := b.p
|
||||
opt := b.opt
|
||||
renderResult, err := defaultRender(p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: opt.XAxis,
|
||||
YAxisOptions: opt.YAxisOptions,
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypeLine)
|
||||
return b.render(renderResult, seriesList)
|
||||
}
|
||||
426
src/server/vendor/github.com/vicanso/go-charts/v2/chart_option.go
generated
vendored
Normal file
426
src/server/vendor/github.com/vicanso/go-charts/v2/chart_option.go
generated
vendored
Normal file
@ -0,0 +1,426 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
type ChartOption struct {
|
||||
theme ColorPalette
|
||||
font *truetype.Font
|
||||
// The output type of chart, "svg" or "png", default value is "svg"
|
||||
Type string
|
||||
// The font family, which should be installed first
|
||||
FontFamily string
|
||||
// The theme of chart, "light" and "dark".
|
||||
// The default theme is "light"
|
||||
Theme string
|
||||
// The title option
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
// The x axis option
|
||||
XAxis XAxisOption
|
||||
// The y axis option list
|
||||
YAxisOptions []YAxisOption
|
||||
// The width of chart, default width is 600
|
||||
Width int
|
||||
// The height of chart, default height is 400
|
||||
Height int
|
||||
Parent *Painter
|
||||
// The padding for chart, default padding is [20, 10, 10, 10]
|
||||
Padding Box
|
||||
// The canvas box for chart
|
||||
Box Box
|
||||
// The series list
|
||||
SeriesList SeriesList
|
||||
// The radar indicator list
|
||||
RadarIndicators []RadarIndicator
|
||||
// The background color of chart
|
||||
BackgroundColor Color
|
||||
// The flag for show symbol of line, set this to *false will hide symbol
|
||||
SymbolShow *bool
|
||||
// The stroke width of line chart
|
||||
LineStrokeWidth float64
|
||||
// The bar with of bar chart
|
||||
BarWidth int
|
||||
// The margin of each bar
|
||||
BarMargin int
|
||||
// The bar height of horizontal bar chart
|
||||
BarHeight int
|
||||
// Fill the area of line chart
|
||||
FillArea bool
|
||||
// background fill (alpha) opacity
|
||||
Opacity uint8
|
||||
// The child charts
|
||||
Children []ChartOption
|
||||
// The value formatter
|
||||
ValueFormatter ValueFormatter
|
||||
}
|
||||
|
||||
// OptionFunc option function
|
||||
type OptionFunc func(opt *ChartOption)
|
||||
|
||||
// SVGTypeOption set svg type of chart's output
|
||||
func SVGTypeOption() OptionFunc {
|
||||
return TypeOptionFunc(ChartOutputSVG)
|
||||
}
|
||||
|
||||
// PNGTypeOption set png type of chart's output
|
||||
func PNGTypeOption() OptionFunc {
|
||||
return TypeOptionFunc(ChartOutputPNG)
|
||||
}
|
||||
|
||||
// TypeOptionFunc set type of chart's output
|
||||
func TypeOptionFunc(t string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Type = t
|
||||
}
|
||||
}
|
||||
|
||||
// FontFamilyOptionFunc set font family of chart
|
||||
func FontFamilyOptionFunc(fontFamily string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.FontFamily = fontFamily
|
||||
}
|
||||
}
|
||||
|
||||
// ThemeOptionFunc set them of chart
|
||||
func ThemeOptionFunc(theme string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// TitleOptionFunc set title of chart
|
||||
func TitleOptionFunc(title TitleOption) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Title = title
|
||||
}
|
||||
}
|
||||
|
||||
// TitleTextOptionFunc set title text of chart
|
||||
func TitleTextOptionFunc(text string, subtext ...string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Title.Text = text
|
||||
if len(subtext) != 0 {
|
||||
opt.Title.Subtext = subtext[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LegendOptionFunc set legend of chart
|
||||
func LegendOptionFunc(legend LegendOption) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Legend = legend
|
||||
}
|
||||
}
|
||||
|
||||
// LegendLabelsOptionFunc set legend labels of chart
|
||||
func LegendLabelsOptionFunc(labels []string, left ...string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Legend = NewLegendOption(labels, left...)
|
||||
}
|
||||
}
|
||||
|
||||
// XAxisOptionFunc set x axis of chart
|
||||
func XAxisOptionFunc(xAxisOption XAxisOption) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.XAxis = xAxisOption
|
||||
}
|
||||
}
|
||||
|
||||
// XAxisDataOptionFunc set x axis data of chart
|
||||
func XAxisDataOptionFunc(data []string, boundaryGap ...*bool) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.XAxis = NewXAxisOption(data, boundaryGap...)
|
||||
}
|
||||
}
|
||||
|
||||
// YAxisOptionFunc set y axis of chart, support two y axis
|
||||
func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.YAxisOptions = yAxisOption
|
||||
}
|
||||
}
|
||||
|
||||
// YAxisDataOptionFunc set y axis data of chart
|
||||
func YAxisDataOptionFunc(data []string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.YAxisOptions = NewYAxisOptions(data)
|
||||
}
|
||||
}
|
||||
|
||||
// WidthOptionFunc set width of chart
|
||||
func WidthOptionFunc(width int) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Width = width
|
||||
}
|
||||
}
|
||||
|
||||
// HeightOptionFunc set height of chart
|
||||
func HeightOptionFunc(height int) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Height = height
|
||||
}
|
||||
}
|
||||
|
||||
// PaddingOptionFunc set padding of chart
|
||||
func PaddingOptionFunc(padding Box) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Padding = padding
|
||||
}
|
||||
}
|
||||
|
||||
// BoxOptionFunc set box of chart
|
||||
func BoxOptionFunc(box Box) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.Box = box
|
||||
}
|
||||
}
|
||||
|
||||
// PieSeriesShowLabel set pie series show label
|
||||
func PieSeriesShowLabel() OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
for index := range opt.SeriesList {
|
||||
opt.SeriesList[index].Label.Show = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChildOptionFunc add child chart
|
||||
func ChildOptionFunc(child ...ChartOption) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
if opt.Children == nil {
|
||||
opt.Children = make([]ChartOption, 0)
|
||||
}
|
||||
opt.Children = append(opt.Children, child...)
|
||||
}
|
||||
}
|
||||
|
||||
// RadarIndicatorOptionFunc set radar indicator of chart
|
||||
func RadarIndicatorOptionFunc(names []string, values []float64) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.RadarIndicators = NewRadarIndicators(names, values)
|
||||
}
|
||||
}
|
||||
|
||||
// BackgroundColorOptionFunc set background color of chart
|
||||
func BackgroundColorOptionFunc(color Color) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
opt.BackgroundColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// MarkLineOptionFunc set mark line for series of chart
|
||||
func MarkLineOptionFunc(seriesIndex int, markLineTypes ...string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
if len(opt.SeriesList) <= seriesIndex {
|
||||
return
|
||||
}
|
||||
opt.SeriesList[seriesIndex].MarkLine = NewMarkLine(markLineTypes...)
|
||||
}
|
||||
}
|
||||
|
||||
// MarkPointOptionFunc set mark point for series of chart
|
||||
func MarkPointOptionFunc(seriesIndex int, markPointTypes ...string) OptionFunc {
|
||||
return func(opt *ChartOption) {
|
||||
if len(opt.SeriesList) <= seriesIndex {
|
||||
return
|
||||
}
|
||||
opt.SeriesList[seriesIndex].MarkPoint = NewMarkPoint(markPointTypes...)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ChartOption) fillDefault() {
|
||||
t := NewTheme(o.Theme)
|
||||
o.theme = t
|
||||
// 如果为空,初始化
|
||||
axisCount := 1
|
||||
for _, series := range o.SeriesList {
|
||||
if series.AxisIndex >= axisCount {
|
||||
axisCount++
|
||||
}
|
||||
}
|
||||
o.Width = getDefaultInt(o.Width, defaultChartWidth)
|
||||
o.Height = getDefaultInt(o.Height, defaultChartHeight)
|
||||
yAxisOptions := make([]YAxisOption, axisCount)
|
||||
copy(yAxisOptions, o.YAxisOptions)
|
||||
o.YAxisOptions = yAxisOptions
|
||||
o.font, _ = GetFont(o.FontFamily)
|
||||
|
||||
if o.font == nil {
|
||||
o.font, _ = GetDefaultFont()
|
||||
} else {
|
||||
// 如果指定了字体,则设置主题的字体
|
||||
t.SetFont(o.font)
|
||||
}
|
||||
if o.BackgroundColor.IsZero() {
|
||||
o.BackgroundColor = t.GetBackgroundColor()
|
||||
}
|
||||
if o.Padding.IsZero() {
|
||||
o.Padding = Box{
|
||||
Top: 20,
|
||||
Right: 20,
|
||||
Bottom: 20,
|
||||
Left: 20,
|
||||
}
|
||||
}
|
||||
// legend与series name的关联
|
||||
if len(o.Legend.Data) == 0 {
|
||||
o.Legend.Data = o.SeriesList.Names()
|
||||
} else {
|
||||
seriesCount := len(o.SeriesList)
|
||||
for index, name := range o.Legend.Data {
|
||||
if index < seriesCount &&
|
||||
len(o.SeriesList[index].Name) == 0 {
|
||||
o.SeriesList[index].Name = name
|
||||
}
|
||||
}
|
||||
nameIndexDict := map[string]int{}
|
||||
for index, name := range o.Legend.Data {
|
||||
nameIndexDict[name] = index
|
||||
}
|
||||
// 保证series的顺序与legend一致
|
||||
sort.Slice(o.SeriesList, func(i, j int) bool {
|
||||
return nameIndexDict[o.SeriesList[i].Name] < nameIndexDict[o.SeriesList[j].Name]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// LineRender line chart render
|
||||
func LineRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
|
||||
seriesList := NewSeriesListDataFromValues(values, ChartTypeLine)
|
||||
return Render(ChartOption{
|
||||
SeriesList: seriesList,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// BarRender bar chart render
|
||||
func BarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
|
||||
seriesList := NewSeriesListDataFromValues(values, ChartTypeBar)
|
||||
return Render(ChartOption{
|
||||
SeriesList: seriesList,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// HorizontalBarRender horizontal bar chart render
|
||||
func HorizontalBarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
|
||||
seriesList := NewSeriesListDataFromValues(values, ChartTypeHorizontalBar)
|
||||
return Render(ChartOption{
|
||||
SeriesList: seriesList,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// PieRender pie chart render
|
||||
func PieRender(values []float64, opts ...OptionFunc) (*Painter, error) {
|
||||
return Render(ChartOption{
|
||||
SeriesList: NewPieSeriesList(values),
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// RadarRender radar chart render
|
||||
func RadarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
|
||||
seriesList := NewSeriesListDataFromValues(values, ChartTypeRadar)
|
||||
return Render(ChartOption{
|
||||
SeriesList: seriesList,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// FunnelRender funnel chart render
|
||||
func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, error) {
|
||||
seriesList := NewFunnelSeriesList(values)
|
||||
return Render(ChartOption{
|
||||
SeriesList: seriesList,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// TableRender table chart render
|
||||
func TableRender(header []string, data [][]string, spanMaps ...map[int]int) (*Painter, error) {
|
||||
opt := TableChartOption{
|
||||
Header: header,
|
||||
Data: data,
|
||||
}
|
||||
if len(spanMaps) != 0 {
|
||||
spanMap := spanMaps[0]
|
||||
spans := make([]int, len(opt.Header))
|
||||
for index := range spans {
|
||||
v, ok := spanMap[index]
|
||||
if !ok {
|
||||
v = 1
|
||||
}
|
||||
spans[index] = v
|
||||
}
|
||||
opt.Spans = spans
|
||||
}
|
||||
return TableOptionRender(opt)
|
||||
}
|
||||
|
||||
// TableOptionRender table render with option
|
||||
func TableOptionRender(opt TableChartOption) (*Painter, error) {
|
||||
if opt.Type == "" {
|
||||
opt.Type = ChartOutputPNG
|
||||
}
|
||||
if opt.Width <= 0 {
|
||||
opt.Width = defaultChartWidth
|
||||
}
|
||||
if opt.FontFamily != "" {
|
||||
opt.Font, _ = GetFont(opt.FontFamily)
|
||||
}
|
||||
if opt.Font == nil {
|
||||
opt.Font, _ = GetDefaultFont()
|
||||
}
|
||||
|
||||
p, err := NewPainter(PainterOptions{
|
||||
Type: opt.Type,
|
||||
Width: opt.Width,
|
||||
// 仅用于计算表格高度,因此随便设置即可
|
||||
Height: 100,
|
||||
Font: opt.Font,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := NewTableChart(p, opt).render()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err = NewPainter(PainterOptions{
|
||||
Type: opt.Type,
|
||||
Width: info.Width,
|
||||
Height: info.Height,
|
||||
Font: opt.Font,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = NewTableChart(p, opt).renderWithInfo(info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
473
src/server/vendor/github.com/vicanso/go-charts/v2/charts.go
generated
vendored
Normal file
473
src/server/vendor/github.com/vicanso/go-charts/v2/charts.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
const labelFontSize = 10
|
||||
const smallLabelFontSize = 8
|
||||
const defaultDotWidth = 2.0
|
||||
const defaultStrokeWidth = 2.0
|
||||
|
||||
var defaultChartWidth = 600
|
||||
var defaultChartHeight = 400
|
||||
|
||||
// SetDefaultWidth sets default width of chart
|
||||
func SetDefaultWidth(width int) {
|
||||
if width > 0 {
|
||||
defaultChartWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
// SetDefaultHeight sets default height of chart
|
||||
func SetDefaultHeight(height int) {
|
||||
if height > 0 {
|
||||
defaultChartHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
var nullValue = math.MaxFloat64
|
||||
|
||||
// SetNullValue sets the null value, default is MaxFloat64
|
||||
func SetNullValue(v float64) {
|
||||
nullValue = v
|
||||
}
|
||||
|
||||
// GetNullValue gets the null value
|
||||
func GetNullValue() float64 {
|
||||
return nullValue
|
||||
}
|
||||
|
||||
type Renderer interface {
|
||||
Render() (Box, error)
|
||||
}
|
||||
|
||||
type renderHandler struct {
|
||||
list []func() error
|
||||
}
|
||||
|
||||
func (rh *renderHandler) Add(fn func() error) {
|
||||
list := rh.list
|
||||
if len(list) == 0 {
|
||||
list = make([]func() error, 0)
|
||||
}
|
||||
rh.list = append(list, fn)
|
||||
}
|
||||
|
||||
func (rh *renderHandler) Do() error {
|
||||
for _, fn := range rh.list {
|
||||
err := fn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type defaultRenderOption struct {
|
||||
Theme ColorPalette
|
||||
Padding Box
|
||||
SeriesList SeriesList
|
||||
// The y axis option
|
||||
YAxisOptions []YAxisOption
|
||||
// The x axis option
|
||||
XAxis XAxisOption
|
||||
// The title option
|
||||
TitleOption TitleOption
|
||||
// The legend option
|
||||
LegendOption LegendOption
|
||||
// background is filled
|
||||
backgroundIsFilled bool
|
||||
// x y axis is reversed
|
||||
axisReversed bool
|
||||
}
|
||||
|
||||
type defaultRenderResult struct {
|
||||
axisRanges map[int]axisRange
|
||||
// 图例区域
|
||||
seriesPainter *Painter
|
||||
}
|
||||
|
||||
func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, error) {
|
||||
seriesList := opt.SeriesList
|
||||
seriesList.init()
|
||||
if !opt.backgroundIsFilled {
|
||||
p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor())
|
||||
}
|
||||
|
||||
if !opt.Padding.IsZero() {
|
||||
p = p.Child(PainterPaddingOption(opt.Padding))
|
||||
}
|
||||
|
||||
legendHeight := 0
|
||||
if len(opt.LegendOption.Data) != 0 {
|
||||
if opt.LegendOption.Theme == nil {
|
||||
opt.LegendOption.Theme = opt.Theme
|
||||
}
|
||||
legendResult, err := NewLegendPainter(p, opt.LegendOption).Render()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
legendHeight = legendResult.Height()
|
||||
}
|
||||
|
||||
// 如果有标题
|
||||
if opt.TitleOption.Text != "" {
|
||||
if opt.TitleOption.Theme == nil {
|
||||
opt.TitleOption.Theme = opt.Theme
|
||||
}
|
||||
titlePainter := NewTitlePainter(p, opt.TitleOption)
|
||||
|
||||
titleBox, err := titlePainter.Render()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
top := chart.MaxInt(legendHeight, titleBox.Height())
|
||||
// 如果是垂直方式,则不计算legend高度
|
||||
if opt.LegendOption.Orient == OrientVertical {
|
||||
top = titleBox.Height()
|
||||
}
|
||||
p = p.Child(PainterPaddingOption(Box{
|
||||
// 标题下留白
|
||||
Top: top + 20,
|
||||
}))
|
||||
}
|
||||
|
||||
result := defaultRenderResult{
|
||||
axisRanges: make(map[int]axisRange),
|
||||
}
|
||||
|
||||
// 计算图表对应的轴有哪些
|
||||
axisIndexList := make([]int, 0)
|
||||
for _, series := range opt.SeriesList {
|
||||
if containsInt(axisIndexList, series.AxisIndex) {
|
||||
continue
|
||||
}
|
||||
axisIndexList = append(axisIndexList, series.AxisIndex)
|
||||
}
|
||||
// 高度需要减去x轴的高度
|
||||
rangeHeight := p.Height() - defaultXAxisHeight
|
||||
rangeWidthLeft := 0
|
||||
rangeWidthRight := 0
|
||||
|
||||
// 倒序
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(axisIndexList)))
|
||||
|
||||
// 计算对应的axis range
|
||||
for _, index := range axisIndexList {
|
||||
yAxisOption := YAxisOption{}
|
||||
if len(opt.YAxisOptions) > index {
|
||||
yAxisOption = opt.YAxisOptions[index]
|
||||
}
|
||||
divideCount := yAxisOption.DivideCount
|
||||
if divideCount <= 0 {
|
||||
divideCount = defaultAxisDivideCount
|
||||
}
|
||||
max, min := opt.SeriesList.GetMaxMin(index)
|
||||
r := NewRange(AxisRangeOption{
|
||||
Painter: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
// 高度需要减去x轴的高度
|
||||
Size: rangeHeight,
|
||||
// 分隔数量
|
||||
DivideCount: divideCount,
|
||||
})
|
||||
if yAxisOption.Min != nil && *yAxisOption.Min <= min {
|
||||
r.min = *yAxisOption.Min
|
||||
}
|
||||
if yAxisOption.Max != nil && *yAxisOption.Max >= max {
|
||||
r.max = *yAxisOption.Max
|
||||
}
|
||||
result.axisRanges[index] = r
|
||||
|
||||
if yAxisOption.Theme == nil {
|
||||
yAxisOption.Theme = opt.Theme
|
||||
}
|
||||
if !opt.axisReversed {
|
||||
yAxisOption.Data = r.Values()
|
||||
} else {
|
||||
yAxisOption.isCategoryAxis = true
|
||||
// 由于x轴为value部分,因此计算其label单独处理
|
||||
opt.XAxis.Data = NewRange(AxisRangeOption{
|
||||
Painter: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
// 高度需要减去x轴的高度
|
||||
Size: rangeHeight,
|
||||
// 分隔数量
|
||||
DivideCount: defaultAxisDivideCount,
|
||||
}).Values()
|
||||
opt.XAxis.isValueAxis = true
|
||||
}
|
||||
reverseStringSlice(yAxisOption.Data)
|
||||
// TODO生成其它位置既yAxis
|
||||
var yAxis *axisPainter
|
||||
child := p.Child(PainterPaddingOption(Box{
|
||||
Left: rangeWidthLeft,
|
||||
Right: rangeWidthRight,
|
||||
}))
|
||||
if index == 0 {
|
||||
yAxis = NewLeftYAxis(child, yAxisOption)
|
||||
} else {
|
||||
yAxis = NewRightYAxis(child, yAxisOption)
|
||||
}
|
||||
yAxisBox, err := yAxis.Render()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index == 0 {
|
||||
rangeWidthLeft += yAxisBox.Width()
|
||||
} else {
|
||||
rangeWidthRight += yAxisBox.Width()
|
||||
}
|
||||
}
|
||||
|
||||
if opt.XAxis.Theme == nil {
|
||||
opt.XAxis.Theme = opt.Theme
|
||||
}
|
||||
xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{
|
||||
Left: rangeWidthLeft,
|
||||
Right: rangeWidthRight,
|
||||
})), opt.XAxis)
|
||||
_, err := xAxis.Render()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.seriesPainter = p.Child(PainterPaddingOption(Box{
|
||||
Bottom: defaultXAxisHeight,
|
||||
Left: rangeWidthLeft,
|
||||
Right: rangeWidthRight,
|
||||
}))
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func doRender(renderers ...Renderer) error {
|
||||
for _, r := range renderers {
|
||||
_, err := r.Render()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
|
||||
for _, fn := range opts {
|
||||
fn(&opt)
|
||||
}
|
||||
opt.fillDefault()
|
||||
|
||||
isChild := true
|
||||
if opt.Parent == nil {
|
||||
isChild = false
|
||||
p, err := NewPainter(PainterOptions{
|
||||
Type: opt.Type,
|
||||
Width: opt.Width,
|
||||
Height: opt.Height,
|
||||
Font: opt.font,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt.Parent = p
|
||||
}
|
||||
p := opt.Parent
|
||||
if opt.ValueFormatter != nil {
|
||||
p.valueFormatter = opt.ValueFormatter
|
||||
}
|
||||
if !opt.Box.IsZero() {
|
||||
p = p.Child(PainterBoxOption(opt.Box))
|
||||
}
|
||||
if !isChild {
|
||||
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
|
||||
}
|
||||
seriesList := opt.SeriesList
|
||||
seriesList.init()
|
||||
|
||||
seriesCount := len(seriesList)
|
||||
|
||||
// line chart
|
||||
lineSeriesList := seriesList.Filter(ChartTypeLine)
|
||||
barSeriesList := seriesList.Filter(ChartTypeBar)
|
||||
horizontalBarSeriesList := seriesList.Filter(ChartTypeHorizontalBar)
|
||||
pieSeriesList := seriesList.Filter(ChartTypePie)
|
||||
radarSeriesList := seriesList.Filter(ChartTypeRadar)
|
||||
funnelSeriesList := seriesList.Filter(ChartTypeFunnel)
|
||||
|
||||
if len(horizontalBarSeriesList) != 0 && len(horizontalBarSeriesList) != seriesCount {
|
||||
return nil, errors.New("Horizontal bar can not mix other charts")
|
||||
}
|
||||
if len(pieSeriesList) != 0 && len(pieSeriesList) != seriesCount {
|
||||
return nil, errors.New("Pie can not mix other charts")
|
||||
}
|
||||
if len(radarSeriesList) != 0 && len(radarSeriesList) != seriesCount {
|
||||
return nil, errors.New("Radar can not mix other charts")
|
||||
}
|
||||
if len(funnelSeriesList) != 0 && len(funnelSeriesList) != seriesCount {
|
||||
return nil, errors.New("Funnel can not mix other charts")
|
||||
}
|
||||
|
||||
axisReversed := len(horizontalBarSeriesList) != 0
|
||||
renderOpt := defaultRenderOption{
|
||||
Theme: opt.theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: opt.XAxis,
|
||||
YAxisOptions: opt.YAxisOptions,
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
axisReversed: axisReversed,
|
||||
// 前置已设置背景色
|
||||
backgroundIsFilled: true,
|
||||
}
|
||||
if len(pieSeriesList) != 0 ||
|
||||
len(radarSeriesList) != 0 ||
|
||||
len(funnelSeriesList) != 0 {
|
||||
renderOpt.XAxis.Show = FalseFlag()
|
||||
renderOpt.YAxisOptions = []YAxisOption{
|
||||
{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(horizontalBarSeriesList) != 0 {
|
||||
renderOpt.YAxisOptions[0].DivideCount = len(renderOpt.YAxisOptions[0].Data)
|
||||
renderOpt.YAxisOptions[0].Unit = 1
|
||||
}
|
||||
|
||||
renderResult, err := defaultRender(p, renderOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler := renderHandler{}
|
||||
|
||||
// bar chart
|
||||
if len(barSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewBarChart(p, BarChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
XAxis: opt.XAxis,
|
||||
BarWidth: opt.BarWidth,
|
||||
BarMargin: opt.BarMargin,
|
||||
}).render(renderResult, barSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// horizontal bar chart
|
||||
if len(horizontalBarSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewHorizontalBarChart(p, HorizontalBarChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
BarHeight: opt.BarHeight,
|
||||
BarMargin: opt.BarMargin,
|
||||
YAxisOptions: opt.YAxisOptions,
|
||||
}).render(renderResult, horizontalBarSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// pie chart
|
||||
if len(pieSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewPieChart(p, PieChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
}).render(renderResult, pieSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// line chart
|
||||
if len(lineSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewLineChart(p, LineChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
XAxis: opt.XAxis,
|
||||
SymbolShow: opt.SymbolShow,
|
||||
StrokeWidth: opt.LineStrokeWidth,
|
||||
FillArea: opt.FillArea,
|
||||
Opacity: opt.Opacity,
|
||||
}).render(renderResult, lineSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// radar chart
|
||||
if len(radarSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewRadarChart(p, RadarChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
// 相应值
|
||||
RadarIndicators: opt.RadarIndicators,
|
||||
}).render(renderResult, radarSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// funnel chart
|
||||
if len(funnelSeriesList) != 0 {
|
||||
handler.Add(func() error {
|
||||
_, err := NewFunnelChart(p, FunnelChartOption{
|
||||
Theme: opt.theme,
|
||||
Font: opt.font,
|
||||
}).render(renderResult, funnelSeriesList)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
err = handler.Do()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range opt.Children {
|
||||
item.Parent = p
|
||||
if item.Theme == "" {
|
||||
item.Theme = opt.Theme
|
||||
}
|
||||
if item.FontFamily == "" {
|
||||
item.FontFamily = opt.FontFamily
|
||||
}
|
||||
_, err = Render(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
528
src/server/vendor/github.com/vicanso/go-charts/v2/echarts.go
generated
vendored
Normal file
528
src/server/vendor/github.com/vicanso/go-charts/v2/echarts.go
generated
vendored
Normal file
@ -0,0 +1,528 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
func convertToArray(data []byte) []byte {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if data[0] != '[' {
|
||||
data = []byte("[" + string(data) + "]")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type EChartsPosition string
|
||||
|
||||
func (p *EChartsPosition) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if regexp.MustCompile(`^\d+`).Match(data) {
|
||||
data = []byte(fmt.Sprintf(`"%s"`, string(data)))
|
||||
}
|
||||
s := (*string)(p)
|
||||
return json.Unmarshal(data, s)
|
||||
}
|
||||
|
||||
type EChartStyle struct {
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
func (es *EChartStyle) ToStyle() Style {
|
||||
color := parseColor(es.Color)
|
||||
return Style{
|
||||
FillColor: color,
|
||||
FontColor: color,
|
||||
StrokeColor: color,
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsSeriesDataValue struct {
|
||||
values []float64
|
||||
}
|
||||
|
||||
func (value *EChartsSeriesDataValue) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
return json.Unmarshal(data, &value.values)
|
||||
}
|
||||
func (value *EChartsSeriesDataValue) First() float64 {
|
||||
if len(value.values) == 0 {
|
||||
return 0
|
||||
}
|
||||
return value.values[0]
|
||||
}
|
||||
func NewEChartsSeriesDataValue(values ...float64) EChartsSeriesDataValue {
|
||||
return EChartsSeriesDataValue{
|
||||
values: values,
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsSeriesData struct {
|
||||
Value EChartsSeriesDataValue `json:"value"`
|
||||
Name string `json:"name"`
|
||||
ItemStyle EChartStyle `json:"itemStyle"`
|
||||
}
|
||||
type _EChartsSeriesData EChartsSeriesData
|
||||
|
||||
var numericRep = regexp.MustCompile(`^[-+]?[0-9]+(?:\.[0-9]+)?$`)
|
||||
|
||||
func (es *EChartsSeriesData) UnmarshalJSON(data []byte) error {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if numericRep.Match(data) {
|
||||
v, err := strconv.ParseFloat(string(data), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.Value = EChartsSeriesDataValue{
|
||||
values: []float64{
|
||||
v,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
v := _EChartsSeriesData{}
|
||||
err := json.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.Name = v.Name
|
||||
es.Value = v.Value
|
||||
es.ItemStyle = v.ItemStyle
|
||||
return nil
|
||||
}
|
||||
|
||||
type EChartsXAxisData struct {
|
||||
BoundaryGap *bool `json:"boundaryGap"`
|
||||
SplitNumber int `json:"splitNumber"`
|
||||
Data []string `json:"data"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type EChartsXAxis struct {
|
||||
Data []EChartsXAxisData
|
||||
}
|
||||
|
||||
func (ex *EChartsXAxis) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, &ex.Data)
|
||||
}
|
||||
|
||||
type EChartsAxisLabel struct {
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
type EChartsYAxisData struct {
|
||||
Min *float64 `json:"min"`
|
||||
Max *float64 `json:"max"`
|
||||
AxisLabel EChartsAxisLabel `json:"axisLabel"`
|
||||
AxisLine struct {
|
||||
LineStyle struct {
|
||||
Color string `json:"color"`
|
||||
} `json:"lineStyle"`
|
||||
} `json:"axisLine"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
type EChartsYAxis struct {
|
||||
Data []EChartsYAxisData `json:"data"`
|
||||
}
|
||||
|
||||
func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, &ey.Data)
|
||||
}
|
||||
|
||||
type EChartsPadding struct {
|
||||
Box chart.Box
|
||||
}
|
||||
|
||||
func (eb *EChartsPadding) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
arr := make([]int, 0)
|
||||
err := json.Unmarshal(data, &arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(arr) == 0 {
|
||||
return nil
|
||||
}
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
eb.Box = chart.Box{
|
||||
Left: arr[0],
|
||||
Top: arr[0],
|
||||
Bottom: arr[0],
|
||||
Right: arr[0],
|
||||
}
|
||||
case 2:
|
||||
eb.Box = chart.Box{
|
||||
Top: arr[0],
|
||||
Bottom: arr[0],
|
||||
Left: arr[1],
|
||||
Right: arr[1],
|
||||
}
|
||||
default:
|
||||
result := make([]int, 4)
|
||||
copy(result, arr)
|
||||
if len(arr) == 3 {
|
||||
result[3] = result[1]
|
||||
}
|
||||
// 上右下左
|
||||
eb.Box = chart.Box{
|
||||
Top: result[0],
|
||||
Right: result[1],
|
||||
Bottom: result[2],
|
||||
Left: result[3],
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EChartsLabelOption struct {
|
||||
Show bool `json:"show"`
|
||||
Distance int `json:"distance"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
type EChartsLegend struct {
|
||||
Show *bool `json:"show"`
|
||||
Data []string `json:"data"`
|
||||
Align string `json:"align"`
|
||||
Orient string `json:"orient"`
|
||||
Padding EChartsPadding `json:"padding"`
|
||||
Left EChartsPosition `json:"left"`
|
||||
Top EChartsPosition `json:"top"`
|
||||
TextStyle EChartsTextStyle `json:"textStyle"`
|
||||
}
|
||||
|
||||
type EChartsMarkData struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type _EChartsMarkData EChartsMarkData
|
||||
|
||||
func (emd *EChartsMarkData) UnmarshalJSON(data []byte) error {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
data = convertToArray(data)
|
||||
ds := make([]*_EChartsMarkData, 0)
|
||||
err := json.Unmarshal(data, &ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range ds {
|
||||
if d.Type != "" {
|
||||
emd.Type = d.Type
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EChartsMarkPoint struct {
|
||||
SymbolSize int `json:"symbolSize"`
|
||||
Data []EChartsMarkData `json:"data"`
|
||||
}
|
||||
|
||||
func (emp *EChartsMarkPoint) ToSeriesMarkPoint() SeriesMarkPoint {
|
||||
sp := SeriesMarkPoint{
|
||||
SymbolSize: emp.SymbolSize,
|
||||
}
|
||||
if len(emp.Data) == 0 {
|
||||
return sp
|
||||
}
|
||||
data := make([]SeriesMarkData, len(emp.Data))
|
||||
for index, item := range emp.Data {
|
||||
data[index].Type = item.Type
|
||||
}
|
||||
sp.Data = data
|
||||
return sp
|
||||
}
|
||||
|
||||
type EChartsMarkLine struct {
|
||||
Data []EChartsMarkData `json:"data"`
|
||||
}
|
||||
|
||||
func (eml *EChartsMarkLine) ToSeriesMarkLine() SeriesMarkLine {
|
||||
sl := SeriesMarkLine{}
|
||||
if len(eml.Data) == 0 {
|
||||
return sl
|
||||
}
|
||||
data := make([]SeriesMarkData, len(eml.Data))
|
||||
for index, item := range eml.Data {
|
||||
data[index].Type = item.Type
|
||||
}
|
||||
sl.Data = data
|
||||
return sl
|
||||
}
|
||||
|
||||
type EChartsSeries struct {
|
||||
Data []EChartsSeriesData `json:"data"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Radius string `json:"radius"`
|
||||
YAxisIndex int `json:"yAxisIndex"`
|
||||
ItemStyle EChartStyle `json:"itemStyle"`
|
||||
// label的配置
|
||||
Label EChartsLabelOption `json:"label"`
|
||||
MarkPoint EChartsMarkPoint `json:"markPoint"`
|
||||
MarkLine EChartsMarkLine `json:"markLine"`
|
||||
Max *float64 `json:"max"`
|
||||
Min *float64 `json:"min"`
|
||||
}
|
||||
type EChartsSeriesList []EChartsSeries
|
||||
|
||||
func (esList EChartsSeriesList) ToSeriesList() SeriesList {
|
||||
seriesList := make(SeriesList, 0, len(esList))
|
||||
for _, item := range esList {
|
||||
// 如果是pie,则每个子荐生成一个series
|
||||
if item.Type == ChartTypePie {
|
||||
for _, dataItem := range item.Data {
|
||||
seriesList = append(seriesList, Series{
|
||||
Type: item.Type,
|
||||
Name: dataItem.Name,
|
||||
Label: SeriesLabel{
|
||||
Show: true,
|
||||
},
|
||||
Radius: item.Radius,
|
||||
Data: []SeriesData{
|
||||
{
|
||||
Value: dataItem.Value.First(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 如果是radar或funnel
|
||||
if item.Type == ChartTypeRadar ||
|
||||
item.Type == ChartTypeFunnel {
|
||||
for _, dataItem := range item.Data {
|
||||
seriesList = append(seriesList, Series{
|
||||
Name: dataItem.Name,
|
||||
Type: item.Type,
|
||||
Data: NewSeriesDataFromValues(dataItem.Value.values),
|
||||
Max: item.Max,
|
||||
Min: item.Min,
|
||||
Label: SeriesLabel{
|
||||
Color: parseColor(item.Label.Color),
|
||||
Show: item.Label.Show,
|
||||
Distance: item.Label.Distance,
|
||||
},
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
data := make([]SeriesData, len(item.Data))
|
||||
for j, dataItem := range item.Data {
|
||||
data[j] = SeriesData{
|
||||
Value: dataItem.Value.First(),
|
||||
Style: dataItem.ItemStyle.ToStyle(),
|
||||
}
|
||||
}
|
||||
seriesList = append(seriesList, Series{
|
||||
Type: item.Type,
|
||||
Data: data,
|
||||
AxisIndex: item.YAxisIndex,
|
||||
Style: item.ItemStyle.ToStyle(),
|
||||
Label: SeriesLabel{
|
||||
Color: parseColor(item.Label.Color),
|
||||
Show: item.Label.Show,
|
||||
Distance: item.Label.Distance,
|
||||
},
|
||||
Name: item.Name,
|
||||
MarkPoint: item.MarkPoint.ToSeriesMarkPoint(),
|
||||
MarkLine: item.MarkLine.ToSeriesMarkLine(),
|
||||
})
|
||||
}
|
||||
return seriesList
|
||||
}
|
||||
|
||||
type EChartsTextStyle struct {
|
||||
Color string `json:"color"`
|
||||
FontFamily string `json:"fontFamily"`
|
||||
FontSize float64 `json:"fontSize"`
|
||||
}
|
||||
|
||||
func (et *EChartsTextStyle) ToStyle() chart.Style {
|
||||
s := chart.Style{
|
||||
FontSize: et.FontSize,
|
||||
FontColor: parseColor(et.Color),
|
||||
}
|
||||
if et.FontFamily != "" {
|
||||
s.Font, _ = GetFont(et.FontFamily)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type EChartsOption struct {
|
||||
Type string `json:"type"`
|
||||
Theme string `json:"theme"`
|
||||
FontFamily string `json:"fontFamily"`
|
||||
Padding EChartsPadding `json:"padding"`
|
||||
Box chart.Box `json:"box"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Title struct {
|
||||
Text string `json:"text"`
|
||||
Subtext string `json:"subtext"`
|
||||
Left EChartsPosition `json:"left"`
|
||||
Top EChartsPosition `json:"top"`
|
||||
TextStyle EChartsTextStyle `json:"textStyle"`
|
||||
SubtextStyle EChartsTextStyle `json:"subtextStyle"`
|
||||
} `json:"title"`
|
||||
XAxis EChartsXAxis `json:"xAxis"`
|
||||
YAxis EChartsYAxis `json:"yAxis"`
|
||||
Legend EChartsLegend `json:"legend"`
|
||||
Radar struct {
|
||||
Indicator []RadarIndicator `json:"indicator"`
|
||||
} `json:"radar"`
|
||||
Series EChartsSeriesList `json:"series"`
|
||||
Children []EChartsOption `json:"children"`
|
||||
}
|
||||
|
||||
func (eo *EChartsOption) ToOption() ChartOption {
|
||||
fontFamily := eo.FontFamily
|
||||
if len(fontFamily) == 0 {
|
||||
fontFamily = eo.Title.TextStyle.FontFamily
|
||||
}
|
||||
titleTextStyle := eo.Title.TextStyle.ToStyle()
|
||||
titleSubtextStyle := eo.Title.SubtextStyle.ToStyle()
|
||||
legendTextStyle := eo.Legend.TextStyle.ToStyle()
|
||||
o := ChartOption{
|
||||
Type: eo.Type,
|
||||
FontFamily: fontFamily,
|
||||
Theme: eo.Theme,
|
||||
Title: TitleOption{
|
||||
Text: eo.Title.Text,
|
||||
Subtext: eo.Title.Subtext,
|
||||
FontColor: titleTextStyle.FontColor,
|
||||
FontSize: titleTextStyle.FontSize,
|
||||
SubtextFontSize: titleSubtextStyle.FontSize,
|
||||
SubtextFontColor: titleSubtextStyle.FontColor,
|
||||
Left: string(eo.Title.Left),
|
||||
Top: string(eo.Title.Top),
|
||||
},
|
||||
Legend: LegendOption{
|
||||
Show: eo.Legend.Show,
|
||||
FontSize: legendTextStyle.FontSize,
|
||||
FontColor: legendTextStyle.FontColor,
|
||||
Data: eo.Legend.Data,
|
||||
Left: string(eo.Legend.Left),
|
||||
Top: string(eo.Legend.Top),
|
||||
Align: eo.Legend.Align,
|
||||
Orient: eo.Legend.Orient,
|
||||
},
|
||||
RadarIndicators: eo.Radar.Indicator,
|
||||
Width: eo.Width,
|
||||
Height: eo.Height,
|
||||
Padding: eo.Padding.Box,
|
||||
Box: eo.Box,
|
||||
SeriesList: eo.Series.ToSeriesList(),
|
||||
}
|
||||
isHorizontalChart := false
|
||||
for _, item := range eo.XAxis.Data {
|
||||
if item.Type == "value" {
|
||||
isHorizontalChart = true
|
||||
}
|
||||
}
|
||||
if isHorizontalChart {
|
||||
for index := range o.SeriesList {
|
||||
series := o.SeriesList[index]
|
||||
if series.Type == ChartTypeBar {
|
||||
o.SeriesList[index].Type = ChartTypeHorizontalBar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(eo.XAxis.Data) != 0 {
|
||||
xAxisData := eo.XAxis.Data[0]
|
||||
o.XAxis = XAxisOption{
|
||||
BoundaryGap: xAxisData.BoundaryGap,
|
||||
Data: xAxisData.Data,
|
||||
SplitNumber: xAxisData.SplitNumber,
|
||||
}
|
||||
}
|
||||
yAxisOptions := make([]YAxisOption, len(eo.YAxis.Data))
|
||||
for index, item := range eo.YAxis.Data {
|
||||
yAxisOptions[index] = YAxisOption{
|
||||
Min: item.Min,
|
||||
Max: item.Max,
|
||||
Formatter: item.AxisLabel.Formatter,
|
||||
Color: parseColor(item.AxisLine.LineStyle.Color),
|
||||
Data: item.Data,
|
||||
}
|
||||
}
|
||||
o.YAxisOptions = yAxisOptions
|
||||
|
||||
if len(eo.Children) != 0 {
|
||||
o.Children = make([]ChartOption, len(eo.Children))
|
||||
for index, item := range eo.Children {
|
||||
o.Children[index] = item.ToOption()
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func renderEcharts(options, outputType string) ([]byte, error) {
|
||||
o := EChartsOption{}
|
||||
err := json.Unmarshal([]byte(options), &o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := o.ToOption()
|
||||
opt.Type = outputType
|
||||
d, err := Render(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Bytes()
|
||||
}
|
||||
|
||||
func RenderEChartsToPNG(options string) ([]byte, error) {
|
||||
return renderEcharts(options, "png")
|
||||
}
|
||||
|
||||
func RenderEChartsToSVG(options string) ([]byte, error) {
|
||||
return renderEcharts(options, "svg")
|
||||
}
|
||||
78
src/server/vendor/github.com/vicanso/go-charts/v2/font.go
generated
vendored
Normal file
78
src/server/vendor/github.com/vicanso/go-charts/v2/font.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2/roboto"
|
||||
)
|
||||
|
||||
var fonts = sync.Map{}
|
||||
var ErrFontNotExists = errors.New("font is not exists")
|
||||
var defaultFontFamily = "defaultFontFamily"
|
||||
|
||||
func init() {
|
||||
name := "roboto"
|
||||
_ = InstallFont(name, roboto.Roboto)
|
||||
font, _ := GetFont(name)
|
||||
SetDefaultFont(font)
|
||||
}
|
||||
|
||||
// InstallFont installs the font for charts
|
||||
func InstallFont(fontFamily string, data []byte) error {
|
||||
font, err := truetype.Parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fonts.Store(fontFamily, font)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefaultFont get default font
|
||||
func GetDefaultFont() (*truetype.Font, error) {
|
||||
return GetFont(defaultFontFamily)
|
||||
}
|
||||
|
||||
// SetDefaultFont set default font
|
||||
func SetDefaultFont(font *truetype.Font) {
|
||||
if font == nil {
|
||||
return
|
||||
}
|
||||
fonts.Store(defaultFontFamily, font)
|
||||
}
|
||||
|
||||
// GetFont get the font by font family
|
||||
func GetFont(fontFamily string) (*truetype.Font, error) {
|
||||
value, ok := fonts.Load(fontFamily)
|
||||
if !ok {
|
||||
return nil, ErrFontNotExists
|
||||
}
|
||||
f, ok := value.(*truetype.Font)
|
||||
if !ok {
|
||||
return nil, ErrFontNotExists
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
192
src/server/vendor/github.com/vicanso/go-charts/v2/funnel_chart.go
generated
vendored
Normal file
192
src/server/vendor/github.com/vicanso/go-charts/v2/funnel_chart.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
type funnelChart struct {
|
||||
p *Painter
|
||||
opt *FunnelChartOption
|
||||
}
|
||||
|
||||
// NewFunnelSeriesList returns a series list for funnel
|
||||
func NewFunnelSeriesList(values []float64) SeriesList {
|
||||
seriesList := make(SeriesList, len(values))
|
||||
for index, value := range values {
|
||||
seriesList[index] = NewSeriesFromValues([]float64{
|
||||
value,
|
||||
}, ChartTypeFunnel)
|
||||
}
|
||||
return seriesList
|
||||
}
|
||||
|
||||
// NewFunnelChart returns a funnel chart renderer
|
||||
func NewFunnelChart(p *Painter, opt FunnelChartOption) *funnelChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &funnelChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type FunnelChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
}
|
||||
|
||||
func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
opt := f.opt
|
||||
seriesPainter := result.seriesPainter
|
||||
max := seriesList[0].Data[0].Value
|
||||
min := float64(0)
|
||||
for _, item := range seriesList {
|
||||
if item.Max != nil {
|
||||
max = *item.Max
|
||||
}
|
||||
if item.Min != nil {
|
||||
min = *item.Min
|
||||
}
|
||||
}
|
||||
theme := opt.Theme
|
||||
gap := 2
|
||||
height := seriesPainter.Height()
|
||||
width := seriesPainter.Width()
|
||||
count := len(seriesList)
|
||||
|
||||
h := (height - gap*(count-1)) / count
|
||||
|
||||
y := 0
|
||||
widthList := make([]int, len(seriesList))
|
||||
textList := make([]string, len(seriesList))
|
||||
seriesNames := seriesList.Names()
|
||||
offset := max - min
|
||||
for index, item := range seriesList {
|
||||
value := item.Data[0].Value
|
||||
// 最大最小值一致则为100%
|
||||
widthPercent := 100.0
|
||||
if offset != 0 {
|
||||
widthPercent = (value - min) / offset
|
||||
}
|
||||
w := int(widthPercent * float64(width))
|
||||
widthList[index] = w
|
||||
// 如果最大值为0,则占比100%
|
||||
percent := 1.0
|
||||
if max != 0 {
|
||||
percent = value / max
|
||||
}
|
||||
textList[index] = NewFunnelLabelFormatter(seriesNames, item.Label.Formatter)(index, value, percent)
|
||||
}
|
||||
|
||||
for index, w := range widthList {
|
||||
series := seriesList[index]
|
||||
nextWidth := 0
|
||||
if index+1 < len(widthList) {
|
||||
nextWidth = widthList[index+1]
|
||||
}
|
||||
topStartX := (width - w) >> 1
|
||||
topEndX := topStartX + w
|
||||
bottomStartX := (width - nextWidth) >> 1
|
||||
bottomEndX := bottomStartX + nextWidth
|
||||
points := []Point{
|
||||
{
|
||||
X: topStartX,
|
||||
Y: y,
|
||||
},
|
||||
{
|
||||
X: topEndX,
|
||||
Y: y,
|
||||
},
|
||||
{
|
||||
X: bottomEndX,
|
||||
Y: y + h,
|
||||
},
|
||||
{
|
||||
X: bottomStartX,
|
||||
Y: y + h,
|
||||
},
|
||||
{
|
||||
X: topStartX,
|
||||
Y: y,
|
||||
},
|
||||
}
|
||||
color := theme.GetSeriesColor(series.index)
|
||||
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
FillColor: color,
|
||||
}).FillArea(points)
|
||||
|
||||
// 文本
|
||||
text := textList[index]
|
||||
seriesPainter.OverrideTextStyle(Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
FontSize: labelFontSize,
|
||||
Font: opt.Font,
|
||||
})
|
||||
textBox := seriesPainter.MeasureText(text)
|
||||
textX := width>>1 - textBox.Width()>>1
|
||||
textY := y + h>>1
|
||||
seriesPainter.Text(text, textX, textY)
|
||||
y += (h + gap)
|
||||
}
|
||||
|
||||
return f.p.box, nil
|
||||
}
|
||||
|
||||
func (f *funnelChart) Render() (Box, error) {
|
||||
p := f.p
|
||||
opt := f.opt
|
||||
renderResult, err := defaultRender(p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: XAxisOption{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
YAxisOptions: []YAxisOption{
|
||||
{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
},
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypeFunnel)
|
||||
return f.render(renderResult, seriesList)
|
||||
}
|
||||
92
src/server/vendor/github.com/vicanso/go-charts/v2/grid.go
generated
vendored
Normal file
92
src/server/vendor/github.com/vicanso/go-charts/v2/grid.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
type gridPainter struct {
|
||||
p *Painter
|
||||
opt *GridPainterOption
|
||||
}
|
||||
|
||||
type GridPainterOption struct {
|
||||
// The stroke width
|
||||
StrokeWidth float64
|
||||
// The stroke color
|
||||
StrokeColor Color
|
||||
// The spans of column
|
||||
ColumnSpans []int
|
||||
// The column of grid
|
||||
Column int
|
||||
// The row of grid
|
||||
Row int
|
||||
// Ignore first row
|
||||
IgnoreFirstRow bool
|
||||
// Ignore last row
|
||||
IgnoreLastRow bool
|
||||
// Ignore first column
|
||||
IgnoreFirstColumn bool
|
||||
// Ignore last column
|
||||
IgnoreLastColumn bool
|
||||
}
|
||||
|
||||
// NewGridPainter returns new a grid renderer
|
||||
func NewGridPainter(p *Painter, opt GridPainterOption) *gridPainter {
|
||||
return &gridPainter{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gridPainter) Render() (Box, error) {
|
||||
opt := g.opt
|
||||
ignoreColumnLines := make([]int, 0)
|
||||
if opt.IgnoreFirstColumn {
|
||||
ignoreColumnLines = append(ignoreColumnLines, 0)
|
||||
}
|
||||
if opt.IgnoreLastColumn {
|
||||
ignoreColumnLines = append(ignoreColumnLines, opt.Column)
|
||||
}
|
||||
ignoreRowLines := make([]int, 0)
|
||||
if opt.IgnoreFirstRow {
|
||||
ignoreRowLines = append(ignoreRowLines, 0)
|
||||
}
|
||||
if opt.IgnoreLastRow {
|
||||
ignoreRowLines = append(ignoreRowLines, opt.Row)
|
||||
}
|
||||
strokeWidth := opt.StrokeWidth
|
||||
if strokeWidth <= 0 {
|
||||
strokeWidth = 1
|
||||
}
|
||||
|
||||
g.p.SetDrawingStyle(Style{
|
||||
StrokeWidth: strokeWidth,
|
||||
StrokeColor: opt.StrokeColor,
|
||||
})
|
||||
g.p.Grid(GridOption{
|
||||
Column: opt.Column,
|
||||
ColumnSpans: opt.ColumnSpans,
|
||||
Row: opt.Row,
|
||||
IgnoreColumnLines: ignoreColumnLines,
|
||||
IgnoreRowLines: ignoreRowLines,
|
||||
})
|
||||
return g.p.box, nil
|
||||
}
|
||||
216
src/server/vendor/github.com/vicanso/go-charts/v2/horizontal_bar_chart.go
generated
vendored
Normal file
216
src/server/vendor/github.com/vicanso/go-charts/v2/horizontal_bar_chart.go
generated
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type horizontalBarChart struct {
|
||||
p *Painter
|
||||
opt *HorizontalBarChartOption
|
||||
}
|
||||
|
||||
type HorizontalBarChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The x axis option
|
||||
XAxis XAxisOption
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The y axis option
|
||||
YAxisOptions []YAxisOption
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
BarHeight int
|
||||
// Margin of bar
|
||||
BarMargin int
|
||||
}
|
||||
|
||||
// NewHorizontalBarChart returns a horizontal bar chart renderer
|
||||
func NewHorizontalBarChart(p *Painter, opt HorizontalBarChartOption) *horizontalBarChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &horizontalBarChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
p := h.p
|
||||
opt := h.opt
|
||||
seriesPainter := result.seriesPainter
|
||||
yRange := result.axisRanges[0]
|
||||
y0, y1 := yRange.GetRange(0)
|
||||
height := int(y1 - y0)
|
||||
// 每一块之间的margin
|
||||
margin := 10
|
||||
// 每一个bar之间的margin
|
||||
barMargin := 5
|
||||
if height < 20 {
|
||||
margin = 2
|
||||
barMargin = 2
|
||||
} else if height < 50 {
|
||||
margin = 5
|
||||
barMargin = 3
|
||||
}
|
||||
if opt.BarMargin > 0 {
|
||||
barMargin = opt.BarMargin
|
||||
}
|
||||
seriesCount := len(seriesList)
|
||||
// 总的高度-两个margin-(总数-1)的barMargin
|
||||
barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / seriesCount
|
||||
if opt.BarHeight > 0 && opt.BarHeight < barHeight {
|
||||
barHeight = opt.BarHeight
|
||||
margin = (height - seriesCount*barHeight - barMargin*(seriesCount-1)) / 2
|
||||
}
|
||||
|
||||
theme := opt.Theme
|
||||
|
||||
max, min := seriesList.GetMaxMin(0)
|
||||
xRange := NewRange(AxisRangeOption{
|
||||
Painter: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
DivideCount: defaultAxisDivideCount,
|
||||
Size: seriesPainter.Width(),
|
||||
})
|
||||
seriesNames := seriesList.Names()
|
||||
|
||||
rendererList := []Renderer{}
|
||||
for index := range seriesList {
|
||||
series := seriesList[index]
|
||||
seriesColor := theme.GetSeriesColor(series.index)
|
||||
divideValues := yRange.AutoDivide()
|
||||
|
||||
var labelPainter *SeriesLabelPainter
|
||||
if series.Label.Show {
|
||||
labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{
|
||||
P: seriesPainter,
|
||||
SeriesNames: seriesNames,
|
||||
Label: series.Label,
|
||||
Theme: opt.Theme,
|
||||
Font: opt.Font,
|
||||
})
|
||||
rendererList = append(rendererList, labelPainter)
|
||||
}
|
||||
for j, item := range series.Data {
|
||||
if j >= yRange.divideCount {
|
||||
continue
|
||||
}
|
||||
// 显示位置切换
|
||||
j = yRange.divideCount - j - 1
|
||||
y := divideValues[j]
|
||||
y += margin
|
||||
if index != 0 {
|
||||
y += index * (barHeight + barMargin)
|
||||
}
|
||||
|
||||
w := int(xRange.getHeight(item.Value))
|
||||
fillColor := seriesColor
|
||||
if !item.Style.FillColor.IsZero() {
|
||||
fillColor = item.Style.FillColor
|
||||
}
|
||||
right := w
|
||||
if series.RoundRadius <= 0 {
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
}).Rect(chart.Box{
|
||||
Top: y,
|
||||
Left: 0,
|
||||
Right: right,
|
||||
Bottom: y + barHeight,
|
||||
})
|
||||
} else {
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
}).RoundedRect(chart.Box{
|
||||
Top: y,
|
||||
Left: 0,
|
||||
Right: right,
|
||||
Bottom: y + barHeight,
|
||||
}, series.RoundRadius)
|
||||
}
|
||||
|
||||
// 如果label不需要展示,则返回
|
||||
if labelPainter == nil {
|
||||
continue
|
||||
}
|
||||
labelValue := LabelValue{
|
||||
Orient: OrientHorizontal,
|
||||
Index: index,
|
||||
Value: item.Value,
|
||||
X: right,
|
||||
Y: y + barHeight>>1,
|
||||
Offset: series.Label.Offset,
|
||||
FontColor: series.Label.Color,
|
||||
FontSize: series.Label.FontSize,
|
||||
}
|
||||
if series.Label.Position == PositionLeft {
|
||||
labelValue.X = 0
|
||||
if labelValue.FontColor.IsZero() {
|
||||
if isLightColor(fillColor) {
|
||||
labelValue.FontColor = defaultLightFontColor
|
||||
} else {
|
||||
labelValue.FontColor = defaultDarkFontColor
|
||||
}
|
||||
}
|
||||
}
|
||||
labelPainter.Add(labelValue)
|
||||
}
|
||||
}
|
||||
err := doRender(rendererList...)
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
return p.box, nil
|
||||
}
|
||||
|
||||
func (h *horizontalBarChart) Render() (Box, error) {
|
||||
p := h.p
|
||||
opt := h.opt
|
||||
renderResult, err := defaultRender(p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: opt.XAxis,
|
||||
YAxisOptions: opt.YAxisOptions,
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
axisReversed: true,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypeHorizontalBar)
|
||||
return h.render(renderResult, seriesList)
|
||||
}
|
||||
251
src/server/vendor/github.com/vicanso/go-charts/v2/legend.go
generated
vendored
Normal file
251
src/server/vendor/github.com/vicanso/go-charts/v2/legend.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type legendPainter struct {
|
||||
p *Painter
|
||||
opt *LegendOption
|
||||
}
|
||||
|
||||
const IconRect = "rect"
|
||||
const IconLineDot = "lineDot"
|
||||
|
||||
type LegendOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// Text array of legend
|
||||
Data []string
|
||||
// Distance between legend component and the left side of the container.
|
||||
// It can be pixel value: 20, percentage value: 20%,
|
||||
// or position value: right, center.
|
||||
Left string
|
||||
// Distance between legend component and the top side of the container.
|
||||
// It can be pixel value: 20.
|
||||
Top string
|
||||
// Legend marker and text aligning, it can be left or right, default is left.
|
||||
Align string
|
||||
// The layout orientation of legend, it can be horizontal or vertical, default is horizontal.
|
||||
Orient string
|
||||
// Icon of the legend.
|
||||
Icon string
|
||||
// Font size of legend text
|
||||
FontSize float64
|
||||
// FontColor color of legend text
|
||||
FontColor Color
|
||||
// The flag for show legend, set this to *false will hide legend
|
||||
Show *bool
|
||||
// The padding of legend
|
||||
Padding Box
|
||||
}
|
||||
|
||||
// NewLegendOption returns a legend option
|
||||
func NewLegendOption(labels []string, left ...string) LegendOption {
|
||||
opt := LegendOption{
|
||||
Data: labels,
|
||||
}
|
||||
if len(left) != 0 {
|
||||
opt.Left = left[0]
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// IsEmpty checks legend is empty
|
||||
func (opt *LegendOption) IsEmpty() bool {
|
||||
isEmpty := true
|
||||
for _, v := range opt.Data {
|
||||
if v != "" {
|
||||
isEmpty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return isEmpty
|
||||
}
|
||||
|
||||
// NewLegendPainter returns a legend renderer
|
||||
func NewLegendPainter(p *Painter, opt LegendOption) *legendPainter {
|
||||
return &legendPainter{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *legendPainter) Render() (Box, error) {
|
||||
opt := l.opt
|
||||
theme := opt.Theme
|
||||
if opt.IsEmpty() ||
|
||||
isFalse(opt.Show) {
|
||||
return BoxZero, nil
|
||||
}
|
||||
if theme == nil {
|
||||
theme = l.p.theme
|
||||
}
|
||||
if opt.FontSize == 0 {
|
||||
opt.FontSize = theme.GetFontSize()
|
||||
}
|
||||
if opt.FontColor.IsZero() {
|
||||
opt.FontColor = theme.GetTextColor()
|
||||
}
|
||||
if opt.Left == "" {
|
||||
opt.Left = PositionCenter
|
||||
}
|
||||
padding := opt.Padding
|
||||
if padding.IsZero() {
|
||||
padding.Top = 5
|
||||
}
|
||||
p := l.p.Child(PainterPaddingOption(padding))
|
||||
p.SetTextStyle(Style{
|
||||
FontSize: opt.FontSize,
|
||||
FontColor: opt.FontColor,
|
||||
})
|
||||
measureList := make([]Box, len(opt.Data))
|
||||
maxTextWidth := 0
|
||||
for index, text := range opt.Data {
|
||||
b := p.MeasureText(text)
|
||||
if b.Width() > maxTextWidth {
|
||||
maxTextWidth = b.Width()
|
||||
}
|
||||
measureList[index] = b
|
||||
}
|
||||
|
||||
// 计算展示的宽高
|
||||
width := 0
|
||||
height := 0
|
||||
offset := 20
|
||||
textOffset := 2
|
||||
legendWidth := 30
|
||||
legendHeight := 20
|
||||
itemMaxHeight := 0
|
||||
for _, item := range measureList {
|
||||
if item.Height() > itemMaxHeight {
|
||||
itemMaxHeight = item.Height()
|
||||
}
|
||||
if opt.Orient == OrientVertical {
|
||||
height += item.Height()
|
||||
} else {
|
||||
width += item.Width()
|
||||
}
|
||||
}
|
||||
// 增加padding
|
||||
itemMaxHeight += 10
|
||||
if opt.Orient == OrientVertical {
|
||||
width = maxTextWidth + textOffset + legendWidth
|
||||
height = offset * len(opt.Data)
|
||||
} else {
|
||||
height = legendHeight
|
||||
offsetValue := (len(opt.Data) - 1) * (offset + textOffset)
|
||||
allLegendWidth := len(opt.Data) * legendWidth
|
||||
width += (offsetValue + allLegendWidth)
|
||||
}
|
||||
|
||||
// 计算开始的位置
|
||||
left := 0
|
||||
switch opt.Left {
|
||||
case PositionRight:
|
||||
left = p.Width() - width
|
||||
case PositionCenter:
|
||||
left = (p.Width() - width) >> 1
|
||||
default:
|
||||
if strings.HasSuffix(opt.Left, "%") {
|
||||
value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", ""))
|
||||
left = p.Width() * value / 100
|
||||
} else {
|
||||
value, _ := strconv.Atoi(opt.Left)
|
||||
left = value
|
||||
}
|
||||
}
|
||||
top, _ := strconv.Atoi(opt.Top)
|
||||
|
||||
if left < 0 {
|
||||
left = 0
|
||||
}
|
||||
|
||||
x := int(left)
|
||||
y := int(top) + 10
|
||||
startY := y
|
||||
x0 := x
|
||||
y0 := y
|
||||
|
||||
drawIcon := func(top, left int) int {
|
||||
if opt.Icon == IconRect {
|
||||
p.Rect(Box{
|
||||
Top: top - legendHeight + 8,
|
||||
Left: left,
|
||||
Right: left + legendWidth,
|
||||
Bottom: top + 1,
|
||||
})
|
||||
} else {
|
||||
p.LegendLineDot(Box{
|
||||
Top: top + 1,
|
||||
Left: left,
|
||||
Right: left + legendWidth,
|
||||
Bottom: top + legendHeight + 1,
|
||||
})
|
||||
}
|
||||
return left + legendWidth
|
||||
}
|
||||
lastIndex := len(opt.Data) - 1
|
||||
for index, text := range opt.Data {
|
||||
color := theme.GetSeriesColor(index)
|
||||
p.SetDrawingStyle(Style{
|
||||
FillColor: color,
|
||||
StrokeColor: color,
|
||||
})
|
||||
itemWidth := x0 + measureList[index].Width() + textOffset + offset + legendWidth
|
||||
if lastIndex == index {
|
||||
itemWidth = x0 + measureList[index].Width() + legendWidth
|
||||
}
|
||||
if itemWidth > p.Width() {
|
||||
x0 = 0
|
||||
y += itemMaxHeight
|
||||
y0 = y
|
||||
}
|
||||
if opt.Align != AlignRight {
|
||||
x0 = drawIcon(y0, x0)
|
||||
x0 += textOffset
|
||||
}
|
||||
p.Text(text, x0, y0)
|
||||
x0 += measureList[index].Width()
|
||||
if opt.Align == AlignRight {
|
||||
x0 += textOffset
|
||||
x0 = drawIcon(y0, x0)
|
||||
}
|
||||
if opt.Orient == OrientVertical {
|
||||
y0 += offset
|
||||
x0 = x
|
||||
} else {
|
||||
x0 += offset
|
||||
y0 = y
|
||||
}
|
||||
height = y0 - startY + 10
|
||||
}
|
||||
|
||||
return Box{
|
||||
Right: width,
|
||||
Bottom: height + padding.Bottom + padding.Top,
|
||||
}, nil
|
||||
}
|
||||
240
src/server/vendor/github.com/vicanso/go-charts/v2/line_chart.go
generated
vendored
Normal file
240
src/server/vendor/github.com/vicanso/go-charts/v2/line_chart.go
generated
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
type lineChart struct {
|
||||
p *Painter
|
||||
opt *LineChartOption
|
||||
}
|
||||
|
||||
// NewLineChart returns a line chart render
|
||||
func NewLineChart(p *Painter, opt LineChartOption) *lineChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &lineChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type LineChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The x axis option
|
||||
XAxis XAxisOption
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The y axis option
|
||||
YAxisOptions []YAxisOption
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
// The flag for show symbol of line, set this to *false will hide symbol
|
||||
SymbolShow *bool
|
||||
// The stroke width of line
|
||||
StrokeWidth float64
|
||||
// Fill the area of line
|
||||
FillArea bool
|
||||
// background is filled
|
||||
backgroundIsFilled bool
|
||||
// background fill (alpha) opacity
|
||||
Opacity uint8
|
||||
}
|
||||
|
||||
func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
p := l.p
|
||||
opt := l.opt
|
||||
boundaryGap := true
|
||||
if isFalse(opt.XAxis.BoundaryGap) {
|
||||
boundaryGap = false
|
||||
}
|
||||
|
||||
seriesPainter := result.seriesPainter
|
||||
|
||||
xDivideCount := len(opt.XAxis.Data)
|
||||
if !boundaryGap {
|
||||
xDivideCount--
|
||||
}
|
||||
xDivideValues := autoDivide(seriesPainter.Width(), xDivideCount)
|
||||
xValues := make([]int, len(xDivideValues)-1)
|
||||
if boundaryGap {
|
||||
for i := 0; i < len(xDivideValues)-1; i++ {
|
||||
xValues[i] = (xDivideValues[i] + xDivideValues[i+1]) >> 1
|
||||
}
|
||||
} else {
|
||||
xValues = xDivideValues
|
||||
}
|
||||
markPointPainter := NewMarkPointPainter(seriesPainter)
|
||||
markLinePainter := NewMarkLinePainter(seriesPainter)
|
||||
rendererList := []Renderer{
|
||||
markPointPainter,
|
||||
markLinePainter,
|
||||
}
|
||||
strokeWidth := opt.StrokeWidth
|
||||
if strokeWidth == 0 {
|
||||
strokeWidth = defaultStrokeWidth
|
||||
}
|
||||
seriesNames := seriesList.Names()
|
||||
for index := range seriesList {
|
||||
series := seriesList[index]
|
||||
seriesColor := opt.Theme.GetSeriesColor(series.index)
|
||||
drawingStyle := Style{
|
||||
StrokeColor: seriesColor,
|
||||
StrokeWidth: strokeWidth,
|
||||
}
|
||||
if len(series.Style.StrokeDashArray) > 0 {
|
||||
drawingStyle.StrokeDashArray = series.Style.StrokeDashArray
|
||||
}
|
||||
|
||||
yRange := result.axisRanges[series.AxisIndex]
|
||||
points := make([]Point, 0)
|
||||
var labelPainter *SeriesLabelPainter
|
||||
if series.Label.Show {
|
||||
labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{
|
||||
P: seriesPainter,
|
||||
SeriesNames: seriesNames,
|
||||
Label: series.Label,
|
||||
Theme: opt.Theme,
|
||||
Font: opt.Font,
|
||||
})
|
||||
rendererList = append(rendererList, labelPainter)
|
||||
}
|
||||
for i, item := range series.Data {
|
||||
h := yRange.getRestHeight(item.Value)
|
||||
if item.Value == nullValue {
|
||||
h = int(math.MaxInt32)
|
||||
}
|
||||
p := Point{
|
||||
X: xValues[i],
|
||||
Y: h,
|
||||
}
|
||||
points = append(points, p)
|
||||
|
||||
// 如果label不需要展示,则返回
|
||||
if labelPainter == nil {
|
||||
continue
|
||||
}
|
||||
labelPainter.Add(LabelValue{
|
||||
Index: index,
|
||||
Value: item.Value,
|
||||
X: p.X,
|
||||
Y: p.Y,
|
||||
// 字体大小
|
||||
FontSize: series.Label.FontSize,
|
||||
})
|
||||
}
|
||||
// 如果需要填充区域
|
||||
if opt.FillArea {
|
||||
areaPoints := make([]Point, len(points))
|
||||
copy(areaPoints, points)
|
||||
bottomY := yRange.getRestHeight(yRange.min)
|
||||
var opacity uint8 = 200
|
||||
if opt.Opacity != 0 {
|
||||
opacity = opt.Opacity
|
||||
}
|
||||
areaPoints = append(areaPoints, Point{
|
||||
X: areaPoints[len(areaPoints)-1].X,
|
||||
Y: bottomY,
|
||||
}, Point{
|
||||
X: areaPoints[0].X,
|
||||
Y: bottomY,
|
||||
}, areaPoints[0])
|
||||
seriesPainter.SetDrawingStyle(Style{
|
||||
FillColor: seriesColor.WithAlpha(opacity),
|
||||
})
|
||||
seriesPainter.FillArea(areaPoints)
|
||||
}
|
||||
seriesPainter.SetDrawingStyle(drawingStyle)
|
||||
|
||||
// 画线
|
||||
seriesPainter.LineStroke(points)
|
||||
|
||||
// 画点
|
||||
if opt.Theme.IsDark() {
|
||||
drawingStyle.FillColor = drawingStyle.StrokeColor
|
||||
} else {
|
||||
drawingStyle.FillColor = drawing.ColorWhite
|
||||
}
|
||||
drawingStyle.StrokeWidth = 1
|
||||
seriesPainter.SetDrawingStyle(drawingStyle)
|
||||
if !isFalse(opt.SymbolShow) {
|
||||
seriesPainter.Dots(points)
|
||||
}
|
||||
markPointPainter.Add(markPointRenderOption{
|
||||
FillColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Points: points,
|
||||
Series: series,
|
||||
})
|
||||
markLinePainter.Add(markLineRenderOption{
|
||||
FillColor: seriesColor,
|
||||
FontColor: opt.Theme.GetTextColor(),
|
||||
StrokeColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Series: series,
|
||||
Range: yRange,
|
||||
})
|
||||
}
|
||||
// 最大、最小的mark point
|
||||
err := doRender(rendererList...)
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
|
||||
return p.box, nil
|
||||
}
|
||||
|
||||
func (l *lineChart) Render() (Box, error) {
|
||||
p := l.p
|
||||
opt := l.opt
|
||||
|
||||
renderResult, err := defaultRender(p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: opt.XAxis,
|
||||
YAxisOptions: opt.YAxisOptions,
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
backgroundIsFilled: opt.backgroundIsFilled,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypeLine)
|
||||
|
||||
return l.render(renderResult, seriesList)
|
||||
}
|
||||
113
src/server/vendor/github.com/vicanso/go-charts/v2/mark_line.go
generated
vendored
Normal file
113
src/server/vendor/github.com/vicanso/go-charts/v2/mark_line.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// NewMarkLine returns a series mark line
|
||||
func NewMarkLine(markLineTypes ...string) SeriesMarkLine {
|
||||
data := make([]SeriesMarkData, len(markLineTypes))
|
||||
for index, t := range markLineTypes {
|
||||
data[index] = SeriesMarkData{
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
return SeriesMarkLine{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type markLinePainter struct {
|
||||
p *Painter
|
||||
options []markLineRenderOption
|
||||
}
|
||||
|
||||
func (m *markLinePainter) Add(opt markLineRenderOption) {
|
||||
m.options = append(m.options, opt)
|
||||
}
|
||||
|
||||
// NewMarkLinePainter returns a mark line renderer
|
||||
func NewMarkLinePainter(p *Painter) *markLinePainter {
|
||||
return &markLinePainter{
|
||||
p: p,
|
||||
options: make([]markLineRenderOption, 0),
|
||||
}
|
||||
}
|
||||
|
||||
type markLineRenderOption struct {
|
||||
FillColor Color
|
||||
FontColor Color
|
||||
StrokeColor Color
|
||||
Font *truetype.Font
|
||||
Series Series
|
||||
Range axisRange
|
||||
}
|
||||
|
||||
func (m *markLinePainter) Render() (Box, error) {
|
||||
painter := m.p
|
||||
for _, opt := range m.options {
|
||||
s := opt.Series
|
||||
if len(s.MarkLine.Data) == 0 {
|
||||
continue
|
||||
}
|
||||
font := opt.Font
|
||||
if font == nil {
|
||||
font, _ = GetDefaultFont()
|
||||
}
|
||||
summary := s.Summary()
|
||||
for _, markLine := range s.MarkLine.Data {
|
||||
// 由于mark line会修改style,因此每次重新设置
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: opt.FillColor,
|
||||
StrokeColor: opt.StrokeColor,
|
||||
StrokeWidth: 1,
|
||||
StrokeDashArray: []float64{
|
||||
4,
|
||||
2,
|
||||
},
|
||||
}).OverrideTextStyle(Style{
|
||||
Font: font,
|
||||
FontColor: opt.FontColor,
|
||||
FontSize: labelFontSize,
|
||||
})
|
||||
value := float64(0)
|
||||
switch markLine.Type {
|
||||
case SeriesMarkDataTypeMax:
|
||||
value = summary.MaxValue
|
||||
case SeriesMarkDataTypeMin:
|
||||
value = summary.MinValue
|
||||
default:
|
||||
value = summary.AverageValue
|
||||
}
|
||||
y := opt.Range.getRestHeight(value)
|
||||
width := painter.Width()
|
||||
text := commafWithDigits(value)
|
||||
textBox := painter.MeasureText(text)
|
||||
painter.MarkLine(0, y, width-2)
|
||||
painter.Text(text, width, y+textBox.Height()>>1-2)
|
||||
}
|
||||
}
|
||||
return BoxZero, nil
|
||||
}
|
||||
115
src/server/vendor/github.com/vicanso/go-charts/v2/mark_point.go
generated
vendored
Normal file
115
src/server/vendor/github.com/vicanso/go-charts/v2/mark_point.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// NewMarkPoint returns a series mark point
|
||||
func NewMarkPoint(markPointTypes ...string) SeriesMarkPoint {
|
||||
data := make([]SeriesMarkData, len(markPointTypes))
|
||||
for index, t := range markPointTypes {
|
||||
data[index] = SeriesMarkData{
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
return SeriesMarkPoint{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type markPointPainter struct {
|
||||
p *Painter
|
||||
options []markPointRenderOption
|
||||
}
|
||||
|
||||
func (m *markPointPainter) Add(opt markPointRenderOption) {
|
||||
m.options = append(m.options, opt)
|
||||
}
|
||||
|
||||
type markPointRenderOption struct {
|
||||
FillColor Color
|
||||
Font *truetype.Font
|
||||
Series Series
|
||||
Points []Point
|
||||
}
|
||||
|
||||
// NewMarkPointPainter returns a mark point renderer
|
||||
func NewMarkPointPainter(p *Painter) *markPointPainter {
|
||||
return &markPointPainter{
|
||||
p: p,
|
||||
options: make([]markPointRenderOption, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *markPointPainter) Render() (Box, error) {
|
||||
painter := m.p
|
||||
for _, opt := range m.options {
|
||||
s := opt.Series
|
||||
if len(s.MarkPoint.Data) == 0 {
|
||||
continue
|
||||
}
|
||||
points := opt.Points
|
||||
summary := s.Summary()
|
||||
symbolSize := s.MarkPoint.SymbolSize
|
||||
if symbolSize == 0 {
|
||||
symbolSize = 30
|
||||
}
|
||||
textStyle := Style{
|
||||
FontSize: labelFontSize,
|
||||
StrokeWidth: 1,
|
||||
Font: opt.Font,
|
||||
}
|
||||
if isLightColor(opt.FillColor) {
|
||||
textStyle.FontColor = defaultLightFontColor
|
||||
} else {
|
||||
textStyle.FontColor = defaultDarkFontColor
|
||||
}
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: opt.FillColor,
|
||||
}).OverrideTextStyle(textStyle)
|
||||
for _, markPointData := range s.MarkPoint.Data {
|
||||
textStyle.FontSize = labelFontSize
|
||||
painter.OverrideTextStyle(textStyle)
|
||||
p := points[summary.MinIndex]
|
||||
value := summary.MinValue
|
||||
switch markPointData.Type {
|
||||
case SeriesMarkDataTypeMax:
|
||||
p = points[summary.MaxIndex]
|
||||
value = summary.MaxValue
|
||||
}
|
||||
|
||||
painter.Pin(p.X, p.Y-symbolSize>>1, symbolSize)
|
||||
text := commafWithDigits(value)
|
||||
textBox := painter.MeasureText(text)
|
||||
if textBox.Width() > symbolSize {
|
||||
textStyle.FontSize = smallLabelFontSize
|
||||
painter.OverrideTextStyle(textStyle)
|
||||
textBox = painter.MeasureText(text)
|
||||
}
|
||||
painter.Text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2)
|
||||
}
|
||||
}
|
||||
return BoxZero, nil
|
||||
}
|
||||
866
src/server/vendor/github.com/vicanso/go-charts/v2/painter.go
generated
vendored
Normal file
866
src/server/vendor/github.com/vicanso/go-charts/v2/painter.go
generated
vendored
Normal file
@ -0,0 +1,866 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type ValueFormatter func(float64) string
|
||||
|
||||
type Painter struct {
|
||||
render chart.Renderer
|
||||
box Box
|
||||
font *truetype.Font
|
||||
parent *Painter
|
||||
style Style
|
||||
theme ColorPalette
|
||||
// 类型
|
||||
outputType string
|
||||
valueFormatter ValueFormatter
|
||||
}
|
||||
|
||||
type PainterOptions struct {
|
||||
// Draw type, "svg" or "png", default type is "png"
|
||||
Type string
|
||||
// The width of draw painter
|
||||
Width int
|
||||
// The height of draw painter
|
||||
Height int
|
||||
// The font for painter
|
||||
Font *truetype.Font
|
||||
}
|
||||
|
||||
type PainterOption func(*Painter)
|
||||
|
||||
type TicksOption struct {
|
||||
// the first tick
|
||||
First int
|
||||
Length int
|
||||
Orient string
|
||||
Count int
|
||||
Unit int
|
||||
}
|
||||
|
||||
type MultiTextOption struct {
|
||||
TextList []string
|
||||
Orient string
|
||||
Unit int
|
||||
Position string
|
||||
Align string
|
||||
// The text rotation of label
|
||||
TextRotation float64
|
||||
Offset Box
|
||||
// The first text index
|
||||
First int
|
||||
}
|
||||
|
||||
type GridOption struct {
|
||||
Column int
|
||||
Row int
|
||||
ColumnSpans []int
|
||||
// 忽略不展示的column
|
||||
IgnoreColumnLines []int
|
||||
// 忽略不展示的row
|
||||
IgnoreRowLines []int
|
||||
}
|
||||
|
||||
// PainterPaddingOption sets the padding of draw painter
|
||||
func PainterPaddingOption(padding Box) PainterOption {
|
||||
return func(p *Painter) {
|
||||
p.box.Left += padding.Left
|
||||
p.box.Top += padding.Top
|
||||
p.box.Right -= padding.Right
|
||||
p.box.Bottom -= padding.Bottom
|
||||
}
|
||||
}
|
||||
|
||||
// PainterBoxOption sets the box of draw painter
|
||||
func PainterBoxOption(box Box) PainterOption {
|
||||
return func(p *Painter) {
|
||||
if box.IsZero() {
|
||||
return
|
||||
}
|
||||
p.box = box
|
||||
}
|
||||
}
|
||||
|
||||
// PainterFontOption sets the font of draw painter
|
||||
func PainterFontOption(font *truetype.Font) PainterOption {
|
||||
return func(p *Painter) {
|
||||
if font == nil {
|
||||
return
|
||||
}
|
||||
p.font = font
|
||||
}
|
||||
}
|
||||
|
||||
// PainterStyleOption sets the style of draw painter
|
||||
func PainterStyleOption(style Style) PainterOption {
|
||||
return func(p *Painter) {
|
||||
p.SetStyle(style)
|
||||
}
|
||||
}
|
||||
|
||||
// PainterThemeOption sets the theme of draw painter
|
||||
func PainterThemeOption(theme ColorPalette) PainterOption {
|
||||
return func(p *Painter) {
|
||||
if theme == nil {
|
||||
return
|
||||
}
|
||||
p.theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// PainterWidthHeightOption set width or height of draw painter
|
||||
func PainterWidthHeightOption(width, height int) PainterOption {
|
||||
return func(p *Painter) {
|
||||
if width > 0 {
|
||||
p.box.Right = p.box.Left + width
|
||||
}
|
||||
if height > 0 {
|
||||
p.box.Bottom = p.box.Top + height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewPainter creates a painter
|
||||
func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) {
|
||||
if opts.Width <= 0 || opts.Height <= 0 {
|
||||
return nil, errors.New("width/height can not be nil")
|
||||
}
|
||||
font := opts.Font
|
||||
if font == nil {
|
||||
f, err := GetDefaultFont()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
font = f
|
||||
}
|
||||
fn := chart.PNG
|
||||
if opts.Type == ChartOutputSVG {
|
||||
fn = chart.SVG
|
||||
}
|
||||
width := opts.Width
|
||||
height := opts.Height
|
||||
r, err := fn(width, height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.SetFont(font)
|
||||
|
||||
p := &Painter{
|
||||
render: r,
|
||||
box: Box{
|
||||
Right: opts.Width,
|
||||
Bottom: opts.Height,
|
||||
},
|
||||
font: font,
|
||||
// 类型
|
||||
outputType: opts.Type,
|
||||
}
|
||||
p.setOptions(opt...)
|
||||
if p.theme == nil {
|
||||
p.theme = NewTheme(ThemeLight)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
func (p *Painter) setOptions(opts ...PainterOption) {
|
||||
for _, fn := range opts {
|
||||
fn(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Painter) Child(opt ...PainterOption) *Painter {
|
||||
child := &Painter{
|
||||
// 格式化
|
||||
valueFormatter: p.valueFormatter,
|
||||
// render
|
||||
render: p.render,
|
||||
box: p.box.Clone(),
|
||||
font: p.font,
|
||||
parent: p,
|
||||
style: p.style,
|
||||
theme: p.theme,
|
||||
}
|
||||
child.setOptions(opt...)
|
||||
return child
|
||||
}
|
||||
|
||||
func (p *Painter) SetStyle(style Style) {
|
||||
if style.Font == nil {
|
||||
style.Font = p.font
|
||||
}
|
||||
p.style = style
|
||||
style.WriteToRenderer(p.render)
|
||||
}
|
||||
|
||||
func overrideStyle(defaultStyle Style, style Style) Style {
|
||||
if style.StrokeWidth == 0 {
|
||||
style.StrokeWidth = defaultStyle.StrokeWidth
|
||||
}
|
||||
if style.StrokeColor.IsZero() {
|
||||
style.StrokeColor = defaultStyle.StrokeColor
|
||||
}
|
||||
if style.StrokeDashArray == nil {
|
||||
style.StrokeDashArray = defaultStyle.StrokeDashArray
|
||||
}
|
||||
if style.DotColor.IsZero() {
|
||||
style.DotColor = defaultStyle.DotColor
|
||||
}
|
||||
if style.DotWidth == 0 {
|
||||
style.DotWidth = defaultStyle.DotWidth
|
||||
}
|
||||
if style.FillColor.IsZero() {
|
||||
style.FillColor = defaultStyle.FillColor
|
||||
}
|
||||
if style.FontSize == 0 {
|
||||
style.FontSize = defaultStyle.FontSize
|
||||
}
|
||||
if style.FontColor.IsZero() {
|
||||
style.FontColor = defaultStyle.FontColor
|
||||
}
|
||||
if style.Font == nil {
|
||||
style.Font = defaultStyle.Font
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
func (p *Painter) OverrideDrawingStyle(style Style) *Painter {
|
||||
s := overrideStyle(p.style, style)
|
||||
p.SetDrawingStyle(s)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) SetDrawingStyle(style Style) *Painter {
|
||||
style.WriteDrawingOptionsToRenderer(p.render)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) SetTextStyle(style Style) *Painter {
|
||||
if style.Font == nil {
|
||||
style.Font = p.font
|
||||
}
|
||||
style.WriteTextOptionsToRenderer(p.render)
|
||||
return p
|
||||
}
|
||||
func (p *Painter) OverrideTextStyle(style Style) *Painter {
|
||||
s := overrideStyle(p.style, style)
|
||||
p.SetTextStyle(s)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) ResetStyle() *Painter {
|
||||
p.style.WriteToRenderer(p.render)
|
||||
return p
|
||||
}
|
||||
|
||||
// Bytes returns the data of draw canvas
|
||||
func (p *Painter) Bytes() ([]byte, error) {
|
||||
buffer := bytes.Buffer{}
|
||||
err := p.render.Save(&buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
// MoveTo moves the cursor to a given point
|
||||
func (p *Painter) MoveTo(x, y int) *Painter {
|
||||
p.render.MoveTo(x+p.box.Left, y+p.box.Top)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) *Painter {
|
||||
p.render.ArcTo(cx+p.box.Left, cy+p.box.Top, rx, ry, startAngle, delta)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) LineTo(x, y int) *Painter {
|
||||
p.render.LineTo(x+p.box.Left, y+p.box.Top)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) QuadCurveTo(cx, cy, x, y int) *Painter {
|
||||
p.render.QuadCurveTo(cx+p.box.Left, cy+p.box.Top, x+p.box.Left, y+p.box.Top)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Pin(x, y, width int) *Painter {
|
||||
r := float64(width) / 2
|
||||
y -= width / 4
|
||||
angle := chart.DegreesToRadians(15)
|
||||
box := p.box
|
||||
|
||||
startAngle := math.Pi/2 + angle
|
||||
delta := 2*math.Pi - 2*angle
|
||||
p.ArcTo(x, y, r, r, startAngle, delta)
|
||||
p.LineTo(x, y)
|
||||
p.Close()
|
||||
p.FillStroke()
|
||||
|
||||
startX := x - int(r)
|
||||
startY := y
|
||||
endX := x + int(r)
|
||||
endY := y
|
||||
p.MoveTo(startX, startY)
|
||||
|
||||
left := box.Left
|
||||
top := box.Top
|
||||
cx := x
|
||||
cy := y + int(r*2.5)
|
||||
p.render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top)
|
||||
p.Close()
|
||||
p.Fill()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) arrow(x, y, width, height int, direction string) *Painter {
|
||||
halfWidth := width >> 1
|
||||
halfHeight := height >> 1
|
||||
if direction == PositionTop || direction == PositionBottom {
|
||||
x0 := x - halfWidth
|
||||
x1 := x0 + width
|
||||
dy := -height / 3
|
||||
y0 := y
|
||||
y1 := y0 - height
|
||||
if direction == PositionBottom {
|
||||
y0 = y - height
|
||||
y1 = y
|
||||
dy = 2 * dy
|
||||
}
|
||||
p.MoveTo(x0, y0)
|
||||
p.LineTo(x0+halfWidth, y1)
|
||||
p.LineTo(x1, y0)
|
||||
p.LineTo(x0+halfWidth, y+dy)
|
||||
p.LineTo(x0, y0)
|
||||
} else {
|
||||
x0 := x + width
|
||||
x1 := x0 - width
|
||||
y0 := y - halfHeight
|
||||
dx := -width / 3
|
||||
if direction == PositionRight {
|
||||
x0 = x - width
|
||||
dx = -dx
|
||||
x1 = x0 + width
|
||||
}
|
||||
p.MoveTo(x0, y0)
|
||||
p.LineTo(x1, y0+halfHeight)
|
||||
p.LineTo(x0, y0+height)
|
||||
p.LineTo(x0+dx, y0+halfHeight)
|
||||
p.LineTo(x0, y0)
|
||||
}
|
||||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) ArrowLeft(x, y, width, height int) *Painter {
|
||||
p.arrow(x, y, width, height, PositionLeft)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) ArrowRight(x, y, width, height int) *Painter {
|
||||
p.arrow(x, y, width, height, PositionRight)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) ArrowTop(x, y, width, height int) *Painter {
|
||||
p.arrow(x, y, width, height, PositionTop)
|
||||
return p
|
||||
}
|
||||
func (p *Painter) ArrowBottom(x, y, width, height int) *Painter {
|
||||
p.arrow(x, y, width, height, PositionBottom)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Circle(radius float64, x, y int) *Painter {
|
||||
p.render.Circle(radius, x+p.box.Left, y+p.box.Top)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Stroke() *Painter {
|
||||
p.render.Stroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Close() *Painter {
|
||||
p.render.Close()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) FillStroke() *Painter {
|
||||
p.render.FillStroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Fill() *Painter {
|
||||
p.render.Fill()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Width() int {
|
||||
return p.box.Width()
|
||||
}
|
||||
|
||||
func (p *Painter) Height() int {
|
||||
return p.box.Height()
|
||||
}
|
||||
|
||||
func (p *Painter) MeasureText(text string) Box {
|
||||
return p.render.MeasureText(text)
|
||||
}
|
||||
|
||||
func (p *Painter) MeasureTextMaxWidthHeight(textList []string) (int, int) {
|
||||
maxWidth := 0
|
||||
maxHeight := 0
|
||||
for _, text := range textList {
|
||||
box := p.MeasureText(text)
|
||||
if maxWidth < box.Width() {
|
||||
maxWidth = box.Width()
|
||||
}
|
||||
if maxHeight < box.Height() {
|
||||
maxHeight = box.Height()
|
||||
}
|
||||
}
|
||||
return maxWidth, maxHeight
|
||||
}
|
||||
|
||||
func (p *Painter) LineStroke(points []Point) *Painter {
|
||||
shouldMoveTo := false
|
||||
for index, point := range points {
|
||||
x := point.X
|
||||
y := point.Y
|
||||
if y == int(math.MaxInt32) {
|
||||
p.Stroke()
|
||||
shouldMoveTo = true
|
||||
continue
|
||||
}
|
||||
if shouldMoveTo || index == 0 {
|
||||
p.MoveTo(x, y)
|
||||
shouldMoveTo = false
|
||||
} else {
|
||||
p.LineTo(x, y)
|
||||
}
|
||||
}
|
||||
p.Stroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) SmoothLineStroke(points []Point) *Painter {
|
||||
prevX := 0
|
||||
prevY := 0
|
||||
// TODO 如何生成平滑的折线
|
||||
for index, point := range points {
|
||||
x := point.X
|
||||
y := point.Y
|
||||
if index == 0 {
|
||||
p.MoveTo(x, y)
|
||||
} else {
|
||||
cx := prevX + (x-prevX)/5
|
||||
cy := y + (y-prevY)/2
|
||||
p.QuadCurveTo(cx, cy, x, y)
|
||||
}
|
||||
prevX = x
|
||||
prevY = y
|
||||
}
|
||||
p.Stroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) SetBackground(width, height int, color Color, inside ...bool) *Painter {
|
||||
r := p.render
|
||||
s := chart.Style{
|
||||
FillColor: color,
|
||||
}
|
||||
// 背景色
|
||||
p.SetDrawingStyle(s)
|
||||
defer p.ResetStyle()
|
||||
if len(inside) != 0 && inside[0] {
|
||||
p.MoveTo(0, 0)
|
||||
p.LineTo(width, 0)
|
||||
p.LineTo(width, height)
|
||||
p.LineTo(0, height)
|
||||
p.LineTo(0, 0)
|
||||
} else {
|
||||
// 设置背景色不使用box,因此不直接使用Painter
|
||||
r.MoveTo(0, 0)
|
||||
r.LineTo(width, 0)
|
||||
r.LineTo(width, height)
|
||||
r.LineTo(0, height)
|
||||
r.LineTo(0, 0)
|
||||
}
|
||||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
func (p *Painter) MarkLine(x, y, width int) *Painter {
|
||||
arrowWidth := 16
|
||||
arrowHeight := 10
|
||||
endX := x + width
|
||||
radius := 3
|
||||
p.Circle(3, x+radius, y)
|
||||
p.render.Fill()
|
||||
p.MoveTo(x+radius*3, y)
|
||||
p.LineTo(endX-arrowWidth, y)
|
||||
p.Stroke()
|
||||
p.ArrowRight(endX, y, arrowWidth, arrowHeight)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Polygon(center Point, radius float64, sides int) *Painter {
|
||||
points := getPolygonPoints(center, radius, sides)
|
||||
for i, item := range points {
|
||||
if i == 0 {
|
||||
p.MoveTo(item.X, item.Y)
|
||||
} else {
|
||||
p.LineTo(item.X, item.Y)
|
||||
}
|
||||
}
|
||||
p.LineTo(points[0].X, points[0].Y)
|
||||
p.Stroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) FillArea(points []Point) *Painter {
|
||||
var x, y int
|
||||
for index, point := range points {
|
||||
x = point.X
|
||||
y = point.Y
|
||||
if index == 0 {
|
||||
p.MoveTo(x, y)
|
||||
} else {
|
||||
p.LineTo(x, y)
|
||||
}
|
||||
}
|
||||
p.Fill()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Text(body string, x, y int) *Painter {
|
||||
p.render.Text(body, x+p.box.Left, y+p.box.Top)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) TextRotation(body string, x, y int, radians float64) {
|
||||
p.render.SetTextRotation(radians)
|
||||
p.render.Text(body, x+p.box.Left, y+p.box.Top)
|
||||
p.render.ClearTextRotation()
|
||||
}
|
||||
|
||||
func (p *Painter) SetTextRotation(radians float64) {
|
||||
p.render.SetTextRotation(radians)
|
||||
}
|
||||
func (p *Painter) ClearTextRotation() {
|
||||
p.render.ClearTextRotation()
|
||||
}
|
||||
|
||||
func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) chart.Box {
|
||||
style := p.style
|
||||
textWarp := style.TextWrap
|
||||
style.TextWrap = chart.TextWrapWord
|
||||
r := p.render
|
||||
lines := chart.Text.WrapFit(r, body, width, style)
|
||||
p.SetTextStyle(style)
|
||||
var output chart.Box
|
||||
|
||||
textAlign := ""
|
||||
if len(textAligns) != 0 {
|
||||
textAlign = textAligns[0]
|
||||
}
|
||||
for index, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
x0 := x
|
||||
y0 := y + output.Height()
|
||||
lineBox := r.MeasureText(line)
|
||||
switch textAlign {
|
||||
case AlignRight:
|
||||
x0 += width - lineBox.Width()
|
||||
case AlignCenter:
|
||||
x0 += (width - lineBox.Width()) >> 1
|
||||
}
|
||||
p.Text(line, x0, y0)
|
||||
output.Right = chart.MaxInt(lineBox.Right, output.Right)
|
||||
output.Bottom += lineBox.Height()
|
||||
if index < len(lines)-1 {
|
||||
output.Bottom += +style.GetTextLineSpacing()
|
||||
}
|
||||
}
|
||||
p.style.TextWrap = textWarp
|
||||
return output
|
||||
}
|
||||
|
||||
func (p *Painter) Ticks(opt TicksOption) *Painter {
|
||||
if opt.Count <= 0 || opt.Length <= 0 {
|
||||
return p
|
||||
}
|
||||
count := opt.Count
|
||||
first := opt.First
|
||||
width := p.Width()
|
||||
height := p.Height()
|
||||
unit := 1
|
||||
if opt.Unit > 1 {
|
||||
unit = opt.Unit
|
||||
}
|
||||
var values []int
|
||||
isVertical := opt.Orient == OrientVertical
|
||||
if isVertical {
|
||||
values = autoDivide(height, count)
|
||||
} else {
|
||||
values = autoDivide(width, count)
|
||||
}
|
||||
for index, value := range values {
|
||||
if index < first {
|
||||
continue
|
||||
}
|
||||
if (index-first)%unit != 0 {
|
||||
continue
|
||||
}
|
||||
if isVertical {
|
||||
p.LineStroke([]Point{
|
||||
{
|
||||
X: 0,
|
||||
Y: value,
|
||||
},
|
||||
{
|
||||
X: opt.Length,
|
||||
Y: value,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
p.LineStroke([]Point{
|
||||
{
|
||||
X: value,
|
||||
Y: opt.Length,
|
||||
},
|
||||
{
|
||||
X: value,
|
||||
Y: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) MultiText(opt MultiTextOption) *Painter {
|
||||
if len(opt.TextList) == 0 {
|
||||
return p
|
||||
}
|
||||
count := len(opt.TextList)
|
||||
positionCenter := true
|
||||
showIndex := opt.Unit / 2
|
||||
if containsString([]string{
|
||||
PositionLeft,
|
||||
PositionTop,
|
||||
}, opt.Position) {
|
||||
positionCenter = false
|
||||
count--
|
||||
// 非居中
|
||||
showIndex = 0
|
||||
}
|
||||
width := p.Width()
|
||||
height := p.Height()
|
||||
var values []int
|
||||
isVertical := opt.Orient == OrientVertical
|
||||
if isVertical {
|
||||
values = autoDivide(height, count)
|
||||
} else {
|
||||
values = autoDivide(width, count)
|
||||
}
|
||||
isTextRotation := opt.TextRotation != 0
|
||||
offset := opt.Offset
|
||||
for index, text := range opt.TextList {
|
||||
if index < opt.First {
|
||||
continue
|
||||
}
|
||||
if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex {
|
||||
continue
|
||||
}
|
||||
if isTextRotation {
|
||||
p.ClearTextRotation()
|
||||
p.SetTextRotation(opt.TextRotation)
|
||||
}
|
||||
box := p.MeasureText(text)
|
||||
start := values[index]
|
||||
if positionCenter {
|
||||
start = (values[index] + values[index+1]) >> 1
|
||||
}
|
||||
x := 0
|
||||
y := 0
|
||||
if isVertical {
|
||||
y = start + box.Height()>>1
|
||||
switch opt.Align {
|
||||
case AlignRight:
|
||||
x = width - box.Width()
|
||||
case AlignCenter:
|
||||
x = width - box.Width()>>1
|
||||
default:
|
||||
x = 0
|
||||
}
|
||||
} else {
|
||||
x = start - box.Width()>>1
|
||||
}
|
||||
x += offset.Left
|
||||
y += offset.Top
|
||||
p.Text(text, x, y)
|
||||
}
|
||||
if isTextRotation {
|
||||
p.ClearTextRotation()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Grid(opt GridOption) *Painter {
|
||||
width := p.Width()
|
||||
height := p.Height()
|
||||
drawLines := func(values []int, ignoreIndexList []int, isVertical bool) {
|
||||
for index, v := range values {
|
||||
if containsInt(ignoreIndexList, index) {
|
||||
continue
|
||||
}
|
||||
x0 := 0
|
||||
y0 := 0
|
||||
x1 := 0
|
||||
y1 := 0
|
||||
if isVertical {
|
||||
|
||||
x0 = v
|
||||
x1 = v
|
||||
y1 = height
|
||||
} else {
|
||||
x1 = width
|
||||
y0 = v
|
||||
y1 = v
|
||||
}
|
||||
p.LineStroke([]Point{
|
||||
{
|
||||
X: x0,
|
||||
Y: y0,
|
||||
},
|
||||
{
|
||||
X: x1,
|
||||
Y: y1,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
columnCount := sumInt(opt.ColumnSpans)
|
||||
if columnCount == 0 {
|
||||
columnCount = opt.Column
|
||||
}
|
||||
if columnCount > 0 {
|
||||
values := autoDivideSpans(width, columnCount, opt.ColumnSpans)
|
||||
drawLines(values, opt.IgnoreColumnLines, true)
|
||||
}
|
||||
if opt.Row > 0 {
|
||||
values := autoDivide(height, opt.Row)
|
||||
drawLines(values, opt.IgnoreRowLines, false)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Dots(points []Point) *Painter {
|
||||
for _, item := range points {
|
||||
p.Circle(2, item.X, item.Y)
|
||||
}
|
||||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) Rect(box Box) *Painter {
|
||||
p.MoveTo(box.Left, box.Top)
|
||||
p.LineTo(box.Right, box.Top)
|
||||
p.LineTo(box.Right, box.Bottom)
|
||||
p.LineTo(box.Left, box.Bottom)
|
||||
p.LineTo(box.Left, box.Top)
|
||||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) RoundedRect(box Box, radius int) *Painter {
|
||||
r := (box.Right - box.Left) / 2
|
||||
if radius > r {
|
||||
radius = r
|
||||
}
|
||||
rx := float64(radius)
|
||||
ry := float64(radius)
|
||||
p.MoveTo(box.Left+radius, box.Top)
|
||||
p.LineTo(box.Right-radius, box.Top)
|
||||
|
||||
cx := box.Right - radius
|
||||
cy := box.Top + radius
|
||||
// right top
|
||||
p.ArcTo(cx, cy, rx, ry, -math.Pi/2, math.Pi/2)
|
||||
|
||||
p.LineTo(box.Right, box.Bottom-radius)
|
||||
|
||||
// right bottom
|
||||
cx = box.Right - radius
|
||||
cy = box.Bottom - radius
|
||||
p.ArcTo(cx, cy, rx, ry, 0.0, math.Pi/2)
|
||||
|
||||
p.LineTo(box.Left+radius, box.Bottom)
|
||||
|
||||
// left bottom
|
||||
cx = box.Left + radius
|
||||
cy = box.Bottom - radius
|
||||
p.ArcTo(cx, cy, rx, ry, math.Pi/2, math.Pi/2)
|
||||
|
||||
p.LineTo(box.Left, box.Top+radius)
|
||||
|
||||
// left top
|
||||
cx = box.Left + radius
|
||||
cy = box.Top + radius
|
||||
p.ArcTo(cx, cy, rx, ry, math.Pi, math.Pi/2)
|
||||
|
||||
p.Close()
|
||||
p.FillStroke()
|
||||
p.Fill()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) LegendLineDot(box Box) *Painter {
|
||||
width := box.Width()
|
||||
height := box.Height()
|
||||
strokeWidth := 3
|
||||
dotHeight := 5
|
||||
|
||||
p.render.SetStrokeWidth(float64(strokeWidth))
|
||||
center := (height-strokeWidth)>>1 - 1
|
||||
p.MoveTo(box.Left, box.Top-center)
|
||||
p.LineTo(box.Right, box.Top-center)
|
||||
p.Stroke()
|
||||
p.Circle(float64(dotHeight), box.Left+width>>1, box.Top-center)
|
||||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Painter) GetRenderer() chart.Renderer {
|
||||
return p.render
|
||||
}
|
||||
318
src/server/vendor/github.com/vicanso/go-charts/v2/pie_chart.go
generated
vendored
Normal file
318
src/server/vendor/github.com/vicanso/go-charts/v2/pie_chart.go
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type pieChart struct {
|
||||
p *Painter
|
||||
opt *PieChartOption
|
||||
}
|
||||
|
||||
type PieChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
// background is filled
|
||||
backgroundIsFilled bool
|
||||
}
|
||||
|
||||
// NewPieChart returns a pie chart renderer
|
||||
func NewPieChart(p *Painter, opt PieChartOption) *pieChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &pieChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type sector struct {
|
||||
value float64
|
||||
percent float64
|
||||
cx int
|
||||
cy int
|
||||
rx float64
|
||||
ry float64
|
||||
start float64
|
||||
delta float64
|
||||
offset int
|
||||
quadrant int
|
||||
lineStartX int
|
||||
lineStartY int
|
||||
lineBranchX int
|
||||
lineBranchY int
|
||||
lineEndX int
|
||||
lineEndY int
|
||||
showLabel bool
|
||||
label string
|
||||
series Series
|
||||
color Color
|
||||
}
|
||||
|
||||
func NewSector(cx int, cy int, radius float64, labelRadius float64, value float64, currentValue float64, totalValue float64, labelLineLength int, label string, series Series, color Color) sector {
|
||||
s := sector{}
|
||||
s.value = value
|
||||
s.percent = value / totalValue
|
||||
s.cx = cx
|
||||
s.cy = cy
|
||||
s.rx = radius
|
||||
s.ry = radius
|
||||
p := (currentValue + value/2) / totalValue
|
||||
if p < 0.25 {
|
||||
s.quadrant = 1
|
||||
} else if p < 0.5 {
|
||||
s.quadrant = 4
|
||||
} else if p < 0.75 {
|
||||
s.quadrant = 3
|
||||
} else {
|
||||
s.quadrant = 2
|
||||
}
|
||||
s.start = chart.PercentToRadians(currentValue/totalValue) - math.Pi/2
|
||||
s.delta = chart.PercentToRadians(value / totalValue)
|
||||
angle := s.start + s.delta/2
|
||||
s.lineStartX = cx + int(radius*math.Cos(angle))
|
||||
s.lineStartY = cy + int(radius*math.Sin(angle))
|
||||
s.lineBranchX = cx + int(labelRadius*math.Cos(angle))
|
||||
s.lineBranchY = cy + int(labelRadius*math.Sin(angle))
|
||||
s.offset = labelLineLength
|
||||
if s.lineBranchX <= cx {
|
||||
s.offset *= -1
|
||||
}
|
||||
s.lineEndX = s.lineBranchX + s.offset
|
||||
s.lineEndY = s.lineBranchY
|
||||
s.series = series
|
||||
s.color = color
|
||||
s.showLabel = series.Label.Show
|
||||
s.label = NewPieLabelFormatter([]string{label}, series.Label.Formatter)(0, s.value, s.percent)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *sector) calculateY(prevY int) int {
|
||||
for i := 0; i <= s.cy; i++ {
|
||||
if s.quadrant <= 2 {
|
||||
if (prevY - s.lineBranchY) > labelFontSize+5 {
|
||||
break
|
||||
}
|
||||
s.lineBranchY -= 1
|
||||
} else {
|
||||
if (s.lineBranchY - prevY) > labelFontSize+5 {
|
||||
break
|
||||
}
|
||||
s.lineBranchY += 1
|
||||
}
|
||||
}
|
||||
s.lineEndY = s.lineBranchY
|
||||
return s.lineBranchY
|
||||
}
|
||||
|
||||
func (s *sector) calculateTextXY(textBox Box) (x int, y int) {
|
||||
textMargin := 3
|
||||
x = s.lineEndX + textMargin
|
||||
y = s.lineEndY + textBox.Height()>>1 - 1
|
||||
if s.offset < 0 {
|
||||
textWidth := textBox.Width()
|
||||
x = s.lineEndX - textWidth - textMargin
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
opt := p.opt
|
||||
values := make([]float64, len(seriesList))
|
||||
total := float64(0)
|
||||
radiusValue := ""
|
||||
for index, series := range seriesList {
|
||||
if len(series.Radius) != 0 {
|
||||
radiusValue = series.Radius
|
||||
}
|
||||
value := float64(0)
|
||||
for _, item := range series.Data {
|
||||
value += item.Value
|
||||
}
|
||||
values[index] = value
|
||||
total += value
|
||||
}
|
||||
if total <= 0 {
|
||||
return BoxZero, errors.New("The sum value of pie chart should gt 0")
|
||||
}
|
||||
seriesPainter := result.seriesPainter
|
||||
cx := seriesPainter.Width() >> 1
|
||||
cy := seriesPainter.Height() >> 1
|
||||
|
||||
diameter := chart.MinInt(seriesPainter.Width(), seriesPainter.Height())
|
||||
radius := getRadius(float64(diameter), radiusValue)
|
||||
|
||||
labelLineWidth := 15
|
||||
if radius < 50 {
|
||||
labelLineWidth = 10
|
||||
}
|
||||
labelRadius := radius + float64(labelLineWidth)
|
||||
seriesNames := opt.Legend.Data
|
||||
if len(seriesNames) == 0 {
|
||||
seriesNames = seriesList.Names()
|
||||
}
|
||||
theme := opt.Theme
|
||||
|
||||
currentValue := float64(0)
|
||||
|
||||
var quadrant1, quadrant2, quadrant3, quadrant4 []sector
|
||||
for index, v := range values {
|
||||
series := seriesList[index]
|
||||
color := theme.GetSeriesColor(index)
|
||||
if index == len(values)-1 {
|
||||
if color == theme.GetSeriesColor(0) {
|
||||
color = theme.GetSeriesColor(1)
|
||||
}
|
||||
}
|
||||
s := NewSector(cx, cy, radius, labelRadius, v, currentValue, total, labelLineWidth, seriesNames[index], series, color)
|
||||
switch quadrant := s.quadrant; quadrant {
|
||||
case 1:
|
||||
quadrant1 = append([]sector{s}, quadrant1...)
|
||||
case 2:
|
||||
quadrant2 = append(quadrant2, s)
|
||||
case 3:
|
||||
quadrant3 = append([]sector{s}, quadrant3...)
|
||||
case 4:
|
||||
quadrant4 = append(quadrant4, s)
|
||||
}
|
||||
currentValue += v
|
||||
}
|
||||
sectors := append(quadrant1, quadrant4...)
|
||||
sectors = append(sectors, quadrant3...)
|
||||
sectors = append(sectors, quadrant2...)
|
||||
|
||||
currentQuadrant := 0
|
||||
prevY := 0
|
||||
maxY := 0
|
||||
minY := 0
|
||||
for _, s := range sectors {
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
StrokeWidth: 1,
|
||||
StrokeColor: s.color,
|
||||
FillColor: s.color,
|
||||
})
|
||||
seriesPainter.MoveTo(s.cx, s.cy)
|
||||
seriesPainter.ArcTo(s.cx, s.cy, s.rx, s.ry, s.start, s.delta).LineTo(s.cx, s.cy).Close().FillStroke()
|
||||
if !s.showLabel {
|
||||
continue
|
||||
}
|
||||
if currentQuadrant != s.quadrant {
|
||||
if s.quadrant == 1 {
|
||||
minY = cy * 2
|
||||
maxY = 0
|
||||
prevY = cy * 2
|
||||
}
|
||||
if s.quadrant == 2 {
|
||||
if currentQuadrant != 3 {
|
||||
prevY = s.lineEndY
|
||||
} else {
|
||||
prevY = minY
|
||||
}
|
||||
}
|
||||
if s.quadrant == 3 {
|
||||
if currentQuadrant != 4 {
|
||||
prevY = s.lineEndY
|
||||
} else {
|
||||
minY = cy * 2
|
||||
maxY = 0
|
||||
prevY = 0
|
||||
}
|
||||
}
|
||||
if s.quadrant == 4 {
|
||||
if currentQuadrant != 1 {
|
||||
prevY = s.lineEndY
|
||||
} else {
|
||||
prevY = maxY
|
||||
}
|
||||
}
|
||||
currentQuadrant = s.quadrant
|
||||
}
|
||||
prevY = s.calculateY(prevY)
|
||||
if prevY > maxY {
|
||||
maxY = prevY
|
||||
}
|
||||
if prevY < minY {
|
||||
minY = prevY
|
||||
}
|
||||
seriesPainter.MoveTo(s.lineStartX, s.lineStartY)
|
||||
seriesPainter.LineTo(s.lineBranchX, s.lineBranchY)
|
||||
seriesPainter.MoveTo(s.lineBranchX, s.lineBranchY)
|
||||
seriesPainter.LineTo(s.lineEndX, s.lineEndY)
|
||||
seriesPainter.Stroke()
|
||||
textStyle := Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
FontSize: labelFontSize,
|
||||
Font: opt.Font,
|
||||
}
|
||||
if !s.series.Label.Color.IsZero() {
|
||||
textStyle.FontColor = s.series.Label.Color
|
||||
}
|
||||
seriesPainter.OverrideTextStyle(textStyle)
|
||||
x, y := s.calculateTextXY(seriesPainter.MeasureText(s.label))
|
||||
seriesPainter.Text(s.label, x, y)
|
||||
}
|
||||
return p.p.box, nil
|
||||
}
|
||||
|
||||
func (p *pieChart) Render() (Box, error) {
|
||||
opt := p.opt
|
||||
|
||||
renderResult, err := defaultRender(p.p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: XAxisOption{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
YAxisOptions: []YAxisOption{
|
||||
{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
},
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
backgroundIsFilled: opt.backgroundIsFilled,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypePie)
|
||||
return p.render(renderResult, seriesList)
|
||||
}
|
||||
273
src/server/vendor/github.com/vicanso/go-charts/v2/radar_chart.go
generated
vendored
Normal file
273
src/server/vendor/github.com/vicanso/go-charts/v2/radar_chart.go
generated
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
type radarChart struct {
|
||||
p *Painter
|
||||
opt *RadarChartOption
|
||||
}
|
||||
|
||||
type RadarIndicator struct {
|
||||
// Indicator's name
|
||||
Name string
|
||||
// The maximum value of indicator
|
||||
Max float64
|
||||
// The minimum value of indicator
|
||||
Min float64
|
||||
}
|
||||
|
||||
type RadarChartOption struct {
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The font size
|
||||
Font *truetype.Font
|
||||
// The data series list
|
||||
SeriesList SeriesList
|
||||
// The padding of line chart
|
||||
Padding Box
|
||||
// The option of title
|
||||
Title TitleOption
|
||||
// The legend option
|
||||
Legend LegendOption
|
||||
// The radar indicator list
|
||||
RadarIndicators []RadarIndicator
|
||||
// background is filled
|
||||
backgroundIsFilled bool
|
||||
}
|
||||
|
||||
// NewRadarIndicators returns a radar indicator list
|
||||
func NewRadarIndicators(names []string, values []float64) []RadarIndicator {
|
||||
if len(names) != len(values) {
|
||||
return nil
|
||||
}
|
||||
indicators := make([]RadarIndicator, len(names))
|
||||
for index, name := range names {
|
||||
indicators[index] = RadarIndicator{
|
||||
Name: name,
|
||||
Max: values[index],
|
||||
}
|
||||
}
|
||||
return indicators
|
||||
}
|
||||
|
||||
// NewRadarChart returns a radar chart renderer
|
||||
func NewRadarChart(p *Painter, opt RadarChartOption) *radarChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &radarChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
|
||||
opt := r.opt
|
||||
indicators := opt.RadarIndicators
|
||||
sides := len(indicators)
|
||||
if sides < 3 {
|
||||
return BoxZero, errors.New("The count of indicator should be >= 3")
|
||||
}
|
||||
maxValues := make([]float64, len(indicators))
|
||||
for _, series := range seriesList {
|
||||
for index, item := range series.Data {
|
||||
if index < len(maxValues) && item.Value > maxValues[index] {
|
||||
maxValues[index] = item.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
for index, indicator := range indicators {
|
||||
if indicator.Max <= 0 {
|
||||
indicators[index].Max = maxValues[index]
|
||||
}
|
||||
}
|
||||
|
||||
radiusValue := ""
|
||||
for _, series := range seriesList {
|
||||
if len(series.Radius) != 0 {
|
||||
radiusValue = series.Radius
|
||||
}
|
||||
}
|
||||
|
||||
seriesPainter := result.seriesPainter
|
||||
theme := opt.Theme
|
||||
|
||||
cx := seriesPainter.Width() >> 1
|
||||
cy := seriesPainter.Height() >> 1
|
||||
diameter := chart.MinInt(seriesPainter.Width(), seriesPainter.Height())
|
||||
radius := getRadius(float64(diameter), radiusValue)
|
||||
|
||||
divideCount := 5
|
||||
divideRadius := float64(int(radius / float64(divideCount)))
|
||||
radius = divideRadius * float64(divideCount)
|
||||
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
StrokeColor: theme.GetAxisSplitLineColor(),
|
||||
StrokeWidth: 1,
|
||||
})
|
||||
center := Point{
|
||||
X: cx,
|
||||
Y: cy,
|
||||
}
|
||||
for i := 0; i < divideCount; i++ {
|
||||
seriesPainter.Polygon(center, divideRadius*float64(i+1), sides)
|
||||
}
|
||||
points := getPolygonPoints(center, radius, sides)
|
||||
for _, p := range points {
|
||||
seriesPainter.MoveTo(center.X, center.Y)
|
||||
seriesPainter.LineTo(p.X, p.Y)
|
||||
seriesPainter.Stroke()
|
||||
}
|
||||
seriesPainter.OverrideTextStyle(Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
FontSize: labelFontSize,
|
||||
Font: opt.Font,
|
||||
})
|
||||
offset := 5
|
||||
// 文本生成
|
||||
for index, p := range points {
|
||||
name := indicators[index].Name
|
||||
b := seriesPainter.MeasureText(name)
|
||||
isXCenter := p.X == center.X
|
||||
isYCenter := p.Y == center.Y
|
||||
isRight := p.X > center.X
|
||||
isLeft := p.X < center.X
|
||||
isTop := p.Y < center.Y
|
||||
isBottom := p.Y > center.Y
|
||||
x := p.X
|
||||
y := p.Y
|
||||
if isXCenter {
|
||||
x -= b.Width() >> 1
|
||||
if isTop {
|
||||
y -= b.Height()
|
||||
} else {
|
||||
y += b.Height()
|
||||
}
|
||||
}
|
||||
if isYCenter {
|
||||
y += b.Height() >> 1
|
||||
}
|
||||
if isTop {
|
||||
y += offset
|
||||
}
|
||||
if isBottom {
|
||||
y += offset
|
||||
}
|
||||
if isRight {
|
||||
x += offset
|
||||
}
|
||||
if isLeft {
|
||||
x -= (b.Width() + offset)
|
||||
}
|
||||
seriesPainter.Text(name, x, y)
|
||||
}
|
||||
|
||||
// 雷达图
|
||||
angles := getPolygonPointAngles(sides)
|
||||
maxCount := len(indicators)
|
||||
for _, series := range seriesList {
|
||||
linePoints := make([]Point, 0, maxCount)
|
||||
for j, item := range series.Data {
|
||||
if j >= maxCount {
|
||||
continue
|
||||
}
|
||||
indicator := indicators[j]
|
||||
var percent float64
|
||||
offset := indicator.Max - indicator.Min
|
||||
if offset > 0 {
|
||||
percent = (item.Value - indicator.Min) / offset
|
||||
}
|
||||
r := percent * radius
|
||||
p := getPolygonPoint(center, r, angles[j])
|
||||
linePoints = append(linePoints, p)
|
||||
}
|
||||
color := theme.GetSeriesColor(series.index)
|
||||
dotFillColor := drawing.ColorWhite
|
||||
if theme.IsDark() {
|
||||
dotFillColor = color
|
||||
}
|
||||
linePoints = append(linePoints, linePoints[0])
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
StrokeColor: color,
|
||||
StrokeWidth: defaultStrokeWidth,
|
||||
DotWidth: defaultDotWidth,
|
||||
DotColor: color,
|
||||
FillColor: color.WithAlpha(20),
|
||||
})
|
||||
seriesPainter.LineStroke(linePoints).
|
||||
FillArea(linePoints)
|
||||
dotWith := 2.0
|
||||
seriesPainter.OverrideDrawingStyle(Style{
|
||||
StrokeWidth: defaultStrokeWidth,
|
||||
StrokeColor: color,
|
||||
FillColor: dotFillColor,
|
||||
})
|
||||
for index, point := range linePoints {
|
||||
seriesPainter.Circle(dotWith, point.X, point.Y)
|
||||
seriesPainter.FillStroke()
|
||||
if series.Label.Show && index < len(series.Data) {
|
||||
value := humanize.FtoaWithDigits(series.Data[index].Value, 2)
|
||||
b := seriesPainter.MeasureText(value)
|
||||
seriesPainter.Text(value, point.X-b.Width()/2, point.Y)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return r.p.box, nil
|
||||
}
|
||||
|
||||
func (r *radarChart) Render() (Box, error) {
|
||||
p := r.p
|
||||
opt := r.opt
|
||||
renderResult, err := defaultRender(p, defaultRenderOption{
|
||||
Theme: opt.Theme,
|
||||
Padding: opt.Padding,
|
||||
SeriesList: opt.SeriesList,
|
||||
XAxis: XAxisOption{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
YAxisOptions: []YAxisOption{
|
||||
{
|
||||
Show: FalseFlag(),
|
||||
},
|
||||
},
|
||||
TitleOption: opt.Title,
|
||||
LegendOption: opt.Legend,
|
||||
backgroundIsFilled: opt.backgroundIsFilled,
|
||||
})
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
seriesList := opt.SeriesList.Filter(ChartTypeRadar)
|
||||
return r.render(renderResult, seriesList)
|
||||
}
|
||||
144
src/server/vendor/github.com/vicanso/go-charts/v2/range.go
generated
vendored
Normal file
144
src/server/vendor/github.com/vicanso/go-charts/v2/range.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const defaultAxisDivideCount = 6
|
||||
|
||||
type axisRange struct {
|
||||
p *Painter
|
||||
divideCount int
|
||||
min float64
|
||||
max float64
|
||||
size int
|
||||
boundary bool
|
||||
}
|
||||
|
||||
type AxisRangeOption struct {
|
||||
Painter *Painter
|
||||
// The min value of axis
|
||||
Min float64
|
||||
// The max value of axis
|
||||
Max float64
|
||||
// The size of axis
|
||||
Size int
|
||||
// Boundary gap
|
||||
Boundary bool
|
||||
// The count of divide
|
||||
DivideCount int
|
||||
}
|
||||
|
||||
// NewRange returns a axis range
|
||||
func NewRange(opt AxisRangeOption) axisRange {
|
||||
max := opt.Max
|
||||
min := opt.Min
|
||||
|
||||
max += math.Abs(max * 0.1)
|
||||
min -= math.Abs(min * 0.1)
|
||||
divideCount := opt.DivideCount
|
||||
r := math.Abs(max - min)
|
||||
|
||||
// 最小单位计算
|
||||
unit := 1
|
||||
if r > 5 {
|
||||
unit = 2
|
||||
}
|
||||
if r > 10 {
|
||||
unit = 4
|
||||
}
|
||||
if r > 30 {
|
||||
unit = 5
|
||||
}
|
||||
if r > 100 {
|
||||
unit = 10
|
||||
}
|
||||
if r > 200 {
|
||||
unit = 20
|
||||
}
|
||||
unit = int((r/float64(divideCount))/float64(unit))*unit + unit
|
||||
|
||||
if min != 0 {
|
||||
isLessThanZero := min < 0
|
||||
min = float64(int(min/float64(unit)) * unit)
|
||||
// 如果是小于0,int的时候向上取整了,因此调整
|
||||
if min < 0 ||
|
||||
(isLessThanZero && min == 0) {
|
||||
min -= float64(unit)
|
||||
}
|
||||
}
|
||||
max = min + float64(unit*divideCount)
|
||||
expectMax := opt.Max * 2
|
||||
if max > expectMax {
|
||||
max = float64(ceilFloatToInt(expectMax))
|
||||
}
|
||||
return axisRange{
|
||||
p: opt.Painter,
|
||||
divideCount: divideCount,
|
||||
min: min,
|
||||
max: max,
|
||||
size: opt.Size,
|
||||
boundary: opt.Boundary,
|
||||
}
|
||||
}
|
||||
|
||||
// Values returns values of range
|
||||
func (r axisRange) Values() []string {
|
||||
offset := (r.max - r.min) / float64(r.divideCount)
|
||||
values := make([]string, 0)
|
||||
formatter := commafWithDigits
|
||||
if r.p != nil && r.p.valueFormatter != nil {
|
||||
formatter = r.p.valueFormatter
|
||||
}
|
||||
for i := 0; i <= r.divideCount; i++ {
|
||||
v := r.min + float64(i)*offset
|
||||
value := formatter(v)
|
||||
values = append(values, value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (r *axisRange) getHeight(value float64) int {
|
||||
if r.max <= r.min {
|
||||
return 0
|
||||
}
|
||||
v := (value - r.min) / (r.max - r.min)
|
||||
return int(v * float64(r.size))
|
||||
}
|
||||
|
||||
func (r *axisRange) getRestHeight(value float64) int {
|
||||
return r.size - r.getHeight(value)
|
||||
}
|
||||
|
||||
// GetRange returns a range of index
|
||||
func (r *axisRange) GetRange(index int) (float64, float64) {
|
||||
unit := float64(r.size) / float64(r.divideCount)
|
||||
return unit * float64(index), unit * float64(index+1)
|
||||
}
|
||||
|
||||
// AutoDivide divides the axis
|
||||
func (r *axisRange) AutoDivide() []int {
|
||||
return autoDivide(r.size, r.divideCount)
|
||||
}
|
||||
318
src/server/vendor/github.com/vicanso/go-charts/v2/series.go
generated
vendored
Normal file
318
src/server/vendor/github.com/vicanso/go-charts/v2/series.go
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
package charts
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type SeriesData struct {
|
||||
// The value of series data
|
||||
Value float64
|
||||
// The style of series data
|
||||
Style Style
|
||||
}
|
||||
|
||||
// NewSeriesListDataFromValues returns a series list
|
||||
func NewSeriesListDataFromValues(values [][]float64, chartType ...string) SeriesList {
|
||||
seriesList := make(SeriesList, len(values))
|
||||
for index, value := range values {
|
||||
seriesList[index] = NewSeriesFromValues(value, chartType...)
|
||||
}
|
||||
return seriesList
|
||||
}
|
||||
|
||||
// NewSeriesFromValues returns a series
|
||||
func NewSeriesFromValues(values []float64, chartType ...string) Series {
|
||||
s := Series{
|
||||
Data: NewSeriesDataFromValues(values),
|
||||
}
|
||||
if len(chartType) != 0 {
|
||||
s.Type = chartType[0]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSeriesDataFromValues return a series data
|
||||
func NewSeriesDataFromValues(values []float64) []SeriesData {
|
||||
data := make([]SeriesData, len(values))
|
||||
for index, value := range values {
|
||||
data[index] = SeriesData{
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type SeriesLabel struct {
|
||||
// Data label formatter, which supports string template.
|
||||
// {b}: the name of a data item.
|
||||
// {c}: the value of a data item.
|
||||
// {d}: the percent of a data item(pie chart).
|
||||
Formatter string
|
||||
// The color for label
|
||||
Color Color
|
||||
// Show flag for label
|
||||
Show bool
|
||||
// Distance to the host graphic element.
|
||||
Distance int
|
||||
// The position of label
|
||||
Position string
|
||||
// The offset of label's position
|
||||
Offset Box
|
||||
// The font size of label
|
||||
FontSize float64
|
||||
}
|
||||
|
||||
const (
|
||||
SeriesMarkDataTypeMax = "max"
|
||||
SeriesMarkDataTypeMin = "min"
|
||||
SeriesMarkDataTypeAverage = "average"
|
||||
)
|
||||
|
||||
type SeriesMarkData struct {
|
||||
// The mark data type, it can be "max", "min", "average".
|
||||
// The "average" is only for mark line
|
||||
Type string
|
||||
}
|
||||
type SeriesMarkPoint struct {
|
||||
// The width of symbol, default value is 30
|
||||
SymbolSize int
|
||||
// The mark data of series mark point
|
||||
Data []SeriesMarkData
|
||||
}
|
||||
type SeriesMarkLine struct {
|
||||
// The mark data of series mark line
|
||||
Data []SeriesMarkData
|
||||
}
|
||||
type Series struct {
|
||||
index int
|
||||
// The type of series, it can be "line", "bar" or "pie".
|
||||
// Default value is "line"
|
||||
Type string
|
||||
// The data list of series
|
||||
Data []SeriesData
|
||||
// The Y axis index, it should be 0 or 1.
|
||||
// Default value is 0
|
||||
AxisIndex int
|
||||
// The style for series
|
||||
Style chart.Style
|
||||
// The label for series
|
||||
Label SeriesLabel
|
||||
// The name of series
|
||||
Name string
|
||||
// Radius for Pie chart, e.g.: 40%, default is "40%"
|
||||
Radius string
|
||||
// Round for bar chart
|
||||
RoundRadius int
|
||||
// Mark point for series
|
||||
MarkPoint SeriesMarkPoint
|
||||
// Make line for series
|
||||
MarkLine SeriesMarkLine
|
||||
// Max value of series
|
||||
Min *float64
|
||||
// Min value of series
|
||||
Max *float64
|
||||
}
|
||||
type SeriesList []Series
|
||||
|
||||
func (sl SeriesList) init() {
|
||||
if len(sl) == 0 {
|
||||
return
|
||||
}
|
||||
if sl[len(sl)-1].index != 0 {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(sl); i++ {
|
||||
if sl[i].Type == "" {
|
||||
sl[i].Type = ChartTypeLine
|
||||
}
|
||||
sl[i].index = i
|
||||
}
|
||||
}
|
||||
|
||||
func (sl SeriesList) Filter(chartType string) SeriesList {
|
||||
arr := make(SeriesList, 0)
|
||||
for index, item := range sl {
|
||||
if item.Type == chartType {
|
||||
arr = append(arr, sl[index])
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// GetMaxMin get max and min value of series list
|
||||
func (sl SeriesList) GetMaxMin(axisIndex int) (float64, float64) {
|
||||
min := math.MaxFloat64
|
||||
max := -math.MaxFloat64
|
||||
for _, series := range sl {
|
||||
if series.AxisIndex != axisIndex {
|
||||
continue
|
||||
}
|
||||
for _, item := range series.Data {
|
||||
// 如果为空值,忽略
|
||||
if item.Value == nullValue {
|
||||
continue
|
||||
}
|
||||
if item.Value > max {
|
||||
max = item.Value
|
||||
}
|
||||
if item.Value < min {
|
||||
min = item.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return max, min
|
||||
}
|
||||
|
||||
type PieSeriesOption struct {
|
||||
Radius string
|
||||
Label SeriesLabel
|
||||
Names []string
|
||||
}
|
||||
|
||||
func NewPieSeriesList(values []float64, opts ...PieSeriesOption) SeriesList {
|
||||
result := make([]Series, len(values))
|
||||
var opt PieSeriesOption
|
||||
if len(opts) != 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
for index, v := range values {
|
||||
name := ""
|
||||
if index < len(opt.Names) {
|
||||
name = opt.Names[index]
|
||||
}
|
||||
s := Series{
|
||||
Type: ChartTypePie,
|
||||
Data: []SeriesData{
|
||||
{
|
||||
Value: v,
|
||||
},
|
||||
},
|
||||
Radius: opt.Radius,
|
||||
Label: opt.Label,
|
||||
Name: name,
|
||||
}
|
||||
result[index] = s
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type seriesSummary struct {
|
||||
// The index of max value
|
||||
MaxIndex int
|
||||
// The max value
|
||||
MaxValue float64
|
||||
// The index of min value
|
||||
MinIndex int
|
||||
// The min value
|
||||
MinValue float64
|
||||
// THe average value
|
||||
AverageValue float64
|
||||
}
|
||||
|
||||
// Summary get summary of series
|
||||
func (s *Series) Summary() seriesSummary {
|
||||
minIndex := -1
|
||||
maxIndex := -1
|
||||
minValue := math.MaxFloat64
|
||||
maxValue := -math.MaxFloat64
|
||||
sum := float64(0)
|
||||
for j, item := range s.Data {
|
||||
if item.Value < minValue {
|
||||
minIndex = j
|
||||
minValue = item.Value
|
||||
}
|
||||
if item.Value > maxValue {
|
||||
maxIndex = j
|
||||
maxValue = item.Value
|
||||
}
|
||||
sum += item.Value
|
||||
}
|
||||
return seriesSummary{
|
||||
MaxIndex: maxIndex,
|
||||
MaxValue: maxValue,
|
||||
MinIndex: minIndex,
|
||||
MinValue: minValue,
|
||||
AverageValue: sum / float64(len(s.Data)),
|
||||
}
|
||||
}
|
||||
|
||||
// Names returns the names of series list
|
||||
func (sl SeriesList) Names() []string {
|
||||
names := make([]string, len(sl))
|
||||
for index, s := range sl {
|
||||
names[index] = s.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// LabelFormatter label formatter
|
||||
type LabelFormatter func(index int, value float64, percent float64) string
|
||||
|
||||
// NewPieLabelFormatter returns a pie label formatter
|
||||
func NewPieLabelFormatter(seriesNames []string, layout string) LabelFormatter {
|
||||
if len(layout) == 0 {
|
||||
layout = "{b}: {d}"
|
||||
}
|
||||
return NewLabelFormatter(seriesNames, layout)
|
||||
}
|
||||
|
||||
// NewFunnelLabelFormatter returns a funner label formatter
|
||||
func NewFunnelLabelFormatter(seriesNames []string, layout string) LabelFormatter {
|
||||
if len(layout) == 0 {
|
||||
layout = "{b}({d})"
|
||||
}
|
||||
return NewLabelFormatter(seriesNames, layout)
|
||||
}
|
||||
|
||||
// NewValueLabelFormatter returns a value formatter
|
||||
func NewValueLabelFormatter(seriesNames []string, layout string) LabelFormatter {
|
||||
if len(layout) == 0 {
|
||||
layout = "{c}"
|
||||
}
|
||||
return NewLabelFormatter(seriesNames, layout)
|
||||
}
|
||||
|
||||
// NewLabelFormatter returns a label formaatter
|
||||
func NewLabelFormatter(seriesNames []string, layout string) LabelFormatter {
|
||||
return func(index int, value, percent float64) string {
|
||||
// 如果无percent的则设置为<0
|
||||
percentText := ""
|
||||
if percent >= 0 {
|
||||
percentText = humanize.FtoaWithDigits(percent*100, 2) + "%"
|
||||
}
|
||||
valueText := humanize.FtoaWithDigits(value, 2)
|
||||
name := ""
|
||||
if len(seriesNames) > index {
|
||||
name = seriesNames[index]
|
||||
}
|
||||
text := strings.ReplaceAll(layout, "{c}", valueText)
|
||||
text = strings.ReplaceAll(text, "{d}", percentText)
|
||||
text = strings.ReplaceAll(text, "{b}", name)
|
||||
return text
|
||||
}
|
||||
}
|
||||
148
src/server/vendor/github.com/vicanso/go-charts/v2/series_label.go
generated
vendored
Normal file
148
src/server/vendor/github.com/vicanso/go-charts/v2/series_label.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type labelRenderValue struct {
|
||||
Text string
|
||||
Style Style
|
||||
X int
|
||||
Y int
|
||||
// 旋转
|
||||
Radians float64
|
||||
}
|
||||
|
||||
type LabelValue struct {
|
||||
Index int
|
||||
Value float64
|
||||
X int
|
||||
Y int
|
||||
// 旋转
|
||||
Radians float64
|
||||
// 字体颜色
|
||||
FontColor Color
|
||||
// 字体大小
|
||||
FontSize float64
|
||||
Orient string
|
||||
Offset Box
|
||||
}
|
||||
|
||||
type SeriesLabelPainter struct {
|
||||
p *Painter
|
||||
seriesNames []string
|
||||
label *SeriesLabel
|
||||
theme ColorPalette
|
||||
font *truetype.Font
|
||||
values []labelRenderValue
|
||||
}
|
||||
|
||||
type SeriesLabelPainterParams struct {
|
||||
P *Painter
|
||||
SeriesNames []string
|
||||
Label SeriesLabel
|
||||
Theme ColorPalette
|
||||
Font *truetype.Font
|
||||
}
|
||||
|
||||
func NewSeriesLabelPainter(params SeriesLabelPainterParams) *SeriesLabelPainter {
|
||||
return &SeriesLabelPainter{
|
||||
p: params.P,
|
||||
seriesNames: params.SeriesNames,
|
||||
label: ¶ms.Label,
|
||||
theme: params.Theme,
|
||||
font: params.Font,
|
||||
values: make([]labelRenderValue, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *SeriesLabelPainter) Add(value LabelValue) {
|
||||
label := o.label
|
||||
distance := label.Distance
|
||||
if distance == 0 {
|
||||
distance = 5
|
||||
}
|
||||
text := NewValueLabelFormatter(o.seriesNames, label.Formatter)(value.Index, value.Value, -1)
|
||||
labelStyle := Style{
|
||||
FontColor: o.theme.GetTextColor(),
|
||||
FontSize: labelFontSize,
|
||||
Font: o.font,
|
||||
}
|
||||
if value.FontSize != 0 {
|
||||
labelStyle.FontSize = value.FontSize
|
||||
}
|
||||
if !value.FontColor.IsZero() {
|
||||
label.Color = value.FontColor
|
||||
}
|
||||
if !label.Color.IsZero() {
|
||||
labelStyle.FontColor = label.Color
|
||||
}
|
||||
p := o.p
|
||||
p.OverrideDrawingStyle(labelStyle)
|
||||
rotated := value.Radians != 0
|
||||
if rotated {
|
||||
p.SetTextRotation(value.Radians)
|
||||
}
|
||||
textBox := p.MeasureText(text)
|
||||
renderValue := labelRenderValue{
|
||||
Text: text,
|
||||
Style: labelStyle,
|
||||
X: value.X,
|
||||
Y: value.Y,
|
||||
Radians: value.Radians,
|
||||
}
|
||||
if value.Orient != OrientHorizontal {
|
||||
renderValue.X -= textBox.Width() >> 1
|
||||
renderValue.Y -= distance
|
||||
} else {
|
||||
renderValue.X += distance
|
||||
renderValue.Y += textBox.Height() >> 1
|
||||
renderValue.Y -= 2
|
||||
}
|
||||
if rotated {
|
||||
renderValue.X = value.X + textBox.Width()>>1 - 1
|
||||
p.ClearTextRotation()
|
||||
} else {
|
||||
if textBox.Width()%2 != 0 {
|
||||
renderValue.X++
|
||||
}
|
||||
}
|
||||
renderValue.X += value.Offset.Left
|
||||
renderValue.Y += value.Offset.Top
|
||||
o.values = append(o.values, renderValue)
|
||||
}
|
||||
|
||||
func (o *SeriesLabelPainter) Render() (Box, error) {
|
||||
for _, item := range o.values {
|
||||
o.p.OverrideTextStyle(item.Style)
|
||||
if item.Radians != 0 {
|
||||
o.p.TextRotation(item.Text, item.X, item.Y, item.Radians)
|
||||
} else {
|
||||
o.p.Text(item.Text, item.X, item.Y)
|
||||
}
|
||||
}
|
||||
return chart.BoxZero, nil
|
||||
}
|
||||
254
src/server/vendor/github.com/vicanso/go-charts/v2/start_zh.md
generated
vendored
Normal file
254
src/server/vendor/github.com/vicanso/go-charts/v2/start_zh.md
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
# go-charts
|
||||
|
||||
`go-charts`主要分为了下几个模块:
|
||||
|
||||
- `标题`:图表的标题,包括主副标题,位置为图表的顶部
|
||||
- `图例`:图表的图例列表,用于标识每个图例对应的颜色与名称信息,默认为图表的顶部,可自定义位置
|
||||
- `X轴`:图表的x轴,用于折线图、柱状图中,表示每个点对应的时间,位置图表的底部
|
||||
- `Y轴`:图表的y轴,用于折线图、柱状图中,最多可使用两组y轴(一左一右),默认位置图表的左侧
|
||||
- `内容`: 图表的内容,折线图、柱状图、饼图等,在图表的中间区域
|
||||
|
||||
## 标题
|
||||
|
||||
### 常用设置
|
||||
|
||||
标题一般仅需要设置主副标题即可,其它的属性均会设置默认值,常用的方式是使用`TitleTextOptionFunc`设置,其中副标题为可选值,方式如下:
|
||||
|
||||
```go
|
||||
charts.TitleTextOptionFunc("Text", "Subtext"),
|
||||
```
|
||||
|
||||
### 个性化设置
|
||||
|
||||
```go
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.Title = charts.TitleOption{
|
||||
// 主标题
|
||||
Text: "Text",
|
||||
// 副标题
|
||||
Subtext: "Subtext",
|
||||
// 标题左侧位置,可设置为"center","right",数值("20")或百份比("20%")
|
||||
Left: charts.PositionRight,
|
||||
// 标题顶部位置,只可调为数值
|
||||
Top: "20",
|
||||
// 主标题文字大小
|
||||
FontSize: 14,
|
||||
// 副标题文字大小
|
||||
SubtextFontSize: 12,
|
||||
// 主标题字体颜色
|
||||
FontColor: charts.Color{
|
||||
R: 100,
|
||||
G: 100,
|
||||
B: 100,
|
||||
A: 255,
|
||||
},
|
||||
// 副标题字体影响
|
||||
SubtextFontColor: charts.Color{
|
||||
R: 200,
|
||||
G: 200,
|
||||
B: 200,
|
||||
A: 255,
|
||||
},
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
### 部分属性个性化设置
|
||||
|
||||
```go
|
||||
charts.TitleTextOptionFunc("Text", "Subtext"),
|
||||
func(opt *charts.ChartOption) {
|
||||
// 修改top的值
|
||||
opt.Title.Top = "20"
|
||||
},
|
||||
```
|
||||
|
||||
## 图例
|
||||
|
||||
### 常用设置
|
||||
|
||||
图例组件与图表中的数据一一对应,常用仅设置其名称及左侧的值即可(可选),方式如下:
|
||||
|
||||
|
||||
```go
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
"Direct",
|
||||
"Search Engine",
|
||||
}, "50"),
|
||||
```
|
||||
|
||||
### 个性化设置
|
||||
|
||||
```go
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.Legend = charts.LegendOption{
|
||||
// 图例名称
|
||||
Data: []string{
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
"Direct",
|
||||
"Search Engine",
|
||||
},
|
||||
// 图例左侧位置,可设置为"center","right",数值("20")或百份比("20%")
|
||||
// 如果示例有多行,只影响第一行,而且对于多行的示例,设置"center", "right"无效
|
||||
Left: "50",
|
||||
// 图例顶部位置,只可调为数值
|
||||
Top: "10",
|
||||
// 图例图标的位置,默认为左侧,只允许左或右
|
||||
Align: charts.AlignRight,
|
||||
// 图例排列方式,默认为水平,只允许水平或垂直
|
||||
Orient: charts.OrientVertical,
|
||||
// 图标类型,提供"rect"与"lineDot"两种类型
|
||||
Icon: charts.IconRect,
|
||||
// 字体大小
|
||||
FontSize: 14,
|
||||
// 字体颜色
|
||||
FontColor: charts.Color{
|
||||
R: 150,
|
||||
G: 150,
|
||||
B: 150,
|
||||
A: 255,
|
||||
},
|
||||
// 是否展示,如果不需要展示则设置
|
||||
// Show: charts.FalseFlag(),
|
||||
// 图例区域的padding值
|
||||
Padding: charts.Box{
|
||||
Top: 10,
|
||||
Left: 10,
|
||||
},
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
### 部分属性个性化设置
|
||||
|
||||
```go
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Email",
|
||||
"Union Ads",
|
||||
"Video Ads",
|
||||
"Direct",
|
||||
"Search Engine",
|
||||
}, "50"),
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.Legend.Top = "10"
|
||||
},
|
||||
```
|
||||
|
||||
## X轴
|
||||
|
||||
### 常用设置
|
||||
|
||||
图表中X轴的展示,常用的设置方式是指定数组即可:
|
||||
|
||||
```go
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
}),
|
||||
```
|
||||
|
||||
### 个性化设置
|
||||
|
||||
```go
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.XAxis = charts.XAxisOption{
|
||||
// X轴内容
|
||||
Data: []string{
|
||||
"01",
|
||||
"02",
|
||||
"03",
|
||||
"04",
|
||||
"05",
|
||||
"06",
|
||||
"07",
|
||||
"08",
|
||||
"09",
|
||||
},
|
||||
// 如果数据点不居中,则设置为false
|
||||
BoundaryGap: charts.FalseFlag(),
|
||||
// 字体大小
|
||||
FontSize: 14,
|
||||
// 是否展示,如果不需要展示则设置
|
||||
// Show: charts.FalseFlag(),
|
||||
// 会根据文本内容以及此值选择适合的分块大小,一般不需要设置
|
||||
// SplitNumber: 3,
|
||||
// 线条颜色
|
||||
StrokeColor: charts.Color{
|
||||
R: 200,
|
||||
G: 200,
|
||||
B: 200,
|
||||
A: 255,
|
||||
},
|
||||
// 文字颜色
|
||||
FontColor: charts.Color{
|
||||
R: 100,
|
||||
G: 100,
|
||||
B: 100,
|
||||
A: 255,
|
||||
},
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
### 部分属性个性化设置
|
||||
|
||||
```go
|
||||
charts.XAxisDataOptionFunc([]string{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
}),
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.XAxis.FontColor = charts.Color{
|
||||
R: 100,
|
||||
G: 100,
|
||||
B: 100,
|
||||
A: 255,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
## Y轴
|
||||
|
||||
图表中的y轴展示的相关数据会根据图表中的数据自动生成适合的值,如果需要自定义,则可自定义以下部分数据:
|
||||
|
||||
```go
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.YAxisOptions = []charts.YAxisOption{
|
||||
{
|
||||
// 字体大小
|
||||
FontSize: 16,
|
||||
// 字体颜色
|
||||
FontColor: charts.Color{
|
||||
R: 100,
|
||||
G: 100,
|
||||
B: 100,
|
||||
A: 255,
|
||||
},
|
||||
// 内容,{value}会替换为对应的值
|
||||
Formatter: "{value} ml",
|
||||
// Y轴颜色,如果设置此值,会覆盖font color
|
||||
Color: charts.Color{
|
||||
R: 255,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
```
|
||||
438
src/server/vendor/github.com/vicanso/go-charts/v2/table.go
generated
vendored
Normal file
438
src/server/vendor/github.com/vicanso/go-charts/v2/table.go
generated
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
type tableChart struct {
|
||||
p *Painter
|
||||
opt *TableChartOption
|
||||
}
|
||||
|
||||
// NewTableChart returns a table chart render
|
||||
func NewTableChart(p *Painter, opt TableChartOption) *tableChart {
|
||||
if opt.Theme == nil {
|
||||
opt.Theme = defaultTheme
|
||||
}
|
||||
return &tableChart{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
type TableCell struct {
|
||||
// Text the text of table cell
|
||||
Text string
|
||||
// Style the current style of table cell
|
||||
Style Style
|
||||
// Row the row index of table cell
|
||||
Row int
|
||||
// Column the column index of table cell
|
||||
Column int
|
||||
}
|
||||
|
||||
type TableChartOption struct {
|
||||
// The output type
|
||||
Type string
|
||||
// The width of table
|
||||
Width int
|
||||
// The theme
|
||||
Theme ColorPalette
|
||||
// The padding of table cell
|
||||
Padding Box
|
||||
// The header data of table
|
||||
Header []string
|
||||
// The data of table
|
||||
Data [][]string
|
||||
// The span list of table column
|
||||
Spans []int
|
||||
// The text align list of table cell
|
||||
TextAligns []string
|
||||
// The font size of table
|
||||
FontSize float64
|
||||
// The font family, which should be installed first
|
||||
FontFamily string
|
||||
Font *truetype.Font
|
||||
// The font color of table
|
||||
FontColor Color
|
||||
// The background color of header
|
||||
HeaderBackgroundColor Color
|
||||
// The header font color
|
||||
HeaderFontColor Color
|
||||
// The background color of row
|
||||
RowBackgroundColors []Color
|
||||
// The background color
|
||||
BackgroundColor Color
|
||||
// CellTextStyle customize text style of table cell
|
||||
CellTextStyle func(TableCell) *Style
|
||||
// CellStyle customize drawing style of table cell
|
||||
CellStyle func(TableCell) *Style
|
||||
}
|
||||
|
||||
type TableSetting struct {
|
||||
// The color of header
|
||||
HeaderColor Color
|
||||
// The color of heder text
|
||||
HeaderFontColor Color
|
||||
// The color of table text
|
||||
FontColor Color
|
||||
// The color list of row
|
||||
RowColors []Color
|
||||
// The padding of cell
|
||||
Padding Box
|
||||
}
|
||||
|
||||
var TableLightThemeSetting = TableSetting{
|
||||
HeaderColor: Color{
|
||||
R: 240,
|
||||
G: 240,
|
||||
B: 240,
|
||||
A: 255,
|
||||
},
|
||||
HeaderFontColor: Color{
|
||||
R: 98,
|
||||
G: 105,
|
||||
B: 118,
|
||||
A: 255,
|
||||
},
|
||||
FontColor: Color{
|
||||
R: 70,
|
||||
G: 70,
|
||||
B: 70,
|
||||
A: 255,
|
||||
},
|
||||
RowColors: []Color{
|
||||
drawing.ColorWhite,
|
||||
{
|
||||
R: 247,
|
||||
G: 247,
|
||||
B: 247,
|
||||
A: 255,
|
||||
},
|
||||
},
|
||||
Padding: Box{
|
||||
Left: 10,
|
||||
Top: 10,
|
||||
Right: 10,
|
||||
Bottom: 10,
|
||||
},
|
||||
}
|
||||
|
||||
var TableDarkThemeSetting = TableSetting{
|
||||
HeaderColor: Color{
|
||||
R: 38,
|
||||
G: 38,
|
||||
B: 42,
|
||||
A: 255,
|
||||
},
|
||||
HeaderFontColor: Color{
|
||||
R: 216,
|
||||
G: 217,
|
||||
B: 218,
|
||||
A: 255,
|
||||
},
|
||||
FontColor: Color{
|
||||
R: 216,
|
||||
G: 217,
|
||||
B: 218,
|
||||
A: 255,
|
||||
},
|
||||
RowColors: []Color{
|
||||
{
|
||||
R: 24,
|
||||
G: 24,
|
||||
B: 28,
|
||||
A: 255,
|
||||
},
|
||||
{
|
||||
R: 38,
|
||||
G: 38,
|
||||
B: 42,
|
||||
A: 255,
|
||||
},
|
||||
},
|
||||
Padding: Box{
|
||||
Left: 10,
|
||||
Top: 10,
|
||||
Right: 10,
|
||||
Bottom: 10,
|
||||
},
|
||||
}
|
||||
|
||||
var tableDefaultSetting = TableLightThemeSetting
|
||||
|
||||
// SetDefaultTableSetting sets the default setting for table
|
||||
func SetDefaultTableSetting(setting TableSetting) {
|
||||
tableDefaultSetting = setting
|
||||
}
|
||||
|
||||
type renderInfo struct {
|
||||
Width int
|
||||
Height int
|
||||
HeaderHeight int
|
||||
RowHeights []int
|
||||
ColumnWidths []int
|
||||
}
|
||||
|
||||
func (t *tableChart) render() (*renderInfo, error) {
|
||||
info := renderInfo{
|
||||
RowHeights: make([]int, 0),
|
||||
}
|
||||
p := t.p
|
||||
opt := t.opt
|
||||
if len(opt.Header) == 0 {
|
||||
return nil, errors.New("header can not be nil")
|
||||
}
|
||||
theme := opt.Theme
|
||||
if theme == nil {
|
||||
theme = p.theme
|
||||
}
|
||||
fontSize := opt.FontSize
|
||||
if fontSize == 0 {
|
||||
fontSize = 12
|
||||
}
|
||||
fontColor := opt.FontColor
|
||||
if fontColor.IsZero() {
|
||||
fontColor = tableDefaultSetting.FontColor
|
||||
}
|
||||
font := opt.Font
|
||||
if font == nil {
|
||||
font = theme.GetFont()
|
||||
}
|
||||
headerFontColor := opt.HeaderFontColor
|
||||
if opt.HeaderFontColor.IsZero() {
|
||||
headerFontColor = tableDefaultSetting.HeaderFontColor
|
||||
}
|
||||
|
||||
spans := opt.Spans
|
||||
if len(spans) != len(opt.Header) {
|
||||
newSpans := make([]int, len(opt.Header))
|
||||
for index := range opt.Header {
|
||||
if index >= len(spans) {
|
||||
newSpans[index] = 1
|
||||
} else {
|
||||
newSpans[index] = spans[index]
|
||||
}
|
||||
}
|
||||
spans = newSpans
|
||||
}
|
||||
|
||||
sum := sumInt(spans)
|
||||
values := autoDivideSpans(p.Width(), sum, spans)
|
||||
columnWidths := make([]int, 0)
|
||||
for index, v := range values {
|
||||
if index == len(values)-1 {
|
||||
break
|
||||
}
|
||||
columnWidths = append(columnWidths, values[index+1]-v)
|
||||
}
|
||||
info.ColumnWidths = columnWidths
|
||||
|
||||
height := 0
|
||||
textStyle := Style{
|
||||
FontSize: fontSize,
|
||||
FontColor: headerFontColor,
|
||||
FillColor: headerFontColor,
|
||||
Font: font,
|
||||
}
|
||||
|
||||
headerHeight := 0
|
||||
padding := opt.Padding
|
||||
if padding.IsZero() {
|
||||
padding = tableDefaultSetting.Padding
|
||||
}
|
||||
getCellTextStyle := opt.CellTextStyle
|
||||
if getCellTextStyle == nil {
|
||||
getCellTextStyle = func(_ TableCell) *Style {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// textAligns := opt.TextAligns
|
||||
getTextAlign := func(index int) string {
|
||||
if len(opt.TextAligns) <= index {
|
||||
return ""
|
||||
}
|
||||
return opt.TextAligns[index]
|
||||
}
|
||||
|
||||
// 表格单元的处理
|
||||
renderTableCells := func(
|
||||
currentStyle Style,
|
||||
rowIndex int,
|
||||
textList []string,
|
||||
currentHeight int,
|
||||
cellPadding Box,
|
||||
) int {
|
||||
cellMaxHeight := 0
|
||||
paddingHeight := cellPadding.Top + cellPadding.Bottom
|
||||
paddingWidth := cellPadding.Left + cellPadding.Right
|
||||
for index, text := range textList {
|
||||
cellStyle := getCellTextStyle(TableCell{
|
||||
Text: text,
|
||||
Row: rowIndex,
|
||||
Column: index,
|
||||
Style: currentStyle,
|
||||
})
|
||||
if cellStyle == nil {
|
||||
cellStyle = ¤tStyle
|
||||
}
|
||||
p.SetStyle(*cellStyle)
|
||||
x := values[index]
|
||||
y := currentHeight + cellPadding.Top
|
||||
width := values[index+1] - x
|
||||
x += cellPadding.Left
|
||||
width -= paddingWidth
|
||||
box := p.TextFit(text, x, y+int(fontSize), width, getTextAlign(index))
|
||||
// 计算最高的高度
|
||||
if box.Height()+paddingHeight > cellMaxHeight {
|
||||
cellMaxHeight = box.Height() + paddingHeight
|
||||
}
|
||||
}
|
||||
return cellMaxHeight
|
||||
}
|
||||
|
||||
// 表头的处理
|
||||
headerHeight = renderTableCells(textStyle, 0, opt.Header, height, padding)
|
||||
height += headerHeight
|
||||
info.HeaderHeight = headerHeight
|
||||
|
||||
// 表格内容的处理
|
||||
textStyle.FontColor = fontColor
|
||||
textStyle.FillColor = fontColor
|
||||
for index, textList := range opt.Data {
|
||||
cellHeight := renderTableCells(textStyle, index+1, textList, height, padding)
|
||||
info.RowHeights = append(info.RowHeights, cellHeight)
|
||||
height += cellHeight
|
||||
}
|
||||
|
||||
info.Width = p.Width()
|
||||
info.Height = height
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (t *tableChart) renderWithInfo(info *renderInfo) (Box, error) {
|
||||
p := t.p
|
||||
opt := t.opt
|
||||
if !opt.BackgroundColor.IsZero() {
|
||||
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
|
||||
}
|
||||
headerBGColor := opt.HeaderBackgroundColor
|
||||
if headerBGColor.IsZero() {
|
||||
headerBGColor = tableDefaultSetting.HeaderColor
|
||||
}
|
||||
|
||||
// 如果设置表头背景色
|
||||
p.SetBackground(info.Width, info.HeaderHeight, headerBGColor, true)
|
||||
currentHeight := info.HeaderHeight
|
||||
rowColors := opt.RowBackgroundColors
|
||||
if rowColors == nil {
|
||||
rowColors = tableDefaultSetting.RowColors
|
||||
}
|
||||
for index, h := range info.RowHeights {
|
||||
color := rowColors[index%len(rowColors)]
|
||||
child := p.Child(PainterPaddingOption(Box{
|
||||
Top: currentHeight,
|
||||
}))
|
||||
child.SetBackground(p.Width(), h, color, true)
|
||||
currentHeight += h
|
||||
}
|
||||
// 根据是否有设置表格样式调整背景色
|
||||
getCellStyle := opt.CellStyle
|
||||
if getCellStyle != nil {
|
||||
arr := [][]string{
|
||||
opt.Header,
|
||||
}
|
||||
arr = append(arr, opt.Data...)
|
||||
top := 0
|
||||
heights := []int{
|
||||
info.HeaderHeight,
|
||||
}
|
||||
heights = append(heights, info.RowHeights...)
|
||||
// 循环所有表格单元,生成背景色
|
||||
for i, textList := range arr {
|
||||
left := 0
|
||||
for j, v := range textList {
|
||||
style := getCellStyle(TableCell{
|
||||
Text: v,
|
||||
Row: i,
|
||||
Column: j,
|
||||
})
|
||||
if style != nil && !style.FillColor.IsZero() {
|
||||
padding := style.Padding
|
||||
child := p.Child(PainterPaddingOption(Box{
|
||||
Top: top + padding.Top,
|
||||
Left: left + padding.Left,
|
||||
}))
|
||||
w := info.ColumnWidths[j] - padding.Left - padding.Top
|
||||
h := heights[i] - padding.Top - padding.Bottom
|
||||
child.SetBackground(w, h, style.FillColor, true)
|
||||
}
|
||||
left += info.ColumnWidths[j]
|
||||
}
|
||||
top += heights[i]
|
||||
}
|
||||
}
|
||||
_, err := t.render()
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
|
||||
return Box{
|
||||
Right: info.Width,
|
||||
Bottom: info.Height,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *tableChart) Render() (Box, error) {
|
||||
p := t.p
|
||||
opt := t.opt
|
||||
if !opt.BackgroundColor.IsZero() {
|
||||
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
|
||||
}
|
||||
if opt.Font == nil && opt.FontFamily != "" {
|
||||
opt.Font, _ = GetFont(opt.FontFamily)
|
||||
}
|
||||
|
||||
r := p.render
|
||||
fn := chart.PNG
|
||||
if p.outputType == ChartOutputSVG {
|
||||
fn = chart.SVG
|
||||
}
|
||||
newRender, err := fn(p.Width(), 100)
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
p.render = newRender
|
||||
info, err := t.render()
|
||||
if err != nil {
|
||||
return BoxZero, err
|
||||
}
|
||||
p.render = r
|
||||
return t.renderWithInfo(info)
|
||||
}
|
||||
332
src/server/vendor/github.com/vicanso/go-charts/v2/theme.go
generated
vendored
Normal file
332
src/server/vendor/github.com/vicanso/go-charts/v2/theme.go
generated
vendored
Normal file
@ -0,0 +1,332 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
const ThemeDark = "dark"
|
||||
const ThemeLight = "light"
|
||||
const ThemeGrafana = "grafana"
|
||||
const ThemeAnt = "ant"
|
||||
|
||||
type ColorPalette interface {
|
||||
IsDark() bool
|
||||
GetAxisStrokeColor() Color
|
||||
SetAxisStrokeColor(Color)
|
||||
GetAxisSplitLineColor() Color
|
||||
SetAxisSplitLineColor(Color)
|
||||
GetSeriesColor(int) Color
|
||||
SetSeriesColor([]Color)
|
||||
GetBackgroundColor() Color
|
||||
SetBackgroundColor(Color)
|
||||
GetTextColor() Color
|
||||
SetTextColor(Color)
|
||||
GetFontSize() float64
|
||||
SetFontSize(float64)
|
||||
GetFont() *truetype.Font
|
||||
SetFont(*truetype.Font)
|
||||
}
|
||||
|
||||
type themeColorPalette struct {
|
||||
isDarkMode bool
|
||||
axisStrokeColor Color
|
||||
axisSplitLineColor Color
|
||||
backgroundColor Color
|
||||
textColor Color
|
||||
seriesColors []Color
|
||||
fontSize float64
|
||||
font *truetype.Font
|
||||
}
|
||||
|
||||
type ThemeOption struct {
|
||||
IsDarkMode bool
|
||||
AxisStrokeColor Color
|
||||
AxisSplitLineColor Color
|
||||
BackgroundColor Color
|
||||
TextColor Color
|
||||
SeriesColors []Color
|
||||
}
|
||||
|
||||
var palettes = map[string]*themeColorPalette{}
|
||||
|
||||
const defaultFontSize = 12.0
|
||||
|
||||
var defaultTheme ColorPalette
|
||||
|
||||
var defaultLightFontColor = drawing.Color{
|
||||
R: 70,
|
||||
G: 70,
|
||||
B: 70,
|
||||
A: 255,
|
||||
}
|
||||
var defaultDarkFontColor = drawing.Color{
|
||||
R: 238,
|
||||
G: 238,
|
||||
B: 238,
|
||||
A: 255,
|
||||
}
|
||||
|
||||
func init() {
|
||||
echartSeriesColors := []Color{
|
||||
parseColor("#5470c6"),
|
||||
parseColor("#91cc75"),
|
||||
parseColor("#fac858"),
|
||||
parseColor("#ee6666"),
|
||||
parseColor("#73c0de"),
|
||||
parseColor("#3ba272"),
|
||||
parseColor("#fc8452"),
|
||||
parseColor("#9a60b4"),
|
||||
parseColor("#ea7ccc"),
|
||||
}
|
||||
grafanaSeriesColors := []Color{
|
||||
parseColor("#7EB26D"),
|
||||
parseColor("#EAB839"),
|
||||
parseColor("#6ED0E0"),
|
||||
parseColor("#EF843C"),
|
||||
parseColor("#E24D42"),
|
||||
parseColor("#1F78C1"),
|
||||
parseColor("#705DA0"),
|
||||
parseColor("#508642"),
|
||||
}
|
||||
antSeriesColors := []Color{
|
||||
parseColor("#5b8ff9"),
|
||||
parseColor("#5ad8a6"),
|
||||
parseColor("#5d7092"),
|
||||
parseColor("#f6bd16"),
|
||||
parseColor("#6f5ef9"),
|
||||
parseColor("#6dc8ec"),
|
||||
parseColor("#945fb9"),
|
||||
parseColor("#ff9845"),
|
||||
}
|
||||
AddTheme(
|
||||
ThemeDark,
|
||||
ThemeOption{
|
||||
IsDarkMode: true,
|
||||
AxisStrokeColor: Color{
|
||||
R: 185,
|
||||
G: 184,
|
||||
B: 206,
|
||||
A: 255,
|
||||
},
|
||||
AxisSplitLineColor: Color{
|
||||
R: 72,
|
||||
G: 71,
|
||||
B: 83,
|
||||
A: 255,
|
||||
},
|
||||
BackgroundColor: Color{
|
||||
R: 16,
|
||||
G: 12,
|
||||
B: 42,
|
||||
A: 255,
|
||||
},
|
||||
TextColor: Color{
|
||||
R: 238,
|
||||
G: 238,
|
||||
B: 238,
|
||||
A: 255,
|
||||
},
|
||||
SeriesColors: echartSeriesColors,
|
||||
},
|
||||
)
|
||||
|
||||
AddTheme(
|
||||
ThemeLight,
|
||||
ThemeOption{
|
||||
IsDarkMode: false,
|
||||
AxisStrokeColor: Color{
|
||||
R: 110,
|
||||
G: 112,
|
||||
B: 121,
|
||||
A: 255,
|
||||
},
|
||||
AxisSplitLineColor: Color{
|
||||
R: 224,
|
||||
G: 230,
|
||||
B: 242,
|
||||
A: 255,
|
||||
},
|
||||
BackgroundColor: drawing.ColorWhite,
|
||||
TextColor: Color{
|
||||
R: 70,
|
||||
G: 70,
|
||||
B: 70,
|
||||
A: 255,
|
||||
},
|
||||
SeriesColors: echartSeriesColors,
|
||||
},
|
||||
)
|
||||
AddTheme(
|
||||
ThemeAnt,
|
||||
ThemeOption{
|
||||
IsDarkMode: false,
|
||||
AxisStrokeColor: Color{
|
||||
R: 110,
|
||||
G: 112,
|
||||
B: 121,
|
||||
A: 255,
|
||||
},
|
||||
AxisSplitLineColor: Color{
|
||||
R: 224,
|
||||
G: 230,
|
||||
B: 242,
|
||||
A: 255,
|
||||
},
|
||||
BackgroundColor: drawing.ColorWhite,
|
||||
TextColor: drawing.Color{
|
||||
R: 70,
|
||||
G: 70,
|
||||
B: 70,
|
||||
A: 255,
|
||||
},
|
||||
SeriesColors: antSeriesColors,
|
||||
},
|
||||
)
|
||||
AddTheme(
|
||||
ThemeGrafana,
|
||||
ThemeOption{
|
||||
IsDarkMode: true,
|
||||
AxisStrokeColor: Color{
|
||||
R: 185,
|
||||
G: 184,
|
||||
B: 206,
|
||||
A: 255,
|
||||
},
|
||||
AxisSplitLineColor: Color{
|
||||
R: 68,
|
||||
G: 67,
|
||||
B: 67,
|
||||
A: 255,
|
||||
},
|
||||
BackgroundColor: drawing.Color{
|
||||
R: 31,
|
||||
G: 29,
|
||||
B: 29,
|
||||
A: 255,
|
||||
},
|
||||
TextColor: Color{
|
||||
R: 216,
|
||||
G: 217,
|
||||
B: 218,
|
||||
A: 255,
|
||||
},
|
||||
SeriesColors: grafanaSeriesColors,
|
||||
},
|
||||
)
|
||||
SetDefaultTheme(ThemeLight)
|
||||
}
|
||||
|
||||
// SetDefaultTheme sets default theme
|
||||
func SetDefaultTheme(name string) {
|
||||
defaultTheme = NewTheme(name)
|
||||
}
|
||||
|
||||
func AddTheme(name string, opt ThemeOption) {
|
||||
palettes[name] = &themeColorPalette{
|
||||
isDarkMode: opt.IsDarkMode,
|
||||
axisStrokeColor: opt.AxisStrokeColor,
|
||||
axisSplitLineColor: opt.AxisSplitLineColor,
|
||||
backgroundColor: opt.BackgroundColor,
|
||||
textColor: opt.TextColor,
|
||||
seriesColors: opt.SeriesColors,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTheme(name string) ColorPalette {
|
||||
p, ok := palettes[name]
|
||||
if !ok {
|
||||
p = palettes[ThemeLight]
|
||||
}
|
||||
clone := *p
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) IsDark() bool {
|
||||
return t.isDarkMode
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetAxisStrokeColor() Color {
|
||||
return t.axisStrokeColor
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetAxisStrokeColor(c Color) {
|
||||
t.axisStrokeColor = c
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetAxisSplitLineColor() Color {
|
||||
return t.axisSplitLineColor
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetAxisSplitLineColor(c Color) {
|
||||
t.axisSplitLineColor = c
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetSeriesColor(index int) Color {
|
||||
colors := t.seriesColors
|
||||
return colors[index%len(colors)]
|
||||
}
|
||||
func (t *themeColorPalette) SetSeriesColor(colors []Color) {
|
||||
t.seriesColors = colors
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetBackgroundColor() Color {
|
||||
return t.backgroundColor
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetBackgroundColor(c Color) {
|
||||
t.backgroundColor = c
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetTextColor() Color {
|
||||
return t.textColor
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetTextColor(c Color) {
|
||||
t.textColor = c
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetFontSize() float64 {
|
||||
if t.fontSize != 0 {
|
||||
return t.fontSize
|
||||
}
|
||||
return defaultFontSize
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetFontSize(fontSize float64) {
|
||||
t.fontSize = fontSize
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) GetFont() *truetype.Font {
|
||||
if t.font != nil {
|
||||
return t.font
|
||||
}
|
||||
f, _ := GetDefaultFont()
|
||||
return f
|
||||
}
|
||||
|
||||
func (t *themeColorPalette) SetFont(f *truetype.Font) {
|
||||
t.font = f
|
||||
}
|
||||
197
src/server/vendor/github.com/vicanso/go-charts/v2/title.go
generated
vendored
Normal file
197
src/server/vendor/github.com/vicanso/go-charts/v2/title.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
type TitleOption struct {
|
||||
// The theme of chart
|
||||
Theme ColorPalette
|
||||
// Title text, support \n for new line
|
||||
Text string
|
||||
// Subtitle text, support \n for new line
|
||||
Subtext string
|
||||
// Distance between title component and the left side of the container.
|
||||
// It can be pixel value: 20, percentage value: 20%,
|
||||
// or position value: right, center.
|
||||
Left string
|
||||
// Distance between title component and the top side of the container.
|
||||
// It can be pixel value: 20.
|
||||
Top string
|
||||
// The font of label
|
||||
Font *truetype.Font
|
||||
// The font size of label
|
||||
FontSize float64
|
||||
// The color of label
|
||||
FontColor Color
|
||||
// The subtext font size of label
|
||||
SubtextFontSize float64
|
||||
// The subtext font color of label
|
||||
SubtextFontColor Color
|
||||
}
|
||||
|
||||
type titleMeasureOption struct {
|
||||
width int
|
||||
height int
|
||||
text string
|
||||
style Style
|
||||
}
|
||||
|
||||
func splitTitleText(text string) []string {
|
||||
arr := strings.Split(text, "\n")
|
||||
result := make([]string, 0)
|
||||
for _, v := range arr {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type titlePainter struct {
|
||||
p *Painter
|
||||
opt *TitleOption
|
||||
}
|
||||
|
||||
// NewTitlePainter returns a title renderer
|
||||
func NewTitlePainter(p *Painter, opt TitleOption) *titlePainter {
|
||||
return &titlePainter{
|
||||
p: p,
|
||||
opt: &opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *titlePainter) Render() (Box, error) {
|
||||
opt := t.opt
|
||||
p := t.p
|
||||
theme := opt.Theme
|
||||
|
||||
if theme == nil {
|
||||
theme = p.theme
|
||||
}
|
||||
if opt.Text == "" && opt.Subtext == "" {
|
||||
return BoxZero, nil
|
||||
}
|
||||
|
||||
measureOptions := make([]titleMeasureOption, 0)
|
||||
|
||||
if opt.Font == nil {
|
||||
opt.Font = theme.GetFont()
|
||||
}
|
||||
if opt.FontColor.IsZero() {
|
||||
opt.FontColor = theme.GetTextColor()
|
||||
}
|
||||
if opt.FontSize == 0 {
|
||||
opt.FontSize = theme.GetFontSize()
|
||||
}
|
||||
if opt.SubtextFontColor.IsZero() {
|
||||
opt.SubtextFontColor = opt.FontColor
|
||||
}
|
||||
if opt.SubtextFontSize == 0 {
|
||||
opt.SubtextFontSize = opt.FontSize
|
||||
}
|
||||
|
||||
titleTextStyle := Style{
|
||||
Font: opt.Font,
|
||||
FontSize: opt.FontSize,
|
||||
FontColor: opt.FontColor,
|
||||
}
|
||||
// 主标题
|
||||
for _, v := range splitTitleText(opt.Text) {
|
||||
measureOptions = append(measureOptions, titleMeasureOption{
|
||||
text: v,
|
||||
style: titleTextStyle,
|
||||
})
|
||||
}
|
||||
subtextStyle := Style{
|
||||
Font: opt.Font,
|
||||
FontSize: opt.SubtextFontSize,
|
||||
FontColor: opt.SubtextFontColor,
|
||||
}
|
||||
// 副标题
|
||||
for _, v := range splitTitleText(opt.Subtext) {
|
||||
measureOptions = append(measureOptions, titleMeasureOption{
|
||||
text: v,
|
||||
style: subtextStyle,
|
||||
})
|
||||
}
|
||||
textMaxWidth := 0
|
||||
textMaxHeight := 0
|
||||
for index, item := range measureOptions {
|
||||
p.OverrideTextStyle(item.style)
|
||||
textBox := p.MeasureText(item.text)
|
||||
|
||||
w := textBox.Width()
|
||||
h := textBox.Height()
|
||||
if w > textMaxWidth {
|
||||
textMaxWidth = w
|
||||
}
|
||||
if h > textMaxHeight {
|
||||
textMaxHeight = h
|
||||
}
|
||||
measureOptions[index].height = h
|
||||
measureOptions[index].width = w
|
||||
}
|
||||
width := textMaxWidth
|
||||
|
||||
titleX := 0
|
||||
switch opt.Left {
|
||||
case PositionRight:
|
||||
titleX = p.Width() - textMaxWidth
|
||||
case PositionCenter:
|
||||
titleX = p.Width()>>1 - (textMaxWidth >> 1)
|
||||
default:
|
||||
if strings.HasSuffix(opt.Left, "%") {
|
||||
value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", ""))
|
||||
titleX = p.Width() * value / 100
|
||||
} else {
|
||||
value, _ := strconv.Atoi(opt.Left)
|
||||
titleX = value
|
||||
}
|
||||
}
|
||||
titleY := 0
|
||||
// TODO TOP 暂只支持数值
|
||||
if opt.Top != "" {
|
||||
value, _ := strconv.Atoi(opt.Top)
|
||||
titleY += value
|
||||
}
|
||||
for _, item := range measureOptions {
|
||||
p.OverrideTextStyle(item.style)
|
||||
x := titleX + (textMaxWidth-item.width)>>1
|
||||
y := titleY + item.height
|
||||
p.Text(item.text, x, y)
|
||||
titleY += item.height
|
||||
}
|
||||
|
||||
return Box{
|
||||
Bottom: titleY,
|
||||
Right: titleX + width,
|
||||
}, nil
|
||||
}
|
||||
271
src/server/vendor/github.com/vicanso/go-charts/v2/util.go
generated
vendored
Normal file
271
src/server/vendor/github.com/vicanso/go-charts/v2/util.go
generated
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func TrueFlag() *bool {
|
||||
t := true
|
||||
return &t
|
||||
}
|
||||
|
||||
func FalseFlag() *bool {
|
||||
f := false
|
||||
return &f
|
||||
}
|
||||
|
||||
func containsInt(values []int, value int) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsString(values []string, value string) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ceilFloatToInt(value float64) int {
|
||||
i := int(value)
|
||||
if value == float64(i) {
|
||||
return i
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
|
||||
func getDefaultInt(value, defaultValue int) int {
|
||||
if value == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func autoDivide(max, size int) []int {
|
||||
unit := float64(max) / float64(size)
|
||||
|
||||
values := make([]int, size+1)
|
||||
for i := 0; i < size+1; i++ {
|
||||
if i == size {
|
||||
values[i] = max
|
||||
} else {
|
||||
values[i] = int(float64(i) * unit)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func autoDivideSpans(max, size int, spans []int) []int {
|
||||
values := autoDivide(max, size)
|
||||
// 重新合并
|
||||
if len(spans) != 0 {
|
||||
newValues := make([]int, len(spans)+1)
|
||||
newValues[0] = 0
|
||||
end := 0
|
||||
for index, v := range spans {
|
||||
end += v
|
||||
newValues[index+1] = values[end]
|
||||
}
|
||||
values = newValues
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func sumInt(values []int) int {
|
||||
sum := 0
|
||||
for _, v := range values {
|
||||
sum += v
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// measureTextMaxWidthHeight returns maxWidth and maxHeight of text list
|
||||
func measureTextMaxWidthHeight(textList []string, p *Painter) (int, int) {
|
||||
maxWidth := 0
|
||||
maxHeight := 0
|
||||
for _, text := range textList {
|
||||
box := p.MeasureText(text)
|
||||
maxWidth = chart.MaxInt(maxWidth, box.Width())
|
||||
maxHeight = chart.MaxInt(maxHeight, box.Height())
|
||||
}
|
||||
return maxWidth, maxHeight
|
||||
}
|
||||
|
||||
func reverseStringSlice(stringList []string) {
|
||||
for i, j := 0, len(stringList)-1; i < j; i, j = i+1, j-1 {
|
||||
stringList[i], stringList[j] = stringList[j], stringList[i]
|
||||
}
|
||||
}
|
||||
|
||||
func reverseIntSlice(intList []int) {
|
||||
for i, j := 0, len(intList)-1; i < j; i, j = i+1, j-1 {
|
||||
intList[i], intList[j] = intList[j], intList[i]
|
||||
}
|
||||
}
|
||||
|
||||
func convertPercent(value string) float64 {
|
||||
if !strings.HasSuffix(value, "%") {
|
||||
return -1
|
||||
}
|
||||
v, err := strconv.Atoi(strings.ReplaceAll(value, "%", ""))
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return float64(v) / 100
|
||||
}
|
||||
|
||||
func isFalse(flag *bool) bool {
|
||||
if flag != nil && !*flag {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewFloatPoint(f float64) *float64 {
|
||||
v := f
|
||||
return &v
|
||||
}
|
||||
|
||||
const K_VALUE = float64(1000)
|
||||
const M_VALUE = K_VALUE * K_VALUE
|
||||
const G_VALUE = M_VALUE * K_VALUE
|
||||
const T_VALUE = G_VALUE * K_VALUE
|
||||
|
||||
func commafWithDigits(value float64) string {
|
||||
decimals := 2
|
||||
if value >= T_VALUE {
|
||||
return humanize.CommafWithDigits(value/T_VALUE, decimals) + "T"
|
||||
}
|
||||
if value >= G_VALUE {
|
||||
return humanize.CommafWithDigits(value/G_VALUE, decimals) + "G"
|
||||
}
|
||||
if value >= M_VALUE {
|
||||
return humanize.CommafWithDigits(value/M_VALUE, decimals) + "M"
|
||||
}
|
||||
if value >= K_VALUE {
|
||||
return humanize.CommafWithDigits(value/K_VALUE, decimals) + "k"
|
||||
}
|
||||
return humanize.CommafWithDigits(value, decimals)
|
||||
}
|
||||
|
||||
func parseColor(color string) Color {
|
||||
c := Color{}
|
||||
if color == "" {
|
||||
return c
|
||||
}
|
||||
if strings.HasPrefix(color, "#") {
|
||||
return drawing.ColorFromHex(color[1:])
|
||||
}
|
||||
reg := regexp.MustCompile(`\((\S+)\)`)
|
||||
result := reg.FindAllStringSubmatch(color, 1)
|
||||
if len(result) == 0 || len(result[0]) != 2 {
|
||||
return c
|
||||
}
|
||||
arr := strings.Split(result[0][1], ",")
|
||||
if len(arr) < 3 {
|
||||
return c
|
||||
}
|
||||
// 设置默认为255
|
||||
c.A = 255
|
||||
for index, v := range arr {
|
||||
value, _ := strconv.Atoi(strings.TrimSpace(v))
|
||||
ui8 := uint8(value)
|
||||
switch index {
|
||||
case 0:
|
||||
c.R = ui8
|
||||
case 1:
|
||||
c.G = ui8
|
||||
case 2:
|
||||
c.B = ui8
|
||||
default:
|
||||
c.A = ui8
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
const defaultRadiusPercent = 0.4
|
||||
|
||||
func getRadius(diameter float64, radiusValue string) float64 {
|
||||
var radius float64
|
||||
if len(radiusValue) != 0 {
|
||||
v := convertPercent(radiusValue)
|
||||
if v != -1 {
|
||||
radius = float64(diameter) * v
|
||||
} else {
|
||||
radius, _ = strconv.ParseFloat(radiusValue, 64)
|
||||
}
|
||||
}
|
||||
if radius <= 0 {
|
||||
radius = float64(diameter) * defaultRadiusPercent
|
||||
}
|
||||
return radius
|
||||
}
|
||||
|
||||
func getPolygonPointAngles(sides int) []float64 {
|
||||
angles := make([]float64, sides)
|
||||
for i := 0; i < sides; i++ {
|
||||
angle := 2*math.Pi/float64(sides)*float64(i) - (math.Pi / 2)
|
||||
angles[i] = angle
|
||||
}
|
||||
return angles
|
||||
}
|
||||
|
||||
func getPolygonPoint(center Point, radius, angle float64) Point {
|
||||
x := center.X + int(radius*math.Cos(angle))
|
||||
y := center.Y + int(radius*math.Sin(angle))
|
||||
return Point{
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
}
|
||||
|
||||
func getPolygonPoints(center Point, radius float64, sides int) []Point {
|
||||
points := make([]Point, sides)
|
||||
for i, angle := range getPolygonPointAngles(sides) {
|
||||
points[i] = getPolygonPoint(center, radius, angle)
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func isLightColor(c Color) bool {
|
||||
r := float64(c.R) * float64(c.R) * 0.299
|
||||
g := float64(c.G) * float64(c.G) * 0.587
|
||||
b := float64(c.B) * float64(c.B) * 0.114
|
||||
return math.Sqrt(r+g+b) > 127.5
|
||||
}
|
||||
105
src/server/vendor/github.com/vicanso/go-charts/v2/xaxis.go
generated
vendored
Normal file
105
src/server/vendor/github.com/vicanso/go-charts/v2/xaxis.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
type XAxisOption struct {
|
||||
// The font of x axis
|
||||
Font *truetype.Font
|
||||
// The boundary gap on both sides of a coordinate axis.
|
||||
// Nil or *true means the center part of two axis ticks
|
||||
BoundaryGap *bool
|
||||
// The data value of x axis
|
||||
Data []string
|
||||
// The theme of chart
|
||||
Theme ColorPalette
|
||||
// The font size of x axis label
|
||||
FontSize float64
|
||||
// The flag for show axis, set this to *false will hide axis
|
||||
Show *bool
|
||||
// Number of segments that the axis is split into. Note that this number serves only as a recommendation.
|
||||
SplitNumber int
|
||||
// The position of axis, it can be 'top' or 'bottom'
|
||||
Position string
|
||||
// The line color of axis
|
||||
StrokeColor Color
|
||||
// The color of label
|
||||
FontColor Color
|
||||
// The text rotation of label
|
||||
TextRotation float64
|
||||
// The first axis
|
||||
FirstAxis int
|
||||
// The offset of label
|
||||
LabelOffset Box
|
||||
isValueAxis bool
|
||||
}
|
||||
|
||||
const defaultXAxisHeight = 30
|
||||
|
||||
// NewXAxisOption returns a x axis option
|
||||
func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption {
|
||||
opt := XAxisOption{
|
||||
Data: data,
|
||||
}
|
||||
if len(boundaryGap) != 0 {
|
||||
opt.BoundaryGap = boundaryGap[0]
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func (opt *XAxisOption) ToAxisOption() AxisOption {
|
||||
position := PositionBottom
|
||||
if opt.Position == PositionTop {
|
||||
position = PositionTop
|
||||
}
|
||||
axisOpt := AxisOption{
|
||||
Theme: opt.Theme,
|
||||
Data: opt.Data,
|
||||
BoundaryGap: opt.BoundaryGap,
|
||||
Position: position,
|
||||
SplitNumber: opt.SplitNumber,
|
||||
StrokeColor: opt.StrokeColor,
|
||||
FontSize: opt.FontSize,
|
||||
Font: opt.Font,
|
||||
FontColor: opt.FontColor,
|
||||
Show: opt.Show,
|
||||
SplitLineColor: opt.Theme.GetAxisSplitLineColor(),
|
||||
TextRotation: opt.TextRotation,
|
||||
LabelOffset: opt.LabelOffset,
|
||||
FirstAxis: opt.FirstAxis,
|
||||
}
|
||||
if opt.isValueAxis {
|
||||
axisOpt.SplitLineShow = true
|
||||
axisOpt.StrokeWidth = -1
|
||||
axisOpt.BoundaryGap = FalseFlag()
|
||||
}
|
||||
return axisOpt
|
||||
}
|
||||
|
||||
// NewBottomXAxis returns a bottom x axis renderer
|
||||
func NewBottomXAxis(p *Painter, opt XAxisOption) *axisPainter {
|
||||
return NewAxisPainter(p, opt.ToAxisOption())
|
||||
}
|
||||
128
src/server/vendor/github.com/vicanso/go-charts/v2/yaxis.go
generated
vendored
Normal file
128
src/server/vendor/github.com/vicanso/go-charts/v2/yaxis.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import "github.com/golang/freetype/truetype"
|
||||
|
||||
type YAxisOption struct {
|
||||
// The minimun value of axis.
|
||||
Min *float64
|
||||
// The maximum value of axis.
|
||||
Max *float64
|
||||
// The font of y axis
|
||||
Font *truetype.Font
|
||||
// The data value of x axis
|
||||
Data []string
|
||||
// The theme of chart
|
||||
Theme ColorPalette
|
||||
// The font size of x axis label
|
||||
FontSize float64
|
||||
// The position of axis, it can be 'left' or 'right'
|
||||
Position string
|
||||
// The color of label
|
||||
FontColor Color
|
||||
// Formatter for y axis text value
|
||||
Formatter string
|
||||
// Color for y axis
|
||||
Color Color
|
||||
// The flag for show axis, set this to *false will hide axis
|
||||
Show *bool
|
||||
DivideCount int
|
||||
Unit int
|
||||
isCategoryAxis bool
|
||||
// The flag for show axis split line, set this to true will show axis split line
|
||||
SplitLineShow *bool
|
||||
}
|
||||
|
||||
// NewYAxisOptions returns a y axis option
|
||||
func NewYAxisOptions(data []string, others ...[]string) []YAxisOption {
|
||||
arr := [][]string{
|
||||
data,
|
||||
}
|
||||
arr = append(arr, others...)
|
||||
opts := make([]YAxisOption, 0)
|
||||
for _, data := range arr {
|
||||
opts = append(opts, YAxisOption{
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption {
|
||||
position := PositionLeft
|
||||
if opt.Position == PositionRight {
|
||||
position = PositionRight
|
||||
}
|
||||
theme := opt.Theme
|
||||
if theme == nil {
|
||||
theme = p.theme
|
||||
}
|
||||
axisOpt := AxisOption{
|
||||
Formatter: opt.Formatter,
|
||||
Theme: theme,
|
||||
Data: opt.Data,
|
||||
Position: position,
|
||||
FontSize: opt.FontSize,
|
||||
StrokeWidth: -1,
|
||||
Font: opt.Font,
|
||||
FontColor: opt.FontColor,
|
||||
BoundaryGap: FalseFlag(),
|
||||
SplitLineShow: true,
|
||||
SplitLineColor: theme.GetAxisSplitLineColor(),
|
||||
Show: opt.Show,
|
||||
Unit: opt.Unit,
|
||||
}
|
||||
if !opt.Color.IsZero() {
|
||||
axisOpt.FontColor = opt.Color
|
||||
axisOpt.StrokeColor = opt.Color
|
||||
}
|
||||
if opt.isCategoryAxis {
|
||||
axisOpt.BoundaryGap = TrueFlag()
|
||||
axisOpt.StrokeWidth = 1
|
||||
axisOpt.SplitLineShow = false
|
||||
}
|
||||
if opt.SplitLineShow != nil {
|
||||
axisOpt.SplitLineShow = *opt.SplitLineShow
|
||||
}
|
||||
return axisOpt
|
||||
}
|
||||
|
||||
// NewLeftYAxis returns a left y axis renderer
|
||||
func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter {
|
||||
p = p.Child(PainterPaddingOption(Box{
|
||||
Bottom: defaultXAxisHeight,
|
||||
}))
|
||||
return NewAxisPainter(p, opt.ToAxisOption(p))
|
||||
}
|
||||
|
||||
// NewRightYAxis returns a right y axis renderer
|
||||
func NewRightYAxis(p *Painter, opt YAxisOption) *axisPainter {
|
||||
p = p.Child(PainterPaddingOption(Box{
|
||||
Bottom: defaultXAxisHeight,
|
||||
}))
|
||||
axisOpt := opt.ToAxisOption(p)
|
||||
axisOpt.Position = PositionRight
|
||||
axisOpt.SplitLineShow = false
|
||||
return NewAxisPainter(p, axisOpt)
|
||||
}
|
||||
19
src/server/vendor/github.com/wcharczuk/go-chart/v2/.gitignore
generated
vendored
Normal file
19
src/server/vendor/github.com/wcharczuk/go-chart/v2/.gitignore
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Other
|
||||
.vscode
|
||||
.DS_Store
|
||||
coverage.html
|
||||
1
src/server/vendor/github.com/wcharczuk/go-chart/v2/COVERAGE
generated
vendored
Normal file
1
src/server/vendor/github.com/wcharczuk/go-chart/v2/COVERAGE
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
29.02
|
||||
21
src/server/vendor/github.com/wcharczuk/go-chart/v2/LICENSE
generated
vendored
Normal file
21
src/server/vendor/github.com/wcharczuk/go-chart/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 William Charczuk.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
10
src/server/vendor/github.com/wcharczuk/go-chart/v2/Makefile
generated
vendored
Normal file
10
src/server/vendor/github.com/wcharczuk/go-chart/v2/Makefile
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
all: new-install test
|
||||
|
||||
new-install:
|
||||
@go get -v -u ./...
|
||||
|
||||
generate:
|
||||
@go generate ./...
|
||||
|
||||
test:
|
||||
@go test ./...
|
||||
4
src/server/vendor/github.com/wcharczuk/go-chart/v2/PROFANITY_RULES.yml
generated
vendored
Normal file
4
src/server/vendor/github.com/wcharczuk/go-chart/v2/PROFANITY_RULES.yml
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
go-sdk:
|
||||
excludeFiles: [ "*_test.go" ]
|
||||
importsContain: [ github.com/blend/go-sdk/* ]
|
||||
description: "please don't use go-sdk in this repo"
|
||||
95
src/server/vendor/github.com/wcharczuk/go-chart/v2/README.md
generated
vendored
Normal file
95
src/server/vendor/github.com/wcharczuk/go-chart/v2/README.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
go-chart
|
||||
========
|
||||
[](https://circleci.com/gh/wcharczuk/go-chart) [](https://goreportcard.com/report/github.com/wcharczuk/go-chart)
|
||||
|
||||
Package `chart` is a very simple golang native charting library that supports timeseries and continuous line charts.
|
||||
|
||||
Master should now be on the v3.x codebase, which overhauls the api significantly. Per usual, see `examples` for more information.
|
||||
|
||||
# Installation
|
||||
|
||||
To install `chart` run the following:
|
||||
|
||||
```bash
|
||||
> go get -u github.com/wcharczuk/go-chart
|
||||
```
|
||||
|
||||
Most of the components are interchangeable so feel free to crib whatever you want.
|
||||
|
||||
# Output Examples
|
||||
|
||||
Spark Lines:
|
||||
|
||||

|
||||
|
||||
Single axis:
|
||||
|
||||

|
||||
|
||||
Two axis:
|
||||
|
||||

|
||||
|
||||
# Other Chart Types
|
||||
|
||||
Pie Chart:
|
||||
|
||||

|
||||
|
||||
The code for this chart can be found in `examples/pie_chart/main.go`.
|
||||
|
||||
Stacked Bar:
|
||||
|
||||

|
||||
|
||||
The code for this chart can be found in `examples/stacked_bar/main.go`.
|
||||
|
||||
# Code Examples
|
||||
|
||||
Actual chart configurations and examples can be found in the `./examples/` directory. They are simple CLI programs that write to `output.png` (they are also updated with `go generate`.
|
||||
|
||||
# Usage
|
||||
|
||||
Everything starts with the `chart.Chart` object. The bare minimum to draw a chart would be the following:
|
||||
|
||||
```golang
|
||||
|
||||
import (
|
||||
...
|
||||
"bytes"
|
||||
...
|
||||
"github.com/wcharczuk/go-chart" //exposes "chart"
|
||||
)
|
||||
|
||||
graph := chart.Chart{
|
||||
Series: []chart.Series{
|
||||
chart.ContinuousSeries{
|
||||
XValues: []float64{1.0, 2.0, 3.0, 4.0},
|
||||
YValues: []float64{1.0, 2.0, 3.0, 4.0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
err := graph.Render(chart.PNG, buffer)
|
||||
```
|
||||
|
||||
Explanation of the above: A `chart` can have many `Series`, a `Series` is a collection of things that need to be drawn according to the X range and the Y range(s).
|
||||
|
||||
Here, we have a single series with x range values as float64s, rendered to a PNG. Note; we can pass any type of `io.Writer` into `Render(...)`, meaning that we can render the chart to a file or a resonse or anything else that implements `io.Writer`.
|
||||
|
||||
# API Overview
|
||||
|
||||
Everything on the `chart.Chart` object has defaults that can be overriden. Whenever a developer sets a property on the chart object, it is to be assumed that value will be used instead of the default.
|
||||
|
||||
The best way to see the api in action is to look at the examples in the `./_examples/` directory.
|
||||
|
||||
# Design Philosophy
|
||||
|
||||
I wanted to make a charting library that used only native golang, that could be stood up on a server (i.e. it had built in fonts).
|
||||
|
||||
The goal with the API itself is to have the "zero value be useful", and to require the user to not code more than they absolutely needed.
|
||||
|
||||
# Contributions
|
||||
|
||||
Contributions are welcome though this library is in a holding pattern for the forseable future.
|
||||
91
src/server/vendor/github.com/wcharczuk/go-chart/v2/annotation_series.go
generated
vendored
Normal file
91
src/server/vendor/github.com/wcharczuk/go-chart/v2/annotation_series.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Interface Assertions.
|
||||
var (
|
||||
_ Series = (*AnnotationSeries)(nil)
|
||||
)
|
||||
|
||||
// AnnotationSeries is a series of labels on the chart.
|
||||
type AnnotationSeries struct {
|
||||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
Annotations []Value2
|
||||
}
|
||||
|
||||
// GetName returns the name of the time series.
|
||||
func (as AnnotationSeries) GetName() string {
|
||||
return as.Name
|
||||
}
|
||||
|
||||
// GetStyle returns the line style.
|
||||
func (as AnnotationSeries) GetStyle() Style {
|
||||
return as.Style
|
||||
}
|
||||
|
||||
// GetYAxis returns which YAxis the series draws on.
|
||||
func (as AnnotationSeries) GetYAxis() YAxisType {
|
||||
return as.YAxis
|
||||
}
|
||||
|
||||
func (as AnnotationSeries) annotationStyleDefaults(defaults Style) Style {
|
||||
return Style{
|
||||
FontColor: DefaultTextColor,
|
||||
Font: defaults.Font,
|
||||
FillColor: DefaultAnnotationFillColor,
|
||||
FontSize: DefaultAnnotationFontSize,
|
||||
StrokeColor: defaults.StrokeColor,
|
||||
StrokeWidth: defaults.StrokeWidth,
|
||||
Padding: DefaultAnnotationPadding,
|
||||
}
|
||||
}
|
||||
|
||||
// Measure returns a bounds box of the series.
|
||||
func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box {
|
||||
box := Box{
|
||||
Top: math.MaxInt32,
|
||||
Left: math.MaxInt32,
|
||||
Right: 0,
|
||||
Bottom: 0,
|
||||
}
|
||||
if !as.Style.Hidden {
|
||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||
for _, a := range as.Annotations {
|
||||
style := a.Style.InheritFrom(seriesStyle)
|
||||
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
||||
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
||||
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
||||
box.Top = MinInt(box.Top, ab.Top)
|
||||
box.Left = MinInt(box.Left, ab.Left)
|
||||
box.Right = MaxInt(box.Right, ab.Right)
|
||||
box.Bottom = MaxInt(box.Bottom, ab.Bottom)
|
||||
}
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// Render draws the series.
|
||||
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||
if !as.Style.Hidden {
|
||||
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
|
||||
for _, a := range as.Annotations {
|
||||
style := a.Style.InheritFrom(seriesStyle)
|
||||
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
||||
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
||||
Draw.Annotation(r, canvasBox, style, lx, ly, a.Label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the series.
|
||||
func (as AnnotationSeries) Validate() error {
|
||||
if len(as.Annotations) == 0 {
|
||||
return fmt.Errorf("annotation series requires annotations to be set and not empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
24
src/server/vendor/github.com/wcharczuk/go-chart/v2/array.go
generated
vendored
Normal file
24
src/server/vendor/github.com/wcharczuk/go-chart/v2/array.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
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]
|
||||
}
|
||||
45
src/server/vendor/github.com/wcharczuk/go-chart/v2/axis.go
generated
vendored
Normal file
45
src/server/vendor/github.com/wcharczuk/go-chart/v2/axis.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package chart
|
||||
|
||||
// TickPosition is an enumeration of possible tick drawing positions.
|
||||
type TickPosition int
|
||||
|
||||
const (
|
||||
// TickPositionUnset means to use the default tick position.
|
||||
TickPositionUnset TickPosition = 0
|
||||
// TickPositionBetweenTicks draws the labels for a tick between the previous and current tick.
|
||||
TickPositionBetweenTicks TickPosition = 1
|
||||
// TickPositionUnderTick draws the tick below the tick.
|
||||
TickPositionUnderTick TickPosition = 2
|
||||
)
|
||||
|
||||
// YAxisType is a type of y-axis; it can either be primary or secondary.
|
||||
type YAxisType int
|
||||
|
||||
const (
|
||||
// YAxisPrimary is the primary axis.
|
||||
YAxisPrimary YAxisType = 0
|
||||
// YAxisSecondary is the secondary axis.
|
||||
YAxisSecondary YAxisType = 1
|
||||
)
|
||||
|
||||
// Axis is a chart feature detailing what values happen where.
|
||||
type Axis interface {
|
||||
GetName() string
|
||||
SetName(name string)
|
||||
|
||||
GetStyle() Style
|
||||
SetStyle(style Style)
|
||||
|
||||
GetTicks() []Tick
|
||||
GenerateTicks(r Renderer, ra Range, vf ValueFormatter) []Tick
|
||||
|
||||
// GenerateGridLines returns the gridlines for the axis.
|
||||
GetGridLines(ticks []Tick) []GridLine
|
||||
|
||||
// Measure should return an absolute box for the axis.
|
||||
// This is used when auto-fitting the canvas to the background.
|
||||
Measure(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick) Box
|
||||
|
||||
// Render renders the axis.
|
||||
Render(r Renderer, canvasBox Box, ra Range, style Style, ticks []Tick)
|
||||
}
|
||||
516
src/server/vendor/github.com/wcharczuk/go-chart/v2/bar_chart.go
generated
vendored
Normal file
516
src/server/vendor/github.com/wcharczuk/go-chart/v2/bar_chart.go
generated
vendored
Normal file
@ -0,0 +1,516 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// BarChart is a chart that draws bars on a range.
|
||||
type BarChart struct {
|
||||
Title string
|
||||
TitleStyle Style
|
||||
|
||||
ColorPalette ColorPalette
|
||||
|
||||
Width int
|
||||
Height int
|
||||
DPI float64
|
||||
|
||||
BarWidth int
|
||||
|
||||
Background Style
|
||||
Canvas Style
|
||||
|
||||
XAxis Style
|
||||
YAxis YAxis
|
||||
|
||||
BarSpacing int
|
||||
|
||||
UseBaseValue bool
|
||||
BaseValue float64
|
||||
|
||||
Font *truetype.Font
|
||||
defaultFont *truetype.Font
|
||||
|
||||
Bars []Value
|
||||
Elements []Renderable
|
||||
}
|
||||
|
||||
// GetDPI returns the dpi for the chart.
|
||||
func (bc BarChart) GetDPI() float64 {
|
||||
if bc.DPI == 0 {
|
||||
return DefaultDPI
|
||||
}
|
||||
return bc.DPI
|
||||
}
|
||||
|
||||
// GetFont returns the text font.
|
||||
func (bc BarChart) GetFont() *truetype.Font {
|
||||
if bc.Font == nil {
|
||||
return bc.defaultFont
|
||||
}
|
||||
return bc.Font
|
||||
}
|
||||
|
||||
// GetWidth returns the chart width or the default value.
|
||||
func (bc BarChart) GetWidth() int {
|
||||
if bc.Width == 0 {
|
||||
return DefaultChartWidth
|
||||
}
|
||||
return bc.Width
|
||||
}
|
||||
|
||||
// GetHeight returns the chart height or the default value.
|
||||
func (bc BarChart) GetHeight() int {
|
||||
if bc.Height == 0 {
|
||||
return DefaultChartHeight
|
||||
}
|
||||
return bc.Height
|
||||
}
|
||||
|
||||
// GetBarSpacing returns the spacing between bars.
|
||||
func (bc BarChart) GetBarSpacing() int {
|
||||
if bc.BarSpacing == 0 {
|
||||
return DefaultBarSpacing
|
||||
}
|
||||
return bc.BarSpacing
|
||||
}
|
||||
|
||||
// GetBarWidth returns the default bar width.
|
||||
func (bc BarChart) GetBarWidth() int {
|
||||
if bc.BarWidth == 0 {
|
||||
return DefaultBarWidth
|
||||
}
|
||||
return bc.BarWidth
|
||||
}
|
||||
|
||||
// Render renders the chart with the given renderer to the given io.Writer.
|
||||
func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||
if len(bc.Bars) == 0 {
|
||||
return errors.New("please provide at least one bar")
|
||||
}
|
||||
|
||||
r, err := rp(bc.GetWidth(), bc.GetHeight())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bc.Font == nil {
|
||||
defaultFont, err := GetDefaultFont()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.defaultFont = defaultFont
|
||||
}
|
||||
r.SetDPI(bc.GetDPI())
|
||||
|
||||
bc.drawBackground(r)
|
||||
|
||||
var canvasBox Box
|
||||
var yt []Tick
|
||||
var yr Range
|
||||
var yf ValueFormatter
|
||||
|
||||
canvasBox = bc.getDefaultCanvasBox()
|
||||
yr = bc.getRanges()
|
||||
if yr.GetMax()-yr.GetMin() == 0 {
|
||||
return fmt.Errorf("invalid data range; cannot be zero")
|
||||
}
|
||||
yr = bc.setRangeDomains(canvasBox, yr)
|
||||
yf = bc.getValueFormatters()
|
||||
|
||||
if bc.hasAxes() {
|
||||
yt = bc.getAxesTicks(r, yr, yf)
|
||||
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
||||
yr = bc.setRangeDomains(canvasBox, yr)
|
||||
}
|
||||
bc.drawCanvas(r, canvasBox)
|
||||
bc.drawBars(r, canvasBox, yr)
|
||||
bc.drawXAxis(r, canvasBox)
|
||||
bc.drawYAxis(r, canvasBox, yr, yt)
|
||||
|
||||
bc.drawTitle(r)
|
||||
for _, a := range bc.Elements {
|
||||
a(r, canvasBox, bc.styleDefaultsElements())
|
||||
}
|
||||
|
||||
return r.Save(w)
|
||||
}
|
||||
|
||||
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) {
|
||||
Draw.Box(r, canvasBox, bc.getCanvasStyle())
|
||||
}
|
||||
|
||||
func (bc BarChart) getRanges() Range {
|
||||
var yrange Range
|
||||
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
||||
yrange = bc.YAxis.Range
|
||||
} else {
|
||||
yrange = &ContinuousRange{}
|
||||
}
|
||||
|
||||
if !yrange.IsZero() {
|
||||
return yrange
|
||||
}
|
||||
|
||||
if len(bc.YAxis.Ticks) > 0 {
|
||||
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, t := range bc.YAxis.Ticks {
|
||||
tickMin = math.Min(tickMin, t.Value)
|
||||
tickMax = math.Max(tickMax, t.Value)
|
||||
}
|
||||
yrange.SetMin(tickMin)
|
||||
yrange.SetMax(tickMax)
|
||||
return yrange
|
||||
}
|
||||
|
||||
min, max := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, b := range bc.Bars {
|
||||
min = math.Min(b.Value, min)
|
||||
max = math.Max(b.Value, max)
|
||||
}
|
||||
|
||||
yrange.SetMin(min)
|
||||
yrange.SetMax(max)
|
||||
|
||||
return yrange
|
||||
}
|
||||
|
||||
func (bc BarChart) drawBackground(r Renderer) {
|
||||
Draw.Box(r, Box{
|
||||
Right: bc.GetWidth(),
|
||||
Bottom: bc.GetHeight(),
|
||||
}, bc.getBackgroundStyle())
|
||||
}
|
||||
|
||||
func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||
xoffset := canvasBox.Left
|
||||
|
||||
width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox)
|
||||
bs2 := spacing >> 1
|
||||
|
||||
var barBox Box
|
||||
var bxl, bxr, by int
|
||||
for index, bar := range bc.Bars {
|
||||
bxl = xoffset + bs2
|
||||
bxr = bxl + width
|
||||
|
||||
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{
|
||||
Top: by,
|
||||
Left: bxl,
|
||||
Right: bxr,
|
||||
Bottom: canvasBox.Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
||||
|
||||
xoffset += width + spacing
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||
if !bc.XAxis.Hidden {
|
||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||
axisStyle.WriteToRenderer(r)
|
||||
|
||||
width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox)
|
||||
|
||||
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
|
||||
r.LineTo(canvasBox.Right, canvasBox.Bottom)
|
||||
r.Stroke()
|
||||
|
||||
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
|
||||
r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight)
|
||||
r.Stroke()
|
||||
|
||||
cursor := canvasBox.Left
|
||||
for index, bar := range bc.Bars {
|
||||
barLabelBox := Box{
|
||||
Top: canvasBox.Bottom + DefaultXAxisMargin,
|
||||
Left: cursor,
|
||||
Right: cursor + width + spacing,
|
||||
Bottom: bc.GetHeight(),
|
||||
}
|
||||
|
||||
if len(bar.Label) > 0 {
|
||||
Draw.TextWithin(r, bar.Label, barLabelBox, axisStyle)
|
||||
}
|
||||
|
||||
axisStyle.WriteToRenderer(r)
|
||||
if index < len(bc.Bars)-1 {
|
||||
r.MoveTo(barLabelBox.Right, canvasBox.Bottom)
|
||||
r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight)
|
||||
r.Stroke()
|
||||
}
|
||||
cursor += width + spacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
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 Box
|
||||
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+(tb.Height()>>1), axisStyle)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawTitle(r Renderer) {
|
||||
if len(bc.Title) > 0 && !bc.TitleStyle.Hidden {
|
||||
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
||||
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
||||
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
||||
r.SetFontSize(titleFontSize)
|
||||
|
||||
textBox := r.MeasureText(bc.Title)
|
||||
|
||||
textWidth := textBox.Width()
|
||||
textHeight := textBox.Height()
|
||||
|
||||
titleX := (bc.GetWidth() >> 1) - (textWidth >> 1)
|
||||
titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
||||
|
||||
r.Text(bc.Title, titleX, titleY)
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) getCanvasStyle() Style {
|
||||
return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas())
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsCanvas() Style {
|
||||
return Style{
|
||||
FillColor: bc.GetColorPalette().CanvasColor(),
|
||||
StrokeColor: bc.GetColorPalette().CanvasStrokeColor(),
|
||||
StrokeWidth: DefaultCanvasStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) hasAxes() bool {
|
||||
return !bc.YAxis.Style.Hidden
|
||||
}
|
||||
|
||||
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
||||
yr.SetDomain(canvasBox.Height())
|
||||
return yr
|
||||
}
|
||||
|
||||
func (bc BarChart) getDefaultCanvasBox() Box {
|
||||
return bc.box()
|
||||
}
|
||||
|
||||
func (bc BarChart) getValueFormatters() ValueFormatter {
|
||||
if bc.YAxis.ValueFormatter != nil {
|
||||
return bc.YAxis.ValueFormatter
|
||||
}
|
||||
return FloatValueFormatter
|
||||
}
|
||||
|
||||
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateEffectiveBarSpacing(canvasBox Box) int {
|
||||
totalWithBaseSpacing := bc.calculateTotalBarWidth(bc.GetBarWidth(), bc.GetBarSpacing())
|
||||
if totalWithBaseSpacing > canvasBox.Width() {
|
||||
lessBarWidths := canvasBox.Width() - (len(bc.Bars) * bc.GetBarWidth())
|
||||
if lessBarWidths > 0 {
|
||||
return int(math.Ceil(float64(lessBarWidths) / float64(len(bc.Bars))))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return bc.GetBarSpacing()
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateEffectiveBarWidth(canvasBox Box, spacing int) int {
|
||||
totalWithBaseWidth := bc.calculateTotalBarWidth(bc.GetBarWidth(), spacing)
|
||||
if totalWithBaseWidth > canvasBox.Width() {
|
||||
totalLessBarSpacings := canvasBox.Width() - (len(bc.Bars) * spacing)
|
||||
if totalLessBarSpacings > 0 {
|
||||
return int(math.Ceil(float64(totalLessBarSpacings) / float64(len(bc.Bars))))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return bc.GetBarWidth()
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateTotalBarWidth(barWidth, spacing int) int {
|
||||
return len(bc.Bars) * (barWidth + spacing)
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateScaledTotalWidth(canvasBox Box) (width, spacing, total int) {
|
||||
spacing = bc.calculateEffectiveBarSpacing(canvasBox)
|
||||
width = bc.calculateEffectiveBarWidth(canvasBox, spacing)
|
||||
total = bc.calculateTotalBarWidth(width, spacing)
|
||||
return
|
||||
}
|
||||
|
||||
func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range, yticks []Tick) Box {
|
||||
axesOuterBox := canvasBox.Clone()
|
||||
|
||||
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
||||
|
||||
if !bc.XAxis.Hidden {
|
||||
xaxisHeight := DefaultVerticalTickHeight
|
||||
|
||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||
axisStyle.WriteToRenderer(r)
|
||||
|
||||
cursor := canvasBox.Left
|
||||
for _, bar := range bc.Bars {
|
||||
if len(bar.Label) > 0 {
|
||||
barLabelBox := Box{
|
||||
Top: canvasBox.Bottom + DefaultXAxisMargin,
|
||||
Left: cursor,
|
||||
Right: cursor + bc.GetBarWidth() + bc.GetBarSpacing(),
|
||||
Bottom: bc.GetHeight(),
|
||||
}
|
||||
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||
|
||||
xaxisHeight = MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||
}
|
||||
}
|
||||
|
||||
xbox := Box{
|
||||
Top: canvasBox.Top,
|
||||
Left: canvasBox.Left,
|
||||
Right: canvasBox.Left + totalWidth,
|
||||
Bottom: bc.GetHeight() - xaxisHeight,
|
||||
}
|
||||
|
||||
axesOuterBox = axesOuterBox.Grow(xbox)
|
||||
}
|
||||
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||
}
|
||||
|
||||
return canvasBox.OuterConstrain(bc.box(), axesOuterBox)
|
||||
}
|
||||
|
||||
// box returns the chart bounds as a box.
|
||||
func (bc BarChart) box() Box {
|
||||
dpr := bc.Background.Padding.GetRight(10)
|
||||
dpb := bc.Background.Padding.GetBottom(50)
|
||||
|
||||
return Box{
|
||||
Top: bc.Background.Padding.GetTop(20),
|
||||
Left: bc.Background.Padding.GetLeft(20),
|
||||
Right: bc.GetWidth() - dpr,
|
||||
Bottom: bc.GetHeight() - dpb,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) getBackgroundStyle() Style {
|
||||
return bc.Background.InheritFrom(bc.styleDefaultsBackground())
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsBackground() Style {
|
||||
return Style{
|
||||
FillColor: bc.GetColorPalette().BackgroundColor(),
|
||||
StrokeColor: bc.GetColorPalette().BackgroundStrokeColor(),
|
||||
StrokeWidth: DefaultStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsBar(index int) Style {
|
||||
return Style{
|
||||
StrokeColor: bc.GetColorPalette().GetSeriesColor(index),
|
||||
StrokeWidth: 3.0,
|
||||
FillColor: bc.GetColorPalette().GetSeriesColor(index),
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsTitle() Style {
|
||||
return bc.TitleStyle.InheritFrom(Style{
|
||||
FontColor: bc.GetColorPalette().TextColor(),
|
||||
Font: bc.GetFont(),
|
||||
FontSize: bc.getTitleFontSize(),
|
||||
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||
TextVerticalAlign: TextVerticalAlignTop,
|
||||
TextWrap: TextWrapWord,
|
||||
})
|
||||
}
|
||||
|
||||
func (bc BarChart) getTitleFontSize() float64 {
|
||||
effectiveDimension := MinInt(bc.GetWidth(), bc.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
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsAxes() Style {
|
||||
return Style{
|
||||
StrokeColor: bc.GetColorPalette().AxisStrokeColor(),
|
||||
Font: bc.GetFont(),
|
||||
FontSize: DefaultAxisFontSize,
|
||||
FontColor: bc.GetColorPalette().TextColor(),
|
||||
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||
TextVerticalAlign: TextVerticalAlignTop,
|
||||
TextWrap: TextWrapWord,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsElements() Style {
|
||||
return Style{
|
||||
Font: bc.GetFont(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetColorPalette returns the color palette for the chart.
|
||||
func (bc BarChart) GetColorPalette() ColorPalette {
|
||||
if bc.ColorPalette != nil {
|
||||
return bc.ColorPalette
|
||||
}
|
||||
return AlternateColorPalette
|
||||
}
|
||||
135
src/server/vendor/github.com/wcharczuk/go-chart/v2/bollinger_band_series.go
generated
vendored
Normal file
135
src/server/vendor/github.com/wcharczuk/go-chart/v2/bollinger_band_series.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Interface Assertions.
|
||||
var (
|
||||
_ Series = (*BollingerBandsSeries)(nil)
|
||||
)
|
||||
|
||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||
// Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev.
|
||||
type BollingerBandsSeries struct {
|
||||
Name string
|
||||
Style Style
|
||||
YAxis YAxisType
|
||||
|
||||
Period int
|
||||
K float64
|
||||
InnerSeries ValuesProvider
|
||||
|
||||
valueBuffer *ValueBuffer
|
||||
}
|
||||
|
||||
// GetName returns the name of the time series.
|
||||
func (bbs BollingerBandsSeries) GetName() string {
|
||||
return bbs.Name
|
||||
}
|
||||
|
||||
// GetStyle returns the line style.
|
||||
func (bbs BollingerBandsSeries) GetStyle() Style {
|
||||
return bbs.Style
|
||||
}
|
||||
|
||||
// GetYAxis returns which YAxis the series draws on.
|
||||
func (bbs BollingerBandsSeries) GetYAxis() YAxisType {
|
||||
return bbs.YAxis
|
||||
}
|
||||
|
||||
// GetPeriod returns the window size.
|
||||
func (bbs BollingerBandsSeries) GetPeriod() int {
|
||||
if bbs.Period == 0 {
|
||||
return DefaultSimpleMovingAveragePeriod
|
||||
}
|
||||
return bbs.Period
|
||||
}
|
||||
|
||||
// GetK returns the K value, or the number of standard deviations above and below
|
||||
// to band the simple moving average with.
|
||||
// Typical K value is 2.0.
|
||||
func (bbs BollingerBandsSeries) GetK(defaults ...float64) float64 {
|
||||
if bbs.K == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return 2.0
|
||||
}
|
||||
return bbs.K
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the series.
|
||||
func (bbs BollingerBandsSeries) Len() int {
|
||||
return bbs.InnerSeries.Len()
|
||||
}
|
||||
|
||||
// GetBoundedValues gets the bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
|
||||
if bbs.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
if bbs.valueBuffer == nil || index == 0 {
|
||||
bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod())
|
||||
}
|
||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||
bbs.valueBuffer.Dequeue()
|
||||
}
|
||||
px, py := bbs.InnerSeries.GetValues(index)
|
||||
bbs.valueBuffer.Enqueue(py)
|
||||
x = px
|
||||
|
||||
ay := Seq{bbs.valueBuffer}.Average()
|
||||
std := Seq{bbs.valueBuffer}.StdDev()
|
||||
|
||||
y1 = ay + (bbs.GetK() * std)
|
||||
y2 = ay - (bbs.GetK() * std)
|
||||
return
|
||||
}
|
||||
|
||||
// GetBoundedLastValues returns the last bounded value for the series.
|
||||
func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) {
|
||||
if bbs.InnerSeries == nil {
|
||||
return
|
||||
}
|
||||
period := bbs.GetPeriod()
|
||||
seriesLength := bbs.InnerSeries.Len()
|
||||
startAt := seriesLength - period
|
||||
if startAt < 0 {
|
||||
startAt = 0
|
||||
}
|
||||
|
||||
vb := NewValueBufferWithCapacity(period)
|
||||
for index := startAt; index < seriesLength; index++ {
|
||||
xn, yn := bbs.InnerSeries.GetValues(index)
|
||||
vb.Enqueue(yn)
|
||||
x = xn
|
||||
}
|
||||
|
||||
ay := Seq{vb}.Average()
|
||||
std := Seq{vb}.StdDev()
|
||||
|
||||
y1 = ay + (bbs.GetK() * std)
|
||||
y2 = ay - (bbs.GetK() * std)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Render renders the series.
|
||||
func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||
s := bbs.Style.InheritFrom(defaults.InheritFrom(Style{
|
||||
StrokeWidth: 1.0,
|
||||
StrokeColor: DefaultAxisColor.WithAlpha(64),
|
||||
FillColor: DefaultAxisColor.WithAlpha(32),
|
||||
}))
|
||||
|
||||
Draw.BoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod())
|
||||
}
|
||||
|
||||
// Validate validates the series.
|
||||
func (bbs BollingerBandsSeries) Validate() error {
|
||||
if bbs.InnerSeries == nil {
|
||||
return fmt.Errorf("bollinger bands series requires InnerSeries to be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
src/server/vendor/github.com/wcharczuk/go-chart/v2/bounded_last_values_annotation_series.go
generated
vendored
Normal file
36
src/server/vendor/github.com/wcharczuk/go-chart/v2/bounded_last_values_annotation_series.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
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},
|
||||
},
|
||||
}
|
||||
}
|
||||
351
src/server/vendor/github.com/wcharczuk/go-chart/v2/box.go
generated
vendored
Normal file
351
src/server/vendor/github.com/wcharczuk/go-chart/v2/box.go
generated
vendored
Normal file
@ -0,0 +1,351 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
// BoxZero is a preset box that represents an intentional zero value.
|
||||
BoxZero = Box{IsSet: true}
|
||||
)
|
||||
|
||||
// NewBox returns a new (set) box.
|
||||
func NewBox(top, left, right, bottom int) Box {
|
||||
return Box{
|
||||
IsSet: true,
|
||||
Top: top,
|
||||
Left: left,
|
||||
Right: right,
|
||||
Bottom: bottom,
|
||||
}
|
||||
}
|
||||
|
||||
// Box represents the main 4 dimensions of a box.
|
||||
type Box struct {
|
||||
Top int
|
||||
Left int
|
||||
Right int
|
||||
Bottom int
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
// IsZero returns if the box is set or not.
|
||||
func (b Box) IsZero() bool {
|
||||
if b.IsSet {
|
||||
return false
|
||||
}
|
||||
return b.Top == 0 && b.Left == 0 && b.Right == 0 && b.Bottom == 0
|
||||
}
|
||||
|
||||
// String returns a string representation of the box.
|
||||
func (b Box) String() string {
|
||||
return fmt.Sprintf("box(%d,%d,%d,%d)", b.Top, b.Left, b.Right, b.Bottom)
|
||||
}
|
||||
|
||||
// GetTop returns a coalesced value with a default.
|
||||
func (b Box) GetTop(defaults ...int) int {
|
||||
if !b.IsSet && b.Top == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return b.Top
|
||||
}
|
||||
|
||||
// GetLeft returns a coalesced value with a default.
|
||||
func (b Box) GetLeft(defaults ...int) int {
|
||||
if !b.IsSet && b.Left == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return b.Left
|
||||
}
|
||||
|
||||
// GetRight returns a coalesced value with a default.
|
||||
func (b Box) GetRight(defaults ...int) int {
|
||||
if !b.IsSet && b.Right == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return b.Right
|
||||
}
|
||||
|
||||
// GetBottom returns a coalesced value with a default.
|
||||
func (b Box) GetBottom(defaults ...int) int {
|
||||
if !b.IsSet && b.Bottom == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return b.Bottom
|
||||
}
|
||||
|
||||
// Width returns the width
|
||||
func (b Box) Width() int {
|
||||
return AbsInt(b.Right - b.Left)
|
||||
}
|
||||
|
||||
// Height returns the height
|
||||
func (b Box) Height() int {
|
||||
return AbsInt(b.Bottom - b.Top)
|
||||
}
|
||||
|
||||
// Center returns the center of the box
|
||||
func (b Box) Center() (x, y int) {
|
||||
w2, h2 := b.Width()>>1, b.Height()>>1
|
||||
return b.Left + w2, b.Top + h2
|
||||
}
|
||||
|
||||
// Aspect returns the aspect ratio of the box.
|
||||
func (b Box) Aspect() float64 {
|
||||
return float64(b.Width()) / float64(b.Height())
|
||||
}
|
||||
|
||||
// Clone returns a new copy of the box.
|
||||
func (b Box) Clone() Box {
|
||||
return Box{
|
||||
IsSet: b.IsSet,
|
||||
Top: b.Top,
|
||||
Left: b.Left,
|
||||
Right: b.Right,
|
||||
Bottom: b.Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
// IsBiggerThan returns if a box is bigger than another box.
|
||||
func (b Box) IsBiggerThan(other Box) bool {
|
||||
return b.Top < other.Top ||
|
||||
b.Bottom > other.Bottom ||
|
||||
b.Left < other.Left ||
|
||||
b.Right > other.Right
|
||||
}
|
||||
|
||||
// IsSmallerThan returns if a box is smaller than another box.
|
||||
func (b Box) IsSmallerThan(other Box) bool {
|
||||
return b.Top > other.Top &&
|
||||
b.Bottom < other.Bottom &&
|
||||
b.Left > other.Left &&
|
||||
b.Right < other.Right
|
||||
}
|
||||
|
||||
// Equals returns if the box equals another box.
|
||||
func (b Box) Equals(other Box) bool {
|
||||
return b.Top == other.Top &&
|
||||
b.Left == other.Left &&
|
||||
b.Right == other.Right &&
|
||||
b.Bottom == other.Bottom
|
||||
}
|
||||
|
||||
// Grow grows a box based on another box.
|
||||
func (b Box) Grow(other Box) Box {
|
||||
return Box{
|
||||
Top: MinInt(b.Top, other.Top),
|
||||
Left: MinInt(b.Left, other.Left),
|
||||
Right: MaxInt(b.Right, other.Right),
|
||||
Bottom: MaxInt(b.Bottom, other.Bottom),
|
||||
}
|
||||
}
|
||||
|
||||
// Shift pushes a box by x,y.
|
||||
func (b Box) Shift(x, y int) Box {
|
||||
return Box{
|
||||
Top: b.Top + y,
|
||||
Left: b.Left + x,
|
||||
Right: b.Right + x,
|
||||
Bottom: b.Bottom + y,
|
||||
}
|
||||
}
|
||||
|
||||
// Corners returns the box as a set of corners.
|
||||
func (b Box) Corners() BoxCorners {
|
||||
return BoxCorners{
|
||||
TopLeft: Point{b.Left, b.Top},
|
||||
TopRight: Point{b.Right, b.Top},
|
||||
BottomRight: Point{b.Right, b.Bottom},
|
||||
BottomLeft: Point{b.Left, b.Bottom},
|
||||
}
|
||||
}
|
||||
|
||||
// Fit is functionally the inverse of grow.
|
||||
// Fit maintains the original aspect ratio of the `other` box,
|
||||
// but constrains it to the bounds of the target box.
|
||||
func (b Box) Fit(other Box) Box {
|
||||
ba := b.Aspect()
|
||||
oa := other.Aspect()
|
||||
|
||||
if oa == ba {
|
||||
return b.Clone()
|
||||
}
|
||||
|
||||
bw, bh := float64(b.Width()), float64(b.Height())
|
||||
bw2 := int(bw) >> 1
|
||||
bh2 := int(bh) >> 1
|
||||
if oa > ba { // ex. 16:9 vs. 4:3
|
||||
var noh2 int
|
||||
if oa > 1.0 {
|
||||
noh2 = int(bw/oa) >> 1
|
||||
} else {
|
||||
noh2 = int(bh*oa) >> 1
|
||||
}
|
||||
return Box{
|
||||
Top: (b.Top + bh2) - noh2,
|
||||
Left: b.Left,
|
||||
Right: b.Right,
|
||||
Bottom: (b.Top + bh2) + noh2,
|
||||
}
|
||||
}
|
||||
var now2 int
|
||||
if oa > 1.0 {
|
||||
now2 = int(bh/oa) >> 1
|
||||
} else {
|
||||
now2 = int(bw*oa) >> 1
|
||||
}
|
||||
return Box{
|
||||
Top: b.Top,
|
||||
Left: (b.Left + bw2) - now2,
|
||||
Right: (b.Left + bw2) + now2,
|
||||
Bottom: b.Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
// Constrain is similar to `Fit` except that it will work
|
||||
// more literally like the opposite of grow.
|
||||
func (b Box) Constrain(other Box) Box {
|
||||
newBox := b.Clone()
|
||||
|
||||
newBox.Top = MaxInt(newBox.Top, other.Top)
|
||||
newBox.Left = MaxInt(newBox.Left, other.Left)
|
||||
newBox.Right = MinInt(newBox.Right, other.Right)
|
||||
newBox.Bottom = MinInt(newBox.Bottom, other.Bottom)
|
||||
|
||||
return newBox
|
||||
}
|
||||
|
||||
// OuterConstrain is similar to `Constraint` with the difference
|
||||
// that it applies corrections
|
||||
func (b Box) OuterConstrain(bounds, other Box) Box {
|
||||
newBox := b.Clone()
|
||||
if other.Top < bounds.Top {
|
||||
delta := bounds.Top - other.Top
|
||||
newBox.Top = b.Top + delta
|
||||
}
|
||||
|
||||
if other.Left < bounds.Left {
|
||||
delta := bounds.Left - other.Left
|
||||
newBox.Left = b.Left + delta
|
||||
}
|
||||
|
||||
if other.Right > bounds.Right {
|
||||
delta := other.Right - bounds.Right
|
||||
newBox.Right = b.Right - delta
|
||||
}
|
||||
|
||||
if other.Bottom > bounds.Bottom {
|
||||
delta := other.Bottom - bounds.Bottom
|
||||
newBox.Bottom = b.Bottom - delta
|
||||
}
|
||||
return newBox
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
577
src/server/vendor/github.com/wcharczuk/go-chart/v2/chart.go
generated
vendored
Normal file
577
src/server/vendor/github.com/wcharczuk/go-chart/v2/chart.go
generated
vendored
Normal file
@ -0,0 +1,577 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// Chart is what we're drawing.
|
||||
type Chart struct {
|
||||
Title string
|
||||
TitleStyle Style
|
||||
|
||||
ColorPalette ColorPalette
|
||||
|
||||
Width int
|
||||
Height int
|
||||
DPI float64
|
||||
|
||||
Background Style
|
||||
Canvas Style
|
||||
|
||||
XAxis XAxis
|
||||
YAxis YAxis
|
||||
YAxisSecondary YAxis
|
||||
|
||||
Font *truetype.Font
|
||||
defaultFont *truetype.Font
|
||||
|
||||
Series []Series
|
||||
Elements []Renderable
|
||||
|
||||
Log Logger
|
||||
}
|
||||
|
||||
// GetDPI returns the dpi for the chart.
|
||||
func (c Chart) GetDPI(defaults ...float64) float64 {
|
||||
if c.DPI == 0 {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return DefaultDPI
|
||||
}
|
||||
return c.DPI
|
||||
}
|
||||
|
||||
// GetFont returns the text font.
|
||||
func (c Chart) GetFont() *truetype.Font {
|
||||
if c.Font == nil {
|
||||
return c.defaultFont
|
||||
}
|
||||
return c.Font
|
||||
}
|
||||
|
||||
// GetWidth returns the chart width or the default value.
|
||||
func (c Chart) GetWidth() int {
|
||||
if c.Width == 0 {
|
||||
return DefaultChartWidth
|
||||
}
|
||||
return c.Width
|
||||
}
|
||||
|
||||
// GetHeight returns the chart height or the default value.
|
||||
func (c Chart) GetHeight() int {
|
||||
if c.Height == 0 {
|
||||
return DefaultChartHeight
|
||||
}
|
||||
return c.Height
|
||||
}
|
||||
|
||||
// Render renders the chart with the given renderer to the given io.Writer.
|
||||
func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||
if len(c.Series) == 0 {
|
||||
return errors.New("please provide at least one series")
|
||||
}
|
||||
if err := c.checkHasVisibleSeries(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.YAxisSecondary.AxisType = YAxisSecondary
|
||||
|
||||
r, err := rp(c.GetWidth(), c.GetHeight())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Font == nil {
|
||||
defaultFont, err := GetDefaultFont()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.defaultFont = defaultFont
|
||||
}
|
||||
r.SetDPI(c.GetDPI(DefaultDPI))
|
||||
|
||||
c.drawBackground(r)
|
||||
|
||||
var xt, yt, yta []Tick
|
||||
xr, yr, yra := c.getRanges()
|
||||
canvasBox := c.getDefaultCanvasBox()
|
||||
xf, yf, yfa := c.getValueFormatters()
|
||||
|
||||
Debugf(c.Log, "chart; canvas box: %v", canvasBox)
|
||||
|
||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||
|
||||
err = c.checkRanges(xr, yr, yra)
|
||||
if err != nil {
|
||||
r.Save(w)
|
||||
return err
|
||||
}
|
||||
|
||||
if c.hasAxes() {
|
||||
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
||||
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
||||
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.
|
||||
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
|
||||
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||
}
|
||||
|
||||
if c.hasAnnotationSeries() {
|
||||
canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa)
|
||||
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
|
||||
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.drawAxes(r, canvasBox, xr, yr, yra, xt, yt, yta)
|
||||
for index, series := range c.Series {
|
||||
c.drawSeries(r, canvasBox, xr, yr, yra, series, index)
|
||||
}
|
||||
|
||||
c.drawTitle(r)
|
||||
|
||||
for _, a := range c.Elements {
|
||||
a(r, canvasBox, c.styleDefaultsElements())
|
||||
}
|
||||
|
||||
return r.Save(w)
|
||||
}
|
||||
|
||||
func (c Chart) checkHasVisibleSeries() error {
|
||||
var style Style
|
||||
for _, s := range c.Series {
|
||||
style = s.GetStyle()
|
||||
if !style.Hidden {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("chart render; must have (1) visible series")
|
||||
}
|
||||
|
||||
func (c Chart) validateSeries() error {
|
||||
var err error
|
||||
for _, s := range c.Series {
|
||||
err = s.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||
var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64
|
||||
var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64
|
||||
var minya, maxya float64 = math.MaxFloat64, -math.MaxFloat64
|
||||
|
||||
seriesMappedToSecondaryAxis := false
|
||||
|
||||
// note: a possible future optimization is to not scan the series values if
|
||||
// all axis are represented by either custom ticks or custom ranges.
|
||||
for _, s := range c.Series {
|
||||
if !s.GetStyle().Hidden {
|
||||
seriesAxis := s.GetYAxis()
|
||||
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider {
|
||||
seriesLength := bvp.Len()
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy1, vy2 := bvp.GetBoundedValues(index)
|
||||
|
||||
minx = math.Min(minx, vx)
|
||||
maxx = math.Max(maxx, vx)
|
||||
|
||||
if seriesAxis == YAxisPrimary {
|
||||
miny = math.Min(miny, vy1)
|
||||
miny = math.Min(miny, vy2)
|
||||
maxy = math.Max(maxy, vy1)
|
||||
maxy = math.Max(maxy, vy2)
|
||||
} else if seriesAxis == YAxisSecondary {
|
||||
minya = math.Min(minya, vy1)
|
||||
minya = math.Min(minya, vy2)
|
||||
maxya = math.Max(maxya, vy1)
|
||||
maxya = math.Max(maxya, vy2)
|
||||
seriesMappedToSecondaryAxis = true
|
||||
}
|
||||
}
|
||||
} else if vp, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
seriesLength := vp.Len()
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy := vp.GetValues(index)
|
||||
|
||||
minx = math.Min(minx, vx)
|
||||
maxx = math.Max(maxx, vx)
|
||||
|
||||
if seriesAxis == YAxisPrimary {
|
||||
miny = math.Min(miny, vy)
|
||||
maxy = math.Max(maxy, vy)
|
||||
} else if seriesAxis == YAxisSecondary {
|
||||
minya = math.Min(minya, vy)
|
||||
maxya = math.Max(maxya, vy)
|
||||
seriesMappedToSecondaryAxis = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.XAxis.Range == nil {
|
||||
xrange = &ContinuousRange{}
|
||||
} else {
|
||||
xrange = c.XAxis.Range
|
||||
}
|
||||
|
||||
if c.YAxis.Range == nil {
|
||||
yrange = &ContinuousRange{}
|
||||
} else {
|
||||
yrange = c.YAxis.Range
|
||||
}
|
||||
|
||||
if c.YAxisSecondary.Range == nil {
|
||||
yrangeAlt = &ContinuousRange{}
|
||||
} else {
|
||||
yrangeAlt = c.YAxisSecondary.Range
|
||||
}
|
||||
|
||||
if len(c.XAxis.Ticks) > 0 {
|
||||
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, t := range c.XAxis.Ticks {
|
||||
tickMin = math.Min(tickMin, t.Value)
|
||||
tickMax = math.Max(tickMax, t.Value)
|
||||
}
|
||||
xrange.SetMin(tickMin)
|
||||
xrange.SetMax(tickMax)
|
||||
} else if xrange.IsZero() {
|
||||
xrange.SetMin(minx)
|
||||
xrange.SetMax(maxx)
|
||||
}
|
||||
|
||||
if len(c.YAxis.Ticks) > 0 {
|
||||
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, t := range c.YAxis.Ticks {
|
||||
tickMin = math.Min(tickMin, t.Value)
|
||||
tickMax = math.Max(tickMax, t.Value)
|
||||
}
|
||||
yrange.SetMin(tickMin)
|
||||
yrange.SetMax(tickMax)
|
||||
} else if yrange.IsZero() {
|
||||
yrange.SetMin(miny)
|
||||
yrange.SetMax(maxy)
|
||||
|
||||
if !c.YAxis.Style.Hidden {
|
||||
delta := yrange.GetDelta()
|
||||
roundTo := GetRoundToForDelta(delta)
|
||||
rmin, rmax := RoundDown(yrange.GetMin(), roundTo), RoundUp(yrange.GetMax(), roundTo)
|
||||
|
||||
yrange.SetMin(rmin)
|
||||
yrange.SetMax(rmax)
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.YAxisSecondary.Ticks) > 0 {
|
||||
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, t := range c.YAxis.Ticks {
|
||||
tickMin = math.Min(tickMin, t.Value)
|
||||
tickMax = math.Max(tickMax, t.Value)
|
||||
}
|
||||
yrangeAlt.SetMin(tickMin)
|
||||
yrangeAlt.SetMax(tickMax)
|
||||
} else if seriesMappedToSecondaryAxis && yrangeAlt.IsZero() {
|
||||
yrangeAlt.SetMin(minya)
|
||||
yrangeAlt.SetMax(maxya)
|
||||
|
||||
if !c.YAxisSecondary.Style.Hidden {
|
||||
delta := yrangeAlt.GetDelta()
|
||||
roundTo := GetRoundToForDelta(delta)
|
||||
rmin, rmax := RoundDown(yrangeAlt.GetMin(), roundTo), RoundUp(yrangeAlt.GetMax(), roundTo)
|
||||
yrangeAlt.SetMin(rmin)
|
||||
yrangeAlt.SetMax(rmax)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Chart) checkRanges(xr, yr, yra Range) error {
|
||||
Debugf(c.Log, "checking xrange: %v", xr)
|
||||
xDelta := xr.GetDelta()
|
||||
if math.IsInf(xDelta, 0) {
|
||||
return errors.New("infinite x-range delta")
|
||||
}
|
||||
if math.IsNaN(xDelta) {
|
||||
return errors.New("nan x-range delta")
|
||||
}
|
||||
if xDelta == 0 {
|
||||
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()
|
||||
if math.IsInf(yDelta, 0) {
|
||||
return errors.New("infinite y-range delta")
|
||||
}
|
||||
if math.IsNaN(yDelta) {
|
||||
return errors.New("nan y-range delta")
|
||||
}
|
||||
|
||||
if c.hasSecondarySeries() {
|
||||
Debugf(c.Log, "checking secondary yrange: %v", yra)
|
||||
yraDelta := yra.GetDelta()
|
||||
if math.IsInf(yraDelta, 0) {
|
||||
return errors.New("infinite secondary y-range delta")
|
||||
}
|
||||
if math.IsNaN(yraDelta) {
|
||||
return errors.New("nan secondary y-range delta")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Chart) getDefaultCanvasBox() Box {
|
||||
return c.Box()
|
||||
}
|
||||
|
||||
func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) {
|
||||
for _, s := range c.Series {
|
||||
if vfp, isVfp := s.(ValueFormatterProvider); isVfp {
|
||||
sx, sy := vfp.GetValueFormatters()
|
||||
if s.GetYAxis() == YAxisPrimary {
|
||||
x = sx
|
||||
y = sy
|
||||
} else if s.GetYAxis() == YAxisSecondary {
|
||||
x = sx
|
||||
ya = sy
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.XAxis.ValueFormatter != nil {
|
||||
x = c.XAxis.GetValueFormatter()
|
||||
}
|
||||
if c.YAxis.ValueFormatter != nil {
|
||||
y = c.YAxis.GetValueFormatter()
|
||||
}
|
||||
if c.YAxisSecondary.ValueFormatter != nil {
|
||||
ya = c.YAxisSecondary.GetValueFormatter()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Chart) hasAxes() bool {
|
||||
return !c.XAxis.Style.Hidden || !c.YAxis.Style.Hidden || !c.YAxisSecondary.Style.Hidden
|
||||
}
|
||||
|
||||
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
||||
if !c.XAxis.Style.Hidden {
|
||||
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
||||
}
|
||||
if !c.YAxis.Style.Hidden {
|
||||
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
||||
}
|
||||
if !c.YAxisSecondary.Style.Hidden {
|
||||
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
||||
axesOuterBox := canvasBox.Clone()
|
||||
if !c.XAxis.Style.Hidden {
|
||||
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
||||
Debugf(c.Log, "chart; x-axis measured %v", axesBounds)
|
||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||
}
|
||||
if !c.YAxis.Style.Hidden {
|
||||
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
||||
Debugf(c.Log, "chart; y-axis measured %v", axesBounds)
|
||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||
}
|
||||
if !c.YAxisSecondary.Style.Hidden && c.hasSecondarySeries() {
|
||||
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
||||
Debugf(c.Log, "chart; y-axis secondary measured %v", axesBounds)
|
||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||
}
|
||||
|
||||
return canvasBox.OuterConstrain(c.Box(), axesOuterBox)
|
||||
}
|
||||
|
||||
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range, Range) {
|
||||
xr.SetDomain(canvasBox.Width())
|
||||
yr.SetDomain(canvasBox.Height())
|
||||
yra.SetDomain(canvasBox.Height())
|
||||
return xr, yr, yra
|
||||
}
|
||||
|
||||
func (c Chart) hasAnnotationSeries() bool {
|
||||
for _, s := range c.Series {
|
||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||
if !as.GetStyle().Hidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Chart) hasSecondarySeries() bool {
|
||||
for _, s := range c.Series {
|
||||
if s.GetYAxis() == YAxisSecondary {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box {
|
||||
annotationSeriesBox := canvasBox.Clone()
|
||||
for seriesIndex, s := range c.Series {
|
||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||
if !as.GetStyle().Hidden {
|
||||
style := c.styleDefaultsSeries(seriesIndex)
|
||||
var annotationBounds Box
|
||||
if as.YAxis == YAxisPrimary {
|
||||
annotationBounds = as.Measure(r, canvasBox, xr, yr, style)
|
||||
} else if as.YAxis == YAxisSecondary {
|
||||
annotationBounds = as.Measure(r, canvasBox, xr, yra, style)
|
||||
}
|
||||
|
||||
annotationSeriesBox = annotationSeriesBox.Grow(annotationBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canvasBox.OuterConstrain(c.Box(), annotationSeriesBox)
|
||||
}
|
||||
|
||||
func (c Chart) getBackgroundStyle() Style {
|
||||
return c.Background.InheritFrom(c.styleDefaultsBackground())
|
||||
}
|
||||
|
||||
func (c Chart) drawBackground(r Renderer) {
|
||||
Draw.Box(r, Box{
|
||||
Right: c.GetWidth(),
|
||||
Bottom: c.GetHeight(),
|
||||
}, c.getBackgroundStyle())
|
||||
}
|
||||
|
||||
func (c Chart) getCanvasStyle() Style {
|
||||
return c.Canvas.InheritFrom(c.styleDefaultsCanvas())
|
||||
}
|
||||
|
||||
func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
||||
Draw.Box(r, canvasBox, c.getCanvasStyle())
|
||||
}
|
||||
|
||||
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
||||
if !c.XAxis.Style.Hidden {
|
||||
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
||||
}
|
||||
if !c.YAxis.Style.Hidden {
|
||||
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
||||
}
|
||||
if !c.YAxisSecondary.Style.Hidden {
|
||||
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) {
|
||||
if !s.GetStyle().Hidden {
|
||||
if s.GetYAxis() == YAxisPrimary {
|
||||
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
|
||||
} else if s.GetYAxis() == YAxisSecondary {
|
||||
s.Render(r, canvasBox, xrange, yrangeAlt, c.styleDefaultsSeries(seriesIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) drawTitle(r Renderer) {
|
||||
if len(c.Title) > 0 && !c.TitleStyle.Hidden {
|
||||
r.SetFont(c.TitleStyle.GetFont(c.GetFont()))
|
||||
r.SetFontColor(c.TitleStyle.GetFontColor(c.GetColorPalette().TextColor()))
|
||||
titleFontSize := c.TitleStyle.GetFontSize(DefaultTitleFontSize)
|
||||
r.SetFontSize(titleFontSize)
|
||||
|
||||
textBox := r.MeasureText(c.Title)
|
||||
|
||||
textWidth := textBox.Width()
|
||||
textHeight := textBox.Height()
|
||||
|
||||
titleX := (c.GetWidth() >> 1) - (textWidth >> 1)
|
||||
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
||||
|
||||
r.Text(c.Title, titleX, titleY)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) styleDefaultsBackground() Style {
|
||||
return Style{
|
||||
FillColor: c.GetColorPalette().BackgroundColor(),
|
||||
StrokeColor: c.GetColorPalette().BackgroundStrokeColor(),
|
||||
StrokeWidth: DefaultBackgroundStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) styleDefaultsCanvas() Style {
|
||||
return Style{
|
||||
FillColor: c.GetColorPalette().CanvasColor(),
|
||||
StrokeColor: c.GetColorPalette().CanvasStrokeColor(),
|
||||
StrokeWidth: DefaultCanvasStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) styleDefaultsSeries(seriesIndex int) Style {
|
||||
return Style{
|
||||
DotColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
|
||||
StrokeColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
|
||||
StrokeWidth: DefaultSeriesLineWidth,
|
||||
Font: c.GetFont(),
|
||||
FontSize: DefaultFontSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) styleDefaultsAxes() Style {
|
||||
return Style{
|
||||
Font: c.GetFont(),
|
||||
FontColor: c.GetColorPalette().TextColor(),
|
||||
FontSize: DefaultAxisFontSize,
|
||||
StrokeColor: c.GetColorPalette().AxisStrokeColor(),
|
||||
StrokeWidth: DefaultAxisLineWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) styleDefaultsElements() Style {
|
||||
return Style{
|
||||
Font: c.GetFont(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetColorPalette returns the color palette for the chart.
|
||||
func (c Chart) GetColorPalette() ColorPalette {
|
||||
if c.ColorPalette != nil {
|
||||
return c.ColorPalette
|
||||
}
|
||||
return DefaultColorPalette
|
||||
}
|
||||
|
||||
// Box returns the chart bounds as a box.
|
||||
func (c Chart) Box() Box {
|
||||
dpr := c.Background.Padding.GetRight(DefaultBackgroundPadding.Right)
|
||||
dpb := c.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom)
|
||||
|
||||
return Box{
|
||||
Top: c.Background.Padding.GetTop(DefaultBackgroundPadding.Top),
|
||||
Left: c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left),
|
||||
Right: c.GetWidth() - dpr,
|
||||
Bottom: c.GetHeight() - dpb,
|
||||
}
|
||||
}
|
||||
184
src/server/vendor/github.com/wcharczuk/go-chart/v2/colors.go
generated
vendored
Normal file
184
src/server/vendor/github.com/wcharczuk/go-chart/v2/colors.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
package chart
|
||||
|
||||
import "github.com/wcharczuk/go-chart/v2/drawing"
|
||||
|
||||
var (
|
||||
// ColorWhite is white.
|
||||
ColorWhite = drawing.Color{R: 255, G: 255, B: 255, A: 255}
|
||||
// ColorBlue is the basic theme blue color.
|
||||
ColorBlue = drawing.Color{R: 0, G: 116, B: 217, A: 255}
|
||||
// ColorCyan is the basic theme cyan color.
|
||||
ColorCyan = drawing.Color{R: 0, G: 217, B: 210, A: 255}
|
||||
// ColorGreen is the basic theme green color.
|
||||
ColorGreen = drawing.Color{R: 0, G: 217, B: 101, A: 255}
|
||||
// ColorRed is the basic theme red color.
|
||||
ColorRed = drawing.Color{R: 217, G: 0, B: 116, A: 255}
|
||||
// ColorOrange is the basic theme orange color.
|
||||
ColorOrange = drawing.Color{R: 217, G: 101, B: 0, A: 255}
|
||||
// ColorYellow is the basic theme yellow color.
|
||||
ColorYellow = drawing.Color{R: 217, G: 210, B: 0, A: 255}
|
||||
// ColorBlack is the basic theme black color.
|
||||
ColorBlack = drawing.Color{R: 51, G: 51, B: 51, A: 255}
|
||||
// ColorLightGray is the basic theme light gray color.
|
||||
ColorLightGray = drawing.Color{R: 239, G: 239, B: 239, A: 255}
|
||||
|
||||
// ColorAlternateBlue is a alternate theme color.
|
||||
ColorAlternateBlue = drawing.Color{R: 106, G: 195, B: 203, A: 255}
|
||||
// ColorAlternateGreen is a alternate theme color.
|
||||
ColorAlternateGreen = drawing.Color{R: 42, G: 190, B: 137, A: 255}
|
||||
// ColorAlternateGray is a alternate theme color.
|
||||
ColorAlternateGray = drawing.Color{R: 110, G: 128, B: 139, A: 255}
|
||||
// ColorAlternateYellow is a alternate theme color.
|
||||
ColorAlternateYellow = drawing.Color{R: 240, G: 174, B: 90, A: 255}
|
||||
// ColorAlternateLightGray is a alternate theme color.
|
||||
ColorAlternateLightGray = drawing.Color{R: 187, G: 190, B: 191, A: 255}
|
||||
|
||||
// ColorTransparent is a transparent (alpha zero) color.
|
||||
ColorTransparent = drawing.Color{R: 1, G: 1, B: 1, A: 0}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBackgroundColor is the default chart background color.
|
||||
// It is equivalent to css color:white.
|
||||
DefaultBackgroundColor = ColorWhite
|
||||
// DefaultBackgroundStrokeColor is the default chart border color.
|
||||
// It is equivalent to color:white.
|
||||
DefaultBackgroundStrokeColor = ColorWhite
|
||||
// DefaultCanvasColor is the default chart canvas color.
|
||||
// It is equivalent to css color:white.
|
||||
DefaultCanvasColor = ColorWhite
|
||||
// DefaultCanvasStrokeColor is the default chart canvas stroke color.
|
||||
// It is equivalent to css color:white.
|
||||
DefaultCanvasStrokeColor = ColorWhite
|
||||
// DefaultTextColor is the default chart text color.
|
||||
// It is equivalent to #333333.
|
||||
DefaultTextColor = ColorBlack
|
||||
// DefaultAxisColor is the default chart axis line color.
|
||||
// It is equivalent to #333333.
|
||||
DefaultAxisColor = ColorBlack
|
||||
// DefaultStrokeColor is the default chart border color.
|
||||
// It is equivalent to #efefef.
|
||||
DefaultStrokeColor = ColorLightGray
|
||||
// DefaultFillColor is the default fill color.
|
||||
// It is equivalent to #0074d9.
|
||||
DefaultFillColor = ColorBlue
|
||||
// DefaultAnnotationFillColor is the default annotation background color.
|
||||
DefaultAnnotationFillColor = ColorWhite
|
||||
// DefaultGridLineColor is the default grid line color.
|
||||
DefaultGridLineColor = ColorLightGray
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultColors are a couple default series colors.
|
||||
DefaultColors = []drawing.Color{
|
||||
ColorBlue,
|
||||
ColorGreen,
|
||||
ColorRed,
|
||||
ColorCyan,
|
||||
ColorOrange,
|
||||
}
|
||||
|
||||
// DefaultAlternateColors are a couple alternate colors.
|
||||
DefaultAlternateColors = []drawing.Color{
|
||||
ColorAlternateBlue,
|
||||
ColorAlternateGreen,
|
||||
ColorAlternateGray,
|
||||
ColorAlternateYellow,
|
||||
ColorBlue,
|
||||
ColorGreen,
|
||||
ColorRed,
|
||||
ColorCyan,
|
||||
ColorOrange,
|
||||
}
|
||||
)
|
||||
|
||||
// GetDefaultColor returns a color from the default list by index.
|
||||
// NOTE: the index will wrap around (using a modulo).
|
||||
func GetDefaultColor(index int) drawing.Color {
|
||||
finalIndex := index % len(DefaultColors)
|
||||
return DefaultColors[finalIndex]
|
||||
}
|
||||
|
||||
// GetAlternateColor returns a color from the default list by index.
|
||||
// NOTE: the index will wrap around (using a modulo).
|
||||
func GetAlternateColor(index int) drawing.Color {
|
||||
finalIndex := index % len(DefaultAlternateColors)
|
||||
return DefaultAlternateColors[finalIndex]
|
||||
}
|
||||
|
||||
// ColorPalette is a set of colors that.
|
||||
type ColorPalette interface {
|
||||
BackgroundColor() drawing.Color
|
||||
BackgroundStrokeColor() drawing.Color
|
||||
CanvasColor() drawing.Color
|
||||
CanvasStrokeColor() drawing.Color
|
||||
AxisStrokeColor() drawing.Color
|
||||
TextColor() drawing.Color
|
||||
GetSeriesColor(index int) drawing.Color
|
||||
}
|
||||
|
||||
// DefaultColorPalette represents the default palatte.
|
||||
var DefaultColorPalette defaultColorPalette
|
||||
|
||||
type defaultColorPalette struct{}
|
||||
|
||||
func (dp defaultColorPalette) BackgroundColor() drawing.Color {
|
||||
return DefaultBackgroundColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) BackgroundStrokeColor() drawing.Color {
|
||||
return DefaultBackgroundStrokeColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) CanvasColor() drawing.Color {
|
||||
return DefaultCanvasColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) CanvasStrokeColor() drawing.Color {
|
||||
return DefaultCanvasStrokeColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) AxisStrokeColor() drawing.Color {
|
||||
return DefaultAxisColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) TextColor() drawing.Color {
|
||||
return DefaultTextColor
|
||||
}
|
||||
|
||||
func (dp defaultColorPalette) GetSeriesColor(index int) drawing.Color {
|
||||
return GetDefaultColor(index)
|
||||
}
|
||||
|
||||
// AlternateColorPalette represents the default palatte.
|
||||
var AlternateColorPalette alternateColorPalette
|
||||
|
||||
type alternateColorPalette struct{}
|
||||
|
||||
func (ap alternateColorPalette) BackgroundColor() drawing.Color {
|
||||
return DefaultBackgroundColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) BackgroundStrokeColor() drawing.Color {
|
||||
return DefaultBackgroundStrokeColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) CanvasColor() drawing.Color {
|
||||
return DefaultCanvasColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) CanvasStrokeColor() drawing.Color {
|
||||
return DefaultCanvasStrokeColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) AxisStrokeColor() drawing.Color {
|
||||
return DefaultAxisColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) TextColor() drawing.Color {
|
||||
return DefaultTextColor
|
||||
}
|
||||
|
||||
func (ap alternateColorPalette) GetSeriesColor(index int) drawing.Color {
|
||||
return GetAlternateColor(index)
|
||||
}
|
||||
44
src/server/vendor/github.com/wcharczuk/go-chart/v2/concat_series.go
generated
vendored
Normal file
44
src/server/vendor/github.com/wcharczuk/go-chart/v2/concat_series.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package chart
|
||||
|
||||
// ConcatSeries is a special type of series that concatenates its `InnerSeries`.
|
||||
type ConcatSeries []Series
|
||||
|
||||
// Len returns the length of the concatenated set of series.
|
||||
func (cs ConcatSeries) Len() int {
|
||||
total := 0
|
||||
for _, s := range cs {
|
||||
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
total += typed.Len()
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
// GetValue returns the value at the (meta) index (i.e 0 => totalLen-1)
|
||||
func (cs ConcatSeries) GetValue(index int) (x, y float64) {
|
||||
cursor := 0
|
||||
for _, s := range cs {
|
||||
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider {
|
||||
len := typed.Len()
|
||||
if index < cursor+len {
|
||||
x, y = typed.GetValues(index - cursor) //FENCEPOSTS.
|
||||
return
|
||||
}
|
||||
cursor += typed.Len()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate validates the series.
|
||||
func (cs ConcatSeries) Validate() error {
|
||||
var err error
|
||||
for _, s := range cs {
|
||||
err = s.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
81
src/server/vendor/github.com/wcharczuk/go-chart/v2/continuous_range.go
generated
vendored
Normal file
81
src/server/vendor/github.com/wcharczuk/go-chart/v2/continuous_range.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// ContinuousRange represents a boundary for a set of numbers.
|
||||
type ContinuousRange struct {
|
||||
Min float64
|
||||
Max float64
|
||||
Domain int
|
||||
Descending bool
|
||||
}
|
||||
|
||||
// IsDescending returns if the range is descending.
|
||||
func (r ContinuousRange) IsDescending() bool {
|
||||
return r.Descending
|
||||
}
|
||||
|
||||
// IsZero returns if the ContinuousRange has been set or not.
|
||||
func (r ContinuousRange) IsZero() bool {
|
||||
return (r.Min == 0 || math.IsNaN(r.Min)) &&
|
||||
(r.Max == 0 || math.IsNaN(r.Max)) &&
|
||||
r.Domain == 0
|
||||
}
|
||||
|
||||
// GetMin gets the min value for the continuous range.
|
||||
func (r ContinuousRange) GetMin() float64 {
|
||||
return r.Min
|
||||
}
|
||||
|
||||
// SetMin sets the min value for the continuous range.
|
||||
func (r *ContinuousRange) SetMin(min float64) {
|
||||
r.Min = min
|
||||
}
|
||||
|
||||
// GetMax returns the max value for the continuous range.
|
||||
func (r ContinuousRange) GetMax() float64 {
|
||||
return r.Max
|
||||
}
|
||||
|
||||
// SetMax sets the max value for the continuous range.
|
||||
func (r *ContinuousRange) SetMax(max float64) {
|
||||
r.Max = max
|
||||
}
|
||||
|
||||
// GetDelta returns the difference between the min and max value.
|
||||
func (r ContinuousRange) GetDelta() float64 {
|
||||
return r.Max - r.Min
|
||||
}
|
||||
|
||||
// GetDomain returns the range domain.
|
||||
func (r ContinuousRange) GetDomain() int {
|
||||
return r.Domain
|
||||
}
|
||||
|
||||
// SetDomain sets the range domain.
|
||||
func (r *ContinuousRange) SetDomain(domain int) {
|
||||
r.Domain = domain
|
||||
}
|
||||
|
||||
// String returns a simple string for the ContinuousRange.
|
||||
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)
|
||||
}
|
||||
|
||||
// Translate maps a given value into the ContinuousRange space.
|
||||
func (r ContinuousRange) Translate(value float64) int {
|
||||
normalized := value - r.Min
|
||||
ratio := normalized / r.GetDelta()
|
||||
|
||||
if r.IsDescending() {
|
||||
return r.Domain - int(math.Ceil(ratio*float64(r.Domain)))
|
||||
}
|
||||
|
||||
return int(math.Ceil(ratio * float64(r.Domain)))
|
||||
}
|
||||
96
src/server/vendor/github.com/wcharczuk/go-chart/v2/continuous_series.go
generated
vendored
Normal file
96
src/server/vendor/github.com/wcharczuk/go-chart/v2/continuous_series.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
package chart
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Interface Assertions.
|
||||
var (
|
||||
_ Series = (*ContinuousSeries)(nil)
|
||||
_ FirstValuesProvider = (*ContinuousSeries)(nil)
|
||||
_ LastValuesProvider = (*ContinuousSeries)(nil)
|
||||
)
|
||||
|
||||
// ContinuousSeries represents a line on a chart.
|
||||
type ContinuousSeries struct {
|
||||
Name string
|
||||
Style Style
|
||||
|
||||
YAxis YAxisType
|
||||
|
||||
XValueFormatter ValueFormatter
|
||||
YValueFormatter ValueFormatter
|
||||
|
||||
XValues []float64
|
||||
YValues []float64
|
||||
}
|
||||
|
||||
// GetName returns the name of the time series.
|
||||
func (cs ContinuousSeries) GetName() string {
|
||||
return cs.Name
|
||||
}
|
||||
|
||||
// GetStyle returns the line style.
|
||||
func (cs ContinuousSeries) GetStyle() Style {
|
||||
return cs.Style
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the series.
|
||||
func (cs ContinuousSeries) Len() int {
|
||||
return len(cs.XValues)
|
||||
}
|
||||
|
||||
// GetValues gets the x,y values at a given index.
|
||||
func (cs ContinuousSeries) GetValues(index int) (float64, float64) {
|
||||
return cs.XValues[index], cs.YValues[index]
|
||||
}
|
||||
|
||||
// GetFirstValues gets the first x,y values.
|
||||
func (cs ContinuousSeries) GetFirstValues() (float64, float64) {
|
||||
return cs.XValues[0], cs.YValues[0]
|
||||
}
|
||||
|
||||
// GetLastValues gets the last x,y values.
|
||||
func (cs ContinuousSeries) GetLastValues() (float64, float64) {
|
||||
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
|
||||
}
|
||||
|
||||
// GetValueFormatters returns value formatter defaults for the series.
|
||||
func (cs ContinuousSeries) GetValueFormatters() (x, y ValueFormatter) {
|
||||
if cs.XValueFormatter != nil {
|
||||
x = cs.XValueFormatter
|
||||
} else {
|
||||
x = FloatValueFormatter
|
||||
}
|
||||
if cs.YValueFormatter != nil {
|
||||
y = cs.YValueFormatter
|
||||
} else {
|
||||
y = FloatValueFormatter
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetYAxis returns which YAxis the series draws on.
|
||||
func (cs ContinuousSeries) GetYAxis() YAxisType {
|
||||
return cs.YAxis
|
||||
}
|
||||
|
||||
// Render renders the series.
|
||||
func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||
style := cs.Style.InheritFrom(defaults)
|
||||
Draw.LineSeries(r, canvasBox, xrange, yrange, style, cs)
|
||||
}
|
||||
|
||||
// Validate validates the series.
|
||||
func (cs ContinuousSeries) Validate() error {
|
||||
if len(cs.XValues) == 0 {
|
||||
return fmt.Errorf("continuous series; must have xvalues set")
|
||||
}
|
||||
|
||||
if len(cs.YValues) == 0 {
|
||||
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
|
||||
}
|
||||
103
src/server/vendor/github.com/wcharczuk/go-chart/v2/defaults.go
generated
vendored
Normal file
103
src/server/vendor/github.com/wcharczuk/go-chart/v2/defaults.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package chart
|
||||
|
||||
const (
|
||||
// DefaultChartHeight is the default chart height.
|
||||
DefaultChartHeight = 400
|
||||
// DefaultChartWidth is the default chart width.
|
||||
DefaultChartWidth = 1024
|
||||
// DefaultStrokeWidth is the default chart stroke width.
|
||||
DefaultStrokeWidth = 0.0
|
||||
// DefaultDotWidth is the default chart dot width.
|
||||
DefaultDotWidth = 0.0
|
||||
// DefaultSeriesLineWidth is the default line width.
|
||||
DefaultSeriesLineWidth = 1.0
|
||||
// DefaultAxisLineWidth is the line width of the axis lines.
|
||||
DefaultAxisLineWidth = 1.0
|
||||
//DefaultDPI is the default dots per inch for the chart.
|
||||
DefaultDPI = 92.0
|
||||
// DefaultMinimumFontSize is the default minimum font size.
|
||||
DefaultMinimumFontSize = 8.0
|
||||
// DefaultFontSize is the default font size.
|
||||
DefaultFontSize = 10.0
|
||||
// DefaultTitleFontSize is the default title font size.
|
||||
DefaultTitleFontSize = 18.0
|
||||
// DefaultAnnotationDeltaWidth is the width of the left triangle out of annotations.
|
||||
DefaultAnnotationDeltaWidth = 10
|
||||
// DefaultAnnotationFontSize is the font size of annotations.
|
||||
DefaultAnnotationFontSize = 10.0
|
||||
// DefaultAxisFontSize is the font size of the axis labels.
|
||||
DefaultAxisFontSize = 10.0
|
||||
// DefaultTitleTop is the default distance from the top of the chart to put the title.
|
||||
DefaultTitleTop = 10
|
||||
|
||||
// DefaultBackgroundStrokeWidth is the default stroke on the chart background.
|
||||
DefaultBackgroundStrokeWidth = 0.0
|
||||
// DefaultCanvasStrokeWidth is the default stroke on the chart canvas.
|
||||
DefaultCanvasStrokeWidth = 0.0
|
||||
|
||||
// DefaultLineSpacing is the default vertical distance between lines of text.
|
||||
DefaultLineSpacing = 5
|
||||
|
||||
// DefaultYAxisMargin is the default distance from the right of the canvas to the y axis labels.
|
||||
DefaultYAxisMargin = 10
|
||||
// DefaultXAxisMargin is the default distance from bottom of the canvas to the x axis labels.
|
||||
DefaultXAxisMargin = 10
|
||||
|
||||
//DefaultVerticalTickHeight is half the margin.
|
||||
DefaultVerticalTickHeight = DefaultXAxisMargin >> 1
|
||||
//DefaultHorizontalTickWidth is half the margin.
|
||||
DefaultHorizontalTickWidth = DefaultYAxisMargin >> 1
|
||||
|
||||
// DefaultTickCount is the default number of ticks to show
|
||||
DefaultTickCount = 10
|
||||
// DefaultTickCountSanityCheck is a hard limit on number of ticks to prevent infinite loops.
|
||||
DefaultTickCountSanityCheck = 1 << 10 //1024
|
||||
|
||||
// DefaultMinimumTickHorizontalSpacing is the minimum distance between horizontal ticks.
|
||||
DefaultMinimumTickHorizontalSpacing = 20
|
||||
// DefaultMinimumTickVerticalSpacing is the minimum distance between vertical ticks.
|
||||
DefaultMinimumTickVerticalSpacing = 20
|
||||
|
||||
// DefaultDateFormat is the default date format.
|
||||
DefaultDateFormat = "2006-01-02"
|
||||
// DefaultDateHourFormat is the date format for hour timestamp formats.
|
||||
DefaultDateHourFormat = "01-02 3PM"
|
||||
// DefaultDateMinuteFormat is the date format for minute range timestamp formats.
|
||||
DefaultDateMinuteFormat = "01-02 3:04PM"
|
||||
// DefaultFloatFormat is the default float format.
|
||||
DefaultFloatFormat = "%.2f"
|
||||
// DefaultPercentValueFormat is the default percent format.
|
||||
DefaultPercentValueFormat = "%0.2f%%"
|
||||
|
||||
// DefaultBarSpacing is the default pixel spacing between bars.
|
||||
DefaultBarSpacing = 100
|
||||
// DefaultBarWidth is the default pixel width of bars in a bar chart.
|
||||
DefaultBarWidth = 50
|
||||
)
|
||||
|
||||
var (
|
||||
// DashArrayDots is a dash array that represents '....' style stroke dashes.
|
||||
DashArrayDots = []int{1, 1}
|
||||
// DashArrayDashesSmall is a dash array that represents '- - -' style stroke dashes.
|
||||
DashArrayDashesSmall = []int{3, 3}
|
||||
// DashArrayDashesMedium is a dash array that represents '-- -- --' style stroke dashes.
|
||||
DashArrayDashesMedium = []int{5, 5}
|
||||
// DashArrayDashesLarge is a dash array that represents '----- ----- -----' style stroke dashes.
|
||||
DashArrayDashesLarge = []int{10, 10}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultAnnotationPadding is the padding around an annotation.
|
||||
DefaultAnnotationPadding = Box{Top: 5, Left: 5, Right: 5, Bottom: 5}
|
||||
|
||||
// DefaultBackgroundPadding is the default canvas padding config.
|
||||
DefaultBackgroundPadding = Box{Top: 5, Left: 5, Right: 5, Bottom: 5}
|
||||
)
|
||||
|
||||
const (
|
||||
// ContentTypePNG is the png mime type.
|
||||
ContentTypePNG = "image/png"
|
||||
|
||||
// ContentTypeSVG is the svg mime type.
|
||||
ContentTypeSVG = "image/svg+xml"
|
||||
)
|
||||
315
src/server/vendor/github.com/wcharczuk/go-chart/v2/donut_chart.go
generated
vendored
Normal file
315
src/server/vendor/github.com/wcharczuk/go-chart/v2/donut_chart.go
generated
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
325
src/server/vendor/github.com/wcharczuk/go-chart/v2/draw.go
generated
vendored
Normal file
325
src/server/vendor/github.com/wcharczuk/go-chart/v2/draw.go
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
package chart
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
// Draw contains helpers for drawing common objects.
|
||||
Draw = &draw{}
|
||||
)
|
||||
|
||||
type draw struct{}
|
||||
|
||||
// LineSeries draws a line series with a renderer.
|
||||
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider) {
|
||||
if vs.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cb := canvasBox.Bottom
|
||||
cl := canvasBox.Left
|
||||
|
||||
v0x, v0y := vs.GetValues(0)
|
||||
x0 := cl + xrange.Translate(v0x)
|
||||
y0 := cb - yrange.Translate(v0y)
|
||||
|
||||
yv0 := yrange.Translate(0)
|
||||
|
||||
var vx, vy float64
|
||||
var x, y int
|
||||
|
||||
if style.ShouldDrawStroke() && style.ShouldDrawFill() {
|
||||
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
r.LineTo(x, y)
|
||||
}
|
||||
r.LineTo(x, MinInt(cb, cb-yv0))
|
||||
r.LineTo(x0, MinInt(cb, cb-yv0))
|
||||
r.LineTo(x0, y0)
|
||||
r.Fill()
|
||||
}
|
||||
|
||||
if style.ShouldDrawStroke() {
|
||||
style.GetStrokeOptions().WriteDrawingOptionsToRenderer(r)
|
||||
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
r.LineTo(x, y)
|
||||
}
|
||||
r.Stroke()
|
||||
}
|
||||
|
||||
if style.ShouldDrawDot() {
|
||||
defaultDotWidth := style.GetDotWidth()
|
||||
|
||||
style.GetDotOptions().WriteDrawingOptionsToRenderer(r)
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
vx, vy = vs.GetValues(i)
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy)
|
||||
|
||||
dotWidth := defaultDotWidth
|
||||
if style.DotWidthProvider != nil {
|
||||
dotWidth = style.DotWidthProvider(xrange, yrange, i, vx, vy)
|
||||
}
|
||||
|
||||
if style.DotColorProvider != nil {
|
||||
dotColor := style.DotColorProvider(xrange, yrange, i, vx, vy)
|
||||
|
||||
r.SetFillColor(dotColor)
|
||||
r.SetStrokeColor(dotColor)
|
||||
}
|
||||
|
||||
r.Circle(dotWidth, x, y)
|
||||
r.FillStroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BoundedSeries draws a series that implements BoundedValuesProvider.
|
||||
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValuesProvider, drawOffsetIndexes ...int) {
|
||||
drawOffsetIndex := 0
|
||||
if len(drawOffsetIndexes) > 0 {
|
||||
drawOffsetIndex = drawOffsetIndexes[0]
|
||||
}
|
||||
|
||||
cb := canvasBox.Bottom
|
||||
cl := canvasBox.Left
|
||||
|
||||
v0x, v0y1, v0y2 := bbs.GetBoundedValues(0)
|
||||
x0 := cl + xrange.Translate(v0x)
|
||||
y0 := cb - yrange.Translate(v0y1)
|
||||
|
||||
var vx, vy1, vy2 float64
|
||||
var x, y int
|
||||
|
||||
xvalues := make([]float64, bbs.Len())
|
||||
xvalues[0] = v0x
|
||||
y2values := make([]float64, bbs.Len())
|
||||
y2values[0] = v0y2
|
||||
|
||||
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
r.MoveTo(x0, y0)
|
||||
for i := 1; i < bbs.Len(); i++ {
|
||||
vx, vy1, vy2 = bbs.GetBoundedValues(i)
|
||||
|
||||
xvalues[i] = vx
|
||||
y2values[i] = vy2
|
||||
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy1)
|
||||
if i > drawOffsetIndex {
|
||||
r.LineTo(x, y)
|
||||
} else {
|
||||
r.MoveTo(x, y)
|
||||
}
|
||||
}
|
||||
y = cb - yrange.Translate(vy2)
|
||||
r.LineTo(x, y)
|
||||
for i := bbs.Len() - 1; i >= drawOffsetIndex; i-- {
|
||||
vx, vy2 = xvalues[i], y2values[i]
|
||||
x = cl + xrange.Translate(vx)
|
||||
y = cb - yrange.Translate(vy2)
|
||||
r.LineTo(x, y)
|
||||
}
|
||||
r.Close()
|
||||
r.FillStroke()
|
||||
}
|
||||
|
||||
// HistogramSeries draws a value provider as boxes from 0.
|
||||
func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValuesProvider, barWidths ...int) {
|
||||
if vs.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
//calculate bar width?
|
||||
seriesLength := vs.Len()
|
||||
barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength)))
|
||||
if len(barWidths) > 0 {
|
||||
barWidth = barWidths[0]
|
||||
}
|
||||
|
||||
cb := canvasBox.Bottom
|
||||
cl := canvasBox.Left
|
||||
|
||||
//foreach datapoint, draw a box.
|
||||
for index := 0; index < seriesLength; index++ {
|
||||
vx, vy := vs.GetValues(index)
|
||||
y0 := yrange.Translate(0)
|
||||
x := cl + xrange.Translate(vx)
|
||||
y := yrange.Translate(vy)
|
||||
|
||||
d.Box(r, Box{
|
||||
Top: cb - y0,
|
||||
Left: x - (barWidth >> 1),
|
||||
Right: x + (barWidth >> 1),
|
||||
Bottom: cb - y,
|
||||
}, style)
|
||||
}
|
||||
}
|
||||
|
||||
// MeasureAnnotation measures how big an annotation would be.
|
||||
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
|
||||
style.WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
textBox := r.MeasureText(label)
|
||||
textWidth := textBox.Width()
|
||||
textHeight := textBox.Height()
|
||||
halfTextHeight := textHeight >> 1
|
||||
|
||||
pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
|
||||
pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
||||
pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
|
||||
pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)
|
||||
|
||||
strokeWidth := style.GetStrokeWidth()
|
||||
|
||||
top := ly - (pt + halfTextHeight)
|
||||
right := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth + int(strokeWidth)
|
||||
bottom := ly + (pb + halfTextHeight)
|
||||
|
||||
return Box{
|
||||
Top: top,
|
||||
Left: lx,
|
||||
Right: right,
|
||||
Bottom: bottom,
|
||||
}
|
||||
}
|
||||
|
||||
// Annotation draws an anotation with a renderer.
|
||||
func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) {
|
||||
style.GetTextOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
textBox := r.MeasureText(label)
|
||||
textWidth := textBox.Width()
|
||||
halfTextHeight := textBox.Height() >> 1
|
||||
|
||||
style.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
|
||||
pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
|
||||
pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
||||
pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
|
||||
pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)
|
||||
|
||||
textX := lx + pl + DefaultAnnotationDeltaWidth
|
||||
textY := ly + halfTextHeight
|
||||
|
||||
ltx := lx + DefaultAnnotationDeltaWidth
|
||||
lty := ly - (pt + halfTextHeight)
|
||||
|
||||
rtx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
|
||||
rty := ly - (pt + halfTextHeight)
|
||||
|
||||
rbx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
|
||||
rby := ly + (pb + halfTextHeight)
|
||||
|
||||
lbx := lx + DefaultAnnotationDeltaWidth
|
||||
lby := ly + (pb + halfTextHeight)
|
||||
|
||||
r.MoveTo(lx, ly)
|
||||
r.LineTo(ltx, lty)
|
||||
r.LineTo(rtx, rty)
|
||||
r.LineTo(rbx, rby)
|
||||
r.LineTo(lbx, lby)
|
||||
r.LineTo(lx, ly)
|
||||
r.Close()
|
||||
r.FillStroke()
|
||||
|
||||
style.GetTextOptions().WriteToRenderer(r)
|
||||
r.Text(label, textX, textY)
|
||||
}
|
||||
|
||||
// Box draws a box with a given style.
|
||||
func (d draw) Box(r Renderer, b Box, s Style) {
|
||||
s.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
r.MoveTo(b.Left, b.Top)
|
||||
r.LineTo(b.Right, b.Top)
|
||||
r.LineTo(b.Right, b.Bottom)
|
||||
r.LineTo(b.Left, b.Bottom)
|
||||
r.LineTo(b.Left, b.Top)
|
||||
r.FillStroke()
|
||||
}
|
||||
|
||||
func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) {
|
||||
d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s)
|
||||
}
|
||||
|
||||
func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) {
|
||||
s.GetFillAndStrokeOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y)
|
||||
r.LineTo(bc.TopRight.X, bc.TopRight.Y)
|
||||
r.LineTo(bc.BottomRight.X, bc.BottomRight.Y)
|
||||
r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y)
|
||||
r.Close()
|
||||
r.FillStroke()
|
||||
}
|
||||
|
||||
// DrawText draws text with a given style.
|
||||
func (d draw) Text(r Renderer, text string, x, y int, style Style) {
|
||||
style.GetTextOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
r.Text(text, x, y)
|
||||
}
|
||||
|
||||
func (d draw) MeasureText(r Renderer, text string, style Style) Box {
|
||||
style.GetTextOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
return r.MeasureText(text)
|
||||
}
|
||||
|
||||
// TextWithin draws the text within a given box.
|
||||
func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
|
||||
style.GetTextOptions().WriteToRenderer(r)
|
||||
defer r.ResetStyle()
|
||||
|
||||
lines := Text.WrapFit(r, text, box.Width(), style)
|
||||
linesBox := Text.MeasureLines(r, lines, style)
|
||||
|
||||
y := box.Top
|
||||
|
||||
switch style.GetTextVerticalAlign() {
|
||||
case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
|
||||
y = y - linesBox.Height()
|
||||
case TextVerticalAlignMiddle:
|
||||
y = y + (box.Height() >> 1) - (linesBox.Height() >> 1)
|
||||
case TextVerticalAlignMiddleBaseline:
|
||||
y = y + (box.Height() >> 1) - linesBox.Height()
|
||||
}
|
||||
|
||||
var tx, ty int
|
||||
for _, line := range lines {
|
||||
lineBox := r.MeasureText(line)
|
||||
switch style.GetTextHorizontalAlign() {
|
||||
case TextHorizontalAlignCenter:
|
||||
tx = box.Left + ((box.Width() - lineBox.Width()) >> 1)
|
||||
case TextHorizontalAlignRight:
|
||||
tx = box.Right - lineBox.Width()
|
||||
default:
|
||||
tx = box.Left
|
||||
}
|
||||
if style.TextRotationDegrees == 0 {
|
||||
ty = y + lineBox.Height()
|
||||
} else {
|
||||
ty = y
|
||||
}
|
||||
|
||||
r.Text(line, tx, ty)
|
||||
y += lineBox.Height() + style.GetTextLineSpacing()
|
||||
}
|
||||
}
|
||||
5
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/README.md
generated
vendored
Normal file
5
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/README.md
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
go-chart > drawing
|
||||
==================
|
||||
|
||||
The bulk of the code in this package is based on [draw2d](https://github.com/llgcode/draw2d), but
|
||||
with significant modifications to make the APIs more golang friendly and careful about units (points vs. pixels).
|
||||
126
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/color.go
generated
vendored
Normal file
126
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/color.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// ColorTransparent is a fully transparent color.
|
||||
ColorTransparent = Color{}
|
||||
|
||||
// ColorWhite is white.
|
||||
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
|
||||
|
||||
// ColorBlack is black.
|
||||
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
|
||||
|
||||
// ColorRed is red.
|
||||
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
|
||||
|
||||
// ColorGreen is green.
|
||||
ColorGreen = Color{R: 0, G: 255, B: 0, A: 255}
|
||||
|
||||
// ColorBlue is blue.
|
||||
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
|
||||
)
|
||||
|
||||
func parseHex(hex string) uint8 {
|
||||
v, _ := strconv.ParseInt(hex, 16, 16)
|
||||
return uint8(v)
|
||||
}
|
||||
|
||||
// ColorFromHex returns a color from a css hex code.
|
||||
func ColorFromHex(hex string) Color {
|
||||
var c Color
|
||||
if len(hex) == 3 {
|
||||
c.R = parseHex(string(hex[0])) * 0x11
|
||||
c.G = parseHex(string(hex[1])) * 0x11
|
||||
c.B = parseHex(string(hex[2])) * 0x11
|
||||
} else {
|
||||
c.R = parseHex(string(hex[0:2]))
|
||||
c.G = parseHex(string(hex[2:4]))
|
||||
c.B = parseHex(string(hex[4:6]))
|
||||
}
|
||||
c.A = 255
|
||||
return c
|
||||
}
|
||||
|
||||
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
|
||||
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color {
|
||||
fa := float64(a) / 255.0
|
||||
var c Color
|
||||
c.R = uint8(float64(r) / fa)
|
||||
c.G = uint8(float64(g) / fa)
|
||||
c.B = uint8(float64(b) / fa)
|
||||
c.A = uint8(a | (a >> 8))
|
||||
return c
|
||||
}
|
||||
|
||||
// ColorChannelFromFloat returns a normalized byte from a given float value.
|
||||
func ColorChannelFromFloat(v float64) uint8 {
|
||||
return uint8(v * 255)
|
||||
}
|
||||
|
||||
// Color is our internal color type because color.Color is bullshit.
|
||||
type Color struct {
|
||||
R, G, B, A uint8
|
||||
}
|
||||
|
||||
// RGBA returns the color as a pre-alpha mixed color set.
|
||||
func (c Color) RGBA() (r, g, b, a uint32) {
|
||||
fa := float64(c.A) / 255.0
|
||||
r = uint32(float64(uint32(c.R)) * fa)
|
||||
r |= r << 8
|
||||
g = uint32(float64(uint32(c.G)) * fa)
|
||||
g |= g << 8
|
||||
b = uint32(float64(uint32(c.B)) * fa)
|
||||
b |= b << 8
|
||||
a = uint32(c.A)
|
||||
a |= a << 8
|
||||
return
|
||||
}
|
||||
|
||||
// IsZero returns if the color has been set or not.
|
||||
func (c Color) IsZero() bool {
|
||||
return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0
|
||||
}
|
||||
|
||||
// IsTransparent returns if the colors alpha channel is zero.
|
||||
func (c Color) IsTransparent() bool {
|
||||
return c.A == 0
|
||||
}
|
||||
|
||||
// WithAlpha returns a copy of the color with a given alpha.
|
||||
func (c Color) WithAlpha(a uint8) Color {
|
||||
return Color{
|
||||
R: c.R,
|
||||
G: c.G,
|
||||
B: c.B,
|
||||
A: a,
|
||||
}
|
||||
}
|
||||
|
||||
// Equals returns true if the color equals another.
|
||||
func (c Color) Equals(other Color) bool {
|
||||
return c.R == other.R &&
|
||||
c.G == other.G &&
|
||||
c.B == other.B &&
|
||||
c.A == other.A
|
||||
}
|
||||
|
||||
// AverageWith averages two colors.
|
||||
func (c Color) AverageWith(other Color) Color {
|
||||
return Color{
|
||||
R: (c.R + other.R) >> 1,
|
||||
G: (c.G + other.G) >> 1,
|
||||
B: (c.B + other.B) >> 1,
|
||||
A: c.A,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a css string representation of the color.
|
||||
func (c Color) String() string {
|
||||
fa := float64(c.A) / float64(255)
|
||||
return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa)
|
||||
}
|
||||
6
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/constants.go
generated
vendored
Normal file
6
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/constants.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package drawing
|
||||
|
||||
const (
|
||||
// DefaultDPI is the default image DPI.
|
||||
DefaultDPI = 96.0
|
||||
)
|
||||
185
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/curve.go
generated
vendored
Normal file
185
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/curve.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
package drawing
|
||||
|
||||
import "math"
|
||||
|
||||
const (
|
||||
// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
|
||||
CurveRecursionLimit = 32
|
||||
)
|
||||
|
||||
// Cubic
|
||||
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
|
||||
|
||||
// SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
||||
// c1 and c2 parameters are the resulting curves
|
||||
func SubdivideCubic(c, c1, c2 []float64) {
|
||||
// First point of c is the first point of c1
|
||||
c1[0], c1[1] = c[0], c[1]
|
||||
// Last point of c is the last point of c2
|
||||
c2[6], c2[7] = c[6], c[7]
|
||||
|
||||
// Subdivide segment using midpoints
|
||||
c1[2] = (c[0] + c[2]) / 2
|
||||
c1[3] = (c[1] + c[3]) / 2
|
||||
|
||||
midX := (c[2] + c[4]) / 2
|
||||
midY := (c[3] + c[5]) / 2
|
||||
|
||||
c2[4] = (c[4] + c[6]) / 2
|
||||
c2[5] = (c[5] + c[7]) / 2
|
||||
|
||||
c1[4] = (c1[2] + midX) / 2
|
||||
c1[5] = (c1[3] + midY) / 2
|
||||
|
||||
c2[2] = (midX + c2[4]) / 2
|
||||
c2[3] = (midY + c2[5]) / 2
|
||||
|
||||
c1[6] = (c1[4] + c2[2]) / 2
|
||||
c1[7] = (c1[5] + c2[3]) / 2
|
||||
|
||||
// Last Point of c1 is equal to the first point of c2
|
||||
c2[0], c2[1] = c1[6], c1[7]
|
||||
}
|
||||
|
||||
// TraceCubic generate lines subdividing the cubic curve using a Liner
|
||||
// flattening_threshold helps determines the flattening expectation of the curve
|
||||
func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) {
|
||||
// Allocation curves
|
||||
var curves [CurveRecursionLimit * 8]float64
|
||||
copy(curves[0:8], cubic[0:8])
|
||||
i := 0
|
||||
|
||||
// current curve
|
||||
var c []float64
|
||||
|
||||
var dx, dy, d2, d3 float64
|
||||
|
||||
for i >= 0 {
|
||||
c = curves[i*8:]
|
||||
dx = c[6] - c[0]
|
||||
dy = c[7] - c[1]
|
||||
|
||||
d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx)
|
||||
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
||||
|
||||
// if it's flat then trace a line
|
||||
if (d2+d3)*(d2+d3) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||
t.LineTo(c[6], c[7])
|
||||
i--
|
||||
} else {
|
||||
// second half of bezier go lower onto the stack
|
||||
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quad
|
||||
// x1, y1, cpx1, cpy2, x2, y2 float64
|
||||
|
||||
// SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
|
||||
// c1 and c2 parameters are the resulting curves
|
||||
func SubdivideQuad(c, c1, c2 []float64) {
|
||||
// First point of c is the first point of c1
|
||||
c1[0], c1[1] = c[0], c[1]
|
||||
// Last point of c is the last point of c2
|
||||
c2[4], c2[5] = c[4], c[5]
|
||||
|
||||
// Subdivide segment using midpoints
|
||||
c1[2] = (c[0] + c[2]) / 2
|
||||
c1[3] = (c[1] + c[3]) / 2
|
||||
c2[2] = (c[2] + c[4]) / 2
|
||||
c2[3] = (c[3] + c[5]) / 2
|
||||
c1[4] = (c1[2] + c2[2]) / 2
|
||||
c1[5] = (c1[3] + c2[3]) / 2
|
||||
c2[0], c2[1] = c1[4], c1[5]
|
||||
return
|
||||
}
|
||||
|
||||
func traceWindowIndices(i int) (startAt, endAt int) {
|
||||
startAt = i * 6
|
||||
endAt = startAt + 6
|
||||
return
|
||||
}
|
||||
|
||||
func traceCalcDeltas(c []float64) (dx, dy, d float64) {
|
||||
dx = c[4] - c[0]
|
||||
dy = c[5] - c[1]
|
||||
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
||||
return
|
||||
}
|
||||
|
||||
func traceIsFlat(dx, dy, d, threshold float64) bool {
|
||||
return (d * d) < threshold*(dx*dx+dy*dy)
|
||||
}
|
||||
|
||||
func traceGetWindow(curves []float64, i int) []float64 {
|
||||
startAt, endAt := traceWindowIndices(i)
|
||||
return curves[startAt:endAt]
|
||||
}
|
||||
|
||||
// TraceQuad generate lines subdividing the curve using a Liner
|
||||
// flattening_threshold helps determines the flattening expectation of the curve
|
||||
func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) {
|
||||
const curveLen = CurveRecursionLimit * 6
|
||||
const curveEndIndex = curveLen - 1
|
||||
const lastIteration = CurveRecursionLimit - 1
|
||||
|
||||
// Allocates curves stack
|
||||
curves := make([]float64, curveLen)
|
||||
|
||||
// copy 6 elements from the quad path to the stack
|
||||
copy(curves[0:6], quad[0:6])
|
||||
|
||||
var i int
|
||||
var c []float64
|
||||
var dx, dy, d float64
|
||||
|
||||
for i >= 0 {
|
||||
c = traceGetWindow(curves, i)
|
||||
dx, dy, d = traceCalcDeltas(c)
|
||||
|
||||
// bail early if the distance is 0
|
||||
if d == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// if it's flat then trace a line
|
||||
if traceIsFlat(dx, dy, d, flatteningThreshold) || i == lastIteration {
|
||||
t.LineTo(c[4], c[5])
|
||||
i--
|
||||
} else {
|
||||
SubdivideQuad(c, traceGetWindow(curves, i+1), traceGetWindow(curves, i))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TraceArc trace an arc using a Liner
|
||||
func TraceArc(t Liner, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
|
||||
end := start + angle
|
||||
clockWise := true
|
||||
if angle < 0 {
|
||||
clockWise = false
|
||||
}
|
||||
ra := (math.Abs(rx) + math.Abs(ry)) / 2
|
||||
da := math.Acos(ra/(ra+0.125/scale)) * 2
|
||||
//normalize
|
||||
if !clockWise {
|
||||
da = -da
|
||||
}
|
||||
angle = start + da
|
||||
var curX, curY float64
|
||||
for {
|
||||
if (angle < end-da/4) != clockWise {
|
||||
curX = x + math.Cos(end)*rx
|
||||
curY = y + math.Sin(end)*ry
|
||||
return curX, curY
|
||||
}
|
||||
curX = x + math.Cos(angle)*rx
|
||||
curY = y + math.Sin(angle)*ry
|
||||
|
||||
angle += da
|
||||
t.LineTo(curX, curY)
|
||||
}
|
||||
}
|
||||
89
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/dasher.go
generated
vendored
Normal file
89
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/dasher.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
package drawing
|
||||
|
||||
// NewDashVertexConverter creates a new dash converter.
|
||||
func NewDashVertexConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
|
||||
var dasher DashVertexConverter
|
||||
dasher.dash = dash
|
||||
dasher.currentDash = 0
|
||||
dasher.dashOffset = dashOffset
|
||||
dasher.next = flattener
|
||||
return &dasher
|
||||
}
|
||||
|
||||
// DashVertexConverter is a converter for dash vertexes.
|
||||
type DashVertexConverter struct {
|
||||
next Flattener
|
||||
x, y, distance float64
|
||||
dash []float64
|
||||
currentDash int
|
||||
dashOffset float64
|
||||
}
|
||||
|
||||
// LineTo implements the pathbuilder interface.
|
||||
func (dasher *DashVertexConverter) LineTo(x, y float64) {
|
||||
dasher.lineTo(x, y)
|
||||
}
|
||||
|
||||
// MoveTo implements the pathbuilder interface.
|
||||
func (dasher *DashVertexConverter) MoveTo(x, y float64) {
|
||||
dasher.next.MoveTo(x, y)
|
||||
dasher.x, dasher.y = x, y
|
||||
dasher.distance = dasher.dashOffset
|
||||
dasher.currentDash = 0
|
||||
}
|
||||
|
||||
// LineJoin implements the pathbuilder interface.
|
||||
func (dasher *DashVertexConverter) LineJoin() {
|
||||
dasher.next.LineJoin()
|
||||
}
|
||||
|
||||
// Close implements the pathbuilder interface.
|
||||
func (dasher *DashVertexConverter) Close() {
|
||||
dasher.next.Close()
|
||||
}
|
||||
|
||||
// End implements the pathbuilder interface.
|
||||
func (dasher *DashVertexConverter) End() {
|
||||
dasher.next.End()
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||
rest := dasher.dash[dasher.currentDash] - dasher.distance
|
||||
for rest < 0 {
|
||||
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
|
||||
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
|
||||
rest = dasher.dash[dasher.currentDash] - dasher.distance
|
||||
}
|
||||
d := distance(dasher.x, dasher.y, x, y)
|
||||
for d >= rest {
|
||||
k := rest / d
|
||||
lx := dasher.x + k*(x-dasher.x)
|
||||
ly := dasher.y + k*(y-dasher.y)
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.LineTo(lx, ly)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(lx, ly)
|
||||
}
|
||||
d = d - rest
|
||||
dasher.x, dasher.y = lx, ly
|
||||
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
|
||||
rest = dasher.dash[dasher.currentDash]
|
||||
}
|
||||
dasher.distance = d
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.LineTo(x, y)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(x, y)
|
||||
}
|
||||
if dasher.distance >= dasher.dash[dasher.currentDash] {
|
||||
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
|
||||
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
|
||||
}
|
||||
dasher.x, dasher.y = x, y
|
||||
}
|
||||
41
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/demux_flattener.go
generated
vendored
Normal file
41
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/demux_flattener.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package drawing
|
||||
|
||||
// DemuxFlattener is a flattener
|
||||
type DemuxFlattener struct {
|
||||
Flatteners []Flattener
|
||||
}
|
||||
|
||||
// MoveTo implements the path builder interface.
|
||||
func (dc DemuxFlattener) MoveTo(x, y float64) {
|
||||
for _, flattener := range dc.Flatteners {
|
||||
flattener.MoveTo(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// LineTo implements the path builder interface.
|
||||
func (dc DemuxFlattener) LineTo(x, y float64) {
|
||||
for _, flattener := range dc.Flatteners {
|
||||
flattener.LineTo(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// LineJoin implements the path builder interface.
|
||||
func (dc DemuxFlattener) LineJoin() {
|
||||
for _, flattener := range dc.Flatteners {
|
||||
flattener.LineJoin()
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements the path builder interface.
|
||||
func (dc DemuxFlattener) Close() {
|
||||
for _, flattener := range dc.Flatteners {
|
||||
flattener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// End implements the path builder interface.
|
||||
func (dc DemuxFlattener) End() {
|
||||
for _, flattener := range dc.Flatteners {
|
||||
flattener.End()
|
||||
}
|
||||
}
|
||||
148
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/drawing.go
generated
vendored
Normal file
148
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/drawing.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// FillRule defines the type for fill rules
|
||||
type FillRule int
|
||||
|
||||
const (
|
||||
// FillRuleEvenOdd determines the "insideness" of a point in the shape
|
||||
// by drawing a ray from that point to infinity in any direction
|
||||
// and counting the number of path segments from the given shape that the ray crosses.
|
||||
// If this number is odd, the point is inside; if even, the point is outside.
|
||||
FillRuleEvenOdd FillRule = iota
|
||||
// FillRuleWinding determines the "insideness" of a point in the shape
|
||||
// by drawing a ray from that point to infinity in any direction
|
||||
// and then examining the places where a segment of the shape crosses the ray.
|
||||
// Starting with a count of zero, add one each time a path segment crosses
|
||||
// the ray from left to right and subtract one each time
|
||||
// a path segment crosses the ray from right to left. After counting the crossings,
|
||||
// if the result is zero then the point is outside the path. Otherwise, it is inside.
|
||||
FillRuleWinding
|
||||
)
|
||||
|
||||
// LineCap is the style of line extremities
|
||||
type LineCap int
|
||||
|
||||
const (
|
||||
// RoundCap defines a rounded shape at the end of the line
|
||||
RoundCap LineCap = iota
|
||||
// ButtCap defines a squared shape exactly at the end of the line
|
||||
ButtCap
|
||||
// SquareCap defines a squared shape at the end of the line
|
||||
SquareCap
|
||||
)
|
||||
|
||||
// LineJoin is the style of segments joint
|
||||
type LineJoin int
|
||||
|
||||
const (
|
||||
// BevelJoin represents cut segments joint
|
||||
BevelJoin LineJoin = iota
|
||||
// RoundJoin represents rounded segments joint
|
||||
RoundJoin
|
||||
// MiterJoin represents peaker segments joint
|
||||
MiterJoin
|
||||
)
|
||||
|
||||
// StrokeStyle keeps stroke style attributes
|
||||
// that is used by the Stroke method of a Drawer
|
||||
type StrokeStyle struct {
|
||||
// Color defines the color of stroke
|
||||
Color color.Color
|
||||
// Line width
|
||||
Width float64
|
||||
// Line cap style rounded, butt or square
|
||||
LineCap LineCap
|
||||
// Line join style bevel, round or miter
|
||||
LineJoin LineJoin
|
||||
// offset of the first dash
|
||||
DashOffset float64
|
||||
// array represented dash length pair values are plain dash and impair are space between dash
|
||||
// if empty display plain line
|
||||
Dash []float64
|
||||
}
|
||||
|
||||
// SolidFillStyle define style attributes for a solid fill style
|
||||
type SolidFillStyle struct {
|
||||
// Color defines the line color
|
||||
Color color.Color
|
||||
// FillRule defines the file rule to used
|
||||
FillRule FillRule
|
||||
}
|
||||
|
||||
// Valign Vertical Alignment of the text
|
||||
type Valign int
|
||||
|
||||
const (
|
||||
// ValignTop top align text
|
||||
ValignTop Valign = iota
|
||||
// ValignCenter centered text
|
||||
ValignCenter
|
||||
// ValignBottom bottom aligned text
|
||||
ValignBottom
|
||||
// ValignBaseline align text with the baseline of the font
|
||||
ValignBaseline
|
||||
)
|
||||
|
||||
// Halign Horizontal Alignment of the text
|
||||
type Halign int
|
||||
|
||||
const (
|
||||
// HalignLeft Horizontally align to left
|
||||
HalignLeft = iota
|
||||
// HalignCenter Horizontally align to center
|
||||
HalignCenter
|
||||
// HalignRight Horizontally align to right
|
||||
HalignRight
|
||||
)
|
||||
|
||||
// TextStyle describe text property
|
||||
type TextStyle struct {
|
||||
// Color defines the color of text
|
||||
Color color.Color
|
||||
// Size font size
|
||||
Size float64
|
||||
// The font to use
|
||||
Font *truetype.Font
|
||||
// Horizontal Alignment of the text
|
||||
Halign Halign
|
||||
// Vertical Alignment of the text
|
||||
Valign Valign
|
||||
}
|
||||
|
||||
// ScalingPolicy is a constant to define how to scale an image
|
||||
type ScalingPolicy int
|
||||
|
||||
const (
|
||||
// ScalingNone no scaling applied
|
||||
ScalingNone ScalingPolicy = iota
|
||||
// ScalingStretch the image is stretched so that its width and height are exactly the given width and height
|
||||
ScalingStretch
|
||||
// ScalingWidth the image is scaled so that its width is exactly the given width
|
||||
ScalingWidth
|
||||
// ScalingHeight the image is scaled so that its height is exactly the given height
|
||||
ScalingHeight
|
||||
// ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height
|
||||
ScalingFit
|
||||
// ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height
|
||||
ScalingSameArea
|
||||
// ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height
|
||||
ScalingFill
|
||||
)
|
||||
|
||||
// ImageScaling style attributes used to display the image
|
||||
type ImageScaling struct {
|
||||
// Horizontal Alignment of the image
|
||||
Halign Halign
|
||||
// Vertical Alignment of the image
|
||||
Valign Valign
|
||||
// Width Height used by scaling policy
|
||||
Width, Height float64
|
||||
// ScalingPolicy defines the scaling policy to applied to the image
|
||||
ScalingPolicy ScalingPolicy
|
||||
}
|
||||
97
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/flattener.go
generated
vendored
Normal file
97
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/flattener.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package drawing
|
||||
|
||||
// Liner receive segment definition
|
||||
type Liner interface {
|
||||
// LineTo Draw a line from the current position to the point (x, y)
|
||||
LineTo(x, y float64)
|
||||
}
|
||||
|
||||
// Flattener receive segment definition
|
||||
type Flattener interface {
|
||||
// MoveTo Start a New line from the point (x, y)
|
||||
MoveTo(x, y float64)
|
||||
// LineTo Draw a line from the current position to the point (x, y)
|
||||
LineTo(x, y float64)
|
||||
// LineJoin add the most recent starting point to close the path to create a polygon
|
||||
LineJoin()
|
||||
// Close add the most recent starting point to close the path to create a polygon
|
||||
Close()
|
||||
// End mark the current line as finished so we can draw caps
|
||||
End()
|
||||
}
|
||||
|
||||
// Flatten convert curves into straight segments keeping join segments info
|
||||
func Flatten(path *Path, flattener Flattener, scale float64) {
|
||||
// First Point
|
||||
var startX, startY float64
|
||||
// Current Point
|
||||
var x, y float64
|
||||
var i int
|
||||
for _, cmp := range path.Components {
|
||||
switch cmp {
|
||||
case MoveToComponent:
|
||||
x, y = path.Points[i], path.Points[i+1]
|
||||
startX, startY = x, y
|
||||
if i != 0 {
|
||||
flattener.End()
|
||||
}
|
||||
flattener.MoveTo(x, y)
|
||||
i += 2
|
||||
case LineToComponent:
|
||||
x, y = path.Points[i], path.Points[i+1]
|
||||
flattener.LineTo(x, y)
|
||||
flattener.LineJoin()
|
||||
i += 2
|
||||
case QuadCurveToComponent:
|
||||
// we include the previous point for the start of the curve
|
||||
TraceQuad(flattener, path.Points[i-2:], 0.5)
|
||||
x, y = path.Points[i+2], path.Points[i+3]
|
||||
flattener.LineTo(x, y)
|
||||
i += 4
|
||||
case CubicCurveToComponent:
|
||||
TraceCubic(flattener, path.Points[i-2:], 0.5)
|
||||
x, y = path.Points[i+4], path.Points[i+5]
|
||||
flattener.LineTo(x, y)
|
||||
i += 6
|
||||
case ArcToComponent:
|
||||
x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale)
|
||||
flattener.LineTo(x, y)
|
||||
i += 6
|
||||
case CloseComponent:
|
||||
flattener.LineTo(startX, startY)
|
||||
flattener.Close()
|
||||
}
|
||||
}
|
||||
flattener.End()
|
||||
}
|
||||
|
||||
// SegmentedPath is a path of disparate point sectinos.
|
||||
type SegmentedPath struct {
|
||||
Points []float64
|
||||
}
|
||||
|
||||
// MoveTo implements the path interface.
|
||||
func (p *SegmentedPath) MoveTo(x, y float64) {
|
||||
p.Points = append(p.Points, x, y)
|
||||
// TODO need to mark this point as moveto
|
||||
}
|
||||
|
||||
// LineTo implements the path interface.
|
||||
func (p *SegmentedPath) LineTo(x, y float64) {
|
||||
p.Points = append(p.Points, x, y)
|
||||
}
|
||||
|
||||
// LineJoin implements the path interface.
|
||||
func (p *SegmentedPath) LineJoin() {
|
||||
// TODO need to mark the current point as linejoin
|
||||
}
|
||||
|
||||
// Close implements the path interface.
|
||||
func (p *SegmentedPath) Close() {
|
||||
// TODO Close
|
||||
}
|
||||
|
||||
// End implements the path interface.
|
||||
func (p *SegmentedPath) End() {
|
||||
// Nothing to do
|
||||
}
|
||||
30
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/free_type_path.go
generated
vendored
Normal file
30
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/free_type_path.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/raster"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// FtLineBuilder is a builder for freetype raster glyphs.
|
||||
type FtLineBuilder struct {
|
||||
Adder raster.Adder
|
||||
}
|
||||
|
||||
// MoveTo implements the path builder interface.
|
||||
func (liner FtLineBuilder) MoveTo(x, y float64) {
|
||||
liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
|
||||
}
|
||||
|
||||
// LineTo implements the path builder interface.
|
||||
func (liner FtLineBuilder) LineTo(x, y float64) {
|
||||
liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
|
||||
}
|
||||
|
||||
// LineJoin implements the path builder interface.
|
||||
func (liner FtLineBuilder) LineJoin() {}
|
||||
|
||||
// Close implements the path builder interface.
|
||||
func (liner FtLineBuilder) Close() {}
|
||||
|
||||
// End implements the path builder interface.
|
||||
func (liner FtLineBuilder) End() {}
|
||||
82
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/graphic_context.go
generated
vendored
Normal file
82
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/graphic_context.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
||||
type GraphicContext interface {
|
||||
// PathBuilder describes the interface for path drawing
|
||||
PathBuilder
|
||||
// BeginPath creates a new path
|
||||
BeginPath()
|
||||
// GetMatrixTransform returns the current transformation matrix
|
||||
GetMatrixTransform() Matrix
|
||||
// SetMatrixTransform sets the current transformation matrix
|
||||
SetMatrixTransform(tr Matrix)
|
||||
// ComposeMatrixTransform composes the current transformation matrix with tr
|
||||
ComposeMatrixTransform(tr Matrix)
|
||||
// Rotate applies a rotation to the current transformation matrix. angle is in radian.
|
||||
Rotate(angle float64)
|
||||
// Translate applies a translation to the current transformation matrix.
|
||||
Translate(tx, ty float64)
|
||||
// Scale applies a scale to the current transformation matrix.
|
||||
Scale(sx, sy float64)
|
||||
// SetStrokeColor sets the current stroke color
|
||||
SetStrokeColor(c color.Color)
|
||||
// SetFillColor sets the current fill color
|
||||
SetFillColor(c color.Color)
|
||||
// SetFillRule sets the current fill rule
|
||||
SetFillRule(f FillRule)
|
||||
// SetLineWidth sets the current line width
|
||||
SetLineWidth(lineWidth float64)
|
||||
// SetLineCap sets the current line cap
|
||||
SetLineCap(cap LineCap)
|
||||
// SetLineJoin sets the current line join
|
||||
SetLineJoin(join LineJoin)
|
||||
// SetLineDash sets the current dash
|
||||
SetLineDash(dash []float64, dashOffset float64)
|
||||
// SetFontSize sets the current font size
|
||||
SetFontSize(fontSize float64)
|
||||
// GetFontSize gets the current font size
|
||||
GetFontSize() float64
|
||||
// SetFont sets the font for the context
|
||||
SetFont(f *truetype.Font)
|
||||
// GetFont returns the current font
|
||||
GetFont() *truetype.Font
|
||||
// DrawImage draws the raster image in the current canvas
|
||||
DrawImage(image image.Image)
|
||||
// Save the context and push it to the context stack
|
||||
Save()
|
||||
// Restore remove the current context and restore the last one
|
||||
Restore()
|
||||
// Clear fills the current canvas with a default transparent color
|
||||
Clear()
|
||||
// ClearRect fills the specified rectangle with a default transparent color
|
||||
ClearRect(x1, y1, x2, y2 int)
|
||||
// SetDPI sets the current DPI
|
||||
SetDPI(dpi int)
|
||||
// GetDPI gets the current DPI
|
||||
GetDPI() int
|
||||
// GetStringBounds gets pixel bounds(dimensions) of given string
|
||||
GetStringBounds(s string) (left, top, right, bottom float64)
|
||||
// CreateStringPath creates a path from the string s at x, y
|
||||
CreateStringPath(text string, x, y float64) (cursor float64)
|
||||
// FillString draws the text at point (0, 0)
|
||||
FillString(text string) (cursor float64)
|
||||
// FillStringAt draws the text at the specified point (x, y)
|
||||
FillStringAt(text string, x, y float64) (cursor float64)
|
||||
// StrokeString draws the contour of the text at point (0, 0)
|
||||
StrokeString(text string) (cursor float64)
|
||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||
StrokeStringAt(text string, x, y float64) (cursor float64)
|
||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||
Stroke(paths ...*Path)
|
||||
// Fill fills the paths with the color specified by SetFillColor
|
||||
Fill(paths ...*Path)
|
||||
// FillStroke first fills the paths and than strokes them
|
||||
FillStroke(paths ...*Path)
|
||||
}
|
||||
13
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/image_filter.go
generated
vendored
Normal file
13
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/image_filter.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package drawing
|
||||
|
||||
// ImageFilter defines the type of filter to use
|
||||
type ImageFilter int
|
||||
|
||||
const (
|
||||
// LinearFilter defines a linear filter
|
||||
LinearFilter ImageFilter = iota
|
||||
// BilinearFilter defines a bilinear filter
|
||||
BilinearFilter
|
||||
// BicubicFilter defines a bicubic filter
|
||||
BicubicFilter
|
||||
)
|
||||
48
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/line.go
generated
vendored
Normal file
48
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/line.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"image/draw"
|
||||
)
|
||||
|
||||
// PolylineBresenham draws a polyline to an image
|
||||
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
|
||||
for i := 2; i < len(s); i += 2 {
|
||||
Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
|
||||
}
|
||||
}
|
||||
|
||||
// Bresenham draws a line between (x0, y0) and (x1, y1)
|
||||
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
|
||||
dx := abs(x1 - x0)
|
||||
dy := abs(y1 - y0)
|
||||
var sx, sy int
|
||||
if x0 < x1 {
|
||||
sx = 1
|
||||
} else {
|
||||
sx = -1
|
||||
}
|
||||
if y0 < y1 {
|
||||
sy = 1
|
||||
} else {
|
||||
sy = -1
|
||||
}
|
||||
err := dx - dy
|
||||
|
||||
var e2 int
|
||||
for {
|
||||
img.Set(x0, y0, color)
|
||||
if x0 == x1 && y0 == y1 {
|
||||
return
|
||||
}
|
||||
e2 = 2 * err
|
||||
if e2 > -dy {
|
||||
err = err - dy
|
||||
x0 = x0 + sx
|
||||
}
|
||||
if e2 < dx {
|
||||
err = err + dx
|
||||
y0 = y0 + sy
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/matrix.go
generated
vendored
Normal file
220
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/matrix.go
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Matrix represents an affine transformation
|
||||
type Matrix [6]float64
|
||||
|
||||
const (
|
||||
epsilon = 1e-6
|
||||
)
|
||||
|
||||
// Determinant compute the determinant of the matrix
|
||||
func (tr Matrix) Determinant() float64 {
|
||||
return tr[0]*tr[3] - tr[1]*tr[2]
|
||||
}
|
||||
|
||||
// Transform applies the transformation matrix to points. It modify the points passed in parameter.
|
||||
func (tr Matrix) Transform(points []float64) {
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := points[i]
|
||||
y := points[j]
|
||||
points[i] = x*tr[0] + y*tr[2] + tr[4]
|
||||
points[j] = x*tr[1] + y*tr[3] + tr[5]
|
||||
}
|
||||
}
|
||||
|
||||
// TransformPoint applies the transformation matrix to point. It returns the point the transformed point.
|
||||
func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) {
|
||||
xres = x*tr[0] + y*tr[2] + tr[4]
|
||||
yres = x*tr[1] + y*tr[3] + tr[5]
|
||||
return xres, yres
|
||||
}
|
||||
|
||||
func minMax(x, y float64) (min, max float64) {
|
||||
if x > y {
|
||||
return y, x
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
// TransformRectangle applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle
|
||||
func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) {
|
||||
points := []float64{x0, y0, x2, y0, x2, y2, x0, y2}
|
||||
tr.Transform(points)
|
||||
points[0], points[2] = minMax(points[0], points[2])
|
||||
points[4], points[6] = minMax(points[4], points[6])
|
||||
points[1], points[3] = minMax(points[1], points[3])
|
||||
points[5], points[7] = minMax(points[5], points[7])
|
||||
|
||||
nx0 = math.Min(points[0], points[4])
|
||||
ny0 = math.Min(points[1], points[5])
|
||||
nx2 = math.Max(points[2], points[6])
|
||||
ny2 = math.Max(points[3], points[7])
|
||||
return nx0, ny0, nx2, ny2
|
||||
}
|
||||
|
||||
// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle
|
||||
func (tr Matrix) InverseTransform(points []float64) {
|
||||
d := tr.Determinant() // matrix determinant
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := points[i]
|
||||
y := points[j]
|
||||
points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
|
||||
points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
|
||||
}
|
||||
}
|
||||
|
||||
// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point.
|
||||
func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) {
|
||||
d := tr.Determinant() // matrix determinant
|
||||
xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
|
||||
yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
|
||||
return xres, yres
|
||||
}
|
||||
|
||||
// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix.
|
||||
// It modify the points passed in parameter.
|
||||
func (tr Matrix) VectorTransform(points []float64) {
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := points[i]
|
||||
y := points[j]
|
||||
points[i] = x*tr[0] + y*tr[2]
|
||||
points[j] = x*tr[1] + y*tr[3]
|
||||
}
|
||||
}
|
||||
|
||||
// NewIdentityMatrix creates an identity transformation matrix.
|
||||
func NewIdentityMatrix() Matrix {
|
||||
return Matrix{1, 0, 0, 1, 0, 0}
|
||||
}
|
||||
|
||||
// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter
|
||||
func NewTranslationMatrix(tx, ty float64) Matrix {
|
||||
return Matrix{1, 0, 0, 1, tx, ty}
|
||||
}
|
||||
|
||||
// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor
|
||||
func NewScaleMatrix(sx, sy float64) Matrix {
|
||||
return Matrix{sx, 0, 0, sy, 0, 0}
|
||||
}
|
||||
|
||||
// NewRotationMatrix creates a rotation transformation matrix. angle is in radian
|
||||
func NewRotationMatrix(angle float64) Matrix {
|
||||
c := math.Cos(angle)
|
||||
s := math.Sin(angle)
|
||||
return Matrix{c, s, -s, c, 0, 0}
|
||||
}
|
||||
|
||||
// NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2.
|
||||
func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix {
|
||||
xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0])
|
||||
yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1])
|
||||
xOffset := rectangle2[0] - (rectangle1[0] * xScale)
|
||||
yOffset := rectangle2[1] - (rectangle1[1] * yScale)
|
||||
return Matrix{xScale, 0, 0, yScale, xOffset, yOffset}
|
||||
}
|
||||
|
||||
// Inverse computes the inverse matrix
|
||||
func (tr *Matrix) Inverse() {
|
||||
d := tr.Determinant() // matrix determinant
|
||||
tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
|
||||
tr[0] = tr3 / d
|
||||
tr[1] = -tr1 / d
|
||||
tr[2] = -tr2 / d
|
||||
tr[3] = tr0 / d
|
||||
tr[4] = (tr2*tr5 - tr3*tr4) / d
|
||||
tr[5] = (tr1*tr4 - tr0*tr5) / d
|
||||
}
|
||||
|
||||
// Copy copies the matrix.
|
||||
func (tr Matrix) Copy() Matrix {
|
||||
var result Matrix
|
||||
copy(result[:], tr[:])
|
||||
return result
|
||||
}
|
||||
|
||||
// Compose multiplies trToConcat x tr
|
||||
func (tr *Matrix) Compose(trToCompose Matrix) {
|
||||
tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
|
||||
tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2
|
||||
tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1
|
||||
tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2
|
||||
tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1
|
||||
tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4
|
||||
tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5
|
||||
}
|
||||
|
||||
// Scale adds a scale to the matrix
|
||||
func (tr *Matrix) Scale(sx, sy float64) {
|
||||
tr[0] = sx * tr[0]
|
||||
tr[1] = sx * tr[1]
|
||||
tr[2] = sy * tr[2]
|
||||
tr[3] = sy * tr[3]
|
||||
}
|
||||
|
||||
// Translate adds a translation to the matrix
|
||||
func (tr *Matrix) Translate(tx, ty float64) {
|
||||
tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
|
||||
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
|
||||
}
|
||||
|
||||
// Rotate adds a rotation to the matrix.
|
||||
func (tr *Matrix) Rotate(radians float64) {
|
||||
c := math.Cos(radians)
|
||||
s := math.Sin(radians)
|
||||
t0 := c*tr[0] + s*tr[2]
|
||||
t1 := s*tr[3] + c*tr[1]
|
||||
t2 := c*tr[2] - s*tr[0]
|
||||
t3 := c*tr[3] - s*tr[1]
|
||||
tr[0] = t0
|
||||
tr[1] = t1
|
||||
tr[2] = t2
|
||||
tr[3] = t3
|
||||
}
|
||||
|
||||
// GetTranslation gets the matrix traslation.
|
||||
func (tr Matrix) GetTranslation() (x, y float64) {
|
||||
return tr[4], tr[5]
|
||||
}
|
||||
|
||||
// GetScaling gets the matrix scaling.
|
||||
func (tr Matrix) GetScaling() (x, y float64) {
|
||||
return tr[0], tr[3]
|
||||
}
|
||||
|
||||
// GetScale computes a scale for the matrix
|
||||
func (tr Matrix) GetScale() float64 {
|
||||
x := 0.707106781*tr[0] + 0.707106781*tr[1]
|
||||
y := 0.707106781*tr[2] + 0.707106781*tr[3]
|
||||
return math.Sqrt(x*x + y*y)
|
||||
}
|
||||
|
||||
// ******************** Testing ********************
|
||||
|
||||
// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements.
|
||||
func (tr Matrix) Equals(tr2 Matrix) bool {
|
||||
for i := 0; i < 6; i = i + 1 {
|
||||
if !fequals(tr[i], tr2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements.
|
||||
func (tr Matrix) IsIdentity() bool {
|
||||
return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation()
|
||||
}
|
||||
|
||||
// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements.
|
||||
func (tr Matrix) IsTranslation() bool {
|
||||
return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1)
|
||||
}
|
||||
|
||||
// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise
|
||||
func fequals(float1, float2 float64) bool {
|
||||
return math.Abs(float1-float2) <= epsilon
|
||||
}
|
||||
31
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/painter.go
generated
vendored
Normal file
31
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/painter.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/image/math/f64"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
)
|
||||
|
||||
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
||||
type Painter interface {
|
||||
raster.Painter
|
||||
SetColor(color color.Color)
|
||||
}
|
||||
|
||||
// DrawImage draws an image into dest using an affine transformation matrix, an op and a filter
|
||||
func DrawImage(src image.Image, dest draw.Image, tr Matrix, op draw.Op, filter ImageFilter) {
|
||||
var transformer draw.Transformer
|
||||
switch filter {
|
||||
case LinearFilter:
|
||||
transformer = draw.NearestNeighbor
|
||||
case BilinearFilter:
|
||||
transformer = draw.BiLinear
|
||||
case BicubicFilter:
|
||||
transformer = draw.CatmullRom
|
||||
}
|
||||
transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil)
|
||||
}
|
||||
186
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/path.go
generated
vendored
Normal file
186
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/path.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// PathBuilder describes the interface for path drawing.
|
||||
type PathBuilder interface {
|
||||
// LastPoint returns the current point of the current sub path
|
||||
LastPoint() (x, y float64)
|
||||
// MoveTo creates a new subpath that start at the specified point
|
||||
MoveTo(x, y float64)
|
||||
// LineTo adds a line to the current subpath
|
||||
LineTo(x, y float64)
|
||||
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
||||
QuadCurveTo(cx, cy, x, y float64)
|
||||
// CubicCurveTo adds a cubic Bézier curve to the current subpath
|
||||
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||
// ArcTo adds an arc to the current subpath
|
||||
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
||||
// Close creates a line from the current point to the last MoveTo
|
||||
// point (if not the same) and mark the path as closed so the
|
||||
// first and last lines join nicely.
|
||||
Close()
|
||||
}
|
||||
|
||||
// PathComponent represents component of a path
|
||||
type PathComponent int
|
||||
|
||||
const (
|
||||
// MoveToComponent is a MoveTo component in a Path
|
||||
MoveToComponent PathComponent = iota
|
||||
// LineToComponent is a LineTo component in a Path
|
||||
LineToComponent
|
||||
// QuadCurveToComponent is a QuadCurveTo component in a Path
|
||||
QuadCurveToComponent
|
||||
// CubicCurveToComponent is a CubicCurveTo component in a Path
|
||||
CubicCurveToComponent
|
||||
// ArcToComponent is a ArcTo component in a Path
|
||||
ArcToComponent
|
||||
// CloseComponent is a ArcTo component in a Path
|
||||
CloseComponent
|
||||
)
|
||||
|
||||
// Path stores points
|
||||
type Path struct {
|
||||
// Components is a slice of PathComponent in a Path and mark the role of each points in the Path
|
||||
Components []PathComponent
|
||||
// Points are combined with Components to have a specific role in the path
|
||||
Points []float64
|
||||
// Last Point of the Path
|
||||
x, y float64
|
||||
}
|
||||
|
||||
func (p *Path) appendToPath(cmd PathComponent, points ...float64) {
|
||||
p.Components = append(p.Components, cmd)
|
||||
p.Points = append(p.Points, points...)
|
||||
}
|
||||
|
||||
// LastPoint returns the current point of the current path
|
||||
func (p *Path) LastPoint() (x, y float64) {
|
||||
return p.x, p.y
|
||||
}
|
||||
|
||||
// MoveTo starts a new path at (x, y) position
|
||||
func (p *Path) MoveTo(x, y float64) {
|
||||
p.appendToPath(MoveToComponent, x, y)
|
||||
p.x = x
|
||||
p.y = y
|
||||
}
|
||||
|
||||
// LineTo adds a line to the current path
|
||||
func (p *Path) LineTo(x, y float64) {
|
||||
if len(p.Components) == 0 { //special case when no move has been done
|
||||
p.MoveTo(0, 0)
|
||||
}
|
||||
p.appendToPath(LineToComponent, x, y)
|
||||
p.x = x
|
||||
p.y = y
|
||||
}
|
||||
|
||||
// QuadCurveTo adds a quadratic bezier curve to the current path
|
||||
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
|
||||
if len(p.Components) == 0 { //special case when no move has been done
|
||||
p.MoveTo(0, 0)
|
||||
}
|
||||
p.appendToPath(QuadCurveToComponent, cx, cy, x, y)
|
||||
p.x = x
|
||||
p.y = y
|
||||
}
|
||||
|
||||
// CubicCurveTo adds a cubic bezier curve to the current path
|
||||
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||
if len(p.Components) == 0 { //special case when no move has been done
|
||||
p.MoveTo(0, 0)
|
||||
}
|
||||
p.appendToPath(CubicCurveToComponent, cx1, cy1, cx2, cy2, x, y)
|
||||
p.x = x
|
||||
p.y = y
|
||||
}
|
||||
|
||||
// ArcTo adds an arc to the path
|
||||
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, delta float64) {
|
||||
endAngle := startAngle + delta
|
||||
clockWise := true
|
||||
if delta < 0 {
|
||||
clockWise = false
|
||||
}
|
||||
// normalize
|
||||
if clockWise {
|
||||
for endAngle < startAngle {
|
||||
endAngle += math.Pi * 2.0
|
||||
}
|
||||
} else {
|
||||
for startAngle < endAngle {
|
||||
startAngle += math.Pi * 2.0
|
||||
}
|
||||
}
|
||||
startX := cx + math.Cos(startAngle)*rx
|
||||
startY := cy + math.Sin(startAngle)*ry
|
||||
if len(p.Components) > 0 {
|
||||
p.LineTo(startX, startY)
|
||||
} else {
|
||||
p.MoveTo(startX, startY)
|
||||
}
|
||||
p.appendToPath(ArcToComponent, cx, cy, rx, ry, startAngle, delta)
|
||||
p.x = cx + math.Cos(endAngle)*rx
|
||||
p.y = cy + math.Sin(endAngle)*ry
|
||||
}
|
||||
|
||||
// Close closes the current path
|
||||
func (p *Path) Close() {
|
||||
p.appendToPath(CloseComponent)
|
||||
}
|
||||
|
||||
// Copy make a clone of the current path and return it
|
||||
func (p *Path) Copy() (dest *Path) {
|
||||
dest = new(Path)
|
||||
dest.Components = make([]PathComponent, len(p.Components))
|
||||
copy(dest.Components, p.Components)
|
||||
dest.Points = make([]float64, len(p.Points))
|
||||
copy(dest.Points, p.Points)
|
||||
dest.x, dest.y = p.x, p.y
|
||||
return dest
|
||||
}
|
||||
|
||||
// Clear reset the path
|
||||
func (p *Path) Clear() {
|
||||
p.Components = p.Components[0:0]
|
||||
p.Points = p.Points[0:0]
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the path is empty
|
||||
func (p *Path) IsEmpty() bool {
|
||||
return len(p.Components) == 0
|
||||
}
|
||||
|
||||
// String returns a debug text view of the path
|
||||
func (p *Path) String() string {
|
||||
s := ""
|
||||
j := 0
|
||||
for _, cmd := range p.Components {
|
||||
switch cmd {
|
||||
case MoveToComponent:
|
||||
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
|
||||
j = j + 2
|
||||
case LineToComponent:
|
||||
s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
|
||||
j = j + 2
|
||||
case QuadCurveToComponent:
|
||||
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3])
|
||||
j = j + 4
|
||||
case CubicCurveToComponent:
|
||||
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
|
||||
j = j + 6
|
||||
case ArcToComponent:
|
||||
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
|
||||
j = j + 6
|
||||
case CloseComponent:
|
||||
s += "Close\n"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
283
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/raster_graphic_context.go
generated
vendored
Normal file
283
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/raster_graphic_context.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// NewRasterGraphicContext creates a new Graphic context from an image.
|
||||
func NewRasterGraphicContext(img draw.Image) (*RasterGraphicContext, error) {
|
||||
var painter Painter
|
||||
switch selectImage := img.(type) {
|
||||
case *image.RGBA:
|
||||
painter = raster.NewRGBAPainter(selectImage)
|
||||
default:
|
||||
return nil, errors.New("NewRasterGraphicContext() :: invalid image type")
|
||||
}
|
||||
return NewRasterGraphicContextWithPainter(img, painter), nil
|
||||
}
|
||||
|
||||
// NewRasterGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
|
||||
func NewRasterGraphicContextWithPainter(img draw.Image, painter Painter) *RasterGraphicContext {
|
||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
return &RasterGraphicContext{
|
||||
NewStackGraphicContext(),
|
||||
img,
|
||||
painter,
|
||||
raster.NewRasterizer(width, height),
|
||||
raster.NewRasterizer(width, height),
|
||||
&truetype.GlyphBuf{},
|
||||
DefaultDPI,
|
||||
}
|
||||
}
|
||||
|
||||
// RasterGraphicContext is the implementation of GraphicContext for a raster image
|
||||
type RasterGraphicContext struct {
|
||||
*StackGraphicContext
|
||||
img draw.Image
|
||||
painter Painter
|
||||
fillRasterizer *raster.Rasterizer
|
||||
strokeRasterizer *raster.Rasterizer
|
||||
glyphBuf *truetype.GlyphBuf
|
||||
DPI float64
|
||||
}
|
||||
|
||||
// SetDPI sets the screen resolution in dots per inch.
|
||||
func (rgc *RasterGraphicContext) SetDPI(dpi float64) {
|
||||
rgc.DPI = dpi
|
||||
rgc.recalc()
|
||||
}
|
||||
|
||||
// GetDPI returns the resolution of the Image GraphicContext
|
||||
func (rgc *RasterGraphicContext) GetDPI() float64 {
|
||||
return rgc.DPI
|
||||
}
|
||||
|
||||
// Clear fills the current canvas with a default transparent color
|
||||
func (rgc *RasterGraphicContext) Clear() {
|
||||
width, height := rgc.img.Bounds().Dx(), rgc.img.Bounds().Dy()
|
||||
rgc.ClearRect(0, 0, width, height)
|
||||
}
|
||||
|
||||
// ClearRect fills the current canvas with a default transparent color at the specified rectangle
|
||||
func (rgc *RasterGraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||
imageColor := image.NewUniform(rgc.current.FillColor)
|
||||
draw.Draw(rgc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
|
||||
}
|
||||
|
||||
// DrawImage draws the raster image in the current canvas
|
||||
func (rgc *RasterGraphicContext) DrawImage(img image.Image) {
|
||||
DrawImage(img, rgc.img, rgc.current.Tr, draw.Over, BilinearFilter)
|
||||
}
|
||||
|
||||
// FillString draws the text at point (0, 0)
|
||||
func (rgc *RasterGraphicContext) FillString(text string) (cursor float64, err error) {
|
||||
cursor, err = rgc.FillStringAt(text, 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// FillStringAt draws the text at the specified point (x, y)
|
||||
func (rgc *RasterGraphicContext) FillStringAt(text string, x, y float64) (cursor float64, err error) {
|
||||
cursor, err = rgc.CreateStringPath(text, x, y)
|
||||
rgc.Fill()
|
||||
return
|
||||
}
|
||||
|
||||
// StrokeString draws the contour of the text at point (0, 0)
|
||||
func (rgc *RasterGraphicContext) StrokeString(text string) (cursor float64, err error) {
|
||||
cursor, err = rgc.StrokeStringAt(text, 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||
func (rgc *RasterGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64, err error) {
|
||||
cursor, err = rgc.CreateStringPath(text, x, y)
|
||||
rgc.Stroke()
|
||||
return
|
||||
}
|
||||
|
||||
func (rgc *RasterGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||
if err := rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), glyph, font.HintingNone); err != nil {
|
||||
return err
|
||||
}
|
||||
e0 := 0
|
||||
for _, e1 := range rgc.glyphBuf.Ends {
|
||||
DrawContour(rgc, rgc.glyphBuf.Points[e0:e1], dx, dy)
|
||||
e0 = e1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||
// The text is placed so that the left edge of the em square of the first character of s
|
||||
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
||||
// above and to the right of the point, but some may be below or to the left.
|
||||
// For example, drawing a string that starts with a 'J' in an italic font may
|
||||
// affect pixels below and left of the point.
|
||||
func (rgc *RasterGraphicContext) CreateStringPath(s string, x, y float64) (cursor float64, err error) {
|
||||
f := rgc.GetFont()
|
||||
if f == nil {
|
||||
err = errors.New("No font loaded, cannot continue")
|
||||
return
|
||||
}
|
||||
rgc.recalc()
|
||||
|
||||
startx := x
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, rc := range s {
|
||||
index := f.Index(rc)
|
||||
if hasPrev {
|
||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
|
||||
}
|
||||
err = rgc.drawGlyph(index, x, y)
|
||||
if err != nil {
|
||||
cursor = x - startx
|
||||
return
|
||||
}
|
||||
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
cursor = x - startx
|
||||
return
|
||||
}
|
||||
|
||||
// GetStringBounds returns the approximate pixel bounds of a string.
|
||||
func (rgc *RasterGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64, err error) {
|
||||
f := rgc.GetFont()
|
||||
if f == nil {
|
||||
err = errors.New("No font loaded, cannot continue")
|
||||
return
|
||||
}
|
||||
rgc.recalc()
|
||||
|
||||
left = math.MaxFloat64
|
||||
top = math.MaxFloat64
|
||||
|
||||
cursor := 0.0
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, rc := range s {
|
||||
index := f.Index(rc)
|
||||
if hasPrev {
|
||||
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
|
||||
}
|
||||
|
||||
if err = rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), index, font.HintingNone); err != nil {
|
||||
return
|
||||
}
|
||||
e0 := 0
|
||||
for _, e1 := range rgc.glyphBuf.Ends {
|
||||
ps := rgc.glyphBuf.Points[e0:e1]
|
||||
for _, p := range ps {
|
||||
x, y := pointToF64Point(p)
|
||||
top = math.Min(top, y)
|
||||
bottom = math.Max(bottom, y)
|
||||
left = math.Min(left, x+cursor)
|
||||
right = math.Max(right, x+cursor)
|
||||
}
|
||||
e0 = e1
|
||||
}
|
||||
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen
|
||||
// resolution and font metrics, and invalidates the glyph cache.
|
||||
func (rgc *RasterGraphicContext) recalc() {
|
||||
rgc.current.Scale = rgc.current.FontSizePoints * float64(rgc.DPI)
|
||||
}
|
||||
|
||||
// SetFont sets the font used to draw text.
|
||||
func (rgc *RasterGraphicContext) SetFont(font *truetype.Font) {
|
||||
rgc.current.Font = font
|
||||
}
|
||||
|
||||
// GetFont returns the font used to draw text.
|
||||
func (rgc *RasterGraphicContext) GetFont() *truetype.Font {
|
||||
return rgc.current.Font
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) {
|
||||
rgc.current.FontSizePoints = fontSizePoints
|
||||
rgc.recalc()
|
||||
}
|
||||
|
||||
func (rgc *RasterGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
||||
rgc.painter.SetColor(color)
|
||||
rasterizer.Rasterize(rgc.painter)
|
||||
rasterizer.Clear()
|
||||
rgc.current.Path.Clear()
|
||||
}
|
||||
|
||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||
func (rgc *RasterGraphicContext) Stroke(paths ...*Path) {
|
||||
paths = append(paths, rgc.current.Path)
|
||||
rgc.strokeRasterizer.UseNonZeroWinding = true
|
||||
|
||||
stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
|
||||
stroker.HalfLineWidth = rgc.current.LineWidth / 2
|
||||
|
||||
var liner Flattener
|
||||
if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
|
||||
liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
|
||||
} else {
|
||||
liner = stroker
|
||||
}
|
||||
for _, p := range paths {
|
||||
Flatten(p, liner, rgc.current.Tr.GetScale())
|
||||
}
|
||||
|
||||
rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
|
||||
}
|
||||
|
||||
// Fill fills the paths with the color specified by SetFillColor
|
||||
func (rgc *RasterGraphicContext) Fill(paths ...*Path) {
|
||||
paths = append(paths, rgc.current.Path)
|
||||
rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding
|
||||
|
||||
flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}
|
||||
for _, p := range paths {
|
||||
Flatten(p, flattener, rgc.current.Tr.GetScale())
|
||||
}
|
||||
|
||||
rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
|
||||
}
|
||||
|
||||
// FillStroke first fills the paths and than strokes them
|
||||
func (rgc *RasterGraphicContext) FillStroke(paths ...*Path) {
|
||||
paths = append(paths, rgc.current.Path)
|
||||
rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding
|
||||
rgc.strokeRasterizer.UseNonZeroWinding = true
|
||||
|
||||
flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}
|
||||
|
||||
stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
|
||||
stroker.HalfLineWidth = rgc.current.LineWidth / 2
|
||||
|
||||
var liner Flattener
|
||||
if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
|
||||
liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
|
||||
} else {
|
||||
liner = stroker
|
||||
}
|
||||
|
||||
demux := DemuxFlattener{Flatteners: []Flattener{flattener, liner}}
|
||||
for _, p := range paths {
|
||||
Flatten(p, demux, rgc.current.Tr.GetScale())
|
||||
}
|
||||
|
||||
// Fill
|
||||
rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
|
||||
// Stroke
|
||||
rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
|
||||
}
|
||||
211
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/stack_graphic_context.go
generated
vendored
Normal file
211
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/stack_graphic_context.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// StackGraphicContext is a context that does thngs.
|
||||
type StackGraphicContext struct {
|
||||
current *ContextStack
|
||||
}
|
||||
|
||||
// ContextStack is a graphic context implementation.
|
||||
type ContextStack struct {
|
||||
Tr Matrix
|
||||
Path *Path
|
||||
LineWidth float64
|
||||
Dash []float64
|
||||
DashOffset float64
|
||||
StrokeColor color.Color
|
||||
FillColor color.Color
|
||||
FillRule FillRule
|
||||
Cap LineCap
|
||||
Join LineJoin
|
||||
|
||||
FontSizePoints float64
|
||||
Font *truetype.Font
|
||||
|
||||
Scale float64
|
||||
|
||||
Previous *ContextStack
|
||||
}
|
||||
|
||||
// NewStackGraphicContext Create a new Graphic context from an image
|
||||
func NewStackGraphicContext() *StackGraphicContext {
|
||||
gc := &StackGraphicContext{}
|
||||
gc.current = new(ContextStack)
|
||||
gc.current.Tr = NewIdentityMatrix()
|
||||
gc.current.Path = new(Path)
|
||||
gc.current.LineWidth = 1.0
|
||||
gc.current.StrokeColor = image.Black
|
||||
gc.current.FillColor = image.White
|
||||
gc.current.Cap = RoundCap
|
||||
gc.current.FillRule = FillRuleEvenOdd
|
||||
gc.current.Join = RoundJoin
|
||||
gc.current.FontSizePoints = 10
|
||||
return gc
|
||||
}
|
||||
|
||||
// GetMatrixTransform returns the matrix transform.
|
||||
func (gc *StackGraphicContext) GetMatrixTransform() Matrix {
|
||||
return gc.current.Tr
|
||||
}
|
||||
|
||||
// SetMatrixTransform sets the matrix transform.
|
||||
func (gc *StackGraphicContext) SetMatrixTransform(tr Matrix) {
|
||||
gc.current.Tr = tr
|
||||
}
|
||||
|
||||
// ComposeMatrixTransform composes a transform into the current transform.
|
||||
func (gc *StackGraphicContext) ComposeMatrixTransform(tr Matrix) {
|
||||
gc.current.Tr.Compose(tr)
|
||||
}
|
||||
|
||||
// Rotate rotates the matrix transform by an angle in degrees.
|
||||
func (gc *StackGraphicContext) Rotate(angle float64) {
|
||||
gc.current.Tr.Rotate(angle)
|
||||
}
|
||||
|
||||
// Translate translates a transform.
|
||||
func (gc *StackGraphicContext) Translate(tx, ty float64) {
|
||||
gc.current.Tr.Translate(tx, ty)
|
||||
}
|
||||
|
||||
// Scale scales a transform.
|
||||
func (gc *StackGraphicContext) Scale(sx, sy float64) {
|
||||
gc.current.Tr.Scale(sx, sy)
|
||||
}
|
||||
|
||||
// SetStrokeColor sets the stroke color.
|
||||
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
|
||||
gc.current.StrokeColor = c
|
||||
}
|
||||
|
||||
// SetFillColor sets the fill color.
|
||||
func (gc *StackGraphicContext) SetFillColor(c color.Color) {
|
||||
gc.current.FillColor = c
|
||||
}
|
||||
|
||||
// SetFillRule sets the fill rule.
|
||||
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
|
||||
gc.current.FillRule = f
|
||||
}
|
||||
|
||||
// SetLineWidth sets the line width.
|
||||
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
|
||||
gc.current.LineWidth = lineWidth
|
||||
}
|
||||
|
||||
// SetLineCap sets the line cap.
|
||||
func (gc *StackGraphicContext) SetLineCap(cap LineCap) {
|
||||
gc.current.Cap = cap
|
||||
}
|
||||
|
||||
// SetLineJoin sets the line join.
|
||||
func (gc *StackGraphicContext) SetLineJoin(join LineJoin) {
|
||||
gc.current.Join = join
|
||||
}
|
||||
|
||||
// SetLineDash sets the line dash.
|
||||
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
|
||||
gc.current.Dash = dash
|
||||
gc.current.DashOffset = dashOffset
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size.
|
||||
func (gc *StackGraphicContext) SetFontSize(fontSizePoints float64) {
|
||||
gc.current.FontSizePoints = fontSizePoints
|
||||
}
|
||||
|
||||
// GetFontSize gets the font size.
|
||||
func (gc *StackGraphicContext) GetFontSize() float64 {
|
||||
return gc.current.FontSizePoints
|
||||
}
|
||||
|
||||
// SetFont sets the current font.
|
||||
func (gc *StackGraphicContext) SetFont(f *truetype.Font) {
|
||||
gc.current.Font = f
|
||||
}
|
||||
|
||||
// GetFont returns the font.
|
||||
func (gc *StackGraphicContext) GetFont() *truetype.Font {
|
||||
return gc.current.Font
|
||||
}
|
||||
|
||||
// BeginPath starts a new path.
|
||||
func (gc *StackGraphicContext) BeginPath() {
|
||||
gc.current.Path.Clear()
|
||||
}
|
||||
|
||||
// IsEmpty returns if the path is empty.
|
||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||
return gc.current.Path.IsEmpty()
|
||||
}
|
||||
|
||||
// LastPoint returns the last point on the path.
|
||||
func (gc *StackGraphicContext) LastPoint() (x float64, y float64) {
|
||||
return gc.current.Path.LastPoint()
|
||||
}
|
||||
|
||||
// MoveTo moves the cursor for a path.
|
||||
func (gc *StackGraphicContext) MoveTo(x, y float64) {
|
||||
gc.current.Path.MoveTo(x, y)
|
||||
}
|
||||
|
||||
// LineTo draws a line.
|
||||
func (gc *StackGraphicContext) LineTo(x, y float64) {
|
||||
gc.current.Path.LineTo(x, y)
|
||||
}
|
||||
|
||||
// QuadCurveTo draws a quad curve.
|
||||
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
|
||||
gc.current.Path.QuadCurveTo(cx, cy, x, y)
|
||||
}
|
||||
|
||||
// CubicCurveTo draws a cubic curve.
|
||||
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||
gc.current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
||||
}
|
||||
|
||||
// ArcTo draws an arc.
|
||||
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, delta float64) {
|
||||
gc.current.Path.ArcTo(cx, cy, rx, ry, startAngle, delta)
|
||||
}
|
||||
|
||||
// Close closes a path.
|
||||
func (gc *StackGraphicContext) Close() {
|
||||
gc.current.Path.Close()
|
||||
}
|
||||
|
||||
// Save pushes a context onto the stack.
|
||||
func (gc *StackGraphicContext) Save() {
|
||||
context := new(ContextStack)
|
||||
context.FontSizePoints = gc.current.FontSizePoints
|
||||
context.Font = gc.current.Font
|
||||
context.LineWidth = gc.current.LineWidth
|
||||
context.StrokeColor = gc.current.StrokeColor
|
||||
context.FillColor = gc.current.FillColor
|
||||
context.FillRule = gc.current.FillRule
|
||||
context.Dash = gc.current.Dash
|
||||
context.DashOffset = gc.current.DashOffset
|
||||
context.Cap = gc.current.Cap
|
||||
context.Join = gc.current.Join
|
||||
context.Path = gc.current.Path.Copy()
|
||||
context.Font = gc.current.Font
|
||||
context.Scale = gc.current.Scale
|
||||
copy(context.Tr[:], gc.current.Tr[:])
|
||||
context.Previous = gc.current
|
||||
gc.current = context
|
||||
}
|
||||
|
||||
// Restore restores the previous context.
|
||||
func (gc *StackGraphicContext) Restore() {
|
||||
if gc.current.Previous != nil {
|
||||
oldContext := gc.current
|
||||
gc.current = gc.current.Previous
|
||||
oldContext.Previous = nil
|
||||
}
|
||||
}
|
||||
85
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/stroker.go
generated
vendored
Normal file
85
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/stroker.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
package drawing
|
||||
|
||||
// NewLineStroker creates a new line stroker.
|
||||
func NewLineStroker(c LineCap, j LineJoin, flattener Flattener) *LineStroker {
|
||||
l := new(LineStroker)
|
||||
l.Flattener = flattener
|
||||
l.HalfLineWidth = 0.5
|
||||
l.Cap = c
|
||||
l.Join = j
|
||||
return l
|
||||
}
|
||||
|
||||
// LineStroker draws the stroke portion of a line.
|
||||
type LineStroker struct {
|
||||
Flattener Flattener
|
||||
HalfLineWidth float64
|
||||
Cap LineCap
|
||||
Join LineJoin
|
||||
vertices []float64
|
||||
rewind []float64
|
||||
x, y, nx, ny float64
|
||||
}
|
||||
|
||||
// MoveTo implements the path builder interface.
|
||||
func (l *LineStroker) MoveTo(x, y float64) {
|
||||
l.x, l.y = x, y
|
||||
}
|
||||
|
||||
// LineTo implements the path builder interface.
|
||||
func (l *LineStroker) LineTo(x, y float64) {
|
||||
l.line(l.x, l.y, x, y)
|
||||
}
|
||||
|
||||
// LineJoin implements the path builder interface.
|
||||
func (l *LineStroker) LineJoin() {}
|
||||
|
||||
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
|
||||
dx := (x2 - x1)
|
||||
dy := (y2 - y1)
|
||||
d := vectorDistance(dx, dy)
|
||||
if d != 0 {
|
||||
nx := dy * l.HalfLineWidth / d
|
||||
ny := -(dx * l.HalfLineWidth / d)
|
||||
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
|
||||
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements the path builder interface.
|
||||
func (l *LineStroker) Close() {
|
||||
if len(l.vertices) > 1 {
|
||||
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
|
||||
}
|
||||
}
|
||||
|
||||
// End implements the path builder interface.
|
||||
func (l *LineStroker) End() {
|
||||
if len(l.vertices) > 1 {
|
||||
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
|
||||
for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
|
||||
l.Flattener.LineTo(l.vertices[i], l.vertices[j])
|
||||
}
|
||||
}
|
||||
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
|
||||
l.Flattener.LineTo(l.rewind[i], l.rewind[j])
|
||||
}
|
||||
if len(l.vertices) > 1 {
|
||||
l.Flattener.LineTo(l.vertices[0], l.vertices[1])
|
||||
}
|
||||
l.Flattener.End()
|
||||
// reinit vertices
|
||||
l.vertices = l.vertices[0:0]
|
||||
l.rewind = l.rewind[0:0]
|
||||
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
|
||||
|
||||
}
|
||||
|
||||
func (l *LineStroker) appendVertex(vertices ...float64) {
|
||||
s := len(vertices) / 2
|
||||
l.vertices = append(l.vertices, vertices[:s]...)
|
||||
l.rewind = append(l.rewind, vertices[s:]...)
|
||||
}
|
||||
67
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/text.go
generated
vendored
Normal file
67
src/server/vendor/github.com/wcharczuk/go-chart/v2/drawing/text.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package drawing
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
||||
func DrawContour(path PathBuilder, ps []truetype.Point, dx, dy float64) {
|
||||
if len(ps) == 0 {
|
||||
return
|
||||
}
|
||||
startX, startY := pointToF64Point(ps[0])
|
||||
path.MoveTo(startX+dx, startY+dy)
|
||||
q0X, q0Y, on0 := startX, startY, true
|
||||
for _, p := range ps[1:] {
|
||||
qX, qY := pointToF64Point(p)
|
||||
on := p.Flags&0x01 != 0
|
||||
if on {
|
||||
if on0 {
|
||||
path.LineTo(qX+dx, qY+dy)
|
||||
} else {
|
||||
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
|
||||
}
|
||||
} else if !on0 {
|
||||
midX := (q0X + qX) / 2
|
||||
midY := (q0Y + qY) / 2
|
||||
path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
|
||||
}
|
||||
q0X, q0Y, on0 = qX, qY, on
|
||||
}
|
||||
// Close the curve.
|
||||
if on0 {
|
||||
path.LineTo(startX+dx, startY+dy)
|
||||
} else {
|
||||
path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
|
||||
}
|
||||
}
|
||||
|
||||
// FontExtents contains font metric information.
|
||||
type FontExtents struct {
|
||||
// Ascent is the distance that the text
|
||||
// extends above the baseline.
|
||||
Ascent float64
|
||||
|
||||
// Descent is the distance that the text
|
||||
// extends below the baseline. The descent
|
||||
// is given as a negative value.
|
||||
Descent float64
|
||||
|
||||
// Height is the distance from the lowest
|
||||
// descending point to the highest ascending
|
||||
// point.
|
||||
Height float64
|
||||
}
|
||||
|
||||
// Extents returns the FontExtents for a font.
|
||||
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
|
||||
func Extents(font *truetype.Font, size float64) FontExtents {
|
||||
bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
|
||||
scale := size / float64(font.FUnitsPerEm())
|
||||
return FontExtents{
|
||||
Ascent: float64(bounds.Max.Y) * scale,
|
||||
Descent: float64(bounds.Min.Y) * scale,
|
||||
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user