# HG changeset patch # User Sean E. Russell # Date 1428624679 14400 # Thu Apr 09 20:11:19 2015 -0400 # Node ID 6f79d744bfce7a982554bf11d65a788584e70600 # Parent 39077142b8050eb230a8db57544bd93dae9919ff Report-to (show filtered people's superior) diff --git a/cmd/orgchart/main.go b/cmd/orgchart/main.go --- a/cmd/orgchart/main.go +++ b/cmd/orgchart/main.go @@ -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 @@ * 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 @@ } } } - 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 @@ 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 { diff --git a/cmd/orgchart/main_test.go b/cmd/orgchart/main_test.go --- a/cmd/orgchart/main_test.go +++ b/cmd/orgchart/main_test.go @@ -37,7 +37,7 @@ 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()) } diff --git a/cmd/orgchart/svg.go b/cmd/orgchart/svg.go --- a/cmd/orgchart/svg.go +++ b/cmd/orgchart/svg.go @@ -27,7 +27,7 @@ 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 @@ 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 @@ 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 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 @@ } 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 @@ 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 {