func GenericCircle(bg BasicGraphics, x, y, r int, style Style) {
	x0, y0 := x+r, y
	rf := float64(r)
	for a := 0.2; a < 2*math.Pi; a += 0.2 {
		x1, y1 := int(rf*math.Cos(a))+x, int(rf*math.Sin(a))+y
		bg.Line(x0, y0, x1, y1, style)
		x0, y0 = x1, y1
	}
}
GenericCircle approximates a circle of radius r around (x,y) with lines.
package chart
import (
	"fmt"
	"image/color"
	"math"
)
type MinimalGraphics interface {
	Background() (r, g, b, a uint8)                          
	FontMetrics(font Font) (fw float32, fh int, mono bool)   
	TextLen(t string, font Font) int                         
	Line(x0, y0, x1, y1 int, style Style)                    
	Text(x, y int, t string, align string, rot int, f Font)  
}
type BasicGraphics interface {
	MinimalGraphics
	Symbol(x, y int, style Style)                           
	Rect(x, y, w, h int, style Style)                       
	Wedge(x, y, ro, ri int, phi, psi float64, style Style)  
	Path(x, y []int, style Style)                           
	Options() PlotOptions                                   
}
type Graphics interface {
	BasicGraphics
	Dimensions() (int, int)  
	Begin()  
	End()    
	XAxis(xr Range, ys, yms int, options PlotOptions)  
	YAxis(yr Range, xs, xms int, options PlotOptions)  
	Scatter(points []EPoint, plotstyle PlotStyle, style Style)  
	Boxes(boxes []Box, width int, style Style)                  
	Bars(bars []Barinfo, style Style)                           
	Rings(wedeges []Wedgeinfo, x, y, ro, ri int)                
	Key(x, y int, key Key, options PlotOptions)  
}
type Barinfo struct {
	x, y  int     
	w, h  int     
	t, tp string  
	f     Font    
}
type Wedgeinfo struct {
	Phi, Psi float64  
	Text, Tp string   
	Style    Style    
	Font     Font     
	Shift    int      
}
func GenericTextLen(mg MinimalGraphics, t string, font Font) (width int) {
	fw, _, mono := mg.FontMetrics(font)
	if mono {
		for _ = range t {
			width++
		}
		width = int(float32(width)*fw + 0.5)
	} else {
		var length float32
		for _, r := range t {
			if w, ok := CharacterWidth[int(r)]; ok {
				length += w
			} else {
				length += 20  
			}
		}
		length /= averageCharacterWidth
		length *= fw
		width = int(length + 0.5)
	}
	return
}
func SanitizeRect(x, y, w, h, r int) (int, int, int, int) {
	if w < 0 {
		x += w
		w = -w
	}
	if h < 0 {
		y += h
		h = -h
	}
	d := (imax(1, r) - 1) / 2
	return x + d, y + d, w - 2*d, h - 2*d
}
func GenericRect(mg MinimalGraphics, x, y, w, h int, style Style) {
	x, y, w, h = SanitizeRect(x, y, w, h, style.LineWidth)
	if style.FillColor != nil {
		fs := Style{LineWidth: 1, LineColor: style.FillColor, LineStyle: SolidLine}
		for i := 1; i < h; i++ {
			mg.Line(x+1, y+i, x+w-1, y+i, fs)
		}
	}
	mg.Line(x, y, x+w, y, style)
	mg.Line(x+w, y, x+w, y+h, style)
	mg.Line(x+w, y+h, x, y+h, style)
	mg.Line(x, y+h, x, y, style)
}
func GenericPath(mg MinimalGraphics, x, y []int, style Style) {
	n := imin(len(x), len(y))
	for i := 1; i < n; i++ {
		mg.Line(x[i-1], y[i-1], x[i], y[i], style)
	}
}
func drawXTics(bg BasicGraphics, rng Range, y, ym, ticLen int, options PlotOptions) {
	xe := rng.Data2Screen(rng.Max)
	if rng.TicSetting.Grid > GridOff {
		for ticcnt, tic := range rng.Tics {
			x := rng.Data2Screen(tic.Pos)
			if ticcnt >= 0 && ticcnt <= len(rng.Tics)-1 && rng.TicSetting.Grid == GridLines {
				bg.Line(x, y-1, x, ym+1, elementStyle(options, GridLineElement))
			} else if rng.TicSetting.Grid == GridBlocks {
				if ticcnt%2 == 1 {
					x0 := rng.Data2Screen(rng.Tics[ticcnt-1].Pos)
					bg.Rect(x0, ym, x-x0, y-ym, elementStyle(options, GridBlockElement))
				} else if ticcnt == len(rng.Tics)-1 && x < xe-1 {
					bg.Rect(x, ym, xe-x, y-ym, elementStyle(options, GridBlockElement))
				}
			}
		}
	}
	ticstyle := elementStyle(options, MajorTicElement)
	ticfont := ticstyle.Font
	for _, tic := range rng.Tics {
		x := rng.Data2Screen(tic.Pos)
		lx := rng.Data2Screen(tic.LabelPos)
		switch rng.TicSetting.Tics {
		case 0:
			bg.Line(x, y-ticLen, x, y+ticLen, ticstyle)
		case 1:
			bg.Line(x, y-ticLen, x, y, ticstyle)
		case 2:
			bg.Line(x, y, x, y+ticLen, ticstyle)
		default:
		}
		if rng.TicSetting.Mirror >= 2 {
			switch rng.TicSetting.Tics {
			case 0:
				bg.Line(x, ym-ticLen, x, ym+ticLen, ticstyle)
			case 1:
				bg.Line(x, ym, x, ym+ticLen, ticstyle)
			case 2:
				bg.Line(x, ym-ticLen, x, ym, ticstyle)
			default:
			}
		}
		if !rng.TicSetting.HideLabels {
			if rng.Time && tic.Align == -1 {
				bg.Line(x, y+ticLen, x, y+2*ticLen, ticstyle)
				bg.Text(lx, y+2*ticLen, tic.Label, "tl", 0, ticfont)
			} else {
				bg.Text(lx, y+2*ticLen, tic.Label, "tc", 0, ticfont)
			}
		}
	}
}
func GenericXAxis(bg BasicGraphics, rng Range, y, ym int, options PlotOptions) {
	_, fontheight, _ := bg.FontMetrics(elementStyle(options, MajorTicElement).Font)
	var ticLen int = 0
	if !rng.TicSetting.Hide {
		ticLen = imin(12, imax(4, fontheight/2))
	}
	xa, xe := rng.Data2Screen(rng.Min), rng.Data2Screen(rng.Max)
	aly := y + 2*ticLen
	if !rng.TicSetting.Hide {
		aly += (3 * fontheight) / 2
	}
	if rng.ShowLimits {
		font := elementStyle(options, RangeLimitElement).Font
		if rng.Time {
			bg.Text(xa, aly, rng.TMin.Format("2006-01-02 15:04:05"), "tl", 0, font)
			bg.Text(xe, aly, rng.TMax.Format("2006-01-02 15:04:05"), "tr", 0, font)
		} else {
			bg.Text(xa, aly, fmt.Sprintf("%g", rng.Min), "tl", 0, font)
			bg.Text(xe, aly, fmt.Sprintf("%g", rng.Max), "tr", 0, font)
		}
	}
	if rng.Label != "" {  
		font := elementStyle(options, MajorAxisElement).Font
		bg.Text((xa+xe)/2, aly, "  "+rng.Label+"  ", "tc", 0, font)
	}
	if !rng.TicSetting.Hide {
		drawXTics(bg, rng, y, ym, ticLen, options)
	}
	bg.Line(xa, y, xe, y, elementStyle(options, MajorAxisElement))
	if rng.TicSetting.Mirror >= 1 {
		bg.Line(xa, ym, xe, ym, elementStyle(options, MinorAxisElement))
	}
	if rng.ShowZero && rng.Min < 0 && rng.Max > 0 {
		z := rng.Data2Screen(0)
		bg.Line(z, y, z, ym, elementStyle(options, ZeroAxisElement))
	}
}
func drawYTics(bg BasicGraphics, rng Range, x, xm, ticLen int, options PlotOptions) {
	ye := rng.Data2Screen(rng.Max)
	if rng.TicSetting.Grid > GridOff {
		for ticcnt, tic := range rng.Tics {
			y := rng.Data2Screen(tic.Pos)
			if rng.TicSetting.Grid == GridLines {
				if ticcnt > 0 && ticcnt < len(rng.Tics)-1 {
					bg.Line(x+1, y, xm-1, y, elementStyle(options, GridLineElement))
				}
			} else if rng.TicSetting.Grid == GridBlocks {
				if ticcnt%2 == 1 {
					y0 := rng.Data2Screen(rng.Tics[ticcnt-1].Pos)
					bg.Rect(x, y0, xm-x, y-y0, elementStyle(options, GridBlockElement))
				} else if ticcnt == len(rng.Tics)-1 && y > ye+1 {
					bg.Rect(x, ye, xm-x, y-ye, elementStyle(options, GridBlockElement))
				}
			}
		}
	}
	ticstyle := elementStyle(options, MajorTicElement)
	ticfont := ticstyle.Font
	for _, tic := range rng.Tics {
		y := rng.Data2Screen(tic.Pos)
		ly := rng.Data2Screen(tic.LabelPos)
		switch rng.TicSetting.Tics {
		case 0:
			bg.Line(x-ticLen, y, x+ticLen, y, ticstyle)
		case 1:
			bg.Line(x, y, x+ticLen, y, ticstyle)
		case 2:
			bg.Line(x-ticLen, y, x, y, ticstyle)
		default:
		}
		if rng.TicSetting.Mirror >= 2 {
			switch rng.TicSetting.Tics {
			case 0:
				bg.Line(xm-ticLen, y, xm+ticLen, y, ticstyle)
			case 1:
				bg.Line(xm-ticLen, y, xm, y, ticstyle)
			case 2:
				bg.Line(xm, y, xm+ticLen, y, ticstyle)
			default:
			}
		}
		if !rng.TicSetting.HideLabels {
			if rng.Time && tic.Align == 0 {  
				bg.Line(x-2*ticLen, y, x+ticLen, y, ticstyle)
				bg.Text(x-ticLen, ly, tic.Label, "cr", 0, ticfont)
			} else {
				bg.Text(x-2*ticLen, ly, tic.Label, "cr", 0, ticfont)
			}
		}
	}
}
func GenericYAxis(bg BasicGraphics, rng Range, x, xm int, options PlotOptions) {
	font := elementStyle(options, MajorAxisElement).Font
	_, fontheight, _ := bg.FontMetrics(font)
	var ticLen int = 0
	if !rng.TicSetting.Hide {
		ticLen = imin(10, imax(4, fontheight/2))
	}
	ya, ye := rng.Data2Screen(rng.Min), rng.Data2Screen(rng.Max)
	alx := 2 * fontheight
	if rng.ShowLimits {
	}
	if rng.Label != "" {
		y := (ya + ye) / 2
		bg.Text(alx, y, rng.Label, "bc", 90, font)
	}
	if !rng.TicSetting.Hide {
		drawYTics(bg, rng, x, xm, ticLen, options)
	}
	bg.Line(x, ya, x, ye, elementStyle(options, MajorAxisElement))
	if rng.TicSetting.Mirror >= 1 {
		bg.Line(xm, ya, xm, ye, elementStyle(options, MinorAxisElement))
	}
	if rng.ShowZero && rng.Min < 0 && rng.Max > 0 {
		z := rng.Data2Screen(0)
		bg.Line(x, z, xm, z, elementStyle(options, ZeroAxisElement))
	}
}
func GenericScatter(bg BasicGraphics, points []EPoint, plotstyle PlotStyle, style Style) {
	ebs := style
	ebs.LineColor, ebs.LineWidth, ebs.LineStyle = ebs.FillColor, 1, SolidLine
	if ebs.LineColor == nil {
		ebs.LineColor = color.NRGBA{0x40, 0x40, 0x40, 0xff}
	}
	if ebs.LineWidth == 0 {
		ebs.LineWidth = 1
	}
	for _, p := range points {
		xl, yl, xh, yh := p.BoundingBox()
		if !math.IsNaN(p.DeltaX) {
			bg.Line(int(xl), int(p.Y), int(xh), int(p.Y), ebs)
		}
		if !math.IsNaN(p.DeltaY) {
			bg.Line(int(p.X), int(yl), int(p.X), int(yh), ebs)
		}
	}
	if (plotstyle&PlotStyleLines) != 0 && len(points) > 0 {
		lastx, lasty := int(points[0].X), int(points[0].Y)
		for i := 1; i < len(points); i++ {
			x, y := int(points[i].X), int(points[i].Y)
			bg.Line(lastx, lasty, x, y, style)
			lastx, lasty = x, y
		}
	}
	if (plotstyle&PlotStylePoints) != 0 && len(points) != 0 {
		for _, p := range points {
			bg.Symbol(int(p.X), int(p.Y), style)
		}
	}
}
func GenericBoxes(bg BasicGraphics, boxes []Box, width int, style Style) {
	if width%2 == 0 {
		width++
	}
	hbw := (width - 1) / 2
	for _, d := range boxes {
		x := int(d.X)
		q1, q3 := int(d.Q1), int(d.Q3)
		bg.Rect(x-hbw, q1, width, q3-q1, style)
		if !math.IsNaN(d.Med) {
			med := int(d.Med)
			bg.Line(x-hbw, med, x+hbw, med, style)
		}
		if !math.IsNaN(d.Avg) {
			bg.Symbol(x, int(d.Avg), style)
		}
		if !math.IsNaN(d.High) {
			bg.Line(x, q3, x, int(d.High), style)
		}
		if !math.IsNaN(d.Low) {
			bg.Line(x, q1, x, int(d.Low), style)
		}
		for _, y := range d.Outliers {
			bg.Symbol(x, int(y), style)
		}
	}
}
func GenericBars(bg BasicGraphics, bars []Barinfo, style Style) {
	for _, b := range bars {
		bg.Rect(b.x, b.y, b.w, b.h, style)
		if b.t != "" {
			var tx, ty int
			var a string
			_, fh, _ := bg.FontMetrics(b.f)
			if fh > 1 {
				fh /= 2
			}
			switch b.tp {
			case "ot":
				tx, ty, a = b.x+b.w/2, b.y-fh, "bc"
			case "it":
				tx, ty, a = b.x+b.w/2, b.y+fh, "tc"
			case "ib":
				tx, ty, a = b.x+b.w/2, b.y+b.h-fh, "bc"
			case "ob":
				tx, ty, a = b.x+b.w/2, b.y+b.h+fh, "tc"
			case "ol":
				tx, ty, a = b.x-fh, b.y+b.h/2, "cr"
			case "il":
				tx, ty, a = b.x+fh, b.y+b.h/2, "cl"
			case "or":
				tx, ty, a = b.x+b.w+fh, b.y+b.h/2, "cl"
			case "ir":
				tx, ty, a = b.x+b.w-fh, b.y+b.h/2, "cr"
			default:
				tx, ty, a = b.x+b.w/2, b.y+b.h/2, "cc"
			}
			bg.Text(tx, ty, b.t, a, 0, b.f)
		}
	}
}
func GenericWedge(mg MinimalGraphics, x, y, ro, ri int, phi, psi, ecc float64, style Style) {
	for phi < 0 {
		phi += 2 * math.Pi
	}
	for psi < 0 {
		psi += 2 * math.Pi
	}
	for phi >= 2*math.Pi {
		phi -= 2 * math.Pi
	}
	for psi >= 2*math.Pi {
		psi -= 2 * math.Pi
	}
	if ri > ro {
		panic("ri > ro is not possible")
	}
	if style.FillColor != nil {
		fillWedge(mg, x, y, ro, ri, phi, psi, ecc, style)
	}
	roe, rof := float64(ro)*ecc, float64(ro)
	rie, rif := float64(ri)*ecc, float64(ri)
	xa, ya := int(math.Cos(phi)*roe)+x, y-int(math.Sin(phi)*rof)
	xc, yc := int(math.Cos(psi)*roe)+x, y-int(math.Sin(psi)*rof)
	xai, yai := int(math.Cos(phi)*rie)+x, y-int(math.Sin(phi)*rif)
	xci, yci := int(math.Cos(psi)*rie)+x, y-int(math.Sin(psi)*rif)
	if math.Abs(phi-psi) >= 4*math.Pi {
		phi, psi = 0, 2*math.Pi
	} else {
		if ri > 0 {
			mg.Line(xai, yai, xa, ya, style)
			mg.Line(xci, yci, xc, yc, style)
		} else {
			mg.Line(x, y, xa, ya, style)
			mg.Line(x, y, xc, yc, style)
		}
	}
	var xb, yb int
	exit := phi < psi
	for rho := phi; !exit || rho < psi; rho += 0.05 {  
		if rho >= 2*math.Pi {
			exit = true
			rho -= 2 * math.Pi
		}
		xb, yb = int(math.Cos(rho)*roe)+x, y-int(math.Sin(rho)*rof)
		mg.Line(xa, ya, xb, yb, style)
		xa, ya = xb, yb
	}
	mg.Line(xb, yb, xc, yc, style)
	if ri > 0 {
		exit := phi < psi
		for rho := phi; !exit || rho < psi; rho += 0.1 {  
			if rho >= 2*math.Pi {
				exit = true
				rho -= 2 * math.Pi
			}
			xb, yb = int(math.Cos(rho)*rie)+x, y-int(math.Sin(rho)*rif)
			mg.Line(xai, yai, xb, yb, style)
			xai, yai = xb, yb
		}
		mg.Line(xb, yb, xci, yci, style)
	}
}
func fillQuarterWedge(mg MinimalGraphics, xi, yi, ri int, alpha, beta, e float64, style Style, quadrant int) {
	if alpha < beta {
		alpha, beta = beta, alpha
	}
	r := float64(ri)
	ta, tb := math.Tan(alpha), math.Tan(beta)
	for y := int(r * math.Sin(alpha)); y >= 0; y-- {
		yf := float64(y)
		x0 := yf / ta
		x1 := yf / tb
		x2 := math.Sqrt(r*r - yf*yf)
		if math.IsNaN(x1) || x2 < x1 {
			x1 = x2
		}
		var xx0, xx1, yy int
		switch quadrant {
		case 0:
			xx0 = int(x0*e+0.5) + xi
			xx1 = int(x1*e-0.5) + xi
			yy = yi - y
		case 3:
			xx0 = int(x0*e+0.5) + xi
			xx1 = int(x1*e-0.5) + xi
			yy = yi + y
		case 2:
			xx0 = xi - int(x0*e+0.5)
			xx1 = xi - int(x1*e-0.5)
			yy = yi + y
		case 1:
			xx0 = xi - int(x0*e+0.5)
			xx1 = xi - int(x1*e-0.5)
			yy = yi - y
		default:
			panic("No such quadrant.")
		}
		mg.Line(xx0, yy, xx1, yy, style)
	}
}
func quadrant(w float64) int {
	return int(math.Floor(2 * w / math.Pi))
}
func mapQ(w float64, q int) float64 {
	switch q {
	case 0:
		return w
	case 1:
		return math.Pi - w
	case 2:
		return w - math.Pi
	case 3:
		return 2*math.Pi - w
	default:
		panic("No such quadrant")
	}
}
func fillWedge(mg MinimalGraphics, xi, yi, ro, ri int, phi, psi, epsilon float64, style Style) {
	qPhi := quadrant(phi)
	qPsi := quadrant(psi)
	style.LineColor = style.FillColor
	style.LineWidth = 1
	style.LineStyle = SolidLine
	blank := Style{
		Symbol:    ' ',
		LineColor: color.NRGBA{0xff, 0xff, 0xff, 0x00},
		FillColor: color.NRGBA{0xff, 0xff, 0xff, 0x00},
	}
	for qPhi != qPsi {
		w := float64(qPhi+1) * math.Pi / 2
		if math.Abs(w-phi) > 0.01 {
			fillQuarterWedge(mg, xi, yi, ro, mapQ(phi, qPhi), mapQ(w, qPhi), epsilon, style, qPhi)
			if ri > 0 {
				fillQuarterWedge(mg, xi, yi, ri, mapQ(phi, qPhi), mapQ(w, qPhi), epsilon, blank, qPhi)
			}
		}
		phi = w
		qPhi++
		if qPhi == 4 {
			phi, qPhi = 0, 0
		}
	}
	if phi != psi {
		fillQuarterWedge(mg, xi, yi, ro, mapQ(phi, qPhi), mapQ(psi, qPhi), epsilon, style, qPhi)
		if ri > 0 {
			fillQuarterWedge(mg, xi, yi, ri, mapQ(phi, qPhi), mapQ(psi, qPhi), epsilon, blank, qPhi)
		}
	}
}
func GenericRings(bg BasicGraphics, wedges []Wedgeinfo, x, y, ro, ri int, eccentricity float64) {
	for _, w := range wedges {
		d := float64(w.Style.LineWidth) / 2
		delta := (w.Psi - w.Phi) / 2
		SinDelta := math.Sin(delta)
		gamma := (w.Phi + w.Psi) / 2
		k := d / SinDelta
		shift := float64(w.Shift)
		kx, ky := (k+shift)*math.Cos(gamma), (k+shift)*math.Sin(gamma)
		DebugLogger.Printf("Center adjustment (lw=%d, d=%.2f), for wedge %d°-%d° of (%.1f,%.1f), k=%.1f",
			w.Style.LineWidth, d, int(180*w.Phi/math.Pi), int(180*w.Psi/math.Pi), kx, ky, k)
		xi, yi := x+int(kx+0.5), y+int(ky+0.5)
		roc, ric := ro-int(d+k), ri-int(d+k)
		bg.Wedge(xi, yi, roc, ric, w.Phi, w.Psi, w.Style)
		if w.Text != "" {
			_, fh, _ := bg.FontMetrics(w.Font)
			fh += 0
			alpha := (w.Phi + w.Psi) / 2
			var rt int
			if ri > 0 {
				rt = (ri + ro) / 2
			} else {
				rt = ro - 3*fh
				if rt <= ro/2 {
					rt = ro - 2*fh
				}
			}
			tx := int(float64(rt)*math.Cos(alpha)*eccentricity+0.5) + x
			ty := y + int(float64(rt)*math.Sin(alpha)+0.5)
			bg.Text(tx, ty, w.Text, "cc", 0, w.Font)
		}
	}
}