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)
+}