a color type that actually works.

This commit is contained in:
Will Charczuk 2016-07-09 11:23:35 -07:00
parent 0da2b41a9d
commit 1357950324
10 changed files with 181 additions and 69 deletions

View file

@ -350,7 +350,7 @@ func (c Chart) drawSeries(r Renderer, canvasBox Box, index int, s Series, xrange
var x, y int var x, y int
fill := s.GetStyle().GetFillColor() fill := s.GetStyle().GetFillColor()
if !ColorIsZero(fill) { if !fill.IsZero() {
r.SetFillColor(fill) r.SetFillColor(fill)
r.MoveTo(x0+cx, y0+cy) r.MoveTo(x0+cx, y0+cy)
for i := 1; i < s.Len(); i++ { for i := 1; i < s.Len(); i++ {

View file

@ -1,10 +1,10 @@
package chart package chart
import ( import (
"image/color"
"sync" "sync"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
) )
const ( const (
@ -47,44 +47,44 @@ const (
var ( var (
// DefaultBackgroundColor is the default chart background color. // DefaultBackgroundColor is the default chart background color.
// It is equivalent to css color:white. // It is equivalent to css color:white.
DefaultBackgroundColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} DefaultBackgroundColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
// DefaultBackgroundStrokeColor is the default chart border color. // DefaultBackgroundStrokeColor is the default chart border color.
// It is equivalent to color:white. // It is equivalent to color:white.
DefaultBackgroundStrokeColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} DefaultBackgroundStrokeColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
// DefaultCanvasColor is the default chart canvas color. // DefaultCanvasColor is the default chart canvas color.
// It is equivalent to css color:white. // It is equivalent to css color:white.
DefaultCanvasColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} DefaultCanvasColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
// DefaultCanvasStrokColor is the default chart canvas stroke color. // DefaultCanvasStrokColor is the default chart canvas stroke color.
// It is equivalent to css color:white. // It is equivalent to css color:white.
DefaultCanvasStrokColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} DefaultCanvasStrokColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
// DefaultTextColor is the default chart text color. // DefaultTextColor is the default chart text color.
// It is equivalent to #333333. // It is equivalent to #333333.
DefaultTextColor = color.RGBA{R: 51, G: 51, B: 51, A: 255} DefaultTextColor = drawing.Color{R: 51, G: 51, B: 51, A: 255}
// DefaultAxisColor is the default chart axis line color. // DefaultAxisColor is the default chart axis line color.
// It is equivalent to #333333. // It is equivalent to #333333.
DefaultAxisColor = color.RGBA{R: 51, G: 51, B: 51, A: 255} DefaultAxisColor = drawing.Color{R: 51, G: 51, B: 51, A: 255}
// DefaultStrokeColor is the default chart border color. // DefaultStrokeColor is the default chart border color.
// It is equivalent to #efefef. // It is equivalent to #efefef.
DefaultStrokeColor = color.RGBA{R: 239, G: 239, B: 239, A: 255} DefaultStrokeColor = drawing.Color{R: 239, G: 239, B: 239, A: 255}
// DefaultFillColor is the default fill color. // DefaultFillColor is the default fill color.
// It is equivalent to #0074d9. // It is equivalent to #0074d9.
DefaultFillColor = color.RGBA{R: 0, G: 217, B: 116, A: 255} DefaultFillColor = drawing.Color{R: 0, G: 217, B: 116, A: 255}
// DefaultFinalLabelBackgroundColor is the default final label background color. // DefaultFinalLabelBackgroundColor is the default final label background color.
DefaultFinalLabelBackgroundColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} DefaultFinalLabelBackgroundColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
) )
var ( var (
// DefaultSeriesStrokeColors are a couple default series colors. // DefaultSeriesStrokeColors are a couple default series colors.
DefaultSeriesStrokeColors = []color.RGBA{ DefaultSeriesStrokeColors = []drawing.Color{
color.RGBA{R: 0, G: 116, B: 217, A: 255}, drawing.Color{R: 0, G: 116, B: 217, A: 255},
color.RGBA{R: 0, G: 217, B: 116, A: 255}, drawing.Color{R: 0, G: 217, B: 116, A: 255},
color.RGBA{R: 217, G: 0, B: 116, A: 255}, drawing.Color{R: 217, G: 0, B: 116, A: 255},
} }
) )
// GetDefaultSeriesStrokeColor returns a color from the default list by index. // GetDefaultSeriesStrokeColor returns a color from the default list by index.
// NOTE: the index will wrap around (using a modulo).g // NOTE: the index will wrap around (using a modulo).g
func GetDefaultSeriesStrokeColor(index int) color.RGBA { func GetDefaultSeriesStrokeColor(index int) drawing.Color {
finalIndex := index % len(DefaultSeriesStrokeColors) finalIndex := index % len(DefaultSeriesStrokeColors)
return DefaultSeriesStrokeColors[finalIndex] return DefaultSeriesStrokeColors[finalIndex]
} }

85
drawing/color.go Normal file
View file

@ -0,0 +1,85 @@
package drawing
import (
"fmt"
"strconv"
)
var (
// ColorTransparent is a fully transparent color.
ColorTransparent = Color{}
// ColorWhite is white.
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
// ColorBlack is black.
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
// ColorRed is red.
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
// ColorGreen is green.
ColorGreen = Color{R: 0, G: 255, B: 0, A: 255}
// ColorBlue is blue.
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
)
func parseHex(hex string) uint8 {
v, _ := strconv.ParseInt(hex, 16, 16)
return uint8(v)
}
// ColorFromHex returns a color from a css hex code.
func ColorFromHex(hex string) Color {
var c Color
if len(hex) == 3 {
c.R = parseHex(string(hex[0])) * 0x11
c.G = parseHex(string(hex[1])) * 0x11
c.B = parseHex(string(hex[2])) * 0x11
} else {
c.R = parseHex(string(hex[0:2]))
c.G = parseHex(string(hex[2:4]))
c.B = parseHex(string(hex[4:6]))
}
c.A = 255
return c
}
// Color is our internal color type because color.Color is bullshit.
type Color struct {
R uint8
G uint8
B uint8
A uint8
}
// RGBA returns the color as a pre-alpha mixed color set.
func (c Color) RGBA() (r, g, b, a uint32) {
fa := float64(c.A) / 255.0
r = uint32(float64(uint32(c.R)) * fa)
r |= r << 8
g = uint32(float64(uint32(c.G)) * fa)
g |= g << 8
b = uint32(float64(uint32(c.B)) * fa)
b |= b << 8
a = uint32(c.A)
a |= a << 8
return
}
// IsZero returns if the color has been set or not.
func (c Color) IsZero() bool {
return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0
}
// IsTransparent returns if the colors alpha channel is zero.
func (c Color) IsTransparent() bool {
return c.A == 0
}
// String returns a css string representation of the color.
func (c Color) String() string {
fa := float64(c.A) / float64(255)
return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa)
}

41
drawing/color_test.go Normal file
View file

@ -0,0 +1,41 @@
package drawing
import (
"testing"
"github.com/blendlabs/go-assert"
)
func TestColorFromHex(t *testing.T) {
assert := assert.New(t)
white := ColorFromHex("FFFFFF")
assert.Equal(ColorWhite, white)
shortWhite := ColorFromHex("FFF")
assert.Equal(ColorWhite, shortWhite)
black := ColorFromHex("000000")
assert.Equal(ColorBlack, black)
shortBlack := ColorFromHex("000")
assert.Equal(ColorBlack, shortBlack)
red := ColorFromHex("FF0000")
assert.Equal(ColorRed, red)
shortRed := ColorFromHex("F00")
assert.Equal(ColorRed, shortRed)
green := ColorFromHex("00FF00")
assert.Equal(ColorGreen, green)
shortGreen := ColorFromHex("0F0")
assert.Equal(ColorGreen, shortGreen)
blue := ColorFromHex("0000FF")
assert.Equal(ColorBlue, blue)
shortBlue := ColorFromHex("00F")
assert.Equal(ColorBlue, shortBlue)
}

View file

@ -29,7 +29,7 @@ type rasterRenderer struct {
gc *drawing.RasterGraphicContext gc *drawing.RasterGraphicContext
fontSize float64 fontSize float64
fontColor color.RGBA fontColor color.Color
f *truetype.Font f *truetype.Font
} }
@ -39,12 +39,12 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
} }
// SetStrokeColor implements the interface method. // SetStrokeColor implements the interface method.
func (rr *rasterRenderer) SetStrokeColor(c color.RGBA) { func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
rr.gc.SetStrokeColor(c) rr.gc.SetStrokeColor(c)
} }
// SetFillColor implements the interface method. // SetFillColor implements the interface method.
func (rr *rasterRenderer) SetFillColor(c color.RGBA) { func (rr *rasterRenderer) SetFillColor(c drawing.Color) {
rr.gc.SetFillColor(c) rr.gc.SetFillColor(c)
} }
@ -109,7 +109,7 @@ func (rr *rasterRenderer) SetFontSize(size float64) {
} }
// SetFontColor implements the interface method. // SetFontColor implements the interface method.
func (rr *rasterRenderer) SetFontColor(c color.RGBA) { func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
rr.fontColor = c rr.fontColor = c
rr.gc.SetFillColor(c) rr.gc.SetFillColor(c)
rr.gc.SetStrokeColor(c) rr.gc.SetStrokeColor(c)

View file

@ -1,10 +1,10 @@
package chart package chart
import ( import (
"image/color"
"io" "io"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
) )
// RendererProvider is a function that returns a renderer. // RendererProvider is a function that returns a renderer.
@ -16,10 +16,10 @@ type Renderer interface {
SetDPI(dpi float64) SetDPI(dpi float64)
// SetStrokeColor sets the current stroke color. // SetStrokeColor sets the current stroke color.
SetStrokeColor(color.RGBA) SetStrokeColor(drawing.Color)
// SetFillColor sets the current fill color. // SetFillColor sets the current fill color.
SetFillColor(color.RGBA) SetFillColor(drawing.Color)
// SetStrokeWidth sets the stroke width. // SetStrokeWidth sets the stroke width.
SetStrokeWidth(width float64) SetStrokeWidth(width float64)
@ -50,7 +50,7 @@ type Renderer interface {
SetFont(*truetype.Font) SetFont(*truetype.Font)
// SetFontColor sets a font's color // SetFontColor sets a font's color
SetFontColor(color.RGBA) SetFontColor(drawing.Color)
// SetFontSize sets the font size for a text field. // SetFontSize sets the font size for a text field.
SetFontSize(size float64) SetFontSize(size float64)

View file

@ -2,7 +2,6 @@ package chart
import ( import (
"fmt" "fmt"
"image/color"
"strings" "strings"
"github.com/wcharczuk/go-chart/drawing" "github.com/wcharczuk/go-chart/drawing"
@ -11,37 +10,37 @@ import (
// Style is a simple style set. // Style is a simple style set.
type Style struct { type Style struct {
Show bool Show bool
StrokeColor color.RGBA StrokeColor drawing.Color
FillColor color.RGBA FillColor drawing.Color
StrokeWidth float64 StrokeWidth float64
FontSize float64 FontSize float64
FontColor color.RGBA FontColor drawing.Color
Padding Box Padding Box
} }
// IsZero returns if the object is set or not. // IsZero returns if the object is set or not.
func (s Style) IsZero() bool { func (s Style) IsZero() bool {
return ColorIsZero(s.StrokeColor) && ColorIsZero(s.FillColor) && s.StrokeWidth == 0 && s.FontSize == 0 return s.StrokeColor.IsZero() && s.FillColor.IsZero() && s.StrokeWidth == 0 && s.FontSize == 0
} }
// GetStrokeColor returns the stroke color. // GetStrokeColor returns the stroke color.
func (s Style) GetStrokeColor(defaults ...color.RGBA) color.RGBA { func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color {
if ColorIsZero(s.StrokeColor) { if s.StrokeColor.IsZero() {
if len(defaults) > 0 { if len(defaults) > 0 {
return defaults[0] return defaults[0]
} }
return color.RGBA{} return drawing.ColorTransparent
} }
return s.StrokeColor return s.StrokeColor
} }
// GetFillColor returns the fill color. // GetFillColor returns the fill color.
func (s Style) GetFillColor(defaults ...color.RGBA) color.RGBA { func (s Style) GetFillColor(defaults ...drawing.Color) drawing.Color {
if ColorIsZero(s.FillColor) { if s.FillColor.IsZero() {
if len(defaults) > 0 { if len(defaults) > 0 {
return defaults[0] return defaults[0]
} }
return color.RGBA{} return drawing.ColorTransparent
} }
return s.FillColor return s.FillColor
} }
@ -69,12 +68,12 @@ func (s Style) GetFontSize(defaults ...float64) float64 {
} }
// GetFontColor gets the font size. // GetFontColor gets the font size.
func (s Style) GetFontColor(defaults ...color.RGBA) color.RGBA { func (s Style) GetFontColor(defaults ...drawing.Color) drawing.Color {
if ColorIsZero(s.FontColor) { if s.FontColor.IsZero() {
if len(defaults) > 0 { if len(defaults) > 0 {
return defaults[0] return defaults[0]
} }
return color.RGBA{} return drawing.ColorTransparent
} }
return s.FontColor return s.FontColor
} }
@ -93,13 +92,13 @@ func (s Style) SVG(dpi float64) string {
} }
strokeText := "stroke:none" strokeText := "stroke:none"
if !ColorIsZero(sc) { if !sc.IsZero() {
strokeText = "stroke:" + ColorAsString(sc) strokeText = "stroke:" + sc.String()
} }
fillText := "fill:none" fillText := "fill:none"
if !ColorIsZero(fc) { if !fc.IsZero() {
fillText = "fill:" + ColorAsString(fc) fillText = "fill:" + fc.String()
} }
fontSizeText := "" fontSizeText := ""
@ -107,8 +106,8 @@ func (s Style) SVG(dpi float64) string {
fontSizeText = "font-size:" + fmt.Sprintf("%.1fpx", drawing.PointsToPixels(dpi, fs)) fontSizeText = "font-size:" + fmt.Sprintf("%.1fpx", drawing.PointsToPixels(dpi, fs))
} }
if !ColorIsZero(fnc) { if !fnc.IsZero() {
fillText = "fill:" + ColorAsString(fnc) fillText = "fill:" + fnc.String()
} }
return strings.Join([]string{strokeWidthText, strokeText, fillText, fontSizeText}, ";") return strings.Join([]string{strokeWidthText, strokeText, fillText, fontSizeText}, ";")
} }

View file

@ -1,11 +1,11 @@
package main package main
import ( import (
"image/color"
"log" "log"
"net/http" "net/http"
"github.com/wcharczuk/go-chart" "github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-web" "github.com/wcharczuk/go-web"
) )
@ -46,7 +46,7 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
XValues: []float64{1.0, 2.0, 3.0, 4.0}, XValues: []float64{1.0, 2.0, 3.0, 4.0},
YValues: []float64{2.5, 5.0, 2.0, 3.3}, YValues: []float64{2.5, 5.0, 2.0, 3.3},
Style: chart.Style{ Style: chart.Style{
FillColor: color.RGBA{R: 0, G: 116, B: 217, A: 255}, FillColor: drawing.Color{R: 0, G: 116, B: 217, A: 128},
}, },
}, },
chart.ContinuousSeries{ chart.ContinuousSeries{

12
util.go
View file

@ -2,21 +2,9 @@ package chart
import ( import (
"fmt" "fmt"
"image/color"
"time" "time"
) )
// ColorIsZero returns if a color.RGBA is unset or not.
func ColorIsZero(c color.RGBA) bool {
return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0
}
// ColorAsString returns if a color.RGBA is unset or not.
func ColorAsString(c color.RGBA) string {
a := float64(c.A) / float64(255)
return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, a)
}
// MinAndMax returns both the min and max in one pass. // MinAndMax returns both the min and max in one pass.
func MinAndMax(values ...float64) (min float64, max float64) { func MinAndMax(values ...float64) (min float64, max float64) {
if len(values) == 0 { if len(values) == 0 {

View file

@ -3,7 +3,6 @@ package chart
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"image/color"
"io" "io"
"strings" "strings"
@ -43,12 +42,12 @@ func (vr *vectorRenderer) SetDPI(dpi float64) {
} }
// SetStrokeColor implements the interface method. // SetStrokeColor implements the interface method.
func (vr *vectorRenderer) SetStrokeColor(c color.RGBA) { func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) {
vr.s.StrokeColor = c vr.s.StrokeColor = c
} }
// SetFillColor implements the interface method. // SetFillColor implements the interface method.
func (vr *vectorRenderer) SetFillColor(c color.RGBA) { func (vr *vectorRenderer) SetFillColor(c drawing.Color) {
vr.s.FillColor = c vr.s.FillColor = c
} }
@ -73,22 +72,22 @@ func (vr *vectorRenderer) Close() {
// Stroke draws the path with no fill. // Stroke draws the path with no fill.
func (vr *vectorRenderer) Stroke() { func (vr *vectorRenderer) Stroke() {
vr.s.FillColor = color.RGBA{} vr.s.FillColor = drawing.ColorTransparent
vr.s.FontColor = color.RGBA{} vr.s.FontColor = drawing.ColorTransparent
vr.drawPath() vr.drawPath()
} }
// Fill draws the path with no stroke. // Fill draws the path with no stroke.
func (vr *vectorRenderer) Fill() { func (vr *vectorRenderer) Fill() {
vr.s.StrokeColor = color.RGBA{} vr.s.StrokeColor = drawing.ColorTransparent
vr.s.StrokeWidth = 0 vr.s.StrokeWidth = 0
vr.s.FontColor = color.RGBA{} vr.s.FontColor = drawing.ColorTransparent
vr.drawPath() vr.drawPath()
} }
// FillStroke draws the path with both fill and stroke. // FillStroke draws the path with both fill and stroke.
func (vr *vectorRenderer) FillStroke() { func (vr *vectorRenderer) FillStroke() {
vr.s.FontColor = color.RGBA{} vr.s.FontColor = drawing.ColorTransparent
vr.drawPath() vr.drawPath()
} }
@ -108,7 +107,7 @@ func (vr *vectorRenderer) SetFont(f *truetype.Font) {
} }
// SetFontColor implements the interface method. // SetFontColor implements the interface method.
func (vr *vectorRenderer) SetFontColor(c color.RGBA) { func (vr *vectorRenderer) SetFontColor(c drawing.Color) {
vr.s.FontColor = c vr.s.FontColor = c
} }
@ -130,8 +129,8 @@ func (vr *vectorRenderer) svgFontFace() string {
// Text draws a text blob. // Text draws a text blob.
func (vr *vectorRenderer) Text(body string, x, y int) { func (vr *vectorRenderer) Text(body string, x, y int) {
vr.s.FillColor = color.RGBA{} vr.s.FillColor = drawing.ColorTransparent
vr.s.StrokeColor = color.RGBA{} vr.s.StrokeColor = drawing.ColorTransparent
vr.s.StrokeWidth = 0 vr.s.StrokeWidth = 0
vr.c.Text(x, y, body, vr.s.SVG(vr.dpi)+";"+vr.svgFontFace()) vr.c.Text(x, y, body, vr.s.SVG(vr.dpi)+";"+vr.svgFontFace())
} }