diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index 89ee90444..9d2cb1bd1 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -509,6 +509,91 @@ func defineShadowFilter(writer io.Writer) {
`)
}
+func render3dRect(targetShape d2target.Shape) string {
+ moveTo := func(p d2target.Point) string {
+ return fmt.Sprintf("M%d,%d", p.X+targetShape.Pos.X, p.Y+targetShape.Pos.Y)
+ }
+ lineTo := func(p d2target.Point) string {
+ return fmt.Sprintf("L%d,%d", p.X+targetShape.Pos.X, p.Y+targetShape.Pos.Y)
+ }
+
+ // draw border all in one path to prevent overlapping sections
+ var borderSegments []string
+ borderSegments = append(borderSegments,
+ moveTo(d2target.Point{X: 0, Y: 0}),
+ )
+ for _, v := range []d2target.Point{
+ {X: threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
+ {X: targetShape.Width, Y: targetShape.Height},
+ {X: 0, Y: targetShape.Height},
+ {X: 0, Y: 0},
+ {X: targetShape.Width, Y: 0},
+ {X: targetShape.Width, Y: targetShape.Height},
+ } {
+ borderSegments = append(borderSegments, lineTo(v))
+ }
+ // move to top right to draw last segment without overlapping
+ borderSegments = append(borderSegments,
+ moveTo(d2target.Point{X: targetShape.Width, Y: 0}),
+ )
+ borderSegments = append(borderSegments,
+ lineTo(d2target.Point{X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset}),
+ )
+ border := targetShape
+ border.Fill = "none"
+ borderStyle := shapeStyle(border)
+ renderedBorder := fmt.Sprintf(``,
+ strings.Join(borderSegments, " "), borderStyle)
+
+ // create mask from border stroke, to cut away from the shape fills
+ maskID := fmt.Sprintf("border-mask-%v", escapeText(targetShape.ID))
+ borderMask := strings.Join([]string{
+ fmt.Sprintf(``,
+ maskID, targetShape.Pos.X, targetShape.Pos.Y-threeDeeOffset, targetShape.Width+threeDeeOffset, targetShape.Height+threeDeeOffset,
+ ),
+ fmt.Sprintf(``,
+ targetShape.Pos.X, targetShape.Pos.Y-threeDeeOffset, targetShape.Width+threeDeeOffset, targetShape.Height+threeDeeOffset,
+ ),
+ fmt.Sprintf(``,
+ strings.Join(borderSegments, ""), borderStyle),
+ }, "\n")
+
+ // render the main rectangle without stroke and the border mask
+ mainShape := targetShape
+ mainShape.Stroke = "none"
+ mainRect := fmt.Sprintf(``,
+ targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(mainShape), maskID,
+ )
+
+ // render the side shapes in the darkened color without stroke and the border mask
+ var sidePoints []string
+ for _, v := range []d2target.Point{
+ {X: 0, Y: 0},
+ {X: threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
+ {X: targetShape.Width, Y: targetShape.Height},
+ {X: targetShape.Width, Y: 0},
+ } {
+ sidePoints = append(sidePoints,
+ fmt.Sprintf("%d,%d", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
+ )
+ }
+ darkerColor, err := color.Darken(targetShape.Fill)
+ if err != nil {
+ darkerColor = targetShape.Fill
+ }
+ sideShape := targetShape
+ sideShape.Fill = darkerColor
+ sideShape.Stroke = "none"
+ renderedSides := fmt.Sprintf(``,
+ strings.Join(sidePoints, " "), shapeStyle(sideShape), maskID)
+
+ return borderMask + mainRect + renderedSides + renderedBorder
+}
+
func drawShape(writer io.Writer, targetShape d2target.Shape) error {
fmt.Fprintf(writer, ``, escapeText(targetShape.ID))
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
@@ -561,50 +646,15 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error {
// TODO should standardize "" to rectangle
case d2target.ShapeRectangle, "":
if targetShape.ThreeDee {
- darkerColor, err := color.Darken(targetShape.Fill)
- if err != nil {
- darkerColor = targetShape.Fill
+ fmt.Fprint(writer, render3dRect(targetShape))
+ } else {
+ if targetShape.Multiple {
+ fmt.Fprintf(writer, ``,
+ targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
}
- sideShape := targetShape
- sideShape.Fill = darkerColor
- sideStyle := shapeStyle(sideShape)
-
- var topPolygonPoints []string
- for _, v := range []d2target.Point{
- {X: 0, Y: 0},
- {X: threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width + threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width, Y: 0},
- {X: 0, Y: 0},
- } {
- topPolygonPoints = append(topPolygonPoints,
- fmt.Sprintf("%d,%d ", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
- )
- }
- fmt.Fprintf(writer, ``,
- strings.Join(topPolygonPoints, ""), sideStyle)
-
- var rightPolygonPoints []string
- for _, v := range []d2target.Point{
- {X: targetShape.Width, Y: 0},
- {X: targetShape.Width + threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
- {X: targetShape.Width, Y: targetShape.Height},
- } {
- rightPolygonPoints = append(rightPolygonPoints,
- fmt.Sprintf("%d,%d ", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
- )
- }
- fmt.Fprintf(writer, ``,
- strings.Join(rightPolygonPoints, ""), sideStyle)
- }
- if targetShape.Multiple {
fmt.Fprintf(writer, ``,
- targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
+ targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
}
- fmt.Fprintf(writer, ``,
- targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
-
case d2target.ShapeText, d2target.ShapeCode:
default:
if targetShape.Multiple {
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index a7e6ac671..02b66f0a3 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -913,7 +913,7 @@ y: {
opacity: 0.6
fill: red
3d: true
- stroke: black
+ stroke: black
}
}
@@ -1000,6 +1000,20 @@ b: {
a -> b -> c -> d -> e
}
}
+`,
+ },
+ {
+ name: "transparent_3d",
+ script: `
+cube: {
+ style: {
+ 3d: true
+ opacity: 0.5
+ fill: orange
+ stroke: "#53C0D8"
+ stroke-width: 7
+ }
+}
`,
},
}
diff --git a/e2etests/testdata/stable/square_3d/dagre/sketch.exp.svg b/e2etests/testdata/stable/square_3d/dagre/sketch.exp.svg
index 8756afe80..bdec94d45 100644
--- a/e2etests/testdata/stable/square_3d/dagre/sketch.exp.svg
+++ b/e2etests/testdata/stable/square_3d/dagre/sketch.exp.svg
@@ -14,7 +14,11 @@ width="371" height="580" viewBox="-100 -100 371 580">rectanglesquare
+
+rectangle
+
+square rectanglesquare
+
+rectangle
+
+square
+
+cube
\ No newline at end of file
diff --git a/e2etests/testdata/stable/transparent_3d/elk/board.exp.json b/e2etests/testdata/stable/transparent_3d/elk/board.exp.json
new file mode 100644
index 000000000..af5b43a50
--- /dev/null
+++ b/e2etests/testdata/stable/transparent_3d/elk/board.exp.json
@@ -0,0 +1,44 @@
+{
+ "name": "",
+ "shapes": [
+ {
+ "id": "cube",
+ "type": "",
+ "pos": {
+ "x": 12,
+ "y": 12
+ },
+ "width": 139,
+ "height": 126,
+ "level": 1,
+ "opacity": 0.5,
+ "strokeDash": 0,
+ "strokeWidth": 7,
+ "borderRadius": 0,
+ "fill": "orange",
+ "stroke": "#53C0D8",
+ "shadow": false,
+ "3d": true,
+ "multiple": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "cube",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 39,
+ "labelHeight": 26,
+ "labelPosition": "INSIDE_MIDDLE_CENTER"
+ }
+ ],
+ "connections": []
+}
diff --git a/e2etests/testdata/stable/transparent_3d/elk/sketch.exp.svg b/e2etests/testdata/stable/transparent_3d/elk/sketch.exp.svg
new file mode 100644
index 000000000..e5e521a73
--- /dev/null
+++ b/e2etests/testdata/stable/transparent_3d/elk/sketch.exp.svg
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file