diff --git a/chart.go b/chart.go
index a485df5..7c511de 100644
--- a/chart.go
+++ b/chart.go
@@ -43,6 +43,7 @@ type Point struct {
}
type ChartOption struct {
+ Type string
Font *truetype.Font
Theme string
Title TitleOption
@@ -271,6 +272,7 @@ func Render(opt ChartOption) (*Draw, error) {
func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
d, err := NewDraw(
DrawOption{
+ Type: opt.Type,
Parent: opt.Parent,
Width: opt.getWidth(),
Height: opt.getHeight(),
diff --git a/draw.go b/draw.go
index bdd5a2b..c7f0849 100644
--- a/draw.go
+++ b/draw.go
@@ -25,6 +25,7 @@ package charts
import (
"bytes"
"errors"
+ "math"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2"
@@ -151,6 +152,30 @@ func (d *Draw) lineTo(x, y int) {
d.Render.LineTo(x+d.Box.Left, y+d.Box.Top)
}
+func (d *Draw) pin(x, y, width int) {
+ r := float64(width) / 2
+ y -= width / 4
+ angle := chart.DegreesToRadians(15)
+
+ startAngle := math.Pi/2 + angle
+ delta := 2*math.Pi - 2*angle
+ d.arcTo(x, y, r, r, startAngle, delta)
+ d.lineTo(x, y)
+ d.Render.Fill()
+ startX := x - int(r)
+ startY := y
+ endX := x + int(r)
+ endY := y
+ d.moveTo(startX, startY)
+
+ left := d.Box.Left
+ top := d.Box.Top
+ cx := x
+ cy := y + int(r*2.5)
+ d.Render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top)
+ d.Render.Fill()
+}
+
func (d *Draw) circle(radius float64, x, y int) {
d.Render.Circle(radius, x+d.Box.Left, y+d.Box.Top)
}
diff --git a/draw_test.go b/draw_test.go
index 9627d0e..dcba961 100644
--- a/draw_test.go
+++ b/draw_test.go
@@ -239,6 +239,27 @@ func TestDraw(t *testing.T) {
},
result: "",
},
+ {
+ 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.pin(30, 30, 30)
+ },
+ result: "",
+ },
}
for _, tt := range tests {
d, err := NewDraw(DrawOption{
diff --git a/legend.go b/legend.go
index e1202cb..7b6722e 100644
--- a/legend.go
+++ b/legend.go
@@ -129,9 +129,14 @@ func (l *legend) Render() (chart.Box, error) {
}
x = left
for index, text := range opt.Data {
+ seriesColor := theme.GetSeriesColor(index)
+ fillColor := seriesColor
+ if !theme.IsDark() {
+ fillColor = theme.GetBackgroundColor()
+ }
style := chart.Style{
- StrokeColor: theme.GetSeriesColor(index),
- FillColor: theme.GetSeriesColor(index),
+ StrokeColor: seriesColor,
+ FillColor: fillColor,
StrokeWidth: 3,
}
style.GetFillAndStrokeOptions().WriteDrawingOptionsToRenderer(r)
@@ -144,7 +149,7 @@ func (l *legend) Render() (chart.Box, error) {
x = left
renderText = func() {
x += textPadding
- legendDraw.text(text, x, y+legendDotHeight-2)
+ legendDraw.text(text, x, y+legendDotHeight)
x += textBox.Width()
y += (2*legendDotHeight + legendMargin)
}
@@ -156,7 +161,7 @@ func (l *legend) Render() (chart.Box, error) {
}
renderText = func() {
x += textPadding
- legendDraw.text(text, x, y+legendDotHeight-2)
+ legendDraw.text(text, x, y+legendDotHeight)
x += textBox.Width()
x += textPadding
}
diff --git a/legend_test.go b/legend_test.go
index 0274269..db9d459 100644
--- a/legend_test.go
+++ b/legend_test.go
@@ -65,7 +65,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 214,
Bottom: 25,
@@ -86,7 +86,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 400,
Bottom: 25,
@@ -106,7 +106,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 307,
Bottom: 25,
@@ -127,7 +127,7 @@ func TestLegendRender(t *testing.T) {
Orient: OrientVertical,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 61,
Bottom: 70,
@@ -152,7 +152,7 @@ func TestLegendRender(t *testing.T) {
Right: 101,
Bottom: 70,
},
- result: "",
+ result: "",
},
}
diff --git a/line_chart.go b/line_chart.go
index c9b1b7a..91e1a6d 100644
--- a/line_chart.go
+++ b/line_chart.go
@@ -23,6 +23,8 @@
package charts
import (
+ "math"
+
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
)
@@ -47,7 +49,19 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
xRange := result.xRange
for i, series := range opt.SeriesList {
points := make([]Point, 0)
+ minIndex := -1
+ maxIndex := -1
+ minValue := math.MaxFloat64
+ maxValue := -math.MaxFloat64
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{
@@ -86,6 +100,34 @@ func lineChartRender(opt ChartOption, result *basicRenderResult) (*Draw, error)
DotWidth: 2,
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{
+ 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)
+ }
}
return result.d, nil
diff --git a/range.go b/range.go
index 8a3db1e..0d1b459 100644
--- a/range.go
+++ b/range.go
@@ -24,8 +24,6 @@ package charts
import (
"math"
-
- "github.com/dustin/go-humanize"
)
type Range struct {
@@ -69,7 +67,7 @@ func (r Range) Values() []string {
values := make([]string, 0)
for i := 0; i <= r.divideCount; i++ {
v := r.Min + float64(i)*offset
- value := humanize.CommafWithDigits(v, 2)
+ value := commafWithDigits(v)
values = append(values, value)
}
return values
diff --git a/series.go b/series.go
index 1ab95f8..8b5161d 100644
--- a/series.go
+++ b/series.go
@@ -60,6 +60,19 @@ type SeriesLabel struct {
Color drawing.Color
Show bool
}
+
+const (
+ SeriesMarkPointDataTypeMax = "max"
+ SeriesMarkPointDataTypeMin = "min"
+)
+
+type SeriesMarkPointData struct {
+ Type string
+}
+type SeriesMarkPoint struct {
+ SymbolSize int
+ Data []SeriesMarkPointData
+}
type Series struct {
index int
Type string
@@ -69,7 +82,8 @@ type Series struct {
Label SeriesLabel
Name string
// Radius of Pie chart, e.g.: 40%
- Radius string
+ Radius string
+ MarkPoint SeriesMarkPoint
}
type LabelFormatter func(index int, value float64, percent float64) string
diff --git a/theme.go b/theme.go
index 5aa4bf9..23a010c 100644
--- a/theme.go
+++ b/theme.go
@@ -131,8 +131,8 @@ func (t *Theme) GetTextColor() drawing.Color {
if t.IsDark() {
return drawing.Color{
R: 238,
- G: 241,
- B: 250,
+ G: 238,
+ B: 238,
A: 255,
}
}
diff --git a/util.go b/util.go
index 9765bb5..5c1415d 100644
--- a/util.go
+++ b/util.go
@@ -26,6 +26,7 @@ import (
"strconv"
"strings"
+ "github.com/dustin/go-humanize"
"github.com/wcharczuk/go-chart/v2"
)
@@ -109,3 +110,15 @@ func toFloatPoint(f float64) *float64 {
v := f
return &v
}
+func commafWithDigits(value float64) string {
+ decimals := 2
+ m := float64(1000 * 1000)
+ if value >= 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, decimals)
+}
diff --git a/yaxis.go b/yaxis.go
index 8603924..1742e99 100644
--- a/yaxis.go
+++ b/yaxis.go
@@ -23,6 +23,8 @@
package charts
import (
+ "strings"
+
"github.com/wcharczuk/go-chart/v2"
)
@@ -33,6 +35,8 @@ type YAxisOption struct {
Max *float64
// Hidden y axis
Hidden bool
+ // Formatter for y axis text value
+ Formatter string
}
const YAxisWidth = 40
@@ -40,7 +44,15 @@ const YAxisWidth = 40
func drawYAxis(p *Draw, opt *ChartOption, xAxisHeight int, padding chart.Box) (*Range, error) {
theme := NewTheme(opt.Theme)
yRange := opt.getYRange(0)
- data := NewAxisDataListFromStringList(yRange.Values())
+ values := yRange.Values()
+ formatter := opt.YAxis.Formatter
+ if len(formatter) != 0 {
+ for index, text := range values {
+ values[index] = strings.ReplaceAll(formatter, "{value}", text)
+ }
+ }
+
+ data := NewAxisDataListFromStringList(values)
style := AxisOption{
Position: PositionLeft,
BoundaryGap: FalseFlag(),