Support timeline
This commit is contained in:
parent
406d35c6a6
commit
a1f58d4315
9 changed files with 246 additions and 22 deletions
|
|
@ -101,7 +101,7 @@ func main() {
|
|||
4,
|
||||
2,
|
||||
},
|
||||
}).MarkLine(0, 0, p.Width())
|
||||
}).MarkLine(0, 0, p.Width(), false)
|
||||
|
||||
top += 60
|
||||
// Polygon
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
|
|||
Font: opt.Font,
|
||||
Series: series,
|
||||
Range: yRange,
|
||||
Points: points,
|
||||
})
|
||||
}
|
||||
// 最大、最小的mark point
|
||||
|
|
|
|||
157
main/main.go
Normal file
157
main/main.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
|
||||
charts "github.com/vicanso/go-charts/v2"
|
||||
)
|
||||
|
||||
func writeFile(buf []byte) error {
|
||||
tmpPath := "./main"
|
||||
err := os.MkdirAll(tmpPath, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := filepath.Join(tmpPath, "time-line-chart.png")
|
||||
err = os.WriteFile(file, buf, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
xAxisValue := []string{}
|
||||
values := []float64{}
|
||||
now := time.Now()
|
||||
firstAxis := 0
|
||||
for i := 0; i < 300; i++ {
|
||||
if firstAxis == 0 && now.Minute() == 0 {
|
||||
firstAxis = i
|
||||
}
|
||||
xAxisValue = append(xAxisValue, now.Format("15:04"))
|
||||
now = now.Add(time.Minute)
|
||||
value, _ := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if (i%50)/10 < 3 {
|
||||
values = append(values, math.NaN())
|
||||
} else {
|
||||
values = append(values, float64(value.Int64()))
|
||||
}
|
||||
}
|
||||
p, err := charts.LineRender(
|
||||
[][]float64{
|
||||
values,
|
||||
},
|
||||
charts.TitleTextOptionFunc("Line"),
|
||||
charts.XAxisDataOptionFunc(xAxisValue, charts.FalseFlag()),
|
||||
charts.LegendLabelsOptionFunc([]string{
|
||||
"Demo",
|
||||
}, "50"),
|
||||
func(opt *charts.ChartOption) {
|
||||
opt.SeriesList[0].MarkLine = charts.SeriesMarkLine{
|
||||
Data: []charts.SeriesMarkData{
|
||||
{
|
||||
Type: charts.SeriesMarkDataTypeCustom,
|
||||
CustomYVal: -1,
|
||||
FillColor: &charts.Color{
|
||||
R: 240,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
StrokeColor: &charts.Color{
|
||||
R: 240,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 150,
|
||||
},
|
||||
HideValue: true,
|
||||
IgnoreStrokeDashed: true,
|
||||
IgnoreArrow: true,
|
||||
StrokeWidth: 4,
|
||||
XAxisIndex: 200,
|
||||
XAxisEndIndex: 250,
|
||||
},
|
||||
{
|
||||
Type: charts.SeriesMarkDataTypeCustom,
|
||||
CustomYVal: 80,
|
||||
FillColor: &charts.Color{
|
||||
R: 240,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
StrokeColor: &charts.Color{
|
||||
R: 240,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
AboveColor: &charts.Color{
|
||||
R: 240,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: charts.SeriesMarkDataTypeCustom,
|
||||
CustomYVal: 50,
|
||||
FillColor: &charts.Color{
|
||||
R: 255,
|
||||
G: 165,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
StrokeColor: &charts.Color{
|
||||
R: 255,
|
||||
G: 165,
|
||||
B: 0,
|
||||
A: 255,
|
||||
},
|
||||
BelowColor: &charts.Color{
|
||||
R: 255,
|
||||
G: 165,
|
||||
B: 0,
|
||||
A: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opt.XAxis.FirstAxis = firstAxis
|
||||
opt.XAxis.SplitNumber = 60
|
||||
opt.Legend.Padding = charts.Box{
|
||||
Top: 5,
|
||||
Bottom: 10,
|
||||
}
|
||||
opt.SymbolShow = charts.FalseFlag()
|
||||
opt.LineStrokeWidth = 1
|
||||
opt.ValueFormatter = func(f float64) string {
|
||||
return fmt.Sprintf("%.0f", f)
|
||||
}
|
||||
},
|
||||
charts.PaddingOptionFunc(chart.NewBox(10, 10, 30, 10)),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = writeFile(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
55
mark_line.go
55
mark_line.go
|
|
@ -24,6 +24,7 @@ package charts
|
|||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
// NewMarkLine returns a series mark line
|
||||
|
|
@ -63,6 +64,7 @@ type markLineRenderOption struct {
|
|||
Font *truetype.Font
|
||||
Series Series
|
||||
Range axisRange
|
||||
Points []Point
|
||||
}
|
||||
|
||||
func (m *markLinePainter) Render() (Box, error) {
|
||||
|
|
@ -93,14 +95,23 @@ func (m *markLinePainter) Render() (Box, error) {
|
|||
fontColor = *markLine.FontColor
|
||||
}
|
||||
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
StrokeColor: strokeColor,
|
||||
StrokeWidth: 1,
|
||||
StrokeDashArray: []float64{
|
||||
strokeWidth := float64(1)
|
||||
if markLine.StrokeWidth != 0 {
|
||||
strokeWidth = markLine.StrokeWidth
|
||||
}
|
||||
|
||||
var strokeDashArray []float64
|
||||
if !markLine.IgnoreStrokeDashed {
|
||||
strokeDashArray = []float64{
|
||||
4,
|
||||
2,
|
||||
},
|
||||
}
|
||||
}
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: fillColor,
|
||||
StrokeColor: strokeColor,
|
||||
StrokeWidth: strokeWidth,
|
||||
StrokeDashArray: strokeDashArray,
|
||||
}).OverrideTextStyle(Style{
|
||||
Font: font,
|
||||
FontColor: fontColor,
|
||||
|
|
@ -121,8 +132,36 @@ func (m *markLinePainter) Render() (Box, error) {
|
|||
width := painter.Width()
|
||||
text := commafWithDigits(value)
|
||||
textBox := painter.MeasureText(text)
|
||||
painter.MarkLine(0, y, width-2)
|
||||
painter.Text(text, width, y+textBox.Height()>>1-2)
|
||||
endOffset := 2
|
||||
if markLine.IgnoreArrow {
|
||||
endOffset = 0
|
||||
}
|
||||
xPoint := 0
|
||||
if markLine.XAxisIndex > 0 {
|
||||
xPoint = opt.Points[markLine.XAxisIndex].X
|
||||
}
|
||||
xAxiesEndIndex := width
|
||||
if markLine.XAxisEndIndex > 0 {
|
||||
xAxiesEndIndex = opt.Points[markLine.XAxisEndIndex].X
|
||||
}
|
||||
painter.MarkLine(xPoint, y, xAxiesEndIndex-endOffset-xPoint, markLine.IgnoreArrow)
|
||||
if !markLine.HideValue {
|
||||
painter.Text(text, width, y+textBox.Height()>>1-2)
|
||||
}
|
||||
|
||||
if markLine.AboveColor != nil {
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: *markLine.AboveColor,
|
||||
})
|
||||
painter.Rect(chart.NewBox(y, 0, width, 0))
|
||||
}
|
||||
|
||||
if markLine.BelowColor != nil {
|
||||
painter.OverrideDrawingStyle(Style{
|
||||
FillColor: *markLine.BelowColor,
|
||||
})
|
||||
painter.Rect(chart.NewBox(opt.Range.size, 0, width, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
return BoxZero, nil
|
||||
|
|
|
|||
|
|
@ -113,7 +113,11 @@ func (m *markPointPainter) Render() (Box, error) {
|
|||
value = markPointData.CustomYVal
|
||||
}
|
||||
|
||||
painter.Pin(p.X, p.Y-symbolSize>>1, symbolSize)
|
||||
pinY := p.Y
|
||||
if p.Y < symbolSize {
|
||||
pinY = symbolSize
|
||||
}
|
||||
painter.Pin(p.X, pinY-symbolSize>>1, symbolSize)
|
||||
text := commafWithDigits(value)
|
||||
textBox := painter.MeasureText(text)
|
||||
if textBox.Width() > symbolSize {
|
||||
|
|
|
|||
25
painter.go
25
painter.go
|
|
@ -460,6 +460,12 @@ func (p *Painter) LineStroke(points []Point) *Painter {
|
|||
shouldMoveTo = true
|
||||
continue
|
||||
}
|
||||
if y < 0 {
|
||||
// Ignore point, create a gap.
|
||||
shouldMoveTo = true
|
||||
continue
|
||||
}
|
||||
|
||||
if shouldMoveTo || index == 0 {
|
||||
p.MoveTo(x, y)
|
||||
shouldMoveTo = false
|
||||
|
|
@ -517,17 +523,24 @@ func (p *Painter) SetBackground(width, height int, color Color, inside ...bool)
|
|||
p.FillStroke()
|
||||
return p
|
||||
}
|
||||
func (p *Painter) MarkLine(x, y, width int) *Painter {
|
||||
func (p *Painter) MarkLine(x, y, width int, ignoreArrow bool) *Painter {
|
||||
arrowWidth := 16
|
||||
arrowHeight := 10
|
||||
endX := x + width
|
||||
radius := 3
|
||||
p.Circle(3, x+radius, y)
|
||||
p.render.Fill()
|
||||
p.MoveTo(x+radius*3, y)
|
||||
p.LineTo(endX-arrowWidth, y)
|
||||
if !ignoreArrow {
|
||||
p.Circle(3, x+radius, y)
|
||||
p.render.Fill()
|
||||
p.MoveTo(x+radius*3, y)
|
||||
p.LineTo(endX-arrowWidth, y)
|
||||
} else {
|
||||
p.MoveTo(x, y)
|
||||
p.LineTo(endX, y)
|
||||
}
|
||||
p.Stroke()
|
||||
p.ArrowRight(endX, y, arrowWidth, arrowHeight)
|
||||
if !ignoreArrow {
|
||||
p.ArrowRight(endX, y, arrowWidth, arrowHeight)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ func TestPainter(t *testing.T) {
|
|||
2,
|
||||
},
|
||||
})
|
||||
p.MarkLine(0, 20, 300)
|
||||
p.MarkLine(0, 20, 300, false)
|
||||
},
|
||||
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>",
|
||||
},
|
||||
|
|
|
|||
3
range.go
3
range.go
|
|
@ -129,6 +129,9 @@ func (r *axisRange) getHeight(value float64) int {
|
|||
}
|
||||
|
||||
func (r *axisRange) getRestHeight(value float64) int {
|
||||
if math.IsNaN(value) {
|
||||
return -1
|
||||
}
|
||||
return r.size - r.getHeight(value)
|
||||
}
|
||||
|
||||
|
|
|
|||
17
series.go
17
series.go
|
|
@ -100,11 +100,18 @@ type SeriesMarkData struct {
|
|||
Type string
|
||||
|
||||
// Custom options.
|
||||
XAxisIndex int
|
||||
CustomYVal float64
|
||||
FillColor *Color
|
||||
StrokeColor *Color
|
||||
FontColor *Color
|
||||
XAxisIndex int
|
||||
XAxisEndIndex int
|
||||
CustomYVal float64
|
||||
FillColor *Color
|
||||
StrokeColor *Color
|
||||
FontColor *Color
|
||||
AboveColor *Color
|
||||
BelowColor *Color
|
||||
HideValue bool
|
||||
StrokeWidth float64
|
||||
IgnoreStrokeDashed bool
|
||||
IgnoreArrow bool
|
||||
}
|
||||
type SeriesMarkPoint struct {
|
||||
// The width of symbol, default value is 30
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue