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