Add response time badge and chart

This commit is contained in:
TwinProduction
2021-08-21 17:38:23 -04:00
committed by Chris
parent bab69478dd
commit 470e3a3ebc
140 changed files with 26995 additions and 106 deletions

19
vendor/github.com/wcharczuk/go-chart/v2/.gitignore generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/COVERAGE generated vendored Normal file
View File

@ -0,0 +1 @@
29.02

21
vendor/github.com/wcharczuk/go-chart/v2/LICENSE generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/Makefile generated vendored Normal file
View File

@ -0,0 +1,10 @@
all: new-install test
new-install:
@go get -v -u ./...
generate:
@go generate ./...
test:
@go test ./...

View 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
vendor/github.com/wcharczuk/go-chart/v2/README.md generated vendored Normal file
View File

@ -0,0 +1,95 @@
go-chart
========
[![CircleCI](https://circleci.com/gh/wcharczuk/go-chart.svg?style=svg)](https://circleci.com/gh/wcharczuk/go-chart) [![Go Report Card](https://goreportcard.com/badge/github.com/wcharczuk/go-chart)](https://goreportcard.com/report/github.com/wcharczuk/go-chart)
Package `chart` is a very simple golang native charting library that supports timeseries and continuous line charts.
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:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/tvix_ltm.png)
Single axis:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/goog_ltm.png)
Two axis:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/two_axis.png)
# Other Chart Types
Pie Chart:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/pie_chart.png)
The code for this chart can be found in `examples/pie_chart/main.go`.
Stacked Bar:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/_images/stacked_bar.png)
The code for this chart can be found in `examples/stacked_bar/main.go`.
# Code Examples
Actual chart configurations and examples can be found in the `./examples/` directory. They are 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.

View 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
vendor/github.com/wcharczuk/go-chart/v2/array.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/axis.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/bar_chart.go generated vendored Normal file
View 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
}

View 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
}

View 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
vendor/github.com/wcharczuk/go-chart/v2/box.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/chart.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/colors.go generated vendored Normal file
View 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)
}

View 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
}

View 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)))
}

View 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
vendor/github.com/wcharczuk/go-chart/v2/defaults.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/donut_chart.go generated vendored Normal file
View 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
vendor/github.com/wcharczuk/go-chart/v2/draw.go generated vendored Normal file
View 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()
}
}

View 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).

View 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)
}

View File

@ -0,0 +1,6 @@
package drawing
const (
// DefaultDPI is the default image DPI.
DefaultDPI = 96.0
)

View 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)
}
}

View 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
}

View 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()
}
}

View 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
}

View 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
}

View 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() {}

View 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)
}

View 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
)

View 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
}
}
}

View 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
}

View 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
vendor/github.com/wcharczuk/go-chart/v2/drawing/path.go generated vendored Normal file
View 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
}

View 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)
}

View 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
}
}

View 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:]...)
}

View 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,
}
}

View File

@ -0,0 +1,39 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package drawing
// Transformer apply the Matrix transformation tr
type Transformer struct {
Tr Matrix
Flattener Flattener
}
// MoveTo implements the path builder interface.
func (t Transformer) MoveTo(x, y float64) {
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
t.Flattener.MoveTo(u, v)
}
// LineTo implements the path builder interface.
func (t Transformer) LineTo(x, y float64) {
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
t.Flattener.LineTo(u, v)
}
// LineJoin implements the path builder interface.
func (t Transformer) LineJoin() {
t.Flattener.LineJoin()
}
// Close implements the path builder interface.
func (t Transformer) Close() {
t.Flattener.Close()
}
// End implements the path builder interface.
func (t Transformer) End() {
t.Flattener.End()
}

View File

@ -0,0 +1,68 @@
package drawing
import (
"math"
"golang.org/x/image/math/fixed"
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
)
// PixelsToPoints returns the points for a given number of pixels at a DPI.
func PixelsToPoints(dpi, pixels float64) (points float64) {
points = (pixels * 72.0) / dpi
return
}
// PointsToPixels returns the pixels for a given number of points at a DPI.
func PointsToPixels(dpi, points float64) (pixels float64) {
pixels = (points * dpi) / 72.0
return
}
func abs(i int) int {
if i < 0 {
return -i
}
return i
}
func distance(x1, y1, x2, y2 float64) float64 {
return vectorDistance(x2-x1, y2-y1)
}
func vectorDistance(dx, dy float64) float64 {
return float64(math.Sqrt(dx*dx + dy*dy))
}
func toFtCap(c LineCap) raster.Capper {
switch c {
case RoundCap:
return raster.RoundCapper
case ButtCap:
return raster.ButtCapper
case SquareCap:
return raster.SquareCapper
}
return raster.RoundCapper
}
func toFtJoin(j LineJoin) raster.Joiner {
switch j {
case RoundJoin:
return raster.RoundJoiner
case BevelJoin:
return raster.BevelJoiner
}
return raster.RoundJoiner
}
func pointToF64Point(p truetype.Point) (x, y float64) {
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
}
func fUnitsToFloat64(x fixed.Int26_6) float64 {
scaled := x << 2
return float64(scaled/256) + float64(scaled%256)/256.0
}

131
vendor/github.com/wcharczuk/go-chart/v2/ema_series.go generated vendored Normal file
View File

@ -0,0 +1,131 @@
package chart
import "fmt"
const (
// DefaultEMAPeriod is the default EMA period used in the sigma calculation.
DefaultEMAPeriod = 12
)
// Interface Assertions.
var (
_ Series = (*EMASeries)(nil)
_ FirstValuesProvider = (*EMASeries)(nil)
_ LastValuesProvider = (*EMASeries)(nil)
)
// EMASeries is a computed series.
type EMASeries struct {
Name string
Style Style
YAxis YAxisType
Period int
InnerSeries ValuesProvider
cache []float64
}
// GetName returns the name of the time series.
func (ema EMASeries) GetName() string {
return ema.Name
}
// GetStyle returns the line style.
func (ema EMASeries) GetStyle() Style {
return ema.Style
}
// GetYAxis returns which YAxis the series draws on.
func (ema EMASeries) GetYAxis() YAxisType {
return ema.YAxis
}
// GetPeriod returns the window size.
func (ema EMASeries) GetPeriod() int {
if ema.Period == 0 {
return DefaultEMAPeriod
}
return ema.Period
}
// Len returns the number of elements in the series.
func (ema EMASeries) Len() int {
return ema.InnerSeries.Len()
}
// GetSigma returns the smoothing factor for the serise.
func (ema EMASeries) GetSigma() float64 {
return 2.0 / (float64(ema.GetPeriod()) + 1)
}
// GetValues gets a value at a given index.
func (ema *EMASeries) GetValues(index int) (x, y float64) {
if ema.InnerSeries == nil {
return
}
if len(ema.cache) == 0 {
ema.ensureCachedValues()
}
vx, _ := ema.InnerSeries.GetValues(index)
x = vx
y = ema.cache[index]
return
}
// GetFirstValues computes the first moving average value.
func (ema *EMASeries) GetFirstValues() (x, y float64) {
if ema.InnerSeries == nil {
return
}
if len(ema.cache) == 0 {
ema.ensureCachedValues()
}
x, _ = ema.InnerSeries.GetValues(0)
y = ema.cache[0]
return
}
// GetLastValues computes the last moving average value but walking back window size samples,
// and recomputing the last moving average chunk.
func (ema *EMASeries) GetLastValues() (x, y float64) {
if ema.InnerSeries == nil {
return
}
if len(ema.cache) == 0 {
ema.ensureCachedValues()
}
lastIndex := ema.InnerSeries.Len() - 1
x, _ = ema.InnerSeries.GetValues(lastIndex)
y = ema.cache[lastIndex]
return
}
func (ema *EMASeries) ensureCachedValues() {
seriesLength := ema.InnerSeries.Len()
ema.cache = make([]float64, seriesLength)
sigma := ema.GetSigma()
for x := 0; x < seriesLength; x++ {
_, y := ema.InnerSeries.GetValues(x)
if x == 0 {
ema.cache[x] = y
continue
}
previousEMA := ema.cache[x-1]
ema.cache[x] = ((y - previousEMA) * sigma) + previousEMA
}
}
// Render renders the series.
func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ema.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ema)
}
// Validate validates the series.
func (ema *EMASeries) Validate() error {
if ema.InnerSeries == nil {
return fmt.Errorf("ema series requires InnerSeries to be set")
}
return nil
}

49
vendor/github.com/wcharczuk/go-chart/v2/fileutil.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package chart
import (
"bufio"
"io"
"os"
)
// ReadLines reads a file and calls the handler for each line.
func ReadLines(filePath string, handler func(string) error) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
err = handler(line)
if err != nil {
return err
}
}
return nil
}
// ReadChunks reads a file in `chunkSize` pieces, dispatched to the handler.
func ReadChunks(filePath string, chunkSize int, handler func([]byte) error) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
chunk := make([]byte, chunkSize)
for {
readBytes, err := f.Read(chunk)
if err == io.EOF {
break
}
readData := chunk[:readBytes]
err = handler(readData)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,37 @@
package chart
import "fmt"
// FirstValueAnnotation returns an annotation series of just the first value of a value provider as an annotation.
func FirstValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries {
var vf ValueFormatter
if len(vfs) > 0 {
vf = vfs[0]
} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped {
_, vf = typed.GetValueFormatters()
} else {
vf = FloatValueFormatter
}
var firstValue Value2
if typed, isTyped := innerSeries.(FirstValuesProvider); isTyped {
firstValue.XValue, firstValue.YValue = typed.GetFirstValues()
firstValue.Label = vf(firstValue.YValue)
} else {
firstValue.XValue, firstValue.YValue = innerSeries.GetValues(0)
firstValue.Label = vf(firstValue.YValue)
}
var seriesName string
var seriesStyle Style
if typed, isTyped := innerSeries.(Series); isTyped {
seriesName = fmt.Sprintf("%s - First Value", typed.GetName())
seriesStyle = typed.GetStyle()
}
return AnnotationSeries{
Name: seriesName,
Style: seriesStyle,
Annotations: []Value2{firstValue},
}
}

29
vendor/github.com/wcharczuk/go-chart/v2/font.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package chart
import (
"sync"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/roboto"
)
var (
_defaultFontLock sync.Mutex
_defaultFont *truetype.Font
)
// GetDefaultFont returns the default font (Roboto-Medium).
func GetDefaultFont() (*truetype.Font, error) {
if _defaultFont == nil {
_defaultFontLock.Lock()
defer _defaultFontLock.Unlock()
if _defaultFont == nil {
font, err := truetype.Parse(roboto.Roboto)
if err != nil {
return nil, err
}
_defaultFont = font
}
}
return _defaultFont, nil
}

8
vendor/github.com/wcharczuk/go-chart/v2/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/wcharczuk/go-chart/v2
go 1.15
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
)

5
vendor/github.com/wcharczuk/go-chart/v2/go.sum generated vendored Normal file
View File

@ -0,0 +1,5 @@
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=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

72
vendor/github.com/wcharczuk/go-chart/v2/grid_line.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package chart
// GridLineProvider is a type that provides grid lines.
type GridLineProvider interface {
GetGridLines(ticks []Tick, isVertical bool, majorStyle, minorStyle Style) []GridLine
}
// GridLine is a line on a graph canvas.
type GridLine struct {
IsMinor bool
Style Style
Value float64
}
// Major returns if the gridline is a `major` line.
func (gl GridLine) Major() bool {
return !gl.IsMinor
}
// Minor returns if the gridline is a `minor` line.
func (gl GridLine) Minor() bool {
return gl.IsMinor
}
// Render renders the gridline
func (gl GridLine) Render(r Renderer, canvasBox Box, ra Range, isVertical bool, defaults Style) {
r.SetStrokeColor(gl.Style.GetStrokeColor(defaults.GetStrokeColor()))
r.SetStrokeWidth(gl.Style.GetStrokeWidth(defaults.GetStrokeWidth()))
r.SetStrokeDashArray(gl.Style.GetStrokeDashArray(defaults.GetStrokeDashArray()))
if isVertical {
lineLeft := canvasBox.Left + ra.Translate(gl.Value)
lineBottom := canvasBox.Bottom
lineTop := canvasBox.Top
r.MoveTo(lineLeft, lineBottom)
r.LineTo(lineLeft, lineTop)
r.Stroke()
} else {
lineLeft := canvasBox.Left
lineRight := canvasBox.Right
lineHeight := canvasBox.Bottom - ra.Translate(gl.Value)
r.MoveTo(lineLeft, lineHeight)
r.LineTo(lineRight, lineHeight)
r.Stroke()
}
}
// GenerateGridLines generates grid lines.
func GenerateGridLines(ticks []Tick, majorStyle, minorStyle Style) []GridLine {
var gl []GridLine
isMinor := false
if len(ticks) < 3 {
return gl
}
for _, t := range ticks[1 : len(ticks)-1] {
s := majorStyle
if isMinor {
s = minorStyle
}
gl = append(gl, GridLine{
Style: s,
IsMinor: isMinor,
Value: t.Value,
})
isMinor = !isMinor
}
return gl
}

View File

@ -0,0 +1,67 @@
package chart
import "fmt"
// HistogramSeries is a special type of series that draws as a histogram.
// Some peculiarities; it will always be lower bounded at 0 (at the very least).
// This may alter ranges a bit and generally you want to put a histogram series on it's own y-axis.
type HistogramSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
}
// GetName implements Series.GetName.
func (hs HistogramSeries) GetName() string {
return hs.Name
}
// GetStyle implements Series.GetStyle.
func (hs HistogramSeries) GetStyle() Style {
return hs.Style
}
// GetYAxis returns which yaxis the series is mapped to.
func (hs HistogramSeries) GetYAxis() YAxisType {
return hs.YAxis
}
// Len implements BoundedValuesProvider.Len.
func (hs HistogramSeries) Len() int {
return hs.InnerSeries.Len()
}
// GetValues implements ValuesProvider.GetValues.
func (hs HistogramSeries) GetValues(index int) (x, y float64) {
return hs.InnerSeries.GetValues(index)
}
// GetBoundedValues implements BoundedValuesProvider.GetBoundedValue
func (hs HistogramSeries) GetBoundedValues(index int) (x, y1, y2 float64) {
vx, vy := hs.InnerSeries.GetValues(index)
x = vx
if vy > 0 {
y1 = vy
return
}
y2 = vy
return
}
// Render implements Series.Render.
func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := hs.Style.InheritFrom(defaults)
Draw.HistogramSeries(r, canvasBox, xrange, yrange, style, hs)
}
// Validate validates the series.
func (hs HistogramSeries) Validate() error {
if hs.InnerSeries == nil {
return fmt.Errorf("histogram series requires InnerSeries to be set")
}
return nil
}

View File

@ -0,0 +1,42 @@
package chart
import (
"bytes"
"errors"
"image"
"image/png"
)
// RGBACollector is a render target for a chart.
type RGBACollector interface {
SetRGBA(i *image.RGBA)
}
// ImageWriter is a special type of io.Writer that produces a final image.
type ImageWriter struct {
rgba *image.RGBA
contents *bytes.Buffer
}
func (ir *ImageWriter) Write(buffer []byte) (int, error) {
if ir.contents == nil {
ir.contents = bytes.NewBuffer([]byte{})
}
return ir.contents.Write(buffer)
}
// SetRGBA sets a raw version of the image.
func (ir *ImageWriter) SetRGBA(i *image.RGBA) {
ir.rgba = i
}
// Image returns an *image.Image for the result.
func (ir *ImageWriter) Image() (image.Image, error) {
if ir.rgba != nil {
return ir.rgba, nil
}
if ir.contents != nil && ir.contents.Len() > 0 {
return png.Decode(ir.contents)
}
return nil, errors.New("no valid sources for image data, cannot continue")
}

33
vendor/github.com/wcharczuk/go-chart/v2/jet.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package chart
import "github.com/wcharczuk/go-chart/v2/drawing"
// Jet is a color map provider based on matlab's jet color map.
func Jet(v, vmin, vmax float64) drawing.Color {
c := drawing.Color{R: 0xff, G: 0xff, B: 0xff, A: 0xff} // white
var dv float64
if v < vmin {
v = vmin
}
if v > vmax {
v = vmax
}
dv = vmax - vmin
if v < (vmin + 0.25*dv) {
c.R = 0
c.G = drawing.ColorChannelFromFloat(4 * (v - vmin) / dv)
} else if v < (vmin + 0.5*dv) {
c.R = 0
c.B = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.25*dv-v)/dv)
} else if v < (vmin + 0.75*dv) {
c.R = drawing.ColorChannelFromFloat(4 * (v - vmin - 0.5*dv) / dv)
c.B = 0
} else {
c.G = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.75*dv-v)/dv)
c.B = 0
}
return c
}

View File

@ -0,0 +1,37 @@
package chart
import "fmt"
// LastValueAnnotationSeries returns an annotation series of just the last value of a value provider.
func LastValueAnnotationSeries(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries {
var vf ValueFormatter
if len(vfs) > 0 {
vf = vfs[0]
} else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped {
_, vf = typed.GetValueFormatters()
} else {
vf = FloatValueFormatter
}
var lastValue Value2
if typed, isTyped := innerSeries.(LastValuesProvider); isTyped {
lastValue.XValue, lastValue.YValue = typed.GetLastValues()
lastValue.Label = vf(lastValue.YValue)
} else {
lastValue.XValue, lastValue.YValue = innerSeries.GetValues(innerSeries.Len() - 1)
lastValue.Label = vf(lastValue.YValue)
}
var seriesName string
var seriesStyle Style
if typed, isTyped := innerSeries.(Series); isTyped {
seriesName = fmt.Sprintf("%s - Last Value", typed.GetName())
seriesStyle = typed.GetStyle()
}
return AnnotationSeries{
Name: seriesName,
Style: seriesStyle,
Annotations: []Value2{lastValue},
}
}

331
vendor/github.com/wcharczuk/go-chart/v2/legend.go generated vendored Normal file
View File

@ -0,0 +1,331 @@
package chart
import (
"github.com/wcharczuk/go-chart/v2/drawing"
)
// Legend returns a legend renderable function.
func Legend(c *Chart, userDefaults ...Style) Renderable {
return func(r Renderer, cb Box, chartDefaults Style) {
legendDefaults := Style{
FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor,
FontSize: 8.0,
StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth,
}
var legendStyle Style
if len(userDefaults) > 0 {
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
} else {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}
// DEFAULTS
legendPadding := Box{
Top: 5,
Left: 5,
Right: 5,
Bottom: 5,
}
lineTextGap := 5
lineLengthMinimum := 25
var labels []string
var lines []Style
for index, s := range c.Series {
if !s.GetStyle().Hidden {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}
}
}
legend := Box{
Top: cb.Top,
Left: cb.Left,
// bottom and right will be sized by the legend content + relevant padding.
}
legendContent := Box{
Top: legend.Top + legendPadding.Top,
Left: legend.Left + legendPadding.Left,
Right: legend.Left + legendPadding.Left,
Bottom: legend.Top + legendPadding.Top,
}
legendStyle.GetTextOptions().WriteToRenderer(r)
// measure
labelCount := 0
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
tb := r.MeasureText(labels[x])
if labelCount > 0 {
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}
legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Right = MaxInt(legendContent.Right, right)
labelCount++
}
}
legend = legend.Grow(legendContent)
legend.Right = legendContent.Right + legendPadding.Right
legend.Bottom = legendContent.Bottom + legendPadding.Bottom
Draw.Box(r, legend, legendStyle)
legendStyle.GetTextOptions().WriteToRenderer(r)
ycursor := legendContent.Top
tx := legendContent.Left
legendCount := 0
var label string
for x := 0; x < len(labels); x++ {
label = labels[x]
if len(label) > 0 {
if legendCount > 0 {
ycursor += DefaultMinimumTickVerticalSpacing
}
tb := r.MeasureText(label)
ty := ycursor + tb.Height()
r.Text(label, tx, ty)
th2 := tb.Height() >> 1
lx := tx + tb.Width() + lineTextGap
ly := ty - th2
lx2 := legendContent.Right - legendPadding.Right
r.SetStrokeColor(lines[x].GetStrokeColor())
r.SetStrokeWidth(lines[x].GetStrokeWidth())
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
r.MoveTo(lx, ly)
r.LineTo(lx2, ly)
r.Stroke()
ycursor += tb.Height()
legendCount++
}
}
}
}
// LegendThin is a legend that doesn't obscure the chart area.
func LegendThin(c *Chart, userDefaults ...Style) Renderable {
return func(r Renderer, cb Box, chartDefaults Style) {
legendDefaults := Style{
FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor,
FontSize: 8.0,
StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth,
Padding: Box{
Top: 2,
Left: 7,
Right: 7,
Bottom: 5,
},
}
var legendStyle Style
if len(userDefaults) > 0 {
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
} else {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
var labels []string
var lines []Style
for index, s := range c.Series {
if !s.GetStyle().Hidden {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}
}
}
var textHeight int
var textWidth int
var textBox Box
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
textBox = r.MeasureText(labels[x])
textHeight = MaxInt(textBox.Height(), textHeight)
textWidth = MaxInt(textBox.Width(), textWidth)
}
}
legendBoxHeight := textHeight + legendStyle.Padding.Top + legendStyle.Padding.Bottom
chartPadding := cb.Top
legendYMargin := (chartPadding - legendBoxHeight) >> 1
legendBox := Box{
Left: cb.Left,
Right: cb.Right,
Top: legendYMargin,
Bottom: legendYMargin + legendBoxHeight,
}
Draw.Box(r, legendBox, legendDefaults)
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
lineTextGap := 5
lineLengthMinimum := 25
tx := legendBox.Left + legendStyle.Padding.Left
ty := legendYMargin + legendStyle.Padding.Top + textHeight
var label string
var lx, ly int
th2 := textHeight >> 1
for index := range labels {
label = labels[index]
if len(label) > 0 {
textBox = r.MeasureText(label)
r.Text(label, tx, ty)
lx = tx + textBox.Width() + lineTextGap
ly = ty - th2
r.SetStrokeColor(lines[index].GetStrokeColor())
r.SetStrokeWidth(lines[index].GetStrokeWidth())
r.SetStrokeDashArray(lines[index].GetStrokeDashArray())
r.MoveTo(lx, ly)
r.LineTo(lx+lineLengthMinimum, ly)
r.Stroke()
tx += textBox.Width() + DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum
}
}
}
}
// LegendLeft is a legend that is designed for longer series lists.
func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
return func(r Renderer, cb Box, chartDefaults Style) {
legendDefaults := Style{
FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor,
FontSize: 8.0,
StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth,
}
var legendStyle Style
if len(userDefaults) > 0 {
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
} else {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}
// DEFAULTS
legendPadding := Box{
Top: 5,
Left: 5,
Right: 5,
Bottom: 5,
}
lineTextGap := 5
lineLengthMinimum := 25
var labels []string
var lines []Style
for index, s := range c.Series {
if !s.GetStyle().Hidden {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}
}
}
legend := Box{
Top: 5,
Left: 5,
// bottom and right will be sized by the legend content + relevant padding.
}
legendContent := Box{
Top: legend.Top + legendPadding.Top,
Left: legend.Left + legendPadding.Left,
Right: legend.Left + legendPadding.Left,
Bottom: legend.Top + legendPadding.Top,
}
legendStyle.GetTextOptions().WriteToRenderer(r)
// measure
labelCount := 0
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
tb := r.MeasureText(labels[x])
if labelCount > 0 {
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}
legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Right = MaxInt(legendContent.Right, right)
labelCount++
}
}
legend = legend.Grow(legendContent)
legend.Right = legendContent.Right + legendPadding.Right
legend.Bottom = legendContent.Bottom + legendPadding.Bottom
Draw.Box(r, legend, legendStyle)
legendStyle.GetTextOptions().WriteToRenderer(r)
ycursor := legendContent.Top
tx := legendContent.Left
legendCount := 0
var label string
for x := 0; x < len(labels); x++ {
label = labels[x]
if len(label) > 0 {
if legendCount > 0 {
ycursor += DefaultMinimumTickVerticalSpacing
}
tb := r.MeasureText(label)
ty := ycursor + tb.Height()
r.Text(label, tx, ty)
th2 := tb.Height() >> 1
lx := tx + tb.Width() + lineTextGap
ly := ty - th2
lx2 := legendContent.Right - legendPadding.Right
r.SetStrokeColor(lines[x].GetStrokeColor())
r.SetStrokeWidth(lines[x].GetStrokeWidth())
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
r.MoveTo(lx, ly)
r.LineTo(lx2, ly)
r.Stroke()
ycursor += tb.Height()
legendCount++
}
}
}
}

View File

@ -0,0 +1,42 @@
package chart
// LinearCoefficientProvider is a type that returns linear cofficients.
type LinearCoefficientProvider interface {
Coefficients() (m, b, stdev, avg float64)
}
// LinearCoefficients returns a fixed linear coefficient pair.
func LinearCoefficients(m, b float64) LinearCoefficientSet {
return LinearCoefficientSet{
M: m,
B: b,
}
}
// NormalizedLinearCoefficients returns a fixed linear coefficient pair.
func NormalizedLinearCoefficients(m, b, stdev, avg float64) LinearCoefficientSet {
return LinearCoefficientSet{
M: m,
B: b,
StdDev: stdev,
Avg: avg,
}
}
// LinearCoefficientSet is the m and b values for the linear equation in the form:
// y = (m*x) + b
type LinearCoefficientSet struct {
M float64
B float64
StdDev float64
Avg float64
}
// Coefficients returns the coefficients.
func (lcs LinearCoefficientSet) Coefficients() (m, b, stdev, avg float64) {
m = lcs.M
b = lcs.B
stdev = lcs.StdDev
avg = lcs.Avg
return
}

