chore: support axias ticks render
This commit is contained in:
parent
ddd5cf6d43
commit
4201c7d439
3 changed files with 165 additions and 69 deletions
217
painter.go
217
painter.go
|
|
@ -32,13 +32,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Painter struct {
|
type Painter struct {
|
||||||
render Renderer
|
render Renderer
|
||||||
box Box
|
box Box
|
||||||
font *truetype.Font
|
font *truetype.Font
|
||||||
parent *Painter
|
parent *Painter
|
||||||
style Style
|
style Style
|
||||||
previousStyle Style
|
theme ColorPalette
|
||||||
theme ColorPalette
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PainterOptions struct {
|
type PainterOptions struct {
|
||||||
|
|
@ -54,6 +53,12 @@ type PainterOptions struct {
|
||||||
|
|
||||||
type PainterOption func(*Painter)
|
type PainterOption func(*Painter)
|
||||||
|
|
||||||
|
type TicksOption struct {
|
||||||
|
Length int
|
||||||
|
Orient string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
// PainterPaddingOption sets the padding of draw painter
|
// PainterPaddingOption sets the padding of draw painter
|
||||||
func PainterPaddingOption(padding Box) PainterOption {
|
func PainterPaddingOption(padding Box) PainterOption {
|
||||||
return func(p *Painter) {
|
return func(p *Painter) {
|
||||||
|
|
@ -87,7 +92,7 @@ func PainterFontOption(font *truetype.Font) PainterOption {
|
||||||
// PainterStyleOption sets the style of draw painter
|
// PainterStyleOption sets the style of draw painter
|
||||||
func PainterStyleOption(style Style) PainterOption {
|
func PainterStyleOption(style Style) PainterOption {
|
||||||
return func(p *Painter) {
|
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 {
|
func (p *Painter) Child(opt ...PainterOption) *Painter {
|
||||||
child := &Painter{
|
child := &Painter{
|
||||||
render: p.render,
|
render: p.render,
|
||||||
box: p.box.Clone(),
|
box: p.box.Clone(),
|
||||||
font: p.font,
|
font: p.font,
|
||||||
parent: p,
|
parent: p,
|
||||||
style: p.style,
|
style: p.style,
|
||||||
previousStyle: p.previousStyle,
|
theme: p.theme,
|
||||||
theme: p.theme,
|
|
||||||
}
|
}
|
||||||
child.setOptions(opt...)
|
child.setOptions(opt...)
|
||||||
return child
|
return child
|
||||||
|
|
@ -172,29 +176,65 @@ func (p *Painter) SetStyle(style Style) {
|
||||||
if style.Font == nil {
|
if style.Font == nil {
|
||||||
style.Font = p.font
|
style.Font = p.font
|
||||||
}
|
}
|
||||||
p.previousStyle = p.style
|
|
||||||
p.style = style
|
p.style = style
|
||||||
style.WriteToRenderer(p.render)
|
style.WriteToRenderer(p.render)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) SetDrawingStyle(style Style) {
|
func overrideStyle(defaultStyle Style, style Style) Style {
|
||||||
p.previousStyle = p.style
|
if style.StrokeWidth == 0 {
|
||||||
p.style = style
|
style.StrokeWidth = defaultStyle.StrokeWidth
|
||||||
style.WriteDrawingOptionsToRenderer(p.render)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Painter) SetTextStyle(style Style) {
|
|
||||||
if style.Font == nil {
|
|
||||||
style.Font = p.font
|
|
||||||
}
|
}
|
||||||
p.previousStyle = p.style
|
if style.StrokeColor.IsZero() {
|
||||||
p.style = style
|
style.StrokeColor = defaultStyle.StrokeColor
|
||||||
style.WriteTextOptionsToRenderer(p.render)
|
}
|
||||||
|
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() {
|
func (p *Painter) OverrideDrawingStyle(style Style) *Painter {
|
||||||
p.style = p.previousStyle
|
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)
|
p.style.WriteToRenderer(p.render)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the data of draw canvas
|
// 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
|
// 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)
|
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)
|
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)
|
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
|
r := float64(width) / 2
|
||||||
y -= width / 4
|
y -= width / 4
|
||||||
angle := chart.DegreesToRadians(15)
|
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.render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top)
|
||||||
p.Close()
|
p.Close()
|
||||||
p.Fill()
|
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
|
halfWidth := width >> 1
|
||||||
halfHeight := height >> 1
|
halfHeight := height >> 1
|
||||||
if direction == PositionTop || direction == PositionBottom {
|
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.LineTo(x0, y0)
|
||||||
}
|
}
|
||||||
p.FillStroke()
|
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)
|
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)
|
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)
|
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)
|
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)
|
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()
|
p.render.Stroke()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) Close() {
|
func (p *Painter) Close() *Painter {
|
||||||
p.render.Close()
|
p.render.Close()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) FillStroke() {
|
func (p *Painter) FillStroke() *Painter {
|
||||||
p.render.FillStroke()
|
p.render.FillStroke()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) Fill() {
|
func (p *Painter) Fill() *Painter {
|
||||||
p.render.Fill()
|
p.render.Fill()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) Width() int {
|
func (p *Painter) Width() int {
|
||||||
|
|
@ -333,11 +387,7 @@ func (p *Painter) MeasureText(text string) Box {
|
||||||
return p.render.MeasureText(text)
|
return p.render.MeasureText(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) SetStrokeColor(color Color) {
|
func (p *Painter) LineStroke(points []Point) *Painter {
|
||||||
p.render.SetStrokeColor(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Painter) LineStroke(points []Point) {
|
|
||||||
for index, point := range points {
|
for index, point := range points {
|
||||||
x := point.X
|
x := point.X
|
||||||
y := point.Y
|
y := point.Y
|
||||||
|
|
@ -348,16 +398,17 @@ func (p *Painter) LineStroke(points []Point) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Stroke()
|
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
|
r := p.render
|
||||||
s := chart.Style{
|
s := chart.Style{
|
||||||
FillColor: color,
|
FillColor: color,
|
||||||
}
|
}
|
||||||
// 背景色
|
// 背景色
|
||||||
p.SetStyle(s)
|
p.SetDrawingStyle(s)
|
||||||
defer p.RestoreStyle()
|
defer p.ResetStyle()
|
||||||
// 设置背景色不使用box,因此不直接使用Painter
|
// 设置背景色不使用box,因此不直接使用Painter
|
||||||
r.MoveTo(0, 0)
|
r.MoveTo(0, 0)
|
||||||
r.LineTo(width, 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, height)
|
||||||
r.LineTo(0, 0)
|
r.LineTo(0, 0)
|
||||||
p.FillStroke()
|
p.FillStroke()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
func (p *Painter) MarkLine(x, y, width int) {
|
func (p *Painter) MarkLine(x, y, width int) *Painter {
|
||||||
arrowWidth := 16
|
arrowWidth := 16
|
||||||
arrowHeight := 10
|
arrowHeight := 10
|
||||||
endX := x + width
|
endX := x + width
|
||||||
p.Circle(3, x, y)
|
radius := 3
|
||||||
|
p.Circle(3, x+radius, y)
|
||||||
p.render.Fill()
|
p.render.Fill()
|
||||||
p.MoveTo(x+5, y)
|
p.MoveTo(x+radius*3, y)
|
||||||
p.LineTo(endX-arrowWidth, y)
|
p.LineTo(endX-arrowWidth, y)
|
||||||
p.Stroke()
|
p.Stroke()
|
||||||
p.render.SetStrokeDashArray([]float64{})
|
|
||||||
p.ArrowRight(endX, y, arrowWidth, arrowHeight)
|
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)
|
points := getPolygonPoints(center, radius, sides)
|
||||||
for i, item := range points {
|
for i, item := range points {
|
||||||
if i == 0 {
|
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.LineTo(points[0].X, points[0].Y)
|
||||||
p.Stroke()
|
p.Stroke()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Painter) FillArea(points []Point) {
|
func (p *Painter) FillArea(points []Point) *Painter {
|
||||||
var x, y int
|
var x, y int
|
||||||
for index, point := range points {
|
for index, point := range points {
|
||||||
x = point.X
|
x = point.X
|
||||||
|
|
@ -404,10 +458,12 @@ func (p *Painter) FillArea(points []Point) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Fill()
|
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)
|
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 {
|
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
|
p.style.TextWrap = textWarp
|
||||||
return output
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ func TestPainter(t *testing.T) {
|
||||||
})
|
})
|
||||||
p.MarkLine(0, 20, 300)
|
p.MarkLine(0, 20, 300)
|
||||||
},
|
},
|
||||||
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<circle cx=\"5\" cy=\"30\" r=\"3\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 10 30\nL 289 30\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path d=\"M 289 25\nL 305 30\nL 289 35\nL 294 30\nL 289 25\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
|
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<circle cx=\"8\" cy=\"30\" r=\"3\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 14 30\nL 289 30\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 289 25\nL 305 30\nL 289 35\nL 294 30\nL 289 25\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
|
||||||
},
|
},
|
||||||
// polygon
|
// polygon
|
||||||
{
|
{
|
||||||
|
|
|
||||||
15
util.go
15
util.go
|
|
@ -59,19 +59,16 @@ func getDefaultInt(value, defaultValue int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoDivide(max, size 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)
|
values := make([]int, size+1)
|
||||||
value := 0
|
for i := 0; i < size+1; i++ {
|
||||||
for i := 0; i < size; i++ {
|
if i == size {
|
||||||
values[i] = value
|
values[i] = max
|
||||||
if i < rest {
|
} else {
|
||||||
value++
|
values[i] = int(float64(i) * unit)
|
||||||
}
|
}
|
||||||
value += unit
|
|
||||||
}
|
}
|
||||||
values[size] = max
|
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue