legend
This commit is contained in:
parent
27d60d9e7d
commit
79e7b69552
6 changed files with 1375 additions and 1 deletions
|
|
@ -96,6 +96,8 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
|
||||||
c.validateEdges(g)
|
c.validateEdges(g)
|
||||||
c.validatePositionsCompatibility(g)
|
c.validatePositionsCompatibility(g)
|
||||||
|
|
||||||
|
c.compileLegend(g, ir)
|
||||||
|
|
||||||
c.compileBoardsField(g, ir, "layers")
|
c.compileBoardsField(g, ir, "layers")
|
||||||
c.compileBoardsField(g, ir, "scenarios")
|
c.compileBoardsField(g, ir, "scenarios")
|
||||||
c.compileBoardsField(g, ir, "steps")
|
c.compileBoardsField(g, ir, "steps")
|
||||||
|
|
@ -110,6 +112,42 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *compiler) compileLegend(g *d2graph.Graph, m *d2ir.Map) {
|
||||||
|
varsField := m.GetField(d2ast.FlatUnquotedString("vars"))
|
||||||
|
if varsField == nil || varsField.Map() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
legendField := varsField.Map().GetField(d2ast.FlatUnquotedString("d2-legend"))
|
||||||
|
if legendField == nil || legendField.Map() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
legendGraph := d2graph.NewGraph()
|
||||||
|
|
||||||
|
c.compileMap(legendGraph.Root, legendField.Map())
|
||||||
|
c.setDefaultShapes(legendGraph)
|
||||||
|
|
||||||
|
objects := make([]*d2graph.Object, 0)
|
||||||
|
for _, obj := range legendGraph.Objects {
|
||||||
|
if obj.Style.Opacity != nil {
|
||||||
|
if opacity, err := strconv.ParseFloat(obj.Style.Opacity.Value, 64); err == nil && opacity == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects = append(objects, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
legend := &d2graph.Legend{
|
||||||
|
Objects: objects,
|
||||||
|
Edges: legendGraph.Edges,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(legend.Objects) > 0 || len(legend.Edges) > 0 {
|
||||||
|
g.Legend = legend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName string) {
|
func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName string) {
|
||||||
boards := ir.GetField(d2ast.FlatUnquotedString(fieldName))
|
boards := ir.GetField(d2ast.FlatUnquotedString(fieldName))
|
||||||
if boards.Map() == nil {
|
if boards.Map() == nil {
|
||||||
|
|
|
||||||
|
|
@ -720,6 +720,142 @@ x: {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "legend",
|
||||||
|
|
||||||
|
text: `
|
||||||
|
vars: {
|
||||||
|
d2-legend: {
|
||||||
|
User: "A person who interacts with the system" {
|
||||||
|
shape: person
|
||||||
|
style: {
|
||||||
|
fill: "#f5f5f5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Database: "Stores application data" {
|
||||||
|
shape: cylinder
|
||||||
|
style.fill: "#b5d3ff"
|
||||||
|
}
|
||||||
|
|
||||||
|
HiddenShape: "This should not appear in the legend" {
|
||||||
|
style.opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
User -> Database: "Reads data" {
|
||||||
|
style.stroke: "blue"
|
||||||
|
}
|
||||||
|
|
||||||
|
Database -> User: "Returns results" {
|
||||||
|
style.stroke-dash: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user: User
|
||||||
|
db: Database
|
||||||
|
user -> db: Uses
|
||||||
|
`,
|
||||||
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
|
if g.Legend == nil {
|
||||||
|
t.Fatal("Expected Legend to be non-nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Verify the correct objects are in the legend
|
||||||
|
if len(g.Legend.Objects) != 2 {
|
||||||
|
t.Errorf("Expected 2 objects in legend, got %d", len(g.Legend.Objects))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for User object
|
||||||
|
hasUser := false
|
||||||
|
hasDatabase := false
|
||||||
|
for _, obj := range g.Legend.Objects {
|
||||||
|
if obj.ID == "User" {
|
||||||
|
hasUser = true
|
||||||
|
if obj.Shape.Value != "person" {
|
||||||
|
t.Errorf("User shape incorrect, expected 'person', got: %s", obj.Shape.Value)
|
||||||
|
}
|
||||||
|
} else if obj.ID == "Database" {
|
||||||
|
hasDatabase = true
|
||||||
|
if obj.Shape.Value != "cylinder" {
|
||||||
|
t.Errorf("Database shape incorrect, expected 'cylinder', got: %s", obj.Shape.Value)
|
||||||
|
}
|
||||||
|
} else if obj.ID == "HiddenShape" {
|
||||||
|
t.Errorf("HiddenShape should not be in legend due to opacity: 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasUser {
|
||||||
|
t.Errorf("User object missing from legend")
|
||||||
|
}
|
||||||
|
if !hasDatabase {
|
||||||
|
t.Errorf("Database object missing from legend")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Verify the correct edges are in the legend
|
||||||
|
if len(g.Legend.Edges) != 2 {
|
||||||
|
t.Errorf("Expected 2 edges in legend, got %d", len(g.Legend.Edges))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for expected edges
|
||||||
|
hasReadsEdge := false
|
||||||
|
hasReturnsEdge := false
|
||||||
|
for _, edge := range g.Legend.Edges {
|
||||||
|
if edge.Label.Value == "Reads data" {
|
||||||
|
hasReadsEdge = true
|
||||||
|
// Check edge properties
|
||||||
|
if edge.Style.Stroke == nil {
|
||||||
|
t.Errorf("Reads edge stroke is nil")
|
||||||
|
} else if edge.Style.Stroke.Value != "blue" {
|
||||||
|
t.Errorf("Reads edge stroke incorrect, expected 'blue', got: %s", edge.Style.Stroke.Value)
|
||||||
|
}
|
||||||
|
} else if edge.Label.Value == "Returns results" {
|
||||||
|
hasReturnsEdge = true
|
||||||
|
// Check edge properties
|
||||||
|
if edge.Style.StrokeDash == nil {
|
||||||
|
t.Errorf("Returns edge stroke-dash is nil")
|
||||||
|
} else if edge.Style.StrokeDash.Value != "5" {
|
||||||
|
t.Errorf("Returns edge stroke-dash incorrect, expected '5', got: %s", edge.Style.StrokeDash.Value)
|
||||||
|
}
|
||||||
|
} else if edge.Label.Value == "Hidden connection" {
|
||||||
|
t.Errorf("Hidden connection should not be in legend due to opacity: 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasReadsEdge {
|
||||||
|
t.Errorf("'Reads data' edge missing from legend")
|
||||||
|
}
|
||||||
|
if !hasReturnsEdge {
|
||||||
|
t.Errorf("'Returns results' edge missing from legend")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verify the regular diagram content is still there
|
||||||
|
userObj, hasUserObj := g.Root.HasChild([]string{"user"})
|
||||||
|
if !hasUserObj {
|
||||||
|
t.Errorf("Main diagram missing 'user' object")
|
||||||
|
} else if userObj.Label.Value != "User" {
|
||||||
|
t.Errorf("User label incorrect, expected 'User', got: %s", userObj.Label.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbObj, hasDBObj := g.Root.HasChild([]string{"db"})
|
||||||
|
if !hasDBObj {
|
||||||
|
t.Errorf("Main diagram missing 'db' object")
|
||||||
|
} else if dbObj.Label.Value != "Database" {
|
||||||
|
t.Errorf("DB label incorrect, expected 'Database', got: %s", dbObj.Label.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the main edge
|
||||||
|
if len(g.Edges) == 0 {
|
||||||
|
t.Errorf("No edges found in main diagram")
|
||||||
|
} else {
|
||||||
|
mainEdge := g.Edges[0]
|
||||||
|
if mainEdge.Label.Value != "Uses" {
|
||||||
|
t.Errorf("Main edge label incorrect, expected 'Uses', got: %s", mainEdge.Label.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "underscore_edge_nested",
|
name: "underscore_edge_nested",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,26 @@ func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamil
|
||||||
diagram.Connections[i] = toConnection(g.Edges[i], g.Theme)
|
diagram.Connections[i] = toConnection(g.Edges[i], g.Theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if g.Legend != nil {
|
||||||
|
legend := &d2target.Legend{}
|
||||||
|
|
||||||
|
if len(g.Legend.Objects) > 0 {
|
||||||
|
legend.Shapes = make([]d2target.Shape, len(g.Legend.Objects))
|
||||||
|
for i, obj := range g.Legend.Objects {
|
||||||
|
legend.Shapes[i] = toShape(obj, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Legend.Edges) > 0 {
|
||||||
|
legend.Connections = make([]d2target.Connection, len(g.Legend.Edges))
|
||||||
|
for i, edge := range g.Legend.Edges {
|
||||||
|
legend.Connections[i] = toConnection(edge, g.Theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagram.Legend = legend
|
||||||
|
}
|
||||||
|
|
||||||
return diagram, nil
|
return diagram, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ type Graph struct {
|
||||||
BaseAST *d2ast.Map `json:"-"`
|
BaseAST *d2ast.Map `json:"-"`
|
||||||
|
|
||||||
Root *Object `json:"root"`
|
Root *Object `json:"root"`
|
||||||
|
Legend *Legend `json:"legend,omitempty"`
|
||||||
Edges []*Edge `json:"edges"`
|
Edges []*Edge `json:"edges"`
|
||||||
Objects []*Object `json:"objects"`
|
Objects []*Object `json:"objects"`
|
||||||
|
|
||||||
|
|
@ -67,6 +68,11 @@ type Graph struct {
|
||||||
Data map[string]interface{} `json:"data,omitempty"`
|
Data map[string]interface{} `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Legend struct {
|
||||||
|
Objects []*Object `json:"objects,omitempty"`
|
||||||
|
Edges []*Edge `json:"edges,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewGraph() *Graph {
|
func NewGraph() *Graph {
|
||||||
d := &Graph{}
|
d := &Graph{}
|
||||||
d.Root = &Object{
|
d.Root = &Object{
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,8 @@ type Diagram struct {
|
||||||
Shapes []Shape `json:"shapes"`
|
Shapes []Shape `json:"shapes"`
|
||||||
Connections []Connection `json:"connections"`
|
Connections []Connection `json:"connections"`
|
||||||
|
|
||||||
Root Shape `json:"root"`
|
Root Shape `json:"root"`
|
||||||
|
Legend *Legend `json:"legend,omitempty"`
|
||||||
// Maybe Icon can be used as a watermark in the root shape
|
// Maybe Icon can be used as a watermark in the root shape
|
||||||
|
|
||||||
Layers []*Diagram `json:"layers,omitempty"`
|
Layers []*Diagram `json:"layers,omitempty"`
|
||||||
|
|
@ -94,6 +95,11 @@ type Diagram struct {
|
||||||
Steps []*Diagram `json:"steps,omitempty"`
|
Steps []*Diagram `json:"steps,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Legend struct {
|
||||||
|
Shapes []Shape `json:"shapes,omitempty"`
|
||||||
|
Connections []Connection `json:"connections,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Diagram) GetBoard(boardPath []string) *Diagram {
|
func (d *Diagram) GetBoard(boardPath []string) *Diagram {
|
||||||
if len(boardPath) == 0 {
|
if len(boardPath) == 0 {
|
||||||
return d
|
return d
|
||||||
|
|
|
||||||
1168
testdata/d2compiler/TestCompile/legend.exp.json
generated
vendored
Normal file
1168
testdata/d2compiler/TestCompile/legend.exp.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue