Flow Chart
An interactive flowchart.FlowChart with custom node content, styled and animated edges, a context menu, a toolbar and automatic layout.

package main
import (
_ "embed"
"fmt"
"strings"
"go.wdy.de/nago/application"
"go.wdy.de/nago/presentation/core"
"go.wdy.de/nago/presentation/ui"
"go.wdy.de/nago/presentation/ui/colorpicker"
"go.wdy.de/nago/presentation/ui/flowchart"
"go.wdy.de/nago/web/vuejs"
)
//go:embed joe_schmoe_green.svg
var JoeSchmoeGreen core.SVG
//go:embed joe_schmoe_red.svg
var JoeSchmoeRed core.SVG
func main() {
application.Configure(func(cfg *application.Configurator) {
cfg.SetApplicationID("de.worldiety.tutorial_98")
cfg.Serve(vuejs.Dist())
cfg.RootView(".", func(wnd core.Window) core.View {
colorState := core.StateOf[ui.Color](wnd, "colorState")
menuVisibleState := core.StateOf[bool](wnd, "menuVisibleState")
actionState := core.StateOf[flowchart.FlowChartActionData](wnd, "actionState")
actionState.Observe(func(action flowchart.FlowChartActionData) {
fmt.Println("Latest action", action)
if action.Node.ID == "" && action.Edge.ID == "" && action.RightClick {
menuVisibleState.Set(true)
return
}
menuVisibleState.Set(false)
})
nodes := make([]flowchart.Node, 0)
contents := make([]flowchart.CustomContent, 0)
jbusseNode, jbusseContent := personNode("jochen-busse", "Jochen Busse", "Geschäftsführer", JoeSchmoeGreen, flowchart.Point{
X: 100,
Y: -50,
}, flowchart.NodeTypeStart, flowchart.NodeStyleDefault)
nodes = append(nodes, jbusseNode)
contents = append(contents, jbusseContent)
gkoesterNode, gkoesterContent := personNode("gabi-koester", "Gabi Köster", "Projektmanager", JoeSchmoeGreen, flowchart.Point{
X: -100,
Y: 100,
}, flowchart.NodeTypeDefault, flowchart.NodeStyleDefault)
nodes = append(nodes, gkoesterNode)
contents = append(contents, gkoesterContent)
kpohlNode, kpohlContent := personNode("kalle-pohl", "Kalle Pohl", "Projektmanager", JoeSchmoeGreen, flowchart.Point{
X: 300,
Y: 100,
}, flowchart.NodeTypeDefault, flowchart.NodeStyleDefault)
nodes = append(nodes, kpohlNode)
contents = append(contents, kpohlContent)
gcantzNode, gcantzContent := personNode("guido-cantz", "Guido Cantz", "Projektmanager", JoeSchmoeGreen, flowchart.Point{
X: 100,
Y: 100,
}, flowchart.NodeTypeDefault, flowchart.NodeStyleDefault)
nodes = append(nodes, gcantzNode)
contents = append(contents, gcantzContent)
bstelterNode, bstelterContent := personNode("bernd-stelter", "Bernd Stelter", "Softwareentwickler", JoeSchmoeRed, flowchart.Point{
X: -100,
Y: 250,
}, flowchart.NodeTypeEnd, flowchart.NodeStyleDefault)
nodes = append(nodes, bstelterNode)
contents = append(contents, bstelterContent)
gcantznichtNode, gcantznichtContent := personNode("guido-cantz-nicht", "Guido Cantz Nicht", "Praktikant", JoeSchmoeGreen, flowchart.Point{
X: 100,
Y: 250,
}, flowchart.NodeTypeEnd, flowchart.NodeStyleDefault)
nodes = append(nodes, gcantznichtNode)
contents = append(contents, gcantznichtContent)
colorNode, colorContent := colorNode(colorState, "color", flowchart.Point{
X: 300,
Y: 250,
}, flowchart.NodeTypeDefault)
nodes = append(nodes, colorNode)
contents = append(contents, colorContent)
edges := []flowchart.Edge{
{
ID: "jbusse-gkoester",
SourceNodeID: "jochen-busse",
TargetNodeID: "gabi-koester",
Animated: true,
},
{
ID: "jbusse-kpohl",
SourceNodeID: "jochen-busse",
TargetNodeID: "kalle-pohl",
Animated: true,
},
{
ID: "jbusse-gcantz",
SourceNodeID: "jochen-busse",
TargetNodeID: "guido-cantz",
Animated: true,
},
{
ID: "gkoester-bstelter",
SourceNodeID: "gabi-koester",
TargetNodeID: "bernd-stelter",
Label: "hat entlassen",
LabelBgColor: ui.ColorError,
LabelTextColor: ui.ColorWhite,
Width: 2,
Style: flowchart.EdgeStyleDashed,
Color: ui.ColorError,
MarkerEnd: flowchart.EdgeMarkerArrow,
},
{
ID: "gcantz-gcantznicht",
SourceNodeID: "guido-cantz",
TargetNodeID: "guido-cantz-nicht",
},
{
ID: "kpohl-color",
SourceNodeID: "kalle-pohl",
TargetNodeID: "color",
Style: flowchart.EdgeStyleDotted,
Color: ui.ColorSemanticGood,
},
}
state := core.AutoState[flowchart.Model](wnd).Init(func() flowchart.Model {
return flowchart.Model{
Nodes: nodes,
Edges: edges,
}
})
var menu flowchart.Menu
if menuVisibleState.Get() {
menu = flowchart.Menu{
Position: flowchart.Point{
X: actionState.Get().PaneX,
Y: actionState.Get().PaneY,
},
Content: ui.VStack(
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
ui.Text("Menüeintrag"),
).
BackgroundColor(ui.I0).
Padding(ui.Padding{}.All(ui.L8)).
Border(ui.Border{}.Radius(ui.L8).Shadow(ui.L8)),
}
}
chart := flowchart.FlowChart(state.Get()).
InputValue(state).
ActionValue(actionState).
NodesDraggable(true).
NodesConnectable(true).
ElementsSelectable(true).
Background(flowchart.Background{
Color: ui.M1,
GridGap: 16,
}).
Frame(ui.Frame{}.MatchScreen()).
Layout(flowchart.FlowChartLayoutVertical).
CustomContents(contents).
MaxZoom(1.5).
Toolbar(flowchart.Toolbar{
Actions: []flowchart.FlowChartToolbarAction{flowchart.FlowChartToolbarActionAutoLayout},
}).
Menu(menu)
return ui.Stack(
chart,
ui.Stack(
ui.ThemeSwitcher(ui.PrimaryButton(nil).Title("Toggle theme")),
ui.SecondaryButton(func() {
chart.AutoLayout(wnd)
}).Title("Auto Layout"),
).Position(ui.Position{
Type: ui.PositionFixed,
Left: ui.L0,
Top: ui.L16,
Right: ui.L0,
}).Alignment(ui.Center).
Gap(ui.L8),
lastAction(actionState),
).Frame(ui.Frame{}.MatchScreen())
})
}).Run()
}
func personNode(id, name, title string, icon core.SVG, position flowchart.Point, nodeType flowchart.NodeType, style flowchart.NodeStyle) (flowchart.Node, flowchart.CustomContent) {
var menu core.View
if strings.Contains(strings.ToLower(name), "kalle") {
menu = ui.Stack(
ui.Text("node menu of " + name).
WhiteSpace(ui.WhiteSpaceNoWrap).
Font(ui.MonoSmall).
BackgroundColor(ui.ColorSemanticError).
Border(ui.Border{}.Radius(ui.L4)).
Padding(ui.Padding{}.All(ui.L4)),
)
}
return flowchart.Node{
ID: id,
Position: position,
Label: name,
Type: nodeType,
Style: style,
},
flowchart.CustomContent{
NodeID: id,
Content: ui.VStack(
ui.ImageIcon(icon),
ui.VStack(
ui.Text(name).Font(ui.TitleLarge),
ui.Text(title).Font(ui.TitleSmall),
),
).Gap(ui.L4).Padding(ui.Padding{}.All(ui.L8)),
Menu: menu,
}
}
func colorNode(state *core.State[ui.Color], id string, position flowchart.Point, nodeType flowchart.NodeType) (flowchart.Node, flowchart.CustomContent) {
return flowchart.Node{
ID: id,
Position: position,
Type: nodeType,
Style: flowchart.NodeStyleNone,
},
flowchart.CustomContent{
NodeID: id,
Content: ui.VStack(
colorpicker.PalettePicker("Farbe", colorpicker.DefaultPalette).Value(state.Get()).State(state),
).Gap(ui.L4).BackgroundColor(state.Get().WithTransparency(50)).Padding(ui.Padding{}.All(ui.L8)).Border(ui.Border{}.Radius(ui.L8)),
}
}
func lastAction(state *core.State[flowchart.FlowChartActionData]) core.View {
actionData := state.Get()
return ui.Stack(
ui.Grid(
ui.GridCell(ui.Text("Node:")),
ui.GridCell(ui.IfElse(len(actionData.Node.ID) > 0, ui.Text(fmt.Sprintf("%+v", actionData.Node)), ui.Text("-"))),
ui.GridCell(ui.Text("Edge:")),
ui.GridCell(ui.IfElse(len(actionData.Edge.ID) > 0, ui.Text(fmt.Sprintf("%+v", actionData.Edge)), ui.Text("-"))),
ui.GridCell(ui.Text("Pane Point:")),
ui.GridCell(ui.Text(fmt.Sprintf("%d %d", int(actionData.PaneX), int(actionData.PaneY)))),
ui.GridCell(ui.Text("View Point:")),
ui.GridCell(ui.Text(fmt.Sprintf("%d %d", int(actionData.ViewX), int(actionData.ViewY)))),
ui.GridCell(ui.Text("Selected nodes:")),
ui.GridCell(ui.Text(fmt.Sprintf("%+v", actionData.SelectedNodes))),
ui.GridCell(ui.Text("Selected edges:")),
ui.GridCell(ui.Text(fmt.Sprintf("%+v", actionData.SelectedEdges))),
ui.GridCell(ui.Text("Right click:")),
ui.GridCell(ui.Text(fmt.Sprintf("%t", actionData.RightClick))),
).
Columns(2).
RowGap(ui.L2).
ColGap(ui.L8).
Widths("auto", "auto"),
).
Position(ui.Position{
Type: ui.PositionAbsolute,
Left: ui.L0,
Bottom: ui.L0,
}).
BackgroundColor(ui.ColorBackground.WithTransparency(10)).
Font(ui.MonoSmall).
Padding(ui.Padding{}.All(ui.L8))
}
View on GitHub ↗
· run it with go run go.wdy.de/nago/example/cmd/tutorial-98-flow-chart@latest