View File

@ -0,0 +1,187 @@
package chart
import (
"fmt"
)
// Interface Assertions.
var (
_ Series = (*LinearRegressionSeries)(nil)
_ FirstValuesProvider = (*LinearRegressionSeries)(nil)
_ LastValuesProvider = (*LinearRegressionSeries)(nil)
_ LinearCoefficientProvider = (*LinearRegressionSeries)(nil)
)
// LinearRegressionSeries is a series that plots the n-nearest neighbors
// linear regression for the values.
type LinearRegressionSeries struct {
Name string
Style Style
YAxis YAxisType
Limit int
Offset int
InnerSeries ValuesProvider
m float64
b float64
avgx float64
stddevx float64
}
// Coefficients returns the linear coefficients for the series.
func (lrs LinearRegressionSeries) Coefficients() (m, b, stdev, avg float64) {
if lrs.IsZero() {
lrs.computeCoefficients()
}
m = lrs.m
b = lrs.b
stdev = lrs.stddevx
avg = lrs.avgx
return
}
// GetName returns the name of the time series.
func (lrs LinearRegressionSeries) GetName() string {
return lrs.Name
}
// GetStyle returns the line style.
func (lrs LinearRegressionSeries) GetStyle() Style {
return lrs.Style
}
// GetYAxis returns which YAxis the series draws on.
func (lrs LinearRegressionSeries) GetYAxis() YAxisType {
return lrs.YAxis
}
// Len returns the number of elements in the series.
func (lrs LinearRegressionSeries) Len() int {
return MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
}
// GetLimit returns the window size.
func (lrs LinearRegressionSeries) GetLimit() int {
if lrs.Limit == 0 {
return lrs.InnerSeries.Len()
}
return lrs.Limit
}
// GetEndIndex returns the effective limit end.
func (lrs LinearRegressionSeries) GetEndIndex() int {
windowEnd := lrs.GetOffset() + lrs.GetLimit()
innerSeriesLastIndex := lrs.InnerSeries.Len() - 1
return MinInt(windowEnd, innerSeriesLastIndex)
}
// GetOffset returns the data offset.
func (lrs LinearRegressionSeries) GetOffset() int {
if lrs.Offset == 0 {
return 0
}
return lrs.Offset
}
// GetValues gets a value at a given index.
func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.IsZero() {
lrs.computeCoefficients()
}
offset := lrs.GetOffset()
effectiveIndex := MinInt(index+offset, lrs.InnerSeries.Len())
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
// GetFirstValues computes the first linear regression value.
func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.IsZero() {
lrs.computeCoefficients()
}
x, y = lrs.InnerSeries.GetValues(0)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
// GetLastValues computes the last linear regression value.
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.IsZero() {
lrs.computeCoefficients()
}
endIndex := lrs.GetEndIndex()
x, y = lrs.InnerSeries.GetValues(endIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
// Render renders the series.
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := lrs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
}
// Validate validates the series.
func (lrs *LinearRegressionSeries) Validate() error {
if lrs.InnerSeries == nil {
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}
return nil
}
// IsZero returns if we've computed the coefficients or not.
func (lrs *LinearRegressionSeries) IsZero() bool {
return lrs.m == 0 && lrs.b == 0
}
//
// internal helpers
//
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
return (xvalue - lrs.avgx) / lrs.stddevx
}
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
func (lrs *LinearRegressionSeries) computeCoefficients() {
startIndex := lrs.GetOffset()
endIndex := lrs.GetEndIndex()
p := float64(endIndex - startIndex)
xvalues := NewValueBufferWithCapacity(lrs.Len())
for index := startIndex; index < endIndex; index++ {
x, _ := lrs.InnerSeries.GetValues(index)
xvalues.Enqueue(x)
}
lrs.avgx = Seq{xvalues}.Average()
lrs.stddevx = Seq{xvalues}.StdDev()
var sumx, sumy, sumxx, sumxy float64
for index := startIndex; index < endIndex; index++ {
x, y := lrs.InnerSeries.GetValues(index)
x = lrs.normalize(x)
sumx += x
sumy += y
sumxx += x * x
sumxy += x * y
}
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
lrs.b = (sumy / p) - (lrs.m * sumx / p)
}

View File

@ -0,0 +1,73 @@
package chart
// LinearRange returns an array of values representing the range from start to end, incremented by 1.0.
func LinearRange(start, end float64) []float64 {
return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(1.0)}.Values()
}
// LinearRangeWithStep returns the array values of a linear seq with a given start, end and optional step.
func LinearRangeWithStep(start, end, step float64) []float64 {
return Seq{NewLinearSequence().WithStart(start).WithEnd(end).WithStep(step)}.Values()
}
// NewLinearSequence returns a new linear generator.
func NewLinearSequence() *LinearSeq {
return &LinearSeq{step: 1.0}
}
// LinearSeq is a stepwise generator.
type LinearSeq struct {
start float64
end float64
step float64
}
// Start returns the start value.
func (lg LinearSeq) Start() float64 {
return lg.start
}
// End returns the end value.
func (lg LinearSeq) End() float64 {
return lg.end
}
// Step returns the step value.
func (lg LinearSeq) Step() float64 {
return lg.step
}
// Len returns the number of elements in the seq.
func (lg LinearSeq) Len() int {
if lg.start < lg.end {
return int((lg.end-lg.start)/lg.step) + 1
}
return int((lg.start-lg.end)/lg.step) + 1
}
// GetValue returns the value at a given index.
func (lg LinearSeq) GetValue(index int) float64 {
fi := float64(index)
if lg.start < lg.end {
return lg.start + (fi * lg.step)
}
return lg.start - (fi * lg.step)
}
// WithStart sets the start and returns the linear generator.
func (lg *LinearSeq) WithStart(start float64) *LinearSeq {
lg.start = start
return lg
}
// WithEnd sets the end and returns the linear generator.
func (lg *LinearSeq) WithEnd(end float64) *LinearSeq {
lg.end = end
return lg
}
// WithStep sets the step and returns the linear generator.
func (lg *LinearSeq) WithStep(step float64) *LinearSeq {
lg.step = step
return lg
}

View File

@ -0,0 +1,119 @@
package chart
import (
"fmt"
)
// Interface Assertions.
var (
_ Series = (*LinearSeries)(nil)
_ FirstValuesProvider = (*LinearSeries)(nil)
_ LastValuesProvider = (*LinearSeries)(nil)
)
// LinearSeries is a series that plots a line in a given domain.
type LinearSeries struct {
Name string
Style Style
YAxis YAxisType
XValues []float64
InnerSeries LinearCoefficientProvider
m float64
b float64
stdev float64
avg float64
}
// GetName returns the name of the time series.
func (ls LinearSeries) GetName() string {
return ls.Name
}
// GetStyle returns the line style.
func (ls LinearSeries) GetStyle() Style {
return ls.Style
}
// GetYAxis returns which YAxis the series draws on.
func (ls LinearSeries) GetYAxis() YAxisType {
return ls.YAxis
}
// Len returns the number of elements in the series.
func (ls LinearSeries) Len() int {
return len(ls.XValues)
}
// GetEndIndex returns the effective limit end.
func (ls LinearSeries) GetEndIndex() int {
return len(ls.XValues) - 1
}
// GetValues gets a value at a given index.
func (ls *LinearSeries) GetValues(index int) (x, y float64) {
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
return
}
if ls.IsZero() {
ls.computeCoefficients()
}
x = ls.XValues[index]
y = (ls.m * ls.normalize(x)) + ls.b
return
}
// GetFirstValues computes the first linear regression value.
func (ls *LinearSeries) GetFirstValues() (x, y float64) {
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
return
}
if ls.IsZero() {
ls.computeCoefficients()
}
x, y = ls.GetValues(0)
return
}
// GetLastValues computes the last linear regression value.
func (ls *LinearSeries) GetLastValues() (x, y float64) {
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
return
}
if ls.IsZero() {
ls.computeCoefficients()
}
x, y = ls.GetValues(ls.GetEndIndex())
return
}
// Render renders the series.
func (ls *LinearSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
Draw.LineSeries(r, canvasBox, xrange, yrange, ls.Style.InheritFrom(defaults), ls)
}
// Validate validates the series.
func (ls LinearSeries) Validate() error {
if ls.InnerSeries == nil {
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}
return nil
}
// IsZero returns if the linear series has computed coefficients or not.
func (ls LinearSeries) IsZero() bool {
return ls.m == 0 && ls.b == 0
}
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
func (ls *LinearSeries) computeCoefficients() {
ls.m, ls.b, ls.stdev, ls.avg = ls.InnerSeries.Coefficients()
}
func (ls *LinearSeries) normalize(xvalue float64) float64 {
if ls.avg > 0 && ls.stdev > 0 {
return (xvalue - ls.avg) / ls.stdev
}
return xvalue
}

148
vendor/github.com/wcharczuk/go-chart/v2/logger.go generated vendored Normal file
View File

@ -0,0 +1,148 @@
package chart
import (
"fmt"
"io"
"os"
"time"
)
var (
_ Logger = (*StdoutLogger)(nil)
)
// NewLogger returns a new logger.
func NewLogger(options ...LoggerOption) Logger {
stl := &StdoutLogger{
TimeFormat: time.RFC3339Nano,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
for _, option := range options {
option(stl)
}
return stl
}
// Logger is a type that implements the logging interface.
type Logger interface {
Info(...interface{})
Infof(string, ...interface{})
Debug(...interface{})
Debugf(string, ...interface{})
Err(error)
FatalErr(error)
Error(...interface{})
Errorf(string, ...interface{})
}
// Info logs an info message if the logger is set.
func Info(log Logger, arguments ...interface{}) {
if log == nil {
return
}
log.Info(arguments...)
}
// Infof logs an info message if the logger is set.
func Infof(log Logger, format string, arguments ...interface{}) {
if log == nil {
return
}
log.Infof(format, arguments...)
}
// Debug logs an debug message if the logger is set.
func Debug(log Logger, arguments ...interface{}) {
if log == nil {
return
}
log.Debug(arguments...)
}
// Debugf logs an debug message if the logger is set.
func Debugf(log Logger, format string, arguments ...interface{}) {
if log == nil {
return
}
log.Debugf(format, arguments...)
}
// LoggerOption mutates a stdout logger.
type LoggerOption = func(*StdoutLogger)
//OptLoggerStdout sets the Stdout writer.
func OptLoggerStdout(wr io.Writer) LoggerOption {
return func(stl *StdoutLogger) {
stl.Stdout = wr
}
}
// OptLoggerStderr sets the Stdout writer.
func OptLoggerStderr(wr io.Writer) LoggerOption {
return func(stl *StdoutLogger) {
stl.Stderr = wr
}
}
// StdoutLogger is a basic logger.
type StdoutLogger struct {
TimeFormat string
Stdout io.Writer
Stderr io.Writer
}
// Info writes an info message.
func (l *StdoutLogger) Info(arguments ...interface{}) {
l.Println(append([]interface{}{"[INFO]"}, arguments...)...)
}
// Infof writes an info message.
func (l *StdoutLogger) Infof(format string, arguments ...interface{}) {
l.Println(append([]interface{}{"[INFO]"}, fmt.Sprintf(format, arguments...))...)
}
// Debug writes an debug message.
func (l *StdoutLogger) Debug(arguments ...interface{}) {
l.Println(append([]interface{}{"[DEBUG]"}, arguments...)...)
}
// Debugf writes an debug message.
func (l *StdoutLogger) Debugf(format string, arguments ...interface{}) {
l.Println(append([]interface{}{"[DEBUG]"}, fmt.Sprintf(format, arguments...))...)
}
// Error writes an error message.
func (l *StdoutLogger) Error(arguments ...interface{}) {
l.Println(append([]interface{}{"[ERROR]"}, arguments...)...)
}
// Errorf writes an error message.
func (l *StdoutLogger) Errorf(format string, arguments ...interface{}) {
l.Println(append([]interface{}{"[ERROR]"}, fmt.Sprintf(format, arguments...))...)
}
// Err writes an error message.
func (l *StdoutLogger) Err(err error) {
if err != nil {
l.Println(append([]interface{}{"[ERROR]"}, err.Error())...)
}
}
// FatalErr writes an error message and exits.
func (l *StdoutLogger) FatalErr(err error) {
if err != nil {
l.Println(append([]interface{}{"[FATAL]"}, err.Error())...)
os.Exit(1)
}
}
// Println prints a new message.
func (l *StdoutLogger) Println(arguments ...interface{}) {
fmt.Fprintln(l.Stdout, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
}
// Errorln prints a new message.
func (l *StdoutLogger) Errorln(arguments ...interface{}) {
fmt.Fprintln(l.Stderr, append([]interface{}{time.Now().UTC().Format(l.TimeFormat)}, arguments...)...)
}

338
vendor/github.com/wcharczuk/go-chart/v2/macd_series.go generated vendored Normal file
View File

@ -0,0 +1,338 @@
package chart
import "fmt"
const (
// DefaultMACDPeriodPrimary is the long window.
DefaultMACDPeriodPrimary = 26
// DefaultMACDPeriodSecondary is the short window.
DefaultMACDPeriodSecondary = 12
// DefaultMACDSignalPeriod is the signal period to compute for the MACD.
DefaultMACDSignalPeriod = 9
)
// MACDSeries computes the difference between the MACD line and the MACD Signal line.
// It is used in technical analysis and gives a lagging indicator of momentum.
type MACDSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
PrimaryPeriod int
SecondaryPeriod int
SignalPeriod int
signal *MACDSignalSeries
macdl *MACDLineSeries
}
// Validate validates the series.
func (macd MACDSeries) Validate() error {
var err error
if macd.signal != nil {
err = macd.signal.Validate()
}
if err != nil {
return err
}
if macd.macdl != nil {
err = macd.macdl.Validate()
}
if err != nil {
return err
}
return nil
}
// GetPeriods returns the primary and secondary periods.
func (macd MACDSeries) GetPeriods() (w1, w2, sig int) {
if macd.PrimaryPeriod == 0 {
w1 = DefaultMACDPeriodPrimary
} else {
w1 = macd.PrimaryPeriod
}
if macd.SecondaryPeriod == 0 {
w2 = DefaultMACDPeriodSecondary
} else {
w2 = macd.SecondaryPeriod
}
if macd.SignalPeriod == 0 {
sig = DefaultMACDSignalPeriod
} else {
sig = macd.SignalPeriod
}
return
}
// GetName returns the name of the time series.
func (macd MACDSeries) GetName() string {
return macd.Name
}
// GetStyle returns the line style.
func (macd MACDSeries) GetStyle() Style {
return macd.Style
}
// GetYAxis returns which YAxis the series draws on.
func (macd MACDSeries) GetYAxis() YAxisType {
return macd.YAxis
}
// Len returns the number of elements in the series.
func (macd MACDSeries) Len() int {
if macd.InnerSeries == nil {
return 0
}
return macd.InnerSeries.Len()
}
// GetValues gets a value at a given index. For MACD it is the signal value.
func (macd *MACDSeries) GetValues(index int) (x float64, y float64) {
if macd.InnerSeries == nil {
return
}
if macd.signal == nil || macd.macdl == nil {
macd.ensureChildSeries()
}
_, lv := macd.macdl.GetValues(index)
_, sv := macd.signal.GetValues(index)
x, _ = macd.InnerSeries.GetValues(index)
y = lv - sv
return
}
func (macd *MACDSeries) ensureChildSeries() {
w1, w2, sig := macd.GetPeriods()
macd.signal = &MACDSignalSeries{
InnerSeries: macd.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
SignalPeriod: sig,
}
macd.macdl = &MACDLineSeries{
InnerSeries: macd.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
}
}
// MACDSignalSeries computes the EMA of the MACDLineSeries.
type MACDSignalSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
PrimaryPeriod int
SecondaryPeriod int
SignalPeriod int
signal *EMASeries
}
// Validate validates the series.
func (macds MACDSignalSeries) Validate() error {
if macds.signal != nil {
return macds.signal.Validate()
}
return nil
}
// GetPeriods returns the primary and secondary periods.
func (macds MACDSignalSeries) GetPeriods() (w1, w2, sig int) {
if macds.PrimaryPeriod == 0 {
w1 = DefaultMACDPeriodPrimary
} else {
w1 = macds.PrimaryPeriod
}
if macds.SecondaryPeriod == 0 {
w2 = DefaultMACDPeriodSecondary
} else {
w2 = macds.SecondaryPeriod
}
if macds.SignalPeriod == 0 {
sig = DefaultMACDSignalPeriod
} else {
sig = macds.SignalPeriod
}
return
}
// GetName returns the name of the time series.
func (macds MACDSignalSeries) GetName() string {
return macds.Name
}
// GetStyle returns the line style.
func (macds MACDSignalSeries) GetStyle() Style {
return macds.Style
}
// GetYAxis returns which YAxis the series draws on.
func (macds MACDSignalSeries) GetYAxis() YAxisType {
return macds.YAxis
}
// Len returns the number of elements in the series.
func (macds *MACDSignalSeries) Len() int {
if macds.InnerSeries == nil {
return 0
}
return macds.InnerSeries.Len()
}
// GetValues gets a value at a given index. For MACD it is the signal value.
func (macds *MACDSignalSeries) GetValues(index int) (x float64, y float64) {
if macds.InnerSeries == nil {
return
}
if macds.signal == nil {
macds.ensureSignal()
}
x, _ = macds.InnerSeries.GetValues(index)
_, y = macds.signal.GetValues(index)
return
}
func (macds *MACDSignalSeries) ensureSignal() {
w1, w2, sig := macds.GetPeriods()
macds.signal = &EMASeries{
InnerSeries: &MACDLineSeries{
InnerSeries: macds.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
},
Period: sig,
}
}
// Render renders the series.
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macds.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macds)
}
// MACDLineSeries is a series that computes the inner ema1-ema2 value as a series.
type MACDLineSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
PrimaryPeriod int
SecondaryPeriod int
ema1 *EMASeries
ema2 *EMASeries
Sigma float64
}
// Validate validates the series.
func (macdl MACDLineSeries) Validate() error {
var err error
if macdl.ema1 != nil {
err = macdl.ema1.Validate()
}
if err != nil {
return err
}
if macdl.ema2 != nil {
err = macdl.ema2.Validate()
}
if err != nil {
return err
}
if macdl.InnerSeries == nil {
return fmt.Errorf("MACDLineSeries: must provide an inner series")
}
return nil
}
// GetName returns the name of the time series.
func (macdl MACDLineSeries) GetName() string {
return macdl.Name
}
// GetStyle returns the line style.
func (macdl MACDLineSeries) GetStyle() Style {
return macdl.Style
}
// GetYAxis returns which YAxis the series draws on.
func (macdl MACDLineSeries) GetYAxis() YAxisType {
return macdl.YAxis
}
// GetPeriods returns the primary and secondary periods.
func (macdl MACDLineSeries) GetPeriods() (w1, w2 int) {
if macdl.PrimaryPeriod == 0 {
w1 = DefaultMACDPeriodPrimary
} else {
w1 = macdl.PrimaryPeriod
}
if macdl.SecondaryPeriod == 0 {
w2 = DefaultMACDPeriodSecondary
} else {
w2 = macdl.SecondaryPeriod
}
return
}
// Len returns the number of elements in the series.
func (macdl *MACDLineSeries) Len() int {
if macdl.InnerSeries == nil {
return 0
}
return macdl.InnerSeries.Len()
}
// GetValues gets a value at a given index. For MACD it is the signal value.
func (macdl *MACDLineSeries) GetValues(index int) (x float64, y float64) {
if macdl.InnerSeries == nil {
return
}
if macdl.ema1 == nil && macdl.ema2 == nil {
macdl.ensureEMASeries()
}
x, _ = macdl.InnerSeries.GetValues(index)
_, emav1 := macdl.ema1.GetValues(index)
_, emav2 := macdl.ema2.GetValues(index)
y = emav2 - emav1
return
}
func (macdl *MACDLineSeries) ensureEMASeries() {
w1, w2 := macdl.GetPeriods()
macdl.ema1 = &EMASeries{
InnerSeries: macdl.InnerSeries,
Period: w1,
}
macdl.ema2 = &EMASeries{
InnerSeries: macdl.InnerSeries,
Period: w2,
}
}
// Render renders the series.
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macdl.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl)
}

252
vendor/github.com/wcharczuk/go-chart/v2/mathutil.go generated vendored Normal file
View File

@ -0,0 +1,252 @@
package chart
import "math"
const (
_pi = math.Pi
_2pi = 2 * math.Pi
_3pi4 = (3 * math.Pi) / 4.0
_4pi3 = (4 * math.Pi) / 3.0
_3pi2 = (3 * math.Pi) / 2.0
_5pi4 = (5 * math.Pi) / 4.0
_7pi4 = (7 * math.Pi) / 4.0
_pi2 = math.Pi / 2.0
_pi4 = math.Pi / 4.0
_d2r = (math.Pi / 180.0)
_r2d = (180.0 / math.Pi)
)
// MinMax returns the minimum and maximum of a given set of values.
func MinMax(values ...float64) (min, max float64) {
if len(values) == 0 {
return
}
max = values[0]
min = values[0]
var value float64
for index := 1; index < len(values); index++ {
value = values[index]
if value < min {
min = value
}
if value > max {
max = value
}
}
return
}
// MinInt returns the minimum int.
func MinInt(values ...int) (min int) {
if len(values) == 0 {
return
}
min = values[0]
var value int
for index := 1; index < len(values); index++ {
value = values[index]
if value < min {
min = value
}
}
return
}
// MaxInt returns the maximum int.
func MaxInt(values ...int) (max int) {
if len(values) == 0 {
return
}
max = values[0]
var value int
for index := 1; index < len(values); index++ {
value = values[index]
if value > max {
max = value
}
}
return
}
// AbsInt returns the absolute value of an int.
func AbsInt(value int) int {
if value < 0 {
return -value
}
return value
}
// DegreesToRadians returns degrees as radians.
func DegreesToRadians(degrees float64) float64 {
return degrees * _d2r
}
// RadiansToDegrees translates a radian value to a degree value.
func RadiansToDegrees(value float64) float64 {
return math.Mod(value, _2pi) * _r2d
}
// PercentToRadians converts a normalized value (0,1) to radians.
func PercentToRadians(pct float64) float64 {
return DegreesToRadians(360.0 * pct)
}
// RadianAdd adds a delta to a base in radians.
func RadianAdd(base, delta float64) float64 {
value := base + delta
if value > _2pi {
return math.Mod(value, _2pi)
} else if value < 0 {
return math.Mod(_2pi+value, _2pi)
}
return value
}
// DegreesAdd adds a delta to a base in radians.
func DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
value := baseDegrees + deltaDegrees
if value > _2pi {
return math.Mod(value, 360.0)
} else if value < 0 {
return math.Mod(360.0+value, 360.0)
}
return value
}
// DegreesToCompass returns the degree value in compass / clock orientation.
func DegreesToCompass(deg float64) float64 {
return DegreesAdd(deg, -90.0)
}
// CirclePoint returns the absolute position of a circle diameter point given
// by the radius and the theta.
func CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
x = cx + int(radius*math.Sin(thetaRadians))
y = cy - int(radius*math.Cos(thetaRadians))
return
}
// RotateCoordinate rotates a coordinate around a given center by a theta in radians.
func RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
tempX, tempY := float64(x-cx), float64(y-cy)
rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)
rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)
rx = int(rotatedX) + cx
ry = int(rotatedY) + cy
return
}
// RoundUp rounds up to a given roundTo value.
func RoundUp(value, roundTo float64) float64 {
if roundTo < 0.000000000000001 {
return value
}
d1 := math.Ceil(value / roundTo)
return d1 * roundTo
}
// RoundDown rounds down to a given roundTo value.
func RoundDown(value, roundTo float64) float64 {
if roundTo < 0.000000000000001 {
return value
}
d1 := math.Floor(value / roundTo)
return d1 * roundTo
}
// Normalize returns a set of numbers on the interval [0,1] for a given set of inputs.
// An example: 4,3,2,1 => 0.4, 0.3, 0.2, 0.1
// Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc.
func Normalize(values ...float64) []float64 {
var total float64
for _, v := range values {
total += v
}
output := make([]float64, len(values))
for x, v := range values {
output[x] = RoundDown(v/total, 0.0001)
}
return output
}
// Mean returns the mean of a set of values
func Mean(values ...float64) float64 {
return Sum(values...) / float64(len(values))
}
// MeanInt returns the mean of a set of integer values.
func MeanInt(values ...int) int {
return SumInt(values...) / len(values)
}
// Sum sums a set of values.
func Sum(values ...float64) float64 {
var total float64
for _, v := range values {
total += v
}
return total
}
// SumInt sums a set of values.
func SumInt(values ...int) int {
var total int
for _, v := range values {
total += v
}
return total
}
// PercentDifference computes the percentage difference between two values.
// The formula is (v2-v1)/v1.
func PercentDifference(v1, v2 float64) float64 {
if v1 == 0 {
return 0
}
return (v2 - v1) / v1
}
// GetRoundToForDelta returns a `roundTo` value for a given delta.
func GetRoundToForDelta(delta float64) float64 {
startingDeltaBound := math.Pow(10.0, 10.0)
for cursor := startingDeltaBound; cursor > 0; cursor /= 10.0 {
if delta > cursor {
return cursor / 10.0
}
}
return 0.0
}
// RoundPlaces rounds an input to a given places.
func RoundPlaces(input float64, places int) (rounded float64) {
if math.IsNaN(input) {
return 0.0
}
sign := 1.0
if input < 0 {
sign = -1
input *= -1
}
precision := math.Pow(10, float64(places))
digit := input * precision
_, decimal := math.Modf(digit)
if decimal >= 0.5 {
rounded = math.Ceil(digit)
} else {
rounded = math.Floor(digit)
}
return rounded / precision * sign
}
func f64i(value float64) int {
r := RoundPlaces(value, 0)
return int(r)
}

