Report-to (show filtered people's superior)
3 files changed, 76 insertions(+), 28 deletions(-)

M cmd/orgchart/main.go
M cmd/orgchart/main_test.go
M cmd/orgchart/svg.go
M cmd/orgchart/main.go +34 -20
@@ 1,9 1,9 @@ 
 package main
 
-// TODO: Report-to (e.g., show anchor person's superior)
+// TODO: Coloring by regexp
 // TODO: Support for ordering
 // TODO: Max-depth (e.g., show only X levels below anchor person)
-// TODO: Dotted lines
+// TODO: Dotted line report-to
 
 import (
 	"encoding/csv"

          
@@ 40,24 40,25 @@ var boxH int
  *  Main                                                                  *
  **************************************************************************/
 func main() {
-	c := flag.String("c", "", "CSV input file")
-	o := flag.String("f", "", "Filter for these people's organization (regex)")
-	t := flag.String("t", "", "Display column numbers, indexed by 0, separated by commas; empty cells are omitted; badly formatted numbers are ignored")
+	csv := flag.String("c", "", "CSV input file")
+	fltr := flag.String("f", "", "Filter for these people's organization (regex)")
+	coln := flag.String("t", "", "Display column numbers, indexed by 0, separated by commas; empty cells are omitted; badly formatted numbers are ignored")
+	sup := flag.Bool("s", false, "Display superior; only used if results are filtered")
 	flag.Parse()
-	if *c == "" {
+	if *csv == "" {
 		fmt.Println("CSV is required")
 		flag.Usage()
 		os.Exit(1)
 	}
-	f, e := os.Open(*c)
-	defer f.Close()
-	if e != nil {
-		fmt.Println(e.Error())
+	fin, err := os.Open(*csv)
+	defer fin.Close()
+	if err != nil {
+		fmt.Println(err.Error())
 		os.Exit(1)
 	}
 	var cols []int
-	if *t != "" {
-		cols_s := strings.Split(*t, ",")
+	if *coln != "" {
+		cols_s := strings.Split(*coln, ",")
 		cols = make([]int, len(cols_s))
 		for i, k := range cols_s {
 			v, e := strconv.Atoi(k)

          
@@ 68,16 69,26 @@ func main() {
 			}
 		}
 	}
-	ps, e := makePeople(f, cols)
-	if e != nil {
-		fmt.Println(e.Error())
+	ps, err := makePeople(fin, cols)
+	if err != nil {
+		fmt.Println(err.Error())
 		os.Exit(1)
 	}
-	if *o != "" {
-		ps = filter(ps, *o)
+	if *fltr != "" {
+		ps = filter(ps, *fltr)
 	}
 	rs := rank(ps)
-	renderSVG(os.Stdout, rs)
+	if *sup {
+		found := false
+		for _, k := range rs[0] {
+			if k.Manager != nil {
+				found = true
+				break
+			}
+		}
+		*sup = *sup && found
+	}
+	renderSVG(os.Stdout, rs, *sup)
 }
 
 /**************************************************************************

          
@@ 142,12 153,15 @@ func makePeople(f io.Reader, cols []int)
 			m, ok := ps[mgrName]
 			if !ok {
 				m = New(mgrName)
+				m.LineMask = mask
 				ps[mgrName] = m
 			}
 			p.SetManager(m)
 		}
-		if strings.Contains(l[2], "Contractor") {
-			p.Contractor = true
+		if len(l) > 2 {
+			if strings.Contains(l[2], "Contractor") {
+				p.Contractor = true
+			}
 		}
 		p.Lines = make([]string, len(cols))
 		for i, k := range cols {

          
M cmd/orgchart/main_test.go +1 -1
@@ 37,7 37,7 @@ B2,A
 D1,B2
 C2,B2`
 	b := bytes.NewBufferString(s)
-	ps, e := makePeople(b)
+	ps, e := makePeople(b, []int{})
 	if e != nil {
 		assert.Fail(t, "failed to parse: %s", e.Error())
 	}

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

          
@@ 38,6 38,9 @@ func renderSVG(out io.Writer, rs Ranks) 
 	c := svg.New(out)
 	pageWidth := rs.width()
 	h := rs.height()
+	if sup {
+		h += boxH + gapH + (marginH * 2)
+	}
 	c.Start(pageWidth, h)
 	c.Filter("blur")
 	c.FeGaussianBlur(svg.Filterspec{In: "SourceAlpha", Result: "blur"}, 5, 5)

          
@@ 50,15 53,19 @@ func renderSVG(out io.Writer, rs Ranks) 
 	c.Rect(0, 0, boxW, boxH, "fill: inherit; stroke: black; stroke-width: 1;")
 	c.Gend()
 	c.Gid(_root_id)
-	c.Rect(0, 0, boxW, boxH, "fill: inherit; stroke: black; stroke-width: 3;")
+	c.Rect(0, 0, boxW, boxH, "fill: inherit; stroke: black; stroke-width: 5;")
+	c.Rect(0, 0, boxW, boxH, "fill: none; stroke: white; stroke-width: 1;")
 	c.Gend()
 	c.DefEnd()
 	defer c.End()
 
 	b := make(map[string]Box)
 	xoff := marginW
+	if sup {
+		rs = addSuperiors(rs)
+	}
 	for _, p := range rs[0] {
-		drawOrg(c, xoff, marginH/2, p, b, true, false)
+		drawOrg(c, xoff, marginH/2, p, b, true, false, sup)
 		xoff += p.Width()
 	}
 	for _, r := range rs {

          
@@ 66,7 73,29 @@ func renderSVG(out io.Writer, rs Ranks) 
 	}
 }
 
-func drawOrg(canvas *svg.SVG, xoffset, yoffset int, p *Person, boxes map[string]Box, root, leaf bool) {
+func addSuperiors(rs Ranks) Ranks {
+	r := Rank{}
+	for _, p := range rs[0] {
+		s := p.Manager
+		if r.contains(s) {
+			s.Reports = append(s.Reports, p)
+		} else {
+			s.Reports = []*Person{p}
+			r = append(r, s)
+		}
+	}
+	rv := make(Ranks, len(rs)+1)
+	rv[0] = r
+	for i, n := range rs {
+		rv[i+1] = n
+	}
+	return rv
+}
+
+func drawOrg(canvas *svg.SVG, xoffset, yoffset int, p *Person, boxes map[string]Box, root, leaf, sup bool) {
+	if sup {
+		root = false
+	}
 	drawPerson(canvas, xoffset, yoffset, p.Index(), p, boxes, root, leaf)
 	if len(p.Reports) > 0 {
 		isLeaf := true

          
@@ 75,7 104,12 @@ func drawOrg(canvas *svg.SVG, xoffset, y
 		}
 		yoffset += boxH + (gapH / 2)
 		for _, q := range p.Reports {
-			drawOrg(canvas, xoffset, yoffset, q, boxes, false, isLeaf)
+			// Using sup for root looks wierd, but what it's saying is: if we just
+			// drew the superior (sup == true), then what we really want to
+			// highlight is this person's reports, so pass in true for root.  In
+			// any case, pass in false for sup -- and if it wasn't the superior
+			// (sup == false), then don't highlight the reports.
+			drawOrg(canvas, xoffset, yoffset, q, boxes, sup, isLeaf, false)
 			if isLeaf {
 				yoffset += boxH + (gapH / 2)
 			} else {

          
@@ 106,14 140,14 @@ func drawBox(c *svg.SVG, x, y int, p *Pe
 	c.Use(x, y, ref, fill)
 	toff := x + (boxW / 2)
 	toffY := y + textSize + textPY
-	if p.LineMask[0] {
+	if len(p.LineMask) > 0 && p.LineMask[0] && len(p.Lines) > 0 {
 		c.Text(toff, y+textSize+textPY, p.Lines[0], "text-anchor: middle; font-size: smaller;")
 		toffY += textSize + textPY
 	}
 	c.Text(toff, toffY, p.Name, "text-anchor: middle; font-weight: bold;")
 	toffY += textSize + textPY
 	for i, k := range p.LineMask {
-		if i == 0 {
+		if i == 0 || i >= len(p.Lines) {
 			continue
 		}
 		if k {