Skip to content

Commit

Permalink
Merge pull request #40 from dnnrly/feature/styles
Browse files Browse the repository at this point in the history
Add styling for nodes and edges
  • Loading branch information
dnnrly committed Jan 27, 2024
2 parents ce67055 + 46cc95c commit d853df8
Show file tree
Hide file tree
Showing 20 changed files with 661 additions and 116 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ test: ## run unit tests

.PHONY: fuzz
fuzz: ## run fuzz tests
go test -fuzz=FuzzDrawing
go test -tags fuzz -fuzz=FuzzDrawing

.PHONY: ci-test
ci-test: ## ci target - run tests to generate coverage data
rm -rf ./tmp/coverage/ci-test.txt
mkdir -p ./tmp/coverage
go test -coverprofile=./tmp/coverage/ci-test.txt -covermode=set ./...
go test -tags fuzz -coverprofile=./tmp/coverage/ci-test.txt -covermode=set ./...

.PHONY: check-docs
check-docs: build ## make sure that the documentation is up to date
Expand Down
8 changes: 8 additions & 0 deletions arrangements.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func LayoutFlowSquare(c *Config) (LayoutNodes, error) {
(y*c.NodeHeight)+
(y*(c.Margin*2)),
c.NodeWidth, c.NodeHeight,
c.Nodes[pos].Class,
c.Nodes[pos].Style,
)

pos++
Expand Down Expand Up @@ -101,6 +103,8 @@ func LayoutTopologicalSort(config *Config) (LayoutNodes, error) {
(0*config.NodeHeight)+
(0*(config.Margin*2)),
config.NodeWidth, config.NodeHeight,
c.Class,
c.Style,
))
}

Expand Down Expand Up @@ -133,6 +137,8 @@ func LayoutTarjan(config *Config) (LayoutNodes, error) {
(col*config.NodeHeight)+
(col*(config.Margin*2)),
config.NodeWidth, config.NodeHeight,
c.Class,
c.Style,
))
}
}
Expand Down Expand Up @@ -178,6 +184,8 @@ func LayoutAbsolute(c *Config) (LayoutNodes, error) {
n.Position.X,
n.Position.Y,
c.NodeWidth, c.NodeHeight,
n.Class,
n.Style,
)
}

Expand Down
113 changes: 79 additions & 34 deletions arrangements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,47 +58,47 @@ func TestLayoutFlowSquare(t *testing.T) {
l, _ := LayoutFlowSquare(newConfig(2, 5, 3, 1, 1))

require.Len(t, l, 2)
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 2, 4, 2, 6}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 2, 4, 9, 13}, l[1])
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 2, 4, 2, 6, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 2, 4, 9, 13, "", ""}, l[1])
}

{
l, _ := LayoutFlowSquare(newConfig(4, 5, 3, 1, 1))

require.Len(t, l, 4)
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 2, 4, 2, 6}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 2, 4, 9, 13}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 7, 9, 2, 6}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 7, 9, 9, 13}, l[3])
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 2, 4, 2, 6, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 2, 4, 9, 13, "", ""}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 7, 9, 2, 6, "", ""}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 7, 9, 9, 13, "", ""}, l[3])
}

{
l, _ := LayoutFlowSquare(newConfig(4, 5, 3, 2, 1))

require.Len(t, l, 4)
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 3, 5, 3, 7}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 3, 5, 12, 16}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 10, 12, 3, 7}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 10, 12, 12, 16}, l[3])
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 3, 5, 3, 7, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 3, 3, 5, 12, 16, "", ""}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 10, 12, 3, 7, "", ""}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 10, 12, 12, 16, "", ""}, l[3])
}

{
l, _ := LayoutFlowSquare(newConfig(8, 5, 3, 2, 1))

require.Len(t, l, 8)
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 3, 5, 3, 7}, l[0])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 3, 5, 21, 25}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 10, 12, 3, 7}, l[3])
assert.EqualValues(t, LayoutNode{"6", "", 5, 3, 10, 12, 21, 25}, l[5])
assert.EqualValues(t, LayoutNode{"8", "", 5, 3, 17, 19, 12, 16}, l[7])
assert.EqualValues(t, LayoutNode{"1", "", 5, 3, 3, 5, 3, 7, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"3", "", 5, 3, 3, 5, 21, 25, "", ""}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 3, 10, 12, 3, 7, "", ""}, l[3])
assert.EqualValues(t, LayoutNode{"6", "", 5, 3, 10, 12, 21, 25, "", ""}, l[5])
assert.EqualValues(t, LayoutNode{"8", "", 5, 3, 17, 19, 12, 16, "", ""}, l[7])
}

