feat: support mulit y axis

This commit is contained in:
vicanso 2022-02-09 23:44:39 +08:00
parent c0bb1654c2
commit fd05250305
16 changed files with 393 additions and 96 deletions

View file

@ -78,14 +78,17 @@ func NewAxis(d *Draw, data AxisDataList, style AxisOption) *axis {
}
// GetLabelMargin returns the label margin value
func (as *AxisOption) GetLabelMargin() int {
return getDefaultInt(as.LabelMargin, 8)
}
// GetTickLength returns the tick length value
func (as *AxisOption) GetTickLength() int {
return getDefaultInt(as.TickLength, 5)
}
// Style returns the style of axis
func (as *AxisOption) Style(f *truetype.Font) chart.Style {
s := chart.Style{
ClassName: as.ClassName,
@ -109,6 +112,7 @@ type AxisData struct {
}
type AxisDataList []AxisData
// TextList returns the text list of axis data
func (l AxisDataList) TextList() []string {
textList := make([]string, len(l))
for index, item := range l {
@ -125,6 +129,7 @@ type axisRenderOption struct {
modValue int
}
// NewAxisDataListFromStringList creates a new axis data list from string list
func NewAxisDataListFromStringList(textList []string) AxisDataList {
list := make(AxisDataList, len(textList))
for index, text := range textList {

View file

@ -37,7 +37,7 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
if err != nil {
return nil, err
}
yRange := result.yRange
// yRange := result.yRange
xRange := result.xRange
x0, x1 := xRange.GetRange(0)
width := int(x1 - x0)
@ -50,7 +50,7 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
// 总的宽度-两个margin-(总数-1)的barMargin
barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(opt.SeriesList)
barMaxHeight := yRange.Size
barMaxHeight := result.getYRange(0).Size
theme := NewTheme(opt.Theme)
seriesNames := opt.Legend.Data
@ -58,6 +58,9 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
r := d.Render
for i, series := range opt.SeriesList {
yRange := result.getYRange(series.YAxisIndex)
points := make([]Point, len(series.Data))
seriesColor := theme.GetSeriesColor(i)
for j, item := range series.Data {
x0, _ := xRange.GetRange(j)
x := int(x0)
@ -67,26 +70,32 @@ func barChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
}
h := int(yRange.getHeight(item.Value))
fillColor := theme.GetSeriesColor(i)
fillColor := seriesColor
if !item.Style.FillColor.IsZero() {
fillColor = item.Style.FillColor
}
top := barMaxHeight - h
d.Bar(chart.Box{
Top: barMaxHeight - h,
Top: top,
Left: x,
Right: x + barWidth,
Bottom: barMaxHeight - 1,
}, BarStyle{
FillColor: fillColor,
})
// 用于生成marker point
points[j] = Point{
// 居中的位置
X: x + barWidth>>1,
Y: top,
}
if !series.Label.Show {
continue
}
text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(i, item.Value, -1)
labelStyle := chart.Style{
FontColor: theme.GetTextColor(),
FontSize: 10,
FontSize: labelFontSize,
Font: opt.Font,
}
if !series.Label.Color.IsZero() {
@ -96,6 +105,12 @@ 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{
FillColor: seriesColor,
Font: opt.Font,
Points: points,
Series: &series,
})
}
return result.d, nil

View file

@ -42,6 +42,8 @@ type Point struct {
Y int
}
const labelFontSize = 10
type ChartOption struct {
Type string
Font *truetype.Font
@ -49,7 +51,7 @@ type ChartOption struct {
Title TitleOption
Legend LegendOption
XAxis XAxisOption
YAxis YAxisOption
YAxisList []YAxisOption
Width int
Height int
Parent *Draw
@ -61,6 +63,17 @@ type ChartOption struct {
func (o *ChartOption) FillDefault(theme string) {
t := NewTheme(theme)
// 如果为空,初始化
yAxisCount := 1
for _, series := range o.SeriesList {
if series.YAxisIndex >= yAxisCount {
yAxisCount++
}
}
yAxisList := make([]YAxisOption, yAxisCount)
copy(yAxisList, o.YAxisList)
o.YAxisList = yAxisList
if o.Font == nil {
o.Font, _ = chart.GetDefaultFont()
}
@ -136,9 +149,13 @@ func (o *ChartOption) getHeight() int {
return o.Height
}
func (o *ChartOption) getYRange(axisIndex int) Range {
func (o *ChartOption) newYRange(axisIndex int) Range {
min := math.MaxFloat64
max := -math.MaxFloat64
if axisIndex >= len(o.YAxisList) {
axisIndex = 0
}
yAxis := o.YAxisList[axisIndex]
for _, series := range o.SeriesList {
if series.YAxisIndex != axisIndex {
@ -155,21 +172,21 @@ func (o *ChartOption) getYRange(axisIndex int) Range {
}
min = min * 0.9
max = max * 1.1
if o.YAxis.Min != nil {
min = *o.YAxis.Min
if yAxis.Min != nil {
min = *yAxis.Min
}
if o.YAxis.Max != nil {
max = *o.YAxis.Max
if yAxis.Max != nil {
max = *yAxis.Max
}
divideCount := 6
// y轴分设置默认划分为6块
r := NewRange(min, max, divideCount)
// 由于NewRange会重新计算min max
if o.YAxis.Min != nil {
if yAxis.Min != nil {
r.Min = min
}
if o.YAxis.Max != nil {
if yAxis.Max != nil {
r.Max = max
}
@ -178,11 +195,18 @@ func (o *ChartOption) getYRange(axisIndex int) Range {
type basicRenderResult struct {
xRange *Range
yRange *Range
yRangeList []*Range
d *Draw
titleBox chart.Box
}
func (r *basicRenderResult) getYRange(index int) *Range {
if index >= len(r.yRangeList) {
index = 0
}
return r.yRangeList[index]
}
func Render(opt ChartOption) (*Draw, error) {
if len(opt.SeriesList) == 0 {
return nil, errors.New("series can not be nil")
@ -206,7 +230,9 @@ func Render(opt ChartOption) (*Draw, error) {
// pie不需要axis
if isPieChart {
opt.XAxis.Hidden = true
opt.YAxis.Hidden = true
for index := range opt.YAxisList {
opt.YAxisList[index].Hidden = true
}
}
result, err := chartBasicRender(&opt)
if err != nil {
@ -284,6 +310,9 @@ func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
}
opt.FillDefault(opt.Theme)
if len(opt.YAxisList) > 2 {
return nil, errors.New("y axis should not be gt 2")
}
if opt.Parent == nil {
d.setBackground(opt.getWidth(), opt.getHeight(), opt.BackgroundColor)
}
@ -299,25 +328,29 @@ func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
if !opt.XAxis.Hidden {
// xAxis
xAxisHeight, xRange, err = drawXAxis(d, &opt.XAxis)
xAxisHeight, xRange, err = drawXAxis(d, &opt.XAxis, len(opt.YAxisList))
if err != nil {
return nil, err
}
}
// 暂时仅支持单一yaxis
yRangeList := make([]*Range, len(opt.YAxisList))
for index, yAxis := range opt.YAxisList {
var yRange *Range
if !opt.YAxis.Hidden {
yRange, err = drawYAxis(d, opt, xAxisHeight, chart.Box{
if !yAxis.Hidden {
yRange, err = drawYAxis(d, opt, index, xAxisHeight, chart.Box{
Top: titleBox.Height(),
})
if err != nil {
return nil, err
}
yRangeList[index] = yRange
}
}
return &basicRenderResult{
xRange: xRange,
yRange: yRange,
yRangeList: yRangeList,
d: d,
titleBox: titleBox,
}, nil

55
draw.go
View file

@ -173,7 +173,60 @@ 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.Fill()
d.Render.Stroke()
}
func (d *Draw) arrowLeft(x, y, width, height int) {
d.arrow(x, y, width, height, PositionLeft)
}
func (d *Draw) arrowRight(x, y, width, height int) {
d.arrow(x, y, width, height, PositionRight)
}
func (d *Draw) arrowTop(x, y, width, height int) {
d.arrow(x, y, width, height, PositionTop)
}
func (d *Draw) arrowBottom(x, y, width, height int) {
d.arrow(x, y, width, height, PositionBottom)
}
func (d *Draw) arrow(x, y, width, height int, direction string) {
halfWidth := width >> 1
halfHeight := height >> 1
if direction == PositionTop || direction == PositionBottom {
x0 := x - halfWidth
x1 := x0 + width
dy := -height / 3
y0 := y
y1 := y0 - height
if direction == PositionBottom {
y0 = y - height
y1 = y
dy = 2 * dy
}
d.moveTo(x0, y0)
d.lineTo(x0+halfWidth, y1)
d.lineTo(x1, y0)
d.lineTo(x0+halfWidth, y+dy)
d.lineTo(x0, y0)
} else {
x0 := x + width
x1 := x0 - width
y0 := y - halfHeight
dx := -width / 3
if direction == PositionRight {
x0 = x - width
dx = -dx
x1 = x0 + width
}
d.moveTo(x0, y0)
d.lineTo(x1, y0+halfHeight)
d.lineTo(x0, y0+height)
d.lineTo(x0+dx, y0+halfHeight)
d.lineTo(x0, y0)
}
d.Render.Stroke()
}
func (d *Draw) circle(radius float64, x, y int) {

View file

@ -239,6 +239,7 @@ 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 205 110\nA 100 100 90.00 0 1 105 210\nZ\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,255,1.0)\"/></svg>",
},
// pin
{
fn: func(d *Draw) {
chart.Style{
@ -260,6 +261,94 @@ 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 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>",
},
// arrow left
{
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,
},
}.WriteToRenderer(d.Render)
d.arrowLeft(30, 30, 16, 10)
},
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 51 35\nL 35 40\nL 51 45\nL 46 40\nL 51 35\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
},
// arrow right
{
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,
},
}.WriteToRenderer(d.Render)
d.arrowRight(30, 30, 16, 10)
},
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 19 35\nL 35 40\nL 19 45\nL 24 40\nL 19 35\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
},
// arrow top
{
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,
},
}.WriteToRenderer(d.Render)
d.arrowTop(30, 30, 10, 16)
},
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 40\nL 35 24\nL 40 40\nL 35 35\nL 30 40\" style=\"stroke-width:1;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/></svg>",
},
// arrow bottom
{
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,
},
}.WriteToRenderer(d.Render)
d.arrowBottom(30, 30, 10, 16)
},
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>",
},
}
for _, tt := range tests {
d, err := NewDraw(DrawOption{

View file

@ -23,8 +23,6 @@
package charts
import (
"math"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
)
@ -45,36 +43,25 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
seriesNames := opt.Legend.Data
r := d.Render
yRange := result.yRange
xRange := result.xRange
for i, series := range opt.SeriesList {
points := make([]Point, 0)
minIndex := -1
maxIndex := -1
minValue := math.MaxFloat64
maxValue := -math.MaxFloat64
yRange := result.getYRange(series.YAxisIndex)
points := make([]Point, len(series.Data))
for j, item := range series.Data {
if item.Value < minValue {
minIndex = j
minValue = item.Value
}
if item.Value > maxValue {
maxIndex = j
maxValue = item.Value
}
y := yRange.getRestHeight(item.Value)
x := xRange.getWidth(float64(j))
points = append(points, Point{
points[j] = Point{
Y: y,
X: x,
})
}
if !series.Label.Show {
continue
}
text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(i, item.Value, -1)
labelStyle := chart.Style{
FontColor: theme.GetTextColor(),
FontSize: 10,
FontSize: labelFontSize,
Font: opt.Font,
}
if !series.Label.Color.IsZero() {
@ -101,33 +88,12 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
DotFillColor: dotFillColor,
})
// draw mark point
symbolSize := 30
if series.MarkPoint.SymbolSize > 0 {
symbolSize = series.MarkPoint.SymbolSize
}
for _, markPointData := range series.MarkPoint.Data {
p := points[minIndex]
value := minValue
switch markPointData.Type {
case SeriesMarkPointDataTypeMax:
p = points[maxIndex]
value = maxValue
}
chart.Style{
markPointRender(d, markPointRenderOption{
FillColor: seriesColor,
}.WriteToRenderer(r)
d.pin(p.X, p.Y-symbolSize>>1, symbolSize)
chart.Style{
FontColor: NewTheme(ThemeDark).GetTextColor(),
FontSize: 10,
StrokeWidth: 1,
Font: opt.Font,
}.WriteTextOptionsToRenderer(d.Render)
text := commafWithDigits(value)
textBox := r.MeasureText(text)
d.text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2)
}
Points: points,
Series: &series,
})
}
return result.d, nil

75
mark_point.go Normal file
View file

@ -0,0 +1,75 @@
// 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"
)
type markPointRenderOption struct {
FillColor drawing.Color
Font *truetype.Font
Series *Series
Points []Point
}
func markPointRender(d *Draw, opt markPointRenderOption) {
s := opt.Series
if len(s.MarkPoint.Data) == 0 {
return
}
points := opt.Points
summary := s.Summary()
symbolSize := s.MarkPoint.SymbolSize
if symbolSize == 0 {
symbolSize = 30
}
r := d.Render
// 设置填充样式
chart.Style{
FillColor: opt.FillColor,
}.WriteToRenderer(r)
// 设置文本样式
chart.Style{
FontColor: NewTheme(ThemeDark).GetTextColor(),
FontSize: 10,
StrokeWidth: 1,
Font: opt.Font,
}.WriteTextOptionsToRenderer(r)
for _, markPointData := range s.MarkPoint.Data {
p := points[summary.MinIndex]
value := summary.MinValue
switch markPointData.Type {
case SeriesMarkPointDataTypeMax:
p = points[summary.MaxIndex]
value = summary.MaxValue
}
d.pin(p.X, p.Y-symbolSize>>1, symbolSize)
text := commafWithDigits(value)
textBox := r.MeasureText(text)
d.text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2)
}
}

View file

@ -136,7 +136,7 @@ func pieChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error) {
r.Stroke()
textStyle := chart.Style{
FontColor: theme.GetTextColor(),
FontSize: 10,
FontSize: labelFontSize,
Font: opt.Font,
}
if !series.Label.Color.IsZero() {

View file

@ -40,9 +40,15 @@ func NewRange(min, max float64, divideCount int) Range {
// 最小单位计算
unit := 2
if r > 10 {
unit = 4
}
if r > 30 {
unit = 5
}
if r > 100 {
unit = 10
}
if r > 200 {
unit = 20
}
unit = int((r/float64(divideCount))/float64(unit))*unit + unit

View file

@ -37,12 +37,16 @@ func TestRange(t *testing.T) {
r = NewRange(0, 12, 6)
assert.Equal(0.0, r.Min)
assert.Equal(30.0, r.Max)
assert.Equal(24.0, r.Max)
r = NewRange(-13, 18, 6)
assert.Equal(-20.0, r.Min)
assert.Equal(40.0, r.Max)
r = NewRange(0, 150, 6)
assert.Equal(0.0, r.Min)
assert.Equal(180.0, r.Max)
r = NewRange(0, 400, 6)
assert.Equal(0.0, r.Min)
assert.Equal(480.0, r.Max)

View file

@ -23,6 +23,7 @@
package charts
import (
"math"
"strings"
"github.com/dustin/go-humanize"
@ -86,6 +87,40 @@ type Series struct {
MarkPoint SeriesMarkPoint
}
type seriesSummary struct {
MaxIndex int
MaxValue float64
MinIndex int
MinValue float64
AverageValue float64
}
func (s *Series) Summary() seriesSummary {
minIndex := -1
maxIndex := -1
minValue := math.MaxFloat64
maxValue := -math.MaxFloat64
sum := float64(0)
for j, item := range s.Data {
if item.Value < minValue {
minIndex = j
minValue = item.Value
}
if item.Value > maxValue {
maxIndex = j
maxValue = item.Value
}
sum += item.Value
}
return seriesSummary{
MaxIndex: maxIndex,
MaxValue: maxValue,
MinIndex: minIndex,
MinValue: minValue,
AverageValue: sum / float64(len(s.Data)),
}
}
type LabelFormatter func(index int, value float64, percent float64) string
func NewPieLabelFormatter(seriesNames []string, layout string) LabelFormatter {

View file

@ -106,7 +106,7 @@ func isFalse(flag *bool) bool {
return false
}
func toFloatPoint(f float64) *float64 {
func NewFloatPoint(f float64) *float64 {
v := f
return &v
}
@ -114,11 +114,11 @@ func commafWithDigits(value float64) string {
decimals := 2
m := float64(1000 * 1000)
if value >= m {
return humanize.CommafWithDigits(value/m, decimals) + " M"
return humanize.CommafWithDigits(value/m, decimals) + "M"
}
k := float64(1000)
if value >= k {
return humanize.CommafWithDigits(value/k, decimals) + " K"
return humanize.CommafWithDigits(value/k, decimals) + "k"
}
return humanize.CommafWithDigits(value, decimals)
}

View file

@ -22,7 +22,9 @@
package charts
import "github.com/wcharczuk/go-chart/v2"
import (
"github.com/wcharczuk/go-chart/v2"
)
type XAxisOption struct {
// The boundary gap on both sides of a coordinate axis.
@ -39,16 +41,19 @@ type XAxisOption struct {
}
// drawXAxis draws x axis, and returns the height, range of if.
func drawXAxis(p *Draw, opt *XAxisOption) (int, *Range, error) {
func drawXAxis(p *Draw, opt *XAxisOption, yAxisCount int) (int, *Range, error) {
if opt.Hidden {
return 0, nil, nil
}
left := YAxisWidth
right := (yAxisCount - 1) * YAxisWidth
dXAxis, err := NewDraw(
DrawOption{
Parent: p,
},
PaddingOption(chart.Box{
Left: YAxisWidth,
Left: left,
Right: right,
}),
)
if err != nil {

View file

@ -81,7 +81,7 @@ func TestDrawXAxis(t *testing.T) {
for _, tt := range tests {
d := tt.newDraw()
height, _, err := drawXAxis(d, tt.newOption())
height, _, err := drawXAxis(d, tt.newOption(), 1)
assert.Nil(err)
assert.Equal(25, height)
data, err := d.Bytes()

View file

@ -41,11 +41,11 @@ type YAxisOption struct {
const YAxisWidth = 40
func drawYAxis(p *Draw, opt *ChartOption, xAxisHeight int, padding chart.Box) (*Range, error) {
func drawYAxis(p *Draw, opt *ChartOption, axisIndex, xAxisHeight int, padding chart.Box) (*Range, error) {
theme := NewTheme(opt.Theme)
yRange := opt.getYRange(0)
yRange := opt.newYRange(axisIndex)
values := yRange.Values()
formatter := opt.YAxis.Formatter
formatter := opt.YAxisList[axisIndex].Formatter
if len(formatter) != 0 {
for index, text := range values {
values[index] = strings.ReplaceAll(formatter, "{value}", text)
@ -64,12 +64,21 @@ func drawYAxis(p *Draw, opt *ChartOption, xAxisHeight int, padding chart.Box) (*
}
width := NewAxis(p, data, style).measureAxis()
yAxisCount := len(opt.YAxisList)
boxWidth := p.Box.Width()
if axisIndex > 0 {
style.SplitLineShow = false
style.Position = PositionRight
padding.Right += (axisIndex - 1) * YAxisWidth
} else {
boxWidth = p.Box.Width() - (yAxisCount-1)*YAxisWidth
padding.Left += (YAxisWidth - width)
}
dYAxis, err := NewDraw(
DrawOption{
Parent: p,
Width: p.Box.Width(),
Width: boxWidth,
// 减去x轴的高
Height: p.Box.Height() - xAxisHeight,
},

View file

@ -49,8 +49,10 @@ func TestDrawYAxis(t *testing.T) {
newDraw: newDraw,
newOption: func() *ChartOption {
return &ChartOption{
YAxis: YAxisOption{
Max: toFloatPoint(20),
YAxisList: []YAxisOption{
{
Max: NewFloatPoint(20),
},
},
SeriesList: []Series{
{
@ -72,7 +74,7 @@ func TestDrawYAxis(t *testing.T) {
for _, tt := range tests {
d := tt.newDraw()
r, err := drawYAxis(d, tt.newOption(), tt.xAxisHeight, chart.NewBox(10, 10, 10, 10))
r, err := drawYAxis(d, tt.newOption(), 0, tt.xAxisHeight, chart.NewBox(10, 10, 10, 10))
assert.Nil(err)
assert.Equal(&Range{
divideCount: 6,