feat: support make point and make line
This commit is contained in:
parent
fd05250305
commit
e558634dda
16 changed files with 308 additions and 51 deletions
25
axis.go
25
axis.go
|
|
@ -68,6 +68,10 @@ type axis struct {
|
|||
data *AxisDataList
|
||||
style *AxisOption
|
||||
}
|
||||
type axisMeasurement struct {
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func NewAxis(d *Draw, data AxisDataList, style AxisOption) *axis {
|
||||
return &axis{
|
||||
|
|
@ -379,7 +383,7 @@ func (a *axis) axisTick(opt *axisRenderOption) {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *axis) axisMeasureTextMaxWidthHeight() (int, int) {
|
||||
func (a *axis) measureTextMaxWidthHeight() (int, int) {
|
||||
d := a.d
|
||||
r := d.Render
|
||||
s := a.style.Style(d.Font)
|
||||
|
|
@ -389,18 +393,21 @@ func (a *axis) axisMeasureTextMaxWidthHeight() (int, int) {
|
|||
return measureTextMaxWidthHeight(data.TextList(), r)
|
||||
}
|
||||
|
||||
// measureAxis returns the measurement of axis.
|
||||
// If the position is left or right, it will be textMaxWidth + labelMargin + tickLength.
|
||||
// If the position is top or bottom, it will be textMaxHeight + labelMargin + tickLength.
|
||||
func (a *axis) measureAxis() int {
|
||||
// measure returns the measurement of axis.
|
||||
// Width will be textMaxWidth + labelMargin + tickLength for position left or right.
|
||||
// Height will be textMaxHeight + labelMargin + tickLength for position top or bottom.
|
||||
func (a *axis) measure() axisMeasurement {
|
||||
style := a.style
|
||||
value := style.GetLabelMargin() + style.GetTickLength()
|
||||
textMaxWidth, textMaxHeight := a.axisMeasureTextMaxWidthHeight()
|
||||
textMaxWidth, textMaxHeight := a.measureTextMaxWidthHeight()
|
||||
info := axisMeasurement{}
|
||||
if style.Position == PositionLeft ||
|
||||
style.Position == PositionRight {
|
||||
return textMaxWidth + value
|
||||
info.Width = textMaxWidth + value
|
||||
} else {
|
||||
info.Height = textMaxHeight + value
|
||||
}
|
||||
return textMaxHeight + value
|
||||
return info
|
||||
}
|
||||
|
||||
// Render renders the axis for chart
|
||||
|
|
@ -409,7 +416,7 @@ func (a *axis) Render() {
|
|||
if isFalse(style.Show) {
|
||||
return
|
||||
}
|
||||
textMaxWidth, textMaxHeight := a.axisMeasureTextMaxWidthHeight()
|
||||
textMaxWidth, textMaxHeight := a.measureTextMaxWidthHeight()
|
||||
opt := &axisRenderOption{
|
||||
textMaxWith: textMaxWidth,
|
||||
textMaxHeight: textMaxHeight,
|
||||
|
|
|
|||
|
|
@ -247,13 +247,13 @@ func TestMeasureAxis(t *testing.T) {
|
|||
FontSize: 12,
|
||||
Font: f,
|
||||
Position: PositionLeft,
|
||||
}).measureAxis()
|
||||
}).measure().Width
|
||||
assert.Equal(44, width)
|
||||
|
||||
height := NewAxis(d, data, AxisOption{
|
||||
FontSize: 12,
|
||||
Font: f,
|
||||
Position: PositionTop,
|
||||
}).measureAxis()
|
||||
}).measure().Height
|
||||
assert.Equal(28, height)
|
||||
}
|
||||
|
|
|
|||
31
bar_chart.go
31
bar_chart.go
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
||||
func barChartRender(opt ChartOption, result *basicRenderResult) ([]*markPointRenderOption, error) {
|
||||
|
||||
d, err := NewDraw(DrawOption{
|
||||
Parent: result.d,
|
||||
|
|
@ -57,10 +57,29 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
|||
|
||||
r := d.Render
|
||||
|
||||
for i, series := range opt.SeriesList {
|
||||
markPointRenderOptions := make([]*markPointRenderOption, 0)
|
||||
|
||||
for i, s := range opt.SeriesList {
|
||||
// 由于series是for range,为同一个数据,因此需要clone
|
||||
// 后续需要使用,如mark point
|
||||
series := s
|
||||
yRange := result.getYRange(series.YAxisIndex)
|
||||
points := make([]Point, len(series.Data))
|
||||
seriesColor := theme.GetSeriesColor(i)
|
||||
index := series.index
|
||||
if index == 0 {
|
||||
index = i
|
||||
}
|
||||
seriesColor := theme.GetSeriesColor(index)
|
||||
// mark line
|
||||
markLineRender(&markLineRenderOption{
|
||||
Draw: d,
|
||||
FillColor: seriesColor,
|
||||
FontColor: theme.GetTextColor(),
|
||||
StrokeColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Series: &series,
|
||||
Range: yRange,
|
||||
})
|
||||
for j, item := range series.Data {
|
||||
x0, _ := xRange.GetRange(j)
|
||||
x := int(x0)
|
||||
|
|
@ -105,7 +124,9 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
|||
textBox := r.MeasureText(text)
|
||||
d.text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-5)
|
||||
}
|
||||
markPointRender(d, markPointRenderOption{
|
||||
|
||||
markPointRenderOptions = append(markPointRenderOptions, &markPointRenderOption{
|
||||
Draw: d,
|
||||
FillColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Points: points,
|
||||
|
|
@ -113,5 +134,5 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
|||
})
|
||||
}
|
||||
|
||||
return result.d, nil
|
||||
return markPointRenderOptions, nil
|
||||
}
|
||||
|
|
|
|||
37
chart.go
37
chart.go
|
|
@ -80,6 +80,14 @@ func (o *ChartOption) FillDefault(theme string) {
|
|||
if o.BackgroundColor.IsZero() {
|
||||
o.BackgroundColor = t.GetBackgroundColor()
|
||||
}
|
||||
if o.Padding.IsZero() {
|
||||
o.Padding = chart.Box{
|
||||
Top: 20,
|
||||
Right: 10,
|
||||
Bottom: 10,
|
||||
Left: 10,
|
||||
}
|
||||
}
|
||||
|
||||
// 标题的默认值
|
||||
if o.Title.Style.FontColor.IsZero() {
|
||||
|
|
@ -110,7 +118,7 @@ func (o *ChartOption) FillDefault(theme string) {
|
|||
o.Title.SubtextStyle.Font = o.Font
|
||||
}
|
||||
|
||||
o.Legend.Theme = theme
|
||||
o.Legend.theme = theme
|
||||
if o.Legend.Style.FontSize == 0 {
|
||||
o.Legend.Style.FontSize = 10
|
||||
}
|
||||
|
|
@ -238,13 +246,14 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
markPointRenderOptions := make([]*markPointRenderOption, 0)
|
||||
fns := []func() error{
|
||||
// pie render
|
||||
func() error {
|
||||
if !isPieChart {
|
||||
return nil
|
||||
}
|
||||
_, err := pieChartRender(opt, result)
|
||||
err := pieChartRender(opt, result)
|
||||
return err
|
||||
},
|
||||
// bar render
|
||||
|
|
@ -255,8 +264,12 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
}
|
||||
o := opt
|
||||
o.SeriesList = barSeries
|
||||
_, err := barChartRender(o, result)
|
||||
options, err := barChartRender(o, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
markPointRenderOptions = append(markPointRenderOptions, options...)
|
||||
return nil
|
||||
},
|
||||
// line render
|
||||
func() error {
|
||||
|
|
@ -266,14 +279,26 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
}
|
||||
o := opt
|
||||
o.SeriesList = lineSeries
|
||||
_, err := lineChartRender(o, result)
|
||||
options, err := lineChartRender(o, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
markPointRenderOptions = append(markPointRenderOptions, options...)
|
||||
return nil
|
||||
},
|
||||
// legend需要在顶层,因此最后render
|
||||
// legend需要在顶层,因此此处render
|
||||
func() error {
|
||||
_, err := NewLegend(result.d, opt.Legend).Render()
|
||||
return err
|
||||
},
|
||||
// mark point最后render
|
||||
func() error {
|
||||
// mark point render不会出错
|
||||
for _, opt := range markPointRenderOptions {
|
||||
markPointRender(opt)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
|
|
@ -296,6 +321,7 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
}
|
||||
|
||||
func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
|
||||
opt.FillDefault(opt.Theme)
|
||||
d, err := NewDraw(
|
||||
DrawOption{
|
||||
Type: opt.Type,
|
||||
|
|
@ -309,7 +335,6 @@ func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
opt.FillDefault(opt.Theme)
|
||||
if len(opt.YAxisList) > 2 {
|
||||
return nil, errors.New("y axis should not be gt 2")
|
||||
}
|
||||
|
|
|
|||
20
draw.go
20
draw.go
|
|
@ -161,7 +161,9 @@ func (d *Draw) pin(x, y, width int) {
|
|||
delta := 2*math.Pi - 2*angle
|
||||
d.arcTo(x, y, r, r, startAngle, delta)
|
||||
d.lineTo(x, y)
|
||||
d.Render.Fill()
|
||||
d.Render.Close()
|
||||
d.Render.FillStroke()
|
||||
|
||||
startX := x - int(r)
|
||||
startY := y
|
||||
endX := x + int(r)
|
||||
|
|
@ -173,7 +175,8 @@ func (d *Draw) pin(x, y, width int) {
|
|||
cx := x
|
||||
cy := y + int(r*2.5)
|
||||
d.Render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top)
|
||||
d.Render.Stroke()
|
||||
d.Render.Close()
|
||||
d.Render.Fill()
|
||||
}
|
||||
|
||||
func (d *Draw) arrowLeft(x, y, width, height int) {
|
||||
|
|
@ -226,7 +229,20 @@ func (d *Draw) arrow(x, y, width, height int, direction string) {
|
|||
d.lineTo(x0+dx, y0+halfHeight)
|
||||
d.lineTo(x0, y0)
|
||||
}
|
||||
d.Render.FillStroke()
|
||||
}
|
||||
|
||||
func (d *Draw) makeLine(x, y, width int) {
|
||||
arrowWidth := 16
|
||||
arrowHeight := 10
|
||||
endX := x + width
|
||||
d.circle(3, x, y)
|
||||
d.Render.Fill()
|
||||
d.moveTo(x+5, y)
|
||||
d.lineTo(endX-arrowWidth, y)
|
||||
d.Render.Stroke()
|
||||
d.Render.SetStrokeDashArray([]float64{})
|
||||
d.arrowRight(endX, y, arrowWidth, arrowHeight)
|
||||
}
|
||||
|
||||
func (d *Draw) circle(radius float64, x, y int) {
|
||||
|
|
|
|||
28
draw_test.go
28
draw_test.go
|
|
@ -259,7 +259,7 @@ func TestDraw(t *testing.T) {
|
|||
}.WriteToRenderer(d.Render)
|
||||
d.pin(30, 30, 30)
|
||||
},
|
||||
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<path d=\"M 32 47\nA 15 15 330.00 1 1 38 47\nL 35 33\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path d=\"M 20 33\nQ35,70 50,33\" 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<path d=\"M 32 47\nA 15 15 330.00 1 1 38 47\nL 35 33\nZ\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path d=\"M 20 33\nQ35,70 50,33\nZ\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
|
||||
},
|
||||
// arrow left
|
||||
{
|
||||
|
|
@ -349,6 +349,32 @@ func TestDraw(t *testing.T) {
|
|||
},
|
||||
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<path d=\"M 30 24\nL 35 40\nL 40 24\nL 35 30\nL 30 24\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
|
||||
},
|
||||
// mark line
|
||||
{
|
||||
fn: func(d *Draw) {
|
||||
chart.Style{
|
||||
StrokeWidth: 1,
|
||||
StrokeColor: drawing.Color{
|
||||
R: 84,
|
||||
G: 112,
|
||||
B: 198,
|
||||
A: 255,
|
||||
},
|
||||
FillColor: drawing.Color{
|
||||
R: 84,
|
||||
G: 112,
|
||||
B: 198,
|
||||
A: 255,
|
||||
},
|
||||
StrokeDashArray: []float64{
|
||||
4,
|
||||
2,
|
||||
},
|
||||
}.WriteToRenderer(d.Render)
|
||||
d.makeLine(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>",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
d, err := NewDraw(DrawOption{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import (
|
|||
)
|
||||
|
||||
type LegendOption struct {
|
||||
Theme string
|
||||
theme string
|
||||
// Legend show flag, if nil or true, the legend will be shown
|
||||
Show *bool
|
||||
// Legend text style
|
||||
|
|
@ -67,7 +67,7 @@ func (l *legend) Render() (chart.Box, error) {
|
|||
if len(opt.Data) == 0 || isFalse(opt.Show) {
|
||||
return chart.BoxZero, nil
|
||||
}
|
||||
theme := NewTheme(opt.Theme)
|
||||
theme := NewTheme(opt.theme)
|
||||
padding := opt.Style.Padding
|
||||
legendDraw, err := NewDraw(DrawOption{
|
||||
Parent: d,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
||||
func lineChartRender(opt ChartOption, result *basicRenderResult) ([]*markPointRenderOption, error) {
|
||||
|
||||
theme := NewTheme(opt.Theme)
|
||||
|
||||
|
|
@ -44,9 +44,29 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
|
|||
|
||||
r := d.Render
|
||||
xRange := result.xRange
|
||||
for i, series := range opt.SeriesList {
|
||||
markPointRenderOptions := make([]*markPointRenderOption, 0)
|
||||
for i, s := range opt.SeriesList {
|
||||
// 由于series是for range,为同一个数据,因此需要clone
|
||||
// 后续需要使用,如mark point
|
||||
series := s
|
||||
index := series.index
|
||||
if index == 0 {
|
||||
index = i
|
||||
}
|
||||
seriesColor := theme.GetSeriesColor(index)
|
||||
|
||||
yRange := result.getYRange(series.YAxisIndex)
|
||||
points := make([]Point, len(series.Data))
|
||||
// mark line
|
||||
markLineRender(&markLineRenderOption{
|
||||
Draw: d,
|
||||
FillColor: seriesColor,
|
||||
FontColor: theme.GetTextColor(),
|
||||
StrokeColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Series: &series,
|
||||
Range: yRange,
|
||||
})
|
||||
|
||||
for j, item := range series.Data {
|
||||
y := yRange.getRestHeight(item.Value)
|
||||
|
|
@ -71,11 +91,7 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
|
|||
textBox := r.MeasureText(text)
|
||||
d.text(text, x-textBox.Width()>>1, y-5)
|
||||
}
|
||||
index := series.index
|
||||
if index == 0 {
|
||||
index = i
|
||||
}
|
||||
seriesColor := theme.GetSeriesColor(index)
|
||||
|
||||
dotFillColor := drawing.ColorWhite
|
||||
if theme.IsDark() {
|
||||
dotFillColor = seriesColor
|
||||
|
|
@ -88,7 +104,8 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
|
|||
DotFillColor: dotFillColor,
|
||||
})
|
||||
// draw mark point
|
||||
markPointRender(d, markPointRenderOption{
|
||||
markPointRenderOptions = append(markPointRenderOptions, &markPointRenderOption{
|
||||
Draw: d,
|
||||
FillColor: seriesColor,
|
||||
Font: opt.Font,
|
||||
Points: points,
|
||||
|
|
@ -96,5 +113,5 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
|
|||
})
|
||||
}
|
||||
|
||||
return result.d, nil
|
||||
return markPointRenderOptions, nil
|
||||
}
|
||||
|
|
|
|||
92
mark_line.go
Normal file
92
mark_line.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func NewMarkLine(markLineTypes ...string) SeriesMarkLine {
|
||||
data := make([]SeriesMarkLineData, len(markLineTypes))
|
||||
for index, t := range markLineTypes {
|
||||
data[index] = SeriesMarkLineData{
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
return SeriesMarkLine{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type markLineRenderOption struct {
|
||||
Draw *Draw
|
||||
FillColor drawing.Color
|
||||
FontColor drawing.Color
|
||||
StrokeColor drawing.Color
|
||||
Font *truetype.Font
|
||||
Series *Series
|
||||
Range *Range
|
||||
}
|
||||
|
||||
func markLineRender(opt *markLineRenderOption) {
|
||||
d := opt.Draw
|
||||
s := opt.Series
|
||||
if len(s.MarkLine.Data) == 0 {
|
||||
return
|
||||
}
|
||||
r := d.Render
|
||||
summary := s.Summary()
|
||||
for _, markLine := range s.MarkLine.Data {
|
||||
// 由于mark line会修改style,因此每次重新设置
|
||||
chart.Style{
|
||||
FillColor: opt.FillColor,
|
||||
FontColor: opt.FontColor,
|
||||
FontSize: labelFontSize,
|
||||
StrokeColor: opt.StrokeColor,
|
||||
StrokeWidth: 1,
|
||||
Font: opt.Font,
|
||||
StrokeDashArray: []float64{
|
||||
4,
|
||||
2,
|
||||
},
|
||||
}.WriteToRenderer(r)
|
||||
value := float64(0)
|
||||
switch markLine.Type {
|
||||
case SeriesMarkDataTypeMax:
|
||||
value = summary.MaxValue
|
||||
case SeriesMarkDataTypeMin:
|
||||
value = summary.MinValue
|
||||
default:
|
||||
value = summary.AverageValue
|
||||
}
|
||||
y := opt.Range.getRestHeight(value)
|
||||
width := d.Box.Width()
|
||||
text := commafWithDigits(value)
|
||||
textBox := r.MeasureText(text)
|
||||
d.makeLine(0, y, width)
|
||||
d.text(text, width, y+textBox.Height()>>1-2)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,14 +28,28 @@ import (
|
|||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func NewMarkPoint(markPointTypes ...string) SeriesMarkPoint {
|
||||
data := make([]SeriesMarkPointData, len(markPointTypes))
|
||||
for index, t := range markPointTypes {
|
||||
data[index] = SeriesMarkPointData{
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
return SeriesMarkPoint{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type markPointRenderOption struct {
|
||||
Draw *Draw
|
||||
FillColor drawing.Color
|
||||
Font *truetype.Font
|
||||
Series *Series
|
||||
Points []Point
|
||||
}
|
||||
|
||||
func markPointRender(d *Draw, opt markPointRenderOption) {
|
||||
func markPointRender(opt *markPointRenderOption) {
|
||||
d := opt.Draw
|
||||
s := opt.Series
|
||||
if len(s.MarkPoint.Data) == 0 {
|
||||
return
|
||||
|
|
@ -54,7 +68,7 @@ func markPointRender(d *Draw, opt markPointRenderOption) {
|
|||
// 设置文本样式
|
||||
chart.Style{
|
||||
FontColor: NewTheme(ThemeDark).GetTextColor(),
|
||||
FontSize: 10,
|
||||
FontSize: labelFontSize,
|
||||
StrokeWidth: 1,
|
||||
Font: opt.Font,
|
||||
}.WriteTextOptionsToRenderer(r)
|
||||
|
|
@ -62,7 +76,7 @@ func markPointRender(d *Draw, opt markPointRenderOption) {
|
|||
p := points[summary.MinIndex]
|
||||
value := summary.MinValue
|
||||
switch markPointData.Type {
|
||||
case SeriesMarkPointDataTypeMax:
|
||||
case SeriesMarkDataTypeMax:
|
||||
p = points[summary.MaxIndex]
|
||||
value = summary.MaxValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,14 +41,14 @@ func getPieStyle(theme *Theme, index int) chart.Style {
|
|||
|
||||
}
|
||||
|
||||
func pieChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
||||
func pieChartRender(opt ChartOption, result *basicRenderResult) error {
|
||||
d, err := NewDraw(DrawOption{
|
||||
Parent: result.d,
|
||||
}, PaddingOption(chart.Box{
|
||||
Top: result.titleBox.Height(),
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
values := make([]float64, len(opt.SeriesList))
|
||||
|
|
@ -155,5 +155,5 @@ func pieChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
|
|||
d.text(text, x, y)
|
||||
}
|
||||
}
|
||||
return result.d, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
4
range.go
4
range.go
|
|
@ -54,9 +54,11 @@ func NewRange(min, max float64, divideCount int) Range {
|
|||
unit = int((r/float64(divideCount))/float64(unit))*unit + unit
|
||||
|
||||
if min != 0 {
|
||||
isLessThanZero := min < 0
|
||||
min = float64(int(min/float64(unit)) * unit)
|
||||
// 如果是小于0,int的时候向上取整了,因此调整
|
||||
if min < 0 {
|
||||
if min < 0 ||
|
||||
(isLessThanZero && min == 0) {
|
||||
min -= float64(unit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
series.go
12
series.go
|
|
@ -63,8 +63,9 @@ type SeriesLabel struct {
|
|||
}
|
||||
|
||||
const (
|
||||
SeriesMarkPointDataTypeMax = "max"
|
||||
SeriesMarkPointDataTypeMin = "min"
|
||||
SeriesMarkDataTypeMax = "max"
|
||||
SeriesMarkDataTypeMin = "min"
|
||||
SeriesMarkDataTypeAverage = "average"
|
||||
)
|
||||
|
||||
type SeriesMarkPointData struct {
|
||||
|
|
@ -74,6 +75,12 @@ type SeriesMarkPoint struct {
|
|||
SymbolSize int
|
||||
Data []SeriesMarkPointData
|
||||
}
|
||||
type SeriesMarkLineData struct {
|
||||
Type string
|
||||
}
|
||||
type SeriesMarkLine struct {
|
||||
Data []SeriesMarkLineData
|
||||
}
|
||||
type Series struct {
|
||||
index int
|
||||
Type string
|
||||
|
|
@ -85,6 +92,7 @@ type Series struct {
|
|||
// Radius of Pie chart, e.g.: 40%
|
||||
Radius string
|
||||
MarkPoint SeriesMarkPoint
|
||||
MarkLine SeriesMarkLine
|
||||
}
|
||||
|
||||
type seriesSummary struct {
|
||||
|
|
|
|||
3
xaxis.go
3
xaxis.go
|
|
@ -77,8 +77,7 @@ func drawXAxis(p *Draw, opt *XAxisOption, yAxisCount int) (int, *Range, error) {
|
|||
}
|
||||
axis := NewAxis(dXAxis, data, style)
|
||||
axis.Render()
|
||||
|
||||
return axis.measureAxis(), &Range{
|
||||
return axis.measure().Height, &Range{
|
||||
divideCount: len(opt.Data),
|
||||
Min: 0,
|
||||
Max: max,
|
||||
|
|
|
|||
2
yaxis.go
2
yaxis.go
|
|
@ -62,7 +62,7 @@ func drawYAxis(p *Draw, opt *ChartOption, axisIndex, xAxisHeight int, padding ch
|
|||
SplitLineColor: theme.GetAxisSplitLineColor(),
|
||||
SplitLineShow: true,
|
||||
}
|
||||
width := NewAxis(p, data, style).measureAxis()
|
||||
width := NewAxis(p, data, style).measure().Width
|
||||
|
||||
yAxisCount := len(opt.YAxisList)
|
||||
boxWidth := p.Box.Width()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ func TestDrawYAxis(t *testing.T) {
|
|||
tests := []struct {
|
||||
newDraw func() *Draw
|
||||
newOption func() *ChartOption
|
||||
axisIndex int
|
||||
xAxisHeight int
|
||||
result string
|
||||
}{
|
||||
|
|
@ -70,11 +71,40 @@ func TestDrawYAxis(t *testing.T) {
|
|||
},
|
||||
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<path d=\"M 50 10\nL 50 290\" style=\"stroke-width:1;stroke:none;fill:none\"/><path d=\"M 50 10\nL 390 10\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 50 57\nL 390 57\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 50 104\nL 390 104\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 50 151\nL 390 151\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 50 198\nL 390 198\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 50 244\nL 390 244\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><text x=\"36\" y=\"294\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">0</text><text x=\"18\" y=\"248\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">3.33</text><text x=\"18\" y=\"202\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">6.66</text><text x=\"29\" y=\"155\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">10</text><text x=\"11\" y=\"108\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">13.33</text><text x=\"11\" y=\"61\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">16.66</text><text x=\"29\" y=\"14\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">20</text></svg>",
|
||||
},
|
||||
{
|
||||
newDraw: newDraw,
|
||||
newOption: func() *ChartOption {
|
||||
return &ChartOption{
|
||||
YAxisList: []YAxisOption{
|
||||
{},
|
||||
{
|
||||
Max: NewFloatPoint(20),
|
||||
Formatter: "{value} C",
|
||||
},
|
||||
},
|
||||
SeriesList: []Series{
|
||||
{
|
||||
YAxisIndex: 1,
|
||||
Data: []SeriesData{
|
||||
{
|
||||
Value: 1,
|
||||
},
|
||||
{
|
||||
Value: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
axisIndex: 1,
|
||||
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<path d=\"M 337 10\nL 337 290\" style=\"stroke-width:1;stroke:none;fill:none\"/><text x=\"345\" y=\"294\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">0 C</text><text x=\"345\" y=\"248\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">3.33 C</text><text x=\"345\" y=\"202\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">6.66 C</text><text x=\"345\" y=\"155\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">10 C</text><text x=\"345\" y=\"108\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">13.33 C</text><text x=\"345\" y=\"61\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">16.66 C</text><text x=\"345\" y=\"14\" style=\"stroke-width:0;stroke:none;fill:rgba(110,112,121,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">20 C</text></svg>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
d := tt.newDraw()
|
||||
r, err := drawYAxis(d, tt.newOption(), 0, tt.xAxisHeight, chart.NewBox(10, 10, 10, 10))
|
||||
r, err := drawYAxis(d, tt.newOption(), tt.axisIndex, tt.xAxisHeight, chart.NewBox(10, 10, 10, 10))
|
||||
assert.Nil(err)
|
||||
assert.Equal(&Range{
|
||||
divideCount: 6,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue