From 4201c7d4393caec7f6c15725ead9602274e41d41 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 24 May 2022 23:25:08 +0800 Subject: [PATCH] chore: support axias ticks render --- painter.go | 217 +++++++++++++++++++++++++++++++++++------------- painter_test.go | 2 +- util.go | 15 ++-- 3 files changed, 165 insertions(+), 69 deletions(-) diff --git a/painter.go b/painter.go index 47c494f..971a028 100644 --- a/painter.go +++ b/painter.go @@ -32,13 +32,12 @@ import ( ) type Painter struct { - render Renderer - box Box - font *truetype.Font - parent *Painter - style Style - previousStyle Style - theme ColorPalette + render Renderer + box Box + font *truetype.Font + parent *Painter + style Style + theme ColorPalette } type PainterOptions struct { @@ -54,6 +53,12 @@ type PainterOptions struct { type PainterOption func(*Painter) +type TicksOption struct { + Length int + Orient string + Count int +} + // PainterPaddingOption sets the padding of draw painter func PainterPaddingOption(padding Box) PainterOption { return func(p *Painter) { @@ -87,7 +92,7 @@ func PainterFontOption(font *truetype.Font) PainterOption { // PainterStyleOption sets the style of draw painter func PainterStyleOption(style Style) PainterOption { return func(p *Painter) { - p.SetDrawingStyle(style) + p.SetStyle(style) } } @@ -156,13 +161,12 @@ func (p *Painter) setOptions(opts ...PainterOption) { func (p *Painter) Child(opt ...PainterOption) *Painter { child := &Painter{ - render: p.render, - box: p.box.Clone(), - font: p.font, - parent: p, - style: p.style, - previousStyle: p.previousStyle, - theme: p.theme, + render: p.render, + box: p.box.Clone(), + font: p.font, + parent: p, + style: p.style, + theme: p.theme, } child.setOptions(opt...) return child @@ -172,29 +176,65 @@ func (p *Painter) SetStyle(style Style) { if style.Font == nil { style.Font = p.font } - p.previousStyle = p.style p.style = style style.WriteToRenderer(p.render) } -func (p *Painter) SetDrawingStyle(style Style) { - p.previousStyle = p.style - p.style = style - style.WriteDrawingOptionsToRenderer(p.render) -} - -func (p *Painter) SetTextStyle(style Style) { - if style.Font == nil { - style.Font = p.font +func overrideStyle(defaultStyle Style, style Style) Style { + if style.StrokeWidth == 0 { + style.StrokeWidth = defaultStyle.StrokeWidth } - p.previousStyle = p.style - p.style = style - style.WriteTextOptionsToRenderer(p.render) + if style.StrokeColor.IsZero() { + style.StrokeColor = defaultStyle.StrokeColor + } + if style.StrokeDashArray == nil { + style.StrokeDashArray = defaultStyle.StrokeDashArray + } + if style.DotColor.IsZero() { + style.DotColor = defaultStyle.DotColor + } + if style.DotWidth == 0 { + style.DotWidth = defaultStyle.DotWidth + } + if style.FillColor.IsZero() { + style.FillColor = defaultStyle.FillColor + } + if style.FontSize == 0 { + style.FontSize = defaultStyle.FontSize + } + if style.FontColor.IsZero() { + style.FontColor = defaultStyle.FontColor + } + if style.Font == nil { + style.Font = defaultStyle.Font + } + return style } -func (p *Painter) RestoreStyle() { - p.style = p.previousStyle +func (p *Painter) OverrideDrawingStyle(style Style) *Painter { + s := overrideStyle(p.style, style) + p.SetDrawingStyle(s) + return p +} + +func (p *Painter) SetDrawingStyle(style Style) *Painter { + style.WriteDrawingOptionsToRenderer(p.render) + return p +} + +func (p *Painter) SetTextStyle(style Style) *Painter { + style.WriteTextOptionsToRenderer(p.render) + return p +} +func (p *Painter) OverrideTextStyle(style Style) *Painter { + s := overrideStyle(p.style, style) + p.SetTextStyle(s) + return p +} + +func (p *Painter) ResetStyle() *Painter { p.style.WriteToRenderer(p.render) + return p } // Bytes returns the data of draw canvas @@ -208,19 +248,22 @@ func (p *Painter) Bytes() ([]byte, error) { } // MoveTo moves the cursor to a given point -func (p *Painter) MoveTo(x, y int) { +func (p *Painter) MoveTo(x, y int) *Painter { p.render.MoveTo(x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) { +func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) *Painter { p.render.ArcTo(cx+p.box.Left, cy+p.box.Top, rx, ry, startAngle, delta) + return p } -func (p *Painter) LineTo(x, y int) { +func (p *Painter) LineTo(x, y int) *Painter { p.render.LineTo(x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) Pin(x, y, width int) { +func (p *Painter) Pin(x, y, width int) *Painter { r := float64(width) / 2 y -= width / 4 angle := chart.DegreesToRadians(15) @@ -246,9 +289,10 @@ func (p *Painter) Pin(x, y, width int) { p.render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top) p.Close() p.Fill() + return p } -func (p *Painter) arrow(x, y, width, height int, direction string) { +func (p *Painter) arrow(x, y, width, height int, direction string) *Painter { halfWidth := width >> 1 halfHeight := height >> 1 if direction == PositionTop || direction == PositionBottom { @@ -284,41 +328,51 @@ func (p *Painter) arrow(x, y, width, height int, direction string) { p.LineTo(x0, y0) } p.FillStroke() + return p } -func (p *Painter) ArrowLeft(x, y, width, height int) { +func (p *Painter) ArrowLeft(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionLeft) + return p } -func (p *Painter) ArrowRight(x, y, width, height int) { +func (p *Painter) ArrowRight(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionRight) + return p } -func (p *Painter) ArrowTop(x, y, width, height int) { +func (p *Painter) ArrowTop(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionTop) + return p } -func (p *Painter) ArrowBottom(x, y, width, height int) { +func (p *Painter) ArrowBottom(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionBottom) + return p } -func (p *Painter) Circle(radius float64, x, y int) { +func (p *Painter) Circle(radius float64, x, y int) *Painter { p.render.Circle(radius, x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) Stroke() { +func (p *Painter) Stroke() *Painter { p.render.Stroke() + return p } -func (p *Painter) Close() { +func (p *Painter) Close() *Painter { p.render.Close() + return p } -func (p *Painter) FillStroke() { +func (p *Painter) FillStroke() *Painter { p.render.FillStroke() + return p } -func (p *Painter) Fill() { +func (p *Painter) Fill() *Painter { p.render.Fill() + return p } func (p *Painter) Width() int { @@ -333,11 +387,7 @@ func (p *Painter) MeasureText(text string) Box { return p.render.MeasureText(text) } -func (p *Painter) SetStrokeColor(color Color) { - p.render.SetStrokeColor(color) -} - -func (p *Painter) LineStroke(points []Point) { +func (p *Painter) LineStroke(points []Point) *Painter { for index, point := range points { x := point.X y := point.Y @@ -348,16 +398,17 @@ func (p *Painter) LineStroke(points []Point) { } } p.Stroke() + return p } -func (p *Painter) SetBackground(width, height int, color Color) { +func (p *Painter) SetBackground(width, height int, color Color) *Painter { r := p.render s := chart.Style{ FillColor: color, } // 背景色 - p.SetStyle(s) - defer p.RestoreStyle() + p.SetDrawingStyle(s) + defer p.ResetStyle() // 设置背景色不使用box,因此不直接使用Painter r.MoveTo(0, 0) r.LineTo(width, 0) @@ -365,21 +416,23 @@ func (p *Painter) SetBackground(width, height int, color Color) { r.LineTo(0, height) r.LineTo(0, 0) p.FillStroke() + return p } -func (p *Painter) MarkLine(x, y, width int) { +func (p *Painter) MarkLine(x, y, width int) *Painter { arrowWidth := 16 arrowHeight := 10 endX := x + width - p.Circle(3, x, y) + radius := 3 + p.Circle(3, x+radius, y) p.render.Fill() - p.MoveTo(x+5, y) + p.MoveTo(x+radius*3, y) p.LineTo(endX-arrowWidth, y) p.Stroke() - p.render.SetStrokeDashArray([]float64{}) p.ArrowRight(endX, y, arrowWidth, arrowHeight) + return p } -func (p *Painter) Polygon(center Point, radius float64, sides int) { +func (p *Painter) Polygon(center Point, radius float64, sides int) *Painter { points := getPolygonPoints(center, radius, sides) for i, item := range points { if i == 0 { @@ -390,9 +443,10 @@ func (p *Painter) Polygon(center Point, radius float64, sides int) { } p.LineTo(points[0].X, points[0].Y) p.Stroke() + return p } -func (p *Painter) FillArea(points []Point) { +func (p *Painter) FillArea(points []Point) *Painter { var x, y int for index, point := range points { x = point.X @@ -404,10 +458,12 @@ func (p *Painter) FillArea(points []Point) { } } p.Fill() + return p } -func (p *Painter) Text(body string, x, y int) { +func (p *Painter) Text(body string, x, y int) *Painter { p.render.Text(body, x+p.box.Left, y+p.box.Top) + return p } func (p *Painter) TextFit(body string, x, y, width int) chart.Box { @@ -433,3 +489,46 @@ func (p *Painter) TextFit(body string, x, y, width int) chart.Box { p.style.TextWrap = textWarp return output } + +func (p *Painter) Ticks(opt TicksOption) *Painter { + if opt.Count <= 0 || opt.Length <= 0 { + return p + } + count := opt.Count - 1 + width := p.Width() + height := p.Height() + var values []int + if opt.Orient == OrientHorizontal { + values = autoDivide(height, count) + } else { + values = autoDivide(width, count) + } + + for _, value := range values { + if opt.Orient == OrientVertical { + p.LineStroke([]Point{ + { + X: 0, + Y: value, + }, + { + X: opt.Length, + Y: value, + }, + }) + } else { + p.LineStroke([]Point{ + { + X: value, + Y: opt.Length, + }, + { + X: value, + Y: 0, + }, + }) + } + } + + return p +} diff --git a/painter_test.go b/painter_test.go index 34c0c05..c847aff 100644 --- a/painter_test.go +++ b/painter_test.go @@ -271,7 +271,7 @@ func TestPainter(t *testing.T) { }) p.MarkLine(0, 20, 300) }, - result: "\\n", + result: "\\n", }, // polygon { diff --git a/util.go b/util.go index 5fee163..7306919 100644 --- a/util.go +++ b/util.go @@ -59,19 +59,16 @@ func getDefaultInt(value, defaultValue int) int { } func autoDivide(max, size int) []int { - unit := max / size + unit := float64(max) / float64(size) - rest := max - unit*size values := make([]int, size+1) - value := 0 - for i := 0; i < size; i++ { - values[i] = value - if i < rest { - value++ + for i := 0; i < size+1; i++ { + if i == size { + values[i] = max + } else { + values[i] = int(float64(i) * unit) } - value += unit } - values[size] = max return values }