Fixes width calculation
M Makefile +1 -0
@@ 5,4 5,5 @@ all:
 	-i "${DATA}" \
 	-s \
 	-c "2,Contractor,fill:yellow" \
+	-c "2,Convert,fill:orange" \
 	-c "2,[^ ]Manager,fill:lightgreen" > "${WHO}.svg"

          
M cmd/orgchart/main.go +2 -1
@@ 52,7 52,8 @@ func main() {
 	fltr := goopt.String([]string{"-f"}, "", "Filter for these people's organization (regex)")
 	coln := goopt.String([]string{"-t"}, "", "Display column numbers, indexed by 0, separated by commas; empty cells are omitted; badly formatted numbers are ignored")
 	sup := goopt.Flag([]string{"-s"}, []string{}, "Display superior; only used if results are filtered", "")
-	hil := goopt.Strings([]string{"-c"}, "", "Coloring. Format: <col#>,<regexp>,<css>. First match wins.")
+	hil := goopt.Strings([]string{"-c"}, "", "Coloring. Format: <col#>,<regexp>,<css>. Multiple allowed; first match for each line wins.")
+	//lin := goopt.Strings([]string{"-l"}, "", "Box stroke. Format: <col#>,<regexp>.")
 	goopt.Version = ""
 	goopt.Summary = "Organization chart generator"
 	goopt.Parse(nil)

          
M cmd/orgchart/main_test.go +94 -43
@@ 28,49 28,6 @@ func TestRank(t *testing.T) {
 	assert.Equal(t, 1, len(rs[3]), "wrong number of people in rank 3")
 }
 
-func TestMakePeople(t *testing.T) {
-	s := `C1,B1
-E1,D1
-B1,A
-A,
-B2,A
-D1,B2
-C2,B2`
-	b := bytes.NewBufferString(s)
-	ps, e := makePeople(b, []int{})
-	if e != nil {
-		assert.Fail(t, "failed to parse: %s", e.Error())
-	}
-	rs := rank(ps)
-	d, _ := makeData()
-	ex := rank(d)
-
-	assert.Equal(t, len(ex), len(rs), "different number of ranks")
-	for i, r := range rs {
-		e := ex[i]
-		sort.Sort(r)
-		sort.Sort(e)
-		if assert.Equal(t, len(e), len(r), "different number of people in rank %d", i) {
-			for i, p1 := range r {
-				p0 := e[i]
-				if assert.Equal(t, p0.Name, p1.Name, "different people") {
-					if p0.Manager != nil {
-						if p1.Manager != nil {
-							if assert.Equal(t, p0.Manager.Name, p1.Manager.Name, "different managers") {
-								assert.Equal(t, len(p0.Reports), len(p1.Reports), "different # of reports")
-							}
-						} else {
-							assert.Fail(t, "managers are not equal", "%v & %v", p0.Manager, p1.Manager)
-						}
-					} else if p1.Manager != nil {
-						assert.Fail(t, "managers are not equal", "%v & %v", p0.Manager, p1.Manager)
-					}
-				}
-			}
-		}
-	}
-}
-
 func makeData() (map[string]*Person, *Person) {
 	ps := make(map[string]*Person)
 	a := New("A")

          
@@ 92,3 49,97 @@ func makeData() (map[string]*Person, *Pe
 	ps["E1"] = e1
 	return ps, a
 }
+
+func testMakePeople(t *testing.T) {
+	s := `C1,B1
+E1,D1
+B1,A
+A,
+B2,A
+D1,B2
+C2,B2`
+	b := bytes.NewBufferString(s)
+	rx := make(Ruleset, 0)
+	ps, er := makePeople(b, []int{}, rx)
+	if er != nil {
+		assert.Fail(t, "failed to parse: %s", er.Error())
+	}
+	rs := rank(ps)
+	d, _ := makeData()
+	ex := rank(d)
+
+	assert.Equal(t, len(ex), len(rs), "different number of ranks")
+	for i, r := range rs {
+		e := ex[i]
+		sort.Sort(r)
+		sort.Sort(e)
+		if assert.Equal(t, len(e), len(r), "different number of people in rank %d", i) {
+			for i, p1 := range r {
+				p0 := e[i]
+				if assert.Equal(t, p0.Name, p1.Name, "different people") {
+					if p0.Manager != nil {
+						if p1.Manager != nil {
+							if assert.Equal(t, p0.Manager.Name, p1.Manager.Name, "different managers") {
+								assert.Equal(t, len(p0.Reports), len(p1.Reports), "different # of reports for %s and %s", p0.Name, p1.Name)
+							}
+						} else {
+							assert.Fail(t, "managers are not equal", "%v & %v", p0.Manager, p1.Manager)
+						}
+					} else if p1.Manager != nil {
+						assert.Fail(t, "managers are not equal", "%v & %v", p0.Manager, p1.Manager)
+					}
+				}
+			}
+		}
+	}
+}
+
+func TestDims(t *testing.T) {
+	s := `
+X,
+A,X
+B1,A
+B2,A
+B3,A
+C1B1,B1
+C2B1,B1
+C3B1,B1
+C4B1,B1
+C5B1,B1
+C1B2,B2
+C2B2,B2
+C3B2,B2`
+	b := bytes.NewBufferString(s)
+	rx := make(Ruleset, 0)
+	ps, er := makePeople(b, []int{}, rx)
+	if er != nil {
+		assert.Fail(t, "failed to parse: %s", er.Error())
+	}
+	rs := rank(ps)
+
+	assert.Equal(t, 3, rs.width())
+
+	s = `X,
+A,X
+B1,A
+B2,A
+B3,A
+C1B1,B1
+C2B1,B1
+C3B1,B1
+C1B2,B2
+C1B3,B3
+C2B3,B3
+C3B3,B3
+D1,C2B3
+D2,C2B3`
+	b = bytes.NewBufferString(s)
+	rx = make(Ruleset, 0)
+	ps, er = makePeople(b, []int{}, rx)
+	if er != nil {
+		assert.Fail(t, "failed to parse: %s", er.Error())
+	}
+	rs = rank(ps)
+
+	assert.Equal(t, 5, rs.width())
+}

          
M cmd/orgchart/person.go +10 -14
@@ 45,24 45,20 @@ func (p Person) String() string {
 	return fmt.Sprintf("%s -> %s, %#v", p.Name, mn, rpts)
 }
 
-// FIXME: Feature envy -- belongs in SVG
 func (p Person) Width() int {
 	// Width of children =
-	// 	If all children are leaves, then boxW + gapH
-	// 	Else, sum(map(width, children))
-	allChildrenLeaves := true
-	for _, p := range p.Reports {
-		allChildrenLeaves = allChildrenLeaves && (len(p.Reports) == 0)
-		if !allChildrenLeaves {
-			break
+	// 	If all children are leaves, then 1 (vertical children)
+	// 	Else, sum(map(width, children)) (horizontal children)
+	var w int
+	allLeaves := true
+	for _, c := range p.Reports {
+		if len(c.Reports) > 0 {
+			allLeaves = false
 		}
-	}
-	if allChildrenLeaves {
-		return boxW + gapW
+		w += c.Width()
 	}
-	w := 0
-	for _, r := range p.Reports {
-		w += r.Width()
+	if allLeaves {
+		return 1
 	}
 	return w
 }

          
M cmd/orgchart/rank.go +4 -7
@@ 53,14 53,11 @@ func (rs Ranks) contains(p *Person) bool
 }
 
 func (rs Ranks) width() int {
-	var mw int
-	for _, r := range rs {
-		w := r.width()
-		if w > mw {
-			mw = w
-		}
+	var w int
+	for _, r := range rs[0] {
+		w += r.Width()
 	}
-	return mw
+	return w
 }
 
 func (rs Ranks) height() int {

          
M cmd/orgchart/svg.go +15 -5
@@ 7,6 7,7 @@ import (
 )
 
 var pageWidth int
+var bgW int
 
 /**************************************************************************
  *  SVG generating code                                                   *

          
@@ 27,7 28,7 @@ func (b Box) in() (int, int) {
 	return b.X + boxW/2, b.Y
 }
 
-func renderSVG(out io.Writer, rs Ranks, sup bool) {
+func setBoxH(rs Ranks) {
 	boxH = 30
 	ty := textSize + textPY
 	for _, b := range rs[0][0].LineMask {

          
@@ 35,8 36,13 @@ func renderSVG(out io.Writer, rs Ranks, 
 			boxH += ty
 		}
 	}
+}
+
+func renderSVG(out io.Writer, rs Ranks, sup bool) {
+	setBoxH(rs)
 	c := svg.New(out)
-	pageWidth := rs.width()
+	bgW = boxW + gapW
+	pageWidth := rs.width() * bgW
 	h := rs.height()
 	if sup {
 		h += boxH + gapH + (marginH * 2)

          
@@ 66,7 72,7 @@ func renderSVG(out io.Writer, rs Ranks, 
 	}
 	for _, p := range rs[0] {
 		drawOrg(c, xoff, marginH/2, p, b, true, false, sup)
-		xoff += p.Width()
+		xoff += width(p)
 	}
 	for _, r := range rs {
 		connectBoxes(c, r, b)

          
@@ 113,7 119,7 @@ func drawOrg(canvas *svg.SVG, xoffset, y
 			if isLeaf {
 				yoffset += boxH + (gapH / 2)
 			} else {
-				xoffset += q.Width()
+				xoffset += width(q)
 			}
 		}
 	}

          
@@ 121,7 127,7 @@ func drawOrg(canvas *svg.SVG, xoffset, y
 
 func drawPerson(canvas *svg.SVG, x, y, ind int, person *Person, boxes map[string]Box, root, leaf bool) {
 	if !leaf {
-		x += (person.Width() - (boxW + (gapH / 2))) / 2
+		x += (width(person) - (boxW + (gapH / 2))) / 2
 	}
 	drawBox(canvas, x, y, person, root)
 	boxes[person.Name] = Box{X: x, Y: y, Leaf: leaf}

          
@@ 187,3 193,7 @@ func connect(c *svg.SVG, f, t Box) {
 	}
 	c.Polyline(xs, ys, `fill: none; stroke: indigo; stroke-width: 3; opacity: 1`)
 }
+
+func width(p *Person) int {
+	return p.Width() * (boxW + gapW)
+}