<!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>