View File

@ -0,0 +1,592 @@
package matrix
import (
"bytes"
"errors"
"fmt"
"math"
)
const (
// DefaultEpsilon represents the minimum precision for matrix math operations.
DefaultEpsilon = 0.000001
)
var (
// ErrDimensionMismatch is a typical error.
ErrDimensionMismatch = errors.New("dimension mismatch")
// ErrSingularValue is a typical error.
ErrSingularValue = errors.New("singular value")
)
// New returns a new matrix.
func New(rows, cols int, values ...float64) *Matrix {
if len(values) == 0 {
return &Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: make([]float64, rows*cols),
}
}
elems := make([]float64, rows*cols)
copy(elems, values)
return &Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: elems,
}
}
// Identity returns the identity matrix of a given order.
func Identity(order int) *Matrix {
m := New(order, order)
for i := 0; i < order; i++ {
m.Set(i, i, 1)
}
return m
}
// Zero returns a matrix of a given size zeroed.
func Zero(rows, cols int) *Matrix {
return New(rows, cols)
}
// Ones returns an matrix of ones.
func Ones(rows, cols int) *Matrix {
ones := make([]float64, rows*cols)
for i := 0; i < (rows * cols); i++ {
ones[i] = 1
}
return &Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: ones,
}
}
// Eye returns the eye matrix.
func Eye(n int) *Matrix {
m := Zero(n, n)
for i := 0; i < len(m.elements); i += n + 1 {
m.elements[i] = 1
}
return m
}
// NewFromArrays creates a matrix from a jagged array set.
func NewFromArrays(a [][]float64) *Matrix {
rows := len(a)
if rows == 0 {
return nil
}
cols := len(a[0])
m := New(rows, cols)
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
m.Set(row, col, a[row][col])
}
}
return m
}
// Matrix represents a 2d dense array of floats.
type Matrix struct {
epsilon float64
elements []float64
stride int
}
// String returns a string representation of the matrix.
func (m *Matrix) String() string {
buffer := bytes.NewBuffer(nil)
rows, cols := m.Size()
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
buffer.WriteString(f64s(m.Get(row, col)))
buffer.WriteRune(' ')
}
buffer.WriteRune('\n')
}
return buffer.String()
}
// Epsilon returns the maximum precision for math operations.
func (m *Matrix) Epsilon() float64 {
return m.epsilon
}
// WithEpsilon sets the epsilon on the matrix and returns a reference to the matrix.
func (m *Matrix) WithEpsilon(epsilon float64) *Matrix {
m.epsilon = epsilon
return m
}
// Each applies the action to each element of the matrix in
// rows => cols order.
func (m *Matrix) Each(action func(row, col int, value float64)) {
rows, cols := m.Size()
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
action(row, col, m.Get(row, col))
}
}
}
// Round rounds all the values in a matrix to it epsilon,
// returning a reference to the original
func (m *Matrix) Round() *Matrix {
rows, cols := m.Size()
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
m.Set(row, col, roundToEpsilon(m.Get(row, col), m.epsilon))
}
}
return m
}
// Arrays returns the matrix as a two dimensional jagged array.
func (m *Matrix) Arrays() [][]float64 {
rows, cols := m.Size()
a := make([][]float64, rows)
for row := 0; row < rows; row++ {
a[row] = make([]float64, cols)
for col := 0; col < cols; col++ {
a[row][col] = m.Get(row, col)
}
}
return a
}
// Size returns the dimensions of the matrix.
func (m *Matrix) Size() (rows, cols int) {
rows = len(m.elements) / m.stride
cols = m.stride
return
}
// IsSquare returns if the row count is equal to the column count.
func (m *Matrix) IsSquare() bool {
return m.stride == (len(m.elements) / m.stride)
}
// IsSymmetric returns if the matrix is symmetric about its diagonal.
func (m *Matrix) IsSymmetric() bool {
rows, cols := m.Size()
if rows != cols {
return false
}
for i := 0; i < rows; i++ {
for j := 0; j < i; j++ {
if m.Get(i, j) != m.Get(j, i) {
return false
}
}
}
return true
}
// Get returns the element at the given row, col.
func (m *Matrix) Get(row, col int) float64 {
index := (m.stride * row) + col
return m.elements[index]
}
// Set sets a value.
func (m *Matrix) Set(row, col int, val float64) {
index := (m.stride * row) + col
m.elements[index] = val
}
// Col returns a column of the matrix as a vector.
func (m *Matrix) Col(col int) Vector {
rows, _ := m.Size()
values := make([]float64, rows)
for row := 0; row < rows; row++ {
values[row] = m.Get(row, col)
}
return Vector(values)
}
// Row returns a row of the matrix as a vector.
func (m *Matrix) Row(row int) Vector {
_, cols := m.Size()
values := make([]float64, cols)
for col := 0; col < cols; col++ {
values[col] = m.Get(row, col)
}
return Vector(values)
}
// SubMatrix returns a sub matrix from a given outer matrix.
func (m *Matrix) SubMatrix(i, j, rows, cols int) *Matrix {
return &Matrix{
elements: m.elements[i*m.stride+j : i*m.stride+j+(rows-1)*m.stride+cols],
stride: m.stride,
epsilon: m.epsilon,
}
}
// ScaleRow applies a scale to an entire row.
func (m *Matrix) ScaleRow(row int, scale float64) {
startIndex := row * m.stride
for i := startIndex; i < m.stride; i++ {
m.elements[i] = m.elements[i] * scale
}
}
func (m *Matrix) scaleAddRow(rd int, rs int, f float64) {
indexd := rd * m.stride
indexs := rs * m.stride
for col := 0; col < m.stride; col++ {
m.elements[indexd] += f * m.elements[indexs]
indexd++
indexs++
}
}
// SwapRows swaps a row in the matrix in place.
func (m *Matrix) SwapRows(i, j int) {
var vi, vj float64
for col := 0; col < m.stride; col++ {
vi = m.Get(i, col)
vj = m.Get(j, col)
m.Set(i, col, vj)
m.Set(j, col, vi)
}
}
// Augment concatenates two matrices about the horizontal.
func (m *Matrix) Augment(m2 *Matrix) (*Matrix, error) {
mr, mc := m.Size()
m2r, m2c := m2.Size()
if mr != m2r {
return nil, ErrDimensionMismatch
}
m3 := Zero(mr, mc+m2c)
for row := 0; row < mr; row++ {
for col := 0; col < mc; col++ {
m3.Set(row, col, m.Get(row, col))
}
for col := 0; col < m2c; col++ {
m3.Set(row, mc+col, m2.Get(row, col))
}
}
return m3, nil
}
// Copy returns a duplicate of a given matrix.
func (m *Matrix) Copy() *Matrix {
m2 := &Matrix{stride: m.stride, epsilon: m.epsilon, elements: make([]float64, len(m.elements))}
copy(m2.elements, m.elements)
return m2
}
// DiagonalVector returns a vector from the diagonal of a matrix.
func (m *Matrix) DiagonalVector() Vector {
rows, cols := m.Size()
rank := minInt(rows, cols)
values := make([]float64, rank)
for index := 0; index < rank; index++ {
values[index] = m.Get(index, index)
}
return Vector(values)
}
// Diagonal returns a matrix from the diagonal of a matrix.
func (m *Matrix) Diagonal() *Matrix {
rows, cols := m.Size()
rank := minInt(rows, cols)
m2 := New(rank, rank)
for index := 0; index < rank; index++ {
m2.Set(index, index, m.Get(index, index))
}
return m2
}
// Equals returns if a matrix equals another matrix.
func (m *Matrix) Equals(other *Matrix) bool {
if other == nil && m != nil {
return false
} else if other == nil {
return true
}
if m.stride != other.stride {
return false
}
msize := len(m.elements)
m2size := len(other.elements)
if msize != m2size {
return false
}
for i := 0; i < msize; i++ {
if m.elements[i] != other.elements[i] {
return false
}
}
return true
}
// L returns the matrix with zeros below the diagonal.
func (m *Matrix) L() *Matrix {
rows, cols := m.Size()
m2 := New(rows, cols)
for row := 0; row < rows; row++ {
for col := row; col < cols; col++ {
m2.Set(row, col, m.Get(row, col))
}
}
return m2
}
// U returns the matrix with zeros above the diagonal.
// Does not include the diagonal.
func (m *Matrix) U() *Matrix {
rows, cols := m.Size()
m2 := New(rows, cols)
for row := 0; row < rows; row++ {
for col := 0; col < row && col < cols; col++ {
m2.Set(row, col, m.Get(row, col))
}
}
return m2
}
// math operations
// Multiply multiplies two matrices.
func (m *Matrix) Multiply(m2 *Matrix) (m3 *Matrix, err error) {
if m.stride*m2.stride != len(m2.elements) {
return nil, ErrDimensionMismatch
}
m3 = &Matrix{epsilon: m.epsilon, stride: m2.stride, elements: make([]float64, (len(m.elements)/m.stride)*m2.stride)}
for m1c0, m3x := 0, 0; m1c0 < len(m.elements); m1c0 += m.stride {
for m2r0 := 0; m2r0 < m2.stride; m2r0++ {
for m1x, m2x := m1c0, m2r0; m2x < len(m2.elements); m2x += m2.stride {
m3.elements[m3x] += m.elements[m1x] * m2.elements[m2x]
m1x++
}
m3x++
}
}
return
}
// Pivotize does something i'm not sure what.
func (m *Matrix) Pivotize() *Matrix {
pv := make([]int, m.stride)
for i := range pv {
pv[i] = i
}
for j, dx := 0, 0; j < m.stride; j++ {
row := j
max := m.elements[dx]
for i, ixcj := j, dx; i < m.stride; i++ {
if m.elements[ixcj] > max {
max = m.elements[ixcj]
row = i
}
ixcj += m.stride
}
if j != row {
pv[row], pv[j] = pv[j], pv[row]
}
dx += m.stride + 1
}
p := Zero(m.stride, m.stride)
for r, c := range pv {
p.elements[r*m.stride+c] = 1
}
return p
}
// Times returns the product of a matrix and another.
func (m *Matrix) Times(m2 *Matrix) (*Matrix, error) {
mr, mc := m.Size()
m2r, m2c := m2.Size()
if mc != m2r {
return nil, fmt.Errorf("cannot multiply (%dx%d) and (%dx%d)", mr, mc, m2r, m2c)
//return nil, ErrDimensionMismatch
}
c := Zero(mr, m2c)
for i := 0; i < mr; i++ {
sums := c.elements[i*c.stride : (i+1)*c.stride]
for k, a := range m.elements[i*m.stride : i*m.stride+m.stride] {
for j, b := range m2.elements[k*m2.stride : k*m2.stride+m2.stride] {
sums[j] += a * b
}
}
}
return c, nil
}
// Decompositions
// LU performs the LU decomposition.
func (m *Matrix) LU() (l, u, p *Matrix) {
l = Zero(m.stride, m.stride)
u = Zero(m.stride, m.stride)
p = m.Pivotize()
m, _ = p.Multiply(m)
for j, jxc0 := 0, 0; j < m.stride; j++ {
l.elements[jxc0+j] = 1
for i, ixc0 := 0, 0; ixc0 <= jxc0; i++ {
sum := 0.
for k, kxcj := 0, j; k < i; k++ {
sum += u.elements[kxcj] * l.elements[ixc0+k]
kxcj += m.stride
}
u.elements[ixc0+j] = m.elements[ixc0+j] - sum
ixc0 += m.stride
}
for ixc0 := jxc0; ixc0 < len(m.elements); ixc0 += m.stride {
sum := 0.
for k, kxcj := 0, j; k < j; k++ {
sum += u.elements[kxcj] * l.elements[ixc0+k]
kxcj += m.stride
}
l.elements[ixc0+j] = (m.elements[ixc0+j] - sum) / u.elements[jxc0+j]
}
jxc0 += m.stride
}
return
}
// QR performs the qr decomposition.
func (m *Matrix) QR() (q, r *Matrix) {
defer func() {
q = q.Round()
r = r.Round()
}()
rows, cols := m.Size()
qr := m.Copy()
q = New(rows, cols)
r = New(rows, cols)
var i, j, k int
var norm, s float64
for k = 0; k < cols; k++ {
norm = 0
for i = k; i < rows; i++ {
norm = math.Hypot(norm, qr.Get(i, k))
}
if norm != 0 {
if qr.Get(k, k) < 0 {
norm = -norm
}
for i = k; i < rows; i++ {
qr.Set(i, k, qr.Get(i, k)/norm)
}
qr.Set(k, k, qr.Get(k, k)+1.0)
for j = k + 1; j < cols; j++ {
s = 0
for i = k; i < rows; i++ {
s += qr.Get(i, k) * qr.Get(i, j)
}
s = -s / qr.Get(k, k)
for i = k; i < rows; i++ {
qr.Set(i, j, qr.Get(i, j)+s*qr.Get(i, k))
if i < j {
r.Set(i, j, qr.Get(i, j))
}
}
}
}
r.Set(k, k, -norm)
}
//Q Matrix:
i, j, k = 0, 0, 0
for k = cols - 1; k >= 0; k-- {
q.Set(k, k, 1.0)
for j = k; j < cols; j++ {
if qr.Get(k, k) != 0 {
s = 0
for i = k; i < rows; i++ {
s += qr.Get(i, k) * q.Get(i, j)
}
s = -s / qr.Get(k, k)
for i = k; i < rows; i++ {
q.Set(i, j, q.Get(i, j)+s*qr.Get(i, k))
}
}
}
}
return
}
// Transpose flips a matrix about its diagonal, returning a new copy.
func (m *Matrix) Transpose() *Matrix {
rows, cols := m.Size()
m2 := Zero(cols, rows)
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
m2.Set(j, i, m.Get(i, j))
}
}
return m2
}
// Inverse returns a matrix such that M*I==1.
func (m *Matrix) Inverse() (*Matrix, error) {
if !m.IsSymmetric() {
return nil, ErrDimensionMismatch
}
rows, cols := m.Size()
aug, _ := m.Augment(Eye(rows))
for i := 0; i < rows; i++ {
j := i
for k := i; k < rows; k++ {
if math.Abs(aug.Get(k, i)) > math.Abs(aug.Get(j, i)) {
j = k
}
}
if j != i {
aug.SwapRows(i, j)
}
if aug.Get(i, i) == 0 {
return nil, ErrSingularValue
}
aug.ScaleRow(i, 1.0/aug.Get(i, i))
for k := 0; k < rows; k++ {
if k == i {
continue
}
aug.scaleAddRow(k, i, -aug.Get(k, i))
}
}
return aug.SubMatrix(0, cols, rows, cols), nil
}

View File

@ -0,0 +1,45 @@
package matrix
import "errors"
var (
// ErrPolyRegArraysSameLength is a common error.
ErrPolyRegArraysSameLength = errors.New("polynomial array inputs must be the same length")
)
// Poly returns the polynomial regress of a given degree over the given values.
func Poly(xvalues, yvalues []float64, degree int) ([]float64, error) {
if len(xvalues) != len(yvalues) {
return nil, ErrPolyRegArraysSameLength
}
m := len(yvalues)
n := degree + 1
y := New(m, 1, yvalues...)
x := Zero(m, n)
for i := 0; i < m; i++ {
ip := float64(1)
for j := 0; j < n; j++ {
x.Set(i, j, ip)
ip *= xvalues[i]
}
}
q, r := x.QR()
qty, err := q.Transpose().Times(y)
if err != nil {
return nil, err
}
c := make([]float64, n)
for i := n - 1; i >= 0; i-- {
c[i] = qty.Get(i, 0)
for j := i + 1; j < n; j++ {
c[i] -= c[j] * r.Get(i, j)
}
c[i] /= r.Get(i, i)
}
return c, nil
}

36
vendor/github.com/wcharczuk/go-chart/v2/matrix/util.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package matrix
import (
"math"
"strconv"
)
func minInt(values ...int) int {
min := math.MaxInt32
for x := 0; x < len(values); x++ {
if values[x] < min {
min = values[x]
}
}
return min
}
func maxInt(values ...int) int {
max := math.MinInt32
for x := 0; x < len(values); x++ {
if values[x] > max {
max = values[x]
}
}
return max
}
func f64s(v float64) string {
return strconv.FormatFloat(v, 'f', -1, 64)
}
func roundToEpsilon(value, epsilon float64) float64 {
return math.Nextafter(value, value)
}

View File

@ -0,0 +1,17 @@
package matrix
// Vector is just an array of values.
type Vector []float64
// DotProduct returns the dot product of two vectors.
func (v Vector) DotProduct(v2 Vector) (result float64, err error) {
if len(v) != len(v2) {
err = ErrDimensionMismatch
return
}
for i := 0; i < len(v); i++ {
result = result + (v[i] * v2[i])
}
return
}

View File

@ -0,0 +1,138 @@
package chart
import (
"fmt"
"math"
)
// MinSeries draws a horizontal line at the minimum value of the inner series.
type MinSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
minValue *float64
}
// GetName returns the name of the time series.
func (ms MinSeries) GetName() string {
return ms.Name
}
// GetStyle returns the line style.
func (ms MinSeries) GetStyle() Style {
return ms.Style
}
// GetYAxis returns which YAxis the series draws on.
func (ms MinSeries) GetYAxis() YAxisType {
return ms.YAxis
}
// Len returns the number of elements in the series.
func (ms MinSeries) Len() int {
return ms.InnerSeries.Len()
}
// GetValues gets a value at a given index.
func (ms *MinSeries) GetValues(index int) (x, y float64) {
ms.ensureMinValue()
x, _ = ms.InnerSeries.GetValues(index)
y = *ms.minValue
return
}
// Render renders the series.
func (ms *MinSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ms.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms)
}
func (ms *MinSeries) ensureMinValue() {
if ms.minValue == nil {
minValue := math.MaxFloat64
var y float64
for x := 0; x < ms.InnerSeries.Len(); x++ {
_, y = ms.InnerSeries.GetValues(x)
if y < minValue {
minValue = y
}
}
ms.minValue = &minValue
}
}
// Validate validates the series.
func (ms *MinSeries) Validate() error {
if ms.InnerSeries == nil {
return fmt.Errorf("min series requires InnerSeries to be set")
}
return nil
}
// MaxSeries draws a horizontal line at the maximum value of the inner series.
type MaxSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries ValuesProvider
maxValue *float64
}
// GetName returns the name of the time series.
func (ms MaxSeries) GetName() string {
return ms.Name
}
// GetStyle returns the line style.
func (ms MaxSeries) GetStyle() Style {
return ms.Style
}
// GetYAxis returns which YAxis the series draws on.
func (ms MaxSeries) GetYAxis() YAxisType {
return ms.YAxis
}
// Len returns the number of elements in the series.
func (ms MaxSeries) Len() int {
return ms.InnerSeries.Len()
}
// GetValues gets a value at a given index.
func (ms *MaxSeries) GetValues(index int) (x, y float64) {
ms.ensureMaxValue()
x, _ = ms.InnerSeries.GetValues(index)
y = *ms.maxValue
return
}
// Render renders the series.
func (ms *MaxSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ms.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms)
}
func (ms *MaxSeries) ensureMaxValue() {
if ms.maxValue == nil {
maxValue := -math.MaxFloat64
var y float64
for x := 0; x < ms.InnerSeries.Len(); x++ {
_, y = ms.InnerSeries.GetValues(x)
if y > maxValue {
maxValue = y
}
}
ms.maxValue = &maxValue
}
}
// Validate validates the series.
func (ms *MaxSeries) Validate() error {
if ms.InnerSeries == nil {
return fmt.Errorf("max series requires InnerSeries to be set")
}
return nil
}

40
vendor/github.com/wcharczuk/go-chart/v2/parse.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package chart
import (
"strconv"
"strings"
"time"
)
// ParseFloats parses a list of floats.
func ParseFloats(values ...string) ([]float64, error) {
var output []float64
var parsedValue float64
var err error
var cleaned string
for _, value := range values {
cleaned = strings.TrimSpace(strings.Replace(value, ",", "", -1))
if cleaned == "" {
continue
}
if parsedValue, err = strconv.ParseFloat(cleaned, 64); err != nil {
return nil, err
}
output = append(output, parsedValue)
}
return output, nil
}
// ParseTimes parses a list of times with a given format.
func ParseTimes(layout string, values ...string) ([]time.Time, error) {
var output []time.Time
var parsedValue time.Time
var err error
for _, value := range values {
if parsedValue, err = time.Parse(layout, value); err != nil {
return nil, err
}
output = append(output, parsedValue)
}
return output, nil
}

View File

@ -0,0 +1,89 @@
package chart
// Interface Assertions.
var (
_ Series = (*PercentChangeSeries)(nil)
_ FirstValuesProvider = (*PercentChangeSeries)(nil)
_ LastValuesProvider = (*PercentChangeSeries)(nil)
_ ValueFormatterProvider = (*PercentChangeSeries)(nil)
)
// PercentChangeSeriesSource is a series that
// can be used with a PercentChangeSeries
type PercentChangeSeriesSource interface {
Series
FirstValuesProvider
LastValuesProvider
ValuesProvider
ValueFormatterProvider
}
// PercentChangeSeries applies a
// percentage difference function to a given continuous series.
type PercentChangeSeries struct {
Name string
Style Style
YAxis YAxisType
InnerSeries PercentChangeSeriesSource
}
// GetName returns the name of the time series.
func (pcs PercentChangeSeries) GetName() string {
return pcs.Name
}
// GetStyle returns the line style.
func (pcs PercentChangeSeries) GetStyle() Style {
return pcs.Style
}
// Len implements part of Series.
func (pcs PercentChangeSeries) Len() int {
return pcs.InnerSeries.Len()
}
// GetFirstValues implements FirstValuesProvider.
func (pcs PercentChangeSeries) GetFirstValues() (x, y float64) {
return pcs.InnerSeries.GetFirstValues()
}
// GetValues gets x, y values at a given index.
func (pcs PercentChangeSeries) GetValues(index int) (x, y float64) {
_, fy := pcs.InnerSeries.GetFirstValues()
x0, y0 := pcs.InnerSeries.GetValues(index)
x = x0
y = PercentDifference(fy, y0)
return
}
// GetValueFormatters returns value formatter defaults for the series.
func (pcs PercentChangeSeries) GetValueFormatters() (x, y ValueFormatter) {
x, _ = pcs.InnerSeries.GetValueFormatters()
y = PercentValueFormatter
return
}
// GetYAxis returns which YAxis the series draws on.
func (pcs PercentChangeSeries) GetYAxis() YAxisType {
return pcs.YAxis
}
// GetLastValues gets the last values.
func (pcs PercentChangeSeries) GetLastValues() (x, y float64) {
_, fy := pcs.InnerSeries.GetFirstValues()
x0, y0 := pcs.InnerSeries.GetLastValues()
x = x0
y = PercentDifference(fy, y0)
return
}
// Render renders the series.
func (pcs PercentChangeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := pcs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, pcs)
}
// Validate validates the series.
func (pcs PercentChangeSeries) Validate() error {
return pcs.InnerSeries.Validate()
}

311
vendor/github.com/wcharczuk/go-chart/v2/pie_chart.go generated vendored Normal file
View File

