This commit is contained in:
Will Charczuk 2016-07-07 14:44:03 -07:00
parent e09aa43a1e
commit cbc4672f1e
13 changed files with 359 additions and 303 deletions

224
chart.go
View file

@ -1,138 +1,83 @@
package chart
import (
"image/color"
"io"
"github.com/golang/freetype/truetype"
)
// Chart is what we're drawing.
/*
The chart box model is as follows:
0,0 width,0
cl,ct cr,ct
cl,cb cr,cb
0, height width,height
*/
type Chart struct {
Title string
TitleFontSize float64
Title string
TitleStyle Style
Width int
Height int
Padding int
Padding Box
BackgroundColor color.RGBA
CanvasBackgroundColor color.RGBA
Background Style
Canvas Style
Axes Style
FinalValueLabel Style
AxisShow bool
AxisStyle Style
AxisFontSize float64
CanvasBorderShow bool
CanvasBorderStyle Style
FinalValueLabelShow bool
FinalValueStyle Style
FontColor color.RGBA
Font *truetype.Font
XRange Range
YRange Range
Font *truetype.Font
Series []Series
}
// GetTitleFontSize calculates or returns the title font size.
func (c Chart) GetTitleFontSize() float64 {
if c.TitleFontSize != 0 {
if c.TitleFontSize > DefaultMinimumFontSize {
return c.TitleFontSize
}
}
fontSize := float64(c.Height >> 3)
if fontSize > DefaultMinimumFontSize {
return fontSize
}
return DefaultMinimumFontSize
}
// GetCanvasTop gets the top corner pixel.
func (c Chart) GetCanvasTop() int {
return c.Padding
return c.Padding.GetTop(DefaultCanvasPadding.Top)
}
// GetCanvasLeft gets the left corner pixel.
func (c Chart) GetCanvasLeft() int {
return c.Padding
return c.Padding.GetLeft(DefaultCanvasPadding.Left)
}
// GetCanvasBottom gets the bottom corner pixel.
func (c Chart) GetCanvasBottom() int {
return c.Height - c.Padding
return c.Height - c.Padding.GetBottom(DefaultCanvasPadding.Bottom)
}
// GetCanvasRight gets the right corner pixel.
func (c Chart) GetCanvasRight() int {
return c.Width - c.Padding
return c.Width - c.Padding.GetRight(DefaultCanvasPadding.Right)
}
// GetCanvasWidth returns the width of the canvas.
func (c Chart) GetCanvasWidth() int {
if c.Padding > 0 {
return c.Width - (c.Padding << 1)
}
return c.Width
return c.Width - (c.Padding.GetLeft(DefaultCanvasPadding.Left) + c.Padding.GetRight(DefaultCanvasPadding.Right))
}
// GetCanvasHeight returns the height of the canvas.
func (c Chart) GetCanvasHeight() int {
if c.Padding > 0 {
return c.Height - (c.Padding << 1)
}
return c.Height
return c.Height - (c.Padding.GetTop(DefaultCanvasPadding.Top) + c.Padding.GetBottom(DefaultCanvasPadding.Bottom))
}
// GetBackgroundColor returns the chart background color.
func (c Chart) GetBackgroundColor() color.RGBA {
if ColorIsZero(c.BackgroundColor) {
c.BackgroundColor = DefaultBackgroundColor
}
return c.BackgroundColor
}
// GetCanvasBackgroundColor returns the canvas background color.
func (c Chart) GetCanvasBackgroundColor() color.RGBA {
if ColorIsZero(c.CanvasBackgroundColor) {
c.CanvasBackgroundColor = DefaultCanvasColor
}
return c.CanvasBackgroundColor
}
// GetTextFont returns the text font.
func (c Chart) GetTextFont() (*truetype.Font, error) {
// GetFont returns the text font.
func (c Chart) GetFont() (*truetype.Font, error) {
if c.Font != nil {
return c.Font, nil
}
return GetDefaultFont()
}
// GetTextFontColor returns the text font color.
func (c Chart) GetTextFontColor() color.RGBA {
if ColorIsZero(c.FontColor) {
c.FontColor = DefaultTextColor
}
return c.FontColor
}
// Render renders the chart with the given renderer to the given io.Writer.
func (c Chart) Render(provider RendererProvider, w io.Writer) error {
func (c *Chart) Render(provider RendererProvider, w io.Writer) error {
xrange, yrange := c.initRanges()
println("xrange", xrange.String())
println("yrange", yrange.String())
r := provider(c.Width, c.Height)
c.drawBackground(r)
c.drawCanvas(r)
c.drawAxes(r)
for _, series := range c.Series {
c.drawSeries(r, series)
c.drawSeries(r, series, xrange, yrange)
}
err := c.drawTitle(r)
if err != nil {
@ -141,46 +86,83 @@ func (c Chart) Render(provider RendererProvider, w io.Writer) error {
return r.Save(w)
}
func (c Chart) initRanges() (xrange Range, yrange Range) {
//iterate over each series, pull out the min/max for x,y
var didSetFirstValues bool
var globalMinY, globalMinX float64
var globalMaxY, globalMaxX float64
for _, s := range c.Series {
seriesLength := s.Len()
for index := 0; index < seriesLength; index++ {
vx, vy := s.GetValue(index)
if didSetFirstValues {
if globalMinX > vx {
globalMinX = vx
}
if globalMinY > vy {
globalMinY = vy
}
if globalMaxX < vx {
globalMaxX = vx
}
if globalMaxY < vy {
globalMaxY = vy
}
} else {
globalMinX, globalMaxX = vx, vx
globalMinY, globalMaxY = vy, vy
didSetFirstValues = true
}
}
}
if c.XRange.IsZero() {
xrange.Min = globalMinX
xrange.Max = globalMaxX
} else {
xrange.Min = c.XRange.Min
xrange.Max = c.XRange.Max
}
xrange.Domain = c.GetCanvasWidth()
if c.YRange.IsZero() {
yrange.Min = globalMinY
yrange.Max = globalMaxY
} else {
yrange.Min = c.YRange.Min
yrange.Max = c.YRange.Max
}
yrange.Domain = c.GetCanvasHeight()
return
}
func (c Chart) drawBackground(r Renderer) {
r.SetStrokeColor(c.GetBackgroundColor())
r.SetFillColor(c.GetBackgroundColor())
r.SetLineWidth(0)
r.SetFillColor(c.Background.GetFillColor(DefaultBackgroundColor))
r.MoveTo(0, 0)
r.LineTo(c.Width, 0)
r.LineTo(c.Width, c.Height)
r.LineTo(0, c.Height)
r.LineTo(0, 0)
r.FillStroke()
r.Close()
r.Fill()
}
func (c Chart) drawCanvas(r Renderer) {
if !c.CanvasBorderStyle.IsZero() {
r.SetStrokeColor(c.CanvasBorderStyle.GetStrokeColor())
r.SetLineWidth(c.CanvasBorderStyle.GetStrokeWidth())
} else {
r.SetStrokeColor(c.GetCanvasBackgroundColor())
r.SetLineWidth(0)
}
r.SetFillColor(c.GetCanvasBackgroundColor())
r.SetFillColor(c.Canvas.GetFillColor(DefaultCanvasColor))
r.MoveTo(c.GetCanvasLeft(), c.GetCanvasTop())
r.LineTo(c.GetCanvasRight(), c.GetCanvasTop())
r.LineTo(c.GetCanvasRight(), c.GetCanvasBottom())
r.LineTo(c.GetCanvasLeft(), c.GetCanvasBottom())
r.LineTo(c.GetCanvasLeft(), c.GetCanvasTop())
r.FillStroke()
r.Fill()
r.Close()
}
func (c Chart) drawAxes(r Renderer) {
if c.AxisShow {
if !c.AxisStyle.IsZero() {
r.SetStrokeColor(c.AxisStyle.GetStrokeColor())
r.SetLineWidth(c.AxisStyle.GetStrokeWidth())
} else {
r.SetStrokeColor(DefaultAxisColor)
r.SetLineWidth(DefaultAxisLineWidth)
}
if c.Axes.Show {
r.SetStrokeColor(c.Axes.GetStrokeColor(DefaultAxisColor))
r.SetLineWidth(c.Axes.GetStrokeWidth(DefaultLineWidth))
r.MoveTo(c.GetCanvasLeft(), c.GetCanvasBottom())
r.LineTo(c.GetCanvasRight(), c.GetCanvasBottom())
r.LineTo(c.GetCanvasRight(), c.GetCanvasTop())
@ -188,46 +170,48 @@ func (c Chart) drawAxes(r Renderer) {
}
}
func (c Chart) drawSeries(r Renderer, s Series) {
r.SetLineWidth(s.GetStyle().GetStrokeWidth())
r.SetStrokeColor(s.GetStyle().GetStrokeColor())
xrange := s.GetXRange(c.GetCanvasWidth())
yrange := s.GetYRange(c.GetCanvasHeight())
func (c Chart) drawSeries(r Renderer, s Series, xrange, yrange Range) {
r.SetStrokeColor(s.GetStyle().GetStrokeColor(DefaultLineColor))
r.SetLineWidth(s.GetStyle().GetStrokeWidth(DefaultLineWidth))
if s.Len() == 0 {
return
}
v0x, v0y := s.GetValue(0)
x0 := xrange.Translate(v0x)
y0 := yrange.Translate(v0y)
r.MoveTo(x0, y0)
px := c.Padding.GetLeft(DefaultCanvasPadding.Left)
py := c.Padding.GetTop(DefaultCanvasPadding.Top)
var vx interface{}
var vy float64
cw := c.GetCanvasWidth()
v0x, v0y := s.GetValue(0)
x0 := cw - xrange.Translate(v0x)
y0 := yrange.Translate(v0y)
r.MoveTo(x0+px, y0+py)
var vx, vy float64
var x, y int
for index := 0; index < s.Len(); index++ {
for index := 1; index < s.Len(); index++ {
vx, vy = s.GetValue(index)
x = xrange.Translate(vx)
x = cw - xrange.Translate(vx)
y = yrange.Translate(vy)
r.LineTo(x, y)
r.LineTo(x+px, y+py)
}
r.Stroke()
}
func (c Chart) drawTitle(r Renderer) error {
if len(c.Title) > 0 {
font, err := c.GetTextFont()
if len(c.Title) > 0 && c.TitleStyle.Show {
font, err := c.GetFont()
if err != nil {
return err
}
r.SetFontColor(c.GetTextFontColor())
r.SetFontColor(c.Canvas.GetFontColor(DefaultTextColor))
r.SetFont(font)
r.SetFontSize(c.GetTitleFontSize())
titleFontSize := c.Canvas.GetFontSize(DefaultTitleFontSize)
r.SetFontSize(titleFontSize)
textWidth := r.MeasureText(c.Title)
titleX := (c.Width >> 1) - (textWidth >> 1)
titleY := c.GetCanvasTop() + int(c.GetTitleFontSize()/2.0)
titleY := c.GetCanvasTop() + int(titleFontSize)
r.Text(c.Title, titleX, titleY)
}
return nil