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
fill := s.GetStyle().GetFillColor()
if !ColorIsZero(fill) {
if !fill.IsZero() {
r.SetFillColor(fill)
r.MoveTo(x0+cx, y0+cy)
for i := 1; i < s.Len(); i++ {

View file

@ -1,10 +1,10 @@
package chart
import (
"image/color"
"sync"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
)
const (
@ -47,44 +47,44 @@ const (
var (
// DefaultBackgroundColor is the default chart background color.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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 = color.RGBA{R: 255, G: 255, B: 255, A: 255}
DefaultFinalLabelBackgroundColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
)
var (
// DefaultSeriesStrokeColors are a couple default series colors.
DefaultSeriesStrokeColors = []color.RGBA{
color.RGBA{R: 0, G: 116, B: 217, A: 255},
color.RGBA{R: 0, G: 217, B: 116, A: 255},
color.RGBA{R: 217, G: 0, B: 116, A: 255},
DefaultSeriesStrokeColors = []drawing.Color{
drawing.Color{R: 0, G: 116, B: 217, A: 255},
drawing.Color{R: 0, G: 217, B: 116, A: 255},
drawing.Color{R: 217, G: 0, B: 116, A: 255},
}
)
// GetDefaultSeriesStrokeColor returns a color from the default list by index.
// 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)
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
fontSize float64
fontColor color.RGBA
fontColor color.Color
f *truetype.Font
}
@ -39,12 +39,12 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
}
// SetStrokeColor implements the interface method.
func (rr *rasterRenderer) SetStrokeColor(c color.RGBA) {
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
rr.gc.SetStrokeColor(c)
}
// SetFillColor implements the interface method.
func (rr *rasterRenderer) SetFillColor(c color.RGBA) {
func (rr *rasterRenderer) SetFillColor(c drawing.Color) {
rr.gc.SetFillColor(c)
}
@ -109,7 +109,7 @@ func (rr *rasterRenderer) SetFontSize(size float64) {
}
// SetFontColor implements the interface method.
func (rr *rasterRenderer) SetFontColor(c color.RGBA) {
func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
rr.fontColor = c
rr.gc.SetFillColor(c)
rr.gc.SetStrokeColor(c)

View file

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

View file

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

View file

@ -1,11 +1,11 @@
package main
import (
"image/color"
"log"
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"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},
YValues: []float64{2.5, 5.0, 2.0, 3.3},
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{

12
util.go
View file

@ -2,21 +2,9 @@ package chart
import (
"fmt"
"image/color"
"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.
func MinAndMax(values ...float64) (min float64, max float64) {
if len(values) == 0 {

View file

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