@ -0,0 +1,311 @@
package chart
import (
"errors"
"fmt"
"io"
"github.com/golang/freetype/truetype"
)
// PieChart is a chart that draws sections of a circle based on percentages.
type PieChart 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 PieChart) 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 PieChart) GetFont() *truetype.Font {
if pc.Font == nil {
return pc.defaultFont
}
return pc.Font
}
// GetWidth returns the chart width or the default value.
func (pc PieChart) GetWidth() int {
if pc.Width == 0 {
return DefaultChartWidth
}
return pc.Width
}
// GetHeight returns the chart height or the default value.
func (pc PieChart) 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 PieChart) 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 PieChart) drawBackground(r Renderer) {
Draw.Box(r, Box{
Right: pc.GetWidth(),
Bottom: pc.GetHeight(),
}, pc.getBackgroundStyle())
}
func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) {
Draw.Box(r, canvasBox, pc.getCanvasStyle())
}
func (pc PieChart) drawTitle(r Renderer) {
if len(pc.Title) > 0 && !pc.TitleStyle.Hidden {
Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle())
}
}
func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
cx, cy := canvasBox.Center()
diameter := MinInt(canvasBox.Width(), canvasBox.Height())
radius := float64(diameter >> 1)
labelRadius := (radius * 2.0) / 3.0
// draw the pie slices
var rads, delta, delta2, total float64
var lx, ly int
if len(values) == 1 {
pc.stylePieChartValue(0).WriteToRenderer(r)
r.MoveTo(cx, cy)
r.Circle(radius, cx, cy)
} else {
for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
r.MoveTo(cx, cy)
rads = PercentToRadians(total)
delta = PercentToRadians(v.Value)
r.ArcTo(cx, cy, radius, radius, rads, delta)
r.LineTo(cx, cy)
r.Close()
r.FillStroke()
total = total + v.Value
}
}
// draw the labels
total = 0
for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(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)
if lx < 0 {
lx = 0
}
if ly < 0 {
lx = 0
}
r.Text(v.Label, lx, ly)
}
total = total + v.Value
}
}
func (pc PieChart) finalizeValues(values []Value) ([]Value, error) {
finalValues := Values(values).Normalize()
if len(finalValues) == 0 {
return nil, fmt.Errorf("pie chart must contain at least (1) non-zero value")
}
return finalValues, nil
}
func (pc PieChart) getDefaultCanvasBox() Box {
return pc.Box()
}
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box {
circleDiameter := MinInt(canvasBox.Width(), canvasBox.Height())
square := Box{
Right: circleDiameter,
Bottom: circleDiameter,
}
return canvasBox.Fit(square)
}
func (pc PieChart) getBackgroundStyle() Style {
return pc.Background.InheritFrom(pc.styleDefaultsBackground())
}
func (pc PieChart) getCanvasStyle() Style {
return pc.Canvas.InheritFrom(pc.styleDefaultsCanvas())
}
func (pc PieChart) styleDefaultsCanvas() Style {
return Style{
FillColor: pc.GetColorPalette().CanvasColor(),
StrokeColor: pc.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultStrokeWidth,
}
}
func (pc PieChart) styleDefaultsPieChartValue() Style {
return Style{
StrokeColor: pc.GetColorPalette().TextColor(),
StrokeWidth: 5.0,
FillColor: pc.GetColorPalette().TextColor(),
}
}
func (pc PieChart) stylePieChartValue(index int) Style {
return pc.SliceStyle.InheritFrom(Style{
StrokeColor: ColorWhite,
StrokeWidth: 5.0,
FillColor: pc.GetColorPalette().GetSeriesColor(index),
FontSize: pc.getScaledFontSize(),
FontColor: pc.GetColorPalette().TextColor(),
Font: pc.GetFont(),
})
}
func (pc PieChart) 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 PieChart) styleDefaultsBackground() Style {
return Style{
FillColor: pc.GetColorPalette().BackgroundColor(),
StrokeColor: pc.GetColorPalette().BackgroundStrokeColor(),
StrokeWidth: DefaultStrokeWidth,
}
}
func (pc PieChart) styleDefaultsElements() Style {
return Style{
Font: pc.GetFont(),
}
}
func (pc PieChart) 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 PieChart) 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 PieChart) GetColorPalette() ColorPalette {
if pc.ColorPalette != nil {
return pc.ColorPalette
}
return AlternateColorPalette
}
// Box returns the chart bounds as a box.
func (pc PieChart) 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,
}
}

View File

@ -0,0 +1,177 @@
package chart
import (
"fmt"
"math"
"github.com/wcharczuk/go-chart/v2/matrix"
)
// Interface Assertions.
var (
_ Series = (*PolynomialRegressionSeries)(nil)
_ FirstValuesProvider = (*PolynomialRegressionSeries)(nil)
_ LastValuesProvider = (*PolynomialRegressionSeries)(nil)
)
// PolynomialRegressionSeries implements a polynomial regression over a given
// inner series.
type PolynomialRegressionSeries struct {
Name string
Style Style
YAxis YAxisType
Limit int
Offset int
Degree int
InnerSeries ValuesProvider
coeffs []float64
}
// GetName returns the name of the time series.
func (prs PolynomialRegressionSeries) GetName() string {
return prs.Name
}
// GetStyle returns the line style.
func (prs PolynomialRegressionSeries) GetStyle() Style {
return prs.Style
}
// GetYAxis returns which YAxis the series draws on.
func (prs PolynomialRegressionSeries) GetYAxis() YAxisType {
return prs.YAxis
}
// Len returns the number of elements in the series.
func (prs PolynomialRegressionSeries) Len() int {
return MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
}
// GetLimit returns the window size.
func (prs PolynomialRegressionSeries) GetLimit() int {
if prs.Limit == 0 {
return prs.InnerSeries.Len()
}
return prs.Limit
}
// GetEndIndex returns the effective limit end.
func (prs PolynomialRegressionSeries) GetEndIndex() int {
windowEnd := prs.GetOffset() + prs.GetLimit()
innerSeriesLastIndex := prs.InnerSeries.Len() - 1
return MinInt(windowEnd, innerSeriesLastIndex)
}
// GetOffset returns the data offset.
func (prs PolynomialRegressionSeries) GetOffset() int {
if prs.Offset == 0 {
return 0
}
return prs.Offset
}
// Validate validates the series.
func (prs *PolynomialRegressionSeries) Validate() error {
if prs.InnerSeries == nil {
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}
endIndex := prs.GetEndIndex()
if endIndex >= prs.InnerSeries.Len() {
return fmt.Errorf("invalid window; inner series has length %d but end index is %d", prs.InnerSeries.Len(), endIndex)
}
return nil
}
// GetValues returns the series value for a given index.
func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) {
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
return
}
if prs.coeffs == nil {
coeffs, err := prs.computeCoefficients()
if err != nil {
panic(err)
}
prs.coeffs = coeffs
}
offset := prs.GetOffset()
effectiveIndex := MinInt(index+offset, prs.InnerSeries.Len())
x, y = prs.InnerSeries.GetValues(effectiveIndex)
y = prs.apply(x)
return
}
// GetFirstValues computes the first poly regression value.
func (prs *PolynomialRegressionSeries) GetFirstValues() (x, y float64) {
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
return
}
if prs.coeffs == nil {
coeffs, err := prs.computeCoefficients()
if err != nil {
panic(err)
}
prs.coeffs = coeffs
}
x, y = prs.InnerSeries.GetValues(0)
y = prs.apply(x)
return
}
// GetLastValues computes the last poly regression value.
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) {
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 {
return
}
if prs.coeffs == nil {
coeffs, err := prs.computeCoefficients()
if err != nil {
panic(err)
}
prs.coeffs = coeffs
}
endIndex := prs.GetEndIndex()
x, y = prs.InnerSeries.GetValues(endIndex)
y = prs.apply(x)
return
}
func (prs *PolynomialRegressionSeries) apply(v float64) (out float64) {
for index, coeff := range prs.coeffs {
out = out + (coeff * math.Pow(v, float64(index)))
}
return
}
func (prs *PolynomialRegressionSeries) computeCoefficients() ([]float64, error) {
xvalues, yvalues := prs.values()
return matrix.Poly(xvalues, yvalues, prs.Degree)
}
func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) {
startIndex := prs.GetOffset()
endIndex := prs.GetEndIndex()
xvalues = make([]float64, endIndex-startIndex)
yvalues = make([]float64, endIndex-startIndex)
for index := startIndex; index < endIndex; index++ {
x, y := prs.InnerSeries.GetValues(index)
xvalues[index-startIndex] = x
yvalues[index-startIndex] = y
}
return
}
// Render renders the series.
func (prs *PolynomialRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := prs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, prs)
}

View File

@ -0,0 +1,92 @@
package chart
import (
"math"
"math/rand"
"time"
)
var (
_ Sequence = (*RandomSeq)(nil)
)
// RandomValues returns an array of random values.
func RandomValues(count int) []float64 {
return Seq{NewRandomSequence().WithLen(count)}.Values()
}
// RandomValuesWithMax returns an array of random values with a given average.
func RandomValuesWithMax(count int, max float64) []float64 {
return Seq{NewRandomSequence().WithMax(max).WithLen(count)}.Values()
}
// NewRandomSequence creates a new random seq.
func NewRandomSequence() *RandomSeq {
return &RandomSeq{
rnd: rand.New(rand.NewSource(time.Now().Unix())),
}
}
// RandomSeq is a random number seq generator.
type RandomSeq struct {
rnd *rand.Rand
max *float64
min *float64
len *int
}
// Len returns the number of elements that will be generated.
func (r *RandomSeq) Len() int {
if r.len != nil {
return *r.len
}
return math.MaxInt32
}
// GetValue returns the value.
func (r *RandomSeq) GetValue(_ int) float64 {
if r.min != nil && r.max != nil {
var delta float64
if *r.max > *r.min {
delta = *r.max - *r.min
} else {
delta = *r.min - *r.max
}
return *r.min + (r.rnd.Float64() * delta)
} else if r.max != nil {
return r.rnd.Float64() * *r.max
} else if r.min != nil {
return *r.min + (r.rnd.Float64())
}
return r.rnd.Float64()
}
// WithLen sets a maximum len
func (r *RandomSeq) WithLen(length int) *RandomSeq {
r.len = &length
return r
}
// Min returns the minimum value.
func (r RandomSeq) Min() *float64 {
return r.min
}
// WithMin sets the scale and returns the Random.
func (r *RandomSeq) WithMin(min float64) *RandomSeq {
r.min = &min
return r
}
// Max returns the maximum value.
func (r RandomSeq) Max() *float64 {
return r.max
}
// WithMax sets the average and returns the Random.
func (r *RandomSeq) WithMax(max float64) *RandomSeq {
r.max = &max
return r
}

43
vendor/github.com/wcharczuk/go-chart/v2/range.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
package chart
// NameProvider is a type that returns a name.
type NameProvider interface {
GetName() string
}
// StyleProvider is a type that returns a style.
type StyleProvider interface {
GetStyle() Style
}
// IsZeroable is a type that returns if it's been set or not.
type IsZeroable interface {
IsZero() bool
}
// Stringable is a type that has a string representation.
type Stringable interface {
String() string
}
// Range is a common interface for a range of values.
type Range interface {
Stringable
IsZeroable
GetMin() float64
SetMin(min float64)
GetMax() float64
SetMax(max float64)
GetDelta() float64
GetDomain() int
SetDomain(domain int)
IsDescending() bool
// Translate the range to the domain.
Translate(value float64) int
}

View File

@ -0,0 +1,230 @@
package chart
import (
"image"
"image/png"
"io"
"math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/drawing"
)
// PNG returns a new png/raster renderer.
func PNG(width, height int) (Renderer, error) {
i := image.NewRGBA(image.Rect(0, 0, width, height))
gc, err := drawing.NewRasterGraphicContext(i)
if err == nil {
return &rasterRenderer{
i: i,
gc: gc,
}, nil
}
return nil, err
}
// rasterRenderer renders chart commands to a bitmap.
type rasterRenderer struct {
i *image.RGBA
gc *drawing.RasterGraphicContext
rotateRadians *float64
s Style
}
func (rr *rasterRenderer) ResetStyle() {
rr.s = Style{Font: rr.s.Font}
rr.ClearTextRotation()
}
// GetDPI returns the dpi.
func (rr *rasterRenderer) GetDPI() float64 {
return rr.gc.GetDPI()
}
// SetDPI implements the interface method.
func (rr *rasterRenderer) SetDPI(dpi float64) {
rr.gc.SetDPI(dpi)
}
// SetClassName implements the interface method. However, PNGs have no classes.
func (rr *rasterRenderer) SetClassName(_ string) {}
// SetStrokeColor implements the interface method.
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
rr.s.StrokeColor = c
}
// SetLineWidth implements the interface method.
func (rr *rasterRenderer) SetStrokeWidth(width float64) {
rr.s.StrokeWidth = width
}
// StrokeDashArray sets the stroke dash array.
func (rr *rasterRenderer) SetStrokeDashArray(dashArray []float64) {
rr.s.StrokeDashArray = dashArray
}
// SetFillColor implements the interface method.
func (rr *rasterRenderer) SetFillColor(c drawing.Color) {
rr.s.FillColor = c
}
// MoveTo implements the interface method.
func (rr *rasterRenderer) MoveTo(x, y int) {
rr.gc.MoveTo(float64(x), float64(y))
}
// LineTo implements the interface method.
func (rr *rasterRenderer) LineTo(x, y int) {
rr.gc.LineTo(float64(x), float64(y))
}
// QuadCurveTo implements the interface method.
func (rr *rasterRenderer) QuadCurveTo(cx, cy, x, y int) {
rr.gc.QuadCurveTo(float64(cx), float64(cy), float64(x), float64(y))
}
// ArcTo implements the interface method.
func (rr *rasterRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
rr.gc.ArcTo(float64(cx), float64(cy), rx, ry, startAngle, delta)
}
// Close implements the interface method.
func (rr *rasterRenderer) Close() {
rr.gc.Close()
}
// Stroke implements the interface method.
func (rr *rasterRenderer) Stroke() {
rr.gc.SetStrokeColor(rr.s.StrokeColor)
rr.gc.SetLineWidth(rr.s.StrokeWidth)
rr.gc.SetLineDash(rr.s.StrokeDashArray, 0)
rr.gc.Stroke()
}
// Fill implements the interface method.
func (rr *rasterRenderer) Fill() {
rr.gc.SetFillColor(rr.s.FillColor)
rr.gc.Fill()
}
// FillStroke implements the interface method.
func (rr *rasterRenderer) FillStroke() {
rr.gc.SetFillColor(rr.s.FillColor)
rr.gc.SetStrokeColor(rr.s.StrokeColor)
rr.gc.SetLineWidth(rr.s.StrokeWidth)
rr.gc.SetLineDash(rr.s.StrokeDashArray, 0)
rr.gc.FillStroke()
}
// Circle fully draws a circle at a given point but does not apply the fill or stroke.
func (rr *rasterRenderer) Circle(radius float64, x, y int) {
xf := float64(x)
yf := float64(y)
rr.gc.MoveTo(xf-radius, yf) //9
rr.gc.QuadCurveTo(xf-radius, yf-radius, xf, yf-radius) //12
rr.gc.QuadCurveTo(xf+radius, yf-radius, xf+radius, yf) //3
rr.gc.QuadCurveTo(xf+radius, yf+radius, xf, yf+radius) //6
rr.gc.QuadCurveTo(xf-radius, yf+radius, xf-radius, yf) //9
}
// SetFont implements the interface method.
func (rr *rasterRenderer) SetFont(f *truetype.Font) {
rr.s.Font = f
}
// SetFontSize implements the interface method.
func (rr *rasterRenderer) SetFontSize(size float64) {
rr.s.FontSize = size
}
// SetFontColor implements the interface method.
func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
rr.s.FontColor = c
}
// Text implements the interface method.
func (rr *rasterRenderer) Text(body string, x, y int) {
xf, yf := rr.getCoords(x, y)
rr.gc.SetFont(rr.s.Font)
rr.gc.SetFontSize(rr.s.FontSize)
rr.gc.SetFillColor(rr.s.FontColor)
rr.gc.CreateStringPath(body, float64(xf), float64(yf))
rr.gc.Fill()
}
// MeasureText returns the height and width in pixels of a string.
func (rr *rasterRenderer) MeasureText(body string) Box {
rr.gc.SetFont(rr.s.Font)
rr.gc.SetFontSize(rr.s.FontSize)
rr.gc.SetFillColor(rr.s.FontColor)
l, t, r, b, err := rr.gc.GetStringBounds(body)
if err != nil {
return Box{}
}
if l < 0 {
r = r - l // equivalent to r+(-1*l)
l = 0
}
if t < 0 {
b = b - t
t = 0
}
if l > 0 {
r = r + l
l = 0
}
if t > 0 {
b = b + t
t = 0
}
textBox := Box{
Top: int(math.Ceil(t)),
Left: int(math.Ceil(l)),
Right: int(math.Ceil(r)),
Bottom: int(math.Ceil(b)),
}
if rr.rotateRadians == nil {
return textBox
}
return textBox.Corners().Rotate(RadiansToDegrees(*rr.rotateRadians)).Box()
}
// SetTextRotation sets a text rotation.
func (rr *rasterRenderer) SetTextRotation(radians float64) {
rr.rotateRadians = &radians
}
func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) {
if rr.rotateRadians == nil {
xf = x
yf = y
return
}
rr.gc.Translate(float64(x), float64(y))
rr.gc.Rotate(*rr.rotateRadians)
return
}
// ClearTextRotation clears text rotation.
func (rr *rasterRenderer) ClearTextRotation() {
rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix())
rr.rotateRadians = nil
}
// Save implements the interface method.
func (rr *rasterRenderer) Save(w io.Writer) error {
if typed, isTyped := w.(RGBACollector); isTyped {
typed.SetRGBA(rr.i)
return nil
}
return png.Encode(w, rr.i)
}

View File

@ -0,0 +1,4 @@
package chart
// Renderable is a function that can be called to render custom elements on the chart.
type Renderable func(r Renderer, canvasBox Box, defaults Style)

89
vendor/github.com/wcharczuk/go-chart/v2/renderer.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
package chart
import (
"io"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/drawing"
)
// Renderer represents the basic methods required to draw a chart.
type Renderer interface {
// ResetStyle should reset any style related settings on the renderer.
ResetStyle()
// GetDPI gets the DPI for the renderer.
GetDPI() float64
// SetDPI sets the DPI for the renderer.
SetDPI(dpi float64)
// SetClassName sets the current class name.
SetClassName(string)
// SetStrokeColor sets the current stroke color.
SetStrokeColor(drawing.Color)
// SetFillColor sets the current fill color.
SetFillColor(drawing.Color)
// SetStrokeWidth sets the stroke width.
SetStrokeWidth(width float64)
// SetStrokeDashArray sets the stroke dash array.
SetStrokeDashArray(dashArray []float64)
// MoveTo moves the cursor to a given point.
MoveTo(x, y int)
// LineTo both starts a shape and draws a line to a given point
// from the previous point.
LineTo(x, y int)
// QuadCurveTo draws a quad curve.
// cx and cy represent the bezier "control points".
QuadCurveTo(cx, cy, x, y int)
// ArcTo draws an arc with a given center (cx,cy)
// a given set of radii (rx,ry), a startAngle and delta (in radians).
ArcTo(cx, cy int, rx, ry, startAngle, delta float64)
// Close finalizes a shape as drawn by LineTo.
Close()
// Stroke strokes the path.
Stroke()
// Fill fills the path, but does not stroke.
Fill()
// FillStroke fills and strokes a path.
FillStroke()
// Circle draws a circle at the given coords with a given radius.
Circle(radius float64, x, y int)
// SetFont sets a font for a text field.
SetFont(*truetype.Font)
// SetFontColor sets a font's color
SetFontColor(drawing.Color)
// SetFontSize sets the font size for a text field.
SetFontSize(size float64)
// Text draws a text blob.
Text(body string, x, y int)
// MeasureText measures text.
MeasureText(body string) Box
// SetTextRotatation sets a rotation for drawing elements.
SetTextRotation(radians float64)
// ClearTextRotation clears rotation.
ClearTextRotation()
// Save writes the image to the given writer.
Save(w io.Writer) error
}

View File

@ -0,0 +1,4 @@
package chart
// RendererProvider is a function that returns a renderer.
type RendererProvider func(int, int) (Renderer, error)

File diff suppressed because one or more lines are too long

275
vendor/github.com/wcharczuk/go-chart/v2/seq.go generated vendored Normal file
View File

@ -0,0 +1,275 @@
package chart
import (
"math"
"sort"
)
// ValueSequence returns a sequence for a given values set.
func ValueSequence(values ...float64) Seq {
return Seq{NewArray(values...)}
}
// Sequence is a provider for values for a seq.
type Sequence interface {
Len() int
GetValue(int) float64
}
// Seq is a utility wrapper for seq providers.
type Seq struct {
Sequence
}
// Values enumerates the seq into a slice.
func (s Seq) Values() (output []float64) {
if s.Len() == 0 {
return
}
output = make([]float64, s.Len())
for i := 0; i < s.Len(); i++ {
output[i] = s.GetValue(i)
}
return
}
// Each applies the `mapfn` to all values in the value provider.
func (s Seq) Each(mapfn func(int, float64)) {
for i := 0; i < s.Len(); i++ {
mapfn(i, s.GetValue(i))
}
}
// Map applies the `mapfn` to all values in the value provider,
// returning a new seq.
func (s Seq) Map(mapfn func(i int, v float64) float64) Seq {
output := make([]float64, s.Len())
for i := 0; i < s.Len(); i++ {
mapfn(i, s.GetValue(i))
}
return Seq{Array(output)}
}
// FoldLeft collapses a seq from left to right.
func (s Seq) FoldLeft(mapfn func(i int, v0, v float64) float64) (v0 float64) {
if s.Len() == 0 {
return 0
}
if s.Len() == 1 {
return s.GetValue(0)
}
v0 = s.GetValue(0)
for i := 1; i < s.Len(); i++ {
v0 = mapfn(i, v0, s.GetValue(i))
}
return
}
// FoldRight collapses a seq from right to left.
func (s Seq) FoldRight(mapfn func(i int, v0, v float64) float64) (v0 float64) {
if s.Len() == 0 {
return 0
}
if s.Len() == 1 {
return s.GetValue(0)
}
v0 = s.GetValue(s.Len() - 1)
for i := s.Len() - 2; i >= 0; i-- {
v0 = mapfn(i, v0, s.GetValue(i))
}
return
}
// Min returns the minimum value in the seq.
func (s Seq) Min() float64 {
if s.Len() == 0 {
return 0
}
min := s.GetValue(0)
var value float64
for i := 1; i < s.Len(); i++ {
value = s.GetValue(i)
if value < min {
min = value
}
}
return min
}
// Max returns the maximum value in the seq.
func (s Seq) Max() float64 {
if s.Len() == 0 {
return 0
}
max := s.GetValue(0)
var value float64
for i := 1; i < s.Len(); i++ {
value = s.GetValue(i)
if value > max {
max = value
}
}
return max
}
// MinMax returns the minimum and the maximum in one pass.
func (s Seq) MinMax() (min, max float64) {
if s.Len() == 0 {
return
}
min = s.GetValue(0)
max = min
var value float64
for i := 1; i < s.Len(); i++ {
value = s.GetValue(i)
if value < min {
min = value
}
if value > max {
max = value
}
}
return
}
// Sort returns the seq sorted in ascending order.
// This fully enumerates the seq.
func (s Seq) Sort() Seq {
if s.Len() == 0 {
return s
}
values := s.Values()
sort.Float64s(values)
return Seq{Array(values)}
}
// Reverse reverses the sequence
func (s Seq) Reverse() Seq {
if s.Len() == 0 {
return s
}
values := s.Values()
valuesLen := len(values)
valuesLen1 := len(values) - 1
valuesLen2 := valuesLen >> 1
var i, j float64
for index := 0; index < valuesLen2; index++ {
i = values[index]
j = values[valuesLen1-index]
values[index] = j
values[valuesLen1-index] = i
}
return Seq{Array(values)}
}
// Median returns the median or middle value in the sorted seq.
func (s Seq) Median() (median float64) {
l := s.Len()
if l == 0 {
return
}
sorted := s.Sort()
if l%2 == 0 {
v0 := sorted.GetValue(l/2 - 1)
v1 := sorted.GetValue(l/2 + 1)
median = (v0 + v1) / 2
} else {
median = float64(sorted.GetValue(l << 1))
}
return
}
// Sum adds all the elements of a series together.
func (s Seq) Sum() (accum float64) {
if s.Len() == 0 {
return 0
}
for i := 0; i < s.Len(); i++ {
accum += s.GetValue(i)
}
return
}
// Average returns the float average of the values in the buffer.
func (s Seq) Average() float64 {
if s.Len() == 0 {
return 0
}
return s.Sum() / float64(s.Len())
}
// Variance computes the variance of the buffer.
func (s Seq) Variance() float64 {
if s.Len() == 0 {
return 0
}
m := s.Average()
var variance, v float64
for i := 0; i < s.Len(); i++ {
v = s.GetValue(i)
variance += (v - m) * (v - m)
}
return variance / float64(s.Len())
}
// StdDev returns the standard deviation.
func (s Seq) StdDev() float64 {
if s.Len() == 0 {
return 0
}
return math.Pow(s.Variance(), 0.5)
}
//Percentile finds the relative standing in a slice of floats.
// `percent` should be given on the interval [0,1.0).
func (s Seq) Percentile(percent float64) (percentile float64) {
l := s.Len()
if l == 0 {
return 0
}
if percent < 0 || percent > 1.0 {
panic("percent out of range [0.0, 1.0)")
}
sorted := s.Sort()
index := percent * float64(l)
if index == float64(int64(index)) {
i := f64i(index)
ci := sorted.GetValue(i - 1)
c := sorted.GetValue(i)
percentile = (ci + c) / 2.0
} else {
i := f64i(index)
percentile = sorted.GetValue(i)
}
return percentile
}
// Normalize maps every value to the interval [0, 1.0].
func (s Seq) Normalize() Seq {
min, max := s.MinMax()
delta := max - min
output := make([]float64, s.Len())
for i := 0; i < s.Len(); i++ {
output[i] = (s.GetValue(i) - min) / delta
}
return Seq{Array(output)}
}

10
vendor/github.com/wcharczuk/go-chart/v2/series.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package chart
// Series is an alias to Renderable.
type Series interface {
GetName() string
GetYAxis() YAxisType
GetStyle() Style
Validate() error
Render(r Renderer, canvasBox Box, xrange, yrange Range, s Style)
}

120
vendor/github.com/wcharczuk/go-chart/v2/sma_series.go generated vendored Normal file
View File

