11486 lines
447 KiB
HTML
11486 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 && !bc.YAxis.Range.IsZero() </span><span class="cov8" title="1">{
|
|
yrange = bc.YAxis.Range
|
|
}</span> else<span class="cov8" title="1"> {
|
|
yrange = &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) > 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 >> 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) > 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 < 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()>>1), axisStyle)
|
|
}</span>
|
|
|
|
}
|
|
}
|
|
|
|
func (bc BarChart) drawTitle(r Renderer) <span class="cov8" title="1">{
|
|
if len(bc.Title) > 0 && 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() >> 1) - (textWidth >> 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 > canvasBox.Width() </span><span class="cov8" title="1">{
|
|
lessBarWidths := canvasBox.Width() - (len(bc.Bars) * bc.GetBarWidth())
|
|
if lessBarWidths > 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 > canvasBox.Width() </span><span class="cov8" title="1">{
|
|
totalLessBarSpacings := canvasBox.Width() - (len(bc.Bars) * spacing)
|
|
if totalLessBarSpacings > 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) > 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 >= 2048 </span><span class="cov8" title="1">{
|
|
return 48
|
|
}</span> else<span class="cov8" title="1"> if effectiveDimension >= 1024 </span><span class="cov8" title="1">{
|
|
return 24
|
|
}</span> else<span class="cov8" title="1"> if effectiveDimension >= 512 </span><span class="cov8" title="1">{
|
|
return 18
|
|
}</span> else<span class="cov8" title="1"> if effectiveDimension >= 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) > 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() >= 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 < 0 </span><span class="cov0" title="0">{
|
|
startAt = 0
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">vb := seq.NewBufferWithCapacity(period)
|
|
for index := startAt; index < 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 && b.Left == 0 && b.Right == 0 && 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 && b.Top == 0 </span><span class="cov8" title="1">{
|
|
if len(defaults) > 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 && b.Left == 0 </span><span class="cov8" title="1">{
|
|
if len(defaults) > 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 && b.Right == 0 </span><span class="cov8" title="1">{
|
|
if len(defaults) > 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 && b.Bottom == 0 </span><span class="cov8" title="1">{
|
|
if len(defaults) > 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()>>1, b.Height()>>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 < other.Top ||
|
|
b.Bottom > other.Bottom ||
|
|
b.Left < other.Left ||
|
|
b.Right > 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 > other.Top &&
|
|
b.Bottom < other.Bottom &&
|
|
b.Left > other.Left &&
|
|
b.Right < 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 &&
|
|
b.Left == other.Left &&
|
|
b.Right == other.Right &&
|
|
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) >> 1
|
|
bh2 := int(bh) >> 1
|
|
if oa > ba </span><span class="cov8" title="1">{ // ex. 16:9 vs. 4:3
|
|
var noh2 int
|
|
if oa > 1.0 </span><span class="cov8" title="1">{
|
|
noh2 = int(bw/oa) >> 1
|
|
}</span> else<span class="cov0" title="0"> {
|
|
noh2 = int(bh*oa) >> 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 > 1.0 </span><span class="cov0" title="0">{
|
|
now2 = int(bh/oa) >> 1
|
|
}</span> else<span class="cov8" title="1"> {
|
|
now2 = int(bw*oa) >> 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 < 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 < 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 > 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 > 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) >> 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) >> 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) &&
|
|
bc.TopRight.Equals(other.TopRight) &&
|
|
bc.BottomRight.Equals(other.BottomRight) &&
|
|
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 && 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) > 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 < 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 < 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 = &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 = &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 = &ContinuousRange{}
|
|
}</span> else<span class="cov8" title="1"> {
|
|
yrangeAlt = c.YAxisSecondary.Range
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">if len(c.XAxis.Ticks) > 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) > 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) > 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 && 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) > 0 && 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() >> 1) - (textWidth >> 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 => 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 < 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)) &&
|
|
(r.Max == 0 || math.IsNaN(r.Max)) &&
|
|
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] => %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 = &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() && style.ShouldDrawFill() </span><span class="cov8" title="1">{
|
|
style.GetFillOptions().WriteDrawingOptionsToRenderer(r)
|
|
r.MoveTo(x0, y0)
|
|
for i := 1; i < 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 < 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 < 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) > 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 < 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 > 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 >= 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) > 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 < 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 >> 1),
|
|
Right: x + (barWidth >> 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 >> 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() >> 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()) >> 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()) >> 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 >> 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 << 8
|
|
g = uint32(float64(uint32(c.G)) * fa)
|
|
g |= g << 8
|
|
b = uint32(float64(uint32(c.B)) * fa)
|
|
b |= b << 8
|
|
a = uint32(c.A)
|
|
a |= a << 8
|
|
return
|
|
}</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 && c.G == 0 && c.B == 0 && 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 &&
|
|
c.G == other.G &&
|
|
c.B == other.B &&
|
|
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) >> 1,
|
|
G: (c.G + other.G) >> 1,
|
|
B: (c.B + other.B) >> 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 >= 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) < 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) < 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 >= 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 < 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 < 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 &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 < 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 >= 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 >= 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 < 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 < 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 < 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 && y0 == y1 </span><span class="cov0" title="0">{
|
|
return
|
|
}</span>
|
|
<span class="cov0" title="0">e2 = 2 * err
|
|
if e2 > -dy </span><span class="cov0" title="0">{
|
|
err = err - dy
|
|
x0 = x0 + sx
|
|
}</span>
|
|
<span class="cov0" title="0">if e2 < 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 < 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 > 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 < 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 < 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 < 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) && fequals(tr[5], 0) && 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) && fequals(tr[1], 0) && fequals(tr[2], 0) && 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) <= 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 < 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 < startAngle </span><span class="cov0" title="0">{
|
|
endAngle += math.Pi * 2.0
|
|
}</span>
|
|
} else<span class="cov0" title="0"> {
|
|
for startAngle < 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) > 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 &RasterGraphicContext{
|
|
NewStackGraphicContext(),
|
|
img,
|
|
painter,
|
|
raster.NewRasterizer(width, height),
|
|
raster.NewRasterizer(width, height),
|
|
&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 && len(rgc.current.Dash) > 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 && len(rgc.current.Dash) > 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 := &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) > 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) > 1 </span><span class="cov0" title="0">{
|
|
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
|
|
for i, j := 2, 3; j < 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 > 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) > 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&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 < 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 << 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 < 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) > 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) < 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 > 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 && ir.contents.Len() > 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 < vmin </span><span class="cov0" title="0">{
|
|
v = vmin
|
|
}</span>
|
|
<span class="cov0" title="0">if v > vmax </span><span class="cov0" title="0">{
|
|
v = vmax
|
|
}</span>
|
|
<span class="cov0" title="0">dv = vmax - vmin
|
|
|
|
if v < (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 < (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 < (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) > 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) > 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 < len(labels); x++ </span><span class="cov8" title="1">{
|
|
if len(labels[x]) > 0 </span><span class="cov8" title="1">{
|
|
tb := r.MeasureText(labels[x])
|
|
if labelCount > 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 < len(labels); x++ </span><span class="cov8" title="1">{
|
|
label = labels[x]
|
|
if len(label) > 0 </span><span class="cov8" title="1">{
|
|
if legendCount > 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() >> 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) > 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 < len(labels); x++ </span><span class="cov0" title="0">{
|
|
if len(labels[x]) > 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) >> 1
|
|
|
|
legendBox := Box{
|
|
Left: cb.Left,
|
|
Right: cb.Right,
|
|
Top: legendYMargin,
|
|
Bottom: legendYMargin + legendBoxHeight,
|
|
}
|
|
|
|
Draw.Box(r, legendBox, legendDefaults)
|
|
|
|
r.SetFont(legendStyle.GetFont())
|
|
r.SetFontColor(legendStyle.GetFontColor())
|
|
r.SetFontSize(legendStyle.GetFontSize())
|
|
|
|
lineTextGap := 5
|
|
lineLengthMinimum := 25
|
|
|
|
tx := legendBox.Left + legendStyle.Padding.Left
|
|
ty := legendYMargin + legendStyle.Padding.Top + textHeight
|
|
var label string
|
|
var lx, ly int
|
|
th2 := textHeight >> 1
|
|
for index := range labels </span><span class="cov0" title="0">{
|
|
label = labels[index]
|
|
if len(label) > 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) > 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 < len(labels); x++ </span><span class="cov0" title="0">{
|
|
if len(labels[x]) > 0 </span><span class="cov0" title="0">{
|
|
tb := r.MeasureText(labels[x])
|
|
if labelCount > 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 < len(labels); x++ </span><span class="cov0" title="0">{
|
|
label = labels[x]
|
|
if len(label) > 0 </span><span class="cov0" title="0">{
|
|
if legendCount > 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() >> 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 && 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 < 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 < 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 && 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 > 0 && ls.stdev > 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 = &MACDSignalSeries{
|
|
InnerSeries: macd.InnerSeries,
|
|
PrimaryPeriod: w1,
|
|
SecondaryPeriod: w2,
|
|
SignalPeriod: sig,
|
|
}
|
|
|
|
macd.macdl = &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 = &EMASeries{
|
|
InnerSeries: &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 && 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 = &EMASeries{
|
|
InnerSeries: macdl.InnerSeries,
|
|
Period: w1,
|
|
}
|
|
macdl.ema2 = &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 &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 &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 < 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 < (rows * cols); i++ </span><span class="cov8" title="1">{
|
|
ones[i] = 1
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">return &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 < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
for col := 0; col < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
for col := 0; col < 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 => 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 < rows; row++ </span><span class="cov0" title="0">{
|
|
for col := 0; col < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
for col := 0; col < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
a[row] = make([]float64, cols)
|
|
|
|
for col := 0; col < 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 < rows; i++ </span><span class="cov8" title="1">{
|
|
for j := 0; j < 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 < 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 < 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 &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 < 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 < 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 < 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 < mr; row++ </span><span class="cov0" title="0">{
|
|
for col := 0; col < 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 < 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 := &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 < 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 < 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 && 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 < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
for col := row; col < 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 < rows; row++ </span><span class="cov8" title="1">{
|
|
for col := 0; col < row && col < 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 = &Matrix{epsilon: m.epsilon, stride: m2.stride, elements: make([]float64, (len(m.elements)/m.stride)*m2.stride)}
|
|
for m1c0, m3x := 0, 0; m1c0 < len(m.elements); m1c0 += m.stride </span><span class="cov8" title="1">{
|
|
for m2r0 := 0; m2r0 < m2.stride; m2r0++ </span><span class="cov8" title="1">{
|
|
for m1x, m2x := m1c0, m2r0; m2x < 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 < m.stride; j++ </span><span class="cov8" title="1">{
|
|
row := j
|
|
max := m.elements[dx]
|
|
for i, ixcj := j, dx; i < m.stride; i++ </span><span class="cov8" title="1">{
|
|
if m.elements[ixcj] > 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 < 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 < m.stride; j++ </span><span class="cov8" title="1">{
|
|
l.elements[jxc0+j] = 1
|
|
for i, ixc0 := 0, 0; ixc0 <= jxc0; i++ </span><span class="cov8" title="1">{
|
|
sum := 0.
|
|
for k, kxcj := 0, j; k < 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 < len(m.elements); ixc0 += m.stride </span><span class="cov8" title="1">{
|
|
sum := 0.
|
|
for k, kxcj := 0, j; k < 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 < cols; k++ </span><span class="cov8" title="1">{
|
|
norm = 0
|
|
for i = k; i < 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) < 0 </span><span class="cov8" title="1">{
|
|
norm = -norm
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">for i = k; i < 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 < cols; j++ </span><span class="cov8" title="1">{
|
|
s = 0
|
|
for i = k; i < 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 < rows; i++ </span><span class="cov8" title="1">{
|
|
qr.Set(i, j, qr.Get(i, j)+s*qr.Get(i, k))
|
|
|
|
if i < 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 >= 0; k-- </span><span class="cov8" title="1">{
|
|
q.Set(k, k, 1.0)
|
|
for j = k; j < 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 < 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 < 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 < rows; i++ </span><span class="cov8" title="1">{
|
|
for j := 0; j < 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 < rows; i++ </span><span class="cov0" title="0">{
|
|
j := i
|
|
for k := i; k < rows; k++ </span><span class="cov0" title="0">{
|
|
if math.Abs(aug.Get(k, i)) > 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 < 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 < m; i++ </span><span class="cov8" title="1">{
|
|
ip := float64(1)
|
|
for j := 0; j < 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 >= 0; i-- </span><span class="cov8" title="1">{
|
|
c[i] = qty.Get(i, 0)
|
|
for j := i + 1; j < 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 < len(values); x++ </span><span class="cov8" title="1">{
|
|
if values[x] < 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 < len(values); x++ </span><span class="cov0" title="0">{
|
|
if values[x] > 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 < 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 < ms.InnerSeries.Len(); x++ </span><span class="cov0" title="0">{
|
|
_, y = ms.InnerSeries.GetValues(x)
|
|
if y < minValue </span><span class="cov0" title="0">{
|
|
minValue = y
|
|
}</span>
|
|
}
|
|
<span class="cov0" title="0">ms.minValue = &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 < ms.InnerSeries.Len(); x++ </span><span class="cov0" title="0">{
|
|
_, y = ms.InnerSeries.GetValues(x)
|
|
if y > maxValue </span><span class="cov0" title="0">{
|
|
maxValue = y
|
|
}</span>
|
|
}
|
|
<span class="cov0" title="0">ms.maxValue = &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) > 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) > 0 && 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 >> 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) > 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() >> 1)
|
|
ly = ly + (tb.Height() >> 1)
|
|
|
|
if lx < 0 </span><span class="cov0" title="0">{
|
|
lx = 0
|
|
}</span>
|
|
<span class="cov8" title="1">if ly < 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 >= 2048 </span><span class="cov0" title="0">{
|
|
return 48.0
|
|
}</span> else<span class="cov8" title="1"> if effectiveDimension >= 1024 </span><span class="cov8" title="1">{
|
|
return 24.0
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension > 512 </span><span class="cov0" title="0">{
|
|
return 18.0
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension > 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 >= 2048 </span><span class="cov0" title="0">{
|
|
return 48
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 1024 </span><span class="cov0" title="0">{
|
|
return 24
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 512 </span><span class="cov0" title="0">{
|
|
return 18
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 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 >= 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 < 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 &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 < 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 < 0 </span><span class="cov8" title="1">{
|
|
b = b - t
|
|
t = 0
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">if l > 0 </span><span class="cov8" title="1">{
|
|
r = r + l
|
|
l = 0
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">if t > 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 = &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) > 0 </span><span class="cov8" title="1">{
|
|
copy(array, values)
|
|
tail = len(values)
|
|
}</span>
|
|
<span class="cov8" title="1">return &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 &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 > 0 </span><span class="cov8" title="1">{
|
|
if b.head < 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 < (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 < 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 < 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 < b.tail </span><span class="cov0" title="0">{
|
|
for cursor := b.head; cursor < 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 < 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 < 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, " <= ")</span>
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Util methods
|
|
// --------------------------------------------------------------------------------
|
|
|
|
func arrayClear(source []float64, index, length int) <span class="cov0" title="0">{
|
|
for x := 0; x < 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 < 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 &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 < 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 < 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 &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 && r.max != nil </span><span class="cov0" title="0">{
|
|
var delta float64
|
|
|
|
if *r.max > *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 = &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 = &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 = &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 < 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 < 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 < 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 < 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 >= 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 < s.Len(); i++ </span><span class="cov0" title="0">{
|
|
value = s.GetValue(i)
|
|
if value < 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 < s.Len(); i++ </span><span class="cov0" title="0">{
|
|
value = s.GetValue(i)
|
|
if value > 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 < s.Len(); i++ </span><span class="cov8" title="1">{
|
|
value = s.GetValue(i)
|
|
if value < min </span><span class="cov0" title="0">{
|
|
min = value
|
|
}</span>
|
|
<span class="cov8" title="1">if value > 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 << 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 < 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 < 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 < 0 || percent > 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 < 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 >= 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 < 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 < 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 >= 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) > 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 >= 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) > 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() >> 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) > 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()>>1), axisStyle)
|
|
}</span>
|
|
|
|
}
|
|
}
|
|
|
|
func (sbc StackedBarChart) drawTitle(r Renderer) <span class="cov0" title="0">{
|
|
if len(sbc.Title) > 0 && 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() >> 1) - (textWidth >> 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) > 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 >= 2048 </span><span class="cov0" title="0">{
|
|
return 48
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 1024 </span><span class="cov0" title="0">{
|
|
return 24
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 512 </span><span class="cov0" title="0">{
|
|
return 18
|
|
}</span> else<span class="cov0" title="0"> if effectiveDimension >= 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() &&
|
|
s.StrokeWidth == 0 &&
|
|
s.DotColor.IsZero() &&
|
|
s.DotWidth == 0 &&
|
|
s.FillColor.IsZero() &&
|
|
s.FontColor.IsZero() &&
|
|
s.FontSize == 0 &&
|
|
s.Font == nil &&
|
|
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 >= 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) > 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 >= 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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) > 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() && s.StrokeWidth > 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() && s.DotWidth > 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 = &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() >= 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() >= 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 < 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 < 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 < 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<<uint(time.Sunday) | 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday) | 1<<uint(time.Saturday)
|
|
// WeekDaysMask is a bitmask of all the weekdays of the week.
|
|
WeekDaysMask = 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday)
|
|
//WeekendDaysMask is a bitmask of the weekend days of the week.
|
|
WeekendDaysMask = 1<<uint(time.Sunday) | 1<<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->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->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() < reference.Year() </span><span class="cov8" title="1">{
|
|
return true
|
|
}</span>
|
|
<span class="cov8" title="1">if tzAdjustedBefore.Month() < reference.Month() </span><span class="cov8" title="1">{
|
|
return true
|
|
}</span>
|
|
<span class="cov8" title="1">return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < 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 < 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 = &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 < 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 < v </span><span class="cov8" title="1">{
|
|
max = v
|
|
}</span>
|
|
<span class="cov8" title="1">if min > 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 > 0; cursor /= 10.0 </span><span class="cov8" title="1">{
|
|
if delta > 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 < 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 < 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 => 0.4, 0.3, 0.2, 0.1
|
|
// Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc.
|
|
func (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 < 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 > 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 < 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 < 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 > _2pi </span><span class="cov8" title="1">{
|
|
return math.Mod(value, _2pi)
|
|
}</span> else<span class="cov8" title="1"> if value < 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 > _2pi </span><span class="cov0" title="0">{
|
|
return math.Mod(value, 360.0)
|
|
}</span> else<span class="cov0" title="0"> if value < 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 > 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 > 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 > 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 &vectorRenderer{
|
|
b: buffer,
|
|
c: canvas,
|
|
s: &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 = &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) > 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 > _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 = &font.Drawer{
|
|
Face: truetype.NewFace(vr.s.GetFont(), &truetype.Options{
|
|
DPI: vr.dpi,
|
|
Size: vr.s.FontSize,
|
|
}),
|
|
}
|
|
w := vr.fc.MeasureString(body).Ceil()
|
|
|
|
box.Right = w
|
|
box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
|
|
if vr.c.textTheta == nil </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 = &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 &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(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d">\n`, c.width, c.height)))
|
|
}</span>
|
|
|
|
func (c *canvas) Path(d string, style Style) <span class="cov8" title="1">{
|
|
var strokeDashArrayProperty string
|
|
if len(style.StrokeDashArray) > 0 </span><span class="cov0" title="0">{
|
|
strokeDashArrayProperty = c.getStrokeDashArray(style)
|
|
}</span>
|
|
<span class="cov8" title="1">c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" %s/>`, 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(`<text x="%d" y="%d" %s>%s</text>`, 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(`<text x="%d" y="%d" %s%s>%s</text>`, 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(`<circle cx="%d" cy="%d" r="%d" %s/>`, x, y, r, c.styleAsSVG(style))))
|
|
}</span>
|
|
|
|
func (c *canvas) End() <span class="cov8" title="1">{
|
|
c.w.Write([]byte("</svg>"))
|
|
}</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) > 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) > 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) > 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) > 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()>>1
|
|
rtx = tx + tb.Width()>>1
|
|
break</span>
|
|
case TickPositionBetweenTicks:<span class="cov0" title="0">
|
|
if index > 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 && len(xa.Name) > 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()>>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 > 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 && len(xa.Name) > 0 </span><span class="cov0" title="0">{
|
|
tb := Draw.MeasureText(r, xa.Name, nameStyle)
|
|
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
|
|
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
|
|
Draw.Text(r, xa.Name, tx, ty, nameStyle)
|
|
}</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 && xa.GridMinorStyle.Show) || (!gl.IsMinor && 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) > 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) > 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() >> 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 && len(ya.Name) > 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() > 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()>>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 && len(ya.Name) > 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()>>1 - tb.Width()>>1)
|
|
}</span> else<span class="cov0" title="0"> {
|
|
ty = canvasBox.Top + (canvasBox.Height()>>1 - tb.Height()>>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 && ya.GridMinorStyle.Show) || (!gl.IsMinor && 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>
|