-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Use Case diagram type #4628
Comments
This is very important, even making me want to give up mermaid |
I totally agree with the above comments : the "Use Case" diagram is probably one of the most important of all UML diagrams ! The "Use Case" diagram is usually the first to show in a projet, as it clearly presents the whole app/solution functionalities visually, without delving into technical details (as they are usually not known at this analysis stage). Actors, resources, ... are all there on a single diagram focusing on "who" and "what" and not on "how". Mermaid developers : that diagram availability would really be wonderful and super useful, since the whole project UML documentation could be done with mermaid. |
I would also love to have the use case diagram type. |
I'll add my voice to the growing chorus 😉 |
yes please! |
+1. Using flowcharts to simulate the effect of use cases for now |
+1 for this feature |
+1 |
11 similar comments
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+100 |
+1 |
3 similar comments
+1 |
+1 |
+1 |
Hello @karollewandowski @simone-boa-ideas @fidding @paul-friedli you can use flow chart; try and adapt this :)
yas in |
+1 |
2 similar comments
+1 |
+1 |
+1 |
1 similar comment
+1 |
+10 |
+1 |
Any plans to support use case diagrams? |
We could do with this too. Loving Mermaid but this does feel like it is missing. |
+1 |
2 similar comments
+1 |
+1 |
I didnt read quite well, sorry about that :) |
it would be very important to have this feature, I'm currently trying to do this with jointjs const { shapes: defaultShapes, dia, util, linkTools } = joint;
const paperContainer = document.getElementById("paper-container");
const COLORS = [
"#3f84e5", // Azul
"#49306B", // Roxo
"#fe7f2d", // Laranja
"#ad343e", // Vermelho
"#899e8b", // Verde
"#ede9e9", // Cinza Claro
"#b2a29f", // Bege
"#392F2D" // Marrom
];
const shapes = { ...defaultShapes };
const graph = new dia.Graph({}, { cellNamespace: shapes });
const paper = new dia.Paper({
el: document.getElementById("paper"),
width: "100%",
height: "100%",
model: graph,
async: true,
multiLinks: false,
linkPinning: false,
cellViewNamespace: shapes,
sorting: dia.Paper.sorting.APPROX,
defaultConnectionPoint: {
name: "boundary",
args: {
offset: 5
}
},
defaultConnector: {
name: "jumpover"
},
background: {
color: "#f6f4f4"
},
highlighting: {
connecting: {
name: "mask",
options: {
attrs: {
stroke: "#0A100D",
"stroke-width": 3
}
}
}
},
restrictTranslate: function (elementView) {
const parent = elementView.model.getParentCell();
if (parent) {
return parent.getBBox().inflate(-6);
}
return null;
},
validateConnection: function (cellViewS, _, cellViewT) {
if (cellViewT.model instanceof UseCase) return true;
return false;
}
});
paperContainer.appendChild(paper.el);
class Boundary extends dia.Element {
defaults() {
return {
...super.defaults,
type: "Boundary",
attrs: {
body: {
width: "calc(w)",
height: "calc(h)",
fill: COLORS[5],
stroke: COLORS[6],
strokeWidth: 1,
rx: 20,
ry: 20
},
label: {
y: 10,
x: "calc(w / 2)",
textAnchor: "middle",
textVerticalAnchor: "top",
fontSize: 18,
fontFamily: "sans-serif",
fontWeight: "bold",
fill: COLORS[7]
},
}
};
}
preinitialize(...args) {
super.preinitialize(...args);
this.markup = util.svg`
<rect @selector="body" />
<text @selector="label" />
<image @selector="logo" />
`;
}
}
class Actor extends dia.Element {
defaults() {
return {
...super.defaults,
type: "Actor",
attrs: {
background: {
width: "calc(w)",
height: "calc(h)",
fill: "transparent"
},
body: {
d: `M 0 calc(0.4 * h) h calc(w) M 0 calc(h) calc(0.5 * w) calc(0.7 * h) calc(w) calc(h) M calc(0.5 * w) calc(0.7 * h) V calc(0.3 * h)`,
fill: "none",
stroke: COLORS[7],
strokeWidth: 2
},
head: {
cx: "calc(0.5 * w)",
cy: `calc(0.15 * h)`,
r: `calc(0.15 * h)`,
stroke: COLORS[7],
strokeWidth: 2,
fill: "#ffffff"
},
label: {
y: "calc(h + 10)",
x: "calc(0.5 * w)",
textAnchor: "middle",
textVerticalAnchor: "top",
fontSize: 14,
fontFamily: "sans-serif",
fill: COLORS[7],
textWrap: {
width: "calc(3 * w)",
height: null
}
}
}
};
}
preinitialize(...args) {
super.preinitialize(...args);
this.markup = util.svg`
<rect @selector="background" />
<path @selector="body" />
<circle @selector="head" />
<text @selector="label" />
`;
}
}
class UseCase extends dia.Element {
defaults() {
return {
...super.defaults,
type: "UseCase",
attrs: {
root: {
highlighterSelector: "body"
},
body: {
cx: "calc(0.5 * w)",
cy: "calc(0.5 * h)",
rx: "calc(0.5 * w)",
ry: "calc(0.5 * h)",
stroke: COLORS[7],
strokeWidth: 2
},
label: {
x: "calc(0.5 * w)",
y: "calc(0.5 * h)",
textVerticalAnchor: "middle",
textAnchor: "middle",
fontSize: 14,
fontFamily: "sans-serif",
fill: "#ffffff",
textWrap: {
width: "calc(w - 30)",
height: "calc(h - 10)",
ellipsis: true
}
}
}
};
}
preinitialize(...args) {
super.preinitialize(...args);
this.markup = util.svg`
<ellipse @selector="body" />
<text @selector="label" />
`;
}
}
class Use extends shapes.standard.Link {
defaults() {
return util.defaultsDeep(
{
type: "Use",
attrs: {
line: {
stroke: COLORS[7],
strokeWidth: 2,
targetMarker: null
}
}
},
super.defaults
);
}
}
class Include extends shapes.standard.Link {
defaults() {
return util.defaultsDeep(
{
type: "Include",
attrs: {
line: {
stroke: COLORS[7],
strokeDasharray: "6,2",
strokeWidth: 2,
targetMarker: {
type: "path",
fill: "none",
stroke: COLORS[7],
"stroke-width": 2,
d: "M 10 -5 0 0 10 5"
}
}
},
labels: [
{
position: 0.5,
attrs: {
labelText: {
text: "<<include>>",
fill: COLORS[7],
fontSize: 12
}
}
}
]
},
super.defaults
);
}
}
class Extend extends shapes.standard.Link {
defaults() {
return util.defaultsDeep(
{
type: "Extend",
attrs: {
line: {
stroke: COLORS[7],
strokeDasharray: "6,2",
strokeWidth: 2,
targetMarker: {
type: "path",
fill: "none",
stroke: COLORS[7],
"stroke-width": 2,
d: "M 10 -5 0 0 10 5"
}
}
},
labels: [
{
position: 0.5,
attrs: {
labelText: {
text: "<<extend>>",
fill: COLORS[7],
fontSize: 12
}
}
}
]
},
super.defaults
);
}
}
// Criando Boundary (Sistema Bancário)
const boundary = new Boundary({
size: { width: 800, height: 1000 },
position: { x: 200, y: 100 },
attrs: { label: { text: "Sistema Bancário" } }
});
// Criando Atores
const cliente = new Actor({
position: { x: 50, y: 150 },
size: { width: 40, height: 80 },
attrs: { label: { text: "Cliente" } }
});
const funcionario = new Actor({
position: { x: 50, y: 400 },
size: { width: 40, height: 80 },
attrs: { label: { text: "Funcionário" } }
});
const caixaEletronico = new Actor({
position: { x: 50, y: 650 },
size: { width: 40, height: 80 },
attrs: { label: { text: "Caixa Eletrônico" } }
});
// Cor para os casos de uso
const useCaseColor = { body: { fill: "lightblue", stroke: "blue" } };
// Criando Casos de Uso
const abrirConta = new UseCase({
position: { x: 300, y: 150 },
size: { width: 125, height: 75 },
attrs: { label: { text: "Abrir Conta" }, ...useCaseColor }
});
const encerrarConta = new UseCase({
position: { x: 500, y: 150 },
size: { width: 125, height: 75 },
attrs: { label: { text: "Encerrar Conta" }, ...useCaseColor }
});
const sacar = new UseCase({
position: { x: 300, y: 300 },
size: { width: 125, height: 75 },
attrs: { label: { text: "Sacar" }, ...useCaseColor }
});
const depositar = new UseCase({
position: { x: 500, y: 300 },
size: { width: 125, height: 75 },
attrs: { label: { text: "Depositar" }, ...useCaseColor }
});
const consultarSaldo = new UseCase({
position: { x: 400, y: 450 },
size: { width: 125, height: 75 },
attrs: { label: { text: "Consultar Saldo" }, ...useCaseColor }
});
// Adicionando elementos ao gráfico
graph.addCell([boundary, cliente, funcionario, caixaEletronico, abrirConta, encerrarConta, sacar, depositar, consultarSaldo]);
// Criando Conexões
new Use({ source: { id: cliente.id }, target: { id: abrirConta.id } }).addTo(graph);
new Use({ source: { id: cliente.id }, target: { id: sacar.id } }).addTo(graph);
new Use({ source: { id: cliente.id }, target: { id: depositar.id } }).addTo(graph);
new Use({ source: { id: cliente.id }, target: { id: consultarSaldo.id } }).addTo(graph);
new Use({ source: { id: funcionario.id }, target: { id: abrirConta.id } }).addTo(graph);
new Use({ source: { id: funcionario.id }, target: { id: encerrarConta.id } }).addTo(graph);
new Use({ source: { id: caixaEletronico.id }, target: { id: sacar.id } }).addTo(graph);
new Use({ source: { id: caixaEletronico.id }, target: { id: depositar.id } }).addTo(graph);
new Use({ source: { id: caixaEletronico.id }, target: { id: consultarSaldo.id } }).addTo(graph);
// Relação <<include>> entre Consultar Saldo e outros casos de uso
new Include({ source: { id: sacar.id }, target: { id: consultarSaldo.id } }).addTo(graph);
new Include({ source: { id: depositar.id }, target: { id: consultarSaldo.id } }).addTo(graph);
// Relação <<extend>> entre Encerrar Conta e Sacar
new Extend({ source: { id: encerrarConta.id }, target: { id: sacar.id } }).addTo(graph);
// Ajustando cores ao conectar elementos
function fillUseCaseColors() {
const colorMap = {};
graph.getLinks().forEach(link => {
const targetId = link.getTargetElement().id;
const sourceId = link.getSourceElement().id;
colorMap[targetId] = COLORS[graph.getCells().indexOf(graph.getCell(sourceId)) % COLORS.length];
});
graph.getElements().forEach(element => {
if (element instanceof UseCase && colorMap[element.id]) {
element.attr("body/stroke", colorMap[element.id]);
}
});
}
graph.on("change:target", fillUseCaseColors);
graph.on("remove", fillUseCaseColors);
fillUseCaseColors(); I tried like this but it didn't turn out wellgraph TD
Cliente((Cliente))
Funcionario((Funcionário))
CaixaEletronico((Caixa Eletrônico))
subgraph Sistema Bancário
AbrirConta[Abrir Conta]
EncerrarConta[Encerrar Conta]
Depositar[Depositar]
Sacar[Sacar]
EmitirSaldo[Emitir Saldo]
EmitirExtrato[Emitir Extrato]
RegistrarMovimentacao[Registrar Movimentação]
AbrirContaEspecial[Abrir Conta Especial]
AbrirContaPoupanca[Abrir Conta Poupança]
VerificarSaldoZerado[Verificar Saldo Zerado]
end
Cliente --> AbrirConta
Cliente --> EncerrarConta
Cliente --> Depositar
Cliente --> Sacar
Cliente --> EmitirSaldo
Cliente --> EmitirExtrato
Funcionario --> AbrirConta
Funcionario --> EncerrarConta
CaixaEletronico --> Depositar
CaixaEletronico --> Sacar
CaixaEletronico --> EmitirSaldo
CaixaEletronico --> EmitirExtrato
AbrirContaEspecial -.-> |<<extend>>| AbrirConta
AbrirContaPoupanca -.-> |<<extend>>| AbrirConta
EncerrarConta -.-> |<<include>>| VerificarSaldoZerado
Depositar -.-> |<<include>>| RegistrarMovimentacao
Sacar -.-> |<<include>>| RegistrarMovimentacao |
+1 |
Yes please! |
+1 |
3 similar comments
+1 |
+1 |
+1 |
Ngl I'm monitoring the issue and it's getting quite annoying having almost only notifications about messages adding a "+1" and no contribution 🙂 |
With PlantUML, since Mermaid currently does not support use case diagrams (...) Issue is just a +1 spam: mermaid-js/mermaid#4628
I've got a stab at it here: #6141
|
Thanks @teyc !!! You're a genius! |
|
yeah, the layout indeed is ugly, hahaha |
It's improving, latest iteration looks like |
Please do not +1 via comments. Click 👍 under this post.
Proposal
There is a lack of UML Use Case diagrams (or I failed to find it):
https://www.lucidchart.com/pages/uml-use-case-diagram
Use Cases
Screenshots
Source: https://www.lucidchart.com/pages/uml-use-case-diagram
Syntax
I have no knowledge of Mermaid conventions, but PlantUML can be a good inspiration:
https://plantuml.com/use-case-diagram
Implementation
This is a proposal which I'd love to see built into mermaid by the wonderful community.
The text was updated successfully, but these errors were encountered: