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:
parent
17b28beae8
commit
b713ff85cc
22 changed files with 511 additions and 72 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
35
drawing/curve_test.go
Normal 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())
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue