go-chart/coverage.html

11487 lines
447 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">github.com/wcharczuk/go-chart/annotation_series.go (77.8%)</option>
<option value="file1">github.com/wcharczuk/go-chart/bar_chart.go (95.8%)</option>
<option value="file2">github.com/wcharczuk/go-chart/bollinger_band_series.go (68.8%)</option>
<option value="file3">github.com/wcharczuk/go-chart/box.go (82.5%)</option>
<option value="file4">github.com/wcharczuk/go-chart/chart.go (71.8%)</option>
<option value="file5">github.com/wcharczuk/go-chart/colors.go (100.0%)</option>
<option value="file6">github.com/wcharczuk/go-chart/concat_series.go (65.0%)</option>
<option value="file7">github.com/wcharczuk/go-chart/continuous_range.go (93.3%)</option>
<option value="file8">github.com/wcharczuk/go-chart/continuous_series.go (100.0%)</option>
<option value="file9">github.com/wcharczuk/go-chart/draw.go (57.4%)</option>
<option value="file10">github.com/wcharczuk/go-chart/drawing/color.go (59.5%)</option>
<option value="file11">github.com/wcharczuk/go-chart/drawing/curve.go (42.0%)</option>
<option value="file12">github.com/wcharczuk/go-chart/drawing/dasher.go (0.0%)</option>
<option value="file13">github.com/wcharczuk/go-chart/drawing/demux_flattener.go (0.0%)</option>
<option value="file14">github.com/wcharczuk/go-chart/drawing/flattener.go (0.0%)</option>
<option value="file15">github.com/wcharczuk/go-chart/drawing/free_type_path.go (0.0%)</option>
<option value="file16">github.com/wcharczuk/go-chart/drawing/line.go (0.0%)</option>
<option value="file17">github.com/wcharczuk/go-chart/drawing/matrix.go (0.0%)</option>
<option value="file18">github.com/wcharczuk/go-chart/drawing/painter.go (0.0%)</option>
<option value="file19">github.com/wcharczuk/go-chart/drawing/path.go (0.0%)</option>
<option value="file20">github.com/wcharczuk/go-chart/drawing/raster_graphic_context.go (0.0%)</option>
<option value="file21">github.com/wcharczuk/go-chart/drawing/stack_graphic_context.go (0.0%)</option>
<option value="file22">github.com/wcharczuk/go-chart/drawing/stroker.go (0.0%)</option>
<option value="file23">github.com/wcharczuk/go-chart/drawing/text.go (0.0%)</option>
<option value="file24">github.com/wcharczuk/go-chart/drawing/transformer.go (0.0%)</option>
<option value="file25">github.com/wcharczuk/go-chart/drawing/util.go (0.0%)</option>
<option value="file26">github.com/wcharczuk/go-chart/ema_series.go (58.7%)</option>
<option value="file27">github.com/wcharczuk/go-chart/first_value_annotation.go (77.8%)</option>
<option value="file28">github.com/wcharczuk/go-chart/font.go (88.9%)</option>
<option value="file29">github.com/wcharczuk/go-chart/grid_line.go (34.5%)</option>
<option value="file30">github.com/wcharczuk/go-chart/histogram_series.go (35.3%)</option>
<option value="file31">github.com/wcharczuk/go-chart/image_writer.go (0.0%)</option>
<option value="file32">github.com/wcharczuk/go-chart/jet.go (0.0%)</option>
<option value="file33">github.com/wcharczuk/go-chart/last_value_annotation.go (77.8%)</option>
<option value="file34">github.com/wcharczuk/go-chart/legend.go (32.5%)</option>
<option value="file35">github.com/wcharczuk/go-chart/linear_coefficient_provider.go (0.0%)</option>
<option value="file36">github.com/wcharczuk/go-chart/linear_regression_series.go (64.3%)</option>
<option value="file37">github.com/wcharczuk/go-chart/linear_series.go (0.0%)</option>
<option value="file38">github.com/wcharczuk/go-chart/macd_series.go (49.5%)</option>
<option value="file39">github.com/wcharczuk/go-chart/matrix/matrix.go (81.0%)</option>
<option value="file40">github.com/wcharczuk/go-chart/matrix/regression.go (90.9%)</option>
<option value="file41">github.com/wcharczuk/go-chart/matrix/util.go (58.3%)</option>
<option value="file42">github.com/wcharczuk/go-chart/matrix/vector.go (0.0%)</option>
<option value="file43">github.com/wcharczuk/go-chart/min_max_series.go (0.0%)</option>
<option value="file44">github.com/wcharczuk/go-chart/pie_chart.go (70.6%)</option>
<option value="file45">github.com/wcharczuk/go-chart/polynomial_regression_series.go (45.6%)</option>
<option value="file46">github.com/wcharczuk/go-chart/raster_renderer.go (74.1%)</option>
<option value="file47">github.com/wcharczuk/go-chart/seq/array.go (100.0%)</option>
<option value="file48">github.com/wcharczuk/go-chart/seq/buffer.go (69.9%)</option>
<option value="file49">github.com/wcharczuk/go-chart/seq/linear.go (94.7%)</option>
<option value="file50">github.com/wcharczuk/go-chart/seq/random.go (44.0%)</option>
<option value="file51">github.com/wcharczuk/go-chart/seq/seq.go (47.0%)</option>
<option value="file52">github.com/wcharczuk/go-chart/seq/time.go (78.9%)</option>
<option value="file53">github.com/wcharczuk/go-chart/seq/times.go (0.0%)</option>
<option value="file54">github.com/wcharczuk/go-chart/seq/util.go (0.0%)</option>
<option value="file55">github.com/wcharczuk/go-chart/sma_series.go (54.8%)</option>
<option value="file56">github.com/wcharczuk/go-chart/stacked_bar_chart.go (0.0%)</option>
<option value="file57">github.com/wcharczuk/go-chart/style.go (66.1%)</option>
<option value="file58">github.com/wcharczuk/go-chart/text.go (87.9%)</option>
<option value="file59">github.com/wcharczuk/go-chart/tick.go (78.9%)</option>
<option value="file60">github.com/wcharczuk/go-chart/time_series.go (69.6%)</option>
<option value="file61">github.com/wcharczuk/go-chart/util/date.go (60.9%)</option>
<option value="file62">github.com/wcharczuk/go-chart/util/file_util.go (0.0%)</option>
<option value="file63">github.com/wcharczuk/go-chart/util/math.go (50.5%)</option>
<option value="file64">github.com/wcharczuk/go-chart/util/time.go (53.7%)</option>
<option value="file65">github.com/wcharczuk/go-chart/value.go (100.0%)</option>
<option value="file66">github.com/wcharczuk/go-chart/value_formatter.go (46.9%)</option>
<option value="file67">github.com/wcharczuk/go-chart/vector_renderer.go (56.6%)</option>
<option value="file68">github.com/wcharczuk/go-chart/viridis.go (0.0%)</option>
<option value="file69">github.com/wcharczuk/go-chart/xaxis.go (60.4%)</option>
<option value="file70">github.com/wcharczuk/go-chart/yaxis.go (63.1%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">package chart
import (
"fmt"
"math"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return as.Name
}</span>
// GetStyle returns the line style.
func (as AnnotationSeries) GetStyle() Style <span class="cov0" title="0">{
return as.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (as AnnotationSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return as.YAxis
}</span>
func (as AnnotationSeries) annotationStyleDefaults(defaults Style) Style <span class="cov8" title="1">{
return Style{
FontColor: DefaultTextColor,
Font: defaults.Font,
FillColor: DefaultAnnotationFillColor,
FontSize: DefaultAnnotationFontSize,
StrokeColor: defaults.StrokeColor,
StrokeWidth: defaults.StrokeWidth,
Padding: DefaultAnnotationPadding,
}
}</span>
// Measure returns a bounds box of the series.
func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box <span class="cov8" title="1">{
box := Box{
Top: math.MaxInt32,
Left: math.MaxInt32,
Right: 0,
Bottom: 0,
}
if as.Style.IsZero() || as.Style.Show </span><span class="cov8" title="1">{
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
for _, a := range as.Annotations </span><span class="cov8" title="1">{
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 = util.Math.MinInt(box.Top, ab.Top)
box.Left = util.Math.MinInt(box.Left, ab.Left)
box.Right = util.Math.MaxInt(box.Right, ab.Right)
box.Bottom = util.Math.MaxInt(box.Bottom, ab.Bottom)
}</span>
}
<span class="cov8" title="1">return box</span>
}
// Render draws the series.
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov8" title="1">{
if as.Style.IsZero() || as.Style.Show </span><span class="cov8" title="1">{
seriesStyle := as.Style.InheritFrom(as.annotationStyleDefaults(defaults))
for _, a := range as.Annotations </span><span class="cov8" title="1">{
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)
}</span>
}
}
// Validate validates the series.
func (as AnnotationSeries) Validate() error <span class="cov0" title="0">{
if len(as.Annotations) == 0 </span><span class="cov0" title="0">{
return fmt.Errorf("annotation series requires annotations to be set and not empty")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file1" style="display: none">package chart
import (
"errors"
"fmt"
"io"
"math"
"github.com/golang/freetype/truetype"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov8" title="1">{
if bc.DPI == 0 </span><span class="cov8" title="1">{
return DefaultDPI
}</span>
<span class="cov8" title="1">return bc.DPI</span>
}
// GetFont returns the text font.
func (bc BarChart) GetFont() *truetype.Font <span class="cov8" title="1">{
if bc.Font == nil </span><span class="cov8" title="1">{
return bc.defaultFont
}</span>
<span class="cov8" title="1">return bc.Font</span>
}
// GetWidth returns the chart width or the default value.
func (bc BarChart) GetWidth() int <span class="cov8" title="1">{
if bc.Width == 0 </span><span class="cov8" title="1">{
return DefaultChartWidth
}</span>
<span class="cov8" title="1">return bc.Width</span>
}
// GetHeight returns the chart height or the default value.
func (bc BarChart) GetHeight() int <span class="cov8" title="1">{
if bc.Height == 0 </span><span class="cov8" title="1">{
return DefaultChartHeight
}</span>
<span class="cov8" title="1">return bc.Height</span>
}
// GetBarSpacing returns the spacing between bars.
func (bc BarChart) GetBarSpacing() int <span class="cov8" title="1">{
if bc.BarSpacing == 0 </span><span class="cov8" title="1">{
return DefaultBarSpacing
}</span>
<span class="cov8" title="1">return bc.BarSpacing</span>
}
// GetBarWidth returns the default bar width.
func (bc BarChart) GetBarWidth() int <span class="cov8" title="1">{
if bc.BarWidth == 0 </span><span class="cov8" title="1">{
return DefaultBarWidth
}</span>
<span class="cov8" title="1">return bc.BarWidth</span>
}
// Render renders the chart with the given renderer to the given io.Writer.
func (bc BarChart) Render(rp RendererProvider, w io.Writer) error <span class="cov8" title="1">{
if len(bc.Bars) == 0 </span><span class="cov8" title="1">{
return errors.New("please provide at least one bar")
}</span>
<span class="cov8" title="1">r, err := rp(bc.GetWidth(), bc.GetHeight())
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">if bc.Font == nil </span><span class="cov8" title="1">{
defaultFont, err := GetDefaultFont()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">bc.defaultFont = defaultFont</span>
}
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
return fmt.Errorf("invalid data range; cannot be zero")
}</span>
<span class="cov8" title="1">yr = bc.setRangeDomains(canvasBox, yr)
yf = bc.getValueFormatters()
if bc.hasAxes() </span><span class="cov8" title="1">{
yt = bc.getAxesTicks(r, yr, yf)
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
yr = bc.setRangeDomains(canvasBox, yr)
}</span>
<span class="cov8" title="1">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 </span><span class="cov0" title="0">{
a(r, canvasBox, bc.styleDefaultsElements())
}</span>
<span class="cov8" title="1">return r.Save(w)</span>
}
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) <span class="cov8" title="1">{
Draw.Box(r, canvasBox, bc.getCanvasStyle())
}</span>
func (bc BarChart) getRanges() Range <span class="cov8" title="1">{
var yrange Range
if bc.YAxis.Range != nil &amp;&amp; !bc.YAxis.Range.IsZero() </span><span class="cov8" title="1">{
yrange = bc.YAxis.Range
}</span> else<span class="cov8" title="1"> {
yrange = &amp;ContinuousRange{}
}</span>
<span class="cov8" title="1">if !yrange.IsZero() </span><span class="cov8" title="1">{
return yrange
}</span>
<span class="cov8" title="1">if len(bc.YAxis.Ticks) &gt; 0 </span><span class="cov8" title="1">{
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
for _, t := range bc.YAxis.Ticks </span><span class="cov8" title="1">{
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}</span>
<span class="cov8" title="1">yrange.SetMin(tickMin)
yrange.SetMax(tickMax)
return yrange</span>
}
<span class="cov8" title="1">min, max := math.MaxFloat64, -math.MaxFloat64
for _, b := range bc.Bars </span><span class="cov8" title="1">{
min = math.Min(b.Value, min)
max = math.Max(b.Value, max)
}</span>
<span class="cov8" title="1">yrange.SetMin(min)
yrange.SetMax(max)
return yrange</span>
}
func (bc BarChart) drawBackground(r Renderer) <span class="cov8" title="1">{
Draw.Box(r, Box{
Right: bc.GetWidth(),
Bottom: bc.GetHeight(),
}, bc.getBackgroundStyle())
}</span>
func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) <span class="cov8" title="1">{
xoffset := canvasBox.Left
width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox)
bs2 := spacing &gt;&gt; 1
var barBox Box
var bxl, bxr, by int
for index, bar := range bc.Bars </span><span class="cov8" title="1">{
bxl = xoffset + bs2
bxr = bxl + width
by = canvasBox.Bottom - yr.Translate(bar.Value)
if bc.UseBaseValue </span><span class="cov0" title="0">{
barBox = Box{
Top: by,
Left: bxl,
Right: bxr,
Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue),
}
}</span> else<span class="cov8" title="1"> {
barBox = Box{
Top: by,
Left: bxl,
Right: bxr,
Bottom: canvasBox.Bottom,
}
}</span>
<span class="cov8" title="1">Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
xoffset += width + spacing</span>
}
}
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) <span class="cov8" title="1">{
if bc.XAxis.Show </span><span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
barLabelBox := Box{
Top: canvasBox.Bottom + DefaultXAxisMargin,
Left: cursor,
Right: cursor + width + spacing,
Bottom: bc.GetHeight(),
}
if len(bar.Label) &gt; 0 </span><span class="cov8" title="1">{
Draw.TextWithin(r, bar.Label, barLabelBox, axisStyle)
}</span>
<span class="cov8" title="1">axisStyle.WriteToRenderer(r)
if index &lt; len(bc.Bars)-1 </span><span class="cov8" title="1">{
r.MoveTo(barLabelBox.Right, canvasBox.Bottom)
r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke()
}</span>
<span class="cov8" title="1">cursor += width + spacing</span>
}
}
}
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) <span class="cov8" title="1">{
if bc.YAxis.Style.Show </span><span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
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()&gt;&gt;1), axisStyle)
}</span>
}
}
func (bc BarChart) drawTitle(r Renderer) <span class="cov8" title="1">{
if len(bc.Title) &gt; 0 &amp;&amp; bc.TitleStyle.Show </span><span class="cov8" title="1">{
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() &gt;&gt; 1) - (textWidth &gt;&gt; 1)
titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
r.Text(bc.Title, titleX, titleY)
}</span>
}
func (bc BarChart) getCanvasStyle() Style <span class="cov8" title="1">{
return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas())
}</span>
func (bc BarChart) styleDefaultsCanvas() Style <span class="cov8" title="1">{
return Style{
FillColor: bc.GetColorPalette().CanvasColor(),
StrokeColor: bc.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultCanvasStrokeWidth,
}
}</span>
func (bc BarChart) hasAxes() bool <span class="cov8" title="1">{
return bc.YAxis.Style.Show
}</span>
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range <span class="cov8" title="1">{
yr.SetDomain(canvasBox.Height())
return yr
}</span>
func (bc BarChart) getDefaultCanvasBox() Box <span class="cov8" title="1">{
return bc.box()
}</span>
func (bc BarChart) getValueFormatters() ValueFormatter <span class="cov8" title="1">{
if bc.YAxis.ValueFormatter != nil </span><span class="cov8" title="1">{
return bc.YAxis.ValueFormatter
}</span>
<span class="cov8" title="1">return FloatValueFormatter</span>
}
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) <span class="cov8" title="1">{
if bc.YAxis.Style.Show </span><span class="cov8" title="1">{
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
}</span>
<span class="cov8" title="1">return</span>
}
func (bc BarChart) calculateEffectiveBarSpacing(canvasBox Box) int <span class="cov8" title="1">{
totalWithBaseSpacing := bc.calculateTotalBarWidth(bc.GetBarWidth(), bc.GetBarSpacing())
if totalWithBaseSpacing &gt; canvasBox.Width() </span><span class="cov8" title="1">{
lessBarWidths := canvasBox.Width() - (len(bc.Bars) * bc.GetBarWidth())
if lessBarWidths &gt; 0 </span><span class="cov0" title="0">{
return int(math.Ceil(float64(lessBarWidths) / float64(len(bc.Bars))))
}</span>
<span class="cov8" title="1">return 0</span>
}
<span class="cov8" title="1">return bc.GetBarSpacing()</span>
}
func (bc BarChart) calculateEffectiveBarWidth(canvasBox Box, spacing int) int <span class="cov8" title="1">{
totalWithBaseWidth := bc.calculateTotalBarWidth(bc.GetBarWidth(), spacing)
if totalWithBaseWidth &gt; canvasBox.Width() </span><span class="cov8" title="1">{
totalLessBarSpacings := canvasBox.Width() - (len(bc.Bars) * spacing)
if totalLessBarSpacings &gt; 0 </span><span class="cov8" title="1">{
return int(math.Ceil(float64(totalLessBarSpacings) / float64(len(bc.Bars))))
}</span>
<span class="cov0" title="0">return 0</span>
}
<span class="cov8" title="1">return bc.GetBarWidth()</span>
}
func (bc BarChart) calculateTotalBarWidth(barWidth, spacing int) int <span class="cov8" title="1">{
return len(bc.Bars) * (barWidth + spacing)
}</span>
func (bc BarChart) calculateScaledTotalWidth(canvasBox Box) (width, spacing, total int) <span class="cov8" title="1">{
spacing = bc.calculateEffectiveBarSpacing(canvasBox)
width = bc.calculateEffectiveBarWidth(canvasBox, spacing)
total = bc.calculateTotalBarWidth(width, spacing)
return
}</span>
func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range, yticks []Tick) Box <span class="cov8" title="1">{
axesOuterBox := canvasBox.Clone()
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
if bc.XAxis.Show </span><span class="cov8" title="1">{
xaxisHeight := DefaultVerticalTickHeight
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
axisStyle.WriteToRenderer(r)
cursor := canvasBox.Left
for _, bar := range bc.Bars </span><span class="cov8" title="1">{
if len(bar.Label) &gt; 0 </span><span class="cov8" title="1">{
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 = util.Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
}</span>
}
<span class="cov8" title="1">xbox := Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Left + totalWidth,
Bottom: bc.GetHeight() - xaxisHeight,
}
axesOuterBox = axesOuterBox.Grow(xbox)</span>
}
<span class="cov8" title="1">if bc.YAxis.Style.Show </span><span class="cov8" title="1">{
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
axesOuterBox = axesOuterBox.Grow(axesBounds)
}</span>
<span class="cov8" title="1">return canvasBox.OuterConstrain(bc.box(), axesOuterBox)</span>
}
// box returns the chart bounds as a box.
func (bc BarChart) box() Box <span class="cov8" title="1">{
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,
}
}</span>
func (bc BarChart) getBackgroundStyle() Style <span class="cov8" title="1">{
return bc.Background.InheritFrom(bc.styleDefaultsBackground())
}</span>
func (bc BarChart) styleDefaultsBackground() Style <span class="cov8" title="1">{
return Style{
FillColor: bc.GetColorPalette().BackgroundColor(),
StrokeColor: bc.GetColorPalette().BackgroundStrokeColor(),
StrokeWidth: DefaultStrokeWidth,
}
}</span>
func (bc BarChart) styleDefaultsBar(index int) Style <span class="cov8" title="1">{
return Style{
StrokeColor: bc.GetColorPalette().GetSeriesColor(index),
StrokeWidth: 3.0,
FillColor: bc.GetColorPalette().GetSeriesColor(index),
}
}</span>
func (bc BarChart) styleDefaultsTitle() Style <span class="cov0" title="0">{
return bc.TitleStyle.InheritFrom(Style{
FontColor: bc.GetColorPalette().TextColor(),
Font: bc.GetFont(),
FontSize: bc.getTitleFontSize(),
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
})
}</span>
func (bc BarChart) getTitleFontSize() float64 <span class="cov8" title="1">{
effectiveDimension := util.Math.MinInt(bc.GetWidth(), bc.GetHeight())
if effectiveDimension &gt;= 2048 </span><span class="cov8" title="1">{
return 48
}</span> else<span class="cov8" title="1"> if effectiveDimension &gt;= 1024 </span><span class="cov8" title="1">{
return 24
}</span> else<span class="cov8" title="1"> if effectiveDimension &gt;= 512 </span><span class="cov8" title="1">{
return 18
}</span> else<span class="cov8" title="1"> if effectiveDimension &gt;= 256 </span><span class="cov8" title="1">{
return 12
}</span>
<span class="cov8" title="1">return 10</span>
}
func (bc BarChart) styleDefaultsAxes() Style <span class="cov8" title="1">{
return Style{
StrokeColor: bc.GetColorPalette().AxisStrokeColor(),
Font: bc.GetFont(),
FontSize: DefaultAxisFontSize,
FontColor: bc.GetColorPalette().TextColor(),
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
}
}</span>
func (bc BarChart) styleDefaultsElements() Style <span class="cov0" title="0">{
return Style{
Font: bc.GetFont(),
}
}</span>
// GetColorPalette returns the color palette for the chart.
func (bc BarChart) GetColorPalette() ColorPalette <span class="cov8" title="1">{
if bc.ColorPalette != nil </span><span class="cov0" title="0">{
return bc.ColorPalette
}</span>
<span class="cov8" title="1">return AlternateColorPalette</span>
}
</pre>
<pre class="file" id="file2" style="display: none">package chart
import (
"fmt"
"github.com/wcharczuk/go-chart/seq"
)
// 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 *seq.Buffer
}
// GetName returns the name of the time series.
func (bbs BollingerBandsSeries) GetName() string <span class="cov0" title="0">{
return bbs.Name
}</span>
// GetStyle returns the line style.
func (bbs BollingerBandsSeries) GetStyle() Style <span class="cov0" title="0">{
return bbs.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (bbs BollingerBandsSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return bbs.YAxis
}</span>
// GetPeriod returns the window size.
func (bbs BollingerBandsSeries) GetPeriod() int <span class="cov8" title="1">{
if bbs.Period == 0 </span><span class="cov8" title="1">{
return DefaultSimpleMovingAveragePeriod
}</span>
<span class="cov0" title="0">return bbs.Period</span>
}
// 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 <span class="cov8" title="1">{
if bbs.K == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov0" title="0">{
return defaults[0]
}</span>
<span class="cov8" title="1">return 2.0</span>
}
<span class="cov0" title="0">return bbs.K</span>
}
// Len returns the number of elements in the series.
func (bbs BollingerBandsSeries) Len() int <span class="cov0" title="0">{
return bbs.InnerSeries.Len()
}</span>
// GetBoundedValues gets the bounded value for the series.
func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64) <span class="cov8" title="1">{
if bbs.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if bbs.valueBuffer == nil || index == 0 </span><span class="cov8" title="1">{
bbs.valueBuffer = seq.NewBufferWithCapacity(bbs.GetPeriod())
}</span>
<span class="cov8" title="1">if bbs.valueBuffer.Len() &gt;= bbs.GetPeriod() </span><span class="cov8" title="1">{
bbs.valueBuffer.Dequeue()
}</span>
<span class="cov8" title="1">px, py := bbs.InnerSeries.GetValues(index)
bbs.valueBuffer.Enqueue(py)
x = px
ay := seq.New(bbs.valueBuffer).Average()
std := seq.New(bbs.valueBuffer).StdDev()
y1 = ay + (bbs.GetK() * std)
y2 = ay - (bbs.GetK() * std)
return</span>
}
// GetBoundedLastValues returns the last bounded value for the series.
func (bbs *BollingerBandsSeries) GetBoundedLastValues() (x, y1, y2 float64) <span class="cov8" title="1">{
if bbs.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">period := bbs.GetPeriod()
seriesLength := bbs.InnerSeries.Len()
startAt := seriesLength - period
if startAt &lt; 0 </span><span class="cov0" title="0">{
startAt = 0
}</span>
<span class="cov8" title="1">vb := seq.NewBufferWithCapacity(period)
for index := startAt; index &lt; seriesLength; index++ </span><span class="cov8" title="1">{
xn, yn := bbs.InnerSeries.GetValues(index)
vb.Enqueue(yn)
x = xn
}</span>
<span class="cov8" title="1">ay := seq.Seq{Provider: vb}.Average()
std := seq.Seq{Provider: vb}.StdDev()
y1 = ay + (bbs.GetK() * std)
y2 = ay - (bbs.GetK() * std)
return</span>
}
// Render renders the series.
func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
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())
}</span>
// Validate validates the series.
func (bbs BollingerBandsSeries) Validate() error <span class="cov0" title="0">{
if bbs.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("bollinger bands series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file3" style="display: none">package chart
import (
"fmt"
"math"
util "github.com/wcharczuk/go-chart/util"
)
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 <span class="cov8" title="1">{
return Box{
IsSet: true,
Top: top,
Left: left,
Right: right,
Bottom: bottom,
}
}</span>
// 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 <span class="cov8" title="1">{
if b.IsSet </span><span class="cov8" title="1">{
return false
}</span>
<span class="cov8" title="1">return b.Top == 0 &amp;&amp; b.Left == 0 &amp;&amp; b.Right == 0 &amp;&amp; b.Bottom == 0</span>
}
// String returns a string representation of the box.
func (b Box) String() string <span class="cov8" title="1">{
return fmt.Sprintf("box(%d,%d,%d,%d)", b.Top, b.Left, b.Right, b.Bottom)
}</span>
// GetTop returns a coalesced value with a default.
func (b Box) GetTop(defaults ...int) int <span class="cov8" title="1">{
if !b.IsSet &amp;&amp; b.Top == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return 0</span>
}
<span class="cov8" title="1">return b.Top</span>
}
// GetLeft returns a coalesced value with a default.
func (b Box) GetLeft(defaults ...int) int <span class="cov8" title="1">{
if !b.IsSet &amp;&amp; b.Left == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return 0</span>
}
<span class="cov8" title="1">return b.Left</span>
}
// GetRight returns a coalesced value with a default.
func (b Box) GetRight(defaults ...int) int <span class="cov8" title="1">{
if !b.IsSet &amp;&amp; b.Right == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return 0</span>
}
<span class="cov8" title="1">return b.Right</span>
}
// GetBottom returns a coalesced value with a default.
func (b Box) GetBottom(defaults ...int) int <span class="cov8" title="1">{
if !b.IsSet &amp;&amp; b.Bottom == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return 0</span>
}
<span class="cov8" title="1">return b.Bottom</span>
}
// Width returns the width
func (b Box) Width() int <span class="cov8" title="1">{
return util.Math.AbsInt(b.Right - b.Left)
}</span>
// Height returns the height
func (b Box) Height() int <span class="cov8" title="1">{
return util.Math.AbsInt(b.Bottom - b.Top)
}</span>
// Center returns the center of the box
func (b Box) Center() (x, y int) <span class="cov8" title="1">{
w2, h2 := b.Width()&gt;&gt;1, b.Height()&gt;&gt;1
return b.Left + w2, b.Top + h2
}</span>
// Aspect returns the aspect ratio of the box.
func (b Box) Aspect() float64 <span class="cov8" title="1">{
return float64(b.Width()) / float64(b.Height())
}</span>
// Clone returns a new copy of the box.
func (b Box) Clone() Box <span class="cov8" title="1">{
return Box{
IsSet: b.IsSet,
Top: b.Top,
Left: b.Left,
Right: b.Right,
Bottom: b.Bottom,
}
}</span>
// IsBiggerThan returns if a box is bigger than another box.
func (b Box) IsBiggerThan(other Box) bool <span class="cov8" title="1">{
return b.Top &lt; other.Top ||
b.Bottom &gt; other.Bottom ||
b.Left &lt; other.Left ||
b.Right &gt; other.Right
}</span>
// IsSmallerThan returns if a box is smaller than another box.
func (b Box) IsSmallerThan(other Box) bool <span class="cov8" title="1">{
return b.Top &gt; other.Top &amp;&amp;
b.Bottom &lt; other.Bottom &amp;&amp;
b.Left &gt; other.Left &amp;&amp;
b.Right &lt; other.Right
}</span>
// Equals returns if the box equals another box.
func (b Box) Equals(other Box) bool <span class="cov8" title="1">{
return b.Top == other.Top &amp;&amp;
b.Left == other.Left &amp;&amp;
b.Right == other.Right &amp;&amp;
b.Bottom == other.Bottom
}</span>
// Grow grows a box based on another box.
func (b Box) Grow(other Box) Box <span class="cov8" title="1">{
return Box{
Top: util.Math.MinInt(b.Top, other.Top),
Left: util.Math.MinInt(b.Left, other.Left),
Right: util.Math.MaxInt(b.Right, other.Right),
Bottom: util.Math.MaxInt(b.Bottom, other.Bottom),
}
}</span>
// Shift pushes a box by x,y.
func (b Box) Shift(x, y int) Box <span class="cov8" title="1">{
return Box{
Top: b.Top + y,
Left: b.Left + x,
Right: b.Right + x,
Bottom: b.Bottom + y,
}
}</span>
// Corners returns the box as a set of corners.
func (b Box) Corners() BoxCorners <span class="cov0" title="0">{
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},
}
}</span>
// 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 <span class="cov8" title="1">{
ba := b.Aspect()
oa := other.Aspect()
if oa == ba </span><span class="cov8" title="1">{
return b.Clone()
}</span>
<span class="cov8" title="1">bw, bh := float64(b.Width()), float64(b.Height())
bw2 := int(bw) &gt;&gt; 1
bh2 := int(bh) &gt;&gt; 1
if oa &gt; ba </span><span class="cov8" title="1">{ // ex. 16:9 vs. 4:3
var noh2 int
if oa &gt; 1.0 </span><span class="cov8" title="1">{
noh2 = int(bw/oa) &gt;&gt; 1
}</span> else<span class="cov0" title="0"> {
noh2 = int(bh*oa) &gt;&gt; 1
}</span>
<span class="cov8" title="1">return Box{
Top: (b.Top + bh2) - noh2,
Left: b.Left,
Right: b.Right,
Bottom: (b.Top + bh2) + noh2,
}</span>
}
<span class="cov8" title="1">var now2 int
if oa &gt; 1.0 </span><span class="cov0" title="0">{
now2 = int(bh/oa) &gt;&gt; 1
}</span> else<span class="cov8" title="1"> {
now2 = int(bw*oa) &gt;&gt; 1
}</span>
<span class="cov8" title="1">return Box{
Top: b.Top,
Left: (b.Left + bw2) - now2,
Right: (b.Left + bw2) + now2,
Bottom: b.Bottom,
}</span>
}
// Constrain is similar to `Fit` except that it will work
// more literally like the opposite of grow.
func (b Box) Constrain(other Box) Box <span class="cov8" title="1">{
newBox := b.Clone()
newBox.Top = util.Math.MaxInt(newBox.Top, other.Top)
newBox.Left = util.Math.MaxInt(newBox.Left, other.Left)
newBox.Right = util.Math.MinInt(newBox.Right, other.Right)
newBox.Bottom = util.Math.MinInt(newBox.Bottom, other.Bottom)
return newBox
}</span>
// OuterConstrain is similar to `Constraint` with the difference
// that it applies corrections
func (b Box) OuterConstrain(bounds, other Box) Box <span class="cov8" title="1">{
newBox := b.Clone()
if other.Top &lt; bounds.Top </span><span class="cov8" title="1">{
delta := bounds.Top - other.Top
newBox.Top = b.Top + delta
}</span>
<span class="cov8" title="1">if other.Left &lt; bounds.Left </span><span class="cov8" title="1">{
delta := bounds.Left - other.Left
newBox.Left = b.Left + delta
}</span>
<span class="cov8" title="1">if other.Right &gt; bounds.Right </span><span class="cov8" title="1">{
delta := other.Right - bounds.Right
newBox.Right = b.Right - delta
}</span>
<span class="cov8" title="1">if other.Bottom &gt; bounds.Bottom </span><span class="cov8" title="1">{
delta := other.Bottom - bounds.Bottom
newBox.Bottom = b.Bottom - delta
}</span>
<span class="cov8" title="1">return newBox</span>
}
// 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 <span class="cov0" title="0">{
return Box{
Top: util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
Left: util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
Right: util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
Bottom: util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
}
}</span>
// Width returns the width
func (bc BoxCorners) Width() int <span class="cov0" title="0">{
minLeft := util.Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
maxRight := util.Math.MaxInt(bc.TopRight.X, bc.BottomRight.X)
return maxRight - minLeft
}</span>
// Height returns the height
func (bc BoxCorners) Height() int <span class="cov0" title="0">{
minTop := util.Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
maxBottom := util.Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
return maxBottom - minTop
}</span>
// Center returns the center of the box
func (bc BoxCorners) Center() (x, y int) <span class="cov8" title="1">{
left := util.Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
right := util.Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
x = ((right - left) &gt;&gt; 1) + left
top := util.Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
bottom := util.Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
y = ((bottom - top) &gt;&gt; 1) + top
return
}</span>
// Rotate rotates the box.
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners <span class="cov8" title="1">{
cx, cy := bc.Center()
thetaRadians := util.Math.DegreesToRadians(thetaDegrees)
tlx, tly := util.Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
trx, try := util.Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
brx, bry := util.Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
blx, bly := util.Math.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},
}
}</span>
// Equals returns if the box equals another box.
func (bc BoxCorners) Equals(other BoxCorners) bool <span class="cov0" title="0">{
return bc.TopLeft.Equals(other.TopLeft) &amp;&amp;
bc.TopRight.Equals(other.TopRight) &amp;&amp;
bc.BottomRight.Equals(other.BottomRight) &amp;&amp;
bc.BottomLeft.Equals(other.BottomLeft)
}</span>
func (bc BoxCorners) String() string <span class="cov8" title="1">{
return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
}</span>
// 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 <span class="cov0" title="0">{
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)
}</span>
// Equals returns if a point equals another point.
func (p Point) Equals(other Point) bool <span class="cov8" title="1">{
return p.X == other.X &amp;&amp; p.Y == other.Y
}</span>
// String returns a string representation of the point.
func (p Point) String() string <span class="cov8" title="1">{
return fmt.Sprintf("P{%d,%d}", p.X, p.Y)
}</span>
</pre>
<pre class="file" id="file4" style="display: none">package chart
import (
"errors"
"fmt"
"io"
"math"
"github.com/golang/freetype/truetype"
util "github.com/wcharczuk/go-chart/util"
)
// 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
}
// GetDPI returns the dpi for the chart.
func (c Chart) GetDPI(defaults ...float64) float64 <span class="cov8" title="1">{
if c.DPI == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return DefaultDPI</span>
}
<span class="cov8" title="1">return c.DPI</span>
}
// GetFont returns the text font.
func (c Chart) GetFont() *truetype.Font <span class="cov8" title="1">{
if c.Font == nil </span><span class="cov8" title="1">{
return c.defaultFont
}</span>
<span class="cov8" title="1">return c.Font</span>
}
// GetWidth returns the chart width or the default value.
func (c Chart) GetWidth() int <span class="cov8" title="1">{
if c.Width == 0 </span><span class="cov8" title="1">{
return DefaultChartWidth
}</span>
<span class="cov8" title="1">return c.Width</span>
}
// GetHeight returns the chart height or the default value.
func (c Chart) GetHeight() int <span class="cov8" title="1">{
if c.Height == 0 </span><span class="cov8" title="1">{
return DefaultChartHeight
}</span>
<span class="cov8" title="1">return c.Height</span>
}
// Render renders the chart with the given renderer to the given io.Writer.
func (c Chart) Render(rp RendererProvider, w io.Writer) error <span class="cov8" title="1">{
if len(c.Series) == 0 </span><span class="cov0" title="0">{
return errors.New("please provide at least one series")
}</span>
<span class="cov8" title="1">if visibleSeriesErr := c.checkHasVisibleSeries(); visibleSeriesErr != nil </span><span class="cov0" title="0">{
return visibleSeriesErr
}</span>
<span class="cov8" title="1">c.YAxisSecondary.AxisType = YAxisSecondary
r, err := rp(c.GetWidth(), c.GetHeight())
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">if c.Font == nil </span><span class="cov8" title="1">{
defaultFont, err := GetDefaultFont()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">c.defaultFont = defaultFont</span>
}
<span class="cov8" title="1">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()
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
err = c.checkRanges(xr, yr, yra)
if err != nil </span><span class="cov8" title="1">{
r.Save(w)
return err
}</span>
<span class="cov8" title="1">if c.hasAxes() </span><span class="cov8" title="1">{
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)
// 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)
}</span>
<span class="cov8" title="1">if c.hasAnnotationSeries() </span><span class="cov0" title="0">{
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)
}</span>
<span class="cov8" title="1">c.drawCanvas(r, canvasBox)
c.drawAxes(r, canvasBox, xr, yr, yra, xt, yt, yta)
for index, series := range c.Series </span><span class="cov8" title="1">{
c.drawSeries(r, canvasBox, xr, yr, yra, series, index)
}</span>
<span class="cov8" title="1">c.drawTitle(r)
for _, a := range c.Elements </span><span class="cov8" title="1">{
a(r, canvasBox, c.styleDefaultsElements())
}</span>
<span class="cov8" title="1">return r.Save(w)</span>
}
func (c Chart) checkHasVisibleSeries() error <span class="cov8" title="1">{
hasVisibleSeries := false
var style Style
for _, s := range c.Series </span><span class="cov8" title="1">{
style = s.GetStyle()
hasVisibleSeries = hasVisibleSeries || (style.IsZero() || style.Show)
}</span>
<span class="cov8" title="1">if !hasVisibleSeries </span><span class="cov0" title="0">{
return fmt.Errorf("must have (1) visible series; make sure if you set a style, you set .Show = true")
}</span>
<span class="cov8" title="1">return nil</span>
}
func (c Chart) validateSeries() error <span class="cov8" title="1">{
var err error
for _, s := range c.Series </span><span class="cov8" title="1">{
err = s.Validate()
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
}
<span class="cov8" title="1">return nil</span>
}
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
if s.GetStyle().IsZero() || s.GetStyle().Show </span><span class="cov8" title="1">{
seriesAxis := s.GetYAxis()
if bvp, isBoundedValuesProvider := s.(BoundedValuesProvider); isBoundedValuesProvider </span><span class="cov0" title="0">{
seriesLength := bvp.Len()
for index := 0; index &lt; seriesLength; index++ </span><span class="cov0" title="0">{
vx, vy1, vy2 := bvp.GetBoundedValues(index)
minx = math.Min(minx, vx)
maxx = math.Max(maxx, vx)
if seriesAxis == YAxisPrimary </span><span class="cov0" title="0">{
miny = math.Min(miny, vy1)
miny = math.Min(miny, vy2)
maxy = math.Max(maxy, vy1)
maxy = math.Max(maxy, vy2)
}</span> else<span class="cov0" title="0"> if seriesAxis == YAxisSecondary </span><span class="cov0" title="0">{
minya = math.Min(minya, vy1)
minya = math.Min(minya, vy2)
maxya = math.Max(maxya, vy1)
maxya = math.Max(maxya, vy2)
seriesMappedToSecondaryAxis = true
}</span>
}
} else<span class="cov8" title="1"> if vp, isValuesProvider := s.(ValuesProvider); isValuesProvider </span><span class="cov8" title="1">{
seriesLength := vp.Len()
for index := 0; index &lt; seriesLength; index++ </span><span class="cov8" title="1">{
vx, vy := vp.GetValues(index)
minx = math.Min(minx, vx)
maxx = math.Max(maxx, vx)
if seriesAxis == YAxisPrimary </span><span class="cov8" title="1">{
miny = math.Min(miny, vy)
maxy = math.Max(maxy, vy)
}</span> else<span class="cov8" title="1"> if seriesAxis == YAxisSecondary </span><span class="cov8" title="1">{
minya = math.Min(minya, vy)
maxya = math.Max(maxya, vy)
seriesMappedToSecondaryAxis = true
}</span>
}
}
}
}
<span class="cov8" title="1">if c.XAxis.Range == nil </span><span class="cov8" title="1">{
xrange = &amp;ContinuousRange{}
}</span> else<span class="cov8" title="1"> {
xrange = c.XAxis.Range
}</span>
<span class="cov8" title="1">if c.YAxis.Range == nil </span><span class="cov8" title="1">{
yrange = &amp;ContinuousRange{}
}</span> else<span class="cov8" title="1"> {
yrange = c.YAxis.Range
}</span>
<span class="cov8" title="1">if c.YAxisSecondary.Range == nil </span><span class="cov8" title="1">{
yrangeAlt = &amp;ContinuousRange{}
}</span> else<span class="cov8" title="1"> {
yrangeAlt = c.YAxisSecondary.Range
}</span>
<span class="cov8" title="1">if len(c.XAxis.Ticks) &gt; 0 </span><span class="cov0" title="0">{
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
for _, t := range c.XAxis.Ticks </span><span class="cov0" title="0">{
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}</span>
<span class="cov0" title="0">xrange.SetMin(tickMin)
xrange.SetMax(tickMax)</span>
} else<span class="cov8" title="1"> if xrange.IsZero() </span><span class="cov8" title="1">{
xrange.SetMin(minx)
xrange.SetMax(maxx)
}</span>
<span class="cov8" title="1">if len(c.YAxis.Ticks) &gt; 0 </span><span class="cov8" title="1">{
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
for _, t := range c.YAxis.Ticks </span><span class="cov8" title="1">{
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}</span>
<span class="cov8" title="1">yrange.SetMin(tickMin)
yrange.SetMax(tickMax)</span>
} else<span class="cov8" title="1"> if yrange.IsZero() </span><span class="cov8" title="1">{
yrange.SetMin(miny)
yrange.SetMax(maxy)
// only round if we're showing the axis
if c.YAxis.Style.Show </span><span class="cov0" title="0">{
delta := yrange.GetDelta()
roundTo := util.Math.GetRoundToForDelta(delta)
rmin, rmax := util.Math.RoundDown(yrange.GetMin(), roundTo), util.Math.RoundUp(yrange.GetMax(), roundTo)
yrange.SetMin(rmin)
yrange.SetMax(rmax)
}</span>
}
<span class="cov8" title="1">if len(c.YAxisSecondary.Ticks) &gt; 0 </span><span class="cov0" title="0">{
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
for _, t := range c.YAxis.Ticks </span><span class="cov0" title="0">{
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}</span>
<span class="cov0" title="0">yrangeAlt.SetMin(tickMin)
yrangeAlt.SetMax(tickMax)</span>
} else<span class="cov8" title="1"> if seriesMappedToSecondaryAxis &amp;&amp; yrangeAlt.IsZero() </span><span class="cov8" title="1">{
yrangeAlt.SetMin(minya)
yrangeAlt.SetMax(maxya)
if c.YAxisSecondary.Style.Show </span><span class="cov0" title="0">{
delta := yrangeAlt.GetDelta()
roundTo := util.Math.GetRoundToForDelta(delta)
rmin, rmax := util.Math.RoundDown(yrangeAlt.GetMin(), roundTo), util.Math.RoundUp(yrangeAlt.GetMax(), roundTo)
yrangeAlt.SetMin(rmin)
yrangeAlt.SetMax(rmax)
}</span>
}
<span class="cov8" title="1">return</span>
}
func (c Chart) checkRanges(xr, yr, yra Range) error <span class="cov8" title="1">{
xDelta := xr.GetDelta()
if math.IsInf(xDelta, 0) </span><span class="cov8" title="1">{
return errors.New("infinite x-range delta")
}</span>
<span class="cov8" title="1">if math.IsNaN(xDelta) </span><span class="cov0" title="0">{
return errors.New("nan x-range delta")
}</span>
<span class="cov8" title="1">if xDelta == 0 </span><span class="cov0" title="0">{
return errors.New("zero x-range delta; there needs to be at least (2) values")
}</span>
<span class="cov8" title="1">yDelta := yr.GetDelta()
if math.IsInf(yDelta, 0) </span><span class="cov8" title="1">{
return errors.New("infinite y-range delta")
}</span>
<span class="cov8" title="1">if math.IsNaN(yDelta) </span><span class="cov0" title="0">{
return errors.New("nan y-range delta")
}</span>
<span class="cov8" title="1">if c.hasSecondarySeries() </span><span class="cov0" title="0">{
yraDelta := yra.GetDelta()
if math.IsInf(yraDelta, 0) </span><span class="cov0" title="0">{
return errors.New("infinite secondary y-range delta")
}</span>
<span class="cov0" title="0">if math.IsNaN(yraDelta) </span><span class="cov0" title="0">{
return errors.New("nan secondary y-range delta")
}</span>
}
<span class="cov8" title="1">return nil</span>
}
func (c Chart) getDefaultCanvasBox() Box <span class="cov8" title="1">{
return c.Box()
}</span>
func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) <span class="cov8" title="1">{
for _, s := range c.Series </span><span class="cov8" title="1">{
if vfp, isVfp := s.(ValueFormatterProvider); isVfp </span><span class="cov8" title="1">{
sx, sy := vfp.GetValueFormatters()
if s.GetYAxis() == YAxisPrimary </span><span class="cov8" title="1">{
x = sx
y = sy
}</span> else<span class="cov8" title="1"> if s.GetYAxis() == YAxisSecondary </span><span class="cov8" title="1">{
x = sx
ya = sy
}</span>
}
}
<span class="cov8" title="1">if c.XAxis.ValueFormatter != nil </span><span class="cov0" title="0">{
x = c.XAxis.GetValueFormatter()
}</span>
<span class="cov8" title="1">if c.YAxis.ValueFormatter != nil </span><span class="cov0" title="0">{
y = c.YAxis.GetValueFormatter()
}</span>
<span class="cov8" title="1">if c.YAxisSecondary.ValueFormatter != nil </span><span class="cov0" title="0">{
ya = c.YAxisSecondary.GetValueFormatter()
}</span>
<span class="cov8" title="1">return</span>
}
func (c Chart) hasAxes() bool <span class="cov8" title="1">{
return c.XAxis.Style.Show || c.YAxis.Style.Show || c.YAxisSecondary.Style.Show
}</span>
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) <span class="cov8" title="1">{
if c.XAxis.Style.Show </span><span class="cov8" title="1">{
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
}</span>
<span class="cov8" title="1">if c.YAxis.Style.Show </span><span class="cov8" title="1">{
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
}</span>
<span class="cov8" title="1">if c.YAxisSecondary.Style.Show </span><span class="cov8" title="1">{
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
}</span>
<span class="cov8" title="1">return</span>
}
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box <span class="cov8" title="1">{
axesOuterBox := canvasBox.Clone()
if c.XAxis.Style.Show </span><span class="cov8" title="1">{
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
axesOuterBox = axesOuterBox.Grow(axesBounds)
}</span>
<span class="cov8" title="1">if c.YAxis.Style.Show </span><span class="cov8" title="1">{
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
axesOuterBox = axesOuterBox.Grow(axesBounds)
}</span>
<span class="cov8" title="1">if c.YAxisSecondary.Style.Show </span><span class="cov0" title="0">{
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
axesOuterBox = axesOuterBox.Grow(axesBounds)
}</span>
<span class="cov8" title="1">return canvasBox.OuterConstrain(c.Box(), axesOuterBox)</span>
}
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (Range, Range, Range) <span class="cov8" title="1">{
xr.SetDomain(canvasBox.Width())
yr.SetDomain(canvasBox.Height())
yra.SetDomain(canvasBox.Height())
return xr, yr, yra
}</span>
func (c Chart) hasAnnotationSeries() bool <span class="cov8" title="1">{
for _, s := range c.Series </span><span class="cov8" title="1">{
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries </span><span class="cov0" title="0">{
if as.Style.IsZero() || as.Style.Show </span><span class="cov0" title="0">{
return true
}</span>
}
}
<span class="cov8" title="1">return false</span>
}
func (c Chart) hasSecondarySeries() bool <span class="cov8" title="1">{
for _, s := range c.Series </span><span class="cov8" title="1">{
if s.GetYAxis() == YAxisSecondary </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov8" title="1">return false</span>
}
func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box <span class="cov0" title="0">{
annotationSeriesBox := canvasBox.Clone()
for seriesIndex, s := range c.Series </span><span class="cov0" title="0">{
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries </span><span class="cov0" title="0">{
if as.Style.IsZero() || as.Style.Show </span><span class="cov0" title="0">{
style := c.styleDefaultsSeries(seriesIndex)
var annotationBounds Box
if as.YAxis == YAxisPrimary </span><span class="cov0" title="0">{
annotationBounds = as.Measure(r, canvasBox, xr, yr, style)
}</span> else<span class="cov0" title="0"> if as.YAxis == YAxisSecondary </span><span class="cov0" title="0">{
annotationBounds = as.Measure(r, canvasBox, xr, yra, style)
}</span>
<span class="cov0" title="0">annotationSeriesBox = annotationSeriesBox.Grow(annotationBounds)</span>
}
}
}
<span class="cov0" title="0">return canvasBox.OuterConstrain(c.Box(), annotationSeriesBox)</span>
}
func (c Chart) getBackgroundStyle() Style <span class="cov8" title="1">{
return c.Background.InheritFrom(c.styleDefaultsBackground())
}</span>
func (c Chart) drawBackground(r Renderer) <span class="cov8" title="1">{
Draw.Box(r, Box{
Right: c.GetWidth(),
Bottom: c.GetHeight(),
}, c.getBackgroundStyle())
}</span>
func (c Chart) getCanvasStyle() Style <span class="cov8" title="1">{
return c.Canvas.InheritFrom(c.styleDefaultsCanvas())
}</span>
func (c Chart) drawCanvas(r Renderer, canvasBox Box) <span class="cov8" title="1">{
Draw.Box(r, canvasBox, c.getCanvasStyle())
}</span>
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) <span class="cov8" title="1">{
if c.XAxis.Style.Show </span><span class="cov8" title="1">{
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
}</span>
<span class="cov8" title="1">if c.YAxis.Style.Show </span><span class="cov8" title="1">{
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
}</span>
<span class="cov8" title="1">if c.YAxisSecondary.Style.Show </span><span class="cov0" title="0">{
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
}</span>
}
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) <span class="cov8" title="1">{
if s.GetStyle().IsZero() || s.GetStyle().Show </span><span class="cov8" title="1">{
if s.GetYAxis() == YAxisPrimary </span><span class="cov8" title="1">{
s.Render(r, canvasBox, xrange, yrange, c.styleDefaultsSeries(seriesIndex))
}</span> else<span class="cov0" title="0"> if s.GetYAxis() == YAxisSecondary </span><span class="cov0" title="0">{
s.Render(r, canvasBox, xrange, yrangeAlt, c.styleDefaultsSeries(seriesIndex))
}</span>
}
}
func (c Chart) drawTitle(r Renderer) <span class="cov8" title="1">{
if len(c.Title) &gt; 0 &amp;&amp; c.TitleStyle.Show </span><span class="cov8" title="1">{
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() &gt;&gt; 1) - (textWidth &gt;&gt; 1)
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
r.Text(c.Title, titleX, titleY)
}</span>
}
func (c Chart) styleDefaultsBackground() Style <span class="cov8" title="1">{
return Style{
FillColor: c.GetColorPalette().BackgroundColor(),
StrokeColor: c.GetColorPalette().BackgroundStrokeColor(),
StrokeWidth: DefaultBackgroundStrokeWidth,
}
}</span>
func (c Chart) styleDefaultsCanvas() Style <span class="cov8" title="1">{
return Style{
FillColor: c.GetColorPalette().CanvasColor(),
StrokeColor: c.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultCanvasStrokeWidth,
}
}</span>
func (c Chart) styleDefaultsSeries(seriesIndex int) Style <span class="cov8" title="1">{
return Style{
DotColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
StrokeColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
StrokeWidth: DefaultSeriesLineWidth,
Font: c.GetFont(),
FontSize: DefaultFontSize,
}
}</span>
func (c Chart) styleDefaultsAxes() Style <span class="cov8" title="1">{
return Style{
Font: c.GetFont(),
FontColor: c.GetColorPalette().TextColor(),
FontSize: DefaultAxisFontSize,
StrokeColor: c.GetColorPalette().AxisStrokeColor(),
StrokeWidth: DefaultAxisLineWidth,
}
}</span>
func (c Chart) styleDefaultsElements() Style <span class="cov8" title="1">{
return Style{
Font: c.GetFont(),
}
}</span>
// GetColorPalette returns the color palette for the chart.
func (c Chart) GetColorPalette() ColorPalette <span class="cov8" title="1">{
if c.ColorPalette != nil </span><span class="cov0" title="0">{
return c.ColorPalette
}</span>
<span class="cov8" title="1">return DefaultColorPalette</span>
}
// Box returns the chart bounds as a box.
func (c Chart) Box() Box <span class="cov8" title="1">{
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,
}
}</span>
</pre>
<pre class="file" id="file5" style="display: none">package chart
import "github.com/wcharczuk/go-chart/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 <span class="cov8" title="1">{
finalIndex := index % len(DefaultColors)
return DefaultColors[finalIndex]
}</span>
// 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 <span class="cov8" title="1">{
finalIndex := index % len(DefaultAlternateColors)
return DefaultAlternateColors[finalIndex]
}</span>
// 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 <span class="cov8" title="1">{
return DefaultBackgroundColor
}</span>
func (dp defaultColorPalette) BackgroundStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultBackgroundStrokeColor
}</span>
func (dp defaultColorPalette) CanvasColor() drawing.Color <span class="cov8" title="1">{
return DefaultCanvasColor
}</span>
func (dp defaultColorPalette) CanvasStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultCanvasStrokeColor
}</span>
func (dp defaultColorPalette) AxisStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultAxisColor
}</span>
func (dp defaultColorPalette) TextColor() drawing.Color <span class="cov8" title="1">{
return DefaultTextColor
}</span>
func (dp defaultColorPalette) GetSeriesColor(index int) drawing.Color <span class="cov8" title="1">{
return GetDefaultColor(index)
}</span>
// AlternateColorPalette represents the default palatte.
var AlternateColorPalette alternateColorPalette
type alternateColorPalette struct{}
func (ap alternateColorPalette) BackgroundColor() drawing.Color <span class="cov8" title="1">{
return DefaultBackgroundColor
}</span>
func (ap alternateColorPalette) BackgroundStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultBackgroundStrokeColor
}</span>
func (ap alternateColorPalette) CanvasColor() drawing.Color <span class="cov8" title="1">{
return DefaultCanvasColor
}</span>
func (ap alternateColorPalette) CanvasStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultCanvasStrokeColor
}</span>
func (ap alternateColorPalette) AxisStrokeColor() drawing.Color <span class="cov8" title="1">{
return DefaultAxisColor
}</span>
func (ap alternateColorPalette) TextColor() drawing.Color <span class="cov8" title="1">{
return DefaultTextColor
}</span>
func (ap alternateColorPalette) GetSeriesColor(index int) drawing.Color <span class="cov8" title="1">{
return GetAlternateColor(index)
}</span>
</pre>
<pre class="file" id="file6" style="display: none">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 <span class="cov8" title="1">{
total := 0
for _, s := range cs </span><span class="cov8" title="1">{
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider </span><span class="cov8" title="1">{
total += typed.Len()
}</span>
}
<span class="cov8" title="1">return total</span>
}
// GetValue returns the value at the (meta) index (i.e 0 =&gt; totalLen-1)
func (cs ConcatSeries) GetValue(index int) (x, y float64) <span class="cov8" title="1">{
cursor := 0
for _, s := range cs </span><span class="cov8" title="1">{
if typed, isValuesProvider := s.(ValuesProvider); isValuesProvider </span><span class="cov8" title="1">{
len := typed.Len()
if index &lt; cursor+len </span><span class="cov8" title="1">{
x, y = typed.GetValues(index - cursor) //FENCEPOSTS.
return
}</span>
<span class="cov8" title="1">cursor += typed.Len()</span>
}
}
<span class="cov0" title="0">return</span>
}
// Validate validates the series.
func (cs ConcatSeries) Validate() error <span class="cov0" title="0">{
var err error
for _, s := range cs </span><span class="cov0" title="0">{
err = s.Validate()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
}
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file7" style="display: none">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 <span class="cov8" title="1">{
return r.Descending
}</span>
// IsZero returns if the ContinuousRange has been set or not.
func (r ContinuousRange) IsZero() bool <span class="cov8" title="1">{
return (r.Min == 0 || math.IsNaN(r.Min)) &amp;&amp;
(r.Max == 0 || math.IsNaN(r.Max)) &amp;&amp;
r.Domain == 0
}</span>
// GetMin gets the min value for the continuous range.
func (r ContinuousRange) GetMin() float64 <span class="cov8" title="1">{
return r.Min
}</span>
// SetMin sets the min value for the continuous range.
func (r *ContinuousRange) SetMin(min float64) <span class="cov8" title="1">{
r.Min = min
}</span>
// GetMax returns the max value for the continuous range.
func (r ContinuousRange) GetMax() float64 <span class="cov8" title="1">{
return r.Max
}</span>
// SetMax sets the max value for the continuous range.
func (r *ContinuousRange) SetMax(max float64) <span class="cov8" title="1">{
r.Max = max
}</span>
// GetDelta returns the difference between the min and max value.
func (r ContinuousRange) GetDelta() float64 <span class="cov8" title="1">{
return r.Max - r.Min
}</span>
// GetDomain returns the range domain.
func (r ContinuousRange) GetDomain() int <span class="cov8" title="1">{
return r.Domain
}</span>
// SetDomain sets the range domain.
func (r *ContinuousRange) SetDomain(domain int) <span class="cov8" title="1">{
r.Domain = domain
}</span>
// String returns a simple string for the ContinuousRange.
func (r ContinuousRange) String() string <span class="cov8" title="1">{
return fmt.Sprintf("ContinuousRange [%.2f,%.2f] =&gt; %d", r.Min, r.Max, r.Domain)
}</span>
// Translate maps a given value into the ContinuousRange space.
func (r ContinuousRange) Translate(value float64) int <span class="cov8" title="1">{
normalized := value - r.Min
ratio := normalized / r.GetDelta()
if r.IsDescending() </span><span class="cov0" title="0">{
return r.Domain - int(math.Ceil(ratio*float64(r.Domain)))
}</span>
<span class="cov8" title="1">return int(math.Ceil(ratio * float64(r.Domain)))</span>
}
</pre>
<pre class="file" id="file8" style="display: none">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 <span class="cov8" title="1">{
return cs.Name
}</span>
// GetStyle returns the line style.
func (cs ContinuousSeries) GetStyle() Style <span class="cov8" title="1">{
return cs.Style
}</span>
// Len returns the number of elements in the series.
func (cs ContinuousSeries) Len() int <span class="cov8" title="1">{
return len(cs.XValues)
}</span>
// GetValues gets the x,y values at a given index.
func (cs ContinuousSeries) GetValues(index int) (float64, float64) <span class="cov8" title="1">{
return cs.XValues[index], cs.YValues[index]
}</span>
// GetFirstValues gets the first x,y values.
func (cs ContinuousSeries) GetFirstValues() (float64, float64) <span class="cov8" title="1">{
return cs.XValues[0], cs.YValues[0]
}</span>
// GetLastValues gets the last x,y values.
func (cs ContinuousSeries) GetLastValues() (float64, float64) <span class="cov8" title="1">{
return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1]
}</span>
// GetValueFormatters returns value formatter defaults for the series.
func (cs ContinuousSeries) GetValueFormatters() (x, y ValueFormatter) <span class="cov8" title="1">{
if cs.XValueFormatter != nil </span><span class="cov8" title="1">{
x = cs.XValueFormatter
}</span> else<span class="cov8" title="1"> {
x = FloatValueFormatter
}</span>
<span class="cov8" title="1">if cs.YValueFormatter != nil </span><span class="cov8" title="1">{
y = cs.YValueFormatter
}</span> else<span class="cov8" title="1"> {
y = FloatValueFormatter
}</span>
<span class="cov8" title="1">return</span>
}
// GetYAxis returns which YAxis the series draws on.
func (cs ContinuousSeries) GetYAxis() YAxisType <span class="cov8" title="1">{
return cs.YAxis
}</span>
// Render renders the series.
func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov8" title="1">{
style := cs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, cs)
}</span>
// Validate validates the series.
func (cs ContinuousSeries) Validate() error <span class="cov8" title="1">{
if len(cs.XValues) == 0 </span><span class="cov8" title="1">{
return fmt.Errorf("continuous series must have xvalues set")
}</span>
<span class="cov8" title="1">if len(cs.YValues) == 0 </span><span class="cov8" title="1">{
return fmt.Errorf("continuous series must have yvalues set")
}</span>
<span class="cov8" title="1">return nil</span>
}
</pre>
<pre class="file" id="file9" style="display: none">package chart
import (
"math"
util "github.com/wcharczuk/go-chart/util"
)
var (
// Draw contains helpers for drawing common objects.
Draw = &amp;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) <span class="cov8" title="1">{
if vs.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">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() &amp;&amp; style.ShouldDrawFill() </span><span class="cov8" title="1">{
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
r.MoveTo(x0, y0)
for i := 1; i &lt; vs.Len(); i++ </span><span class="cov8" title="1">{
vx, vy = vs.GetValues(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)
r.LineTo(x, y)
}</span>
<span class="cov8" title="1">r.LineTo(x, util.Math.MinInt(cb, cb-yv0))
r.LineTo(x0, util.Math.MinInt(cb, cb-yv0))
r.LineTo(x0, y0)
r.Fill()</span>
}
<span class="cov8" title="1">if style.ShouldDrawStroke() </span><span class="cov8" title="1">{
style.GetStrokeOptions().WriteDrawingOptionsToRenderer(r)
r.MoveTo(x0, y0)
for i := 1; i &lt; vs.Len(); i++ </span><span class="cov8" title="1">{
vx, vy = vs.GetValues(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)
r.LineTo(x, y)
}</span>
<span class="cov8" title="1">r.Stroke()</span>
}
<span class="cov8" title="1">if style.ShouldDrawDot() </span><span class="cov0" title="0">{
defaultDotWidth := style.GetDotWidth()
style.GetDotOptions().WriteDrawingOptionsToRenderer(r)
for i := 0; i &lt; vs.Len(); i++ </span><span class="cov0" title="0">{
vx, vy = vs.GetValues(i)
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy)
dotWidth := defaultDotWidth
if style.DotWidthProvider != nil </span><span class="cov0" title="0">{
dotWidth = style.DotWidthProvider(xrange, yrange, i, vx, vy)
}</span>
<span class="cov0" title="0">if style.DotColorProvider != nil </span><span class="cov0" title="0">{
dotColor := style.DotColorProvider(xrange, yrange, i, vx, vy)
r.SetFillColor(dotColor)
r.SetStrokeColor(dotColor)
}</span>
<span class="cov0" title="0">r.Circle(dotWidth, x, y)
r.FillStroke()</span>
}
}
}
// BoundedSeries draws a series that implements BoundedValuesProvider.
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValuesProvider, drawOffsetIndexes ...int) <span class="cov0" title="0">{
drawOffsetIndex := 0
if len(drawOffsetIndexes) &gt; 0 </span><span class="cov0" title="0">{
drawOffsetIndex = drawOffsetIndexes[0]
}</span>
<span class="cov0" title="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 &lt; bbs.Len(); i++ </span><span class="cov0" title="0">{
vx, vy1, vy2 = bbs.GetBoundedValues(i)
xvalues[i] = vx
y2values[i] = vy2
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy1)
if i &gt; drawOffsetIndex </span><span class="cov0" title="0">{
r.LineTo(x, y)
}</span> else<span class="cov0" title="0"> {
r.MoveTo(x, y)
}</span>
}
<span class="cov0" title="0">y = cb - yrange.Translate(vy2)
r.LineTo(x, y)
for i := bbs.Len() - 1; i &gt;= drawOffsetIndex; i-- </span><span class="cov0" title="0">{
vx, vy2 = xvalues[i], y2values[i]
x = cl + xrange.Translate(vx)
y = cb - yrange.Translate(vy2)
r.LineTo(x, y)
}</span>
<span class="cov0" title="0">r.Close()
r.FillStroke()</span>
}
// 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) <span class="cov0" title="0">{
if vs.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
//calculate bar width?
<span class="cov0" title="0">seriesLength := vs.Len()
barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength)))
if len(barWidths) &gt; 0 </span><span class="cov0" title="0">{
barWidth = barWidths[0]
}</span>
<span class="cov0" title="0">cb := canvasBox.Bottom
cl := canvasBox.Left
//foreach datapoint, draw a box.
for index := 0; index &lt; seriesLength; index++ </span><span class="cov0" title="0">{
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 &gt;&gt; 1),
Right: x + (barWidth &gt;&gt; 1),
Bottom: cb - y,
}, style)
}</span>
}
// MeasureAnnotation measures how big an annotation would be.
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box <span class="cov8" title="1">{
style.WriteToRenderer(r)
defer r.ResetStyle()
textBox := r.MeasureText(label)
textWidth := textBox.Width()
textHeight := textBox.Height()
halfTextHeight := textHeight &gt;&gt; 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,
}
}</span>
// Annotation draws an anotation with a renderer.
func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) <span class="cov8" title="1">{
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
textBox := r.MeasureText(label)
textWidth := textBox.Width()
halfTextHeight := textBox.Height() &gt;&gt; 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)
}</span>
// Box draws a box with a given style.
func (d draw) Box(r Renderer, b Box, s Style) <span class="cov8" title="1">{
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()
}</span>
func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) <span class="cov0" title="0">{
d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s)
}</span>
func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) <span class="cov0" title="0">{
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()
}</span>
// DrawText draws text with a given style.
func (d draw) Text(r Renderer, text string, x, y int, style Style) <span class="cov8" title="1">{
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
r.Text(text, x, y)
}</span>
func (d draw) MeasureText(r Renderer, text string, style Style) Box <span class="cov8" title="1">{
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
return r.MeasureText(text)
}</span>
// TextWithin draws the text within a given box.
func (d draw) TextWithin(r Renderer, text string, box Box, style Style) <span class="cov8" title="1">{
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() </span>{
case TextVerticalAlignBottom, TextVerticalAlignBaseline:<span class="cov0" title="0"> // i have to build better baseline handling into measure text
y = y - linesBox.Height()</span>
case TextVerticalAlignMiddle, TextVerticalAlignMiddleBaseline:<span class="cov0" title="0">
y = (y - linesBox.Height()) &gt;&gt; 1</span>
}
<span class="cov8" title="1">var tx, ty int
for _, line := range lines </span><span class="cov8" title="1">{
lineBox := r.MeasureText(line)
switch style.GetTextHorizontalAlign() </span>{
case TextHorizontalAlignCenter:<span class="cov8" title="1">
tx = box.Left + ((box.Width() - lineBox.Width()) &gt;&gt; 1)</span>
case TextHorizontalAlignRight:<span class="cov0" title="0">
tx = box.Right - lineBox.Width()</span>
default:<span class="cov0" title="0">
tx = box.Left</span>
}
<span class="cov8" title="1">if style.TextRotationDegrees == 0 </span><span class="cov8" title="1">{
ty = y + lineBox.Height()
}</span> else<span class="cov0" title="0"> {
ty = y
}</span>
<span class="cov8" title="1">r.Text(line, tx, ty)
y += lineBox.Height() + style.GetTextLineSpacing()</span>
}
}
</pre>
<pre class="file" id="file10" style="display: none">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 <span class="cov8" title="1">{
v, _ := strconv.ParseInt(hex, 16, 16)
return uint8(v)
}</span>
// ColorFromHex returns a color from a css hex code.
func ColorFromHex(hex string) Color <span class="cov8" title="1">{
var c Color
if len(hex) == 3 </span><span class="cov8" title="1">{
c.R = parseHex(string(hex[0])) * 0x11
c.G = parseHex(string(hex[1])) * 0x11
c.B = parseHex(string(hex[2])) * 0x11
}</span> else<span class="cov8" title="1"> {
c.R = parseHex(string(hex[0:2]))
c.G = parseHex(string(hex[2:4]))
c.B = parseHex(string(hex[4:6]))
}</span>
<span class="cov8" title="1">c.A = 255
return c</span>
}
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color <span class="cov8" title="1">{
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 &gt;&gt; 8))
return c
}</span>
// ColorChannelFromFloat returns a normalized byte from a given float value.
func ColorChannelFromFloat(v float64) uint8 <span class="cov0" title="0">{
return uint8(v * 255)
}</span>
// 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) <span class="cov0" title="0">{
fa := float64(c.A) / 255.0
r = uint32(float64(uint32(c.R)) * fa)
r |= r &lt;&lt; 8
g = uint32(float64(uint32(c.G)) * fa)
g |= g &lt;&lt; 8
b = uint32(float64(uint32(c.B)) * fa)
b |= b &lt;&lt; 8
a = uint32(c.A)
a |= a &lt;&lt; 8
return
}</span>
// IsZero returns if the color has been set or not.
func (c Color) IsZero() bool <span class="cov0" title="0">{
return c.R == 0 &amp;&amp; c.G == 0 &amp;&amp; c.B == 0 &amp;&amp; c.A == 0
}</span>
// IsTransparent returns if the colors alpha channel is zero.
func (c Color) IsTransparent() bool <span class="cov0" title="0">{
return c.A == 0
}</span>
// WithAlpha returns a copy of the color with a given alpha.
func (c Color) WithAlpha(a uint8) Color <span class="cov0" title="0">{
return Color{
R: c.R,
G: c.G,
B: c.B,
A: a,
}
}</span>
// Equals returns true if the color equals another.
func (c Color) Equals(other Color) bool <span class="cov8" title="1">{
return c.R == other.R &amp;&amp;
c.G == other.G &amp;&amp;
c.B == other.B &amp;&amp;
c.A == other.A
}</span>
// AverageWith averages two colors.
func (c Color) AverageWith(other Color) Color <span class="cov0" title="0">{
return Color{
R: (c.R + other.R) &gt;&gt; 1,
G: (c.G + other.G) &gt;&gt; 1,
B: (c.B + other.B) &gt;&gt; 1,
A: c.A,
}
}</span>
// String returns a css string representation of the color.
func (c Color) String() string <span class="cov8" title="1">{
fa := float64(c.A) / float64(255)
return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa)
}</span>
</pre>
<pre class="file" id="file11" style="display: none">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) <span class="cov0" title="0">{
// 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]
}</span>
// 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) <span class="cov0" title="0">{
// 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 &gt;= 0 </span><span class="cov0" title="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) &lt; flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 </span><span class="cov0" title="0">{
t.LineTo(c[6], c[7])
i--
}</span> else<span class="cov0" title="0"> {
// second half of bezier go lower onto the stack
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
i++
}</span>
}
}
// 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) <span class="cov8" title="1">{
// 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
}</span>
func traceWindowIndices(i int) (startAt, endAt int) <span class="cov8" title="1">{
startAt = i * 6
endAt = startAt + 6
return
}</span>
func traceCalcDeltas(c []float64) (dx, dy, d float64) <span class="cov8" title="1">{
dx = c[4] - c[0]
dy = c[5] - c[1]
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
return
}</span>
func traceIsFlat(dx, dy, d, threshold float64) bool <span class="cov8" title="1">{
return (d * d) &lt; threshold*(dx*dx+dy*dy)
}</span>
func traceGetWindow(curves []float64, i int) []float64 <span class="cov8" title="1">{
startAt, endAt := traceWindowIndices(i)
return curves[startAt:endAt]
}</span>
// 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) <span class="cov8" title="1">{
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 &gt;= 0 </span><span class="cov8" title="1">{
c = traceGetWindow(curves, i)
dx, dy, d = traceCalcDeltas(c)
// bail early if the distance is 0
if d == 0 </span><span class="cov0" title="0">{
return
}</span>
// if it's flat then trace a line
<span class="cov8" title="1">if traceIsFlat(dx, dy, d, flatteningThreshold) || i == lastIteration </span><span class="cov8" title="1">{
t.LineTo(c[4], c[5])
i--
}</span> else<span class="cov8" title="1"> {
SubdivideQuad(c, traceGetWindow(curves, i+1), traceGetWindow(curves, i))
i++
}</span>
}
}
// TraceArc trace an arc using a Liner
func TraceArc(t Liner, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) <span class="cov0" title="0">{
end := start + angle
clockWise := true
if angle &lt; 0 </span><span class="cov0" title="0">{
clockWise = false
}</span>
<span class="cov0" title="0">ra := (math.Abs(rx) + math.Abs(ry)) / 2
da := math.Acos(ra/(ra+0.125/scale)) * 2
//normalize
if !clockWise </span><span class="cov0" title="0">{
da = -da
}</span>
<span class="cov0" title="0">angle = start + da
var curX, curY float64
for </span><span class="cov0" title="0">{
if (angle &lt; end-da/4) != clockWise </span><span class="cov0" title="0">{
curX = x + math.Cos(end)*rx
curY = y + math.Sin(end)*ry
return curX, curY
}</span>
<span class="cov0" title="0">curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
t.LineTo(curX, curY)</span>
}
}
</pre>
<pre class="file" id="file12" style="display: none">package drawing
// NewDashVertexConverter creates a new dash converter.
func NewDashVertexConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter <span class="cov0" title="0">{
var dasher DashVertexConverter
dasher.dash = dash
dasher.currentDash = 0
dasher.dashOffset = dashOffset
dasher.next = flattener
return &amp;dasher
}</span>
// 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) <span class="cov0" title="0">{
dasher.lineTo(x, y)
}</span>
// MoveTo implements the pathbuilder interface.
func (dasher *DashVertexConverter) MoveTo(x, y float64) <span class="cov0" title="0">{
dasher.next.MoveTo(x, y)
dasher.x, dasher.y = x, y
dasher.distance = dasher.dashOffset
dasher.currentDash = 0
}</span>
// LineJoin implements the pathbuilder interface.
func (dasher *DashVertexConverter) LineJoin() <span class="cov0" title="0">{
dasher.next.LineJoin()
}</span>
// Close implements the pathbuilder interface.
func (dasher *DashVertexConverter) Close() <span class="cov0" title="0">{
dasher.next.Close()
}</span>
// End implements the pathbuilder interface.
func (dasher *DashVertexConverter) End() <span class="cov0" title="0">{
dasher.next.End()
}</span>
func (dasher *DashVertexConverter) lineTo(x, y float64) <span class="cov0" title="0">{
rest := dasher.dash[dasher.currentDash] - dasher.distance
for rest &lt; 0 </span><span class="cov0" title="0">{
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
rest = dasher.dash[dasher.currentDash] - dasher.distance
}</span>
<span class="cov0" title="0">d := distance(dasher.x, dasher.y, x, y)
for d &gt;= rest </span><span class="cov0" title="0">{
k := rest / d
lx := dasher.x + k*(x-dasher.x)
ly := dasher.y + k*(y-dasher.y)
if dasher.currentDash%2 == 0 </span><span class="cov0" title="0">{
// line
dasher.next.LineTo(lx, ly)
}</span> else<span class="cov0" title="0"> {
// gap
dasher.next.End()
dasher.next.MoveTo(lx, ly)
}</span>
<span class="cov0" title="0">d = d - rest
dasher.x, dasher.y = lx, ly
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
rest = dasher.dash[dasher.currentDash]</span>
}
<span class="cov0" title="0">dasher.distance = d
if dasher.currentDash%2 == 0 </span><span class="cov0" title="0">{
// line
dasher.next.LineTo(x, y)
}</span> else<span class="cov0" title="0"> {
// gap
dasher.next.End()
dasher.next.MoveTo(x, y)
}</span>
<span class="cov0" title="0">if dasher.distance &gt;= dasher.dash[dasher.currentDash] </span><span class="cov0" title="0">{
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
}</span>
<span class="cov0" title="0">dasher.x, dasher.y = x, y</span>
}
</pre>
<pre class="file" id="file13" style="display: none">package drawing
// DemuxFlattener is a flattener
type DemuxFlattener struct {
Flatteners []Flattener
}
// MoveTo implements the path builder interface.
func (dc DemuxFlattener) MoveTo(x, y float64) <span class="cov0" title="0">{
for _, flattener := range dc.Flatteners </span><span class="cov0" title="0">{
flattener.MoveTo(x, y)
}</span>
}
// LineTo implements the path builder interface.
func (dc DemuxFlattener) LineTo(x, y float64) <span class="cov0" title="0">{
for _, flattener := range dc.Flatteners </span><span class="cov0" title="0">{
flattener.LineTo(x, y)
}</span>
}
// LineJoin implements the path builder interface.
func (dc DemuxFlattener) LineJoin() <span class="cov0" title="0">{
for _, flattener := range dc.Flatteners </span><span class="cov0" title="0">{
flattener.LineJoin()
}</span>
}
// Close implements the path builder interface.
func (dc DemuxFlattener) Close() <span class="cov0" title="0">{
for _, flattener := range dc.Flatteners </span><span class="cov0" title="0">{
flattener.Close()
}</span>
}
// End implements the path builder interface.
func (dc DemuxFlattener) End() <span class="cov0" title="0">{
for _, flattener := range dc.Flatteners </span><span class="cov0" title="0">{
flattener.End()
}</span>
}
</pre>
<pre class="file" id="file14" style="display: none">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) <span class="cov0" title="0">{
// First Point
var startX, startY float64
// Current Point
var x, y float64
var i int
for _, cmp := range path.Components </span><span class="cov0" title="0">{
switch cmp </span>{
case MoveToComponent:<span class="cov0" title="0">
x, y = path.Points[i], path.Points[i+1]
startX, startY = x, y
if i != 0 </span><span class="cov0" title="0">{
flattener.End()
}</span>
<span class="cov0" title="0">flattener.MoveTo(x, y)
i += 2</span>
case LineToComponent:<span class="cov0" title="0">
x, y = path.Points[i], path.Points[i+1]
flattener.LineTo(x, y)
flattener.LineJoin()
i += 2</span>
case QuadCurveToComponent:<span class="cov0" title="0">
// 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</span>
case CubicCurveToComponent:<span class="cov0" title="0">
TraceCubic(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+4], path.Points[i+5]
flattener.LineTo(x, y)
i += 6</span>
case ArcToComponent:<span class="cov0" title="0">
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</span>
case CloseComponent:<span class="cov0" title="0">
flattener.LineTo(startX, startY)
flattener.Close()</span>
}
}
<span class="cov0" title="0">flattener.End()</span>
}
// 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) <span class="cov0" title="0">{
p.Points = append(p.Points, x, y)
// TODO need to mark this point as moveto
}</span>
// LineTo implements the path interface.
func (p *SegmentedPath) LineTo(x, y float64) <span class="cov0" title="0">{
p.Points = append(p.Points, x, y)
}</span>
// LineJoin implements the path interface.
func (p *SegmentedPath) LineJoin() {<span class="cov0" title="0">
// TODO need to mark the current point as linejoin
}</span>
// Close implements the path interface.
func (p *SegmentedPath) Close() {<span class="cov0" title="0">
// TODO Close
}</span>
// End implements the path interface.
func (p *SegmentedPath) End() {<span class="cov0" title="0">
// Nothing to do
}</span>
</pre>
<pre class="file" id="file15" style="display: none">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) <span class="cov0" title="0">{
liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
}</span>
// LineTo implements the path builder interface.
func (liner FtLineBuilder) LineTo(x, y float64) <span class="cov0" title="0">{
liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
}</span>
// LineJoin implements the path builder interface.
func (liner FtLineBuilder) LineJoin() {<span class="cov0" title="0">}</span>
// Close implements the path builder interface.
func (liner FtLineBuilder) Close() {<span class="cov0" title="0">}</span>
// End implements the path builder interface.
func (liner FtLineBuilder) End() {<span class="cov0" title="0">}</span>
</pre>
<pre class="file" id="file16" style="display: none">package drawing
import (
"image/color"
"image/draw"
)
// PolylineBresenham draws a polyline to an image
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) <span class="cov0" title="0">{
for i := 2; i &lt; len(s); i += 2 </span><span class="cov0" title="0">{
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))
}</span>
}
// Bresenham draws a line between (x0, y0) and (x1, y1)
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) <span class="cov0" title="0">{
dx := abs(x1 - x0)
dy := abs(y1 - y0)
var sx, sy int
if x0 &lt; x1 </span><span class="cov0" title="0">{
sx = 1
}</span> else<span class="cov0" title="0"> {
sx = -1
}</span>
<span class="cov0" title="0">if y0 &lt; y1 </span><span class="cov0" title="0">{
sy = 1
}</span> else<span class="cov0" title="0"> {
sy = -1
}</span>
<span class="cov0" title="0">err := dx - dy
var e2 int
for </span><span class="cov0" title="0">{
img.Set(x0, y0, color)
if x0 == x1 &amp;&amp; y0 == y1 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">e2 = 2 * err
if e2 &gt; -dy </span><span class="cov0" title="0">{
err = err - dy
x0 = x0 + sx
}</span>
<span class="cov0" title="0">if e2 &lt; dx </span><span class="cov0" title="0">{
err = err + dx
y0 = y0 + sy
}</span>
}
}
</pre>
<pre class="file" id="file17" style="display: none">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 <span class="cov0" title="0">{
return tr[0]*tr[3] - tr[1]*tr[2]
}</span>
// Transform applies the transformation matrix to points. It modify the points passed in parameter.
func (tr Matrix) Transform(points []float64) <span class="cov0" title="0">{
for i, j := 0, 1; j &lt; len(points); i, j = i+2, j+2 </span><span class="cov0" title="0">{
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]
}</span>
}
// TransformPoint applies the transformation matrix to point. It returns the point the transformed point.
func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) <span class="cov0" title="0">{
xres = x*tr[0] + y*tr[2] + tr[4]
yres = x*tr[1] + y*tr[3] + tr[5]
return xres, yres
}</span>
func minMax(x, y float64) (min, max float64) <span class="cov0" title="0">{
if x &gt; y </span><span class="cov0" title="0">{
return y, x
}</span>
<span class="cov0" title="0">return x, y</span>
}
// 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) <span class="cov0" title="0">{
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
}</span>
// 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) <span class="cov0" title="0">{
d := tr.Determinant() // matrix determinant
for i, j := 0, 1; j &lt; len(points); i, j = i+2, j+2 </span><span class="cov0" title="0">{
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
}</span>
}
// 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) <span class="cov0" title="0">{
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
}</span>
// 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) <span class="cov0" title="0">{
for i, j := 0, 1; j &lt; len(points); i, j = i+2, j+2 </span><span class="cov0" title="0">{
x := points[i]
y := points[j]
points[i] = x*tr[0] + y*tr[2]
points[j] = x*tr[1] + y*tr[3]
}</span>
}
// NewIdentityMatrix creates an identity transformation matrix.
func NewIdentityMatrix() Matrix <span class="cov0" title="0">{
return Matrix{1, 0, 0, 1, 0, 0}
}</span>
// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter
func NewTranslationMatrix(tx, ty float64) Matrix <span class="cov0" title="0">{
return Matrix{1, 0, 0, 1, tx, ty}
}</span>
// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor
func NewScaleMatrix(sx, sy float64) Matrix <span class="cov0" title="0">{
return Matrix{sx, 0, 0, sy, 0, 0}
}</span>
// NewRotationMatrix creates a rotation transformation matrix. angle is in radian
func NewRotationMatrix(angle float64) Matrix <span class="cov0" title="0">{
c := math.Cos(angle)
s := math.Sin(angle)
return Matrix{c, s, -s, c, 0, 0}
}</span>
// NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2.
func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix <span class="cov0" title="0">{
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}
}</span>
// Inverse computes the inverse matrix
func (tr *Matrix) Inverse() <span class="cov0" title="0">{
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
}</span>
// Copy copies the matrix.
func (tr Matrix) Copy() Matrix <span class="cov0" title="0">{
var result Matrix
copy(result[:], tr[:])
return result
}</span>
// Compose multiplies trToConcat x tr
func (tr *Matrix) Compose(trToCompose Matrix) <span class="cov0" title="0">{
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
}</span>
// Scale adds a scale to the matrix
func (tr *Matrix) Scale(sx, sy float64) <span class="cov0" title="0">{
tr[0] = sx * tr[0]
tr[1] = sx * tr[1]
tr[2] = sy * tr[2]
tr[3] = sy * tr[3]
}</span>
// Translate adds a translation to the matrix
func (tr *Matrix) Translate(tx, ty float64) <span class="cov0" title="0">{
tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
}</span>
// Rotate adds a rotation to the matrix.
func (tr *Matrix) Rotate(radians float64) <span class="cov0" title="0">{
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
}</span>
// GetTranslation gets the matrix traslation.
func (tr Matrix) GetTranslation() (x, y float64) <span class="cov0" title="0">{
return tr[4], tr[5]
}</span>
// GetScaling gets the matrix scaling.
func (tr Matrix) GetScaling() (x, y float64) <span class="cov0" title="0">{
return tr[0], tr[3]
}</span>
// GetScale computes a scale for the matrix
func (tr Matrix) GetScale() float64 <span class="cov0" title="0">{
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)
}</span>
// ******************** Testing ********************
// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements.
func (tr Matrix) Equals(tr2 Matrix) bool <span class="cov0" title="0">{
for i := 0; i &lt; 6; i = i + 1 </span><span class="cov0" title="0">{
if !fequals(tr[i], tr2[i]) </span><span class="cov0" title="0">{
return false
}</span>
}
<span class="cov0" title="0">return true</span>
}
// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements.
func (tr Matrix) IsIdentity() bool <span class="cov0" title="0">{
return fequals(tr[4], 0) &amp;&amp; fequals(tr[5], 0) &amp;&amp; tr.IsTranslation()
}</span>
// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements.
func (tr Matrix) IsTranslation() bool <span class="cov0" title="0">{
return fequals(tr[0], 1) &amp;&amp; fequals(tr[1], 0) &amp;&amp; fequals(tr[2], 0) &amp;&amp; fequals(tr[3], 1)
}</span>
// 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 <span class="cov0" title="0">{
return math.Abs(float1-float2) &lt;= epsilon
}</span>
</pre>
<pre class="file" id="file18" style="display: none">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) <span class="cov0" title="0">{
var transformer draw.Transformer
switch filter </span>{
case LinearFilter:<span class="cov0" title="0">
transformer = draw.NearestNeighbor</span>
case BilinearFilter:<span class="cov0" title="0">
transformer = draw.BiLinear</span>
case BicubicFilter:<span class="cov0" title="0">
transformer = draw.CatmullRom</span>
}
<span class="cov0" title="0">transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil)</span>
}
</pre>
<pre class="file" id="file19" style="display: none">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) <span class="cov0" title="0">{
p.Components = append(p.Components, cmd)
p.Points = append(p.Points, points...)
}</span>
// LastPoint returns the current point of the current path
func (p *Path) LastPoint() (x, y float64) <span class="cov0" title="0">{
return p.x, p.y
}</span>
// MoveTo starts a new path at (x, y) position
func (p *Path) MoveTo(x, y float64) <span class="cov0" title="0">{
p.appendToPath(MoveToComponent, x, y)
p.x = x
p.y = y
}</span>
// LineTo adds a line to the current path
func (p *Path) LineTo(x, y float64) <span class="cov0" title="0">{
if len(p.Components) == 0 </span><span class="cov0" title="0">{ //special case when no move has been done
p.MoveTo(0, 0)
}</span>
<span class="cov0" title="0">p.appendToPath(LineToComponent, x, y)
p.x = x
p.y = y</span>
}
// QuadCurveTo adds a quadratic bezier curve to the current path
func (p *Path) QuadCurveTo(cx, cy, x, y float64) <span class="cov0" title="0">{
if len(p.Components) == 0 </span><span class="cov0" title="0">{ //special case when no move has been done
p.MoveTo(0, 0)
}</span>
<span class="cov0" title="0">p.appendToPath(QuadCurveToComponent, cx, cy, x, y)
p.x = x
p.y = y</span>
}
// CubicCurveTo adds a cubic bezier curve to the current path
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) <span class="cov0" title="0">{
if len(p.Components) == 0 </span><span class="cov0" title="0">{ //special case when no move has been done
p.MoveTo(0, 0)
}</span>
<span class="cov0" title="0">p.appendToPath(CubicCurveToComponent, cx1, cy1, cx2, cy2, x, y)
p.x = x
p.y = y</span>
}
// ArcTo adds an arc to the path
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, delta float64) <span class="cov0" title="0">{
endAngle := startAngle + delta
clockWise := true
if delta &lt; 0 </span><span class="cov0" title="0">{
clockWise = false
}</span>
// normalize
<span class="cov0" title="0">if clockWise </span><span class="cov0" title="0">{
for endAngle &lt; startAngle </span><span class="cov0" title="0">{
endAngle += math.Pi * 2.0
}</span>
} else<span class="cov0" title="0"> {
for startAngle &lt; endAngle </span><span class="cov0" title="0">{
startAngle += math.Pi * 2.0
}</span>
}
<span class="cov0" title="0">startX := cx + math.Cos(startAngle)*rx
startY := cy + math.Sin(startAngle)*ry
if len(p.Components) &gt; 0 </span><span class="cov0" title="0">{
p.LineTo(startX, startY)
}</span> else<span class="cov0" title="0"> {
p.MoveTo(startX, startY)
}</span>
<span class="cov0" title="0">p.appendToPath(ArcToComponent, cx, cy, rx, ry, startAngle, delta)
p.x = cx + math.Cos(endAngle)*rx
p.y = cy + math.Sin(endAngle)*ry</span>
}
// Close closes the current path
func (p *Path) Close() <span class="cov0" title="0">{
p.appendToPath(CloseComponent)
}</span>
// Copy make a clone of the current path and return it
func (p *Path) Copy() (dest *Path) <span class="cov0" title="0">{
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
}</span>
// Clear reset the path
func (p *Path) Clear() <span class="cov0" title="0">{
p.Components = p.Components[0:0]
p.Points = p.Points[0:0]
return
}</span>
// IsEmpty returns true if the path is empty
func (p *Path) IsEmpty() bool <span class="cov0" title="0">{
return len(p.Components) == 0
}</span>
// String returns a debug text view of the path
func (p *Path) String() string <span class="cov0" title="0">{
s := ""
j := 0
for _, cmd := range p.Components </span><span class="cov0" title="0">{
switch cmd </span>{
case MoveToComponent:<span class="cov0" title="0">
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2</span>
case LineToComponent:<span class="cov0" title="0">
s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2</span>
case QuadCurveToComponent:<span class="cov0" title="0">
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</span>
case CubicCurveToComponent:<span class="cov0" title="0">
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</span>
case ArcToComponent:<span class="cov0" title="0">
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</span>
case CloseComponent:<span class="cov0" title="0">
s += "Close\n"</span>
}
}
<span class="cov0" title="0">return s</span>
}
</pre>
<pre class="file" id="file20" style="display: none">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) <span class="cov0" title="0">{
var painter Painter
switch selectImage := img.(type) </span>{
case *image.RGBA:<span class="cov0" title="0">
painter = raster.NewRGBAPainter(selectImage)</span>
default:<span class="cov0" title="0">
return nil, errors.New("NewRasterGraphicContext() :: invalid image type")</span>
}
<span class="cov0" title="0">return NewRasterGraphicContextWithPainter(img, painter), nil</span>
}
// NewRasterGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewRasterGraphicContextWithPainter(img draw.Image, painter Painter) *RasterGraphicContext <span class="cov0" title="0">{
width, height := img.Bounds().Dx(), img.Bounds().Dy()
return &amp;RasterGraphicContext{
NewStackGraphicContext(),
img,
painter,
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
&amp;truetype.GlyphBuf{},
DefaultDPI,
}
}</span>
// 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) <span class="cov0" title="0">{
rgc.DPI = dpi
rgc.recalc()
}</span>
// GetDPI returns the resolution of the Image GraphicContext
func (rgc *RasterGraphicContext) GetDPI() float64 <span class="cov0" title="0">{
return rgc.DPI
}</span>
// Clear fills the current canvas with a default transparent color
func (rgc *RasterGraphicContext) Clear() <span class="cov0" title="0">{
width, height := rgc.img.Bounds().Dx(), rgc.img.Bounds().Dy()
rgc.ClearRect(0, 0, width, height)
}</span>
// ClearRect fills the current canvas with a default transparent color at the specified rectangle
func (rgc *RasterGraphicContext) ClearRect(x1, y1, x2, y2 int) <span class="cov0" title="0">{
imageColor := image.NewUniform(rgc.current.FillColor)
draw.Draw(rgc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}</span>
// DrawImage draws the raster image in the current canvas
func (rgc *RasterGraphicContext) DrawImage(img image.Image) <span class="cov0" title="0">{
DrawImage(img, rgc.img, rgc.current.Tr, draw.Over, BilinearFilter)
}</span>
// FillString draws the text at point (0, 0)
func (rgc *RasterGraphicContext) FillString(text string) (cursor float64, err error) <span class="cov0" title="0">{
cursor, err = rgc.FillStringAt(text, 0, 0)
return
}</span>
// FillStringAt draws the text at the specified point (x, y)
func (rgc *RasterGraphicContext) FillStringAt(text string, x, y float64) (cursor float64, err error) <span class="cov0" title="0">{
cursor, err = rgc.CreateStringPath(text, x, y)
rgc.Fill()
return
}</span>
// StrokeString draws the contour of the text at point (0, 0)
func (rgc *RasterGraphicContext) StrokeString(text string) (cursor float64, err error) <span class="cov0" title="0">{
cursor, err = rgc.StrokeStringAt(text, 0, 0)
return
}</span>
// StrokeStringAt draws the contour of the text at point (x, y)
func (rgc *RasterGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64, err error) <span class="cov0" title="0">{
cursor, err = rgc.CreateStringPath(text, x, y)
rgc.Stroke()
return
}</span>
func (rgc *RasterGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error <span class="cov0" title="0">{
if err := rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), glyph, font.HintingNone); err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">e0 := 0
for _, e1 := range rgc.glyphBuf.Ends </span><span class="cov0" title="0">{
DrawContour(rgc, rgc.glyphBuf.Points[e0:e1], dx, dy)
e0 = e1
}</span>
<span class="cov0" title="0">return nil</span>
}
// 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) <span class="cov0" title="0">{
f := rgc.GetFont()
if f == nil </span><span class="cov0" title="0">{
err = errors.New("No font loaded, cannot continue")
return
}</span>
<span class="cov0" title="0">rgc.recalc()
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rc := range s </span><span class="cov0" title="0">{
index := f.Index(rc)
if hasPrev </span><span class="cov0" title="0">{
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
}</span>
<span class="cov0" title="0">err = rgc.drawGlyph(index, x, y)
if err != nil </span><span class="cov0" title="0">{
cursor = x - startx
return
}</span>
<span class="cov0" title="0">x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true</span>
}
<span class="cov0" title="0">cursor = x - startx
return</span>
}
// GetStringBounds returns the approximate pixel bounds of a string.
func (rgc *RasterGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64, err error) <span class="cov0" title="0">{
f := rgc.GetFont()
if f == nil </span><span class="cov0" title="0">{
err = errors.New("No font loaded, cannot continue")
return
}</span>
<span class="cov0" title="0">rgc.recalc()
left = math.MaxFloat64
top = math.MaxFloat64
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
for _, rc := range s </span><span class="cov0" title="0">{
index := f.Index(rc)
if hasPrev </span><span class="cov0" title="0">{
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
}</span>
<span class="cov0" title="0">if err = rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), index, font.HintingNone); err != nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">e0 := 0
for _, e1 := range rgc.glyphBuf.Ends </span><span class="cov0" title="0">{
ps := rgc.glyphBuf.Points[e0:e1]
for _, p := range ps </span><span class="cov0" title="0">{
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)
}</span>
<span class="cov0" title="0">e0 = e1</span>
}
<span class="cov0" title="0">cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true</span>
}
<span class="cov0" title="0">return</span>
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (rgc *RasterGraphicContext) recalc() <span class="cov0" title="0">{
rgc.current.Scale = rgc.current.FontSizePoints * float64(rgc.DPI)
}</span>
// SetFont sets the font used to draw text.
func (rgc *RasterGraphicContext) SetFont(font *truetype.Font) <span class="cov0" title="0">{
rgc.current.Font = font
}</span>
// GetFont returns the font used to draw text.
func (rgc *RasterGraphicContext) GetFont() *truetype.Font <span class="cov0" title="0">{
return rgc.current.Font
}</span>
// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) <span class="cov0" title="0">{
rgc.current.FontSizePoints = fontSizePoints
rgc.recalc()
}</span>
func (rgc *RasterGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) <span class="cov0" title="0">{
rgc.painter.SetColor(color)
rasterizer.Rasterize(rgc.painter)
rasterizer.Clear()
rgc.current.Path.Clear()
}</span>
// Stroke strokes the paths with the color specified by SetStrokeColor
func (rgc *RasterGraphicContext) Stroke(paths ...*Path) <span class="cov0" title="0">{
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 &amp;&amp; len(rgc.current.Dash) &gt; 0 </span><span class="cov0" title="0">{
liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
}</span> else<span class="cov0" title="0"> {
liner = stroker
}</span>
<span class="cov0" title="0">for _, p := range paths </span><span class="cov0" title="0">{
Flatten(p, liner, rgc.current.Tr.GetScale())
}</span>
<span class="cov0" title="0">rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)</span>
}
// Fill fills the paths with the color specified by SetFillColor
func (rgc *RasterGraphicContext) Fill(paths ...*Path) <span class="cov0" title="0">{
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 </span><span class="cov0" title="0">{
Flatten(p, flattener, rgc.current.Tr.GetScale())
}</span>
<span class="cov0" title="0">rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)</span>
}
// FillStroke first fills the paths and than strokes them
func (rgc *RasterGraphicContext) FillStroke(paths ...*Path) <span class="cov0" title="0">{
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 &amp;&amp; len(rgc.current.Dash) &gt; 0 </span><span class="cov0" title="0">{
liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
}</span> else<span class="cov0" title="0"> {
liner = stroker
}</span>
<span class="cov0" title="0">demux := DemuxFlattener{Flatteners: []Flattener{flattener, liner}}
for _, p := range paths </span><span class="cov0" title="0">{
Flatten(p, demux, rgc.current.Tr.GetScale())
}</span>
// Fill
<span class="cov0" title="0">rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
// Stroke
rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)</span>
}
</pre>
<pre class="file" id="file21" style="display: none">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 <span class="cov0" title="0">{
gc := &amp;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
}</span>
// GetMatrixTransform returns the matrix transform.
func (gc *StackGraphicContext) GetMatrixTransform() Matrix <span class="cov0" title="0">{
return gc.current.Tr
}</span>
// SetMatrixTransform sets the matrix transform.
func (gc *StackGraphicContext) SetMatrixTransform(tr Matrix) <span class="cov0" title="0">{
gc.current.Tr = tr
}</span>
// ComposeMatrixTransform composes a transform into the current transform.
func (gc *StackGraphicContext) ComposeMatrixTransform(tr Matrix) <span class="cov0" title="0">{
gc.current.Tr.Compose(tr)
}</span>
// Rotate rotates the matrix transform by an angle in degrees.
func (gc *StackGraphicContext) Rotate(angle float64) <span class="cov0" title="0">{
gc.current.Tr.Rotate(angle)
}</span>
// Translate translates a transform.
func (gc *StackGraphicContext) Translate(tx, ty float64) <span class="cov0" title="0">{
gc.current.Tr.Translate(tx, ty)
}</span>
// Scale scales a transform.
func (gc *StackGraphicContext) Scale(sx, sy float64) <span class="cov0" title="0">{
gc.current.Tr.Scale(sx, sy)
}</span>
// SetStrokeColor sets the stroke color.
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) <span class="cov0" title="0">{
gc.current.StrokeColor = c
}</span>
// SetFillColor sets the fill color.
func (gc *StackGraphicContext) SetFillColor(c color.Color) <span class="cov0" title="0">{
gc.current.FillColor = c
}</span>
// SetFillRule sets the fill rule.
func (gc *StackGraphicContext) SetFillRule(f FillRule) <span class="cov0" title="0">{
gc.current.FillRule = f
}</span>
// SetLineWidth sets the line width.
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) <span class="cov0" title="0">{
gc.current.LineWidth = lineWidth
}</span>
// SetLineCap sets the line cap.
func (gc *StackGraphicContext) SetLineCap(cap LineCap) <span class="cov0" title="0">{
gc.current.Cap = cap
}</span>
// SetLineJoin sets the line join.
func (gc *StackGraphicContext) SetLineJoin(join LineJoin) <span class="cov0" title="0">{
gc.current.Join = join
}</span>
// SetLineDash sets the line dash.
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) <span class="cov0" title="0">{
gc.current.Dash = dash
gc.current.DashOffset = dashOffset
}</span>
// SetFontSize sets the font size.
func (gc *StackGraphicContext) SetFontSize(fontSizePoints float64) <span class="cov0" title="0">{
gc.current.FontSizePoints = fontSizePoints
}</span>
// GetFontSize gets the font size.
func (gc *StackGraphicContext) GetFontSize() float64 <span class="cov0" title="0">{
return gc.current.FontSizePoints
}</span>
// SetFont sets the current font.
func (gc *StackGraphicContext) SetFont(f *truetype.Font) <span class="cov0" title="0">{
gc.current.Font = f
}</span>
// GetFont returns the font.
func (gc *StackGraphicContext) GetFont() *truetype.Font <span class="cov0" title="0">{
return gc.current.Font
}</span>
// BeginPath starts a new path.
func (gc *StackGraphicContext) BeginPath() <span class="cov0" title="0">{
gc.current.Path.Clear()
}</span>
// IsEmpty returns if the path is empty.
func (gc *StackGraphicContext) IsEmpty() bool <span class="cov0" title="0">{
return gc.current.Path.IsEmpty()
}</span>
// LastPoint returns the last point on the path.
func (gc *StackGraphicContext) LastPoint() (x float64, y float64) <span class="cov0" title="0">{
return gc.current.Path.LastPoint()
}</span>
// MoveTo moves the cursor for a path.
func (gc *StackGraphicContext) MoveTo(x, y float64) <span class="cov0" title="0">{
gc.current.Path.MoveTo(x, y)
}</span>
// LineTo draws a line.
func (gc *StackGraphicContext) LineTo(x, y float64) <span class="cov0" title="0">{
gc.current.Path.LineTo(x, y)
}</span>
// QuadCurveTo draws a quad curve.
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) <span class="cov0" title="0">{
gc.current.Path.QuadCurveTo(cx, cy, x, y)
}</span>
// CubicCurveTo draws a cubic curve.
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) <span class="cov0" title="0">{
gc.current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
}</span>
// ArcTo draws an arc.
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, delta float64) <span class="cov0" title="0">{
gc.current.Path.ArcTo(cx, cy, rx, ry, startAngle, delta)
}</span>
// Close closes a path.
func (gc *StackGraphicContext) Close() <span class="cov0" title="0">{
gc.current.Path.Close()
}</span>
// Save pushes a context onto the stack.
func (gc *StackGraphicContext) Save() <span class="cov0" title="0">{
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
}</span>
// Restore restores the previous context.
func (gc *StackGraphicContext) Restore() <span class="cov0" title="0">{
if gc.current.Previous != nil </span><span class="cov0" title="0">{
oldContext := gc.current
gc.current = gc.current.Previous
oldContext.Previous = nil
}</span>
}
</pre>
<pre class="file" id="file22" style="display: none">// 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 <span class="cov0" title="0">{
l := new(LineStroker)
l.Flattener = flattener
l.HalfLineWidth = 0.5
l.Cap = c
l.Join = j
return l
}</span>
// 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) <span class="cov0" title="0">{
l.x, l.y = x, y
}</span>
// LineTo implements the path builder interface.
func (l *LineStroker) LineTo(x, y float64) <span class="cov0" title="0">{
l.line(l.x, l.y, x, y)
}</span>
// LineJoin implements the path builder interface.
func (l *LineStroker) LineJoin() {<span class="cov0" title="0">}</span>
func (l *LineStroker) line(x1, y1, x2, y2 float64) <span class="cov0" title="0">{
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 </span><span class="cov0" title="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
}</span>
}
// Close implements the path builder interface.
func (l *LineStroker) Close() <span class="cov0" title="0">{
if len(l.vertices) &gt; 1 </span><span class="cov0" title="0">{
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
}</span>
}
// End implements the path builder interface.
func (l *LineStroker) End() <span class="cov0" title="0">{
if len(l.vertices) &gt; 1 </span><span class="cov0" title="0">{
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
for i, j := 2, 3; j &lt; len(l.vertices); i, j = i+2, j+2 </span><span class="cov0" title="0">{
l.Flattener.LineTo(l.vertices[i], l.vertices[j])
}</span>
}
<span class="cov0" title="0">for i, j := len(l.rewind)-2, len(l.rewind)-1; j &gt; 0; i, j = i-2, j-2 </span><span class="cov0" title="0">{
l.Flattener.LineTo(l.rewind[i], l.rewind[j])
}</span>
<span class="cov0" title="0">if len(l.vertices) &gt; 1 </span><span class="cov0" title="0">{
l.Flattener.LineTo(l.vertices[0], l.vertices[1])
}</span>
<span class="cov0" title="0">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</span>
}
func (l *LineStroker) appendVertex(vertices ...float64) <span class="cov0" title="0">{
s := len(vertices) / 2
l.vertices = append(l.vertices, vertices[:s]...)
l.rewind = append(l.rewind, vertices[s:]...)
}</span>
</pre>
<pre class="file" id="file23" style="display: none">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) <span class="cov0" title="0">{
if len(ps) == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">startX, startY := pointToF64Point(ps[0])
path.MoveTo(startX+dx, startY+dy)
q0X, q0Y, on0 := startX, startY, true
for _, p := range ps[1:] </span><span class="cov0" title="0">{
qX, qY := pointToF64Point(p)
on := p.Flags&amp;0x01 != 0
if on </span><span class="cov0" title="0">{
if on0 </span><span class="cov0" title="0">{
path.LineTo(qX+dx, qY+dy)
}</span> else<span class="cov0" title="0"> {
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
}</span>
} else<span class="cov0" title="0"> if !on0 </span><span class="cov0" title="0">{
midX := (q0X + qX) / 2
midY := (q0Y + qY) / 2
path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
}</span>
<span class="cov0" title="0">q0X, q0Y, on0 = qX, qY, on</span>
}
// Close the curve.
<span class="cov0" title="0">if on0 </span><span class="cov0" title="0">{
path.LineTo(startX+dx, startY+dy)
}</span> else<span class="cov0" title="0"> {
path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
}</span>
}
// 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 <span class="cov0" title="0">{
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,
}
}</span>
</pre>
<pre class="file" id="file24" style="display: none">// 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) <span class="cov0" title="0">{
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)
}</span>
// LineTo implements the path builder interface.
func (t Transformer) LineTo(x, y float64) <span class="cov0" title="0">{
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)
}</span>
// LineJoin implements the path builder interface.
func (t Transformer) LineJoin() <span class="cov0" title="0">{
t.Flattener.LineJoin()
}</span>
// Close implements the path builder interface.
func (t Transformer) Close() <span class="cov0" title="0">{
t.Flattener.Close()
}</span>
// End implements the path builder interface.
func (t Transformer) End() <span class="cov0" title="0">{
t.Flattener.End()
}</span>
</pre>
<pre class="file" id="file25" style="display: none">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) <span class="cov0" title="0">{
points = (pixels * 72.0) / dpi
return
}</span>
// PointsToPixels returns the pixels for a given number of points at a DPI.
func PointsToPixels(dpi, points float64) (pixels float64) <span class="cov0" title="0">{
pixels = (points * dpi) / 72.0
return
}</span>
func abs(i int) int <span class="cov0" title="0">{
if i &lt; 0 </span><span class="cov0" title="0">{
return -i
}</span>
<span class="cov0" title="0">return i</span>
}
func distance(x1, y1, x2, y2 float64) float64 <span class="cov0" title="0">{
return vectorDistance(x2-x1, y2-y1)
}</span>
func vectorDistance(dx, dy float64) float64 <span class="cov0" title="0">{
return float64(math.Sqrt(dx*dx + dy*dy))
}</span>
func toFtCap(c LineCap) raster.Capper <span class="cov0" title="0">{
switch c </span>{
case RoundCap:<span class="cov0" title="0">
return raster.RoundCapper</span>
case ButtCap:<span class="cov0" title="0">
return raster.ButtCapper</span>
case SquareCap:<span class="cov0" title="0">
return raster.SquareCapper</span>
}
<span class="cov0" title="0">return raster.RoundCapper</span>
}
func toFtJoin(j LineJoin) raster.Joiner <span class="cov0" title="0">{
switch j </span>{
case RoundJoin:<span class="cov0" title="0">
return raster.RoundJoiner</span>
case BevelJoin:<span class="cov0" title="0">
return raster.BevelJoiner</span>
}
<span class="cov0" title="0">return raster.RoundJoiner</span>
}
func pointToF64Point(p truetype.Point) (x, y float64) <span class="cov0" title="0">{
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
}</span>
func fUnitsToFloat64(x fixed.Int26_6) float64 <span class="cov0" title="0">{
scaled := x &lt;&lt; 2
return float64(scaled/256) + float64(scaled%256)/256.0
}</span>
</pre>
<pre class="file" id="file26" style="display: none">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 <span class="cov0" title="0">{
return ema.Name
}</span>
// GetStyle returns the line style.
func (ema EMASeries) GetStyle() Style <span class="cov0" title="0">{
return ema.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (ema EMASeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return ema.YAxis
}</span>
// GetPeriod returns the window size.
func (ema EMASeries) GetPeriod() int <span class="cov8" title="1">{
if ema.Period == 0 </span><span class="cov0" title="0">{
return DefaultEMAPeriod
}</span>
<span class="cov8" title="1">return ema.Period</span>
}
// Len returns the number of elements in the series.
func (ema EMASeries) Len() int <span class="cov8" title="1">{
return ema.InnerSeries.Len()
}</span>
// GetSigma returns the smoothing factor for the serise.
func (ema EMASeries) GetSigma() float64 <span class="cov8" title="1">{
return 2.0 / (float64(ema.GetPeriod()) + 1)
}</span>
// GetValues gets a value at a given index.
func (ema *EMASeries) GetValues(index int) (x, y float64) <span class="cov8" title="1">{
if ema.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if len(ema.cache) == 0 </span><span class="cov8" title="1">{
ema.ensureCachedValues()
}</span>
<span class="cov8" title="1">vx, _ := ema.InnerSeries.GetValues(index)
x = vx
y = ema.cache[index]
return</span>
}
// GetFirstValues computes the first moving average value.
func (ema *EMASeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
if ema.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if len(ema.cache) == 0 </span><span class="cov0" title="0">{
ema.ensureCachedValues()
}</span>
<span class="cov0" title="0">x, _ = ema.InnerSeries.GetValues(0)
y = ema.cache[0]
return</span>
}
// 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) <span class="cov8" title="1">{
if ema.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if len(ema.cache) == 0 </span><span class="cov0" title="0">{
ema.ensureCachedValues()
}</span>
<span class="cov8" title="1">lastIndex := ema.InnerSeries.Len() - 1
x, _ = ema.InnerSeries.GetValues(lastIndex)
y = ema.cache[lastIndex]
return</span>
}
func (ema *EMASeries) ensureCachedValues() <span class="cov8" title="1">{
seriesLength := ema.InnerSeries.Len()
ema.cache = make([]float64, seriesLength)
sigma := ema.GetSigma()
for x := 0; x &lt; seriesLength; x++ </span><span class="cov8" title="1">{
_, y := ema.InnerSeries.GetValues(x)
if x == 0 </span><span class="cov8" title="1">{
ema.cache[x] = y
continue</span>
}
<span class="cov8" title="1">previousEMA := ema.cache[x-1]
ema.cache[x] = ((y - previousEMA) * sigma) + previousEMA</span>
}
}
// Render renders the series.
func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := ema.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ema)
}</span>
// Validate validates the series.
func (ema *EMASeries) Validate() error <span class="cov0" title="0">{
if ema.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("ema series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file27" style="display: none">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 <span class="cov8" title="1">{
var vf ValueFormatter
if len(vfs) &gt; 0 </span><span class="cov0" title="0">{
vf = vfs[0]
}</span> else<span class="cov8" title="1"> if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped </span><span class="cov8" title="1">{
_, vf = typed.GetValueFormatters()
}</span> else<span class="cov0" title="0"> {
vf = FloatValueFormatter
}</span>
<span class="cov8" title="1">var firstValue Value2
if typed, isTyped := innerSeries.(FirstValuesProvider); isTyped </span><span class="cov8" title="1">{
firstValue.XValue, firstValue.YValue = typed.GetFirstValues()
firstValue.Label = vf(firstValue.YValue)
}</span> else<span class="cov0" title="0"> {
firstValue.XValue, firstValue.YValue = innerSeries.GetValues(0)
firstValue.Label = vf(firstValue.YValue)
}</span>
<span class="cov8" title="1">var seriesName string
var seriesStyle Style
if typed, isTyped := innerSeries.(Series); isTyped </span><span class="cov8" title="1">{
seriesName = fmt.Sprintf("%s - First Value", typed.GetName())
seriesStyle = typed.GetStyle()
}</span>
<span class="cov8" title="1">return AnnotationSeries{
Name: seriesName,
Style: seriesStyle,
Annotations: []Value2{firstValue},
}</span>
}
</pre>
<pre class="file" id="file28" style="display: none">package chart
import (
"sync"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/roboto"
)
var (
_defaultFontLock sync.Mutex
_defaultFont *truetype.Font
)
// GetDefaultFont returns the default font (Roboto-Medium).
func GetDefaultFont() (*truetype.Font, error) <span class="cov8" title="1">{
if _defaultFont == nil </span><span class="cov8" title="1">{
_defaultFontLock.Lock()
defer _defaultFontLock.Unlock()
if _defaultFont == nil </span><span class="cov8" title="1">{
font, err := truetype.Parse(roboto.Roboto)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">_defaultFont = font</span>
}
}
<span class="cov8" title="1">return _defaultFont, nil</span>
}
</pre>
<pre class="file" id="file29" style="display: none">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 <span class="cov0" title="0">{
return !gl.IsMinor
}</span>
// Minor returns if the gridline is a `minor` line.
func (gl GridLine) Minor() bool <span class="cov0" title="0">{
return gl.IsMinor
}</span>
// Render renders the gridline
func (gl GridLine) Render(r Renderer, canvasBox Box, ra Range, isVertical bool, defaults Style) <span class="cov0" title="0">{
r.SetStrokeColor(gl.Style.GetStrokeColor(defaults.GetStrokeColor()))
r.SetStrokeWidth(gl.Style.GetStrokeWidth(defaults.GetStrokeWidth()))
r.SetStrokeDashArray(gl.Style.GetStrokeDashArray(defaults.GetStrokeDashArray()))
if isVertical </span><span class="cov0" title="0">{
lineLeft := canvasBox.Left + ra.Translate(gl.Value)
lineBottom := canvasBox.Bottom
lineTop := canvasBox.Top
r.MoveTo(lineLeft, lineBottom)
r.LineTo(lineLeft, lineTop)
r.Stroke()
}</span> else<span class="cov0" title="0"> {
lineLeft := canvasBox.Left
lineRight := canvasBox.Right
lineHeight := canvasBox.Bottom - ra.Translate(gl.Value)
r.MoveTo(lineLeft, lineHeight)
r.LineTo(lineRight, lineHeight)
r.Stroke()
}</span>
}
// GenerateGridLines generates grid lines.
func GenerateGridLines(ticks []Tick, majorStyle, minorStyle Style) []GridLine <span class="cov8" title="1">{
var gl []GridLine
isMinor := false
if len(ticks) &lt; 3 </span><span class="cov0" title="0">{
return gl
}</span>
<span class="cov8" title="1">for _, t := range ticks[1 : len(ticks)-1] </span><span class="cov8" title="1">{
s := majorStyle
if isMinor </span><span class="cov8" title="1">{
s = minorStyle
}</span>
<span class="cov8" title="1">gl = append(gl, GridLine{
Style: s,
IsMinor: isMinor,
Value: t.Value,
})
isMinor = !isMinor</span>
}
<span class="cov8" title="1">return gl</span>
}
</pre>
<pre class="file" id="file30" style="display: none">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 <span class="cov0" title="0">{
return hs.Name
}</span>
// GetStyle implements Series.GetStyle.
func (hs HistogramSeries) GetStyle() Style <span class="cov0" title="0">{
return hs.Style
}</span>
// GetYAxis returns which yaxis the series is mapped to.
func (hs HistogramSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return hs.YAxis
}</span>
// Len implements BoundedValuesProvider.Len.
func (hs HistogramSeries) Len() int <span class="cov8" title="1">{
return hs.InnerSeries.Len()
}</span>
// GetValues implements ValuesProvider.GetValues.
func (hs HistogramSeries) GetValues(index int) (x, y float64) <span class="cov0" title="0">{
return hs.InnerSeries.GetValues(index)
}</span>
// GetBoundedValues implements BoundedValuesProvider.GetBoundedValue
func (hs HistogramSeries) GetBoundedValues(index int) (x, y1, y2 float64) <span class="cov8" title="1">{
vx, vy := hs.InnerSeries.GetValues(index)
x = vx
if vy &gt; 0 </span><span class="cov8" title="1">{
y1 = vy
return
}</span>
<span class="cov0" title="0">y2 = vy
return</span>
}
// Render implements Series.Render.
func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := hs.Style.InheritFrom(defaults)
Draw.HistogramSeries(r, canvasBox, xrange, yrange, style, hs)
}</span>
// Validate validates the series.
func (hs HistogramSeries) Validate() error <span class="cov0" title="0">{
if hs.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("histogram series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file31" style="display: none">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) <span class="cov0" title="0">{
if ir.contents == nil </span><span class="cov0" title="0">{
ir.contents = bytes.NewBuffer([]byte{})
}</span>
<span class="cov0" title="0">return ir.contents.Write(buffer)</span>
}
// SetRGBA sets a raw version of the image.
func (ir *ImageWriter) SetRGBA(i *image.RGBA) <span class="cov0" title="0">{
ir.rgba = i
}</span>
// Image returns an *image.Image for the result.
func (ir *ImageWriter) Image() (image.Image, error) <span class="cov0" title="0">{
if ir.rgba != nil </span><span class="cov0" title="0">{
return ir.rgba, nil
}</span>
<span class="cov0" title="0">if ir.contents != nil &amp;&amp; ir.contents.Len() &gt; 0 </span><span class="cov0" title="0">{
return png.Decode(ir.contents)
}</span>
<span class="cov0" title="0">return nil, errors.New("no valid sources for image data, cannot continue")</span>
}
</pre>
<pre class="file" id="file32" style="display: none">package chart
import "github.com/wcharczuk/go-chart/drawing"
// Jet is a color map provider based on matlab's jet color map.
func Jet(v, vmin, vmax float64) drawing.Color <span class="cov0" title="0">{
c := drawing.Color{R: 0xff, G: 0xff, B: 0xff, A: 0xff} // white
var dv float64
if v &lt; vmin </span><span class="cov0" title="0">{
v = vmin
}</span>
<span class="cov0" title="0">if v &gt; vmax </span><span class="cov0" title="0">{
v = vmax
}</span>
<span class="cov0" title="0">dv = vmax - vmin
if v &lt; (vmin + 0.25*dv) </span><span class="cov0" title="0">{
c.R = 0
c.G = drawing.ColorChannelFromFloat(4 * (v - vmin) / dv)
}</span> else<span class="cov0" title="0"> if v &lt; (vmin + 0.5*dv) </span><span class="cov0" title="0">{
c.R = 0
c.B = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.25*dv-v)/dv)
}</span> else<span class="cov0" title="0"> if v &lt; (vmin + 0.75*dv) </span><span class="cov0" title="0">{
c.R = drawing.ColorChannelFromFloat(4 * (v - vmin - 0.5*dv) / dv)
c.B = 0
}</span> else<span class="cov0" title="0"> {
c.G = drawing.ColorChannelFromFloat(1 + 4*(vmin+0.75*dv-v)/dv)
c.B = 0
}</span>
<span class="cov0" title="0">return c</span>
}
</pre>
<pre class="file" id="file33" style="display: none">package chart
import "fmt"
// LastValueAnnotation returns an annotation series of just the last value of a value provider.
func LastValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries <span class="cov8" title="1">{
var vf ValueFormatter
if len(vfs) &gt; 0 </span><span class="cov0" title="0">{
vf = vfs[0]
}</span> else<span class="cov8" title="1"> if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped </span><span class="cov8" title="1">{
_, vf = typed.GetValueFormatters()
}</span> else<span class="cov0" title="0"> {
vf = FloatValueFormatter
}</span>
<span class="cov8" title="1">var lastValue Value2
if typed, isTyped := innerSeries.(LastValuesProvider); isTyped </span><span class="cov8" title="1">{
lastValue.XValue, lastValue.YValue = typed.GetLastValues()
lastValue.Label = vf(lastValue.YValue)
}</span> else<span class="cov0" title="0"> {
lastValue.XValue, lastValue.YValue = innerSeries.GetValues(innerSeries.Len() - 1)
lastValue.Label = vf(lastValue.YValue)
}</span>
<span class="cov8" title="1">var seriesName string
var seriesStyle Style
if typed, isTyped := innerSeries.(Series); isTyped </span><span class="cov8" title="1">{
seriesName = fmt.Sprintf("%s - Last Value", typed.GetName())
seriesStyle = typed.GetStyle()
}</span>
<span class="cov8" title="1">return AnnotationSeries{
Name: seriesName,
Style: seriesStyle,
Annotations: []Value2{lastValue},
}</span>
}
</pre>
<pre class="file" id="file34" style="display: none">package chart
import (
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
)
// Legend returns a legend renderable function.
func Legend(c *Chart, userDefaults ...Style) Renderable <span class="cov8" title="1">{
return func(r Renderer, cb Box, chartDefaults Style) </span><span class="cov8" title="1">{
legendDefaults := Style{
FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor,
FontSize: 8.0,
StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth,
}
var legendStyle Style
if len(userDefaults) &gt; 0 </span><span class="cov0" title="0">{
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
}</span> else<span class="cov8" title="1"> {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}</span>
// DEFAULTS
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
if s.GetStyle().IsZero() || s.GetStyle().Show </span><span class="cov8" title="1">{
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries </span><span class="cov8" title="1">{
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}</span>
}
}
<span class="cov8" title="1">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 &lt; len(labels); x++ </span><span class="cov8" title="1">{
if len(labels[x]) &gt; 0 </span><span class="cov8" title="1">{
tb := r.MeasureText(labels[x])
if labelCount &gt; 0 </span><span class="cov0" title="0">{
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}</span>
<span class="cov8" title="1">legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
labelCount++</span>
}
}
<span class="cov8" title="1">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 &lt; len(labels); x++ </span><span class="cov8" title="1">{
label = labels[x]
if len(label) &gt; 0 </span><span class="cov8" title="1">{
if legendCount &gt; 0 </span><span class="cov0" title="0">{
ycursor += DefaultMinimumTickVerticalSpacing
}</span>
<span class="cov8" title="1">tb := r.MeasureText(label)
ty := ycursor + tb.Height()
r.Text(label, tx, ty)
th2 := tb.Height() &gt;&gt; 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++</span>
}
}
}
}
// LegendThin is a legend that doesn't obscure the chart area.
func LegendThin(c *Chart, userDefaults ...Style) Renderable <span class="cov0" title="0">{
return func(r Renderer, cb Box, chartDefaults Style) </span><span class="cov0" title="0">{
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) &gt; 0 </span><span class="cov0" title="0">{
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
}</span> else<span class="cov0" title="0"> {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}</span>
<span class="cov0" title="0">r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
var labels []string
var lines []Style
for index, s := range c.Series </span><span class="cov0" title="0">{
if s.GetStyle().IsZero() || s.GetStyle().Show </span><span class="cov0" title="0">{
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries </span><span class="cov0" title="0">{
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}</span>
}
}
<span class="cov0" title="0">var textHeight int
var textWidth int
var textBox Box
for x := 0; x &lt; len(labels); x++ </span><span class="cov0" title="0">{
if len(labels[x]) &gt; 0 </span><span class="cov0" title="0">{
textBox = r.MeasureText(labels[x])
textHeight = util.Math.MaxInt(textBox.Height(), textHeight)
textWidth = util.Math.MaxInt(textBox.Width(), textWidth)
}</span>
}
<span class="cov0" title="0">legendBoxHeight := textHeight + legendStyle.Padding.Top + legendStyle.Padding.Bottom
chartPadding := cb.Top
legendYMargin := (chartPadding - legendBoxHeight) &gt;&gt; 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 &gt;&gt; 1
for index := range labels </span><span class="cov0" title="0">{
label = labels[index]
if len(label) &gt; 0 </span><span class="cov0" title="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
}</span>
}
}
}
// LegendLeft is a legend that is designed for longer series lists.
func LegendLeft(c *Chart, userDefaults ...Style) Renderable <span class="cov0" title="0">{
return func(r Renderer, cb Box, chartDefaults Style) </span><span class="cov0" title="0">{
legendDefaults := Style{
FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor,
FontSize: 8.0,
StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth,
}
var legendStyle Style
if len(userDefaults) &gt; 0 </span><span class="cov0" title="0">{
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
}</span> else<span class="cov0" title="0"> {
legendStyle = chartDefaults.InheritFrom(legendDefaults)
}</span>
// DEFAULTS
<span class="cov0" title="0">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 </span><span class="cov0" title="0">{
if s.GetStyle().IsZero() || s.GetStyle().Show </span><span class="cov0" title="0">{
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries </span><span class="cov0" title="0">{
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
}</span>
}
}
<span class="cov0" title="0">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 &lt; len(labels); x++ </span><span class="cov0" title="0">{
if len(labels[x]) &gt; 0 </span><span class="cov0" title="0">{
tb := r.MeasureText(labels[x])
if labelCount &gt; 0 </span><span class="cov0" title="0">{
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}</span>
<span class="cov0" title="0">legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
labelCount++</span>
}
}
<span class="cov0" title="0">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 &lt; len(labels); x++ </span><span class="cov0" title="0">{
label = labels[x]
if len(label) &gt; 0 </span><span class="cov0" title="0">{
if legendCount &gt; 0 </span><span class="cov0" title="0">{
ycursor += DefaultMinimumTickVerticalSpacing
}</span>
<span class="cov0" title="0">tb := r.MeasureText(label)
ty := ycursor + tb.Height()
r.Text(label, tx, ty)
th2 := tb.Height() &gt;&gt; 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++</span>
}
}
}
}
</pre>
<pre class="file" id="file35" style="display: none">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 <span class="cov0" title="0">{
return LinearCoefficientSet{
M: m,
B: b,
}
}</span>
// NormalizedLinearCoefficients returns a fixed linear coefficient pair.
func NormalizedLinearCoefficients(m, b, stdev, avg float64) LinearCoefficientSet <span class="cov0" title="0">{
return LinearCoefficientSet{
M: m,
B: b,
StdDev: stdev,
Avg: avg,
}
}</span>
// 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) <span class="cov0" title="0">{
m = lcs.M
b = lcs.B
stdev = lcs.StdDev
avg = lcs.Avg
return
}</span>
</pre>
<pre class="file" id="file36" style="display: none">package chart
import (
"fmt"
"github.com/wcharczuk/go-chart/seq"
util "github.com/wcharczuk/go-chart/util"
)
// Interface Assertions.
var (
_ Series = (*LinearRegressionSeries)(nil)
_ FirstValuesProvider = (*LinearRegressionSeries)(nil)
_ LastValuesProvider = (*LinearRegressionSeries)(nil)
_ LinearCoefficientProvider = (*LinearRegressionSeries)(nil)
)
// LinearRegressionSeries is a series that plots the n-nearest neighbors
// 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) <span class="cov0" title="0">{
if lrs.IsZero() </span><span class="cov0" title="0">{
lrs.computeCoefficients()
}</span>
<span class="cov0" title="0">m = lrs.m
b = lrs.b
stdev = lrs.stddevx
avg = lrs.avgx
return</span>
}
// GetName returns the name of the time series.
func (lrs LinearRegressionSeries) GetName() string <span class="cov0" title="0">{
return lrs.Name
}</span>
// GetStyle returns the line style.
func (lrs LinearRegressionSeries) GetStyle() Style <span class="cov0" title="0">{
return lrs.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (lrs LinearRegressionSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return lrs.YAxis
}</span>
// Len returns the number of elements in the series.
func (lrs LinearRegressionSeries) Len() int <span class="cov8" title="1">{
return util.Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
}</span>
// GetLimit returns the window size.
func (lrs LinearRegressionSeries) GetLimit() int <span class="cov8" title="1">{
if lrs.Limit == 0 </span><span class="cov8" title="1">{
return lrs.InnerSeries.Len()
}</span>
<span class="cov8" title="1">return lrs.Limit</span>
}
// GetEndIndex returns the effective limit end.
func (lrs LinearRegressionSeries) GetEndIndex() int <span class="cov8" title="1">{
windowEnd := lrs.GetOffset() + lrs.GetLimit()
innerSeriesLastIndex := lrs.InnerSeries.Len() - 1
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
}</span>
// GetOffset returns the data offset.
func (lrs LinearRegressionSeries) GetOffset() int <span class="cov8" title="1">{
if lrs.Offset == 0 </span><span class="cov8" title="1">{
return 0
}</span>
<span class="cov8" title="1">return lrs.Offset</span>
}
// GetValues gets a value at a given index.
func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) <span class="cov8" title="1">{
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if lrs.IsZero() </span><span class="cov8" title="1">{
lrs.computeCoefficients()
}</span>
<span class="cov8" title="1">offset := lrs.GetOffset()
effectiveIndex := util.Math.MinInt(index+offset, lrs.InnerSeries.Len())
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return</span>
}
// GetFirstValues computes the first linear regression value.
func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if lrs.IsZero() </span><span class="cov0" title="0">{
lrs.computeCoefficients()
}</span>
<span class="cov0" title="0">x, y = lrs.InnerSeries.GetValues(0)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return</span>
}
// GetLastValues computes the last linear regression value.
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) <span class="cov8" title="1">{
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if lrs.IsZero() </span><span class="cov0" title="0">{
lrs.computeCoefficients()
}</span>
<span class="cov8" title="1">endIndex := lrs.GetEndIndex()
x, y = lrs.InnerSeries.GetValues(endIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return</span>
}
// Render renders the series.
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := lrs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
}</span>
// Validate validates the series.
func (lrs *LinearRegressionSeries) Validate() error <span class="cov0" title="0">{
if lrs.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
// IsZero returns if we've computed the coefficients or not.
func (lrs *LinearRegressionSeries) IsZero() bool <span class="cov8" title="1">{
return lrs.m == 0 &amp;&amp; lrs.b == 0
}</span>
//
// internal helpers
//
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 <span class="cov8" title="1">{
return (xvalue - lrs.avgx) / lrs.stddevx
}</span>
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
func (lrs *LinearRegressionSeries) computeCoefficients() <span class="cov8" title="1">{
startIndex := lrs.GetOffset()
endIndex := lrs.GetEndIndex()
p := float64(endIndex - startIndex)
xvalues := seq.NewBufferWithCapacity(lrs.Len())
for index := startIndex; index &lt; endIndex; index++ </span><span class="cov8" title="1">{
x, _ := lrs.InnerSeries.GetValues(index)
xvalues.Enqueue(x)
}</span>
<span class="cov8" title="1">lrs.avgx = seq.Seq{Provider: xvalues}.Average()
lrs.stddevx = seq.Seq{Provider: xvalues}.StdDev()
var sumx, sumy, sumxx, sumxy float64
for index := startIndex; index &lt; endIndex; index++ </span><span class="cov8" title="1">{
x, y := lrs.InnerSeries.GetValues(index)
x = lrs.normalize(x)
sumx += x
sumy += y
sumxx += x * x
sumxy += x * y
}</span>
<span class="cov8" title="1">lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
lrs.b = (sumy / p) - (lrs.m * sumx / p)</span>
}
</pre>
<pre class="file" id="file37" style="display: none">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 <span class="cov0" title="0">{
return ls.Name
}</span>
// GetStyle returns the line style.
func (ls LinearSeries) GetStyle() Style <span class="cov0" title="0">{
return ls.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (ls LinearSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return ls.YAxis
}</span>
// Len returns the number of elements in the series.
func (ls LinearSeries) Len() int <span class="cov0" title="0">{
return len(ls.XValues)
}</span>
// GetEndIndex returns the effective limit end.
func (ls LinearSeries) GetEndIndex() int <span class="cov0" title="0">{
return len(ls.XValues) - 1
}</span>
// GetValues gets a value at a given index.
func (ls *LinearSeries) GetValues(index int) (x, y float64) <span class="cov0" title="0">{
if ls.InnerSeries == nil || len(ls.XValues) == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if ls.IsZero() </span><span class="cov0" title="0">{
ls.computeCoefficients()
}</span>
<span class="cov0" title="0">x = ls.XValues[index]
y = (ls.m * ls.normalize(x)) + ls.b
return</span>
}
// GetFirstValues computes the first linear regression value.
func (ls *LinearSeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
if ls.InnerSeries == nil || len(ls.XValues) == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if ls.IsZero() </span><span class="cov0" title="0">{
ls.computeCoefficients()
}</span>
<span class="cov0" title="0">x, y = ls.GetValues(0)
return</span>
}
// GetLastValues computes the last linear regression value.
func (ls *LinearSeries) GetLastValues() (x, y float64) <span class="cov0" title="0">{
if ls.InnerSeries == nil || len(ls.XValues) == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if ls.IsZero() </span><span class="cov0" title="0">{
ls.computeCoefficients()
}</span>
<span class="cov0" title="0">x, y = ls.GetValues(ls.GetEndIndex())
return</span>
}
// Render renders the series.
func (ls *LinearSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
Draw.LineSeries(r, canvasBox, xrange, yrange, ls.Style.InheritFrom(defaults), ls)
}</span>
// Validate validates the series.
func (ls LinearSeries) Validate() error <span class="cov0" title="0">{
if ls.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
// IsZero returns if the linear series has computed coefficients or not.
func (ls LinearSeries) IsZero() bool <span class="cov0" title="0">{
return ls.m == 0 &amp;&amp; ls.b == 0
}</span>
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
func (ls *LinearSeries) computeCoefficients() <span class="cov0" title="0">{
ls.m, ls.b, ls.stdev, ls.avg = ls.InnerSeries.Coefficients()
}</span>
func (ls *LinearSeries) normalize(xvalue float64) float64 <span class="cov0" title="0">{
if ls.avg &gt; 0 &amp;&amp; ls.stdev &gt; 0 </span><span class="cov0" title="0">{
return (xvalue - ls.avg) / ls.stdev
}</span>
<span class="cov0" title="0">return xvalue</span>
}
</pre>
<pre class="file" id="file38" style="display: none">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 <span class="cov0" title="0">{
var err error
if macd.signal != nil </span><span class="cov0" title="0">{
err = macd.signal.Validate()
}</span>
<span class="cov0" title="0">if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">if macd.macdl != nil </span><span class="cov0" title="0">{
err = macd.macdl.Validate()
}</span>
<span class="cov0" title="0">if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">return nil</span>
}
// GetPeriods returns the primary and secondary periods.
func (macd MACDSeries) GetPeriods() (w1, w2, sig int) <span class="cov8" title="1">{
if macd.PrimaryPeriod == 0 </span><span class="cov8" title="1">{
w1 = DefaultMACDPeriodPrimary
}</span> else<span class="cov0" title="0"> {
w1 = macd.PrimaryPeriod
}</span>
<span class="cov8" title="1">if macd.SecondaryPeriod == 0 </span><span class="cov8" title="1">{
w2 = DefaultMACDPeriodSecondary
}</span> else<span class="cov0" title="0"> {
w2 = macd.SecondaryPeriod
}</span>
<span class="cov8" title="1">if macd.SignalPeriod == 0 </span><span class="cov8" title="1">{
sig = DefaultMACDSignalPeriod
}</span> else<span class="cov0" title="0"> {
sig = macd.SignalPeriod
}</span>
<span class="cov8" title="1">return</span>
}
// GetName returns the name of the time series.
func (macd MACDSeries) GetName() string <span class="cov0" title="0">{
return macd.Name
}</span>
// GetStyle returns the line style.
func (macd MACDSeries) GetStyle() Style <span class="cov0" title="0">{
return macd.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (macd MACDSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return macd.YAxis
}</span>
// Len returns the number of elements in the series.
func (macd MACDSeries) Len() int <span class="cov8" title="1">{
if macd.InnerSeries == nil </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">return macd.InnerSeries.Len()</span>
}
// 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) <span class="cov8" title="1">{
if macd.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if macd.signal == nil || macd.macdl == nil </span><span class="cov8" title="1">{
macd.ensureChildSeries()
}</span>
<span class="cov8" title="1">_, lv := macd.macdl.GetValues(index)
_, sv := macd.signal.GetValues(index)
x, _ = macd.InnerSeries.GetValues(index)
y = lv - sv
return</span>
}
func (macd *MACDSeries) ensureChildSeries() <span class="cov8" title="1">{
w1, w2, sig := macd.GetPeriods()
macd.signal = &amp;MACDSignalSeries{
InnerSeries: macd.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
SignalPeriod: sig,
}
macd.macdl = &amp;MACDLineSeries{
InnerSeries: macd.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
}
}</span>
// 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 <span class="cov0" title="0">{
if macds.signal != nil </span><span class="cov0" title="0">{
return macds.signal.Validate()
}</span>
<span class="cov0" title="0">return nil</span>
}
// GetPeriods returns the primary and secondary periods.
func (macds MACDSignalSeries) GetPeriods() (w1, w2, sig int) <span class="cov8" title="1">{
if macds.PrimaryPeriod == 0 </span><span class="cov0" title="0">{
w1 = DefaultMACDPeriodPrimary
}</span> else<span class="cov8" title="1"> {
w1 = macds.PrimaryPeriod
}</span>
<span class="cov8" title="1">if macds.SecondaryPeriod == 0 </span><span class="cov0" title="0">{
w2 = DefaultMACDPeriodSecondary
}</span> else<span class="cov8" title="1"> {
w2 = macds.SecondaryPeriod
}</span>
<span class="cov8" title="1">if macds.SignalPeriod == 0 </span><span class="cov0" title="0">{
sig = DefaultMACDSignalPeriod
}</span> else<span class="cov8" title="1"> {
sig = macds.SignalPeriod
}</span>
<span class="cov8" title="1">return</span>
}
// GetName returns the name of the time series.
func (macds MACDSignalSeries) GetName() string <span class="cov0" title="0">{
return macds.Name
}</span>
// GetStyle returns the line style.
func (macds MACDSignalSeries) GetStyle() Style <span class="cov0" title="0">{
return macds.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (macds MACDSignalSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return macds.YAxis
}</span>
// Len returns the number of elements in the series.
func (macds *MACDSignalSeries) Len() int <span class="cov0" title="0">{
if macds.InnerSeries == nil </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">return macds.InnerSeries.Len()</span>
}
// 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) <span class="cov8" title="1">{
if macds.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if macds.signal == nil </span><span class="cov8" title="1">{
macds.ensureSignal()
}</span>
<span class="cov8" title="1">x, _ = macds.InnerSeries.GetValues(index)
_, y = macds.signal.GetValues(index)
return</span>
}
func (macds *MACDSignalSeries) ensureSignal() <span class="cov8" title="1">{
w1, w2, sig := macds.GetPeriods()
macds.signal = &amp;EMASeries{
InnerSeries: &amp;MACDLineSeries{
InnerSeries: macds.InnerSeries,
PrimaryPeriod: w1,
SecondaryPeriod: w2,
},
Period: sig,
}
}</span>
// Render renders the series.
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := macds.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macds)
}</span>
// 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 <span class="cov0" title="0">{
var err error
if macdl.ema1 != nil </span><span class="cov0" title="0">{
err = macdl.ema1.Validate()
}</span>
<span class="cov0" title="0">if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">if macdl.ema2 != nil </span><span class="cov0" title="0">{
err = macdl.ema2.Validate()
}</span>
<span class="cov0" title="0">if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">if macdl.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("MACDLineSeries: must provide an inner series")
}</span>
<span class="cov0" title="0">return nil</span>
}
// GetName returns the name of the time series.
func (macdl MACDLineSeries) GetName() string <span class="cov0" title="0">{
return macdl.Name
}</span>
// GetStyle returns the line style.
func (macdl MACDLineSeries) GetStyle() Style <span class="cov0" title="0">{
return macdl.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (macdl MACDLineSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return macdl.YAxis
}</span>
// GetPeriods returns the primary and secondary periods.
func (macdl MACDLineSeries) GetPeriods() (w1, w2 int) <span class="cov8" title="1">{
if macdl.PrimaryPeriod == 0 </span><span class="cov0" title="0">{
w1 = DefaultMACDPeriodPrimary
}</span> else<span class="cov8" title="1"> {
w1 = macdl.PrimaryPeriod
}</span>
<span class="cov8" title="1">if macdl.SecondaryPeriod == 0 </span><span class="cov0" title="0">{
w2 = DefaultMACDPeriodSecondary
}</span> else<span class="cov8" title="1"> {
w2 = macdl.SecondaryPeriod
}</span>
<span class="cov8" title="1">return</span>
}
// Len returns the number of elements in the series.
func (macdl *MACDLineSeries) Len() int <span class="cov8" title="1">{
if macdl.InnerSeries == nil </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">return macdl.InnerSeries.Len()</span>
}
// 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) <span class="cov8" title="1">{
if macdl.InnerSeries == nil </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if macdl.ema1 == nil &amp;&amp; macdl.ema2 == nil </span><span class="cov8" title="1">{
macdl.ensureEMASeries()
}</span>
<span class="cov8" title="1">x, _ = macdl.InnerSeries.GetValues(index)
_, emav1 := macdl.ema1.GetValues(index)
_, emav2 := macdl.ema2.GetValues(index)
y = emav2 - emav1
return</span>
}
func (macdl *MACDLineSeries) ensureEMASeries() <span class="cov8" title="1">{
w1, w2 := macdl.GetPeriods()
macdl.ema1 = &amp;EMASeries{
InnerSeries: macdl.InnerSeries,
Period: w1,
}
macdl.ema2 = &amp;EMASeries{
InnerSeries: macdl.InnerSeries,
Period: w2,
}
}</span>
// Render renders the series.
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := macdl.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl)
}</span>
</pre>
<pre class="file" id="file39" style="display: none">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 <span class="cov8" title="1">{
if len(values) == 0 </span><span class="cov8" title="1">{
return &amp;Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: make([]float64, rows*cols),
}
}</span>
<span class="cov8" title="1">elems := make([]float64, rows*cols)
copy(elems, values)
return &amp;Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: elems,
}</span>
}
// Identity returns the identity matrix of a given order.
func Identity(order int) *Matrix <span class="cov8" title="1">{
m := New(order, order)
for i := 0; i &lt; order; i++ </span><span class="cov8" title="1">{
m.Set(i, i, 1)
}</span>
<span class="cov8" title="1">return m</span>
}
// Zero returns a matrix of a given size zeroed.
func Zero(rows, cols int) *Matrix <span class="cov8" title="1">{
return New(rows, cols)
}</span>
// Ones returns an matrix of ones.
func Ones(rows, cols int) *Matrix <span class="cov8" title="1">{
ones := make([]float64, rows*cols)
for i := 0; i &lt; (rows * cols); i++ </span><span class="cov8" title="1">{
ones[i] = 1
}</span>
<span class="cov8" title="1">return &amp;Matrix{
stride: cols,
epsilon: DefaultEpsilon,
elements: ones,
}</span>
}
// Eye returns the eye matrix.
func Eye(n int) *Matrix <span class="cov0" title="0">{
m := Zero(n, n)
for i := 0; i &lt; len(m.elements); i += n + 1 </span><span class="cov0" title="0">{
m.elements[i] = 1
}</span>
<span class="cov0" title="0">return m</span>
}
// NewFromArrays creates a matrix from a jagged array set.
func NewFromArrays(a [][]float64) *Matrix <span class="cov8" title="1">{
rows := len(a)
if rows == 0 </span><span class="cov0" title="0">{
return nil
}</span>
<span class="cov8" title="1">cols := len(a[0])
m := New(rows, cols)
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
for col := 0; col &lt; cols; col++ </span><span class="cov8" title="1">{
m.Set(row, col, a[row][col])
}</span>
}
<span class="cov8" title="1">return m</span>
}
// 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 <span class="cov8" title="1">{
buffer := bytes.NewBuffer(nil)
rows, cols := m.Size()
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
for col := 0; col &lt; cols; col++ </span><span class="cov8" title="1">{
buffer.WriteString(f64s(m.Get(row, col)))
buffer.WriteRune(' ')
}</span>
<span class="cov8" title="1">buffer.WriteRune('\n')</span>
}
<span class="cov8" title="1">return buffer.String()</span>
}
// Epsilon returns the maximum precision for math operations.
func (m *Matrix) Epsilon() float64 <span class="cov8" title="1">{
return m.epsilon
}</span>
// WithEpsilon sets the epsilon on the matrix and returns a reference to the matrix.
func (m *Matrix) WithEpsilon(epsilon float64) *Matrix <span class="cov8" title="1">{
m.epsilon = epsilon
return m
}</span>
// Each applies the action to each element of the matrix in
// rows =&gt; cols order.
func (m *Matrix) Each(action func(row, col int, value float64)) <span class="cov0" title="0">{
rows, cols := m.Size()
for row := 0; row &lt; rows; row++ </span><span class="cov0" title="0">{
for col := 0; col &lt; cols; col++ </span><span class="cov0" title="0">{
action(row, col, m.Get(row, col))
}</span>
}
}
// Round rounds all the values in a matrix to it epsilon,
// returning a reference to the original
func (m *Matrix) Round() *Matrix <span class="cov8" title="1">{
rows, cols := m.Size()
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
for col := 0; col &lt; cols; col++ </span><span class="cov8" title="1">{
m.Set(row, col, roundToEpsilon(m.Get(row, col), m.epsilon))
}</span>
}
<span class="cov8" title="1">return m</span>
}
// Arrays returns the matrix as a two dimensional jagged array.
func (m *Matrix) Arrays() [][]float64 <span class="cov8" title="1">{
rows, cols := m.Size()
a := make([][]float64, rows)
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
a[row] = make([]float64, cols)
for col := 0; col &lt; cols; col++ </span><span class="cov8" title="1">{
a[row][col] = m.Get(row, col)
}</span>
}
<span class="cov8" title="1">return a</span>
}
// Size returns the dimensions of the matrix.
func (m *Matrix) Size() (rows, cols int) <span class="cov8" title="1">{
rows = len(m.elements) / m.stride
cols = m.stride
return
}</span>
// IsSquare returns if the row count is equal to the column count.
func (m *Matrix) IsSquare() bool <span class="cov8" title="1">{
return m.stride == (len(m.elements) / m.stride)
}</span>
// IsSymmetric returns if the matrix is symmetric about its diagonal.
func (m *Matrix) IsSymmetric() bool <span class="cov8" title="1">{
rows, cols := m.Size()
if rows != cols </span><span class="cov8" title="1">{
return false
}</span>
<span class="cov8" title="1">for i := 0; i &lt; rows; i++ </span><span class="cov8" title="1">{
for j := 0; j &lt; i; j++ </span><span class="cov8" title="1">{
if m.Get(i, j) != m.Get(j, i) </span><span class="cov8" title="1">{
return false
}</span>
}
}
<span class="cov8" title="1">return true</span>
}
// Get returns the element at the given row, col.
func (m *Matrix) Get(row, col int) float64 <span class="cov8" title="1">{
index := (m.stride * row) + col
return m.elements[index]
}</span>
// Set sets a value.
func (m *Matrix) Set(row, col int, val float64) <span class="cov8" title="1">{
index := (m.stride * row) + col
m.elements[index] = val
}</span>
// Col returns a column of the matrix as a vector.
func (m *Matrix) Col(col int) Vector <span class="cov8" title="1">{
rows, _ := m.Size()
values := make([]float64, rows)
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
values[row] = m.Get(row, col)
}</span>
<span class="cov8" title="1">return Vector(values)</span>
}
// Row returns a row of the matrix as a vector.
func (m *Matrix) Row(row int) Vector <span class="cov8" title="1">{
_, cols := m.Size()
values := make([]float64, cols)
for col := 0; col &lt; cols; col++ </span><span class="cov8" title="1">{
values[col] = m.Get(row, col)
}</span>
<span class="cov8" title="1">return Vector(values)</span>
}
// SubMatrix returns a sub matrix from a given outer matrix.
func (m *Matrix) SubMatrix(i, j, rows, cols int) *Matrix <span class="cov0" title="0">{
return &amp;Matrix{
elements: m.elements[i*m.stride+j : i*m.stride+j+(rows-1)*m.stride+cols],
stride: m.stride,
epsilon: m.epsilon,
}
}</span>
// ScaleRow applies a scale to an entire row.
func (m *Matrix) ScaleRow(row int, scale float64) <span class="cov0" title="0">{
startIndex := row * m.stride
for i := startIndex; i &lt; m.stride; i++ </span><span class="cov0" title="0">{
m.elements[i] = m.elements[i] * scale
}</span>
}
func (m *Matrix) scaleAddRow(rd int, rs int, f float64) <span class="cov0" title="0">{
indexd := rd * m.stride
indexs := rs * m.stride
for col := 0; col &lt; m.stride; col++ </span><span class="cov0" title="0">{
m.elements[indexd] += f * m.elements[indexs]
indexd++
indexs++
}</span>
}
// SwapRows swaps a row in the matrix in place.
func (m *Matrix) SwapRows(i, j int) <span class="cov8" title="1">{
var vi, vj float64
for col := 0; col &lt; m.stride; col++ </span><span class="cov8" title="1">{
vi = m.Get(i, col)
vj = m.Get(j, col)
m.Set(i, col, vj)
m.Set(j, col, vi)
}</span>
}
// Augment concatenates two matrices about the horizontal.
func (m *Matrix) Augment(m2 *Matrix) (*Matrix, error) <span class="cov0" title="0">{
mr, mc := m.Size()
m2r, m2c := m2.Size()
if mr != m2r </span><span class="cov0" title="0">{
return nil, ErrDimensionMismatch
}</span>
<span class="cov0" title="0">m3 := Zero(mr, mc+m2c)
for row := 0; row &lt; mr; row++ </span><span class="cov0" title="0">{
for col := 0; col &lt; mc; col++ </span><span class="cov0" title="0">{
m3.Set(row, col, m.Get(row, col))
}</span>
<span class="cov0" title="0">for col := 0; col &lt; m2c; col++ </span><span class="cov0" title="0">{
m3.Set(row, mc+col, m2.Get(row, col))
}</span>
}
<span class="cov0" title="0">return m3, nil</span>
}
// Copy returns a duplicate of a given matrix.
func (m *Matrix) Copy() *Matrix <span class="cov8" title="1">{
m2 := &amp;Matrix{stride: m.stride, epsilon: m.epsilon, elements: make([]float64, len(m.elements))}
copy(m2.elements, m.elements)
return m2
}</span>
// DiagonalVector returns a vector from the diagonal of a matrix.
func (m *Matrix) DiagonalVector() Vector <span class="cov8" title="1">{
rows, cols := m.Size()
rank := minInt(rows, cols)
values := make([]float64, rank)
for index := 0; index &lt; rank; index++ </span><span class="cov8" title="1">{
values[index] = m.Get(index, index)
}</span>
<span class="cov8" title="1">return Vector(values)</span>
}
// Diagonal returns a matrix from the diagonal of a matrix.
func (m *Matrix) Diagonal() *Matrix <span class="cov8" title="1">{
rows, cols := m.Size()
rank := minInt(rows, cols)
m2 := New(rank, rank)
for index := 0; index &lt; rank; index++ </span><span class="cov8" title="1">{
m2.Set(index, index, m.Get(index, index))
}</span>
<span class="cov8" title="1">return m2</span>
}
// Equals returns if a matrix equals another matrix.
func (m *Matrix) Equals(other *Matrix) bool <span class="cov8" title="1">{
if other == nil &amp;&amp; m != nil </span><span class="cov8" title="1">{
return false
}</span> else<span class="cov8" title="1"> if other == nil </span><span class="cov8" title="1">{
return true
}</span>
<span class="cov8" title="1">if m.stride != other.stride </span><span class="cov8" title="1">{
return false
}</span>
<span class="cov8" title="1">msize := len(m.elements)
m2size := len(other.elements)
if msize != m2size </span><span class="cov0" title="0">{
return false
}</span>
<span class="cov8" title="1">for i := 0; i &lt; msize; i++ </span><span class="cov8" title="1">{
if m.elements[i] != other.elements[i] </span><span class="cov8" title="1">{
return false
}</span>
}
<span class="cov8" title="1">return true</span>
}
// L returns the matrix with zeros below the diagonal.
func (m *Matrix) L() *Matrix <span class="cov8" title="1">{
rows, cols := m.Size()
m2 := New(rows, cols)
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
for col := row; col &lt; cols; col++ </span><span class="cov8" title="1">{
m2.Set(row, col, m.Get(row, col))
}</span>
}
<span class="cov8" title="1">return m2</span>
}
// U returns the matrix with zeros above the diagonal.
// Does not include the diagonal.
func (m *Matrix) U() *Matrix <span class="cov8" title="1">{
rows, cols := m.Size()
m2 := New(rows, cols)
for row := 0; row &lt; rows; row++ </span><span class="cov8" title="1">{
for col := 0; col &lt; row &amp;&amp; col &lt; cols; col++ </span><span class="cov8" title="1">{
m2.Set(row, col, m.Get(row, col))
}</span>
}
<span class="cov8" title="1">return m2</span>
}
// math operations
// Multiply multiplies two matrices.
func (m *Matrix) Multiply(m2 *Matrix) (m3 *Matrix, err error) <span class="cov8" title="1">{
if m.stride*m2.stride != len(m2.elements) </span><span class="cov0" title="0">{
return nil, ErrDimensionMismatch
}</span>
<span class="cov8" title="1">m3 = &amp;Matrix{epsilon: m.epsilon, stride: m2.stride, elements: make([]float64, (len(m.elements)/m.stride)*m2.stride)}
for m1c0, m3x := 0, 0; m1c0 &lt; len(m.elements); m1c0 += m.stride </span><span class="cov8" title="1">{
for m2r0 := 0; m2r0 &lt; m2.stride; m2r0++ </span><span class="cov8" title="1">{
for m1x, m2x := m1c0, m2r0; m2x &lt; len(m2.elements); m2x += m2.stride </span><span class="cov8" title="1">{
m3.elements[m3x] += m.elements[m1x] * m2.elements[m2x]
m1x++
}</span>
<span class="cov8" title="1">m3x++</span>
}
}
<span class="cov8" title="1">return</span>
}
// Pivotize does something i'm not sure what.
func (m *Matrix) Pivotize() *Matrix <span class="cov8" title="1">{
pv := make([]int, m.stride)
for i := range pv </span><span class="cov8" title="1">{
pv[i] = i
}</span>
<span class="cov8" title="1">for j, dx := 0, 0; j &lt; m.stride; j++ </span><span class="cov8" title="1">{
row := j
max := m.elements[dx]
for i, ixcj := j, dx; i &lt; m.stride; i++ </span><span class="cov8" title="1">{
if m.elements[ixcj] &gt; max </span><span class="cov8" title="1">{
max = m.elements[ixcj]
row = i
}</span>
<span class="cov8" title="1">ixcj += m.stride</span>
}
<span class="cov8" title="1">if j != row </span><span class="cov8" title="1">{
pv[row], pv[j] = pv[j], pv[row]
}</span>
<span class="cov8" title="1">dx += m.stride + 1</span>
}
<span class="cov8" title="1">p := Zero(m.stride, m.stride)
for r, c := range pv </span><span class="cov8" title="1">{
p.elements[r*m.stride+c] = 1
}</span>
<span class="cov8" title="1">return p</span>
}
// Times returns the product of a matrix and another.
func (m *Matrix) Times(m2 *Matrix) (*Matrix, error) <span class="cov8" title="1">{
mr, mc := m.Size()
m2r, m2c := m2.Size()
if mc != m2r </span><span class="cov0" title="0">{
return nil, fmt.Errorf("cannot multiply (%dx%d) and (%dx%d)", mr, mc, m2r, m2c)
//return nil, ErrDimensionMismatch
}</span>
<span class="cov8" title="1">c := Zero(mr, m2c)
for i := 0; i &lt; mr; i++ </span><span class="cov8" title="1">{
sums := c.elements[i*c.stride : (i+1)*c.stride]
for k, a := range m.elements[i*m.stride : i*m.stride+m.stride] </span><span class="cov8" title="1">{
for j, b := range m2.elements[k*m2.stride : k*m2.stride+m2.stride] </span><span class="cov8" title="1">{
sums[j] += a * b
}</span>
}
}
<span class="cov8" title="1">return c, nil</span>
}
// Decompositions
// LU performs the LU decomposition.
func (m *Matrix) LU() (l, u, p *Matrix) <span class="cov8" title="1">{
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 &lt; m.stride; j++ </span><span class="cov8" title="1">{
l.elements[jxc0+j] = 1
for i, ixc0 := 0, 0; ixc0 &lt;= jxc0; i++ </span><span class="cov8" title="1">{
sum := 0.
for k, kxcj := 0, j; k &lt; i; k++ </span><span class="cov8" title="1">{
sum += u.elements[kxcj] * l.elements[ixc0+k]
kxcj += m.stride
}</span>
<span class="cov8" title="1">u.elements[ixc0+j] = m.elements[ixc0+j] - sum
ixc0 += m.stride</span>
}
<span class="cov8" title="1">for ixc0 := jxc0; ixc0 &lt; len(m.elements); ixc0 += m.stride </span><span class="cov8" title="1">{
sum := 0.
for k, kxcj := 0, j; k &lt; j; k++ </span><span class="cov8" title="1">{
sum += u.elements[kxcj] * l.elements[ixc0+k]
kxcj += m.stride
}</span>
<span class="cov8" title="1">l.elements[ixc0+j] = (m.elements[ixc0+j] - sum) / u.elements[jxc0+j]</span>
}
<span class="cov8" title="1">jxc0 += m.stride</span>
}
<span class="cov8" title="1">return</span>
}
// QR performs the qr decomposition.
func (m *Matrix) QR() (q, r *Matrix) <span class="cov8" title="1">{
defer func() </span><span class="cov8" title="1">{
q = q.Round()
r = r.Round()
}</span>()
<span class="cov8" title="1">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 &lt; cols; k++ </span><span class="cov8" title="1">{
norm = 0
for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
norm = math.Hypot(norm, qr.Get(i, k))
}</span>
<span class="cov8" title="1">if norm != 0 </span><span class="cov8" title="1">{
if qr.Get(k, k) &lt; 0 </span><span class="cov8" title="1">{
norm = -norm
}</span>
<span class="cov8" title="1">for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
qr.Set(i, k, qr.Get(i, k)/norm)
}</span>
<span class="cov8" title="1">qr.Set(k, k, qr.Get(k, k)+1.0)
for j = k + 1; j &lt; cols; j++ </span><span class="cov8" title="1">{
s = 0
for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
s += qr.Get(i, k) * qr.Get(i, j)
}</span>
<span class="cov8" title="1">s = -s / qr.Get(k, k)
for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
qr.Set(i, j, qr.Get(i, j)+s*qr.Get(i, k))
if i &lt; j </span><span class="cov8" title="1">{
r.Set(i, j, qr.Get(i, j))
}</span>
}
}
}
<span class="cov8" title="1">r.Set(k, k, -norm)</span>
}
//Q Matrix:
<span class="cov8" title="1">i, j, k = 0, 0, 0
for k = cols - 1; k &gt;= 0; k-- </span><span class="cov8" title="1">{
q.Set(k, k, 1.0)
for j = k; j &lt; cols; j++ </span><span class="cov8" title="1">{
if qr.Get(k, k) != 0 </span><span class="cov8" title="1">{
s = 0
for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
s += qr.Get(i, k) * q.Get(i, j)
}</span>
<span class="cov8" title="1">s = -s / qr.Get(k, k)
for i = k; i &lt; rows; i++ </span><span class="cov8" title="1">{
q.Set(i, j, q.Get(i, j)+s*qr.Get(i, k))
}</span>
}
}
}
<span class="cov8" title="1">return</span>
}
// Transpose flips a matrix about its diagonal, returning a new copy.
func (m *Matrix) Transpose() *Matrix <span class="cov8" title="1">{
rows, cols := m.Size()
m2 := Zero(cols, rows)
for i := 0; i &lt; rows; i++ </span><span class="cov8" title="1">{
for j := 0; j &lt; cols; j++ </span><span class="cov8" title="1">{
m2.Set(j, i, m.Get(i, j))
}</span>
}
<span class="cov8" title="1">return m2</span>
}
// Inverse returns a matrix such that M*I==1.
func (m *Matrix) Inverse() (*Matrix, error) <span class="cov0" title="0">{
if !m.IsSymmetric() </span><span class="cov0" title="0">{
return nil, ErrDimensionMismatch
}</span>
<span class="cov0" title="0">rows, cols := m.Size()
aug, _ := m.Augment(Eye(rows))
for i := 0; i &lt; rows; i++ </span><span class="cov0" title="0">{
j := i
for k := i; k &lt; rows; k++ </span><span class="cov0" title="0">{
if math.Abs(aug.Get(k, i)) &gt; math.Abs(aug.Get(j, i)) </span><span class="cov0" title="0">{
j = k
}</span>
}
<span class="cov0" title="0">if j != i </span><span class="cov0" title="0">{
aug.SwapRows(i, j)
}</span>
<span class="cov0" title="0">if aug.Get(i, i) == 0 </span><span class="cov0" title="0">{
return nil, ErrSingularValue
}</span>
<span class="cov0" title="0">aug.ScaleRow(i, 1.0/aug.Get(i, i))
for k := 0; k &lt; rows; k++ </span><span class="cov0" title="0">{
if k == i </span><span class="cov0" title="0">{
continue</span>
}
<span class="cov0" title="0">aug.scaleAddRow(k, i, -aug.Get(k, i))</span>
}
}
<span class="cov0" title="0">return aug.SubMatrix(0, cols, rows, cols), nil</span>
}
</pre>
<pre class="file" id="file40" style="display: none">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) <span class="cov8" title="1">{
if len(xvalues) != len(yvalues) </span><span class="cov0" title="0">{
return nil, ErrPolyRegArraysSameLength
}</span>
<span class="cov8" title="1">m := len(yvalues)
n := degree + 1
y := New(m, 1, yvalues...)
x := Zero(m, n)
for i := 0; i &lt; m; i++ </span><span class="cov8" title="1">{
ip := float64(1)
for j := 0; j &lt; n; j++ </span><span class="cov8" title="1">{
x.Set(i, j, ip)
ip *= xvalues[i]
}</span>
}
<span class="cov8" title="1">q, r := x.QR()
qty, err := q.Transpose().Times(y)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">c := make([]float64, n)
for i := n - 1; i &gt;= 0; i-- </span><span class="cov8" title="1">{
c[i] = qty.Get(i, 0)
for j := i + 1; j &lt; n; j++ </span><span class="cov8" title="1">{
c[i] -= c[j] * r.Get(i, j)
}</span>
<span class="cov8" title="1">c[i] /= r.Get(i, i)</span>
}
<span class="cov8" title="1">return c, nil</span>
}
</pre>
<pre class="file" id="file41" style="display: none">package matrix
import (
"math"
"strconv"
)
func minInt(values ...int) int <span class="cov8" title="1">{
min := math.MaxInt32
for x := 0; x &lt; len(values); x++ </span><span class="cov8" title="1">{
if values[x] &lt; min </span><span class="cov8" title="1">{
min = values[x]
}</span>
}
<span class="cov8" title="1">return min</span>
}
func maxInt(values ...int) int <span class="cov0" title="0">{
max := math.MinInt32
for x := 0; x &lt; len(values); x++ </span><span class="cov0" title="0">{
if values[x] &gt; max </span><span class="cov0" title="0">{
max = values[x]
}</span>
}
<span class="cov0" title="0">return max</span>
}
func f64s(v float64) string <span class="cov8" title="1">{
return strconv.FormatFloat(v, 'f', -1, 64)
}</span>
func roundToEpsilon(value, epsilon float64) float64 <span class="cov8" title="1">{
return math.Nextafter(value, value)
}</span>
</pre>
<pre class="file" id="file42" style="display: none">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) <span class="cov0" title="0">{
if len(v) != len(v2) </span><span class="cov0" title="0">{
err = ErrDimensionMismatch
return
}</span>
<span class="cov0" title="0">for i := 0; i &lt; len(v); i++ </span><span class="cov0" title="0">{
result = result + (v[i] * v2[i])
}</span>
<span class="cov0" title="0">return</span>
}
</pre>
<pre class="file" id="file43" style="display: none">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 <span class="cov0" title="0">{
return ms.Name
}</span>
// GetStyle returns the line style.
func (ms MinSeries) GetStyle() Style <span class="cov0" title="0">{
return ms.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (ms MinSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return ms.YAxis
}</span>
// Len returns the number of elements in the series.
func (ms MinSeries) Len() int <span class="cov0" title="0">{
return ms.InnerSeries.Len()
}</span>
// GetValues gets a value at a given index.
func (ms *MinSeries) GetValues(index int) (x, y float64) <span class="cov0" title="0">{
ms.ensureMinValue()
x, _ = ms.InnerSeries.GetValues(index)
y = *ms.minValue
return
}</span>
// Render renders the series.
func (ms *MinSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := ms.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms)
}</span>
func (ms *MinSeries) ensureMinValue() <span class="cov0" title="0">{
if ms.minValue == nil </span><span class="cov0" title="0">{
minValue := math.MaxFloat64
var y float64
for x := 0; x &lt; ms.InnerSeries.Len(); x++ </span><span class="cov0" title="0">{
_, y = ms.InnerSeries.GetValues(x)
if y &lt; minValue </span><span class="cov0" title="0">{
minValue = y
}</span>
}
<span class="cov0" title="0">ms.minValue = &amp;minValue</span>
}
}
// Validate validates the series.
func (ms *MinSeries) Validate() error <span class="cov0" title="0">{
if ms.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("min series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
// 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 <span class="cov0" title="0">{
return ms.Name
}</span>
// GetStyle returns the line style.
func (ms MaxSeries) GetStyle() Style <span class="cov0" title="0">{
return ms.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (ms MaxSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return ms.YAxis
}</span>
// Len returns the number of elements in the series.
func (ms MaxSeries) Len() int <span class="cov0" title="0">{
return ms.InnerSeries.Len()
}</span>
// GetValues gets a value at a given index.
func (ms *MaxSeries) GetValues(index int) (x, y float64) <span class="cov0" title="0">{
ms.ensureMaxValue()
x, _ = ms.InnerSeries.GetValues(index)
y = *ms.maxValue
return
}</span>
// Render renders the series.
func (ms *MaxSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := ms.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ms)
}</span>
func (ms *MaxSeries) ensureMaxValue() <span class="cov0" title="0">{
if ms.maxValue == nil </span><span class="cov0" title="0">{
maxValue := -math.MaxFloat64
var y float64
for x := 0; x &lt; ms.InnerSeries.Len(); x++ </span><span class="cov0" title="0">{
_, y = ms.InnerSeries.GetValues(x)
if y &gt; maxValue </span><span class="cov0" title="0">{
maxValue = y
}</span>
}
<span class="cov0" title="0">ms.maxValue = &amp;maxValue</span>
}
}
// Validate validates the series.
func (ms *MaxSeries) Validate() error <span class="cov0" title="0">{
if ms.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("max series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file44" style="display: none">package chart
import (
"errors"
"fmt"
"io"
"math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/util"
)
const (
_pi = math.Pi
_pi2 = math.Pi / 2.0
_pi4 = math.Pi / 4.0
)
// 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 <span class="cov8" title="1">{
if pc.DPI == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return DefaultDPI</span>
}
<span class="cov0" title="0">return pc.DPI</span>
}
// GetFont returns the text font.
func (pc PieChart) GetFont() *truetype.Font <span class="cov8" title="1">{
if pc.Font == nil </span><span class="cov8" title="1">{
return pc.defaultFont
}</span>
<span class="cov0" title="0">return pc.Font</span>
}
// GetWidth returns the chart width or the default value.
func (pc PieChart) GetWidth() int <span class="cov8" title="1">{
if pc.Width == 0 </span><span class="cov8" title="1">{
return DefaultChartWidth
}</span>
<span class="cov0" title="0">return pc.Width</span>
}
// GetHeight returns the chart height or the default value.
func (pc PieChart) GetHeight() int <span class="cov8" title="1">{
if pc.Height == 0 </span><span class="cov8" title="1">{
return DefaultChartWidth
}</span>
<span class="cov0" title="0">return pc.Height</span>
}
// Render renders the chart with the given renderer to the given io.Writer.
func (pc PieChart) Render(rp RendererProvider, w io.Writer) error <span class="cov8" title="1">{
if len(pc.Values) == 0 </span><span class="cov0" title="0">{
return errors.New("please provide at least one value")
}</span>
<span class="cov8" title="1">r, err := rp(pc.GetWidth(), pc.GetHeight())
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">if pc.Font == nil </span><span class="cov8" title="1">{
defaultFont, err := GetDefaultFont()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">pc.defaultFont = defaultFont</span>
}
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">pc.drawSlices(r, canvasBox, finalValues)
pc.drawTitle(r)
for _, a := range pc.Elements </span><span class="cov0" title="0">{
a(r, canvasBox, pc.styleDefaultsElements())
}</span>
<span class="cov8" title="1">return r.Save(w)</span>
}
func (pc PieChart) drawBackground(r Renderer) <span class="cov8" title="1">{
Draw.Box(r, Box{
Right: pc.GetWidth(),
Bottom: pc.GetHeight(),
}, pc.getBackgroundStyle())
}</span>
func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) <span class="cov8" title="1">{
Draw.Box(r, canvasBox, pc.getCanvasStyle())
}</span>
func (pc PieChart) drawTitle(r Renderer) <span class="cov8" title="1">{
if len(pc.Title) &gt; 0 &amp;&amp; pc.TitleStyle.Show </span><span class="cov0" title="0">{
Draw.TextWithin(r, pc.Title, pc.Box(), pc.styleDefaultsTitle())
}</span>
}
func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) <span class="cov8" title="1">{
cx, cy := canvasBox.Center()
diameter := util.Math.MinInt(canvasBox.Width(), canvasBox.Height())
radius := float64(diameter &gt;&gt; 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 </span><span class="cov0" title="0">{
pc.stylePieChartValue(0).WriteToRenderer(r)
r.MoveTo(cx, cy)
r.Circle(radius, cx, cy)
}</span> else<span class="cov8" title="1"> {
for index, v := range values </span><span class="cov8" title="1">{
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
r.MoveTo(cx, cy)
rads = util.Math.PercentToRadians(total)
delta = util.Math.PercentToRadians(v.Value)
r.ArcTo(cx, cy, radius, radius, rads, delta)
r.LineTo(cx, cy)
r.Close()
r.FillStroke()
total = total + v.Value
}</span>
}
// draw the labels
<span class="cov8" title="1">total = 0
for index, v := range values </span><span class="cov8" title="1">{
v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
if len(v.Label) &gt; 0 </span><span class="cov8" title="1">{
delta2 = util.Math.PercentToRadians(total + (v.Value / 2.0))
delta2 = util.Math.RadianAdd(delta2, _pi2)
lx, ly = util.Math.CirclePoint(cx, cy, labelRadius, delta2)
tb := r.MeasureText(v.Label)
lx = lx - (tb.Width() &gt;&gt; 1)
ly = ly + (tb.Height() &gt;&gt; 1)
if lx &lt; 0 </span><span class="cov0" title="0">{
lx = 0
}</span>
<span class="cov8" title="1">if ly &lt; 0 </span><span class="cov0" title="0">{
lx = 0
}</span>
<span class="cov8" title="1">r.Text(v.Label, lx, ly)</span>
}
<span class="cov8" title="1">total = total + v.Value</span>
}
}
func (pc PieChart) finalizeValues(values []Value) ([]Value, error) <span class="cov8" title="1">{
finalValues := Values(values).Normalize()
if len(finalValues) == 0 </span><span class="cov8" title="1">{
return nil, fmt.Errorf("pie chart must contain at least (1) non-zero value")
}</span>
<span class="cov8" title="1">return finalValues, nil</span>
}
func (pc PieChart) getDefaultCanvasBox() Box <span class="cov8" title="1">{
return pc.Box()
}</span>
func (pc PieChart) getCircleAdjustedCanvasBox(canvasBox Box) Box <span class="cov8" title="1">{
circleDiameter := util.Math.MinInt(canvasBox.Width(), canvasBox.Height())
square := Box{
Right: circleDiameter,
Bottom: circleDiameter,
}
return canvasBox.Fit(square)
}</span>
func (pc PieChart) getBackgroundStyle() Style <span class="cov8" title="1">{
return pc.Background.InheritFrom(pc.styleDefaultsBackground())
}</span>
func (pc PieChart) getCanvasStyle() Style <span class="cov8" title="1">{
return pc.Canvas.InheritFrom(pc.styleDefaultsCanvas())
}</span>
func (pc PieChart) styleDefaultsCanvas() Style <span class="cov8" title="1">{
return Style{
FillColor: pc.GetColorPalette().CanvasColor(),
StrokeColor: pc.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultStrokeWidth,
}
}</span>
func (pc PieChart) styleDefaultsPieChartValue() Style <span class="cov0" title="0">{
return Style{
StrokeColor: pc.GetColorPalette().TextColor(),
StrokeWidth: 5.0,
FillColor: pc.GetColorPalette().TextColor(),
}
}</span>
func (pc PieChart) stylePieChartValue(index int) Style <span class="cov8" title="1">{
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(),
})
}</span>
func (pc PieChart) getScaledFontSize() float64 <span class="cov8" title="1">{
effectiveDimension := util.Math.MinInt(pc.GetWidth(), pc.GetHeight())
if effectiveDimension &gt;= 2048 </span><span class="cov0" title="0">{
return 48.0
}</span> else<span class="cov8" title="1"> if effectiveDimension &gt;= 1024 </span><span class="cov8" title="1">{
return 24.0
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt; 512 </span><span class="cov0" title="0">{
return 18.0
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt; 256 </span><span class="cov0" title="0">{
return 12.0
}</span>
<span class="cov0" title="0">return 10.0</span>
}
func (pc PieChart) styleDefaultsBackground() Style <span class="cov8" title="1">{
return Style{
FillColor: pc.GetColorPalette().BackgroundColor(),
StrokeColor: pc.GetColorPalette().BackgroundStrokeColor(),
StrokeWidth: DefaultStrokeWidth,
}
}</span>
func (pc PieChart) styleDefaultsElements() Style <span class="cov0" title="0">{
return Style{
Font: pc.GetFont(),
}
}</span>
func (pc PieChart) styleDefaultsTitle() Style <span class="cov0" title="0">{
return pc.TitleStyle.InheritFrom(Style{
FontColor: pc.GetColorPalette().TextColor(),
Font: pc.GetFont(),
FontSize: pc.getTitleFontSize(),
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
})
}</span>
func (pc PieChart) getTitleFontSize() float64 <span class="cov0" title="0">{
effectiveDimension := util.Math.MinInt(pc.GetWidth(), pc.GetHeight())
if effectiveDimension &gt;= 2048 </span><span class="cov0" title="0">{
return 48
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 1024 </span><span class="cov0" title="0">{
return 24
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 512 </span><span class="cov0" title="0">{
return 18
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 256 </span><span class="cov0" title="0">{
return 12
}</span>
<span class="cov0" title="0">return 10</span>
}
// GetColorPalette returns the color palette for the chart.
func (pc PieChart) GetColorPalette() ColorPalette <span class="cov8" title="1">{
if pc.ColorPalette != nil </span><span class="cov0" title="0">{
return pc.ColorPalette
}</span>
<span class="cov8" title="1">return AlternateColorPalette</span>
}
// Box returns the chart bounds as a box.
func (pc PieChart) Box() Box <span class="cov8" title="1">{
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,
}
}</span>
</pre>
<pre class="file" id="file45" style="display: none">package chart
import (
"fmt"
"math"
"github.com/wcharczuk/go-chart/matrix"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return prs.Name
}</span>
// GetStyle returns the line style.
func (prs PolynomialRegressionSeries) GetStyle() Style <span class="cov0" title="0">{
return prs.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (prs PolynomialRegressionSeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return prs.YAxis
}</span>
// Len returns the number of elements in the series.
func (prs PolynomialRegressionSeries) Len() int <span class="cov0" title="0">{
return util.Math.MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset())
}</span>
// GetLimit returns the window size.
func (prs PolynomialRegressionSeries) GetLimit() int <span class="cov8" title="1">{
if prs.Limit == 0 </span><span class="cov8" title="1">{
return prs.InnerSeries.Len()
}</span>
<span class="cov0" title="0">return prs.Limit</span>
}
// GetEndIndex returns the effective limit end.
func (prs PolynomialRegressionSeries) GetEndIndex() int <span class="cov8" title="1">{
windowEnd := prs.GetOffset() + prs.GetLimit()
innerSeriesLastIndex := prs.InnerSeries.Len() - 1
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
}</span>
// GetOffset returns the data offset.
func (prs PolynomialRegressionSeries) GetOffset() int <span class="cov8" title="1">{
if prs.Offset == 0 </span><span class="cov8" title="1">{
return 0
}</span>
<span class="cov0" title="0">return prs.Offset</span>
}
// Validate validates the series.
func (prs *PolynomialRegressionSeries) Validate() error <span class="cov0" title="0">{
if prs.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">endIndex := prs.GetEndIndex()
if endIndex &gt;= prs.InnerSeries.Len() </span><span class="cov0" title="0">{
return fmt.Errorf("invalid window; inner series has length %d but end index is %d", prs.InnerSeries.Len(), endIndex)
}</span>
<span class="cov0" title="0">return nil</span>
}
// GetValues returns the series value for a given index.
func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) <span class="cov8" title="1">{
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">if prs.coeffs == nil </span><span class="cov8" title="1">{
coeffs, err := prs.computeCoefficients()
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov8" title="1">prs.coeffs = coeffs</span>
}
<span class="cov8" title="1">offset := prs.GetOffset()
effectiveIndex := util.Math.MinInt(index+offset, prs.InnerSeries.Len())
x, y = prs.InnerSeries.GetValues(effectiveIndex)
y = prs.apply(x)
return</span>
}
// GetFirstValues computes the first poly regression value.
func (prs *PolynomialRegressionSeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if prs.coeffs == nil </span><span class="cov0" title="0">{
coeffs, err := prs.computeCoefficients()
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov0" title="0">prs.coeffs = coeffs</span>
}
<span class="cov0" title="0">x, y = prs.InnerSeries.GetValues(0)
y = prs.apply(x)
return</span>
}
// GetLastValues computes the last poly regression value.
func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) <span class="cov0" title="0">{
if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">if prs.coeffs == nil </span><span class="cov0" title="0">{
coeffs, err := prs.computeCoefficients()
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov0" title="0">prs.coeffs = coeffs</span>
}
<span class="cov0" title="0">endIndex := prs.GetEndIndex()
x, y = prs.InnerSeries.GetValues(endIndex)
y = prs.apply(x)
return</span>
}
func (prs *PolynomialRegressionSeries) apply(v float64) (out float64) <span class="cov8" title="1">{
for index, coeff := range prs.coeffs </span><span class="cov8" title="1">{
out = out + (coeff * math.Pow(v, float64(index)))
}</span>
<span class="cov8" title="1">return</span>
}
func (prs *PolynomialRegressionSeries) computeCoefficients() ([]float64, error) <span class="cov8" title="1">{
xvalues, yvalues := prs.values()
return matrix.Poly(xvalues, yvalues, prs.Degree)
}</span>
func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) <span class="cov8" title="1">{
startIndex := prs.GetOffset()
endIndex := prs.GetEndIndex()
xvalues = make([]float64, endIndex-startIndex)
yvalues = make([]float64, endIndex-startIndex)
for index := startIndex; index &lt; endIndex; index++ </span><span class="cov8" title="1">{
x, y := prs.InnerSeries.GetValues(index)
xvalues[index-startIndex] = x
yvalues[index-startIndex] = y
}</span>
<span class="cov8" title="1">return</span>
}
// Render renders the series.
func (prs *PolynomialRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := prs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, prs)
}</span>
</pre>
<pre class="file" id="file46" style="display: none">package chart
import (
"image"
"image/png"
"io"
"math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
)
// PNG returns a new png/raster renderer.
func PNG(width, height int) (Renderer, error) <span class="cov8" title="1">{
i := image.NewRGBA(image.Rect(0, 0, width, height))
gc, err := drawing.NewRasterGraphicContext(i)
if err == nil </span><span class="cov8" title="1">{
return &amp;rasterRenderer{
i: i,
gc: gc,
}, nil
}</span>
<span class="cov0" title="0">return nil, err</span>
}
// rasterRenderer renders chart commands to a bitmap.
type rasterRenderer struct {
i *image.RGBA
gc *drawing.RasterGraphicContext
rotateRadians *float64
s Style
}
func (rr *rasterRenderer) ResetStyle() <span class="cov8" title="1">{
rr.s = Style{Font: rr.s.Font}
rr.ClearTextRotation()
}</span>
// GetDPI returns the dpi.
func (rr *rasterRenderer) GetDPI() float64 <span class="cov0" title="0">{
return rr.gc.GetDPI()
}</span>
// SetDPI implements the interface method.
func (rr *rasterRenderer) SetDPI(dpi float64) <span class="cov8" title="1">{
rr.gc.SetDPI(dpi)
}</span>
// SetClassName implements the interface method. However, PNGs have no classes.
func (vr *rasterRenderer) SetClassName(_ string) {<span class="cov8" title="1">
}</span>
// SetStrokeColor implements the interface method.
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) <span class="cov8" title="1">{
rr.s.StrokeColor = c
}</span>
// SetLineWidth implements the interface method.
func (rr *rasterRenderer) SetStrokeWidth(width float64) <span class="cov8" title="1">{
rr.s.StrokeWidth = width
}</span>
// StrokeDashArray sets the stroke dash array.
func (rr *rasterRenderer) SetStrokeDashArray(dashArray []float64) <span class="cov8" title="1">{
rr.s.StrokeDashArray = dashArray
}</span>
// SetFillColor implements the interface method.
func (rr *rasterRenderer) SetFillColor(c drawing.Color) <span class="cov8" title="1">{
rr.s.FillColor = c
}</span>
// MoveTo implements the interface method.
func (rr *rasterRenderer) MoveTo(x, y int) <span class="cov8" title="1">{
rr.gc.MoveTo(float64(x), float64(y))
}</span>
// LineTo implements the interface method.
func (rr *rasterRenderer) LineTo(x, y int) <span class="cov8" title="1">{
rr.gc.LineTo(float64(x), float64(y))
}</span>
// QuadCurveTo implements the interface method.
func (rr *rasterRenderer) QuadCurveTo(cx, cy, x, y int) <span class="cov0" title="0">{
rr.gc.QuadCurveTo(float64(cx), float64(cy), float64(x), float64(y))
}</span>
// ArcTo implements the interface method.
func (rr *rasterRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) <span class="cov8" title="1">{
rr.gc.ArcTo(float64(cx), float64(cy), rx, ry, startAngle, delta)
}</span>
// Close implements the interface method.
func (rr *rasterRenderer) Close() <span class="cov8" title="1">{
rr.gc.Close()
}</span>
// Stroke implements the interface method.
func (rr *rasterRenderer) Stroke() <span class="cov8" title="1">{
rr.gc.SetStrokeColor(rr.s.StrokeColor)
rr.gc.SetLineWidth(rr.s.StrokeWidth)
rr.gc.SetLineDash(rr.s.StrokeDashArray, 0)
rr.gc.Stroke()
}</span>
// Fill implements the interface method.
func (rr *rasterRenderer) Fill() <span class="cov8" title="1">{
rr.gc.SetFillColor(rr.s.FillColor)
rr.gc.Fill()
}</span>
// FillStroke implements the interface method.
func (rr *rasterRenderer) FillStroke() <span class="cov8" title="1">{
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()
}</span>
// 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) <span class="cov0" title="0">{
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
}</span>
// SetFont implements the interface method.
func (rr *rasterRenderer) SetFont(f *truetype.Font) <span class="cov8" title="1">{
rr.s.Font = f
}</span>
// SetFontSize implements the interface method.
func (rr *rasterRenderer) SetFontSize(size float64) <span class="cov8" title="1">{
rr.s.FontSize = size
}</span>
// SetFontColor implements the interface method.
func (rr *rasterRenderer) SetFontColor(c drawing.Color) <span class="cov8" title="1">{
rr.s.FontColor = c
}</span>
// Text implements the interface method.
func (rr *rasterRenderer) Text(body string, x, y int) <span class="cov8" title="1">{
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()
}</span>
// MeasureText returns the height and width in pixels of a string.
func (rr *rasterRenderer) MeasureText(body string) Box <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
return Box{}
}</span>
<span class="cov8" title="1">if l &lt; 0 </span><span class="cov0" title="0">{
r = r - l // equivalent to r+(-1*l)
l = 0
}</span>
<span class="cov8" title="1">if t &lt; 0 </span><span class="cov8" title="1">{
b = b - t
t = 0
}</span>
<span class="cov8" title="1">if l &gt; 0 </span><span class="cov8" title="1">{
r = r + l
l = 0
}</span>
<span class="cov8" title="1">if t &gt; 0 </span><span class="cov0" title="0">{
b = b + t
t = 0
}</span>
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
return textBox
}</span>
<span class="cov0" title="0">return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians)).Box()</span>
}
// SetTextRotation sets a text rotation.
func (rr *rasterRenderer) SetTextRotation(radians float64) <span class="cov0" title="0">{
rr.rotateRadians = &amp;radians
}</span>
func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) <span class="cov8" title="1">{
if rr.rotateRadians == nil </span><span class="cov8" title="1">{
xf = x
yf = y
return
}</span>
<span class="cov0" title="0">rr.gc.Translate(float64(x), float64(y))
rr.gc.Rotate(*rr.rotateRadians)
return</span>
}
// ClearTextRotation clears text rotation.
func (rr *rasterRenderer) ClearTextRotation() <span class="cov8" title="1">{
rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix())
rr.rotateRadians = nil
}</span>
// Save implements the interface method.
func (rr *rasterRenderer) Save(w io.Writer) error <span class="cov8" title="1">{
if typed, isTyped := w.(RGBACollector); isTyped </span><span class="cov0" title="0">{
typed.SetRGBA(rr.i)
return nil
}</span>
<span class="cov8" title="1">return png.Encode(w, rr.i)</span>
}
</pre>
<pre class="file" id="file47" style="display: none">package seq
// NewArray creates a new array.
func NewArray(values ...float64) Array <span class="cov8" title="1">{
return Array(values)
}</span>
// 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 <span class="cov8" title="1">{
return len(a)
}</span>
// GetValue returns the value at a given index.
func (a Array) GetValue(index int) float64 <span class="cov8" title="1">{
return a[index]
}</span>
</pre>
<pre class="file" id="file48" style="display: none">package seq
import (
"fmt"
"strings"
util "github.com/wcharczuk/go-chart/util"
)
const (
bufferMinimumGrow = 4
bufferShrinkThreshold = 32
bufferGrowFactor = 200
bufferDefaultCapacity = 4
)
var (
emptyArray = make([]float64, 0)
)
// NewBuffer creates a new value buffer with an optional set of values.
func NewBuffer(values ...float64) *Buffer <span class="cov8" title="1">{
var tail int
array := make([]float64, util.Math.MaxInt(len(values), bufferDefaultCapacity))
if len(values) &gt; 0 </span><span class="cov8" title="1">{
copy(array, values)
tail = len(values)
}</span>
<span class="cov8" title="1">return &amp;Buffer{
array: array,
head: 0,
tail: tail,
size: len(values),
}</span>
}
// NewBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
func NewBufferWithCapacity(capacity int) *Buffer <span class="cov0" title="0">{
return &amp;Buffer{
array: make([]float64, capacity),
head: 0,
tail: 0,
size: 0,
}
}</span>
// Buffer 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 Buffer 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 *Buffer) Len() int <span class="cov8" title="1">{
return b.size
}</span>
// GetValue implements seq provider.
func (b *Buffer) GetValue(index int) float64 <span class="cov0" title="0">{
effectiveIndex := (b.head + index) % len(b.array)
return b.array[effectiveIndex]
}</span>
// Capacity returns the total size of the Buffer, including empty elements.
func (b *Buffer) Capacity() int <span class="cov8" title="1">{
return len(b.array)
}</span>
// SetCapacity sets the capacity of the Buffer.
func (b *Buffer) SetCapacity(capacity int) <span class="cov8" title="1">{
newArray := make([]float64, capacity)
if b.size &gt; 0 </span><span class="cov8" title="1">{
if b.head &lt; b.tail </span><span class="cov8" title="1">{
arrayCopy(b.array, b.head, newArray, 0, b.size)
}</span> else<span class="cov8" title="1"> {
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
}</span>
}
<span class="cov8" title="1">b.array = newArray
b.head = 0
if b.size == capacity </span><span class="cov0" title="0">{
b.tail = 0
}</span> else<span class="cov8" title="1"> {
b.tail = b.size
}</span>
}
// Clear removes all objects from the Buffer.
func (b *Buffer) Clear() <span class="cov8" title="1">{
b.array = make([]float64, bufferDefaultCapacity)
b.head = 0
b.tail = 0
b.size = 0
}</span>
// Enqueue adds an element to the "back" of the Buffer.
func (b *Buffer) Enqueue(value float64) <span class="cov8" title="1">{
if b.size == len(b.array) </span><span class="cov8" title="1">{
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
if newCapacity &lt; (len(b.array) + bufferMinimumGrow) </span><span class="cov0" title="0">{
newCapacity = len(b.array) + bufferMinimumGrow
}</span>
<span class="cov8" title="1">b.SetCapacity(newCapacity)</span>
}
<span class="cov8" title="1">b.array[b.tail] = value
b.tail = (b.tail + 1) % len(b.array)
b.size++</span>
}
// Dequeue removes the first element from the RingBuffer.
func (b *Buffer) Dequeue() float64 <span class="cov8" title="1">{
if b.size == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">removed := b.array[b.head]
b.head = (b.head + 1) % len(b.array)
b.size--
return removed</span>
}
// Peek returns but does not remove the first element.
func (b *Buffer) Peek() float64 <span class="cov8" title="1">{
if b.size == 0 </span><span class="cov8" title="1">{
return 0
}</span>
<span class="cov8" title="1">return b.array[b.head]</span>
}
// PeekBack returns but does not remove the last element.
func (b *Buffer) PeekBack() float64 <span class="cov8" title="1">{
if b.size == 0 </span><span class="cov8" title="1">{
return 0
}</span>
<span class="cov8" title="1">if b.tail == 0 </span><span class="cov8" title="1">{
return b.array[len(b.array)-1]
}</span>
<span class="cov8" title="1">return b.array[b.tail-1]</span>
}
// TrimExcess resizes the capacity of the buffer to better fit the contents.
func (b *Buffer) TrimExcess() <span class="cov0" title="0">{
threshold := float64(len(b.array)) * 0.9
if b.size &lt; int(threshold) </span><span class="cov0" title="0">{
b.SetCapacity(b.size)
}</span>
}
// Array returns the ring buffer, in order, as an array.
func (b *Buffer) Array() Array <span class="cov8" title="1">{
newArray := make([]float64, b.size)
if b.size == 0 </span><span class="cov0" title="0">{
return newArray
}</span>
<span class="cov8" title="1">if b.head &lt; b.tail </span><span class="cov8" title="1">{
arrayCopy(b.array, b.head, newArray, 0, b.size)
}</span> else<span class="cov0" title="0"> {
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
}</span>
<span class="cov8" title="1">return Array(newArray)</span>
}
// Each calls the consumer for each element in the buffer.
func (b *Buffer) Each(mapfn func(int, float64)) <span class="cov8" title="1">{
if b.size == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">var index int
if b.head &lt; b.tail </span><span class="cov0" title="0">{
for cursor := b.head; cursor &lt; b.tail; cursor++ </span><span class="cov0" title="0">{
mapfn(index, b.array[cursor])
index++
}</span>
} else<span class="cov8" title="1"> {
for cursor := b.head; cursor &lt; len(b.array); cursor++ </span><span class="cov8" title="1">{
mapfn(index, b.array[cursor])
index++
}</span>
<span class="cov8" title="1">for cursor := 0; cursor &lt; b.tail; cursor++ </span><span class="cov0" title="0">{
mapfn(index, b.array[cursor])
index++
}</span>
}
}
// String returns a string representation for value buffers.
func (b *Buffer) String() string <span class="cov0" title="0">{
var values []string
for _, elem := range b.Array() </span><span class="cov0" title="0">{
values = append(values, fmt.Sprintf("%v", elem))
}</span>
<span class="cov0" title="0">return strings.Join(values, " &lt;= ")</span>
}
// --------------------------------------------------------------------------------
// Util methods
// --------------------------------------------------------------------------------
func arrayClear(source []float64, index, length int) <span class="cov0" title="0">{
for x := 0; x &lt; length; x++ </span><span class="cov0" title="0">{
absoluteIndex := x + index
source[absoluteIndex] = 0
}</span>
}
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) <span class="cov8" title="1">{
for x := 0; x &lt; length; x++ </span><span class="cov8" title="1">{
from := sourceIndex + x
to := destinationIndex + x
destination[to] = source[from]
}</span>
}
</pre>
<pre class="file" id="file49" style="display: none">package seq
// Range returns the array values of a linear seq with a given start, end and optional step.
func Range(start, end float64) []float64 <span class="cov8" title="1">{
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(1.0)}.Array()
}</span>
// RangeWithStep returns the array values of a linear seq with a given start, end and optional step.
func RangeWithStep(start, end, step float64) []float64 <span class="cov8" title="1">{
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(step)}.Array()
}</span>
// NewLinear returns a new linear generator.
func NewLinear() *Linear <span class="cov8" title="1">{
return &amp;Linear{step: 1.0}
}</span>
// Linear is a stepwise generator.
type Linear struct {
start float64
end float64
step float64
}
// Start returns the start value.
func (lg Linear) Start() float64 <span class="cov8" title="1">{
return lg.start
}</span>
// End returns the end value.
func (lg Linear) End() float64 <span class="cov8" title="1">{
return lg.end
}</span>
// Step returns the step value.
func (lg Linear) Step() float64 <span class="cov0" title="0">{
return lg.step
}</span>
// Len returns the number of elements in the seq.
func (lg Linear) Len() int <span class="cov8" title="1">{
if lg.start &lt; lg.end </span><span class="cov8" title="1">{
return int((lg.end-lg.start)/lg.step) + 1
}</span>
<span class="cov8" title="1">return int((lg.start-lg.end)/lg.step) + 1</span>
}
// GetValue returns the value at a given index.
func (lg Linear) GetValue(index int) float64 <span class="cov8" title="1">{
fi := float64(index)
if lg.start &lt; lg.end </span><span class="cov8" title="1">{
return lg.start + (fi * lg.step)
}</span>
<span class="cov8" title="1">return lg.start - (fi * lg.step)</span>
}
// WithStart sets the start and returns the linear generator.
func (lg *Linear) WithStart(start float64) *Linear <span class="cov8" title="1">{
lg.start = start
return lg
}</span>
// WithEnd sets the end and returns the linear generator.
func (lg *Linear) WithEnd(end float64) *Linear <span class="cov8" title="1">{
lg.end = end
return lg
}</span>
// WithStep sets the step and returns the linear generator.
func (lg *Linear) WithStep(step float64) *Linear <span class="cov8" title="1">{
lg.step = step
return lg
}</span>
</pre>
<pre class="file" id="file50" style="display: none">package seq
import (
"math"
"math/rand"
"time"
)
// RandomValues returns an array of random values.
func RandomValues(count int) []float64 <span class="cov0" title="0">{
return Seq{NewRandom().WithLen(count)}.Array()
}</span>
// RandomValuesWithMax returns an array of random values with a given average.
func RandomValuesWithMax(count int, max float64) []float64 <span class="cov0" title="0">{
return Seq{NewRandom().WithMax(max).WithLen(count)}.Array()
}</span>
// NewRandom creates a new random seq.
func NewRandom() *Random <span class="cov8" title="1">{
return &amp;Random{
rnd: rand.New(rand.NewSource(time.Now().Unix())),
}
}</span>
// Random is a random number seq generator.
type Random struct {
rnd *rand.Rand
max *float64
min *float64
len *int
}
// Len returns the number of elements that will be generated.
func (r *Random) Len() int <span class="cov8" title="1">{
if r.len != nil </span><span class="cov8" title="1">{
return *r.len
}</span>
<span class="cov0" title="0">return math.MaxInt32</span>
}
// GetValue returns the value.
func (r *Random) GetValue(_ int) float64 <span class="cov8" title="1">{
if r.min != nil &amp;&amp; r.max != nil </span><span class="cov0" title="0">{
var delta float64
if *r.max &gt; *r.min </span><span class="cov0" title="0">{
delta = *r.max - *r.min
}</span> else<span class="cov0" title="0"> {
delta = *r.min - *r.max
}</span>
<span class="cov0" title="0">return *r.min + (r.rnd.Float64() * delta)</span>
} else<span class="cov8" title="1"> if r.max != nil </span><span class="cov8" title="1">{
return r.rnd.Float64() * *r.max
}</span> else<span class="cov0" title="0"> if r.min != nil </span><span class="cov0" title="0">{
return *r.min + (r.rnd.Float64())
}</span>
<span class="cov0" title="0">return r.rnd.Float64()</span>
}
// WithLen sets a maximum len
func (r *Random) WithLen(length int) *Random <span class="cov8" title="1">{
r.len = &amp;length
return r
}</span>
// Min returns the minimum value.
func (r Random) Min() *float64 <span class="cov0" title="0">{
return r.min
}</span>
// WithMin sets the scale and returns the Random.
func (r *Random) WithMin(min float64) *Random <span class="cov0" title="0">{
r.min = &amp;min
return r
}</span>
// Max returns the maximum value.
func (r Random) Max() *float64 <span class="cov8" title="1">{
return r.max
}</span>
// WithMax sets the average and returns the Random.
func (r *Random) WithMax(max float64) *Random <span class="cov8" title="1">{
r.max = &amp;max
return r
}</span>
</pre>
<pre class="file" id="file51" style="display: none">package seq
import (
"math"
"sort"
)
// New wraps a provider with a seq.
func New(provider Provider) Seq <span class="cov8" title="1">{
return Seq{Provider: provider}
}</span>
// Values returns a new seq composed of a given set of values.
func Values(values ...float64) Seq <span class="cov8" title="1">{
return Seq{Provider: Array(values)}
}</span>
// Provider is a provider for values for a seq.
type Provider interface {
Len() int
GetValue(int) float64
}
// Seq is a utility wrapper for seq providers.
type Seq struct {
Provider
}
// Array enumerates the seq into a slice.
func (s Seq) Array() (output []float64) <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">output = make([]float64, s.Len())
for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
output[i] = s.GetValue(i)
}</span>
<span class="cov8" title="1">return</span>
}
// Each applies the `mapfn` to all values in the value provider.
func (s Seq) Each(mapfn func(int, float64)) <span class="cov8" title="1">{
for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
mapfn(i, s.GetValue(i))
}</span>
}
// 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 <span class="cov8" title="1">{
output := make([]float64, s.Len())
for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
mapfn(i, s.GetValue(i))
}</span>
<span class="cov8" title="1">return Seq{Array(output)}</span>
}
// FoldLeft collapses a seq from left to right.
func (s Seq) FoldLeft(mapfn func(i int, v0, v float64) float64) (v0 float64) <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">if s.Len() == 1 </span><span class="cov0" title="0">{
return s.GetValue(0)
}</span>
<span class="cov8" title="1">v0 = s.GetValue(0)
for i := 1; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
v0 = mapfn(i, v0, s.GetValue(i))
}</span>
<span class="cov8" title="1">return</span>
}
// FoldRight collapses a seq from right to left.
func (s Seq) FoldRight(mapfn func(i int, v0, v float64) float64) (v0 float64) <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">if s.Len() == 1 </span><span class="cov0" title="0">{
return s.GetValue(0)
}</span>
<span class="cov8" title="1">v0 = s.GetValue(s.Len() - 1)
for i := s.Len() - 2; i &gt;= 0; i-- </span><span class="cov8" title="1">{
v0 = mapfn(i, v0, s.GetValue(i))
}</span>
<span class="cov8" title="1">return</span>
}
// Min returns the minimum value in the seq.
func (s Seq) Min() float64 <span class="cov0" title="0">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">min := s.GetValue(0)
var value float64
for i := 1; i &lt; s.Len(); i++ </span><span class="cov0" title="0">{
value = s.GetValue(i)
if value &lt; min </span><span class="cov0" title="0">{
min = value
}</span>
}
<span class="cov0" title="0">return min</span>
}
// Max returns the maximum value in the seq.
func (s Seq) Max() float64 <span class="cov0" title="0">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">max := s.GetValue(0)
var value float64
for i := 1; i &lt; s.Len(); i++ </span><span class="cov0" title="0">{
value = s.GetValue(i)
if value &gt; max </span><span class="cov0" title="0">{
max = value
}</span>
}
<span class="cov0" title="0">return max</span>
}
// MinMax returns the minimum and the maximum in one pass.
func (s Seq) MinMax() (min, max float64) <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">min = s.GetValue(0)
max = min
var value float64
for i := 1; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
value = s.GetValue(i)
if value &lt; min </span><span class="cov0" title="0">{
min = value
}</span>
<span class="cov8" title="1">if value &gt; max </span><span class="cov8" title="1">{
max = value
}</span>
}
<span class="cov8" title="1">return</span>
}
// Sort returns the seq sorted in ascending order.
// This fully enumerates the seq.
func (s Seq) Sort() Seq <span class="cov0" title="0">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return s
}</span>
<span class="cov0" title="0">values := s.Array()
sort.Float64s(values)
return Seq{Provider: Array(values)}</span>
}
// Median returns the median or middle value in the sorted seq.
func (s Seq) Median() (median float64) <span class="cov0" title="0">{
l := s.Len()
if l == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">sorted := s.Sort()
if l%2 == 0 </span><span class="cov0" title="0">{
v0 := sorted.GetValue(l/2 - 1)
v1 := sorted.GetValue(l/2 + 1)
median = (v0 + v1) / 2
}</span> else<span class="cov0" title="0"> {
median = float64(sorted.GetValue(l &lt;&lt; 1))
}</span>
<span class="cov0" title="0">return</span>
}
// Sum adds all the elements of a series together.
func (s Seq) Sum() (accum float64) <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
accum += s.GetValue(i)
}</span>
<span class="cov8" title="1">return</span>
}
// Average returns the float average of the values in the buffer.
func (s Seq) Average() float64 <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">return s.Sum() / float64(s.Len())</span>
}
// Variance computes the variance of the buffer.
func (s Seq) Variance() float64 <span class="cov8" title="1">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">m := s.Average()
var variance, v float64
for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
v = s.GetValue(i)
variance += (v - m) * (v - m)
}</span>
<span class="cov8" title="1">return variance / float64(s.Len())</span>
}
// StdDev returns the standard deviation.
func (s Seq) StdDev() float64 <span class="cov0" title="0">{
if s.Len() == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">return math.Pow(s.Variance(), 0.5)</span>
}
//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) <span class="cov0" title="0">{
l := s.Len()
if l == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">if percent &lt; 0 || percent &gt; 1.0 </span><span class="cov0" title="0">{
panic("percent out of range [0.0, 1.0)")</span>
}
<span class="cov0" title="0">sorted := s.Sort()
index := percent * float64(l)
if index == float64(int64(index)) </span><span class="cov0" title="0">{
i := f64i(index)
ci := sorted.GetValue(i - 1)
c := sorted.GetValue(i)
percentile = (ci + c) / 2.0
}</span> else<span class="cov0" title="0"> {
i := f64i(index)
percentile = sorted.GetValue(i)
}</span>
<span class="cov0" title="0">return percentile</span>
}
// Normalize maps every value to the interval [0, 1.0].
func (s Seq) Normalize() Seq <span class="cov8" title="1">{
min, max := s.MinMax()
delta := max - min
output := make([]float64, s.Len())
for i := 0; i &lt; s.Len(); i++ </span><span class="cov8" title="1">{
output[i] = (s.GetValue(i) - min) / delta
}</span>
<span class="cov8" title="1">return Seq{Provider: Array(output)}</span>
}
</pre>
<pre class="file" id="file52" style="display: none">package seq
import (
"time"
"github.com/wcharczuk/go-chart/util"
)
// Time is a utility singleton with helper functions for time seq generation.
var Time timeSequence
type timeSequence struct{}
// Days generates a seq of timestamps by day, from -days to today.
func (ts timeSequence) Days(days int) []time.Time <span class="cov0" title="0">{
var values []time.Time
for day := days; day &gt;= 0; day-- </span><span class="cov0" title="0">{
values = append(values, time.Now().AddDate(0, 0, -day))
}</span>
<span class="cov0" title="0">return values</span>
}
func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time <span class="cov8" title="1">{
times := make([]time.Time, totalHours)
last := start
for i := 0; i &lt; totalHours; i++ </span><span class="cov8" title="1">{
times[i] = last
last = last.Add(time.Hour)
}</span>
<span class="cov8" title="1">return times</span>
}
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
func (ts timeSequence) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) <span class="cov8" title="1">{
start, end := util.Time.StartAndEnd(xdata...)
totalHours := util.Time.DiffHours(start, end)
finalTimes := ts.Hours(start, totalHours+1)
finalValues := make([]float64, totalHours+1)
var hoursFromStart int
for i, xd := range xdata </span><span class="cov8" title="1">{
hoursFromStart = util.Time.DiffHours(start, xd)
finalValues[hoursFromStart] = ydata[i]
}</span>
<span class="cov8" title="1">return finalTimes, finalValues</span>
}
</pre>
<pre class="file" id="file53" style="display: none">package seq
import (
"time"
"github.com/wcharczuk/go-chart/util"
)
// Assert types implement interfaces.
var (
_ Provider = (*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 <span class="cov0" title="0">{
return []time.Time(t)
}</span>
// Len returns the length of the array.
func (t Times) Len() int <span class="cov0" title="0">{
return len(t)
}</span>
// GetValue returns a value at an index as a time.
func (t Times) GetValue(index int) float64 <span class="cov0" title="0">{
return util.Time.ToFloat64(t[index])
}</span>
</pre>
<pre class="file" id="file54" style="display: none">package seq
import "math"
func round(input float64, places int) (rounded float64) <span class="cov0" title="0">{
if math.IsNaN(input) </span><span class="cov0" title="0">{
return 0.0
}</span>
<span class="cov0" title="0">sign := 1.0
if input &lt; 0 </span><span class="cov0" title="0">{
sign = -1
input *= -1
}</span>
<span class="cov0" title="0">precision := math.Pow(10, float64(places))
digit := input * precision
_, decimal := math.Modf(digit)
if decimal &gt;= 0.5 </span><span class="cov0" title="0">{
rounded = math.Ceil(digit)
}</span> else<span class="cov0" title="0"> {
rounded = math.Floor(digit)
}</span>
<span class="cov0" title="0">return rounded / precision * sign</span>
}
func f64i(value float64) int <span class="cov0" title="0">{
r := round(value, 0)
return int(r)
}</span>
</pre>
<pre class="file" id="file55" style="display: none">package chart
import (
"fmt"
util "github.com/wcharczuk/go-chart/util"
)
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 <span class="cov0" title="0">{
return sma.Name
}</span>
// GetStyle returns the line style.
func (sma SMASeries) GetStyle() Style <span class="cov0" title="0">{
return sma.Style
}</span>
// GetYAxis returns which YAxis the series draws on.
func (sma SMASeries) GetYAxis() YAxisType <span class="cov0" title="0">{
return sma.YAxis
}</span>
// Len returns the number of elements in the series.
func (sma SMASeries) Len() int <span class="cov8" title="1">{
return sma.InnerSeries.Len()
}</span>
// GetPeriod returns the window size.
func (sma SMASeries) GetPeriod(defaults ...int) int <span class="cov8" title="1">{
if sma.Period == 0 </span><span class="cov0" title="0">{
if len(defaults) &gt; 0 </span><span class="cov0" title="0">{
return defaults[0]
}</span>
<span class="cov0" title="0">return DefaultSimpleMovingAveragePeriod</span>
}
<span class="cov8" title="1">return sma.Period</span>
}
// GetValues gets a value at a given index.
func (sma SMASeries) GetValues(index int) (x, y float64) <span class="cov8" title="1">{
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">px, _ := sma.InnerSeries.GetValues(index)
x = px
y = sma.getAverage(index)
return</span>
}
// GetFirstValues computes the first moving average value.
func (sma SMASeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov0" title="0">px, _ := sma.InnerSeries.GetValues(0)
x = px
y = sma.getAverage(0)
return</span>
}
// 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) <span class="cov8" title="1">{
if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 </span><span class="cov0" title="0">{
return
}</span>
<span class="cov8" title="1">seriesLen := sma.InnerSeries.Len()
px, _ := sma.InnerSeries.GetValues(seriesLen - 1)
x = px
y = sma.getAverage(seriesLen - 1)
return</span>
}
func (sma SMASeries) getAverage(index int) float64 <span class="cov8" title="1">{
period := sma.GetPeriod()
floor := util.Math.MaxInt(0, index-period)
var accum float64
var count float64
for x := index; x &gt;= floor; x-- </span><span class="cov8" title="1">{
_, vy := sma.InnerSeries.GetValues(x)
accum += vy
count += 1.0
}</span>
<span class="cov8" title="1">return accum / count</span>
}
// Render renders the series.
func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov0" title="0">{
style := sma.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, sma)
}</span>
// Validate validates the series.
func (sma SMASeries) Validate() error <span class="cov0" title="0">{
if sma.InnerSeries == nil </span><span class="cov0" title="0">{
return fmt.Errorf("sma series requires InnerSeries to be set")
}</span>
<span class="cov0" title="0">return nil</span>
}
</pre>
<pre class="file" id="file56" style="display: none">package chart
import (
"errors"
"fmt"
"io"
"math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/seq"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
if sb.Width == 0 </span><span class="cov0" title="0">{
return 50
}</span>
<span class="cov0" title="0">return sb.Width</span>
}
// 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
Bars []StackedBar
Elements []Renderable
}
// GetDPI returns the dpi for the chart.
func (sbc StackedBarChart) GetDPI(defaults ...float64) float64 <span class="cov0" title="0">{
if sbc.DPI == 0 </span><span class="cov0" title="0">{
if len(defaults) &gt; 0 </span><span class="cov0" title="0">{
return defaults[0]
}</span>
<span class="cov0" title="0">return DefaultDPI</span>
}
<span class="cov0" title="0">return sbc.DPI</span>
}
// GetFont returns the text font.
func (sbc StackedBarChart) GetFont() *truetype.Font <span class="cov0" title="0">{
if sbc.Font == nil </span><span class="cov0" title="0">{
return sbc.defaultFont
}</span>
<span class="cov0" title="0">return sbc.Font</span>
}
// GetWidth returns the chart width or the default value.
func (sbc StackedBarChart) GetWidth() int <span class="cov0" title="0">{
if sbc.Width == 0 </span><span class="cov0" title="0">{
return DefaultChartWidth
}</span>
<span class="cov0" title="0">return sbc.Width</span>
}
// GetHeight returns the chart height or the default value.
func (sbc StackedBarChart) GetHeight() int <span class="cov0" title="0">{
if sbc.Height == 0 </span><span class="cov0" title="0">{
return DefaultChartWidth
}</span>
<span class="cov0" title="0">return sbc.Height</span>
}
// GetBarSpacing returns the spacing between bars.
func (sbc StackedBarChart) GetBarSpacing() int <span class="cov0" title="0">{
if sbc.BarSpacing == 0 </span><span class="cov0" title="0">{
return 100
}</span>
<span class="cov0" title="0">return sbc.BarSpacing</span>
}
// Render renders the chart with the given renderer to the given io.Writer.
func (sbc StackedBarChart) Render(rp RendererProvider, w io.Writer) error <span class="cov0" title="0">{
if len(sbc.Bars) == 0 </span><span class="cov0" title="0">{
return errors.New("please provide at least one bar")
}</span>
<span class="cov0" title="0">r, err := rp(sbc.GetWidth(), sbc.GetHeight())
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">if sbc.Font == nil </span><span class="cov0" title="0">{
defaultFont, err := GetDefaultFont()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">sbc.defaultFont = defaultFont</span>
}
<span class="cov0" title="0">r.SetDPI(sbc.GetDPI(DefaultDPI))
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 </span><span class="cov0" title="0">{
a(r, canvasBox, sbc.styleDefaultsElements())
}</span>
<span class="cov0" title="0">return r.Save(w)</span>
}
func (sbc StackedBarChart) drawCanvas(r Renderer, canvasBox Box) <span class="cov0" title="0">{
Draw.Box(r, canvasBox, sbc.getCanvasStyle())
}</span>
func (sbc StackedBarChart) drawBars(r Renderer, canvasBox Box) <span class="cov0" title="0">{
xoffset := canvasBox.Left
for _, bar := range sbc.Bars </span><span class="cov0" title="0">{
sbc.drawBar(r, canvasBox, xoffset, bar)
xoffset += (sbc.GetBarSpacing() + bar.GetWidth())
}</span>
}
func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar StackedBar) int <span class="cov0" title="0">{
barSpacing2 := sbc.GetBarSpacing() &gt;&gt; 1
bxl := xoffset + barSpacing2
bxr := bxl + bar.GetWidth()
normalizedBarComponents := Values(bar.Values).Normalize()
yoffset := canvasBox.Top
for index, bv := range normalizedBarComponents </span><span class="cov0" title="0">{
barHeight := int(math.Ceil(bv.Value * float64(canvasBox.Height())))
barBox := Box{
Top: yoffset,
Left: bxl,
Right: bxr,
Bottom: util.Math.MinInt(yoffset+barHeight, canvasBox.Bottom-DefaultStrokeWidth),
}
Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
yoffset += barHeight
}</span>
<span class="cov0" title="0">return bxr</span>
}
func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) <span class="cov0" title="0">{
if sbc.XAxis.Show </span><span class="cov0" title="0">{
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 </span><span class="cov0" title="0">{
barLabelBox := Box{
Top: canvasBox.Bottom + DefaultXAxisMargin,
Left: cursor,
Right: cursor + bar.GetWidth() + sbc.GetBarSpacing(),
Bottom: sbc.GetHeight(),
}
if len(bar.Name) &gt; 0 </span><span class="cov0" title="0">{
Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle)
}</span>
<span class="cov0" title="0">axisStyle.WriteToRenderer(r)
r.MoveTo(barLabelBox.Right, canvasBox.Bottom)
r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke()
cursor += bar.GetWidth() + sbc.GetBarSpacing()</span>
}
}
}
func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) <span class="cov0" title="0">{
if sbc.YAxis.Show </span><span class="cov0" title="0">{
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 := seq.RangeWithStep(0.0, 1.0, 0.2)
for _, t := range ticks </span><span class="cov0" title="0">{
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()&gt;&gt;1), axisStyle)
}</span>
}
}
func (sbc StackedBarChart) drawTitle(r Renderer) <span class="cov0" title="0">{
if len(sbc.Title) &gt; 0 &amp;&amp; sbc.TitleStyle.Show </span><span class="cov0" title="0">{
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() &gt;&gt; 1) - (textWidth &gt;&gt; 1)
titleY := sbc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
r.Text(sbc.Title, titleX, titleY)
}</span>
}
func (sbc StackedBarChart) getCanvasStyle() Style <span class="cov0" title="0">{
return sbc.Canvas.InheritFrom(sbc.styleDefaultsCanvas())
}</span>
func (sbc StackedBarChart) styleDefaultsCanvas() Style <span class="cov0" title="0">{
return Style{
FillColor: sbc.GetColorPalette().CanvasColor(),
StrokeColor: sbc.GetColorPalette().CanvasStrokeColor(),
StrokeWidth: DefaultCanvasStrokeWidth,
}
}</span>
// GetColorPalette returns the color palette for the chart.
func (sbc StackedBarChart) GetColorPalette() ColorPalette <span class="cov0" title="0">{
if sbc.ColorPalette != nil </span><span class="cov0" title="0">{
return sbc.ColorPalette
}</span>
<span class="cov0" title="0">return AlternateColorPalette</span>
}
func (sbc StackedBarChart) getDefaultCanvasBox() Box <span class="cov0" title="0">{
return sbc.Box()
}</span>
func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box <span class="cov0" title="0">{
var totalWidth int
for _, bar := range sbc.Bars </span><span class="cov0" title="0">{
totalWidth += bar.GetWidth() + sbc.GetBarSpacing()
}</span>
<span class="cov0" title="0">if sbc.XAxis.Show </span><span class="cov0" title="0">{
xaxisHeight := DefaultVerticalTickHeight
axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes())
axisStyle.WriteToRenderer(r)
cursor := canvasBox.Left
for _, bar := range sbc.Bars </span><span class="cov0" title="0">{
if len(bar.Name) &gt; 0 </span><span class="cov0" title="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 = util.Math.MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
}</span>
}
<span class="cov0" title="0">return Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Left + totalWidth,
Bottom: sbc.GetHeight() - xaxisHeight,
}</span>
}
<span class="cov0" title="0">return Box{
Top: canvasBox.Top,
Left: canvasBox.Left,
Right: canvasBox.Left + totalWidth,
Bottom: canvasBox.Bottom,
}</span>
}
// Box returns the chart bounds as a box.
func (sbc StackedBarChart) Box() Box <span class="cov0" title="0">{
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,
}
}</span>
func (sbc StackedBarChart) styleDefaultsStackedBarValue(index int) Style <span class="cov0" title="0">{
return Style{
StrokeColor: sbc.GetColorPalette().GetSeriesColor(index),
StrokeWidth: 3.0,
FillColor: sbc.GetColorPalette().GetSeriesColor(index),
}
}</span>
func (sbc StackedBarChart) styleDefaultsTitle() Style <span class="cov0" title="0">{
return sbc.TitleStyle.InheritFrom(Style{
FontColor: DefaultTextColor,
Font: sbc.GetFont(),
FontSize: sbc.getTitleFontSize(),
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
})
}</span>
func (sbc StackedBarChart) getTitleFontSize() float64 <span class="cov0" title="0">{
effectiveDimension := util.Math.MinInt(sbc.GetWidth(), sbc.GetHeight())
if effectiveDimension &gt;= 2048 </span><span class="cov0" title="0">{
return 48
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 1024 </span><span class="cov0" title="0">{
return 24
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 512 </span><span class="cov0" title="0">{
return 18
}</span> else<span class="cov0" title="0"> if effectiveDimension &gt;= 256 </span><span class="cov0" title="0">{
return 12
}</span>
<span class="cov0" title="0">return 10</span>
}
func (sbc StackedBarChart) styleDefaultsAxes() Style <span class="cov0" title="0">{
return Style{
StrokeColor: DefaultAxisColor,
Font: sbc.GetFont(),
FontSize: DefaultAxisFontSize,
FontColor: DefaultAxisColor,
TextHorizontalAlign: TextHorizontalAlignCenter,
TextVerticalAlign: TextVerticalAlignTop,
TextWrap: TextWrapWord,
}
}</span>
func (sbc StackedBarChart) styleDefaultsElements() Style <span class="cov0" title="0">{
return Style{
Font: sbc.GetFont(),
}
}</span>
</pre>
<pre class="file" id="file57" style="display: none">package chart
import (
"fmt"
"strings"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
)
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
)
// StyleShow is a prebuilt style with the `Show` property set to true.
func StyleShow() Style <span class="cov8" title="1">{
return Style{
Show: true,
}
}</span>
// StyleTextDefaults returns a style for drawing outside a
// chart context.
func StyleTextDefaults() Style <span class="cov0" title="0">{
font, _ := GetDefaultFont()
return Style{
Show: true,
Font: font,
FontColor: DefaultTextColor,
FontSize: DefaultTitleFontSize,
}
}</span>
// Style is a simple style set.
type Style struct {
Show 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 <span class="cov8" title="1">{
return s.StrokeColor.IsZero() &amp;&amp;
s.StrokeWidth == 0 &amp;&amp;
s.DotColor.IsZero() &amp;&amp;
s.DotWidth == 0 &amp;&amp;
s.FillColor.IsZero() &amp;&amp;
s.FontColor.IsZero() &amp;&amp;
s.FontSize == 0 &amp;&amp;
s.Font == nil &amp;&amp;
s.ClassName == ""
}</span>
// String returns a text representation of the style.
func (s Style) String() string <span class="cov0" title="0">{
if s.IsZero() </span><span class="cov0" title="0">{
return "{}"
}</span>
<span class="cov0" title="0">var output []string
if s.Show </span><span class="cov0" title="0">{
output = []string{"\"show\": true"}
}</span> else<span class="cov0" title="0"> {
output = []string{"\"show\": false"}
}</span>
<span class="cov0" title="0">if s.ClassName != "" </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"class_name\": %s", s.ClassName))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"class_name\": null")
}</span>
<span class="cov0" title="0">if !s.Padding.IsZero() </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"padding\": %s", s.Padding.String()))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"padding\": null")
}</span>
<span class="cov0" title="0">if s.StrokeWidth &gt;= 0 </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"stroke_width\": %0.2f", s.StrokeWidth))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"stroke_width\": null")
}</span>
<span class="cov0" title="0">if !s.StrokeColor.IsZero() </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"stroke_color\": %s", s.StrokeColor.String()))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"stroke_color\": null")
}</span>
<span class="cov0" title="0">if len(s.StrokeDashArray) &gt; 0 </span><span class="cov0" title="0">{
var elements []string
for _, v := range s.StrokeDashArray </span><span class="cov0" title="0">{
elements = append(elements, fmt.Sprintf("%.2f", v))
}</span>
<span class="cov0" title="0">dashArray := strings.Join(elements, ", ")
output = append(output, fmt.Sprintf("\"stroke_dash_array\": [%s]", dashArray))</span>
} else<span class="cov0" title="0"> {
output = append(output, "\"stroke_dash_array\": null")
}</span>
<span class="cov0" title="0">if s.DotWidth &gt;= 0 </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"dot_width\": %0.2f", s.DotWidth))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"dot_width\": null")
}</span>
<span class="cov0" title="0">if !s.DotColor.IsZero() </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"dot_color\": %s", s.DotColor.String()))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"dot_color\": null")
}</span>
<span class="cov0" title="0">if !s.FillColor.IsZero() </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"fill_color\": %s", s.FillColor.String()))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"fill_color\": null")
}</span>
<span class="cov0" title="0">if s.FontSize != 0 </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"font_size\": \"%0.2fpt\"", s.FontSize))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"font_size\": null")
}</span>
<span class="cov0" title="0">if !s.FontColor.IsZero() </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"font_color\": %s", s.FontColor.String()))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"font_color\": null")
}</span>
<span class="cov0" title="0">if s.Font != nil </span><span class="cov0" title="0">{
output = append(output, fmt.Sprintf("\"font\": \"%s\"", s.Font.Name(truetype.NameIDFontFamily)))
}</span> else<span class="cov0" title="0"> {
output = append(output, "\"font_color\": null")
}</span>
<span class="cov0" title="0">return "{" + strings.Join(output, ", ") + "}"</span>
}
func (s Style) GetClassName(defaults ...string) string <span class="cov8" title="1">{
if s.ClassName == "" </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return ""</span>
}
<span class="cov0" title="0">return s.ClassName</span>
}
// GetStrokeColor returns the stroke color.
func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color <span class="cov8" title="1">{
if s.StrokeColor.IsZero() </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return drawing.ColorTransparent</span>
}
<span class="cov8" title="1">return s.StrokeColor</span>
}
// GetFillColor returns the fill color.
func (s Style) GetFillColor(defaults ...drawing.Color) drawing.Color <span class="cov8" title="1">{
if s.FillColor.IsZero() </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return drawing.ColorTransparent</span>
}
<span class="cov8" title="1">return s.FillColor</span>
}
// GetDotColor returns the stroke color.
func (s Style) GetDotColor(defaults ...drawing.Color) drawing.Color <span class="cov8" title="1">{
if s.DotColor.IsZero() </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return drawing.ColorTransparent</span>
}
<span class="cov0" title="0">return s.DotColor</span>
}
// GetStrokeWidth returns the stroke width.
func (s Style) GetStrokeWidth(defaults ...float64) float64 <span class="cov8" title="1">{
if s.StrokeWidth == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return DefaultStrokeWidth</span>
}
<span class="cov8" title="1">return s.StrokeWidth</span>
}
// GetDotWidth returns the dot width for scatter plots.
func (s Style) GetDotWidth(defaults ...float64) float64 <span class="cov8" title="1">{
if s.DotWidth == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return DefaultDotWidth</span>
}
<span class="cov0" title="0">return s.DotWidth</span>
}
// GetStrokeDashArray returns the stroke dash array.
func (s Style) GetStrokeDashArray(defaults ...[]float64) []float64 <span class="cov8" title="1">{
if len(s.StrokeDashArray) == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return nil</span>
}
<span class="cov0" title="0">return s.StrokeDashArray</span>
}
// GetFontSize gets the font size.
func (s Style) GetFontSize(defaults ...float64) float64 <span class="cov8" title="1">{
if s.FontSize == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return DefaultFontSize</span>
}
<span class="cov8" title="1">return s.FontSize</span>
}
// GetFontColor gets the font size.
func (s Style) GetFontColor(defaults ...drawing.Color) drawing.Color <span class="cov8" title="1">{
if s.FontColor.IsZero() </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return drawing.ColorTransparent</span>
}
<span class="cov8" title="1">return s.FontColor</span>
}
// GetFont returns the font face.
func (s Style) GetFont(defaults ...*truetype.Font) *truetype.Font <span class="cov8" title="1">{
if s.Font == nil </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return nil</span>
}
<span class="cov8" title="1">return s.Font</span>
}
// GetPadding returns the padding.
func (s Style) GetPadding(defaults ...Box) Box <span class="cov8" title="1">{
if s.Padding.IsZero() </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return Box{}</span>
}
<span class="cov8" title="1">return s.Padding</span>
}
// GetTextHorizontalAlign returns the horizontal alignment.
func (s Style) GetTextHorizontalAlign(defaults ...TextHorizontalAlign) TextHorizontalAlign <span class="cov8" title="1">{
if s.TextHorizontalAlign == TextHorizontalAlignUnset </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return TextHorizontalAlignUnset</span>
}
<span class="cov8" title="1">return s.TextHorizontalAlign</span>
}
// GetTextVerticalAlign returns the vertical alignment.
func (s Style) GetTextVerticalAlign(defaults ...TextVerticalAlign) TextVerticalAlign <span class="cov8" title="1">{
if s.TextVerticalAlign == TextVerticalAlignUnset </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return TextVerticalAlignUnset</span>
}
<span class="cov8" title="1">return s.TextVerticalAlign</span>
}
// GetTextWrap returns the word wrap.
func (s Style) GetTextWrap(defaults ...TextWrap) TextWrap <span class="cov8" title="1">{
if s.TextWrap == TextWrapUnset </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov0" title="0">return TextWrapUnset</span>
}
<span class="cov0" title="0">return s.TextWrap</span>
}
// GetTextLineSpacing returns the spacing in pixels between lines of text (vertically).
func (s Style) GetTextLineSpacing(defaults ...int) int <span class="cov8" title="1">{
if s.TextLineSpacing == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
<span class="cov8" title="1">return DefaultLineSpacing</span>
}
<span class="cov0" title="0">return s.TextLineSpacing</span>
}
// GetTextRotationDegrees returns the text rotation in degrees.
func (s Style) GetTextRotationDegrees(defaults ...float64) float64 <span class="cov8" title="1">{
if s.TextRotationDegrees == 0 </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov8" title="1">{
return defaults[0]
}</span>
}
<span class="cov8" title="1">return s.TextRotationDegrees</span>
}
// WriteToRenderer passes the style's options to a renderer.
func (s Style) WriteToRenderer(r Renderer) <span class="cov8" title="1">{
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 </span><span class="cov0" title="0">{
r.SetTextRotation(util.Math.DegreesToRadians(s.GetTextRotationDegrees()))
}</span>
}
// WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer.
func (s Style) WriteDrawingOptionsToRenderer(r Renderer) <span class="cov8" title="1">{
r.SetClassName(s.GetClassName())
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray())
r.SetFillColor(s.GetFillColor())
}</span>
// WriteTextOptionsToRenderer passes just the text style options to a renderer.
func (s Style) WriteTextOptionsToRenderer(r Renderer) <span class="cov8" title="1">{
r.SetClassName(s.GetClassName())
r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize())
}</span>
// InheritFrom coalesces two styles into a new style.
func (s Style) InheritFrom(defaults Style) (final Style) <span class="cov8" title="1">{
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
}</span>
// GetStrokeOptions returns the stroke components.
func (s Style) GetStrokeOptions() Style <span class="cov8" title="1">{
return Style{
ClassName: s.ClassName,
StrokeDashArray: s.StrokeDashArray,
StrokeColor: s.StrokeColor,
StrokeWidth: s.StrokeWidth,
}
}</span>
// GetFillOptions returns the fill components.
func (s Style) GetFillOptions() Style <span class="cov8" title="1">{
return Style{
ClassName: s.ClassName,
FillColor: s.FillColor,
}
}</span>
// GetDotOptions returns the dot components.
func (s Style) GetDotOptions() Style <span class="cov0" title="0">{
return Style{
ClassName: s.ClassName,
StrokeDashArray: nil,
FillColor: s.DotColor,
StrokeColor: s.DotColor,
StrokeWidth: 1.0,
}
}</span>
// GetFillAndStrokeOptions returns the fill and stroke components.
func (s Style) GetFillAndStrokeOptions() Style <span class="cov8" title="1">{
return Style{
ClassName: s.ClassName,
StrokeDashArray: s.StrokeDashArray,
FillColor: s.FillColor,
StrokeColor: s.StrokeColor,
StrokeWidth: s.StrokeWidth,
}
}</span>
// GetTextOptions returns just the text components of the style.
func (s Style) GetTextOptions() Style <span class="cov8" title="1">{
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,
}
}</span>
// ShouldDrawStroke tells drawing functions if they should draw the stroke.
func (s Style) ShouldDrawStroke() bool <span class="cov8" title="1">{
return !s.StrokeColor.IsZero() &amp;&amp; s.StrokeWidth &gt; 0
}</span>
// ShouldDrawDot tells drawing functions if they should draw the dot.
func (s Style) ShouldDrawDot() bool <span class="cov8" title="1">{
return (!s.DotColor.IsZero() &amp;&amp; s.DotWidth &gt; 0) || s.DotColorProvider != nil || s.DotWidthProvider != nil
}</span>
// ShouldDrawFill tells drawing functions if they should draw the stroke.
func (s Style) ShouldDrawFill() bool <span class="cov8" title="1">{
return !s.FillColor.IsZero()
}</span>
</pre>
<pre class="file" id="file58" style="display: none">package chart
import (
"strings"
util "github.com/wcharczuk/go-chart/util"
)
// 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 veritcally 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 = &amp;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 <span class="cov8" title="1">{
switch style.TextWrap </span>{
case TextWrapRune:<span class="cov0" title="0">
return t.WrapFitRune(r, value, width, style)</span>
case TextWrapWord:<span class="cov8" title="1">
return t.WrapFitWord(r, value, width, style)</span>
}
<span class="cov0" title="0">return []string{value}</span>
}
func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []string <span class="cov8" title="1">{
style.WriteToRenderer(r)
var output []string
var line string
var word string
var textBox Box
for _, c := range value </span><span class="cov8" title="1">{
if c == rune('\n') </span><span class="cov8" title="1">{ // commit the line to output
output = append(output, t.Trim(line+word))
line = ""
word = ""
continue</span>
}
<span class="cov8" title="1">textBox = r.MeasureText(line + word + string(c))
if textBox.Width() &gt;= width </span><span class="cov8" title="1">{
output = append(output, t.Trim(line))
line = word
word = string(c)
continue</span>
}
<span class="cov8" title="1">if c == rune(' ') || c == rune('\t') </span><span class="cov8" title="1">{
line = line + word + string(c)
word = ""
continue</span>
}
<span class="cov8" title="1">word = word + string(c)</span>
}
<span class="cov8" title="1">return append(output, t.Trim(line+word))</span>
}
func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []string <span class="cov8" title="1">{
style.WriteToRenderer(r)
var output []string
var line string
var textBox Box
for _, c := range value </span><span class="cov8" title="1">{
if c == rune('\n') </span><span class="cov0" title="0">{
output = append(output, line)
line = ""
continue</span>
}
<span class="cov8" title="1">textBox = r.MeasureText(line + string(c))
if textBox.Width() &gt;= width </span><span class="cov8" title="1">{
output = append(output, line)
line = string(c)
continue</span>
}
<span class="cov8" title="1">line = line + string(c)</span>
}
<span class="cov8" title="1">return t.appendLast(output, line)</span>
}
func (t text) Trim(value string) string <span class="cov8" title="1">{
return strings.Trim(value, " \t\n\r")
}</span>
func (t text) MeasureLines(r Renderer, lines []string, style Style) Box <span class="cov8" title="1">{
style.WriteTextOptionsToRenderer(r)
var output Box
for index, line := range lines </span><span class="cov8" title="1">{
lineBox := r.MeasureText(line)
output.Right = util.Math.MaxInt(lineBox.Right, output.Right)
output.Bottom += lineBox.Height()
if index &lt; len(lines)-1 </span><span class="cov0" title="0">{
output.Bottom += +style.GetTextLineSpacing()
}</span>
}
<span class="cov8" title="1">return output</span>
}
func (t text) appendLast(lines []string, text string) []string <span class="cov8" title="1">{
if len(lines) == 0 </span><span class="cov0" title="0">{
return []string{text}
}</span>
<span class="cov8" title="1">lastLine := lines[len(lines)-1]
lines[len(lines)-1] = lastLine + text
return lines</span>
}
</pre>
<pre class="file" id="file59" style="display: none">package chart
import (
"fmt"
"math"
"strings"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return len(t)
}</span>
// Swap swaps two elements.
func (t Ticks) Swap(i, j int) <span class="cov0" title="0">{
t[i], t[j] = t[j], t[i]
}</span>
// Less returns if i's value is less than j's value.
func (t Ticks) Less(i, j int) bool <span class="cov0" title="0">{
return t[i].Value &lt; t[j].Value
}</span>
// String returns a string representation of the set of ticks.
func (t Ticks) String() string <span class="cov0" title="0">{
var values []string
for i, tick := range t </span><span class="cov0" title="0">{
values = append(values, fmt.Sprintf("[%d: %s]", i, tick.Label))
}</span>
<span class="cov0" title="0">return strings.Join(values, ", ")</span>
}
// GenerateContinuousTicks generates a set of ticks.
func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, vf ValueFormatter) []Tick <span class="cov8" title="1">{
if vf == nil </span><span class="cov0" title="0">{
vf = FloatValueFormatter
}</span>
<span class="cov8" title="1">var ticks []Tick
min, max := ra.GetMin(), ra.GetMax()
if ra.IsDescending() </span><span class="cov8" title="1">{
ticks = append(ticks, Tick{
Value: max,
Label: vf(max),
})
}</span> else<span class="cov8" title="1"> {
ticks = append(ticks, Tick{
Value: min,
Label: vf(min),
})
}</span>
<span class="cov8" title="1">minLabel := vf(min)
style.GetTextOptions().WriteToRenderer(r)
labelBox := r.MeasureText(minLabel)
var tickSize float64
if isVertical </span><span class="cov8" title="1">{
tickSize = float64(labelBox.Height() + DefaultMinimumTickVerticalSpacing)
}</span> else<span class="cov8" title="1"> {
tickSize = float64(labelBox.Width() + DefaultMinimumTickHorizontalSpacing)
}</span>
<span class="cov8" title="1">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 := util.Math.GetRoundToForDelta(rangeDelta) / 10
intermediateTickCount = util.Math.MinInt(intermediateTickCount, DefaultTickCountSanityCheck)
for x := 1; x &lt; intermediateTickCount; x++ </span><span class="cov8" title="1">{
var tickValue float64
if ra.IsDescending() </span><span class="cov8" title="1">{
tickValue = max - util.Math.RoundUp(tickStep*float64(x), roundTo)
}</span> else<span class="cov8" title="1"> {
tickValue = min + util.Math.RoundUp(tickStep*float64(x), roundTo)
}</span>
<span class="cov8" title="1">ticks = append(ticks, Tick{
Value: tickValue,
Label: vf(tickValue),
})</span>
}
<span class="cov8" title="1">if ra.IsDescending() </span><span class="cov8" title="1">{
ticks = append(ticks, Tick{
Value: min,
Label: vf(min),
})
}</span> else<span class="cov8" title="1"> {
ticks = append(ticks, Tick{
Value: max,
Label: vf(max),
})
}</span>
<span class="cov8" title="1">return ticks</span>
}
</pre>
<pre class="file" id="file60" style="display: none">package chart
import (
"fmt"
"time"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return ts.Name
}</span>
// GetStyle returns the line style.
func (ts TimeSeries) GetStyle() Style <span class="cov8" title="1">{
return ts.Style
}</span>
// Len returns the number of elements in the series.
func (ts TimeSeries) Len() int <span class="cov8" title="1">{
return len(ts.XValues)
}</span>
// GetValues gets x, y values at a given index.
func (ts TimeSeries) GetValues(index int) (x, y float64) <span class="cov8" title="1">{
x = util.Time.ToFloat64(ts.XValues[index])
y = ts.YValues[index]
return
}</span>
// GetFirstValues gets the first values.
func (ts TimeSeries) GetFirstValues() (x, y float64) <span class="cov0" title="0">{
x = util.Time.ToFloat64(ts.XValues[0])
y = ts.YValues[0]
return
}</span>
// GetLastValues gets the last values.
func (ts TimeSeries) GetLastValues() (x, y float64) <span class="cov0" title="0">{
x = util.Time.ToFloat64(ts.XValues[len(ts.XValues)-1])
y = ts.YValues[len(ts.YValues)-1]
return
}</span>
// GetValueFormatters returns value formatter defaults for the series.
func (ts TimeSeries) GetValueFormatters() (x, y ValueFormatter) <span class="cov8" title="1">{
x = TimeValueFormatter
y = FloatValueFormatter
return
}</span>
// GetYAxis returns which YAxis the series draws on.
func (ts TimeSeries) GetYAxis() YAxisType <span class="cov8" title="1">{
return ts.YAxis
}</span>
// Render renders the series.
func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) <span class="cov8" title="1">{
style := ts.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, ts)
}</span>
// Validate validates the series.
func (ts TimeSeries) Validate() error <span class="cov8" title="1">{
if len(ts.XValues) == 0 </span><span class="cov8" title="1">{
return fmt.Errorf("time series must have xvalues set")
}</span>
<span class="cov8" title="1">if len(ts.YValues) == 0 </span><span class="cov8" title="1">{
return fmt.Errorf("time series must have yvalues set")
}</span>
<span class="cov8" title="1">return nil</span>
}
</pre>
<pre class="file" id="file61" style="display: none">package util
import (
"time"
)
const (
// AllDaysMask is a bitmask of all the days of the week.
AllDaysMask = 1&lt;&lt;uint(time.Sunday) | 1&lt;&lt;uint(time.Monday) | 1&lt;&lt;uint(time.Tuesday) | 1&lt;&lt;uint(time.Wednesday) | 1&lt;&lt;uint(time.Thursday) | 1&lt;&lt;uint(time.Friday) | 1&lt;&lt;uint(time.Saturday)
// WeekDaysMask is a bitmask of all the weekdays of the week.
WeekDaysMask = 1&lt;&lt;uint(time.Monday) | 1&lt;&lt;uint(time.Tuesday) | 1&lt;&lt;uint(time.Wednesday) | 1&lt;&lt;uint(time.Thursday) | 1&lt;&lt;uint(time.Friday)
//WeekendDaysMask is a bitmask of the weekend days of the week.
WeekendDaysMask = 1&lt;&lt;uint(time.Sunday) | 1&lt;&lt;uint(time.Saturday)
)
var (
// DaysOfWeek are all the time.Weekday in an array for utility purposes.
DaysOfWeek = []time.Weekday{
time.Sunday,
time.Monday,
time.Tuesday,
time.Wednesday,
time.Thursday,
time.Friday,
time.Saturday,
}
// WeekDays are the business time.Weekday in an array.
WeekDays = []time.Weekday{
time.Monday,
time.Tuesday,
time.Wednesday,
time.Thursday,
time.Friday,
}
// WeekendDays are the weekend time.Weekday in an array.
WeekendDays = []time.Weekday{
time.Sunday,
time.Saturday,
}
//Epoch is unix epoc saved for utility purposes.
Epoch = time.Unix(0, 0)
)
// Date contains utility functions that operate on dates.
var Date date
type date struct{}
func (d date) MustEastern() *time.Location <span class="cov8" title="1">{
if eastern, err := d.Eastern(); err != nil </span><span class="cov0" title="0">{
panic(err)</span>
} else<span class="cov8" title="1"> {
return eastern
}</span>
}
// Eastern returns the eastern timezone.
func (d date) Eastern() (*time.Location, error) <span class="cov8" title="1">{
// Try POSIX
est, err := time.LoadLocation("America/New_York")
if err != nil </span><span class="cov0" title="0">{
// Try Windows
est, err = time.LoadLocation("EST")
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
}
<span class="cov8" title="1">return est, nil</span>
}
func (d date) MustPacific() *time.Location <span class="cov0" title="0">{
if pst, err := d.Pacific(); err != nil </span><span class="cov0" title="0">{
panic(err)</span>
} else<span class="cov0" title="0"> {
return pst
}</span>
}
// Pacific returns the pacific timezone.
func (d date) Pacific() (*time.Location, error) <span class="cov0" title="0">{
// Try POSIX
pst, err := time.LoadLocation("America/Los_Angeles")
if err != nil </span><span class="cov0" title="0">{
// Try Windows
pst, err = time.LoadLocation("PST")
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
}
<span class="cov0" title="0">return pst, nil</span>
}
// TimeUTC returns a new time.Time for the given clock components in UTC.
// It is meant to be used with the `OnDate` function.
func (d date) TimeUTC(hour, min, sec, nsec int) time.Time <span class="cov0" title="0">{
return time.Date(0, 0, 0, hour, min, sec, nsec, time.UTC)
}</span>
// Time returns a new time.Time for the given clock components.
// It is meant to be used with the `OnDate` function.
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time <span class="cov8" title="1">{
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
}</span>
// DateUTC returns a new time.Time for the given date comonents at (noon) in UTC.
func (d date) DateUTC(year, month, day int) time.Time <span class="cov0" title="0">{
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, time.UTC)
}</span>
// DateUTC returns a new time.Time for the given date comonents at (noon) in a given location.
func (d date) Date(year, month, day int, loc *time.Location) time.Time <span class="cov8" title="1">{
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
}</span>
// OnDate returns the clock components of clock (hour,minute,second) on the date components of d.
func (d date) OnDate(clock, date time.Time) time.Time <span class="cov8" title="1">{
tzAdjusted := date.In(clock.Location())
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
}</span>
// NoonOnDate is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
func (d date) NoonOnDate(cd time.Time) time.Time <span class="cov8" title="1">{
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
}</span>
// IsWeekDay returns if the day is a monday-&gt;friday.
func (d date) IsWeekDay(day time.Weekday) bool <span class="cov0" title="0">{
return !d.IsWeekendDay(day)
}</span>
// IsWeekendDay returns if the day is a monday-&gt;friday.
func (d date) IsWeekendDay(day time.Weekday) bool <span class="cov0" title="0">{
return day == time.Saturday || day == time.Sunday
}</span>
// Before returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
func (d date) Before(before, reference time.Time) bool <span class="cov8" title="1">{
tzAdjustedBefore := before.In(reference.Location())
if tzAdjustedBefore.Year() &lt; reference.Year() </span><span class="cov8" title="1">{
return true
}</span>
<span class="cov8" title="1">if tzAdjustedBefore.Month() &lt; reference.Month() </span><span class="cov8" title="1">{
return true
}</span>
<span class="cov8" title="1">return tzAdjustedBefore.Year() == reference.Year() &amp;&amp; tzAdjustedBefore.Month() == reference.Month() &amp;&amp; tzAdjustedBefore.Day() &lt; reference.Day()</span>
}
const (
_secondsPerHour = 60 * 60
_secondsPerDay = 60 * 60 * 24
)
// NextDay returns the timestamp advanced a day.
func (d date) NextDay(ts time.Time) time.Time <span class="cov0" title="0">{
return ts.AddDate(0, 0, 1)
}</span>
// NextHour returns the next timestamp on the hour.
func (d date) NextHour(ts time.Time) time.Time <span class="cov8" title="1">{
//advance a full hour ...
advanced := ts.Add(time.Hour)
minutes := time.Duration(advanced.Minute()) * time.Minute
final := advanced.Add(-minutes)
return time.Date(final.Year(), final.Month(), final.Day(), final.Hour(), 0, 0, 0, final.Location())
}</span>
// NextDayOfWeek returns the next instance of a given weekday after a given timestamp.
func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time <span class="cov8" title="1">{
afterWeekday := after.Weekday()
if afterWeekday == dayOfWeek </span><span class="cov8" title="1">{
return after.AddDate(0, 0, 7)
}</span>
// 1 vs 5 ~ add 4 days
<span class="cov8" title="1">if afterWeekday &lt; dayOfWeek </span><span class="cov8" title="1">{
dayDelta := int(dayOfWeek - afterWeekday)
return after.AddDate(0, 0, dayDelta)
}</span>
// 5 vs 1, add 7-(5-1) ~ 3 days
<span class="cov8" title="1">dayDelta := 7 - int(afterWeekday-dayOfWeek)
return after.AddDate(0, 0, dayDelta)</span>
}
</pre>
<pre class="file" id="file62" style="display: none">package util
import (
"bufio"
"io"
"os"
)
var (
// File contains file utility functions
File = fileUtil{}
)
type fileUtil struct{}
// ReadByLines reads a file and calls the handler for each line.
func (fu fileUtil) ReadByLines(filePath string, handler func(line string) error) error <span class="cov0" title="0">{
var f *os.File
var err error
if f, err = os.Open(filePath); err == nil </span><span class="cov0" title="0">{
defer f.Close()
var line string
scanner := bufio.NewScanner(f)
for scanner.Scan() </span><span class="cov0" title="0">{
line = scanner.Text()
err = handler(line)
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
}
}
<span class="cov0" title="0">return err</span>
}
// ReadByChunks reads a file in `chunkSize` pieces, dispatched to the handler.
func (fu fileUtil) ReadByChunks(filePath string, chunkSize int, handler func(line []byte) error) error <span class="cov0" title="0">{
var f *os.File
var err error
if f, err = os.Open(filePath); err == nil </span><span class="cov0" title="0">{
defer f.Close()
chunk := make([]byte, chunkSize)
for </span><span class="cov0" title="0">{
readBytes, err := f.Read(chunk)
if err == io.EOF </span><span class="cov0" title="0">{
break</span>
}
<span class="cov0" title="0">readData := chunk[:readBytes]
err = handler(readData)
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
}
}
<span class="cov0" title="0">return err</span>
}
</pre>
<pre class="file" id="file63" style="display: none">package util
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)
)
var (
// Math contains helper methods for common math operations.
Math = &amp;mathUtil{}
)
type mathUtil struct{}
// Max returns the maximum value of a group of floats.
func (m mathUtil) Max(values ...float64) float64 <span class="cov0" title="0">{
if len(values) == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov0" title="0">max := values[0]
for _, v := range values </span><span class="cov0" title="0">{
if max &lt; v </span><span class="cov0" title="0">{
max = v
}</span>
}
<span class="cov0" title="0">return max</span>
}
// MinAndMax returns both the min and max in one pass.
func (m mathUtil) MinAndMax(values ...float64) (min float64, max float64) <span class="cov8" title="1">{
if len(values) == 0 </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">min = values[0]
max = values[0]
for _, v := range values[1:] </span><span class="cov8" title="1">{
if max &lt; v </span><span class="cov8" title="1">{
max = v
}</span>
<span class="cov8" title="1">if min &gt; v </span><span class="cov8" title="1">{
min = v
}</span>
}
<span class="cov8" title="1">return</span>
}
// GetRoundToForDelta returns a `roundTo` value for a given delta.
func (m mathUtil) GetRoundToForDelta(delta float64) float64 <span class="cov8" title="1">{
startingDeltaBound := math.Pow(10.0, 10.0)
for cursor := startingDeltaBound; cursor &gt; 0; cursor /= 10.0 </span><span class="cov8" title="1">{
if delta &gt; cursor </span><span class="cov8" title="1">{
return cursor / 10.0
}</span>
}
<span class="cov0" title="0">return 0.0</span>
}
// RoundUp rounds up to a given roundTo value.
func (m mathUtil) RoundUp(value, roundTo float64) float64 <span class="cov8" title="1">{
if roundTo &lt; 0.000000000000001 </span><span class="cov8" title="1">{
return value
}</span>
<span class="cov8" title="1">d1 := math.Ceil(value / roundTo)
return d1 * roundTo</span>
}
// RoundDown rounds down to a given roundTo value.
func (m mathUtil) RoundDown(value, roundTo float64) float64 <span class="cov8" title="1">{
if roundTo &lt; 0.000000000000001 </span><span class="cov8" title="1">{
return value
}</span>
<span class="cov8" title="1">d1 := math.Floor(value / roundTo)
return d1 * roundTo</span>
}
// Normalize returns a set of numbers on the interval [0,1] for a given set of inputs.
// An example: 4,3,2,1 =&gt; 0.4, 0.3, 0.2, 0.1
// Caveat; the total may be &lt; 1.0; there are going to be issues with irrational numbers etc.
func (m mathUtil) Normalize(values ...float64) []float64 <span class="cov8" title="1">{
var total float64
for _, v := range values </span><span class="cov8" title="1">{
total += v
}</span>
<span class="cov8" title="1">output := make([]float64, len(values))
for x, v := range values </span><span class="cov8" title="1">{
output[x] = m.RoundDown(v/total, 0.0001)
}</span>
<span class="cov8" title="1">return output</span>
}
// MinInt returns the minimum of a set of integers.
func (m mathUtil) MinInt(values ...int) int <span class="cov0" title="0">{
min := math.MaxInt32
for _, v := range values </span><span class="cov0" title="0">{
if v &lt; min </span><span class="cov0" title="0">{
min = v
}</span>
}
<span class="cov0" title="0">return min</span>
}
// MaxInt returns the maximum of a set of integers.
func (m mathUtil) MaxInt(values ...int) int <span class="cov0" title="0">{
max := math.MinInt32
for _, v := range values </span><span class="cov0" title="0">{
if v &gt; max </span><span class="cov0" title="0">{
max = v
}</span>
}
<span class="cov0" title="0">return max</span>
}
// AbsInt returns the absolute value of an integer.
func (m mathUtil) AbsInt(value int) int <span class="cov0" title="0">{
if value &lt; 0 </span><span class="cov0" title="0">{
return -value
}</span>
<span class="cov0" title="0">return value</span>
}
// AbsInt64 returns the absolute value of a long.
func (m mathUtil) AbsInt64(value int64) int64 <span class="cov0" title="0">{
if value &lt; 0 </span><span class="cov0" title="0">{
return -value
}</span>
<span class="cov0" title="0">return value</span>
}
// Mean returns the mean of a set of values
func (m mathUtil) Mean(values ...float64) float64 <span class="cov0" title="0">{
return m.Sum(values...) / float64(len(values))
}</span>
// MeanInt returns the mean of a set of integer values.
func (m mathUtil) MeanInt(values ...int) int <span class="cov0" title="0">{
return m.SumInt(values...) / len(values)
}</span>
// Sum sums a set of values.
func (m mathUtil) Sum(values ...float64) float64 <span class="cov0" title="0">{
var total float64
for _, v := range values </span><span class="cov0" title="0">{
total += v
}</span>
<span class="cov0" title="0">return total</span>
}
// SumInt sums a set of values.
func (m mathUtil) SumInt(values ...int) int <span class="cov0" title="0">{
var total int
for _, v := range values </span><span class="cov0" title="0">{
total += v
}</span>
<span class="cov0" title="0">return total</span>
}
// PercentDifference computes the percentage difference between two values.
// The formula is (v2-v1)/v1.
func (m mathUtil) PercentDifference(v1, v2 float64) float64 <span class="cov8" title="1">{
if v1 == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">return (v2 - v1) / v1</span>
}
// DegreesToRadians returns degrees as radians.
func (m mathUtil) DegreesToRadians(degrees float64) float64 <span class="cov8" title="1">{
return degrees * _d2r
}</span>
// RadiansToDegrees translates a radian value to a degree value.
func (m mathUtil) RadiansToDegrees(value float64) float64 <span class="cov8" title="1">{
return math.Mod(value, _2pi) * _r2d
}</span>
// PercentToRadians converts a normalized value (0,1) to radians.
func (m mathUtil) PercentToRadians(pct float64) float64 <span class="cov8" title="1">{
return m.DegreesToRadians(360.0 * pct)
}</span>
// RadianAdd adds a delta to a base in radians.
func (m mathUtil) RadianAdd(base, delta float64) float64 <span class="cov8" title="1">{
value := base + delta
if value &gt; _2pi </span><span class="cov8" title="1">{
return math.Mod(value, _2pi)
}</span> else<span class="cov8" title="1"> if value &lt; 0 </span><span class="cov8" title="1">{
return math.Mod(_2pi+value, _2pi)
}</span>
<span class="cov8" title="1">return value</span>
}
// DegreesAdd adds a delta to a base in radians.
func (m mathUtil) DegreesAdd(baseDegrees, deltaDegrees float64) float64 <span class="cov0" title="0">{
value := baseDegrees + deltaDegrees
if value &gt; _2pi </span><span class="cov0" title="0">{
return math.Mod(value, 360.0)
}</span> else<span class="cov0" title="0"> if value &lt; 0 </span><span class="cov0" title="0">{
return math.Mod(360.0+value, 360.0)
}</span>
<span class="cov0" title="0">return value</span>
}
// DegreesToCompass returns the degree value in compass / clock orientation.
func (m mathUtil) DegreesToCompass(deg float64) float64 <span class="cov0" title="0">{
return m.DegreesAdd(deg, -90.0)
}</span>
// CirclePoint returns the absolute position of a circle diameter point given
// by the radius and the theta.
func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) <span class="cov0" title="0">{
x = cx + int(radius*math.Sin(thetaRadians))
y = cy - int(radius*math.Cos(thetaRadians))
return
}</span>
func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) <span class="cov8" title="1">{
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
}</span>
</pre>
<pre class="file" id="file64" style="display: none">package util
import "time"
var (
// Time contains time utility functions.
Time = timeUtil{}
)
type timeUtil struct{}
// Millis returns the duration as milliseconds.
func (tu timeUtil) Millis(d time.Duration) float64 <span class="cov0" title="0">{
return float64(d) / float64(time.Millisecond)
}</span>
// TimeToFloat64 returns a float64 representation of a time.
func (tu timeUtil) ToFloat64(t time.Time) float64 <span class="cov0" title="0">{
return float64(t.UnixNano())
}</span>
// Float64ToTime returns a time from a float64.
func (tu timeUtil) FromFloat64(tf float64) time.Time <span class="cov0" title="0">{
return time.Unix(0, int64(tf))
}</span>
func (tu timeUtil) DiffDays(t1, t2 time.Time) (days int) <span class="cov8" title="1">{
t1n := t1.Unix()
t2n := t2.Unix()
var diff int64
if t1n &gt; t2n </span><span class="cov0" title="0">{
diff = t1n - t2n //yields seconds
}</span> else<span class="cov8" title="1"> {
diff = t2n - t1n //yields seconds
}</span>
<span class="cov8" title="1">return int(diff / (_secondsPerDay))</span>
}
func (tu timeUtil) DiffHours(t1, t2 time.Time) (hours int) <span class="cov8" title="1">{
t1n := t1.Unix()
t2n := t2.Unix()
var diff int64
if t1n &gt; t2n </span><span class="cov0" title="0">{
diff = t1n - t2n
}</span> else<span class="cov8" title="1"> {
diff = t2n - t1n
}</span>
<span class="cov8" title="1">return int(diff / (_secondsPerHour))</span>
}
// Start returns the earliest (min) time in a list of times.
func (tu timeUtil) Start(times ...time.Time) time.Time <span class="cov0" title="0">{
if len(times) == 0 </span><span class="cov0" title="0">{
return time.Time{}
}</span>
<span class="cov0" title="0">start := times[0]
for _, t := range times[1:] </span><span class="cov0" title="0">{
if t.Before(start) </span><span class="cov0" title="0">{
start = t
}</span>
}
<span class="cov0" title="0">return start</span>
}
// Start returns the earliest (min) time in a list of times.
func (tu timeUtil) End(times ...time.Time) time.Time <span class="cov0" title="0">{
if len(times) == 0 </span><span class="cov0" title="0">{
return time.Time{}
}</span>
<span class="cov0" title="0">end := times[0]
for _, t := range times[1:] </span><span class="cov0" title="0">{
if t.After(end) </span><span class="cov0" title="0">{
end = t
}</span>
}
<span class="cov0" title="0">return end</span>
}
// StartAndEnd returns the start and end of a given set of time in one pass.
func (tu timeUtil) StartAndEnd(values ...time.Time) (start time.Time, end time.Time) <span class="cov8" title="1">{
if len(values) == 0 </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">start = values[0]
end = values[0]
for _, v := range values[1:] </span><span class="cov8" title="1">{
if end.Before(v) </span><span class="cov8" title="1">{
end = v
}</span>
<span class="cov8" title="1">if start.After(v) </span><span class="cov8" title="1">{
start = v
}</span>
}
<span class="cov8" title="1">return</span>
}
</pre>
<pre class="file" id="file65" style="display: none">package chart
import util "github.com/wcharczuk/go-chart/util"
// 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 <span class="cov8" title="1">{
values := make([]float64, len(vs))
for index, v := range vs </span><span class="cov8" title="1">{
values[index] = v.Value
}</span>
<span class="cov8" title="1">return values</span>
}
// ValuesNormalized returns normalized values.
func (vs Values) ValuesNormalized() []float64 <span class="cov8" title="1">{
return util.Math.Normalize(vs.Values()...)
}</span>
// Normalize returns the values normalized.
func (vs Values) Normalize() []Value <span class="cov8" title="1">{
var output []Value
var total float64
for _, v := range vs </span><span class="cov8" title="1">{
total += v.Value
}</span>
<span class="cov8" title="1">for _, v := range vs </span><span class="cov8" title="1">{
if v.Value &gt; 0 </span><span class="cov8" title="1">{
output = append(output, Value{
Style: v.Style,
Label: v.Label,
Value: util.Math.RoundDown(v.Value/total, 0.0001),
})
}</span>
}
<span class="cov8" title="1">return output</span>
}
// Value2 is a two axis value.
type Value2 struct {
Style Style
Label string
XValue, YValue float64
}
</pre>
<pre class="file" id="file66" style="display: none">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 <span class="cov8" title="1">{
return formatTime(v, DefaultDateFormat)
}</span>
// TimeHourValueFormatter is a ValueFormatter for timestamps.
func TimeHourValueFormatter(v interface{}) string <span class="cov0" title="0">{
return formatTime(v, DefaultDateHourFormat)
}</span>
// TimeMinuteValueFormatter is a ValueFormatter for timestamps.
func TimeMinuteValueFormatter(v interface{}) string <span class="cov0" title="0">{
return formatTime(v, DefaultDateMinuteFormat)
}</span>
// TimeDateValueFormatter is a ValueFormatter for timestamps.
func TimeDateValueFormatter(v interface{}) string <span class="cov0" title="0">{
return formatTime(v, "2006-01-02")
}</span>
// TimeValueFormatterWithFormat returns a time formatter with a given format.
func TimeValueFormatterWithFormat(format string) ValueFormatter <span class="cov0" title="0">{
return func(v interface{}) string </span><span class="cov0" title="0">{
return formatTime(v, format)
}</span>
}
// TimeValueFormatterWithFormat is a ValueFormatter for timestamps with a given format.
func formatTime(v interface{}, dateFormat string) string <span class="cov8" title="1">{
if typed, isTyped := v.(time.Time); isTyped </span><span class="cov8" title="1">{
return typed.Format(dateFormat)
}</span>
<span class="cov8" title="1">if typed, isTyped := v.(int64); isTyped </span><span class="cov0" title="0">{
return time.Unix(0, typed).Format(dateFormat)
}</span>
<span class="cov8" title="1">if typed, isTyped := v.(float64); isTyped </span><span class="cov8" title="1">{
return time.Unix(0, int64(typed)).Format(dateFormat)
}</span>
<span class="cov0" title="0">return ""</span>
}
// IntValueFormatter is a ValueFormatter for float64.
func IntValueFormatter(v interface{}) string <span class="cov0" title="0">{
switch v.(type) </span>{
case int:<span class="cov0" title="0">
return strconv.Itoa(v.(int))</span>
case int64:<span class="cov0" title="0">
return strconv.FormatInt(v.(int64), 10)</span>
case float32:<span class="cov0" title="0">
return strconv.FormatInt(int64(v.(float32)), 10)</span>
case float64:<span class="cov0" title="0">
return strconv.FormatInt(int64(v.(float64)), 10)</span>
default:<span class="cov0" title="0">
return ""</span>
}
}
// FloatValueFormatter is a ValueFormatter for float64.
func FloatValueFormatter(v interface{}) string <span class="cov8" title="1">{
return FloatValueFormatterWithFormat(v, DefaultFloatFormat)
}</span>
// PercentValueFormatter is a formatter for percent values.
// NOTE: it normalizes the values, i.e. multiplies by 100.0.
func PercentValueFormatter(v interface{}) string <span class="cov0" title="0">{
if typed, isTyped := v.(float64); isTyped </span><span class="cov0" title="0">{
return FloatValueFormatterWithFormat(typed*100.0, DefaultPercentValueFormat)
}</span>
<span class="cov0" title="0">return ""</span>
}
// FloatValueFormatterWithFormat is a ValueFormatter for float64 with a given format.
func FloatValueFormatterWithFormat(v interface{}, floatFormat string) string <span class="cov8" title="1">{
if typed, isTyped := v.(int); isTyped </span><span class="cov8" title="1">{
return fmt.Sprintf(floatFormat, float64(typed))
}</span>
<span class="cov8" title="1">if typed, isTyped := v.(int64); isTyped </span><span class="cov8" title="1">{
return fmt.Sprintf(floatFormat, float64(typed))
}</span>
<span class="cov8" title="1">if typed, isTyped := v.(float32); isTyped </span><span class="cov8" title="1">{
return fmt.Sprintf(floatFormat, typed)
}</span>
<span class="cov8" title="1">if typed, isTyped := v.(float64); isTyped </span><span class="cov8" title="1">{
return fmt.Sprintf(floatFormat, typed)
}</span>
<span class="cov0" title="0">return ""</span>
}
</pre>
<pre class="file" id="file67" style="display: none">package chart
import (
"bytes"
"fmt"
"io"
"math"
"strings"
"golang.org/x/image/font"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
)
// SVG returns a new png/raster renderer.
func SVG(width, height int) (Renderer, error) <span class="cov8" title="1">{
buffer := bytes.NewBuffer([]byte{})
canvas := newCanvas(buffer)
canvas.Start(width, height)
return &amp;vectorRenderer{
b: buffer,
c: canvas,
s: &amp;Style{},
p: []string{},
dpi: DefaultDPI,
}, nil
}</span>
// 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() <span class="cov0" title="0">{
vr.s = &amp;Style{Font: vr.s.Font}
vr.fc = nil
}</span>
// GetDPI returns the dpi.
func (vr *vectorRenderer) GetDPI() float64 <span class="cov0" title="0">{
return vr.dpi
}</span>
// SetDPI implements the interface method.
func (vr *vectorRenderer) SetDPI(dpi float64) <span class="cov8" title="1">{
vr.dpi = dpi
vr.c.dpi = dpi
}</span>
// SetClassName implements the interface method.
func (vr *vectorRenderer) SetClassName(classname string) <span class="cov0" title="0">{
vr.s.ClassName = classname
}</span>
// SetStrokeColor implements the interface method.
func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) <span class="cov0" title="0">{
vr.s.StrokeColor = c
}</span>
// SetFillColor implements the interface method.
func (vr *vectorRenderer) SetFillColor(c drawing.Color) <span class="cov0" title="0">{
vr.s.FillColor = c
}</span>
// SetLineWidth implements the interface method.
func (vr *vectorRenderer) SetStrokeWidth(width float64) <span class="cov0" title="0">{
vr.s.StrokeWidth = width
}</span>
// StrokeDashArray sets the stroke dash array.
func (vr *vectorRenderer) SetStrokeDashArray(dashArray []float64) <span class="cov0" title="0">{
vr.s.StrokeDashArray = dashArray
}</span>
// MoveTo implements the interface method.
func (vr *vectorRenderer) MoveTo(x, y int) <span class="cov8" title="1">{
vr.p = append(vr.p, fmt.Sprintf("M %d %d", x, y))
}</span>
// LineTo implements the interface method.
func (vr *vectorRenderer) LineTo(x, y int) <span class="cov8" title="1">{
vr.p = append(vr.p, fmt.Sprintf("L %d %d", x, y))
}</span>
// QuadCurveTo draws a quad curve.
func (vr *vectorRenderer) QuadCurveTo(cx, cy, x, y int) <span class="cov0" title="0">{
vr.p = append(vr.p, fmt.Sprintf("Q%d,%d %d,%d", cx, cy, x, y))
}</span>
func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) <span class="cov0" title="0">{
startAngle = util.Math.RadianAdd(startAngle, _pi2)
endAngle := util.Math.RadianAdd(startAngle, delta)
startx := cx + int(rx*math.Sin(startAngle))
starty := cy - int(ry*math.Cos(startAngle))
if len(vr.p) &gt; 0 </span><span class="cov0" title="0">{
vr.p = append(vr.p, fmt.Sprintf("L %d %d", startx, starty))
}</span> else<span class="cov0" title="0"> {
vr.p = append(vr.p, fmt.Sprintf("M %d %d", startx, starty))
}</span>
<span class="cov0" title="0">endx := cx + int(rx*math.Sin(endAngle))
endy := cy - int(ry*math.Cos(endAngle))
dd := util.Math.RadiansToDegrees(delta)
largeArcFlag := 0
if delta &gt; _pi </span><span class="cov0" title="0">{
largeArcFlag = 1
}</span>
<span class="cov0" title="0">vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f %d 1 %d %d", int(rx), int(ry), dd, largeArcFlag, endx, endy))</span>
}
// Close closes a shape.
func (vr *vectorRenderer) Close() <span class="cov8" title="1">{
vr.p = append(vr.p, fmt.Sprintf("Z"))
}</span>
// Stroke draws the path with no fill.
func (vr *vectorRenderer) Stroke() <span class="cov0" title="0">{
vr.drawPath(vr.s.GetStrokeOptions())
}</span>
// Fill draws the path with no stroke.
func (vr *vectorRenderer) Fill() <span class="cov0" title="0">{
vr.drawPath(vr.s.GetFillOptions())
}</span>
// FillStroke draws the path with both fill and stroke.
func (vr *vectorRenderer) FillStroke() <span class="cov8" title="1">{
vr.drawPath(vr.s.GetFillAndStrokeOptions())
}</span>
// drawPath draws a path.
func (vr *vectorRenderer) drawPath(s Style) <span class="cov8" title="1">{
vr.c.Path(strings.Join(vr.p, "\n"), vr.s.GetFillAndStrokeOptions())
vr.p = []string{} // clear the path
}</span>
// Circle implements the interface method.
func (vr *vectorRenderer) Circle(radius float64, x, y int) <span class="cov0" title="0">{
vr.c.Circle(x, y, int(radius), vr.s.GetFillAndStrokeOptions())
}</span>
// SetFont implements the interface method.
func (vr *vectorRenderer) SetFont(f *truetype.Font) <span class="cov8" title="1">{
vr.s.Font = f
}</span>
// SetFontColor implements the interface method.
func (vr *vectorRenderer) SetFontColor(c drawing.Color) <span class="cov0" title="0">{
vr.s.FontColor = c
}</span>
// SetFontSize implements the interface method.
func (vr *vectorRenderer) SetFontSize(size float64) <span class="cov8" title="1">{
vr.s.FontSize = size
}</span>
// Text draws a text blob.
func (vr *vectorRenderer) Text(body string, x, y int) <span class="cov0" title="0">{
vr.c.Text(x, y, body, vr.s.GetTextOptions())
}</span>
// MeasureText uses the truetype font drawer to measure the width of text.
func (vr *vectorRenderer) MeasureText(body string) (box Box) <span class="cov8" title="1">{
if vr.s.GetFont() != nil </span><span class="cov8" title="1">{
vr.fc = &amp;font.Drawer{
Face: truetype.NewFace(vr.s.GetFont(), &amp;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 </span><span class="cov8" title="1">{
return
}</span>
<span class="cov0" title="0">box = box.Corners().Rotate(util.Math.RadiansToDegrees(*vr.c.textTheta)).Box()</span>
}
<span class="cov0" title="0">return</span>
}
// SetTextRotation sets the text rotation.
func (vr *vectorRenderer) SetTextRotation(radians float64) <span class="cov0" title="0">{
vr.c.textTheta = &amp;radians
}</span>
// ClearTextRotation clears the text rotation.
func (vr *vectorRenderer) ClearTextRotation() <span class="cov0" title="0">{
vr.c.textTheta = nil
}</span>
// Save saves the renderer's contents to a writer.
func (vr *vectorRenderer) Save(w io.Writer) error <span class="cov8" title="1">{
vr.c.End()
_, err := w.Write(vr.b.Bytes())
return err
}</span>
func newCanvas(w io.Writer) *canvas <span class="cov8" title="1">{
return &amp;canvas{
w: w,
dpi: DefaultDPI,
}
}</span>
type canvas struct {
w io.Writer
dpi float64
textTheta *float64
width int
height int
}
func (c *canvas) Start(width, height int) <span class="cov8" title="1">{
c.width = width
c.height = height
c.w.Write([]byte(fmt.Sprintf(`&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d"&gt;\n`, c.width, c.height)))
}</span>
func (c *canvas) Path(d string, style Style) <span class="cov8" title="1">{
var strokeDashArrayProperty string
if len(style.StrokeDashArray) &gt; 0 </span><span class="cov0" title="0">{
strokeDashArrayProperty = c.getStrokeDashArray(style)
}</span>
<span class="cov8" title="1">c.w.Write([]byte(fmt.Sprintf(`&lt;path %s d="%s" %s/&gt;`, strokeDashArrayProperty, d, c.styleAsSVG(style))))</span>
}
func (c *canvas) Text(x, y int, body string, style Style) <span class="cov0" title="0">{
if c.textTheta == nil </span><span class="cov0" title="0">{
c.w.Write([]byte(fmt.Sprintf(`&lt;text x="%d" y="%d" %s&gt;%s&lt;/text&gt;`, x, y, c.styleAsSVG(style), body)))
}</span> else<span class="cov0" title="0"> {
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, util.Math.RadiansToDegrees(*c.textTheta), x, y)
c.w.Write([]byte(fmt.Sprintf(`&lt;text x="%d" y="%d" %s%s&gt;%s&lt;/text&gt;`, x, y, c.styleAsSVG(style), transform, body)))
}</span>
}
func (c *canvas) Circle(x, y, r int, style Style) <span class="cov0" title="0">{
c.w.Write([]byte(fmt.Sprintf(`&lt;circle cx="%d" cy="%d" r="%d" %s/&gt;`, x, y, r, c.styleAsSVG(style))))
}</span>
func (c *canvas) End() <span class="cov8" title="1">{
c.w.Write([]byte("&lt;/svg&gt;"))
}</span>
// getStrokeDashArray returns the stroke-dasharray property of a style.
func (c *canvas) getStrokeDashArray(s Style) string <span class="cov0" title="0">{
if len(s.StrokeDashArray) &gt; 0 </span><span class="cov0" title="0">{
var values []string
for _, v := range s.StrokeDashArray </span><span class="cov0" title="0">{
values = append(values, fmt.Sprintf("%0.1f", v))
}</span>
<span class="cov0" title="0">return "stroke-dasharray=\"" + strings.Join(values, ", ") + "\""</span>
}
<span class="cov0" title="0">return ""</span>
}
// GetFontFace returns the font face for the style.
func (c *canvas) getFontFace(s Style) string <span class="cov8" title="1">{
family := "sans-serif"
if s.GetFont() != nil </span><span class="cov8" title="1">{
name := s.GetFont().Name(truetype.NameIDFontFamily)
if len(name) != 0 </span><span class="cov8" title="1">{
family = fmt.Sprintf(`'%s',%s`, name, family)
}</span>
}
<span class="cov8" title="1">return fmt.Sprintf("font-family:%s", family)</span>
}
// styleAsSVG returns the style as a svg style or class string.
func (c *canvas) styleAsSVG(s Style) string <span class="cov8" title="1">{
if s.ClassName != "" </span><span class="cov8" title="1">{
return fmt.Sprintf("class=\"%s\"", s.ClassName)
}</span>
<span class="cov8" title="1">sw := s.StrokeWidth
sc := s.StrokeColor
fc := s.FillColor
fs := s.FontSize
fnc := s.FontColor
var pieces []string
if sw != 0 </span><span class="cov8" title="1">{
pieces = append(pieces, "stroke-width:"+fmt.Sprintf("%d", int(sw)))
}</span> else<span class="cov8" title="1"> {
pieces = append(pieces, "stroke-width:0")
}</span>
<span class="cov8" title="1">if !sc.IsZero() </span><span class="cov8" title="1">{
pieces = append(pieces, "stroke:"+sc.String())
}</span> else<span class="cov8" title="1"> {
pieces = append(pieces, "stroke:none")
}</span>
<span class="cov8" title="1">if !fnc.IsZero() </span><span class="cov8" title="1">{
pieces = append(pieces, "fill:"+fnc.String())
}</span> else<span class="cov8" title="1"> if !fc.IsZero() </span><span class="cov0" title="0">{
pieces = append(pieces, "fill:"+fc.String())
}</span> else<span class="cov8" title="1"> {
pieces = append(pieces, "fill:none")
}</span>
<span class="cov8" title="1">if fs != 0 </span><span class="cov0" title="0">{
pieces = append(pieces, "font-size:"+fmt.Sprintf("%.1fpx", drawing.PointsToPixels(c.dpi, fs)))
}</span>
<span class="cov8" title="1">if s.Font != nil </span><span class="cov8" title="1">{
pieces = append(pieces, c.getFontFace(s))
}</span>
<span class="cov8" title="1">return fmt.Sprintf("style=\"%s\"", strings.Join(pieces, ";"))</span>
}
</pre>
<pre class="file" id="file68" style="display: none">package chart
import "github.com/wcharczuk/go-chart/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 <span class="cov0" title="0">{
normalized := (v - vmin) / (vmax - vmin)
index := uint8(normalized * 255)
return viridisColors[index]
}</span>
</pre>
<pre class="file" id="file69" style="display: none">package chart
import (
"math"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return xa.Name
}</span>
// GetStyle returns the style.
func (xa XAxis) GetStyle() Style <span class="cov0" title="0">{
return xa.Style
}</span>
// GetValueFormatter returns the value formatter for the axis.
func (xa XAxis) GetValueFormatter() ValueFormatter <span class="cov0" title="0">{
if xa.ValueFormatter != nil </span><span class="cov0" title="0">{
return xa.ValueFormatter
}</span>
<span class="cov0" title="0">return FloatValueFormatter</span>
}
// GetTickPosition returns the tick position option for the axis.
func (xa XAxis) GetTickPosition(defaults ...TickPosition) TickPosition <span class="cov8" title="1">{
if xa.TickPosition == TickPositionUnset </span><span class="cov8" title="1">{
if len(defaults) &gt; 0 </span><span class="cov0" title="0">{
return defaults[0]
}</span>
<span class="cov8" title="1">return TickPositionUnderTick</span>
}
<span class="cov0" title="0">return xa.TickPosition</span>
}
// 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 <span class="cov8" title="1">{
if len(xa.Ticks) &gt; 0 </span><span class="cov8" title="1">{
return xa.Ticks
}</span>
<span class="cov8" title="1">if tp, isTickProvider := ra.(TicksProvider); isTickProvider </span><span class="cov0" title="0">{
return tp.GetTicks(r, defaults, vf)
}</span>
<span class="cov8" title="1">tickStyle := xa.Style.InheritFrom(defaults)
return GenerateContinuousTicks(r, ra, false, tickStyle, vf)</span>
}
// GetGridLines returns the gridlines for the axis.
func (xa XAxis) GetGridLines(ticks []Tick) []GridLine <span class="cov0" title="0">{
if len(xa.GridLines) &gt; 0 </span><span class="cov0" title="0">{
return xa.GridLines
}</span>
<span class="cov0" title="0">return GenerateGridLines(ticks, xa.GridMajorStyle, xa.GridMinorStyle)</span>
}
// Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
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 </span>{
case TickPositionUnderTick, TickPositionUnset:<span class="cov8" title="1">
ltx = tx - tb.Width()&gt;&gt;1
rtx = tx + tb.Width()&gt;&gt;1
break</span>
case TickPositionBetweenTicks:<span class="cov0" title="0">
if index &gt; 0 </span><span class="cov0" title="0">{
ltx = ra.Translate(ticks[index-1].Value)
rtx = tx
}</span>
<span class="cov0" title="0">break</span>
}
<span class="cov8" title="1">left = util.Math.MinInt(left, ltx)
right = util.Math.MaxInt(right, rtx)
bottom = util.Math.MaxInt(bottom, ty)</span>
}
<span class="cov8" title="1">if xa.NameStyle.Show &amp;&amp; len(xa.Name) &gt; 0 </span><span class="cov0" title="0">{
tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
bottom += DefaultXAxisMargin + tb.Height()
}</span>
<span class="cov8" title="1">return Box{
Top: canvasBox.Bottom,
Left: left,
Right: right,
Bottom: bottom,
}</span>
}
// Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
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 </span>{
case TickPositionUnderTick, TickPositionUnset:<span class="cov8" title="1">
if tickStyle.TextRotationDegrees == 0 </span><span class="cov8" title="1">{
tx = tx - tb.Width()&gt;&gt;1
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
}</span> else<span class="cov0" title="0"> {
ty = canvasBox.Bottom + (2 * DefaultXAxisMargin)
}</span>
<span class="cov8" title="1">Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
maxTextHeight = util.Math.MaxInt(maxTextHeight, tb.Height())
break</span>
case TickPositionBetweenTicks:<span class="cov0" title="0">
if index &gt; 0 </span><span class="cov0" title="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 = util.Math.MaxInt(maxTextHeight, ftb.Height())
}</span>
<span class="cov0" title="0">break</span>
}
}
<span class="cov8" title="1">nameStyle := xa.NameStyle.InheritFrom(defaults)
if xa.NameStyle.Show &amp;&amp; len(xa.Name) &gt; 0 </span><span class="cov0" title="0">{
tb := Draw.MeasureText(r, xa.Name, nameStyle)
tx := canvasBox.Right - (canvasBox.Width()&gt;&gt;1 + tb.Width()&gt;&gt;1)
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
Draw.Text(r, xa.Name, tx, ty, nameStyle)
}</span>
<span class="cov8" title="1">if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show </span><span class="cov0" title="0">{
for _, gl := range xa.GetGridLines(ticks) </span><span class="cov0" title="0">{
if (gl.IsMinor &amp;&amp; xa.GridMinorStyle.Show) || (!gl.IsMinor &amp;&amp; xa.GridMajorStyle.Show) </span><span class="cov0" title="0">{
defaults := xa.GridMajorStyle
if gl.IsMinor </span><span class="cov0" title="0">{
defaults = xa.GridMinorStyle
}</span>
<span class="cov0" title="0">gl.Render(r, canvasBox, ra, true, gl.Style.InheritFrom(defaults))</span>
}
}
}
}
</pre>
<pre class="file" id="file70" style="display: none">package chart
import (
"math"
util "github.com/wcharczuk/go-chart/util"
)
// 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 <span class="cov0" title="0">{
return ya.Name
}</span>
// GetNameStyle returns the name style.
func (ya YAxis) GetNameStyle() Style <span class="cov0" title="0">{
return ya.NameStyle
}</span>
// GetStyle returns the style.
func (ya YAxis) GetStyle() Style <span class="cov0" title="0">{
return ya.Style
}</span>
// GetValueFormatter returns the value formatter for the axis.
func (ya YAxis) GetValueFormatter() ValueFormatter <span class="cov0" title="0">{
if ya.ValueFormatter != nil </span><span class="cov0" title="0">{
return ya.ValueFormatter
}</span>
<span class="cov0" title="0">return FloatValueFormatter</span>
}
// GetTickStyle returns the tick style.
func (ya YAxis) GetTickStyle() Style <span class="cov0" title="0">{
return ya.TickStyle
}</span>
// 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 <span class="cov8" title="1">{
if len(ya.Ticks) &gt; 0 </span><span class="cov8" title="1">{
return ya.Ticks
}</span>
<span class="cov8" title="1">if tp, isTickProvider := ra.(TicksProvider); isTickProvider </span><span class="cov0" title="0">{
return tp.GetTicks(r, defaults, vf)
}</span>
<span class="cov8" title="1">tickStyle := ya.Style.InheritFrom(defaults)
return GenerateContinuousTicks(r, ra, true, tickStyle, vf)</span>
}
// GetGridLines returns the gridlines for the axis.
func (ya YAxis) GetGridLines(ticks []Tick) []GridLine <span class="cov0" title="0">{
if len(ya.GridLines) &gt; 0 </span><span class="cov0" title="0">{
return ya.GridLines
}</span>
<span class="cov0" title="0">return GenerateGridLines(ticks, ya.GridMajorStyle, ya.GridMinorStyle)</span>
}
// Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box <span class="cov8" title="1">{
var tx int
if ya.AxisType == YAxisPrimary </span><span class="cov8" title="1">{
tx = canvasBox.Right + DefaultYAxisMargin
}</span> else<span class="cov8" title="1"> if ya.AxisType == YAxisSecondary </span><span class="cov8" title="1">{
tx = canvasBox.Left - DefaultYAxisMargin
}</span>
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
tb := r.MeasureText(t.Label)
tbh2 := tb.Height() &gt;&gt; 1
finalTextX := tx
if ya.AxisType == YAxisSecondary </span><span class="cov8" title="1">{
finalTextX = tx - tb.Width()
}</span>
<span class="cov8" title="1">maxTextHeight = util.Math.MaxInt(tb.Height(), maxTextHeight)
if ya.AxisType == YAxisPrimary </span><span class="cov8" title="1">{
minx = canvasBox.Right
maxx = util.Math.MaxInt(maxx, tx+tb.Width())
}</span> else<span class="cov8" title="1"> if ya.AxisType == YAxisSecondary </span><span class="cov8" title="1">{
minx = util.Math.MinInt(minx, finalTextX)
maxx = util.Math.MaxInt(maxx, tx)
}</span>
<span class="cov8" title="1">miny = util.Math.MinInt(miny, ly-tbh2)
maxy = util.Math.MaxInt(maxy, ly+tbh2)</span>
}
<span class="cov8" title="1">if ya.NameStyle.Show &amp;&amp; len(ya.Name) &gt; 0 </span><span class="cov0" title="0">{
maxx += (DefaultYAxisMargin + maxTextHeight)
}</span>
<span class="cov8" title="1">return Box{
Top: miny,
Left: minx,
Right: maxx,
Bottom: maxy,
}</span>
}
// Render renders the axis.
func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
lx = canvasBox.Right + int(sw)
tx = lx + DefaultYAxisMargin
}</span> else<span class="cov0" title="0"> if ya.AxisType == YAxisSecondary </span><span class="cov0" title="0">{
lx = canvasBox.Left - int(sw)
tx = lx - DefaultYAxisMargin
}</span>
<span class="cov8" title="1">r.MoveTo(lx, canvasBox.Bottom)
r.LineTo(lx, canvasBox.Top)
r.Stroke()
var maxTextWidth int
var finalTextX, finalTextY int
for _, t := range ticks </span><span class="cov8" title="1">{
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
tb := Draw.MeasureText(r, t.Label, tickStyle)
if tb.Width() &gt; maxTextWidth </span><span class="cov8" title="1">{
maxTextWidth = tb.Width()
}</span>
<span class="cov8" title="1">if ya.AxisType == YAxisSecondary </span><span class="cov0" title="0">{
finalTextX = tx - tb.Width()
}</span> else<span class="cov8" title="1"> {
finalTextX = tx
}</span>
<span class="cov8" title="1">if tickStyle.TextRotationDegrees == 0 </span><span class="cov8" title="1">{
finalTextY = ly + tb.Height()&gt;&gt;1
}</span> else<span class="cov0" title="0"> {
finalTextY = ly
}</span>
<span class="cov8" title="1">tickStyle.WriteToRenderer(r)
r.MoveTo(lx, ly)
if ya.AxisType == YAxisPrimary </span><span class="cov8" title="1">{
r.LineTo(lx+DefaultHorizontalTickWidth, ly)
}</span> else<span class="cov0" title="0"> if ya.AxisType == YAxisSecondary </span><span class="cov0" title="0">{
r.LineTo(lx-DefaultHorizontalTickWidth, ly)
}</span>
<span class="cov8" title="1">r.Stroke()
Draw.Text(r, t.Label, finalTextX, finalTextY, tickStyle)</span>
}
<span class="cov8" title="1">nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
if ya.NameStyle.Show &amp;&amp; len(ya.Name) &gt; 0 </span><span class="cov0" title="0">{
nameStyle.GetTextOptions().WriteToRenderer(r)
tb := Draw.MeasureText(r, ya.Name, nameStyle)
var tx int
if ya.AxisType == YAxisPrimary </span><span class="cov0" title="0">{
tx = canvasBox.Right + int(sw) + DefaultYAxisMargin + maxTextWidth + DefaultYAxisMargin
}</span> else<span class="cov0" title="0"> if ya.AxisType == YAxisSecondary </span><span class="cov0" title="0">{
tx = canvasBox.Left - (DefaultYAxisMargin + int(sw) + maxTextWidth + DefaultYAxisMargin)
}</span>
<span class="cov0" title="0">var ty int
if nameStyle.TextRotationDegrees == 0 </span><span class="cov0" title="0">{
ty = canvasBox.Top + (canvasBox.Height()&gt;&gt;1 - tb.Width()&gt;&gt;1)
}</span> else<span class="cov0" title="0"> {
ty = canvasBox.Top + (canvasBox.Height()&gt;&gt;1 - tb.Height()&gt;&gt;1)
}</span>
<span class="cov0" title="0">Draw.Text(r, ya.Name, tx, ty, nameStyle)</span>
}
<span class="cov8" title="1">if ya.Zero.Style.Show </span><span class="cov0" title="0">{
ya.Zero.Render(r, canvasBox, ra, false, Style{})
}</span>
<span class="cov8" title="1">if ya.GridMajorStyle.Show || ya.GridMinorStyle.Show </span><span class="cov0" title="0">{
for _, gl := range ya.GetGridLines(ticks) </span><span class="cov0" title="0">{
if (gl.IsMinor &amp;&amp; ya.GridMinorStyle.Show) || (!gl.IsMinor &amp;&amp; ya.GridMajorStyle.Show) </span><span class="cov0" title="0">{
defaults := ya.GridMajorStyle
if gl.IsMinor </span><span class="cov0" title="0">{
defaults = ya.GridMinorStyle
}</span>
<span class="cov0" title="0">gl.Render(r, canvasBox, ra, false, gl.Style.InheritFrom(defaults))</span>
}
}
}
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>