@ -0,0 +1,120 @@
package chart
import (
"fmt"
)
const (
// DefaultSimpleMovingAveragePeriod is the default number of values to average.
DefaultSimpleMovingAveragePeriod = 16
)
// Interface Assertions.
var (
_ Series = (*SMASeries)(nil)
_ FirstValuesProvider = (*SMASeries)(nil)
_ LastValuesProvider = (*SMASeries)(nil)
)
// SMASeries is a computed series.
type SMASeries struct {
Name string
Style Style
YAxis YAxisType
Period int
InnerSeries ValuesProvider
}
// GetName returns the name of the time series.
func (sma SMASeries) GetName() string {
return sma.Name
}
// GetStyle returns the line style.
func (sma SMASeries) GetStyle() Style {
return sma.Style
}
// GetYAxis returns which YAxis the series draws on.
func (sma SMASeries) GetYAxis() YAxisType {
return sma.YAxis
}
// Len returns the number of elements in the series.
func (sma SMASeries) Len() int {
return sma.InnerSeries.Len()
}
// GetPeriod returns the window size.
func (sma SMASeries) GetPeriod(defaults ...int) int {
if sma.Period == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultSimpleMovingAveragePeriod
}
return sma.Period
}
// GetValues gets a value at a given index.
func (sma SMASeries) GetValues(index int) (x, y float64) {
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
return
}
px, _ := sma.InnerSeries.GetValues(index)
x = px
y = sma.getAverage(index)
return
}
// GetFirstValues computes the first moving average value.
func (sma SMASeries) GetFirstValues() (x, y float64) {
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
return
}
px, _ := sma.InnerSeries.GetValues(0)
x = px
y = sma.getAverage(0)
return
}
// GetLastValues computes the last moving average value but walking back window size samples,
// and recomputing the last moving average chunk.
func (sma SMASeries) GetLastValues() (x, y float64) {
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 {
return
}
seriesLen := sma.InnerSeries.Len()
px, _ := sma.InnerSeries.GetValues(seriesLen - 1)
x = px
y = sma.getAverage(seriesLen - 1)
return
}
func (sma SMASeries) getAverage(index int) float64 {
period := sma.GetPeriod()
floor := MaxInt(0, index-period)
var accum float64
var count float64
for x := index; x >= floor; x-- {
_, vy := sma.InnerSeries.GetValues(x)
accum += vy
count += 1.0
}
return accum / count
}
// Render renders the series.
func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := sma.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, sma)
}
// Validate validates the series.
func (sma SMASeries) Validate() error {
if sma.InnerSeries == nil {
return fmt.Errorf("sma series requires InnerSeries to be set")
}
return nil
}

View File

@ -0,0 +1,610 @@
package chart
import (
"errors"
"fmt"
"io"
"math"
"github.com/golang/freetype/truetype"
)
// StackedBar is a bar within a StackedBarChart.
type StackedBar struct {
Name string
Width int
Values []Value
}
// GetWidth returns the width of the bar.
func (sb StackedBar) GetWidth() int {
if sb.Width == 0 {
return 50
}
return sb.Width
}
// StackedBarChart is a chart that draws sections of a bar based on percentages.
type StackedBarChart struct {
Title string
TitleStyle Style
ColorPalette ColorPalette
Width int
Height int
DPI float64
Background Style
Canvas Style
XAxis Style
YAxis Style
BarSpacing int
Font *truetype.Font
defaultFont *truetype.Font
IsHorizontal bool
Bars []StackedBar
Elements []Renderable
}
// GetDPI returns the dpi for the chart.
func (sbc StackedBarChart) GetDPI(defaults ...float64) float64 {
if sbc.DPI == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultDPI
}
return sbc.DPI
}
// GetFont returns the text font.
func (sbc StackedBarChart) GetFont() *truetype.Font {
if sbc.Font == nil {
return sbc.defaultFont
}
return sbc.Font
}
// GetWidth returns the chart width or the default value.
func (sbc StackedBarChart) GetWidth() int {
if sbc.Width == 0 {
return DefaultChartWidth
}
return sbc.Width
}
// GetHeight returns the chart height or the default value.
func (sbc StackedBarChart) GetHeight() int {
if sbc.Height == 0 {
return DefaultChartWidth
}
return sbc.Height
}
// GetBarSpacing returns the spacing between bars.
func (sbc StackedBarChart) GetBarSpacing() int {
if sbc.BarSpacing == 0 {
return 100
}
return sbc.BarSpacing
}
// Render renders the chart with the given renderer to the given io.Writer.
func (sbc StackedBarChart) Render(rp RendererProvider, w io.Writer) error {
if len(sbc.Bars) == 0 {
return errors.New("please provide at least one bar")
}
r, err := rp(sbc.GetWidth(), sbc.GetHeight())
if err != nil {
return err
}
if sbc.Font == nil {
defaultFont, err := GetDefaultFont()
if err != nil {
return err
}
sbc.defaultFont = defaultFont
}
r.SetDPI(sbc.GetDPI(DefaultDPI))
var canvasBox Box
if sbc.IsHorizontal {
canvasBox = sbc.getHorizontalAdjustedCanvasBox(r, sbc.getDefaultCanvasBox())
sbc.drawCanvas(r, canvasBox)
sbc.drawHorizontalBars(r, canvasBox)
sbc.drawHorizontalXAxis(r, canvasBox)
sbc.drawHorizontalYAxis(r, canvasBox)
} else {
canvasBox = sbc.getAdjustedCanvasBox(r, sbc.getDefaultCanvasBox())
sbc.drawCanvas(r, canvasBox)
sbc.drawBars(r, canvasBox)
sbc.drawXAxis(r, canvasBox)
sbc.drawYAxis(r, canvasBox)
}
sbc.drawTitle(r)
for _, a := range sbc.Elements {
a(r, canvasBox, sbc.styleDefaultsElements())
}
return r.Save(w)
}
func (sbc StackedBarChart) drawCanvas(r Renderer, canvasBox Box) {
Draw.Box(r, canvasBox, sbc.getCanvasStyle())
}
func (sbc StackedBarChart) drawBars(r Renderer, canvasBox Box) {
xoffset := canvasBox.Left
for _, bar := range sbc.Bars {
sbc.drawBar(r, canvasBox, xoffset, bar)
xoffset += (sbc.GetBarSpacing() + bar.GetWidth())
}
}
func (sbc StackedBarChart) drawHorizontalBars(r Renderer, canvasBox Box) {
yOffset := canvasBox.Top
for _, bar := range sbc.Bars {
sbc.drawHorizontalBar(r, canvasBox, yOffset, bar)
yOffset += sbc.GetBarSpacing() + bar.GetWidth()
}
}
func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar StackedBar) int {
barSpacing2 := sbc.GetBarSpacing() >> 1
bxl := xoffset + barSpacing2
bxr := bxl + bar.GetWidth()
normalizedBarComponents := Values(bar.Values).Normalize()
yoffset := canvasBox.Top
for index, bv := range normalizedBarComponents {
barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Height())))
barBox := Box{
Top: yoffset,
Left: bxl,
Right: bxr,
Bottom: MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth),
}
Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
yoffset += barHeight
}
// draw the labels
yoffset = canvasBox.Top
var lx, ly int
for index, bv := range normalizedBarComponents {
barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Height())))
if len(bv.Label) > 0 {
lx = bxl + ((bxr - bxl) / 2)
ly = yoffset + (barHeight / 2)
bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)).WriteToRenderer(r)
tb := r.MeasureText(bv.Label)
lx = lx - (tb.Width() >> 1)
ly = ly + (tb.Height() >> 1)
if lx < 0 {
lx = 0
}
if ly < 0 {
lx = 0
}
r.Text(bv.Label, lx, ly)
}
yoffset += barHeight
}
return bxr
}
func (sbc StackedBarChart) drawHorizontalBar(r Renderer, canvasBox Box, yoffset int, bar StackedBar) {
halfBarSpacing := sbc.GetBarSpacing() >> 1
boxTop := yoffset + halfBarSpacing
boxBottom := boxTop + bar.GetWidth()
normalizedBarComponents := Values(bar.Values).Normalize()
xOffset := canvasBox.Right
for index, bv := range normalizedBarComponents {
barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Width())))
barBox := Box{
Top: boxTop,
Left: MinInt(xOffset-barHeight, canvasBox.Left+DefaultStrokeWidth),
Right: xOffset,
Bottom: boxBottom,
}
Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
xOffset -= barHeight
}
// draw the labels
xOffset = canvasBox.Right
var lx, ly int
for index, bv := range normalizedBarComponents {
barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Width())))
if len(bv.Label) > 0 {
lx = xOffset - (barHeight / 2)
ly = boxTop + ((boxBottom - boxTop) / 2)
bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)).WriteToRenderer(r)
tb := r.MeasureText(bv.Label)
lx = lx - (tb.Width() >> 1)
ly = ly + (tb.Height() >> 1)
if lx < 0 {
lx = 0
}
if ly < 0 {
lx = 0
}
r.Text(bv.Label, lx, ly)
}
xOffset -= barHeight
}
}
func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) {
if !sbc.XAxis.Hidden {
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
axisStyle.WriteToRenderer(r)
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 _, bar := range sbc.Bars {
barLabelBox := Box{
Top: canvasBox.Bottom + DefaultXAxisMargin,
Left: cursor,
Right: cursor + bar.GetWidth() + sbc.GetBarSpacing(),
Bottom: sbc.GetHeight(),
}
if len(bar.Name) > 0 {
Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle)
}
axisStyle.WriteToRenderer(r)
r.MoveTo(barLabelBox.Right, canvasBox.Bottom)
r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke()
cursor += bar.GetWidth() + sbc.GetBarSpacing()
}
}
}
func (sbc StackedBarChart) drawHorizontalXAxis(r Renderer, canvasBox Box) {
if !sbc.XAxis.Hidden {
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
axisStyle.WriteToRenderer(r)
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()
ticks := LinearRangeWithStep(0.0, 1.0, 0.2)
for _, t := range ticks {
axisStyle.GetStrokeOptions().WriteToRenderer(r)
tx := canvasBox.Left + int(t*float64(canvasBox.Width()))
r.MoveTo(tx, canvasBox.Bottom)
r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke()
axisStyle.GetTextOptions().WriteToRenderer(r)
text := fmt.Sprintf("%0.0f%%", t*100)
textBox := r.MeasureText(text)
textX := tx - (textBox.Width() >> 1)
textY := canvasBox.Bottom + DefaultXAxisMargin + 10
if t == 1 {
textX = canvasBox.Right - textBox.Width()
}
Draw.Text(r, text, textX, textY, axisStyle)
}
}
}
func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
if !sbc.YAxis.Hidden {
axisStyle := sbc.YAxis.InheritFrom(sbc.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()
ticks := LinearRangeWithStep(0.0, 1.0, 0.2)
for _, t := range ticks {
axisStyle.GetStrokeOptions().WriteToRenderer(r)
ty := canvasBox.Bottom - int(t*float64(canvasBox.Height()))
r.MoveTo(canvasBox.Right, ty)
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty)
r.Stroke()
axisStyle.GetTextOptions().WriteToRenderer(r)
text := fmt.Sprintf("%0.0f%%", t*100)
tb := r.MeasureText(text)
Draw.Text(r, text, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle)
}
}
}
func (sbc StackedBarChart) drawHorizontalYAxis(r Renderer, canvasBox Box) {
if !sbc.YAxis.Hidden {
axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsHorizontalAxes())
axisStyle.WriteToRenderer(r)
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Left, canvasBox.Top)
r.Stroke()
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Left-DefaultHorizontalTickWidth, canvasBox.Bottom)
r.Stroke()
cursor := canvasBox.Top
for _, bar := range sbc.Bars {
barLabelBox := Box{
Top: cursor,
Left: 0,
Right: canvasBox.Left - DefaultYAxisMargin,
Bottom: cursor + bar.GetWidth() + sbc.GetBarSpacing(),
}
if len(bar.Name) > 0 {
Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle)
}
axisStyle.WriteToRenderer(r)
r.MoveTo(canvasBox.Left, barLabelBox.Bottom)
r.LineTo(canvasBox.Left-DefaultHorizontalTickWidth, barLabelBox.Bottom)
r.Stroke()
cursor += bar.GetWidth() + sbc.GetBarSpacing()
}
}
}
func (sbc StackedBarChart) drawTitle(r Renderer) {
if len(sbc.Title) > 0 && !sbc.TitleStyle.Hidden {
r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont()))
r.SetFontColor(sbc.TitleStyle.GetFontColor(sbc.GetColorPalette().TextColor()))
titleFontSize := sbc.TitleStyle.GetFontSize(DefaultTitleFontSize)
r.SetFontSize(titleFontSize)
textBox := r.MeasureText(sbc.Title)
textWidth := textBox.Width()
textHeight := textBox.Height()
titleX := (sbc.GetWidth() >> 1) - (textWidth >> 1)
titleY := sbc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
r.Text(sbc.Title, titleX, titleY)
}
}
func (sbc StackedBarChart) getCanvasStyle() Style {
return sbc.Canvas.InheritFrom(sbc.styleDefaultsCanvas())
}
func (sbc StackedBarChart) styleDefaultsCanvas() Style {
return Style{
FillColor: sbc.GetColorPalette().CanvasColor(),
StrokeColor: sbc.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultCanvasStrokeWidth,
}
}
// GetColorPalette returns the color palette for the chart.
func (sbc StackedBarChart) GetColorPalette() ColorPalette {
if sbc.ColorPalette != nil {
return sbc.ColorPalette
}
return AlternateColorPalette
}
func (sbc StackedBarChart) getDefaultCanvasBox() Box {
return sbc.Box()
}
func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box {
var totalWidth int
for _, bar := range sbc.Bars {
totalWidth += bar.GetWidth() + sbc.GetBarSpacing()
}
if !sbc.XAxis.Hidden {
xaxisHeight := DefaultVerticalTickHeight
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
axisStyle.WriteToRenderer(r)
cursor := canvasBox.Left
for _, bar := range sbc.Bars {
if len(bar.Name) > 0 {
barLabelBox := Box{
Top: canvasBox.Bottom + DefaultXAxisMargin,
Left: cursor,
Right: cursor + bar.GetWidth() + sbc.GetBarSpacing(),
Bottom: sbc.GetHeight(),
}
lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle)
linesBox := Text.MeasureLines(r, lines, axisStyle)
xaxisHeight = MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
}
}
return Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Left + totalWidth,
Bottom: sbc.GetHeight() - xaxisHeight,
}
}
return Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Left + totalWidth,
Bottom: canvasBox.Bottom,
}
}
func (sbc StackedBarChart) getHorizontalAdjustedCanvasBox(r Renderer, canvasBox Box) Box {
var totalHeight int
for _, bar := range sbc.Bars {
totalHeight += bar.GetWidth() + sbc.GetBarSpacing()
}
if !sbc.YAxis.Hidden {
yAxisWidth := DefaultHorizontalTickWidth
axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsHorizontalAxes())
axisStyle.WriteToRenderer(r)
cursor := canvasBox.Top
for _, bar := range sbc.Bars {
if len(bar.Name) > 0 {
barLabelBox := Box{
Top: cursor,
Left: 0,
Right: canvasBox.Left + DefaultYAxisMargin,
Bottom: cursor + bar.GetWidth() + sbc.GetBarSpacing(),
}
lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle)
linesBox := Text.MeasureLines(r, lines, axisStyle)
yAxisWidth = MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), yAxisWidth)
}
}
return Box{
Top: canvasBox.Top,
Left: canvasBox.Left + yAxisWidth,
Right: canvasBox.Right,
Bottom: canvasBox.Top + totalHeight,
}
}
return Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Right,
Bottom: canvasBox.Top + totalHeight,
}
}
// Box returns the chart bounds as a box.
func (sbc StackedBarChart) Box() Box {
dpr := sbc.Background.Padding.GetRight(10)
dpb := sbc.Background.Padding.GetBottom(50)
return Box{
Top: sbc.Background.Padding.GetTop(20),
Left: sbc.Background.Padding.GetLeft(20),
Right: sbc.GetWidth() - dpr,
Bottom: sbc.GetHeight() - dpb,
}
}
func (sbc StackedBarChart) styleDefaultsStackedBarValue(index int) Style {
return Style{
StrokeColor: sbc.GetColorPalette().GetSeriesColor(index),
StrokeWidth: 3.0,
FillColor: sbc.GetColorPalette().GetSeriesColor(index),
FontSize: sbc.getScaledFontSize(),
FontColor: sbc.GetColorPalette().TextColor(),
Font: sbc.GetFont(),
}
}
func (sbc StackedBarChart) styleDefaultsTitle() Style {
return sbc.TitleStyle.InheritFrom(Style{
FontColor: DefaultTextColor,
Font: sbc.GetFont(),
FontSize: sbc.getTitleFontSize(),
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
})
}
func (sbc StackedBarChart) getScaledFontSize() float64 {
effectiveDimension := MinInt(sbc.GetWidth(), sbc.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 (sbc StackedBarChart) getTitleFontSize() float64 {
effectiveDimension := MinInt(sbc.GetWidth(), sbc.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 (sbc StackedBarChart) styleDefaultsAxes() Style {
return Style{
StrokeColor: DefaultAxisColor,
Font: sbc.GetFont(),
FontSize: DefaultAxisFontSize,
FontColor: DefaultAxisColor,
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
}
}
func (sbc StackedBarChart) styleDefaultsHorizontalAxes() Style {
return Style{
StrokeColor: DefaultAxisColor,
Font: sbc.GetFont(),
FontSize: DefaultAxisFontSize,
FontColor: DefaultAxisColor,
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignMiddle,
TextWrap: TextWrapWord,
}
}
func (sbc StackedBarChart) styleDefaultsElements() Style {
return Style{
Font: sbc.GetFont(),
}
}

57
vendor/github.com/wcharczuk/go-chart/v2/stringutil.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package chart
import "strings"
// SplitCSV splits a corpus by the `,`, dropping leading or trailing whitespace unless quoted.
func SplitCSV(text string) (output []string) {
if len(text) == 0 {
return
}
var state int
var word []rune
var opened rune
for _, r := range text {
switch state {
case 0: // word
if isQuote(r) {
opened = r
state = 1
} else if isCSVDelim(r) {
output = append(output, strings.TrimSpace(string(word)))
word = nil
} else {
word = append(word, r)
}
case 1: // we're in a quoted section
if matchesQuote(opened, r) {
state = 0
continue
}
word = append(word, r)
}
}
if len(word) > 0 {
output = append(output, strings.TrimSpace(string(word)))
}
return
}
func isCSVDelim(r rune) bool {
return r == rune(',')
}
func isQuote(r rune) bool {
return r == '"' || r == '\'' || r == '“' || r == '”' || r == '`'
}
func matchesQuote(a, b rune) bool {
if a == '“' && b == '”' {
return true
}
if a == '”' && b == '“' {
return true
}
return a == b
}

480
vendor/github.com/wcharczuk/go-chart/v2/style.go generated vendored Normal file
View File

@ -0,0 +1,480 @@
package chart
import (
"fmt"
"strings"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/drawing"
)
const (
// Disabled indicates if the value should be interpreted as set intentionally to zero.
// this is because golang optionals aren't here yet.
Disabled = -1
)
// Hidden is a prebuilt style with the `Hidden` property set to true.
func Hidden() Style {
return Style{
Hidden: true,
}
}
// Shown is a prebuilt style with the `Hidden` property set to false.
// You can also think of this as the default.
func Shown() Style {
return Style{
Hidden: false,
}
}
// StyleTextDefaults returns a style for drawing outside a
// chart context.
func StyleTextDefaults() Style {
font, _ := GetDefaultFont()
return Style{
Hidden: false,
Font: font,
FontColor: DefaultTextColor,
FontSize: DefaultTitleFontSize,
}
}
// Style is a simple style set.
type Style struct {
Hidden bool
Padding Box
ClassName string
StrokeWidth float64
StrokeColor drawing.Color
StrokeDashArray []float64
DotColor drawing.Color
DotWidth float64
DotWidthProvider SizeProvider
DotColorProvider DotColorProvider
FillColor drawing.Color
FontSize float64
FontColor drawing.Color
Font *truetype.Font
TextHorizontalAlign TextHorizontalAlign
TextVerticalAlign TextVerticalAlign
TextWrap TextWrap
TextLineSpacing int
TextRotationDegrees float64 //0 is unset or normal
}
// IsZero returns if the object is set or not.
func (s Style) IsZero() bool {
return !s.Hidden &&
s.StrokeColor.IsZero() &&
s.StrokeWidth == 0 &&
s.DotColor.IsZero() &&
s.DotWidth == 0 &&
s.FillColor.IsZero() &&
s.FontColor.IsZero() &&
s.FontSize == 0 &&
s.Font == nil &&
s.ClassName == ""
}
// String returns a text representation of the style.
func (s Style) String() string {
if s.IsZero() {
return "{}"
}
var output []string
if s.Hidden {
output = []string{"\"hidden\": true"}
} else {
output = []string{"\"hidden\": false"}
}
if s.ClassName != "" {
output = append(output, fmt.Sprintf("\"class_name\": %s", s.ClassName))
} else {
output = append(output, "\"class_name\": null")
}
if !s.Padding.IsZero() {
output = append(output, fmt.Sprintf("\"padding\": %s", s.Padding.String()))
} else {
output = append(output, "\"padding\": null")
}
if s.StrokeWidth >= 0 {
output = append(output, fmt.Sprintf("\"stroke_width\": %0.2f", s.StrokeWidth))
} else {
output = append(output, "\"stroke_width\": null")
}
if !s.StrokeColor.IsZero() {
output = append(output, fmt.Sprintf("\"stroke_color\": %s", s.StrokeColor.String()))
} else {
output = append(output, "\"stroke_color\": null")
}
if len(s.StrokeDashArray) > 0 {
var elements []string
for _, v := range s.StrokeDashArray {
elements = append(elements, fmt.Sprintf("%.2f", v))
}
dashArray := strings.Join(elements, ", ")
output = append(output, fmt.Sprintf("\"stroke_dash_array\": [%s]", dashArray))
} else {
output = append(output, "\"stroke_dash_array\": null")
}
if s.DotWidth >= 0 {
output = append(output, fmt.Sprintf("\"dot_width\": %0.2f", s.DotWidth))
} else {
output = append(output, "\"dot_width\": null")
}
if !s.DotColor.IsZero() {
output = append(output, fmt.Sprintf("\"dot_color\": %s", s.DotColor.String()))
} else {
output = append(output, "\"dot_color\": null")
}
if !s.FillColor.IsZero() {
output = append(output, fmt.Sprintf("\"fill_color\": %s", s.FillColor.String()))
} else {
output = append(output, "\"fill_color\": null")
}
if s.FontSize != 0 {
output = append(output, fmt.Sprintf("\"font_size\": \"%0.2fpt\"", s.FontSize))
} else {
output = append(output, "\"font_size\": null")
}
if !s.FontColor.IsZero() {
output = append(output, fmt.Sprintf("\"font_color\": %s", s.FontColor.String()))
} else {
output = append(output, "\"font_color\": null")
}
if s.Font != nil {
output = append(output, fmt.Sprintf("\"font\": \"%s\"", s.Font.Name(truetype.NameIDFontFamily)))
} else {
output = append(output, "\"font_color\": null")
}
return "{" + strings.Join(output, ", ") + "}"
}
// GetClassName returns the class name or a default.
func (s Style) GetClassName(defaults ...string) string {
if s.ClassName == "" {
if len(defaults) > 0 {
return defaults[0]
}
return ""
}
return s.ClassName
}
// GetStrokeColor returns the stroke color.
func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color {
if s.StrokeColor.IsZero() {
if len(defaults) > 0 {
return defaults[0]
}
return drawing.ColorTransparent
}
return s.StrokeColor
}
// GetFillColor returns the fill color.
func (s Style) GetFillColor(defaults ...drawing.Color) drawing.Color {
if s.FillColor.IsZero() {
if len(defaults) > 0 {
return defaults[0]
}
return drawing.ColorTransparent
}
return s.FillColor
}
// GetDotColor returns the stroke color.
func (s Style) GetDotColor(defaults ...drawing.Color) drawing.Color {
if s.DotColor.IsZero() {
if len(defaults) > 0 {
return defaults[0]
}
return drawing.ColorTransparent
}
return s.DotColor
}
// GetStrokeWidth returns the stroke width.
func (s Style) GetStrokeWidth(defaults ...float64) float64 {
if s.StrokeWidth == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultStrokeWidth
}
return s.StrokeWidth
}
// GetDotWidth returns the dot width for scatter plots.
func (s Style) GetDotWidth(defaults ...float64) float64 {
if s.DotWidth == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultDotWidth
}
return s.DotWidth
}
// GetStrokeDashArray returns the stroke dash array.
func (s Style) GetStrokeDashArray(defaults ...[]float64) []float64 {
if len(s.StrokeDashArray) == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return nil
}
return s.StrokeDashArray
}
// GetFontSize gets the font size.
func (s Style) GetFontSize(defaults ...float64) float64 {
if s.FontSize == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultFontSize
}
return s.FontSize
}
// GetFontColor gets the font size.
func (s Style) GetFontColor(defaults ...drawing.Color) drawing.Color {
if s.FontColor.IsZero() {
if len(defaults) > 0 {
return defaults[0]
}
return drawing.ColorTransparent
}
return s.FontColor
}
// GetFont returns the font face.
func (s Style) GetFont(defaults ...*truetype.Font) *truetype.Font {
if s.Font == nil {
if len(defaults) > 0 {
return defaults[0]
}
return nil
}
return s.Font
}
// GetPadding returns the padding.
func (s Style) GetPadding(defaults ...Box) Box {
if s.Padding.IsZero() {
if len(defaults) > 0 {
return defaults[0]
}
return Box{}
}
return s.Padding
}
// GetTextHorizontalAlign returns the horizontal alignment.
func (s Style) GetTextHorizontalAlign(defaults ...TextHorizontalAlign) TextHorizontalAlign {
if s.TextHorizontalAlign == TextHorizontalAlignUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextHorizontalAlignUnset
}
return s.TextHorizontalAlign
}
// GetTextVerticalAlign returns the vertical alignment.
func (s Style) GetTextVerticalAlign(defaults ...TextVerticalAlign) TextVerticalAlign {
if s.TextVerticalAlign == TextVerticalAlignUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextVerticalAlignUnset
}
return s.TextVerticalAlign
}
// GetTextWrap returns the word wrap.
func (s Style) GetTextWrap(defaults ...TextWrap) TextWrap {
if s.TextWrap == TextWrapUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextWrapUnset
}
return s.TextWrap
}
// GetTextLineSpacing returns the spacing in pixels between lines of text (vertically).
func (s Style) GetTextLineSpacing(defaults ...int) int {
if s.TextLineSpacing == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultLineSpacing
}
return s.TextLineSpacing
}
// GetTextRotationDegrees returns the text rotation in degrees.
func (s Style) GetTextRotationDegrees(defaults ...float64) float64 {
if s.TextRotationDegrees == 0 {
if len(defaults) > 0 {
return defaults[0]
}
}
return s.TextRotationDegrees
}
// WriteToRenderer passes the style's options to a renderer.
func (s Style) WriteToRenderer(r Renderer) {
r.SetClassName(s.GetClassName())
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray())
r.SetFillColor(s.GetFillColor())
r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize())
r.ClearTextRotation()
if s.GetTextRotationDegrees() != 0 {
r.SetTextRotation(DegreesToRadians(s.GetTextRotationDegrees()))
}
}
// WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer.
func (s Style) WriteDrawingOptionsToRenderer(r Renderer) {
r.SetClassName(s.GetClassName())
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray())
r.SetFillColor(s.GetFillColor())
}
// WriteTextOptionsToRenderer passes just the text style options to a renderer.
func (s Style) WriteTextOptionsToRenderer(r Renderer) {
r.SetClassName(s.GetClassName())
r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize())
}
// InheritFrom coalesces two styles into a new style.
func (s Style) InheritFrom(defaults Style) (final Style) {
final.ClassName = s.GetClassName(defaults.ClassName)
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray)
final.DotColor = s.GetDotColor(defaults.DotColor)
final.DotWidth = s.GetDotWidth(defaults.DotWidth)
final.DotWidthProvider = s.DotWidthProvider
final.DotColorProvider = s.DotColorProvider
final.FillColor = s.GetFillColor(defaults.FillColor)
final.FontColor = s.GetFontColor(defaults.FontColor)
final.FontSize = s.GetFontSize(defaults.FontSize)
final.Font = s.GetFont(defaults.Font)
final.Padding = s.GetPadding(defaults.Padding)
final.TextHorizontalAlign = s.GetTextHorizontalAlign(defaults.TextHorizontalAlign)
final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign)
final.TextWrap = s.GetTextWrap(defaults.TextWrap)
final.TextLineSpacing = s.GetTextLineSpacing(defaults.TextLineSpacing)
final.TextRotationDegrees = s.GetTextRotationDegrees(defaults.TextRotationDegrees)
return
}
// GetStrokeOptions returns the stroke components.
func (s Style) GetStrokeOptions() Style {
return Style{
ClassName: s.ClassName,
StrokeDashArray: s.StrokeDashArray,
StrokeColor: s.StrokeColor,
StrokeWidth: s.StrokeWidth,
}
}
// GetFillOptions returns the fill components.
func (s Style) GetFillOptions() Style {
return Style{
ClassName: s.ClassName,
FillColor: s.FillColor,
}
}
// GetDotOptions returns the dot components.
func (s Style) GetDotOptions() Style {
return Style{
ClassName: s.ClassName,
StrokeDashArray: nil,
FillColor: s.DotColor,
StrokeColor: s.DotColor,
StrokeWidth: 1.0,
}
}
// GetFillAndStrokeOptions returns the fill and stroke components.
func (s Style) GetFillAndStrokeOptions() Style {
return Style{
ClassName: s.ClassName,
StrokeDashArray: s.StrokeDashArray,
FillColor: s.FillColor,
StrokeColor: s.StrokeColor,
StrokeWidth: s.StrokeWidth,
}
}
// GetTextOptions returns just the text components of the style.
func (s Style) GetTextOptions() Style {
return Style{
ClassName: s.ClassName,
FontColor: s.FontColor,
FontSize: s.FontSize,
Font: s.Font,
TextHorizontalAlign: s.TextHorizontalAlign,
TextVerticalAlign: s.TextVerticalAlign,
TextWrap: s.TextWrap,
TextLineSpacing: s.TextLineSpacing,
TextRotationDegrees: s.TextRotationDegrees,
}
}
// ShouldDrawStroke tells drawing functions if they should draw the stroke.
func (s Style) ShouldDrawStroke() bool {
return !s.StrokeColor.IsZero() && s.StrokeWidth > 0
}
// ShouldDrawDot tells drawing functions if they should draw the dot.
func (s Style) ShouldDrawDot() bool {
return (!s.DotColor.IsZero() && s.DotWidth > 0) || s.DotColorProvider != nil || s.DotWidthProvider != nil
}
// ShouldDrawFill tells drawing functions if they should draw the stroke.
func (s Style) ShouldDrawFill() bool {
return !s.FillColor.IsZero()
}

