Lição 32 - Texto para diagrama
Na lição anterior, habilitamos a expressão de conexões entre gráficos, o que significa que agora dá para renderizar muitos tipos de diagramas. Os modelos grandes oferecem excelente suporte a várias linguagens de diagramação baseadas em texto, como mermaid, D2 e draw.io.
Mermaid
O Excalidraw oferece a mermaid-to-excalidraw/api. Se você tiver interesse na implementação interna, dá para ler a documentação oficial: How the Parser works under the hood ?
Em resumo, o Excalidraw oferece suporte apenas a um subconjunto limitado de tipos de diagrama do Mermaid. Ele analisa a saída SVG do renderizador do Mermaid, converte isso em uma representação interna de diagrama de cena e usa o JSON do diagrama obtido do parser para recuperar as relações entre os nós.
Fluxograma
Tome como exemplo o fluxograma Mermaid mais simples abaixo:
flowchart LR
start-->stopPrimeiro, a API obsoleta do Mermaid é usada para analisar o texto, extraindo tipos de diagrama, nós e relações de arestas, mas sem informação geométrica:
import mermaid, { MermaidConfig } from 'mermaid';
const diagram = await mermaid.mermaidAPI.getDiagramFromText(definition); // "flowchart LR..."Depois use o método de renderização do Mermaid para renderizar o SVG em um contêiner oculto na página. Isso também revela as limitações dessa abordagem: ela só pode ser executada em um ambiente de navegador. Extraia a informação geométrica de nós e arestas da saída SVG renderizada, usando os IDs de nó e de aresta obtidos no passo anterior:
const { svg } = await mermaid.render('mermaid-to-excalidraw', definition);Por fim, converta isso em um grafo de cena aceito pela nossa tela, com o texto nos nós criando nós filhos separados:
function convertFlowchartToSerializedNodes(
vertices: Map<string, Vertex>,
edges: Edge[],
options: { fontSize: number },
): SerializedNode[] {
vertices.forEach((vertex) => {
// Vertex
const serializedNode: SerializedNode = {
id: vertex.id,
type: 'rect',
x: vertex.x,
y: vertex.y,
width: vertex.width,
height: vertex.height,
stroke: 'black',
strokeWidth: 2,
};
// Label of vertex
const textSerializedNode: TextSerializedNode = {
parentId: vertex.id,
content: getText(vertex),
//...
};
});
// Edges
}Basta substituir o tipo dos gráficos para obter a renderização no estilo de esboço:
nodes.forEach((node) => {
if (node.type === 'rect') {
node.type = 'rough-rect';
} else if (node.type === 'line') {
node.type = 'rough-line';
} else if (node.type === 'text') {
node.fontFamily = 'Gaegu';
}
});Sequência
[WIP] Estado
D2
Em comparação com o Mermaid, o D2 já vem com um parser conveniente.
import { D2 } from '@terrastruct/d2';
const d2 = new D2();
const { diagram, graph } = await d2.compile(definition);
const { connections, shapes } = diagram;
const {
theme: { colors },
} = graph;x -> y: hello worldAs conexões viram Paths vinculados aos nós das extremidades, com um traço e fills: []. Preencher um caminho aberto junta implicitamente as extremidades em uma área, então a cor da conexão não pode ser usada também como preenchimento. As conexões continuam sendo atualizadas quando os nós se movem, e os marcadores desenham as pontas de seta.
Contêiner
good chips: {
doritos
ruffles
}
bad chips.lays
bad chips.pringles
chocolate.chip.cookiesConexões
dogs -> cats -> mice: chase
replica 1 <-> replica 2
a -> b: To err is human, to moo bovine {
source-arrowhead: 1
target-arrowhead: * {
shape: diamond
}
}drawio
Há um parser da comunidade disponível:
import { parseDrawIO } from 'mxgraphdata';
const mxfile = await parseDrawIO(xml);
console.log(mxfile.diagram);Rótulo da aresta
Os rótulos de texto nas arestas devem permanecer em uma posição bem escolhida ao longo da geometria; vamos tratar de uma abordagem geral na próxima seção, Lição 33 - Motor de layout. Para este problema específico, ajuda comparar como o Excalidraw, o tldraw e o draw.io tratam isso.
Nó separado ou atributo
No Excalidraw, colocar rótulos de texto nas arestas (linhas/setas) não trata, no fundo, de “fazer o texto seguir caminhos ou envolver ao longo de curvas”. Em vez disso, ele se apoia em uma abordagem mais simples e confiável:
- O rótulo continua sendo um elemento de texto independente (não é renderizado como parte da própria linha).
- Vincule o texto à linha (seta/linha) nos dados: o texto registra “a qual linha ele pertence”, e a linha registra “o ID do texto do rótulo” ou uma relação equivalente.
- Use cálculos geométricos para atribuir um ponto de âncora ao texto: em geral o “ponto médio” da linha ou um parâmetro labelPosition (0~1) e, em seguida, calcule o ponto correspondente com base na forma da linha (reta/poligonal/curva).
- Trate o texto como um elemento vinculado: quando a linha se move, as extremidades são arrastadas, as dobras mudam ou as setas invertem, a posição do rótulo é recalculada e as coordenadas do texto são atualizadas (tratando também de evitar sobreposição com a linha, deslocamento e alinhamento).
O tldraw adota uma abordagem diferente: o rótulo não é uma forma de texto separada, mas uma prop (richText) da própria forma de seta, combinada com um conjunto de posicionamento geométrico e interações de edição.
No draw.io, colocar rótulos de texto nas arestas (conectores) é uma das capacidades centrais. A implementação se alinha mais de perto com os editores de fluxograma tradicionais: “as arestas possuem a própria funcionalidade de rótulo (texto), em que os rótulos existem como estados filhos da aresta, com posições armazenadas por meio de parâmetros e deslocamentos geométricos”, em vez de criar um nó de texto separado e vinculá-lo.
Nossa implementação
Seguimos o modelo no estilo do Excalidraw: o texto da aresta continua sendo um nó de texto independente, anexado à polilinha por meio de parentId, com edgeLabelPosition (0–1 ao longo do comprimento de arco total da polilinha) indicando onde ele fica ao longo da aresta.
export interface TextSerializedNode
extends BaseSerializeNode<'text'>,
Partial<TextAttributes>,
Partial<{
/**
* When set, this text is an edge label: parent should be a bound polyline/line.
* Value is 0–1 along total edge length (arc-length parameter).
*/
edgeLabelPosition: number;
}>;Melhorar a legibilidade
Os rótulos de aresta muitas vezes precisam de ajustes de legibilidade, sobretudo para evitar ficar exatamente em cima do traço.
Preenchimento de fundo
Um fundo opaco pode esconder a linha que está por baixo.
- mxGraph (draw.io): estilo
labelBackgroundColor, por exemplolabelBackgroundColor=whiteacrescenta uma caixa branca atrás do rótulo.labelBorderColorpode acrescentar uma borda. - tldraw: os rótulos de seta não expõem uma opção dedicada de fundo, mas as formas de texto oferecem suporte a
showTextOutlinepara maior contraste. - Excalidraw: como elementos de texto independentes,
backgroundColordesenha automaticamente um retângulo preenchido atrás do texto.
Espaçamento e recuo
O deslocamento perpendicular e o padding interno mantêm os glifos fisicamente afastados do traço. Acrescentamos a propriedade edgeLabelOffset que, ao contrário de edgeLabelPosition, usa unidades absolutas para definir o deslocamento ao longo da direção da normal.
Desvio automático
O Excalidraw e o tldraw, como ferramentas no estilo de esboço, evitam um layout automático pesado para sobreposições; o usuário ajusta os rótulos à mão. O mxGraph (draw.io), como editor profissional de diagramas, oferece um controle mais fino de sobreposição.
Contorno de texto
Uma alternativa mais leve a um fundo completo: traço ou sombra ao redor dos glifos para contraste. O tldraw desenha um contorno fino atrás do texto quando showTextOutline está habilitado, para que ele continue legível quando há sobreposição.
Já apresentamos esse método na Lição 16 - Traço de texto.
[WIP] Saltos de linha
