# HG changeset patch # User Sean E. Russell # Date 1462013871 14400 # Sat Apr 30 06:57:51 2016 -0400 # Node ID f50693de6c0e9e1afcb80cdd389d79392045c7dd # Parent d9d9c9bfdd09e733d480cfa333a50bef05fbc186 Fixes width calculation diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -5,4 +5,5 @@ -i "${DATA}" \ -s \ -c "2,Contractor,fill:yellow" \ + -c "2,Convert,fill:orange" \ -c "2,[^ ]Manager,fill:lightgreen" > "${WHO}.svg" diff --git a/cmd/orgchart/main.go b/cmd/orgchart/main.go --- a/cmd/orgchart/main.go +++ b/cmd/orgchart/main.go @@ -52,7 +52,8 @@ 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: ,,. First match wins.") + hil := goopt.Strings([]string{"-c"}, "", "Coloring. Format: ,,. Multiple allowed; first match for each line wins.") + //lin := goopt.Strings([]string{"-l"}, "", "Box stroke. Format: ,.") goopt.Version = "" goopt.Summary = "Organization chart generator" goopt.Parse(nil) 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 @@ -28,49 +28,6 @@ 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 @@ 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()) +} diff --git a/cmd/orgchart/person.go b/cmd/orgchart/person.go --- a/cmd/orgchart/person.go +++ b/cmd/orgchart/person.go @@ -45,24 +45,20 @@ 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 } diff --git a/cmd/orgchart/rank.go b/cmd/orgchart/rank.go --- a/cmd/orgchart/rank.go +++ b/cmd/orgchart/rank.go @@ -53,14 +53,11 @@ } 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 { diff --git a/cmd/orgchart/svg.go b/cmd/orgchart/svg.go --- a/cmd/orgchart/svg.go +++ b/cmd/orgchart/svg.go @@ -7,6 +7,7 @@ ) var pageWidth int +var bgW int /************************************************************************** * SVG generating code * @@ -27,7 +28,7 @@ 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 @@ 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 @@ } 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 @@ if isLeaf { yoffset += boxH + (gapH / 2) } else { - xoffset += q.Width() + xoffset += width(q) } } } @@ -121,7 +127,7 @@ 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 @@ } c.Polyline(xs, ys, `fill: none; stroke: indigo; stroke-width: 3; opacity: 1`) } + +func width(p *Person) int { + return p.Width() * (boxW + gapW) +}