166
vendor/github.com/wcharczuk/go-chart/v2/text.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
package chart
import (
"strings"
)
// TextHorizontalAlign is an enum for the horizontal alignment options.
type TextHorizontalAlign int
const (
// TextHorizontalAlignUnset is the unset state for text horizontal alignment.
TextHorizontalAlignUnset TextHorizontalAlign = 0
// TextHorizontalAlignLeft aligns a string horizontally so that it's left ligature starts at horizontal pixel 0.
TextHorizontalAlignLeft TextHorizontalAlign = 1
// TextHorizontalAlignCenter left aligns a string horizontally so that there are equal pixels
// to the left and to the right of a string within a box.
TextHorizontalAlignCenter TextHorizontalAlign = 2
// TextHorizontalAlignRight right aligns a string horizontally so that the right ligature ends at the right-most pixel
// of a box.
TextHorizontalAlignRight TextHorizontalAlign = 3
)
// TextWrap is an enum for the word wrap options.
type TextWrap int
const (
// TextWrapUnset is the unset state for text wrap options.
TextWrapUnset TextWrap = 0
// TextWrapNone will spill text past horizontal boundaries.
TextWrapNone TextWrap = 1
// TextWrapWord will split a string on words (i.e. spaces) to fit within a horizontal boundary.
TextWrapWord TextWrap = 2
// TextWrapRune will split a string on a rune (i.e. utf-8 codepage) to fit within a horizontal boundary.
TextWrapRune TextWrap = 3
)
// TextVerticalAlign is an enum for the vertical alignment options.
type TextVerticalAlign int
const (
// TextVerticalAlignUnset is the unset state for vertical alignment options.
TextVerticalAlignUnset TextVerticalAlign = 0
// TextVerticalAlignBaseline aligns text according to the "baseline" of the string, or where a normal ascender begins.
TextVerticalAlignBaseline TextVerticalAlign = 1
// TextVerticalAlignBottom aligns the text according to the lowers pixel of any of the ligatures (ex. g or q both extend below the baseline).
TextVerticalAlignBottom TextVerticalAlign = 2
// TextVerticalAlignMiddle aligns the text so that there is an equal amount of space above and below the top and bottom of the ligatures.
TextVerticalAlignMiddle TextVerticalAlign = 3
// TextVerticalAlignMiddleBaseline aligns the text vertically so that there is an equal number of pixels above and below the baseline of the string.
TextVerticalAlignMiddleBaseline TextVerticalAlign = 4
// TextVerticalAlignTop alignts the text so that the top of the ligatures are at y-pixel 0 in the container.
TextVerticalAlignTop TextVerticalAlign = 5
)
var (
// Text contains utilities for text.
Text = &text{}
)
// TextStyle encapsulates text style options.
type TextStyle struct {
HorizontalAlign TextHorizontalAlign
VerticalAlign TextVerticalAlign
Wrap TextWrap
}
type text struct{}
func (t text) WrapFit(r Renderer, value string, width int, style Style) []string {
switch style.TextWrap {
case TextWrapRune:
return t.WrapFitRune(r, value, width, style)
case TextWrapWord:
return t.WrapFitWord(r, value, width, style)
}
return []string{value}
}
func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []string {
style.WriteToRenderer(r)
var output []string
var line string
var word string
var textBox Box
for _, c := range value {
if c == rune('\n') { // commit the line to output
output = append(output, t.Trim(line+word))
line = ""
word = ""
continue
}
textBox = r.MeasureText(line + word + string(c))
if textBox.Width() >= width {
output = append(output, t.Trim(line))
line = word
word = string(c)
continue
}
if c == rune(' ') || c == rune('\t') {
line = line + word + string(c)
word = ""
continue
}
word = word + string(c)
}
return append(output, t.Trim(line+word))
}
func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []string {
style.WriteToRenderer(r)
var output []string
var line string
var textBox Box
for _, c := range value {
if c == rune('\n') {
output = append(output, line)
line = ""
continue
}
textBox = r.MeasureText(line + string(c))
if textBox.Width() >= width {
output = append(output, line)
line = string(c)
continue
}
line = line + string(c)
}
return t.appendLast(output, line)
}
func (t text) Trim(value string) string {
return strings.Trim(value, " \t\n\r")
}
func (t text) MeasureLines(r Renderer, lines []string, style Style) Box {
style.WriteTextOptionsToRenderer(r)
var output Box
for index, line := range lines {
lineBox := r.MeasureText(line)
output.Right = MaxInt(lineBox.Right, output.Right)
output.Bottom += lineBox.Height()
if index < len(lines)-1 {
output.Bottom += +style.GetTextLineSpacing()
}
}
return output
}
func (t text) appendLast(lines []string, text string) []string {
if len(lines) == 0 {
return []string{text}
}
lastLine := lines[len(lines)-1]
lines[len(lines)-1] = lastLine + text
return lines
}

115
vendor/github.com/wcharczuk/go-chart/v2/tick.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
package chart
import (
"fmt"
"math"
"strings"
)
// TicksProvider is a type that provides ticks.
type TicksProvider interface {
GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick
}
// Tick represents a label on an axis.
type Tick struct {
Value float64
Label string
}
// Ticks is an array of ticks.
type Ticks []Tick
// Len returns the length of the ticks set.
func (t Ticks) Len() int {
return len(t)
}
// Swap swaps two elements.
func (t Ticks) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
// Less returns if i's value is less than j's value.
func (t Ticks) Less(i, j int) bool {
return t[i].Value < t[j].Value
}
// String returns a string representation of the set of ticks.
func (t Ticks) String() string {
var values []string
for i, tick := range t {
values = append(values, fmt.Sprintf("[%d: %s]", i, tick.Label))
}
return strings.Join(values, ", ")
}
// GenerateContinuousTicks generates a set of ticks.
func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, vf ValueFormatter) []Tick {
if vf == nil {
vf = FloatValueFormatter
}
var ticks []Tick
min, max := ra.GetMin(), ra.GetMax()
if ra.IsDescending() {
ticks = append(ticks, Tick{
Value: max,
Label: vf(max),
})
} else {
ticks = append(ticks, Tick{
Value: min,
Label: vf(min),
})
}
minLabel := vf(min)
style.GetTextOptions().WriteToRenderer(r)
labelBox := r.MeasureText(minLabel)
var tickSize float64
if isVertical {
tickSize = float64(labelBox.Height() + DefaultMinimumTickVerticalSpacing)
} else {
tickSize = float64(labelBox.Width() + DefaultMinimumTickHorizontalSpacing)
}
domain := float64(ra.GetDomain())
domainRemainder := domain - (tickSize * 2)
intermediateTickCount := int(math.Floor(float64(domainRemainder) / float64(tickSize)))
rangeDelta := math.Abs(max - min)
tickStep := rangeDelta / float64(intermediateTickCount)
roundTo := GetRoundToForDelta(rangeDelta) / 10
intermediateTickCount = MinInt(intermediateTickCount, DefaultTickCountSanityCheck)
for x := 1; x < intermediateTickCount; x++ {
var tickValue float64
if ra.IsDescending() {
tickValue = max - RoundUp(tickStep*float64(x), roundTo)
} else {
tickValue = min + RoundUp(tickStep*float64(x), roundTo)
}
ticks = append(ticks, Tick{
Value: tickValue,
Label: vf(tickValue),
})
}
if ra.IsDescending() {
ticks = append(ticks, Tick{
Value: min,
Label: vf(min),
})
} else {
ticks = append(ticks, Tick{
Value: max,
Label: vf(max),
})
}
return ticks
}

91
vendor/github.com/wcharczuk/go-chart/v2/time_series.go generated vendored Normal file
View File

@ -0,0 +1,91 @@
package chart
import (
"fmt"
"time"
)
// Interface Assertions.
var (
_ Series = (*TimeSeries)(nil)
_ FirstValuesProvider = (*TimeSeries)(nil)
_ LastValuesProvider = (*TimeSeries)(nil)
_ ValueFormatterProvider = (*TimeSeries)(nil)
)
// TimeSeries is a line on a chart.
type TimeSeries struct {
Name string
Style Style
YAxis YAxisType
XValues []time.Time
YValues []float64
}
// GetName returns the name of the time series.
func (ts TimeSeries) GetName() string {
return ts.Name
}
// GetStyle returns the line style.
func (ts TimeSeries) GetStyle() Style {
return ts.Style
}
// Len returns the number of elements in the series.
func (ts TimeSeries) Len() int {
return len(ts.XValues)
}
// GetValues gets x, y values at a given index.
func (ts TimeSeries) GetValues(index int) (x, y float64) {
x = TimeToFloat64(ts.XValues[index])
y = ts.YValues[index]
return
}
// GetFirstValues gets the first values.
func (ts TimeSeries) GetFirstValues() (x, y float64) {
x = TimeToFloat64(ts.XValues[0])
y = ts.YValues[0]
return
}
// GetLastValues gets the last values.
func (ts TimeSeries) GetLastValues() (x, y float64) {
x = TimeToFloat64(ts.XValues[len(ts.XValues)-1])
y = ts.YValues[len(ts.YValues)-1]
return
}
// GetValueFormatters returns value formatter defaults for the series.
func (ts TimeSeries) GetValueFormatters() (x, y ValueFormatter) {
x = TimeValueFormatter
y = FloatValueFormatter
return
}
// GetYAxis returns which YAxis the series draws on.
func (ts TimeSeries) GetYAxis() YAxisType {
return ts.YAxis
}
// Render renders the series.
func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ts.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ts)
}
// Validate validates the series.
func (ts TimeSeries) Validate() error {
if len(ts.XValues) == 0 {
return fmt.Errorf("time series must have xvalues set")
}
if len(ts.YValues) == 0 {
return fmt.Errorf("time series must have yvalues set")
}
return nil
}

46
vendor/github.com/wcharczuk/go-chart/v2/times.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package chart
import (
"sort"
"time"
)
// Assert types implement interfaces.
var (
_ Sequence = (*Times)(nil)
_ sort.Interface = (*Times)(nil)
)
// Times are an array of times.
// It wraps the array with methods that implement `seq.Provider`.
type Times []time.Time
// Array returns the times to an array.
func (t Times) Array() []time.Time {
return []time.Time(t)
}
// Len returns the length of the array.
func (t Times) Len() int {
return len(t)
}
// GetValue returns a value at an index as a time.
func (t Times) GetValue(index int) float64 {
return ToFloat64(t[index])
}
// Swap implements sort.Interface.
func (t Times) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
// Less implements sort.Interface.
func (t Times) Less(i, j int) bool {
return t[i].Before(t[j])
}
// ToFloat64 returns a float64 representation of a time.
func ToFloat64(t time.Time) float64 {
return float64(t.UnixNano())
}

150
vendor/github.com/wcharczuk/go-chart/v2/timeutil.go generated vendored Normal file
View File

@ -0,0 +1,150 @@
package chart
import "time"
// SecondsPerXYZ
const (
SecondsPerHour = 60 * 60
SecondsPerDay = 60 * 60 * 24
)
// TimeMillis returns a duration as a float millis.
func TimeMillis(d time.Duration) float64 {
return float64(d) / float64(time.Millisecond)
}
// DiffHours returns the difference in hours between two times.
func DiffHours(t1, t2 time.Time) (hours int) {
t1n := t1.Unix()
t2n := t2.Unix()
var diff int64
if t1n > t2n {
diff = t1n - t2n
} else {
diff = t2n - t1n
}
return int(diff / (SecondsPerHour))
}
// TimeMin returns the minimum and maximum times in a given range.
func TimeMin(times ...time.Time) (min time.Time) {
if len(times) == 0 {
return
}
min = times[0]
for index := 1; index < len(times); index++ {
if times[index].Before(min) {
min = times[index]
}
}
return
}
// TimeMax returns the minimum and maximum times in a given range.
func TimeMax(times ...time.Time) (max time.Time) {
if len(times) == 0 {
return
}
max = times[0]
for index := 1; index < len(times); index++ {
if times[index].After(max) {
max = times[index]
}
}
return
}
// TimeMinMax returns the minimum and maximum times in a given range.
func TimeMinMax(times ...time.Time) (min, max time.Time) {
if len(times) == 0 {
return
}
min = times[0]
max = times[0]
for index := 1; index < len(times); index++ {
if times[index].Before(min) {
min = times[index]
}
if times[index].After(max) {
max = times[index]
}
}
return
}
// TimeToFloat64 returns a float64 representation of a time.
func TimeToFloat64(t time.Time) float64 {
return float64(t.UnixNano())
}
// TimeFromFloat64 returns a time from a float64.
func TimeFromFloat64(tf float64) time.Time {
return time.Unix(0, int64(tf))
}
// TimeDescending sorts a given list of times ascending, or min to max.
type TimeDescending []time.Time
// Len implements sort.Sorter
func (d TimeDescending) Len() int { return len(d) }
// Swap implements sort.Sorter
func (d TimeDescending) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
// Less implements sort.Sorter
func (d TimeDescending) Less(i, j int) bool { return d[i].After(d[j]) }
// TimeAscending sorts a given list of times ascending, or min to max.
type TimeAscending []time.Time
// Len implements sort.Sorter
func (a TimeAscending) Len() int { return len(a) }
// Swap implements sort.Sorter
func (a TimeAscending) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Less implements sort.Sorter
func (a TimeAscending) Less(i, j int) bool { return a[i].Before(a[j]) }
// Days generates a seq of timestamps by day, from -days to today.
func Days(days int) []time.Time {
var values []time.Time
for day := days; day >= 0; day-- {
values = append(values, time.Now().AddDate(0, 0, -day))
}
return values
}
// Hours returns a sequence of times by the hour for a given number of hours
// after a given start.
func Hours(start time.Time, totalHours int) []time.Time {
times := make([]time.Time, totalHours)
last := start
for i := 0; i < totalHours; i++ {
times[i] = last
last = last.Add(time.Hour)
}
return times
}
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
func HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
start, end := TimeMinMax(xdata...)
totalHours := DiffHours(start, end)
finalTimes := Hours(start, totalHours+1)
finalValues := make([]float64, totalHours+1)
var hoursFromStart int
for i, xd := range xdata {
hoursFromStart = DiffHours(start, xd)
finalValues[hoursFromStart] = ydata[i]
}
return finalTimes, finalValues
}

53
vendor/github.com/wcharczuk/go-chart/v2/value.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package chart
// Value is a chart value.
type Value struct {
Style Style
Label string
Value float64
}
// Values is an array of Value.
type Values []Value
// Values returns the values.
func (vs Values) Values() []float64 {
values := make([]float64, len(vs))
for index, v := range vs {
values[index] = v.Value
}
return values
}
// ValuesNormalized returns normalized values.
func (vs Values) ValuesNormalized() []float64 {
return Normalize(vs.Values()...)
}
// Normalize returns the values normalized.
func (vs Values) Normalize() []Value {
var output []Value
var total float64
for _, v := range vs {
total += v.Value
}
for _, v := range vs {
if v.Value > 0 {
output = append(output, Value{
Style: v.Style,
Label: v.Label,
Value: RoundDown(v.Value/total, 0.0001),
})
}
}
return output
}
// Value2 is a two axis value.
type Value2 struct {
Style Style
Label string
XValue, YValue float64
}

220
vendor/github.com/wcharczuk/go-chart/v2/value_buffer.go generated vendored Normal file
View File

@ -0,0 +1,220 @@
package chart
import (
"fmt"
"strings"
)
const (
bufferMinimumGrow = 4
bufferShrinkThreshold = 32
bufferGrowFactor = 200
bufferDefaultCapacity = 4
)
// NewValueBuffer creates a new value buffer with an optional set of values.
func NewValueBuffer(values ...float64) *ValueBuffer {
var tail int
array := make([]float64, MaxInt(len(values), bufferDefaultCapacity))
if len(values) > 0 {
copy(array, values)
tail = len(values)
}
return &ValueBuffer{
array: array,
head: 0,
tail: tail,
size: len(values),
}
}
// NewValueBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
func NewValueBufferWithCapacity(capacity int) *ValueBuffer {
return &ValueBuffer{
array: make([]float64, capacity),
head: 0,
tail: 0,
size: 0,
}
}
// ValueBuffer is a fifo datastructure that is backed by a pre-allocated array.
// Instead of allocating a whole new node object for each element, array elements are re-used (which saves GC churn).
// Enqueue can be O(n), Dequeue is generally O(1).
// Buffer implements `seq.Provider`
type ValueBuffer struct {
array []float64
head int
tail int
size int
}
// Len returns the length of the Buffer (as it is currently populated).
// Actual memory footprint may be different.
func (b *ValueBuffer) Len() int {
return b.size
}
// GetValue implements seq provider.
func (b *ValueBuffer) GetValue(index int) float64 {
effectiveIndex := (b.head + index) % len(b.array)
return b.array[effectiveIndex]
}
// Capacity returns the total size of the Buffer, including empty elements.
func (b *ValueBuffer) Capacity() int {
return len(b.array)
}
// SetCapacity sets the capacity of the Buffer.
func (b *ValueBuffer) SetCapacity(capacity int) {
newArray := make([]float64, capacity)
if b.size > 0 {
if b.head < b.tail {
arrayCopy(b.array, b.head, newArray, 0, b.size)
} else {
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
}
}
b.array = newArray
b.head = 0
if b.size == capacity {
b.tail = 0
} else {
b.tail = b.size
}
}
// Clear removes all objects from the Buffer.
func (b *ValueBuffer) Clear() {
b.array = make([]float64, bufferDefaultCapacity)
b.head = 0
b.tail = 0
b.size = 0
}
// Enqueue adds an element to the "back" of the Buffer.
func (b *ValueBuffer) Enqueue(value float64) {
if b.size == len(b.array) {
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
if newCapacity < (len(b.array) + bufferMinimumGrow) {
newCapacity = len(b.array) + bufferMinimumGrow
}
b.SetCapacity(newCapacity)
}
b.array[b.tail] = value
b.tail = (b.tail + 1) % len(b.array)
b.size++
}
// Dequeue removes the first element from the RingBuffer.
func (b *ValueBuffer) Dequeue() float64 {
if b.size == 0 {
return 0
}
removed := b.array[b.head]
b.head = (b.head + 1) % len(b.array)
b.size--
return removed
}
// Peek returns but does not remove the first element.
func (b *ValueBuffer) Peek() float64 {
if b.size == 0 {
return 0
}
return b.array[b.head]
}
// PeekBack returns but does not remove the last element.
func (b *ValueBuffer) PeekBack() float64 {
if b.size == 0 {
return 0
}
if b.tail == 0 {
return b.array[len(b.array)-1]
}
return b.array[b.tail-1]
}
// TrimExcess resizes the capacity of the buffer to better fit the contents.
func (b *ValueBuffer) TrimExcess() {
threshold := float64(len(b.array)) * 0.9
if b.size < int(threshold) {
b.SetCapacity(b.size)
}
}
// Array returns the ring buffer, in order, as an array.
func (b *ValueBuffer) Array() Array {
newArray := make([]float64, b.size)
if b.size == 0 {
return newArray
}
if b.head < b.tail {
arrayCopy(b.array, b.head, newArray, 0, b.size)
} else {
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
}
return Array(newArray)
}
// Each calls the consumer for each element in the buffer.
func (b *ValueBuffer) Each(mapfn func(int, float64)) {
if b.size == 0 {
return
}
var index int
if b.head < b.tail {
for cursor := b.head; cursor < b.tail; cursor++ {
mapfn(index, b.array[cursor])
index++
}
} else {
for cursor := b.head; cursor < len(b.array); cursor++ {
mapfn(index, b.array[cursor])
index++
}
for cursor := 0; cursor < b.tail; cursor++ {
mapfn(index, b.array[cursor])
index++
}
}
}
// String returns a string representation for value buffers.
func (b *ValueBuffer) String() string {
var values []string
for _, elem := range b.Array() {
values = append(values, fmt.Sprintf("%v", elem))
}
return strings.Join(values, " <= ")
}
// --------------------------------------------------------------------------------
// Util methods
// --------------------------------------------------------------------------------
func arrayClear(source []float64, index, length int) {
for x := 0; x < length; x++ {
absoluteIndex := x + index
source[absoluteIndex] = 0
}
}
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) {
for x := 0; x < length; x++ {
from := sourceIndex + x
to := destinationIndex + x
destination[to] = source[from]
}
}

View File

@ -0,0 +1,105 @@
package chart
import (
"fmt"
"strconv"
"time"
)
// ValueFormatter is a function that takes a value and produces a string.
type ValueFormatter func(v interface{}) string
// TimeValueFormatter is a ValueFormatter for timestamps.
func TimeValueFormatter(v interface{}) string {
return formatTime(v, DefaultDateFormat)
}
// TimeHourValueFormatter is a ValueFormatter for timestamps.
func TimeHourValueFormatter(v interface{}) string {
return formatTime(v, DefaultDateHourFormat)
}
// TimeMinuteValueFormatter is a ValueFormatter for timestamps.
func TimeMinuteValueFormatter(v interface{}) string {
return formatTime(v, DefaultDateMinuteFormat)
}
// TimeDateValueFormatter is a ValueFormatter for timestamps.
func TimeDateValueFormatter(v interface{}) string {
return formatTime(v, "2006-01-02")
}
// TimeValueFormatterWithFormat returns a time formatter with a given format.
func TimeValueFormatterWithFormat(format string) ValueFormatter {
return func(v interface{}) string {
return formatTime(v, format)
}
}
// TimeValueFormatterWithFormat is a ValueFormatter for timestamps with a given format.
func formatTime(v interface{}, dateFormat string) string {
if typed, isTyped := v.(time.Time); isTyped {
return typed.Format(dateFormat)
}
if typed, isTyped := v.(int64); isTyped {
return time.Unix(0, typed).Format(dateFormat)
}
if typed, isTyped := v.(float64); isTyped {
return time.Unix(0, int64(typed)).Format(dateFormat)
}
return ""
}
// IntValueFormatter is a ValueFormatter for float64.
func IntValueFormatter(v interface{}) string {
switch v.(type) {
case int:
return strconv.Itoa(v.(int))
case int64:
return strconv.FormatInt(v.(int64), 10)
case float32:
return strconv.FormatInt(int64(v.(float32)), 10)
case float64:
return strconv.FormatInt(int64(v.(float64)), 10)
default:
return ""
}
}
// FloatValueFormatter is a ValueFormatter for float64.
func FloatValueFormatter(v interface{}) string {
return FloatValueFormatterWithFormat(v, DefaultFloatFormat)
}
// PercentValueFormatter is a formatter for percent values.
// NOTE: it normalizes the values, i.e. multiplies by 100.0.
func PercentValueFormatter(v interface{}) string {
if typed, isTyped := v.(float64); isTyped {
return FloatValueFormatterWithFormat(typed*100.0, DefaultPercentValueFormat)
}
return ""
}
// FloatValueFormatterWithFormat is a ValueFormatter for float64 with a given format.
func FloatValueFormatterWithFormat(v interface{}, floatFormat string) string {
if typed, isTyped := v.(int); isTyped {
return fmt.Sprintf(floatFormat, float64(typed))
}
if typed, isTyped := v.(int64); isTyped {
return fmt.Sprintf(floatFormat, float64(typed))
}
if typed, isTyped := v.(float32); isTyped {
return fmt.Sprintf(floatFormat, typed)
}
if typed, isTyped := v.(float64); isTyped {
return fmt.Sprintf(floatFormat, typed)
}
return ""
}
// KValueFormatter is a formatter for K values.
func KValueFormatter(k float64, vf ValueFormatter) ValueFormatter {
return func(v interface{}) string {
return fmt.Sprintf("%0.0fσ %s", k, vf(v))
}
}

View File

@ -0,0 +1,6 @@
package chart
// ValueFormatterProvider is a series that has custom formatters.
type ValueFormatterProvider interface {
GetValueFormatters() (x, y ValueFormatter)
}

View File

@ -0,0 +1,51 @@
package chart
import "github.com/wcharczuk/go-chart/v2/drawing"
// ValuesProvider is a type that produces values.
type ValuesProvider interface {
Len() int
GetValues(index int) (float64, float64)
}
// BoundedValuesProvider allows series to return a range.
type BoundedValuesProvider interface {
Len() int
GetBoundedValues(index int) (x, y1, y2 float64)
}
// FirstValuesProvider is a special type of value provider that can return it's (potentially computed) first value.
type FirstValuesProvider interface {
GetFirstValues() (x, y float64)
}
// LastValuesProvider is a special type of value provider that can return it's (potentially computed) last value.
type LastValuesProvider interface {
GetLastValues() (x, y float64)
}
// BoundedLastValuesProvider is a special type of value provider that can return it's (potentially computed) bounded last value.
type BoundedLastValuesProvider interface {
GetBoundedLastValues() (x, y1, y2 float64)
}
// FullValuesProvider is an interface that combines `ValuesProvider` and `LastValuesProvider`
type FullValuesProvider interface {
ValuesProvider
LastValuesProvider
}
// FullBoundedValuesProvider is an interface that combines `BoundedValuesProvider` and `BoundedLastValuesProvider`
type FullBoundedValuesProvider interface {
BoundedValuesProvider
BoundedLastValuesProvider
}
// SizeProvider is a provider for integer size.
type SizeProvider func(xrange, yrange Range, index int, x, y float64) float64
// ColorProvider is a general provider for color ranges based on values.
type ColorProvider func(v, vmin, vmax float64) drawing.Color
// DotColorProvider is a provider for dot color.
type DotColorProvider func(xrange, yrange Range, index int, x, y float64) drawing.Color

View File

@ -0,0 +1,365 @@
package chart
import (
"bytes"
"fmt"
"io"
"math"
"strings"
"golang.org/x/image/font"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/drawing"
)
// SVG returns a new png/raster renderer.
func SVG(width, height int) (Renderer, error) {
buffer := bytes.NewBuffer([]byte{})
canvas := newCanvas(buffer)
canvas.Start(width, height)
return &vectorRenderer{
b: buffer,
c: canvas,
s: &Style{},
p: []string{},
dpi: DefaultDPI,
}, nil
}
// SVGWithCSS returns a new png/raster renderer with attached custom CSS
// The optional nonce argument sets a CSP nonce.
func SVGWithCSS(css string, nonce string) func(width, height int) (Renderer, error) {
return func(width, height int) (Renderer, error) {
buffer := bytes.NewBuffer([]byte{})
canvas := newCanvas(buffer)
canvas.css = css
canvas.nonce = nonce
canvas.Start(width, height)
return &vectorRenderer{
b: buffer,
c: canvas,
s: &Style{},
p: []string{},
dpi: DefaultDPI,
}, nil
}
}
// vectorRenderer renders chart commands to a bitmap.
type vectorRenderer struct {
dpi float64
b *bytes.Buffer
c *canvas
s *Style
p []string
fc *font.Drawer
}
func (vr *vectorRenderer) ResetStyle() {
vr.s = &Style{Font: vr.s.Font}
vr.fc = nil
}
// GetDPI returns the dpi.
func (vr *vectorRenderer) GetDPI() float64 {
return vr.dpi
}
// SetDPI implements the interface method.
func (vr *vectorRenderer) SetDPI(dpi float64) {
vr.dpi = dpi
vr.c.dpi = dpi
}
// SetClassName implements the interface method.
func (vr *vectorRenderer) SetClassName(classname string) {
vr.s.ClassName = classname
}
// SetStrokeColor implements the interface method.
func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) {
vr.s.StrokeColor = c
}
// SetFillColor implements the interface method.
func (vr *vectorRenderer) SetFillColor(c drawing.Color) {
vr.s.FillColor = c
}
// SetLineWidth implements the interface method.
func (vr *vectorRenderer) SetStrokeWidth(width float64) {
vr.s.StrokeWidth = width
}
// StrokeDashArray sets the stroke dash array.
func (vr *vectorRenderer) SetStrokeDashArray(dashArray []float64) {
vr.s.StrokeDashArray = dashArray
}
// MoveTo implements the interface method.
func (vr *vectorRenderer) MoveTo(x, y int) {
vr.p = append(vr.p, fmt.Sprintf("M %d %d", x, y))
}
// LineTo implements the interface method.
func (vr *vectorRenderer) LineTo(x, y int) {
vr.p = append(vr.p, fmt.Sprintf("L %d %d", x, y))
}
// QuadCurveTo draws a quad curve.
func (vr *vectorRenderer) QuadCurveTo(cx, cy, x, y int) {
vr.p = append(vr.p, fmt.Sprintf("Q%d,%d %d,%d", cx, cy, x, y))
}
func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
startAngle = RadianAdd(startAngle, _pi2)
endAngle := RadianAdd(startAngle, delta)
startx := cx + int(rx*math.Sin(startAngle))
starty := cy - int(ry*math.Cos(startAngle))
if len(vr.p) > 0 {
vr.p = append(vr.p, fmt.Sprintf("L %d %d", startx, starty))
} else {
vr.p = append(vr.p, fmt.Sprintf("M %d %d", startx, starty))
}
endx := cx + int(rx*math.Sin(endAngle))
endy := cy - int(ry*math.Cos(endAngle))
dd := RadiansToDegrees(delta)
largeArcFlag := 0
if delta > _pi {
largeArcFlag = 1
}
vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f %d 1 %d %d", int(rx), int(ry), dd, largeArcFlag, endx, endy))
}
// Close closes a shape.
func (vr *vectorRenderer) Close() {
vr.p = append(vr.p, fmt.Sprintf("Z"))
}
// Stroke draws the path with no fill.
func (vr *vectorRenderer) Stroke() {
vr.drawPath(vr.s.GetStrokeOptions())
}
// Fill draws the path with no stroke.
func (vr *vectorRenderer) Fill() {
vr.drawPath(vr.s.GetFillOptions())
}
// FillStroke draws the path with both fill and stroke.
func (vr *vectorRenderer) FillStroke() {
vr.drawPath(vr.s.GetFillAndStrokeOptions())
}
// drawPath draws a path.
func (vr *vectorRenderer) drawPath(s Style) {
vr.c.Path(strings.Join(vr.p, "\n"), vr.s.GetFillAndStrokeOptions())
vr.p = []string{} // clear the path
}
// Circle implements the interface method.
func (vr *vectorRenderer) Circle(radius float64, x, y int) {
vr.c.Circle(x, y, int(radius), vr.s.GetFillAndStrokeOptions())
}
// SetFont implements the interface method.
func (vr *vectorRenderer) SetFont(f *truetype.Font) {
vr.s.Font = f
}
// SetFontColor implements the interface method.
func (vr *vectorRenderer) SetFontColor(c drawing.Color) {
vr.s.FontColor = c
}
// SetFontSize implements the interface method.
func (vr *vectorRenderer) SetFontSize(size float64) {
vr.s.FontSize = size
}
// Text draws a text blob.
func (vr *vectorRenderer) Text(body string, x, y int) {
vr.c.Text(x, y, body, vr.s.GetTextOptions())
}
// MeasureText uses the truetype font drawer to measure the width of text.
func (vr *vectorRenderer) MeasureText(body string) (box Box) {
if vr.s.GetFont() != nil {
vr.fc = &font.Drawer{
Face: truetype.NewFace(vr.s.GetFont(), &truetype.Options{
DPI: vr.dpi,
Size: vr.s.FontSize,
}),
}
w := vr.fc.MeasureString(body).Ceil()
box.Right = w
box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
if vr.c.textTheta == nil {
return
}
box = box.Corners().Rotate(RadiansToDegrees(*vr.c.textTheta)).Box()
}
return
}
// SetTextRotation sets the text rotation.
func (vr *vectorRenderer) SetTextRotation(radians float64) {
vr.c.textTheta = &radians
}
// ClearTextRotation clears the text rotation.
func (vr *vectorRenderer) ClearTextRotation() {
vr.c.textTheta = nil
}
// Save saves the renderer's contents to a writer.
func (vr *vectorRenderer) Save(w io.Writer) error {
vr.c.End()
_, err := w.Write(vr.b.Bytes())
return err
}
func newCanvas(w io.Writer) *canvas {
return &canvas{
w: w,
dpi: DefaultDPI,
}
}
type canvas struct {
w io.Writer
dpi float64
textTheta *float64
width int
height int
css string
nonce string
}
func (c *canvas) Start(width, height int) {
c.width = width
c.height = height
c.w.Write([]byte(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d">\n`, c.width, c.height)))
if c.css != "" {
c.w.Write([]byte(`<style type="text/css"`))
if c.nonce != "" {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
c.w.Write([]byte(fmt.Sprintf(` nonce="%s"`, c.nonce)))
}
// To avoid compatibility issues between XML and CSS (f.e. with child selectors) we should encapsulate the CSS with CDATA.
c.w.Write([]byte(fmt.Sprintf(`><![CDATA[%s]]></style>`, c.css)))
}
}
func (c *canvas) Path(d string, style Style) {
var strokeDashArrayProperty string
if len(style.StrokeDashArray) > 0 {
strokeDashArrayProperty = c.getStrokeDashArray(style)
}
c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" %s/>`, strokeDashArrayProperty, d, c.styleAsSVG(style))))
}
func (c *canvas) Text(x, y int, body string, style Style) {
if c.textTheta == nil {
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s>%s</text>`, x, y, c.styleAsSVG(style), body)))
} else {
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, RadiansToDegrees(*c.textTheta), x, y)
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
}
}
func (c *canvas) Circle(x, y, r int, style Style) {
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" %s/>`, x, y, r, c.styleAsSVG(style))))
}
func (c *canvas) End() {
c.w.Write([]byte("</svg>"))
}
// getStrokeDashArray returns the stroke-dasharray property of a style.
func (c *canvas) getStrokeDashArray(s Style) string {
if len(s.StrokeDashArray) > 0 {
var values []string
for _, v := range s.StrokeDashArray {
values = append(values, fmt.Sprintf("%0.1f", v))
}
return "stroke-dasharray=\"" + strings.Join(values, ", ") + "\""
}
return ""
}
// GetFontFace returns the font face for the style.
func (c *canvas) getFontFace(s Style) string {
family := "sans-serif"
if s.GetFont() != nil {
name := s.GetFont().Name(truetype.NameIDFontFamily)
if len(name) != 0 {
family = fmt.Sprintf(`'%s',%s`, name, family)
}
}
return fmt.Sprintf("font-family:%s", family)
}
// styleAsSVG returns the style as a svg style or class string.
func (c *canvas) styleAsSVG(s Style) string {
sw := s.StrokeWidth
sc := s.StrokeColor
fc := s.FillColor
fs := s.FontSize
fnc := s.FontColor
if s.ClassName != "" {
var classes []string
classes = append(classes, s.ClassName)
if !sc.IsZero() {
classes = append(classes, "stroke")
}
if !fc.IsZero() {
classes = append(classes, "fill")
}
if fs != 0 || s.Font != nil {
classes = append(classes, "text")
}
return fmt.Sprintf("class=\"%s\"", strings.Join(classes, " "))
}
var pieces []string
if sw != 0 {
pieces = append(pieces, "stroke-width:"+fmt.Sprintf("%d", int(sw)))
} else {
pieces = append(pieces, "stroke-width:0")
}
if !sc.IsZero() {
pieces = append(pieces, "stroke:"+sc.String())
} else {
pieces = append(pieces, "stroke:none")
}
if !fnc.IsZero() {
pieces = append(pieces, "fill:"+fnc.String())
} else if !fc.IsZero() {
pieces = append(pieces, "fill:"+fc.String())
} else {
pieces = append(pieces, "fill:none")
}
if fs != 0 {
pieces = append(pieces, "font-size:"+fmt.Sprintf("%.1fpx", drawing.PointsToPixels(c.dpi, fs)))
}
if s.Font != nil {
pieces = append(pieces, c.getFontFace(s))
}
return fmt.Sprintf("style=\"%s\"", strings.Join(pieces, ";"))
}

269
vendor/github.com/wcharczuk/go-chart/v2/viridis.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
package chart
import "github.com/wcharczuk/go-chart/v2/drawing"
var viridisColors = [256]drawing.Color{
drawing.Color{R: 0x44, G: 0x1, B: 0x54, A: 0xff},
drawing.Color{R: 0x44, G: 0x2, B: 0x55, A: 0xff},
drawing.Color{R: 0x45, G: 0x3, B: 0x57, A: 0xff},
drawing.Color{R: 0x45, G: 0x5, B: 0x58, A: 0xff},
drawing.Color{R: 0x45, G: 0x6, B: 0x5a, A: 0xff},
drawing.Color{R: 0x46, G: 0x8, B: 0x5b, A: 0xff},
drawing.Color{R: 0x46, G: 0x9, B: 0x5d, A: 0xff},
drawing.Color{R: 0x46, G: 0xb, B: 0x5e, A: 0xff},
drawing.Color{R: 0x46, G: 0xc, B: 0x60, A: 0xff},
drawing.Color{R: 0x47, G: 0xe, B: 0x61, A: 0xff},
drawing.Color{R: 0x47, G: 0xf, B: 0x62, A: 0xff},
drawing.Color{R: 0x47, G: 0x11, B: 0x64, A: 0xff},
drawing.Color{R: 0x47, G: 0x12, B: 0x65, A: 0xff},
drawing.Color{R: 0x47, G: 0x14, B: 0x66, A: 0xff},
drawing.Color{R: 0x48, G: 0x15, B: 0x68, A: 0xff},
drawing.Color{R: 0x48, G: 0x16, B: 0x69, A: 0xff},
drawing.Color{R: 0x48, G: 0x18, B: 0x6a, A: 0xff},
drawing.Color{R: 0x48, G: 0x19, B: 0x6c, A: 0xff},
drawing.Color{R: 0x48, G: 0x1a, B: 0x6d, A: 0xff},
drawing.Color{R: 0x48, G: 0x1c, B: 0x6e, A: 0xff},
drawing.Color{R: 0x48, G: 0x1d, B: 0x6f, A: 0xff},
drawing.Color{R: 0x48, G: 0x1e, B: 0x70, A: 0xff},
drawing.Color{R: 0x48, G: 0x20, B: 0x71, A: 0xff},
drawing.Color{R: 0x48, G: 0x21, B: 0x73, A: 0xff},
drawing.Color{R: 0x48, G: 0x22, B: 0x74, A: 0xff},
drawing.Color{R: 0x48, G: 0x24, B: 0x75, A: 0xff},
drawing.Color{R: 0x48, G: 0x25, B: 0x76, A: 0xff},
drawing.Color{R: 0x48, G: 0x26, B: 0x77, A: 0xff},
drawing.Color{R: 0x48, G: 0x27, B: 0x78, A: 0xff},
drawing.Color{R: 0x47, G: 0x29, B: 0x79, A: 0xff},
drawing.Color{R: 0x47, G: 0x2a, B: 0x79, A: 0xff},
drawing.Color{R: 0x47, G: 0x2b, B: 0x7a, A: 0xff},
drawing.Color{R: 0x47, G: 0x2c, B: 0x7b, A: 0xff},
drawing.Color{R: 0x47, G: 0x2e, B: 0x7c, A: 0xff},
drawing.Color{R: 0x46, G: 0x2f, B: 0x7d, A: 0xff},
drawing.Color{R: 0x46, G: 0x30, B: 0x7e, A: 0xff},
drawing.Color{R: 0x46, G: 0x31, B: 0x7e, A: 0xff},
drawing.Color{R: 0x46, G: 0x33, B: 0x7f, A: 0xff},
drawing.Color{R: 0x45, G: 0x34, B: 0x80, A: 0xff},
drawing.Color{R: 0x45, G: 0x35, B: 0x81, A: 0xff},
drawing.Color{R: 0x45, G: 0x36, B: 0x81, A: 0xff},
drawing.Color{R: 0x44, G: 0x38, B: 0x82, A: 0xff},
drawing.Color{R: 0x44, G: 0x39, B: 0x83, A: 0xff},
drawing.Color{R: 0x44, G: 0x3a, B: 0x83, A: 0xff},
drawing.Color{R: 0x43, G: 0x3b, B: 0x84, A: 0xff},
drawing.Color{R: 0x43, G: 0x3c, B: 0x84, A: 0xff},
drawing.Color{R: 0x43, G: 0x3e, B: 0x85, A: 0xff},
drawing.Color{R: 0x42, G: 0x3f, B: 0x85, A: 0xff},
drawing.Color{R: 0x42, G: 0x40, B: 0x86, A: 0xff},
drawing.Color{R: 0x41, G: 0x41, B: 0x86, A: 0xff},
drawing.Color{R: 0x41, G: 0x42, B: 0x87, A: 0xff},
drawing.Color{R: 0x41, G: 0x43, B: 0x87, A: 0xff},
drawing.Color{R: 0x40, G: 0x45, B: 0x88, A: 0xff},
drawing.Color{R: 0x40, G: 0x46, B: 0x88, A: 0xff},
drawing.Color{R: 0x3f, G: 0x47, B: 0x88, A: 0xff},
drawing.Color{R: 0x3f, G: 0x48, B: 0x89, A: 0xff},
drawing.Color{R: 0x3e, G: 0x49, B: 0x89, A: 0xff},
drawing.Color{R: 0x3e, G: 0x4a, B: 0x89, A: 0xff},
drawing.Color{R: 0x3d, G: 0x4b, B: 0x8a, A: 0xff},
drawing.Color{R: 0x3d, G: 0x4d, B: 0x8a, A: 0xff},
drawing.Color{R: 0x3c, G: 0x4e, B: 0x8a, A: 0xff},
drawing.Color{R: 0x3c, G: 0x4f, B: 0x8a, A: 0xff},
drawing.Color{R: 0x3b, G: 0x50, B: 0x8b, A: 0xff},
drawing.Color{R: 0x3b, G: 0x51, B: 0x8b, A: 0xff},
drawing.Color{R: 0x3a, G: 0x52, B: 0x8b, A: 0xff},
drawing.Color{R: 0x3a, G: 0x53, B: 0x8b, A: 0xff},
drawing.Color{R: 0x39, G: 0x54, B: 0x8c, A: 0xff},
drawing.Color{R: 0x39, G: 0x55, B: 0x8c, A: 0xff},
drawing.Color{R: 0x38, G: 0x56, B: 0x8c, A: 0xff},
drawing.Color{R: 0x38, G: 0x57, B: 0x8c, A: 0xff},
drawing.Color{R: 0x37, G: 0x58, B: 0x8c, A: 0xff},
drawing.Color{R: 0x37, G: 0x59, B: 0x8c, A: 0xff},
drawing.Color{R: 0x36, G: 0x5b, B: 0x8d, A: 0xff},
drawing.Color{R: 0x36, G: 0x5c, B: 0x8d, A: 0xff},
drawing.Color{R: 0x35, G: 0x5d, B: 0x8d, A: 0xff},
drawing.Color{R: 0x35, G: 0x5e, B: 0x8d, A: 0xff},
drawing.Color{R: 0x34, G: 0x5f, B: 0x8d, A: 0xff},
drawing.Color{R: 0x34, G: 0x60, B: 0x8d, A: 0xff},
drawing.Color{R: 0x33, G: 0x61, B: 0x8d, A: 0xff},
drawing.Color{R: 0x33, G: 0x62, B: 0x8d, A: 0xff},
drawing.Color{R: 0x33, G: 0x63, B: 0x8d, A: 0xff},
drawing.Color{R: 0x32, G: 0x64, B: 0x8e, A: 0xff},
drawing.Color{R: 0x32, G: 0x65, B: 0x8e, A: 0xff},
drawing.Color{R: 0x31, G: 0x66, B: 0x8e, A: 0xff},
drawing.Color{R: 0x31, G: 0x67, B: 0x8e, A: 0xff},
drawing.Color{R: 0x30, G: 0x68, B: 0x8e, A: 0xff},
drawing.Color{R: 0x30, G: 0x69, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2f, G: 0x6a, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2f, G: 0x6b, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2f, G: 0x6c, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2e, G: 0x6d, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2e, G: 0x6e, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2d, G: 0x6f, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2d, G: 0x70, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2d, G: 0x70, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2c, G: 0x71, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2c, G: 0x72, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2b, G: 0x73, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2b, G: 0x74, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2b, G: 0x75, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2a, G: 0x76, B: 0x8e, A: 0xff},
drawing.Color{R: 0x2a, G: 0x77, B: 0x8e, A: 0xff},
drawing.Color{R: 0x29, G: 0x78, B: 0x8e, A: 0xff},
drawing.Color{R: 0x29, G: 0x79, B: 0x8e, A: 0xff},
drawing.Color{R: 0x29, G: 0x7a, B: 0x8e, A: 0xff},
drawing.Color{R: 0x28, G: 0x7b, B: 0x8e, A: 0xff},
drawing.Color{R: 0x28, G: 0x7c, B: 0x8e, A: 0xff},
drawing.Color{R: 0x28, G: 0x7d, B: 0x8e, A: 0xff},
drawing.Color{R: 0x27, G: 0x7e, B: 0x8e, A: 0xff},
drawing.Color{R: 0x27, G: 0x7f, B: 0x8e, A: 0xff},
drawing.Color{R: 0x26, G: 0x80, B: 0x8e, A: 0xff},
drawing.Color{R: 0x26, G: 0x81, B: 0x8e, A: 0xff},
drawing.Color{R: 0x26, G: 0x82, B: 0x8e, A: 0xff},
drawing.Color{R: 0x25, G: 0x83, B: 0x8e, A: 0xff},
drawing.Color{R: 0x25, G: 0x83, B: 0x8e, A: 0xff},
drawing.Color{R: 0x25, G: 0x84, B: 0x8e, A: 0xff},
drawing.Color{R: 0x24, G: 0x85, B: 0x8e, A: 0xff},
drawing.Color{R: 0x24, G: 0x86, B: 0x8e, A: 0xff},
drawing.Color{R: 0x23, G: 0x87, B: 0x8e, A: 0xff},
drawing.Color{R: 0x23, G: 0x88, B: 0x8e, A: 0xff},
drawing.Color{R: 0x23, G: 0x89, B: 0x8e, A: 0xff},
drawing.Color{R: 0x22, G: 0x8a, B: 0x8d, A: 0xff},
drawing.Color{R: 0x22, G: 0x8b, B: 0x8d, A: 0xff},
drawing.Color{R: 0x22, G: 0x8c, B: 0x8d, A: 0xff},
drawing.Color{R: 0x21, G: 0x8d, B: 0x8d, A: 0xff},
drawing.Color{R: 0x21, G: 0x8e, B: 0x8d, A: 0xff},
drawing.Color{R: 0x21, G: 0x8f, B: 0x8d, A: 0xff},
drawing.Color{R: 0x20, G: 0x90, B: 0x8d, A: 0xff},
drawing.Color{R: 0x20, G: 0x91, B: 0x8c, A: 0xff},
drawing.Color{R: 0x20, G: 0x92, B: 0x8c, A: 0xff},
drawing.Color{R: 0x20, G: 0x93, B: 0x8c, A: 0xff},
drawing.Color{R: 0x1f, G: 0x93, B: 0x8c, A: 0xff},
drawing.Color{R: 0x1f, G: 0x94, B: 0x8c, A: 0xff},
drawing.Color{R: 0x1f, G: 0x95, B: 0x8b, A: 0xff},
drawing.Color{R: 0x1f, G: 0x96, B: 0x8b, A: 0xff},
drawing.Color{R: 0x1f, G: 0x97, B: 0x8b, A: 0xff},
drawing.Color{R: 0x1e, G: 0x98, B: 0x8b, A: 0xff},
drawing.Color{R: 0x1e, G: 0x99, B: 0x8a, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9a, B: 0x8a, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9b, B: 0x8a, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9c, B: 0x89, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9d, B: 0x89, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9e, B: 0x89, A: 0xff},
drawing.Color{R: 0x1e, G: 0x9f, B: 0x88, A: 0xff},
drawing.Color{R: 0x1e, G: 0xa0, B: 0x88, A: 0xff},
drawing.Color{R: 0x1f, G: 0xa1, B: 0x88, A: 0xff},
drawing.Color{R: 0x1f, G: 0xa2, B: 0x87, A: 0xff},
drawing.Color{R: 0x1f, G: 0xa3, B: 0x87, A: 0xff},
drawing.Color{R: 0x1f, G: 0xa3, B: 0x86, A: 0xff},
drawing.Color{R: 0x20, G: 0xa4, B: 0x86, A: 0xff},
drawing.Color{R: 0x20, G: 0xa5, B: 0x86, A: 0xff},
drawing.Color{R: 0x21, G: 0xa6, B: 0x85, A: 0xff},
drawing.Color{R: 0x21, G: 0xa7, B: 0x85, A: 0xff},
drawing.Color{R: 0x22, G: 0xa8, B: 0x84, A: 0xff},
drawing.Color{R: 0x23, G: 0xa9, B: 0x83, A: 0xff},
drawing.Color{R: 0x23, G: 0xaa, B: 0x83, A: 0xff},
drawing.Color{R: 0x24, G: 0xab, B: 0x82, A: 0xff},
drawing.Color{R: 0x25, G: 0xac, B: 0x82, A: 0xff},
drawing.Color{R: 0x26, G: 0xad, B: 0x81, A: 0xff},
drawing.Color{R: 0x27, G: 0xae, B: 0x81, A: 0xff},
drawing.Color{R: 0x28, G: 0xaf, B: 0x80, A: 0xff},
drawing.Color{R: 0x29, G: 0xaf, B: 0x7f, A: 0xff},
drawing.Color{R: 0x2a, G: 0xb0, B: 0x7f, A: 0xff},
drawing.Color{R: 0x2b, G: 0xb1, B: 0x7e, A: 0xff},
drawing.Color{R: 0x2c, G: 0xb2, B: 0x7d, A: 0xff},
drawing.Color{R: 0x2e, G: 0xb3, B: 0x7c, A: 0xff},
drawing.Color{R: 0x2f, G: 0xb4, B: 0x7c, A: 0xff},
drawing.Color{R: 0x30, G: 0xb5, B: 0x7b, A: 0xff},
drawing.Color{R: 0x32, G: 0xb6, B: 0x7a, A: 0xff},
drawing.Color{R: 0x33, G: 0xb7, B: 0x79, A: 0xff},
drawing.Color{R: 0x35, G: 0xb7, B: 0x79, A: 0xff},
drawing.Color{R: 0x36, G: 0xb8, B: 0x78, A: 0xff},
drawing.Color{R: 0x38, G: 0xb9, B: 0x77, A: 0xff},
drawing.Color{R: 0x39, G: 0xba, B: 0x76, A: 0xff},
drawing.Color{R: 0x3b, G: 0xbb, B: 0x75, A: 0xff},
drawing.Color{R: 0x3d, G: 0xbc, B: 0x74, A: 0xff},
drawing.Color{R: 0x3e, G: 0xbd, B: 0x73, A: 0xff},
drawing.Color{R: 0x40, G: 0xbe, B: 0x72, A: 0xff},
drawing.Color{R: 0x42, G: 0xbe, B: 0x71, A: 0xff},
drawing.Color{R: 0x44, G: 0xbf, B: 0x70, A: 0xff},
drawing.Color{R: 0x46, G: 0xc0, B: 0x6f, A: 0xff},
drawing.Color{R: 0x48, G: 0xc1, B: 0x6e, A: 0xff},
drawing.Color{R: 0x49, G: 0xc2, B: 0x6d, A: 0xff},
drawing.Color{R: 0x4b, G: 0xc2, B: 0x6c, A: 0xff},
drawing.Color{R: 0x4d, G: 0xc3, B: 0x6b, A: 0xff},
drawing.Color{R: 0x4f, G: 0xc4, B: 0x6a, A: 0xff},
drawing.Color{R: 0x51, G: 0xc5, B: 0x69, A: 0xff},
drawing.Color{R: 0x53, G: 0xc6, B: 0x68, A: 0xff},
drawing.Color{R: 0x55, G: 0xc6, B: 0x66, A: 0xff},
drawing.Color{R: 0x58, G: 0xc7, B: 0x65, A: 0xff},
drawing.Color{R: 0x5a, G: 0xc8, B: 0x64, A: 0xff},
drawing.Color{R: 0x5c, G: 0xc9, B: 0x63, A: 0xff},
drawing.Color{R: 0x5e, G: 0xc9, B: 0x62, A: 0xff},
drawing.Color{R: 0x60, G: 0xca, B: 0x60, A: 0xff},
drawing.Color{R: 0x62, G: 0xcb, B: 0x5f, A: 0xff},
drawing.Color{R: 0x65, G: 0xcc, B: 0x5e, A: 0xff},
drawing.Color{R: 0x67, G: 0xcc, B: 0x5c, A: 0xff},
drawing.Color{R: 0x69, G: 0xcd, B: 0x5b, A: 0xff},
drawing.Color{R: 0x6c, G: 0xce, B: 0x5a, A: 0xff},
drawing.Color{R: 0x6e, G: 0xce, B: 0x58, A: 0xff},
drawing.Color{R: 0x70, G: 0xcf, B: 0x57, A: 0xff},
drawing.Color{R: 0x73, G: 0xd0, B: 0x55, A: 0xff},
drawing.Color{R: 0x75, G: 0xd0, B: 0x54, A: 0xff},
drawing.Color{R: 0x77, G: 0xd1, B: 0x52, A: 0xff},
drawing.Color{R: 0x7a, G: 0xd2, B: 0x51, A: 0xff},
drawing.Color{R: 0x7c, G: 0xd2, B: 0x4f, A: 0xff},
drawing.Color{R: 0x7f, G: 0xd3, B: 0x4e, A: 0xff},
drawing.Color{R: 0x81, G: 0xd4, B: 0x4c, A: 0xff},
drawing.Color{R: 0x84, G: 0xd4, B: 0x4b, A: 0xff},
drawing.Color{R: 0x86, G: 0xd5, B: 0x49, A: 0xff},
drawing.Color{R: 0x89, G: 0xd5, B: 0x48, A: 0xff},
drawing.Color{R: 0x8b, G: 0xd6, B: 0x46, A: 0xff},
drawing.Color{R: 0x8e, G: 0xd7, B: 0x44, A: 0xff},
drawing.Color{R: 0x90, G: 0xd7, B: 0x43, A: 0xff},
drawing.Color{R: 0x93, G: 0xd8, B: 0x41, A: 0xff},
drawing.Color{R: 0x95, G: 0xd8, B: 0x3f, A: 0xff},
drawing.Color{R: 0x98, G: 0xd9, B: 0x3e, A: 0xff},
drawing.Color{R: 0x9b, G: 0xd9, B: 0x3c, A: 0xff},
drawing.Color{R: 0x9d, G: 0xda, B: 0x3a, A: 0xff},
drawing.Color{R: 0xa0, G: 0xda, B: 0x39, A: 0xff},
drawing.Color{R: 0xa3, G: 0xdb, B: 0x37, A: 0xff},
drawing.Color{R: 0xa5, G: 0xdb, B: 0x35, A: 0xff},
drawing.Color{R: 0xa8, G: 0xdc, B: 0x33, A: 0xff},
drawing.Color{R: 0xab, G: 0xdc, B: 0x32, A: 0xff},
drawing.Color{R: 0xad, G: 0xdd, B: 0x30, A: 0xff},
drawing.Color{R: 0xb0, G: 0xdd, B: 0x2e, A: 0xff},
drawing.Color{R: 0xb3, G: 0xdd, B: 0x2d, A: 0xff},
drawing.Color{R: 0xb5, G: 0xde, B: 0x2b, A: 0xff},
drawing.Color{R: 0xb8, G: 0xde, B: 0x29, A: 0xff},
drawing.Color{R: 0xbb, G: 0xdf, B: 0x27, A: 0xff},
drawing.Color{R: 0xbd, G: 0xdf, B: 0x26, A: 0xff},
drawing.Color{R: 0xc0, G: 0xdf, B: 0x24, A: 0xff},
drawing.Color{R: 0xc3, G: 0xe0, B: 0x23, A: 0xff},
drawing.Color{R: 0xc5, G: 0xe0, B: 0x21, A: 0xff},
drawing.Color{R: 0xc8, G: 0xe1, B: 0x20, A: 0xff},
drawing.Color{R: 0xcb, G: 0xe1, B: 0x1e, A: 0xff},
drawing.Color{R: 0xcd, G: 0xe1, B: 0x1d, A: 0xff},
drawing.Color{R: 0xd0, G: 0xe2, B: 0x1c, A: 0xff},
drawing.Color{R: 0xd3, G: 0xe2, B: 0x1b, A: 0xff},
drawing.Color{R: 0xd5, G: 0xe2, B: 0x1a, A: 0xff},
drawing.Color{R: 0xd8, G: 0xe3, B: 0x19, A: 0xff},
drawing.Color{R: 0xdb, G: 0xe3, B: 0x18, A: 0xff},
drawing.Color{R: 0xdd, G: 0xe3, B: 0x18, A: 0xff},
drawing.Color{R: 0xe0, G: 0xe4, B: 0x18, A: 0xff},
drawing.Color{R: 0xe2, G: 0xe4, B: 0x18, A: 0xff},
drawing.Color{R: 0xe5, G: 0xe4, B: 0x18, A: 0xff},
drawing.Color{R: 0xe8, G: 0xe5, B: 0x19, A: 0xff},
drawing.Color{R: 0xea, G: 0xe5, B: 0x19, A: 0xff},
drawing.Color{R: 0xed, G: 0xe5, B: 0x1a, A: 0xff},
drawing.Color{R: 0xef, G: 0xe6, B: 0x1b, A: 0xff},
drawing.Color{R: 0xf2, G: 0xe6, B: 0x1c, A: 0xff},
drawing.Color{R: 0xf4, G: 0xe6, B: 0x1e, A: 0xff},
drawing.Color{R: 0xf7, G: 0xe6, B: 0x1f, A: 0xff},
drawing.Color{R: 0xf9, G: 0xe7, B: 0x21, A: 0xff},
drawing.Color{R: 0xfb, G: 0xe7, B: 0x23, A: 0xff},
drawing.Color{R: 0xfe, G: 0xe7, B: 0x24, A: 0xff},
}
// Viridis creates a color map provider.
func Viridis(v, vmin, vmax float64) drawing.Color {
normalized := (v - vmin) / (vmax - vmin)
index := uint8(normalized * 255)
return viridisColors[index]
}

208
vendor/github.com/wcharczuk/go-chart/v2/xaxis.go generated vendored Normal file
View File

@ -0,0 +1,208 @@
package chart
import (
"math"
)
// HideXAxis hides the x-axis.
func HideXAxis() XAxis {
return XAxis{
Style: Hidden(),
}
}
// XAxis represents the horizontal axis.
type XAxis struct {
Name string
NameStyle Style
Style Style
ValueFormatter ValueFormatter
Range Range
TickStyle Style
Ticks []Tick
TickPosition TickPosition
GridLines []GridLine
GridMajorStyle Style
GridMinorStyle Style
}
// GetName returns the name.
func (xa XAxis) GetName() string {
return xa.Name
}
// GetStyle returns the style.
func (xa XAxis) GetStyle() Style {
return xa.Style
}
// GetValueFormatter returns the value formatter for the axis.
func (xa XAxis) GetValueFormatter() ValueFormatter {
if xa.ValueFormatter != nil {
return xa.ValueFormatter
}
return FloatValueFormatter
}
// GetTickPosition returns the tick position option for the axis.
func (xa XAxis) GetTickPosition(defaults ...TickPosition) TickPosition {
if xa.TickPosition == TickPositionUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TickPositionUnderTick
}
return xa.TickPosition
}
// GetTicks returns the ticks for a series.
// The coalesce priority is:
// - User Supplied Ticks (i.e. Ticks array on the axis itself).
// - Range ticks (i.e. if the range provides ticks).
// - Generating continuous ticks based on minimum spacing and canvas width.
func (xa XAxis) GetTicks(r Renderer, ra Range, defaults Style, vf ValueFormatter) []Tick {
if len(xa.Ticks) > 0 {
return xa.Ticks
}
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
return tp.GetTicks(r, defaults, vf)
}
tickStyle := xa.Style.InheritFrom(defaults)
return GenerateContinuousTicks(r, ra, false, tickStyle, vf)
}
// GetGridLines returns the gridlines for the axis.
func (xa XAxis) GetGridLines(ticks []Tick) []GridLine {
if len(xa.GridLines) > 0 {
return xa.GridLines
}
return GenerateGridLines(ticks, xa.GridMajorStyle, xa.GridMinorStyle)
}
// Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
tp := xa.GetTickPosition()
var ltx, rtx int
var tx, ty int
var left, right, bottom = math.MaxInt32, 0, 0
for index, t := range ticks {
v := t.Value
tb := Draw.MeasureText(r, t.Label, tickStyle.GetTextOptions())
tx = canvasBox.Left + ra.Translate(v)
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
switch tp {
case TickPositionUnderTick, TickPositionUnset:
ltx = tx - tb.Width()>>1
rtx = tx + tb.Width()>>1
break
case TickPositionBetweenTicks:
if index > 0 {
ltx = ra.Translate(ticks[index-1].Value)
rtx = tx
}
break
}
left = MinInt(left, ltx)
right = MaxInt(right, rtx)
bottom = MaxInt(bottom, ty)
}
if !xa.NameStyle.Hidden && len(xa.Name) > 0 {
tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
bottom += DefaultXAxisMargin + tb.Height()
}
return Box{
Top: canvasBox.Bottom,
Left: left,
Right: right,
Bottom: bottom,
}
}
// Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
tickStyle.GetStrokeOptions().WriteToRenderer(r)
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Right, canvasBox.Bottom)
r.Stroke()
tp := xa.GetTickPosition()
var tx, ty int
var maxTextHeight int
for index, t := range ticks {
v := t.Value
lx := ra.Translate(v)
tx = canvasBox.Left + lx
tickStyle.GetStrokeOptions().WriteToRenderer(r)
r.MoveTo(tx, canvasBox.Bottom)
r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke()
tickWithAxisStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
tb := Draw.MeasureText(r, t.Label, tickWithAxisStyle)
switch tp {
case TickPositionUnderTick, TickPositionUnset:
if tickStyle.TextRotationDegrees == 0 {
tx = tx - tb.Width()>>1
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
} else {
ty = canvasBox.Bottom + (2 * DefaultXAxisMargin)
}
Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
maxTextHeight = MaxInt(maxTextHeight, tb.Height())
break
case TickPositionBetweenTicks:
if index > 0 {
llx := ra.Translate(ticks[index-1].Value)
ltx := canvasBox.Left + llx
finalTickStyle := tickWithAxisStyle.InheritFrom(Style{TextHorizontalAlign: TextHorizontalAlignCenter})
Draw.TextWithin(r, t.Label, Box{
Left: ltx,
Right: tx,
Top: canvasBox.Bottom + DefaultXAxisMargin,
Bottom: canvasBox.Bottom + DefaultXAxisMargin,
}, finalTickStyle)
ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle)
maxTextHeight = MaxInt(maxTextHeight, ftb.Height())
}
break
}
}
nameStyle := xa.NameStyle.InheritFrom(defaults)
if !xa.NameStyle.Hidden && len(xa.Name) > 0 {
tb := Draw.MeasureText(r, xa.Name, nameStyle)
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
Draw.Text(r, xa.Name, tx, ty, nameStyle)
}
if !xa.GridMajorStyle.Hidden || !xa.GridMinorStyle.Hidden {
for _, gl := range xa.GetGridLines(ticks) {
if (gl.IsMinor && !xa.GridMinorStyle.Hidden) || (!gl.IsMinor && !xa.GridMajorStyle.Hidden) {
defaults := xa.GridMajorStyle
if gl.IsMinor {
defaults = xa.GridMinorStyle
}
gl.Render(r, canvasBox, ra, true, gl.Style.InheritFrom(defaults))
}
}
}
}

234
vendor/github.com/wcharczuk/go-chart/v2/yaxis.go generated vendored Normal file
View File

@ -0,0 +1,234 @@
package chart
import (
"math"
)
// HideYAxis hides a y-axis.
func HideYAxis() YAxis {
return YAxis{
Style: Hidden(),
}
}
// YAxis is a veritcal rule of the range.
// There can be (2) y-axes; a primary and secondary.
type YAxis struct {
Name string
NameStyle Style
Style Style
Zero GridLine
AxisType YAxisType
Ascending bool
ValueFormatter ValueFormatter
Range Range
TickStyle Style
Ticks []Tick
GridLines []GridLine
GridMajorStyle Style
GridMinorStyle Style
}
// GetName returns the name.
func (ya YAxis) GetName() string {
return ya.Name
}
// GetNameStyle returns the name style.
func (ya YAxis) GetNameStyle() Style {
return ya.NameStyle
}
// GetStyle returns the style.
func (ya YAxis) GetStyle() Style {
return ya.Style
}
// GetValueFormatter returns the value formatter for the axis.
func (ya YAxis) GetValueFormatter() ValueFormatter {
if ya.ValueFormatter != nil {
return ya.ValueFormatter
}
return FloatValueFormatter
}
// GetTickStyle returns the tick style.
func (ya YAxis) GetTickStyle() Style {
return ya.TickStyle
}
// GetTicks returns the ticks for a series.
// The coalesce priority is:
// - User Supplied Ticks (i.e. Ticks array on the axis itself).
// - Range ticks (i.e. if the range provides ticks).
// - Generating continuous ticks based on minimum spacing and canvas width.
func (ya YAxis) GetTicks(r Renderer, ra Range, defaults Style, vf ValueFormatter) []Tick {
if len(ya.Ticks) > 0 {
return ya.Ticks
}
if tp, isTickProvider := ra.(TicksProvider); isTickProvider {
return tp.GetTicks(r, defaults, vf)
}
tickStyle := ya.Style.InheritFrom(defaults)
return GenerateContinuousTicks(r, ra, true, tickStyle, vf)
}
// GetGridLines returns the gridlines for the axis.
func (ya YAxis) GetGridLines(ticks []Tick) []GridLine {
if len(ya.GridLines) > 0 {
return ya.GridLines
}
return GenerateGridLines(ticks, ya.GridMajorStyle, ya.GridMinorStyle)
}
// Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
var tx int
if ya.AxisType == YAxisPrimary {
tx = canvasBox.Right + DefaultYAxisMargin
} else if ya.AxisType == YAxisSecondary {
tx = canvasBox.Left - DefaultYAxisMargin
}
ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults)).WriteToRenderer(r)
var minx, maxx, miny, maxy = math.MaxInt32, 0, math.MaxInt32, 0
var maxTextHeight int
for _, t := range ticks {
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
tb := r.MeasureText(t.Label)
tbh2 := tb.Height() >> 1
finalTextX := tx
if ya.AxisType == YAxisSecondary {
finalTextX = tx - tb.Width()
}
maxTextHeight = MaxInt(tb.Height(), maxTextHeight)
if ya.AxisType == YAxisPrimary {
minx = canvasBox.Right
maxx = MaxInt(maxx, tx+tb.Width())
} else if ya.AxisType == YAxisSecondary {
minx = MinInt(minx, finalTextX)
maxx = MaxInt(maxx, tx)
}
miny = MinInt(miny, ly-tbh2)
maxy = MaxInt(maxy, ly+tbh2)
}
if !ya.NameStyle.Hidden && len(ya.Name) > 0 {
maxx += (DefaultYAxisMargin + maxTextHeight)
}
return Box{
Top: miny,
Left: minx,
Right: maxx,
Bottom: maxy,
}
}
// Render renders the axis.
func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
tickStyle := ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults))
tickStyle.WriteToRenderer(r)
sw := tickStyle.GetStrokeWidth(defaults.StrokeWidth)
var lx int
var tx int
if ya.AxisType == YAxisPrimary {
lx = canvasBox.Right + int(sw)
tx = lx + DefaultYAxisMargin
} else if ya.AxisType == YAxisSecondary {
lx = canvasBox.Left - int(sw)
tx = lx - DefaultYAxisMargin
}
r.MoveTo(lx, canvasBox.Bottom)
r.LineTo(lx, canvasBox.Top)
r.Stroke()
var maxTextWidth int
var finalTextX, finalTextY int
for _, t := range ticks {
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
tb := Draw.MeasureText(r, t.Label, tickStyle)
if tb.Width() > maxTextWidth {
maxTextWidth = tb.Width()
}
if ya.AxisType == YAxisSecondary {
finalTextX = tx - tb.Width()
} else {
finalTextX = tx
}
if tickStyle.TextRotationDegrees == 0 {
finalTextY = ly + tb.Height()>>1
} else {
finalTextY = ly
}
tickStyle.WriteToRenderer(r)
r.MoveTo(lx, ly)
if ya.AxisType == YAxisPrimary {
r.LineTo(lx+DefaultHorizontalTickWidth, ly)
} else if ya.AxisType == YAxisSecondary {
r.LineTo(lx-DefaultHorizontalTickWidth, ly)
}
r.Stroke()
Draw.Text(r, t.Label, finalTextX, finalTextY, tickStyle)
}
nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
if !ya.NameStyle.Hidden && len(ya.Name) > 0 {
nameStyle.GetTextOptions().WriteToRenderer(r)
tb := Draw.MeasureText(r, ya.Name, nameStyle)
var tx int
if ya.AxisType == YAxisPrimary {
tx = canvasBox.Right + int(sw) + DefaultYAxisMargin + maxTextWidth + DefaultYAxisMargin
} else if ya.AxisType == YAxisSecondary {
tx = canvasBox.Left - (DefaultYAxisMargin + int(sw) + maxTextWidth + DefaultYAxisMargin)
}
var ty int
if nameStyle.TextRotationDegrees == 0 {
ty = canvasBox.Top + (canvasBox.Height()>>1 - tb.Width()>>1)
} else {
ty = canvasBox.Top + (canvasBox.Height()>>1 - tb.Height()>>1)
}
Draw.Text(r, ya.Name, tx, ty, nameStyle)
}
if !ya.Zero.Style.Hidden {
ya.Zero.Render(r, canvasBox, ra, false, Style{})
}
if !ya.GridMajorStyle.Hidden || !ya.GridMinorStyle.Hidden {
for _, gl := range ya.GetGridLines(ticks) {
if (gl.IsMinor && !ya.GridMinorStyle.Hidden) || (!gl.IsMinor && !ya.GridMajorStyle.Hidden) {
defaults := ya.GridMajorStyle
if gl.IsMinor {
defaults = ya.GridMinorStyle
}
gl.Render(r, canvasBox, ra, false, gl.Style.InheritFrom(defaults))
}
}
}
}