Here’s the definition of a figure that cuts off 45-degree corners. You can control the length of the side of the triangle, and you can control which corners are cut off.
// parameter1 controls the length of the side of the triangle that is cut off from a corner.
// The corner is always cut at 45 degrees. If the width or height is not large enough,
// the length of the side of the triangle is limited to half of the width or height.
// parameter2 is a bit mask controlling which corners are cut off:
// 1: top-left
// 2: top-right
// 4: bottom-right
// 8: bottom-left
go.Shape.defineFigureGenerator("ChamferedRectangle", (shape, w, h) => {
let param1 = shape ? shape.parameter1 : NaN; // how much to cut off from the corner, both X and Y
if (isNaN(param1)) param1 = 12;
param1 = Math.min(param1, w / 2);
param1 = Math.min(param1, h / 2);
let param2 = shape ? shape.parameter2 : NaN; // which corners to cut off
if (isNaN(param2)) param2 = (1 | 2 | 4 | 8); // default: all corners
const fig = new go.PathFigure(param1, 0, true);
const geo = new go.Geometry().add(fig);
let spot1 = go.Spot.TopLeft.copy();
let spot2 = go.Spot.BottomRight.copy();
if (param2 & 2) { // top right
fig.add(new go.PathSegment(go.PathSegment.Line, w - param1, 0))
.add(new go.PathSegment(go.PathSegment.Line, w, param1));
spot1.offsetY = param1/2;
spot2.offsetX = -param1/2;
} else {
fig.add(new go.PathSegment(go.PathSegment.Line, w, 0));
}
if (param2 & 4) { // bottom right
fig.add(new go.PathSegment(go.PathSegment.Line, w, h - param1))
.add(new go.PathSegment(go.PathSegment.Line, w - param1, h));
spot2.offsetX = -param1/2;
spot2.offsetY = -param1/2;
} else {
fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
}
if (param2 & 8) { // bottom left
fig.add(new go.PathSegment(go.PathSegment.Line, param1, h))
.add(new go.PathSegment(go.PathSegment.Line, 0, h - param1));
spot1.offsetX = param1/2;
spot2.offsetY = -param1/2;
} else {
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h));
}
if (param2 & 1) { // top left
fig.add(new go.PathSegment(go.PathSegment.Line, 0, param1).close());
spot1.offsetX = param1/2;
spot1.offsetY = param1/2;
} else {
fig.add(new go.PathSegment(go.PathSegment.Line, 0, 0).close());
}
geo.spot1 = spot1;
geo.spot2 = spot2;
return geo;
});
and the top-left and bottom-right corners of the content of the panel will still stick out, beyond the Shape’s area.
Here’s a possibility for your node template. The header is a separate “Auto” Panel using a “ChamferedRectangle”, and the bottom “Auto” Panel has its own “ChamferedRectangle”, so that each can have its own fill (dark gray for the header and light gray for the list below).
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{ selectionAdorned: false },
$(go.Shape, "ChamferedRectangle",
{ parameter2: 1|4, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight,
fill: null, strokeWidth: 6 },
new go.Binding("stroke", "isSelected", s => s ? "red" : "black").ofObject()),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.Panel, "Auto",
$(go.Shape, "ChamferedRectangle",
{ parameter2: 1, fill: "#444", strokeWidth: 0 }),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Left },
$(go.Shape, "Diamond", { width: 16, height: 16, fill: "white" }),
$(go.TextBlock, { editable: true, stroke: "white" }, "Header")
)
),
$(go.Panel, "Auto",
$(go.Shape, "ChamferedRectangle",
{ parameter2: 4, fill: "lightgray", strokeWidth: 0 }),
$(go.Panel, "Vertical",
$(go.TextBlock, { editable: true },
new go.Binding("text").makeTwoWay()),
$(go.TextBlock, { editable: true }, "FooterFooter")
)
)
)
);