diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 961ab0bcb..d9a96d83a 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -331,6 +331,20 @@ func (l ContainerLevel) LabelSize() int { func (obj *Object) GetFill(theme *d2themes.Theme) string { level := int(obj.Level()) + if obj.Parent.IsSequenceDiagram() { + return theme.Colors.B5 + } else if obj.IsSequenceDiagramNote() { + return theme.Colors.Neutrals.N7 + } else if obj.IsSequenceDiagramGroup() { + sd := obj.outerSequenceDiagram() + // Alternate + if (level-int(sd.Level()))%2 == 0 { + return theme.Colors.Neutrals.N7 + } else { + return theme.Colors.Neutrals.N6 + } + } + shape := obj.Attributes.Shape.Value if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) { @@ -399,10 +413,6 @@ func (obj *Object) IsContainer() bool { return len(obj.Children) > 0 } -func (obj *Object) IsSequenceDiagram() bool { - return obj != nil && obj.Attributes.Shape.Value == d2target.ShapeSequenceDiagram -} - func (obj *Object) AbsID() string { if obj.Parent != nil && obj.Parent.ID != "" { return obj.Parent.AbsID() + "." + obj.ID @@ -711,16 +721,6 @@ func (e *Edge) AbsID() string { return fmt.Sprintf("%s(%s %s %s)[%d]", commonKey, strings.Join(srcIDA, "."), e.ArrowString(), strings.Join(dstIDA, "."), e.Index) } -func (obj *Object) outerSequenceDiagram() *Object { - for obj != nil { - obj = obj.Parent - if obj.IsSequenceDiagram() { - return obj - } - } - return nil -} - func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label string) (*Edge, error) { srcObj, srcID, err := ResolveUnderscoreKey(srcID, obj) if err != nil { diff --git a/d2graph/seqdiagram.go b/d2graph/seqdiagram.go new file mode 100644 index 000000000..fbd4d4bb6 --- /dev/null +++ b/d2graph/seqdiagram.go @@ -0,0 +1,77 @@ +package d2graph + +import "oss.terrastruct.com/d2/d2target" + +func (obj *Object) IsSequenceDiagram() bool { + return obj != nil && obj.Attributes.Shape.Value == d2target.ShapeSequenceDiagram +} + +func (obj *Object) outerSequenceDiagram() *Object { + for obj != nil { + obj = obj.Parent + if obj.IsSequenceDiagram() { + return obj + } + } + return nil +} + +// groups are objects in sequence diagrams that have no messages connected +// and does not have a note as a child (a note can appear within a group, but it's a child of an actor) +func (obj *Object) IsSequenceDiagramGroup() bool { + if obj.outerSequenceDiagram() == nil { + return false + } + for _, e := range obj.Graph.Edges { + if e.Src == obj || e.Dst == obj { + return false + } + } + for _, ch := range obj.ChildrenArray { + // if the child contains a message, it's a span, not a note + if !ch.ContainsAnyEdge(obj.Graph.Edges) { + return false + } + } + return true +} + +// notes are descendant of actors with no edges and no children +func (obj *Object) IsSequenceDiagramNote() bool { + if obj.outerSequenceDiagram() == nil { + return false + } + return !obj.hasEdgeRef() && !obj.ContainsAnyEdge(obj.Graph.Edges) && len(obj.ChildrenArray) == 0 +} + +func (obj *Object) hasEdgeRef() bool { + for _, ref := range obj.References { + if ref.MapKey != nil && len(ref.MapKey.Edges) > 0 { + return true + } + } + + return false +} + +func (obj *Object) ContainsAnyEdge(edges []*Edge) bool { + for _, e := range edges { + if e.ContainedBy(obj) { + return true + } + } + return false +} + +func (e *Edge) ContainedBy(obj *Object) bool { + for _, ref := range e.References { + curr := ref.ScopeObj + for curr != nil { + if curr == obj { + return true + } + curr = curr.Parent + } + } + return false +} diff --git a/d2layouts/d2sequence/constants.go b/d2layouts/d2sequence/constants.go index 676eb3d73..78ff0ce16 100644 --- a/d2layouts/d2sequence/constants.go +++ b/d2layouts/d2sequence/constants.go @@ -6,10 +6,14 @@ const HORIZONTAL_PAD = 50. // leaves at least 25 units of space on the top/bottom when computing the space required between messages const VERTICAL_PAD = 50. -const MIN_ACTOR_DISTANCE = 250. +const MIN_ACTOR_DISTANCE = 70. const MIN_ACTOR_WIDTH = 150. +const SELF_MESSAGE_HORIZONTAL_TRAVEL = 100. + +const GROUP_CONTAINER_PADDING = 24. + // min vertical distance between messages const MIN_MESSAGE_DISTANCE = 80. @@ -30,3 +34,11 @@ const LIFELINE_STROKE_DASH int = 6 // pad when the actor has the label placed OutsideMiddleBottom so that the lifeline is not so close to the text const LIFELINE_LABEL_PAD = 5. + +const ( + GROUP_Z_INDEX = 1 + LIFELINE_Z_INDEX = 2 + SPAN_Z_INDEX = 3 + MESSAGE_Z_INDEX = 4 + NOTE_Z_INDEX = 5 +) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 3f8650259..33e82a0a4 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -56,6 +56,9 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte for _, obj := range sd.notes { objectsToRemove[obj] = struct{}{} } + for _, obj := range sd.groups { + objectsToRemove[obj] = struct{}{} + } for _, obj := range sd.spans { objectsToRemove[obj] = struct{}{} } @@ -151,6 +154,7 @@ func cleanup(g *d2graph.Graph, sequenceDiagrams map[string]*sequenceDiagram, obj g.Edges = append(g.Edges, sequenceDiagrams[obj.AbsID()].lifelines...) g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].actors...) g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].notes...) + g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].groups...) g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].spans...) } diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go index dc4e6d07c..1e3dbfd60 100644 --- a/d2layouts/d2sequence/layout_test.go +++ b/d2layouts/d2sequence/layout_test.go @@ -231,8 +231,8 @@ b -> a.t2` } for _, span := range []*d2graph.Object{a_t1, a_t2, b_t1} { - if span.ZIndex != 1 { - t.Fatalf("expected span ZIndex=1, got %d", span.ZIndex) + if span.ZIndex != d2sequence.SPAN_Z_INDEX { + t.Fatalf("expected span ZIndex=%d, got %d", d2sequence.SPAN_Z_INDEX, span.ZIndex) } } diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go index 11df81b59..fdf214eff 100644 --- a/d2layouts/d2sequence/sequence_diagram.go +++ b/d2layouts/d2sequence/sequence_diagram.go @@ -64,61 +64,12 @@ func getEdgeEarliestLineNum(e *d2graph.Edge) int { return min } -func hasEdge(o *d2graph.Object) bool { - for _, ref := range o.References { - if ref.MapKey != nil && len(ref.MapKey.Edges) > 0 { - return true - } - } - - return false -} - -func containsAnyMessage(o *d2graph.Object, messages []*d2graph.Edge) bool { - for _, m := range messages { - if containsMessage(o, m) { - return true - } - } - return false -} - -func containsMessage(o *d2graph.Object, m *d2graph.Edge) bool { - for _, ref := range m.References { - curr := ref.ScopeObj - for curr != nil { - if curr == o { - return true - } - curr = curr.Parent - } - } - return false -} - func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *sequenceDiagram { var actors []*d2graph.Object var groups []*d2graph.Object for _, obj := range objects { - messageRecipient := false - for _, m := range messages { - if m.Src == obj || m.Dst == obj { - messageRecipient = true - break - } - } - hasNote := false - for _, ch := range obj.ChildrenArray { - // if the child contains a message, it's a span, not a note - if !containsAnyMessage(ch, messages) { - hasNote = true - break - } - } - if messageRecipient || hasNote { - actors = append(actors, obj) - } else { + if obj.IsSequenceDiagramGroup() { queue := []*d2graph.Object{obj} // Groups may have more nested groups for len(queue) > 0 { @@ -129,6 +80,8 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se queue = append(queue, c) } } + } else { + actors = append(actors, obj) } } @@ -153,9 +106,7 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se sd.objectRank[actor] = rank if actor.Width < MIN_ACTOR_WIDTH { - aspectRatio := actor.Height / actor.Width actor.Width = MIN_ACTOR_WIDTH - actor.Height = math.Round(aspectRatio * actor.Width) } sd.maxActorHeight = math.Max(sd.maxActorHeight, actor.Height) @@ -166,22 +117,21 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se queue = queue[1:] // spans are children of actors that have edges - // notes are children of actors with no edges and no children // edge groups are children of actors with no edges and children edges - if hasEdge(child) && !containsAnyMessage(child, sd.messages) { - // spans have no labels - // TODO why not? Spans should be able to - child.Attributes.Label = d2graph.Scalar{Value: ""} - child.Attributes.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE} - sd.spans = append(sd.spans, child) - sd.objectRank[child] = rank - } else { + if child.IsSequenceDiagramNote() { sd.verticalIndices[child.AbsID()] = getObjEarliestLineNum(child) // TODO change to page type when it doesn't look deformed child.Attributes.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE} sd.notes = append(sd.notes, child) sd.objectRank[child] = rank child.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) + } else { + // spans have no labels + // TODO why not? Spans should be able to + child.Attributes.Label = d2graph.Scalar{Value: ""} + child.Attributes.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE} + sd.spans = append(sd.spans, child) + sd.objectRank[child] = rank } queue = append(queue, child.ChildrenArray...) @@ -240,7 +190,11 @@ func (sd *sequenceDiagram) layout() error { } func (sd *sequenceDiagram) placeGroups() { + sort.SliceStable(sd.groups, func(i, j int) bool { + return sd.groups[i].Level() > sd.groups[j].Level() + }) for _, group := range sd.groups { + group.ZIndex = GROUP_Z_INDEX sd.placeGroup(group) } } @@ -252,12 +206,12 @@ func (sd *sequenceDiagram) placeGroup(group *d2graph.Object) { maxY := math.Inf(-1) for _, m := range sd.messages { - if containsMessage(group, m) { + if m.ContainedBy(group) { for _, p := range m.Route { - minX = math.Min(minX, p.X) - minY = math.Min(minY, p.Y) - maxX = math.Max(maxX, p.X) - maxY = math.Max(maxY, p.Y) + minX = math.Min(minX, p.X-HORIZONTAL_PAD) + minY = math.Min(minY, p.Y-MIN_MESSAGE_DISTANCE/2.) + maxX = math.Max(maxX, p.X+HORIZONTAL_PAD) + maxY = math.Max(maxY, p.Y+MIN_MESSAGE_DISTANCE/2.) } } } @@ -278,20 +232,32 @@ func (sd *sequenceDiagram) placeGroup(group *d2graph.Object) { } } if inGroup { - minY = math.Min(minY, n.TopLeft.Y) - maxY = math.Max(maxY, n.TopLeft.Y+n.Height) - minX = math.Min(minX, n.TopLeft.X) - maxX = math.Max(maxX, n.TopLeft.X+n.Width) + minX = math.Min(minX, n.TopLeft.X-HORIZONTAL_PAD) + minY = math.Min(minY, n.TopLeft.Y-MIN_MESSAGE_DISTANCE/2.) + maxY = math.Max(maxY, n.TopLeft.Y+n.Height+HORIZONTAL_PAD) + maxX = math.Max(maxX, n.TopLeft.X+n.Width+MIN_MESSAGE_DISTANCE/2.) + } + } + + for _, ch := range group.ChildrenArray { + for _, g := range sd.groups { + if ch == g { + minX = math.Min(minX, ch.TopLeft.X-GROUP_CONTAINER_PADDING) + minY = math.Min(minY, ch.TopLeft.Y-GROUP_CONTAINER_PADDING) + maxX = math.Max(maxX, ch.TopLeft.X+ch.Width+GROUP_CONTAINER_PADDING) + maxY = math.Max(maxY, ch.TopLeft.Y+ch.Height+GROUP_CONTAINER_PADDING) + break + } } } group.Box = geo.NewBox( geo.NewPoint( - minX-HORIZONTAL_PAD, - minY-(MIN_MESSAGE_DISTANCE/2.), + minX, + minY, ), - maxX-minX+HORIZONTAL_PAD*2, - maxY-minY+MIN_MESSAGE_DISTANCE, + maxX-minX, + maxY-minY, ) } @@ -360,6 +326,7 @@ func (sd *sequenceDiagram) addLifelineEdges() { }, DstArrow: false, Route: []*geo.Point{actorBottom, actorLifelineEnd}, + ZIndex: LIFELINE_Z_INDEX, }) } } @@ -385,7 +352,7 @@ func (sd *sequenceDiagram) placeNotes() { x := rankToX[sd.objectRank[note]] - (note.Width / 2.) note.Box.TopLeft = geo.NewPoint(x, y) - note.ZIndex = 1 + note.ZIndex = NOTE_Z_INDEX } } @@ -457,11 +424,11 @@ func (sd *sequenceDiagram) placeSpans() { } height := math.Max(maxY-minY, MIN_SPAN_HEIGHT) - // -2 because the actors count as level 1 making the first level span getting 2*SPAN_DEPTH_GROW_FACTOR - width := SPAN_BASE_WIDTH + (float64(span.Level()-2) * SPAN_DEPTH_GROWTH_FACTOR) + // -1 because the actors count as 1 level + width := SPAN_BASE_WIDTH + (float64(span.Level()-sd.root.Level()-2) * SPAN_DEPTH_GROWTH_FACTOR) x := rankToX[sd.objectRank[span]] - (width / 2.) span.Box = geo.NewBox(geo.NewPoint(x, minY), width, height) - span.ZIndex = 1 + span.ZIndex = SPAN_Z_INDEX } } @@ -470,6 +437,7 @@ func (sd *sequenceDiagram) placeSpans() { func (sd *sequenceDiagram) routeMessages() error { messageOffset := sd.maxActorHeight + sd.yStep for _, message := range sd.messages { + message.ZIndex = MESSAGE_Z_INDEX noteOffset := 0. for _, note := range sd.notes { if sd.verticalIndices[note.AbsID()] < sd.verticalIndices[message.AbsID()] { @@ -478,7 +446,6 @@ func (sd *sequenceDiagram) routeMessages() error { } startY := messageOffset + noteOffset - message.ZIndex = 2 var startX, endX float64 if startCenter := getCenter(message.Src); startCenter != nil { startX = startCenter.X @@ -495,7 +462,7 @@ func (sd *sequenceDiagram) routeMessages() error { isSelfMessage := message.Src == message.Dst if isSelfMessage || isToDescendant || isFromDescendant { - midX := startX + MIN_MESSAGE_DISTANCE + midX := startX + SELF_MESSAGE_HORIZONTAL_TRAVEL endY := startY + MIN_MESSAGE_DISTANCE message.Route = []*geo.Point{ geo.NewPoint(startX, startY), diff --git a/d2lib/d2.go b/d2lib/d2.go index 182dd0ae3..6ab141d95 100644 --- a/d2lib/d2.go +++ b/d2lib/d2.go @@ -35,15 +35,17 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target return nil, nil, err } - err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler) - if err != nil { - return nil, nil, err - } + if len(g.Objects) > 0 { + err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler) + if err != nil { + return nil, nil, err + } - if layout, err := getLayout(opts); err != nil { - return nil, nil, err - } else if err := d2sequence.Layout(ctx, g, layout); err != nil { - return nil, nil, err + if layout, err := getLayout(opts); err != nil { + return nil, nil, err + } else if err := d2sequence.Layout(ctx, g, layout); err != nil { + return nil, nil, err + } } diagram, err := d2exporter.Export(ctx, g, opts.ThemeID) diff --git a/d2themes/d2themes.go b/d2themes/d2themes.go index c5217c592..64510eb2f 100644 --- a/d2themes/d2themes.go +++ b/d2themes/d2themes.go @@ -14,6 +14,7 @@ type Neutral struct { N3 string `json:"n3"` N4 string `json:"n4"` N5 string `json:"n5"` + N6 string `json:"n6"` N7 string `json:"n7"` } @@ -44,6 +45,7 @@ var CoolNeutral = Neutral{ N3: "#9499AB", N4: "#CFD2DD", N5: "#F0F3F9", + N6: "#EEF1F8", N7: "#FFFFFF", } @@ -53,5 +55,6 @@ var WarmNeutral = Neutral{ N3: "#787777", N4: "#CCCACA", N5: "#DFDCDC", + N6: "#ECEBEB", N7: "#FFFFFF", } diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index fa3e07ba8..b77a4cc59 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -38,6 +38,10 @@ func TestE2E(t *testing.T) { func testSanity(t *testing.T) { tcs := []testCase{ + { + name: "empty", + script: ``, + }, { name: "basic", script: `a -> b diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 526d22aca..3596355f2 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -1399,7 +1399,9 @@ choo: { d2compiler -> CLI: objects and edges CLI -> d2layout.layout: run layout engines d2layout.layout -> d2sequencelayout: run engine on shape: sequence_diagram, temporarily remove - d2layout.layout -> d2dagrelayout: run core engine on rest + only if root is not sequence: { + _.d2layout.layout -> _.d2dagrelayout: run core engine on rest + } d2layout.layout <- d2sequencelayout: add back in sequence diagrams d2layout -> CLI: diagram with correct positions and dimensions CLI -> d2exporter: export diagram with chosen theme and renderer diff --git a/e2etests/testdata/sanity/empty/dagre/board.exp.json b/e2etests/testdata/sanity/empty/dagre/board.exp.json new file mode 100644 index 000000000..8591adc9e --- /dev/null +++ b/e2etests/testdata/sanity/empty/dagre/board.exp.json @@ -0,0 +1,5 @@ +{ + "name": "", + "shapes": [], + "connections": [] +} diff --git a/e2etests/testdata/sanity/empty/dagre/sketch.exp.svg b/e2etests/testdata/sanity/empty/dagre/sketch.exp.svg new file mode 100644 index 000000000..5c521366b --- /dev/null +++ b/e2etests/testdata/sanity/empty/dagre/sketch.exp.svg @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/e2etests/testdata/sanity/empty/elk/board.exp.json b/e2etests/testdata/sanity/empty/elk/board.exp.json new file mode 100644 index 000000000..8591adc9e --- /dev/null +++ b/e2etests/testdata/sanity/empty/elk/board.exp.json @@ -0,0 +1,5 @@ +{ + "name": "", + "shapes": [], + "connections": [] +} diff --git a/e2etests/testdata/sanity/empty/elk/sketch.exp.svg b/e2etests/testdata/sanity/empty/elk/sketch.exp.svg new file mode 100644 index 000000000..5c521366b --- /dev/null +++ b/e2etests/testdata/sanity/empty/elk/sketch.exp.svg @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json index 4c42ba6d9..73b2fad99 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json @@ -14,7 +14,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -44,16 +44,16 @@ "id": "b", "type": "oval", "pos": { - "x": 259, - "y": 84 + "x": 223, + "y": 87 }, "width": 150, - "height": 150, + "height": 147, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F7F8FE", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -83,7 +83,7 @@ "id": "c", "type": "class", "pos": { - "x": 463, + "x": 443, "y": 50 }, "width": 241, @@ -92,7 +92,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0A0F25", "shadow": false, "3d": false, @@ -133,7 +133,7 @@ "id": "d", "type": "cloud", "pos": { - "x": 744, + "x": 754, "y": 108 }, "width": 179, @@ -142,7 +142,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -172,7 +172,7 @@ "id": "e", "type": "code", "pos": { - "x": 986, + "x": 1003, "y": 164 }, "width": 196, @@ -181,7 +181,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0A0F25", "shadow": false, "3d": false, @@ -211,11 +211,11 @@ "id": "f", "type": "cylinder", "pos": { - "x": 1259, - "y": 84 + "x": 1269, + "y": 108 }, "width": 150, - "height": 150, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -250,16 +250,16 @@ "id": "g", "type": "diamond", "pos": { - "x": 1509, - "y": 85 + "x": 1489, + "y": 108 }, "width": 150, - "height": 149, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#CFD2DD", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -289,11 +289,11 @@ "id": "h", "type": "document", "pos": { - "x": 1759, - "y": 97 + "x": 1709, + "y": 108 }, "width": 150, - "height": 137, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -328,7 +328,7 @@ "id": "i", "type": "hexagon", "pos": { - "x": 1993, + "x": 1929, "y": 108 }, "width": 182, @@ -337,7 +337,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F0F3F9", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -367,16 +367,16 @@ "id": "j", "type": "image", "pos": { - "x": 2259, - "y": 63 + "x": 2181, + "y": 85 }, "width": 150, - "height": 150, + "height": 128, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -417,16 +417,16 @@ "id": "k", "type": "oval", "pos": { - "x": 2509, - "y": 97 + "x": 2401, + "y": 108 }, "width": 150, - "height": 137, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F7F8FE", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -456,11 +456,11 @@ "id": "l", "type": "package", "pos": { - "x": 2759, - "y": 98 + "x": 2621, + "y": 108 }, "width": 150, - "height": 136, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -495,7 +495,7 @@ "id": "m", "type": "page", "pos": { - "x": 2996, + "x": 2841, "y": 108 }, "width": 175, @@ -534,7 +534,7 @@ "id": "n", "type": "parallelogram", "pos": { - "x": 3242, + "x": 3086, "y": 92 }, "width": 183, @@ -543,7 +543,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F0F3F9", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -573,7 +573,7 @@ "id": "o", "type": "person", "pos": { - "x": 3507, + "x": 3339, "y": 55 }, "width": 154, @@ -582,7 +582,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#E3E9FD", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -612,7 +612,7 @@ "id": "p", "type": "queue", "pos": { - "x": 3753, + "x": 3563, "y": 108 }, "width": 161, @@ -621,7 +621,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F0F3F9", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -651,7 +651,7 @@ "id": "q", "type": "rectangle", "pos": { - "x": 4001, + "x": 3794, "y": 69 }, "width": 165, @@ -660,7 +660,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#F7F8FE", + "fill": "#EDF0FD", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -690,7 +690,7 @@ "id": "r", "type": "step", "pos": { - "x": 4227, + "x": 4029, "y": 108 }, "width": 213, @@ -729,11 +729,11 @@ "id": "s", "type": "stored_data", "pos": { - "x": 4509, - "y": 96 + "x": 4312, + "y": 108 }, "width": 150, - "height": 138, + "height": 126, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -768,7 +768,7 @@ "id": "t", "type": "sql_table", "pos": { - "x": 4729, + "x": 4532, "y": 126 }, "width": 210, @@ -777,7 +777,7 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", + "fill": "#EDF0FD", "stroke": "#0A0F25", "shadow": false, "3d": false, @@ -848,14 +848,14 @@ "y": 364 }, { - "x": 334, + "x": 298, "y": 364 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(b <-> c)[0]", @@ -883,18 +883,18 @@ "labelPercentage": 0, "route": [ { - "x": 334, + "x": 298, "y": 494 }, { - "x": 583.5, + "x": 563.5, "y": 494 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(c -> d)[0]", @@ -922,18 +922,18 @@ "labelPercentage": 0, "route": [ { - "x": 583.5, + "x": 563.5, "y": 624 }, { - "x": 833.5, + "x": 843.5, "y": 624 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(d -> e)[0]", @@ -961,18 +961,18 @@ "labelPercentage": 0, "route": [ { - "x": 833.5, + "x": 843.5, "y": 754 }, { - "x": 1084, + "x": 1101, "y": 754 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(e -> f)[0]", @@ -1000,18 +1000,18 @@ "labelPercentage": 0, "route": [ { - "x": 1084, + "x": 1101, "y": 884 }, { - "x": 1334, + "x": 1344, "y": 884 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(f -> g)[0]", @@ -1039,18 +1039,18 @@ "labelPercentage": 0, "route": [ { - "x": 1334, + "x": 1344, "y": 1014 }, { - "x": 1584, + "x": 1564, "y": 1014 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(g -> h)[0]", @@ -1078,18 +1078,18 @@ "labelPercentage": 0, "route": [ { - "x": 1584, + "x": 1564, "y": 1144 }, { - "x": 1834, + "x": 1784, "y": 1144 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(h -> i)[0]", @@ -1117,18 +1117,18 @@ "labelPercentage": 0, "route": [ { - "x": 1834, + "x": 1784, "y": 1274 }, { - "x": 2084, + "x": 2020, "y": 1274 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(i -> j)[0]", @@ -1156,18 +1156,18 @@ "labelPercentage": 0, "route": [ { - "x": 2084, + "x": 2020, "y": 1404 }, { - "x": 2334, + "x": 2256, "y": 1404 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(j -> k)[0]", @@ -1195,18 +1195,18 @@ "labelPercentage": 0, "route": [ { - "x": 2334, + "x": 2256, "y": 1534 }, { - "x": 2584, + "x": 2476, "y": 1534 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(k -> l)[0]", @@ -1234,18 +1234,18 @@ "labelPercentage": 0, "route": [ { - "x": 2584, + "x": 2476, "y": 1664 }, { - "x": 2834, + "x": 2696, "y": 1664 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(l -> m)[0]", @@ -1273,18 +1273,18 @@ "labelPercentage": 0, "route": [ { - "x": 2834, + "x": 2696, "y": 1794 }, { - "x": 3083.5, + "x": 2928.5, "y": 1794 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(m -> n)[0]", @@ -1312,18 +1312,18 @@ "labelPercentage": 0, "route": [ { - "x": 3083.5, + "x": 2928.5, "y": 1924 }, { - "x": 3333.5, + "x": 3177.5, "y": 1924 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(n -> o)[0]", @@ -1351,18 +1351,18 @@ "labelPercentage": 0, "route": [ { - "x": 3333.5, + "x": 3177.5, "y": 2054 }, { - "x": 3584, + "x": 3416, "y": 2054 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(o -> p)[0]", @@ -1390,18 +1390,18 @@ "labelPercentage": 0, "route": [ { - "x": 3584, + "x": 3416, "y": 2184 }, { - "x": 3833.5, + "x": 3643.5, "y": 2184 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(p -> q)[0]", @@ -1429,18 +1429,18 @@ "labelPercentage": 0, "route": [ { - "x": 3833.5, + "x": 3643.5, "y": 2314 }, { - "x": 4083.5, + "x": 3876.5, "y": 2314 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(q -> r)[0]", @@ -1468,18 +1468,18 @@ "labelPercentage": 0, "route": [ { - "x": 4083.5, + "x": 3876.5, "y": 2444 }, { - "x": 4333.5, + "x": 4135.5, "y": 2444 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(r -> s)[0]", @@ -1507,18 +1507,18 @@ "labelPercentage": 0, "route": [ { - "x": 4333.5, + "x": 4135.5, "y": 2574 }, { - "x": 4584, + "x": 4387, "y": 2574 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(s -> t)[0]", @@ -1546,18 +1546,18 @@ "labelPercentage": 0, "route": [ { - "x": 4584, + "x": 4387, "y": 2704 }, { - "x": 4834, + "x": 4637, "y": 2704 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 2 + "zIndex": 4 }, { "id": "(a -- )[0]", @@ -1596,7 +1596,7 @@ "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(b -- )[0]", @@ -1624,18 +1624,18 @@ "labelPercentage": 0, "route": [ { - "x": 334, + "x": 298, "y": 234 }, { - "x": 334, + "x": 298, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(c -- )[0]", @@ -1663,18 +1663,18 @@ "labelPercentage": 0, "route": [ { - "x": 583.5, + "x": 563.5, "y": 234 }, { - "x": 583.5, + "x": 563.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(d -- )[0]", @@ -1702,18 +1702,18 @@ "labelPercentage": 0, "route": [ { - "x": 833.5, + "x": 843.5, "y": 234 }, { - "x": 833.5, + "x": 843.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(e -- )[0]", @@ -1741,18 +1741,18 @@ "labelPercentage": 0, "route": [ { - "x": 1084, + "x": 1101, "y": 234 }, { - "x": 1084, + "x": 1101, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(f -- )[0]", @@ -1780,18 +1780,18 @@ "labelPercentage": 0, "route": [ { - "x": 1334, + "x": 1344, "y": 234 }, { - "x": 1334, + "x": 1344, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(g -- )[0]", @@ -1819,18 +1819,18 @@ "labelPercentage": 0, "route": [ { - "x": 1584, + "x": 1564, "y": 234 }, { - "x": 1584, + "x": 1564, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(h -- )[0]", @@ -1858,18 +1858,18 @@ "labelPercentage": 0, "route": [ { - "x": 1834, + "x": 1784, "y": 234 }, { - "x": 1834, + "x": 1784, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(i -- )[0]", @@ -1897,18 +1897,18 @@ "labelPercentage": 0, "route": [ { - "x": 2084, + "x": 2020, "y": 234 }, { - "x": 2084, + "x": 2020, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(j -- )[0]", @@ -1936,18 +1936,18 @@ "labelPercentage": 0, "route": [ { - "x": 2334, + "x": 2256, "y": 239 }, { - "x": 2334, + "x": 2256, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(k -- )[0]", @@ -1975,18 +1975,18 @@ "labelPercentage": 0, "route": [ { - "x": 2584, + "x": 2476, "y": 234 }, { - "x": 2584, + "x": 2476, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(l -- )[0]", @@ -2014,18 +2014,18 @@ "labelPercentage": 0, "route": [ { - "x": 2834, + "x": 2696, "y": 234 }, { - "x": 2834, + "x": 2696, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(m -- )[0]", @@ -2053,18 +2053,18 @@ "labelPercentage": 0, "route": [ { - "x": 3083.5, + "x": 2928.5, "y": 234 }, { - "x": 3083.5, + "x": 2928.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(n -- )[0]", @@ -2092,18 +2092,18 @@ "labelPercentage": 0, "route": [ { - "x": 3333.5, + "x": 3177.5, "y": 234 }, { - "x": 3333.5, + "x": 3177.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(o -- )[0]", @@ -2131,18 +2131,18 @@ "labelPercentage": 0, "route": [ { - "x": 3584, + "x": 3416, "y": 239 }, { - "x": 3584, + "x": 3416, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(p -- )[0]", @@ -2170,18 +2170,18 @@ "labelPercentage": 0, "route": [ { - "x": 3833.5, + "x": 3643.5, "y": 234 }, { - "x": 3833.5, + "x": 3643.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(q -- )[0]", @@ -2209,18 +2209,18 @@ "labelPercentage": 0, "route": [ { - "x": 4083.5, + "x": 3876.5, "y": 234 }, { - "x": 4083.5, + "x": 3876.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(r -- )[0]", @@ -2248,18 +2248,18 @@ "labelPercentage": 0, "route": [ { - "x": 4333.5, + "x": 4135.5, "y": 234 }, { - "x": 4333.5, + "x": 4135.5, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(s -- )[0]", @@ -2287,18 +2287,18 @@ "labelPercentage": 0, "route": [ { - "x": 4584, + "x": 4387, "y": 234 }, { - "x": 4584, + "x": 4387, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 }, { "id": "(t -- )[0]", @@ -2326,18 +2326,18 @@ "labelPercentage": 0, "route": [ { - "x": 4834, + "x": 4637, "y": 234 }, { - "x": 4834, + "x": 4637, "y": 2834 } ], "animated": false, "tooltip": "", "icon": null, - "zIndex": 0 + "zIndex": 2 } ] } diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg index 2d74a90f0..7e13b2b3a 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ a labelblabelsa class+ +public() bool +void- +private() int +voidcloudyyyy:= 5 := a + 7 -fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid -int -name -varchar - result := callThisFunction(obj, 5) midthis sideother side - - - +fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid +int +name +varchar + result := callThisFunction(obj, 5) midthis sideother side + + + a labelblabelsa class+ -public() bool -void- -private() int -voidcloudyyyy:= 5 +a labelblabelsa class+ +public() bool +void- +private() int +voidcloudyyyy:= 5 := a + 7 -fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid -int -name -varchar - result := callThisFunction(obj, 5) midthis sideother side - - - +fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid +int +name +varchar + result := callThisFunction(obj, 5) midthis sideother side + + + abcdggggroup 1group bchoonested guywhat would arnold saythis note lalaeyokayokay - - - - - +abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note + + + + + abcdggggroup 1group bchoonested guywhat would arnold saythis note lalaeyokayokay - - - - - +abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note + + + + + scoreritemResponseitemessayRubricconceptitemOutcome scoreritemResponseitemessayRubricconceptitemOutcome abcdexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier okay - - +abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier + + abcdexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier okay - - +abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier + + How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG - - - - - - - - - - - - - - +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place + + + + + + + + + + + + + + How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG - - - - - - - - - - - - - - +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place + + + + + + + + + + + + + + ab a self edge herebetween actorsto descendantto deeper descendantto parentactor - - - - - - - +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + + + + + + + ab a self edge herebetween actorsto descendantto deeper descendantto parentactor - - - - - - - +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + + + + + + + AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response - - - - - - - - - +AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response + + + + + + + + + AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response - - - - - - - - - +AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response + + + + + + + + + scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - - - - - - - - - - - - - +scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + + + + + + + + + + + + + scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - - - - - - - - - - - - - +scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + + + + + + + + + + + + + a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - - - - - - - - - - - - - - - - - - - - - - - - - +a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + + + + + + + + + + + + + + + + + + + + + + + + + a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - - - - - - - - - - - - - - - - - - - - - - - - - +a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + + + + + + + + + + + + + + + + + + + + + + + + +