{
l, _ := LayoutFlowSquare(newConfig(4, 5, 4, 2, 2))

require.Len(t, l, 4)
assert.EqualValues(t, LayoutNode{"1", "", 5, 4, 3, 6, 3, 7}, l[0])
assert.EqualValues(t, LayoutNode{"4", "", 5, 4, 11, 14, 12, 16}, l[3])
assert.EqualValues(t, LayoutNode{"1", "", 5, 4, 3, 6, 3, 7, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"4", "", 5, 4, 11, 14, 12, 16, "", ""}, l[3])
}
}

Expand Down Expand Up @@ -188,7 +188,7 @@ func TestShuffleNodes_shufflesNumTimes(t *testing.T) {
_, _ = shuffleNodes(shuffleConfig(), func(config *Config) (LayoutNodes, error) {
assert.NotEqual(t, lastConfig, config)
count++
return LayoutNodes{NewLayoutNode("A", "c", 0, 0, 1, 1)}, nil
return LayoutNodes{NewLayoutNode("A", "c", 0, 0, 1, 1, "", "")}, nil
})

assert.Equal(t, 10, count)
Expand All @@ -197,9 +197,9 @@ func TestShuffleNodes_shufflesNumTimes(t *testing.T) {
func TestShuffleNodes_selectsShortsConnectionDistances(t *testing.T) {
var count int
options := []LayoutNodes{
{NewLayoutNode("1", "", 0, 0, 1, 1), NewLayoutNode("9", "", 0, 25, 1, 29)},
{NewLayoutNode("1", "", 0, 0, 1, 1), NewLayoutNode("9", "", 0, 15, 1, 20)},
{NewLayoutNode("1", "", 0, 0, 1, 1), NewLayoutNode("9", "", 0, 45, 1, 50)},
{NewLayoutNode("1", "", 0, 0, 1, 1, "", ""), NewLayoutNode("9", "", 0, 25, 1, 29, "", "")},
{NewLayoutNode("1", "", 0, 0, 1, 1, "", ""), NewLayoutNode("9", "", 0, 15, 1, 20, "", "")},
{NewLayoutNode("1", "", 0, 0, 1, 1, "", ""), NewLayoutNode("9", "", 0, 45, 1, 50, "", "")},
}

c := shuffleConfig()
Expand Down Expand Up @@ -233,11 +233,11 @@ func TestAbsoluteArrangement(t *testing.T) {

require.NoError(t, err)
require.Equal(t, 5, len(l))
assert.EqualValues(t, LayoutNode{"1", "", 5, 4, 10, 13, 50, 54}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 4, 20, 23, 40, 44}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 4, 30, 33, 30, 34}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 4, 40, 43, 20, 24}, l[3])
assert.EqualValues(t, LayoutNode{"5", "", 5, 4, 50, 53, 10, 14}, l[4])
assert.EqualValues(t, LayoutNode{"1", "", 5, 4, 10, 13, 50, 54, "", ""}, l[0])
assert.EqualValues(t, LayoutNode{"2", "", 5, 4, 20, 23, 40, 44, "", ""}, l[1])
assert.EqualValues(t, LayoutNode{"3", "", 5, 4, 30, 33, 30, 34, "", ""}, l[2])
assert.EqualValues(t, LayoutNode{"4", "", 5, 4, 40, 43, 20, 24, "", ""}, l[3])
assert.EqualValues(t, LayoutNode{"5", "", 5, 4, 50, 53, 10, 14, "", ""}, l[4])
}

func TestAbsoluteArrangement_ErrorsOnOverlaps(t *testing.T) {
Expand Down Expand Up @@ -334,29 +334,29 @@ func TestNodesOverlapWithMargin(t *testing.T) {
}{
{
name: "Nodes overlap with margin",
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20),
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30, "", ""),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20, "", ""),
margin: 5,
overlap: true,
},
{
name: "Nodes do not overlap with margin",
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30),
node2: NewLayoutNode("B", "Node B", 30, 40, 40, 20),
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30, "", ""),
node2: NewLayoutNode("B", "Node B", 30, 40, 40, 20, "", ""),
margin: 5,
overlap: false,
},
{
name: "Nodes overlap with zero margin",
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20),
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30, "", ""),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20, "", ""),
margin: 0,
overlap: true,
},
{
name: "Nodes overlap with large margin",
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20),
node1: NewLayoutNode("A", "Node A", 0, 0, 50, 30, "", ""),
node2: NewLayoutNode("B", "Node B", 10, 20, 40, 20, "", ""),
margin: 50,
overlap: true,
},
Expand All @@ -371,3 +371,48 @@ func TestNodesOverlapWithMargin(t *testing.T) {
})
}
}

func TestArrangementsPassClassAndStyle(t *testing.T) {
c := &Config{
Nodes: ConfigNodes{
ConfigNode{Id: "with-class", Class: "wobble"},
ConfigNode{Id: "with-style", Style: "wibble"},
ConfigNode{Id: "without-any"},
},
Edges: ConfigEdges{
ConfigEdge{From: "with-class", To: "without-any"},
ConfigEdge{From: "with-class", To: "with-style"},
},
LayoutAttempts: 1,
}
name := func(f LayoutArrangementFunc) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}

arrangements := []LayoutArrangementFunc{
LayoutFlowSquare,
LayoutTarjan,
LayoutAbsolute,
LayoutTopologicalSort,
LayoutRandomShortestSquare,
}

for _, f := range arrangements {
t.Run(fmt.Sprintf("Checking %s", name(f)), func(t *testing.T) {
result, err := f(c)
require.NoError(t, err)

require.NotNil(t, result.ByID("with-class"))
assert.Equal(t, "wobble", result.ByID("with-class").class)
assert.Empty(t, result.ByID("with-class").style)

assert.NotNil(t, result.ByID("with-style"))
assert.Empty(t, result.ByID("with-style").class)
assert.Equal(t, "wibble", result.ByID("with-style").style)

assert.NotNil(t, result.ByID("without-any"))
assert.Empty(t, result.ByID("without-any").class)
assert.Empty(t, result.ByID("without-any").style)
})
}
}
39 changes: 36 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,36 @@ package layli
import (
"fmt"
"io"
"regexp"
"sort"
"strings"

"gopkg.in/yaml.v3"
)

type ConfigPath struct {
Attempts int `yaml:"attempts"`
Strategy string `yaml:"strategy"`
Class string `yaml:"class"`
}

type ConfigStyles map[string]string

func (styles ConfigStyles) toCSS() string {
keys := make([]string, 0, len(styles))
for k := range styles {
keys = append(keys, k)
}
sort.Strings(keys)

pattern := regexp.MustCompile(`\n[\r\t ]*`)
css := []string{}
for _, k := range keys {
s := pattern.Split(styles[k], -1)
css = append(css, fmt.Sprintf("%s { %s }", k, strings.Join(s, " ")))
}

return strings.Join(css, "\n")
}

type Config struct {
Expand All @@ -24,6 +47,8 @@ type Config struct {
NodeHeight int `yaml:"height"`
Border int `yaml:"border"`
Margin int `yaml:"margin"`

Styles ConfigStyles `yaml:"styles"`
}

type Position struct {
Expand All @@ -35,6 +60,8 @@ type ConfigNode struct {
Id string `yaml:"id"`
Contents string `yaml:"contents"`
Position Position `yaml:"position"`
Class string `yaml:"class"`
Style string `yaml:"style"`
}

type ConfigNodes []ConfigNode
Expand All @@ -49,8 +76,11 @@ func (nodes ConfigNodes) ByID(id string) *ConfigNode {
}

type ConfigEdge struct {
From string `yaml:"from"`
To string `yaml:"to"`
ID string `yaml:"id"`
From string `yaml:"from"`
To string `yaml:"to"`
Class string `yaml:"class"`
Style string `yaml:"style"`
}

type ConfigEdges []ConfigEdge
Expand Down Expand Up @@ -102,7 +132,10 @@ func NewConfigFromFile(r io.Reader) (*Config, error) {
}
}

for _, e := range config.Edges {
for i, e := range config.Edges {
if e.ID == "" {
config.Edges[i].ID = fmt.Sprintf("edge-%d", i+1)
}
if e.From == "" || e.To == "" {
return nil, fmt.Errorf("all edges must have a from and a to")
}
Expand Down
Loading

0 comments on commit d853df8

Please sign in to comment.