Adds the ability to draw an XY scatter plot. (#27)

* works more or less

* updating comment

* removing debugging printf

* adding output

* tweaks

* missed a couple series validations

* testing auto coloring

* updated output.png

* color tests etc.

* sanity check tests.

* should not use unkeyed fields anyway.
This commit is contained in:
Will Charczuk 2017-03-05 16:54:40 -08:00 committed by GitHub
parent 17b28beae8
commit b713ff85cc
22 changed files with 511 additions and 72 deletions

View file

@ -46,12 +46,20 @@ func ColorFromHex(hex string) Color {
return c
}
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color {
fa := float64(a) / 255.0
var c Color
c.R = uint8(float64(r) / fa)
c.G = uint8(float64(g) / fa)
c.B = uint8(float64(b) / fa)
c.A = uint8(a | (a >> 8))
return c
}
// Color is our internal color type because color.Color is bullshit.
type Color struct {
R uint8
G uint8
B uint8
A uint8
R, G, B, A uint8
}
// RGBA returns the color as a pre-alpha mixed color set.
@ -88,6 +96,24 @@ func (c Color) WithAlpha(a uint8) Color {
}
}
// Equals returns true if the color equals another.
func (c Color) Equals(other Color) bool {
return c.R == other.R &&
c.G == other.G &&
c.B == other.B &&
c.A == other.A
}
// AverageWith averages two colors.
func (c Color) AverageWith(other Color) Color {
return Color{
R: (c.R + other.R) >> 1,
G: (c.G + other.G) >> 1,
B: (c.B + other.B) >> 1,
A: c.A,
}
}
// String returns a css string representation of the color.
func (c Color) String() string {
fa := float64(c.A) / float64(255)

View file

@ -3,6 +3,8 @@ package drawing
import (
"testing"
"image/color"
"github.com/blendlabs/go-assert"
)
@ -39,3 +41,13 @@ func TestColorFromHex(t *testing.T) {
shortBlue := ColorFromHex("00F")
assert.Equal(ColorBlue, shortBlue)
}
func TestColorFromAlphaMixedRGBA(t *testing.T) {
assert := assert.New(t)
black := ColorFromAlphaMixedRGBA(color.Black.RGBA())
assert.True(black.Equals(ColorBlack), black.String())
white := ColorFromAlphaMixedRGBA(color.White.RGBA())
assert.True(white.Equals(ColorWhite), white.String())
}

View file

@ -1,8 +1,6 @@
package drawing
import (
"math"
)
import "math"
const (
// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
@ -98,31 +96,60 @@ func SubdivideQuad(c, c1, c2 []float64) {
return
}
func traceWindowIndices(i int) (startAt, endAt int) {
startAt = i * 6
endAt = startAt + 6
return
}
func traceCalcDeltas(c []float64) (dx, dy, d float64) {
dx = c[4] - c[0]
dy = c[5] - c[1]
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
return
}
func traceIsFlat(dx, dy, d, threshold float64) bool {
return (d * d) < threshold*(dx*dx+dy*dy)
}
func traceGetWindow(curves []float64, i int) []float64 {
startAt, endAt := traceWindowIndices(i)
return curves[startAt:endAt]
}
// TraceQuad generate lines subdividing the curve using a Liner
// flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) {
const curveLen = CurveRecursionLimit * 6
const curveEndIndex = curveLen - 1
const lastIteration = CurveRecursionLimit - 1
// Allocates curves stack
var curves [CurveRecursionLimit * 6]float64
curves := make([]float64, curveLen)
// copy 6 elements from the quad path to the stack
copy(curves[0:6], quad[0:6])
i := 0
// current curve
var i int
var c []float64
var dx, dy, d float64
for i >= 0 {
c = curves[i*6:]
dx = c[4] - c[0]
dy = c[5] - c[1]
c = traceGetWindow(curves, i)
dx, dy, d = traceCalcDeltas(c)
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
// bail early if the distance is 0
if d == 0 {
return
}
// if it's flat then trace a line
if (d*d) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
if traceIsFlat(dx, dy, d, flatteningThreshold) || i == lastIteration {
t.LineTo(c[4], c[5])
i--
} else {
// second half of bezier go lower onto the stack
SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:])
SubdivideQuad(c, traceGetWindow(curves, i+1), traceGetWindow(curves, i))
i++
}
}

35
drawing/curve_test.go Normal file
View file

@ -0,0 +1,35 @@
package drawing
import (
"testing"
assert "github.com/blendlabs/go-assert"
)
type point struct {
X, Y float64
}
type mockLine struct {
inner []point
}
func (ml *mockLine) LineTo(x, y float64) {
ml.inner = append(ml.inner, point{x, y})
}
func (ml mockLine) Len() int {
return len(ml.inner)
}
func TestTraceQuad(t *testing.T) {
assert := assert.New(t)
// Quad
// x1, y1, cpx1, cpy2, x2, y2 float64
// do the 9->12 circle segment
quad := []float64{10, 20, 20, 20, 20, 10}
liner := &mockLine{}
TraceQuad(liner, quad, 0.5)
assert.NotZero(liner.Len())
}

View file

@ -23,10 +23,10 @@ type Flattener interface {
// Flatten convert curves into straight segments keeping join segments info
func Flatten(path *Path, flattener Flattener, scale float64) {
// First Point
var startX, startY float64 = 0, 0
var startX, startY float64
// Current Point
var x, y float64 = 0, 0
i := 0
var x, y float64
var i int
for _, cmp := range path.Components {
switch cmp {
case MoveToComponent:
@ -43,6 +43,7 @@ func Flatten(path *Path, flattener Flattener, scale float64) {
flattener.LineJoin()
i += 2
case QuadCurveToComponent:
// we include the previous point for the start of the curve
TraceQuad(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+2], path.Points[i+3]
flattener.LineTo(x, y)